@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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const crypto = require('node:crypto');
|
|
8
|
+
const { taskStoreDir, resolveTaskScope } = require('./task-scope.js');
|
|
9
|
+
const { assertSafeTaskId } = require('./evidence-ledger.js');
|
|
10
|
+
const { readTaskState, updateTaskState } = require('./task-state.js');
|
|
11
|
+
const { storeFile, activeAttempt } = require('./task-attempts.js');
|
|
12
|
+
const { projectRoot: lifecycleProjectRoot } = require('./lifecycle/root.js');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Path of a task's retrieval-plan store.
|
|
16
|
+
*
|
|
17
|
+
* The task id is validated by the evidence ledger's own rule, unchanged: it becomes a filename, so
|
|
18
|
+
* it must never be able to name a path.
|
|
19
|
+
* @param {string} projectRoot
|
|
20
|
+
* @param {string} taskId
|
|
21
|
+
* @param {string|null} [slug]
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function planPath(projectRoot , taskId , slug = null) {
|
|
25
|
+
const dir = taskStoreDir({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug });
|
|
26
|
+
return path.join(dir, `${taskId}.json`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reads and validates one task's retrieval plan via readTaskState.
|
|
31
|
+
* Returns null when the plan file does not exist.
|
|
32
|
+
* @param {string} projectRoot
|
|
33
|
+
* @param {string} taskId
|
|
34
|
+
* @param {string|null} [slug]
|
|
35
|
+
* @param {any} [fsImpl]
|
|
36
|
+
* @returns {any|null}
|
|
37
|
+
*/
|
|
38
|
+
function readPlan(projectRoot , taskId , slug = null, fsImpl = fs) {
|
|
39
|
+
const file = planPath(projectRoot, taskId, slug);
|
|
40
|
+
return readTaskState(fsImpl, file);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Updates a task's retrieval plan under the task-state lock.
|
|
45
|
+
* @param {string} projectRoot
|
|
46
|
+
* @param {string} taskId
|
|
47
|
+
* @param {string|null} slug
|
|
48
|
+
* @param {(disk: any|null) => any} build
|
|
49
|
+
* @param {any} [fsImpl]
|
|
50
|
+
* @returns {string} the written file path
|
|
51
|
+
*/
|
|
52
|
+
function updatePlan(projectRoot , taskId , slug , build , fsImpl = fs, prepare ) {
|
|
53
|
+
const file = planPath(projectRoot, taskId, slug);
|
|
54
|
+
return updateTaskState({ fsImpl, file, build, prepare });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Move a syntactically unreadable plan aside before its replacement is written under the lock. */
|
|
58
|
+
function moveCorruptPlanAside(projectRoot , taskId , slug = null, fsImpl = fs) {
|
|
59
|
+
const file = planPath(projectRoot, taskId, slug);
|
|
60
|
+
if (!fsImpl.existsSync(file)) return null;
|
|
61
|
+
try {
|
|
62
|
+
JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
63
|
+
return null;
|
|
64
|
+
} catch (error ) {
|
|
65
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
66
|
+
const corruptFile = `${file}.corrupt-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
67
|
+
fsImpl.renameSync(file, corruptFile);
|
|
68
|
+
return corruptFile;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Mints a new plan id: rp_<base36 ms>_<4 hex>.
|
|
74
|
+
* @returns {string}
|
|
75
|
+
*/
|
|
76
|
+
function mintPlanId() {
|
|
77
|
+
return `rp_${Date.now().toString(36)}_${crypto.randomBytes(2).toString('hex')}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Formats declared intents from a plan: sorted, comma-joined, or 'none declared'.
|
|
82
|
+
*
|
|
83
|
+
* @param {any} plan
|
|
84
|
+
* @returns {string}
|
|
85
|
+
*/
|
|
86
|
+
function declaredIntentList(plan , sep = ', ') {
|
|
87
|
+
const declaredIntents = Array.isArray(plan?.needs)
|
|
88
|
+
? plan.needs.map((n ) => n.intent).filter(Boolean).sort()
|
|
89
|
+
: [];
|
|
90
|
+
return declaredIntents.length > 0 ? declaredIntents.join(sep) : 'none declared';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resolves a declared need from a plan for a given intent.
|
|
95
|
+
*
|
|
96
|
+
* @param {any} plan
|
|
97
|
+
* @param {string} intent
|
|
98
|
+
* @param {string} taskId
|
|
99
|
+
* @returns {{ ok: true, queryRef: string, need: any } | { ok: false, message: string }}
|
|
100
|
+
*/
|
|
101
|
+
function resolveNeed(plan , intent , taskId ) {
|
|
102
|
+
if (!plan) {
|
|
103
|
+
const message = `no retrieval plan is declared for task '${taskId}', so --need '${intent}' names nothing. Declare one first: doflow retrieval-plan --task-id ${taskId} --action declare --need ${intent}, then re-run.`;
|
|
104
|
+
return { ok: false, message };
|
|
105
|
+
}
|
|
106
|
+
const list = declaredIntentList(plan);
|
|
107
|
+
|
|
108
|
+
if (!plan.planId) {
|
|
109
|
+
const message = `the retrieval plan for task '${taskId}' was declared before query binding and has no plan id. Re-declare it: doflow retrieval-plan --task-id ${taskId} --action declare --need ${declaredIntentList(plan, ',')}, then re-run.`;
|
|
110
|
+
return { ok: false, message };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const need = Array.isArray(plan.needs) ? plan.needs.find((n ) => n.intent === intent) : null;
|
|
114
|
+
if (!need) {
|
|
115
|
+
const message = `'${intent}' is not a declared need of task '${taskId}' (plan ${plan.planId}). Declared: ${list}. Re-run with one of them, or re-declare the plan with this need.`;
|
|
116
|
+
return { ok: false, message };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!need.queryRef) {
|
|
120
|
+
const message = `the retrieval plan for task '${taskId}' was declared before query binding and has no plan id. Re-declare it: doflow retrieval-plan --task-id ${taskId} --action declare --need ${declaredIntentList(plan, ',')}, then re-run.`;
|
|
121
|
+
return { ok: false, message };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
queryRef: need.queryRef,
|
|
127
|
+
need,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Reads the 070 active attempt ref for a task, returning `${taskId}#${token}` or null.
|
|
133
|
+
*
|
|
134
|
+
* Resolves the feature slug via resolveTaskScope, reads storeFile(lifecycleProjectRoot(projectRoot), featureSlug),
|
|
135
|
+
* and finds the active attempt for the task. If no slug, no store, no entry or no active attempt exists,
|
|
136
|
+
* or if any failure occurs finding or reading the store, returns null (IC-005: "no attempt" never fails the write).
|
|
137
|
+
*
|
|
138
|
+
* @param {string} projectRoot
|
|
139
|
+
* @param {string} taskId
|
|
140
|
+
* @param {string|null} [slug]
|
|
141
|
+
* @param {any} [fsImpl]
|
|
142
|
+
* @returns {string|null}
|
|
143
|
+
*/
|
|
144
|
+
function activeAttemptRef(projectRoot , taskId , slug = null, fsImpl = fs, attemptRoot = null) {
|
|
145
|
+
try {
|
|
146
|
+
const featureSlug = resolveTaskScope({ projectRoot, taskId, slug }).slug;
|
|
147
|
+
if (!featureSlug) return null;
|
|
148
|
+
|
|
149
|
+
const file = storeFile(attemptRoot || lifecycleProjectRoot(projectRoot), featureSlug);
|
|
150
|
+
if (!fsImpl.existsSync(file)) return null;
|
|
151
|
+
|
|
152
|
+
const data = readTaskState(fsImpl, file);
|
|
153
|
+
if (!data || !data.tasks || !data.tasks[taskId]) return null;
|
|
154
|
+
|
|
155
|
+
const active = activeAttempt(data.tasks[taskId]);
|
|
156
|
+
if (!active || active.token === undefined || active.token === null) return null;
|
|
157
|
+
|
|
158
|
+
return `${taskId}#${active.token}`;
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Binds active evidence to declared query refs and explains evidence that could have matched. */
|
|
165
|
+
function bindEvidence(plan , items ) {
|
|
166
|
+
const byNeed = new Map ();
|
|
167
|
+
const unbound = [];
|
|
168
|
+
const needs = Array.isArray(plan?.needs) ? plan.needs : [];
|
|
169
|
+
for (const need of needs) byNeed.set(need.intent, []);
|
|
170
|
+
for (const item of items) {
|
|
171
|
+
if (item.status !== 'active') continue;
|
|
172
|
+
const source = item.source || {};
|
|
173
|
+
const exact = needs.find((need ) => need.queryRef && need.queryRef === source.queryRef);
|
|
174
|
+
if (exact) {
|
|
175
|
+
if (plan.attemptRef && source.attemptRef && plan.attemptRef !== source.attemptRef) {
|
|
176
|
+
unbound.push({ id: item.id, cause: 'other-attempt' });
|
|
177
|
+
} else {
|
|
178
|
+
const matches = byNeed.get(exact.intent);
|
|
179
|
+
if (matches) matches.push(item);
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (source.queryRef) {
|
|
184
|
+
unbound.push({ id: item.id, cause: 'replaced-plan' });
|
|
185
|
+
} else if (needs.some((need ) => need.capability && need.capability === source.capability)) {
|
|
186
|
+
unbound.push({ id: item.id, cause: 'no-query-ref' });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { byNeed, unbound };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Selects the highest-precedence recorded attempt, with the earliest attempt winning ties. */
|
|
193
|
+
function deriveOutcome(need ) {
|
|
194
|
+
const precedence = { RETRIEVED: 0, EMPTY: 1, UNVERIFIED: 3, UNREACHED: 4 };
|
|
195
|
+
const attempts = (Array.isArray(need?.attempts) ? need.attempts : []).filter((attempt ) =>
|
|
196
|
+
!(attempt.recordedBy === 'derived' && attempt.basis !== 'report-need'
|
|
197
|
+
&& (!Array.isArray(attempt.evidenceIds) || attempt.evidenceIds.length === 0)));
|
|
198
|
+
const ranked = [...attempts].sort((a, b) => {
|
|
199
|
+
const rank = (attempt ) => attempt.status === 'EMPTY' && ['STALE', 'UNKNOWN'].includes(attempt.indexState)
|
|
200
|
+
? (attempt.indexState === 'STALE' ? 2 : 3)
|
|
201
|
+
: (precedence[attempt.status] ?? 4);
|
|
202
|
+
return rank(a) - rank(b) || a.n - b.n;
|
|
203
|
+
});
|
|
204
|
+
const winner = ranked[0];
|
|
205
|
+
return {
|
|
206
|
+
result: winner ? winner.status : 'UNREACHED',
|
|
207
|
+
resolvedBy: winner ? winner.n : null,
|
|
208
|
+
viaFallback: Boolean(winner && winner.role === 'fallback'),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = {
|
|
213
|
+
planPath,
|
|
214
|
+
readPlan,
|
|
215
|
+
updatePlan,
|
|
216
|
+
moveCorruptPlanAside,
|
|
217
|
+
mintPlanId,
|
|
218
|
+
resolveNeed,
|
|
219
|
+
activeAttemptRef,
|
|
220
|
+
declaredIntentList,
|
|
221
|
+
bindEvidence,
|
|
222
|
+
deriveOutcome,
|
|
223
|
+
};
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/** Returns the body of the first `## …<title>…` section, or null when there is none. */
|
|
16
|
-
function section(text
|
|
16
|
+
function section(text , titlePattern ) {
|
|
17
17
|
const lines = text.split('\n');
|
|
18
18
|
let start = -1;
|
|
19
19
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -28,7 +28,7 @@ function section(text: string, titlePattern: RegExp): string | null {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/** Splits a markdown table row into trimmed cells, or null when the line is not a row. */
|
|
31
|
-
function row(line
|
|
31
|
+
function row(line ) {
|
|
32
32
|
if (!/^\s*\|/.test(line)) return null;
|
|
33
33
|
const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
|
|
34
34
|
if (cells.every((c) => /^:?-{3,}:?$/.test(c))) return null; // the header separator
|
|
@@ -36,8 +36,8 @@ function row(line: string): string[] | null {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** Markdown wraps long list items; a criterion or task split over three lines is still one item. */
|
|
39
|
-
function joinWrapped(body
|
|
40
|
-
const out
|
|
39
|
+
function joinWrapped(body ) {
|
|
40
|
+
const out = [];
|
|
41
41
|
for (const line of body.split('\n')) {
|
|
42
42
|
if (/^\s+\S/.test(line) && out.length && out[out.length - 1].trim() !== '') {
|
|
43
43
|
out[out.length - 1] += ` ${line.trim()}`;
|
|
@@ -52,9 +52,9 @@ function joinWrapped(body: string): string[] {
|
|
|
52
52
|
* `requirement.md` → requirement summaries (for traceability headers) and acceptance criteria
|
|
53
53
|
* (for test stubs).
|
|
54
54
|
*/
|
|
55
|
-
function parseRequirement(text
|
|
56
|
-
const gaps
|
|
57
|
-
const requirements = new Map
|
|
55
|
+
function parseRequirement(text ) {
|
|
56
|
+
const gaps = [];
|
|
57
|
+
const requirements = new Map ();
|
|
58
58
|
|
|
59
59
|
const frBody = section(text, /Functional Requirements/i) ?? '';
|
|
60
60
|
const nfrBody = section(text, /Non-Functional Requirements/i) ?? '';
|
|
@@ -72,7 +72,7 @@ function parseRequirement(text: string): any {
|
|
|
72
72
|
gaps.push({ artifact: 'requirement.md', what: 'requirement index', why: 'no `| FR-nnn | … |` table row found under a Functional Requirements heading' });
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const criteria
|
|
75
|
+
const criteria = [];
|
|
76
76
|
const acceptanceBody = section(text, /Acceptance Criteria/i);
|
|
77
77
|
if (acceptanceBody === null) {
|
|
78
78
|
gaps.push({ artifact: 'requirement.md', what: 'acceptance criteria', why: 'no "Acceptance Criteria" section — no test stubs can be derived' });
|
|
@@ -96,9 +96,9 @@ function parseRequirement(text: string): any {
|
|
|
96
96
|
* `design.md` → the component index (traceability) and whatever §4 declares as an interface
|
|
97
97
|
* (carried through verbatim rather than paraphrased).
|
|
98
98
|
*/
|
|
99
|
-
function parseDesign(text
|
|
100
|
-
const gaps
|
|
101
|
-
const components = new Map
|
|
99
|
+
function parseDesign(text ) {
|
|
100
|
+
const gaps = [];
|
|
101
|
+
const components = new Map ();
|
|
102
102
|
|
|
103
103
|
const body = section(text, /Components\s*(&|and)\s*Boundaries/i) ?? '';
|
|
104
104
|
for (const line of body.split('\n')) {
|
|
@@ -121,9 +121,9 @@ function parseDesign(text: string): any {
|
|
|
121
121
|
gaps.push({ artifact: 'design.md', what: 'interface contracts', why: 'no "API / Interface Contracts" section — every emitted file falls back to a placeholder signature' });
|
|
122
122
|
} else {
|
|
123
123
|
const lines = contractBody.split('\n');
|
|
124
|
-
let heading
|
|
125
|
-
let fence
|
|
126
|
-
let buffer
|
|
124
|
+
let heading = null;
|
|
125
|
+
let fence = null;
|
|
126
|
+
let buffer = [];
|
|
127
127
|
for (const line of lines) {
|
|
128
128
|
const headingMatch = line.match(/^###\s+(.*)$/);
|
|
129
129
|
if (headingMatch && fence === null) { heading = headingMatch[1].trim(); continue; }
|
|
@@ -155,9 +155,9 @@ function parseDesign(text: string): any {
|
|
|
155
155
|
const TASK_KEYS = ['owner', 'files', 'depends-on', 'external-contract'];
|
|
156
156
|
|
|
157
157
|
/** `plan.md` → the task list, which is where the intended file layout actually comes from. */
|
|
158
|
-
function parsePlan(text
|
|
159
|
-
const gaps
|
|
160
|
-
const tasks
|
|
158
|
+
function parsePlan(text ) {
|
|
159
|
+
const gaps = [];
|
|
160
|
+
const tasks = [];
|
|
161
161
|
|
|
162
162
|
// Anchored on the whole heading: `### Task Summary` is orientation, not the task list, and a
|
|
163
163
|
// loose match would read the summary table's rows as tasks.
|
|
@@ -182,14 +182,14 @@ function parsePlan(text: string): any {
|
|
|
182
182
|
const stories = [...new Set(head.match(/US\d+/g) || [])].sort();
|
|
183
183
|
const description = head.replace(/\[P\]/g, '').replace(/\[US\d+\]/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
184
184
|
|
|
185
|
-
const fields
|
|
185
|
+
const fields = {};
|
|
186
186
|
for (const chunk of meta.split(';')) {
|
|
187
187
|
const fieldMatch = chunk.match(/^\s*([a-z-]+):\s*(.*)$/);
|
|
188
188
|
if (!fieldMatch || !TASK_KEYS.includes(fieldMatch[1])) continue;
|
|
189
189
|
fields[fieldMatch[1]] = fieldMatch[2].trim();
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
const list = (value
|
|
192
|
+
const list = (value ) => (value ? value.split(',').map((v) => v.replace(/`/g, '').trim()).filter(Boolean) : []);
|
|
193
193
|
|
|
194
194
|
tasks.push({
|
|
195
195
|
id,
|
|
@@ -211,6 +211,9 @@ function parsePlan(text: string): any {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
module.exports = {
|
|
214
|
+
section,
|
|
215
|
+
row,
|
|
216
|
+
joinWrapped,
|
|
214
217
|
parseRequirement,
|
|
215
218
|
parseDesign,
|
|
216
219
|
parsePlan,
|
|
@@ -17,7 +17,7 @@ const crypto = require('node:crypto');
|
|
|
17
17
|
const FINGERPRINT_MARKER = 'doflow-scaffold-fingerprint';
|
|
18
18
|
const FINGERPRINT_RE = new RegExp(`${FINGERPRINT_MARKER} sha256:([0-9a-f]{64})`);
|
|
19
19
|
|
|
20
|
-
function sha256(text
|
|
20
|
+
function sha256(text ) {
|
|
21
21
|
return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -28,7 +28,7 @@ function sha256(text: string): string {
|
|
|
28
28
|
* reading as human-edited; and it lives in the file rather than in a side ledger, so a scaffold
|
|
29
29
|
* copied, moved or committed on its own still carries the evidence needed to protect it.
|
|
30
30
|
*/
|
|
31
|
-
function compose(emitter
|
|
31
|
+
function compose(emitter , headerLines , body ) {
|
|
32
32
|
const fingerprint = sha256(body);
|
|
33
33
|
const header = [
|
|
34
34
|
...headerLines.map((l) => emitter.line(l)),
|
|
@@ -38,7 +38,7 @@ function compose(emitter: any, headerLines: string[], body: string): string {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/** The hash a file claims for its own body, and the body it actually has. */
|
|
41
|
-
function inspectExisting(text
|
|
41
|
+
function inspectExisting(text ) {
|
|
42
42
|
const match = text.match(FINGERPRINT_RE);
|
|
43
43
|
if (!match) return { claimed: null, actual: null, body: null };
|
|
44
44
|
const cut = text.indexOf('\n', text.indexOf(match[0]));
|