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

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 (167) hide show
  1. package/bin/doflow.js +2 -2
  2. package/core/.claude-plugin/plugin.json +1 -1
  3. package/core/.codex-plugin/plugin.json +1 -1
  4. package/core/.plugin/plugin.json +1 -1
  5. package/core/registry/storage.json +87 -83
  6. package/core/registry/verification.json +1 -1
  7. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
  8. package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
  9. package/package.json +4 -2
  10. package/src/adapters/antigravity/{index.ts → index.js} +60 -60
  11. package/src/adapters/claude/{index.ts → index.js} +48 -48
  12. package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
  13. package/src/adapters/codex/{agents.ts → agents.js} +24 -24
  14. package/src/adapters/codex/{config.ts → config.js} +49 -49
  15. package/src/adapters/codex/{hooks.ts → hooks.js} +23 -23
  16. package/src/adapters/codex/{index.ts → index.js} +98 -98
  17. package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
  18. package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
  19. package/src/adapters/copilot/{index.ts → index.js} +42 -42
  20. package/src/adapters/{copy-tree.ts → copy-tree.js} +70 -70
  21. package/src/adapters/gemini/{hooks.ts → hooks.js} +24 -24
  22. package/src/adapters/gemini/{index.ts → index.js} +41 -41
  23. package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
  24. package/src/adapters/{index.ts → index.js} +13 -13
  25. package/src/adapters/kiro/{index.ts → index.js} +34 -34
  26. package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
  27. package/src/adapters/opencode/{index.ts → index.js} +52 -52
  28. package/src/adapters/pi/{index.ts → index.js} +30 -30
  29. package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
  30. package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
  31. package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
  32. package/src/cli/commands/{install.ts → install.js} +24 -24
  33. package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
  34. package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
  35. package/src/cli/commands/{remove.ts → remove.js} +10 -10
  36. package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
  37. package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
  38. package/src/cli/commands/{status.ts → status.js} +24 -24
  39. package/src/cli/commands/{tools.ts → tools.js} +16 -16
  40. package/src/cli/commands/{update.ts → update.js} +19 -19
  41. package/src/cli/{index.ts → index.js} +26 -26
  42. package/src/cli/{runtime-commands.ts → runtime-commands.js} +48 -48
  43. package/src/cli/{shared.ts → shared.js} +40 -40
  44. package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
  45. package/src/helper/{git.ts → git.js} +2 -2
  46. package/src/helper/{guardrails.ts → guardrails.js} +10 -10
  47. package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
  48. package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
  49. package/src/helper/{prompt.ts → prompt.js} +5 -5
  50. package/src/helper/{repo-root.ts → repo-root.js} +2 -2
  51. package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
  52. package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
  53. package/src/helper/{toml.ts → toml.js} +48 -48
  54. package/src/install/{backup-set.ts → backup-set.js} +16 -16
  55. package/src/install/{backup.ts → backup.js} +65 -65
  56. package/src/install/{context.ts → context.js} +15 -15
  57. package/src/install/{manifest.ts → manifest.js} +27 -27
  58. package/src/install/{mcp.ts → mcp.js} +30 -30
  59. package/src/install/{paths.ts → paths.js} +15 -15
  60. package/src/install/{targets.ts → targets.js} +10 -10
  61. package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
  62. package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
  63. package/src/lifecycle/{index.ts → index.js} +140 -140
  64. package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
  65. package/src/lifecycle/{policies.ts → policies.js} +11 -11
  66. package/src/lifecycle/{view.ts → view.js} +28 -28
  67. package/src/registry/{index.ts → index.js} +60 -60
  68. package/src/release/build-package.js +91 -0
  69. package/src/release/changelog-section.js +70 -0
  70. package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
  71. package/src/runtime/advice/{cli.ts → cli.js} +31 -31
  72. package/src/runtime/advice/{client.ts → client.js} +44 -44
  73. package/src/runtime/advice/{config.ts → config.js} +16 -16
  74. package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
  75. package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
  76. package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
  77. package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
  78. package/src/runtime/advice/{record.ts → record.js} +19 -19
  79. package/src/runtime/advice/{registry.ts → registry.js} +16 -16
  80. package/src/runtime/advice/{strength.ts → strength.js} +7 -7
  81. package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
  82. package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
  83. package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
  84. package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
  85. package/src/runtime/{claims.ts → claims.js} +30 -30
  86. package/src/runtime/{cli-result.ts → cli-result.js} +4 -4
  87. package/src/runtime/{cli.ts → cli.js} +37 -37
  88. package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
  89. package/src/runtime/{context-pack.ts → context-pack.js} +20 -20
  90. package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
  91. package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +25 -25
  92. package/src/runtime/failure/{capture.ts → capture.js} +29 -29
  93. package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
  94. package/src/runtime/failure/{cli.ts → cli.js} +30 -30
  95. package/src/runtime/failure/{home.ts → home.js} +6 -6
  96. package/src/runtime/failure/{store.ts → store.js} +32 -32
  97. package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
  98. package/src/runtime/{freshness.ts → freshness.js} +15 -15
  99. package/src/runtime/{health.ts → health.js} +72 -72
  100. package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
  101. package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
  102. package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
  103. package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
  104. package/src/runtime/inventory/{index.ts → index.js} +36 -36
  105. package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
  106. package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
  107. package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
  108. package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
  109. package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
  110. package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
  111. package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
  112. package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
  113. package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
  114. package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
  115. package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
  116. package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
  117. package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
  118. package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
  119. package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
  120. package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
  121. package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
  122. package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
  123. package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
  124. package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
  125. package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
  126. package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
  127. package/src/runtime/{mask.ts → mask.js} +22 -22
  128. package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
  129. package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
  130. package/src/runtime/{model-router.ts → model-router.js} +20 -20
  131. package/src/runtime/{outcome.ts → outcome.js} +27 -27
  132. package/src/runtime/{reach.ts → reach.js} +21 -21
  133. package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
  134. package/src/runtime/{readiness.ts → readiness.js} +35 -35
  135. package/src/runtime/{recovery.ts → recovery.js} +15 -15
  136. package/src/runtime/{research-request.ts → research-request.js} +34 -34
  137. package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +40 -40
  138. package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +19 -19
  139. package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
  140. package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
  141. package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
  142. package/src/runtime/storage/{cli.ts → cli.js} +9 -9
  143. package/src/runtime/storage/{engine.ts → engine.js} +37 -37
  144. package/src/runtime/storage/{registry.ts → registry.js} +19 -19
  145. package/src/runtime/storage/{roots.ts → roots.js} +8 -8
  146. package/src/runtime/storage/{rules.ts → rules.js} +26 -26
  147. package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
  148. package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
  149. package/src/runtime/{task-attempts.ts → task-attempts.js} +61 -61
  150. package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
  151. package/src/runtime/{task-scope.ts → task-scope.js} +13 -13
  152. package/src/runtime/{task-state.ts → task-state.js} +15 -15
  153. package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
  154. package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
  155. package/src/runtime/trace/{render.ts → render.js} +7 -7
  156. package/src/runtime/trace/{views.ts → views.js} +40 -40
  157. package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
  158. package/src/runtime/verification/{engine.ts → engine.js} +143 -143
  159. package/src/runtime/verification/{registry.ts → registry.js} +6 -6
  160. package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
  161. package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
  162. package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
  163. package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
  164. package/src/runtime/{worktree.ts → worktree.js} +31 -31
  165. package/src/state/{index.ts → index.js} +26 -26
  166. package/src/state/{lockfile.ts → lockfile.js} +20 -20
  167. package/src/state/{run-lock.ts → run-lock.js} +44 -44
