@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
@@ -16,11 +16,11 @@
16
16
  */
17
17
 
18
18
  const nodeFs = require('node:fs');
19
- const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.ts');
20
- const { appendEvents, readFold } = require('./event-store.ts');
21
- const { deriveStatuses } = require('./status.ts');
22
- const { FollowupUsageError, oneLine, channelBy } = require('./followup.ts');
23
- const { goalView, goalConflicts, goalConflictNext } = require('./overview.ts');
19
+ const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.js');
20
+ const { appendEvents, readFold } = require('./event-store.js');
21
+ const { deriveStatuses } = require('./status.js');
22
+ const { FollowupUsageError, oneLine, channelBy } = require('./followup.js');
23
+ const { goalView, goalConflicts, goalConflictNext } = require('./overview.js');
24
24
 
25
25
  const GOAL_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
26
26
  const GOAL_ID_MAX = 40;
@@ -32,14 +32,14 @@ const USER_CHANNELS = ['question', 'gate', 'prompt'];
32
32
  /** Fold conflicts that are a refusal under their own name; any other conflict is an `illegal-transition`. */
33
33
  const CONFLICT_FINDINGS = new Set(['goal-exists', 'unknown-goal', 'unknown-item', 'goal-already-linked', 'untracked-feature', 'evidence-required']);
34
34
 
35
- function refusal(action: any, finding: any, message: any) { return { ok: false, action, finding, message }; }
35
+ function refusal(action , finding , message ) { return { ok: false, action, finding, message }; }
36
36
 
