@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
@@ -17,10 +17,10 @@
17
17
 
18
18
  const nodeFs = require('node:fs');
19
19
  const path = require('node:path');
20
- const { acquireLock } = require('./task-state.ts');
21
- const { resolveActiveFeature } = require('./feature-resolve.ts');
22
- const { compactHistory } = require('./history-compactor.ts');
23
- const { finishRuntime, usageError } = require('./cli-result.ts');
20
+ const { acquireLock } = require('./task-state.js');
21
+ const { resolveActiveFeature } = require('./feature-resolve.js');
22
+ const { compactHistory } = require('./history-compactor.js');
23
+ const { finishRuntime, usageError } = require('./cli-result.js');
24
24
 
25
25
  const REGISTER_VERSION = 1;
26
26
  const TOPIC_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
@@ -30,7 +30,7 @@ const REF_RE = /^[A-Za-z]+-?[0-9]+$/;
30
30
  const DEC_RE = /^DEC-[0-9]{3,}$/;
31
31
  const STAGES = ['discovery', 'design', 'planning', 'implementation', 'verification', 'review'];
32
32
  /** Channel -> who decided. The caller names the channel; `decidedBy` follows from it. */
33
- const CHANNEL_DECIDED_BY: Record<string, string> = { question: 'user', gate: 'user', prompt: 'user', default: 'agent', resolution: 'agent' };
33
+ const CHANNEL_DECIDED_BY = { question: 'user', gate: 'user', prompt: 'user', default: 'agent', resolution: 'agent' };
34
34
  const ITEM_KEYS = ['topic', 'statement', 'channel', 'stage', 'rationale', 'supersedes', 'refs', 'source'];
35
35
  const REQUIRED_KEYS = ['topic', 'statement', 'channel', 'stage', 'rationale'];
36
36
 
@@ -44,9 +44,9 @@ class DecisionUsageError extends Error {}
44
44
 
45
45
  /** A finding the caller must act on (exit 1). */
