@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
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('node:fs');
|
|
13
13
|
const path = require('node:path');
|
|
14
|
-
const { loadRegistry } = require('../registry/index.
|
|
15
|
-
const { finishRuntime, usageError, captureCaught } = require('./cli-result.
|
|
16
|
-
const { REPO_ROOT } = require('../helper/repo-root.
|
|
14
|
+
const { loadRegistry } = require('../registry/index.js');
|
|
15
|
+
const { finishRuntime, usageError, captureCaught } = require('./cli-result.js');
|
|
16
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
17
17
|
|
|
18
18
|
/** The machine-level command each provider's backend answers to. A provider without a local CLI
|
|
19
19
|
* (hosted-only access through another harness) reports backendCli null and is never "available". */
|
|
20
|
-
const BACKEND_CLI
|
|
20
|
+
const BACKEND_CLI = Object.freeze({ claude: 'claude', codex: 'codex', copilot: 'copilot', ollama: 'ollama' });
|
|
21
21
|
|
|
22
|
-
function whichLike(command
|
|
22
|
+
function whichLike(command , { fsImpl = fs, pathEnv = process.env.PATH } = {}) {
|
|
23
23
|
if (!command) return false;
|
|
24
24
|
for (const dir of String(pathEnv ?? '').split(path.delimiter)) {
|
|
25
25
|
if (!dir) continue;
|
|
@@ -33,7 +33,7 @@ function whichLike(command: string | undefined, { fsImpl = fs, pathEnv = process
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/** Providers whose backend CLI resolves on PATH. Injectable in tests; never guessed otherwise. */
|
|
36
|
-
function availableProviderIds({ fsImpl = fs }
|
|
36
|
+
function availableProviderIds({ fsImpl = fs } = {}) {
|
|
37
37
|
return Object.entries(BACKEND_CLI)
|
|
38
38
|
.filter(([, cli]) => whichLike(cli, { fsImpl }))
|
|
39
39
|
.map(([id]) => id);
|
|
@@ -46,13 +46,13 @@ function availableProviderIds({ fsImpl = fs }: { fsImpl?: any } = {}): string[]
|
|
|
46
46
|
* review with require:different-family) and is reported back rather than hidden.
|
|
47
47
|
* Availability unknown stays null — absence of evidence is not reported as absent.
|
|
48
48
|
*/
|
|
49
|
-
function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [] }
|
|
50
|
-
const role = (registry.modelRoles || []).find((entry
|
|
49
|
+
function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [] } = {}) {
|
|
50
|
+
const role = (registry.modelRoles || []).find((entry ) => entry.id === roleId);
|
|
51
51
|
if (!role) {
|
|
52
|
-
throw new Error(`Unknown model role '${roleId}'; valid: ${(registry.modelRoles || []).map((r
|
|
52
|
+
throw new Error(`Unknown model role '${roleId}'; valid: ${(registry.modelRoles || []).map((r ) => r.id).join(', ')}`);
|
|
53
53
|
}
|
|
54
54
|
const excluded = new Set(exclude);
|
|
55
|
-
const candidates
|
|
55
|
+
const candidates = [];
|
|
56
56
|
for (const [index, provider] of (registry.modelProviders || []).entries()) {
|
|
57
57
|
if (excluded.has(provider.id)) continue;
|
|
58
58
|
const tiers = provider.tiers ?? [];
|
|
@@ -67,13 +67,13 @@ function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [] }
|
|
|
67
67
|
_index: index,
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
|
-
const rank = (candidate
|
|
70
|
+
const rank = (candidate ) => [
|
|
71
71
|
candidate.available === true ? 0 : candidate.available === false ? 1 : 2,
|
|
72
72
|
candidate.servesPreferredTier ? 0 : 1,
|
|
73
73
|
candidate.servesFallbackTier ? 0 : 1,
|
|
74
74
|
candidate._index,
|
|
75
75
|
];
|
|
76
|
-
candidates.sort((a
|
|
76
|
+
candidates.sort((a , b ) => {
|
|
77
77
|
const ra = rank(a); const rb = rank(b);
|
|
78
78
|
for (let i = 0; i < ra.length; i += 1) if (ra[i] !== rb[i]) return ra[i] - rb[i];
|
|
79
79
|
return 0;
|
|
@@ -98,10 +98,10 @@ function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [] }
|
|
|
98
98
|
* lexical rather than guess. Unknown slot ids also resolve to `{active:false}` rather than
|
|
99
99
|
* throwing: absence of a slot is every install's normal state, unlike an unknown role.
|
|
100
100
|
*/
|
|
101
|
-
function resolveRetrievalSlot({ registry, slotId, isAvailable = null }
|
|
102
|
-
const slot = (registry.retrievalSlots || []).find((entry
|
|
101
|
+
function resolveRetrievalSlot({ registry, slotId, isAvailable = null } = {}) {
|
|
102
|
+
const slot = (registry.retrievalSlots || []).find((entry ) => entry.id === slotId);
|
|
103
103
|
if (!slot || !slot.enabled) return { slotId, active: false, reason: slot ? 'disabled' : 'undeclared' };
|
|
104
|
-
const provider = (registry.modelProviders || []).find((entry
|
|
104
|
+
const provider = (registry.modelProviders || []).find((entry ) => entry.id === slot.provider);
|
|
105
105
|
if (!provider) return { slotId, active: false, reason: 'provider-missing' };
|
|
106
106
|
return {
|
|
107
107
|
slotId,
|
|
@@ -118,11 +118,11 @@ function resolveRetrievalSlot({ registry, slotId, isAvailable = null }: any = {}
|
|
|
118
118
|
|
|
119
119
|
/** CLI handler for `doflow model-role`. Read-only advisory routing; exits 1 on an unknown role
|
|
120
120
|
* because a silently-empty candidate list would read as "no providers" rather than "bad input". */
|
|
121
|
-
function handleModelRoleCommand({ role, exclude, json = false, repoRoot }
|
|
121
|
+
function handleModelRoleCommand({ role, exclude, json = false, repoRoot } = {}) {
|
|
122
122
|
if (!role) return usageError('model-role', '--role is required (one of the roles in core/registry/models.json)', json);
|
|
123
123
|
const registry = (() => {
|
|
124
124
|
try { return loadRegistry({ repoRoot: repoRoot || REPO_ROOT }); }
|
|
125
|
-
catch (error
|
|
125
|
+
catch (error ) { captureCaught(error, 'model-role', 1); console.error(`[ERROR] model-role: ${error.message}`); return finishRuntime(1); }
|
|
126
126
|
})();
|
|
127
127
|
if (!registry) return undefined;
|
|
128
128
|
const available = new Set(availableProviderIds());
|
|
@@ -130,8 +130,8 @@ function handleModelRoleCommand({ role, exclude, json = false, repoRoot }: any =
|
|
|
130
130
|
const resolution = resolveModelRole({
|
|
131
131
|
registry,
|
|
132
132
|
roleId: role,
|
|
133
|
-
isAvailable: (providerId
|
|
134
|
-
exclude: Array.isArray(exclude) ? exclude : String(exclude ?? '').split(',').map((s
|
|
133
|
+
isAvailable: (providerId ) => available.has(providerId),
|
|
134
|
+
exclude: Array.isArray(exclude) ? exclude : String(exclude ?? '').split(',').map((s ) => s.trim()).filter(Boolean),
|
|
135
135
|
});
|
|
136
136
|
if (json) console.log(JSON.stringify(resolution, null, 2));
|
|
137
137
|
else {
|
|
@@ -143,7 +143,7 @@ function handleModelRoleCommand({ role, exclude, json = false, repoRoot }: any =
|
|
|
143
143
|
if (resolution.excluded.length) console.log(`Excluded: ${resolution.excluded.join(', ')}`);
|
|
144
144
|
}
|
|
145
145
|
return finishRuntime(0);
|
|
146
|
-
} catch (error
|
|
146
|
+
} catch (error ) {
|
|
147
147
|
captureCaught(error, 'model-role', 1);
|
|
148
148
|
console.error(`[ERROR] model-role: ${error.message}`);
|
|
149
149
|
return finishRuntime(1);
|
|
@@ -42,22 +42,22 @@
|
|
|
42
42
|
|
|
43
43
|
const fs = require('node:fs');
|
|
44
44
|
const path = require('node:path');
|
|
45
|
-
const { EvidenceLedger, assertSafeTaskId } = require('./evidence-ledger.
|
|
46
|
-
const { taskStoreDir } = require('./task-scope.
|
|
45
|
+
const { EvidenceLedger, assertSafeTaskId } = require('./evidence-ledger.js');
|
|
46
|
+
const { taskStoreDir } = require('./task-scope.js');
|
|
47
47
|
// The two verdict vocabularies, imported from the modules that own them rather than respelled
|
|
48
48
|
// here. A second copy would let this file accept a readiness state readiness itself has retired.
|
|
49
|
-
const { READINESS_STATES } = require('./readiness.
|
|
50
|
-
const { VERIFICATION_STATUSES } = require('./verification/engine.
|
|
51
|
-
const { WorkflowEngine } = require('./workflow-engine.
|
|
49
|
+
const { READINESS_STATES } = require('./readiness.js');
|
|
50
|
+
const { VERIFICATION_STATUSES } = require('./verification/engine.js');
|
|
51
|
+
const { WorkflowEngine } = require('./workflow-engine.js');
|
|
52
52
|
// Read, not re-derived: the retrieval plan's location is `retrieval-plan.ts`'s to define, and the
|
|
53
53
|
// unreached items this record carries forward are the ones that record already holds.
|
|
54
|
-
const { planPath, UNREACHED } = require('./retrieval-plan.
|
|
54
|
+
const { planPath, UNREACHED } = require('./retrieval-plan.js');
|
|
55
55
|
// One definition of the score refusal, applied at a further boundary. See the note on
|
|
56
56
|
// EVIDENCE_SCORE_FIELDS in cli.js: a terminal state that carried a confidence would be exactly the
|
|
57
57
|
// number NFR-001 exists to keep out of the one record the next worker reads first.
|
|
58
|
-
const { assertNoScoreFields } = require('./cli.
|
|
59
|
-
const { finishRuntime, usageError } = require('./cli-result.
|
|
60
|
-
const { REPO_ROOT } = require('../helper/repo-root.
|
|
58
|
+
const { assertNoScoreFields } = require('./cli.js');
|
|
59
|
+
const { finishRuntime, usageError } = require('./cli-result.js');
|
|
60
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
61
61
|
|
|
62
62
|
// ── the closed terminal vocabulary (design §4) ───────────────────────────────────────────────
|
|
63
63
|
|
|
@@ -96,7 +96,7 @@ const NOT_RECORDED = 'NOT_RECORDED';
|
|
|
96
96
|
* @param {string|null} [slug] the feature the task belongs to, when not the branch's (task-scope.js)
|
|
97
97
|
* @returns {string}
|
|
98
98
|
*/
|
|
99
|
-
function outcomePath(projectRoot
|
|
99
|
+
function outcomePath(projectRoot , taskId , slug = null) {
|
|
100
100
|
const dir = taskStoreDir({ projectRoot, store: 'outcome', taskId: assertSafeTaskId(taskId), slug });
|
|
101
101
|
return path.join(dir, `${taskId}.json`);
|
|
102
102
|
}
|
|
@@ -106,12 +106,12 @@ function outcomePath(projectRoot: any, taskId: any, slug: any = null) {
|
|
|
106
106
|
* @param {string} taskId
|
|
107
107
|
* @returns {Object|null} the record, or null when no outcome was recorded
|
|
108
108
|
*/
|
|
109
|
-
function readOutcome(projectRoot
|
|
109
|
+
function readOutcome(projectRoot , taskId , slug = null) {
|
|
110
110
|
const file = outcomePath(projectRoot, taskId, slug);
|
|
111
111
|
if (!fs.existsSync(file)) return null;
|
|
112
112
|
try {
|
|
113
113
|
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
114
|
-
} catch (error
|
|
114
|
+
} catch (error ) {
|
|
115
115
|
throw new Error(`the outcome record at '${file}' is not readable: ${error.message}`);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -122,7 +122,7 @@ function readOutcome(projectRoot: any, taskId: any, slug: any = null) {
|
|
|
122
122
|
* @param {Object} record
|
|
123
123
|
* @returns {string} the file written
|
|
124
124
|
*/
|
|
125
|
-
function writeOutcome(projectRoot
|
|
125
|
+
function writeOutcome(projectRoot , taskId , record , slug = null) {
|
|
126
126
|
const file = outcomePath(projectRoot, taskId, slug);
|
|
127
127
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
128
128
|
fs.writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
|
|
@@ -142,7 +142,7 @@ function writeOutcome(projectRoot: any, taskId: any, record: any, slug: any = nu
|
|
|
142
142
|
* @param {string} taskId
|
|
143
143
|
* @returns {number}
|
|
144
144
|
*/
|
|
145
|
-
function countEvidence(projectRoot
|
|
145
|
+
function countEvidence(projectRoot , taskId , slug = null) {
|
|
146
146
|
const ledger = new EvidenceLedger({ repoRoot: projectRoot, slug });
|
|
147
147
|
ledger.load(taskId);
|
|
148
148
|
return ledger.queryEvidence({ taskId }).length;
|
|
@@ -164,14 +164,14 @@ function countEvidence(projectRoot: any, taskId: any, slug: any = null) {
|
|
|
164
164
|
* @param {string} verification the stated verification verdict, or NOT_RECORDED
|
|
165
165
|
* @returns {Array<string>}
|
|
166
166
|
*/
|
|
167
|
-
function unreachedItems(projectRoot
|
|
167
|
+
function unreachedItems(projectRoot , taskId , verification , slug = null) {
|
|
168
168
|
const items = [];
|
|
169
169
|
const file = planPath(projectRoot, taskId, slug);
|
|
170
170
|
if (fs.existsSync(file)) {
|
|
171
171
|
let plan;
|
|
172
172
|
try {
|
|
173
173
|
plan = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
174
|
-
} catch (error
|
|
174
|
+
} catch (error ) {
|
|
175
175
|
throw new Error(`the retrieval plan at '${file}' is not readable: ${error.message}`);
|
|
176
176
|
}
|
|
177
177
|
for (const need of plan.needs || []) {
|
|
@@ -198,7 +198,7 @@ function unreachedItems(projectRoot: any, taskId: any, verification: any, slug:
|
|
|
198
198
|
* @param {string} flag the flag name, for the refusal
|
|
199
199
|
* @returns {{value: string, stated: boolean}}
|
|
200
200
|
*/
|
|
201
|
-
function statedVerdict(value
|
|
201
|
+
function statedVerdict(value , vocabulary , flag ) {
|
|
202
202
|
if (value === undefined || value === null || String(value).trim() === '') {
|
|
203
203
|
return { value: NOT_RECORDED, stated: false };
|
|
204
204
|
}
|
|
@@ -226,7 +226,7 @@ function statedVerdict(value: any, vocabulary: any, flag: any) {
|
|
|
226
226
|
*
|
|
227
227
|
* @returns {number} exit code
|
|
228
228
|
*/
|
|
229
|
-
function recordOutcome({ projectRoot, packageRoot, taskId, taskClass, stage, state, readiness, verification, json, slug }
|
|
229
|
+
function recordOutcome({ projectRoot, packageRoot, taskId, taskClass, stage, state, readiness, verification, json, slug } ) {
|
|
230
230
|
if (state === undefined || state === null || String(state).trim() === '') {
|
|
231
231
|
return usageError('outcome',
|
|
232
232
|
`--action record needs --state <state>. Valid: ${OUTCOME_STATES.join(', ')}`, json);
|
|
@@ -249,7 +249,7 @@ function recordOutcome({ projectRoot, packageRoot, taskId, taskClass, stage, sta
|
|
|
249
249
|
let workflow;
|
|
250
250
|
try {
|
|
251
251
|
workflow = new WorkflowEngine({ repoRoot: packageRoot }).resolveWorkflow(taskClass);
|
|
252
|
-
} catch (error
|
|
252
|
+
} catch (error ) {
|
|
253
253
|
return usageError('outcome', error.message, json, error);
|
|
254
254
|
}
|
|
255
255
|
const terminalStage = workflow.terminalStage;
|
|
@@ -266,7 +266,7 @@ function recordOutcome({ projectRoot, packageRoot, taskId, taskClass, stage, sta
|
|
|
266
266
|
try {
|
|
267
267
|
basisReadiness = statedVerdict(readiness, READINESS_STATES, '--readiness');
|
|
268
268
|
basisVerification = statedVerdict(verification, VERIFICATION_STATUSES, '--verification');
|
|
269
|
-
} catch (error
|
|
269
|
+
} catch (error ) {
|
|
270
270
|
return usageError('outcome', error.message, json, error);
|
|
271
271
|
}
|
|
272
272
|
|
|
@@ -275,7 +275,7 @@ function recordOutcome({ projectRoot, packageRoot, taskId, taskClass, stage, sta
|
|
|
275
275
|
try {
|
|
276
276
|
evidenceCount = countEvidence(projectRoot, taskId, slug);
|
|
277
277
|
unreached = unreachedItems(projectRoot, taskId, basisVerification.value, slug);
|
|
278
|
-
} catch (error
|
|
278
|
+
} catch (error ) {
|
|
279
279
|
return usageError('outcome', error.message, json, error);
|
|
280
280
|
}
|
|
281
281
|
|
|
@@ -331,11 +331,11 @@ function recordOutcome({ projectRoot, packageRoot, taskId, taskClass, stage, sta
|
|
|
331
331
|
*
|
|
332
332
|
* @returns {number} exit code
|
|
333
333
|
*/
|
|
334
|
-
function showOutcome({ projectRoot, taskId, json, slug }
|
|
334
|
+
function showOutcome({ projectRoot, taskId, json, slug } ) {
|
|
335
335
|
let record;
|
|
336
336
|
try {
|
|
337
337
|
record = readOutcome(projectRoot, taskId, slug);
|
|
338
|
-
} catch (error
|
|
338
|
+
} catch (error ) {
|
|
339
339
|
return usageError('outcome', error.message, json, error);
|
|
340
340
|
}
|
|
341
341
|
if (!record) {
|
|
@@ -365,7 +365,7 @@ function showOutcome({ projectRoot, taskId, json, slug }: any) {
|
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
/** The one text rendering both actions print, so a shown record reads exactly as the written one. */
|
|
368
|
-
function printOutcome(record
|
|
368
|
+
function printOutcome(record , file , verb ) {
|
|
369
369
|
const basis = record.basis || {};
|
|
370
370
|
console.log(`\nDoFlow Task Outcome [Task: ${record.taskId}] — ${record.state}:`);
|
|
371
371
|
console.log('═'.repeat(78));
|
|
@@ -413,7 +413,7 @@ function printOutcome(record: any, file: any, verb: any) {
|
|
|
413
413
|
* @param {string} [options.stateRoot] the project the task belongs to, holding its state
|
|
414
414
|
* @returns {number} exit code
|
|
415
415
|
*/
|
|
416
|
-
function handleOutcomeCommand(options
|
|
416
|
+
function handleOutcomeCommand(options = {}) {
|
|
417
417
|
const {
|
|
418
418
|
taskId, action = 'show', state, taskClass, stage, readiness, verification,
|
|
419
419
|
json = false, repoRoot, stateRoot, slug,
|
|
@@ -424,7 +424,7 @@ function handleOutcomeCommand(options: any = {}) {
|
|
|
424
424
|
// option object, so a second input surface cannot arrive later without inheriting the refusal.
|
|
425
425
|
try {
|
|
426
426
|
assertNoScoreFields(options, '');
|
|
427
|
-
} catch (error
|
|
427
|
+
} catch (error ) {
|
|
428
428
|
return usageError('outcome', error.message, json, error);
|
|
429
429
|
}
|
|
430
430
|
|
|
@@ -443,7 +443,7 @@ function handleOutcomeCommand(options: any = {}) {
|
|
|
443
443
|
|
|
444
444
|
try {
|
|
445
445
|
assertSafeTaskId(taskId);
|
|
446
|
-
} catch (error
|
|
446
|
+
} catch (error ) {
|
|
447
447
|
return usageError('outcome', error.message, json, error);
|
|
448
448
|
}
|
|
449
449
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
const fs = require('node:fs');
|
|
16
16
|
const os = require('node:os');
|
|
17
17
|
const path = require('node:path');
|
|
18
|
-
const { readLedger, stateRoot } = require('../state/index.
|
|
18
|
+
const { readLedger, stateRoot } = require('../state/index.js');
|
|
19
19
|
|
|
20
20
|
/** Where the dispatcher sits under a `.doflow` root; the skill resolver and the locator shim spell the same path. */
|
|
21
21
|
const REACH_DISPATCHER_REL = 'scripts/doflow/bin/doflow-run';
|
|
@@ -23,18 +23,18 @@ const REACH_DISPATCHER_REL = 'scripts/doflow/bin/doflow-run';
|
|
|
23
23
|
const REACH_RUNTIME_REL = 'runtime/bin/doflow.js';
|
|
24
24
|
|
|
25
25
|
/** `dir`, then each parent, ending when the parent is the directory itself (`/`, `C:\`). */
|
|
26
|
-
function* ancestors(dir
|
|
26
|
+
function* ancestors(dir ) {
|
|
27
27
|
for (let current = dir; ; current = path.dirname(current)) {
|
|
28
28
|
yield current;
|
|
29
29
|
if (path.dirname(current) === current) return;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function statIs(fsImpl
|
|
33
|
+
function statIs(fsImpl , target , kind ) {
|
|
34
34
|
try { return fsImpl.statSync(target)[kind](); } catch { return false; }
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function isExecutableFile(fsImpl
|
|
37
|
+
function isExecutableFile(fsImpl , target ) {
|
|
38
38
|
if (!statIs(fsImpl, target, 'isFile')) return false;
|
|
39
39
|
try { fsImpl.accessSync(target, fs.constants.X_OK); return true; } catch { return false; }
|
|
40
40
|
}
|
|
@@ -47,8 +47,8 @@ function isExecutableFile(fsImpl: any, target: string): boolean {
|
|
|
47
47
|
* @param {Object} [options.fsImpl]
|
|
48
48
|
* @returns {{rows: Array<{harness: string, scope: 'project'|'global', installRoot: string, state: 'REACHED'|'NO-REACH'|'N/A', root?: string, reason?: string, fix?: string}>, unreadable: Array<{scope: string, ledger: string, detail: string}>}}
|
|
49
49
|
*/
|
|
50
|
-
function evaluateReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs }
|
|
51
|
-
const real = (target
|
|
50
|
+
function evaluateReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs } ) {
|
|
51
|
+
const real = (target ) => { try { return fsImpl.realpathSync(target); } catch { return path.resolve(target); } };
|
|
52
52
|
const start = real(projectRoot);
|
|
53
53
|
const home = real(homeDir);
|
|
54
54
|
const homeDoflow = path.join(home, '.doflow');
|
|
@@ -57,14 +57,14 @@ function evaluateReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl =
|
|
|
57
57
|
// Every ledger on the walk is read, not only the nearest: a removed harness's residue in a
|
|
58
58
|
// subdirectory must not hide the install that encloses it. The walk stops at the home directory,
|
|
59
59
|
// whose own ledger is the global scope; nothing above it is read.
|
|
60
|
-
const scopes
|
|
60
|
+
const scopes = [];
|
|
61
61
|
if (start !== home) {
|
|
62
|
-
const walk
|
|
62
|
+
const walk = [];
|
|
63
63
|
for (const dir of ancestors(start)) {
|
|
64
64
|
if (dir === home) break;
|
|
65
65
|
walk.push(dir);
|
|
66
66
|
}
|
|
67
|
-
for (const installRoot of walk.filter((dir
|
|
67
|
+
for (const installRoot of walk.filter((dir ) => statIs(fsImpl, path.join(dir, '.doflow', 'state'), 'isDirectory'))) {
|
|
68
68
|
scopes.push({
|
|
69
69
|
scope: 'project',
|
|
70
70
|
installRoot,
|
|
@@ -72,7 +72,7 @@ function evaluateReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl =
|
|
|
72
72
|
from: start,
|
|
73
73
|
searched: `${path.join(start, '.doflow', REACH_DISPATCHER_REL)} and each directory above it, then ${homeDispatcher}`,
|
|
74
74
|
doflowDirs: [...ancestors(start)].map((dir) => path.join(dir, '.doflow')).concat(homeDoflow),
|
|
75
|
-
fix: (harness
|
|
75
|
+
fix: (harness ) => `npx @khoavu882/doflow install ${installRoot} -t ${harness}`,
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -83,14 +83,14 @@ function evaluateReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl =
|
|
|
83
83
|
from: home,
|
|
84
84
|
searched: homeDispatcher,
|
|
85
85
|
doflowDirs: [homeDoflow],
|
|
86
|
-
fix: (harness
|
|
86
|
+
fix: (harness ) => `npx @khoavu882/doflow install -g -t ${harness}`,
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
const harnessIds = registry.harnesses.map((harness
|
|
90
|
-
const rows
|
|
91
|
-
const unreadable
|
|
92
|
-
const seen = new Set
|
|
93
|
-
const addRow = (row
|
|
89
|
+
const harnessIds = registry.harnesses.map((harness ) => harness.id);
|
|
90
|
+
const rows = [];
|
|
91
|
+
const unreadable = [];
|
|
92
|
+
const seen = new Set ();
|
|
93
|
+
const addRow = (row ) => {
|
|
94
94
|
// A stale outer ledger that says what a nearer one already said adds nothing.
|
|
95
95
|
const key = JSON.stringify([row.harness, row.scope, row.state, row.root, row.reason]);
|
|
96
96
|
if (seen.has(key)) return;
|
|
@@ -99,20 +99,20 @@ function evaluateReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl =
|
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
for (const { scope, installRoot, stateDir, from, searched, doflowDirs, fix } of scopes) {
|
|
102
|
-
let ledger
|
|
102
|
+
let ledger ;
|
|
103
103
|
try {
|
|
104
104
|
ledger = readLedger(stateDir, { fsImpl });
|
|
105
|
-
} catch (error
|
|
105
|
+
} catch (error ) {
|
|
106
106
|
unreadable.push({ scope, ledger: stateDir, detail: error.message });
|
|
107
107
|
continue;
|
|
108
108
|
}
|
|
109
109
|
if (!ledger) continue;
|
|
110
110
|
|
|
111
111
|
const resources = ledger.resources || [];
|
|
112
|
-
const installed = harnessIds.filter((id
|
|
112
|
+
const installed = harnessIds.filter((id ) => resources.some((resource ) => resource.harness === id));
|
|
113
113
|
if (!installed.length) continue;
|
|
114
114
|
|
|
115
|
-
const root = doflowDirs.find((dir
|
|
115
|
+
const root = doflowDirs.find((dir ) => isExecutableFile(fsImpl, path.join(dir, REACH_DISPATCHER_REL)));
|
|
116
116
|
const dispatcher = root && path.join(root, REACH_DISPATCHER_REL);
|
|
117
117
|
const configDir = [...ancestors(from)].map((dir) => path.join(dir, '.doflow')).find((dir) => statIs(fsImpl, dir, 'isDirectory'));
|
|
118
118
|
const runtime = [configDir, homeDoflow].filter(Boolean).some((dir) => statIs(fsImpl, path.join(dir, REACH_RUNTIME_REL), 'isFile'));
|
|
@@ -121,7 +121,7 @@ function evaluateReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl =
|
|
|
121
121
|
const hiddenBy = scope === 'project' && configDir && configDir !== path.join(installRoot, '.doflow') ? configDir : null;
|
|
122
122
|
|
|
123
123
|
for (const harness of installed) {
|
|
124
|
-
if (!resources.some((resource
|
|
124
|
+
if (!resources.some((resource ) => resource.harness === harness && resource.assetId === 'skills.doflow')) {
|
|
125
125
|
addRow({ harness, scope, installRoot, state: 'N/A', reason: `no skills at ${scope} scope` });
|
|
126
126
|
} else if (dispatcher && runtime) {
|
|
127
127
|
addRow({ harness, scope, installRoot, state: 'REACHED', root });
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
-
const { updateTaskState } = require('./task-state.
|
|
8
|
-
const { resolveTaskScope } = require('./task-scope.
|
|
9
|
-
const { findStateFile } = require('./checkouts.
|
|
7
|
+
const { updateTaskState } = require('./task-state.js');
|
|
8
|
+
const { resolveTaskScope } = require('./task-scope.js');
|
|
9
|
+
const { findStateFile } = require('./checkouts.js');
|
|
10
10
|
|
|
11
11
|
// The readiness record: the latest evaluation of one task, kept where `readiness` ran.
|
|
12
12
|
//
|
|
@@ -26,19 +26,19 @@ const STORE = path.join('.doflow', 'state', 'readiness');
|
|
|
26
26
|
* @param {string|null} slug the task's feature slug, when one is known
|
|
27
27
|
* @returns {Array<string>} paths relative to a checkout root
|
|
28
28
|
*/
|
|
29
|
-
function recordCandidates(taskId
|
|
29
|
+
function recordCandidates(taskId , slug ) {
|
|
30
30
|
const flat = path.join(STORE, `${taskId}.json`);
|
|
31
31
|
return slug && slug !== taskId ? [path.join(STORE, slug, `${taskId}.json`), flat] : [flat];
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** A record version this runtime cannot read because a newer DoFlow wrote it, else null. */
|
|
35
|
-
function newerVersionOf(record
|
|
35
|
+
function newerVersionOf(record ) {
|
|
36
36
|
const v = record && typeof record === 'object' ? record.version : undefined;
|
|
37
37
|
return Number.isInteger(v) && v > RECORD_VERSION ? v : null;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/** The refusal for a record a newer DoFlow wrote: it names the file and is never overwritten. */
|
|
41
|
-
function newerRecordText(file
|
|
41
|
+
function newerRecordText(file , version ) {
|
|
42
42
|
return `the readiness record ${file} was written by a newer DoFlow (record version ${version}; this runtime reads ${RECORD_VERSION}), `
|
|
43
43
|
+ 'so it was left as it is. Run this command with the DoFlow that wrote the record, or upgrade DoFlow here.';
|
|
44
44
|
}
|
|
@@ -48,11 +48,11 @@ function newerRecordText(file: any, version: any) {
|
|
|
48
48
|
* wrote, or bytes no evaluation can be read from.
|
|
49
49
|
* @returns {{kind: 'none'|'record'|'newer'|'unreadable', version?: number}}
|
|
50
50
|
*/
|
|
51
|
-
function priorRecord(file
|
|
51
|
+
function priorRecord(file , fsImpl ) {
|
|
52
52
|
let text;
|
|
53
53
|
try {
|
|
54
54
|
text = fsImpl.readFileSync(file, 'utf8');
|
|
55
|
-
} catch (error
|
|
55
|
+
} catch (error ) {
|
|
56
56
|
if (error.code === 'ENOENT') return { kind: 'none' };
|
|
57
57
|
throw error;
|
|
58
58
|
}
|
|
@@ -86,7 +86,7 @@ function priorRecord(file: any, fsImpl: any) {
|
|
|
86
86
|
* @returns {{file: string, record: Object, replacedUnreadable: string|null}} the `.unreadable` path when
|
|
87
87
|
* a previous file was set aside
|
|
88
88
|
*/
|
|
89
|
-
function writeReadinessRecord({ stateRoot, taskId, slug = null, report, inputs, declaredScope = null, mode, now = new Date(), fsImpl = fs }
|
|
89
|
+
function writeReadinessRecord({ stateRoot, taskId, slug = null, report, inputs, declaredScope = null, mode, now = new Date(), fsImpl = fs } ) {
|
|
90
90
|
const scope = resolveTaskScope({ projectRoot: stateRoot, taskId, slug });
|
|
91
91
|
const recordSlug = scope.slug || slug || null;
|
|
92
92
|
// The record a reader would find in this checkout is the one replaced; with none, the record goes
|
|
@@ -104,7 +104,7 @@ function writeReadinessRecord({ stateRoot, taskId, slug = null, report, inputs,
|
|
|
104
104
|
executionMode: mode,
|
|
105
105
|
inputs: { ...inputs },
|
|
106
106
|
declaredScope: Array.isArray(declaredScope) ? [...declaredScope] : null,
|
|
107
|
-
unmet: (report.requirements || []).filter((r
|
|
107
|
+
unmet: (report.requirements || []).filter((r ) => r.required && !r.satisfied).map((r ) => r.id),
|
|
108
108
|
evidenceCount: Number.isInteger(report.evidenceCount) ? report.evidenceCount : 0,
|
|
109
109
|
evaluatedAt: now.toISOString(),
|
|
110
110
|
};
|
|
@@ -136,13 +136,13 @@ function writeReadinessRecord({ stateRoot, taskId, slug = null, report, inputs,
|
|
|
136
136
|
* @returns {{status: 'found'|'missing'|'ambiguous'|'unreadable', record: Object|null, file: string|null,
|
|
137
137
|
* origin: 'current'|'other'|null, candidates: Array<string>, detail: string|null}}
|
|
138
138
|
*/
|
|
139
|
-
function readReadinessRecord({ stateRoot, taskId, slug = null, exec, fsImpl = fs }
|
|
139
|
+
function readReadinessRecord({ stateRoot, taskId, slug = null, exec, fsImpl = fs } ) {
|
|
140
140
|
const found = findStateFile({ stateRoot, relPath: recordCandidates(taskId, slug), ...(exec ? { exec } : {}), fsImpl });
|
|
141
141
|
const base = { record: null, file: found.file, origin: found.origin, candidates: found.candidates, detail: null };
|
|
142
142
|
if (found.status !== 'found') return { ...base, status: found.status };
|
|
143
143
|
try {
|
|
144
144
|
return { ...base, status: 'found', record: JSON.parse(fsImpl.readFileSync(found.file, 'utf8')) };
|
|
145
|
-
} catch (error
|
|
145
|
+
} catch (error ) {
|
|
146
146
|
return { ...base, status: 'unreadable', detail: `unparsable JSON: ${error.message}` };
|
|
147
147
|
}
|
|
148
148
|
}
|