@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.
- package/bin/doflow.js +2 -2
- package/core/.antigravity-plugin/plugin.json +0 -1
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
- package/core/harnesses/gemini/hooks/session-start.sh +5 -3
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
- package/core/harnesses/kiro/hooks/session-start.sh +11 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
- package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
- package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
- package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
- package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
- package/core/registry/assets.json +19 -0
- package/core/registry/contracts.json +2 -2
- package/core/registry/harnesses.json +196 -10
- package/core/registry/lifecycle.json +2626 -7
- package/core/registry/storage.json +110 -83
- package/core/registry/verification.json +1 -1
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
- package/core/shared/scripts/doflow/bin/doflow-run +10 -6
- package/core/shared/skills/do-design/SKILL.md +6 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
- package/core/shared/skills/do-implement/SKILL.md +3 -2
- package/core/shared/skills/do-plan/SKILL.md +7 -8
- package/package.json +4 -2
- package/src/adapters/antigravity/{index.ts → index.js} +141 -105
- package/src/adapters/claude/{index.ts → index.js} +63 -49
- package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
- package/src/adapters/codex/{agents.ts → agents.js} +24 -24
- package/src/adapters/codex/{config.ts → config.js} +49 -49
- package/src/adapters/codex/{hooks.ts → hooks.js} +62 -27
- package/src/adapters/codex/{index.ts → index.js} +148 -105
- package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
- package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
- package/src/adapters/copilot/{index.ts → index.js} +42 -42
- package/src/adapters/{copy-tree.ts → copy-tree.js} +74 -74
- package/src/adapters/gemini/{hooks.ts → hooks.js} +78 -37
- package/src/adapters/gemini/{index.ts → index.js} +96 -57
- package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
- package/src/adapters/{index.ts → index.js} +13 -13
- package/src/adapters/kiro/{index.ts → index.js} +49 -35
- package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
- package/src/adapters/opencode/{index.ts → index.js} +52 -52
- package/src/adapters/pi/hooks.js +119 -0
- package/src/adapters/pi/{index.ts → index.js} +64 -39
- package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
- package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
- package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
- package/src/cli/commands/{install.ts → install.js} +25 -25
- package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
- package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
- package/src/cli/commands/{remove.ts → remove.js} +10 -10
- package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
- package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
- package/src/cli/commands/{status.ts → status.js} +24 -24
- package/src/cli/commands/{tools.ts → tools.js} +16 -16
- package/src/cli/commands/{update.ts → update.js} +20 -20
- package/src/cli/{index.ts → index.js} +43 -31
- package/src/cli/{runtime-commands.ts → runtime-commands.js} +97 -58
- package/src/cli/{shared.ts → shared.js} +40 -40
- package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
- package/src/helper/{git.ts → git.js} +2 -2
- package/src/helper/{guardrails.ts → guardrails.js} +10 -10
- package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
- package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
- package/src/helper/{prompt.ts → prompt.js} +5 -5
- package/src/helper/{repo-root.ts → repo-root.js} +2 -2
- package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
- package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
- package/src/helper/{toml.ts → toml.js} +48 -48
- package/src/install/{backup-set.ts → backup-set.js} +16 -16
- package/src/install/{backup.ts → backup.js} +65 -65
- package/src/install/{context.ts → context.js} +15 -15
- package/src/install/{manifest.ts → manifest.js} +27 -27
- package/src/install/{mcp.ts → mcp.js} +30 -30
- package/src/install/{paths.ts → paths.js} +15 -15
- package/src/install/{targets.ts → targets.js} +10 -10
- package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
- package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
- package/src/lifecycle/coverage.js +57 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/{index.ts → index.js} +160 -146
- package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
- package/src/lifecycle/{policies.ts → policies.js} +11 -11
- package/src/lifecycle/{view.ts → view.js} +28 -28
- package/src/registry/{index.ts → index.js} +195 -66
- package/src/release/build-package.js +91 -0
- package/src/release/changelog-section.js +70 -0
- package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
- package/src/runtime/advice/{cli.ts → cli.js} +31 -31
- package/src/runtime/advice/{client.ts → client.js} +44 -44
- package/src/runtime/advice/{config.ts → config.js} +16 -16
- package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
- package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
- package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
- package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
- package/src/runtime/advice/{record.ts → record.js} +19 -19
- package/src/runtime/advice/{registry.ts → registry.js} +16 -16
- package/src/runtime/advice/{strength.ts → strength.js} +7 -7
- package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
- package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
- package/src/runtime/candidate.js +505 -0
- package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
- package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
- package/src/runtime/{claims.ts → claims.js} +44 -35
- package/src/runtime/{cli-result.ts → cli-result.js} +15 -8
- package/src/runtime/{cli.ts → cli.js} +59 -292
- package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +102 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context-pack.js +1277 -0
- package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +32 -25
- package/src/runtime/failure/{capture.ts → capture.js} +29 -29
- package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
- package/src/runtime/failure/{cli.ts → cli.js} +30 -30
- package/src/runtime/failure/{home.ts → home.js} +6 -6
- package/src/runtime/failure/{store.ts → store.js} +32 -32
- package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
- package/src/runtime/{freshness.ts → freshness.js} +59 -22
- package/src/runtime/{health.ts → health.js} +72 -72
- package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
- package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
- package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
- package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
- package/src/runtime/inventory/{index.ts → index.js} +36 -36
- package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
- package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
- package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
- package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
- package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
- package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
- package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
- package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
- package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
- package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
- package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
- package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
- package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
- package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
- package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
- package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
- package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
- package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
- package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
- package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
- package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
- package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
- package/src/runtime/{mask.ts → mask.js} +22 -22
- package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
- package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
- package/src/runtime/{model-router.ts → model-router.js} +20 -20
- package/src/runtime/{outcome.ts → outcome.js} +27 -27
- package/src/runtime/{reach.ts → reach.js} +21 -21
- package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
- package/src/runtime/{readiness.ts → readiness.js} +113 -43
- package/src/runtime/{recovery.ts → recovery.js} +15 -15
- package/src/runtime/{research-request.ts → research-request.js} +34 -34
- package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +453 -104
- package/src/runtime/retrieval-record.js +223 -0
- package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +22 -19
- package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
- package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
- package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
- package/src/runtime/source-version.js +289 -0
- package/src/runtime/storage/{cli.ts → cli.js} +9 -9
- package/src/runtime/storage/{engine.ts → engine.js} +37 -37
- package/src/runtime/storage/{registry.ts → registry.js} +19 -19
- package/src/runtime/storage/{roots.ts → roots.js} +8 -8
- package/src/runtime/storage/{rules.ts → rules.js} +26 -26
- package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
- package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
- package/src/runtime/{task-attempts.ts → task-attempts.js} +62 -62
- package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
- package/src/runtime/{task-scope.ts → task-scope.js} +40 -15
- package/src/runtime/{task-state.ts → task-state.js} +86 -21
- package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
- package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
- package/src/runtime/trace/{render.ts → render.js} +7 -7
- package/src/runtime/trace/{views.ts → views.js} +40 -40
- package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
- package/src/runtime/verification/{engine.ts → engine.js} +143 -143
- package/src/runtime/verification/{registry.ts → registry.js} +6 -6
- package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
- package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
- package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
- package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
- package/src/runtime/{worktree.ts → worktree.js} +31 -31
- package/src/state/{index.ts → index.js} +26 -26
- package/src/state/{lockfile.ts → lockfile.js} +20 -20
- package/src/state/{run-lock.ts → run-lock.js} +44 -44
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
- 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
|
|
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
|
|
64
|
-
const roots
|
|
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
|
|
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
|
|
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
|
|
95
|
+
const DERIVATIONS = Object.freeze(['declared', 'inferred', 'override']);
|
|
96
96
|
|
|
97
|
-
function readFileIfPresent(filePath
|
|
97
|
+
function readFileIfPresent(filePath , fsImpl ) {
|
|
98
98
|
try {
|
|
99
99
|
return fsImpl.readFileSync(filePath, 'utf8');
|
|
100
|
-
} catch (error
|
|
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
|
|
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
|
|
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
|
|
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
|
|
142
|
+
let pkg ;
|
|
143
143
|
try {
|
|
144
144
|
pkg = JSON.parse(raw);
|
|
145
|
-
} catch (error
|
|
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
|
|
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
|
|
158
|
+
const absent = ['parse'];
|
|
159
159
|
|
|
160
|
-
const script = (...names
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
239
|
-
const roles
|
|
240
|
-
const absent
|
|
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
|
|
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
|
|
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
|
|
280
|
-
const absent
|
|
281
|
-
const targets = new Set
|
|
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
|
|
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
|
|
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
|
|
322
|
+
let parsed ;
|
|
323
323
|
try {
|
|
324
324
|
parsed = JSON.parse(match[1]);
|
|
325
|
-
} catch (error
|
|
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
|
|
333
|
-
for (const [key, value] of Object.entries(parsed)
|
|
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
|
|
365
|
+
function detectCommands(options = {}) {
|
|
366
366
|
const fsImpl = options.fsImpl || fs;
|
|
367
367
|
const projectRoot = options.projectRoot || process.cwd();
|
|
368
368
|
|
|
369
|
-
const manifests
|
|
370
|
-
const commands
|
|
371
|
-
const alternatives
|
|
372
|
-
const absentByRole = new Map
|
|
373
|
-
const errors
|
|
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
|
|
376
|
+
let found ;
|
|
377
377
|
try {
|
|
378
378
|
found = detector(projectRoot, fsImpl);
|
|
379
|
-
} catch (error
|
|
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)
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
+
};
|