46
46
  class DecisionFinding extends Error {
47
- finding: string;
47
+ finding ;
48
48
 
49
- constructor(finding: string, message: string) {
49
+ constructor(finding , message ) {
50
50
  super(message);
51
51
  this.finding = finding;
52
52
  }
@@ -54,19 +54,19 @@ class DecisionFinding extends Error {
54
54
 
55
55
  // ── paths ──────────────────────────────────────────────────────────────────────────────────────
56
56
 
57
- function registerFile(featureDir: string): string { return path.join(featureDir, 'decisions', 'register.json'); }
58
- function liveViewFile(featureDir: string): string { return path.join(featureDir, 'decisions.md'); }
59
- function archiveFile(featureDir: string): string { return path.join(featureDir, 'decisions', 'archive.md'); }
57
+ function registerFile(featureDir ) { return path.join(featureDir, 'decisions', 'register.json'); }
58
+ function liveViewFile(featureDir ) { return path.join(featureDir, 'decisions.md'); }
59
+ function archiveFile(featureDir ) { return path.join(featureDir, 'decisions', 'archive.md'); }
60
60
 
61
61
  // ── register io ────────────────────────────────────────────────────────────────────────────────
62
62
 
63
- function readRegister(featureDir: string, fsImpl: any = nodeFs): any {
63
+ function readRegister(featureDir , fsImpl = nodeFs) {
64
64
  const file = registerFile(featureDir);
65
65
  if (!fsImpl.existsSync(file)) return null;
66
- let data: any;
66
+ let data ;
67
67
  try {
68
68
  data = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
69
- } catch (error: any) {
69
+ } catch (error ) {
70
70
  throw new DecisionUsageError(`${file} is not valid JSON (${error.message}). The register is written atomically, so a torn file means outside interference; restore it from version control.`);
71
71
  }
72
72
  if (!data || data.version !== REGISTER_VERSION || !Array.isArray(data.decisions) || !Number.isInteger(data.nextId)) {
@@ -75,62 +75,62 @@ function readRegister(featureDir: string, fsImpl: any = nodeFs): any {
75
75
  return data;
76
76
  }
77
77
 
78
- function writeAtomic(fsImpl: any, file: string, content: string): void {
78
+ function writeAtomic(fsImpl , file , content ) {
79
79
  fsImpl.mkdirSync(path.dirname(file), { recursive: true });
80
80
  const tmp = `${file}.${process.pid}.tmp`;
81
81
  try {
82
82
  fsImpl.writeFileSync(tmp, content, 'utf8');
83
83
  fsImpl.renameSync(tmp, file);
84
- } catch (error: any) {
84
+ } catch (error ) {
85
85
  try { fsImpl.rmSync(tmp, { force: true }); } catch { /* nothing to clean */ }
86
86
  throw error;
87
87
  }
88
88
  }
89
89
 
90
90
  /** Writes only when the content differs, so re-rendering an unchanged view is a no-op. */
91
- function writeIfChanged(fsImpl: any, file: string, content: string): void {
91
+ function writeIfChanged(fsImpl , file , content ) {
92
92
  if (fsImpl.existsSync(file) && fsImpl.readFileSync(file, 'utf8') === content) return;
93
93
  writeAtomic(fsImpl, file, content);
94
94
  }
95
95
 
96
- function writeRegister(fsImpl: any, featureDir: string, register: any): void {
96
+ function writeRegister(fsImpl , featureDir , register ) {
97
97
  writeAtomic(fsImpl, registerFile(featureDir), `${JSON.stringify(register, null, 2)}\n`);
98
98
  }
99
99
 
100
- function renderViews(fsImpl: any, featureDir: string, register: any): void {
100
+ function renderViews(fsImpl , featureDir , register ) {
101
101
  writeIfChanged(fsImpl, liveViewFile(featureDir), renderLiveView(register));
102
102
  writeIfChanged(fsImpl, archiveFile(featureDir), renderArchive(register));
103
103
  }
104
104
 
105
105
  // ── ordering and ids ───────────────────────────────────────────────────────────────────────────
106
106
 
107
- function idNumber(id: string): number { return Number(id.slice(4)); }
108
- function formatId(n: number): string { return `DEC-${String(n).padStart(3, '0')}`; }
109
- function byId(a: any, b: any): number { return idNumber(a.id) - idNumber(b.id); }
110
- function byTopic(a: any, b: any): number { return a.topic < b.topic ? -1 : a.topic > b.topic ? 1 : idNumber(a.id) - idNumber(b.id); }
111
- function liveOf(register: any): any[] { return register.decisions.filter((d: any) => d.status === 'live'); }
107
+ function idNumber(id ) { return Number(id.slice(4)); }
108
+ function formatId(n ) { return `DEC-${String(n).padStart(3, '0')}`; }
109
+ function byId(a , b ) { return idNumber(a.id) - idNumber(b.id); }
110
+ function byTopic(a , b ) { return a.topic < b.topic ? -1 : a.topic > b.topic ? 1 : idNumber(a.id) - idNumber(b.id); }
111
+ function liveOf(register ) { return register.decisions.filter((d ) => d.status === 'live'); }
112
112
 
113
113
  // ── renderers ──────────────────────────────────────────────────────────────────────────────────
114
114
 
115
115
  /** `\` is escaped first, so a statement ending in one cannot escape the pipe that closes the cell
116
116
  * (IC-007); `|` would end the cell; `<` could open an HTML comment that hides every later row. */
117
- function cell(text: any): string { return String(text).replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/</g, '\\<'); }
117
+ function cell(text ) { return String(text).replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/</g, '\\<'); }
118
118
 
119
119
  /** Archive text is read by people and agents, never as HTML: `<` is escaped so `<!--` cannot hide later entries. */
120
- function plain(text: any): string { return String(text).replace(/</g, '\\<'); }
120
+ function plain(text ) { return String(text).replace(/</g, '\\<'); }
121
121
 
122
122
  /**
123
123
  * A one-line rationale reads inline. A longer one goes in a blockquote under the bullet, every line
124
124
  * prefixed, so no line of it can start a heading or pose as one of the entry's own bullets.
125
125
  */
126
- function rationaleLines(rationale: any): string[] {
126
+ function rationaleLines(rationale ) {
127
127
  const parts = plain(rationale).split(/\r\n|[\n\r\u2028\u2029\u0085]/);
128
128
  if (parts.length === 1) return [`- **Rationale:** ${parts[0]}`];
129
- return ['- **Rationale:**', ...parts.map((l: string) => (l === '' ? ' >' : ` > ${l}`))];
129
+ return ['- **Rationale:**', ...parts.map((l ) => (l === '' ? ' >' : ` > ${l}`))];
130
130
  }
131
131
 
132
132
  /** IC-007. Size depends on the number of live topics, never on how many decisions were made. */
133
- function renderLiveView(register: any): string {
133
+ function renderLiveView(register ) {
134
134
  const live = liveOf(register).sort(byTopic);
135
135
  const out = [
136
136
  `# Decisions: ${register.slug}`,
@@ -150,7 +150,7 @@ function renderLiveView(register: any): string {
150
150
  }
151
151
 
152
152
  /** IC-008. One entry per decision, by id; superseded entries stay with their rationale. */
153
- function renderArchive(register: any): string {
153
+ function renderArchive(register ) {
154
154
  const out = [`# Decision archive: ${register.slug}`, '', `> ${GENERATED_NOTE}`];
155
155
  for (const d of [...register.decisions].sort(byId)) {
156
156
  out.push('', `### ${d.id}: ${d.topic}`, '');
@@ -167,13 +167,13 @@ function renderArchive(register: any): string {
167
167
 
168
168
  // ── item validation ────────────────────────────────────────────────────────────────────────────
169
169
 
170
- function toList(value: any, what: string, label: string): string[] {
170
+ function toList(value , what , label ) {
171
171
  if (value === undefined || value === null || value === '') return [];
172
- let list: any[];
172
+ let list ;
173
173
  if (Array.isArray(value)) list = value;
174
174
  else if (typeof value === 'string') list = value.split(',');
175
175
  else throw new DecisionUsageError(`${label}: ${what} must be an array of strings`);
176
- const out: string[] = [];
176
+ const out = [];
177
177
  for (const entry of list) {
178
178
  if (typeof entry !== 'string') throw new DecisionUsageError(`${label}: ${what} must be an array of strings`);
179
179
  const trimmed = entry.trim();
@@ -183,11 +183,11 @@ function toList(value: any, what: string, label: string): string[] {
183
183
  }
184
184
 
185
185
  /** Validates one add item against IC-003's field rules. Throws DecisionUsageError. */
186
- function normalizeItem(raw: any, label: string): any {
186
+ function normalizeItem(raw , label ) {
187
187
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new DecisionUsageError(`${label}: must be an object`);
188
- const extra = Object.keys(raw).filter((k: string) => !ITEM_KEYS.includes(k));
188
+ const extra = Object.keys(raw).filter((k ) => !ITEM_KEYS.includes(k));
189
189
  if (extra.length) {
190
- throw new DecisionUsageError(`${label}: unknown key${extra.length > 1 ? 's' : ''} ${extra.map((k: string) => `'${k}'`).join(', ')}. Allowed: ${ITEM_KEYS.join(', ')} (decidedBy is set by the runtime from the channel)`);
190
+ throw new DecisionUsageError(`${label}: unknown key${extra.length > 1 ? 's' : ''} ${extra.map((k ) => `'${k}'`).join(', ')}. Allowed: ${ITEM_KEYS.join(', ')} (decidedBy is set by the runtime from the channel)`);
191
191
  }
192
192
  for (const key of REQUIRED_KEYS) {
193
193
  if (typeof raw[key] !== 'string' || raw[key].trim() === '') throw new DecisionUsageError(`${label}: '${key}' is required`);
@@ -226,9 +226,9 @@ function normalizeItem(raw: any, label: string): any {
226
226
  }
227
227
 
228
228
  /** Normalizes the batch (`--batch` contents) or the single item, refusing everything on one fault. */
229
- function normalizeItems(items: any): any[] {
229
+ function normalizeItems(items ) {
230
230
  if (!Array.isArray(items)) throw new DecisionUsageError('a batch must be a JSON array of decision objects');
231
- const normalized = items.map((raw: any, i: number) => normalizeItem(raw, `item ${i + 1}`));
231
+ const normalized = items.map((raw , i ) => normalizeItem(raw, `item ${i + 1}`));
232
232
  const seen = new Set();
233
233
  for (const item of normalized) {
234
234
  if (seen.has(item.topic)) throw new DecisionUsageError(`topic '${item.topic}' appears more than once in the batch; a topic takes one decision per batch`);
@@ -239,15 +239,15 @@ function normalizeItems(items: any): any[] {
239
239
 
240
240
  // ── actions ────────────────────────────────────────────────────────────────────────────────────
241
241
 
242
- function summary({ action, slug, featureDir }: { action: string; slug: string; featureDir: string }): { action: string; slug: string; featureDir: string } { return { action, slug, featureDir }; }
242
+ function summary({ action, slug, featureDir } ) { return { action, slug, featureDir }; }
243
243
 
244
244
  const FOLDER_ARTIFACTS = ['intention/requirement.md', 'requirement.md', 'design/design.md', 'design.md', 'plan.md'];
245
245
 
246
246
  /** IC-002. */
247
- function initRegister({ featureDir, slug, fsImpl = nodeFs }: any): any {
247
+ function initRegister({ featureDir, slug, fsImpl = nodeFs } ) {
248
248
  const base = summary({ action: 'init', slug, featureDir });
249
249
  if (fsImpl.existsSync(registerFile(featureDir))) return { ...base, created: false };
250
- const present = FOLDER_ARTIFACTS.filter((rel: string) => fsImpl.existsSync(path.join(featureDir, rel)));
250
+ const present = FOLDER_ARTIFACTS.filter((rel ) => fsImpl.existsSync(path.join(featureDir, rel)));
251
251
  if (present.length) {
252
252
  return {
253
253
  ...base,
@@ -273,7 +273,7 @@ function initRegister({ featureDir, slug, fsImpl = nodeFs }: any): any {
273
273
  * IC-003. All-or-nothing: the whole batch is validated and simulated against the register first,
274
274
  * and nothing is written unless every item passes.
275
275
  */
276
- function addDecisions({ featureDir, slug, items, now = new Date(), fsImpl = nodeFs }: any): any {
276
+ function addDecisions({ featureDir, slug, items, now = new Date(), fsImpl = nodeFs } ) {
277
277
  const normalized = normalizeItems(items);
278
278
  const base = summary({ action: 'add', slug, featureDir });
279
279
  if (!fsImpl.existsSync(registerFile(featureDir))) {
@@ -285,24 +285,24 @@ function addDecisions({ featureDir, slug, items, now = new Date(), fsImpl = node
285
285
  try {
286
286
  const register = readRegister(featureDir, fsImpl);
287
287
  const iso = now.toISOString();
288
- const added: any[] = [];
289
- const superseded: any[] = [];
290
- let conflict: any = null;
288
+ const added = [];
289
+ const superseded = [];
290
+ let conflict = null;
291
291
  let nextId = register.nextId;
292
292
  for (const item of normalized) {
293
293
  for (const id of item.supersedes) {
294
- const target = register.decisions.find((d: any) => d.id === id);
294
+ const target = register.decisions.find((d ) => d.id === id);
295
295
  if (!target) throw new DecisionUsageError(`topic '${item.topic}': supersedes ${id}, which is not a decision in this register`);
296
296
  if (target.status !== 'live') throw new DecisionUsageError(`topic '${item.topic}': supersedes ${id}, which is not live (it was superseded by ${target.supersededBy})`);
297
297
  }
298
- const unnamed = register.decisions.find((d: any) => d.status === 'live' && d.topic === item.topic && !item.supersedes.includes(d.id));
298
+ const unnamed = register.decisions.find((d ) => d.status === 'live' && d.topic === item.topic && !item.supersedes.includes(d.id));
299
299
  if (unnamed && !conflict) {
300
300
  conflict = { finding: 'topic-conflict', message: `topic '${item.topic}' already has live decision ${unnamed.id}; name it in supersedes to replace it. Nothing was written` };
301
301
  }
302
302
  const id = formatId(nextId);
303
303
  nextId += 1;
304
304
  for (const old of item.supersedes) {
305
- const target = register.decisions.find((d: any) => d.id === old);
305
+ const target = register.decisions.find((d ) => d.id === old);
306
306
  target.status = 'superseded';
307
307
  target.supersededBy = id;
308
308
  superseded.push({ id: old, by: id });
@@ -336,7 +336,7 @@ function addDecisions({ featureDir, slug, items, now = new Date(), fsImpl = node
336
336
  }
337
337
 
338
338
  /** IC-004. */
339
- function listDecisions({ featureDir, slug, all = false, fsImpl = nodeFs }: any): any {
339
+ function listDecisions({ featureDir, slug, all = false, fsImpl = nodeFs } ) {
340
340
  const base = summary({ action: 'list', slug, featureDir });
341
341
  const register = readRegister(featureDir, fsImpl);
342
342
  if (!register) return { ...base, finding: 'no-register', message: `${featureDir} has no decisions/register.json` };
@@ -345,7 +345,7 @@ function listDecisions({ featureDir, slug, all = false, fsImpl = nodeFs }: any):
345
345
  }
346
346
 
347
347
  /** IC-005: compaction plus the view re-render, under the register lock `compactHistory` itself lacks. */
348
- function compactDecisions({ featureDir, slug, repoRoot, paths, now = new Date(), fsImpl = nodeFs }: any): any {
348
+ function compactDecisions({ featureDir, slug, repoRoot, paths, now = new Date(), fsImpl = nodeFs } ) {
349
349
  const base = summary({ action: 'compact', slug, featureDir });
350
350
  if (!fsImpl.existsSync(registerFile(featureDir))) {
351
351
  return { ...base, finding: 'no-register', message: `${featureDir} has no decisions/register.json; nothing was touched` };
@@ -360,7 +360,7 @@ function compactDecisions({ featureDir, slug, repoRoot, paths, now = new Date(),
360
360
  if (result.failed.length) {
361
361
  return {
362
362
  ...base, status: result.status, finding: 'compaction-failed',
363
- message: result.failed.map((f: any) => f.message).join('; '),
363
+ message: result.failed.map((f ) => f.message).join('; '),
364
364
  artifact: result.failed[0].path, moved: result.moved, failed: result.failed,
365
365
  };
366
366
  }
@@ -374,17 +374,17 @@ function compactDecisions({ featureDir, slug, repoRoot, paths, now = new Date(),
374
374
 
375
375
  const VALID_ACTIONS = ['init', 'add', 'list', 'compact'];
376
376
 
377
- function readBatchFile(batchPath: string, fsImpl: any): any {
378
- let text: string;
377
+ function readBatchFile(batchPath , fsImpl ) {
378
+ let text ;
379
379
  try {
380
380
  // `-` is stdin, as for the evidence verb.
381
381
  text = batchPath === '-' ? fsImpl.readFileSync(0, 'utf8') : fsImpl.readFileSync(path.resolve(batchPath), 'utf8');
382
- } catch (error: any) {
382
+ } catch (error ) {
383
383
  throw new DecisionUsageError(`cannot read --batch ${batchPath}: ${error.message}`);
384
384
  }
385
385
  try {
386
386
  return JSON.parse(text);
387
- } catch (error: any) {
387
+ } catch (error ) {
388
388
  throw new DecisionUsageError(`--batch ${batchPath} is not valid JSON: ${error.message}`);
389
389
  }
390
390
  }
@@ -401,15 +401,15 @@ function readBatchFile(batchPath: string, fsImpl: any): any {
401
401
  * refs, source, batch (path); list: all
402
402
  * @returns {{exitCode:number, result?:Object, usage?:string}}
403
403
  */
404
- function runDecision({ action = 'list', projectRoot, slug = null, flags = {}, now = new Date(), fsImpl = nodeFs }: any): { exitCode: number; result?: any; usage?: string } {
404
+ function runDecision({ action = 'list', projectRoot, slug = null, flags = {}, now = new Date(), fsImpl = nodeFs } ) {
405
405
  const act = action === 'status' ? 'list' : action;
406
- let resolved: any = null;
406
+ let resolved = null;
407
407
  try {
408
408
  if (!VALID_ACTIONS.includes(act)) throw new DecisionUsageError(`unknown --action '${action}'. Valid: ${VALID_ACTIONS.join(', ')}`);
409
409
  const feature = resolveActiveFeature({ projectRoot, slug });
410
410
  if (feature.error) throw new DecisionUsageError(feature.message);
411
411
  resolved = { featureDir: feature.featureDir, slug: feature.paths.feature_slug };
412
- let result: any;
412
+ let result ;
413
413
  if (act === 'init') {
414
414
  result = initRegister({ ...resolved, fsImpl });
415
415
  } else if (act === 'list') {
@@ -417,13 +417,13 @@ function runDecision({ action = 'list', projectRoot, slug = null, flags = {}, no
417
417
  } else if (act === 'compact') {
418
418
  result = compactDecisions({ ...resolved, repoRoot: feature.repoRoot, paths: feature.paths, now, fsImpl });
419
419
  } else {
420
- let items: any;
420
+ let items ;
421
421
  if (flags.batch) {
422
422
  const named = ITEM_KEYS.filter((k) => flags[k] !== undefined);
423
423
  if (named.length) throw new DecisionUsageError(`--batch cannot be combined with --${named[0]}`);
424
424
  items = readBatchFile(flags.batch, fsImpl);
425
425
  } else {
426
- const single: Record<string, any> = {};
426
+ const single = {};
427
427
  for (const key of ITEM_KEYS) if (flags[key] !== undefined) single[key] = flags[key];
428
428
  for (const key of REQUIRED_KEYS) {
429
429
  if (single[key] === undefined) throw new DecisionUsageError(`--${key} is required for --action add (or pass --batch <file.json>)`);
@@ -433,7 +433,7 @@ function runDecision({ action = 'list', projectRoot, slug = null, flags = {}, no
433
433
  result = addDecisions({ ...resolved, items, now, fsImpl });
434
434
  }
435
435
  return { exitCode: result.finding ? 1 : 0, result };
436
- } catch (error: any) {
436
+ } catch (error ) {
437
437
  if (error instanceof DecisionUsageError) return { exitCode: 2, usage: error.message };
438
438
  // Another writer holds the register lock: a condition to retry, not a mistake in the call.
439
439
  if (resolved && error && /^Could not lock/.test(error.message || '')) {
@@ -443,21 +443,21 @@ function runDecision({ action = 'list', projectRoot, slug = null, flags = {}, no
443
443
  }
444
444
  }
445
445
 
446
- function humanLines(result: any): string[] {
447
- const lines: string[] = [];
446
+ function humanLines(result ) {
447
+ const lines = [];
448
448
  if (result.finding) lines.push(`${result.finding}: ${result.message}`);
449
449
  if (result.action === 'init' && !result.finding) {
450
450
  lines.push(result.created ? `created ${path.join(result.featureDir, 'decisions', 'register.json')}` : 'register already exists; nothing written');
451
451
  }
452
- for (const a of (result.added || []) as any[]) lines.push(`added ${a.id} (${a.topic})`);
453
- for (const s of (result.superseded || []) as any[]) lines.push(`superseded ${s.id} by ${s.by}`);
452
+ for (const a of (result.added || []) ) lines.push(`added ${a.id} (${a.topic})`);
453
+ for (const s of (result.superseded || []) ) lines.push(`superseded ${s.id} by ${s.by}`);
454
454
  if (result.action === 'list' && !result.finding) {
455
455
  if (result.decisions.length === 0) lines.push('No decisions.');
456
- for (const d of result.decisions as any[]) lines.push(`${d.id} ${d.topic} ${d.statement} [${d.decidedBy}, ${d.stage}${d.status === 'live' ? '' : `, superseded by ${d.supersededBy}`}]`);
456
+ for (const d of result.decisions ) lines.push(`${d.id} ${d.topic} ${d.statement} [${d.decidedBy}, ${d.stage}${d.status === 'live' ? '' : `, superseded by ${d.supersededBy}`}]`);
457
457
  }
458
458
  if (result.action === 'compact') {
459
459
  if (result.status === 'unchanged') lines.push('nothing to compact');
460
- for (const m of (result.moved || []) as any[]) lines.push(`moved ${m.lines} line${m.lines === 1 ? '' : 's'} from ${m.path} to ${m.archive}`);
460
+ for (const m of (result.moved || []) ) lines.push(`moved ${m.lines} line${m.lines === 1 ? '' : 's'} from ${m.path} to ${m.archive}`);
461
461
  }
462
462
  return lines;
463
463
  }
@@ -466,7 +466,7 @@ function humanLines(result: any): string[] {
466
466
  * `decision` verb. Exit 0 = answered, 1 = a finding the caller must act on, 2 = usage.
467
467
  * `--json` prints the result object unmodified.
468
468
  */
469
- function handleDecisionCommand({ action = 'list', projectRoot, slug = null, flags = {}, json = false }: any = {}): number {
469
+ function handleDecisionCommand({ action = 'list', projectRoot, slug = null, flags = {}, json = false } = {}) {
470
470
  const run = runDecision({ action, projectRoot: projectRoot || process.cwd(), slug, flags });
471
471
  if (run.usage !== undefined) return usageError('decision', run.usage, json);
472
472
  if (json) console.log(JSON.stringify(run.result, null, 2));
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /** The names a retrieval score arrives under, in camelCase (`--relevance-score` normalises here).
6
+ * Exported because `bin/doflow.js` refuses the same names on the command line, and because
7
+ * `retrieval-plan.ts` and `outcome.ts` refuse them on the option object a declared plan and a
8
+ * terminal record are built from: one definition, three input surfaces (argv, a `--batch` JSON
9
+ * file, and a verb's own options), each with exactly one enforcement point. A second copy of
10
+ * this list is how one of them drifts. */
11
+ const EVIDENCE_SCORE_FIELDS = new Set([
12
+ 'confidence', 'score', 'relevance', 'relevanceScore', 'similarity',
13
+ 'certainty', 'probability', 'rank', 'distance', 'weight',
14
+ ]);
15
+
16
+ /** The one refusal message for a score offered as a property of a fact. */
17
+ function scoreFieldRefusal(name ) {
18
+ return `'${name}' is a retrieval score and this boundary records none (FR-007). Relevance is a `
19
+ + 'property of a search, not of a fact: stored beside the fact it is read as confidence by the '
20
+ + 'next worker, which is the specific interpretation the requirement forbids. Record the '
21
+ + 'locator and the content instead — a reader who can see where a fact came from does not need '
22
+ + 'to be told how sure to be.';
23
+ }
24
+
25
+ /** Refuses a score-shaped key anywhere in an item, including nested under source or locator. */
26
+ function assertNoScoreFields(value , prefix ) {
27
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return;
28
+ for (const [key, inner] of Object.entries(value )) {
29
+ const dotted = prefix ? `${prefix}.${key}` : key;
30
+ if (EVIDENCE_SCORE_FIELDS.has(key)) throw new Error(scoreFieldRefusal(dotted));
31
+ assertNoScoreFields(inner, dotted);
32
+ }
33
+ }
34
+
35
+ module.exports = {
36
+ EVIDENCE_SCORE_FIELDS,
37
+ scoreFieldRefusal,
38
+ assertNoScoreFields,
39
+ };
@@ -5,9 +5,10 @@
5
5
  const fs = require('node:fs');
6
6
  const path = require('node:path');
7
7
  const crypto = require('node:crypto');
8
- const { REPO_ROOT } = require('../helper/repo-root.ts');
9
- const { updateTaskState, readTaskState, mergeRecords } = require('./task-state.ts');
10
- const { taskStoreDir } = require('./task-scope.ts');
8
+ const { REPO_ROOT } = require('../helper/repo-root.js');
9
+ const { updateTaskState, readTaskState, mergeRecords } = require('./task-state.js');
10
+ const { taskStoreDir } = require('./task-scope.js');
11
+ const { assertNoScoreFields } = require('./evidence-fields.js');
11
12
 
12
13
  const VALID_EVIDENCE_KINDS = new Set([
13
14
  'exact-search',
@@ -47,7 +48,7 @@ const EVIDENCE_STATUSES = new Set(['active', 'superseded']);
47
48
  * @param {string} file the ledger path, so the message says which file to look at
48
49
  * @returns {object} the same item, once its status is one of the two
49
50
  */
50
- function assertKnownStatus(item: any, file: string) {
51
+ function assertKnownStatus(item , file ) {
51
52
  const status = item?.status;
52
53
  if (!EVIDENCE_STATUSES.has(status)) {
53
54
  throw new Error(
@@ -66,7 +67,7 @@ function assertKnownStatus(item: any, file: string) {
66
67
  * @param {string} taskId
67
68
  * @returns {string} the validated id, for use in an expression
68
69
  */
69
- function assertSafeTaskId(taskId: string): string {
70
+ function assertSafeTaskId(taskId ) {
70
71
  if (typeof taskId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(taskId) || taskId.includes('..')) {
71
72
  throw new Error(`Invalid task id '${taskId}': expected letters, digits, dot, dash or underscore, and no path separators`);
72
73
  }
@@ -74,14 +75,14 @@ function assertSafeTaskId(taskId: string): string {
74
75
  }
75
76
 
76
77
  class EvidenceLedger {
77
- declare fsImpl: any;
78
- declare repoRoot: any;
79
- declare fixedStateDir: any;
80
- declare slug: any;
81
- declare stateDir: any;
82
- declare evidenceMap: Map<any, any>;
83
- declare baseline: Map<any, any>;
84
- declare seq: number;
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
85
86
  /**
86
87
  * @param {Object} [options]
87
88
  * @param {string} [options.repoRoot]
@@ -89,7 +90,7 @@ class EvidenceLedger {
89
90
  * @param {string} [options.slug] feature whose namespace the records live under (see task-scope.js)
90
91
  * @param {Object} [options.fsImpl]
91
92
  */
92
- constructor(options: any = {}) {
93
+ constructor(options = {}) {
93
94
  this.fsImpl = options.fsImpl || fs;
94
95
  this.repoRoot = options.repoRoot || REPO_ROOT;
95
96
  this.fixedStateDir = options.stateDir || null;
@@ -101,7 +102,7 @@ class EvidenceLedger {
101
102
  }
102
103
 
103
104
  /** Where this task's evidence file lives: the feature's namespace when it has one (IC-002). */
104
- fileFor(taskId: any) {
105
+ fileFor(taskId ) {
105
106
  const dir = this.fixedStateDir
106
107
  || taskStoreDir({ projectRoot: this.repoRoot, store: 'evidence', taskId, slug: this.slug });
107
108
  return path.join(dir, `${assertSafeTaskId(taskId)}.json`);
@@ -115,7 +116,7 @@ class EvidenceLedger {
115
116
  * scheme. The random component carries that burden; timestamp and sequence stay for readability.
116
117
  * @returns {string}
117
118
  */
118
- generateId(): string {
119
+ generateId() {
119
120
  this.seq += 1;
120
121
  return `ev_${Date.now().toString(36)}_${crypto.randomBytes(4).toString('hex')}_${this.seq.toString(36)}`;
121
122
  }
@@ -125,10 +126,11 @@ class EvidenceLedger {
125
126
  * @param {Object} item
126
127
  * @returns {string} evidenceId
127
128
  */
128
- addEvidence(item: any): string {
129
+ addEvidence(item ) {
129
130
  if (!item || typeof item !== 'object') {
130
131
  throw new Error('Evidence item must be an object');
131
132
  }
133
+ assertNoScoreFields(item, '');
132
134
 
133
135
  if (!item.kind || !VALID_EVIDENCE_KINDS.has(item.kind)) {
134
136
  throw new Error(`Invalid evidence kind '${item.kind}'. Allowed: ${[...VALID_EVIDENCE_KINDS].join(', ')}`);
@@ -152,6 +154,8 @@ class EvidenceLedger {
152
154
  fileHash: item.freshness?.fileHash || null,
153
155
  observedAt: item.freshness?.observedAt || new Date().toISOString(),
154
156
  status: item.freshness?.status || 'FRESH',
157
+ isDirty: item.freshness?.isDirty !== undefined ? item.freshness.isDirty : null,
158
+ ...(item.freshness?.treeFingerprint ? { treeFingerprint: item.freshness.treeFingerprint } : {}),
155
159
  };
156
160
 
157
161
  const evidenceRecord = {
@@ -163,6 +167,7 @@ class EvidenceLedger {
163
167
  provenance,
164
168
  content: item.content || null,
165
169
  freshness,
170
+ ...(item.result !== undefined ? { result: item.result } : {}),
166
171
  // Which template requirements this item claims to establish. Readiness counts an item toward
167
172
  // a requirement only when the writer named it here — kind-matching alone let an item saying
168
173
  // "tests failed" satisfy a passing-baseline requirement (architecture review R1, P1).
@@ -190,7 +195,7 @@ class EvidenceLedger {
190
195
  * @param {string} id
191
196
  * @returns {Object|null}
192
197
  */
193
- getEvidence(id: string) {
198
+ getEvidence(id ) {
194
199
  return this.evidenceMap.get(id) || null;
195
200
  }
196
201
 
@@ -199,7 +204,7 @@ class EvidenceLedger {
199
204
  * @param {Object} [filter={}]
200
205
  * @returns {Array<Object>}
201
206
  */
202
- queryEvidence(filter: any = {}) {
207
+ queryEvidence(filter = {}) {
203
208
  const results = [];
204
209
  for (const item of this.evidenceMap.values()) {
205
210
  if (filter.taskId && item.taskId !== filter.taskId) continue;
@@ -208,6 +213,8 @@ class EvidenceLedger {
208
213
  if (filter.capability && item.source.capability !== filter.capability) continue;
209
214
  if (filter.file && item.locator.file !== filter.file) continue;
210
215
  if (filter.status && item.freshness.status !== filter.status) continue;
216
+ if (filter.queryRef && item.source?.queryRef !== filter.queryRef) continue;
217
+ if (filter.attemptRef && item.source?.attemptRef !== filter.attemptRef) continue;
211
218
  results.push(item);
212
219
  }
213
220
  return results;
@@ -225,7 +232,7 @@ class EvidenceLedger {
225
232
  * @param {string} replacedBy the id of the item that supersedes it; must already exist
226
233
  * @returns {string} the new status
227
234
  */
228
- supersedeEvidence(evidenceId: string, replacedBy: string): string {
235
+ supersedeEvidence(evidenceId , replacedBy ) {
229
236
  if (!replacedBy) {
230
237
  throw new Error('supersede requires the id of the evidence item that replaces this one');
231
238
  }
@@ -254,7 +261,7 @@ class EvidenceLedger {
254
261
  * @param {Array<string>} modifiedFiles
255
262
  * @returns {number} count of invalidated items
256
263
  */
257
- invalidateFiles(modifiedFiles: any): number {
264
+ invalidateFiles(modifiedFiles ) {
258
265
  if (!Array.isArray(modifiedFiles) || modifiedFiles.length === 0) return 0;
259
266
  const fileSet = new Set(modifiedFiles);
260
267
  let count = 0;
@@ -283,12 +290,12 @@ class EvidenceLedger {
283
290
  * @param {string} [taskId='default']
284
291
  * @returns {string} filePath
285
292
  */
286
- save(taskId = 'default'): string {
293
+ save(taskId = 'default') {
287
294
  const targetFile = this.fileFor(taskId);
288
- let merged: any;
295
+ let merged ;
289
296
  const file = updateTaskState({
290
297
  fsImpl: this.fsImpl, file: targetFile,
291
- build: (disk: any) => {
298
+ build: (disk ) => {
292
299
  merged = mergeRecords(this.queryEvidence({ taskId }), disk?.evidence || [], this.baseline);
293
300
  return { taskId, updatedAt: new Date().toISOString(), evidenceCount: merged.length, evidence: merged };
294
301
  },
@@ -305,7 +312,7 @@ class EvidenceLedger {
305
312
  * @param {string} [taskId='default']
306
313
  * @returns {number} count of loaded items
307
314
  */
308
- load(taskId = 'default'): number {
315
+ load(taskId = 'default') {
309
316
  const targetFile = this.fileFor(taskId);
310
317
  const data = readTaskState(this.fsImpl, targetFile);
311
318
  if (data && Array.isArray(data.evidence)) {