@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
|
@@ -6,11 +6,11 @@ const fs = require('node:fs');
|
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const crypto = require('node:crypto');
|
|
8
8
|
const { execFileSync } = require('node:child_process');
|
|
9
|
-
const { REPO_ROOT } = require('../helper/repo-root.
|
|
9
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
10
10
|
|
|
11
11
|
// Freshness is measured against the bytes observed, not whether the file was clean at HEAD.
|
|
12
12
|
// Git identity remains provenance and a conservative fallback for older records without hashes.
|
|
13
|
-
function fileHash(root
|
|
13
|
+
function fileHash(root , locator ) {
|
|
14
14
|
if (!locator?.file) return null;
|
|
15
15
|
try {
|
|
16
16
|
const absolute = path.resolve(root, locator.file);
|
|
@@ -19,26 +19,26 @@ function fileHash(root: string, locator: any): string | null {
|
|
|
19
19
|
} catch { return null; }
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function measureFreshness(root
|
|
22
|
+
function measureFreshness(root , locator , gitCommit ) {
|
|
23
23
|
return { gitCommit, fileHash: fileHash(root, locator), observedAt: new Date().toISOString(), status: 'FRESH' };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
class FreshnessValidator {
|
|
27
|
-
repoRoot
|
|
28
|
-
gitRunner
|
|
29
|
-
|
|
27
|
+
repoRoot ;
|
|
28
|
+
gitRunner ;
|
|
29
|
+
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* @param {Object} [options]
|
|
33
33
|
* @param {string} [options.repoRoot]
|
|
34
34
|
* @param {Function} [options.gitRunner]
|
|
35
35
|
*/
|
|
36
|
-
constructor(options
|
|
36
|
+
constructor(options = {}) {
|
|
37
37
|
this.repoRoot = options.repoRoot || REPO_ROOT;
|
|
38
38
|
this.gitRunner = options.gitRunner || this.defaultGitRunner.bind(this);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
defaultGitRunner(args
|
|
41
|
+
defaultGitRunner(args , cwd = this.repoRoot) {
|
|
42
42
|
try {
|
|
43
43
|
return execFileSync('git', args, {
|
|
44
44
|
cwd,
|
|
@@ -55,7 +55,7 @@ class FreshnessValidator {
|
|
|
55
55
|
* Retrieves the current HEAD commit hash.
|
|
56
56
|
* @returns {string|null}
|
|
57
57
|
*/
|
|
58
|
-
getCurrentHead()
|
|
58
|
+
getCurrentHead() {
|
|
59
59
|
return this.gitRunner(['rev-parse', 'HEAD']);
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -63,11 +63,11 @@ class FreshnessValidator {
|
|
|
63
63
|
* Retrieves all modified/unstaged/staged files in the repository.
|
|
64
64
|
* @returns {Set<string>}
|
|
65
65
|
*/
|
|
66
|
-
getDirtyFiles()
|
|
66
|
+
getDirtyFiles() {
|
|
67
67
|
const output = this.gitRunner(['status', '--porcelain']);
|
|
68
68
|
if (!output) return new Set();
|
|
69
69
|
|
|
70
|
-
const dirty = new Set
|
|
70
|
+
const dirty = new Set ();
|
|
71
71
|
const lines = output.split('\n');
|
|
72
72
|
for (const line of lines) {
|
|
73
73
|
if (line.length > 3) {
|
|
@@ -83,14 +83,14 @@ class FreshnessValidator {
|
|
|
83
83
|
* @param {string} [refCommit]
|
|
84
84
|
* @returns {Set<string>}
|
|
85
85
|
*/
|
|
86
|
-
getModifiedFilesSince(refCommit
|
|
86
|
+
getModifiedFilesSince(refCommit ) {
|
|
87
87
|
// `checkEvidenceFreshness` calls this once per evidence item, and a batch of evidence shares
|
|
88
88
|
// one recorded commit — so without a memo a task with ninety items ran ninety `git diff`s to
|
|
89
89
|
// answer the same question. Keyed on the commit because that is the axis the work collapses
|
|
90
90
|
// on. Instance-scoped, so it lives exactly as long as one invocation.
|
|
91
91
|
if (!this.diffCache) this.diffCache = new Map();
|
|
92
92
|
const key = refCommit || '';
|
|
93
|
-
if (this.diffCache.has(key)) return this.diffCache.get(key)
|
|
93
|
+
if (this.diffCache.has(key)) return this.diffCache.get(key) ;
|
|
94
94
|
|
|
95
95
|
const dirty = this.getDirtyFiles();
|
|
96
96
|
if (!refCommit) {
|
|
@@ -113,7 +113,7 @@ class FreshnessValidator {
|
|
|
113
113
|
* @param {Object} item - Evidence item
|
|
114
114
|
* @returns {{ status: 'FRESH'|'STALE', reason?: string }}
|
|
115
115
|
*/
|
|
116
|
-
checkEvidenceFreshness(item
|
|
116
|
+
checkEvidenceFreshness(item ) {
|
|
117
117
|
if (!item) return { status: 'STALE', reason: 'Evidence item is missing' };
|
|
118
118
|
|
|
119
119
|
// If explicitly marked stale
|
|
@@ -152,7 +152,7 @@ class FreshnessValidator {
|
|
|
152
152
|
* @param {Object} ledger - EvidenceLedger instance
|
|
153
153
|
* @returns {number} count of newly invalidated items
|
|
154
154
|
*/
|
|
155
|
-
validateLedgerFreshness(ledger
|
|
155
|
+
validateLedgerFreshness(ledger ) {
|
|
156
156
|
if (!ledger || typeof ledger.getAllEvidence !== 'function') return 0;
|
|
157
157
|
|
|
158
158
|
let count = 0;
|
|
@@ -21,12 +21,12 @@ const fs = require('node:fs');
|
|
|
21
21
|
const os = require('node:os');
|
|
22
22
|
const path = require('node:path');
|
|
23
23
|
const { execFileSync } = require('node:child_process');
|
|
24
|
-
const { CapabilityRouter } = require('./capability-router.
|
|
25
|
-
const { loadRegistry } = require('../registry/index.
|
|
26
|
-
const { evaluateReach } = require('./reach.
|
|
27
|
-
const { mcpReach } = require('./mcp-reach.
|
|
28
|
-
const { OUTCOME_LABELS } = require('./mcp-consultations.
|
|
29
|
-
const { REPO_ROOT } = require('../helper/repo-root.
|
|
24
|
+
const { CapabilityRouter } = require('./capability-router.js');
|
|
25
|
+
const { loadRegistry } = require('../registry/index.js');
|
|
26
|
+
const { evaluateReach } = require('./reach.js');
|
|
27
|
+
const { mcpReach } = require('./mcp-reach.js');
|
|
28
|
+
const { OUTCOME_LABELS } = require('./mcp-consultations.js');
|
|
29
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
30
30
|
|
|
31
31
|
/** Answered a probe. */
|
|
32
32
|
const HEALTHY = 'HEALTHY';
|
|
@@ -49,15 +49,15 @@ const BANNER_MAX = 80;
|
|
|
49
49
|
* them exists the result is UNKNOWN naming the paths searched, because a provider that owns its
|
|
50
50
|
* own cache layout must not have a freshness verdict invented for it.
|
|
51
51
|
*/
|
|
52
|
-
const PROVIDER_ARTIFACTS
|
|
52
|
+
const PROVIDER_ARTIFACTS = Object.freeze({
|
|
53
53
|
'graphify.query': {
|
|
54
54
|
label: 'code graph',
|
|
55
|
-
candidates: (root
|
|
55
|
+
candidates: (root ) => [path.join(root, 'graphify-out', 'graph.json')],
|
|
56
56
|
refresh: 'graphify update .',
|
|
57
57
|
},
|
|
58
58
|
'semble.search': {
|
|
59
59
|
label: 'semantic index',
|
|
60
|
-
candidates: (root
|
|
60
|
+
candidates: (root , home ) => [
|
|
61
61
|
path.join(root, '.semble', 'index.json'),
|
|
62
62
|
path.join(root, '.semble'),
|
|
63
63
|
path.join(home, '.cache', 'semble', 'index'),
|
|
@@ -84,7 +84,7 @@ const SCAN_FILE_LIMIT = 4000;
|
|
|
84
84
|
* @param {number} [options.timeoutMs=PROBE_TIMEOUT_MS]
|
|
85
85
|
* @returns {{ok: boolean, output: string, durationMs: number, timedOut: boolean, error: string|null}}
|
|
86
86
|
*/
|
|
87
|
-
function runProbe(command
|
|
87
|
+
function runProbe(command , { execFileImpl = execFileSync, timeoutMs = PROBE_TIMEOUT_MS } = {}) {
|
|
88
88
|
const [bin, ...args] = command;
|
|
89
89
|
const started = Number(process.hrtime.bigint() / 1000000n);
|
|
90
90
|
try {
|
|
@@ -100,7 +100,7 @@ function runProbe(command: any, { execFileImpl = execFileSync, timeoutMs = PROBE
|
|
|
100
100
|
timedOut: false,
|
|
101
101
|
error: null,
|
|
102
102
|
};
|
|
103
|
-
} catch (error
|
|
103
|
+
} catch (error ) {
|
|
104
104
|
// A killed process and a non-zero exit are both "did not answer", but they are different
|
|
105
105
|
// repairs — a timeout usually means a first-run index build, a non-zero exit usually means a
|
|
106
106
|
// broken install — so they are reported apart rather than as one failure string.
|
|
@@ -117,7 +117,7 @@ function runProbe(command: any, { execFileImpl = execFileSync, timeoutMs = PROBE
|
|
|
117
117
|
|
|
118
118
|
/** First line of probe output, trimmed to a banner. Control characters are stripped so a provider
|
|
119
119
|
* cannot rewrite the doctor report with an escape sequence. */
|
|
120
|
-
function banner(output
|
|
120
|
+
function banner(output ) {
|
|
121
121
|
if (!output) return null;
|
|
122
122
|
// Control characters are removed by code point rather than by a literal class, so this source
|
|
123
123
|
// file stays free of the escape sequences it is defending against.
|
|
@@ -137,7 +137,7 @@ function banner(output: any) {
|
|
|
137
137
|
* @param {number} [options.limit=SCAN_FILE_LIMIT]
|
|
138
138
|
* @returns {{mtimeMs: number|null, filesScanned: number, truncated: boolean}}
|
|
139
139
|
*/
|
|
140
|
-
function newestSourceMtime(root
|
|
140
|
+
function newestSourceMtime(root , { fsImpl = fs, limit = SCAN_FILE_LIMIT } = {}) {
|
|
141
141
|
let mtimeMs = null;
|
|
142
142
|
let filesScanned = 0;
|
|
143
143
|
let truncated = false;
|
|
@@ -180,7 +180,7 @@ function newestSourceMtime(root: any, { fsImpl = fs, limit = SCAN_FILE_LIMIT }:
|
|
|
180
180
|
* @param {Object} [options.fsImpl=fs]
|
|
181
181
|
* @returns {Object|null} null when the provider is not index-backed
|
|
182
182
|
*/
|
|
183
|
-
function probeFreshness(providerId
|
|
183
|
+
function probeFreshness(providerId , { root = process.cwd(), homeDir = os.homedir(), fsImpl = fs } = {}) {
|
|
184
184
|
const spec = PROVIDER_ARTIFACTS[providerId];
|
|
185
185
|
if (!spec) return null;
|
|
186
186
|
|
|
@@ -253,11 +253,11 @@ function probeFreshness(providerId: any, { root = process.cwd(), homeDir = os.ho
|
|
|
253
253
|
* @param {Object} [options.fsImpl]
|
|
254
254
|
* @returns {Object} probe result
|
|
255
255
|
*/
|
|
256
|
-
function probeProvider(provider
|
|
256
|
+
function probeProvider(provider , { router, projectRoot = process.cwd(), execFileImpl, fsImpl = fs } = {}) {
|
|
257
257
|
if (!provider || !provider.id) {
|
|
258
258
|
return { id: null, status: UNVERIFIED, detail: 'no provider declared' };
|
|
259
259
|
}
|
|
260
|
-
const result
|
|
260
|
+
const result = {
|
|
261
261
|
id: provider.id,
|
|
262
262
|
name: provider.name || provider.id,
|
|
263
263
|
binary: provider.binary || null,
|
|
@@ -326,11 +326,11 @@ function probeProvider(provider: any, { router, projectRoot = process.cwd(), exe
|
|
|
326
326
|
* @param {CapabilityRouter} [options.router]
|
|
327
327
|
* @returns {Object} map of provider id -> probe result
|
|
328
328
|
*/
|
|
329
|
-
function probeProviders({ repoRoot, projectRoot = process.cwd(), ids = null, router, execFileImpl, fsImpl = fs }
|
|
329
|
+
function probeProviders({ repoRoot, projectRoot = process.cwd(), ids = null, router, execFileImpl, fsImpl = fs } = {}) {
|
|
330
330
|
const activeRouter = router || new CapabilityRouter({ repoRoot: repoRoot || REPO_ROOT, projectRoot });
|
|
331
331
|
const wanted = ids ? new Set(ids) : null;
|
|
332
|
-
const results
|
|
333
|
-
for (const capability of Object.values
|
|
332
|
+
const results = {};
|
|
333
|
+
for (const capability of Object.values (activeRouter.capabilities || {})) {
|
|
334
334
|
for (const provider of capability.providers || []) {
|
|
335
335
|
if (!provider || !provider.id) continue;
|
|
336
336
|
if (wanted && !wanted.has(provider.id)) continue;
|
|
@@ -355,18 +355,18 @@ function probeProviders({ repoRoot, projectRoot = process.cwd(), ids = null, rou
|
|
|
355
355
|
* @param {Function} [options.detector] injection seam; defaults to the C.2 module when present
|
|
356
356
|
* @returns {{status: string, build: Object|null, test: Object|null, reason?: string}}
|
|
357
357
|
*/
|
|
358
|
-
function detectProjectCommands({ projectRoot = process.cwd(), detector }
|
|
358
|
+
function detectProjectCommands({ projectRoot = process.cwd(), detector } = {}) {
|
|
359
359
|
let detect = detector;
|
|
360
360
|
if (!detect) {
|
|
361
361
|
let mod = null;
|
|
362
362
|
try {
|
|
363
|
-
mod = require('./command-detect.
|
|
363
|
+
mod = require('./command-detect.js');
|
|
364
364
|
} catch {
|
|
365
365
|
return {
|
|
366
366
|
status: 'UNKNOWN',
|
|
367
367
|
build: null,
|
|
368
368
|
test: null,
|
|
369
|
-
reason: 'src/runtime/command-detect.
|
|
369
|
+
reason: 'src/runtime/command-detect.js is not present in this build; DoFlow reports detected commands but does not detect them itself, and will not guess',
|
|
370
370
|
};
|
|
371
371
|
}
|
|
372
372
|
detect = typeof mod.detectCommands === 'function' ? mod.detectCommands : null;
|
|
@@ -375,7 +375,7 @@ function detectProjectCommands({ projectRoot = process.cwd(), detector }: any =
|
|
|
375
375
|
status: 'UNKNOWN',
|
|
376
376
|
build: null,
|
|
377
377
|
test: null,
|
|
378
|
-
reason: `src/runtime/command-detect.
|
|
378
|
+
reason: `src/runtime/command-detect.js exports no detectCommands() (found: ${Object.keys(mod).join(', ') || 'nothing'})`,
|
|
379
379
|
};
|
|
380
380
|
}
|
|
381
381
|
}
|
|
@@ -383,7 +383,7 @@ function detectProjectCommands({ projectRoot = process.cwd(), detector }: any =
|
|
|
383
383
|
let detected;
|
|
384
384
|
try {
|
|
385
385
|
detected = detect({ projectRoot }) || {};
|
|
386
|
-
} catch (error
|
|
386
|
+
} catch (error ) {
|
|
387
387
|
return { status: 'UNKNOWN', build: null, test: null, reason: `command detection failed: ${error.message}` };
|
|
388
388
|
}
|
|
389
389
|
|
|
@@ -414,15 +414,15 @@ function detectProjectCommands({ projectRoot = process.cwd(), detector }: any =
|
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
/** Roll a capability's provider probes up into one status. */
|
|
417
|
-
function capabilityStatus(probes
|
|
417
|
+
function capabilityStatus(probes ) {
|
|
418
418
|
if (!probes.length) return { status: 'UNAVAILABLE', activeProvider: null };
|
|
419
|
-
const firstHealthy = probes.findIndex((probe
|
|
419
|
+
const firstHealthy = probes.findIndex((probe ) => probe.status === HEALTHY);
|
|
420
420
|
if (firstHealthy === 0) return { status: HEALTHY, activeProvider: probes[0].id };
|
|
421
421
|
if (firstHealthy > 0) return { status: 'FALLBACK', activeProvider: probes[firstHealthy].id };
|
|
422
422
|
// Nothing answered. "Installed but broken" and "not installed" are different problems: the first
|
|
423
423
|
// is actionable now, the second is the graceful degradation NFR-002 requires.
|
|
424
|
-
if (probes.some((probe
|
|
425
|
-
if (probes.every((probe
|
|
424
|
+
if (probes.some((probe ) => probe.status === UNHEALTHY)) return { status: 'DEGRADED', activeProvider: null };
|
|
425
|
+
if (probes.every((probe ) => probe.status === ABSENT)) return { status: 'UNAVAILABLE', activeProvider: null };
|
|
426
426
|
return { status: UNVERIFIED, activeProvider: null };
|
|
427
427
|
}
|
|
428
428
|
|
|
@@ -434,7 +434,7 @@ function capabilityStatus(probes: any) {
|
|
|
434
434
|
* @returns {Object} report model
|
|
435
435
|
*/
|
|
436
436
|
/** The installed DoFlow source's own version, read from its package.json; null when unreadable. */
|
|
437
|
-
function installVersion(root
|
|
437
|
+
function installVersion(root , fsImpl = fs) {
|
|
438
438
|
try {
|
|
439
439
|
return JSON.parse(fsImpl.readFileSync(path.join(root, 'package.json'), 'utf8')).version || null;
|
|
440
440
|
} catch {
|
|
@@ -443,8 +443,8 @@ function installVersion(root: any, fsImpl = fs) {
|
|
|
443
443
|
}
|
|
444
444
|
|
|
445
445
|
/** The project's git identity — short commit and branch — or null outside a repository. */
|
|
446
|
-
function projectGitIdentity(projectRoot
|
|
447
|
-
const git = (args
|
|
446
|
+
function projectGitIdentity(projectRoot ) {
|
|
447
|
+
const git = (args ) => {
|
|
448
448
|
try {
|
|
449
449
|
return execFileSync('git', ['-C', projectRoot, ...args], {
|
|
450
450
|
encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -468,19 +468,19 @@ const NO_OUTCOME_SHOWN = 3;
|
|
|
468
468
|
* it and the command that adds it where it is missing. Read-only; starts no server (NFR-001).
|
|
469
469
|
* Server ids come from the registry, never from this file.
|
|
470
470
|
*/
|
|
471
|
-
function mcpServersView({ registry, projectRoot, fsImpl, adapters }
|
|
472
|
-
const named = registry.mcp.filter((server
|
|
471
|
+
function mcpServersView({ registry, projectRoot, fsImpl, adapters } ) {
|
|
472
|
+
const named = registry.mcp.filter((server ) => (server.steps || []).length);
|
|
473
473
|
if (!named.length) return [];
|
|
474
474
|
if (!adapters) throw new Error('no adapter registry was given');
|
|
475
475
|
const reach = mcpReach({ registry, projectRoot, fsImpl, adapters });
|
|
476
|
-
const unreadableFile = (harness
|
|
477
|
-
return named.map((server
|
|
476
|
+
const unreadableFile = (harness ) => reach.unreadable.find((item ) => item.harness === harness.id && item.scope === harness.scope);
|
|
477
|
+
return named.map((server ) => ({
|
|
478
478
|
id: server.id,
|
|
479
479
|
selection: server.selection,
|
|
480
|
-
namedBy: [...new Set(server.steps.map((step
|
|
481
|
-
harnesses: reach.harnesses.map((harness
|
|
480
|
+
namedBy: [...new Set(server.steps.map((step ) => (step.expectedWhen === 'research-request' ? `${step.skill} (research request)` : step.skill)))],
|
|
481
|
+
harnesses: reach.harnesses.map((harness ) => {
|
|
482
482
|
const state = harness.servers[server.id];
|
|
483
|
-
const row
|
|
483
|
+
const row = { id: harness.id, scope: harness.scope, state };
|
|
484
484
|
const unreadable = state === 'unknown' ? unreadableFile(harness) : null;
|
|
485
485
|
// An unreadable config is fixed before anything is added to it (IC-014).
|
|
486
486
|
if (unreadable) row.fix = unreadable.file || `${harness.id}'s MCP config`;
|
|
@@ -490,7 +490,7 @@ function mcpServersView({ registry, projectRoot, fsImpl, adapters }: any) {
|
|
|
490
490
|
}));
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
-
const byText = (a
|
|
493
|
+
const byText = (a , b ) => (a < b ? -1 : a > b ? 1 : 0);
|
|
494
494
|
|
|
495
495
|
/**
|
|
496
496
|
* The `[MCP Consultations]` data (IC-015): the recorded `consultations` of the project's run
|
|
@@ -498,10 +498,10 @@ const byText = (a: any, b: any) => (a < b ? -1 : a > b ? 1 : 0);
|
|
|
498
498
|
* `at`, counted per server. An entry without `consultations` predates the feature and is never
|
|
499
499
|
* counted (NFR-005); "no outcome" is `named` minus the servers with an outcome, never stored.
|
|
500
500
|
*/
|
|
501
|
-
function mcpConsultationsView({ projectRoot, fsImpl }
|
|
501
|
+
function mcpConsultationsView({ projectRoot, fsImpl } ) {
|
|
502
502
|
const dir = path.join(projectRoot, '.doflow', 'state', 'orchestration');
|
|
503
503
|
let files = [];
|
|
504
|
-
try { files = fsImpl.readdirSync(dir).filter((name
|
|
504
|
+
try { files = fsImpl.readdirSync(dir).filter((name ) => name.endsWith('.json')).sort(); } catch { files = []; }
|
|
505
505
|
let unreadableRuns = 0;
|
|
506
506
|
const bySkill = new Map();
|
|
507
507
|
for (const name of files) {
|
|
@@ -517,14 +517,14 @@ function mcpConsultationsView({ projectRoot, fsImpl }: any) {
|
|
|
517
517
|
}
|
|
518
518
|
const stages = [...bySkill.keys()].sort(byText).map((skill) => {
|
|
519
519
|
const kept = bySkill.get(skill)
|
|
520
|
-
.sort((a
|
|
520
|
+
.sort((a , b ) => byText(b.at, a.at) || byText(a.taskId, b.taskId) || byText(String(a.node), String(b.node)))
|
|
521
521
|
.slice(0, CONSULTATION_WINDOW);
|
|
522
522
|
const counts = new Map();
|
|
523
|
-
const row = (server
|
|
523
|
+
const row = (server ) => {
|
|
524
524
|
if (!counts.has(server)) counts.set(server, { server, used: 0, nothingUsable: 0, skipped: 0, unavailable: 0, noOutcome: 0 });
|
|
525
525
|
return counts.get(server);
|
|
526
526
|
};
|
|
527
|
-
const key
|
|
527
|
+
const key = { 'consulted-used': 'used', 'consulted-nothing-usable': 'nothingUsable', skipped: 'skipped', unavailable: 'unavailable' };
|
|
528
528
|
const noOutcome = [];
|
|
529
529
|
const shown = new Map();
|
|
530
530
|
for (const { taskId, node, at, record } of kept) {
|
|
@@ -533,7 +533,7 @@ function mcpConsultationsView({ projectRoot, fsImpl }: any) {
|
|
|
533
533
|
if (outcome && typeof outcome.server === 'string' && key[outcome.state]) outcomes.set(outcome.server, key[outcome.state]);
|
|
534
534
|
}
|
|
535
535
|
for (const [server, field] of outcomes) row(server)[field] += 1;
|
|
536
|
-
for (const server of (Array.isArray(record.named) ? record.named : []).filter((id
|
|
536
|
+
for (const server of (Array.isArray(record.named) ? record.named : []).filter((id ) => typeof id === 'string').sort(byText)) {
|
|
537
537
|
if (outcomes.has(server)) continue;
|
|
538
538
|
row(server).noOutcome += 1;
|
|
539
539
|
// `kept` is newest first, so the first entries per server are its latest.
|
|
@@ -548,14 +548,14 @@ function mcpConsultationsView({ projectRoot, fsImpl }: any) {
|
|
|
548
548
|
|
|
549
549
|
/** The doctor's `mcp` block. A servers view that cannot be built degrades to a one-line note; the
|
|
550
550
|
* sections never enter findings or change the exit code (FR-011, FR-012). */
|
|
551
|
-
function mcpView({ registry, projectRoot, fsImpl, adapters }
|
|
551
|
+
function mcpView({ registry, projectRoot, fsImpl, adapters } ) {
|
|
552
552
|
let servers = [];
|
|
553
553
|
let serversNote;
|
|
554
|
-
try { servers = mcpServersView({ registry, projectRoot, fsImpl, adapters }); } catch (error
|
|
554
|
+
try { servers = mcpServersView({ registry, projectRoot, fsImpl, adapters }); } catch (error ) { serversNote = `MCP server view unavailable: ${error.message}`; }
|
|
555
555
|
return { servers, ...(serversNote ? { serversNote } : {}), consultations: mcpConsultationsView({ projectRoot, fsImpl }) };
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
-
const MCP_STATE_MARK
|
|
558
|
+
const MCP_STATE_MARK = Object.freeze({
|
|
559
559
|
selected: '✓ selected',
|
|
560
560
|
configured: '✓ configured',
|
|
561
561
|
'not-selected': '○ not selected',
|
|
@@ -563,11 +563,11 @@ const MCP_STATE_MARK: Record<string, any> = Object.freeze({
|
|
|
563
563
|
unknown: '? unknown',
|
|
564
564
|
});
|
|
565
565
|
|
|
566
|
-
function printMcpSections(mcp
|
|
566
|
+
function printMcpSections(mcp ) {
|
|
567
567
|
console.log('\n[MCP Servers] — named by shipped stages; not blocking');
|
|
568
568
|
if (mcp.serversNote) {
|
|
569
569
|
console.log(` ${mcp.serversNote}`);
|
|
570
|
-
} else if (!mcp.servers.some((server
|
|
570
|
+
} else if (!mcp.servers.some((server ) => server.harnesses.length)) {
|
|
571
571
|
console.log(' No harness has DoFlow ledger rows here, so no server selection to report.');
|
|
572
572
|
} else {
|
|
573
573
|
for (const server of mcp.servers) {
|
|
@@ -602,11 +602,11 @@ function printMcpSections(mcp: any) {
|
|
|
602
602
|
if (unreadableRuns) console.log(` ${unreadableRuns} run file(s) could not be read`);
|
|
603
603
|
}
|
|
604
604
|
|
|
605
|
-
function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, execFileImpl, fsImpl = fs, detector, adapters }
|
|
605
|
+
function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, execFileImpl, fsImpl = fs, detector, adapters } = {}) {
|
|
606
606
|
const root = repoRoot || REPO_ROOT;
|
|
607
607
|
const registry = loadRegistry({ repoRoot: root });
|
|
608
608
|
const activeRouter = router || new CapabilityRouter({ repoRoot: root, projectRoot });
|
|
609
|
-
const probes
|
|
609
|
+
const probes = probeProviders({ repoRoot: root, projectRoot, router: activeRouter, execFileImpl, fsImpl });
|
|
610
610
|
const project = { root: path.resolve(projectRoot), commands: detectProjectCommands({ projectRoot, detector }) };
|
|
611
611
|
|
|
612
612
|
// `native.test` is the one provider with neither a binary nor a probe: it stands for whatever the
|
|
@@ -621,8 +621,8 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
|
|
|
621
621
|
: 'no probe of its own, and no project test command was detected — nothing establishes that this capability can answer';
|
|
622
622
|
}
|
|
623
623
|
|
|
624
|
-
const capabilities = Object.entries
|
|
625
|
-
const declared = (capability.providers || []).map((provider
|
|
624
|
+
const capabilities = Object.entries (activeRouter.capabilities || {}).map(([id, capability]) => {
|
|
625
|
+
const declared = (capability.providers || []).map((provider ) => probes[provider.id]).filter(Boolean);
|
|
626
626
|
const rolled = capabilityStatus(declared);
|
|
627
627
|
return {
|
|
628
628
|
capability: id,
|
|
@@ -630,14 +630,14 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
|
|
|
630
630
|
status: rolled.status,
|
|
631
631
|
activeProvider: rolled.activeProvider,
|
|
632
632
|
totalProviders: declared.length,
|
|
633
|
-
providers: declared.map((probe
|
|
633
|
+
providers: declared.map((probe ) => ({ id: probe.id, status: probe.status, detail: probe.detail })),
|
|
634
634
|
};
|
|
635
635
|
});
|
|
636
636
|
|
|
637
637
|
// External tools are the same binaries the providers use, so their status comes from the probe
|
|
638
638
|
// that already ran rather than from a second, weaker presence check.
|
|
639
|
-
const externalTools = registry.externalTools.map((tool
|
|
640
|
-
const probe
|
|
639
|
+
const externalTools = registry.externalTools.map((tool ) => {
|
|
640
|
+
const probe = Object.values(probes).find((candidate) => candidate.binary === tool.id);
|
|
641
641
|
if (probe) return { id: tool.id, displayName: tool.displayName, status: probe.status, detail: probe.detail };
|
|
642
642
|
const present = activeRouter.isBinaryAvailable(tool.id);
|
|
643
643
|
return {
|
|
@@ -662,8 +662,8 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
|
|
|
662
662
|
.filter((capability) => capability.status === 'DEGRADED')
|
|
663
663
|
.map((capability) => ({ kind: 'capability-degraded', subject: capability.capability, detail: 'every installed provider for this capability failed its probe' })),
|
|
664
664
|
...reach.rows
|
|
665
|
-
.filter((row
|
|
666
|
-
.map((row
|
|
665
|
+
.filter((row ) => row.state === 'NO-REACH')
|
|
666
|
+
.map((row ) => ({ kind: 'runtime-no-reach', subject: `${row.harness}:${row.scope}`, detail: `${row.reason}; fix: ${row.fix}` })),
|
|
667
667
|
];
|
|
668
668
|
const warnings = [
|
|
669
669
|
...freshness
|
|
@@ -676,7 +676,7 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
|
|
|
676
676
|
.filter((probe) => probe.status === UNVERIFIED)
|
|
677
677
|
.map((probe) => ({ kind: 'provider-unverified', subject: probe.id, detail: probe.detail })),
|
|
678
678
|
...reach.unreadable
|
|
679
|
-
.map((item
|
|
679
|
+
.map((item ) => ({ kind: 'reach-ledger-unreadable', subject: `${item.scope}: ${item.ledger}`, detail: item.detail })),
|
|
680
680
|
...(project.commands.status === 'DETECTED' ? [] : [{ kind: 'project-commands', subject: 'build/test', detail: project.commands.reason }]),
|
|
681
681
|
];
|
|
682
682
|
|
|
@@ -692,11 +692,11 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
|
|
|
692
692
|
|
|
693
693
|
return {
|
|
694
694
|
identity,
|
|
695
|
-
harnesses: registry.harnesses.map((harness
|
|
695
|
+
harnesses: registry.harnesses.map((harness ) => ({
|
|
696
696
|
id: harness.id,
|
|
697
697
|
displayName: harness.displayName,
|
|
698
698
|
status: 'PASS',
|
|
699
|
-
reach: reach.rows.filter((row
|
|
699
|
+
reach: reach.rows.filter((row ) => row.harness === harness.id).map(({ harness: _harness, ...row } ) => row),
|
|
700
700
|
})),
|
|
701
701
|
externalTools,
|
|
702
702
|
capabilities,
|
|
@@ -728,12 +728,12 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
|
|
|
728
728
|
* @param {number} code
|
|
729
729
|
* @returns {number}
|
|
730
730
|
*/
|
|
731
|
-
function finish(code
|
|
731
|
+
function finish(code ) {
|
|
732
732
|
process.exitCode = code;
|
|
733
733
|
return code;
|
|
734
734
|
}
|
|
735
735
|
|
|
736
|
-
const STATUS_MARK
|
|
736
|
+
const STATUS_MARK = Object.freeze({
|
|
737
737
|
HEALTHY: '✓ HEALTHY',
|
|
738
738
|
UNHEALTHY: '✗ UNHEALTHY',
|
|
739
739
|
ABSENT: '○ ABSENT',
|
|
@@ -743,13 +743,13 @@ const STATUS_MARK: Record<string, any> = Object.freeze({
|
|
|
743
743
|
UNAVAILABLE: '○ UNAVAILABLE',
|
|
744
744
|
});
|
|
745
745
|
|
|
746
|
-
const REACH_MARK
|
|
746
|
+
const REACH_MARK = Object.freeze({
|
|
747
747
|
REACHED: '✓ REACHED',
|
|
748
748
|
'NO-REACH': '✗ NO-REACH',
|
|
749
749
|
'N/A': '○ N/A',
|
|
750
750
|
});
|
|
751
751
|
|
|
752
|
-
const FRESHNESS_MARK
|
|
752
|
+
const FRESHNESS_MARK = Object.freeze({
|
|
753
753
|
FRESH: '✓ FRESH',
|
|
754
754
|
STALE: '▲ STALE',
|
|
755
755
|
UNKNOWN: '? UNKNOWN',
|
|
@@ -764,9 +764,9 @@ const FRESHNESS_MARK: Record<string, any> = Object.freeze({
|
|
|
764
764
|
* @returns {number} process exit code — 1 when a provider is installed but does not answer, or an
|
|
765
765
|
* installed harness's skills cannot reach a runtime
|
|
766
766
|
*/
|
|
767
|
-
async function handleDoctorCommand({ json = false, repoRoot, projectRoot = process.cwd(), ...rest }
|
|
768
|
-
const report
|
|
769
|
-
const { buildAdviceDoctor, printAdviceDoctor } = require('./advice/doctor.
|
|
767
|
+
async function handleDoctorCommand({ json = false, repoRoot, projectRoot = process.cwd(), ...rest } = {}) {
|
|
768
|
+
const report = buildHealthReport({ repoRoot, projectRoot, ...rest });
|
|
769
|
+
const { buildAdviceDoctor, printAdviceDoctor } = require('./advice/doctor.js');
|
|
770
770
|
const adviceSections = await buildAdviceDoctor({ repoRoot, projectRoot, fsImpl: rest.fsImpl });
|
|
771
771
|
report.decisionModel = adviceSections.decisionModel;
|
|
772
772
|
report.advice = adviceSections.advice;
|
|
@@ -790,8 +790,8 @@ async function handleDoctorCommand({ json = false, repoRoot, projectRoot = proce
|
|
|
790
790
|
for (const harness of report.harnesses) console.log(` ${harness.displayName.padEnd(28)} adapter PASS`);
|
|
791
791
|
|
|
792
792
|
console.log('\n[Runtime Reach]');
|
|
793
|
-
const reachLines = report.harnesses.flatMap((harness
|
|
794
|
-
const detail = ({ REACHED: row.root, 'NO-REACH': `fix: ${row.fix}`, 'N/A': row.reason }
|
|
793
|
+
const reachLines = report.harnesses.flatMap((harness ) => harness.reach.map((row ) => {
|
|
794
|
+
const detail = ({ REACHED: row.root, 'NO-REACH': `fix: ${row.fix}`, 'N/A': row.reason } )[row.state];
|
|
795
795
|
const where = row.scope === 'project' ? ` [install at ${row.installRoot}]` : '';
|
|
796
796
|
return ` ${harness.id.padEnd(14)}${row.scope.padEnd(9)}${(REACH_MARK[row.state] || row.state).padEnd(13)}${detail}${where}`;
|
|
797
797
|
}));
|
|
@@ -819,7 +819,7 @@ async function handleDoctorCommand({ json = false, repoRoot, projectRoot = proce
|
|
|
819
819
|
|
|
820
820
|
console.log('\n[Project Commands]');
|
|
821
821
|
const commands = report.project.commands;
|
|
822
|
-
const commandLine = (role
|
|
822
|
+
const commandLine = (role ) => (role
|
|
823
823
|
? `✓ ${role.command}${role.source ? ` (${role.source}, ${role.derivation || 'declared'})` : ''}`
|
|
824
824
|
: '? not detected');
|
|
825
825
|
console.log(` build ${commandLine(commands.build)}`);
|