dsh-ptc-plus 0.3.2 → 0.4.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 (199) hide show
  1. package/README.en.md +65 -78
  2. package/README.md +65 -78
  3. package/assets/ptc-plus-bindings-en.png +0 -0
  4. package/assets/ptc-plus-bindings-zh.png +0 -0
  5. package/assets/ptc-plus-repair-en.png +0 -0
  6. package/assets/ptc-plus-repair-zh.png +0 -0
  7. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  8. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  9. package/assets/ptc-plus-settings-en.png +0 -0
  10. package/assets/ptc-plus-settings-zh.png +0 -0
  11. package/client.js +30596 -742
  12. package/compiler-amaro.cjs +5 -0
  13. package/compiler-amaro.cjs.map +6 -0
  14. package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
  15. package/compiler-assets.json +3 -0
  16. package/compiler-core.cjs +554 -0
  17. package/compiler-core.cjs.map +6 -0
  18. package/compiler-platform.cjs +2 -0
  19. package/compiler-platform.cjs.map +6 -0
  20. package/compiler-typescript.cjs +388 -0
  21. package/compiler-typescript.cjs.map +6 -0
  22. package/docs/README.md +6 -0
  23. package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
  24. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +5 -3
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
  26. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
  27. package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
  28. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
  29. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
  30. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +3 -1
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
  32. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
  33. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  34. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
  35. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
  36. package/docs/adr/0019-plugin-settings-and-kill-switch.md +5 -3
  37. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  38. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
  39. package/docs/adr/0023-global-user-bindings.md +104 -0
  40. package/docs/adr/0024-repl-console-observation.md +33 -0
  41. package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
  42. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
  43. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
  44. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
  45. package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
  46. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
  47. package/docs/architecture.md +73 -21
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +95 -19
  51. package/docs/durability-design.md +25 -10
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/publishing.md +5 -3
  55. package/docs/runtime-reference.md +72 -25
  56. package/docs/semantic-validation.md +29 -0
  57. package/docs/stateful-binding-updates-proposal.md +755 -0
  58. package/docs/user-bindings.en.md +64 -0
  59. package/docs/user-bindings.md +64 -0
  60. package/docs/value-wire.md +3 -3
  61. package/docs/verification.md +57 -0
  62. package/index.js +369 -92
  63. package/internal/ast-traversal.js +5 -2
  64. package/internal/binding-descriptors.js +1 -0
  65. package/internal/binding-pattern-traversal.js +25 -0
  66. package/internal/binding-pattern.js +26 -29
  67. package/internal/binding-source-analysis.js +391 -0
  68. package/internal/binding-update-policy.js +66 -0
  69. package/internal/bounded-worker-round.js +56 -0
  70. package/internal/callable-definition-environment.js +42 -0
  71. package/internal/callable-source-catalog.js +129 -0
  72. package/internal/callable-source-encoding.js +47 -0
  73. package/internal/callable-source-facts.js +459 -0
  74. package/internal/callable-source-format.js +2 -0
  75. package/internal/callable-source-lowering.js +123 -0
  76. package/internal/cell-analysis-contract.js +25 -0
  77. package/internal/cell-analysis.js +240 -290
  78. package/internal/cell-completion.js +15 -0
  79. package/internal/cell-error.js +7 -0
  80. package/internal/cell-parser.js +66 -0
  81. package/internal/cell-rewriter.js +3 -677
  82. package/internal/commonjs-export-evidence.js +63 -0
  83. package/internal/commonjs-source-evidence.js +72 -0
  84. package/internal/compiler-amaro.cjs +2 -0
  85. package/internal/compiler-compression.js +12 -0
  86. package/internal/compiler-data.js +58 -0
  87. package/internal/compiler-descriptors.js +54 -0
  88. package/internal/compiler-dynamic-cache.js +48 -0
  89. package/internal/compiler-entry.js +35 -0
  90. package/internal/compiler-generated-names.js +17 -0
  91. package/internal/compiler-intrinsics.js +84 -0
  92. package/internal/compiler-module-links.js +34 -0
  93. package/internal/compiler-operations.js +61 -0
  94. package/internal/compiler-platform-factory.js +84 -0
  95. package/internal/compiler-platform.js +136 -0
  96. package/internal/compiler-record-roles.js +53 -0
  97. package/internal/compiler-region-output.js +259 -0
  98. package/internal/compiler-resource-lowering.js +103 -0
  99. package/internal/compiler-scope-facts.js +115 -0
  100. package/internal/compiler-service.js +109 -0
  101. package/internal/compiler-source-cache.js +32 -0
  102. package/internal/compiler-source-pieces.js +93 -0
  103. package/internal/compiler-source-regions.js +132 -0
  104. package/internal/compiler-storage-source.js +54 -0
  105. package/internal/compiler-text.js +15 -0
  106. package/internal/compiler-typescript.cjs +2 -0
  107. package/internal/config-spec.js +83 -27
  108. package/internal/cordis-tools-owner.js +130 -21
  109. package/internal/diagnostic.js +27 -1
  110. package/internal/direct-surface-owner.js +76 -23
  111. package/internal/dynamic-binding-evidence.js +16 -0
  112. package/internal/dynamic-environment-compiler.js +779 -0
  113. package/internal/dynamic-environment-integration.js +87 -0
  114. package/internal/dynamic-environment-runtime.js +362 -0
  115. package/internal/dynamic-native-calls.js +150 -0
  116. package/internal/dynamic-native-runtime.js +248 -0
  117. package/internal/dynamic-scope-analysis.js +90 -0
  118. package/internal/edit-transport-owner.js +27 -2
  119. package/internal/execution-seam-compat.js +193 -0
  120. package/internal/failure-reporting.js +118 -18
  121. package/internal/host-rpc.js +66 -0
  122. package/internal/isolated-worker.js +548 -0
  123. package/internal/kernel-child.js +139 -0
  124. package/internal/kernel-worker.js +904 -120
  125. package/internal/language-semantics.js +14 -0
  126. package/internal/legacy-binding-catalog.js +48 -0
  127. package/internal/legacy-cell-analysis.js +600 -0
  128. package/internal/legacy-cell-parser.js +50 -0
  129. package/internal/legacy-cell-rewriter.js +631 -0
  130. package/internal/legacy-repl-convenience.js +291 -0
  131. package/internal/managed-module-operations.js +170 -0
  132. package/internal/module-compilation.js +321 -0
  133. package/internal/module-runtime-platform.js +19 -0
  134. package/internal/module-transform-contract.js +12 -0
  135. package/internal/native-await-declarations.js +68 -0
  136. package/internal/native-root-compilation.js +54 -0
  137. package/internal/native-root-dynamic.js +67 -0
  138. package/internal/recovery-tips.js +27 -15
  139. package/internal/rejected-cell-editor.js +13 -8
  140. package/internal/repl-convenience.js +2 -289
  141. package/internal/repl-memory-projection.js +81 -12
  142. package/internal/repl-observation-interest.js +70 -0
  143. package/internal/repl-rewrite-contract.js +6 -0
  144. package/internal/repl-scope-normalizer.js +2091 -0
  145. package/internal/repl-value-observer.js +114 -0
  146. package/internal/rpc-contract.js +52 -0
  147. package/internal/runtime-bridge-owner.js +172 -28
  148. package/internal/runtime-config.js +48 -52
  149. package/internal/runtime-contexts.js +82 -10
  150. package/internal/runtime-messages.js +111 -0
  151. package/internal/session-cell-executor.js +401 -87
  152. package/internal/session-journal-recovery.js +684 -0
  153. package/internal/session-journal-schema.js +106 -0
  154. package/internal/session-journal.js +184 -654
  155. package/internal/session-log-view.js +42 -9
  156. package/internal/session-runtime.js +256 -95
  157. package/internal/session-state.js +247 -71
  158. package/internal/source-position-map.js +211 -71
  159. package/internal/stateful-module-compiler.js +154 -0
  160. package/internal/stateful-module-runtime.js +303 -0
  161. package/internal/stateful-root-compiler.js +598 -0
  162. package/internal/stateful-root-runtime.js +340 -0
  163. package/internal/typert-codec-compat.js +28 -0
  164. package/internal/typescript-transform.js +29 -0
  165. package/internal/user-binding-authoring.js +70 -0
  166. package/internal/user-binding-console-worker.js +184 -0
  167. package/internal/user-binding-console.js +148 -0
  168. package/internal/user-binding-draft-projection.js +335 -0
  169. package/internal/user-binding-model-context.js +31 -0
  170. package/internal/user-binding-runner.js +56 -0
  171. package/internal/user-bindings-owner.js +1065 -0
  172. package/internal/user-bindings-store.js +293 -0
  173. package/internal/user-bindings.js +353 -0
  174. package/internal/value-wire-schema.js +28 -0
  175. package/internal/value-wire.js +29 -20
  176. package/internal/worker-client.js +57 -67
  177. package/internal/worker-environment.js +45 -0
  178. package/internal/worker-output-relay.js +29 -0
  179. package/internal/worker-shutdown.js +14 -0
  180. package/package.json +75 -7
  181. package/src/client-activity.js +88 -53
  182. package/src/client-authoring-view.js +580 -0
  183. package/src/client-binding-review.js +230 -0
  184. package/src/client-bindings-data.js +29 -0
  185. package/src/client-catalog.js +127 -0
  186. package/src/client-code-editor.js +84 -0
  187. package/src/client-console.js +112 -0
  188. package/src/client-copy.js +370 -0
  189. package/src/client-feature-gates.js +62 -0
  190. package/src/client-host-compat.js +150 -0
  191. package/src/client-indicator-view.js +267 -0
  192. package/src/client-repl-view.js +292 -0
  193. package/src/client-rpc.js +20 -0
  194. package/src/client-settings-view.js +221 -0
  195. package/src/client-styles.js +106 -0
  196. package/src/client-tool-view.js +108 -0
  197. package/src/client-transport.js +159 -0
  198. package/src/client-workbench.js +521 -0
  199. package/src/client.js +233 -615
