@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,19 +17,19 @@
17
17
 
18
18
  const SETTLE_AS = ['kept', 'dismissed', 'fix', 'done'];
19
19
 
20
- function byOrder(a: any, b: any): number {
20
+ function byOrder(a , b ) {
21
21
  const delta = Date.parse(a.at) - Date.parse(b.at);
22
22
  if (delta !== 0) return delta;
23
23
  return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
24
24
  }
25
25
 
26
- function isText(value: any): boolean { return typeof value === 'string' && value.trim() !== ''; }
27
- function isIdList(value: any): boolean { return Array.isArray(value) && value.length > 0 && value.every(isText); }
26
+ function isText(value ) { return typeof value === 'string' && value.trim() !== ''; }
27
+ function isIdList(value ) { return Array.isArray(value) && value.length > 0 && value.every(isText); }
28
28
 
29
29
  /** DEC-045: an event dated further ahead than this stays out of the fold and is listed as a conflict. */
30
30
  const FUTURE_LIMIT_MS = 24 * 60 * 60 * 1000;
31
31
 
32
- function newState({ now = new Date() }: { now?: Date } = {}): any {
32
+ function newState({ now = new Date() } = {}) {
33
33
  return {
34
34
  cutoff: now.getTime() + FUTURE_LIMIT_MS,
35
35
  items: new Map(),
@@ -43,15 +43,15 @@ function newState({ now = new Date() }: { now?: Date } = {}): any {
43
43
  };
44
44
  }
45
45
 
46
- function conflict(event: any, code: string, reason: string): { event: string; type: string; reason: string; code: string } {
46
+ function conflict(event , code , reason ) {
47
47
  return { event: event.id, type: event.type, reason, code };
48
48
  }
49
49
 
50
- function touch(item: any, event: any): void {
50
+ function touch(item , event ) {
51
51
  item.history.push({ event: event.id, type: event.type, at: event.at, by: event.by });
52
52
  }
53
53
 
54
- function itemFor(state: any, event: any, id: string): { item?: any; problem?: any } {
54
+ function itemFor(state , event , id ) {
55
55
  const item = state.items.get(id);
56
56
  if (!item) return { problem: conflict(event, 'unknown-id', `${id} is not a follow-up`) };
57
57
  if (state.final.has(id)) return { problem: conflict(event, 'illegal-transition', `${id} is done and accepts no later event`) };
@@ -60,13 +60,13 @@ function itemFor(state: any, event: any, id: string): { item?: any; problem?: an
60
60
 
61
61
  // ── follow-ups ─────────────────────────────────────────────────────────────────────────────────
62
62
 
63
- function applyAdded(state: any, event: any): any[] {
63
+ function applyAdded(state , event ) {
64
64
  const d = event.data;
65
65
  if (!isText(d.id) || !isText(d.statement) || !d.source || typeof d.source !== 'object' || !isText(d.source.kind)) {
66
66
  return [conflict(event, 'malformed', 'followup.added needs id, statement and source.kind')];
67
67
  }
68
68
  if (state.items.has(d.id)) return [conflict(event, 'illegal-transition', `${d.id} was already added`)];
69
- const item: any = {
69
+ const item = {
70
70
  id: d.id,
71
71
  statement: d.statement,
72
72
  source: d.source,
@@ -88,10 +88,10 @@ function applyAdded(state: any, event: any): any[] {
88
88
  return [];
89
89
  }
90
90
 
91
- function applyTaken(state: any, event: any): any[] {
91
+ function applyTaken(state , event ) {
92
92
  const d = event.data;
93
93
  if (!isIdList(d.ids) || !isText(d.feature)) return [conflict(event, 'malformed', 'followup.taken needs ids and feature')];
94
- const problems: any[] = [];
94
+ const problems = [];
95
95
  for (const id of d.ids) {
96
96
  const { item, problem } = itemFor(state, event, id);
97
97
  if (problem) { problems.push(problem); continue; }
@@ -104,11 +104,11 @@ function applyTaken(state: any, event: any): any[] {
104
104
  return problems;
105
105
  }
106
106
 
107
- function settleOne(state: any, event: any, id: string): any {
107
+ function settleOne(state , event , id ) {
108
108
  const d = event.data;
109
109
  const { item, problem } = itemFor(state, event, id);
110
110
  if (problem) return problem;
111
- const refuse = (reason: string) => conflict(event, 'illegal-transition', reason);
111
+ const refuse = (reason ) => conflict(event, 'illegal-transition', reason);
112
112
  switch (d.as) {
113
113
  case 'kept':
114
114
  // IC-003: a reason is required when a taken item is released or a dismissed one reopens, not for an open one.
@@ -139,18 +139,18 @@ function settleOne(state: any, event: any, id: string): any {
139
139
  return null;
140
140
  }
141
141
 
142
- function applySettled(state: any, event: any): any[] {
142
+ function applySettled(state , event ) {
143
143
  const d = event.data;
144
144
  // The legacy single-id shape (`id`) and the batch shape (`ids`) both fold; the verb writes one event per id.
145
145
  const ids = isText(d.id) ? [d.id] : d.ids;
146
146
  if (!isIdList(ids)) return [conflict(event, 'malformed', 'followup.settled needs an id')];
147
- return ids.map((id: string) => settleOne(state, event, id)).filter(Boolean);
147
+ return ids.map((id ) => settleOne(state, event, id)).filter(Boolean);
148
148
  }
149
149
 
150
- function applyPromoted(state: any, event: any): any[] {
150
+ function applyPromoted(state , event ) {
151
151
  const d = event.data;
152
152
  if (!isIdList(d.ids) || !isText(d.intent)) return [conflict(event, 'malformed', 'followup.promoted needs ids and intent')];
153
- const problems: any[] = [];
153
+ const problems = [];
154
154
  for (const id of d.ids) {
155
155
  const { item, problem } = itemFor(state, event, id);
156
156
  if (problem) { problems.push(problem); continue; }
@@ -164,7 +164,7 @@ function applyPromoted(state: any, event: any): any[] {
164
164
 
165
165
  // ── features, goals, releases ──────────────────────────────────────────────────────────────────
166
166
 
167
- function applyTracked(state: any, event: any): any[] {
167
+ function applyTracked(state , event ) {
168
168
  const slug = event.data.slug;
169
169
  if (!isText(slug)) return [conflict(event, 'malformed', 'feature.tracked needs a slug')];
170
170
  // The earliest event counts; a later one from another clone changes nothing and is no conflict.
@@ -172,19 +172,19 @@ function applyTracked(state: any, event: any): any[] {
172
172
  return [];
173
173
  }
174
174
 
175
- function applyMerged(state: any, event: any): any[] {
175
+ function applyMerged(state , event ) {
176
176
  const { slug, reason } = event.data;
177
177
  if (!isText(slug)) return [conflict(event, 'malformed', 'feature.merged needs a slug')];
178
178
  if (!state.merged.has(slug)) state.merged.set(slug, { slug, reason: typeof reason === 'string' ? reason : '', at: event.at });
179
179
  return [];
180
180
  }
181
181
 
182
- function goalFor(state: any, event: any, id: string): { goal?: any; problem?: any } {
182
+ function goalFor(state , event , id ) {
183
183
  const goal = state.goals.get(id);
184
184
  return goal ? { goal } : { problem: conflict(event, 'unknown-goal', `${id} is not a goal`) };
185
185
  }
186
186
 
187
- function applyGoalAdded(state: any, event: any): any[] {
187
+ function applyGoalAdded(state , event ) {
188
188
  const d = event.data;
189
189
  if (!isText(d.goal) || !Array.isArray(d.items)) return [conflict(event, 'malformed', 'goal.added needs goal and items')];
190
190
  // A second `goal.added` for one id, as another clone may write, loses: the first wins.
@@ -195,26 +195,26 @@ function applyGoalAdded(state: any, event: any): any[] {
195
195
  status: 'open',
196
196
  reason: null,
197
197
  added: event.at.slice(0, 10),
198
- items: d.items.filter((i: any) => i && isText(i.id)).map((i: any) => ({ id: i.id, text: String(i.text ?? ''), met: false, evidence: null })),
198
+ items: d.items.filter((i ) => i && isText(i.id)).map((i ) => ({ id: i.id, text: String(i.text ?? ''), met: false, evidence: null })),
199
199
  });
200
200
  return [];
201
201
  }
202
202
 
203
- function applyGoalItemAdded(state: any, event: any): any[] {
203
+ function applyGoalItemAdded(state , event ) {
204
204
  const d = event.data;
205
205
  const { goal, problem } = goalFor(state, event, d.goal);
206
206
  if (problem) return [problem];
207
207
  if (!d.item || !isText(d.item.id)) return [conflict(event, 'malformed', 'goal.item-added needs item.id')];
208
- if (goal.items.some((i: any) => i.id === d.item.id)) return [conflict(event, 'illegal-transition', `${d.goal} already has item ${d.item.id}`)];
208
+ if (goal.items.some((i ) => i.id === d.item.id)) return [conflict(event, 'illegal-transition', `${d.goal} already has item ${d.item.id}`)];
209
209
  goal.items.push({ id: d.item.id, text: String(d.item.text ?? ''), met: false, evidence: null });
210
210
  return [];
211
211
  }
212
212
 
213
- function applyGoalChecked(state: any, event: any): any[] {
213
+ function applyGoalChecked(state , event ) {
214
214
  const d = event.data;
215
215
  const { goal, problem } = goalFor(state, event, d.goal);
216
216
  if (problem) return [problem];
217
- const item = goal.items.find((i: any) => i.id === d.item);
217
+ const item = goal.items.find((i ) => i.id === d.item);
218
218
  if (!item) return [conflict(event, 'unknown-item', `${d.goal} has no item ${d.item}`)];
219
219
  if (!isText(d.evidence)) return [conflict(event, 'evidence-required', 'goal.checked needs evidence')];
220
220
  item.met = d.met === true;
@@ -222,7 +222,7 @@ function applyGoalChecked(state: any, event: any): any[] {
222
222
  return [];
223
223
  }
224
224
 
225
- function applyGoalLinked(state: any, event: any): any[] {
225
+ function applyGoalLinked(state , event ) {
226
226
  const d = event.data;
227
227
  const { problem } = goalFor(state, event, d.goal);
228
228
  if (problem) return [problem];
@@ -235,7 +235,7 @@ function applyGoalLinked(state: any, event: any): any[] {
235
235
  return [];
236
236
  }
237
237
 
238
- function applyGoalDone(state: any, event: any): any[] {
238
+ function applyGoalDone(state , event ) {
239
239
  const d = event.data;
240
240
  const { goal, problem } = goalFor(state, event, d.goal);
241
241
  if (problem) return [problem];
@@ -244,7 +244,7 @@ function applyGoalDone(state: any, event: any): any[] {
244
244
  return [];
245
245
  }
246
246
 
247
- function applyReleaseRecorded(state: any, event: any): any[] {
247
+ function applyReleaseRecorded(state , event ) {
248
248
  const d = event.data;
249
249
  if (!isText(d.tag)) return [conflict(event, 'malformed', 'release.recorded needs a tag')];
250
250
  let release = state.releases.get(d.tag);
@@ -260,7 +260,7 @@ function applyReleaseRecorded(state: any, event: any): any[] {
260
260
  return [];
261
261
  }
262
262
 
263
- const APPLY: Record<string, (state: any, event: any) => any[]> = {
263
+ const APPLY = {
264
264
  'followup.added': applyAdded,
265
265
  'followup.taken': applyTaken,
266
266
  'followup.settled': applySettled,
@@ -283,7 +283,7 @@ const EVENT_TYPES = Object.keys(APPLY);
283
283
  * conflicts to refuse before anything is written.
284
284
  * @returns {Array<{event:string,type:string,reason:string,code:string}>} the conflicts this event raised
285
285
  */
286
- function applyEvent(state: any, event: any, { checkFuture = true }: { checkFuture?: boolean } = {}): any[] {
286
+ function applyEvent(state , event , { checkFuture = true } = {}) {
287
287
  const at = Date.parse(event.at);
288
288
  if (checkFuture && at > state.cutoff) {
289
289
  // Left out of the stamping floor and of every tracking bound: one wrong clock must not move them.
@@ -301,7 +301,7 @@ function applyEvent(state: any, event: any, { checkFuture = true }: { checkFutur
301
301
  }
302
302
 
303
303
  /** @param {Array<Object>} events IC-002 envelopes, in any order */
304
- function foldInto(events: any[], options: any = {}): any {
304
+ function foldInto(events , options = {}) {
305
305
  const state = newState(options);
306
306
  if (options.shouldStop?.()) { state.incomplete = true; return state; }
307
307
  const ordered = [...events].sort(byOrder);
@@ -315,16 +315,16 @@ function foldInto(events: any[], options: any = {}): any {
315
315
  }
316
316
 
317
317
  /** The result of a fold, as plain data. `hasBody(item)` tells a report whose body is on this machine. */
318
- function finalize(state: any, { hasBody = () => false }: any = {}): any {
319
- const followups = [...state.items.values()].map((item: any) => (
318
+ function finalize(state , { hasBody = () => false } = {}) {
319
+ const followups = [...state.items.values()].map((item ) => (
320
320
  item.source.kind === 'report' ? { ...item, body: hasBody(item) ? 'on-this-machine' : 'not-on-this-machine' } : item));
321
- const releases = [...state.releases.values()].map((r: any) => {
321
+ const releases = [...state.releases.values()].map((r ) => {
322
322
  const excluded = [...r.excluded];
323
- return { tag: r.tag, commit: r.commit, features: [...r.features.values()].filter((f: any) => !r.excluded.has(f.slug)), excluded };
323
+ return { tag: r.tag, commit: r.commit, features: [...r.features.values()].filter((f ) => !r.excluded.has(f.slug)), excluded };
324
324
  });
325
325
  return {
326
326
  followups,
327
- features: [...state.tracked.values()].map((f: any) => ({ ...f })),
327
+ features: [...state.tracked.values()].map((f ) => ({ ...f })),
328
328
  merged: [...state.merged.values()],
329
329
  goals: [...state.goals.values()],
330
330
  releases,
@@ -334,7 +334,7 @@ function finalize(state: any, { hasBody = () => false }: any = {}): any {
334
334
  }
335
335
 
336
336
  /** @returns {ReturnType<typeof finalize>} */
337
- function foldEvents(events: any[], options: any = {}): any {
337
+ function foldEvents(events , options = {}) {
338
338
  return finalize(foldInto(events, options), options);
339
339
  }
340
340
 
@@ -344,9 +344,9 @@ function foldEvents(events: any[], options: any = {}): any {
344
344
  * @param {Array<Object>} followups folded items
345
345
  * @param {Object<string,string>|Map<string,string>} statuses feature slug to derived status
346
346
  */
347
- function withDerivedDone(followups: any[], statuses: any): any[] {
348
- const statusOf = (slug: string) => (statuses instanceof Map ? statuses.get(slug) : statuses[slug]);
349
- return followups.map((item: any) => (
347
+ function withDerivedDone(followups , statuses ) {
348
+ const statusOf = (slug ) => (statuses instanceof Map ? statuses.get(slug) : statuses[slug]);
349
+ return followups.map((item ) => (
350
350
  item.state === 'taken' && statusOf(item.takenBy) === 'finished' ? { ...item, state: 'done', derived: true } : item));
351
351
  }
352
352
 
@@ -17,15 +17,15 @@
17
17
  const nodeFs = require('node:fs');
18
18
  const os = require('node:os');
19
19
  const path = require('node:path');
20
- const { maskLine } = require('../mask.ts');
21
- const { STAGES: CHAIN_STAGES } = require('../decision-register.ts');
22
- const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.ts');
23
- const { resolveActiveFeature } = require('../feature-resolve.ts');
24
- const { SETTLE_AS, withDerivedDone } = require('./fold.ts');
25
- const { appendEvents, readFold, byFromChannel, randomChars } = require('./event-store.ts');
26
- const { deriveStatuses } = require('./status.ts');
27
- const { writeIntent, kebabTitle } = require('./intent-writer.ts');
28
- const reportStore = require('./report-store.ts');
20
+ const { maskLine } = require('../mask.js');
21
+ const { STAGES: CHAIN_STAGES } = require('../decision-register.js');
22
+ const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.js');
23
+ const { resolveActiveFeature } = require('../feature-resolve.js');
24
+ const { SETTLE_AS, withDerivedDone } = require('./fold.js');
25
+ const { appendEvents, readFold, byFromChannel, randomChars } = require('./event-store.js');
26
+ const { deriveStatuses } = require('./status.js');
27
+ const { writeIntent, kebabTitle } = require('./intent-writer.js');
28
+ const reportStore = require('./report-store.js');
29
29
 
30
30
  /** The decision register's stages plus the two lifecycle stages (IC-006). */
31
31
  const STAGES = [...CHAIN_STAGES, 'release', 'maintain'];
@@ -43,16 +43,16 @@ const CONFLICT_FINDINGS = new Set(['unknown-id', 'untracked-feature', 'intent-ex
43
43
  /** A caller mistake (exit 2). */
44
44
  class FollowupUsageError extends Error {}
45
45
 
46
- function refusal(action: any, finding: any, message: any) { return { ok: false, action, finding, message }; }
46
+ function refusal(action , finding , message ) { return { ok: false, action, finding, message }; }
47
47
 
48
48
  /** Maps an `appendEvents` failure to a refusal result. */
49
- function refusalFrom(action: any, failed: any) {
49
+ function refusalFrom(action , failed ) {
50
50
  const finding = failed.conflict && CONFLICT_FINDINGS.has(failed.conflict.code) ? failed.conflict.code : failed.finding;
51
51
  return refusal(action, finding, failed.message);
52
52
  }
53
53
 
54
54
  /** Masks a free-text field and checks it is one non-empty line of at most `max` characters. */
55
- function oneLine(raw: any, label: any, problems: any, { max = STATEMENT_MAX }: any = {}) {
55
+ function oneLine(raw , label , problems , { max = STATEMENT_MAX } = {}) {
56
56
  // The length is checked before masking as well, so the masking patterns never see more than a bounded input.
57
57
  const rawLength = String(raw ?? '').length;
58
58
  if (rawLength > max * 4) { problems.push(`${label} is ${rawLength} characters; the limit is ${max}`); return null; }
@@ -64,20 +64,20 @@ function oneLine(raw: any, label: any, problems: any, { max = STATEMENT_MAX }: a
64
64
  return text;
65
65
  }
66
66
 
67
- function newFollowupId(taken: any) {
67
+ function newFollowupId(taken ) {
68
68
  for (;;) {
69
69
  const id = `FU-${randomChars(6)}`;
70
70
  if (!taken.has(id)) { taken.add(id); return id; }
71
71
  }
72
72
  }
73
73
 
74
- function channelBy(channel: any) {
74
+ function channelBy(channel ) {
75
75
  const by = byFromChannel(channel || 'default');
76
76
  if (!by) throw new FollowupUsageError(`unknown --channel '${channel}'. Valid: question, gate, prompt, default`);
77
77
  return by;
78
78
  }
79
79
 
80
- function parseIds(raw: any, label = '--ids') {
80
+ function parseIds(raw , label = '--ids') {
81
81
  const list = Array.isArray(raw) ? raw : String(raw ?? '').split(',');
82
82
  const ids = [...new Set(list.flatMap((entry) => String(entry).split(',')).map((id) => id.trim()).filter(Boolean))];
83
83
  if (ids.length === 0) throw new FollowupUsageError(`${label} is required: one or more follow-up ids, comma separated`);
@@ -87,18 +87,18 @@ function parseIds(raw: any, label = '--ids') {
87
87
  }
88
88
 
89
89
  /** The folded store, with a taken item shown `done` while its feature derives finished (IC-003, IC-021). */
90
- function loadFollowups(root: any, { hasBody, fsImpl = nodeFs, statuses, now = new Date() }: any = {}) {
90
+ function loadFollowups(root , { hasBody, fsImpl = nodeFs, statuses, now = new Date() } = {}) {
91
91
  const fold = readFold(root, { hasBody: hasBody || reportStore.bodyChecker(root, { fsImpl }), fsImpl, now });
92
- const needsStatus = fold.followups.some((item: any) => item.state === 'taken');
92
+ const needsStatus = fold.followups.some((item ) => item.state === 'taken');
93
93
  const derived = needsStatus ? (statuses || deriveStatuses({ root, fold })) : null;
94
- const followups = needsStatus ? withDerivedDone(fold.followups, Object.fromEntries(Object.entries(derived.statuses).map(([slug, s]: [string, any]) => [slug, s.status]))) : fold.followups;
94
+ const followups = needsStatus ? withDerivedDone(fold.followups, Object.fromEntries(Object.entries(derived.statuses).map(([slug, s] ) => [slug, s.status]))) : fold.followups;
95
95
  return { fold, followups };
96
96
  }
97
97
 
98
98
  // ── add ────────────────────────────────────────────────────────────────────────────────────────
99
99
 
100
100
  /** The active feature slug: `--slug`, else the branch through the existing resolver; null when neither. */
101
- function resolveFeatureSlug({ cwd, slug }: any) {
101
+ function resolveFeatureSlug({ cwd, slug } ) {
102
102
  if (slug) {
103
103
  const bad = invalidSlugRefusal(slug);
104
104
  if (bad) throw new FollowupUsageError(bad.message);
@@ -118,8 +118,8 @@ function resolveFeatureSlug({ cwd, slug }: any) {
118
118
  return resolved;
119
119
  }
120
120
 
121
- function sourceFor(kind: any, fields: any, label: any, problems: any) {
122
- const word = (value: any, name: any) => {
121
+ function sourceFor(kind , fields , label , problems ) {
122
+ const word = (value , name ) => {
123
123
  if (typeof value === 'string' && WORD.test(value)) return value;
124
124
  problems.push(`${label}: ${name} must be letters, digits, dot, underscore or dash`);
125
125
  return null;
@@ -145,15 +145,15 @@ function sourceFor(kind: any, fields: any, label: any, problems: any) {
145
145
  * Validates every item and builds its `followup.added` data; nothing is written unless all pass.
146
146
  * @returns {Array<{statement:string, source:Object}>}
147
147
  */
148
- function buildItems({ cwd, items, defaults }: any) {
149
- const problems: any[] = [];
150
- let featureSlug: any;
148
+ function buildItems({ cwd, items, defaults } ) {
149
+ const problems = [];
150
+ let featureSlug ;
151
151
  const feature = () => {
152
152
  if (featureSlug === undefined) featureSlug = resolveFeatureSlug({ cwd, slug: defaults.slug });
153
153
  return featureSlug;
154
154
  };
155
- const built: any[] = [];
156
- items.forEach((raw: any, index: any) => {
155
+ const built = [];
156
+ items.forEach((raw , index ) => {
157
157
  const label = items.length > 1 || defaults.batch ? `item ${index + 1}` : 'the follow-up';
158
158
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) { problems.push(`${label} must be an object`); return; }
159
159
  const statement = oneLine(raw.statement, `${label}: statement`, problems);
@@ -194,7 +194,7 @@ function buildItems({ cwd, items, defaults }: any) {
194
194
  * must be a regular file of at most 16 MiB, so a device or a FIFO is refused, never read.
195
195
  * @returns {Object[]}
196
196
  */
197
- function readBatchFile(file: any, fsImpl = nodeFs) {
197
+ function readBatchFile(file , fsImpl = nodeFs) {
198
198
  let parsed;
199
199
  let fd;
200
200
  try {
@@ -205,7 +205,7 @@ function readBatchFile(file: any, fsImpl = nodeFs) {
205
205
  if (!st.isFile()) throw new Error('not a regular file');
206
206
  if (st.size > reportStore.MAX_READ_BYTES) throw new Error('larger than 16 MiB');
207
207
  parsed = JSON.parse(fsImpl.readFileSync(fd, 'utf8'));
208
- } catch (error: any) {
208
+ } catch (error ) {
209
209
  throw new FollowupUsageError(`cannot read --batch ${file}: ${error.message}`);
210
210
  } finally {
211
211
  if (fd !== undefined) { try { fsImpl.closeSync(fd); } catch { /* read already done */ } }
@@ -218,18 +218,18 @@ function readBatchFile(file: any, fsImpl = nodeFs) {
218
218
  * IC-006 `add`. `items` are `{statement, stage?, source?}`; `defaults` holds the flag values
219
219
  * (`stage`, `slug`, `source`, `taskClass`, `taskId`, `release`, `batch`).
220
220
  */
221
- function addFollowups({ root, cwd = process.cwd(), items, defaults = {}, channel, now = new Date(), fsImpl = nodeFs }: any) {
221
+ function addFollowups({ root, cwd = process.cwd(), items, defaults = {}, channel, now = new Date(), fsImpl = nodeFs } ) {
222
222
  const by = channelBy(channel);
223
223
  const built = buildItems({ cwd, items, defaults });
224
- const taken = new Set(readFold(root, { fsImpl, now }).followups.map((f: any) => f.id));
224
+ const taken = new Set(readFold(root, { fsImpl, now }).followups.map((f ) => f.id));
225
225
  const drafts = built.map(({ statement, source }) => ({ type: 'followup.added', by, data: { id: newFollowupId(taken), statement, source } }));
226
226
  const out = appendEvents(root, drafts, { now, fsImpl });
227
227
  if (!out.ok) return refusalFrom('add', out);
228
228
  return {
229
229
  ok: true,
230
230
  action: 'add',
231
- created: out.written.map(({ event }: any) => ({ id: event.data.id, statement: event.data.statement, source: event.data.source, state: 'open' })),
232
- events: out.written.map((w: any) => w.file),
231
+ created: out.written.map(({ event } ) => ({ id: event.data.id, statement: event.data.statement, source: event.data.source, state: 'open' })),
232
+ events: out.written.map((w ) => w.file),
233
233
  next: [],
234
234
  };
235
235
  }
@@ -237,9 +237,9 @@ function addFollowups({ root, cwd = process.cwd(), items, defaults = {}, channel
237
237
  // ── report ─────────────────────────────────────────────────────────────────────────────────────
238
238
 
239
239
  /** `--release` and `--feature` of a report: letters, digits, dot, underscore or dash, as for `--source release`. */
240
- function reportSource({ release, feature }: any) {
240
+ function reportSource({ release, feature } ) {
241
241
  const problems = [];
242
- const source: any = { kind: 'report' };
242
+ const source = { kind: 'report' };
243
243
  if (release !== undefined && release !== null) {
244
244
  if (typeof release === 'string' && WORD.test(release)) source.release = release; else problems.push('--release must be letters, digits, dot, underscore or dash');
245
245
  }
@@ -269,21 +269,21 @@ function reportSource({ release, feature }: any) {
269
269
  * @param {string} [options.release]
270
270
  * @param {string|string[]} [options.feature]
271
271
  */
272
- function reportFollowup({ root, statement, input, release, feature, channel, now = new Date(), fsImpl = nodeFs, env = process.env }: any) {
272
+ function reportFollowup({ root, statement, input, release, feature, channel, now = new Date(), fsImpl = nodeFs, env = process.env } ) {
273
273
  const by = channelBy(channel);
274
274
  const source = reportSource({ release, feature });
275
- const problems: any[] = [];
275
+ const problems = [];
276
276
  const rawStatement = String(statement ?? '');
277
277
  const statementMasked = maskLine(rawStatement).masked;
278
278
  const cleanStatement = oneLine(rawStatement, '--statement', problems);
279
279
  if (problems.length) throw new FollowupUsageError(problems.join('; '));
280
280
  let raw;
281
- try { raw = reportStore.readReportText(input, { fsImpl }); } catch (error: any) {
281
+ try { raw = reportStore.readReportText(input, { fsImpl }); } catch (error ) {
282
282
  if (error instanceof reportStore.ReportInputError) throw new FollowupUsageError(error.message);
283
283
  throw error;
284
284
  }
285
285
  let body;
286
- try { body = reportStore.prepareBody(raw, { home: env.HOME || os.homedir() }); } catch (error: any) {
286
+ try { body = reportStore.prepareBody(raw, { home: env.HOME || os.homedir() }); } catch (error ) {
287
287
  if (error instanceof reportStore.ReportInputError) throw new FollowupUsageError(error.message);
288
288
  throw error;
289
289
  }
@@ -293,9 +293,9 @@ function reportFollowup({ root, statement, input, release, feature, channel, now
293
293
  // ANSI colour and title sequences were removed before masking; what is left is a real control or bidirectional character.
294
294
  if (CONTROL.test(excerpt)) throw new FollowupUsageError('the first 2 KB of the report body contain a control or bidirectional-override character; remove it and file the report again');
295
295
 
296
- const taken = new Set(readFold(root, { fsImpl, now }).followups.map((f: any) => f.id));
296
+ const taken = new Set(readFold(root, { fsImpl, now }).followups.map((f ) => f.id));
297
297
  let id;
298
- let stored: any = { ok: false, reason: 'no-home' };
298
+ let stored = { ok: false, reason: 'no-home' };
299
299
  for (let attempt = 0; attempt < 5; attempt += 1) {
300
300
  id = newFollowupId(taken);
301
301
  stored = reportStore.writeBody(root, id, body.buffer, { env, fsImpl });
@@ -309,7 +309,7 @@ function reportFollowup({ root, statement, input, release, feature, channel, now
309
309
  let out;
310
310
  try {
311
311
  out = appendEvents(root, [{ type: 'followup.added', by, data }], { now, fsImpl });
312
- } catch (error: any) {
312
+ } catch (error ) {
313
313
  if (kept) reportStore.removeBody(stored.file, fsImpl);
314
314
  throw error;
315
315
  }
@@ -327,7 +327,7 @@ function reportFollowup({ root, statement, input, release, feature, channel, now
327
327
  excerptBytes: Buffer.byteLength(excerpt, 'utf8'), bodyBytes: body.buffer.length,
328
328
  body: kept ? 'on-this-machine' : 'not-on-this-machine', masked: statementMasked + body.masked,
329
329
  }],
330
- events: out.written.map((w: any) => w.file),
330
+ events: out.written.map((w ) => w.file),
331
331
  next,
332
332
  };
333
333
  }
@@ -335,31 +335,31 @@ function reportFollowup({ root, statement, input, release, feature, channel, now
335
335
  // ── list ───────────────────────────────────────────────────────────────────────────────────────
336
336
 
337
337
  /** IC-006 `list`: `state` is open (default), taken, done, dismissed or all. */
338
- function listFollowups({ root, state = 'open', hasBody, fsImpl = nodeFs, statuses, now = new Date() }: any) {
338
+ function listFollowups({ root, state = 'open', hasBody, fsImpl = nodeFs, statuses, now = new Date() } ) {
339
339
  if (state !== 'all' && !STATES.includes(state)) throw new FollowupUsageError(`--state must be one of ${[...STATES, 'all'].join(', ')} (got '${state}')`);
340
340
  const { fold, followups } = loadFollowups(root, { hasBody, fsImpl, statuses, now });
341
- const items = state === 'all' ? followups : followups.filter((item: any) => item.state === state);
341
+ const items = state === 'all' ? followups : followups.filter((item ) => item.state === state);
342
342
  return { ok: true, action: 'list', state, count: items.length, items, conflicts: fold.conflicts, unreadable: fold.unreadable, unreadableReasons: fold.unreadableReasons, next: [] };
343
343
  }
344
344
 
345
345
  // ── take, settle, promote ──────────────────────────────────────────────────────────────────────
346
346
 
347
347
  /** IC-006 `take`: one `followup.taken` event; every id must be open and the feature tracked. */
348
- function takeFollowups({ root, ids, slug, channel, now = new Date(), fsImpl = nodeFs }: any) {
348
+ function takeFollowups({ root, ids, slug, channel, now = new Date(), fsImpl = nodeFs } ) {
349
349
  const list = parseIds(ids);
350
350
  if (!slug) throw new FollowupUsageError('--slug is required for --action take');
351
351
  const bad = invalidSlugRefusal(slug);
352
352
  if (bad) throw new FollowupUsageError(bad.message);
353
353
  const out = appendEvents(root, [{ type: 'followup.taken', by: channelBy(channel), data: { ids: list, feature: slug } }], { now, fsImpl });
354
354
  if (!out.ok) return refusalFrom('take', out);
355
- return { ok: true, action: 'take', ids: list, feature: slug, events: out.written.map((w: any) => w.file), next: [] };
355
+ return { ok: true, action: 'take', ids: list, feature: slug, events: out.written.map((w ) => w.file), next: [] };
356
356
  }
357
357
 
358
358
  /** IC-006 `settle`: one `followup.settled` event per id. Each `as` value's required field is in IC-003. */
359
- function settleFollowups({ root, ids, as, reason, evidence, channel, now = new Date(), fsImpl = nodeFs }: any) {
359
+ function settleFollowups({ root, ids, as, reason, evidence, channel, now = new Date(), fsImpl = nodeFs } ) {
360
360
  const list = parseIds(ids);
361
361
  if (!SETTLE_AS.includes(as)) throw new FollowupUsageError(`--as must be one of ${SETTLE_AS.join(', ')}${as ? ` (got '${as}')` : ''}`);
362
- const problems: any[] = [];
362
+ const problems = [];
363
363
  const cleanReason = reason === undefined ? null : oneLine(reason, '--reason', problems);
364
364
  const cleanEvidence = evidence === undefined ? null : oneLine(evidence, '--evidence', problems);
365
365
  // `kept` needs a reason only when it releases a taken item or reopens a dismissed one; the fold knows the state.
@@ -368,7 +368,7 @@ function settleFollowups({ root, ids, as, reason, evidence, channel, now = new D
368
368
  if (problems.length) throw new FollowupUsageError(problems.join('; '));
369
369
  const by = channelBy(channel);
370
370
  // `list` shows a taken item whose feature finished as `done`, but the fold still holds it `taken`: say what the user was shown.
371
- const derivedDone = new Map(loadFollowups(root, { fsImpl, now }).followups.filter((item: any) => item.derived).map((item: any) => [item.id, item.takenBy]));
371
+ const derivedDone = new Map(loadFollowups(root, { fsImpl, now }).followups.filter((item ) => item.derived).map((item ) => [item.id, item.takenBy]));
372
372
  if (as !== 'kept') {
373
373
  const shown = list.find((id) => derivedDone.has(id));
374
374
  if (shown) {
@@ -384,7 +384,7 @@ function settleFollowups({ root, ids, as, reason, evidence, channel, now = new D
384
384
  action: 'settle',
385
385
  ids: list,
386
386
  as,
387
- events: out.written.map((w: any) => w.file),
387
+ events: out.written.map((w ) => w.file),
388
388
  next: reopened.map((id) => `${id} was shown as done because feature ${derivedDone.get(id)} finished; keeping it released it from that feature, so it is open again`),
389
389
  };
390
390
  }
@@ -393,9 +393,9 @@ function settleFollowups({ root, ids, as, reason, evidence, channel, now = new D
393
393
  * IC-006 `promote` (IC-024): creates a new intent that names the items, then writes one
394
394
  * `followup.promoted` event. It never adds items to an existing intent.
395
395
  */
396
- function promoteFollowups({ root, ids, title, channel, now = new Date(), fsImpl = nodeFs }: any) {
396
+ function promoteFollowups({ root, ids, title, channel, now = new Date(), fsImpl = nodeFs } ) {
397
397
  const list = parseIds(ids);
398
- const problems: any[] = [];
398
+ const problems = [];
399
399
  const cleanTitle = oneLine(title, '--title', problems, { max: 80 });
400
400
  if (problems.length) throw new FollowupUsageError(problems.join('; '));
401
401
  const by = channelBy(channel);
@@ -403,7 +403,7 @@ function promoteFollowups({ root, ids, title, channel, now = new Date(), fsImpl
403
403
  const { followups } = loadFollowups(root, { fsImpl, now });
404
404
  const items = [];
405
405
  for (const id of list) {
406
- const item = followups.find((f: any) => f.id === id);
406
+ const item = followups.find((f ) => f.id === id);
407
407
  if (!item) return refusal('promote', 'unknown-id', `${id} is not a follow-up. Nothing was written.`);
408
408
  if (item.state !== 'open') return refusal('promote', 'illegal-transition', `${id} is ${item.state}; only an open item can be promoted. Nothing was written.`);
409
409
  if (item.intent) return refusal('promote', 'intent-exists', `${id} was already promoted to ${item.intent}. Nothing was written.`);
@@ -418,7 +418,7 @@ function promoteFollowups({ root, ids, title, channel, now = new Date(), fsImpl
418
418
  const out = appendEvents(root, [{ type: 'followup.promoted', by, data: { ids: list, intent: written.path } }], { now, fsImpl });
419
419
  if (!out.ok) return refusalFrom('promote', out);
420
420
  committed = true;
421
- return { ok: true, action: 'promote', intent: written.path, ids: list, events: out.written.map((w: any) => w.file), next: [] };
421
+ return { ok: true, action: 'promote', intent: written.path, ids: list, events: out.written.map((w ) => w.file), next: [] };
422
422
  } finally {
423
423
  // The file is the one this call just created exclusively, so removing it undoes a half-done promotion on every failure path.
424
424
  if (!committed) { try { fsImpl.rmSync(written.file, { force: true }); } catch { /* the failure being reported stands */ } }