37
- function refusalFrom(action: any, failed: any) {
37
+ function refusalFrom(action , failed ) {
38
38
  const finding = failed.conflict && CONFLICT_FINDINGS.has(failed.conflict.code) ? failed.conflict.code : failed.finding;
39
39
  return refusal(action, finding, failed.message);
40
40
  }
41
41
 
42
- function goalIdOf(raw: any) {
42
+ function goalIdOf(raw ) {
43
43
  if (raw === undefined || raw === null || raw === '') throw new FollowupUsageError('--goal is required: a kebab-case goal id of at most 40 characters');
44
44
  const id = String(raw);
45
45
  if (id.length > GOAL_ID_MAX || !GOAL_ID.test(id)) {
@@ -48,7 +48,7 @@ function goalIdOf(raw: any) {
48
48
  return id;
49
49
  }
50
50
 
51
- function itemIdOf(raw: any) {
51
+ function itemIdOf(raw ) {
52
52
  const list = Array.isArray(raw) ? raw : raw === undefined ? [] : [raw];
53
53
  if (list.length !== 1) throw new FollowupUsageError('--item takes exactly one checklist id such as C3 for --action check');
54
54
  if (!ITEM_ID.test(String(list[0]))) throw new FollowupUsageError(`--item must be a checklist id such as C3 (got '${String(list[0]).slice(0, 40)}')`);
@@ -56,25 +56,25 @@ function itemIdOf(raw: any) {
56
56
  }
57
57
 
58
58
  /** One masked, one-line, bounded text; a problem is a usage error naming the flag. */
59
- function textOf(raw: any, label: any) {
60
- const problems: any = [];
59
+ function textOf(raw , label ) {
60
+ const problems = [];
61
61
  const text = raw === undefined ? null : oneLine(raw, label, problems);
62
62
  if (problems.length) throw new FollowupUsageError(problems.join('; '));
63
63
  return text;
64
64
  }
65
65
 
66
- function requiredText(raw: any, label: any) {
66
+ function requiredText(raw , label ) {
67
67
  if (raw === undefined || raw === null) throw new FollowupUsageError(`${label} is required`);
68
68
  return textOf(raw, label);
69
69
  }
70
70
 
71
- function unknownGoal(action: any, fold: any, id: any) {
72
- const known = fold.goals.map((g: any) => g.goal);
71
+ function unknownGoal(action , fold , id ) {
72
+ const known = fold.goals.map((g ) => g.goal);
73
73
  return refusal(action, 'unknown-goal', `${id} is not a goal${known.length ? `; the goals are ${known.join(', ')}` : '; none has been added'}. Nothing was written.`);
74
74
  }
75
75
 
76
76
  /** The next free `C<n>`: one past the largest number in use, so an id is never reused. */
77
- function nextItemId(goal: any) {
77
+ function nextItemId(goal ) {
78
78
  let top = 0;
79
79
  for (const item of goal.items) {
80
80
  const m = /^C(\d+)$/.exec(item.id);
@@ -83,51 +83,51 @@ function nextItemId(goal: any) {
83
83
  return `C${top + 1}`;
84
84
  }
85
85
 
86
- const progressOf = (goal: any) => ({ met: goal.items.filter((i: any) => i.met).length, total: goal.items.length });
86
+ const progressOf = (goal ) => ({ met: goal.items.filter((i ) => i.met).length, total: goal.items.length });
87
87
 
88
- function checkLine(goalId: any, item: any) {
88
+ function checkLine(goalId , item ) {
89
89
  return `Record a met item with evidence: doflow-run goal --action check --goal ${goalId} --item ${item} --evidence "<what shows it>"`;
90
90
  }
91
91
 
92
- function doneLine(goalId: any) {
92
+ function doneLine(goalId ) {
93
93
  return `Every item of ${goalId} is met; ask the user, then: doflow-run goal --action done --goal ${goalId} --channel question`;
94
94
  }
95
95
 
96
96
  // ── add, item ──────────────────────────────────────────────────────────────────────────────────
97
97
 
98
98
  /** IC-022 `add`: one `goal.added` event with the outcome and the checklist, ids `C1`, `C2`, ... */
99
- function addGoal({ root, goal, statement, items, channel, now = new Date(), fsImpl = nodeFs }: any) {
99
+ function addGoal({ root, goal, statement, items, channel, now = new Date(), fsImpl = nodeFs } ) {
100
100
  const id = goalIdOf(goal);
101
101
  const outcome = requiredText(statement, '--statement');
102
102
  const list = Array.isArray(items) ? items : items === undefined ? [] : [items];
103
103
  if (list.length === 0) throw new FollowupUsageError('--item is required for --action add: at least one checklist item (repeat --item for more)');
104
104
  if (list.length > ITEMS_MAX) throw new FollowupUsageError(`--item was given ${list.length} times; a goal starts with at most ${ITEMS_MAX} items`);
105
- const problems: any = [];
105
+ const problems = [];
106
106
  const checklist = list.map((raw, index) => ({ id: `C${index + 1}`, text: oneLine(raw, `--item ${index + 1}`, problems) }));
107
107
  if (problems.length) throw new FollowupUsageError(problems.join('; '));
108
108
  const by = channelBy(channel);
109
- if (readFold(root, { fsImpl, now }).goals.some((g: any) => g.goal === id)) return refusal('add', 'goal-exists', `${id} already exists. Nothing was written.`);
109
+ if (readFold(root, { fsImpl, now }).goals.some((g ) => g.goal === id)) return refusal('add', 'goal-exists', `${id} already exists. Nothing was written.`);
110
110
  const out = appendEvents(root, [{ type: 'goal.added', by, data: { goal: id, outcome, items: checklist } }], { now, fsImpl });
111
111
  if (!out.ok) return refusalFrom('add', out);
112
112
  return {
113
- ok: true, action: 'add', goal: id, outcome, items: checklist, events: out.written.map((w: any) => w.file),
113
+ ok: true, action: 'add', goal: id, outcome, items: checklist, events: out.written.map((w ) => w.file),
114
114
  next: [`Link a feature that serves it: doflow-run goal --action link --goal ${id} --slug <slug>`, checkLine(id, 'C1')],
115
115
  };
116
116
  }
117
117
 
118
118
  /** IC-022 `item`: one `goal.item-added` event; the id is the next free `C<n>`. */
119
- function addItem({ root, goal, text, channel, now = new Date(), fsImpl = nodeFs }: any) {
119
+ function addItem({ root, goal, text, channel, now = new Date(), fsImpl = nodeFs } ) {
120
120
  const id = goalIdOf(goal);
121
121
  const itemText = requiredText(text, '--text');
122
122
  const by = channelBy(channel);
123
123
  // The id is chosen before the lock, so a writer that took it first makes this call refuse; a retry picks the next one.
124
124
  for (let attempt = 0; ; attempt += 1) {
125
125
  const fold = readFold(root, { fsImpl, now });
126
- const found = fold.goals.find((g: any) => g.goal === id);
126
+ const found = fold.goals.find((g ) => g.goal === id);
127
127
  if (!found) return unknownGoal('item', fold, id);
128
128
  const item = { id: nextItemId(found), text: itemText };
129
129
  const out = appendEvents(root, [{ type: 'goal.item-added', by, data: { goal: id, item } }], { now, fsImpl });
130
- if (out.ok) return { ok: true, action: 'item', goal: id, item, events: out.written.map((w: any) => w.file), next: [checkLine(id, item.id)] };
130
+ if (out.ok) return { ok: true, action: 'item', goal: id, item, events: out.written.map((w ) => w.file), next: [checkLine(id, item.id)] };
131
131
  const raced = out.conflict && out.conflict.code === 'illegal-transition' && /already has item/.test(out.conflict.reason);
132
132
  if (!raced || attempt >= 4) return refusalFrom('item', out);
133
133
  }
@@ -136,39 +136,39 @@ function addItem({ root, goal, text, channel, now = new Date(), fsImpl = nodeFs
136
136
  // ── check, link ────────────────────────────────────────────────────────────────────────────────
137
137
 
138
138
  /** IC-022 `check`: `goal.checked` with `met: true`, or `met: false` under `--unmet`; evidence either way (DEC-015). */
139
- function checkItem({ root, goal, item, evidence, unmet = false, channel, now = new Date(), fsImpl = nodeFs }: any) {
139
+ function checkItem({ root, goal, item, evidence, unmet = false, channel, now = new Date(), fsImpl = nodeFs } ) {
140
140
  const id = goalIdOf(goal);
141
141
  const itemId = itemIdOf(item);
142
142
  const proof = requiredText(evidence, '--evidence');
143
143
  const by = channelBy(channel);
144
144
  const before = readFold(root, { fsImpl, now });
145
- const found = before.goals.find((g: any) => g.goal === id);
145
+ const found = before.goals.find((g ) => g.goal === id);
146
146
  if (!found) return unknownGoal('check', before, id);
147
- if (!found.items.some((i: any) => i.id === itemId)) {
148
- return refusal('check', 'unknown-item', `${id} has no item ${itemId}; its items are ${found.items.map((i: any) => i.id).join(', ')}. Nothing was written.`);
147
+ if (!found.items.some((i ) => i.id === itemId)) {
148
+ return refusal('check', 'unknown-item', `${id} has no item ${itemId}; its items are ${found.items.map((i ) => i.id).join(', ')}. Nothing was written.`);
149
149
  }
150
150
  const met = !unmet;
151
151
  const out = appendEvents(root, [{ type: 'goal.checked', by, data: { goal: id, item: itemId, met, evidence: proof } }], { now, fsImpl });
152
152
  if (!out.ok) return refusalFrom('check', out);
153
- const after = readFold(root, { fsImpl, now }).goals.find((g: any) => g.goal === id) || found;
153
+ const after = readFold(root, { fsImpl, now }).goals.find((g ) => g.goal === id) || found;
154
154
  const progress = progressOf(after);
155
155
  const proposeDone = after.status === 'open' && progress.total > 0 && progress.met === progress.total;
156
- const unchecked = after.items.find((i: any) => !i.met);
156
+ const unchecked = after.items.find((i ) => !i.met);
157
157
  return {
158
- ok: true, action: 'check', goal: id, item: itemId, met, evidence: proof, progress, proposeDone, events: out.written.map((w: any) => w.file),
158
+ ok: true, action: 'check', goal: id, item: itemId, met, evidence: proof, progress, proposeDone, events: out.written.map((w ) => w.file),
159
159
  next: proposeDone ? [doneLine(id)] : unchecked && after.status === 'open' ? [checkLine(id, unchecked.id)] : [],
160
160
  };
161
161
  }
162
162
 
163
163
  /** IC-022 `link`: one `goal.linked` event; a feature serves one goal at most (DEC-009). */
164
- function linkFeature({ root, goal, slug, replace = false, channel, now = new Date(), fsImpl = nodeFs }: any) {
164
+ function linkFeature({ root, goal, slug, replace = false, channel, now = new Date(), fsImpl = nodeFs } ) {
165
165
  const id = goalIdOf(goal);
166
166
  if (!slug) throw new FollowupUsageError('--slug is required for --action link');
167
167
  if (!isSafeSlug(slug)) throw new FollowupUsageError(invalidSlugRefusal(slug).message);
168
168
  const by = channelBy(channel);
169
169
  const fold = readFold(root, { fsImpl, now });
170
- if (!fold.goals.some((g: any) => g.goal === id)) return unknownGoal('link', fold, id);
171
- const feature = fold.features.find((f: any) => f.slug === slug);
170
+ if (!fold.goals.some((g ) => g.goal === id)) return unknownGoal('link', fold, id);
171
+ const feature = fold.features.find((f ) => f.slug === slug);
172
172
  if (!feature) return refusal('link', 'untracked-feature', `${slug} is not a tracked feature, so no goal can link it; run doflow-run lifecycle --action init --slug ${slug} first. Nothing was written.`);
173
173
  if (feature.goal === id) return { ok: true, action: 'link', goal: id, slug, linked: 'already', replaced: null, events: [], next: [] };
174
174
  if (feature.goal && !replace) {
@@ -176,17 +176,17 @@ function linkFeature({ root, goal, slug, replace = false, channel, now = new Dat
176
176
  }
177
177
  const out = appendEvents(root, [{ type: 'goal.linked', by, data: { goal: id, slug, replace: Boolean(replace) } }], { now, fsImpl });
178
178
  if (!out.ok) return refusalFrom('link', out);
179
- return { ok: true, action: 'link', goal: id, slug, linked: 'new', replaced: feature.goal || null, events: out.written.map((w: any) => w.file), next: [] };
179
+ return { ok: true, action: 'link', goal: id, slug, linked: 'new', replaced: feature.goal || null, events: out.written.map((w ) => w.file), next: [] };
180
180
  }
181
181
 
182
182
  // ── list ───────────────────────────────────────────────────────────────────────────────────────
183
183
 
184
184
  /** IC-022 progress for one goal: items, `progress`, `proposeDone`, linked features by status and the nudges. */
185
- function progressView(goal: any, fold: any, statuses: any, conflicts = []) {
185
+ function progressView(goal , fold , statuses , conflicts = []) {
186
186
  const view = goalView(goal, fold.features, statuses, conflicts);
187
187
  return {
188
188
  goal: goal.goal, outcome: goal.outcome, status: goal.status,
189
- items: goal.items.map((i: any) => ({ id: i.id, text: i.text, met: i.met, evidence: i.evidence })),
189
+ items: goal.items.map((i ) => ({ id: i.id, text: i.text, met: i.met, evidence: i.evidence })),
190
190
  progress: view.items, proposeDone: view.proposeDone, features: view.features, nudges: view.nudges,
191
191
  ...(view.conflicts ? { conflicts: view.conflicts } : {}),
192
192
  ...(goal.status === 'done' ? { reason: goal.reason } : {}),
@@ -194,25 +194,25 @@ function progressView(goal: any, fold: any, statuses: any, conflicts = []) {
194
194
  }
195
195
 
196
196
  /** IC-022 `list`: every goal, or the one named, with its progress. Reads git only when a goal has a linked feature. */
197
- function listGoals({ root, goal, now = new Date(), fsImpl = nodeFs }: any) {
197
+ function listGoals({ root, goal, now = new Date(), fsImpl = nodeFs } ) {
198
198
  const id = goal === undefined ? null : goalIdOf(goal);
199
199
  const fold = readFold(root, { fsImpl, now });
200
- const shown = id === null ? fold.goals : fold.goals.filter((g: any) => g.goal === id);
200
+ const shown = id === null ? fold.goals : fold.goals.filter((g ) => g.goal === id);
201
201
  if (id !== null && shown.length === 0) return unknownGoal('list', fold, id);
202
- const linked = new Set(fold.features.filter((f: any) => shown.some((g: any) => g.goal === f.goal)).map((f: any) => f.slug));
202
+ const linked = new Set(fold.features.filter((f ) => shown.some((g ) => g.goal === f.goal)).map((f ) => f.slug));
203
203
  const statuses = linked.size ? deriveStatuses({ root, fold }) : { statuses: {} };
204
204
  const conflictsByGoal = goalConflicts(root, fold, fsImpl);
205
- const goals = shown.map((g: any) => progressView(g, fold, statuses, conflictsByGoal.get(g.goal)));
205
+ const goals = shown.map((g ) => progressView(g, fold, statuses, conflictsByGoal.get(g.goal)));
206
206
  const next = [];
207
- for (const g of goals.filter((x: any) => x.status === 'open')) {
207
+ for (const g of goals.filter((x ) => x.status === 'open')) {
208
208
  if (g.proposeDone) next.push(doneLine(g.goal));
209
209
  else {
210
- const unchecked = g.items.find((i: any) => !i.met);
210
+ const unchecked = g.items.find((i ) => !i.met);
211
211
  if (unchecked) next.push(checkLine(g.goal, unchecked.id));
212
212
  }
213
213
  }
214
- for (const g of goals.filter((x: any) => x.conflicts)) next.push(...goalConflictNext(g.goal, g.conflicts));
215
- const result: any = { ok: true, action: 'list', goals, conflicts: fold.conflicts, unreadable: fold.unreadable, unreadableReasons: fold.unreadableReasons, next };
214
+ for (const g of goals.filter((x ) => x.conflicts)) next.push(...goalConflictNext(g.goal, g.conflicts));
215
+ const result = { ok: true, action: 'list', goals, conflicts: fold.conflicts, unreadable: fold.unreadable, unreadableReasons: fold.unreadableReasons, next };
216
216
  if (statuses.reason) result.reason = statuses.reason;
217
217
  return result;
218
218
  }
@@ -223,7 +223,7 @@ function listGoals({ root, goal, now = new Date(), fsImpl = nodeFs }: any) {
223
223
  * IC-022 `done`: one `goal.done` event. Only the user closes a goal (DEC-004), so `default` is
224
224
  * refused; with unmet items it is refused unless `--reason` says why the user closes it anyway.
225
225
  */
226
- function doneGoal({ root, goal, reason, channel, now = new Date(), fsImpl = nodeFs }: any) {
226
+ function doneGoal({ root, goal, reason, channel, now = new Date(), fsImpl = nodeFs } ) {
227
227
  const id = goalIdOf(goal);
228
228
  const why = textOf(reason, '--reason');
229
229
  const by = channelBy(channel);
@@ -231,16 +231,16 @@ function doneGoal({ root, goal, reason, channel, now = new Date(), fsImpl = node
231
231
  return refusal('done', 'not-user', `only the user marks a goal done: pass --channel question, gate or prompt once the user has said so (got ${channel ? `'${channel}'` : 'no channel'}). Nothing was written.`);
232
232
  }
233
233
  const fold = readFold(root, { fsImpl, now });
234
- const found = fold.goals.find((g: any) => g.goal === id);
234
+ const found = fold.goals.find((g ) => g.goal === id);
235
235
  if (!found) return unknownGoal('done', fold, id);
236
236
  if (found.status === 'done') return refusal('done', 'already-done', `${id} is already done. Nothing was written.`);
237
- const unmet = found.items.filter((i: any) => !i.met).map((i: any) => i.id);
237
+ const unmet = found.items.filter((i ) => !i.met).map((i ) => i.id);
238
238
  if (unmet.length && !why) {
239
239
  return refusal('done', 'items-unmet', `${id} has unmet items (${unmet.join(', ')}); check them with evidence, or pass --reason to say why the user closes it anyway. Nothing was written.`);
240
240
  }
241
241
  const out = appendEvents(root, [{ type: 'goal.done', by, data: { goal: id, reason: why } }], { now, fsImpl });
242
242
  if (!out.ok) return refusalFrom('done', out);
243
- return { ok: true, action: 'done', goal: id, reason: why, unmet, events: out.written.map((w: any) => w.file), next: [] };
243
+ return { ok: true, action: 'done', goal: id, reason: why, unmet, events: out.written.map((w ) => w.file), next: [] };
244
244
  }
245
245
 
246
246
  module.exports = {
@@ -27,11 +27,11 @@ const INTENT_SECTIONS = [
27
27
  ];
28
28
 
29
29
  /** `Cart robustness!` -> `cart-robustness`; empty when the title holds no letter or digit. */
30
- function kebabTitle(title: any): string {
30
+ function kebabTitle(title ) {
31
31
  return String(title).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, SLUG_MAX).replace(/-+$/, '');
32
32
  }
33
33
 
34
- function sourceParts(source: any): any[] {
34
+ function sourceParts(source ) {
35
35
  switch (source.kind) {
36
36
  case 'stage': return [source.kind, source.feature, source.stage];
37
37
  case 'run': return [source.kind, source.taskClass, source.taskId, source.stage];
@@ -46,7 +46,7 @@ function sourceParts(source: any): any[] {
46
46
  * @param {{title:string, by:'user'|'agent', date:string, items:Array<{id:string,statement:string,source:Object}>}} intent
47
47
  * @returns {string} the file content
48
48
  */
49
- function renderIntent({ title, by, date, items }: { title: string; by: string; date: string; items: Array<{ id: string; statement: string; source: any }> }): string {
49
+ function renderIntent({ title, by, date, items } ) {
50
50
  const lines = [
51
51
  `# Intent: ${title}`,
52
52
  '',
@@ -65,7 +65,7 @@ function renderIntent({ title, by, date, items }: { title: string; by: string; d
65
65
  * @returns {{ok:true, file:string, path:string}|{ok:false, finding:'intent-exists'|'invalid-title', message:string}}
66
66
  * `path` is root-relative with `/` separators.
67
67
  */
68
- function writeIntent(root: string, intent: any, { fsImpl = nodeFs }: any = {}): any {
68
+ function writeIntent(root , intent , { fsImpl = nodeFs } = {}) {
69
69
  const title = String(intent.title ?? '').trim();
70
70
  const name = kebabTitle(title) || intent.fallbackName || '';
71
71
  if (!name || title.length > TITLE_MAX || /[\r\n\u2028\u2029\u0085]/.test(title)) {
@@ -76,7 +76,7 @@ function writeIntent(root: string, intent: any, { fsImpl = nodeFs }: any = {}):
76
76
  fsImpl.mkdirSync(path.dirname(file), { recursive: true });
77
77
  try {
78
78
  fsImpl.writeFileSync(file, renderIntent({ ...intent, title }), { encoding: 'utf8', flag: 'wx' });
79
- } catch (error: any) {
79
+ } catch (error ) {
80
80
  if (error.code === 'EEXIST') return { ok: false, finding: 'intent-exists', message: `${rel.split(path.sep).join('/')} already exists and was left untouched. Nothing was written.` };
81
81
  throw error;
82
82
  }
@@ -11,15 +11,15 @@
11
11
 
12
12
  const nodeFs = require('node:fs');
13
13
  const path = require('node:path');
14
- const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.ts');
15
- const { withDerivedDone } = require('./fold.ts');
16
- const { appendEvents, readFold, readEvents } = require('./event-store.ts');
17
- const { deriveStatuses, bucketize, behindNote } = require('./status.ts');
18
- const { FollowupUsageError, parseIds } = require('./followup.ts');
14
+ const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.js');
15
+ const { withDerivedDone } = require('./fold.js');
16
+ const { appendEvents, readFold, readEvents } = require('./event-store.js');
17
+ const { deriveStatuses, bucketize, behindNote } = require('./status.js');
18
+ const { FollowupUsageError, parseIds } = require('./followup.js');
19
19
 
20
20
  /** The failure store, or null when its module cannot be loaded: the overview then reports no failures. */
21
21
  function failureStoreOrNull() {
22
- try { return require('../failure/store.ts'); } catch { return null; }
22
+ try { return require('../failure/store.js'); } catch { return null; }
23
23
  }
24
24
 
25
25
  const DISCOVERY_SHOWN = 15;
@@ -27,29 +27,29 @@ const MAINTAIN_SHOWN = 50;
27
27
  /** Slugs shown per finished or awaiting-release bucket: those lists only grow, and the overview runs on every brainstorm. */
28
28
  const FEATURES_SHOWN = 10;
29
29
 
30
- const refusal = (action: any, finding: any, message: any) => ({ ok: false, action, finding, message });
30
+ const refusal = (action , finding , message ) => ({ ok: false, action, finding, message });
31
31
 
32
- function statusMap(derived: any) {
33
- return Object.fromEntries(Object.entries(derived.statuses).map(([slug, entry]: [string, any]) => [slug, entry.status]));
32
+ function statusMap(derived ) {
33
+ return Object.fromEntries(Object.entries(derived.statuses).map(([slug, entry] ) => [slug, entry.status]));
34
34
  }
35
35
 
36
36
  /** The new and regressed failure entries inside the DoFlow repository, else null. */
37
- function failureEntries(root: any) {
37
+ function failureEntries(root ) {
38
38
  const failureStore = failureStoreOrNull();
39
39
  if (failureStore === null || !failureStore.isDoflowRepo(root)) return null;
40
- return failureStore.loadEntries().entries.filter((e: any) => failureStore.SHOWN_BY_DEFAULT.has(e.status)).map(failureStore.listedEntry);
40
+ return failureStore.loadEntries().entries.filter((e ) => failureStore.SHOWN_BY_DEFAULT.has(e.status)).map(failureStore.listedEntry);
41
41
  }
42
42
 
43
43
  /** Reports first, then the oldest first; the id keeps equal instants in a stable order. */
44
- function byDiscoveryOrder(a: any, b: any) {
44
+ function byDiscoveryOrder(a , b ) {
45
45
  const report = Number(b.source.kind === 'report') - Number(a.source.kind === 'report');
46
46
  if (report !== 0) return report;
47
47
  const delta = Date.parse(a.history[0].at) - Date.parse(b.history[0].at);
48
48
  return delta !== 0 ? delta : (a.id < b.id ? -1 : 1);
49
49
  }
50
50
 
51
- function shownItem(item: any, pending: any) {
52
- const shown: any = {
51
+ function shownItem(item , pending ) {
52
+ const shown = {
53
53
  id: item.id, statement: item.statement, source: item.source, state: item.state,
54
54
  promoted: Boolean(item.intent), intent: item.intent, added: item.added,
55
55
  };
@@ -58,7 +58,7 @@ function shownItem(item: any, pending: any) {
58
58
  }
59
59
 
60
60
  /** A bucket for display: the true count and the most recently tracked slugs, in tracking order. */
61
- function shownBucket(slugs: any) {
61
+ function shownBucket(slugs ) {
62
62
  const shown = slugs.slice(-FEATURES_SHOWN);
63
63
  return { count: slugs.length, shown, more: slugs.length - shown.length };
64
64
  }
@@ -70,11 +70,11 @@ function shownBucket(slugs: any) {
70
70
  * whose text the goal now holds (the user re-added it) is no longer reported.
71
71
  * @returns {Map<string, Array<Object>>}
72
72
  */
73
- function goalConflicts(root: any, fold: any, fsImpl = nodeFs) {
73
+ function goalConflicts(root , fold , fsImpl = nodeFs) {
74
74
  const byGoal = new Map();
75
- const raised = fold.conflicts.filter((c: any) => typeof c.type === 'string' && c.type.startsWith('goal.'));
75
+ const raised = fold.conflicts.filter((c ) => typeof c.type === 'string' && c.type.startsWith('goal.'));
76
76
  if (raised.length === 0) return byGoal;
77
- const events = new Map<any, any>(readEvents(root, { fsImpl }).events.map((e: any) => [e.id, e]));
77
+ const events = new Map (readEvents(root, { fsImpl }).events.map((e ) => [e.id, e]));
78
78
  for (const c of raised) {
79
79
  const event = events.get(c.event);
80
80
  const goal = event && event.data && event.data.goal;
@@ -82,8 +82,8 @@ function goalConflicts(root: any, fold: any, fsImpl = nodeFs) {
82
82
  const entry = { ...c };
83
83
  if (c.type === 'goal.item-added' && event.data.item) {
84
84
  // Event files are never removed, so the dropped item is resolved once the goal holds an item with that text.
85
- const held = fold.goals.find((g: any) => g.goal === goal);
86
- if (held && held.items.some((i: any) => i.text === String(event.data.item.text ?? ''))) continue;
85
+ const held = fold.goals.find((g ) => g.goal === goal);
86
+ if (held && held.items.some((i ) => i.text === String(event.data.item.text ?? ''))) continue;
87
87
  Object.assign(entry, { item: event.data.item.id, text: event.data.item.text });
88
88
  }
89
89
  if (!byGoal.has(goal)) byGoal.set(goal, []);
@@ -93,29 +93,29 @@ function goalConflicts(root: any, fold: any, fsImpl = nodeFs) {
93
93
  }
94
94
 
95
95
  /** One `next` line per kind of goal conflict, telling the user how to resolve it. */
96
- function goalConflictNext(goalId: any, conflicts: any) {
96
+ function goalConflictNext(goalId , conflicts ) {
97
97
  const next = [];
98
- if (conflicts.some((c: any) => c.type === 'goal.item-added')) {
98
+ if (conflicts.some((c ) => c.type === 'goal.item-added')) {
99
99
  next.push(`An item added to ${goalId} from another clone was dropped because its id was already taken; re-add it under a new text: doflow-run goal --action item --goal ${goalId} --text "<the item>"`);
100
100
  }
101
- if (conflicts.some((c: any) => c.type === 'goal.added')) {
101
+ if (conflicts.some((c ) => c.type === 'goal.added')) {
102
102
  next.push(`Another clone added goal ${goalId} first and its record won; re-add yours under a different goal id: doflow-run goal --action add --goal <new-id> --statement "<outcome>"`);
103
103
  }
104
- if (conflicts.some((c: any) => c.type !== 'goal.item-added' && c.type !== 'goal.added')) {
104
+ if (conflicts.some((c ) => c.type !== 'goal.item-added' && c.type !== 'goal.added')) {
105
105
  next.push(`An event for ${goalId} from another clone was dropped (see conflicts); redo it with doflow-run goal --action list as the current state`);
106
106
  }
107
107
  return next;
108
108
  }
109
109
 
110
110
  /** Progress, linked features by status and the nudges of one open goal (IC-022). */
111
- function goalView(goal: any, features: any, statuses: any, conflicts: any = []) {
112
- const linked = features.filter((f: any) => f.goal === goal.goal).map((f: any) => f.slug);
113
- const buckets = bucketize(Object.fromEntries(linked.map((slug: any) => [slug, statuses.statuses[slug] || { status: 'unknown' }])), linked);
114
- const met = goal.items.filter((i: any) => i.met).length;
111
+ function goalView(goal , features , statuses , conflicts = []) {
112
+ const linked = features.filter((f ) => f.goal === goal.goal).map((f ) => f.slug);
113
+ const buckets = bucketize(Object.fromEntries(linked.map((slug ) => [slug, statuses.statuses[slug] || { status: 'unknown' }])), linked);
114
+ const met = goal.items.filter((i ) => i.met).length;
115
115
  const nudges = [];
116
116
  if (buckets.finished.length) {
117
117
  const who = buckets.finished.length === 1 ? `linked feature ${buckets.finished[0]} is finished` : `linked features ${buckets.finished.join(', ')} are finished`;
118
- for (const item of goal.items.filter((i: any) => !i.met)) nudges.push(`${item.id} is unchecked and ${who}`);
118
+ for (const item of goal.items.filter((i ) => !i.met)) nudges.push(`${item.id} is unchecked and ${who}`);
119
119
  }
120
120
  return {
121
121
  goal: goal.goal, outcome: goal.outcome,
@@ -127,7 +127,7 @@ function goalView(goal: any, features: any, statuses: any, conflicts: any = [])
127
127
  };
128
128
  }
129
129
 
130
- function nextLines({ shown, open, items, intents, goals, maintain, pendingItems, failures }: any) {
130
+ function nextLines({ shown, open, items, intents, goals, maintain, pendingItems, failures } ) {
131
131
  const next = [];
132
132
  if (shown < open) next.push('List the rest: doflow-run followup --action list');
133
133
  if (items.length) next.push(`Take items into the new feature when its folder exists: doflow-run lifecycle --action init --slug <slug> --take ${items[0].id}`);
@@ -135,10 +135,10 @@ function nextLines({ shown, open, items, intents, goals, maintain, pendingItems,
135
135
  next.push(`Start from a promoted intent: /do-brainstorm --intent ${intents[0].path}, then doflow-run lifecycle --action init --slug <slug> --intent ${intents[0].path}`);
136
136
  }
137
137
  if (goals.length) next.push(`Link the new feature to a goal: add --goal ${goals[0].goal} to the init line`);
138
- for (const g of goals.filter((x: any) => x.conflicts)) next.push(...goalConflictNext(g.goal, g.conflicts));
138
+ for (const g of goals.filter((x ) => x.conflicts)) next.push(...goalConflictNext(g.goal, g.conflicts));
139
139
  if (maintain && pendingItems.length) {
140
140
  const id = pendingItems[0].id;
141
- const unpromoted = pendingItems.find((i: any) => !i.promoted);
141
+ const unpromoted = pendingItems.find((i ) => !i.promoted);
142
142
  next.push(
143
143
  `Keep open: doflow-run followup --action settle --ids ${id} --as kept --reason "<why it stays>" --channel question`,
144
144
  `Dismiss: doflow-run followup --action settle --ids ${id} --as dismissed --reason "<why>" --channel question`,
@@ -151,7 +151,7 @@ function nextLines({ shown, open, items, intents, goals, maintain, pendingItems,
151
151
  next.push('Settle a failure entry: doflow-run failure --action settle --fp <fp> --as noise|fixed|imported --reason "<why>"');
152
152
  }
153
153
  if (maintain) {
154
- for (const goal of goals.filter((g: any) => g.proposeDone)) {
154
+ for (const goal of goals.filter((g ) => g.proposeDone)) {
155
155
  next.push(`Every item of ${goal.goal} is met; ask the user, then: doflow-run goal --action done --goal ${goal.goal} --channel question`);
156
156
  }
157
157
  }
@@ -165,7 +165,7 @@ function nextLines({ shown, open, items, intents, goals, maintain, pendingItems,
165
165
  * @param {boolean} [options.maintain] the `/do maintain` view: up to 50 items and `pending`
166
166
  * @param {string} [options.since] ISO time; with `maintain`, an open item with no settlement at or after it is pending
167
167
  */
168
- function buildOverview({ root, maintain = false, since, now = new Date(), fsImpl = nodeFs }: any) {
168
+ function buildOverview({ root, maintain = false, since, now = new Date(), fsImpl = nodeFs } ) {
169
169
  let sinceMs = null;
170
170
  if (since !== undefined && since !== null) {
171
171
  sinceMs = Date.parse(since);
@@ -174,25 +174,25 @@ function buildOverview({ root, maintain = false, since, now = new Date(), fsImpl
174
174
  const fold = readFold(root, { fsImpl, now });
175
175
  const derived = deriveStatuses({ root, fold });
176
176
  const followups = withDerivedDone(fold.followups, statusMap(derived));
177
- const open = followups.filter((item: any) => item.state === 'open');
177
+ const open = followups.filter((item ) => item.state === 'open');
178
178
  const limit = maintain ? MAINTAIN_SHOWN : DISCOVERY_SHOWN;
179
179
  // A promotion settles an item for the maintain loop as much as a settlement does: it stays open with an intent.
180
- const settledSince = (item: any) => item.history.some((h: any) => (h.type === 'followup.settled' || h.type === 'followup.promoted') && (sinceMs === null || Date.parse(h.at) >= sinceMs));
181
- const pendingOf = (item: any) => !settledSince(item);
180
+ const settledSince = (item ) => item.history.some((h ) => (h.type === 'followup.settled' || h.type === 'followup.promoted') && (sinceMs === null || Date.parse(h.at) >= sinceMs));
181
+ const pendingOf = (item ) => !settledSince(item);
182
182
  // Maintain shows pending items first (IC-007), so a loop over more than MAINTAIN_SHOWN items reaches the rest.
183
183
  const ordered = [...open].sort((a, b) => (maintain ? Number(pendingOf(b)) - Number(pendingOf(a)) : 0) || byDiscoveryOrder(a, b));
184
184
  const items = ordered.slice(0, limit).map((item) => shownItem(item, maintain ? pendingOf(item) : undefined));
185
185
 
186
186
  const intentGroups = new Map();
187
- for (const item of open.filter((i: any) => i.intent)) {
187
+ for (const item of open.filter((i ) => i.intent)) {
188
188
  if (!intentGroups.has(item.intent)) intentGroups.set(item.intent, []);
189
189
  intentGroups.get(item.intent).push(item.id);
190
190
  }
191
191
  const intents = [...intentGroups].map(([intentPath, ids]) => ({ path: intentPath, items: ids }));
192
192
  const conflictsByGoal = goalConflicts(root, fold, fsImpl);
193
- const goals = fold.goals.filter((g: any) => g.status === 'open').map((g: any) => goalView(g, fold.features, derived, conflictsByGoal.get(g.goal)));
193
+ const goals = fold.goals.filter((g ) => g.status === 'open').map((g ) => goalView(g, fold.features, derived, conflictsByGoal.get(g.goal)));
194
194
 
195
- const result: any = {
195
+ const result = {
196
196
  ok: true,
197
197
  mode: maintain ? 'maintain' : 'discovery',
198
198
  releaseMode: derived.releaseMode,
@@ -228,7 +228,7 @@ function buildOverview({ root, maintain = false, since, now = new Date(), fsImpl
228
228
  * The feature folder is looked for under the IC-001 root itself, never in the current worktree, so a
229
229
  * folder that exists only in a linked worktree is refused.
230
230
  */
231
- function initFeature({ root, slug, take, intent, goal, now = new Date(), fsImpl = nodeFs }: any) {
231
+ function initFeature({ root, slug, take, intent, goal, now = new Date(), fsImpl = nodeFs } ) {
232
232
  if (!slug) throw new FollowupUsageError('--slug is required for --action init');
233
233
  const bad = invalidSlugRefusal(slug);
234
234
  if (bad) throw new FollowupUsageError(bad.message);
@@ -240,7 +240,7 @@ function initFeature({ root, slug, take, intent, goal, now = new Date(), fsImpl
240
240
  }
241
241
  const wanted = take === undefined || take === null ? [] : parseIds(take, '--take');
242
242
  const fold = readFold(root, { fsImpl, now });
243
- const tracked = fold.features.find((f: any) => f.slug === slug);
243
+ const tracked = fold.features.find((f ) => f.slug === slug);
244
244
  const ids = [...wanted];
245
245
  if (intent) {
246
246
  for (const item of fold.followups) if (item.state === 'open' && item.intent === intent && !ids.includes(item.id)) ids.push(item.id);
@@ -262,7 +262,7 @@ function initFeature({ root, slug, take, intent, goal, now = new Date(), fsImpl
262
262
  const code = out.conflict && ['unknown-id', 'untracked-feature', 'goal-already-linked', 'unknown-goal'].includes(out.conflict.code) ? out.conflict.code : out.finding;
263
263
  return refusal('init', code, out.message);
264
264
  }
265
- events = out.written.map((w: any) => w.file);
265
+ events = out.written.map((w ) => w.file);
266
266
  }
267
267
  return { ok: true, action: 'init', slug, tracked: tracked ? 'already' : 'new', taken: ids, goal: goal || (tracked && tracked.goal) || null, events, next };
268
268
  }
@@ -270,19 +270,19 @@ function initFeature({ root, slug, take, intent, goal, now = new Date(), fsImpl
270
270
  // ── status ─────────────────────────────────────────────────────────────────────────────────────
271
271
 
272
272
  /** IC-021 `status`: the derived status of one tracked feature. */
273
- function featureStatus({ root, slug, now = new Date(), fsImpl = nodeFs }: any) {
273
+ function featureStatus({ root, slug, now = new Date(), fsImpl = nodeFs } ) {
274
274
  if (!slug) throw new FollowupUsageError('--slug is required for --action status');
275
275
  if (!isSafeSlug(slug)) throw new FollowupUsageError(invalidSlugRefusal(slug).message);
276
276
  const fold = readFold(root, { fsImpl, now });
277
- if (!fold.features.some((f: any) => f.slug === slug)) {
277
+ if (!fold.features.some((f ) => f.slug === slug)) {
278
278
  return refusal('status', 'untracked-feature', `${slug} is not a tracked feature, so no status is derived for it; run doflow-run lifecycle --action init --slug ${slug} first.`);
279
279
  }
280
280
  const derived = deriveStatuses({ root, fold });
281
281
  const entry = derived.statuses[slug];
282
- const result: any = {
282
+ const result = {
283
283
  ok: true, action: 'status', slug, status: entry.status, integrationRef: derived.integrationRef,
284
284
  evidence: entry.evidence, release: entry.release,
285
- takenItems: fold.followups.filter((item: any) => item.takenBy === slug && item.state === 'taken').map((item: any) => item.id),
285
+ takenItems: fold.followups.filter((item ) => item.takenBy === slug && item.state === 'taken').map((item ) => item.id),
286
286
  };
287
287
  if (derived.reason) result.reason = derived.reason;
288
288
  if (derived.integrationBehind > 0) Object.assign(result, { integrationBehind: derived.integrationBehind, note: behindNote(derived.integrationRef, derived.integrationBehind) });