dsh-ptc-plus 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -0
  3. package/README.zh.md +137 -0
  4. package/assets/dsh-ptc-plus-banner.webp +0 -0
  5. package/assets/ptc-plus-repair.png +0 -0
  6. package/assets/ptc-plus-settings.png +0 -0
  7. package/client.js +366 -0
  8. package/cordis.patch.yml +5 -0
  9. package/docs/README.md +21 -0
  10. package/docs/adr/0001-delegate-governance-to-dsh.md +3 -0
  11. package/docs/adr/0002-limit-work-map-scope.md +3 -0
  12. package/docs/adr/0003-prefer-native-program-surfaces.md +3 -0
  13. package/docs/adr/0004-declare-program-bindings-at-the-host.md +5 -0
  14. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +129 -0
  15. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +45 -0
  16. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +39 -0
  17. package/docs/adr/0008-define-agent-operating-contract.md +25 -0
  18. package/docs/adr/0009-bound-expensive-acceptance-default.md +39 -0
  19. package/docs/adr/0010-session-log-derived-recovery-tips.md +26 -0
  20. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +20 -0
  21. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +23 -0
  22. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +21 -0
  23. package/docs/adr/0014-persist-cell-rewrite-policy.md +25 -0
  24. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +25 -0
  25. package/docs/adr/0016-archive-resolved-review-findings.md +43 -0
  26. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +23 -0
  27. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +25 -0
  28. package/docs/adr/0019-plugin-settings-and-kill-switch.md +32 -0
  29. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +36 -0
  30. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +68 -0
  31. package/docs/adr/README.md +18 -0
  32. package/docs/architecture.md +134 -0
  33. package/docs/capability-projection.md +48 -0
  34. package/docs/client-ui.md +47 -0
  35. package/docs/durability-design.md +265 -0
  36. package/docs/evaluation.md +62 -0
  37. package/docs/installation.md +82 -0
  38. package/docs/program-data-plane.md +61 -0
  39. package/docs/publishing.md +37 -0
  40. package/docs/runtime-reference.md +119 -0
  41. package/docs/value-wire.md +61 -0
  42. package/index.js +532 -0
  43. package/internal/ast-traversal.js +20 -0
  44. package/internal/binding-descriptors.js +95 -0
  45. package/internal/binding-pattern.js +54 -0
  46. package/internal/builtin-esm-sync.js +9 -0
  47. package/internal/cell-analysis.js +467 -0
  48. package/internal/cell-rewriter.js +520 -0
  49. package/internal/config-spec.js +179 -0
  50. package/internal/cordis-tools-owner.js +363 -0
  51. package/internal/diagnostic.js +133 -0
  52. package/internal/direct-surface-owner.js +326 -0
  53. package/internal/edit-transport-owner.js +287 -0
  54. package/internal/failure-reporting.js +139 -0
  55. package/internal/kernel-worker.js +484 -0
  56. package/internal/module-policy.js +46 -0
  57. package/internal/program-bindings.js +187 -0
  58. package/internal/record-utils.js +53 -0
  59. package/internal/recovery-tips.js +81 -0
  60. package/internal/rejected-cell-editor.js +328 -0
  61. package/internal/repl-convenience.js +201 -0
  62. package/internal/runtime-bridge-owner.js +417 -0
  63. package/internal/runtime-config.js +77 -0
  64. package/internal/runtime-contexts.js +31 -0
  65. package/internal/sdk-projection.js +88 -0
  66. package/internal/session-cell-executor.js +612 -0
  67. package/internal/session-journal.js +889 -0
  68. package/internal/session-log-view.js +80 -0
  69. package/internal/session-runtime.js +536 -0
  70. package/internal/session-state.js +45 -0
  71. package/internal/source-position-map.js +224 -0
  72. package/internal/tool-call-canonicalizer.js +339 -0
  73. package/internal/value-wire.js +421 -0
  74. package/internal/worker-client.js +218 -0
  75. package/internal/worker-cwd-virtualization.js +155 -0
  76. package/package.json +100 -0
  77. package/src/client.js +165 -0