@@ -58,9 +58,9 @@
58
58
 
59
59
  const nodeFs = require('node:fs');
60
60
  const path = require('node:path');
61
- const { finishRuntime } = require('../cli-result.ts');
62
- const { resolveActiveFeature } = require('../feature-resolve.ts');
63
- const { parseRequirement, parseDesign, parsePlan } = require('./artifacts.ts');
61
+ const { finishRuntime } = require('../cli-result.js');
62
+ const { resolveActiveFeature } = require('../feature-resolve.js');
63
+ const { parseRequirement, parseDesign, parsePlan } = require('./artifacts.js');
64
64
  const {
65
65
  EXTENSION_LANGUAGE,
66
66
  PROSE_EXTENSIONS,
@@ -71,14 +71,14 @@ const {
71
71
  snake,
72
72
  EMITTERS,
73
73
  repoLanguage,
74
- } = require('./languages.ts');
74
+ } = require('./languages.js');
75
75
  const {
76
76
  FINGERPRINT_MARKER,
77
77
  FINGERPRINT_RE,
78
78
  sha256,
79
79
  compose,
80
80
  inspectExisting,
81
- } = require('./fingerprint.ts');
81
+ } = require('./fingerprint.js');
82
82
 
83
83
  // `handleScaffoldCommand` reads through the plain `fs` binding it had when it lived in bin/doflow.js;
84
84
  // aliased to the `node:fs` module this file already imports as `nodeFs`.
@@ -102,7 +102,7 @@ const MANIFEST_NAME = 'MANIFEST.md';
102
102
  * One suffix on everything rather than a per-toolchain exclusion list, because the invariant worth
103
103
  * having is stronger than "no runner we currently know about picks this up": nothing in the
104
104
  * scaffold is loadable by any toolchain until a human deliberately drops the suffix. The mirrored
105
- * layout stays legible either way — `src/runtime/scaffold/generate.ts.stub` reads as exactly what it is.
105
+ * layout stays legible either way — `src/runtime/scaffold/generate.js.stub` reads as exactly what it is.
106
106
  *
107
107
  * Part 2's `contracts/` frames deliberately do not carry it: `default/` exists to be compiled
108
108
  * against, and its tree contains no directory a test runner sweeps.
@@ -111,16 +111,16 @@ const STUB_SUFFIX = '.stub';
111
111
 
112
112
  /** The three chain artifacts, in the order a reader meets them. All three are required: a scaffold
113
113
  * missing one of them is a scaffold with no traceability, no shape, or no file list. */
114
- const ARTIFACTS: readonly string[] = Object.freeze(['requirement.md', 'design.md', 'plan.md']);
114
+ const ARTIFACTS = Object.freeze(['requirement.md', 'design.md', 'plan.md']);
115
115
 
116
116
  /**
117
117
  * Uniform exit codes, matching design §4.2's contract for every DoFlow verb.
118
118
  * `1` is "a finding the caller must act on" — which includes an unparseable artifact, so a caller
119
119
  * that only checks for a crash still sees the difference between a full and a partial scaffold.
120
120
  */
121
- const EXIT: Readonly<Record<string, number>> = Object.freeze({ OK: 0, FINDING: 1, USAGE: 2 });
121
+ const EXIT = Object.freeze({ OK: 0, FINDING: 1, USAGE: 2 });
122
122
 
