New doc-sync gate verify-export-jsdoc walks every module-level exported
name under packages/*/*/src and requires description prose everywhere,
plus @param per parameter and @returns on non-void annotated returns for
function-like exports, public class methods, properties, and accessors.
The parsing + check helpers move out of gen-cordis-catalog.ts into a
shared scripts/jsdoc.ts so 'documented' means one thing on both gated
surfaces.
Deliberate exemptions (documented in the RFC): heritage-declared class
members (the seam declaration is the doc's one home — the one checker
query in an otherwise pure-AST walk), cordis plugin-protocol slots
(name/inject/reusable/Config/apply, top-level and static), constructors,
overload implementations, declare-module augmentation bodies, and
re-export statements (checked at the defining module).
The 203 under-documented exports the gate found at adoption are filled
in this change, so the gate lands green; generated catalogs/graphs are
regenerated for the shifted line pointers.
RFC: docs/rfc/implemented/process/2026-07-06-export-surface-jsdoc-gate.md
Implement the tighten-hook-protocol-contract RFC (moved to implemented/):
- HookDialect narrows to 'claude' | 'codex': the 'native' variant had zero
producers (native plugins on the seams write no hook/* provenance), and the
dialect is defined as the bridge that ran the hook.
- HookOutput.suppressOutput is gone: the codec parsed it and every path
discarded it with no warn and no deferral — hook stdout never enters a
transcript, so there is nothing to suppress.
- hook/result.durationMs is gone: durable timing telemetry with no reader
that the snapshot normalizer had to scrub as replay noise. With no duration
to measure, runHook loses its injected now clock and the single-field
RunHookResult wrapper — it returns the HookOutput directly. The committed
hook fixtures had the field stripped mechanically (field-only diff); the
stdout goldens never carried it.
- The bridges' double-defaulted defaultTimeoutMs config knob is replaced by
one reference-default constant, DEFAULT_HOOK_TIMEOUT_MS, exported from the
lib's runner and applied inside runHook; per-hook timeoutSec stays the
override surface.
- The hook/result semantics move into the lib that declares the event:
HookResultRecord now carries the decoded HookOutput and appendHookResult
derives the decision string (decision ?? stop-on-continue:false ?? pass)
and the 500-char stderrSummary truncation; both bridges delete their
byte-identical private copies. The snapshot suite passes against the
existing goldens, proving the derived values are unchanged.
- Rider: BLOCKING_EXIT_CODE is codec-internal again (zero importers).
Amend the hook-protocol-lib and hook-snapshot-matrix RFCs to the new facts,
update the lib/bridge READMEs and the session.md event tables, and retarget
the affected unit tests (including new lib-level coverage of the derivation
rules).
Address review on the hook-protocol PR: the event-scope guard only rejected a
`hookSpecificOutput` block whose `hookEventName` NAMED a different event than
the firing one. A block with NO `hookEventName` slipped through and applied its
event-scoped permission fields to whatever event was firing. Under the keyed
Claude Code schema (where `hookEventName` is part of the block) a missing
discriminator is as malformed as a mismatched one — a Stop/UserPromptSubmit
hook emitting a bare `{ permissionDecision: 'deny' }` could deny the current
point.
Drop the `eventName !== undefined` clause so the guard fires on both a
mismatch and an omission when the caller passes `expectedEventName`; the
opt-out (no expectedEventName) still applies a discriminator-less block as-is.
Flipped the test that pinned the old behavior (it documented an artifact, not
a contract) and proved the corrected one red on the old guard.
The reference schemas key the `hookSpecificOutput` block by `hookEventName`, so
a block naming a DIFFERENT event than the one firing is malformed — a hook
emitting `hookSpecificOutput.hookEventName: "PreToolUse"` on a `Stop` event must
not deny the Stop. The codec surfaced `hookEventName` for a bridge to compare but
never enforced the discard, so both bridges pushed every parsed output into the
merge unconditionally.
parseHookOutput now takes an optional `expectedEventName`; when the block's
`hookEventName` names a different event, its event-scoped fields
(permissionDecision/permissionDecisionReason/additionalContext/updatedInput) are
discarded (the discriminator is still surfaced for the log, and the
event-agnostic top-level decision/continue/etc. are unaffected). runHook threads
it via RunHookOptions.expectedEventName; a caller that omits it opts out.
Codex review finding on the bridges PR (PR-F); fixed here on the codec that owns
the fold and knows field provenance, then flows down to both bridges.
Codex's PR-E review found three protocol-fidelity blockers + two doc gaps, all
verified against ~/repos/refs:
- (A) Top-level `decision` accepted allow/deny/ask, but both reference schemas
reserve those for hookSpecificOutput.permissionDecision — the legacy top-level
decision is approve/block ONLY. Split topLevelDecisionOf (approve/block) from
permissionDecisionOf (allow/deny/ask), so an out-of-band {"decision":"deny"} is
now invalid and ignored instead of becoming a real blocking decision.
- (A) hookSpecificOutput was parsed without its hookEventName discriminator.
HookOutput now surfaces hookEventName so a bridge can discard a block whose
claimed event doesn't match the firing one (the schemas key the block by event).
- (A) runHook discarded raw stdout. HookOutput now carries `stdout` (trimmed,
verbatim) so a bridge can reproduce CC's plain-stdout rendering / Codex's
plain-stdout-as-additionalContext behavior.
- (B) hook/* SessionEventMap variants were only named in prose; added a payload/role
table to core-data-structures/session.md (a maintained catalog surface).
- (B) Removed PR-stack-position references (PR-F / "future bridge packages") from a
test comment and the RFC, per the current-state-wording rule.
New codec tests: top-level allow/deny/ask invalid+ignored, hookEventName capture,
raw stdout preserved on plain + JSON + empty stdout. 51 tests, per-file 100%.