@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
|
@@ -24,32 +24,32 @@
|
|
|
24
24
|
const path = require('node:path');
|
|
25
25
|
const nodeFs = require('node:fs');
|
|
26
26
|
const { spawnSync } = require('node:child_process');
|
|
27
|
-
const { detectCommands, applyTargetPattern, TARGET_PATTERN_TOKEN } = require('../command-detect.
|
|
28
|
-
const { selectChangedTests } = require('./test-selection.
|
|
29
|
-
const { RecoveryManager } = require('../recovery.
|
|
30
|
-
const { finishRuntime, usageError } = require('../cli-result.
|
|
31
|
-
const { REPO_ROOT } = require('../../helper/repo-root.
|
|
27
|
+
const { detectCommands, applyTargetPattern, TARGET_PATTERN_TOKEN } = require('../command-detect.js');
|
|
28
|
+
const { selectChangedTests } = require('./test-selection.js');
|
|
29
|
+
const { RecoveryManager } = require('../recovery.js');
|
|
30
|
+
const { finishRuntime, usageError } = require('../cli-result.js');
|
|
31
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
32
32
|
const {
|
|
33
33
|
resolveScopeBound, resolveIntegrationBase, resolveExplicitBaseline, parseDeclaredScope, scopeReasonText, boundSourcesText,
|
|
34
|
-
} = require('./scope-bound.
|
|
34
|
+
} = require('./scope-bound.js');
|
|
35
35
|
const nodeCrypto = require('node:crypto');
|
|
36
|
-
const { readReadinessRecord, RECORD_VERSION } = require('../readiness-record.
|
|
37
|
-
const { verifyReadinessCheck, featureSlugFor } = require('../implementation-gate.
|
|
38
|
-
const { assertSafeTaskId } = require('../evidence-ledger.
|
|
39
|
-
const { upkeepAfterVerify } = require('../storage/upkeep.
|
|
36
|
+
const { readReadinessRecord, RECORD_VERSION } = require('../readiness-record.js');
|
|
37
|
+
const { verifyReadinessCheck, featureSlugFor } = require('../implementation-gate.js');
|
|
38
|
+
const { assertSafeTaskId } = require('../evidence-ledger.js');
|
|
39
|
+
const { upkeepAfterVerify } = require('../storage/upkeep.js');
|
|
40
40
|
const {
|
|
41
41
|
VerificationContractRunner,
|
|
42
42
|
FATAL_CHECK_MARKERS,
|
|
43
43
|
TIMEOUT_EXIT_CODE,
|
|
44
44
|
MAX_STREAM_CHARS,
|
|
45
|
-
} = require('./contract-runner.
|
|
45
|
+
} = require('./contract-runner.js');
|
|
46
46
|
const {
|
|
47
47
|
loadVerificationRegistry,
|
|
48
48
|
TIER_RESOLUTIONS,
|
|
49
49
|
TIER_STATUSES,
|
|
50
50
|
RECOVERY_OUTCOMES,
|
|
51
51
|
REGISTRY_FILENAME,
|
|
52
|
-
} = require('./registry.
|
|
52
|
+
} = require('./registry.js');
|
|
53
53
|
|
|
54
54
|
// `handleVerifyCommand` (moved from bin/doflow.js) used bin/doflow.js's own REPO_ROOT — that file's
|
|
55
55
|
// SCRIPT_DIR is bin/, so its repo root is path.dirname(SCRIPT_DIR). This module takes the same root
|
|
@@ -69,12 +69,12 @@ const BROAD_TIER_ID = 'broad-tests';
|
|
|
69
69
|
*/
|
|
70
70
|
/** A stderr excerpt for a message. (Referenced by the observation errors below without ever being
|
|
71
71
|
* defined, so a non-zero `git status` with no spawn error threw a ReferenceError instead of reporting.) */
|
|
72
|
-
function truncate(text
|
|
72
|
+
function truncate(text , max = 200) {
|
|
73
73
|
const flat = String(text || '').trim();
|
|
74
74
|
return flat.length > max ? `${flat.slice(0, max)}…` : flat;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function toStringArray(value
|
|
77
|
+
function toStringArray(value ) {
|
|
78
78
|
return Array.isArray(value) ? value.filter((v) => typeof v === 'string' && v !== '') : [];
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -83,8 +83,8 @@ function toStringArray(value: any): string[] {
|
|
|
83
83
|
* @param {string} stdout
|
|
84
84
|
* @returns {Array<string>}
|
|
85
85
|
*/
|
|
86
|
-
function parsePorcelain(stdout
|
|
87
|
-
const files
|
|
86
|
+
function parsePorcelain(stdout ) {
|
|
87
|
+
const files = [];
|
|
88
88
|
for (const line of String(stdout || '').split('\n')) {
|
|
89
89
|
if (line.trim() === '') continue;
|
|
90
90
|
const body = line.slice(3);
|
|
@@ -96,14 +96,14 @@ function parsePorcelain(stdout: any): string[] {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
class VerificationEngine {
|
|
99
|
-
cwd
|
|
100
|
-
fsImpl
|
|
101
|
-
exec
|
|
102
|
-
clock
|
|
103
|
-
registry
|
|
104
|
-
policy
|
|
105
|
-
runner
|
|
106
|
-
recoveryOverride
|
|
99
|
+
cwd ;
|
|
100
|
+
fsImpl ;
|
|
101
|
+
exec ;
|
|
102
|
+
clock ;
|
|
103
|
+
registry ;
|
|
104
|
+
policy ;
|
|
105
|
+
runner ;
|
|
106
|
+
recoveryOverride ;
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* @param {Object} [options]
|
|
@@ -115,7 +115,7 @@ class VerificationEngine {
|
|
|
115
115
|
* @param {Function} [options.clock] injection seam returning epoch milliseconds
|
|
116
116
|
* @param {RecoveryManager} [options.recovery] overrides the per-risk-level manager
|
|
117
117
|
*/
|
|
118
|
-
constructor(options
|
|
118
|
+
constructor(options = {}) {
|
|
119
119
|
this.cwd = options.cwd || process.cwd();
|
|
120
120
|
this.fsImpl = options.fsImpl || nodeFs;
|
|
121
121
|
this.exec = options.exec || spawnSync;
|
|
@@ -133,7 +133,7 @@ class VerificationEngine {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
/** @returns {string} ISO timestamp from the injected clock */
|
|
136
|
-
timestamp()
|
|
136
|
+
timestamp() {
|
|
137
137
|
return new Date(this.clock()).toISOString();
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -141,7 +141,7 @@ class VerificationEngine {
|
|
|
141
141
|
* @param {string} riskLevel
|
|
142
142
|
* @returns {Object} the risk-level definition
|
|
143
143
|
*/
|
|
144
|
-
riskDefinition(riskLevel
|
|
144
|
+
riskDefinition(riskLevel ) {
|
|
145
145
|
const def = this.registry.riskLevels[riskLevel];
|
|
146
146
|
if (!def) {
|
|
147
147
|
// Never coerce to the default: a caller that mistyped CRITICAL would silently get MEDIUM's
|
|
@@ -178,7 +178,7 @@ class VerificationEngine {
|
|
|
178
178
|
* tier's generic reason and `searched` lists the places looked.
|
|
179
179
|
* @returns {Object} the contract
|
|
180
180
|
*/
|
|
181
|
-
compileContract(input
|
|
181
|
+
compileContract(input = {}) {
|
|
182
182
|
// A contract with no task is the readiness.ts `taskId || 'default'` defect in a new place: a
|
|
183
183
|
// confident verdict filed against the wrong subject. Refuse instead of inventing an id.
|
|
184
184
|
if (typeof input.taskId !== 'string' || input.taskId.trim() === '') {
|
|
@@ -201,8 +201,8 @@ class VerificationEngine {
|
|
|
201
201
|
let requirements = Array.isArray(input.requirements) ? input.requirements : [];
|
|
202
202
|
const scope = input.scope && typeof input.scope === 'object' ? input.scope : {};
|
|
203
203
|
const phase = input.phase === 'postimplementation' ? 'postimplementation' : 'unknown';
|
|
204
|
-
const declaredNone = new Set
|
|
205
|
-
const absent = new Set
|
|
204
|
+
const declaredNone = new Set (detection.declaredNone || []);
|
|
205
|
+
const absent = new Set (detection.absent || []);
|
|
206
206
|
let provenance = input.provenance && typeof input.provenance === 'object' ? { ...input.provenance } : null;
|
|
207
207
|
|
|
208
208
|
if (input.verificationInput !== undefined && input.verificationInput !== null) {
|
|
@@ -216,7 +216,7 @@ class VerificationEngine {
|
|
|
216
216
|
provenance = { ...(provenance || {}), baselineSha: scope.baselineSha };
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
const tiers = this.registry.tiers.map((tier
|
|
219
|
+
const tiers = this.registry.tiers.map((tier ) => {
|
|
220
220
|
const required = requiredTiers.has(tier.id);
|
|
221
221
|
const advisory = advisoryTiers.has(tier.id);
|
|
222
222
|
const base = {
|
|
@@ -263,11 +263,11 @@ class VerificationEngine {
|
|
|
263
263
|
|
|
264
264
|
// Subsumption is a second pass because it depends on how a *later* tier resolved: parse is
|
|
265
265
|
// covered by build only if build actually has a command.
|
|
266
|
-
const byId = new Map
|
|
266
|
+
const byId = new Map (tiers.map((t ) => [t.id, t]));
|
|
267
267
|
for (const tier of this.registry.tiers) {
|
|
268
|
-
const resolved
|
|
268
|
+
const resolved = byId.get(tier.id);
|
|
269
269
|
if (!resolved || resolved.resolution !== 'UNRESOLVED') continue;
|
|
270
|
-
const cover = toStringArray(tier.subsumedBy).find((id
|
|
270
|
+
const cover = toStringArray(tier.subsumedBy).find((id ) => { const e = byId.get(id); return e && e.resolution === 'RESOLVED'; });
|
|
271
271
|
if (cover) {
|
|
272
272
|
resolved.resolution = 'SUBSUMED';
|
|
273
273
|
resolved.subsumedBy = cover;
|
|
@@ -277,11 +277,11 @@ class VerificationEngine {
|
|
|
277
277
|
|
|
278
278
|
// A targeted tier that would run the very command a required broad tier runs is that tier's
|
|
279
279
|
// duplicate, not a cheaper check: it is reported SUBSUMED and never executed (FR-001).
|
|
280
|
-
const targeted
|
|
281
|
-
const broad
|
|
280
|
+
const targeted = byId.get(TARGETED_TIER_ID);
|
|
281
|
+
const broad = byId.get(BROAD_TIER_ID);
|
|
282
282
|
if (targeted && broad && targeted.resolution === 'RESOLVED' && broad.required && broad.resolution === 'RESOLVED') {
|
|
283
|
-
const broadCommands = new Set
|
|
284
|
-
if (targeted.checks.length > 0 && targeted.checks.every((c
|
|
283
|
+
const broadCommands = new Set (broad.checks.map((c ) => c.command ));
|
|
284
|
+
if (targeted.checks.length > 0 && targeted.checks.every((c ) => broadCommands.has(c.command))) {
|
|
285
285
|
targeted.resolution = 'SUBSUMED';
|
|
286
286
|
targeted.subsumedBy = BROAD_TIER_ID;
|
|
287
287
|
targeted.checks = [];
|
|
@@ -290,8 +290,8 @@ class VerificationEngine {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
const unresolvedRequiredTiers = tiers
|
|
293
|
-
.filter((t
|
|
294
|
-
.map((t
|
|
293
|
+
.filter((t ) => t.required && t.resolution === 'UNRESOLVED')
|
|
294
|
+
.map((t ) => ({ tier: t.id, reason: t.reason }));
|
|
295
295
|
|
|
296
296
|
return {
|
|
297
297
|
version: this.registry.version,
|
|
@@ -322,7 +322,7 @@ class VerificationEngine {
|
|
|
322
322
|
},
|
|
323
323
|
tiers,
|
|
324
324
|
unresolvedRequiredTiers,
|
|
325
|
-
runnableCheckCount: tiers.reduce((n
|
|
325
|
+
runnableCheckCount: tiers.reduce((n , t ) => n + t.checks.length, 0),
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
328
|
|
|
@@ -331,7 +331,7 @@ class VerificationEngine {
|
|
|
331
331
|
* `command-detect` lands here.
|
|
332
332
|
* @private
|
|
333
333
|
*/
|
|
334
|
-
resolveCommandTier(tier
|
|
334
|
+
resolveCommandTier(tier , detection , declaredNone , absent , targetPattern , selectorContext = null) {
|
|
335
335
|
if (!detection.usable) {
|
|
336
336
|
return {
|
|
337
337
|
resolution: 'UNRESOLVED',
|
|
@@ -341,9 +341,9 @@ class VerificationEngine {
|
|
|
341
341
|
|
|
342
342
|
const roles = toStringArray(tier.commandRoles);
|
|
343
343
|
const selectAll = tier.commandRoleSelection === 'all';
|
|
344
|
-
const checks
|
|
345
|
-
const notes
|
|
346
|
-
let selection
|
|
344
|
+
const checks = [];
|
|
345
|
+
const notes = [];
|
|
346
|
+
let selection = null;
|
|
347
347
|
|
|
348
348
|
for (const role of roles) {
|
|
349
349
|
const found = detection.commands[role];
|
|
@@ -407,7 +407,7 @@ class VerificationEngine {
|
|
|
407
407
|
|
|
408
408
|
// Explicit human declaration outranks everything: the plan said this project has no such
|
|
409
409
|
// command, which is a statement, not a detection miss.
|
|
410
|
-
if (roles.every((role
|
|
410
|
+
if (roles.every((role ) => declaredNone.has(role))) {
|
|
411
411
|
return {
|
|
412
412
|
resolution: 'NOT_APPLICABLE',
|
|
413
413
|
reason: `plan.md declares this project has no ${roles.join('/')} command.`,
|
|
@@ -417,7 +417,7 @@ class VerificationEngine {
|
|
|
417
417
|
// A manifest was read and does not declare the role. For build or lint that is real evidence
|
|
418
418
|
// the project has no such step; for a test command it is not, and `absenceIsEvidence: false`
|
|
419
419
|
// in the registry is what keeps that case UNRESOLVED and off PASS.
|
|
420
|
-
if (tier.absenceIsEvidence && detection.manifestFound && roles.every((role
|
|
420
|
+
if (tier.absenceIsEvidence && detection.manifestFound && roles.every((role ) => absent.has(role) || declaredNone.has(role))) {
|
|
421
421
|
return {
|
|
422
422
|
resolution: 'NOT_APPLICABLE',
|
|
423
423
|
reason: `${detection.manifests.join(', ')} declares no ${roles.join('/')} step.`,
|
|
@@ -434,7 +434,7 @@ class VerificationEngine {
|
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
/** @private */
|
|
437
|
-
resolveStructuralTier(tier
|
|
437
|
+
resolveStructuralTier(tier , structuralChecks ) {
|
|
438
438
|
const checks = structuralChecks
|
|
439
439
|
.filter((c) => c && typeof c.command === 'string' && c.command.trim() !== '')
|
|
440
440
|
.map((c, i) => ({
|
|
@@ -456,9 +456,9 @@ class VerificationEngine {
|
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
/** @private */
|
|
459
|
-
resolveRequirementTier(tier
|
|
460
|
-
const checks
|
|
461
|
-
const unevaluated
|
|
459
|
+
resolveRequirementTier(tier , requirements ) {
|
|
460
|
+
const checks = [];
|
|
461
|
+
const unevaluated = [];
|
|
462
462
|
for (const req of requirements) {
|
|
463
463
|
if (!req || typeof req.id !== 'string' || req.id === '') continue;
|
|
464
464
|
if (typeof req.command === 'string' && req.command.trim() !== '') {
|
|
@@ -496,7 +496,7 @@ class VerificationEngine {
|
|
|
496
496
|
}
|
|
497
497
|
|
|
498
498
|
/** @private */
|
|
499
|
-
resolveScopeTier(tier
|
|
499
|
+
resolveScopeTier(tier , scope ) {
|
|
500
500
|
const maxFiles = Number.isFinite(scope.maxFiles) ? scope.maxFiles : null;
|
|
501
501
|
const allowedPaths = toStringArray(scope.allowedPaths);
|
|
502
502
|
if (maxFiles === null && allowedPaths.length === 0) {
|
|
@@ -529,14 +529,14 @@ class VerificationEngine {
|
|
|
529
529
|
* merge base with the integration ref to the working tree's
|
|
530
530
|
* @returns {{ files: Array<string>|null, reason: string|null, baseline?: Object }}
|
|
531
531
|
*/
|
|
532
|
-
observeChangedFiles(changedFiles
|
|
532
|
+
observeChangedFiles(changedFiles , bound = null) {
|
|
533
533
|
// An explicit baseline observes git itself: a caller's file list cannot stand in for what changed.
|
|
534
534
|
if (bound && bound.baseline === 'explicit') return this.observeSince(bound.baselineSha, { kind: 'pinned', ref: bound.baselineRef, sha: bound.baselineSha });
|
|
535
|
-
if (Array.isArray(changedFiles)) return { files: changedFiles.filter((f
|
|
535
|
+
if (Array.isArray(changedFiles)) return { files: changedFiles.filter((f ) => typeof f === 'string'), reason: null };
|
|
536
536
|
|
|
537
537
|
if (bound && bound.baseline === 'integration') return this.observeFromBase();
|
|
538
538
|
|
|
539
|
-
let res
|
|
539
|
+
let res ;
|
|
540
540
|
try {
|
|
541
541
|
res = this.exec('git status --porcelain', {
|
|
542
542
|
shell: true,
|
|
@@ -544,7 +544,7 @@ class VerificationEngine {
|
|
|
544
544
|
encoding: 'utf8',
|
|
545
545
|
timeout: 30000,
|
|
546
546
|
});
|
|
547
|
-
} catch (error
|
|
547
|
+
} catch (error ) {
|
|
548
548
|
return { files: null, reason: `Could not observe the changed files: ${error.message}` };
|
|
549
549
|
}
|
|
550
550
|
if (!res || res.error || res.status !== 0) {
|
|
@@ -560,7 +560,7 @@ class VerificationEngine {
|
|
|
560
560
|
* untracked directories expanded, so a non-ASCII name or a new directory of tests is not dropped.
|
|
561
561
|
* @private
|
|
562
562
|
*/
|
|
563
|
-
observeForSelection(changedFiles
|
|
563
|
+
observeForSelection(changedFiles , bound = null) {
|
|
564
564
|
const hasBaseline = bound && (bound.baseline === 'explicit' || bound.baseline === 'integration');
|
|
565
565
|
if (!Array.isArray(changedFiles) && !hasBaseline) return this.observeSince(null, null);
|
|
566
566
|
return this.observeChangedFiles(changedFiles, bound);
|
|
@@ -573,7 +573,7 @@ class VerificationEngine {
|
|
|
573
573
|
* reports both where it was and where it went.
|
|
574
574
|
* @private
|
|
575
575
|
*/
|
|
576
|
-
observeFromBase()
|
|
576
|
+
observeFromBase() {
|
|
577
577
|
const base = resolveIntegrationBase({ cwd: this.cwd, exec: this.exec });
|
|
578
578
|
if (base.reason) {
|
|
579
579
|
const working = this.observeSince(null, null);
|
|
@@ -589,12 +589,12 @@ class VerificationEngine {
|
|
|
589
589
|
* DoFlow's own `.doflow/` state is excluded.
|
|
590
590
|
* @private
|
|
591
591
|
*/
|
|
592
|
-
observeSince(commit
|
|
593
|
-
const git = (args
|
|
594
|
-
let res
|
|
592
|
+
observeSince(commit , baseline ) {
|
|
593
|
+
const git = (args ) => {
|
|
594
|
+
let res ;
|
|
595
595
|
try {
|
|
596
596
|
res = this.exec('git', ['-c', 'core.quotePath=false', ...args], { cwd: this.cwd, encoding: 'utf8', timeout: 30000 });
|
|
597
|
-
} catch (error
|
|
597
|
+
} catch (error ) {
|
|
598
598
|
return { reason: `Could not observe the changed files: ${error.message}` };
|
|
599
599
|
}
|
|
600
600
|
if (!res || res.error || res.status !== 0) {
|
|
@@ -608,13 +608,13 @@ class VerificationEngine {
|
|
|
608
608
|
// `-z` porcelain entries are `XY <path>` separated by NUL.
|
|
609
609
|
// `.doflow/` is DoFlow's own state (records, journals): writing it is not changing the project,
|
|
610
610
|
// and a repo that does not ignore it would otherwise fail its own scope after any record.
|
|
611
|
-
const counts = (f
|
|
612
|
-
const working = status.out
|
|
611
|
+
const counts = (f ) => f !== '.doflow' && !f.startsWith('.doflow/');
|
|
612
|
+
const working = status.out .split('\0').filter(Boolean).map((entry ) => entry.slice(3)).filter(Boolean).filter(counts);
|
|
613
613
|
if (commit === null) return { files: working, reason: null };
|
|
614
614
|
|
|
615
615
|
const diff = git(['diff', '--name-only', '--no-renames', '-z', `${commit}..HEAD`]);
|
|
616
616
|
if (diff.reason) return { files: null, reason: diff.reason };
|
|
617
|
-
const committed = diff.out
|
|
617
|
+
const committed = diff.out .split('\0').filter(Boolean).filter(counts);
|
|
618
618
|
return { files: [...new Set([...committed, ...working])], reason: null, baseline };
|
|
619
619
|
}
|
|
620
620
|
|
|
@@ -627,24 +627,24 @@ class VerificationEngine {
|
|
|
627
627
|
* @param {{status: string, findings?: Array<string>, reviewer?: string}} [runtimeInput.modelReview]
|
|
628
628
|
* @returns {Object} the report
|
|
629
629
|
*/
|
|
630
|
-
runContract(contract
|
|
630
|
+
runContract(contract , runtimeInput = {}) {
|
|
631
631
|
if (!contract || !Array.isArray(contract.tiers)) {
|
|
632
632
|
throw new Error('runContract expects a contract produced by compileContract');
|
|
633
633
|
}
|
|
634
634
|
|
|
635
635
|
const startedAt = this.timestamp();
|
|
636
|
-
const reportTiers
|
|
637
|
-
const failedChecks
|
|
638
|
-
let fatalTier
|
|
636
|
+
const reportTiers = [];
|
|
637
|
+
const failedChecks = [];
|
|
638
|
+
let fatalTier = null;
|
|
639
639
|
let checksRun = 0;
|
|
640
640
|
let checksPassed = 0;
|
|
641
641
|
// One evaluation pass, one cache: two tiers declaring the same command (targeted and broad
|
|
642
642
|
// tests are often both `npm test`) run it once and share the result. A recovery retry calls
|
|
643
643
|
// runContract again and gets a fresh cache, so retries always observe live behaviour.
|
|
644
|
-
const dedupeCache = new Map
|
|
644
|
+
const dedupeCache = new Map ();
|
|
645
645
|
|
|
646
646
|
for (const tier of contract.tiers) {
|
|
647
|
-
const entry
|
|
647
|
+
const entry = {
|
|
648
648
|
id: tier.id,
|
|
649
649
|
name: tier.name,
|
|
650
650
|
kind: tier.kind,
|
|
@@ -694,7 +694,7 @@ class VerificationEngine {
|
|
|
694
694
|
else failedChecks.push(result.name);
|
|
695
695
|
}
|
|
696
696
|
|
|
697
|
-
const anyFailed = entry.checks.some((c
|
|
697
|
+
const anyFailed = entry.checks.some((c ) => c.status === 'FAIL');
|
|
698
698
|
if (anyFailed) {
|
|
699
699
|
entry.status = 'FAIL';
|
|
700
700
|
if (tier.fatal) fatalTier = tier.id;
|
|
@@ -718,13 +718,13 @@ class VerificationEngine {
|
|
|
718
718
|
const evaluated = reportTiers.filter((t) => t.status === 'PASS' || t.status === 'FAIL');
|
|
719
719
|
const modelTier = reportTiers.find((t) => t.modelBased);
|
|
720
720
|
|
|
721
|
-
let status
|
|
722
|
-
let reason
|
|
721
|
+
let status ;
|
|
722
|
+
let reason ;
|
|
723
723
|
if (evaluated.length === 0) {
|
|
724
724
|
// The empty-contract defect, generalized: zero evaluated tiers is a verdict over zero
|
|
725
725
|
// evidence whether the contract was empty or every tier turned out unresolvable.
|
|
726
726
|
status = 'INCONCLUSIVE';
|
|
727
|
-
reason = contract.tiers.every((t
|
|
727
|
+
reason = contract.tiers.every((t ) => t.resolution === 'SKIPPED')
|
|
728
728
|
? 'Contract declared no checks; nothing was verified'
|
|
729
729
|
: 'No tier produced a result; nothing was verified';
|
|
730
730
|
} else if (requiredFailed.length > 0) {
|
|
@@ -769,9 +769,9 @@ class VerificationEngine {
|
|
|
769
769
|
requirementSatisfaction: requirementTier
|
|
770
770
|
? {
|
|
771
771
|
status: requirementTier.status,
|
|
772
|
-
satisfied: requirementTier.checks.filter((c
|
|
773
|
-
failed: requirementTier.checks.filter((c
|
|
774
|
-
unevaluated: (requirementTier.unevaluatedRequirements || []).map((r
|
|
772
|
+
satisfied: requirementTier.checks.filter((c ) => c.status === 'PASS').map((c ) => c.requirementId || c.name),
|
|
773
|
+
failed: requirementTier.checks.filter((c ) => c.status === 'FAIL').map((c ) => c.requirementId || c.name),
|
|
774
|
+
unevaluated: (requirementTier.unevaluatedRequirements || []).map((r ) => r.id),
|
|
775
775
|
}
|
|
776
776
|
: null,
|
|
777
777
|
scope: scopeTier ? scopeTier.scope || { status: scopeTier.status, reason: scopeTier.reason } : null,
|
|
@@ -799,7 +799,7 @@ class VerificationEngine {
|
|
|
799
799
|
}
|
|
800
800
|
|
|
801
801
|
/** @private */
|
|
802
|
-
evaluateModelTier(tier
|
|
802
|
+
evaluateModelTier(tier , modelReview ) {
|
|
803
803
|
if (!modelReview || typeof modelReview.status !== 'string') {
|
|
804
804
|
return {
|
|
805
805
|
status: 'UNRESOLVED',
|
|
@@ -824,7 +824,7 @@ class VerificationEngine {
|
|
|
824
824
|
}
|
|
825
825
|
|
|
826
826
|
/** @private */
|
|
827
|
-
evaluateScopeTier(tier
|
|
827
|
+
evaluateScopeTier(tier , changedFiles ) {
|
|
828
828
|
const bound = tier.bound || { maxFiles: null, allowedPaths: [] };
|
|
829
829
|
const { files, reason, baseline } = this.observeChangedFiles(changedFiles, bound);
|
|
830
830
|
if (files === null) {
|
|
@@ -835,12 +835,12 @@ class VerificationEngine {
|
|
|
835
835
|
};
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
-
const violations
|
|
838
|
+
const violations = [];
|
|
839
839
|
if (Number.isFinite(bound.maxFiles) && files.length > bound.maxFiles) {
|
|
840
840
|
violations.push(`${files.length} files changed, bound was ${bound.maxFiles}`);
|
|
841
841
|
}
|
|
842
842
|
if (bound.allowedPaths.length > 0) {
|
|
843
|
-
const outside = files.filter((f
|
|
843
|
+
const outside = files.filter((f ) => !bound.allowedPaths.some((p ) => f === p || f.startsWith(p.endsWith('/') ? p : `${p}/`)));
|
|
844
844
|
if (outside.length > 0) violations.push(`outside the declared paths: ${outside.join(', ')}`);
|
|
845
845
|
}
|
|
846
846
|
|
|
@@ -869,7 +869,7 @@ class VerificationEngine {
|
|
|
869
869
|
* @param {string} [acceptance.note]
|
|
870
870
|
* @returns {{ complete: boolean, basis: string, reason: string, acceptedFailures?: Array<Object> }}
|
|
871
871
|
*/
|
|
872
|
-
declareCompletion(report
|
|
872
|
+
declareCompletion(report , acceptance = {}) {
|
|
873
873
|
if (!report || typeof report.status !== 'string') {
|
|
874
874
|
throw new Error('declareCompletion expects a report produced by runContract');
|
|
875
875
|
}
|
|
@@ -898,8 +898,8 @@ class VerificationEngine {
|
|
|
898
898
|
basis: 'user-accepted-failures',
|
|
899
899
|
reason: acceptance.note || `Accepted by ${acceptance.acceptedBy} while ${report.status}.`,
|
|
900
900
|
acceptedFailures: (report.tiers || [])
|
|
901
|
-
.filter((t
|
|
902
|
-
.map((t
|
|
901
|
+
.filter((t ) => t.required && t.status !== 'PASS' && t.status !== 'NOT_APPLICABLE' && t.status !== 'SUBSUMED' && t.status !== 'SKIPPED')
|
|
902
|
+
.map((t ) => ({ tier: t.id, status: t.status, reason: t.reason })),
|
|
903
903
|
};
|
|
904
904
|
}
|
|
905
905
|
|
|
@@ -909,9 +909,9 @@ class VerificationEngine {
|
|
|
909
909
|
* @param {Object} report
|
|
910
910
|
* @returns {string}
|
|
911
911
|
*/
|
|
912
|
-
failureSignature(report
|
|
912
|
+
failureSignature(report ) {
|
|
913
913
|
const failed = [...(report.failedChecks || [])].sort().join('|');
|
|
914
|
-
const unresolved = (report.unresolved || []).map((u
|
|
914
|
+
const unresolved = (report.unresolved || []).map((u ) => u.tier).sort().join('|');
|
|
915
915
|
return `${report.status}::${failed}::${unresolved}`;
|
|
916
916
|
}
|
|
917
917
|
|
|
@@ -926,7 +926,7 @@ class VerificationEngine {
|
|
|
926
926
|
* @param {number} [options.maxIterations] defaults to the report's risk level
|
|
927
927
|
* @returns {Object|null} null when there is nothing to recover from
|
|
928
928
|
*/
|
|
929
|
-
planRecovery(report
|
|
929
|
+
planRecovery(report , options = {}) {
|
|
930
930
|
if (!report || report.status === 'PASS') return null;
|
|
931
931
|
|
|
932
932
|
const maxIterations = Number.isInteger(options.maxIterations)
|
|
@@ -934,17 +934,17 @@ class VerificationEngine {
|
|
|
934
934
|
: this.riskDefinition(report.riskLevel).maxRecoveryIterations;
|
|
935
935
|
const manager = this.recoveryOverride || new RecoveryManager({ maxIterations });
|
|
936
936
|
|
|
937
|
-
const failingTier = (report.tiers || []).find((t
|
|
938
|
-
|| (report.tiers || []).find((t
|
|
939
|
-
const unverifiedTier = (report.tiers || []).find((t
|
|
937
|
+
const failingTier = (report.tiers || []).find((t ) => t.required && t.status === 'FAIL')
|
|
938
|
+
|| (report.tiers || []).find((t ) => t.status === 'FAIL');
|
|
939
|
+
const unverifiedTier = (report.tiers || []).find((t ) => t.required && (t.status === 'UNRESOLVED' || t.status === 'NOT_RUN'));
|
|
940
940
|
|
|
941
|
-
let failureClass
|
|
942
|
-
let classificationSource
|
|
943
|
-
let errorMessage
|
|
944
|
-
let hint
|
|
941
|
+
let failureClass ;
|
|
942
|
+
let classificationSource ;
|
|
943
|
+
let errorMessage ;
|
|
944
|
+
let hint = null;
|
|
945
945
|
|
|
946
946
|
if (failingTier) {
|
|
947
|
-
const firstFailure = (failingTier.checks || []).find((c
|
|
947
|
+
const firstFailure = (failingTier.checks || []).find((c ) => c.status === 'FAIL');
|
|
948
948
|
errorMessage = [failingTier.reason, firstFailure && firstFailure.error, firstFailure && firstFailure.stderr]
|
|
949
949
|
.filter(Boolean)
|
|
950
950
|
.join(' ');
|
|
@@ -996,12 +996,12 @@ class VerificationEngine {
|
|
|
996
996
|
* @param {Object} [options.runtimeInput] forwarded to each `runContract`
|
|
997
997
|
* @returns {{ outcome: string, report: Object, plan: Object|null, attempts: Array<Object>, reason: string }}
|
|
998
998
|
*/
|
|
999
|
-
runWithRecovery(contract
|
|
999
|
+
runWithRecovery(contract , options = {}) {
|
|
1000
1000
|
let current = contract;
|
|
1001
1001
|
let lastAgent = options.lastAgent;
|
|
1002
|
-
let previousSignature
|
|
1002
|
+
let previousSignature = null;
|
|
1003
1003
|
let iteration = 0;
|
|
1004
|
-
const attempts
|
|
1004
|
+
const attempts = [];
|
|
1005
1005
|
|
|
1006
1006
|
for (;;) {
|
|
1007
1007
|
const report = this.runContract(current, options.runtimeInput || {});
|
|
@@ -1059,7 +1059,7 @@ class VerificationEngine {
|
|
|
1059
1059
|
* @param {Object} input `compileContract` input
|
|
1060
1060
|
* @param {Object} [runtimeInput] `runContract` input
|
|
1061
1061
|
*/
|
|
1062
|
-
verify(input
|
|
1062
|
+
verify(input , runtimeInput ) {
|
|
1063
1063
|
const contract = this.compileContract(input);
|
|
1064
1064
|
return { contract, report: this.runContract(contract, runtimeInput) };
|
|
1065
1065
|
}
|
|
@@ -1095,27 +1095,27 @@ class VerificationEngine {
|
|
|
1095
1095
|
* @param {Object} [options.crypto]
|
|
1096
1096
|
* @returns {{ ok: true, structuralChecks: Array<Object>, requirements: Array<Object>, provenance: Object } | { ok: false, message: string }}
|
|
1097
1097
|
*/
|
|
1098
|
-
function validateVerificationInput({ cwd, verificationInput, fs = nodeFs, crypto = nodeCrypto }
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1098
|
+
function validateVerificationInput({ cwd, verificationInput, fs = nodeFs, crypto = nodeCrypto }
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
) {
|
|
1104
1104
|
if (typeof verificationInput !== 'string' || verificationInput.trim() === '') {
|
|
1105
1105
|
return { ok: false, message: '--verification-input requires a file path.' };
|
|
1106
1106
|
}
|
|
1107
1107
|
const inputAbs = path.isAbsolute(verificationInput) ? verificationInput : path.resolve(cwd, verificationInput);
|
|
1108
|
-
let rawBytes
|
|
1108
|
+
let rawBytes ;
|
|
1109
1109
|
try {
|
|
1110
1110
|
rawBytes = fs.readFileSync(inputAbs);
|
|
1111
|
-
} catch (err
|
|
1111
|
+
} catch (err ) {
|
|
1112
1112
|
return { ok: false, message: `--verification-input '${verificationInput}' could not be read: ${err.message}` };
|
|
1113
1113
|
}
|
|
1114
1114
|
const manifestDigest = 'sha256:' + crypto.createHash('sha256').update(rawBytes).digest('hex');
|
|
1115
|
-
let manifest
|
|
1115
|
+
let manifest ;
|
|
1116
1116
|
try {
|
|
1117
1117
|
manifest = JSON.parse(rawBytes.toString('utf8'));
|
|
1118
|
-
} catch (err
|
|
1118
|
+
} catch (err ) {
|
|
1119
1119
|
return { ok: false, message: `--verification-input '${verificationInput}' is not valid JSON: ${err.message}` };
|
|
1120
1120
|
}
|
|
1121
1121
|
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
@@ -1142,10 +1142,10 @@ function validateVerificationInput({ cwd, verificationInput, fs = nodeFs, crypto
|
|
|
1142
1142
|
return { ok: false, message: '--verification-input originalContractDigest must be in format sha256:<64 hex chars>.' };
|
|
1143
1143
|
}
|
|
1144
1144
|
const origAbs = path.isAbsolute(manifest.originalContractPath) ? manifest.originalContractPath : path.resolve(cwd, manifest.originalContractPath);
|
|
1145
|
-
let origBytes
|
|
1145
|
+
let origBytes ;
|
|
1146
1146
|
try {
|
|
1147
1147
|
origBytes = fs.readFileSync(origAbs);
|
|
1148
|
-
} catch (err
|
|
1148
|
+
} catch (err ) {
|
|
1149
1149
|
return { ok: false, message: `--verification-input originalContractPath '${manifest.originalContractPath}' could not be read: ${err.message}` };
|
|
1150
1150
|
}
|
|
1151
1151
|
const actualOrigDigest = 'sha256:' + crypto.createHash('sha256').update(origBytes).digest('hex');
|
|
@@ -1154,12 +1154,12 @@ function validateVerificationInput({ cwd, verificationInput, fs = nodeFs, crypto
|
|
|
1154
1154
|
}
|
|
1155
1155
|
}
|
|
1156
1156
|
|
|
1157
|
-
let structuralChecks
|
|
1157
|
+
let structuralChecks = [];
|
|
1158
1158
|
if (manifest.structuralChecks !== undefined) {
|
|
1159
1159
|
if (!Array.isArray(manifest.structuralChecks)) {
|
|
1160
1160
|
return { ok: false, message: '--verification-input structuralChecks must be an array.' };
|
|
1161
1161
|
}
|
|
1162
|
-
const seenNames = new Set
|
|
1162
|
+
const seenNames = new Set ();
|
|
1163
1163
|
const ALLOWED_STRUCT_KEYS = new Set(['name', 'command', 'timeoutMs']);
|
|
1164
1164
|
for (let i = 0; i < manifest.structuralChecks.length; i++) {
|
|
1165
1165
|
const entry = manifest.structuralChecks[i];
|
|
@@ -1188,12 +1188,12 @@ function validateVerificationInput({ cwd, verificationInput, fs = nodeFs, crypto
|
|
|
1188
1188
|
structuralChecks = manifest.structuralChecks;
|
|
1189
1189
|
}
|
|
1190
1190
|
|
|
1191
|
-
let requirements
|
|
1191
|
+
let requirements = [];
|
|
1192
1192
|
if (manifest.requirements !== undefined) {
|
|
1193
1193
|
if (!Array.isArray(manifest.requirements)) {
|
|
1194
1194
|
return { ok: false, message: '--verification-input requirements must be an array.' };
|
|
1195
1195
|
}
|
|
1196
|
-
const seenIds = new Set
|
|
1196
|
+
const seenIds = new Set ();
|
|
1197
1197
|
const ALLOWED_REQ_KEYS = new Set(['id', 'description', 'command', 'timeoutMs']);
|
|
1198
1198
|
for (let i = 0; i < manifest.requirements.length; i++) {
|
|
1199
1199
|
const entry = manifest.requirements[i];
|
|
@@ -1260,23 +1260,23 @@ function validateVerificationInput({ cwd, verificationInput, fs = nodeFs, crypto
|
|
|
1260
1260
|
* @param {string} [options.baselineRef] commit or ref to measure change against (IC-022)
|
|
1261
1261
|
* @returns {number} exit code
|
|
1262
1262
|
*/
|
|
1263
|
-
function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json = false, projectRoot, slug = null, scope = null, verificationInput = null, verificationPhase = null, baselineRef = null }
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1263
|
+
function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json = false, projectRoot, slug = null, scope = null, verificationInput = null, verificationPhase = null, baselineRef = null }
|
|
1264
|
+
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
= {}) {
|
|
1276
1276
|
// The task id names state files here, so it obeys the rule every other task verb applies.
|
|
1277
1277
|
try {
|
|
1278
1278
|
assertSafeTaskId(taskId);
|
|
1279
|
-
} catch (error
|
|
1279
|
+
} catch (error ) {
|
|
1280
1280
|
return usageError('verify', error.message, json);
|
|
1281
1281
|
}
|
|
1282
1282
|
|
|
@@ -1286,7 +1286,7 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
|
|
|
1286
1286
|
}
|
|
1287
1287
|
}
|
|
1288
1288
|
|
|
1289
|
-
let declared
|
|
1289
|
+
let declared = null;
|
|
1290
1290
|
if (scope !== null && scope !== undefined) {
|
|
1291
1291
|
const parsed = parseDeclaredScope(typeof scope === 'string' ? scope : '');
|
|
1292
1292
|
if (parsed.reason) {
|
|
@@ -1296,9 +1296,9 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
|
|
|
1296
1296
|
}
|
|
1297
1297
|
const cwd = projectRoot || process.cwd();
|
|
1298
1298
|
|
|
1299
|
-
let structuralChecks
|
|
1300
|
-
let requirements
|
|
1301
|
-
let inputProvenance
|
|
1299
|
+
let structuralChecks = [];
|
|
1300
|
+
let requirements = [];
|
|
1301
|
+
let inputProvenance = null;
|
|
1302
1302
|
if (verificationInput !== null && verificationInput !== undefined) {
|
|
1303
1303
|
const val = validateVerificationInput({ cwd, verificationInput });
|
|
1304
1304
|
if (!val.ok) {
|
|
@@ -1334,8 +1334,8 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
|
|
|
1334
1334
|
if (!declared) declared = { paths: [...recordedScope], origin: 'readiness-record', record: recordRel };
|
|
1335
1335
|
}
|
|
1336
1336
|
|
|
1337
|
-
let engine
|
|
1338
|
-
let contract
|
|
1337
|
+
let engine ;
|
|
1338
|
+
let contract ;
|
|
1339
1339
|
try {
|
|
1340
1340
|
engine = new VerificationEngine({ cwd, repoRoot: REPO_ROOT });
|
|
1341
1341
|
// The bound comes from the feature's plan, this checkout's or the main checkout's, and from a
|
|
@@ -1356,15 +1356,15 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
|
|
|
1356
1356
|
requirements,
|
|
1357
1357
|
provenance: inputProvenance,
|
|
1358
1358
|
});
|
|
1359
|
-
} catch (error
|
|
1359
|
+
} catch (error ) {
|
|
1360
1360
|
return usageError('verify', error.message, json, error);
|
|
1361
1361
|
}
|
|
1362
1362
|
// The bound's sources print under the change-scope tier's line; no other line changes.
|
|
1363
1363
|
const boundLines = new Map(contract.tiers
|
|
1364
|
-
.filter((t
|
|
1365
|
-
.map((t
|
|
1364
|
+
.filter((t ) => t.bound && Array.isArray(t.bound.sources) && t.bound.sources.length > 0)
|
|
1365
|
+
.map((t ) => [t.id, ` bound: ${boundSourcesText(t.bound.sources)}`]));
|
|
1366
1366
|
if (recordNote) {
|
|
1367
|
-
const scopeTier = contract.tiers.find((t
|
|
1367
|
+
const scopeTier = contract.tiers.find((t ) => t.kind === 'scope');
|
|
1368
1368
|
if (scopeTier) boundLines.set(scopeTier.id, [boundLines.get(scopeTier.id), ` ${recordNote}`].filter(Boolean).join('\n'));
|
|
1369
1369
|
}
|
|
1370
1370
|
|
|
@@ -1394,7 +1394,7 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
|
|
|
1394
1394
|
// record exists: the checks passing says nothing about whether the work should have started.
|
|
1395
1395
|
try {
|
|
1396
1396
|
report.readiness = verifyReadinessCheck({ stateRoot: cwd, taskId, slug: featureSlug });
|
|
1397
|
-
} catch (error
|
|
1397
|
+
} catch (error ) {
|
|
1398
1398
|
return usageError('verify', error.message, json, error);
|
|
1399
1399
|
}
|
|
1400
1400
|
const readinessHeld = report.readiness.applies && !report.readiness.ok;
|
|
@@ -1430,7 +1430,7 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
|
|
|
1430
1430
|
}
|
|
1431
1431
|
|
|
1432
1432
|
/** One line stating what the targeted tier selected, or why it ran the broad command instead. */
|
|
1433
|
-
function selectionText(selection
|
|
1433
|
+
function selectionText(selection ) {
|
|
1434
1434
|
const of = selection.totalCount === null ? '' : ` of ${selection.totalCount}`;
|
|
1435
1435
|
if (selection.fallback) return `selection: ${selection.basis}, fell back to the broad command (${selection.fallback})`;
|
|
1436
1436
|
if (selection.selectedCount === null) return `selection: ${selection.basis}`;
|
|
@@ -1438,7 +1438,7 @@ function selectionText(selection: any): string {
|
|
|
1438
1438
|
}
|
|
1439
1439
|
|
|
1440
1440
|
/** Whether two path lists name the same set. */
|
|
1441
|
-
function sameSet(a
|
|
1441
|
+
function sameSet(a , b ) {
|
|
1442
1442
|
const left = new Set(a);
|
|
1443
1443
|
const right = new Set(b);
|
|
1444
1444
|
return left.size === right.size && [...left].every((p) => right.has(p));
|