@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.5

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 (236) hide show
  1. package/bin/doflow.js +2 -2
  2. package/core/.antigravity-plugin/plugin.json +0 -1
  3. package/core/.claude-plugin/plugin.json +1 -1
  4. package/core/.codex-plugin/plugin.json +1 -1
  5. package/core/.plugin/plugin.json +1 -1
  6. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  7. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  8. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  9. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  10. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  11. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  12. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  13. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  14. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  15. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  16. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  17. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  18. package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
  19. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  20. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
  21. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
  22. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
  23. package/core/harnesses/gemini/hooks/session-start.sh +5 -3
  24. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
  25. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
  26. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
  27. package/core/harnesses/kiro/hooks/session-start.sh +11 -5
  28. package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
  29. package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
  30. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
  31. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  32. package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
  33. package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
  34. package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
  35. package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
  36. package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
  37. package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
  38. package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
  39. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
  40. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
  41. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
  42. package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
  43. package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
  44. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
  45. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
  46. package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
  47. package/core/registry/assets.json +19 -0
  48. package/core/registry/contracts.json +2 -2
  49. package/core/registry/harnesses.json +196 -10
  50. package/core/registry/lifecycle.json +2626 -7
  51. package/core/registry/storage.json +110 -83
  52. package/core/registry/verification.json +1 -1
  53. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
  54. package/core/shared/scripts/doflow/bin/doflow-run +10 -6
  55. package/core/shared/skills/do-design/SKILL.md +6 -6
  56. package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
  57. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
  58. package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
  59. package/core/shared/skills/do-implement/SKILL.md +3 -2
  60. package/core/shared/skills/do-plan/SKILL.md +7 -8
  61. package/package.json +4 -2
  62. package/src/adapters/antigravity/{index.ts → index.js} +141 -105
  63. package/src/adapters/claude/{index.ts → index.js} +63 -49
  64. package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
  65. package/src/adapters/codex/{agents.ts → agents.js} +24 -24
  66. package/src/adapters/codex/{config.ts → config.js} +49 -49
  67. package/src/adapters/codex/{hooks.ts → hooks.js} +62 -27
  68. package/src/adapters/codex/{index.ts → index.js} +148 -105
  69. package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
  70. package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
  71. package/src/adapters/copilot/{index.ts → index.js} +42 -42
  72. package/src/adapters/{copy-tree.ts → copy-tree.js} +74 -74
  73. package/src/adapters/gemini/{hooks.ts → hooks.js} +78 -37
  74. package/src/adapters/gemini/{index.ts → index.js} +96 -57
  75. package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
  76. package/src/adapters/{index.ts → index.js} +13 -13
  77. package/src/adapters/kiro/{index.ts → index.js} +49 -35
  78. package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
  79. package/src/adapters/opencode/{index.ts → index.js} +52 -52
  80. package/src/adapters/pi/hooks.js +119 -0
  81. package/src/adapters/pi/{index.ts → index.js} +64 -39
  82. package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
  83. package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
  84. package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
  85. package/src/cli/commands/{install.ts → install.js} +25 -25
  86. package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
  87. package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
  88. package/src/cli/commands/{remove.ts → remove.js} +10 -10
  89. package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
  90. package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
  91. package/src/cli/commands/{status.ts → status.js} +24 -24
  92. package/src/cli/commands/{tools.ts → tools.js} +16 -16
  93. package/src/cli/commands/{update.ts → update.js} +20 -20
  94. package/src/cli/{index.ts → index.js} +43 -31
  95. package/src/cli/{runtime-commands.ts → runtime-commands.js} +97 -58
  96. package/src/cli/{shared.ts → shared.js} +40 -40
  97. package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
  98. package/src/helper/{git.ts → git.js} +2 -2
  99. package/src/helper/{guardrails.ts → guardrails.js} +10 -10
  100. package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
  101. package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
  102. package/src/helper/{prompt.ts → prompt.js} +5 -5
  103. package/src/helper/{repo-root.ts → repo-root.js} +2 -2
  104. package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
  105. package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
  106. package/src/helper/{toml.ts → toml.js} +48 -48
  107. package/src/install/{backup-set.ts → backup-set.js} +16 -16
  108. package/src/install/{backup.ts → backup.js} +65 -65
  109. package/src/install/{context.ts → context.js} +15 -15
  110. package/src/install/{manifest.ts → manifest.js} +27 -27
  111. package/src/install/{mcp.ts → mcp.js} +30 -30
  112. package/src/install/{paths.ts → paths.js} +15 -15
  113. package/src/install/{targets.ts → targets.js} +10 -10
  114. package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
  115. package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
  116. package/src/lifecycle/coverage.js +57 -0
  117. package/src/lifecycle/hook-step.js +61 -0
  118. package/src/lifecycle/{index.ts → index.js} +160 -146
  119. package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
  120. package/src/lifecycle/{policies.ts → policies.js} +11 -11
  121. package/src/lifecycle/{view.ts → view.js} +28 -28
  122. package/src/registry/{index.ts → index.js} +195 -66
  123. package/src/release/build-package.js +91 -0
  124. package/src/release/changelog-section.js +70 -0
  125. package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
  126. package/src/runtime/advice/{cli.ts → cli.js} +31 -31
  127. package/src/runtime/advice/{client.ts → client.js} +44 -44
  128. package/src/runtime/advice/{config.ts → config.js} +16 -16
  129. package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
  130. package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
  131. package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
  132. package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
  133. package/src/runtime/advice/{record.ts → record.js} +19 -19
  134. package/src/runtime/advice/{registry.ts → registry.js} +16 -16
  135. package/src/runtime/advice/{strength.ts → strength.js} +7 -7
  136. package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
  137. package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
  138. package/src/runtime/candidate.js +505 -0
  139. package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
  140. package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
  141. package/src/runtime/{claims.ts → claims.js} +44 -35
  142. package/src/runtime/{cli-result.ts → cli-result.js} +15 -8
  143. package/src/runtime/{cli.ts → cli.js} +59 -292
  144. package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
  145. package/src/runtime/context/budget.js +203 -0
  146. package/src/runtime/context/compose.js +1367 -0
  147. package/src/runtime/context/delivery-store.js +102 -0
  148. package/src/runtime/context/mandatory.js +594 -0
  149. package/src/runtime/context/pointer.js +142 -0
  150. package/src/runtime/context-pack.js +1277 -0
  151. package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
  152. package/src/runtime/evidence-fields.js +39 -0
  153. package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +32 -25
  154. package/src/runtime/failure/{capture.ts → capture.js} +29 -29
  155. package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
  156. package/src/runtime/failure/{cli.ts → cli.js} +30 -30
  157. package/src/runtime/failure/{home.ts → home.js} +6 -6
  158. package/src/runtime/failure/{store.ts → store.js} +32 -32
  159. package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
  160. package/src/runtime/{freshness.ts → freshness.js} +59 -22
  161. package/src/runtime/{health.ts → health.js} +72 -72
  162. package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
  163. package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
  164. package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
  165. package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
  166. package/src/runtime/inventory/{index.ts → index.js} +36 -36
  167. package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
  168. package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
  169. package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
  170. package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
  171. package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
  172. package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
  173. package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
  174. package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
  175. package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
  176. package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
  177. package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
  178. package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
  179. package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
  180. package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
  181. package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
  182. package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
  183. package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
  184. package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
  185. package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
  186. package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
  187. package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
  188. package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
  189. package/src/runtime/{mask.ts → mask.js} +22 -22
  190. package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
  191. package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
  192. package/src/runtime/{model-router.ts → model-router.js} +20 -20
  193. package/src/runtime/{outcome.ts → outcome.js} +27 -27
  194. package/src/runtime/{reach.ts → reach.js} +21 -21
  195. package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
  196. package/src/runtime/{readiness.ts → readiness.js} +113 -43
  197. package/src/runtime/{recovery.ts → recovery.js} +15 -15
  198. package/src/runtime/{research-request.ts → research-request.js} +34 -34
  199. package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +453 -104
  200. package/src/runtime/retrieval-record.js +223 -0
  201. package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +22 -19
  202. package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
  203. package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
  204. package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
  205. package/src/runtime/source-version.js +289 -0
  206. package/src/runtime/storage/{cli.ts → cli.js} +9 -9
  207. package/src/runtime/storage/{engine.ts → engine.js} +37 -37
  208. package/src/runtime/storage/{registry.ts → registry.js} +19 -19
  209. package/src/runtime/storage/{roots.ts → roots.js} +8 -8
  210. package/src/runtime/storage/{rules.ts → rules.js} +26 -26
  211. package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
  212. package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
  213. package/src/runtime/{task-attempts.ts → task-attempts.js} +62 -62
  214. package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
  215. package/src/runtime/{task-scope.ts → task-scope.js} +40 -15
  216. package/src/runtime/{task-state.ts → task-state.js} +86 -21
  217. package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
  218. package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
  219. package/src/runtime/trace/{render.ts → render.js} +7 -7
  220. package/src/runtime/trace/{views.ts → views.js} +40 -40
  221. package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
  222. package/src/runtime/verification/{engine.ts → engine.js} +143 -143
  223. package/src/runtime/verification/{registry.ts → registry.js} +6 -6
  224. package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
  225. package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
  226. package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
  227. package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
  228. package/src/runtime/{worktree.ts → worktree.js} +31 -31
  229. package/src/state/{index.ts → index.js} +26 -26
  230. package/src/state/{lockfile.ts → lockfile.js} +20 -20
  231. package/src/state/{run-lock.ts → run-lock.js} +44 -44
  232. package/core/.antigravity-plugin/hooks.json +0 -39
  233. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  234. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  235. package/core/harnesses/shared/hooks/hooks.json +0 -47
  236. package/src/runtime/context-pack.ts +0 -492
