@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
@@ -5,19 +5,19 @@
5
5
  const crypto = require('node:crypto');
6
6
  const fs = require('node:fs');
7
7
  const path = require('node:path');
8
- const { execFileSync } = require('node:child_process');
9
- const { CapabilityRouter } = require('./capability-router.ts');
10
- const { EvidenceLedger, VALID_EVIDENCE_KINDS, VALID_PROVENANCE } = require('./evidence-ledger.ts');
11
- const { resolveLocator, describeResolution } = require('./locator-resolve.ts');
12
- const { ClaimsManager } = require('./claims.ts');
13
- const { measureFreshness } = require('./freshness.ts');
14
- const { evaluateTaskReadiness } = require('./readiness.ts');
15
- const { writeReadinessRecord } = require('./readiness-record.ts');
16
- const { parseDeclaredScope } = require('./verification/scope-bound.ts');
17
- const { loadRegistry } = require('../registry/index.ts');
18
- const { REPO_ROOT } = require('../helper/repo-root.ts');
8
+ const { CapabilityRouter } = require('./capability-router.js');
9
+ const { EvidenceLedger } = require('./evidence-ledger.js');
10
+ const { ClaimsManager } = require('./claims.js');
11
+ const { measureTreeState } = require('./source-version.js');
12
+ const { toEvidence, validateEvidenceItem, EVIDENCE_REFUSED_FIELDS } = require('./candidate.js');
13
+ const { sourceModeLookup } = require('./retrieval-plan.js');
14
+ const { evaluateTaskReadiness } = require('./readiness.js');
15
+ const { writeReadinessRecord } = require('./readiness-record.js');
16
+ const { parseDeclaredScope } = require('./verification/scope-bound.js');
17
+ const { loadRegistry } = require('../registry/index.js');
18
+ const { REPO_ROOT } = require('../helper/repo-root.js');
19
19
  // One definition of the exit and usage helpers (design §4.2); `finish` is the local spelling of `finishRuntime`.
20
- const { finishRuntime: finish, usageError } = require('./cli-result.ts');
20
+ const { finishRuntime: finish, usageError } = require('./cli-result.js');
21
21
 
22
22
  /**
23
23
  * Handles `doflow capabilities` command execution.
@@ -26,7 +26,7 @@ const { finishRuntime: finish, usageError } = require('./cli-result.ts');
26
26
  * @param {boolean} [options.check=false]
27
27
  * @param {string} [options.repoRoot]
28
28
  */
