@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
@@ -0,0 +1,223 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const crypto = require('node:crypto');
8
+ const { taskStoreDir, resolveTaskScope } = require('./task-scope.js');
9
+ const { assertSafeTaskId } = require('./evidence-ledger.js');
10
+ const { readTaskState, updateTaskState } = require('./task-state.js');
11
+ const { storeFile, activeAttempt } = require('./task-attempts.js');
12
+ const { projectRoot: lifecycleProjectRoot } = require('./lifecycle/root.js');
13
+
14
+ /**
15
+ * Path of a task's retrieval-plan store.
16
+ *
17
+ * The task id is validated by the evidence ledger's own rule, unchanged: it becomes a filename, so
18
+ * it must never be able to name a path.
19
+ * @param {string} projectRoot
20
+ * @param {string} taskId
21
+ * @param {string|null} [slug]
22
+ * @returns {string}
23
+ */
24
+ function planPath(projectRoot , taskId , slug = null) {
25
+ const dir = taskStoreDir({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug });
26
+ return path.join(dir, `${taskId}.json`);
27
+ }
28
+
29
+ /**
30
+ * Reads and validates one task's retrieval plan via readTaskState.
31
+ * Returns null when the plan file does not exist.
32
+ * @param {string} projectRoot
33
+ * @param {string} taskId
34
+ * @param {string|null} [slug]
35
+ * @param {any} [fsImpl]
36
+ * @returns {any|null}
37
+ */
38
+ function readPlan(projectRoot , taskId , slug = null, fsImpl = fs) {
39
+ const file = planPath(projectRoot, taskId, slug);
40
+ return readTaskState(fsImpl, file);
41
+ }
42
+
43
+ /**
44
+ * Updates a task's retrieval plan under the task-state lock.
45
+ * @param {string} projectRoot
46
+ * @param {string} taskId
47
+ * @param {string|null} slug
48
+ * @param {(disk: any|null) => any} build
49
+ * @param {any} [fsImpl]
50
+ * @returns {string} the written file path
51
+ */
52
+ function updatePlan(projectRoot , taskId , slug , build , fsImpl = fs, prepare ) {
53
+ const file = planPath(projectRoot, taskId, slug);
54
+ return updateTaskState({ fsImpl, file, build, prepare });
55
+ }
56
+
57
+ /** Move a syntactically unreadable plan aside before its replacement is written under the lock. */
58
+ function moveCorruptPlanAside(projectRoot , taskId , slug = null, fsImpl = fs) {
59
+ const file = planPath(projectRoot, taskId, slug);
60
+ if (!fsImpl.existsSync(file)) return null;
61
+ try {
62
+ JSON.parse(fsImpl.readFileSync(file, 'utf8'));
63
+ return null;
64
+ } catch (error ) {
65
+ if (!(error instanceof SyntaxError)) throw error;
66
+ const corruptFile = `${file}.corrupt-${new Date().toISOString().replace(/[:.]/g, '-')}`;
67
+ fsImpl.renameSync(file, corruptFile);
68
+ return corruptFile;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Mints a new plan id: rp_<base36 ms>_<4 hex>.
74
+ * @returns {string}
75
+ */
76
+ function mintPlanId() {
77
+ return `rp_${Date.now().toString(36)}_${crypto.randomBytes(2).toString('hex')}`;
78
+ }
79
+
80
+ /**
81
+ * Formats declared intents from a plan: sorted, comma-joined, or 'none declared'.
82
+ *
83
+ * @param {any} plan
84
+ * @returns {string}
85
+ */
86
+ function declaredIntentList(plan , sep = ', ') {
87
+ const declaredIntents = Array.isArray(plan?.needs)
88
+ ? plan.needs.map((n ) => n.intent).filter(Boolean).sort()
89
+ : [];
90
+ return declaredIntents.length > 0 ? declaredIntents.join(sep) : 'none declared';
91
+ }
92
+
93
+ /**
94
+ * Resolves a declared need from a plan for a given intent.
95
+ *
96
+ * @param {any} plan
97
+ * @param {string} intent
98
+ * @param {string} taskId
99
+ * @returns {{ ok: true, queryRef: string, need: any } | { ok: false, message: string }}
100
+ */
101
+ function resolveNeed(plan , intent , taskId ) {
102
+ if (!plan) {
103
+ const message = `no retrieval plan is declared for task '${taskId}', so --need '${intent}' names nothing. Declare one first: doflow retrieval-plan --task-id ${taskId} --action declare --need ${intent}, then re-run.`;
104
+ return { ok: false, message };
105
+ }
106
+ const list = declaredIntentList(plan);
107
+
108
+ if (!plan.planId) {
109
+ const message = `the retrieval plan for task '${taskId}' was declared before query binding and has no plan id. Re-declare it: doflow retrieval-plan --task-id ${taskId} --action declare --need ${declaredIntentList(plan, ',')}, then re-run.`;
110
+ return { ok: false, message };
111
+ }
112
+
113
+ const need = Array.isArray(plan.needs) ? plan.needs.find((n ) => n.intent === intent) : null;
114
+ if (!need) {
115
+ const message = `'${intent}' is not a declared need of task '${taskId}' (plan ${plan.planId}). Declared: ${list}. Re-run with one of them, or re-declare the plan with this need.`;
116
+ return { ok: false, message };
117
+ }
118
+
119
+ if (!need.queryRef) {
120
+ const message = `the retrieval plan for task '${taskId}' was declared before query binding and has no plan id. Re-declare it: doflow retrieval-plan --task-id ${taskId} --action declare --need ${declaredIntentList(plan, ',')}, then re-run.`;
121
+ return { ok: false, message };
122
+ }
123
+
124
+ return {
125
+ ok: true,
126
+ queryRef: need.queryRef,
127
+ need,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Reads the 070 active attempt ref for a task, returning `${taskId}#${token}` or null.
133
+ *
134
+ * Resolves the feature slug via resolveTaskScope, reads storeFile(lifecycleProjectRoot(projectRoot), featureSlug),
135
+ * and finds the active attempt for the task. If no slug, no store, no entry or no active attempt exists,
136
+ * or if any failure occurs finding or reading the store, returns null (IC-005: "no attempt" never fails the write).
137
+ *
138
+ * @param {string} projectRoot
139
+ * @param {string} taskId
140
+ * @param {string|null} [slug]
141
+ * @param {any} [fsImpl]
142
+ * @returns {string|null}
143
+ */
144
+ function activeAttemptRef(projectRoot , taskId , slug = null, fsImpl = fs, attemptRoot = null) {
145
+ try {
146
+ const featureSlug = resolveTaskScope({ projectRoot, taskId, slug }).slug;
147
+ if (!featureSlug) return null;
148
+
149
+ const file = storeFile(attemptRoot || lifecycleProjectRoot(projectRoot), featureSlug);
150
+ if (!fsImpl.existsSync(file)) return null;
151
+
152
+ const data = readTaskState(fsImpl, file);
153
+ if (!data || !data.tasks || !data.tasks[taskId]) return null;
154
+
155
+ const active = activeAttempt(data.tasks[taskId]);
156
+ if (!active || active.token === undefined || active.token === null) return null;
157
+
158
+ return `${taskId}#${active.token}`;
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+
164
+ /** Binds active evidence to declared query refs and explains evidence that could have matched. */
165
+ function bindEvidence(plan , items ) {
166
+ const byNeed = new Map ();
167
+ const unbound = [];
168
+ const needs = Array.isArray(plan?.needs) ? plan.needs : [];
169
+ for (const need of needs) byNeed.set(need.intent, []);
170
+ for (const item of items) {
171
+ if (item.status !== 'active') continue;
172
+ const source = item.source || {};
173
+ const exact = needs.find((need ) => need.queryRef && need.queryRef === source.queryRef);
174
+ if (exact) {
175
+ if (plan.attemptRef && source.attemptRef && plan.attemptRef !== source.attemptRef) {
176
+ unbound.push({ id: item.id, cause: 'other-attempt' });
177
+ } else {
178
+ const matches = byNeed.get(exact.intent);
179
+ if (matches) matches.push(item);
180
+ }
181
+ continue;
182
+ }
183
+ if (source.queryRef) {
184
+ unbound.push({ id: item.id, cause: 'replaced-plan' });
185
+ } else if (needs.some((need ) => need.capability && need.capability === source.capability)) {
186
+ unbound.push({ id: item.id, cause: 'no-query-ref' });
187
+ }
188
+ }
189
+ return { byNeed, unbound };
190
+ }
191
+
192
+ /** Selects the highest-precedence recorded attempt, with the earliest attempt winning ties. */
193
+ function deriveOutcome(need ) {
194
+ const precedence = { RETRIEVED: 0, EMPTY: 1, UNVERIFIED: 3, UNREACHED: 4 };
195
+ const attempts = (Array.isArray(need?.attempts) ? need.attempts : []).filter((attempt ) =>
196
+ !(attempt.recordedBy === 'derived' && attempt.basis !== 'report-need'
197
+ && (!Array.isArray(attempt.evidenceIds) || attempt.evidenceIds.length === 0)));
198
+ const ranked = [...attempts].sort((a, b) => {
199
+ const rank = (attempt ) => attempt.status === 'EMPTY' && ['STALE', 'UNKNOWN'].includes(attempt.indexState)
200
+ ? (attempt.indexState === 'STALE' ? 2 : 3)
201
+ : (precedence[attempt.status] ?? 4);
202
+ return rank(a) - rank(b) || a.n - b.n;
203
+ });
204
+ const winner = ranked[0];
205
+ return {
206
+ result: winner ? winner.status : 'UNREACHED',
207
+ resolvedBy: winner ? winner.n : null,
208
+ viaFallback: Boolean(winner && winner.role === 'fallback'),
209
+ };
210
+ }
211
+
212
+ module.exports = {
213
+ planPath,
214
+ readPlan,
215
+ updatePlan,
216
+ moveCorruptPlanAside,
217
+ mintPlanId,
218
+ resolveNeed,
219
+ activeAttemptRef,
220
+ declaredIntentList,
221
+ bindEvidence,
222
+ deriveOutcome,
223
+ };
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  /** Returns the body of the first `## …<title>…` section, or null when there is none. */
16
- function section(text: string, titlePattern: RegExp): string | null {
16
+ function section(text , titlePattern ) {
17
17
  const lines = text.split('\n');
18
18
  let start = -1;
19
19
  for (let i = 0; i < lines.length; i += 1) {
@@ -28,7 +28,7 @@ function section(text: string, titlePattern: RegExp): string | null {
28
28
  }
29
29
 
30
30
  /** Splits a markdown table row into trimmed cells, or null when the line is not a row. */
31
- function row(line: string): string[] | null {
31
+ function row(line ) {
32
32
  if (!/^\s*\|/.test(line)) return null;
33
33
  const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
34
34
  if (cells.every((c) => /^:?-{3,}:?$/.test(c))) return null; // the header separator
@@ -36,8 +36,8 @@ function row(line: string): string[] | null {
36
36
  }
37
37
 
38
38
  /** Markdown wraps long list items; a criterion or task split over three lines is still one item. */
39
- function joinWrapped(body: string): string[] {
40
- const out: string[] = [];
39
+ function joinWrapped(body ) {
40
+ const out = [];
41
41
  for (const line of body.split('\n')) {
42
42
  if (/^\s+\S/.test(line) && out.length && out[out.length - 1].trim() !== '') {
43
43
  out[out.length - 1] += ` ${line.trim()}`;
@@ -52,9 +52,9 @@ function joinWrapped(body: string): string[] {
52
52
  * `requirement.md` → requirement summaries (for traceability headers) and acceptance criteria
53
53
  * (for test stubs).
54
54
  */
55
- function parseRequirement(text: string): any {
56
- const gaps: any[] = [];
57
- const requirements = new Map<string, any>();
55
+ function parseRequirement(text ) {
56
+ const gaps = [];
57
+ const requirements = new Map ();
58
58
 
59
59
  const frBody = section(text, /Functional Requirements/i) ?? '';
60
60
  const nfrBody = section(text, /Non-Functional Requirements/i) ?? '';
@@ -72,7 +72,7 @@ function parseRequirement(text: string): any {
72
72
  gaps.push({ artifact: 'requirement.md', what: 'requirement index', why: 'no `| FR-nnn | … |` table row found under a Functional Requirements heading' });
73
73
  }
74
74
 
75
- const criteria: any[] = [];
75
+ const criteria = [];
76
76
  const acceptanceBody = section(text, /Acceptance Criteria/i);
77
77
  if (acceptanceBody === null) {
78
78
  gaps.push({ artifact: 'requirement.md', what: 'acceptance criteria', why: 'no "Acceptance Criteria" section — no test stubs can be derived' });
@@ -96,9 +96,9 @@ function parseRequirement(text: string): any {
96
96
  * `design.md` → the component index (traceability) and whatever §4 declares as an interface
97
97
  * (carried through verbatim rather than paraphrased).
98
98
  */
99
- function parseDesign(text: string): any {
100
- const gaps: any[] = [];
101
- const components = new Map<string, any>();
99
+ function parseDesign(text ) {
100
+ const gaps = [];
101
+ const components = new Map ();
102
102
 
103
103
  const body = section(text, /Components\s*(&|and)\s*Boundaries/i) ?? '';
104
104
  for (const line of body.split('\n')) {
@@ -121,9 +121,9 @@ function parseDesign(text: string): any {
121
121
  gaps.push({ artifact: 'design.md', what: 'interface contracts', why: 'no "API / Interface Contracts" section — every emitted file falls back to a placeholder signature' });
122
122
  } else {
123
123
  const lines = contractBody.split('\n');
124
- let heading: string | null = null;
125
- let fence: string | null = null;
126
- let buffer: string[] = [];
124
+ let heading = null;
125
+ let fence = null;
126
+ let buffer = [];
127
127
  for (const line of lines) {
128
128
  const headingMatch = line.match(/^###\s+(.*)$/);
129
129
  if (headingMatch && fence === null) { heading = headingMatch[1].trim(); continue; }
@@ -155,9 +155,9 @@ function parseDesign(text: string): any {
155
155
  const TASK_KEYS = ['owner', 'files', 'depends-on', 'external-contract'];
156
156
 
157
157
  /** `plan.md` → the task list, which is where the intended file layout actually comes from. */
158
- function parsePlan(text: string): any {
159
- const gaps: any[] = [];
160
- const tasks: any[] = [];
158
+ function parsePlan(text ) {
159
+ const gaps = [];
160
+ const tasks = [];
161
161
 
162
162
  // Anchored on the whole heading: `### Task Summary` is orientation, not the task list, and a
163
163
  // loose match would read the summary table's rows as tasks.
@@ -182,14 +182,14 @@ function parsePlan(text: string): any {
182
182
  const stories = [...new Set(head.match(/US\d+/g) || [])].sort();
183
183
  const description = head.replace(/\[P\]/g, '').replace(/\[US\d+\]/g, '').replace(/\s{2,}/g, ' ').trim();
184
184
 
185
- const fields: Record<string, string> = {};
185
+ const fields = {};
186
186
  for (const chunk of meta.split(';')) {
187
187
  const fieldMatch = chunk.match(/^\s*([a-z-]+):\s*(.*)$/);
188
188
  if (!fieldMatch || !TASK_KEYS.includes(fieldMatch[1])) continue;
189
189
  fields[fieldMatch[1]] = fieldMatch[2].trim();
190
190
  }
191
191
 
192
- const list = (value: string | undefined) => (value ? value.split(',').map((v) => v.replace(/`/g, '').trim()).filter(Boolean) : []);
192
+ const list = (value ) => (value ? value.split(',').map((v) => v.replace(/`/g, '').trim()).filter(Boolean) : []);
193
193
 
194
194
  tasks.push({
195
195
  id,
@@ -211,6 +211,9 @@ function parsePlan(text: string): any {
211
211
  }
212
212
 
213
213
  module.exports = {
214
+ section,
215
+ row,
216
+ joinWrapped,
214
217
  parseRequirement,
215
218
  parseDesign,
216
219
  parsePlan,
@@ -17,7 +17,7 @@ const crypto = require('node:crypto');
17
17
  const FINGERPRINT_MARKER = 'doflow-scaffold-fingerprint';
18
18
  const FINGERPRINT_RE = new RegExp(`${FINGERPRINT_MARKER} sha256:([0-9a-f]{64})`);
19
19
 
20
- function sha256(text: string): string {
20
+ function sha256(text ) {
21
21
  return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
22
22
  }
23
23
 
@@ -28,7 +28,7 @@ function sha256(text: string): string {
28
28
  * reading as human-edited; and it lives in the file rather than in a side ledger, so a scaffold
29
29
  * copied, moved or committed on its own still carries the evidence needed to protect it.
30
30
  */
31
- function compose(emitter: any, headerLines: string[], body: string): string {
31
+ function compose(emitter , headerLines , body ) {
32
32
  const fingerprint = sha256(body);
33
33
  const header = [
34
34
  ...headerLines.map((l) => emitter.line(l)),
@@ -38,7 +38,7 @@ function compose(emitter: any, headerLines: string[], body: string): string {
38
38
  }
39
39
 
40
40
  /** The hash a file claims for its own body, and the body it actually has. */
41
- function inspectExisting(text: string): { claimed: string | null; actual: string | null; body: string | null } {
41
+ function inspectExisting(text ) {
42
42
  const match = text.match(FINGERPRINT_RE);
43
43
  if (!match) return { claimed: null, actual: null, body: null };
44
44
  const cut = text.indexOf('\n', text.indexOf(match[0]));