123
- const STATUS: Readonly<Record<string, string>> = Object.freeze({
123
+ const STATUS = Object.freeze({
124
124
  COMPLETE: 'COMPLETE',
125
125
  INCOMPLETE: 'INCOMPLETE',
126
126
  BLOCKED: 'BLOCKED',
@@ -136,16 +136,16 @@ const STATUS: Readonly<Record<string, string>> = Object.freeze({
136
136
  // `scaffold-languages.js` and `scaffold-artifacts.js` respectively — required above. What is left
137
137
  // here is generic and used only by the generator below.
138
138
 
139
- function readIfPresent(file: string, fsImpl: any): string | null {
139
+ function readIfPresent(file , fsImpl ) {
140
140
  try {
141
141
  return fsImpl.readFileSync(file, 'utf8');
142
- } catch (error: any) {
142
+ } catch (error ) {
143
143
  if (error && ['ENOENT', 'ENOTDIR', 'EISDIR'].includes(error.code)) return null;
144
144
  throw error;
145
145
  }
146
146
  }
147
147
 
148
- function isDirectory(file: string, fsImpl: any): boolean {
148
+ function isDirectory(file , fsImpl ) {
149
149
  try {
150
150
  return fsImpl.statSync(file).isDirectory();
151
151
  } catch {
@@ -154,11 +154,11 @@ function isDirectory(file: string, fsImpl: any): boolean {
154
154
  }
155
155
 
156
156
  /** Table cells are pipe-delimited; an unescaped pipe in a task description would shear the row. */
157
- const cell = (text: any): string => String(text ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ').trim();
157
+ const cell = (text ) => String(text ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ').trim();
158
158
 
159
159
  /** Sorts by a string key without depending on locale collation, which varies by platform and would
160
160
  * make "re-running produces no diff" false across machines. */
161
- const byKey = (key: (a: any) => any) => (a: any, b: any) => (key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0);
161
+ const byKey = (key ) => (a , b ) => (key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0);
162
162
 
163
163
  // ---------------------------------------------------------------------------------------------
164
164
  // Generator
@@ -178,10 +178,10 @@ const byKey = (key: (a: any) => any) => (a: any, b: any) => (key(a) < key(b) ? -
178
178
  * @param {Object} fsImpl
179
179
  * @returns {{ sources: Array<Object>, notEvaluated: Array<Object>, texts: Object }}
180
180
  */
181
- function readArtifacts(featureDir: string, fsImpl: any): { sources: any[]; notEvaluated: any[]; texts: Record<string, string | null> } {
182
- const sources: any[] = [];
183
- const notEvaluated: any[] = [];
184
- const texts: Record<string, string | null> = {};
181
+ function readArtifacts(featureDir , fsImpl ) {
182
+ const sources = [];
183
+ const notEvaluated = [];
184
+ const texts = {};
185
185
  for (const name of ARTIFACTS) {
186
186
  const text = readIfPresent(path.join(featureDir, name), fsImpl);
187
187
  texts[name] = text;
@@ -199,11 +199,11 @@ function readArtifacts(featureDir: string, fsImpl: any): { sources: any[]; notEv
199
199
  * @param {Object} texts keyed by artifact name, from `readArtifacts`
200
200
  * @returns {{ requirement: Object, design: Object, plan: Object, notEvaluated: Array<Object> }}
201
201
  */
202
- function parseArtifacts(texts: Record<string, string | null>): { requirement: any; design: any; plan: any; notEvaluated: any[] } {
202
+ function parseArtifacts(texts ) {
203
203
  const requirement = texts['requirement.md'] ? parseRequirement(texts['requirement.md']) : { requirements: new Map(), criteria: [], gaps: [] };
204
204
  const design = texts['design.md'] ? parseDesign(texts['design.md']) : { components: new Map(), gaps: [] };
205
205
  const plan = texts['plan.md'] ? parsePlan(texts['plan.md']) : { tasks: [], gaps: [] };
206
- const notEvaluated: any[] = [];
206
+ const notEvaluated = [];
207
207
  for (const gap of [...requirement.gaps, ...design.gaps, ...plan.gaps]) {
208
208
  notEvaluated.push({ what: `${gap.artifact} → ${gap.what}`, why: gap.why });
209
209
  }
@@ -217,27 +217,27 @@ function parseArtifacts(texts: Record<string, string | null>): { requirement: an
217
217
  * @param {Object} design from `parseArtifacts`
218
218
  * @returns {{ requirementsForStory: Map, componentsForRequirement: Map, traceTask: Function }}
219
219
  */
220
- function buildTraceabilityIndex(requirement: any, design: any): { requirementsForStory: Map<string, any[]>; componentsForRequirement: Map<string, any[]>; traceTask: (task: any) => any } {
221
- const requirementsForStory = new Map<string, any[]>();
220
+ function buildTraceabilityIndex(requirement , design ) {
221
+ const requirementsForStory = new Map ();
222
222
  for (const req of requirement.requirements.values()) {
223
223
  for (const story of req.stories) {
224
224
  if (!requirementsForStory.has(story)) requirementsForStory.set(story, []);
225
- requirementsForStory.get(story)!.push(req);
225
+ requirementsForStory.get(story) .push(req);
226
226
  }
227
227
  }
228
- const componentsForRequirement = new Map<string, any[]>();
228
+ const componentsForRequirement = new Map ();
229
229
  for (const component of design.components.values()) {
230
230
  for (const id of component.serves) {
231
231
  if (!componentsForRequirement.has(id)) componentsForRequirement.set(id, []);
232
- componentsForRequirement.get(id)!.push(component);
232
+ componentsForRequirement.get(id) .push(component);
233
233
  }
234
234
  }
235
235
 
236
- function traceTask(task: any): any {
237
- const reqs = [...new Set(task.stories.flatMap((s: string) => requirementsForStory.get(s) || []))]
238
- .sort(byKey((r: any) => r.id));
239
- const comps = [...new Set(reqs.flatMap((r: any) => componentsForRequirement.get(r.id) || []))]
240
- .sort(byKey((c: any) => c.id));
236
+ function traceTask(task ) {
237
+ const reqs = [...new Set(task.stories.flatMap((s ) => requirementsForStory.get(s) || []))]
238
+ .sort(byKey((r ) => r.id));
239
+ const comps = [...new Set(reqs.flatMap((r ) => componentsForRequirement.get(r.id) || []))]
240
+ .sort(byKey((c ) => c.id));
241
241
  return { requirements: reqs, components: comps };
242
242
  }
243
243
 
@@ -249,9 +249,9 @@ function buildTraceabilityIndex(requirement: any, design: any): { requirementsFo
249
249
  * @param {Array<Object>} tasks from `plan.tasks`
250
250
  * @returns {{ byPath: Map<string, Array<Object>>, skipped: Array<Object> }}
251
251
  */
252
- function groupTasksByFile(tasks: any[]): { byPath: Map<string, any[]>; skipped: any[] } {
253
- const byPath = new Map<string, any[]>();
254
- const skipped: any[] = [];
252
+ function groupTasksByFile(tasks ) {
253
+ const byPath = new Map ();
254
+ const skipped = [];
255
255
  for (const task of tasks) {
256
256
  if (task.files.length === 0) {
257
257
  skipped.push({ item: `task ${task.id}`, why: 'no `files:` field — nothing to mirror', benign: false });
@@ -259,7 +259,7 @@ function groupTasksByFile(tasks: any[]): { byPath: Map<string, any[]>; skipped:
259
259
  }
260
260
  for (const file of task.files) {
261
261
  if (!byPath.has(file)) byPath.set(file, []);
262
- byPath.get(file)!.push(task);
262
+ byPath.get(file) .push(task);
263
263
  }
264
264
  }
265
265
  return { byPath, skipped };
@@ -277,14 +277,14 @@ function groupTasksByFile(tasks: any[]): { byPath: Map<string, any[]>; skipped:
277
277
  * @param {Function} options.traceTask from `buildTraceabilityIndex`
278
278
  * @returns {{ planned: Map<string, Object>, skipped: Array<Object>, shapeUndeclared: Array<Object> }}
279
279
  */
280
- function planSourceStubs({ byPath, scaffoldDir, repoRoot, fsImpl, slug, message, traceTask }: any): { planned: Map<string, any>; skipped: any[]; shapeUndeclared: any[] } {
281
- const planned = new Map<string, any>(); // relative-to-scaffoldDir path → { body, header, kind, from }
282
- const skipped: any[] = [];
283
- const shapeUndeclared: any[] = [];
280
+ function planSourceStubs({ byPath, scaffoldDir, repoRoot, fsImpl, slug, message, traceTask } ) {
281
+ const planned = new Map (); // relative-to-scaffoldDir path → { body, header, kind, from }
282
+ const skipped = [];
283
+ const shapeUndeclared = [];
284
284
 
285
- for (const [file, owners] of [...byPath.entries()].sort(byKey((e: any) => e[0]))) {
285
+ for (const [file, owners] of [...byPath.entries()].sort(byKey((e ) => e[0]))) {
286
286
  const ext = path.extname(file).toLowerCase();
287
- const label = `${file} (${owners.map((t: any) => t.id).join(', ')})`;
287
+ const label = `${file} (${owners.map((t ) => t.id).join(', ')})`;
288
288
 
289
289
  if (file.endsWith('/') || isDirectory(path.join(repoRoot, file), fsImpl)) {
290
290
  skipped.push({ item: label, why: 'names a directory, not a file — no single signature to emit', benign: true });
@@ -321,34 +321,34 @@ function planSourceStubs({ byPath, scaffoldDir, repoRoot, fsImpl, slug, message,
321
321
  }
322
322
 
323
323
  const emitter = EMITTERS[languageId];
324
- const trace = owners.map((t: any) => ({ task: t, ...traceTask(t) }));
325
- const reqs = [...new Set(trace.flatMap((t: any) => t.requirements))].sort(byKey((r: any) => r.id));
326
- const comps = [...new Set(trace.flatMap((t: any) => t.components))].sort(byKey((c: any) => c.id));
327
- const declared = comps.flatMap((c: any) => c.declared.map((d: any) => ({ ...d, component: c.id })));
324
+ const trace = owners.map((t ) => ({ task: t, ...traceTask(t) }));
325
+ const reqs = [...new Set(trace.flatMap((t ) => t.requirements))].sort(byKey((r ) => r.id));
326
+ const comps = [...new Set(trace.flatMap((t ) => t.components))].sort(byKey((c ) => c.id));
327
+ const declared = comps.flatMap((c ) => c.declared.map((d ) => ({ ...d, component: c.id })));
328
328
 
329
329
  const header = [
330
330
  `doflow scaffold — generated from ${slug}'s requirement.md, design.md and plan.md.`,
331
331
  'Signatures and stubs only. Do not implement here; this tree is never promoted automatically.',
332
332
  '',
333
333
  `intended path: ${file}`,
334
- ...owners.map((t: any) => `task: ${t.id} — ${t.description}`),
334
+ ...owners.map((t ) => `task: ${t.id} — ${t.description}`),
335
335
  ...(reqs.length
336
- ? reqs.map((r: any) => `requirement: ${r.id} — ${r.summary}`)
336
+ ? reqs.map((r ) => `requirement: ${r.id} — ${r.summary}`)
337
337
  : ['requirement: none — no user story on the owning task resolves to a requirement']),
338
338
  ...(comps.length
339
- ? comps.map((c: any) => `component: ${c.id} — ${c.name} (design.md §3)`)
339
+ ? comps.map((c ) => `component: ${c.id} — ${c.name} (design.md §3)`)
340
340
  : ['component: none — no design component serves this task\'s requirements']),
341
341
  ];
342
342
 
343
- const bodyParts: string[] = [''];
343
+ const bodyParts = [''];
344
344
  if (declared.length) {
345
- header.push(`shape: declared in design.md §4 (${[...new Set(declared.map((d: any) => d.component))].join(', ')}), quoted below`);
345
+ header.push(`shape: declared in design.md §4 (${[...new Set(declared.map((d ) => d.component))].join(', ')}), quoted below`);
346
346
  for (const decl of declared) {
347
347
  const lines = decl.block.split('\n');
348
348
  const shown = lines.slice(0, 40);
349
349
  bodyParts.push([
350
350
  emitter.line(`declared interface — design.md "${decl.heading}", verbatim:`),
351
- ...shown.map((l: string) => emitter.line(` ${l}`.replace(/\s+$/, ''))),
351
+ ...shown.map((l ) => emitter.line(` ${l}`.replace(/\s+$/, ''))),
352
352
  ...(lines.length > shown.length ? [emitter.line(` … ${lines.length - shown.length} more lines in design.md`)] : []),
353
353
  '',
354
354
  ].join('\n'));
@@ -371,7 +371,7 @@ function planSourceStubs({ byPath, scaffoldDir, repoRoot, fsImpl, slug, message,
371
371
  bodyParts.push(emitter.module({ symbol, message, pkg: pkg || 'scaffold' }));
372
372
  planned.set(path.join('src', file) + STUB_SUFFIX, {
373
373
  content: compose(emitter, header, `${bodyParts.join('\n')}\n`),
374
- from: owners.map((t: any) => t.id).join(', '),
374
+ from: owners.map((t ) => t.id).join(', '),
375
375
  shape: declared.length ? 'declared interface + placeholder signature' : 'placeholder signature',
376
376
  });
377
377
  }
@@ -389,10 +389,10 @@ function planSourceStubs({ byPath, scaffoldDir, repoRoot, fsImpl, slug, message,
389
389
  * @param {string} options.message the pinned "not implemented" signal
390
390
  * @returns {{ planned: Map<string, Object>, skipped: Array<Object>, notEvaluated: Array<Object> }}
391
391
  */
392
- function planTestStubs({ requirement, componentsForRequirement, language, slug, message }: any): { planned: Map<string, any>; skipped: any[]; notEvaluated: any[] } {
393
- const planned = new Map<string, any>();
394
- const skipped: any[] = [];
395
- const notEvaluated: any[] = [];
392
+ function planTestStubs({ requirement, componentsForRequirement, language, slug, message } ) {
393
+ const planned = new Map ();
394
+ const skipped = [];
395
+ const notEvaluated = [];
396
396
 
397
397
  if (requirement.criteria.length > 0) {
398
398
  if (!language.id) {
@@ -402,27 +402,27 @@ function planTestStubs({ requirement, componentsForRequirement, language, slug,
402
402
  });
403
403
  } else {
404
404
  const emitter = EMITTERS[language.id];
405
- const grouped = new Map<string, any[]>();
405
+ const grouped = new Map ();
406
406
  for (const criterion of requirement.criteria) {
407
407
  const ids = criterion.requirements.length ? criterion.requirements : ['unattributed'];
408
408
  for (const id of ids) {
409
409
  if (!grouped.has(id)) grouped.set(id, []);
410
- grouped.get(id)!.push(criterion);
410
+ grouped.get(id) .push(criterion);
411
411
  }
412
412
  if (criterion.requirements.length === 0) {
413
413
  skipped.push({ item: `acceptance criterion "${criterion.statement}"`, why: 'names no FR/NFR — stubbed under "unattributed", traceability incomplete', benign: false });
414
414
  }
415
415
  }
416
- for (const [id, cases] of [...grouped.entries()].sort(byKey((e: any) => e[0]))) {
416
+ for (const [id, cases] of [...grouped.entries()].sort(byKey((e ) => e[0]))) {
417
417
  const req = requirement.requirements.get(id);
418
- const comps = (componentsForRequirement.get(id) || []).sort(byKey((c: any) => c.id));
418
+ const comps = (componentsForRequirement.get(id) || []).sort(byKey((c ) => c.id));
419
419
  const header = [
420
420
  `doflow scaffold — test stubs generated from ${slug}'s requirement.md acceptance criteria.`,
421
421
  'Each stub fails until implemented, on purpose: a stub that passes asserts nothing and',
422
422
  'reads as coverage. Replace the body, do not delete the failure.',
423
423
  '',
424
424
  `requirement: ${id}${req ? ` — ${req.summary}` : ' — not present in the requirement index'}`,
425
- ...(comps.length ? comps.map((c: any) => `component: ${c.id} — ${c.name} (design.md §3)`) : []),
425
+ ...(comps.length ? comps.map((c ) => `component: ${c.id} — ${c.name} (design.md §3)`) : []),
426
426
  `criteria: ${cases.length} from requirement.md §6`,
427
427
  `language: ${language.id} (detected from ${language.signal} by command-detect.ts)`,
428
428
  ];
@@ -446,21 +446,21 @@ function planTestStubs({ requirement, componentsForRequirement, language, slug,
446
446
  * @param {Set<string>} ownedPaths every path a task in this plan already owns
447
447
  * @returns {Array<Object>} externalDependencies
448
448
  */
449
- function partitionExternalDependencies(tasks: any[], ownedPaths: Set<string>): any[] {
450
- const externalDependencies: any[] = [];
451
- const dependencyTasks = new Map<string, any[]>();
449
+ function partitionExternalDependencies(tasks , ownedPaths ) {
450
+ const externalDependencies = [];
451
+ const dependencyTasks = new Map ();
452
452
  for (const task of tasks) {
453
453
  for (const dep of task.dependsOn) {
454
454
  if (!dependencyTasks.has(dep)) dependencyTasks.set(dep, []);
455
- dependencyTasks.get(dep)!.push(task);
455
+ dependencyTasks.get(dep) .push(task);
456
456
  }
457
457
  }
458
- for (const [dep, owners] of [...dependencyTasks.entries()].sort(byKey((e: any) => e[0]))) {
458
+ for (const [dep, owners] of [...dependencyTasks.entries()].sort(byKey((e ) => e[0]))) {
459
459
  const inScope = [...ownedPaths].some((p) => p === dep || p.startsWith(`${dep.replace(/\/$/, '')}/`));
460
- const docs = [...new Set(owners.map((t: any) => t.externalContract).filter(Boolean))];
460
+ const docs = [...new Set(owners.map((t ) => t.externalContract).filter(Boolean))];
461
461
  externalDependencies.push({
462
462
  dependency: dep,
463
- tasks: owners.map((t: any) => t.id).sort(),
463
+ tasks: owners.map((t ) => t.id).sort(),
464
464
  disposition: inScope
465
465
  ? 'in scope — a task in this plan owns it, no contract frame needed'
466
466
  : docs.length === 1
@@ -482,12 +482,12 @@ function partitionExternalDependencies(tasks: any[], ownedPaths: Set<string>): a
482
482
  * @param {Object} options.fsImpl
483
483
  * @returns {{ written: Array<string>, unchanged: Array<string>, preserved: Array<Object> }}
484
484
  */
485
- function applyPlannedFiles({ planned, blocked, scaffoldDir, fsImpl }: any): { written: string[]; unchanged: string[]; preserved: any[] } {
486
- const written: string[] = [];
487
- const unchanged: string[] = [];
488
- const preserved: any[] = [];
485
+ function applyPlannedFiles({ planned, blocked, scaffoldDir, fsImpl } ) {
486
+ const written = [];
487
+ const unchanged = [];
488
+ const preserved = [];
489
489
 
490
- function place(relative: string, content: string): void {
490
+ function place(relative , content ) {
491
491
  const target = path.join(scaffoldDir, relative);
492
492
  const existing = readIfPresent(target, fsImpl);
493
493
  if (existing !== null) {
@@ -507,7 +507,7 @@ function applyPlannedFiles({ planned, blocked, scaffoldDir, fsImpl }: any): { wr
507
507
  }
508
508
 
509
509
  if (blocked.length === 0) {
510
- for (const [relative, entry] of [...planned.entries()].sort(byKey((e: any) => e[0]))) {
510
+ for (const [relative, entry] of [...planned.entries()].sort(byKey((e ) => e[0]))) {
511
511
  place(relative, entry.content);
512
512
  }
513
513
  }
@@ -524,14 +524,14 @@ function applyPlannedFiles({ planned, blocked, scaffoldDir, fsImpl }: any): { wr
524
524
  * @param {Object} options.fsImpl
525
525
  * @returns {Array<string>} orphans
526
526
  */
527
- function findOrphans({ scaffoldDir, planned, fsImpl }: any): string[] {
528
- const orphans: string[] = [];
529
- (function scan(dir: string, prefix: string) {
530
- let entries: any[];
527
+ function findOrphans({ scaffoldDir, planned, fsImpl } ) {
528
+ const orphans = [];
529
+ (function scan(dir , prefix ) {
530
+ let entries ;
531
531
  try {
532
532
  entries = fsImpl.readdirSync(dir, { withFileTypes: true });
533
533
  } catch { return; }
534
- for (const entry of entries.sort(byKey((e: any) => e.name))) {
534
+ for (const entry of entries.sort(byKey((e ) => e.name))) {
535
535
  const relative = prefix ? path.join(prefix, entry.name) : entry.name;
536
536
  if (entry.isDirectory()) { scan(path.join(dir, entry.name), relative); continue; }
537
537
  if (relative === MANIFEST_NAME || planned.has(relative)) continue;
@@ -554,8 +554,8 @@ function findOrphans({ scaffoldDir, planned, fsImpl }: any): string[] {
554
554
  * @param {number} options.plannedSize
555
555
  * @returns {{ status: string }}
556
556
  */
557
- function classifyRunStatus({ blocked, notEvaluated, skipped, plannedSize }: any): { status: string } {
558
- const realGaps = [...notEvaluated, ...skipped.filter((s: any) => !s.benign)];
557
+ function classifyRunStatus({ blocked, notEvaluated, skipped, plannedSize } ) {
558
+ const realGaps = [...notEvaluated, ...skipped.filter((s ) => !s.benign)];
559
559
  const status = blocked.length
560
560
  ? STATUS.BLOCKED
561
561
  : realGaps.length || plannedSize === 0
@@ -579,14 +579,14 @@ function classifyRunStatus({ blocked, notEvaluated, skipped, plannedSize }: any)
579
579
  * @param {Object} options
580
580
  * @returns {{ stateSummary: string, summary: string }}
581
581
  */
582
- function buildSummaries({ status, blocked, planned, shapeUndeclared, preserved, skipped, notEvaluated, externalDependencies, written, unchanged }: any): { stateSummary: string; summary: string } {
583
- const blockedWhy = blocked.map((b: any) => b.what).join(', ');
582
+ function buildSummaries({ status, blocked, planned, shapeUndeclared, preserved, skipped, notEvaluated, externalDependencies, written, unchanged } ) {
583
+ const blockedWhy = blocked.map((b ) => b.what).join(', ');
584
584
 
585
585
  const stateSummary = status === STATUS.BLOCKED
586
586
  ? `BLOCKED — ${blockedWhy} could not be read or parsed; no scaffold written`
587
587
  : `${status} — ${planned.size} file(s), ${shapeUndeclared.length} of them with no shape declared in design.md, `
588
588
  + `${preserved.length} preserved (hand-edited), ${skipped.length} skipped `
589
- + `(${skipped.filter((s: any) => !s.benign).length} of them a real gap), ${notEvaluated.length} not evaluated, `
589
+ + `(${skipped.filter((s ) => !s.benign).length} of them a real gap), ${notEvaluated.length} not evaluated, `
590
590
  + `${externalDependencies.length} dependency reference(s) routed to the contract-frame case`;
591
591
 
592
592
  const summary = status === STATUS.BLOCKED
@@ -601,7 +601,7 @@ function buildSummaries({ status, blocked, planned, shapeUndeclared, preserved,
601
601
  * @param {Object} options
602
602
  * @returns {Object} result
603
603
  */
604
- function buildResult({ status, slug, featureDir, scaffoldDir, repoRoot, language, sources, planned, written, unchanged, preserved, skipped, shapeUndeclared, notEvaluated, externalDependencies, orphans, stateSummary, summary }: any): any {
604
+ function buildResult({ status, slug, featureDir, scaffoldDir, repoRoot, language, sources, planned, written, unchanged, preserved, skipped, shapeUndeclared, notEvaluated, externalDependencies, orphans, stateSummary, summary } ) {
605
605
  return {
606
606
  status,
607
607
  exitCode: status === STATUS.COMPLETE ? EXIT.OK : EXIT.FINDING,
@@ -616,10 +616,10 @@ function buildResult({ status, slug, featureDir, scaffoldDir, repoRoot, language
616
616
  generated: [...planned.keys()].sort(),
617
617
  written: written.sort(),
618
618
  unchanged: unchanged.sort(),
619
- preserved: preserved.sort(byKey((p: any) => p.path)),
620
- skipped: skipped.sort(byKey((s: any) => s.item)),
621
- shapeUndeclared: shapeUndeclared.sort(byKey((s: any) => s.path)),
622
- notEvaluated: notEvaluated.sort(byKey((n: any) => n.what)),
619
+ preserved: preserved.sort(byKey((p ) => p.path)),
620
+ skipped: skipped.sort(byKey((s ) => s.item)),
621
+ shapeUndeclared: shapeUndeclared.sort(byKey((s ) => s.path)),
622
+ notEvaluated: notEvaluated.sort(byKey((n ) => n.what)),
623
623
  externalDependencies,
624
624
  orphans: orphans.sort(),
625
625
  stateSummary,
@@ -639,7 +639,7 @@ function buildResult({ status, slug, featureDir, scaffoldDir, repoRoot, language
639
639
  * @param {Object} options.fsImpl
640
640
  * @returns {Object} result
641
641
  */
642
- function writeManifest({ result, planned, scaffoldDir, fsImpl }: any): any {
642
+ function writeManifest({ result, planned, scaffoldDir, fsImpl } ) {
643
643
  const manifest = renderManifest(result, planned);
644
644
  const manifestTarget = path.join(scaffoldDir, MANIFEST_NAME);
645
645
  const existingManifest = readIfPresent(manifestTarget, fsImpl);
@@ -672,12 +672,12 @@ function writeManifest({ result, planned, scaffoldDir, fsImpl }: any): any {
672
672
  * @param {Object} [options.fsImpl] injection seam for tests; same contract as `node:fs`
673
673
  * @returns {Object} result — see `summary`, `status` and `exitCode`
674
674
  */
675
- function generateScaffold(options: any = {}): any {
675
+ function generateScaffold(options = {}) {
676
676
  const fsImpl = options.fsImpl || nodeFs;
677
677
  const repoRoot = path.resolve(options.repoRoot || process.cwd());
678
678
  const featureDir = options.featureDir ? path.resolve(options.featureDir) : null;
679
679
 
680
- const usage = (message: string): any => ({
680
+ const usage = (message ) => ({
681
681
  status: STATUS.USAGE, exitCode: EXIT.USAGE, ok: false, summary: message,
682
682
  featureDir, scaffoldDir: null, manifestPath: null,
683
683
  generated: [], written: [], unchanged: [], preserved: [], skipped: [], notEvaluated: [],
@@ -696,7 +696,7 @@ function generateScaffold(options: any = {}): any {
696
696
  const notEvaluated = [...artifactsNotEvaluated, ...parseGapsNotEvaluated];
697
697
 
698
698
  const blocked = notEvaluated.filter(
699
- (n: any) => ARTIFACTS.includes(n.what) || n.what.startsWith('plan.md → task list'),
699
+ (n ) => ARTIFACTS.includes(n.what) || n.what.startsWith('plan.md → task list'),
700
700
  );
701
701
 
702
702
  const { requirementsForStory, componentsForRequirement, traceTask } = buildTraceabilityIndex(requirement, design);
@@ -743,9 +743,9 @@ function generateScaffold(options: any = {}): any {
743
743
  * the second run of an unchanged plan produce a diff — losing the idempotency the whole artifact is
744
744
  * reviewed on.
745
745
  */
746
- function renderManifest(result: any, planned: Map<string, any>): string {
747
- const lines: string[] = [];
748
- const table = (headers: string[], rows: any[][], empty: string) => {
746
+ function renderManifest(result , planned ) {
747
+ const lines = [];
748
+ const table = (headers , rows , empty ) => {
749
749
  if (rows.length === 0) { lines.push(empty, ''); return; }
750
750
  lines.push(`| ${headers.join(' | ')} |`, `|${headers.map(() => '---').join('|')}|`);
751
751
  for (const r of rows) lines.push(`| ${r.map(cell).join(' | ')} |`);
@@ -754,7 +754,7 @@ function renderManifest(result: any, planned: Map<string, any>): string {
754
754
 
755
755
  lines.push(`# Scaffold manifest — ${result.slug}`, '');
756
756
  lines.push(
757
- 'Generated by DoFlow\'s scaffold generator (`src/runtime/scaffold/generate.ts`) from this feature\'s own',
757
+ 'Generated by DoFlow\'s scaffold generator (`src/runtime/scaffold/generate.js`) from this feature\'s own',
758
758
  '`requirement.md`, `design.md` and `plan.md`. Signatures, types and failing stubs only — no',
759
759
  'implementation logic, by design. Nothing here is written into the source tree; promoting any of',
760
760
  'it is a deliberate act.',
@@ -768,23 +768,23 @@ function renderManifest(result: any, planned: Map<string, any>): string {
768
768
 
769
769
  lines.push('## Source artifacts', '');
770
770
  table(['Artifact', 'Read', 'SHA-256'],
771
- result.sources.map((s: any) => [s.name, s.present ? 'yes' : 'NOT FOUND', s.sha256 || '—']),
771
+ result.sources.map((s ) => [s.name, s.present ? 'yes' : 'NOT FOUND', s.sha256 || '—']),
772
772
  '_None read._');
773
773
 
774
774
  lines.push('## Generated', '');
775
775
  table(['Path', 'Derived from', 'Shape'],
776
- result.generated.map((p: string) => [p, planned.get(p).from, planned.get(p).shape]),
776
+ result.generated.map((p ) => [p, planned.get(p).from, planned.get(p).shape]),
777
777
  '_Nothing generated._');
778
778
 
779
779
  lines.push('## Emitted without a declared shape', '');
780
780
  lines.push('These files exist and are traceable, but `design.md` §4 never declared an interface for',
781
781
  'them, so their signature is a placeholder rather than a contract. Reviewing the placeholder as',
782
782
  'if it were the designed API is the mistake this section exists to prevent.', '');
783
- table(['Path', 'Why'], result.shapeUndeclared.map((s: any) => [s.path, s.why]),
783
+ table(['Path', 'Why'], result.shapeUndeclared.map((s ) => [s.path, s.why]),
784
784
  '_Every emitted file has a declared shape behind it._');
785
785
 
786
786
  lines.push('## Preserved — edited by hand, not overwritten', '');
787
- table(['Path', 'Why'], result.preserved.map((p: any) => [p.path, p.why]),
787
+ table(['Path', 'Why'], result.preserved.map((p ) => [p.path, p.why]),
788
788
  '_No hand-edited files detected._');
789
789
 
790
790
  lines.push('## Skipped', '');
@@ -794,14 +794,14 @@ function renderManifest(result: any, planned: Map<string, any>): string {
794
794
  '"Gap" separates the two kinds. A markdown file has no signature to emit and skipping it costs',
795
795
  'nothing; a source file in a language this generator cannot emit is work that silently did not',
796
796
  'happen, and it is what holds the status below `COMPLETE`.', '');
797
- table(['Item', 'Gap', 'Why'], result.skipped.map((s: any) => [s.item, s.benign ? 'no' : 'YES', s.why]),
797
+ table(['Item', 'Gap', 'Why'], result.skipped.map((s ) => [s.item, s.benign ? 'no' : 'YES', s.why]),
798
798
  '_Nothing skipped._');
799
799
 
800
800
  lines.push('## Not evaluated', '');
801
801
  lines.push('Things this run could not judge either way. These, together with every skip marked as a',
802
802
  'gap above, are why the status is not `COMPLETE`. None of them is a claim that the underlying',
803
803
  'item is fine.', '');
804
- table(['What', 'Why'], result.notEvaluated.map((n: any) => [n.what, n.why]),
804
+ table(['What', 'Why'], result.notEvaluated.map((n ) => [n.what, n.why]),
805
805
  '_Everything the artifacts declared was evaluated._');
806
806
 
807
807
  lines.push('## External dependencies', '');
@@ -810,13 +810,13 @@ function renderManifest(result: any, planned: Map<string, any>): string {
810
810
  'nested `.git` boundaries, non-local vendors and `external-contract:` targets; this generator reports',
811
811
  'the partition rather than deriving the boundaries a second way.', '');
812
812
  table(['Dependency', 'Named by', 'Disposition'],
813
- result.externalDependencies.map((d: any) => [d.dependency, d.tasks.join(', '), d.disposition]),
813
+ result.externalDependencies.map((d ) => [d.dependency, d.tasks.join(', '), d.disposition]),
814
814
  '_No task declares a `depends-on:` value._');
815
815
 
816
816
  if (result.orphans.length) {
817
817
  lines.push('## Orphaned', '');
818
818
  lines.push('Generated by an earlier run and no longer implied by the plan. Reported, never deleted.', '');
819
- table(['Path'], result.orphans.map((p: string) => [p]), '');
819
+ table(['Path'], result.orphans.map((p ) => [p]), '');
820
820
  }
821
821
 
822
822
  const body = `\n${lines.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd()}\n`;
@@ -846,7 +846,7 @@ function renderManifest(result: any, planned: Map<string, any>): string {
846
846
  * @param {string|null} [options.slug] explicit feature override
847
847
  * @returns {number} process exit code
848
848
  */
849
- function handleScaffoldCommand({ json = false, projectRoot, slug = null }: any = {}): number {
849
+ function handleScaffoldCommand({ json = false, projectRoot, slug = null } = {}) {
850
850
  const root = projectRoot || process.cwd();
851
851
  const feature = resolveActiveFeature({ projectRoot: root, slug });
852
852
  if (feature.error) {
@@ -870,10 +870,10 @@ function handleScaffoldCommand({ json = false, projectRoot, slug = null }: any =
870
870
 
871
871
  // A reader who cannot see the gaps reads the tree as the whole shape of the plan, so the
872
872
  // negative space is printed at the same level as the counts rather than left to the manifest.
873
- const sections: [string, string[]][] = [
874
- ['Skipped', (result.skipped || []).map((s: any) => `${s.item}${s.benign ? '' : ' [GAP]'} — ${s.why}`)],
875
- ['Not evaluated', (result.notEvaluated || []).map((n: any) => `${n.what} — ${n.why}`)],
876
- ['Preserved (hand-edited, not overwritten)', (result.preserved || []).map((p: any) => `${p.path} — ${p.why}`)],
873
+ const sections = [
874
+ ['Skipped', (result.skipped || []).map((s ) => `${s.item}${s.benign ? '' : ' [GAP]'} — ${s.why}`)],
875
+ ['Not evaluated', (result.notEvaluated || []).map((n ) => `${n.what} — ${n.why}`)],
876
+ ['Preserved (hand-edited, not overwritten)', (result.preserved || []).map((p ) => `${p.path} — ${p.why}`)],
877
877
  ['Orphaned (no longer implied by the plan)', result.orphans || []],
878
878
  ];
879
879
  for (const [title, lines] of sections) {