A name resolved through an export list (or a default-export identifier)
mapped back to its whole VariableStatement, and checkDecl walked every
declarator — so a private sibling sharing the statement with an exported
const was wrongly required to carry JSDoc.
The scope dispatch is now two-phase: requests accumulate per statement
(null = whole statement for a direct export modifier or ambient scope;
name sets union across lists, so two lists naming different declarators
of one statement both count), then each surfaced statement is checked
once with the declarator filter. Regressions pin the private-sibling
skip, the cross-list union, and the default-export sibling.
The #192 review discussion, preserved where design rationale lives: a
schema-object library (zod/schemastery) cannot sit at a wire-data boundary;
ajv replaces only the value walker while the subset gate — the module's
point — stays hand-written; provider JSON mode guarantees valid JSON, not
schema-conforming JSON, and would trade away mid-run tools and in-turn
validation retry for it. Strict tool schemas are named as the accepted
upgrade path when the provider ships them.
The coverage CI lane timed out (5000ms/test) in verify-export-jsdoc.spec.ts:
every test builds a ts.Program, and the fixture branch of
loadCompilerOptions omitted noLib, so each of the 34 tests parsed the
full default lib — measured 231ms/program bare, ~1.5s/test under
coverage instrumentation locally, past 5s on a 2-core runner. Fixtures
are self-contained and nothing in the walk resolves a lib symbol:
noLib + types:[] drops program creation to ~1ms and the spec's
coverage-mode test time from 53s to 0.16s. The real-repo branch
(tsconfig.base.json options) is untouched.
The timeout convergence on this branch made timedOut/aborted mutually
exclusive — one fused deadline reports the single cause that first cut the
command short — as the timeout-library RFC documents deliberately. The seam
type's JSDoc still described the old independent latches, so a consumer could
code the wrong contract. State first-cause classification on both fields and
cross-link the RFC. Docs-only; the code already matches.
finish() derived the exact omitted count from the pre-trim prefix/suffix
budget, but the UTF-8 boundary trims drop additional partial-codepoint bytes,
so an "Omitted N bytes" notice overstated what was kept (head maxBytes:2 over
'a€b' returned 'a' but claimed exact 3 when 4 bytes are absent). Derive the
count from the bytes actually returned (total − keptPrefix − keptSuffix) so
exact metadata matches the text and atLeast stays a valid lower bound.
When headTail budgets cover the whole stream, omitted is 0 and the two
retained halves are contiguous — the split is artificial and a multibyte
codepoint can straddle it. finish() now decodes the contiguous buffer as one
in that case; the per-side UTF-8 boundary trims and separate decoding apply
only when a real middle gap exists. Without this, a headTail retainer could
drop a character while reporting truncated:false.
Master's generated config catalog (#188, flattened paths #191) now records
plugin Configs; the nudge removal dropped structuredNudgeRetries from both
backends, so the regenerated catalog loses those rows.
The markers are touched mid-script, so runPoint's continuation chain (child
exit → merge → the listener's detached .then) can still be in flight when the
marker poll resolves; a vitest worker that exits first leaves the inject
condition's short-circuit path uncounted. Observed as a CI-only 99.03%
branch-coverage flake on hooks-claude — surfaced by this branch shifting suite
timing, latent before it. Two macrotask rounds pin the path deterministically.
Ship @deepseek-ai/dsh-retention under packages/util/: pure ItemRetainer /
TextRetainer plus neutral notice helpers, so tools that cap model-facing
output share one "what did we keep, what did we omit, may we stop reading"
mechanic while keeping grouping, exit codes, provider errors, and recovery
prose tool-owned. The two retainers are separate names because they differ in
resource model: item-head can stop the upstream on the first over-cap probe
(shouldStop), while text tail/head-tail must read to the end. The library
documents glob/grep/bash/web_fetch/web_search mappings but migrates no tool
yet — glob/grep don't exist, and migration is deliberately separate work.
Flips the RFC to implemented/ and rewrites its skeleton to shipped reality.
The parent branch dropped structuredNudgeRetries; the two workflow-side test
setups stop passing it, and the RFC's foundation paragraph now describes the
current design (final-ASSEMBLY enforcement logged via request/header, the
post-capture pre-execute deny, the start() schema snapshot, no re-prompt)
instead of the retired agent/request + nudge shape.
Two human review directives:
- No re-prompt. A structured child that finishes a turn cleanly without
calling structured_output settles error to the parent immediately —
readResult already carried that mapping; the nudge loop only delayed it.
Deletes the loop, its cancellation-window guard, STRUCTURED_OUTPUT_NUDGE,
and the structuredNudgeRetries Config on both backends.
- FIXME in the structured module doc: per-agent/per-session tool registry and
prompt assembly would dissolve the final-assembly enforcement dance (the
placeholder tool, the swap, the strip, the global-registration lifetime).
Residuals from the Codex re-review:
1. A system delta's insert was flattened to one token, so deltas differing
only in inserted-line count compared equal. Now one {{system}} token per
inserted line — position AND extent survive, content does not.
2. The live uniformity guard folded only request/header snapshots, so a
mid-run header CHANGE (request/header-delta) could diverge from the pin
invisibly. Non-pinning runs now assert zero header-delta events: a
scenario that legitimately changes its header mid-run exists to show
that change, so it must pin (fail-loud until it does).
Codex review findings on the pinned-header change:
1. scrubRequestHeaders flattened a request/header-delta's whole
system/tools payload to one token, so two meaningfully different deltas
compared equal. Now the structural facts survive — keepStart/keepEnd
line positions, added/removed/changed tool NAMES — and only the bulk
(inserted prompt lines, schema bodies) is tokenized.
2. The one-pin design rested on an unasserted premise (all sessions
compose the same header). Every non-pinning scenario now asserts, live,
that each request/header its run produces equals the pinned fixture's
header (both sides normalized against their own volatile values), so a
session-dependent header fails loud until it gets its own pin.
Verified the guard bites: perturbing the pinned fixture's prompt fails
a non-pinned scenario with the intended message.
3. RFC de-slopped per docs/AGENTS.md: no PR reference, no SHOULD
spec-speak; Decision/Verification/Consequences updated for 1 and 2.
Codex round-3: alias prose matches the gate's strength only when the
target's own contract is prose-only. An export-import alias to a
function, class, or namespace target (or an unresolvable one) is now
refused — those carry signature/member contracts the alias cannot
hold; export the declaration directly instead. Const/enum/interface/
type-alias targets keep the self-documentation contract.
Tests pin the refusal for function, class, and namespace targets;
module doc and RFC updated.
Two bot findings on the structured runtime:
- Terminal means terminal WITHIN the step: a model response listing
structured_output before further tool calls executed those calls after the
final answer was accepted (the turn-continuation veto only fires at step
end). A third runtime listener now denies every call for a captured agent
at the tools/pre-execute gate — dispatch skipped, isError result naming the
contract. Calls preceding the capture in the same response are untouched.
- The output schema is structuredClone'd before the subset assertion: the
caller keeps its reference, so asserting and attaching the original let a
post-start() mutation drift the enforced schema away from the asserted one.
The clone pins assertion, model-visible parameters, and validation to one
value.
The manifest excluded docs/tool-catalog/ and docs/persistence-catalog/ as
directories; the trailing slash is a deliberate path boundary, so the
flattened single files no longer matched and lost their generated-doc
classification — the gate would have accepted a hand-added .zh.md twin it
must reject. The entries now name the files.
Codex review of the draft: the abort-on-dispose tests asserted only that
dispose() resolved and no warning fired — a regression back to untracked
fire-and-forget runs would pass both. The hook script now records its
shell PID before sleeping, and after dispose the test asserts
process.kill(pid, 0) throws: the drain resolves only after the killed
run settled (the child reaped), so ESRCH-by-dispose-return is
deterministic under the correct implementation, while an alive or
unreaped process fails it. Verified the discriminator by gutting drain()
locally: exactly these two tests fail.
Review findings on the config catalog:
The transitive-paste closure deduplicated by bare name, so two DIFFERENT
package-local declarations sharing a name would silently collapse to the
first one pasted — and a reference satisfied by the wrong declaration is a
wrong catalog, not just a thin one. Every resolution is now identity-checked
by source pointer: reaching the same declaration again is benign, while a
name that resolves to two distinct declarations, to conflicting imports, or
to a declaration in one file and an import in another is a violation — a
verbatim fence has a single flat namespace, so the fix is a rename at the
source. A spec case pins the two-declarations collision.
docs/config-catalog.md also joins the translation-pairing exclusions: it is
a generated English-only doc, and without the entry the gate would accept a
hand-added .zh.md twin it must reject.
Codex round-2 review found three adjacent fail-open shapes:
- Wrapped function expressions escaped classification: parentheses,
as/satisfies casts, and non-null assertions are now peeled before the
arrow/function test (initializers and default exports), and a
single-call-signature type literal counts as the surface signature.
A literal mixing call/construct signatures with anything else is
refused outright — no single signature to hold the tags against.
- The blanket 'export import X = N.member' skip was unsound (the target
can be a non-exported namespace member no walk visits): an alias now
documents itself.
- The heritage extra-parameter duty missed binding-pattern extras,
which no base declaration can name: they now trigger the standard
binding-pattern violation.
Five new negative-path tests pin the closed shapes; module doc and RFC
updated.
Restack on the carved-out foundation (#192), per review feedback on #170.
The seam files resolve to the carve-out's revision — its prompt-order
neutrality fix (backends no longer inject 'tools'; the structured runtime
gates its own capture-tool registration) restores the subagent tools to
master's front position, so every recorded fixture is re-recorded on the
stacked tree and the authored error-finish/cancel headers re-patched to the
stacked tool list ([subagent, subagent_fork, workflow, todo_write, ...]).