29
- function handleCapabilitiesCommand({ json = false, check = false, repoRoot }: any = {}) {
29
+ function handleCapabilitiesCommand({ json = false, check = false, repoRoot } = {}) {
30
30
  const root = repoRoot || REPO_ROOT;
31
31
  const router = new CapabilityRouter({ repoRoot: root });
32
32
  const report = router.getAllCapabilitiesHealth(check);
@@ -90,9 +90,9 @@ function handleCapabilitiesCommand({ json = false, check = false, repoRoot }: an
90
90
  * @returns {number} exit code: 0 once the evaluation is recorded, whatever its state
91
91
  */
92
92
  function handleReadinessCommand({
93
- taskClass = 'feature', taskId = 'default', json = false, repoRoot, stateRoot,
93
+ taskClass = 'feature', taskId = 'default', json = false, repoRoot, stateRoot, sourceRoot,
94
94
  verificationPlan, scopeClear, invariants, userDecisionPending = false, mode = 'workflow', now, slug = null,
95
- }: any = {}) {
95
+ } = {}) {
96
96
  // Two different roots, previously conflated into one. `root` locates the *registry* (the
97
97
  // readiness templates ship inside the DoFlow package). `state` locates the invoking project's
98
98
  // evidence, which belongs to the caller's repo — not to wherever DoFlow happens to be installed.
@@ -102,7 +102,7 @@ function handleReadinessCommand({
102
102
  const state = stateRoot || process.cwd();
103
103
  // Only what the caller actually told us. An absent key must stay absent rather than become a
104
104
  // falsy default, because the engine reads presence, not truth.
105
- const profile: any = { taskId, taskClass };
105
+ const profile = { taskId, taskClass };
106
106
  if (typeof verificationPlan === 'string' && verificationPlan.trim() !== '') profile.verificationPlan = verificationPlan;
107
107
  if (typeof scopeClear === 'string' && scopeClear.trim() !== '') profile.scopeClear = scopeClear;
108
108
  if (typeof invariants === 'string' && invariants.trim() !== '') profile.invariants = invariants;
@@ -111,26 +111,26 @@ function handleReadinessCommand({
111
111
 
112
112
  let report;
113
113
  try {
114
- report = evaluateTaskReadiness({ taskProfile: profile, repoRoot: root, projectRoot: state, mode });
115
- } catch (error: any) {
114
+ report = evaluateTaskReadiness({ taskProfile: profile, repoRoot: root, projectRoot: state, treeRoot: sourceRoot || state, mode });
115
+ } catch (error ) {
116
116
  return usageError('readiness', error.message, json, error);
117
117
  }
118
118
 
119
119
  // The evaluation is recorded so the handoff, `verify` and the edit hook can later ask whether
120
120
  // this task was READY before its work began. A scope that parses as a path list is also the
121
121
  // declared scope `verify` bounds a change by; any other wording stays a statement only.
122
- const inputs: any = {};
122
+ const inputs = {};
123
123
  if (profile.verificationPlan) inputs.verificationPlan = profile.verificationPlan;
124
124
  if (profile.scopeClear) inputs.scope = profile.scopeClear;
125
125
  if (profile.invariants) inputs.invariants = profile.invariants;
126
126
  if (profile.userDecisionPending) inputs.userDecisionPending = true;
127
127
  const declared = inputs.scope ? parseDeclaredScope(inputs.scope) : null;
128
128
  const declaredScope = declared && !declared.reason ? declared.paths : null;
129
- let recorded: any;
129
+ let recorded ;
130
130
  try {
131
131
  const { file, replacedUnreadable } = writeReadinessRecord({ stateRoot: state, taskId, slug, report, inputs, declaredScope, mode, now: now || new Date() });
132
132
  recorded = { file, written: true, ...(replacedUnreadable ? { replacedUnreadable } : {}) };
133
- } catch (error: any) {
133
+ } catch (error ) {
134
134
  recorded = { file: null, written: false, error: error.message };
135
135
  }
136
136
  const exitCode = recorded.written ? 0 : 1;
@@ -196,280 +196,42 @@ function handleReadinessCommand({
196
196
  // every key it does not recognise, so a field this boundary forwards blindly is a field the caller
197
197
  // watches vanish while the command reports success. Nothing reaches the ledger unnamed.
198
198
 
199
- /** Every field an evidence item may carry here. Anything else is refused, never dropped. */
200
- const EVIDENCE_ITEM_FIELDS = ['kind', 'provenance', 'source', 'locator', 'content', 'taskId', 'establishes', 'observation'];
201
- const EVIDENCE_SOURCE_FIELDS = ['provider', 'capability'];
202
- const EVIDENCE_LOCATOR_FIELDS = ['file', 'line', 'symbol', 'uri'];
203
- const EVIDENCE_OBSERVATION_FIELDS = ['command', 'exitCode'];
204
-
205
- /** Kinds that record an execution and may carry a typed observation. */
206
- const OBSERVATION_KINDS = new Set(['test-result', 'runtime-observation']);
207
-
208
- /** Kinds that are by definition not a direct read of the repository. FR-007's second sentence. */
209
- const NON_REPOSITORY_KINDS = new Set(['generated-analysis', 'user-statement']);
210
-
211
- /** Fields a caller might plausibly send that this boundary refuses by name, with the reason.
212
- * A named refusal beats the generic unknown-field error: each of these would otherwise be
213
- * silently discarded by `addEvidence`, and a silent discard reads as a successful write. */
214
- const EVIDENCE_REFUSED_FIELDS = new Map([
215
- ['id', 'evidence ids are minted by the ledger — a caller-chosen id can overwrite an unrelated record'],
216
- ['freshness', 'freshness is measured here from the working tree at write time, never asserted by the writer'],
217
- ['supports', 'link evidence to a claim with `claim --action link`, which updates both sides; a list written here would leave the claim unaware of it'],
218
- ['contradicts', 'link evidence to a claim with `claim --action link`, which updates both sides; a list written here would leave the claim unaware of it'],
219
- ['stage', 'the evidence record has no stage field — the batch *is* the stage boundary, so a stage name recorded here would be dropped'],
220
- ['status', "lifecycle status starts 'active' and changes only through `evidence --action supersede` — asserting it here would let a caller declare an item superseded without naming what replaces it"],
221
- ]);
222
-
223
- /** The names a retrieval score arrives under, in camelCase (`--relevance-score` normalises here).
224
- * Exported because `bin/doflow.js` refuses the same names on the command line, and because
225
- * `retrieval-plan.ts` and `outcome.ts` refuse them on the option object a declared plan and a
226
- * terminal record are built from: one definition, three input surfaces (argv, a `--batch` JSON
227
- * file, and a verb's own options), each with exactly one enforcement point. A second copy of
228
- * this list is how one of them drifts. */
229
- const EVIDENCE_SCORE_FIELDS = new Set([
230
- 'confidence', 'score', 'relevance', 'relevanceScore', 'similarity',
231
- 'certainty', 'probability', 'rank', 'distance', 'weight',
232
- ]);
233
-
234
- /** The one refusal message for a score offered as a property of a fact. */
235
- function scoreFieldRefusal(name: any) {
236
- return `'${name}' is a retrieval score and this boundary records none (FR-007). Relevance is a `
237
- + 'property of a search, not of a fact: stored beside the fact it is read as confidence by the '
238
- + 'next worker, which is the specific interpretation the requirement forbids. Record the '
239
- + 'locator and the content instead — a reader who can see where a fact came from does not need '
240
- + 'to be told how sure to be.';
241
- }
242
-
243
- /** Refuses a score-shaped key anywhere in an item, including nested under source or locator. */
244
- function assertNoScoreFields(value: any, prefix: any) {
245
- if (!value || typeof value !== 'object' || Array.isArray(value)) return;
246
- for (const [key, inner] of Object.entries(value)) {
247
- const dotted = prefix ? `${prefix}.${key}` : key;
248
- if (EVIDENCE_SCORE_FIELDS.has(key)) throw new Error(scoreFieldRefusal(dotted));
249
- assertNoScoreFields(inner, dotted);
250
- }
251
- }
252
-
253
- /**
254
- * Normalises a locator given either as an object or as a `file[:line]` / URI string.
255
- * @param {Object|string} raw
256
- * @returns {Object}
257
- */
258
- function parseLocator(raw: any) {
259
- if (typeof raw === 'string') {
260
- const text = raw.trim();
261
- if (text === '') throw new Error('locator is empty');
262
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(text)) return { uri: text };
263
- const withLine = text.match(/^(.+):(\d+)$/);
264
- if (withLine) return { file: withLine[1], line: Number(withLine[2]) };
265
- return { file: text };
266
- }
267
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
268
- throw new Error("locator must be an object, or a 'path/to/file[:line]' or URI string");
269
- }
270
- const locator: Record<string, any> = {};
271
- for (const [key, value] of Object.entries(raw)) {
272
- if (!EVIDENCE_LOCATOR_FIELDS.includes(key)) {
273
- throw new Error(`unknown locator field '${key}'. Accepted: ${EVIDENCE_LOCATOR_FIELDS.join(', ')}`);
274
- }
275
- locator[key] = value;
276
- }
277
- if (locator.line !== undefined && (!Number.isInteger(locator.line) || locator.line < 1)) {
278
- throw new Error(`locator.line must be a positive integer, got '${locator.line}'`);
279
- }
280
- for (const key of ['file', 'symbol', 'uri']) {
281
- if (locator[key] !== undefined && (typeof locator[key] !== 'string' || locator[key].trim() === '')) {
282
- throw new Error(`locator.${key} must be a non-empty string`);
283
- }
284
- }
285
- if (!locator.file && !locator.uri) throw new Error('a locator needs at least a file or a uri');
286
- return locator;
287
- }
288
-
289
- /**
290
- * Validates one raw evidence item and returns the record fields the ledger will store.
291
- *
292
- * Throws on anything it cannot account for. Every rule here traces to FR-007 or to the
293
- * vacuous-record family: a record with no statement and no locator asserts nothing, and storing it
294
- * inflates the evidence count that the readiness gate then reads.
295
- *
296
- * @param {Object} raw
297
- * @param {string} taskId the task the batch is being written to
298
- * @param {string} [repoRoot] root a relative locator resolves against; defaults to cwd
299
- * @returns {{kind:string, provenance:string, source:Object, locator:Object, content:(string|null)}}
300
- */
301
- function validateEvidenceItem(raw: any, taskId: any, repoRoot: any) {
302
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('an evidence item must be a JSON object');
303
- assertNoScoreFields(raw, '');
304
-
305
- for (const key of Object.keys(raw)) {
306
- if (EVIDENCE_REFUSED_FIELDS.has(key)) throw new Error(`'${key}' is not accepted here: ${EVIDENCE_REFUSED_FIELDS.get(key)}`);
307
- if (!EVIDENCE_ITEM_FIELDS.includes(key)) {
308
- throw new Error(`unknown evidence field '${key}'. Accepted: ${EVIDENCE_ITEM_FIELDS.join(', ')}`);
309
- }
310
- }
311
-
312
- if (raw.taskId !== undefined && raw.taskId !== taskId) {
313
- throw new Error(`item names task '${raw.taskId}' but this write targets '${taskId}' — one write, one task`);
314
- }
315
-
316
- const kinds = [...VALID_EVIDENCE_KINDS].sort().join(', ');
317
- if (typeof raw.kind !== 'string' || raw.kind.trim() === '') throw new Error(`kind is required. Valid: ${kinds}`);
318
- if (!VALID_EVIDENCE_KINDS.has(raw.kind)) throw new Error(`unknown evidence kind '${raw.kind}'. Valid: ${kinds}`);
319
-
320
- const provenances = [...VALID_PROVENANCE].sort().join(', ');
321
- if (typeof raw.provenance !== 'string' || raw.provenance.trim() === '') {
322
- throw new Error('provenance is required and has no default here — defaulting it would file model '
323
- + `analysis as repository fact, the one distinction FR-007 makes binding. Valid: ${provenances}`);
324
- }
325
- if (!VALID_PROVENANCE.has(raw.provenance)) throw new Error(`unknown provenance '${raw.provenance}'. Valid: ${provenances}`);
326
- if (NON_REPOSITORY_KINDS.has(raw.kind) && raw.provenance === 'extracted') {
327
- throw new Error(`kind '${raw.kind}' cannot carry provenance 'extracted': it is not a read of the `
328
- + "repository, and labelling it as one is exactly how model analysis stops being distinguishable "
329
- + "from fact. Use 'inferred' (derived by analysis) or 'asserted' (stated by a person).");
330
- }
331
-
332
- if (raw.source === undefined) {
333
- throw new Error('source is required: which provider answered, under which capability. '
334
- + "It has no default — 'unknown/general' is a plausible-looking answer to a question nobody asked.");
335
- }
336
- if (!raw.source || typeof raw.source !== 'object' || Array.isArray(raw.source)) {
337
- throw new Error('source must be an object with provider and capability');
338
- }
339
- for (const key of Object.keys(raw.source)) {
340
- if (!EVIDENCE_SOURCE_FIELDS.includes(key)) {
341
- throw new Error(`unknown source field '${key}'. Accepted: ${EVIDENCE_SOURCE_FIELDS.join(', ')}`);
342
- }
343
- }
344
- for (const key of EVIDENCE_SOURCE_FIELDS) {
345
- if (typeof raw.source[key] !== 'string' || raw.source[key].trim() === '') {
346
- throw new Error(`source.${key} is required and must be a non-empty string`);
347
- }
348
- }
349
-
350
- const locator = raw.locator === undefined ? null : parseLocator(raw.locator);
351
- // An item carrying an observation is anchored by the command it records — that command is what
352
- // the next worker re-runs to check it — so the locator becomes optional there and only there.
353
- if (raw.provenance === 'extracted' && !locator && raw.observation === undefined) {
354
- throw new Error("provenance 'extracted' requires a locator — a fact read from the repository "
355
- + 'must name where it was read, or the next worker cannot check it and cannot tell when it goes stale');
356
- }
357
-
358
- // A locator that parses is not a locator that resolves (FR-004). Shape validation alone let an
359
- // item naming line 799 of a 28-line file be recorded as support and counted by the readiness
360
- // gate. The refusal names what the file actually offers, so the writer can correct it here rather
361
- // than discover it at the gate.
362
- if (raw.provenance === 'extracted' && locator) {
363
- const resolution = resolveLocator({ locator, repoRoot });
364
- if (!resolution.resolved) {
365
- throw new Error(`the locator does not resolve: ${describeResolution(locator, resolution)}. `
366
- + "An 'extracted' item asserts a read of the repository, so a locator that points at nothing "
367
- + 'records a fact nobody can check');
368
- }
369
- }
370
-
371
- if (raw.content !== undefined && typeof raw.content !== 'string') throw new Error('content must be a string');
372
- const content = typeof raw.content === 'string' && raw.content.trim() !== '' ? raw.content : null;
373
- if (raw.provenance !== 'extracted' && !content) {
374
- throw new Error(`provenance '${raw.provenance}' requires content — an inferred or asserted item `
375
- + 'is the statement itself, so a record without one records nothing while still counting as evidence');
376
- }
377
-
378
- // Requirement bindings: which readiness-template requirements this item claims to establish.
379
- // Declared by the writer, judged by the gate — the gate counts an item toward a requirement only
380
- // when it is named here, so a structural item can no longer satisfy `architecture_mapped` and
381
- // `blast_radius` at once by category alone (review R1).
382
- let establishes = [];
383
- if (raw.establishes !== undefined) {
384
- const list = typeof raw.establishes === 'string' ? raw.establishes.split(',').map((s: any) => s.trim()) : raw.establishes;
385
- if (!Array.isArray(list) || list.length === 0 || list.some((r) => typeof r !== 'string' || r.trim() === '')) {
386
- throw new Error("'establishes' must be a requirement id, a comma-separated list of them, or a non-empty array of non-empty strings");
387
- }
388
- establishes = list.map((s) => s.trim());
389
- }
390
-
391
- // Typed observation: the execution behind a test-result or runtime-observation. Command and exit
392
- // status are the two facts the gate can actually grade; tree identity and observation time are
393
- // measured into freshness at this same write. Only an 'extracted' item may carry one — an
394
- // observation asserts an execution happened, and an inferred item is by definition not one.
395
- let observation = null;
396
- if (raw.observation !== undefined) {
397
- if (!raw.observation || typeof raw.observation !== 'object' || Array.isArray(raw.observation)) {
398
- throw new Error('observation must be an object with command and exitCode');
399
- }
400
- for (const key of Object.keys(raw.observation)) {
401
- if (!EVIDENCE_OBSERVATION_FIELDS.includes(key)) {
402
- throw new Error(`unknown observation field '${key}'. Accepted: ${EVIDENCE_OBSERVATION_FIELDS.join(', ')}`);
403
- }
404
- }
405
- if (typeof raw.observation.command !== 'string' || raw.observation.command.trim() === '') {
406
- throw new Error('observation.command is required: the exact command whose exit status is being recorded');
407
- }
408
- if (!Number.isInteger(raw.observation.exitCode) || raw.observation.exitCode < 0) {
409
- throw new Error(`observation.exitCode must be a non-negative integer, got '${raw.observation.exitCode}'`);
410
- }
411
- if (!OBSERVATION_KINDS.has(raw.kind)) {
412
- throw new Error(`kind '${raw.kind}' cannot carry an observation — only ${[...OBSERVATION_KINDS].join(', ')} record an execution`);
413
- }
414
- if (raw.provenance !== 'extracted') {
415
- throw new Error(`provenance '${raw.provenance}' cannot carry an observation: an observation asserts a command `
416
- + 'was actually run, which is a read of the world, not an inference or an assertion. '
417
- + "Either record the execution as 'extracted', or drop the observation.");
418
- }
419
- observation = { command: raw.observation.command.trim(), exitCode: raw.observation.exitCode };
420
- }
421
- if (raw.kind === 'test-result' && raw.provenance === 'extracted' && !observation) {
422
- throw new Error("an 'extracted' test-result requires an observation ({command, exitCode}) — a test result "
423
- + 'without the command and exit status behind it is a claim about an execution nobody can check, '
424
- + 'and it is exactly how a failed baseline was once accepted as a passing one');
425
- }
426
-
427
- return {
428
- kind: raw.kind,
429
- provenance: raw.provenance,
430
- source: { provider: raw.source.provider, capability: raw.source.capability },
431
- locator: locator || {},
432
- content,
433
- establishes,
434
- observation,
435
- };
436
- }
199
+ const {
200
+ EVIDENCE_SCORE_FIELDS,
201
+ scoreFieldRefusal,
202
+ assertNoScoreFields,
203
+ } = require('./evidence-fields.js');
437
204
 
438
- /** HEAD of the tree the evidence was observed in, or null outside a repo. Measured, not asserted. */
439
- function headCommit(root: any) {
440
- try {
441
- return execFileSync('git', ['-C', root, 'rev-parse', 'HEAD'], {
442
- encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'],
443
- }).trim() || null;
444
- } catch {
445
- return null;
446
- }
447
- }
448
205
 
449
206
 
450
207
  /**
451
208
  * Reads a stage's evidence batch: a JSON array, or `{ "evidence": [...] }`.
452
209
  * @param {string} batchPath a file path, or `-` for stdin
210
+ * @param {string} taskId
211
+ * @param {string} root
212
+ * @param {string|null} [slug]
453
213
  * @returns {Array<Object>}
454
214
  */
455
- function readEvidenceBatch(batchPath: any) {
215
+ function readEvidenceBatch(batchPath , taskId , root , slug = null) {
456
216
  let text;
457
217
  try {
458
218
  text = batchPath === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(path.resolve(batchPath), 'utf8');
459
- } catch (error: any) {
219
+ } catch (error ) {
460
220
  throw new Error(`cannot read the batch at '${batchPath}': ${error.message}`);
461
221
  }
462
222
  let data;
463
223
  try {
464
224
  data = JSON.parse(text);
465
- } catch (error: any) {
225
+ } catch (error ) {
466
226
  throw new Error(`the batch at '${batchPath}' is not valid JSON: ${error.message}`);
467
227
  }
468
228
  if (Array.isArray(data)) return data;
469
229
  if (data && typeof data === 'object') {
470
230
  for (const key of Object.keys(data)) {
471
231
  if (key === 'evidence') continue;
472
- const named = EVIDENCE_REFUSED_FIELDS.has(key) ? ` — ${EVIDENCE_REFUSED_FIELDS.get(key)}` : '';
232
+ const refusedVal = EVIDENCE_REFUSED_FIELDS.get(key);
233
+ const reason = typeof refusedVal === 'function' ? refusedVal(taskId, root, slug) : refusedVal;
234
+ const named = reason ? ` — ${reason.endsWith('.') ? reason.slice(0, -1) : reason}` : '';
473
235
  throw new Error(`unknown batch field '${key}'${named}. A batch is a JSON array of items, `
474
236
  + "or an object whose only key is 'evidence'");
475
237
  }
@@ -483,7 +245,7 @@ function readEvidenceBatch(batchPath: any) {
483
245
  * The `--action add` arm: validate the whole batch, then write it.
484
246
  * @returns {number} exit code
485
247
  */
486
- function addEvidence({ ledger, root, taskId, item, batchPath, json }: any) {
248
+ function addEvidence({ ledger, root, sourceRoot = root, attemptRoot, taskId, item, batchPath, json, slug = null } ) {
487
249
  const flagged = item && Object.keys(item).length > 0;
488
250
  if (flagged && batchPath) {
489
251
  return usageError('evidence', '--batch and the single-item flags are two spellings of the same write; use one', json);
@@ -495,8 +257,8 @@ function addEvidence({ ledger, root, taskId, item, batchPath, json }: any) {
495
257
 
496
258
  let raws;
497
259
  try {
498
- raws = batchPath ? readEvidenceBatch(batchPath) : [item];
499
- } catch (error: any) {
260
+ raws = batchPath ? readEvidenceBatch(batchPath, taskId, root, slug) : [item];
261
+ } catch (error ) {
500
262
  return usageError('evidence', error.message, json, error);
501
263
  }
502
264
  if (raws.length === 0) {
@@ -506,30 +268,34 @@ function addEvidence({ ledger, root, taskId, item, batchPath, json }: any) {
506
268
 
507
269
  // The whole batch is validated before any of it is written. A half-written stage batch is worse
508
270
  // than a rejected one: the half that landed looks complete to every later reader.
271
+ const treeState = measureTreeState(root);
272
+ const hasAbsence = raws.some((raw ) => raw?.absence === true);
273
+ const absenceTreeState = hasAbsence && path.resolve(sourceRoot) !== path.resolve(root)
274
+ ? measureTreeState(sourceRoot) : null;
275
+ const providerFreshness = new Map();
276
+ let sourceModeOf = null;
509
277
  const validated = [];
510
278
  for (const [index, raw] of raws.entries()) {
511
279
  try {
512
- validated.push(validateEvidenceItem(raw, taskId, root));
513
- } catch (error: any) {
280
+ if (raw && raw.absence && !sourceModeOf) {
281
+ sourceModeOf = sourceModeLookup(new CapabilityRouter({ repoRoot: REPO_ROOT }).capabilities);
282
+ }
283
+ validated.push(toEvidence({ item: raw }, { taskId, root, treeState, absenceTreeState, probeRoot: sourceRoot, attemptRoot, slug, providerFreshness, sourceModeOf }));
284
+ } catch (error ) {
514
285
  const where = raws.length > 1 ? `batch item ${index + 1} of ${raws.length}: ` : '';
515
286
  return usageError('evidence', `${where}${error.message}`, json, error);
516
287
  }
517
288
  }
518
289
 
519
- const gitCommit = headCommit(root);
520
290
  const written = [];
521
291
  let stateFile;
522
292
  try {
523
293
  for (const record of validated) {
524
- const id = ledger.addEvidence({
525
- ...record,
526
- taskId,
527
- freshness: measureFreshness(root, record.locator, gitCommit),
528
- });
294
+ const id = ledger.addEvidence(record);
529
295
  written.push(ledger.getEvidence(id));
530
296
  }
531
297
  stateFile = ledger.save(taskId);
532
- } catch (error: any) {
298
+ } catch (error ) {
533
299
  return usageError('evidence', error.message, json, error);
534
300
  }
535
301
 
@@ -552,13 +318,14 @@ function addEvidence({ ledger, root, taskId, item, batchPath, json }: any) {
552
318
  }
553
319
  console.log('─'.repeat(78));
554
320
  console.log(`Wrote ${written.length} item(s) to ${stateFile}`);
555
- console.log(`Commit at observation: ${gitCommit || 'not a git worktree — freshness cannot expire by diff'}`);
321
+ const observedTree = absenceTreeState || treeState;
322
+ console.log(`Commit at observation: ${observedTree.gitCommit || (observedTree.prefix !== null ? 'not measured' : 'not a git worktree — freshness cannot expire by diff')}`);
556
323
  console.log('═'.repeat(78) + '\n');
557
324
  return finish(0);
558
325
  }
559
326
 
560
327
  /** One-line rendering of a locator for the table views. */
561
- function locatorText(locator: any) {
328
+ function locatorText(locator ) {
562
329
  if (!locator) return '';
563
330
  if (locator.file) return locator.line ? `${locator.file}:${locator.line}` : locator.file;
564
331
  return locator.uri || locator.symbol || '';
@@ -569,14 +336,14 @@ function locatorText(locator: any) {
569
336
  * `handleClaimCommand`'s `supersede` branch — validate, write, save, report the same shape.
570
337
  * @returns {number} exit code
571
338
  */
572
- function supersedeEvidence({ ledger, taskId, evidenceId, replacedBy, json }: any) {
339
+ function supersedeEvidence({ ledger, taskId, evidenceId, replacedBy, json } ) {
573
340
  if (!evidenceId || !replacedBy) {
574
341
  return usageError('evidence', '--evidence-id and --replaced-by are both required for --action supersede', json);
575
342
  }
576
343
  let status;
577
344
  try {
578
345
  status = ledger.supersedeEvidence(evidenceId, replacedBy);
579
- } catch (error: any) {
346
+ } catch (error ) {
580
347
  return usageError('evidence', error.message, json, error);
581
348
  }
582
349
  const stateFile = ledger.save(taskId);
@@ -610,7 +377,7 @@ function supersedeEvidence({ ledger, taskId, evidenceId, replacedBy, json }: any
610
377
  * @param {string} [options.stateRoot]
611
378
  * @returns {number} exit code
612
379
  */
613
- function handleEvidenceCommand({ taskId = 'default', action = 'list', item = null, batchPath = null, evidenceId = null, replacedBy = null, json = false, repoRoot, stateRoot }: any = {}) {
380
+ function handleEvidenceCommand({ taskId = 'default', action = 'list', item = null, batchPath = null, evidenceId = null, replacedBy = null, json = false, repoRoot, stateRoot, sourceRoot, attemptRoot, slug = null } = {}) {
614
381
  // See handleReadinessCommand: evidence is the caller's project state, not DoFlow package state.
615
382
  const root = stateRoot || repoRoot || process.cwd();
616
383
  const isQuery = action === 'list' || action === 'status';
@@ -626,11 +393,11 @@ function handleEvidenceCommand({ taskId = 'default', action = 'list', item = nul
626
393
  const ledger = new EvidenceLedger({ repoRoot: root });
627
394
  try {
628
395
  ledger.load(taskId);
629
- } catch (error: any) {
396
+ } catch (error ) {
630
397
  return usageError('evidence', error.message, json, error);
631
398
  }
632
399
 
633
- if (action === 'add') return addEvidence({ ledger, root, taskId, item, batchPath, json });
400
+ if (action === 'add') return addEvidence({ ledger, root, sourceRoot: sourceRoot || root, attemptRoot, taskId, item, batchPath, json, slug });
634
401
  if (action === 'supersede') return supersedeEvidence({ ledger, taskId, evidenceId, replacedBy, json });
635
402
 
636
403
  const items = ledger.queryEvidence({ taskId });