@@ -0,0 +1,5 @@
1
+ # Attach the session REPL after both the tools registry and Code Runtime exist.
2
+ # The plugin adds a virtual rejected-cell edit transport beside DSH's run_code.
3
+ - insert:
4
+ - id: ptc-plus
5
+ name: dsh-ptc-plus
package/docs/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # Documentation
2
+
3
+ ## Getting Started
4
+
5
+ - [Installation](installation.md): npm, Git, source, tarball, development snapshot, and Desktop installation.
6
+ - [Runtime Reference](runtime-reference.md): cell behavior, capability discovery, configuration, diagnostics, and limits.
7
+ - [Evaluation](evaluation.md): paired ordinary-task protocol, metrics, configuration, and limitations.
8
+
9
+ ## Design
10
+
11
+ - [Architecture](architecture.md): ownership boundaries, prompt-prefix stability, runtime lifecycle, journals, and recovery.
12
+ - [Capability Surface](capability-projection.md): typed program bindings and progressive discovery.
13
+ - [Program Data Plane](program-data-plane.md): canonical values and result completeness.
14
+ - [Durable / Volatile Recovery](durability-design.md): replay model and external-input boundaries.
15
+ - [PTC Value Graph V1](value-wire.md): supported JavaScript value encoding.
16
+ - [Architecture Decisions](adr/): stable design constraints and their consequences.
17
+
18
+ ## Maintenance
19
+
20
+ - [Publishing](publishing.md): release checks, package contents, permissions, and platform validation.
21
+ - [Client UI](client-ui.md): current UI boundary and reevaluation criteria.
@@ -0,0 +1,3 @@
1
+ # Delegate Governance to DSH
2
+
3
+ PTC Plus treats `danger-full-access` as its first-class experience and keeps native ambient effects available there, classifying non-replayable state as `volatile`. It does not implement a second cross-platform sandbox or permission system. Other profiles retain only the tool capabilities supplied by the current DSH request; unavailable tool operations fail explicitly. A narrower tool view does not make the worker an ambient Node sandbox. DSH owns tool authority and policy, while process and operating-system confinement own direct Node access.
@@ -0,0 +1,3 @@
1
+ # Limit Work Map Scope
2
+
3
+ PTC Plus exposes only the REPL facts represented by its runtime contracts: structured diagnostics, durable/volatile state, named states, capability metadata, leases, settlement, and replay evidence. It does not implement a Work Map, dynamic model-context materialization, semantic sidecar, or second session coordinator. Those features require upstream aggregation across goals, jobs, effects, and artifacts.
@@ -0,0 +1,3 @@
1
+ # Prefer Native Program Surfaces
2
+
3
+ Under the first-class `danger-full-access` profile, PTC Plus preserves familiar native Node, shell, ecosystem APIs, and the typed `tools.*` surface instead of wrapping effects in plugin-owned adapters. A tool's model/UI rendering is not its program contract: each result keeps its own completeness class. Supplied program-call arguments remain canonical. When a native `tools` call supplies no JavaScript argument, PTC Plus may canonicalize that omission to `{}` only when DSH's maintained JSON Schema validator proves the live object schema accepts `{}`; explicit `undefined`, required-input tools, and other namespaces are unchanged. The canonical `{}` enters dispatch and the call transcript, so live execution and replay compare the same value. Every settled program binding call uses recorded-value replay without redispatching external effects; termination with an unsettled call preserves its `global.member` as a possible-effect boundary. Direct Node/OS access without a call transcript makes the live suffix `volatile`. Bounded results remain callable but cannot be promoted to lossless values. Durable replay describes cold recovery of settled history, not idempotence or retry safety for a cell that failed during live execution.
@@ -0,0 +1,5 @@
1
+ # Declare Program Bindings At The Owner
2
+
3
+ PTC Plus does not create a private cross-plugin registry or reflective invocation bus. The current public CodeRuntime request surface has no registry for discovering non-tool program bindings.
4
+
5
+ If DSH later provides one, capability owners should publish explicit typed contracts, semantic metadata, source references, and binding factories. DSH should own discovery, scope, authority, leasing, dispatch, and settlement. Explicit registration, generated manifests, decorators, or scanning are assembly choices; none replaces those contracts. PTC Plus would consume only bindings already present in the live request.
@@ -0,0 +1,129 @@
1
+ # Register Edit And Run As A Truthful Composite Tool
2
+
3
+ A small correction to a long `run_code` cell should not require the model to emit the complete
4
+ source again. This applies both to cells rejected by the runtime and to cells that completed but did
5
+ not achieve the intended result. PTC Plus therefore registers `edit_run_code` as a real DSH tool:
6
+
7
+ ```ts
8
+ edit_run_code({
9
+ edits: Array<{ old_string: string; new_string: string }>
10
+ })
11
+
12
+ edit_run_code({
13
+ regex_edits: Array<{
14
+ pattern: string
15
+ flags: string
16
+ replacement: string
17
+ expected_matches: number
18
+ }>
19
+ })
20
+ ```
21
+
22
+ PTC mode requests using the code-only direct-tool projection expose `[run_code, edit_run_code]` in
23
+ that order. The first conclusive prompt assembly creates one per-agent composition owner before any
24
+ plugin-owned presentation effect changes the nearest DSH scope. A non-empty `tools:code-only`
25
+ section proves `code`, an empty section proves `both`, and only a missing owner signal permits a
26
+ tool-shape inference. For a code composition, the same owner
27
+ registers `edit_run_code` through `agent.ctx.tools.register()` and uses the same scope's
28
+ `tools.presentAs('both')` to admit both direct calls. It retains both exact disposers and releases
29
+ registration and presentation together with the owning session, agent, or plugin. Native-mode and unrelated agent
30
+ scopes never inherit the tool. Unrelated native tools remain in the program SDK. A top-level native call is outside that
31
+ valid direct protocol. The stream canonicalizer may deliberately replace it with the equivalent
32
+ `run_code` transport when the live schema uniquely proves the native member; otherwise the call
33
+ remains unchanged for host diagnosis. A nested native call from a cell continues through the
34
+ ordinary DSH pipeline. Each assembly derives its `native`, `code`, or `both` presentation from the
35
+ lifecycle owner rather than inferring composition mode again from the plugin-modified scope. Only
36
+ `code` collapses direct tools, normalizes top-level native calls, or
37
+ rejects native dispatch. Both PTC presentations adapt `run_code`, append the program SDK, and
38
+ project session runtime contexts, while `both` also preserves native direct calls. The initial
39
+ transport-only shape may bootstrap the first scoped registration, but the resulting
40
+ `tools.presentAs('both')` declaration is only the executor-enabling effect for the two transports
41
+ and cannot become a second mode owner. Each completed prompt assembly therefore preserves the
42
+ agent composition's direct projection without releasing the per-agent registration. The
43
+ model stream binds every complete tool call's session and call ID to that assembly policy, and
44
+ execution consumes this stable relation even when another public wrapper replaces the cancellation
45
+ signal. A settled result retires the relation. The retained registration does not authorize the
46
+ tool outside a code composition, and one shared session lifecycle revokes outstanding request and
47
+ call records on session, agent, or plugin disposal.
48
+
49
+ `edit_run_code` targets the most recent editable cell in the current turn when its persisted
50
+ `tool/call` event is recorded. Execution resolves that snapshot through the event sequence rather
51
+ than reading the latest target when the handler begins. Intervening inspection tools do not erase
52
+ the target, and later settlements or handler scheduling do not retarget the in-flight edit. A call
53
+ that could otherwise edit a cell but lacks a unique persisted event is rejected instead of using
54
+ the latest target. The derived cell enters replay history at the outer `tool/result` event, so a
55
+ delayed handler is recovered after every cell that settled before it executed. A successful edit
56
+ creates a new derived cell, which
57
+ becomes the next editable source, so successive small adjustments do not require a full resend.
58
+ Invalid edit arguments return `{ edited: false, reason }` and leave the target unchanged.
59
+ Process-local target claims distinguish executing and settled derived dispatches. A rejection,
60
+ cancellation, or result without a valid PTC journal releases the claim because no owned fact proves
61
+ that the derived cell entered the runtime. A journaled dispatch retains the claim through outer
62
+ result projection, closing the interval before the session log exposes the derived edit as the new
63
+ target. If final policy removes that private metadata, `tools/result` releases the claim. The
64
+ session-log projection captures the eligible target at the edit call event and accepts a derived
65
+ source only when its target sequence matches that snapshot and its journal is valid and non-noop,
66
+ so live and recovered eligibility derive from the same persisted relation.
67
+
68
+ The caller sends exactly one of `edits` or `regex_edits`. Exact edits contain at most 16 items; each
69
+ non-empty `old_string` must differ from `new_string` and occur exactly once in the target. Regular
70
+ expression edits also contain at most 16 items, use unique `gimsu` flags, reject zero-length matches,
71
+ and require an exact `expected_matches` count. All ranges resolve against the original target and
72
+ must not overlap. Search work, match count, capture records, replacement templates, expansion,
73
+ materialized replacement text, final source size, and regular-expression CPU time have fixed
74
+ budgets.
75
+
76
+ `internal/rejected-cell-editor.js` owns the schema, validation, range collection, replacement
77
+ semantics, budgets, and atomic linear assembly. The registered tool passes the materialized source
78
+ to DSH's public `tools.execute()` as a derived `run_code`. DSH remains the owner of validation,
79
+ authorization, scheduling, cancellation, execution, and result projection.
80
+
81
+ The session history preserves the model-authored call:
82
+
83
+ ```text
84
+ assistant/tool-call: edit_run_code(delta)
85
+ tool/result: { edited: true, value?, error?, logs }
86
+ ```
87
+
88
+ The complete derived source, its journal, and its target call sequence are stored only in private tool-result metadata, including
89
+ when the derived cell fails after entering the runtime. Cold
90
+ recovery folds that explicitly marked derived run into the durable REPL history. The source is not
91
+ copied into the model-visible edit result, and assistant stream chunks for the two declared
92
+ transports pass through unchanged.
93
+ Consequently the UI and later model requests do not attribute a generated `run_code` call to the
94
+ model.
95
+
96
+ No edit-specific runtime context is emitted. The truthful call/result pair already carries the
97
+ operation and outcome; changing an aggregate runtime-context snapshot merely to restate that fact
98
+ would repeat unrelated policy context. Rewrite feedback and unrelated recovery tips retain their
99
+ own independently justified lifecycles.
100
+
101
+ ## Alternatives considered
102
+
103
+ **Rewrite `edit_run_code` to `run_code` before persistence.** This reuses DSH's default `run_code`-only
104
+ transport, but falsifies the model-authored history, duplicates complete source in model context,
105
+ and requires later prose to reinterpret the stored call. It is rejected.
106
+
107
+ **Return the materialized source in the edit result.** This makes the derivation visible, but sends
108
+ the same large source back into the next model request. Private derived metadata gives recovery the
109
+ required bytes without paying that context cost.
110
+
111
+ **Limit editing to pre-execution failures.** That avoids retrying an effectful failed cell, but it
112
+ also prevents the common case where execution succeeded and only the intended result needs a small
113
+ adjustment. The tool edits the recorded source and performs a new explicit execution; it does not
114
+ claim that the earlier execution was undone or idempotent.
115
+
116
+ **Forbid all stream normalization.** A valid declared call and an invalid
117
+ out-of-surface call are different protocol facts. Rewriting a real `edit_run_code` destroys a valid
118
+ identity; normalizing a live-schema-proven native miscall to the declared `run_code` transport gives
119
+ an otherwise invalid call its executable representation. Native-call canonicalization therefore
120
+ remains available while the two declared transports pass through unchanged.
121
+
122
+ ## Consequences
123
+
124
+ Tool name, schema, and order remain stable regardless of target availability. `edit_run_code` is a
125
+ normal provider tool call, not a synthetic wire alias. The derived run is auditable and replayable,
126
+ but is explicitly host-generated and excluded from model-visible history. This adds no editor DSL,
127
+ authority layer, or retry guarantee; it only avoids re-emitting source already owned by the session.
128
+ Provable top-level native-call repair remains a separate, intentionally history-normalizing transport
129
+ feature.
@@ -0,0 +1,45 @@
1
+ # Rewrite Module Syntax With An AST
2
+
3
+ ## Problem
4
+
5
+ PTC Plus evaluates each cell as an async function body, where static `import` and `export` declarations are syntactically invalid. The adapter must accept TypeScript type syntax, preserve cell-relative source positions and line counts, classify the rewritten program for replay durability, record every automatic rewrite, and reject malformed or unrecognized module syntax without partially deleting or transforming it. Token-by-token text scanning duplicated parser responsibilities and could accept a recognized prefix while leaving an invalid suffix or erase malformed type-only declarations.
6
+
7
+ ## Decision
8
+
9
+ [`internal/cell-rewriter.js`](../../internal/cell-rewriter.js) owns module-syntax adaptation. `@babel/parser` parses the original cell with its TypeScript plugin under script semantics while explicitly allowing top-level module declarations, `return`, and `await`; recognizing a module declaration therefore does not impose module strict mode on the surrounding async-function body. Before any edit or module preload record is produced, `@babel/traverse` builds the original Program scope. Its declaration and hoisting rules reject same-scope collisions between value imports and lexical, `var`, function, or other import bindings while retaining block shadowing and the separate TypeScript type namespace. The same scope validates local exports and drives imported-reference rewriting, supplemented by top-level TypeScript declaration names because Babel intentionally excludes the type namespace from runtime scope. The rewriter derives every accepted import, export, type-only declaration, specifier, source value, and edit range from that AST. One batch edit assembler validates non-overlapping ranges, produces generated source, and composes source mappings without rebuilding either artifact per edit. [`internal/cell-analysis.js`](../../internal/cell-analysis.js) then wraps the rewritten cell as an async function body and first parses it with Acorn. Already-valid JavaScript proceeds unchanged; only a cell that Acorn cannot parse is passed through Node's `stripTypeScriptTypes` and parsed again for binding and durability analysis.
10
+
11
+ Each static import or remote re-export creates an ordered module preload record. A value-bearing record carries its source, import attributes, original position, generated temporary-global identifier, and the default or named exports required by its AST specifiers. The dedicated worker uses Node's synchronous module resolve hook to give REPL imports and static-link adapters one parent at the session cwd, falling back to the worker process cwd when the session has no recorded cwd. Node's ESM resolver therefore owns relative URLs, bare packages, conditional `exports`, URL schemes, import conditions, and platform path conversion; plugin and target-module dependency imports retain their natural parents. Each ephemeral data-URL adapter expresses the required members as static re-exports, so Node completes export linkage before evaluating the target module, then returns the namespace for a value-bearing preload. The worker resolves records before compiling the cell and exposes each namespace under its temporary identifier. One generated lexical prologue after the original directive prologue and before every executable cell statement captures each temporary identifier into a distinct persistent namespace identifier in source order; import declarations are erased at their original ranges, and the temporary globals are removed when the cell settles. Both identities are allocated outside current source identifiers, persistent REPL bindings, reserved program bindings, and every live private namespace. The prologue references the generated host identifier directly, so a lexical `globalThis` binding has ordinary JavaScript semantics and cannot redirect capture. A binding catalog stores only the persistent namespace identity and maps public import aliases to it and the export name; scope-aware rewriting routes unshadowed reads through the namespace, keeps bare, optional, and tagged calls detached from the namespace receiver with the language's comma-reference form, and lowers writes to a deterministic `TypeError`. Private namespace names remain reserved after a public alias is redeclared, so closures from earlier cells cannot be redirected by a later import. The catalog is rebuilt during journal replay. In loose REPL mode an explicit later declaration replaces the alias for future cells, while closures compiled before that declaration retain their namespace read. Destructuring, loop, nested, and delete writes that cannot preserve native evaluation semantics are rejected before execution. Direct `eval` and `WithStatement` are rejected on the original Program while value import aliases are active because generated namespace reads cannot reproduce their dynamic lexical resolution; this validation precedes module preload. Indirect or locally shadowed eval, non-strict `with` without value aliases, and cells containing only type imports retain ordinary JavaScript semantics. Side-effect, default, namespace, named, default-plus-named, and default-plus-namespace forms remain supported. Export modifiers are removed while declarations remain; default exports bind the fixed public name `__default`; anonymous default function and class lowering emits an explicit statement terminator, while default expressions retain their parsed continuation; remote re-exports become preload-only side-effect modules; local export lists are removed; and declaration-level or specifier-level type-only syntax is erased.
12
+
13
+ The rewriter normalizes Babel syntax and local-export validation failures as original-cell positions before they cross the module boundary. Executable wrapper parsing retains Acorn's wrapper-relative positions, and only those positions receive the one-line wrapper subtraction during `PTC-C001` rendering. [`internal/cell-analysis.js`](../../internal/cell-analysis.js) then applies return, redeclaration, collision, and durability analysis to the successfully rewritten program. Return control-flow lowering addresses its per-cell worker-global signal through the top-level `this` receiver, which bypasses both lexical `globalThis` shadowing and `with` object-environment resolution; worker startup validates that this receiver is the evaluator's global context, and settlement removes the signal property. Lowered returns emit an explicit statement terminator so a return in a `switch` clause remains valid after replacement. Rewrite provenance remains parallel to the closed journal schema under `meta.dshPtcPlusRewrites` and does not alter replay equality. A successful rewrite remains private audit metadata; only a failed rewrite or missing valid journal may project its description as a bounded recovery context.
14
+
15
+ ## Alternatives considered
16
+
17
+ **Strip types before any other transformation.** Node's module-mode type stripper rejects the top-level `return` statements that are valid in a PTC cell, so it cannot process ordinary cells.
18
+
19
+ **Wrap the cell before parsing module declarations.** Wrapping makes top-level returns legal, but static module declarations then appear inside a function and become invalid before they can be identified. It also shifts every AST coordinate and makes module edits depend on wrapper offsets.
20
+
21
+ **Continue with token-based source surgery.** Recognizing regular-expression literals, nested template expressions, automatic semicolon insertion, comments between modifiers and declarations, and every import/export production recreates a JavaScript and TypeScript lexer. Partial recognition can either reject a valid cell or silently alter a binding, while a complete maintained parser already exposes the required nodes and ranges.
22
+
23
+ **Extend Acorn with a TypeScript plugin.** This would retain one parser family, but Acorn still cannot represent the PTC cell's combined module-declaration and top-level-return grammar without additional parser customization. It also makes correctness depend on a third-party extension matching both the installed Acorn version and Node's accepted erasable TypeScript syntax.
24
+
25
+ **Mask returns before parsing.** A correct masker must lex every JavaScript literal and nested expression that may contain `return`, which is the same parser duplication this decision removes. Parser support for `allowReturnOutsideFunction` accepts the cell grammar directly without changing source coordinates.
26
+
27
+ **Parse the complete cell as a module or use automatic source-type selection.** Both modes impose module strict semantics whenever static module syntax is present, rejecting JavaScript that remains valid in the actual non-strict async-function body. Permissive script parsing separates recognition of module nodes from the execution grammar, while Babel scope traversal retains explicit local-export validation.
28
+
29
+ **Run the TypeScript stripper for every cell.** Node's TypeScript parser rejects valid non-strict JavaScript such as `with` statements and legacy octal literals even when the cell contains no TypeScript. Letting Acorn accept executable JavaScript first keeps TypeScript erasure conditional and makes the runtime grammar authoritative.
30
+
31
+ **Destructure the dynamic-import namespace into ordinary REPL variables.** This is compact but copies the current property value instead of preserving an ESM live binding. The REPL convenience layer also makes the generated declaration writable, so reads and assignments both diverge from imported-binding semantics.
32
+
33
+ **Install import aliases as global accessors.** Getters can expose live values and setters can reject assignment, but they mutate the user-visible global namespace, interact poorly with lexical redeclaration, and make replay depend on worker-global descriptor cleanup. Rewriting only references resolved by the AST keeps the policy in the module adapter and leaves ordinary shadowing to JavaScript scope.
34
+
35
+ **Emit `await import(...)` inside the REPL cell.** This keeps module loading entirely in generated source, but Node's REPL applies its own top-level-await declaration transform after PTC source mapping. Runtime positions then refer to an unobservable third source, and imports execute at declaration position instead of before the cell body. Worker preloading preserves static-import ordering and leaves one mapped cell source as the diagnostic authority.
36
+
37
+ **Resolve specifiers with path concatenation or `createRequire.resolve`.** String joining handles relative paths but does not implement URL schemes, package `exports`, ESM conditions, or platform URL conversion. `createRequire.resolve` uses CommonJS resolution conditions and can choose a different package entry from `import`. A scoped Node resolve hook retains the runtime's ESM resolver and changes only the synthetic REPL and adapter parent.
38
+
39
+ **Load a namespace and inspect its properties.** An own-property check distinguishes an absent export from one whose value is `undefined`, but it runs after the target module has evaluated. Encoding requirements as static re-exports lets Node reject the adapter during linking and prevents target evaluation for that failed preload.
40
+
41
+ **Compare imported names with generated top-level declarations after rewriting.** This duplicates JavaScript and TypeScript binding rules, loses hoisted declarations nested in statements, and cannot distinguish a valid type-space declaration or block shadow from a runtime collision without rebuilding scope analysis. Validating the original Babel scope keeps the language rule ahead of lowering and preload effects.
42
+
43
+ ## Consequences
44
+
45
+ Each accepted transformation corresponds to a complete TypeScript AST node, and every automatic type erasure produces provenance without recovering module names from source text. Static modules resolve from the session project in source order before cell compilation; their aliases are initialized before body execution regardless of textual declaration position or an earlier return. A resolution, linkage, or evaluation failure leaves REPL declarations and aliases uncommitted, although a successfully completed earlier preload may already have produced effects. A preload with a missing requested export does not evaluate its target module. Imported reads add one namespace property access; bare, optional, and tagged calls use an indirect reference so the namespace cannot become `this` and nullish optional calls still short-circuit. Malformed module or type syntax and unsupported imported writes fail before execution. The production dependency set includes `@babel/parser` and `@babel/traverse`; Acorn remains the executable-JavaScript and scope-analysis authority downstream. Module resolution uses Node's `registerHooks` primitive available at the engine floor. Node's `stripTypeScriptTypes` is invoked only when the rewritten cell is not already valid JavaScript and remains experimental at the current engine floor, so the compatibility suite must be rerun when the supported Node version changes.
@@ -0,0 +1,39 @@
1
+ # Separate Worker Transport From Session Semantics
2
+
3
+ ## Problem
4
+
5
+ The session runtime coordinates two different lifecycles: transport ownership for a worker, private message port, stderr tail, termination, and scratch directory; and REPL semantics for cell settlement, durability, journal replay, host binding calls, diagnostics, and checkpoints. Keeping both in `SessionKernel` made transport faults mutate semantic state through shared fields and duplicated policy facts across the host analyzer and worker.
6
+
7
+ ## Decision
8
+
9
+ [`internal/worker-client.js`](../../internal/worker-client.js) exclusively owns worker creation, the readiness handshake, the private port, bounded stderr capture, reset, concurrent termination tracking, scratch allocation, and scratch cleanup. It reports private-channel messages and terminal failures through callbacks. [`internal/session-runtime.js`](../../internal/session-runtime.js) owns the active cell and decides how a reported message or failure settles the journal, durability state, replay transaction, or binding call. Messages reach the kernel only from the client's current worker, while each active semantic transaction retains its worker identity for lease and replay checks.
10
+
11
+ Cross-process policy that must agree on both sides has one source. [`internal/module-policy.js`](../../internal/module-policy.js) defines durable imports, forbidden kernel-control imports, and ambient globals for both static analysis and the worker runtime. [`internal/failure-reporting.js`](../../internal/failure-reporting.js) defines hostile-error normalization, active-cell stack extraction, bounded diagnostic logs, and repeat-failure tracking for the host and worker. Session-log recovery belongs to [`internal/session-journal.js`](../../internal/session-journal.js), which owns journal shape, event association, and branch folding. Live durable nodes, current-call exclusion, no-op confirmations, recovery boundaries, and cold nodes all use persisted `tool/call.seq` identity. During live dispatch, the journal owner uses `callId` only to resolve the unique unpaired top-level call, then passes its sequence into semantic state. Sessionless direct runtime calls do not fabricate a persisted identity. Journal v3 stores confirmed no-ops as sequences; v2 journals with empty confirmations migrate without ambiguity, while a v2 call-id confirmation forms an unknown suffix. A failed durable replay records one log-only recovery boundary in the current settled `tool/result` metadata under `meta.dshPtcPlusRecoveryBoundaries`; its payload identifies the failed call event and its parent frontier without inventing a DSH event type. Every boundary is normalized as it enters recovery, before its event sequence participates in ordering; malformed boundary data therefore rejects reconstruction instead of disappearing from the fold. A completed record enters branch folding at its `tool/result` event position because that event confirms durability. Folding leaves immutable call/result events intact, removes the failed node and dependent descendants, and recomputes checkpoints from the remaining records. [`internal/session-runtime.js`](../../internal/session-runtime.js) resets the worker and retries the contracted frontier, recursively reaching the empty REPL only if every earlier candidate also fails. The exported migration helper converts old custom boundary events into this metadata before DSH restores a session and never mutates its input.
12
+
13
+ ## Alternatives considered
14
+
15
+ **Keep transport inside `SessionKernel`.** This avoids a class and callback pair, but leaves process handles, temporary filesystem state, private protocol setup, replay semantics, and journal settlement in one long-lived object. Transport tests then depend on semantic internals such as direct worker and port field mutation.
16
+
17
+ **Move message interpretation into the client.** The client could validate the whole private protocol, but `done`, `call`, `volatile`, and `output-limit` messages depend on the active journal, replay transcript, binding lease, output budget, and durability state. Moving those decisions would duplicate semantic state across two owners.
18
+
19
+ **Give each cell a new worker.** Per-cell workers simplify cancellation and failure isolation, but destroy live binding continuity and require source or state transport between cells, contrary to the session-bound REPL contract.
20
+
21
+ **Keep duplicated module and failure helpers near each consumer.** Local copies reduce imports, but policy drift can make static durability classification disagree with runtime behavior, and error normalization differences make worker and host failures observably inconsistent.
22
+
23
+ **Mutate or replace the persisted result after replay failure.** A session exposes immutable event snapshots and an append-only public write path; rewriting a result either fails against frozen data or bypasses the session's persistence and publication contract. The current settled result owns private boundary metadata, preserving the original evidence while keeping the persisted event vocabulary DSH-loadable.
24
+
25
+ **Locate a replay result by journal equality.** Journals encode replay semantics rather than event identity, so unrelated cells may have identical journals. Call identity and `sourceEventSeqs` select the exact result without conflating equal content with log position.
26
+
27
+ **Use `callId` as the recovery-boundary identity.** Provider call IDs correlate calls and results but are not guaranteed to be unique across a session. Persisted event sequences identify exact log records and remain stable when a provider reuses an ID.
28
+
29
+ **Keep call-id confirmations for legacy compatibility.** Dual identity would require every recovery branch to choose which identifier is authoritative and would preserve the ambiguity that caused durable history loss. The decoder instead accepts only the legacy subset whose empty confirmation list needs no association.
30
+
31
+ **Resolve the event identity only after replay fails.** Delayed lookup occurs after later calls and results may have entered the log, making reused IDs or incomplete associations ambiguous. Capturing the current unpaired call at dispatch preserves the identity already established by DSH's append-before-dispatch lifecycle.
32
+
33
+ **Always recover from an empty REPL.** This guarantees a clean worker but discards verified bindings and checkpoints unnecessarily. Parent-by-parent contraction retains the largest frontier whose replay actually succeeds.
34
+
35
+ **Compare raw boundary sequences before decoding boundary events.** This appears to avoid work for boundaries after the current record, but an absent or nonnumeric sequence is not orderable and can evade the decoder entirely. Normalizing at session-log ingestion gives the fold one trusted boundary shape and makes damaged history fail closed.
36
+
37
+ ## Consequences
38
+
39
+ Worker resource cleanup and startup failures are independently testable, while the kernel retains one owner for journal and replay meaning. The client callback is deliberately narrow: it carries transport facts, not journal decisions. Recovery metadata stays private to the settled `tool/result`; it does not change tool schemas, model messages, truthful top-level tool identity, `edit_run_code`, or recorded-value replay. Adding a private protocol message still requires a kernel handler and contract tests because the client does not interpret semantic payloads. Shared policy modules become load-bearing single sources and must remain usable in both the main thread and worker without importing session state.
@@ -0,0 +1,25 @@
1
+ # Keep The Agent Contract Project-Specific
2
+
3
+ ## Problem
4
+
5
+ PTC Plus needs a tracked instruction surface for product boundaries, source ownership, verification, and release safety. Generic reasoning methods, editorial playbooks, editor settings, and private branch conventions have different owners and lifetimes. Publishing them as mandatory project policy enlarges the contributor contract and makes local maintainer workflow appear necessary for product correctness.
6
+
7
+ ## Decision
8
+
9
+ `AGENTS.md` contains only obligations whose subject is PTC Plus: DSH authority, runtime and journal invariants, public source ownership, the checkout-local review ledger, deterministic verification, documentation parity, generated-material exclusions, and delivery safety. It links the canonical product and architecture documents rather than duplicating their detailed behavior.
10
+
11
+ Reusable maintenance methods remain outside tracked project sources. Local editor state and branch topology also remain checkout-owned. They may guide an individual maintainer, but project code, tests, hooks, and acceptance do not require their names, installation, or presence.
12
+
13
+ The tracked ledger template and lifecycle script remain because they enforce a repository-specific commit invariant: unresolved findings cannot be committed, and a verified source tree must match the prospective Git tree. The tracked hook is limited to that invariant and is installed explicitly.
14
+
15
+ ## Alternatives considered
16
+
17
+ **Publish the complete maintainer environment.** This makes one checkout reproducible but exposes private workflow as public policy and couples contributors to tools unrelated to PTC Plus behavior.
18
+
19
+ **Remove the project contract entirely.** `CONTEXT.md` owns product semantics, but it does not own source routing, ledger lifecycle, verification commands, bilingual documentation, or generated-material exclusions. Those repository-specific obligations still need one concise entry point.
20
+
21
+ **Keep generic methods as optional tracked references.** Optional wording does not change their public ownership or maintenance cost. Methods without a PTC Plus-specific executable consequence stay outside the repository.
22
+
23
+ ## Consequences
24
+
25
+ Contributors can determine the obligations needed to preserve PTC Plus without adopting a maintainer's agent stack, editor, model route, or branch workflow. Product semantics retain their canonical owners, while the public operating contract remains small enough to audit for contradictions and private-environment leakage.
@@ -0,0 +1,39 @@
1
+ # Bound The Expensive Acceptance Default
2
+
3
+ ## Problem
4
+
5
+ The model-backed acceptance runner starts one independent DSH session per scenario. Each session repeats the full system prompt and model route, so adding a deterministic edge-case scenario increases token use even when the same behavior already has exhaustive keyless coverage. The default run must retain representative model-facing workflows without making every deterministic rewrite case a quota-consuming session.
6
+
7
+ ## Decision
8
+
9
+ Each descriptor in `scripts/expensive-acceptance-scenarios.json` owns whether it belongs to the default run. The five defaults are rejected-cell editing, durable binding continuity, the typed program surface, direct Node volatile continuity, and static import conversion. The mixed redeclaration scenario remains selectable with `DSH_PTC_ACCEPTANCE_SCENARIOS=partial-redefinition-split`. The executable selection derives from those descriptor flags, so the manifest and runner cannot maintain a second default list.
10
+
11
+ Every expensive scenario and A/B task declares machine budgets for model requests, direct calls, source characters, repeated source, result characters, assistant characters, token traffic, and aggregate runtime-context characters. A model request is one logical loop step recorded by durable `step/start`; `request/header` records an initial, resume, or changed header epoch and is counted separately. Neither metric claims to count provider adapter attempts, because retries can make multiple attempts within one logical step without a distinct durable event. The shared acceptance contract treats an exceeded budget as a failure. It also attributes every aggregate runtime snapshot by named section, enforces allowed PTC Plus contribution names and bounds, and verifies required append, update, and clearance transitions. Edit-specific narration is never allowed.
12
+
13
+ For a trajectory whose configuration, model route, and native capability view remain stable, the shared acceptance contract canonicalizes every `request/header` by DSH rules and compares exact system text and ordered complete tool schemas. An initial epoch establishes the baseline, an equal resume epoch reaffirms it, and every unapproved change epoch fails acceptance. A scenario that changes route, configuration, or capability declares the exact condition and epoch in `headerPolicy.allowedTransitions`; a scenario that replaces history declares the exact `headerPolicy.historyReplacements` count without relaxing header equality. Fingerprints are report metadata only; acceptance compares the canonical values and reports the first differing field.
14
+
15
+ The edit canary covers both a completed cell whose result needs adjustment and a runtime-rejected cell in one session. It requires the model-visible direct sequence `[run_code, edit_run_code, run_code, edit_run_code]`, each exact delta, private target and derived-run metadata, and no model-authored retransmission of either materialized source. Successful static-import and mixed-redeclaration rewrites retain result provenance without creating runtime-context transitions. The typed program-surface task requires focused discovery, schema inspection, native reading, and isolated execution in that order while rejecting unrelated capability expansion.
16
+
17
+ Both model-backed runners complete keyless verification and resolved-config validation before model invocation. They then run one complete representative canary sequentially and audit its process result, session log, tool transport, runtime contexts, budgets, and task oracle before scheduling broader scenarios, replicates, or concurrency. A canary failure stops the run without starting the remaining paid work. Deterministic tests remain the authority for AST rewrite variants and the acceptance contract itself. The ordinary-task A/B canary is the stable fixture's fixed, cheap, machine-checkable transport task rather than a full repository test run; see ADR 0018.
18
+
19
+ Machine acceptance and stochastic comparison are separate from blind semantic review. A/B output reports machine pass or failure independently and keeps overall approval pending until the blind packets have been evaluated; producing packets is not evidence that their review is complete.
20
+
21
+ ## Alternatives considered
22
+
23
+ **Run every scenario by default.** This maximizes model-sampled breadth but pays a complete prompt and session cost for behavior already covered deterministically. It is retained as an explicit scenario selection rather than the quota-consuming default.
24
+
25
+ **Delete the mixed redeclaration scenario.** This would lower maintenance and token use, but it would remove a useful model-facing probe. Keeping it selectable preserves that evidence without imposing its cost on every run.
26
+
27
+ **Shorten only the task prose.** This saves a small number of input tokens but does not remove the repeated per-session prompt and model setup cost. Scenario selection provides the larger reduction while preserving the important workflows; the typed-program task keeps semantic wording so the model-visible discovery contract remains under test.
28
+
29
+ **Run scenarios concurrently from the start.** This lowers elapsed time on a healthy build but spends quota after the first representative trajectory could already prove the transport or context contract invalid. A sequential canary provides the stopping boundary; concurrency remains available after it passes.
30
+
31
+ **Keep efficiency fields observational.** Reports would retain useful numbers, but a green outcome could coexist with repeated source, unbounded context, or excessive output. Explicit scenario budgets turn product value into acceptance without treating stochastic arm-to-arm deltas as deterministic requirements.
32
+
33
+ **Use header epochs as model-request counts and compare only header fingerprints or tool names.** A header epoch represents reconstructable request configuration, not each logical model step, so it can both undercount request traffic and misclassify a prefix change as traffic. Reduced comparisons can also admit schema-field or ordering changes and cannot identify the differing canonical field. Logical steps and complete headers remain separate evidence.
34
+
35
+ **Budget physical provider attempts from usage or assistant messages.** Usage may be absent on failed attempts, and retries may issue several adapter calls within one logical step. A physical-attempt budget requires a distinct durable upstream event rather than an inference from incomplete downstream evidence.
36
+
37
+ ## Consequences
38
+
39
+ The default run spends one canary session before it can expand. A valid build then covers the five representative workflows; an invalid build stops at the first complete discriminator. Full or focused extension runs select additional scenario ids explicitly. Reports can establish machine acceptance and comparative measurements, while blind approval remains an independently completed artifact.
@@ -0,0 +1,26 @@
1
+ # Add Fatigue-Aware Recovery Tips
2
+
3
+ ## Problem
4
+
5
+ The persistent REPL contract must stay short enough for every request, while some failures need a model-visible recovery affordance. Repeated binding failures and platform-specific command errors benefit from a precise next action. A real `edit_run_code` call already carries its identity, arguments, and result; restating those facts in a runtime context wastes tokens and causes DSH to append a new aggregate snapshot containing unrelated contributions. Long failed cells are a separate trigger: after the failure is persisted, a bounded context may distinguish a journal-proven no-execution edit candidate from a possibly effectful failure.
6
+
7
+ ## Decision
8
+
9
+ PTC Plus keeps the stable REPL guidance limited to invariants, discovery entry points, and environment-neutral boundaries. `internal/session-log-view.js` scans the event sequence once, pairs settled calls within each open turn, validates execution journals and rewrite records once, and returns immutable facts for edit targeting, successful-run reset, context distance, and canonical DSH system-prompt snapshots. Each snapshot preserves its named sections. `internal/runtime-contexts.js` renders rewrite feedback only when the rewritten cell failed or lacks a valid journal, plus at most one context from the reserved `tools:ptc-plus-tip/<trigger>/<ordinal>` name family. Successful transparent rewrites remain result metadata and do not create aggregate snapshot churn. It never emits edit provenance or target-lifecycle context: those facts belong to the registered tool call/result and its private derived-execution metadata. Tips never change the tool list, tool schemas, or system sections.
10
+
11
+ The default triggers are a repeated binding failure and a failure whose diagnostic or structured cause identifies an executable, shell, or path problem. The first tip is concise. A matching trigger may produce another tip only after `tipCooldownMessages` model-context steps; unresolved matching tips reach the detailed form after `tipEscalationFailures` occurrences. A successful cell resets the unresolved escalation count. Every emitted tip name carries its stable trigger identity and next per-trigger ordinal. Reconstruction accepts only canonical snapshots from DSH's system-prompt owner, treats a named section as one effective-state transition even when another owner's aggregate update repeats it, and ignores legacy or malformed plugin prose. Visible wording does not determine cooldown or escalation identity.
12
+
13
+ Platform wording names execution-world differences without assuming Windows, WSL, POSIX, a shell, or package-runner availability. `edit_run_code` remains independently available as a fixed real tool and does not depend on a recovery-tip trigger.
14
+
15
+ ## Alternatives considered
16
+
17
+ - **Keep all recovery instructions in the stable system prompt.** This makes every request pay for rare failures and makes platform-specific text look universally applicable.
18
+ - **Emit a tip after every matching failure.** This repeats stale advice, consumes context, and can encourage blind retry loops.
19
+ - **Store mutable tip counters only in the kernel.** A worker restart or replay would then change model-visible behavior without a session-log source.
20
+ - **Infer tip identity from rendered prose or one fixed section name.** Wording changes would reset history, unrelated plugin text could create false matches, and an unchanged aggregate context would not persist repeated occurrences.
21
+ - **Change the available tools when a tip is needed.** This would break the fixed code-only direct-tool prefix and make failure state observable through schema churn.
22
+ - **Let each runtime-context renderer scan the event log.** Independent scans duplicate parsing and can disagree about call/result pairing, turn boundaries, repair consumption, or successful-run resets. A shared projection keeps event interpretation separate from presentation policy.
23
+
24
+ ## Consequences
25
+
26
+ Normal requests receive only the persistent REPL invariants in stable system text. Prompt assembly performs one linear event projection regardless of how many PTC runtime contexts consume it; renderers receive facts rather than raw history or search callbacks. Failure recovery becomes an append-only, bounded context contribution with explicit cooldown and escalation semantics. Changing a tip's name records a new effective context even when compact wording repeats, while aggregate snapshots that repeat the same named section do not advance fatigue state. Tip wording and thresholds are configurable, but their defaults remain conservative; disabling tips removes only the runtime context and does not remove `edit_run_code` or any native capability.
@@ -0,0 +1,20 @@
1
+ # Isolate the Local Recovery Tip Provider
2
+
3
+ ## Problem
4
+
5
+ Recovery tips are a presentation policy, while PTC Plus owns the REPL, diagnostics, journal, and prompt-prefix contract. A future decision-table plugin may need session, project, model, vendor, platform, environment, tool, and output facts, but its public facts and decision interface is not yet stable.
6
+
7
+ ## Decision
8
+
9
+ Keep the current deterministic recovery policy in `internal/recovery-tips.js` as a replaceable local provider. The provider consumes the immutable facts produced by `internal/session-log-view.js`; it does not traverse raw events, pair calls, parse journals, or query the core through callbacks. The core calls it only for a bounded named runtime context, and the provider does not change execution, journal semantics, tool schemas, or stable system text. Defer an external adapter until a public facts and decision contract exists; the adapter must be optional, fail closed, and leave the local provider or disabled mode available when absent.
10
+
11
+ ## Alternatives considered
12
+
13
+ - **Embed a multi-scope decision table in PTC Plus now.** This would couple the REPL core to unowned project, model, vendor, and global policy semantics before their data and precedence contracts are defined.
14
+ - **Guess the external plugin API.** This would create an unstable cross-plugin dependency and make prompt behavior depend on an interface that cannot yet be verified.
15
+ - **Remove recovery tips until the external plugin is ready.** This would discard a bounded, tested recovery aid even though the core can provide it without changing its execution contract.
16
+ - **Pass the complete session log to each provider.** This would expose persistence mechanics as a policy API and let providers derive incompatible lifecycle facts. A projected fact model keeps event ownership in the REPL core and makes a future adapter narrower.
17
+
18
+ ## Consequences
19
+
20
+ The local provider remains useful and independently testable, while the core has a clear replacement boundary for a future decision plugin. Until that plugin publishes a stable interface, integration remains an explicit TODO rather than an implicit dependency. Any future provider must consume the same projected lifecycle facts and preserve session-log reconstruction, bounded context injection, fatigue control, and stable tool/system prefixes.
@@ -0,0 +1,23 @@
1
+ # Preserve Mixed Destructuring Initialization
2
+
3
+ ## Problem
4
+
5
+ Loose cross-cell redeclarations must assign names that already exist while introducing fresh names from one top-level destructuring declarator. A text-level whole-pattern assignment can preserve simple values, but it changes JavaScript binding initialization when a default expression refers to an earlier fresh binding, and a generated helper can also change top-level `await` or initializer ordering.
6
+
7
+ ## Decision
8
+
9
+ `internal/repl-convenience.js` evaluates the initializer as a helper argument, then runs the original destructuring pattern unchanged inside the helper. This leaves initializer references outside the new pattern scope while the JavaScript engine owns binding order, TDZ, computed keys, defaults, iterator handling, lexical shadowing, and abrupt completion inside the pattern. After the complete pattern succeeds, the helper passes every bound value positionally to a commit callback: existing names are assigned to their session bindings and fresh values are returned to one outer declaration. Arrays carry the positional values so names such as `__proto__` have no property-key semantics. A pattern containing direct `await` uses an async helper; a synchronous pattern stays synchronous. Declarator replacements remain in source order, and both lexical and `var` mixed declarations use the same native-pattern model with their corresponding outer declaration kind.
10
+
11
+ ## Alternatives considered
12
+
13
+ **Rename existing bindings inside the pattern.** This can produce the required assignments for simple patterns, but correctly rewriting references requires a complete JavaScript scope transform. Defaults with nested lexical declarations and computed keys expose the gap, so a local reference walker is not a sound compiler boundary.
14
+
15
+ **Keep one whole-pattern assignment for every declaration.** This is shorter, but fresh names require temporaries and the assignment form does not reproduce lexical initialization or TDZ.
16
+
17
+ **Manually lower every object and array pattern.** This could assign existing names at each binding step, but it would duplicate iterator, getter, rest, computed-key, default, and abrupt-completion semantics already implemented by JavaScript, creating a second destructuring engine.
18
+
19
+ **Reject all mixed patterns.** Rejection is fail-safe, but it abandons the core REPL promise that ordinary TypeScript destructuring can continue across cells and provides no value for the common, semantically representable case.
20
+
21
+ ## Consequences
22
+
23
+ Native destructuring remains the semantic authority instead of a plugin-owned scope or pattern compiler. Existing bindings are committed only after the complete pattern succeeds; fresh lexical names keep their TDZ while the initializer is evaluated. Focused tests cover declarator order, TDZ, same-pattern defaults, nested lexical shadowing, computed keys, `__proto__`, existing-binding initializer scope, `var`, and awaited patterns.
@@ -0,0 +1,21 @@
1
+ # Isolate REPL Redeclaration Convenience
2
+
3
+ ## Problem
4
+
5
+ The persistent REPL uses one worker lexical environment so later cells can refer to earlier top-level bindings. That convenience is not JavaScript's module or function semantics, and mixed old/new destructuring requires a policy for combining declaration and assignment. Keeping that policy inside `internal/cell-analysis.js` makes core parsing and durability analysis depend on one replaceable persistence strategy.
6
+
7
+ ## Decision
8
+
9
+ `internal/cell-analysis.js` owns cell parsing, binding inventory, durability classification, return rewriting, and execution preparation. Shared binding-pattern traversal lives in `internal/binding-pattern.js`. `internal/repl-convenience.js` owns the optional loose-redeclaration policy, including all-existing replacement and mixed destructuring lowering; it returns executable source, collisions, redeclaration metadata, and rewrite provenance through one narrow function. The current persistent REPL continues to enable this policy, so the user-facing convenience and journal behavior remain unchanged. A future state/frame persistence implementation can replace this policy without changing the cell language analyzer.
10
+
11
+ ## Alternatives considered
12
+
13
+ **Keep redeclaration lowering in cell analysis.** This keeps calls local, but couples the language analyzer to one REPL implementation and makes a future independent cell scope harder to introduce or verify.
14
+
15
+ **Delete loose redeclaration convenience.** This would give the smallest semantic surface, but repeated top-level declarations are an intentional PTC Plus workflow and removing them would make ordinary iterative model use less effective.
16
+
17
+ **Create a full compiler pipeline immediately.** A general destructuring compiler could cover more syntax, but it would add a larger dependency and semantic surface before the persistence boundary is settled. Isolating the existing policy leaves that decision reversible.
18
+
19
+ ## Consequences
20
+
21
+ The core analyzer has no mixed-redeclaration lowering helpers and can be tested independently of the REPL convenience policy. The compatibility module remains responsible for its custom hybrid semantics and its focused tests; it must not be described as native JavaScript semantics. Future persistence work can introduce fresh cell lexical frames while retaining or replacing the convenience adapter at one explicit boundary.
@@ -0,0 +1,25 @@
1
+ # Persist Cell Rewrite Policy
2
+
3
+ ## Problem
4
+
5
+ Durable replay prepares the original cell source again. AST module and REPL convenience rewrites are configuration-controlled, so using the current profile during replay can reject a cell that was durable when it was recorded. Replay would then discard recoverable bindings even though the journal still proves the cell's calls and completion.
6
+
7
+ ## Decision
8
+
9
+ Journal version 3 records `rewritePolicy` beside `bindingMode` with the three boolean rewrite settings used to prepare the cell. Live journals capture the active profile; replay passes each node's recorded policy to `prepareProgram`. The policy is part of journal normalization and equality, is frozen with the journal, and is required by the closed schema. A profile change therefore affects only new cells; existing durable nodes retain the language policy under which they were evaluated.
10
+
11
+ The journal decoder also owns the predecessor adapters. Version 1 has its original closed field set and migrates with all three later rewrite switches disabled, matching the language pipeline that emitted it; `bindingMode` continues to preserve its top-level redeclaration policy. Its string call-ID confirmations become version 3 event sequences only when the enclosing session log contains exactly one earlier unjournaled `run_code` call with that identity. Missing or repeated candidates are ambiguous and fail closed. Version 2 already contains `rewritePolicy` and normalizes to version 3 only when its confirmed no-op list is absent or empty; version 2 call-ID confirmations remain unsupported.
12
+
13
+ ## Alternatives considered
14
+
15
+ **Persist transformed source.** This would avoid rerunning the rewrite pass, but it duplicates generated code in the session log, makes source provenance less direct, and couples the durable protocol to an analyzer intermediate representation.
16
+
17
+ **Reject recovery when rewrite settings differ.** This preserves semantic caution, but throws away replayable history even when the original source and recorded policy are sufficient to reconstruct it.
18
+
19
+ **Read the current profile during replay.** This keeps the journal small, but makes durable recovery depend on mutable deployment configuration and causes valid historical cells to become unrecoverable after a configuration change.
20
+
21
+ **Accept every version 2 confirmation by call ID.** Preserving those values would require replay to guess which event a reused provider call ID denotes. Only the empty-confirmation subset has an identity-independent meaning, so broader migration would weaken the version 3 event-sequence contract owned by [ADR 0007](0007-separate-worker-transport-from-session-semantics.md).
22
+
23
+ ## Consequences
24
+
25
+ The current journal schema is version 3. Replay remains source-based and deterministic while configuration changes no longer invalidate durable cells. Version 1 histories retain the predecessor language behavior and migrate only uniquely identified confirmations; version 2 histories without call-ID confirmations retain their recorded rewrite policy. Ambiguous identity remains unsupported recovery input rather than receiving guessed defaults. New profile settings apply at the next live cell boundary.
@@ -0,0 +1,25 @@
1
+ # Preserve Source Positions Through Rewrites
2
+
3
+ ## Problem
4
+
5
+ PTC cells are prepared by several source-to-source passes before they reach the worker: module syntax lowering, REPL redeclaration lowering, and return control flow lowering. A generated statement can therefore be longer than the original statement. Reporting the worker's raw line and column against the original cell then points past the source or at the wrong token, especially when rewritten syntax and the failing expression share a line.
6
+
7
+ ## Decision
8
+
9
+ Each source edit carries a compact segment map from generated text back to the original cell. Unchanged text retains exact offsets; generated module bindings and source literals carry token-level anchors to their originating AST nodes, while other introduced text maps to the replaced source span. [`internal/source-position-map.js`](../../internal/source-position-map.js) validates and sorts one pass of non-overlapping edits, then assembles source and composed segments in one traversal. Binary segment lookup handles repeated or non-monotonic explicit token anchors without rescanning the complete map. Maps compose through every preparation pass. Mixed-destructuring generation emits copied pattern, initializer, and binding ranges through the same mapped representation instead of one opaque replacement string. [`internal/cell-analysis.js`](../../internal/cell-analysis.js) converts rewrite-time preflight and collision spans to original-cell coordinates before returning a prepared program; runtime exception positions use the same map after worker execution.
10
+
11
+ ## Alternatives considered
12
+
13
+ **Report generated coordinates.** This is simple, but the displayed source is the original cell, so the diagnostic is not actionable when a rewrite changes a line's width.
14
+
15
+ **Generate and persist full source maps.** A standard source-map artifact would support richer tooling, but it adds serialization and lifecycle surface to a diagnostic-only concern whose required mapping is local and internal.
16
+
17
+ **Pad every rewrite to the original width.** Padding can preserve some columns, but it cannot represent generated statements that need more space and would distort execution source or require unrelated formatting constraints.
18
+
19
+ **Apply edits from right to left and rebuild the map after each edit.** Reverse application avoids offset adjustment, but repeats whole-source concatenation and whole-map traversal for every edit on the host thread. A batch owns the shared input coordinate system already, so one validated assembly is both simpler and bounded by the actual input and output artifacts.
20
+
21
+ **Represent a generated helper as one proportional span.** This keeps generation call sites short but loses the identity of copied expressions and identifiers. A mapped builder makes copied source explicit at generation time and prevents later diagnostic code from reconstructing provenance heuristically.
22
+
23
+ ## Consequences
24
+
25
+ Parse, preflight, collision, and runtime exception diagnostics all reach rendering in original-cell coordinates while the worker still executes the direct generated program. Mapping storage grows with rewrite segments and semantic token anchors rather than cell length; batch assembly avoids per-edit copies of both artifacts. The mapping is not part of journal metadata or model-visible prompt text, so replay and prompt-prefix stability are unchanged. Copied helper expressions retain exact source positions, while syntax that has no original token remains anchored to the rewritten declaration.
@@ -0,0 +1,43 @@
1
+ # Archive Resolved Review Findings
2
+
3
+ ## Problem
4
+
5
+ An ignored root `REVIEW_FINDINGS.md` prevents unresolved review findings from being committed only when every contributor remembers its informal format and checks it manually. Deleting a resolved ledger removes useful local evidence, while keeping it at the root makes later work indistinguishable from an active review. Tests and commit hooks also cannot enforce one lifecycle if each recognizes a different prose convention.
6
+
7
+ ## Decision
8
+
9
+ `.agents/templates/REVIEW_FINDINGS.md` is the tracked schema owner for local review ledgers. Schema `dsh-review-findings/v3` separates each finding's stable required outcome, current implementation plan, accumulated resolution evidence, and optional accepted-disposition owner. A finding can be captured before diagnosis produces a plan; once a concrete correction is selected, the plan is recorded before implementation and kept synchronized when evidence changes the diagnosis, ownership, affected artifacts, or verification path. `resolutionEvidence` records completed facts rather than a chronological work transcript. A finding marked `resolved` retains its implemented plan. A finding marked `accepted` names a tracked document or HTTPS issue in `dispositionRef`; the local ledger cannot be the only copy of an intentionally retained limitation.
10
+
11
+ Review output and ledger mutation follow task authority. A read-only review reports records in the ledger's semantic fields without writing checkout state. Every implementation preserves an active ledger, but an ordinary change with no finding does not create one. `npm run review:new` creates the ignored root ledger only when a task authorizes capture or resolution of at least one finding; implementation imports those findings before editing their fixes. Archives move under Git's local `review-findings/archive` metadata path, so they cannot enter a commit and remain associated with the checkout.
12
+
13
+ Ledger validation, readiness, deterministic verification, archive, and commit proof are distinct states with one implementation. `npm run review:validate` checks schema and tracked-file safety while accepting a valid open ledger. `npm run review:gate` is read-only: it blocks an open ledger, validates durable owners for accepted findings, and never archives. `npm run verify` performs schema validation before syntax and coverage checks, so open-ledger work can collect evidence. `npm run check` applies the readiness gate first, then materializes the current tracked and non-ignored checkout through Git's clean and index semantics in a temporary Git administrative directory. Git selects that directory's default index through `--git-dir` and reads the target checkout through `--work-tree`; it never receives an alternate-index environment path that could fall back to the real repository index. The temporary object database reads the repository's objects through a relative alternates entry and stores new fingerprint objects locally. The resulting tree object ID includes canonical blobs, symlink identity, and file modes while remaining independent of staged content. Sparse checkouts retain skip-worktree paths from the `HEAD` baseline and apply clean conversion only to materialized paths, so an absent out-of-cone file is neither lost nor mistaken for a deletion. Every path discovered by Git is reapplied through NUL-delimited literal input. The command separately snapshots the ignored ledger, runs `verify`, rejects changed source or ledger state, and refreshes the proof before archiving a resolved ledger. Repository scripts that launch npm as a child use one command resolver: an invoking npm process supplies its CLI path and Node executable, while direct script invocation falls back to the platform npm executable. A successful later check without an active ledger also refreshes an older proof. The pre-commit hook rejects open and unverified resolved ledgers, then asks Git for the prospective index tree object ID and requires it to equal the verified source tree. Staging the complete verified tree permits repeated commit attempts; partial staging, additional staged content, mode-only changes, or later checkout changes require reconciliation or another checked workflow. An advanced HEAD proves that the checked commit completed and retires the old proof.
14
+
15
+ Hook installation is explicit. `npm run hooks:install` selects the tracked hook path only when the effective `core.hooksPath` is unset or already `.githooks`. Before the first switch from Git's default hooks directory, installation rejects any non-sample hook file there; it also rejects an incompatible configured path instead of disabling another hook owner. Dependency installation has no Git-configuration side effect.
16
+
17
+ ## Alternatives considered
18
+
19
+ **Keep deleting resolved ledgers.** This preserves a clean root but discards useful local review history and leaves unresolved-commit prevention dependent on agent memory.
20
+
21
+ **Archive under an ignored worktree directory.** This is visible, but it requires another ignore rule and can still be force-added. Git's local metadata directory makes the non-commit property structural.
22
+
23
+ **Let the hook parse headings and status bullets independently.** This avoids YAML frontmatter but duplicates an informal Markdown parser across automation. A structured frontmatter schema keeps the human-readable file while giving tests and hooks one parser and one versioned shape.
24
+
25
+ **Use `requiredOutcome` as both acceptance condition and implementation plan.** This keeps one fewer field, but a changed implementation strategy would rewrite the definition of success and erase the distinction between what must hold and how the current work intends to make it hold. Separate fields keep the acceptance target stable while allowing the plan to evolve with evidence.
26
+
27
+ **Store a chronological implementation diary.** A detailed log preserves every intermediate action but turns a commit guard into a reasoning transcript and becomes expensive to keep accurate. The ledger retains the current plan and factual evidence; durable architectural rationale remains in the owning ADR.
28
+
29
+ **Install the hook whenever `core.hooksPath` is unset.** An unset configuration may still have active hooks under Git's default hooks directory. Switching paths would leave those files intact but stop invoking them, so installation checks both configured and default ownership and requires maintainers to compose competing hook sets explicitly.
30
+
31
+ **Block deterministic verification while a finding is open.** This prevents an unresolved change from appearing ready, but it also prevents the prescribed evidence from being collected before resolution. It loses because validation can accept a well-formed work-in-progress ledger while the later readiness and commit gates still reject it.
32
+
33
+ **Trust terminal ledger text as verification proof.** This keeps the gate simple, but a manually edited status cannot prove that deterministic verification ran or that it covered the same tree being committed. It loses because paired source and index fingerprints bind archive and commit readiness to both the observed verification run and the prospective commit.
34
+
35
+ **Keep accepted limitations only in the local archive.** This preserves review history without tracked documentation, but the decision disappears across clones while the limitation survives. It loses because acceptance is a durable product or engineering decision and therefore needs a durable owner.
36
+
37
+ **Install hooks from the package lifecycle.** This configures contributors automatically, but dependency installation then mutates Git governance or fails because of an unrelated hook owner. It loses because explicit setup preserves authority and keeps verification preparation free of configuration side effects.
38
+
39
+ **Choose `npm` or `npm.cmd` only from `process.platform`.** This works when the child process inherits a platform-native PATH, but fails when a Windows Node executable is reached through a WSL command shim and `npm.cmd` is not visible to Windows process lookup. Reusing npm's own CLI identity preserves the actual toolchain selection; the platform name remains only the fallback for direct script invocation.
40
+
41
+ ## Consequences
42
+
43
+ Review findings have an explicit `reported -> authorized capture -> planned -> implemented and verified -> proof and archive -> matching commit -> proof retirement` lifecycle while retaining `open` and `resolved` as file-level states. An open ledger uses `verify` for deterministic evidence collection and fails `check` before its full gate runs; it also blocks commits. A resolved ledger remains active until verification succeeds against an unchanged source fingerprint, and commit readiness additionally requires the prospective index to match that fingerprint. The archive is intentionally local, while accepted limitations and durable architecture remain in tracked owners. Hook installation is an explicit checkout setup decision rather than a dependency-install side effect.