@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.4
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/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/registry/storage.json +87 -83
- package/core/registry/verification.json +1 -1
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
- package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
- package/package.json +4 -2
- package/src/adapters/antigravity/{index.ts → index.js} +60 -60
- package/src/adapters/claude/{index.ts → index.js} +48 -48
- 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} +23 -23
- package/src/adapters/codex/{index.ts → index.js} +98 -98
- 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} +70 -70
- package/src/adapters/gemini/{hooks.ts → hooks.js} +24 -24
- package/src/adapters/gemini/{index.ts → index.js} +41 -41
- 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} +34 -34
- 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/{index.ts → index.js} +30 -30
- 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} +24 -24
- 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} +19 -19
- package/src/cli/{index.ts → index.js} +26 -26
- package/src/cli/{runtime-commands.ts → runtime-commands.js} +48 -48
- 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/{index.ts → index.js} +140 -140
- 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} +60 -60
- 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/{capability-router.ts → capability-router.js} +39 -39
- package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
- package/src/runtime/{claims.ts → claims.js} +30 -30
- package/src/runtime/{cli-result.ts → cli-result.js} +4 -4
- package/src/runtime/{cli.ts → cli.js} +37 -37
- package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
- package/src/runtime/{context-pack.ts → context-pack.js} +20 -20
- package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
- package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +25 -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} +15 -15
- 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} +35 -35
- 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} +40 -40
- package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +19 -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/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} +61 -61
- package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
- package/src/runtime/{task-scope.ts → task-scope.js} +13 -13
- package/src/runtime/{task-state.ts → task-state.js} +15 -15
- 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
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
-
const { parseYamlFile } = require('./capability-router.
|
|
8
|
-
const { resolveLocator, describeResolution } = require('./locator-resolve.
|
|
9
|
-
const { REPO_ROOT } = require('../helper/repo-root.
|
|
7
|
+
const { parseYamlFile } = require('./capability-router.js');
|
|
8
|
+
const { resolveLocator, describeResolution } = require('./locator-resolve.js');
|
|
9
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
10
10
|
|
|
11
11
|
const READINESS_STATES = new Set([
|
|
12
12
|
'READY',
|
|
@@ -37,7 +37,7 @@ const STAGE_ENTRY_DECISIONS = new Set(['ENTER', 'GATHER_FIRST', 'ASK_USER', 'STO
|
|
|
37
37
|
* @param {string} [mode='workflow'] 'workflow' | 'standalone'
|
|
38
38
|
* @returns {{decision: string, reason: string}}
|
|
39
39
|
*/
|
|
40
|
-
function stageEntryFor(state
|
|
40
|
+
function stageEntryFor(state , mode = 'workflow') {
|
|
41
41
|
if (!EXECUTION_MODES.has(mode)) {
|
|
42
42
|
throw new Error(`Unknown execution mode '${mode}'. Valid: ${[...EXECUTION_MODES].join(', ')} — the standalone exemption is declared, never inferred.`);
|
|
43
43
|
}
|
|
@@ -60,11 +60,11 @@ function stageEntryFor(state: any, mode = 'workflow') {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
class ReadinessEngine {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
68
|
/**
|
|
69
69
|
* @param {Object} [options]
|
|
70
70
|
* @param {string} [options.repoRoot] the DoFlow install, where readiness-templates.json lives
|
|
@@ -75,7 +75,7 @@ class ReadinessEngine {
|
|
|
75
75
|
* @param {Object} [options.templates]
|
|
76
76
|
* @param {Object} [options.fsImpl]
|
|
77
77
|
*/
|
|
78
|
-
constructor(options
|
|
78
|
+
constructor(options = {}) {
|
|
79
79
|
this.fsImpl = options.fsImpl || fs;
|
|
80
80
|
this.repoRoot = options.repoRoot || REPO_ROOT;
|
|
81
81
|
this.projectRoot = options.projectRoot || process.cwd();
|
|
@@ -92,7 +92,7 @@ class ReadinessEngine {
|
|
|
92
92
|
try {
|
|
93
93
|
const data = parseYamlFile(this.templatePath, this.fsImpl);
|
|
94
94
|
this.templates = data.templates || {};
|
|
95
|
-
} catch (error
|
|
95
|
+
} catch (error ) {
|
|
96
96
|
throw new Error(`Failed to load readiness templates from '${this.templatePath}': ${error.message}`);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -104,7 +104,7 @@ class ReadinessEngine {
|
|
|
104
104
|
* @param {Object} taskProfile
|
|
105
105
|
* @returns {Object}
|
|
106
106
|
*/
|
|
107
|
-
suggestAction(reqId
|
|
107
|
+
suggestAction(reqId , reqDef , taskProfile ) {
|
|
108
108
|
if (reqId === 'root_cause') {
|
|
109
109
|
return {
|
|
110
110
|
intent: 'trace-dependency',
|
|
@@ -152,7 +152,7 @@ class ReadinessEngine {
|
|
|
152
152
|
* @param {Object} claimsManager
|
|
153
153
|
* @returns {Object} ReadinessReport
|
|
154
154
|
*/
|
|
155
|
-
evaluateReadiness(taskProfile
|
|
155
|
+
evaluateReadiness(taskProfile , evidenceLedger , claimsManager ) {
|
|
156
156
|
if (!taskProfile || typeof taskProfile !== 'object') {
|
|
157
157
|
throw new Error('taskProfile must be an object');
|
|
158
158
|
}
|
|
@@ -206,7 +206,7 @@ class ReadinessEngine {
|
|
|
206
206
|
|
|
207
207
|
// Inspect claims
|
|
208
208
|
const taskClaims = claimsManager ? claimsManager.getClaims(taskId) : [];
|
|
209
|
-
const conflictedClaims = taskClaims.filter((c
|
|
209
|
+
const conflictedClaims = taskClaims.filter((c ) => c.status === 'conflicted');
|
|
210
210
|
if (conflictedClaims.length > 0) {
|
|
211
211
|
hasBlocked = true;
|
|
212
212
|
}
|
|
@@ -225,8 +225,8 @@ class ReadinessEngine {
|
|
|
225
225
|
// in evaluateClaim — it is not a claim that the file never changed. An item that genuinely went
|
|
226
226
|
// stale and was never superseded still forces NEEDS_EVIDENCE exactly as before.
|
|
227
227
|
const staleEvidence = (evidenceLedger ? evidenceLedger.getAllEvidence() : [])
|
|
228
|
-
.filter((item
|
|
229
|
-
.map((item
|
|
228
|
+
.filter((item ) => item.taskId === taskId && item.freshness?.status === 'STALE' && item.status !== 'superseded')
|
|
229
|
+
.map((item ) => ({ evidenceId: item.id, locator: item.locator, reason: 'file-modified-since-recorded' }));
|
|
230
230
|
|
|
231
231
|
const unresolvableEvidence = [];
|
|
232
232
|
for (const item of taskEvidence) {
|
|
@@ -242,7 +242,7 @@ class ReadinessEngine {
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
for (const [reqId, reqDef] of Object.entries
|
|
245
|
+
for (const [reqId, reqDef] of Object.entries (requirements)) {
|
|
246
246
|
let isSatisfied = false;
|
|
247
247
|
let reason = null;
|
|
248
248
|
let matchedEvidenceIds = [];
|
|
@@ -260,9 +260,9 @@ class ReadinessEngine {
|
|
|
260
260
|
// not by any supported claim the task happens to hold (review R1 — a supported claim about an
|
|
261
261
|
// unrelated invariant satisfied the bug template's root-cause requirement).
|
|
262
262
|
else if (reqDef.requiresClaimStatus) {
|
|
263
|
-
let matchingClaims = taskClaims.filter((c
|
|
263
|
+
let matchingClaims = taskClaims.filter((c ) => c.status === reqDef.requiresClaimStatus);
|
|
264
264
|
if (reqDef.requiresClaimRole) {
|
|
265
|
-
const withRole = matchingClaims.filter((c
|
|
265
|
+
const withRole = matchingClaims.filter((c ) => c.role === reqDef.requiresClaimRole);
|
|
266
266
|
if (withRole.length === 0 && matchingClaims.length > 0) {
|
|
267
267
|
reason = `${matchingClaims.length} claim(s) have status '${reqDef.requiresClaimStatus}' but none `
|
|
268
268
|
+ `declares role '${reqDef.requiresClaimRole}' — add the claim with --role ${reqDef.requiresClaimRole}, `
|
|
@@ -272,7 +272,7 @@ class ReadinessEngine {
|
|
|
272
272
|
}
|
|
273
273
|
if (matchingClaims.length > 0) {
|
|
274
274
|
isSatisfied = true;
|
|
275
|
-
matchedEvidenceIds = matchingClaims.flatMap((c
|
|
275
|
+
matchedEvidenceIds = matchingClaims.flatMap((c ) => c.supportingEvidence);
|
|
276
276
|
} else if (!reason) {
|
|
277
277
|
reason = `Requires at least one claim with status '${reqDef.requiresClaimStatus}'`
|
|
278
278
|
+ `${reqDef.requiresClaimRole ? ` and role '${reqDef.requiresClaimRole}'` : ''}.`;
|
|
@@ -286,21 +286,21 @@ class ReadinessEngine {
|
|
|
286
286
|
// (review R1, P1 — reproduced through the public CLI).
|
|
287
287
|
else if (Array.isArray(reqDef.evidenceKinds) && reqDef.evidenceKinds.length > 0) {
|
|
288
288
|
const allowedKinds = new Set(reqDef.evidenceKinds);
|
|
289
|
-
const kindMatched = taskEvidence.filter((ev
|
|
290
|
-
const extracted = kindMatched.filter((ev
|
|
291
|
-
const bound = extracted.filter((ev
|
|
289
|
+
const kindMatched = taskEvidence.filter((ev ) => allowedKinds.has(ev.kind));
|
|
290
|
+
const extracted = kindMatched.filter((ev ) => ev.provenance === 'extracted');
|
|
291
|
+
const bound = extracted.filter((ev ) => Array.isArray(ev.establishes) && ev.establishes.includes(reqId));
|
|
292
292
|
let qualifying = bound;
|
|
293
293
|
if (reqDef.requiresObservation) {
|
|
294
|
-
qualifying = qualifying.filter((ev
|
|
294
|
+
qualifying = qualifying.filter((ev ) => ev.observation && Number.isInteger(ev.observation.exitCode));
|
|
295
295
|
if (reqDef.expectedExit === 'zero') {
|
|
296
|
-
qualifying = qualifying.filter((ev
|
|
296
|
+
qualifying = qualifying.filter((ev ) => ev.observation.exitCode === 0);
|
|
297
297
|
} else if (reqDef.expectedExit === 'nonzero') {
|
|
298
|
-
qualifying = qualifying.filter((ev
|
|
298
|
+
qualifying = qualifying.filter((ev ) => ev.observation.exitCode !== 0);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
if (qualifying.length > 0) {
|
|
302
302
|
isSatisfied = true;
|
|
303
|
-
matchedEvidenceIds = qualifying.map((ev
|
|
303
|
+
matchedEvidenceIds = qualifying.map((ev ) => ev.id);
|
|
304
304
|
} else if (kindMatched.length === 0) {
|
|
305
305
|
reason = `Missing fresh evidence of kind: ${reqDef.evidenceKinds.join(', ')}.`;
|
|
306
306
|
} else if (extracted.length === 0) {
|
|
@@ -310,8 +310,8 @@ class ReadinessEngine {
|
|
|
310
310
|
reason = `${extracted.length} extracted item(s) of the right kind exist but none declares it `
|
|
311
311
|
+ `establishes '${reqId}' — record evidence with establishes: ['${reqId}'] so the gate grades `
|
|
312
312
|
+ 'what the item was actually gathered to prove.';
|
|
313
|
-
} else if (reqDef.expectedExit === 'zero' && bound.some((ev
|
|
314
|
-
const failed = bound.find((ev
|
|
313
|
+
} else if (reqDef.expectedExit === 'zero' && bound.some((ev ) => ev.observation && ev.observation.exitCode !== 0)) {
|
|
314
|
+
const failed = bound.find((ev ) => ev.observation && ev.observation.exitCode !== 0);
|
|
315
315
|
reason = `The bound observation ran '${failed.observation.command}' and exited ${failed.observation.exitCode}; `
|
|
316
316
|
+ 'this requirement needs an exit status of 0. A failed run does not establish a passing baseline.';
|
|
317
317
|
} else {
|
|
@@ -392,8 +392,8 @@ class ReadinessEngine {
|
|
|
392
392
|
requirements: requirementResults,
|
|
393
393
|
claimsSummary: {
|
|
394
394
|
total: taskClaims.length,
|
|
395
|
-
supported: taskClaims.filter((c
|
|
396
|
-
hypotheses: taskClaims.filter((c
|
|
395
|
+
supported: taskClaims.filter((c ) => c.status === 'supported').length,
|
|
396
|
+
hypotheses: taskClaims.filter((c ) => c.status === 'hypothesis').length,
|
|
397
397
|
conflicts: conflictedClaims.length,
|
|
398
398
|
},
|
|
399
399
|
evidenceCount: taskEvidence.length,
|
|
@@ -406,15 +406,15 @@ class ReadinessEngine {
|
|
|
406
406
|
/** One live snapshot for readiness inspection and orchestration transitions. Reads never persist
|
|
407
407
|
* derived freshness or claim status; every invocation evaluates the current project again.
|
|
408
408
|
* `mode` defaults to 'workflow' — fail closed: the standalone exemption must be declared. */
|
|
409
|
-
function evaluateTaskReadiness({ taskProfile, repoRoot = REPO_ROOT, projectRoot = process.cwd(), mode = 'workflow' }
|
|
409
|
+
function evaluateTaskReadiness({ taskProfile, repoRoot = REPO_ROOT, projectRoot = process.cwd(), mode = 'workflow' } ) {
|
|
410
410
|
// Validated before any ledger work, not just inside stageEntryFor at the end — an invalid mode
|
|
411
411
|
// used to pay a full freshness/claims evaluation before erroring (review suggestion, 026).
|
|
412
412
|
if (!EXECUTION_MODES.has(mode)) {
|
|
413
413
|
throw new Error(`Unknown execution mode '${mode}'. Valid: ${[...EXECUTION_MODES].join(', ')} — the standalone exemption is declared, never inferred.`);
|
|
414
414
|
}
|
|
415
|
-
const { EvidenceLedger } = require('./evidence-ledger.
|
|
416
|
-
const { ClaimsManager } = require('./claims.
|
|
417
|
-
const { FreshnessValidator } = require('./freshness.
|
|
415
|
+
const { EvidenceLedger } = require('./evidence-ledger.js');
|
|
416
|
+
const { ClaimsManager } = require('./claims.js');
|
|
417
|
+
const { FreshnessValidator } = require('./freshness.js');
|
|
418
418
|
const ledger = new EvidenceLedger({ repoRoot: projectRoot });
|
|
419
419
|
ledger.load(taskProfile.taskId);
|
|
420
420
|
new FreshnessValidator({ repoRoot: projectRoot }).validateLedgerFreshness(ledger);
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
* names either raised or silently iterated characters.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
const { finishRuntime, usageError } = require('./cli-result.
|
|
23
|
+
const { finishRuntime, usageError } = require('./cli-result.js');
|
|
24
24
|
|
|
25
25
|
/** The eleven classes, in the order the Python declared them. */
|
|
26
|
-
const FAILURE_CLASSES
|
|
26
|
+
const FAILURE_CLASSES = Object.freeze([
|
|
27
27
|
'CONTEXT_MISSING',
|
|
28
28
|
'WRONG_IMPLEMENTATION',
|
|
29
29
|
'TEST_FAILURE',
|
|
@@ -40,9 +40,9 @@ const FAILURE_CLASSES: readonly string[] = Object.freeze([
|
|
|
40
40
|
const DEFAULT_MAX_ITERATIONS = 3;
|
|
41
41
|
|
|
42
42
|
/** Check-name fragments that identify a build-stage failure regardless of the error text. */
|
|
43
|
-
const BUILD_CHECK_MARKERS
|
|
43
|
+
const BUILD_CHECK_MARKERS = Object.freeze(['syntax', 'compile', 'build']);
|
|
44
44
|
/** Check-name fragments that identify a test-stage failure. */
|
|
45
|
-
const TEST_CHECK_MARKERS
|
|
45
|
+
const TEST_CHECK_MARKERS = Object.freeze(['test', 'unit']);
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Per-class recovery strategy — **one entry per class, all eleven** (plan task C.2).
|
|
@@ -68,7 +68,7 @@ const TEST_CHECK_MARKERS: readonly string[] = Object.freeze(['test', 'unit']);
|
|
|
68
68
|
* already been spent (its `current_iteration >= 2`). `terminal` is new and means the class is not
|
|
69
69
|
* retryable at any iteration.
|
|
70
70
|
*/
|
|
71
|
-
const STRATEGY_MAP
|
|
71
|
+
const STRATEGY_MAP = Object.freeze({
|
|
72
72
|
CONTEXT_MISSING: { action: 'RETRIEVE_MORE_CONTEXT', targetRole: 'SCOUT', switchAtIteration: null },
|
|
73
73
|
WRONG_IMPLEMENTATION: { action: 'REVISE_IMPLEMENTATION', targetRole: 'IMPLEMENTER', switchAtIteration: 2 },
|
|
74
74
|
TEST_FAILURE: { action: 'TARGETED_DEBUGGER_FIX', targetRole: 'DEBUGGER', switchAtIteration: 2 },
|
|
@@ -86,7 +86,7 @@ const STRATEGY_MAP: Readonly<Record<string, any>> = Object.freeze({
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
/** Reachable only for a class string outside the eleven — see the note on STRATEGY_MAP. */
|
|
89
|
-
const GENERIC_STRATEGY
|
|
89
|
+
const GENERIC_STRATEGY = Object.freeze({
|
|
90
90
|
action: 'GENERIC_DEBUGGER_FIX',
|
|
91
91
|
targetRole: 'DEBUGGER',
|
|
92
92
|
switchAtIteration: 2,
|
|
@@ -96,23 +96,23 @@ const GENERIC_STRATEGY: Readonly<Record<string, any>> = Object.freeze({
|
|
|
96
96
|
* @param {*} value
|
|
97
97
|
* @returns {Array<string>} only the string members, lowercased
|
|
98
98
|
*/
|
|
99
|
-
function normalizeCheckNames(value
|
|
99
|
+
function normalizeCheckNames(value ) {
|
|
100
100
|
// Defect fix (2a): the Python accepted `failed_checks` unvalidated. Handed a plain string it
|
|
101
101
|
// iterated characters, so every single-letter "check" was tested for "syntax" and none matched —
|
|
102
102
|
// a build failure silently classified as WRONG_IMPLEMENTATION.
|
|
103
103
|
if (!Array.isArray(value)) return [];
|
|
104
|
-
return value.filter((v
|
|
104
|
+
return value.filter((v ) => typeof v === 'string').map((v ) => v.toLowerCase());
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
class RecoveryManager {
|
|
108
|
-
maxIterations
|
|
108
|
+
maxIterations ;
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
111
|
* @param {Object|number} [options] a number is accepted for parity with the Python's positional
|
|
112
112
|
* `max_iterations` argument
|
|
113
113
|
* @param {number} [options.maxIterations=3]
|
|
114
114
|
*/
|
|
115
|
-
constructor(options
|
|
115
|
+
constructor(options = {}) {
|
|
116
116
|
const opts = typeof options === 'number' ? { maxIterations: options } : options;
|
|
117
117
|
this.maxIterations = opts.maxIterations || DEFAULT_MAX_ITERATIONS;
|
|
118
118
|
}
|
|
@@ -126,13 +126,13 @@ class RecoveryManager {
|
|
|
126
126
|
* @param {Array<string>} [failedChecks=[]]
|
|
127
127
|
* @returns {string} one of FAILURE_CLASSES
|
|
128
128
|
*/
|
|
129
|
-
classifyFailure(errorMessage
|
|
129
|
+
classifyFailure(errorMessage , failedChecks = []) {
|
|
130
130
|
const checks = normalizeCheckNames(failedChecks);
|
|
131
131
|
// Defect fix (2b): `None.lower()` raised. A classifier handed no message should return
|
|
132
132
|
// UNKNOWN_FAILURE, not abort the recovery path that exists to handle failures.
|
|
133
133
|
const msg = typeof errorMessage === 'string' ? errorMessage.toLowerCase() : '';
|
|
134
134
|
|
|
135
|
-
const anyCheck = (markers
|
|
135
|
+
const anyCheck = (markers ) => checks.some((chk) => markers.some((m) => chk.includes(m)));
|
|
136
136
|
|
|
137
137
|
if (anyCheck(BUILD_CHECK_MARKERS) || msg.includes('build error')) {
|
|
138
138
|
return 'BUILD_FAILURE';
|
|
@@ -174,7 +174,7 @@ class RecoveryManager {
|
|
|
174
174
|
* @param {string} [lastAgent='codex']
|
|
175
175
|
* @returns {{ iteration: number, failureClass: string, action: string, targetRole: string|null, agent: string, canRetry: boolean, reason?: string }}
|
|
176
176
|
*/
|
|
177
|
-
planRecovery(failureClass
|
|
177
|
+
planRecovery(failureClass , currentIteration , lastAgent = 'codex') {
|
|
178
178
|
// A negative or non-numeric iteration is caller nonsense; clamping keeps it from reading as
|
|
179
179
|
// "before the first attempt" and skipping the agent switch that iteration 0 is supposed to get.
|
|
180
180
|
const iteration = Number.isFinite(currentIteration) ? Math.max(0, Math.trunc(currentIteration)) : 0;
|
|
@@ -248,7 +248,7 @@ class RecoveryManager {
|
|
|
248
248
|
* @param {boolean} [options.json=false]
|
|
249
249
|
* @returns {number} exit code
|
|
250
250
|
*/
|
|
251
|
-
function handleRecoverCommand({ errorMessage, failedChecks = [], iteration = 0, agent, json = false }
|
|
251
|
+
function handleRecoverCommand({ errorMessage, failedChecks = [], iteration = 0, agent, json = false } = {}) {
|
|
252
252
|
// Handed nothing, the classifier answers UNKNOWN_FAILURE — a confident-looking verdict about a
|
|
253
253
|
// failure it never saw. Refuse instead.
|
|
254
254
|
if ((typeof errorMessage !== 'string' || errorMessage.trim() === '') && failedChecks.length === 0) {
|
|
@@ -258,7 +258,7 @@ function handleRecoverCommand({ errorMessage, failedChecks = [], iteration = 0,
|
|
|
258
258
|
const failureClass = manager.classifyFailure(errorMessage, failedChecks);
|
|
259
259
|
const plan = manager.planRecovery(failureClass, iteration, agent || undefined);
|
|
260
260
|
|
|
261
|
-
if (json) console.log(JSON.stringify({ failureClass, failedChecks, ...(plan
|
|
261
|
+
if (json) console.log(JSON.stringify({ failureClass, failedChecks, ...(plan ) }, null, 2));
|
|
262
262
|
else {
|
|
263
263
|
console.log(`\nDoFlow Recovery [${plan.failureClass}]:`);
|
|
264
264
|
console.log('═'.repeat(78));
|
|
@@ -5,24 +5,24 @@
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const crypto = require('node:crypto');
|
|
8
|
-
const { acquireLock, updateTaskState, readTaskState } = require('./task-state.
|
|
9
|
-
const { EvidenceLedger, assertSafeTaskId } = require('./evidence-ledger.
|
|
10
|
-
const { ClaimsManager } = require('./claims.
|
|
11
|
-
const { taskStoreDir } = require('./task-scope.
|
|
12
|
-
const { FreshnessValidator } = require('./freshness.
|
|
13
|
-
const { resolveLocator } = require('./locator-resolve.
|
|
14
|
-
const { finishRuntime, usageError } = require('./cli-result.
|
|
8
|
+
const { acquireLock, updateTaskState, readTaskState } = require('./task-state.js');
|
|
9
|
+
const { EvidenceLedger, assertSafeTaskId } = require('./evidence-ledger.js');
|
|
10
|
+
const { ClaimsManager } = require('./claims.js');
|
|
11
|
+
const { taskStoreDir } = require('./task-scope.js');
|
|
12
|
+
const { FreshnessValidator } = require('./freshness.js');
|
|
13
|
+
const { resolveLocator } = require('./locator-resolve.js');
|
|
14
|
+
const { finishRuntime, usageError } = require('./cli-result.js');
|
|
15
15
|
|
|
16
16
|
const STATES = new Set(['OPEN', 'ANSWERED', 'UNRESOLVED']);
|
|
17
17
|
const REASONS = new Set(['detected-gap', 'user-request']);
|
|
18
|
-
const safeId = (value
|
|
18
|
+
const safeId = (value , label ) => {
|
|
19
19
|
if (typeof value !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value)) {
|
|
20
20
|
throw new Error(`${label} must be a safe non-empty identifier`);
|
|
21
21
|
}
|
|
22
22
|
return value;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
function hasLocatedSource(item
|
|
25
|
+
function hasLocatedSource(item , projectRoot ) {
|
|
26
26
|
const locator = item.locator || {};
|
|
27
27
|
if (locator.uri !== undefined) {
|
|
28
28
|
if (typeof locator.uri !== 'string') return false;
|
|
@@ -36,11 +36,11 @@ function hasLocatedSource(item: any, projectRoot: any) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
class ResearchRequestStore {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
constructor({ projectRoot = process.cwd(), fsImpl = fs, slug = null }
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
constructor({ projectRoot = process.cwd(), fsImpl = fs, slug = null } = {}) {
|
|
44
44
|
this.projectRoot = projectRoot;
|
|
45
45
|
this.fsImpl = fsImpl;
|
|
46
46
|
this.slug = slug;
|
|
@@ -48,29 +48,29 @@ class ResearchRequestStore {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/** The feature's namespace directory when the task has one (task-scope.js), else the flat store. */
|
|
51
|
-
file(taskId
|
|
51
|
+
file(taskId ) {
|
|
52
52
|
const dir = taskStoreDir({ projectRoot: this.projectRoot, store: 'research', taskId: assertSafeTaskId(taskId), slug: this.slug });
|
|
53
53
|
return path.join(dir, `${taskId}.json`);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
runFile(taskId
|
|
56
|
+
runFile(taskId ) {
|
|
57
57
|
return path.join(this.projectRoot, '.doflow', 'state', 'orchestration', `${assertSafeTaskId(taskId)}.json`);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
read(taskId
|
|
60
|
+
read(taskId ) {
|
|
61
61
|
const data = readTaskState(this.fsImpl, this.file(taskId));
|
|
62
62
|
if (!data) return { taskId, revision: 0, requests: [] };
|
|
63
63
|
if (data.taskId !== taskId || !Array.isArray(data.requests)
|
|
64
|
-
|| data.requests.some((r
|
|
64
|
+
|| data.requests.some((r ) => !STATES.has(r.status))) {
|
|
65
65
|
throw new Error(`Invalid research state for task '${taskId}'`);
|
|
66
66
|
}
|
|
67
67
|
return data;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// Re-read inside updateTaskState's lock: overlapping writers cannot lose a request.
|
|
71
|
-
change(taskId
|
|
71
|
+
change(taskId , update ) {
|
|
72
72
|
let result;
|
|
73
|
-
updateTaskState({ fsImpl: this.fsImpl, file: this.file(taskId), build: (disk
|
|
73
|
+
updateTaskState({ fsImpl: this.fsImpl, file: this.file(taskId), build: (disk ) => {
|
|
74
74
|
if (disk && (disk.taskId !== taskId || !Array.isArray(disk.requests))) {
|
|
75
75
|
throw new Error(`Invalid research state for task '${taskId}'`);
|
|
76
76
|
}
|
|
@@ -81,7 +81,7 @@ class ResearchRequestStore {
|
|
|
81
81
|
return result;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
stageFor(taskId
|
|
84
|
+
stageFor(taskId , stageId ) {
|
|
85
85
|
const run = readTaskState(this.fsImpl, this.runFile(taskId));
|
|
86
86
|
if (!run || run.taskClass !== 'feature' || run.state !== 'RUNNING') {
|
|
87
87
|
throw new Error(`Task '${taskId}' has no active feature stage`);
|
|
@@ -92,7 +92,7 @@ class ResearchRequestStore {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
open({ taskId, stageId, question, reason, blocking }
|
|
95
|
+
open({ taskId, stageId, question, reason, blocking } = {}) {
|
|
96
96
|
safeId(taskId, 'taskId'); safeId(stageId, 'stageId');
|
|
97
97
|
if (typeof question !== 'string' || !question.trim()) throw new Error('question is required');
|
|
98
98
|
if (!REASONS.has(reason)) throw new Error(`reason must be one of: ${[...REASONS].join(', ')}`);
|
|
@@ -104,8 +104,8 @@ class ResearchRequestStore {
|
|
|
104
104
|
const release = acquireLock(this.fsImpl, runFile);
|
|
105
105
|
try {
|
|
106
106
|
this.stageFor(taskId, stageId);
|
|
107
|
-
return this.change(taskId, (requests
|
|
108
|
-
const existing = requests.find((r
|
|
107
|
+
return this.change(taskId, (requests ) => {
|
|
108
|
+
const existing = requests.find((r ) => r.stageId === stageId && r.question === question.trim() && r.status === 'OPEN');
|
|
109
109
|
if (existing) {
|
|
110
110
|
if ((blocking ?? reason === 'detected-gap') && !existing.blocking) {
|
|
111
111
|
existing.blocking = true;
|
|
@@ -124,13 +124,13 @@ class ResearchRequestStore {
|
|
|
124
124
|
} finally { release(); }
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
list(taskId
|
|
127
|
+
list(taskId ) {
|
|
128
128
|
safeId(taskId, 'taskId');
|
|
129
129
|
return this.read(taskId).requests;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
// Validate references before taking the write lock; only the status transition itself mutates state.
|
|
133
|
-
resolve({ taskId, requestId, outcome, claimId, evidenceIds = [], gap }
|
|
133
|
+
resolve({ taskId, requestId, outcome, claimId, evidenceIds = [], gap } = {}) {
|
|
134
134
|
safeId(taskId, 'taskId'); safeId(requestId, 'requestId');
|
|
135
135
|
if (!['answered', 'unresolved'].includes(outcome)) throw new Error('outcome must be answered or unresolved');
|
|
136
136
|
if (!Array.isArray(evidenceIds)) throw new Error('evidenceIds must be an array');
|
|
@@ -159,8 +159,8 @@ class ResearchRequestStore {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
-
return this.change(taskId, (requests
|
|
163
|
-
const request = requests.find((r
|
|
162
|
+
return this.change(taskId, (requests ) => {
|
|
163
|
+
const request = requests.find((r ) => r.id === requestId);
|
|
164
164
|
if (!request) throw new Error(`Unknown research request '${requestId}' for task '${taskId}'`);
|
|
165
165
|
if (request.status !== 'OPEN') throw new Error(`Request '${requestId}' is ${request.status}, not OPEN`);
|
|
166
166
|
Object.assign(request, { status: outcome === 'answered' ? 'ANSWERED' : 'UNRESOLVED',
|
|
@@ -170,20 +170,20 @@ class ResearchRequestStore {
|
|
|
170
170
|
});
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
blockingGap(taskId
|
|
174
|
-
const requests = this.list(taskId).filter((r
|
|
173
|
+
blockingGap(taskId , stageId ) {
|
|
174
|
+
const requests = this.list(taskId).filter((r ) => r.stageId === stageId && r.blocking);
|
|
175
175
|
for (const request of requests) {
|
|
176
176
|
if (request.status !== 'ANSWERED') return request;
|
|
177
177
|
try {
|
|
178
178
|
this.validateAnswer(taskId, request);
|
|
179
|
-
} catch (error
|
|
179
|
+
} catch (error ) {
|
|
180
180
|
return { ...request, failureReason: error.message };
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
return null;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
validateAnswer(taskId
|
|
186
|
+
validateAnswer(taskId , request ) {
|
|
187
187
|
const ledger = new EvidenceLedger({ repoRoot: this.projectRoot, slug: this.slug });
|
|
188
188
|
ledger.load(taskId);
|
|
189
189
|
new FreshnessValidator({ repoRoot: this.projectRoot }).validateLedgerFreshness(ledger);
|
|
@@ -210,7 +210,7 @@ class ResearchRequestStore {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
function handleResearchRequestCommand({ action = 'list', taskId, stageId, question, reason,
|
|
213
|
-
blocking, requestId, outcome, claimId, evidenceIds, gap, json = false, projectRoot, slug }
|
|
213
|
+
blocking, requestId, outcome, claimId, evidenceIds, gap, json = false, projectRoot, slug } = {}) {
|
|
214
214
|
try {
|
|
215
215
|
const store = new ResearchRequestStore({ projectRoot, slug });
|
|
216
216
|
let result;
|
|
@@ -223,7 +223,7 @@ function handleResearchRequestCommand({ action = 'list', taskId, stageId, questi
|
|
|
223
223
|
if (json) console.log(JSON.stringify(result, null, 2));
|
|
224
224
|
else console.log(JSON.stringify(result));
|
|
225
225
|
return finishRuntime(0);
|
|
226
|
-
} catch (error
|
|
226
|
+
} catch (error ) {
|
|
227
227
|
return usageError('research-request', error.message, json, error);
|
|
228
228
|
}
|
|
229
229
|
}
|