# Developer API

Existing cbridge_* names are preserved for compatibility. Use hooks in a separate plugin; never edit KROK LLMs Transit files. Hooks run during previews and builds, so callbacks should be deterministic and should avoid remote requests.

| Hook | Arguments | Return | Since |
| --- | --- | --- | --- |
| cbridge_field_catalog | entries, post_type, sample_id, theme_template | array of definitions | 0.6.0 |
| cbridge_field_value | value, source, post_id | supported scalar or array | Existing |
| cbridge_post_description | description, post_id, Language_Context | string | Existing |
| cbridge_index_output | body, settings, sections | string | 0.6.0 |
| cbridge_markdown_output | body, post_id, Resource, settings | string | 0.6.0 |
| cbridge_restrict_post | restricted, post_id | boolean | Existing |
| cbridge_after_regenerate (action) | build | none | Existing |

Catalog entries require source, label and type. Existing providers post, meta, acf, row, taxonomy and wc are supported; secret keys are rejected even when supplied by a filter. Source values must be explicitly selected by the template. Additional source prefixes require trusted PHP registration through \CBridge\LLMShuttle\Extensions::provider(); unregistered prefixes are rejected.

Example: append a computed field to the catalog and resolve it without storing metadata.

```php
add_filter( 'cbridge_field_catalog', function ( $entries, $post_type ) {
    if ( 'page' === $post_type ) {
        $entries[] = array( 'source' => 'meta:custom_year', 'label' => 'Foundation year', 'type' => 'text', 'provider' => 'Custom' );
    }
    return $entries;
}, 10, 2 );

add_filter( 'cbridge_field_value', function ( $value, $source, $post_id ) {
    return 'meta:custom_year' === $source ? '2015' : $value;
}, 10, 3 );

add_filter( 'cbridge_index_output', function ( $body, $settings, $sections ) {
    return $body . "\n## Contact\n\nFor inquiries, use the website contact form.\n";
}, 10, 3 );

add_filter( 'cbridge_markdown_output', function ( $body, $post_id, $resource, $settings ) {
    return $body . "\nInformation provided by the original website.\n";
}, 10, 4 );
```

Output callbacks must return strings of at most 4 MiB; invalid results retain the original body. Markdown aggregate limits still apply. Output hooks do not grant access to private or excluded posts. Developers are responsible for the data they manually append. cbridge_restrict_post can add restrictions and cannot undo earlier core access checks. Related entries remain subject to eligibility checks, dependency tracking, cycle detection and node limits.

Custom providers, formatters and anonymous access policies use the explicit registries described below. Membership-specific integrations must supply an access policy; compatibility is not automatic.

## Output label filter (0.7.0)

cbridge_token_label receives label, source and post_id and returns a string. It runs only for nonempty formatted values. A non-string result retains the original label. Labels are converted to plain text. Integrations can use the post language to translate labels; saved strings are registered with Polylang, and language publication is isolated by Language_Context. WPML publication is blocked in this release pending real compatibility testing.

## Build lifecycle (0.8.0)

| Action | Arguments | Timing |
| --- | --- | --- |
| cbridge_before_regenerate | settings, Language_Context | Once when a background job starts, or before a synchronous integration build. A restarted job is a new build. |
| cbridge_regenerate_error | WP_Error | Background failures and handled synchronous generation failures. |
| cbridge_after_regenerate | complete build | After successful publication, never for partial batches. |

Job state and Generator::start/advance are implementation details. Integrations should use the documented hooks. A callback must not change content during collection: changes restart or reject the current build. The error callback must handle its own failures and must not expose exception details in public output. A busy build lock is a retry condition rather than a failed publication.

Explicitly selected related fields can come from a publicly eligible type without its own index section. The `cbridge_restrict_post` hook still applies. Private, password-protected, excluded and noindex related entries remain ineligible.

## Explicit registries (0.9.0)

Register trusted PHP integrations on `init` before administrator templates or build callbacks run. No callback or executable code is accepted from JSON imports. Duplicate names and built-in prefix replacement are rejected.

```php
add_action( 'init', static function () {
    \CBridge\LLMShuttle\Extensions::provider(
        'studio',
        static fn( $type, $sample, $theme_template ) => array(
            array( 'source' => 'studio:year', 'label' => __( 'Foundation year', 'my-integration' ), 'type' => 'text' ),
        ),
        static fn( $path, $post ) => 'year' === $path ? '2015' : null
    );
    \CBridge\LLMShuttle\Extensions::formatter(
        'studio_year',
        __( 'Foundation year', 'my-integration' ),
        static fn( $value, $options ) => is_scalar( $value ) ? 'Founded in ' . $value : ''
    );
} );
```

Catalog callbacks receive content type, sample ID and theme-template path; value callbacks receive the selected path and WP_Post. Selectors remain explicit and secret-key protection remains enforced. Formatter callbacks receive raw value and validated options; return plain text, not HTML or executable markup. Custom formatters are available in explicit format selectors; selecting one preserves it when a known field is reopened.

`Extensions::access($integration_id, $callback)` registers an anonymous-access decision for one recognized active integration: pmpro, rcp, wlm, memberpress, wc_memberships, ultimate_member or content_control. The callback receives the post ID and must return strict true only when any logged-out visitor may read the selected content; false, non-booleans and exceptions deny access. Every active recognized integration must have its own adapter. Registration is a contract, not certification of a particular third-party plugin version. Never replace this check with the current administrator's permission.

Call `do_action('cbridge_access_changed')` after integration access rules change to revoke output and queue a fresh build. Cached snapshot reads also recheck registered active policies. Unknown access-control plugins require an explicitly designed integration; the core cannot infer all custom restrictions.

Provider values that change outside WordPress post hooks refresh during recurring full verification or an explicit rebuild. After registering/deactivating integration code, invalidate the snapshot and clear derived representations through `cbridge_access_changed`. Retain the original cbridge_ option/meta/hook names for update compatibility.

`layout_templates` maps a Flexible Content layout name to its child block list inside a repeat node. Rows use their own acf_fc_layout template in original order. Unknown/unconfigured layouts are skipped with a warning. Collection `output_format` accepts inline, list, paragraphs or table. `table_headers` contains headings separated by `|`; table children produce stable columns, including empty values.