@@ -28,33 +28,33 @@
28
28
  //
29
29
  // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
30
30
  // native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
31
- // caller's `require('../helper/harness-paths.ts')` keeps working while the tree is still `.js`. The
31
+ // caller's `require('../helper/harness-paths.js')` keeps working while the tree is still `.js`. The
32
32
  // `./repo-root` require stays bare while repo-root is still `.js`.
33
33
 
34
34
  const fs = require('node:fs');
35
35
  const path = require('node:path');
36
- const { REPO_ROOT } = require('./repo-root.ts');
36
+ const { REPO_ROOT } = require('./repo-root.js');
37
37
 
38
38
  const PATH_BASES = new Set(['root', 'home']);
39
39
  const SURFACE_NAME = /^[a-z][a-zA-Z0-9]*$/;
40
40
  const SEGMENT = /^(?!\.\.$)[^\\/]+$/;
41
41
  const SCOPES = ['project', 'user'];
42
42
 
43
- interface PathRule {
44
- base?: string;
45
- segments?: string[];
46
- }
43
+
44
+
45
+
46
+
47
47
 
48
- interface ResolveInputs {
49
- scope?: string;
50
- scopeRoot?: string;
51
- homeDir?: string;
52
- }
48
+
49
+
50
+
51
+
52
+
53
53
 