@@ -0,0 +1,321 @@
1
+ import { staticModuleLinkReference } from './compiler-module-links.js'
2
+ import { parse } from '@babel/parser'
3
+ import { types as t } from '@babel/core'
4
+ import { CELL_PARSER_PLUGINS, normalizeStatefulScopes, normalizeTypeScriptValues, lowerNativeLanguageSource,
5
+ hasModuleResources } from './repl-scope-normalizer.js'
6
+ import { bindingNodes, createGeneratedNameAllocator } from './binding-pattern.js'
7
+ import { applySourceEdits, identitySourceMap, sourceOffsetAt } from './source-position-map.js'
8
+ import { applyRegionEdits, indexSourceRegions, visitRegionSource, transformRegionSource, validateRegionSource, sourceRegionData } from './compiler-region-output.js'
9
+ import { adaptDynamicCell } from './dynamic-environment-integration.js'
10
+ import { adaptLegacyModuleReferences, adaptModuleOperations } from './managed-module-operations.js'
11
+ import { markCallableSources, collectRegionCallableRanges, createRegionCallableCollector, emitRegionCallableSources, callableSourceCatalog } from './callable-source-facts.js'
12
+ import { commonJsExportEvidence } from './commonjs-source-evidence.js'
13
+ export { commonJsExportEvidence, linkCommonJsEvidenceSource } from './commonjs-source-evidence.js'
14
+ import { COMMONJS_PARAMETERS } from './compiler-scope-facts.js'
15
+ import {
16
+ LEGACY_USER_BINDING_TRANSFORM, USER_BINDING_TRANSFORM, PROTECTED_MODULE_TRANSFORM,
17
+ supportedUserBindingTransform, transformTypeScriptSource,
18
+ } from './typescript-transform.js'
19
+
20
+ const topLevelRegion = region => region.kind === 'program' || region.top === true
21
+
22
+ /** Keep one native export mapping while executing every source occurrence. */
23
+ function normalizeModuleExports(source, sourceMap) {
24
+ const tree = parse(source, { sourceType: 'module', errorRecovery: true,
25
+ plugins: CELL_PARSER_PLUGINS })
26
+ for (const error of tree.errors) if (error.reasonCode === 'ModuleExportUndefined') throw error
27
+ const allocate = createGeneratedNameAllocator(tree)
28
+ const exports = new Map()
29
+ const edits = []
30
+ const anonymousFunctions = []
31
+ const dependencyEdits = []
32
+ const exportName = node => node.type === 'Identifier' ? node.name : node.value
33
+ for (const statement of tree.program.body) {
34
+ if (statement.type === 'ExportDefaultDeclaration') {
35
+ const declaration = statement.declaration
36
+ const named = ['FunctionDeclaration', 'ClassDeclaration'].includes(declaration.type)
37
+ if (declaration.type === 'FunctionDeclaration' && declaration.id === null) {
38
+ // Its synthetic native binding is inaccessible to source writes. Keep
39
+ // instantiation-time identity and the native inferred name "default".
40
+ exports.set('default', { nativeFunction: statement })
41
+ anonymousFunctions.push(statement)
42
+ continue
43
+ }
44
+ const local = named && declaration.id !== null ? declaration.id.name : allocate('module_default')
45
+ exports.set('default', local)
46
+ if (named && declaration.id !== null) {
47
+ edits.push({ start: statement.start, end: declaration.start, text: '' })
48
+ } else {
49
+ const value = source.slice(declaration.start, declaration.end)
50
+ const anonymous = declaration.type === 'ClassDeclaration'
51
+ || ['FunctionExpression', 'ClassExpression', 'ArrowFunctionExpression'].includes(declaration.type) && declaration.id == null
52
+ edits.push({ start: statement.start, end: statement.end,
53
+ text: `let ${local} = (${anonymous ? `({default: (${value})}).default` : value});` })
54
+ }
55
+ continue
56
+ }
57
+ if (statement.type !== 'ExportNamedDeclaration') continue
58
+ const declaration = statement.declaration
59
+ if (declaration !== null && declaration !== undefined) {
60
+ const names = declaration.type === 'VariableDeclaration'
61
+ ? declaration.declarations.flatMap(item => bindingNodes(item.id)).map(item => item.name)
62
+ : declaration.id?.type === 'Identifier' ? [declaration.id.name] : []
63
+ for (const name of names) exports.set(name, name)
64
+ edits.push({ start: statement.start, end: declaration.start, text: '' })
65
+ continue
66
+ }
67
+ const dependencies = []
68
+ for (const specifier of statement.specifiers) {
69
+ if (specifier.exportKind === 'type') continue
70
+ const name = exportName(specifier.exported)
71
+ if (statement.source === null || statement.source === undefined) {
72
+ exports.set(name, exportName(specifier.local))
73
+ } else {
74
+ const local = allocate('module_export')
75
+ const binding = specifier.type === 'ExportNamespaceSpecifier'
76
+ ? `* as ${local}` : `{ ${JSON.stringify(exportName(specifier.local))} as ${local} }`
77
+ const dependency = `import ${binding} from ${source.slice(statement.source.start, statement.end)}`
78
+ const mapping = specifier.type === 'ExportNamespaceSpecifier' ? `* as ${JSON.stringify(name)}`
79
+ : `{ ${JSON.stringify(exportName(specifier.local))} as ${JSON.stringify(name)} }`
80
+ const selected = { native: `export ${mapping} from ${source.slice(statement.source.start, statement.end)}` }
81
+ exports.set(name, selected)
82
+ dependencies.push({ name, selected, dependency })
83
+ }
84
+ }
85
+ if (statement.source != null && statement.specifiers.length === 0) {
86
+ dependencies.push(`import ${source.slice(statement.source.start, statement.end)}`)
87
+ }
88
+ dependencyEdits.push({ start: statement.start, end: statement.end, dependencies })
89
+ }
90
+ for (const statement of anonymousFunctions) {
91
+ if (exports.get('default')?.nativeFunction !== statement) {
92
+ // An anonymous function declaration has no initializer effects or local
93
+ // source name once a later export mapping replaces its only exposure.
94
+ edits.push({ start: statement.start, end: statement.end, text: '' })
95
+ }
96
+ }
97
+ for (const { start, end, dependencies } of dependencyEdits) {
98
+ // A retained native re-export already owns linking. An extra named import
99
+ // can make Node reject an otherwise valid cyclic forwarding graph. Only
100
+ // replaced mappings need a separate import to retain their source effects
101
+ // and missing-export validation.
102
+ edits.push({ start, end, text: dependencies.filter(item => typeof item === 'string' || exports.get(item.name) !== item.selected)
103
+ .map(item => typeof item === 'string' ? item : item.dependency).join('\n') })
104
+ }
105
+ const locals = [...exports].filter(([, binding]) => typeof binding === 'string')
106
+ const native = [...exports.values()].filter(binding => binding.native !== undefined).map(binding => binding.native)
107
+ if (exports.size > 0) edits.push({ start: source.length, end: source.length,
108
+ text: `\n${locals.length > 0 ? `export { ${locals.map(([name, local]) => `${local} as ${JSON.stringify(name)}`).join(', ')} };\n` : ''}${native.join('\n')}\n` })
109
+ return applySourceEdits(source, sourceMap, edits)
110
+ }
111
+
112
+ function moduleLinkPlan(source) {
113
+ const tree = parse(source, { sourceType: 'module', errorRecovery: true,
114
+ allowUndeclaredExports: true, plugins: CELL_PARSER_PLUGINS })
115
+ const allocate = createGeneratedNameAllocator(tree)
116
+ const reader = allocate('module_import_read')
117
+ const nativeReader = allocate('module_native_read')
118
+ const internalBindings = [reader, nativeReader]
119
+ const exports = []
120
+ const stars = []
121
+ const exportName = node => node.name ?? node.value
122
+ for (const statement of tree.program.body) {
123
+ if (statement.type === 'ExportDefaultDeclaration') exports.push({ name: 'default', kind: 'native' })
124
+ if (!['ExportNamedDeclaration', 'ExportAllDeclaration'].includes(statement.type)) continue
125
+ const relation = statement.source ? { source: statement.source.value,
126
+ attributes: Object.fromEntries((statement.attributes ?? [])
127
+ .map(attribute => [exportName(attribute.key), attribute.value.value])) } : undefined
128
+ if (statement.type === 'ExportAllDeclaration') stars.push(relation)
129
+ else for (const item of statement.specifiers) {
130
+ const name = exportName(item.exported)
131
+ exports.push(relation === undefined ? { name, kind: 'accessor' }
132
+ : item.type === 'ExportNamespaceSpecifier' ? { name, kind: 'namespace', ...relation }
133
+ : { name, kind: 'forward', imported: exportName(item.local), ...relation })
134
+ }
135
+ }
136
+ const runtime = new URL('./stateful-module-runtime.js', import.meta.url).href
137
+ return { reader, internalBindings, moduleInterface: { exports, stars },
138
+ prefix: `import {readModuleImport as ${nativeReader}} from ${JSON.stringify(runtime)};\nfunction ${reader}(source,name,get,attributes){return ${nativeReader}(import.meta.url,source,name,get,attributes)}\n` }
139
+ }
140
+
141
+ function adaptStaticModuleLinks(normalized, staticLinks) {
142
+ const edits = []
143
+ visitRegionSource(normalized, { sourceType: 'module', plugins: CELL_PARSER_PLUGINS }, input => {
144
+ for (const node of input.selected.body) {
145
+ if (node.source == null || !['ImportDeclaration', 'ExportNamedDeclaration', 'ExportAllDeclaration'].includes(node.type)) continue
146
+ const source = node.source.value
147
+ const attributes = Object.fromEntries((node.attributes ?? []).map(item => [item.key.name ?? item.key.value, item.value.value]))
148
+ const reference = staticModuleLinkReference(source, attributes)
149
+ staticLinks.push({ source, reference })
150
+ edits.push({ start: sourceOffsetAt(input.sourceMap, node.source.start),
151
+ end: sourceOffsetAt(input.sourceMap, node.source.end - 1) + 1,
152
+ text: JSON.stringify(reference) })
153
+ }
154
+ }, topLevelRegion)
155
+ return applyRegionEdits(normalized, edits)
156
+ }
157
+
158
+ function commonJsBodyRange(source) {
159
+ const body = parse(source).program.body[0].body
160
+ return { start: body.start + 1, end: body.end - 1 }
161
+ }
162
+
163
+ /** Module activations share lexical updates while retaining the native linker. */
164
+ export function compileStatefulModule(source, {
165
+ transform = USER_BINDING_TRANSFORM, target = 'module', url, nativeUsing,
166
+ } = {}) {
167
+ if (transform !== PROTECTED_MODULE_TRANSFORM && !supportedUserBindingTransform(transform)) {
168
+ throw new TypeError('user binding snapshot cannot prove its historical TypeScript transform')
169
+ }
170
+ const staticLinks = []
171
+ const sourceType = target === 'commonjs' ? 'commonjs' : 'module'
172
+ const callableParserOptions = { sourceType, plugins: CELL_PARSER_PLUGINS,
173
+ ...(target === 'commonjs' ? { allowAwaitOutsideFunction: undefined, allowReturnOutsideFunction: undefined } : {}) }
174
+ let mapped = { code: source, sourceMap: identitySourceMap(source.length) }
175
+ let callableSources = []
176
+ let nativeJavaScript = false
177
+ if (source.startsWith('#!')) {
178
+ // Interpreter directives belong to the source's first line, never to the
179
+ // prefixed module or CommonJS wrapper. Retain its original line boundary.
180
+ const directive = source.match(/^#![^\n\r\u2028\u2029]*/u)
181
+ mapped = applySourceEdits(source, mapped.sourceMap, [{ start: 0,
182
+ end: directive[0].length, text: '' }])
183
+ }
184
+ if (transform !== LEGACY_USER_BINDING_TRANSFORM) {
185
+ const marked = markCallableSources(mapped.code, mapped.sourceMap, callableParserOptions,
186
+ { lowerNativeSource: lowerNativeLanguageSource, nativeUsing })
187
+ callableSources = marked.callableSources
188
+ nativeJavaScript = marked.nativeJavaScript
189
+ mapped = marked
190
+ if (!nativeJavaScript) mapped = normalizeTypeScriptValues(mapped.code, mapped.sourceMap, target)
191
+ if (target !== 'commonjs' && (transform === USER_BINDING_TRANSFORM
192
+ || transform === PROTECTED_MODULE_TRANSFORM && hasModuleResources(mapped.code))) {
193
+ mapped = normalizeModuleExports(mapped.code, mapped.sourceMap)
194
+ }
195
+ }
196
+ const commonJsSource = target === 'commonjs'
197
+ ? commonJsExportEvidence(mapped.code, { legacy: transform === LEGACY_USER_BINDING_TRANSFORM }) : undefined
198
+ const links = transform === USER_BINDING_TRANSFORM && target === 'module'
199
+ ? moduleLinkPlan(mapped.code) : undefined
200
+ let normalized = transform === LEGACY_USER_BINDING_TRANSFORM ? mapped
201
+ : normalizeStatefulScopes(mapped.code, mapped.sourceMap, { target, moduleImport: links?.reader, nativeJavaScript, nativeUsing,
202
+ mode: transform === PROTECTED_MODULE_TRANSFORM ? 'protected-v1' : 'stateful-v1' })
203
+ // Protected entries retain native declaration policy; adapters still need
204
+ // bounded source regions after TypeScript and decorator lowering.
205
+ if (transform === PROTECTED_MODULE_TRANSFORM && normalized.sourceRegions === undefined) normalized = { ...normalized,
206
+ sourceRegions: indexSourceRegions(normalized.code, callableParserOptions) }
207
+ let protectedInterface
208
+ if (transform === PROTECTED_MODULE_TRANSFORM && target === 'module') {
209
+ const lexicalExports = normalized.moduleLexicalExports
210
+ normalized = adaptLegacyModuleReferences(normalized)
211
+ protectedInterface = normalized.moduleInterface
212
+ if (lexicalExports !== undefined) protectedInterface = { ...protectedInterface,
213
+ exports: protectedInterface.exports.map(entry => lexicalExports.has(entry.name) ? { ...entry, kind: 'accessor' } : entry) }
214
+ }
215
+ if (links !== undefined) normalized = { ...applyRegionEdits(normalized,
216
+ [{ start: 0, end: 0, text: links.prefix }]),
217
+ internalBindings: new Set([...normalized.internalBindings ?? [], ...links.internalBindings]) }
218
+ const classificationCode = normalized.code
219
+ if (links !== undefined || protectedInterface !== undefined) normalized = adaptStaticModuleLinks(normalized, staticLinks)
220
+ if (transform !== LEGACY_USER_BINDING_TRANSFORM) normalized = adaptModuleOperations(normalized, { target, url })
221
+ if (transform !== LEGACY_USER_BINDING_TRANSFORM) normalized = { ...normalized, ...adaptDynamicCell(normalized.code, normalized.sourceMap, {
222
+ sourceRegions: normalized.sourceRegions,
223
+ callableCollector: createRegionCallableCollector(callableSources),
224
+ module: true, compactOutput: true, importOperation: normalized.importOperation, sourceType, originalSource: source,
225
+ bindings: normalized.dynamicBindings, privateBindings: normalized.privateBindings,
226
+ internalBindings: normalized.internalBindings, parserPlugins: CELL_PARSER_PLUGINS,
227
+ }) }
228
+ // The published legacy map ends at its maintained transform input.
229
+ const wrapper = target === 'commonjs' ? 'function __ptc_commonjs__(){\n' : ''
230
+ let input = normalized
231
+ if (transform === LEGACY_USER_BINDING_TRANSFORM && wrapper) {
232
+ input = applySourceEdits(input.code, input.sourceMap, [{ start: 0, end: 0, text: wrapper }])
233
+ input = applySourceEdits(input.code, input.sourceMap, [{ start: input.code.length, end: input.code.length, text: '\n}' }])
234
+ }
235
+ let transformed, nativeExtraction, emission, sources
236
+ let moduleInterface = links?.moduleInterface ?? protectedInterface
237
+ if (transform === LEGACY_USER_BINDING_TRANSFORM) {
238
+ // Preserve the historical TypeScript lowering before adapting native
239
+ // bindings. Its unexpanded output is the source indexed by shared regions.
240
+ transformed = transformTypeScriptSource(input.code)
241
+ let code = transformed.code
242
+ if (wrapper) {
243
+ nativeExtraction = commonJsBodyRange(code)
244
+ code = code.slice(nativeExtraction.start, nativeExtraction.end)
245
+ }
246
+ let entry = { code, sourceMap: identitySourceMap(code.length),
247
+ sourceRegions: indexSourceRegions(code, callableParserOptions) }
248
+ if (target === 'commonjs') entry = normalizeStatefulScopes(code, entry.sourceMap,
249
+ { mode: 'protected-v1', target, nativeJavaScript: true, deferDecorators: true, deferResources: true })
250
+ if (target === 'module') {
251
+ entry = adaptLegacyModuleReferences(entry)
252
+ moduleInterface = entry.moduleInterface
253
+ entry = adaptStaticModuleLinks(entry, staticLinks)
254
+ }
255
+ entry = adaptModuleOperations(entry, { target, url })
256
+ emission = adaptDynamicCell(entry.code, entry.sourceMap, { module: true,
257
+ sourceRegions: entry.sourceRegions,
258
+ importOperation: entry.importOperation, sourceType, originalSource: source,
259
+ bindings: entry.dynamicBindings, internalBindings: entry.internalBindings, parserPlugins: CELL_PARSER_PLUGINS,
260
+ })
261
+ } else {
262
+ const owners = normalized.callableRanges === undefined ? collectRegionCallableRanges(normalized, callableSources, callableParserOptions)
263
+ : new Map(normalized.callableRanges.map(range => [range.start, range]))
264
+ // Native JavaScript has no remaining TypeScript emission work. Retain the
265
+ // generator's exact source and owner facts instead of expanding it again.
266
+ const output = nativeJavaScript ? { ...normalized, callableRanges: [...owners.values()] }
267
+ : transformRegionSource(normalized, { ...callableParserOptions, wrapCommonJs: true }, region => {
268
+ const generated = transformTypeScriptSource(region.code, {
269
+ module: target === 'module' && !region.prefix, sourceMap: true,
270
+ // Type-only declarations and the complete module interface were
271
+ // resolved before partitioning; omitted bodies cannot erase imports.
272
+ transform: { tsEnumIsMutable: true, verbatimModuleSyntax: true,
273
+ noEmptyExport: target === 'commonjs' || !!region.prefix },
274
+ })
275
+ return emitRegionCallableSources(region, generated, owners, callableParserOptions)
276
+ })
277
+ emission = nativeJavaScript ? output : { ...output, ...applySourceEdits(normalized.code, normalized.sourceMap,
278
+ [{ start: 0, end: normalized.code.length, text: output.code, mappings: output.sourceMap }]) }
279
+ sources = callableSourceCatalog(emission.code, callableSources, output.callableRanges)
280
+ }
281
+ // Callable ownership collection validates native output's exact grammar
282
+ // regions. Only the later TypeScript/legacy emitter changes that code.
283
+ if (!nativeJavaScript) validateRegionSource(emission, { sourceType, plugins: ['importAttributes'] })
284
+ return { code: emission.code, classificationCode, transform, target, moduleInterface, commonJsSource,
285
+ staticLinks, callableSources: sources,
286
+ sourceRegions: sourceRegionData(emission.sourceRegions),
287
+ sourceMap: { normalization: input.sourceMap, typescript: transformed?.map, nativeExtraction,
288
+ ...(transform === LEGACY_USER_BINDING_TRANSFORM ? {} : { emission: emission.sourceMap }) },
289
+ }
290
+ }
291
+
292
+ export function detectModuleSourceFormat(source, { extension } = {}) {
293
+ // Synchronous CommonJS hooks may leave format undefined. For an untyped
294
+ // package Node detects module syntax, including lexical wrapper-name clashes.
295
+ // Parse without executing source, allowing the repetitions PTC owns.
296
+ if (extension === '.ts') source = normalizeTypeScriptValues(source, identitySourceMap(source.length)).code
297
+ const tree = parse(source, { sourceType: 'unambiguous', errorRecovery: true,
298
+ allowReturnOutsideFunction: true, allowNewTargetOutsideFunction: true, plugins: CELL_PARSER_PLUGINS })
299
+ const wrapperNames = new Set(COMMONJS_PARAMETERS)
300
+ const lexicalWrapperName = tree.program.body.some(node => node.type === 'VariableDeclaration' && node.kind !== 'var'
301
+ ? node.declarations.some(item => bindingNodes(item.id).some(binding => wrapperNames.has(binding.name)))
302
+ : node.type === 'ClassDeclaration' && wrapperNames.has(node.id.name))
303
+ return tree.program.sourceType === 'module' || lexicalWrapperName ? 'module' : 'commonjs'
304
+ }
305
+
306
+ export function attachModuleNamespace(source, attributes, reference, sourceRegions) {
307
+ const text = source
308
+ const names = new Set()
309
+ visitRegionSource({ code: text, sourceMap: identitySourceMap(text.length), sourceRegions }, { sourceType: 'module' }, input => {
310
+ t.traverseFast(input.tree, node => { if (t.isIdentifier(node)) names.add(node.name) })
311
+ })
312
+ const allocate = createGeneratedNameAllocator(undefined, names)
313
+ const namespace = allocate('module_native_namespace')
314
+ const register = allocate('module_register_namespace')
315
+ const runtime = new URL('./stateful-module-runtime.js', import.meta.url).href
316
+ // A namespace import adds no named-import validation and reads no exports.
317
+ // Register after successful evaluation, before native require can return.
318
+ const entries = Object.entries(attributes ?? {})
319
+ const suffix = entries.length === 0 ? '' : ` with {${entries.map(([key, value]) => `${JSON.stringify(key)}:${JSON.stringify(value)}`).join(',')}}`
320
+ return `${text}\nimport * as ${namespace} from ${JSON.stringify(reference)}${suffix};\nimport {recordStatefulModuleNamespace as ${register}} from ${JSON.stringify(runtime)};\n${register}(import.meta.url,${namespace});\n`
321
+ }
@@ -0,0 +1,19 @@
1
+ import { registerHooks } from 'node:module'
2
+ import { compilerPlatformBridge } from './compiler-platform.js'
3
+
4
+ const nativeRegisterHooks = registerHooks
5
+ const apply = Reflect.apply
6
+ // Acquire the public hook lifecycle before any owned source can replace Node
7
+ // exports or the hook prototype. The empty bootstrap hook changes no edge.
8
+ const bootstrapHook = nativeRegisterHooks({})
9
+ const deregister = bootstrapHook.deregister
10
+ apply(deregister, bootstrapHook, [])
11
+
12
+ /** Each native resolution scope has one captured registration and disposer. */
13
+ export function registerModuleResolutionHook(resolve) {
14
+ const hook = nativeRegisterHooks({ resolve })
15
+ return () => apply(deregister, hook, [])
16
+ }
17
+
18
+ export const fileURLToPath = compilerPlatformBridge.fileURLToPath
19
+ export const pathToFileURL = compilerPlatformBridge.pathToFileURL
@@ -0,0 +1,12 @@
1
+ export const LEGACY_USER_BINDING_TRANSFORM = 'amaro@1.1.11'
2
+ export const USER_BINDING_TRANSFORM = 'stateful-module-v1+amaro@1.1.11'
3
+ export const PROTECTED_MODULE_TRANSFORM = 'protected-module-v1+amaro@1.1.11'
4
+
5
+ export function moduleTransformForLanguage(languageSemantics) {
6
+ return languageSemantics === 'stateful-v1' ? USER_BINDING_TRANSFORM
7
+ : languageSemantics === 'protected-v1' ? PROTECTED_MODULE_TRANSFORM : LEGACY_USER_BINDING_TRANSFORM
8
+ }
9
+
10
+ export function supportedUserBindingTransform(transform) {
11
+ return transform === USER_BINDING_TRANSFORM || transform === LEGACY_USER_BINDING_TRANSFORM
12
+ }
@@ -0,0 +1,68 @@
1
+ import traverseModule from '@babel/traverse'
2
+ import { types as t } from '@babel/core'
3
+
4
+ const traverse = traverseModule.default ?? traverseModule
5
+
6
+ /** Native await owns persistent declaration instantiation; references own writes. */
7
+ export function rewriteNativeAwaitDeclarations(tree, { hiddenBindings, nativePlaceholders, reference, initialize, namedValue, replaceIterationDeclaration }) {
8
+ const placeholders = []
9
+ const preserve = node => { nativePlaceholders.add(node); placeholders.push(node) }
10
+ const rootIdentifiers = node => {
11
+ if (t.isIdentifier(node)) return [node]
12
+ if (t.isObjectPattern(node)) return node.properties.flatMap(property => rootIdentifiers(property.value ?? property.argument))
13
+ if (t.isArrayPattern(node)) return node.elements.flatMap(element => rootIdentifiers(element))
14
+ // Native REPL await registers direct pattern identifiers. Default targets
15
+ // and array rest targets remain assignment references in that generation.
16
+ return []
17
+ }
18
+ const lexicalPlaceholder = name => preserve(t.variableDeclaration('let', [
19
+ t.variableDeclarator(t.identifier(name), t.identifier(name)),
20
+ ]))
21
+ const assignmentPattern = (node, path) => {
22
+ if (t.isIdentifier(node)) return reference(path, node.name)
23
+ if (t.isArrayPattern(node)) return t.arrayPattern(node.elements.map(element => element === null ? null : assignmentPattern(element, path)))
24
+ if (t.isObjectPattern(node)) return t.objectPattern(node.properties.map(property => t.isRestElement(property)
25
+ ? t.restElement(assignmentPattern(property.argument, path))
26
+ : t.objectProperty(property.key, assignmentPattern(property.value, path), property.computed, false)))
27
+ if (t.isAssignmentPattern(node)) return t.assignmentPattern(assignmentPattern(node.left, path),
28
+ t.isIdentifier(node.left) ? namedValue(node.left.name, node.right) : node.right)
29
+ return t.restElement(assignmentPattern(node.argument, path))
30
+ }
31
+ traverse(tree, {
32
+ VariableDeclaration(path) {
33
+ if (path.node.declarations.some(declaration => t.isIdentifier(declaration.id) && hiddenBindings.has(declaration.id.name))
34
+ || path.getFunctionParent() !== null || path.node.kind !== 'var' && !path.parentPath.isProgram()) return
35
+ const original = path.node
36
+ if (original.kind === 'var') {
37
+ const declaration = t.cloneNode(original, true)
38
+ for (const item of declaration.declarations) item.init = t.unaryExpression('void', t.numericLiteral(0))
39
+ preserve(t.ifStatement(t.booleanLiteral(false), t.blockStatement([declaration])))
40
+ } else for (const item of original.declarations) for (const name of rootIdentifiers(item.id)) lexicalPlaceholder(name.name)
41
+ const assignment = item => {
42
+ const value = item.init ?? t.unaryExpression('void', t.numericLiteral(0))
43
+ return t.isIdentifier(item.id) ? initialize(path, item.id.name, namedValue(item.id.name, value))
44
+ : t.assignmentExpression('=', assignmentPattern(item.id, path), value)
45
+ }
46
+ if (path.parentPath.isForOfStatement() || path.parentPath.isForInStatement()) {
47
+ replaceIterationDeclaration(path, assignmentPattern(original.declarations[0].id, path),
48
+ original.declarations.filter(item => item.init !== null).map(assignment))
49
+ } else {
50
+ const assignments = original.declarations.map(assignment)
51
+ const expression = t.inherits(assignments.length === 1 ? assignments[0] : t.sequenceExpression(assignments), original)
52
+ path.replaceWith(path.parentPath.isForStatement() ? expression : t.expressionStatement(t.unaryExpression('void', expression)))
53
+ }
54
+ path.skip()
55
+ },
56
+ ClassDeclaration(path) {
57
+ if (!path.parentPath.isProgram()) return
58
+ const name = path.node.id.name
59
+ lexicalPlaceholder(name)
60
+ const target = reference(path.parentPath, name)
61
+ path.node.type = 'ClassExpression'
62
+ const assignment = t.inherits(t.assignmentExpression('=', target, path.node), path.node)
63
+ path.replaceWith(t.expressionStatement(t.unaryExpression('void', assignment)))
64
+ path.skip()
65
+ },
66
+ })
67
+ tree.program.body.unshift(...placeholders)
68
+ }
@@ -0,0 +1,54 @@
1
+ import { parse } from '@babel/parser'
2
+ import { bindingNodes, createGeneratedNameAllocator } from './binding-pattern.js'
3
+ import { markCallableSources, collectCallableSources } from './callable-source-facts.js'
4
+ import { adaptDynamicCell } from './dynamic-environment-integration.js'
5
+ import { CELL_PARSER_PLUGINS } from './repl-scope-normalizer.js'
6
+
7
+ /** Native declarations remain frozen; dynamic entries retain their native root. */
8
+ export function adaptNativeRootDynamic(prepared, options, originalSource) {
9
+ if (prepared.collisions.length !== 0) return { ...prepared, nativeLexicals: new Set() }
10
+ const parser = { sourceType: 'script', allowAwaitOutsideFunction: true,
11
+ allowReturnOutsideFunction: true, plugins: CELL_PARSER_PLUGINS }
12
+ const marked = markCallableSources(prepared.code, prepared.sourceMap, parser)
13
+ const tree = parse(marked.code, parser)
14
+ const nativeLexicals = new Set(tree.program.body.flatMap(statement =>
15
+ statement.type === 'VariableDeclaration' && statement.kind !== 'var'
16
+ ? statement.declarations.flatMap(declaration => bindingNodes(declaration.id).map(binding => binding.name))
17
+ : statement.type === 'ClassDeclaration' ? [statement.id.name] : []))
18
+ const nativeAwait = hasRootAwait(tree.program)
19
+ const reservedBindings = new Set([...prepared.importNamespaces,
20
+ ...options.knownBindings ?? [], ...options.reservedBindings ?? []])
21
+ const allocate = createGeneratedNameAllocator(parse(marked.code, parser), reservedBindings)
22
+ const global = allocate('native_environment_global')
23
+ const established = [...options.establishedRoots ?? []]
24
+ const logicalRoots = established.filter(([, source]) => source !== 'absent').map(([name]) => name)
25
+ const nativePublications = prepared.declarations.filter(declaration => options.establishedRoots?.has(declaration.name)
26
+ && !prepared.redeclared.some(redeclaration => redeclaration.name === declaration.name))
27
+ .map(declaration => ({ name: declaration.name, writable: declaration.writable,
28
+ nativeLexical: nativeLexicals.has(declaration.name),
29
+ import: prepared.imports.get(declaration.name) }))
30
+ const dynamic = adaptDynamicCell(marked.code, marked.sourceMap, { nativeRoot: true,
31
+ environmentGlobal: global, reservedBindings, internalBindings: prepared.importNamespaces,
32
+ originalSource, parserPlugins: CELL_PARSER_PLUGINS, nativeAwait, logicalRoots: logicalRoots.length > 0 })
33
+ const callableSources = collectCallableSources(dynamic.code, marked.callableSources, parser)
34
+ return { ...prepared, code: dynamic.code, sourceMap: dynamic.sourceMap, nativeLexicals,
35
+ ...(established.length === 0 ? {} : { rootBindingFacts: true, rootBindings: {
36
+ known: [...options.knownBindings ?? []], established, candidates: [...options.rootCandidates ?? []],
37
+ dynamicOrigins: options.dynamicOrigins ?? [], legacyImports: [...options.importBindings ?? []],
38
+ } }),
39
+ importNamespaces: new Set([...prepared.importNamespaces, dynamic.dynamicEnvironmentName]),
40
+ moduleLoads: [...prepared.moduleLoads, { operation: 'native-dynamic', global, callableSources, awaitRoot: nativeAwait,
41
+ nativeLexicals: [...options.nativeLexicalBindings ?? []],
42
+ logicalRoots, writableRoots: logicalRoots.filter(name => options.writableBindings?.has(name)), nativePublications }] }
43
+ }
44
+
45
+ function hasRootAwait(node) {
46
+ if (node === null || typeof node !== 'object') return false
47
+ if (node.type === 'AwaitExpression' || node.type === 'ForOfStatement' && node.await) return true
48
+ if (['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(node.type)) return false
49
+ if (['ObjectMethod', 'ClassMethod', 'ClassPrivateMethod'].includes(node.type)) {
50
+ return node.computed && hasRootAwait(node.key)
51
+ }
52
+ return Object.entries(node).some(([key, value]) => !['loc', 'extra', 'start', 'end'].includes(key)
53
+ && (Array.isArray(value) ? value.some(hasRootAwait) : value?.type !== undefined && hasRootAwait(value)))
54
+ }
@@ -0,0 +1,67 @@
1
+ import { createDynamicEnvironmentRuntime } from './dynamic-environment-runtime.js'
2
+ import { moduleRuntimeIntrinsics as internal } from './compiler-intrinsics.js'
3
+
4
+ const { Map, Set, mapGet, mapSet, mapHas, mapDelete, setHas, setAdd, appendArray } = internal
5
+
6
+ /** Native storage and catalog-proved logical roots share a captured legacy view. */
7
+ export function createNativeRootDynamic({ intrinsics, importModule, read, write, typeOf, remove, logicalReference }) {
8
+ const interfaceOwner = {}
9
+ const runtime = createDynamicEnvironmentRuntime({ ...intrinsics, importModule, interfaceOwner }).installIntrinsics()
10
+ const descriptor = name => ({ kind: 'var', get: () => read(name),
11
+ set: (value, strict) => write(name, value, strict), delete: () => remove(name), typeof: () => typeOf(name) })
12
+ const environment = runtime.environment({
13
+ ambient(name, strict) {
14
+ return runtime.reference(name, descriptor(name), undefined, strict)
15
+ },
16
+ declareVars: runtime.declareGlobals,
17
+ createVar: descriptor,
18
+ })
19
+ runtime.setRootEnvironment(() => environment)
20
+ return {
21
+ environment(callableSources, awaitRoot = false, logicalRoots = [], writableRoots = []) {
22
+ if (logicalRoots.length === 0) {
23
+ runtime.registerSources(callableSources)
24
+ return awaitRoot ? environment.awaitActivation() : environment
25
+ }
26
+ // Each captured legacy environment keeps its own root view. Installing a
27
+ // later bridge must not change escaped eval/Function calls from old cells.
28
+ const bridgedRuntime = createDynamicEnvironmentRuntime({ ...intrinsics, importModule, interfaceOwner }).installIntrinsics()
29
+ const writable = new Set()
30
+ for (let index = 0; index < writableRoots.length; index++) setAdd(writable, writableRoots[index])
31
+ const bindings = new Map(), varFrame = new Map(), roots = new Map()
32
+ for (let index = 0; index < logicalRoots.length; index++) {
33
+ const name = logicalRoots[index], binding = logicalReference(name, setHas(writable, name))
34
+ mapSet(bindings, name, binding)
35
+ mapSet(roots, name, binding)
36
+ if (binding.kind === 'var') mapSet(varFrame, name, binding)
37
+ const remove = binding.delete
38
+ binding.delete = () => {
39
+ if (!remove()) return false
40
+ mapDelete(bindings, name)
41
+ mapDelete(varFrame, name)
42
+ return true
43
+ }
44
+ }
45
+ const bridged = bridgedRuntime.environment({ frames: [{ kind: 'lexical', bindings }], varFrame,
46
+ ambient: (name, strict) => bridgedRuntime.reference(name, descriptor(name), undefined, strict),
47
+ declareVars(names, functionNames) {
48
+ const globals = []
49
+ for (let index = 0; index < names.length; index++) {
50
+ if (!mapHas(roots, names[index])) appendArray(globals, names[index])
51
+ }
52
+ bridgedRuntime.declareGlobals(globals, functionNames)
53
+ },
54
+ createVar(name) {
55
+ const root = mapGet(roots, name)
56
+ if (root === undefined) return descriptor(name)
57
+ root.declare()
58
+ mapSet(bindings, name, root)
59
+ return root
60
+ },
61
+ })
62
+ bridgedRuntime.setRootEnvironment(() => bridged)
63
+ bridgedRuntime.registerSources(callableSources)
64
+ return awaitRoot ? bridged.awaitActivation() : bridged
65
+ },
66
+ }
67
+ }
@@ -1,3 +1,5 @@
1
+ import { recoveryTipIdentity } from './runtime-messages.js'
2
+
1
3
  const PLATFORM_CAUSE_CODES = new Set(['EACCES', 'EINVAL', 'ENOTDIR', 'ENOENT', 'UNKNOWN'])
2
4
  const TIP_CONTEXT_PREFIX = 'tools:ptc-plus-tip/'
3
5
  const TIP_PREFIXES = Object.freeze({
@@ -10,16 +12,20 @@ function tipHistory(view) {
10
12
  const active = new Map()
11
13
  const seen = new Set()
12
14
  const history = []
13
- for (const snapshot of view.systemPromptSnapshots) {
15
+ const snapshots = [
16
+ ...view.systemPromptSnapshots,
17
+ ...(view.ptcMessages ?? []).filter(record => record.form === 'notice').map(record => ({
18
+ ...record, sections: [{ name: record.name, text: record.text }],
19
+ })),
20
+ ].sort((left, right) => left.index - right.index)
21
+ for (const snapshot of snapshots) {
14
22
  const next = new Map(snapshot.sections.map(section => [section.name, section.text]))
15
23
  for (const [name] of next) {
16
24
  if (active.has(name) || seen.has(name)) continue
17
- const match = /^tools:ptc-plus-tip\/(repeated-binding-failure|platform-command-failure)\/([1-9][0-9]*)$/.exec(name)
18
- if (match === null) continue
19
- const ordinal = Number(match[2])
20
- if (!Number.isSafeInteger(ordinal)) continue
25
+ const identity = recoveryTipIdentity(name)
26
+ if (identity === undefined) continue
21
27
  seen.add(name)
22
- history.push({ id: match[1], ordinal, index: snapshot.index, contextStep: snapshot.contextStep })
28
+ history.push({ ...identity, index: snapshot.index, contextStep: snapshot.contextStep })
23
29
  }
24
30
  active.clear()
25
31
  for (const entry of next) active.set(...entry)
@@ -36,7 +42,7 @@ function hasPlatformCommandDiagnostic(diagnostics) {
36
42
  const causeCode = typeof diagnostic.cause?.code === 'string' ? diagnostic.cause.code.toUpperCase() : undefined
37
43
  if (causeCode !== undefined && PLATFORM_CAUSE_CODES.has(causeCode)
38
44
  && /\b(?:spawn|exec(?:ute)?|command|executable|shell|path|file)\b/i.test(text)) return true
39
- return /(?:spawn|exec(?:ute)?|child process)\b.*(?:failed|error|not found|cannot find)/i.test(text)
45
+ return /\b(?:spawn|exec(?:ute)?|child process)\b.*\b(?:ENOENT|ENOTDIR|EACCES|EINVAL)\b/i.test(text)
40
46
  || /command not found|no such file|not recognized as (?:an )?internal|cannot find (?:the )?(?:path|file)/i.test(text)
41
47
  })
42
48
  }
@@ -45,7 +51,10 @@ function tipCandidate(view) {
45
51
  const { args, journal } = view.latestRun ?? {}
46
52
  if (args === undefined || journal === undefined) return undefined
47
53
  const codes = new Set(journal.diagnostics.map(diagnostic => diagnostic.code))
48
- if (codes.has('PTC-W001')) return { id: 'repeated-binding-failure' }
54
+ if (codes.has('PTC-W001')) return {
55
+ id: 'repeated-binding-failure',
56
+ capability: journal.diagnostics.some(item => item.code === 'PTC-W001' && item.cause?.code === 'PTC-CAPABILITY'),
57
+ }
49
58
  if (hasPlatformCommandDiagnostic(journal.diagnostics)) return { id: 'platform-command-failure' }
50
59
  return undefined
51
60
  }
@@ -56,6 +65,9 @@ function renderTip(id, detailed, candidate = {}) {
56
65
  ? `${TIP_PREFIXES[id]} Re-check the active execution world and the actual executable before retrying. Use direct argv for a normal executable; use a shell only when its syntax or resolution is required. Windows, WSL, POSIX, and package shims have different paths and launch rules.`
57
66
  : `${TIP_PREFIXES[id]} Inspect the executable or path in the current execution world and choose direct argv or a shell only when required; do not assume Windows, WSL, POSIX, or one shell.`
58
67
  }
68
+ if (!candidate.capability) {
69
+ return `${TIP_PREFIXES[id]} Inspect the visible cell source and results for the local name, scope, initialization, or declaration conflict. Correct that expression before continuing; capability discovery does not list session-local variables.${detailed ? ' Earlier statements may have run; use current expression evidence without assuming a failed declaration initialized its binding.' : ''}`
70
+ }
59
71
  return detailed
60
72
  ? `${TIP_PREFIXES[id]} Inspect the live request with \`capabilities.tree()\`, \`capabilities.find()\`, or \`capabilities.inspect()\`, then call the typed member through \`tools.*\`. Do not invent hidden bindings or repeat the same failing expression.`
61
73
  : `${TIP_PREFIXES[id]} Inspect the live request with \`capabilities.tree()\`, \`capabilities.find()\`, or \`capabilities.inspect()\`, then call the typed member through \`tools.*\`.`
@@ -65,14 +77,14 @@ export function latestRecoveryTip(view, config) {
65
77
  if (!config.enabled) return undefined
66
78
  const candidate = tipCandidate(view)
67
79
  if (candidate === undefined) return undefined
68
- const history = tipHistory(view)
69
- const lastTip = history.at(-1)
80
+ // Cooldown, escalation, and ordinal are per trigger kind: one kind's tip must
81
+ // not suppress a different kind.
82
+ const matching = tipHistory(view).filter(item => item.id === candidate.id)
83
+ const lastTip = matching.at(-1)
70
84
  if (lastTip !== undefined && view.contextStep - lastTip.contextStep < config.cooldownMessages) return undefined
71
- const unresolved = history.filter(item => item.id === candidate.id && item.index > (view.lastSuccessfulRunIndex ?? -1))
72
- const ordinal = history.reduce(
73
- (highest, item) => item.id === candidate.id ? Math.max(highest, item.ordinal) : highest,
74
- 0,
75
- ) + 1
85
+ const unresolved = matching.filter(item => item.index > (view.lastSuccessfulRunIndex ?? -1))
86
+ const ordinal = matching.reduce((highest, item) => Math.max(highest, item.ordinal), 0) + 1
87
+ if (!Number.isSafeInteger(ordinal)) return undefined
76
88
  return {
77
89
  name: `${TIP_CONTEXT_PREFIX}${candidate.id}/${ordinal}`,
78
90
  text: renderTip(candidate.id, unresolved.length >= config.escalationFailures, candidate),