@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
|
@@ -5,39 +5,39 @@
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const { execFileSync } = require('node:child_process');
|
|
8
|
-
const { finishRuntime, usageError } = require('./cli-result.
|
|
9
|
-
const { REPO_ROOT } = require('../helper/repo-root.
|
|
10
|
-
const { validateCapabilityProviders } = require('../registry/index.
|
|
8
|
+
const { finishRuntime, usageError } = require('./cli-result.js');
|
|
9
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
10
|
+
const { validateCapabilityProviders } = require('../registry/index.js');
|
|
11
11
|
|
|
12
12
|
// Mirrors bin/doflow.js's own REPO_ROOT computation, relative to this file's location, so
|
|
13
13
|
// handleRouteCommand resolves the same repo root it did before relocation. (D8)
|
|
14
14
|
|
|
15
15
|
/** POSIX single-quote escaping, for the DISPLAY string only — execution always uses the argv
|
|
16
16
|
* vector, never a shell string, so retrieved data can never become shell syntax (review R5). */
|
|
17
|
-
function shellQuote(word
|
|
17
|
+
function shellQuote(word ) {
|
|
18
18
|
const text = String(word);
|
|
19
19
|
if (text !== '' && /^[A-Za-z0-9_@%+=:,.\/-]+$/.test(text)) return text;
|
|
20
20
|
return `'${text.replace(/'/g, `'\\''`)}'`;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function parseYamlFile(filePath
|
|
23
|
+
function parseYamlFile(filePath , fsImpl = fs) {
|
|
24
24
|
try {
|
|
25
25
|
const text = fsImpl.readFileSync(filePath, 'utf8');
|
|
26
26
|
return JSON.parse(text);
|
|
27
|
-
} catch (error
|
|
27
|
+
} catch (error ) {
|
|
28
28
|
throw new Error(`Failed to parse registry file '${filePath}': ${error.message}`);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
class CapabilityRouter {
|
|
33
|
-
fsImpl
|
|
34
|
-
repoRoot
|
|
35
|
-
projectRoot
|
|
36
|
-
registryDir
|
|
37
|
-
binaryChecker
|
|
38
|
-
binaryCache
|
|
39
|
-
capabilities
|
|
40
|
-
routes
|
|
33
|
+
fsImpl ;
|
|
34
|
+
repoRoot ;
|
|
35
|
+
projectRoot ;
|
|
36
|
+
registryDir ;
|
|
37
|
+
binaryChecker ;
|
|
38
|
+
binaryCache ;
|
|
39
|
+
capabilities ;
|
|
40
|
+
routes ;
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* @param {Object} [options]
|
|
@@ -48,7 +48,7 @@ class CapabilityRouter {
|
|
|
48
48
|
* @param {Object} [options.fsImpl]
|
|
49
49
|
* @param {Function} [options.binaryChecker]
|
|
50
50
|
*/
|
|
51
|
-
constructor(options
|
|
51
|
+
constructor(options = {}) {
|
|
52
52
|
this.fsImpl = options.fsImpl || fs;
|
|
53
53
|
this.repoRoot = options.repoRoot || REPO_ROOT;
|
|
54
54
|
// Two roots, deliberately distinct (review A3, surfaced by R5's tests): `repoRoot` locates the
|
|
@@ -68,7 +68,7 @@ class CapabilityRouter {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
loadRegistries()
|
|
71
|
+
loadRegistries() {
|
|
72
72
|
const capsPath = path.join(this.registryDir, 'capabilities.json');
|
|
73
73
|
const routesPath = path.join(this.registryDir, 'routes.json');
|
|
74
74
|
|
|
@@ -78,7 +78,7 @@ class CapabilityRouter {
|
|
|
78
78
|
this.capabilities = capsData.capabilities || {};
|
|
79
79
|
// A provider without a valid `sourceMode` cannot be told apart from an index-backed one, so the
|
|
80
80
|
// file is refused whole rather than loaded with a guess (068).
|
|
81
|
-
const providerErrors
|
|
81
|
+
const providerErrors = [];
|
|
82
82
|
validateCapabilityProviders(this.capabilities, providerErrors);
|
|
83
83
|
if (providerErrors.length > 0) {
|
|
84
84
|
throw new Error(`Invalid registry file '${capsPath}':\n- ${providerErrors.join('\n- ')}`);
|
|
@@ -91,13 +91,13 @@ class CapabilityRouter {
|
|
|
91
91
|
* @param {string} binary
|
|
92
92
|
* @returns {boolean}
|
|
93
93
|
*/
|
|
94
|
-
isBinaryAvailable(binary
|
|
94
|
+
isBinaryAvailable(binary ) {
|
|
95
95
|
if (!binary) return false;
|
|
96
96
|
if (this.binaryChecker) {
|
|
97
97
|
return this.binaryChecker(binary);
|
|
98
98
|
}
|
|
99
99
|
if (this.binaryCache.has(binary)) {
|
|
100
|
-
return this.binaryCache.get(binary)
|
|
100
|
+
return this.binaryCache.get(binary) ;
|
|
101
101
|
}
|
|
102
102
|
try {
|
|
103
103
|
const isWin = process.platform === 'win32';
|
|
@@ -116,7 +116,7 @@ class CapabilityRouter {
|
|
|
116
116
|
* @param {Array<string>} checkCommand
|
|
117
117
|
* @returns {{ ok: boolean, output?: string, error?: string }}
|
|
118
118
|
*/
|
|
119
|
-
executeSmokeCheck(checkCommand
|
|
119
|
+
executeSmokeCheck(checkCommand ) {
|
|
120
120
|
if (!Array.isArray(checkCommand) || checkCommand.length === 0) {
|
|
121
121
|
return { ok: true };
|
|
122
122
|
}
|
|
@@ -128,7 +128,7 @@ class CapabilityRouter {
|
|
|
128
128
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
129
129
|
});
|
|
130
130
|
return { ok: true, output: stdout.trim() };
|
|
131
|
-
} catch (error
|
|
131
|
+
} catch (error ) {
|
|
132
132
|
return { ok: false, error: error.message };
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -139,13 +139,13 @@ class CapabilityRouter {
|
|
|
139
139
|
* @param {boolean} [deepCheck=false]
|
|
140
140
|
* @returns {{ status: 'HEALTHY'|'UNAVAILABLE', details?: string }}
|
|
141
141
|
*/
|
|
142
|
-
evaluateProviderHealth(provider
|
|
142
|
+
evaluateProviderHealth(provider , deepCheck = false) {
|
|
143
143
|
if (!provider) return { status: 'UNAVAILABLE', details: 'No provider declared' };
|
|
144
144
|
|
|
145
145
|
// Declared / installed / responsive are DISTINCT facts (review R5): `facts` reports each one
|
|
146
146
|
// as true, false, or null-for-not-measured, so "healthy" can never silently mean only
|
|
147
147
|
// "declared in the registry". The summary `status` stays for existing callers.
|
|
148
|
-
const facts
|
|
148
|
+
const facts = { declared: true, installed: null, responsive: null };
|
|
149
149
|
|
|
150
150
|
// `native.test` has no binary — its installedness is whether this project's manifests declare
|
|
151
151
|
// a test command at all. Reporting HEALTHY without that check routed verify-runtime-behavior
|
|
@@ -205,16 +205,16 @@ class CapabilityRouter {
|
|
|
205
205
|
* @param {boolean} [options.deepCheck=false]
|
|
206
206
|
* @returns {{ capabilityId: string, provider: Object|null, status: string, fallbackProviders: Array<Object> }}
|
|
207
207
|
*/
|
|
208
|
-
resolveCapability(capabilityId
|
|
208
|
+
resolveCapability(capabilityId , { deepCheck = false } = {}) {
|
|
209
209
|
const capability = this.capabilities[capabilityId];
|
|
210
210
|
if (!capability) {
|
|
211
211
|
throw new Error(`Unknown capability '${capabilityId}'`);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
const providers = capability.providers || [];
|
|
215
|
-
let selectedProvider
|
|
215
|
+
let selectedProvider = null;
|
|
216
216
|
let selectedStatus = 'UNAVAILABLE';
|
|
217
|
-
const fallbackProviders
|
|
217
|
+
const fallbackProviders = [];
|
|
218
218
|
|
|
219
219
|
for (let i = 0; i < providers.length; i++) {
|
|
220
220
|
const prov = providers[i];
|
|
@@ -255,16 +255,16 @@ class CapabilityRouter {
|
|
|
255
255
|
* @param {Object} params
|
|
256
256
|
* @returns {Object}
|
|
257
257
|
*/
|
|
258
|
-
formatExecution(intent
|
|
258
|
+
formatExecution(intent , provider , params = {}) {
|
|
259
259
|
const query = params.query || params.symbol || params.concept || '';
|
|
260
260
|
const targetPath = params.path || '.';
|
|
261
261
|
// The MCP tool name comes from the provider's registry descriptor (`mcp: {server, tool}`), so a
|
|
262
262
|
// new MCP-backed provider needs no code change here. A caller may pass a bare `{ id }`, so fall
|
|
263
263
|
// back to the registry entry with that id.
|
|
264
264
|
const mcp = provider.mcp
|
|
265
|
-
|| Object.values(this.capabilities).flatMap((cap
|
|
265
|
+
|| Object.values(this.capabilities).flatMap((cap ) => cap.providers || []).find((p ) => p.id === provider.id && p.mcp)?.mcp;
|
|
266
266
|
const mcpTool = mcp ? { mcpTool: `mcp__${mcp.server}__${mcp.tool}` } : {};
|
|
267
|
-
const withDisplay = (result
|
|
267
|
+
const withDisplay = (result ) => ({ ...mcpTool, ...result, ...(result.argv ? { cliCommand: result.argv.map(shellQuote).join(' ') } : {}) });
|
|
268
268
|
|
|
269
269
|
if (provider.id === 'semble.search') {
|
|
270
270
|
const content = params.content || 'code';
|
|
@@ -338,9 +338,9 @@ class CapabilityRouter {
|
|
|
338
338
|
/** The project's own test entrypoint, read from its manifests by the shared command detector —
|
|
339
339
|
* the same detector `doflow verify` uses, so the two agree. Returns null when no manifest
|
|
340
340
|
* declares one: a wrong guess here becomes a command the model runs. */
|
|
341
|
-
detectTestCommand()
|
|
341
|
+
detectTestCommand() {
|
|
342
342
|
try {
|
|
343
|
-
const { detectCommands } = require('./command-detect.
|
|
343
|
+
const { detectCommands } = require('./command-detect.js');
|
|
344
344
|
const detected = detectCommands({ projectRoot: this.projectRoot });
|
|
345
345
|
return detected.commands?.test?.command || null;
|
|
346
346
|
} catch {
|
|
@@ -355,7 +355,7 @@ class CapabilityRouter {
|
|
|
355
355
|
* @param {Object} [options={}]
|
|
356
356
|
* @returns {Object} RetrievalPlan
|
|
357
357
|
*/
|
|
358
|
-
resolveIntent(intentId
|
|
358
|
+
resolveIntent(intentId , params = {}, { deepCheck = false } = {}) {
|
|
359
359
|
const route = this.routes[intentId];
|
|
360
360
|
if (!route) {
|
|
361
361
|
throw new Error(`Unknown route/intent '${intentId}'`);
|
|
@@ -372,7 +372,7 @@ class CapabilityRouter {
|
|
|
372
372
|
selectedProvider: primaryResolution.provider,
|
|
373
373
|
status: primaryResolution.status,
|
|
374
374
|
execution: this.formatExecution(intentId, primaryResolution.provider, params),
|
|
375
|
-
fallbackChain: (route.fallback || []).map((fbCap
|
|
375
|
+
fallbackChain: (route.fallback || []).map((fbCap ) => ({
|
|
376
376
|
capability: fbCap,
|
|
377
377
|
resolved: this.resolveCapability(fbCap, { deepCheck }),
|
|
378
378
|
})),
|
|
@@ -408,7 +408,7 @@ class CapabilityRouter {
|
|
|
408
408
|
selectedProvider: null,
|
|
409
409
|
status: 'UNAVAILABLE',
|
|
410
410
|
execution: null,
|
|
411
|
-
fallbackChain: (route.fallback || []).map((cap
|
|
411
|
+
fallbackChain: (route.fallback || []).map((cap ) => ({ capability: cap, status: 'UNAVAILABLE' })),
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
414
|
|
|
@@ -417,8 +417,8 @@ class CapabilityRouter {
|
|
|
417
417
|
* @param {boolean} [deepCheck=false]
|
|
418
418
|
* @returns {Array<Object>}
|
|
419
419
|
*/
|
|
420
|
-
getAllCapabilitiesHealth(deepCheck
|
|
421
|
-
const report
|
|
420
|
+
getAllCapabilitiesHealth(deepCheck = false) {
|
|
421
|
+
const report = [];
|
|
422
422
|
for (const [capId, cap] of Object.entries(this.capabilities)) {
|
|
423
423
|
const resolution = this.resolveCapability(capId, { deepCheck });
|
|
424
424
|
report.push({
|
|
@@ -448,16 +448,16 @@ class CapabilityRouter {
|
|
|
448
448
|
* @param {string} [options.projectRoot]
|
|
449
449
|
* @returns {number} exit code
|
|
450
450
|
*/
|
|
451
|
-
function handleRouteCommand({ intent, query, check = false, json = false, projectRoot }
|
|
451
|
+
function handleRouteCommand({ intent, query, check = false, json = false, projectRoot } = {}) {
|
|
452
452
|
const router = new CapabilityRouter({ repoRoot: REPO_ROOT, projectRoot });
|
|
453
453
|
if (typeof intent !== 'string' || intent.trim() === '') {
|
|
454
454
|
return usageError('route', `--intent is required. Declared intents: ${Object.keys(router.routes).join(', ')}`, json);
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
-
let resolution
|
|
457
|
+
let resolution ;
|
|
458
458
|
try {
|
|
459
459
|
resolution = router.resolveIntent(intent, { query, path: projectRoot || '.' }, { deepCheck: check });
|
|
460
|
-
} catch (error
|
|
460
|
+
} catch (error ) {
|
|
461
461
|
return usageError('route', `${error.message}. Declared intents: ${Object.keys(router.routes).join(', ')}`, json, error);
|
|
462
462
|
}
|
|
463
463
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const { spawnSync } = require('node:child_process');
|
|
8
|
-
const { BASE_FILE } = require('./worktree.
|
|
8
|
+
const { BASE_FILE } = require('./worktree.js');
|
|
9
9
|
|
|
10
10
|
// The checkouts of one repository: the main working tree and its linked worktrees.
|
|
11
11
|
//
|
|
@@ -15,9 +15,9 @@ const { BASE_FILE } = require('./worktree.ts');
|
|
|
15
15
|
// source that names every checkout, and it is asked once per process.
|
|
16
16
|
|
|
17
17
|
/** realpath(cwd) -> listCheckouts result. */
|
|
18
|
-
const cache = new Map
|
|
18
|
+
const cache = new Map ();
|
|
19
19
|
|
|
20
|
-
function realOrNull(p
|
|
20
|
+
function realOrNull(p , fsImpl ) {
|
|
21
21
|
try {
|
|
22
22
|
return fsImpl.realpathSync(p);
|
|
23
23
|
} catch {
|
|
@@ -25,7 +25,7 @@ function realOrNull(p: string, fsImpl: any): string | null {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function failed(reason
|
|
28
|
+
function failed(reason , detail ) {
|
|
29
29
|
return {
|
|
30
30
|
ok: false, reason, detail, current: null, main: null, mainReason: null, others: [], isLinked: false, sandbox: false,
|
|
31
31
|
};
|
|
@@ -46,17 +46,17 @@ function failed(reason: string, detail: string): any {
|
|
|
46
46
|
* @returns {{ok: boolean, reason: string|null, detail: string|null, current: string|null, main: string|null,
|
|
47
47
|
* mainReason: string|null, others: Array<string>, isLinked: boolean, sandbox: boolean}}
|
|
48
48
|
*/
|
|
49
|
-
function listCheckouts({ cwd, exec = spawnSync, fsImpl = fs }
|
|
49
|
+
function listCheckouts({ cwd, exec = spawnSync, fsImpl = fs } ) {
|
|
50
50
|
const realCwd = realOrNull(cwd, fsImpl) || path.resolve(cwd);
|
|
51
51
|
if (cache.has(realCwd)) return cache.get(realCwd);
|
|
52
52
|
|
|
53
|
-
let res
|
|
53
|
+
let res ;
|
|
54
54
|
try {
|
|
55
55
|
res = exec('git', ['-C', realCwd, 'worktree', 'list', '--porcelain'], { encoding: 'utf8', timeout: 10000 });
|
|
56
|
-
} catch (error
|
|
56
|
+
} catch (error ) {
|
|
57
57
|
res = { error };
|
|
58
58
|
}
|
|
59
|
-
let result
|
|
59
|
+
let result ;
|
|
60
60
|
if (res && res.error) {
|
|
61
61
|
result = res.error.code === 'ENOENT'
|
|
62
62
|
? failed('git-unavailable', String(res.error.message || '').trim().slice(0, 200))
|
|
@@ -71,8 +71,8 @@ function listCheckouts({ cwd, exec = spawnSync, fsImpl = fs }: { cwd: string; ex
|
|
|
71
71
|
return result;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
function fromPorcelain(stdout
|
|
75
|
-
const entries
|
|
74
|
+
function fromPorcelain(stdout , realCwd , fsImpl ) {
|
|
75
|
+
const entries = [];
|
|
76
76
|
for (const block of stdout.split(/\n\s*\n/)) {
|
|
77
77
|
const lines = block.split('\n').filter(Boolean);
|
|
78
78
|
if (lines.length === 0 || !lines[0].startsWith('worktree ')) continue;
|
|
@@ -84,7 +84,7 @@ function fromPorcelain(stdout: string, realCwd: string, fsImpl: any): any {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
let current
|
|
87
|
+
let current = null;
|
|
88
88
|
for (const e of entries) {
|
|
89
89
|
if (e.real === null || e.bare) continue;
|
|
90
90
|
if ((realCwd === e.real || realCwd.startsWith(e.real + path.sep)) && (current === null || e.real.length > current.real.length)) {
|
|
@@ -93,8 +93,8 @@ function fromPorcelain(stdout: string, realCwd: string, fsImpl: any): any {
|
|
|
93
93
|
}
|
|
94
94
|
const sandbox = Boolean(current && current.sandbox);
|
|
95
95
|
const first = entries[0];
|
|
96
|
-
let main
|
|
97
|
-
let mainReason
|
|
96
|
+
let main = null;
|
|
97
|
+
let mainReason = null;
|
|
98
98
|
if (sandbox) mainReason = 'sandbox';
|
|
99
99
|
else if (first && first.bare) mainReason = 'bare';
|
|
100
100
|
else if (!first || first.real === null) mainReason = 'missing';
|
|
@@ -102,7 +102,7 @@ function fromPorcelain(stdout: string, realCwd: string, fsImpl: any): any {
|
|
|
102
102
|
|
|
103
103
|
const others = sandbox ? [] : entries
|
|
104
104
|
.filter((e) => e.real !== null && !e.bare && !e.sandbox && e !== current)
|
|
105
|
-
.map((e) => e.real
|
|
105
|
+
.map((e) => e.real );
|
|
106
106
|
const currentPath = current ? current.real : null;
|
|
107
107
|
return {
|
|
108
108
|
ok: true,
|
|
@@ -118,7 +118,7 @@ function fromPorcelain(stdout: string, realCwd: string, fsImpl: any): any {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/** Forgets every memoized `listCheckouts` result (tests that rebuild a repository in place). */
|
|
121
|
-
function clearCheckoutCache()
|
|
121
|
+
function clearCheckoutCache() {
|
|
122
122
|
cache.clear();
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -138,8 +138,8 @@ function clearCheckoutCache(): void {
|
|
|
138
138
|
* @returns {{status: 'found'|'missing'|'ambiguous', file: string|null, root: string|null,
|
|
139
139
|
* origin: 'current'|'other'|null, candidates: Array<string>}}
|
|
140
140
|
*/
|
|
141
|
-
function findStateFile({ stateRoot, relPath, exec = spawnSync, fsImpl = fs }
|
|
142
|
-
const at = (root
|
|
141
|
+
function findStateFile({ stateRoot, relPath, exec = spawnSync, fsImpl = fs } ) {
|
|
142
|
+
const at = (root , isCurrent ) => {
|
|
143
143
|
const rel = typeof relPath === 'function' ? relPath(root, isCurrent) : relPath;
|
|
144
144
|
for (const candidate of Array.isArray(rel) ? rel : [rel]) {
|
|
145
145
|
if (typeof candidate !== 'string' || candidate === '') continue;
|
|
@@ -148,21 +148,21 @@ function findStateFile({ stateRoot, relPath, exec = spawnSync, fsImpl = fs }: {
|
|
|
148
148
|
}
|
|
149
149
|
return null;
|
|
150
150
|
};
|
|
151
|
-
const missing = { status: 'missing', file: null, root: null, origin: null, candidates: []
|
|
151
|
+
const missing = { status: 'missing', file: null, root: null, origin: null, candidates: [] };
|
|
152
152
|
|
|
153
153
|
const here = at(stateRoot, true);
|
|
154
154
|
if (here) return { status: 'found', file: here, root: stateRoot, origin: 'current', candidates: [] };
|
|
155
155
|
|
|
156
156
|
const checkouts = listCheckouts({ cwd: stateRoot, exec, fsImpl });
|
|
157
157
|
if (!checkouts.ok || checkouts.sandbox) return missing;
|
|
158
|
-
const matches
|
|
159
|
-
for (const root of checkouts.others
|
|
158
|
+
const matches = [];
|
|
159
|
+
for (const root of checkouts.others ) {
|
|
160
160
|
const file = at(root, false);
|
|
161
161
|
if (file) matches.push({ file, root });
|
|
162
162
|
}
|
|
163
163
|
if (matches.length === 0) return missing;
|
|
164
164
|
if (matches.length > 1) {
|
|
165
|
-
return { status: 'ambiguous', file: null, root: null, origin: null, candidates: matches.map((m
|
|
165
|
+
return { status: 'ambiguous', file: null, root: null, origin: null, candidates: matches.map((m ) => m.file) };
|
|
166
166
|
}
|
|
167
167
|
return { status: 'found', file: matches[0].file, root: matches[0].root, origin: 'other', candidates: [] };
|
|
168
168
|
}
|
|
@@ -6,11 +6,11 @@ const fs = require('node:fs');
|
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const crypto = require('node:crypto');
|
|
8
8
|
// Shared with EvidenceLedger so both stores enforce one definition of a safe task id.
|
|
9
|
-
const { assertSafeTaskId, EvidenceLedger } = require('./evidence-ledger.
|
|
10
|
-
const { updateTaskState, readTaskState, mergeRecords } = require('./task-state.
|
|
11
|
-
const { taskStoreDir } = require('./task-scope.
|
|
12
|
-
const { finishRuntime, usageError } = require('./cli-result.
|
|
13
|
-
const { REPO_ROOT } = require('../helper/repo-root.
|
|
9
|
+
const { assertSafeTaskId, EvidenceLedger } = require('./evidence-ledger.js');
|
|
10
|
+
const { updateTaskState, readTaskState, mergeRecords } = require('./task-state.js');
|
|
11
|
+
const { taskStoreDir } = require('./task-scope.js');
|
|
12
|
+
const { finishRuntime, usageError } = require('./cli-result.js');
|
|
13
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
14
14
|
|
|
15
15
|
const CLAIM_STATUSES = new Set([
|
|
16
16
|
'hypothesis',
|
|
@@ -38,15 +38,15 @@ const CLAIM_STATUSES = new Set([
|
|
|
38
38
|
const TERMINAL_CLAIM_STATUSES = new Set(['retracted', 'superseded']);
|
|
39
39
|
|
|
40
40
|
class ClaimsManager {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
50
|
/**
|
|
51
51
|
* @param {Object} [options]
|
|
52
52
|
* @param {Object} [options.evidenceLedger]
|
|
@@ -55,7 +55,7 @@ class ClaimsManager {
|
|
|
55
55
|
* @param {string} [options.slug] feature whose namespace the records live under (see task-scope.js)
|
|
56
56
|
* @param {Object} [options.fsImpl]
|
|
57
57
|
*/
|
|
58
|
-
constructor(options
|
|
58
|
+
constructor(options = {}) {
|
|
59
59
|
this.fsImpl = options.fsImpl || fs;
|
|
60
60
|
this.evidenceLedger = options.evidenceLedger || null;
|
|
61
61
|
this.repoRoot = options.repoRoot || REPO_ROOT;
|
|
@@ -68,7 +68,7 @@ class ClaimsManager {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/** Where this task's claims file lives: beside its evidence, in the feature's namespace when it has one. */
|
|
71
|
-
fileFor(taskId
|
|
71
|
+
fileFor(taskId ) {
|
|
72
72
|
const dir = this.fixedStateDir
|
|
73
73
|
|| taskStoreDir({ projectRoot: this.repoRoot, store: 'evidence', taskId, slug: this.slug });
|
|
74
74
|
return path.join(dir, `${assertSafeTaskId(taskId)}_claims.json`);
|
|
@@ -87,7 +87,7 @@ class ClaimsManager {
|
|
|
87
87
|
* @param {Object} item - { statement, taskId, status, id, role }
|
|
88
88
|
* @returns {string} claimId
|
|
89
89
|
*/
|
|
90
|
-
addClaim(item
|
|
90
|
+
addClaim(item ) {
|
|
91
91
|
if (!item || typeof item !== 'object') {
|
|
92
92
|
throw new Error('Claim item must be an object');
|
|
93
93
|
}
|
|
@@ -125,7 +125,7 @@ class ClaimsManager {
|
|
|
125
125
|
* @param {string} id
|
|
126
126
|
* @returns {Object|null}
|
|
127
127
|
*/
|
|
128
|
-
getClaim(id
|
|
128
|
+
getClaim(id ) {
|
|
129
129
|
return this.claimsMap.get(id) || null;
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -135,7 +135,7 @@ class ClaimsManager {
|
|
|
135
135
|
* @param {string} evidenceId
|
|
136
136
|
* @param {'supports'|'contradicts'} [relation='supports']
|
|
137
137
|
*/
|
|
138
|
-
linkEvidence(claimId
|
|
138
|
+
linkEvidence(claimId , evidenceId , relation = 'supports') {
|
|
139
139
|
const claim = this.claimsMap.get(claimId);
|
|
140
140
|
if (!claim) {
|
|
141
141
|
throw new Error(`Unknown claim '${claimId}'`);
|
|
@@ -174,7 +174,7 @@ class ClaimsManager {
|
|
|
174
174
|
* @param {string} claimId
|
|
175
175
|
* @returns {'hypothesis'|'supported'|'conflicted'|'invalidated'|'unknown'}
|
|
176
176
|
*/
|
|
177
|
-
evaluateClaim(claimId
|
|
177
|
+
evaluateClaim(claimId ) {
|
|
178
178
|
const claim = this.claimsMap.get(claimId);
|
|
179
179
|
if (!claim) {
|
|
180
180
|
throw new Error(`Unknown claim '${claimId}'`);
|
|
@@ -193,22 +193,28 @@ class ClaimsManager {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
let hasFreshSupport = false;
|
|
196
|
-
let hasFreshContradiction = false;
|
|
197
196
|
let hasStaleSupport = false;
|
|
197
|
+
let hasFreshContradiction = false;
|
|
198
|
+
let hasUnknownContradiction = false;
|
|
198
199
|
|
|
199
200
|
for (const evId of claim.supportingEvidence) {
|
|
200
201
|
const ev = this.evidenceLedger ? this.evidenceLedger.getEvidence(evId) : null;
|
|
201
202
|
if (!ev || ev.freshness?.status === 'STALE') {
|
|
202
203
|
hasStaleSupport = true;
|
|
203
|
-
} else {
|
|
204
|
+
} else if (ev.freshness?.status === 'FRESH') {
|
|
204
205
|
hasFreshSupport = true;
|
|
205
206
|
}
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
for (const evId of claim.contradictingEvidence) {
|
|
209
210
|
const ev = this.evidenceLedger ? this.evidenceLedger.getEvidence(evId) : null;
|
|
210
|
-
if (ev
|
|
211
|
-
|
|
211
|
+
if (ev) {
|
|
212
|
+
const status = ev.freshness?.status;
|
|
213
|
+
if (status === 'FRESH') {
|
|
214
|
+
hasFreshContradiction = true;
|
|
215
|
+
} else if (status !== 'STALE') {
|
|
216
|
+
hasUnknownContradiction = true;
|
|
217
|
+
}
|
|
212
218
|
}
|
|
213
219
|
}
|
|
214
220
|
|
|
@@ -219,9 +225,12 @@ class ClaimsManager {
|
|
|
219
225
|
// settled. Support that has merely gone stale still counts as support, so a claim that once
|
|
220
226
|
// had backing stays 'conflicted' rather than being declared false on a technicality.
|
|
221
227
|
claim.status = claim.supportingEvidence.length === 0 ? 'rejected' : 'conflicted';
|
|
228
|
+
} else if (hasUnknownContradiction) {
|
|
229
|
+
// A contradiction whose freshness is unknown cannot prove the claim false, so it never yields 'rejected' (DEC-016).
|
|
230
|
+
claim.status = claim.supportingEvidence.length > 0 ? 'conflicted' : 'hypothesis';
|
|
222
231
|
} else if (hasFreshSupport) {
|
|
223
232
|
claim.status = 'supported';
|
|
224
|
-
} else if (hasStaleSupport
|
|
233
|
+
} else if (hasStaleSupport) {
|
|
225
234
|
claim.status = 'invalidated';
|
|
226
235
|
} else {
|
|
227
236
|
claim.status = 'hypothesis';
|
|
@@ -239,7 +248,7 @@ class ClaimsManager {
|
|
|
239
248
|
* @param {string} [supersededBy]
|
|
240
249
|
* @returns {string} the new status
|
|
241
250
|
*/
|
|
242
|
-
finalizeClaim(claimId
|
|
251
|
+
finalizeClaim(claimId , status , supersededBy ) {
|
|
243
252
|
const claim = this.claimsMap.get(claimId);
|
|
244
253
|
if (!claim) {
|
|
245
254
|
throw new Error(`Unknown claim '${claimId}'`);
|
|
@@ -260,7 +269,7 @@ class ClaimsManager {
|
|
|
260
269
|
* @param {string} claimId
|
|
261
270
|
* @returns {string} the new status
|
|
262
271
|
*/
|
|
263
|
-
retractClaim(claimId
|
|
272
|
+
retractClaim(claimId ) {
|
|
264
273
|
return this.finalizeClaim(claimId, 'retracted');
|
|
265
274
|
}
|
|
266
275
|
|
|
@@ -273,7 +282,7 @@ class ClaimsManager {
|
|
|
273
282
|
* @param {string} replacedBy
|
|
274
283
|
* @returns {string} the new status
|
|
275
284
|
*/
|
|
276
|
-
supersedeClaim(claimId
|
|
285
|
+
supersedeClaim(claimId , replacedBy ) {
|
|
277
286
|
// The target is checked first: validating the replacement before the claim being superseded
|
|
278
287
|
// reported a missing replacement when the caller had actually mistyped the target, sending them
|
|
279
288
|
// to fix the wrong id.
|
|
@@ -307,7 +316,7 @@ class ClaimsManager {
|
|
|
307
316
|
* @param {string} [taskId]
|
|
308
317
|
* @returns {Array<Object>}
|
|
309
318
|
*/
|
|
310
|
-
getClaims(taskId
|
|
319
|
+
getClaims(taskId ) {
|
|
311
320
|
const results = [];
|
|
312
321
|
for (const claim of this.claimsMap.values()) {
|
|
313
322
|
if (!taskId || claim.taskId === taskId) {
|
|
@@ -322,13 +331,13 @@ class ClaimsManager {
|
|
|
322
331
|
* @param {string} [taskId='default']
|
|
323
332
|
* @returns {string} filePath
|
|
324
333
|
*/
|
|
325
|
-
save(taskId = 'default')
|
|
334
|
+
save(taskId = 'default') {
|
|
326
335
|
const targetFile = this.fileFor(taskId);
|
|
327
|
-
let merged
|
|
336
|
+
let merged ;
|
|
328
337
|
const file = updateTaskState({
|
|
329
338
|
fsImpl: this.fsImpl,
|
|
330
339
|
file: targetFile,
|
|
331
|
-
build: (disk
|
|
340
|
+
build: (disk ) => {
|
|
332
341
|
merged = mergeRecords(this.getClaims(taskId), disk?.claims || [], this.baseline);
|
|
333
342
|
return { taskId, updatedAt: new Date().toISOString(), claimsCount: merged.length, claims: merged };
|
|
334
343
|
},
|
|
@@ -345,7 +354,7 @@ class ClaimsManager {
|
|
|
345
354
|
* @param {string} [taskId='default']
|
|
346
355
|
* @returns {number}
|
|
347
356
|
*/
|
|
348
|
-
load(taskId = 'default')
|
|
357
|
+
load(taskId = 'default') {
|
|
349
358
|
const targetFile = this.fileFor(taskId);
|
|
350
359
|
const data = readTaskState(this.fsImpl, targetFile);
|
|
351
360
|
if (data && Array.isArray(data.claims)) {
|
|
@@ -385,7 +394,7 @@ class ClaimsManager {
|
|
|
385
394
|
* @param {string} [options.slug] feature the task belongs to, when not the branch's
|
|
386
395
|
* @returns {number} exit code
|
|
387
396
|
*/
|
|
388
|
-
function handleClaimCommand({ taskId, action = 'list', statement, claimId, evidenceId, replacedBy, relation = 'supports', role, json = false, stateRoot, slug }
|
|
397
|
+
function handleClaimCommand({ taskId, action = 'list', statement, claimId, evidenceId, replacedBy, relation = 'supports', role, json = false, stateRoot, slug } = {}) {
|
|
389
398
|
const root = stateRoot || process.cwd();
|
|
390
399
|
const ledger = new EvidenceLedger({ repoRoot: root, slug });
|
|
391
400
|
ledger.load(taskId);
|
|
@@ -437,7 +446,7 @@ function handleClaimCommand({ taskId, action = 'list', statement, claimId, evide
|
|
|
437
446
|
} else {
|
|
438
447
|
return usageError('claim', `unknown --action '${action}'. Valid: list, add, link, retract, supersede`, json);
|
|
439
448
|
}
|
|
440
|
-
} catch (error
|
|
449
|
+
} catch (error ) {
|
|
441
450
|
return usageError('claim', error.message, json, error);
|
|
442
451
|
}
|
|
443
452
|
|
|
@@ -11,12 +11,19 @@
|
|
|
11
11
|
* it, and requires only the mask module.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
const EXIT = Object.freeze({
|
|
15
|
+
OK: 0,
|
|
16
|
+
EMPTY: 1,
|
|
17
|
+
USAGE: 2,
|
|
18
|
+
OVERFLOW: 3,
|
|
19
|
+
});
|
|
20
|
+
|
|
14
21
|
/**
|
|
15
22
|
* Sets the process exit code.
|
|
16
23
|
* @param {number} code
|
|
17
24
|
* @returns {number} the code, for chaining
|
|
18
25
|
*/
|
|
19
|
-
function finishRuntime(code
|
|
26
|
+
function finishRuntime(code ) {
|
|
20
27
|
process.exitCode = code;
|
|
21
28
|
return code;
|
|
22
29
|
}
|
|
@@ -29,8 +36,8 @@ function finishRuntime(code: number): number {
|
|
|
29
36
|
* @param {string} command the verb or installer command name
|
|
30
37
|
* @param {number} exit the exit status the caller is about to produce
|
|
31
38
|
*/
|
|
32
|
-
function captureCaught(error
|
|
33
|
-
try { require('./failure/capture.
|
|
39
|
+
function captureCaught(error , command , exit ) {
|
|
40
|
+
try { require('./failure/capture.js').captureError(error, { command, exit }); } catch { /* capture is best-effort */ }
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
/**
|
|
@@ -45,11 +52,11 @@ function captureCaught(error: any, command: string, exit: number): void {
|
|
|
45
52
|
* @param {Error} [error]
|
|
46
53
|
* @returns {number} 2 (USAGE error exit code)
|
|
47
54
|
*/
|
|
48
|
-
function usageError(verb
|
|
49
|
-
if (error !== undefined) captureCaught(error, verb,
|
|
50
|
-
if (json) console.log(JSON.stringify({ ok: false, status: 'USAGE', exitCode:
|
|
55
|
+
function usageError(verb , message , json , error ) {
|
|
56
|
+
if (error !== undefined) captureCaught(error, verb, EXIT.USAGE);
|
|
57
|
+
if (json) console.log(JSON.stringify({ ok: false, status: 'USAGE', exitCode: EXIT.USAGE, error: 'usage', summary: message }, null, 2));
|
|
51
58
|
else console.error(`doflow ${verb}: ${message}`);
|
|
52
|
-
return finishRuntime(
|
|
59
|
+
return finishRuntime(EXIT.USAGE);
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
module.exports = { finishRuntime, usageError, captureCaught };
|
|
62
|
+
module.exports = { finishRuntime, usageError, captureCaught, EXIT };
|