54
- function issue(errors: string[], location: string, message: string): void { errors.push(`${location}: ${message}`); }
54
+ function issue(errors , location , message ) { errors.push(`${location}: ${message}`); }
55
55
 
56
56
  /** Validate one {base, segments} rule. Returns errors via the pushed array. */
57
- function validateRule(rule: any, location: string, errors: string[]): void {
57
+ function validateRule(rule , location , errors ) {
58
58
  if (!rule || typeof rule !== 'object' || Array.isArray(rule)) {
59
59
  issue(errors, location, 'must be an object'); return;
60
60
  }
@@ -63,7 +63,7 @@ function validateRule(rule: any, location: string, errors: string[]): void {
63
63
  }
64
64
  if (!PATH_BASES.has(rule.base)) issue(errors, location, `base must be one of ${[...PATH_BASES].join(', ')}`);
65
65
  const segments = rule.segments;
66
- if (!Array.isArray(segments) || segments.some((segment: any) => typeof segment !== 'string' || !SEGMENT.test(segment))) {
66
+ if (!Array.isArray(segments) || segments.some((segment ) => typeof segment !== 'string' || !SEGMENT.test(segment))) {
67
67
  issue(errors, location, "segments must be an array of plain single path segments (no '/', no '\\', no '..')");
68
68
  }
69
69
  }
@@ -71,12 +71,12 @@ function validateRule(rule: any, location: string, errors: string[]): void {
71
71
  /** Validate one harness's whole paths section. Unknown keys and malformed rules are rejected
72
72
  * loudly: a typo'd declaration must fail the registry load, not silently fall back to whatever
73
73
  * the adapter used to hardcode. Returns an array of error messages (empty when valid). */
74
- function validatePathsSection(section: any, location: string): string[] {
75
- const errors: string[] = [];
74
+ function validatePathsSection(section , location ) {
75
+ const errors = [];
76
76
  if (!section || typeof section !== 'object' || Array.isArray(section)) {
77
77
  return [`${location}: must be an object`];
78
78
  }
79
- for (const [name, value] of Object.entries<any>(section)) {
79
+ for (const [name, value] of Object.entries (section)) {
80
80
  const at = `${location}.${name}`;
81
81
  if (!SURFACE_NAME.test(name)) { issue(errors, at, `surface name '${name}' must be camelCase`); continue; }
82
82
  if (!value || typeof value !== 'object' || Array.isArray(value)) { issue(errors, at, 'must be an object'); continue; }
@@ -98,7 +98,7 @@ function validatePathsSection(section: any, location: string): string[] {
98
98
  }
99
99
 
100
100
  /** Resolve one rule to an absolute path. */
101
- function resolveRule(rule: PathRule, { root, home }: { root: string; home: string }): string {
101
+ function resolveRule(rule , { root, home } ) {
102
102
  const base = rule.base === 'home' ? home : root;
103
103
  return path.join(base, ...(rule.segments || []));
104
104
  }
@@ -109,12 +109,12 @@ function resolveRule(rule: PathRule, { root, home }: { root: string; home: strin
109
109
  * Returns { <surface>: absolutePath | null } — null when the scope has no rule for a surface —
110
110
  * plus `root`, the scope's install root itself (an input, not a declaration).
111
111
  */
112
- function resolveHarnessPaths(section: any, { scope, scopeRoot, homeDir }: ResolveInputs = {}): Record<string, string | null> {
112
+ function resolveHarnessPaths(section , { scope, scopeRoot, homeDir } = {}) {
113
113
  const userScope = scope === 'user' || scope === 'global';
114
114
  const home = path.resolve(homeDir || scopeRoot);
115
115
  const root = userScope ? home : path.resolve(scopeRoot);
116
- const resolved: Record<string, string | null> = { root };
117
- for (const [name, value] of Object.entries<any>(section || {})) {
116
+ const resolved = { root };
117
+ for (const [name, value] of Object.entries (section || {})) {
118
118
  const rule = (value.base !== undefined || value.segments !== undefined)
119
119
  ? value
120
120
  : (value[userScope ? 'user' : 'project'] ?? null);
@@ -123,16 +123,16 @@ function resolveHarnessPaths(section: any, { scope, scopeRoot, homeDir }: Resolv
123
123
  return resolved;
124
124
  }
125
125
 
126
- let declaredCache: { repoRoot: string; paths: Record<string, any> } | undefined;
126
+ let declaredCache ;
127
127
  /** Every harness's declared paths section, keyed by harness id, read straight from
128
128
  * core/registry/harnesses.json (memoized per process — the file ships inside the package).
129
129
  * Adapters default their factories to this, so a factory called with no arguments and the CLI
130
130
  * wiring in src/cli/shared.js#buildAdapterRegistry consume byte-identical declarations. */
131
- function declaredHarnessPaths(repoRoot: string = REPO_ROOT): Record<string, any> {
131
+ function declaredHarnessPaths(repoRoot = REPO_ROOT) {
132
132
  if (declaredCache && declaredCache.repoRoot === path.resolve(repoRoot)) return declaredCache.paths;
133
133
  const file = path.join(path.resolve(repoRoot), 'core', 'registry', 'harnesses.json');
134
134
  const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
135
- const paths = Object.fromEntries((parsed.harnesses || []).map((harness: any) => [harness.id, harness.paths ?? {}]));
135
+ const paths = Object.fromEntries((parsed.harnesses || []).map((harness ) => [harness.id, harness.paths ?? {}]));
136
136
  declaredCache = { repoRoot: path.resolve(repoRoot), paths };
137
137
  return paths;
138
138
  }
@@ -5,7 +5,7 @@
5
5
  //
6
6
  // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
7
7
  // native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
8
- // caller's `require('../helper/marker-merge.ts')` keeps working while the tree is still `.js`.
8
+ // caller's `require('../helper/marker-merge.js')` keeps working while the tree is still `.js`.
9
9
  const fs = require('node:fs');
10
10
  const path = require('node:path');
11
11
 
@@ -17,14 +17,14 @@ const MARKER_END = '<!-- doflow:end -->';
17
17
  * Merge `srcAbs`'s content into `dstAbs` inside a doflow-owned marker span,
18
18
  * preserving any of `dstAbs`'s own content outside that span byte-for-byte.
19
19
  */
20
- function mergeMarkedSection(srcAbs: string, dstAbs: string, { dryRun = false }: { dryRun?: boolean } = {}): { changed: boolean } {
20
+ function mergeMarkedSection(srcAbs , dstAbs , { dryRun = false } = {}) {
21
21
  const srcContent = fs.readFileSync(srcAbs, 'utf8').replace(/\s+$/, '');
22
22
  const section = `${MARKER_START}\n${srcContent}\n${MARKER_END}\n`;
23
23
 
24
24
  // Read once, reused both for parsing and for the final no-op comparison below.
25
25
  const existing = fs.existsSync(dstAbs) ? fs.readFileSync(dstAbs, 'utf8') : null;
26
26
 
27
- let newContent: string;
27
+ let newContent ;
28
28
  if (existing === null) {
29
29
  newContent = section;
30
30
  } else {
@@ -90,7 +90,7 @@ function mergeMarkedSection(srcAbs: string, dstAbs: string, { dryRun = false }:
90
90
  * @returns whether a managed span was found and stripped (false if the file doesn't
91
91
  * exist or has no doflow markers — both are no-ops, not errors)
92
92
  */
93
- function removeMarkedSection(file: string): boolean {
93
+ function removeMarkedSection(file ) {
94
94
  if (!fs.existsSync(file)) return false;
95
95
  const existing = fs.readFileSync(file, 'utf8');
96
96
  const start = existing.indexOf(MARKER_START);
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
5
5
  // native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
6
- // caller's `require('../helper/prompt.ts')` keeps working while the rest of the tree is still `.js`.
6
+ // caller's `require('../helper/prompt.js')` keeps working while the rest of the tree is still `.js`.
7
7
  const fs = require('node:fs');
8
8
 
9
9
  /**
@@ -15,12 +15,12 @@ const fs = require('node:fs');
15
15
  * done when stdin is actually a TTY (a human might still type) and bounded by a deadline (a fd
16
16
  * that will genuinely never produce data must still fail closed, not hang forever).
17
17
  */
18
- function readSyncBlocking(fd: number, buf: Buffer): number {
18
+ function readSyncBlocking(fd , buf ) {
19
19
  const deadline = Date.now() + 5 * 60 * 1000;
20
20
  for (;;) {
21
21
  try {
22
22
  return fs.readSync(fd, buf, 0, buf.length, null);
23
- } catch (e: any) {
23
+ } catch (e ) {
24
24
  if (!process.stdin.isTTY || e.code !== 'EAGAIN' || Date.now() > deadline) throw e;
25
25
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
26
26
  }
@@ -28,7 +28,7 @@ function readSyncBlocking(fd: number, buf: Buffer): number {
28
28
  }
29
29
 
30
30
  /** true if confirmed (via --force or an explicit y/Y reply) */
31
- function confirm(message: string, force: boolean): boolean {
31
+ function confirm(message , force ) {
32
32
  if (force) {
33
33
  console.error(`[INFO] ${message} [auto-confirmed via --force]`);
34
34
  return true;
@@ -46,7 +46,7 @@ function confirm(message: string, force: boolean): boolean {
46
46
  }
47
47
 
48
48
  /** Free-text stdin read (e.g. "which backup id?"), matching sync.sh's `read -r reply`. */
49
- function promptLine(message: string): string {
49
+ function promptLine(message ) {
50
50
  process.stdout.write(message);
51
51
  const buf = Buffer.alloc(1024);
52
52
  let bytesRead = 0;
@@ -21,9 +21,9 @@ const path = require('node:path');
21
21
  *
22
22
  * TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
23
23
  * native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
24
- * caller's `require('../helper/repo-root.ts')` keeps working while the rest of the tree is still
24
+ * caller's `require('../helper/repo-root.js')` keeps working while the rest of the tree is still
25
25
  * `.js`. As the highest-fan-out helper, this is the last T1 leaf converted.
26
26
  */
27
- const REPO_ROOT: string = path.resolve(__dirname, '..', '..');
27
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
28
28
 
29
29
  module.exports = { REPO_ROOT };
@@ -15,36 +15,36 @@
15
15
  //
16
16
  // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
17
17
  // native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
18
- // caller's `require('../helper/settings-merge.ts')` keeps working while the tree is still `.js`.
18
+ // caller's `require('../helper/settings-merge.js')` keeps working while the tree is still `.js`.
19
19
 
20
- interface HookCommand { command?: unknown }
21
- interface HookEntry { matcher?: unknown; hooks?: HookCommand[] }
22
- type Settings = Record<string, any>;
20
+
21
+
22
+
23
23
 
24
24
  /** A hook entry's identity is its (matcher, commands) pair — NOT the commands alone. One script is
25
25
  * legitimately registered under several matchers for the same event (post-edit-lint.sh runs on
26
26
  * both `Edit` and `Write`), so comparing commands only makes those entries indistinguishable and
27
27
  * silently collapses them to one during a merge. Position is not usable: it is not stable across
28
28
  * a re-render. */
29
- function hookCommands(entry: HookEntry | null | undefined): string[] {
30
- return (entry?.hooks || []).map((hook) => hook?.command).filter((command): command is string => typeof command === 'string');
29
+ function hookCommands(entry ) {
30
+ return (entry?.hooks || []).map((hook) => hook?.command).filter((command) => typeof command === 'string');
31
31
  }
32
32
 
33
- function sameHookEntry(a: HookEntry | null | undefined, b: HookEntry | null | undefined): boolean {
33
+ function sameHookEntry(a , b ) {
34
34
  if ((a?.matcher ?? null) !== (b?.matcher ?? null)) return false;
35
35
  const left = hookCommands(a);
36
36
  const right = hookCommands(b);
37
37
  return left.length === right.length && left.every((command, index) => command === right[index]);
38
38
  }
39
39
 
40
- function isPlainObject(value: any): value is Record<string, any> {
40
+ function isPlainObject(value ) {
41
41
  return value !== null && typeof value === 'object' && !Array.isArray(value);
42
42
  }
43
43
 
44
44
  /** Union of managed hook entries into whatever is already registered for an event. Foreign
45
45
  * entries keep their relative order and stay first, so a third-party guard that was already
46
46
  * intercepting a tool keeps intercepting it. */
47
- function mergeHookEvent(existing: HookEntry[] | undefined, managed: HookEntry[] | undefined): HookEntry[] {
47
+ function mergeHookEvent(existing , managed ) {
48
48
  const result = Array.isArray(existing) ? existing.slice() : [];
49
49
  for (const entry of managed || []) {
50
50
  if (!result.some((present) => sameHookEntry(present, entry))) result.push(entry);
@@ -52,9 +52,9 @@ function mergeHookEvent(existing: HookEntry[] | undefined, managed: HookEntry[]
52
52
  return result;
53
53
  }
54
54
 
55
- function mergeHooks(existing: any, managed: any): Record<string, HookEntry[]> {
55
+ function mergeHooks(existing , managed ) {
56
56
  const result = isPlainObject(existing) ? { ...existing } : {};
57
- for (const [event, entries] of Object.entries<any>(managed || {})) {
57
+ for (const [event, entries] of Object.entries (managed || {})) {
58
58
  result[event] = mergeHookEvent(result[event], entries);
59
59
  }
60
60
  return result;
@@ -70,10 +70,10 @@ function mergeHooks(existing: any, managed: any): Record<string, HookEntry[]> {
70
70
  * install is exactly the surprise this module exists to prevent. Managed keys the file does
71
71
  * not have yet are still added, so a fresh install is unchanged.
72
72
  */
73
- function mergeSettings(existing: any, managed: any): Settings {
73
+ function mergeSettings(existing , managed ) {
74
74
  if (!isPlainObject(existing)) return managed;
75
- const result: Settings = { ...existing };
76
- for (const [key, value] of Object.entries<any>(managed || {})) {
75
+ const result = { ...existing };
76
+ for (const [key, value] of Object.entries (managed || {})) {
77
77
  if (key === 'hooks') { result.hooks = mergeHooks(existing.hooks, value); continue; }
78
78
  if (!Object.hasOwn(result, key)) { result[key] = value; continue; }
79
79
  if (isPlainObject(result[key]) && isPlainObject(value)) result[key] = mergeSettings(result[key], value);
@@ -85,15 +85,15 @@ function mergeSettings(existing: any, managed: any): Settings {
85
85
  /** Verification predicate replacing byte-equality: DoFlow's own keys must be present, and every
86
86
  * managed hook entry must still be registered. A foreign key, a user-tuned `model`, or an extra
87
87
  * hook entry are all legitimate and must NOT read as drift. */
88
- function settingsContains(current: any, managed: any): boolean {
88
+ function settingsContains(current , managed ) {
89
89
  if (!isPlainObject(current)) return false;
90
- for (const [key, value] of Object.entries<any>(managed || {})) {
90
+ for (const [key, value] of Object.entries (managed || {})) {
91
91
  if (key === 'hooks') {
92
- for (const [event, entries] of Object.entries<any>(value || {})) {
92
+ for (const [event, entries] of Object.entries (value || {})) {
93
93
  const present = current.hooks?.[event];
94
94
  if (!Array.isArray(present)) return false;
95
95
  for (const entry of entries || []) {
96
- if (!present.some((candidate: HookEntry) => sameHookEntry(candidate, entry))) return false;
96
+ if (!present.some((candidate ) => sameHookEntry(candidate, entry))) return false;
97
97
  }
98
98
  }
99
99
  continue;
@@ -108,15 +108,15 @@ function settingsContains(current: any, managed: any): boolean {
108
108
  * rmSync'd the whole file, which on a shared settings.json would delete the user's entire
109
109
  * own configuration along with DoFlow's contribution. Returns null only when nothing
110
110
  * meaningful remains, letting the caller delete a file DoFlow genuinely created. */
111
- function stripManagedSettings(current: any, managed: any): Settings | null {
111
+ function stripManagedSettings(current , managed ) {
112
112
  if (!isPlainObject(current)) return null;
113
- const result: Settings = { ...current };
113
+ const result = { ...current };
114
114
  const managedHooks = managed?.hooks;
115
115
  if (isPlainObject(result.hooks) && isPlainObject(managedHooks)) {
116
- const hooks: Record<string, HookEntry[]> = {};
117
- for (const [event, entries] of Object.entries<any>(result.hooks)) {
116
+ const hooks = {};
117
+ for (const [event, entries] of Object.entries (result.hooks)) {
118
118
  const remaining = (entries || []).filter(
119
- (entry: HookEntry) => !(managedHooks[event] || []).some((owned: HookEntry) => sameHookEntry(owned, entry)),
119
+ (entry ) => !(managedHooks[event] || []).some((owned ) => sameHookEntry(owned, entry)),
120
120
  );
121
121
  if (remaining.length) hooks[event] = remaining;
122
122
  }
@@ -126,7 +126,7 @@ function stripManagedSettings(current: any, managed: any): Settings | null {
126
126
  // nothing has touched it since. A key the user changed no longer matches and is left alone.
127
127
  // Without this, a file DoFlow created outright (keybindings.json, which has no hooks at all)
128
128
  // would survive `remove` fully intact and read as a resource that refused to go away.
129
- for (const [key, value] of Object.entries<any>(managed || {})) {
129
+ for (const [key, value] of Object.entries (managed || {})) {
130
130
  if (key === 'hooks') continue;
131
131
  if (Object.hasOwn(result, key) && JSON.stringify(result[key]) === JSON.stringify(value)) delete result[key];
132
132
  }
@@ -138,12 +138,12 @@ function stripManagedSettings(current: any, managed: any): Settings | null {
138
138
  * non-hook managed keys are ordinary settings that survive a remove and are indistinguishable
139
139
  * from the user's own, so they cannot signal DoFlow's presence. Lets verification tell "removed"
140
140
  * (none of ours left) apart from "damaged" (some of ours left, some missing). */
141
- function settingsContainsAny(current: any, managed: any): boolean {
141
+ function settingsContainsAny(current , managed ) {
142
142
  if (!isPlainObject(current)) return false;
143
- for (const [event, entries] of Object.entries<any>(managed?.hooks || {})) {
143
+ for (const [event, entries] of Object.entries (managed?.hooks || {})) {
144
144
  const present = current.hooks?.[event];
145
145
  if (!Array.isArray(present)) continue;
146
- if ((entries || []).some((entry: HookEntry) => present.some((candidate: HookEntry) => sameHookEntry(candidate, entry)))) return true;
146
+ if ((entries || []).some((entry ) => present.some((candidate ) => sameHookEntry(candidate, entry)))) return true;
147
147
  }
148
148
  return false;
149
149
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  // settings-scope.js — GLOBAL_HOOK_PREFIX/PROJECT_HOOK_PREFIX are the shared constants the Claude
3
- // adapter's settings-asset renderer (src/adapters/claude/index.ts#settingsContent) uses to rewrite
3
+ // adapter's settings-asset renderer (src/adapters/claude/index.js#settingsContent) uses to rewrite
4
4
  // a project-scoped settings.json's hook `command` paths from the global-only `~/.claude/hooks/`
5
5
  // form to Claude Code's documented ${CLAUDE_PROJECT_DIR}/.claude/hooks/ placeholder — see
6
6
  // https://code.claude.com/docs/en/hooks. chmodHooksExecutable is a defensive final pass: an
@@ -10,7 +10,7 @@
10
10
  //
11
11
  // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
12
12
  // native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
13
- // caller's `require('../helper/settings-scope.ts')` keeps working while the tree is still `.js`.
13
+ // caller's `require('../helper/settings-scope.js')` keeps working while the tree is still `.js`.
14
14
  const fs = require('node:fs');
15
15
  const path = require('node:path');
16
16
 
@@ -19,7 +19,7 @@ const PROJECT_HOOK_PREFIX = '${CLAUDE_PROJECT_DIR}/.claude/hooks/';
19
19
 
20
20
  /** Adds ugo+x (relative — preserves existing bits), not an absolute mode — a hook file copied in
21
21
  * at 664/775 must stay that way, only gaining +x. */
22
- function chmodHooksExecutable(claudeDir: string): void {
22
+ function chmodHooksExecutable(claudeDir ) {
23
23
  const hooksDir = path.join(claudeDir, 'hooks');
24
24
  if (!fs.existsSync(hooksDir)) return;
25
25
  for (const f of fs.readdirSync(hooksDir)) {
@@ -8,37 +8,37 @@
8
8
  //
9
9
  // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
10
10
  // native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
11
- // caller's `require('../helper/toml.ts')` keeps working while the rest of the tree is still `.js`.
11
+ // caller's `require('../helper/toml.js')` keeps working while the rest of the tree is still `.js`.
12
12
 
13
- type TomlScalar = string | number | boolean;
13
+
14
14
 
15
- interface TomlEntry {
16
- value: TomlScalar;
17
- line: number;
18
- table: string;
19
- }
15
+
16
+
17
+
18
+
19
+
20
20
 
21
- interface TomlHeader {
22
- line: number;
23
- table: string;
24
- }
21
+
22
+
23
+
24
+
25
25
 
26
- interface ParsedToml {
27
- lines: string[];
28
- entries: Map<string, TomlEntry>;
29
- headers: TomlHeader[];
30
- }
26
+
27
+
28
+
29
+
30
+
31
31
 
32
- type TableViolationKind = 'duplicate-table' | 'table-and-value';
32
+
33
33
 
34
- interface TableViolation {
35
- kind: TableViolationKind;
36
- table: string;
37
- line: number;
38
- }
34
+
35
+
36
+
37
+
38
+
39
39
 
40
- function stripComment(line: string): string {
41
- let quote: string | null = null;
40
+ function stripComment(line ) {
41
+ let quote = null;
42
42
  let escape = false;
43
43
  for (let i = 0; i < line.length; i++) {
44
44
  const char = line[i];
@@ -52,8 +52,8 @@ function stripComment(line: string): string {
52
52
  return line;
53
53
  }
54
54
 
55
- function splitAssignment(line: string): [string, string] | null {
56
- let quote: string | null = null;
55
+ function splitAssignment(line ) {
56
+ let quote = null;
57
57
  let escape = false;
58
58
  let depth = 0;
59
59
  for (let i = 0; i < line.length; i++) {
@@ -72,8 +72,8 @@ function splitAssignment(line: string): [string, string] | null {
72
72
  return null;
73
73
  }
74
74
 
75
- function balanced(value: string): boolean {
76
- let quote: string | null = null;
75
+ function balanced(value ) {
76
+ let quote = null;
77
77
  let escape = false;
78
78
  let depth = 0;
79
79
  for (const char of value) {
@@ -91,7 +91,7 @@ function balanced(value: string): boolean {
91
91
  return !quote && depth === 0;
92
92
  }
93
93
 
94
- function parseValue(raw: string): TomlScalar {
94
+ function parseValue(raw ) {
95
95
  const value = raw.trim();
96
96
  if (!value || !balanced(value)) throw new Error('invalid TOML value');
97
97
  if (value === 'true') return true;
@@ -116,14 +116,14 @@ const BARE_KEY = /^[A-Za-z0-9_-]+$/;
116
116
  *
117
117
  * A dot *inside* a quoted segment is legal TOML (`"gpt-5.5" = 4`) and is preserved — see
118
118
  * flattenKeyPath for how it is kept from colliding with a genuine path separator. */
119
- function parseKeyPath(raw: string): string[] | null {
120
- const segments: string[] = [];
119
+ function parseKeyPath(raw ) {
120
+ const segments = [];
121
121
  let i = 0;
122
122
  const skipSpace = () => { while (i < raw.length && (raw[i] === ' ' || raw[i] === '\t')) i += 1; };
123
123
  skipSpace();
124
124
  if (i >= raw.length) return null;
125
125
  while (i < raw.length) {
126
- let segment: string;
126
+ let segment ;
127
127
  const quote = raw[i];
128
128
  if (quote === '"' || quote === "'") {
129
129
  i += 1;
@@ -158,18 +158,18 @@ function parseKeyPath(raw: string): string[] | null {
158
158
  * `["a.b"]` and `[a.b]` collide and a surgical edit could rewrite the wrong line. Managed
159
159
  * identities (normaliseDesired) are bare-key-only, so their flattened form is unchanged and
160
160
  * lookups against them still match. */
161
- function flattenKeyPath(segments: string[]): string {
161
+ function flattenKeyPath(segments ) {
162
162
  return segments.map((segment) => segment.replace(/\\/g, '\\\\').replace(/\./g, '\\.')).join('.');
163
163
  }
164
164
 
165
165
  /** A conservative TOML scanner. It validates the subset needed to safely locate scalar keys;
166
166
  * unsupported multi-line syntax fails closed rather than risking a destructive rewrite. `headers`
167
167
  * lists each table header line in file order, so a caller never re-detects headers itself. */
168
- function parseToml(text: string): ParsedToml {
168
+ function parseToml(text ) {
169
169
  const lines = text.split(/\r?\n/);
170
- let table: string[] = [];
171
- const entries = new Map<string, TomlEntry>();
172
- const headers: TomlHeader[] = [];
170
+ let table = [];
171
+ const entries = new Map ();
172
+ const headers = [];
173
173
  for (let index = 0; index < lines.length; index++) {
174
174
  const clean = stripComment(lines[index]).trim();
175
175
  if (!clean) continue;
@@ -188,7 +188,7 @@ function parseToml(text: string): ParsedToml {
188
188
  if (!assignment) throw new Error(`Malformed TOML assignment on line ${index + 1}`);
189
189
  const keyPath = parseKeyPath(assignment[0].trim());
190
190
  if (!keyPath) throw new Error(`Unsupported TOML key on line ${index + 1}`);
191
- let value: TomlScalar;
191
+ let value ;
192
192
  try { value = parseValue(assignment[1]); } catch { throw new Error(`Malformed TOML value on line ${index + 1}`); }
193
193
  const fullKey = flattenKeyPath([...table, ...keyPath]);
194
194
  if (entries.has(fullKey)) throw new Error(`Duplicate TOML key '${fullKey}'`);
@@ -202,33 +202,33 @@ function parseToml(text: string): ParsedToml {
202
202
  * keys already defined it; `table-and-value` when one path is both a key's value and a table (by a
203
203
  * header, or implied by a longer dotted key). `line` is the 1-based line of the second definition.
204
204
  * Throws what parseToml throws. */
205
- function tableViolations(text: string): TableViolation[] {
205
+ function tableViolations(text ) {
206
206
  parseToml(text);
207
207
  const lines = text.split(/\r?\n/);
208
- const violations: TableViolation[] = [];
209
- const opened = new Set<string>();
210
- const dotted = new Set<string>();
211
- const tables = new Set<string>();
212
- const values = new Set<string>();
213
- const asTable = (segments: string[], line: number) => {
208
+ const violations = [];
209
+ const opened = new Set ();
210
+ const dotted = new Set ();
211
+ const tables = new Set ();
212
+ const values = new Set ();
213
+ const asTable = (segments , line ) => {
214
214
  const flat = flattenKeyPath(segments);
215
215
  if (values.has(flat)) violations.push({ kind: 'table-and-value', table: flat, line });
216
216
  tables.add(flat);
217
217
  };
218
- let table: string[] = [];
218
+ let table = [];
219
219
  for (let index = 0; index < lines.length; index++) {
220
220
  const clean = stripComment(lines[index]).trim();
221
221
  if (!clean) continue;
222
222
  const tableMatch = clean.match(/^\[(.+)\]$/);
223
223
  if (tableMatch) {
224
- table = parseKeyPath(tableMatch[1].trim())!;
224
+ table = parseKeyPath(tableMatch[1].trim()) ;
225
225
  const flat = flattenKeyPath(table);
226
226
  if (opened.has(flat) || dotted.has(flat)) violations.push({ kind: 'duplicate-table', table: flat, line: index + 1 });
227
227
  opened.add(flat);
228
228
  for (let length = 1; length <= table.length; length++) asTable(table.slice(0, length), index + 1);
229
229
  continue;
230
230
  }
231
- const keyPath = parseKeyPath(splitAssignment(clean)![0].trim())!;
231
+ const keyPath = parseKeyPath(splitAssignment(clean) [0].trim()) ;
232
232
  for (let length = 1; length < keyPath.length; length++) {
233
233
  const segments = [...table, ...keyPath.slice(0, length)];
234
234
  dotted.add(flattenKeyPath(segments));