@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
@@ -32,7 +32,7 @@ const path = require('node:path');
32
32
 
33
33
  /** The roles a tier in `verification.json` can ask for. Anything else in an override is an error,
34
34
  * because a typo'd key that is silently ignored is a command the user believes is running. */
35
- const COMMAND_ROLES: readonly string[] = Object.freeze([
35
+ const COMMAND_ROLES = Object.freeze([
36
36
  'parse',
37
37
  'build',
38
38
  'lint',
@@ -60,14 +60,14 @@ const GLOB_CHARS = /[*?[{]/;
60
60
  * segment that has one (`test/**\/*.test.js` -> `test`, `**\/*.test.js` -> `.`); a plain file or
61
61
  * directory is kept as written. Duplicates are dropped.
62
62
  */
63
- function testRootsFromArgs(args: Array<string>): Array<string> {
64
- const roots: Array<string> = [];
63
+ function testRootsFromArgs(args ) {
64
+ const roots = [];
65
65
  for (const arg of args) {
66
66
  const bare = arg.replace(/^(["'])(.*)\1$/, '$2');
67
67
  let root = bare;
68
68
  if (GLOB_CHARS.test(bare)) {
69
69
  const segments = bare.split('/');
70
- const firstGlob = segments.findIndex((segment: string) => GLOB_CHARS.test(segment));
70
+ const firstGlob = segments.findIndex((segment ) => GLOB_CHARS.test(segment));
71
71
  root = segments.slice(0, firstGlob).join('/') || (bare.startsWith('/') ? '/' : '.');
72
72
  }
73
73
  if (!roots.includes(root)) roots.push(root);
@@ -85,19 +85,19 @@ const SHELL_SYNTAX = /[&|;<>$`()\\]/;
85
85
  * while leaving a targeted variant behind would report a tier as runnable that the plan just said
86
86
  * does not exist.
87
87
  */
88
- const DERIVED_ROLES: Readonly<Record<string, string>> = Object.freeze({ testTargeted: 'test' });
88
+ const DERIVED_ROLES = Object.freeze({ testTargeted: 'test' });
89
89
 
90
90
  /**
91
91
  * How a command was arrived at. `declared` means a human or a manifest script named it exactly;
92
92
  * `inferred` means this module constructed it from the toolchain's conventions and it may be wrong.
93
93
  * Kept out of any numeric form on purpose — FR-008 forbids numeric confidence in output paths.
94
94
  */
95
- const DERIVATIONS: readonly string[] = Object.freeze(['declared', 'inferred', 'override']);
95
+ const DERIVATIONS = Object.freeze(['declared', 'inferred', 'override']);
96
96
 
97
- function readFileIfPresent(filePath: string, fsImpl: any): string | null {
97
+ function readFileIfPresent(filePath , fsImpl ) {
98
98
  try {
99
99
  return fsImpl.readFileSync(filePath, 'utf8');
100
- } catch (error: any) {
100
+ } catch (error ) {
101
101
  if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR' || error.code === 'EISDIR')) {
102
102
  return null;
103
103
  }
@@ -105,7 +105,7 @@ function readFileIfPresent(filePath: string, fsImpl: any): string | null {
105
105
  }
106
106
  }
107
107
 
108
- function exists(filePath: string, fsImpl: any): boolean {
108
+ function exists(filePath , fsImpl ) {
109
109
  try {
110
110
  fsImpl.statSync(filePath);
111
111
  return true;
@@ -119,7 +119,7 @@ function exists(filePath: string, fsImpl: any): boolean {
119
119
  * workspace usually works and occasionally installs a second, divergent `node_modules`.
120
120
  * @returns {{ run: string, test: string, manager: string }}
121
121
  */
122
- function nodePackageManager(root: string, fsImpl: any): { run: string; test: string; manager: string } {
122
+ function nodePackageManager(root , fsImpl ) {
123
123
  if (exists(path.join(root, 'pnpm-lock.yaml'), fsImpl)) {
124
124
  return { run: 'pnpm run', test: 'pnpm test', manager: 'pnpm' };
125
125
  }
@@ -135,14 +135,14 @@ function nodePackageManager(root: string, fsImpl: any): { run: string; test: str
135
135
  /**
136
136
  * @returns {{ id: string, roles: Object, absent: Array<string> }|null}
137
137
  */
138
- function detectNode(root: string, fsImpl: any): any {
138
+ function detectNode(root , fsImpl ) {
139
139
  const raw = readFileIfPresent(path.join(root, 'package.json'), fsImpl);
140
140
  if (raw === null) return null;
141
141
 
142
- let pkg: any;
142
+ let pkg ;
143
143
  try {
144
144
  pkg = JSON.parse(raw);
145
- } catch (error: any) {
145
+ } catch (error ) {
146
146
  // A malformed manifest is not an absent manifest. Reporting it as "no build script" would
147
147
  // hand the caller a confident answer derived from a file it failed to read.
148
148
  return { id: 'package.json', roles: {}, absent: [], error: `package.json is not valid JSON: ${error.message}` };
@@ -150,14 +150,14 @@ function detectNode(root: string, fsImpl: any): any {
150
150
 
151
151
  const scripts = (pkg && typeof pkg.scripts === 'object' && pkg.scripts) || {};
152
152
  const pm = nodePackageManager(root, fsImpl);
153
- const roles: Record<string, any> = {};
153
+ const roles = {};
154
154
  // Node has no project-wide parse step distinct from type-checking or running the code; `node
155
155
  // --check` needs an explicit file list this module does not have. Reporting it absent (rather
156
156
  // than leaving it undetected) lets the parse tier resolve NOT_APPLICABLE instead of stalling
157
157
  // every contract in every plain-JavaScript repository on a step that does not exist.
158
- const absent: string[] = ['parse'];
158
+ const absent = ['parse'];
159
159
 
160
- const script = (...names: string[]) => names.find((n) => typeof scripts[n] === 'string' && scripts[n].trim() !== '');
160
+ const script = (...names ) => names.find((n) => typeof scripts[n] === 'string' && scripts[n].trim() !== '');
161
161
 
162
162
  const buildScript = script('build');
163
163
  if (buildScript) roles.build = { command: `${pm.run} ${buildScript}`, derivation: 'declared' };
@@ -180,7 +180,7 @@ function detectNode(root: string, fsImpl: any): any {
180
180
  const testText = scripts[testScript].trim();
181
181
  const scriptArgs = testText.split(' ').slice(2);
182
182
  // A flag (`--require ./setup.js`) would be dropped by the targeted form, so it is not declared.
183
- if (NODE_TEST_SCRIPT.test(testText) && !SHELL_SYNTAX.test(testText) && !scriptArgs.some((arg: string) => arg.startsWith('-'))) {
183
+ if (NODE_TEST_SCRIPT.test(testText) && !SHELL_SYNTAX.test(testText) && !scriptArgs.some((arg ) => arg.startsWith('-'))) {
184
184
  // The runner is named by the script itself, so the targeted form is declared, not guessed.
185
185
  // `testDirs` are the roots the script's arguments name: where its test files live. A quoted
186
186
  // argument loses its quotes and a glob keeps only the path before its first glob segment.
@@ -197,7 +197,7 @@ function detectNode(root: string, fsImpl: any): any {
197
197
  return { id: 'package.json', roles, absent };
198
198
  }
199
199
 
200
- function detectRust(root: string, fsImpl: any): any {
200
+ function detectRust(root , fsImpl ) {
201
201
  if (!exists(path.join(root, 'Cargo.toml'), fsImpl)) return null;
202
202
  return {
203
203
  id: 'Cargo.toml',
@@ -212,7 +212,7 @@ function detectRust(root: string, fsImpl: any): any {
212
212
  };
213
213
  }
214
214
 
215
- function detectGo(root: string, fsImpl: any): any {
215
+ function detectGo(root , fsImpl ) {
216
216
  if (!exists(path.join(root, 'go.mod'), fsImpl)) return null;
217
217
  return {
218
218
  id: 'go.mod',
@@ -231,13 +231,13 @@ function detectGo(root: string, fsImpl: any): any {
231
231
  * dependency for tool-table presence detection is not worth it. Only the presence of a `[tool.X]`
232
232
  * table is used, which is robust to the parts of TOML this does not understand.
233
233
  */
234
- function detectPython(root: string, fsImpl: any): any {
234
+ function detectPython(root , fsImpl ) {
235
235
  const raw = readFileIfPresent(path.join(root, 'pyproject.toml'), fsImpl);
236
236
  if (raw === null) return null;
237
237
 
238
- const hasTable = (name: string) => new RegExp(`^\\s*\\[tool\\.${name}[\\].]`, 'm').test(raw);
239
- const roles: Record<string, any> = {};
240
- const absent: string[] = [];
238
+ const hasTable = (name ) => new RegExp(`^\\s*\\[tool\\.${name}[\\].]`, 'm').test(raw);
239
+ const roles = {};
240
+ const absent = [];
241
241
 
242
242
  if (hasTable('pytest') || exists(path.join(root, 'pytest.ini'), fsImpl) || exists(path.join(root, 'tests'), fsImpl)) {
243
243
  roles.test = { command: 'pytest', derivation: 'inferred' };
@@ -259,7 +259,7 @@ function detectPython(root: string, fsImpl: any): any {
259
259
  }
260
260
 
261
261
  /** Makefile target name → the role it serves. Only unambiguous names are mapped. */
262
- const MAKE_TARGET_ROLES: Readonly<Record<string, string>> = Object.freeze({
262
+ const MAKE_TARGET_ROLES = Object.freeze({
263
263
  build: 'build',
264
264
  compile: 'build',
265
265
  test: 'test',
@@ -271,14 +271,14 @@ const MAKE_TARGET_ROLES: Readonly<Record<string, string>> = Object.freeze({
271
271
  check: 'parse',
272
272
  });
273
273
 
274
- function detectMake(root: string, fsImpl: any): any {
274
+ function detectMake(root , fsImpl ) {
275
275
  const raw = readFileIfPresent(path.join(root, 'Makefile'), fsImpl) ??
276
276
  readFileIfPresent(path.join(root, 'makefile'), fsImpl);
277
277
  if (raw === null) return null;
278
278
 
279
- const roles: Record<string, any> = {};
280
- const absent: string[] = [];
281
- const targets = new Set<string>();
279
+ const roles = {};
280
+ const absent = [];
281
+ const targets = new Set ();
282
282
  // Target lines only: a leading tab means a recipe line, and `:=` is a variable assignment.
283
283
  for (const line of raw.split('\n')) {
284
284
  const m = /^([A-Za-z0-9_.\-/]+)\s*:(?!=)/.exec(line);
@@ -302,7 +302,7 @@ function detectMake(root: string, fsImpl: any): any {
302
302
  * and a repository where it is the real entrypoint is precisely the case the `plan.md` override
303
303
  * exists for.
304
304
  */
305
- const DETECTORS: readonly ((root: string, fsImpl: any) => any)[] = Object.freeze([detectNode, detectRust, detectGo, detectPython, detectMake]);
305
+ const DETECTORS = Object.freeze([detectNode, detectRust, detectGo, detectPython, detectMake]);
306
306
 
307
307
  /**
308
308
  * Extracts the override object from a feature's `plan.md`.
@@ -312,25 +312,25 @@ const DETECTORS: readonly ((root: string, fsImpl: any) => any)[] = Object.freeze
312
312
  * @param {string} planText
313
313
  * @returns {{ override: Object|null, errors: Array<string> }}
314
314
  */
315
- function parsePlanOverride(planText: any): { override: any; errors: string[] } {
315
+ function parsePlanOverride(planText ) {
316
316
  if (typeof planText !== 'string' || planText === '') return { override: null, errors: [] };
317
317
 
318
318
  const fence = new RegExp(`^[ \\t]*\`\`\`${PLAN_OVERRIDE_BLOCK}[ \\t]*\\n([\\s\\S]*?)^[ \\t]*\`\`\``, 'm');
319
319
  const match = fence.exec(planText);
320
320
  if (!match) return { override: null, errors: [] };
321
321
 
322
- let parsed: any;
322
+ let parsed ;
323
323
  try {
324
324
  parsed = JSON.parse(match[1]);
325
- } catch (error: any) {
325
+ } catch (error ) {
326
326
  return { override: null, errors: [`plan.md '${PLAN_OVERRIDE_BLOCK}' block is not valid JSON: ${error.message}`] };
327
327
  }
328
328
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
329
329
  return { override: null, errors: [`plan.md '${PLAN_OVERRIDE_BLOCK}' block must be a JSON object mapping command roles to commands`] };
330
330
  }
331
331
 
332
- const errors: string[] = [];
333
- for (const [key, value] of Object.entries(parsed) as [string, any][]) {
332
+ const errors = [];
333
+ for (const [key, value] of Object.entries(parsed) ) {
334
334
  if (!COMMAND_ROLES.includes(key)) {
335
335
  errors.push(`plan.md override declares unknown command role '${key}'; known roles: ${COMMAND_ROLES.join(', ')}`);
336
336
  continue;
@@ -362,21 +362,21 @@ function parsePlanOverride(planText: any): { override: any; errors: string[] } {
362
362
  * errors: Array<string>
363
363
  * }}
364
364
  */
365
- function detectCommands(options: any = {}): any {
365
+ function detectCommands(options = {}) {
366
366
  const fsImpl = options.fsImpl || fs;
367
367
  const projectRoot = options.projectRoot || process.cwd();
368
368
 
369
- const manifests: string[] = [];
370
- const commands: Record<string, any> = {};
371
- const alternatives: any[] = [];
372
- const absentByRole = new Map<string, string>();
373
- const errors: string[] = [];
369
+ const manifests = [];
370
+ const commands = {};
371
+ const alternatives = [];
372
+ const absentByRole = new Map ();
373
+ const errors = [];
374
374
 
375
375
  for (const detector of DETECTORS) {
376
- let found: any;
376
+ let found ;
377
377
  try {
378
378
  found = detector(projectRoot, fsImpl);
379
- } catch (error: any) {
379
+ } catch (error ) {
380
380
  errors.push(`Detector failed while reading ${projectRoot}: ${error.message}`);
381
381
  continue;
382
382
  }
@@ -386,7 +386,7 @@ function detectCommands(options: any = {}): any {
386
386
  errors.push(found.error);
387
387
  continue;
388
388
  }
389
- for (const [role, entry] of Object.entries(found.roles) as [string, any][]) {
389
+ for (const [role, entry] of Object.entries(found.roles) ) {
390
390
  if (commands[role]) {
391
391
  alternatives.push({ role, command: entry.command, source: found.id, derivation: entry.derivation });
392
392
  } else {
@@ -401,7 +401,7 @@ function detectCommands(options: any = {}): any {
401
401
  // A role another manifest supplied is not absent, whatever the manifest that lacked it said.
402
402
  for (const role of Object.keys(commands)) absentByRole.delete(role);
403
403
 
404
- let planText: any = options.planText;
404
+ let planText = options.planText;
405
405
  if (typeof planText !== 'string' && options.planPath) {
406
406
  planText = readFileIfPresent(options.planPath, fsImpl);
407
407
  if (planText === null) {
@@ -410,12 +410,12 @@ function detectCommands(options: any = {}): any {
410
410
  }
411
411
  }
412
412
 
413
- const declaredNone: string[] = [];
413
+ const declaredNone = [];
414
414
  const { override, errors: overrideErrors } = parsePlanOverride(planText);
415
415
  errors.push(...overrideErrors);
416
416
 
417
417
  if (override && overrideErrors.length === 0) {
418
- for (const [role, value] of Object.entries(override) as [string, any][]) {
418
+ for (const [role, value] of Object.entries(override) ) {
419
419
  if (value === null) {
420
420
  delete commands[role];
421
421
  absentByRole.delete(role);
@@ -494,7 +494,7 @@ function detectCommands(options: any = {}): any {
494
494
  * @param {string} [pattern]
495
495
  * @returns {{ command: string, substituted: boolean }}
496
496
  */
497
- function applyTargetPattern(command: any, pattern?: string): { command: string | null; substituted: boolean } {
497
+ function applyTargetPattern(command , pattern ) {
498
498
  if (typeof command !== 'string' || !command.includes(TARGET_PATTERN_TOKEN)) {
499
499
  return { command, substituted: false };
500
500
  }
@@ -0,0 +1,203 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const path = require('node:path');
6
+ const fs = require('node:fs');
7
+ const { loadRegistry } = require('../../registry/index.js');
8
+
9
+ const TASK_CLASS_BUDGETS = Object.freeze({
10
+ feature: 225000,
11
+ 'dependency-change': 85000,
12
+ bug: 71000,
13
+ refactor: 71000,
14
+ documentation: 57000,
15
+ operations: 45000,
16
+ review: 42000,
17
+ research: 35302,
18
+ 'trivial-edit': 30000,
19
+ });
20
+
21
+ let cachedHarnesses = null;
22
+ let cachedHarnessIds = null;
23
+ let registryLoadCount = 0;
24
+
25
+ function clearRegistryCache() {
26
+ cachedHarnesses = null;
27
+ cachedHarnessIds = null;
28
+ cachedBudgetsJson = null;
29
+ }
30
+
31
+ function getRegistryLoadCount() {
32
+ return registryLoadCount;
33
+ }
34
+
35
+ function getRegisteredHarnesses() {
36
+ if (cachedHarnesses) return cachedHarnesses;
37
+ registryLoadCount++;
38
+ const repoRoot = path.resolve(__dirname, '../../../');
39
+ const harnessesPath = path.join(repoRoot, 'core', 'registry', 'harnesses.json');
40
+ try {
41
+ const raw = JSON.parse(fs.readFileSync(harnessesPath, 'utf8'));
42
+ if (Array.isArray(raw?.harnesses)) {
43
+ cachedHarnesses = raw.harnesses;
44
+ return cachedHarnesses;
45
+ }
46
+ } catch {
47
+ // fallback if unreadable
48
+ }
49
+ cachedHarnesses = loadRegistry({ repoRoot }).harnesses;
50
+ return cachedHarnesses;
51
+ }
52
+
53
+ function getRegisteredHarnessIds() {
54
+ if (cachedHarnessIds && cachedHarnesses) return cachedHarnessIds;
55
+ const ids = getRegisteredHarnesses().map((h ) => h.id);
56
+ cachedHarnessIds = ids;
57
+ return ids;
58
+ }
59
+
60
+ let cachedBudgetsJson = null;
61
+
62
+ function getBudgetsJson() {
63
+ if (cachedBudgetsJson) return cachedBudgetsJson;
64
+ try {
65
+ const budgetsPath = path.resolve(__dirname, '../../../bench/perf/budgets.json');
66
+ if (fs.existsSync(budgetsPath)) {
67
+ cachedBudgetsJson = JSON.parse(fs.readFileSync(budgetsPath, 'utf8'));
68
+ return cachedBudgetsJson;
69
+ }
70
+ } catch {
71
+ // fallback if unreadable
72
+ }
73
+ return null;
74
+ }
75
+
76
+ /**
77
+ * Resolves budget and harness identity per IC-012.
78
+ * Precedence:
79
+ * 1. Harness identity: flag -> env (DOFLOW_AGENT) -> unstated
80
+ * 2. Cap: override (--max-bytes) -> profile -> task-class
81
+ * 3. Capacity: declared -> unknown
82
+ */
83
+ function resolveBudget({
84
+ taskClass = 'feature',
85
+ maxBytes,
86
+ harnessFlag = null,
87
+ env = process.env,
88
+ profiles = null,
89
+ budgetsJson = null,
90
+ } = {}) {
91
+ const harnesses = profiles || getRegisteredHarnesses();
92
+ const validIds = harnesses.map((h ) => h.id);
93
+
94
+ // 1. Harness identity
95
+ let harness ;
96
+
97
+ if (harnessFlag !== null && harnessFlag !== undefined && harnessFlag !== '') {
98
+ if (!validIds.includes(harnessFlag)) {
99
+ const err = new Error(`Unknown harness '${harnessFlag}'. Valid harnesses: ${validIds.join(', ')}`);
100
+ err.code = 'USAGE_ERROR';
101
+ err.validIds = validIds;
102
+ throw err;
103
+ }
104
+ harness = { id: harnessFlag, source: 'flag' };
105
+ } else if (env && env.DOFLOW_AGENT) {
106
+ const agentEnv = env.DOFLOW_AGENT;
107
+ if (validIds.includes(agentEnv)) {
108
+ harness = { id: agentEnv, source: 'env' };
109
+ } else {
110
+ harness = { id: null, source: 'unstated', basis: 'env-unregistered' };
111
+ }
112
+ } else {
113
+ harness = { id: null, source: 'unstated' };
114
+ }
115
+
116
+ // Find harness declaration for profile lookup
117
+ const harnessObj = harness.id ? harnesses.find((h ) => h.id === harness.id) : null;
118
+ const contextProfile = harnessObj?.contextProfile;
119
+
120
+ // Capacity & Tool Limit
121
+ let capacity ;
122
+ let toolResultLimitBytes = null;
123
+
124
+ if (!harness.id) {
125
+ capacity = { value: null, basis: 'unknown:harness-unstated' };
126
+ } else if (contextProfile?.capacityTokens && typeof contextProfile.capacityTokens.value === 'number') {
127
+ capacity = {
128
+ value: contextProfile.capacityTokens.value,
129
+ basis: `declared:${contextProfile.capacityTokens.source || 'profile'}`,
130
+ };
131
+ } else if (contextProfile?.capacityTokens?.unverified) {
132
+ capacity = {
133
+ value: null,
134
+ basis: `unknown:${contextProfile.capacityTokens.unverified}`,
135
+ };
136
+ } else {
137
+ capacity = {
138
+ value: null,
139
+ basis: 'unknown:profile-value-unavailable',
140
+ };
141
+ }
142
+
143
+ if (contextProfile?.toolResultLimitBytes && typeof contextProfile.toolResultLimitBytes.value === 'number') {
144
+ toolResultLimitBytes = contextProfile.toolResultLimitBytes.value;
145
+ }
146
+
147
+ // 2. Cap resolution
148
+ let resolvedMaxBytes ;
149
+ let source ;
150
+ let startupTokens = null;
151
+
152
+ if (Number.isInteger(maxBytes) && maxBytes > 0) {
153
+ resolvedMaxBytes = maxBytes;
154
+ source = 'override';
155
+ } else if (
156
+ contextProfile?.capacityTokens &&
157
+ typeof contextProfile.capacityTokens.value === 'number' &&
158
+ contextProfile?.reserveTokens &&
159
+ typeof contextProfile.reserveTokens.value === 'number'
160
+ ) {
161
+ // Profile available calculation: (capacity - startup - reserve) * 4
162
+ const effectiveBudgets = budgetsJson !== null && budgetsJson !== undefined ? budgetsJson : getBudgetsJson();
163
+ let startupTokensEstimate = 0;
164
+ let startupTokensBasis = 'unmeasured:no-entry';
165
+ if (effectiveBudgets?.startupContext && Array.isArray(effectiveBudgets.startupContext)) {
166
+ const entry = effectiveBudgets.startupContext.find((s ) => s.harness === harness.id);
167
+ if (typeof entry?.tokensEst === 'number') {
168
+ startupTokensEstimate = entry.tokensEst;
169
+ startupTokensBasis = entry.tokensBasis || 'estimated:bytes/4';
170
+ } else if (entry?.sessionStartStdoutBytes?.unmeasured) {
171
+ startupTokensBasis = `unmeasured:${entry.sessionStartStdoutBytes.unmeasured}`;
172
+ }
173
+ }
174
+ startupTokens = {
175
+ value: startupTokensEstimate,
176
+ basis: startupTokensBasis,
177
+ };
178
+ const available = (contextProfile.capacityTokens.value - startupTokensEstimate - contextProfile.reserveTokens.value) * 4;
179
+ resolvedMaxBytes = Math.max(0, available);
180
+ source = 'profile';
181
+ } else {
182
+ resolvedMaxBytes = TASK_CLASS_BUDGETS[taskClass] || 225000;
183
+ source = 'task-class';
184
+ }
185
+
186
+ return {
187
+ maxBytes: resolvedMaxBytes,
188
+ source,
189
+ harness,
190
+ capacity,
191
+ toolResultLimitBytes,
192
+ ...(startupTokens ? { startupTokens } : {}),
193
+ };
194
+ }
195
+
196
+ module.exports = {
197
+ TASK_CLASS_BUDGETS,
198
+ resolveBudget,
199
+ getRegisteredHarnessIds,
200
+ getRegisteredHarnesses,
201
+ clearRegistryCache,
202
+ getRegistryLoadCount,
203
+ };