@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
|
@@ -33,10 +33,10 @@ const RESOLUTION_REASONS = new Set([
|
|
|
33
33
|
* every language in the repository, and a parser per language is a far larger thing than the
|
|
34
34
|
* question being asked.
|
|
35
35
|
*/
|
|
36
|
-
function findSymbolLines(lines
|
|
36
|
+
function findSymbolLines(lines , symbol ) {
|
|
37
37
|
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
38
38
|
const pattern = new RegExp(`(?:^|[^A-Za-z0-9_$])${escaped}(?:[^A-Za-z0-9_$]|$)`);
|
|
39
|
-
const hits
|
|
39
|
+
const hits = [];
|
|
40
40
|
for (let i = 0; i < lines.length; i += 1) {
|
|
41
41
|
if (pattern.test(lines[i])) hits.push(i + 1);
|
|
42
42
|
}
|
|
@@ -52,10 +52,10 @@ function findSymbolLines(lines: string[], symbol: string): number[] {
|
|
|
52
52
|
*
|
|
53
53
|
* @returns {{lines: string[]}|{missing: true}}
|
|
54
54
|
*/
|
|
55
|
-
function readTargetLines(locator
|
|
55
|
+
function readTargetLines(locator , root , impl ) {
|
|
56
56
|
const target = path.isAbsolute(locator.file) ? locator.file : path.join(root, locator.file);
|
|
57
57
|
|
|
58
|
-
let content
|
|
58
|
+
let content ;
|
|
59
59
|
try {
|
|
60
60
|
if (!impl.existsSync(target)) return { missing: true };
|
|
61
61
|
content = impl.readFileSync(target, 'utf8');
|
|
@@ -78,7 +78,7 @@ function readTargetLines(locator: any, root: string, impl: any): { lines: string
|
|
|
78
78
|
* already reports. Only absence is unresolvable — that distinction is the whole of this function
|
|
79
79
|
* and is easier to see with nothing else around it.
|
|
80
80
|
*/
|
|
81
|
-
function checkSymbol(lines
|
|
81
|
+
function checkSymbol(lines , symbol ) {
|
|
82
82
|
const symbolLines = findSymbolLines(lines, symbol);
|
|
83
83
|
if (symbolLines.length === 0) {
|
|
84
84
|
return { resolved: false, reason: 'symbol-absent', actual: { lines: lines.length, symbolLines: [] } };
|
|
@@ -105,7 +105,7 @@ const NOT_CHECKABLE = { resolved: true, reason: 'not-checkable', actual: null };
|
|
|
105
105
|
* `actual` names what the file does offer, so a caller can build a message the writer can act on:
|
|
106
106
|
* `{ lines }` for a line miss, `{ symbolLines }` for a symbol miss.
|
|
107
107
|
*/
|
|
108
|
-
function resolveLocator({ locator, repoRoot, fsImpl }
|
|
108
|
+
function resolveLocator({ locator, repoRoot, fsImpl } = {}) {
|
|
109
109
|
if (!locator || typeof locator !== 'object' || Array.isArray(locator)) return { ...NOT_CHECKABLE };
|
|
110
110
|
// A uri-only locator, or an empty locator on a non-extracted item. Nothing readable here.
|
|
111
111
|
if (!locator.file) return { ...NOT_CHECKABLE };
|
|
@@ -130,7 +130,7 @@ function resolveLocator({ locator, repoRoot, fsImpl }: any = {}): { resolved: bo
|
|
|
130
130
|
* @param {{resolved: boolean, reason: (string|null), actual: (Object|null)}} result
|
|
131
131
|
* @returns {string|null}
|
|
132
132
|
*/
|
|
133
|
-
function describeResolution(locator
|
|
133
|
+
function describeResolution(locator , result ) {
|
|
134
134
|
if (!result || result.resolved) return null;
|
|
135
135
|
const file = (locator && locator.file) || '(no file)';
|
|
136
136
|
switch (result.reason) {
|
|
@@ -52,10 +52,10 @@ const KEY_VALUE_BODY = new RegExp(`(${KEY_NAME}["']?[ \\t]*[=:][ \\t]*)("[^"\\r\
|
|
|
52
52
|
const AUTH_HEADER = /((?:Proxy-)?Authorization:)[ \t]*([^\r\n]*)/gi;
|
|
53
53
|
const SECRET_FLAG = /(--(?:password|passwd|token|secret|api-key|client-secret)(?:=|[ \t]+))("[^"\r\n]*"|'[^'\r\n]*'|[^\s"']+)/gi;
|
|
54
54
|
|
|
55
|
-
function escapeRegExp(text
|
|
55
|
+
function escapeRegExp(text ) { return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
56
56
|
|
|
57
57
|
/** The home directory to abbreviate: an absolute path other than the file system root. */
|
|
58
|
-
function homeOf(options
|
|
58
|
+
function homeOf(options ) {
|
|
59
59
|
const raw = options && 'home' in options ? options.home : process.env.HOME;
|
|
60
60
|
if (typeof raw !== 'string' || !raw.startsWith('/')) return null;
|
|
61
61
|
const home = raw.replace(/\/+$/, '');
|
|
@@ -63,12 +63,12 @@ function homeOf(options?: any): string | null {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/** A run of 32 or more characters that is a path: starts with `/`, `~` or `.` and has no part of 32+. */
|
|
66
|
-
function isPathRun(run
|
|
66
|
+
function isPathRun(run , before ) {
|
|
67
67
|
if (!(run.startsWith('/') || before === '~' || before === '.')) return false;
|
|
68
|
-
return run.split('/').every((part
|
|
68
|
+
return run.split('/').every((part ) => part.length < 32);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function looksLikeSecretRun(run
|
|
71
|
+
function looksLikeSecretRun(run , minDigits ) {
|
|
72
72
|
const digits = (run.match(/[0-9]/g) || []).length;
|
|
73
73
|
const upper = (run.match(/[A-Z]/g) || []).length;
|
|
74
74
|
const lower = (run.match(/[a-z]/g) || []).length;
|
|
@@ -84,14 +84,14 @@ function looksLikeSecretRun(run: string, minDigits: number): boolean {
|
|
|
84
84
|
const KEY_VALUE_40 = /(?<![A-Za-z0-9+/])[A-Za-z0-9+/]{40}(?![A-Za-z0-9+/])/g;
|
|
85
85
|
|
|
86
86
|
/** Masks the 40-character values inside a run that did not qualify as a whole. */
|
|
87
|
-
function mask40(run
|
|
88
|
-
return run.replace(KEY_VALUE_40, (value
|
|
87
|
+
function mask40(run , before ) {
|
|
88
|
+
return run.replace(KEY_VALUE_40, (value , index ) => (
|
|
89
89
|
looksLikeSecretRun(value, 1) && !isPathRun(value, index === 0 ? before : run[index - 1]) ? MASKED : value));
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
function isMasked(value
|
|
92
|
+
function isMasked(value ) { return value.replace(/^["']|["']$/g, '') === MASKED; }
|
|
93
93
|
|
|
94
|
-
function count(text
|
|
94
|
+
function count(text , token ) { return text.split(token).length - 1; }
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
97
|
* Masks `text` with a profile.
|
|
@@ -102,7 +102,7 @@ function count(text: string, token: string): number { return text.split(token).l
|
|
|
102
102
|
* @returns {{text: string, masked: number}} `masked` counts the values replaced: the net growth in
|
|
103
103
|
* the number of `<masked>`, `<email>` and `<ip>` markers, so a value two rules both reach counts once
|
|
104
104
|
*/
|
|
105
|
-
function mask(text
|
|
105
|
+
function mask(text , profile , options = {}) {
|
|
106
106
|
if (profile !== 'line' && profile !== 'body') throw new TypeError(`unknown mask profile '${profile}'`);
|
|
107
107
|
const input = text == null ? '' : String(text);
|
|
108
108
|
let out = input;
|
|
@@ -119,33 +119,33 @@ function mask(text: any, profile: 'line' | 'body', options: any = {}): { text: s
|
|
|
119
119
|
// 3. Bearer tokens and URL credentials.
|
|
120
120
|
out = out.replace(BEARER, `$1$2${MASKED}`).replace(URL_CREDENTIALS, `://${MASKED}@`);
|
|
121
121
|
// 4. Key=value, unquoted, when the value is not plain letters.
|
|
122
|
-
out = out.replace(KEY_VALUE_LINE, (match
|
|
122
|
+
out = out.replace(KEY_VALUE_LINE, (match , key , separator , value ) => (
|
|
123
123
|
isMasked(value) || value.startsWith(MASKED) || !/[^A-Za-z]/.test(value) ? match : `${key}${separator}${MASKED}`));
|
|
124
124
|
// 5. Long secret-looking runs that are not paths.
|
|
125
|
-
out = out.replace(LONG_RUN, (run
|
|
125
|
+
out = out.replace(LONG_RUN, (run , offset ) => (
|
|
126
126
|
looksLikeSecretRun(run, 2) && !isPathRun(run, out[offset - 1]) ? MASKED : mask40(run, out[offset - 1])));
|
|
127
127
|
// 6. Email addresses and IPv4 addresses.
|
|
128
128
|
out = out.replace(EMAIL_ADDRESS, EMAIL).replace(IPV4, IP);
|
|
129
129
|
|
|
130
130
|
if (profile === 'body') {
|
|
131
131
|
// 7. Key and value, quoted or not, whatever the value looks like.
|
|
132
|
-
out = out.replace(KEY_VALUE_BODY, (match
|
|
132
|
+
out = out.replace(KEY_VALUE_BODY, (match , head , value ) => {
|
|
133
133
|
if (isMasked(value) || value.startsWith(MASKED)) return match;
|
|
134
134
|
const quote = value[0] === '"' || value[0] === "'" ? value[0] : '';
|
|
135
135
|
return `${head}${quote}${MASKED}${quote}`;
|
|
136
136
|
});
|
|
137
137
|
// 8. Authorization headers: everything after the colon.
|
|
138
|
-
out = out.replace(AUTH_HEADER, (match
|
|
138
|
+
out = out.replace(AUTH_HEADER, (match , name , rest ) => (rest === '' || rest === MASKED ? match : `${name} ${MASKED}`));
|
|
139
139
|
// 9. Secret-bearing command-line flags.
|
|
140
|
-
out = out.replace(SECRET_FLAG, (match
|
|
140
|
+
out = out.replace(SECRET_FLAG, (match , flag , value ) => (isMasked(value) ? match : `${flag}${MASKED}`));
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
const masked = Math.max(0, [MASKED, EMAIL, IP].reduce((sum, token) => sum + count(out, token) - count(input, token), 0));
|
|
144
144
|
return { text: out, masked };
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
const maskLine = (text
|
|
148
|
-
const maskBody = (text
|
|
147
|
+
const maskLine = (text , options ) => mask(text, 'line', options);
|
|
148
|
+
const maskBody = (text , options ) => mask(text, 'body', options);
|
|
149
149
|
|
|
150
150
|
const ABSOLUTE_PATH = /(?<![A-Za-z0-9_.:/~-])(?:~(?![A-Za-z0-9_])\/?|\/)[^\s"'`<>:;,()[\]{}]*|[A-Za-z]:\\[^\s"'`<>:;,()[\]{}]*/g;
|
|
151
151
|
|
|
@@ -154,7 +154,7 @@ const ABSOLUTE_PATH = /(?<![A-Za-z0-9_.:/~-])(?:~(?![A-Za-z0-9_])\/?|\/)[^\s"'`<
|
|
|
154
154
|
* @param {*} text
|
|
155
155
|
* @returns {string}
|
|
156
156
|
*/
|
|
157
|
-
function normalise(text
|
|
157
|
+
function normalise(text ) {
|
|
158
158
|
let out = text == null ? '' : String(text);
|
|
159
159
|
out = out.replace(ABSOLUTE_PATH, '<path>'); // 10
|
|
160
160
|
out = out.replace(/"[^"]*"|(?<![A-Za-z0-9])'[^']*'|`[^`]*`/g, '"..."'); // 11
|
|
@@ -168,7 +168,7 @@ function normalise(text: any): string {
|
|
|
168
168
|
* a path with a long mixed-case segment would otherwise be masked by rule 5 as `<masked>` while a
|
|
169
169
|
* short one becomes `<path>`.
|
|
170
170
|
*/
|
|
171
|
-
function normaliseMessage(text
|
|
171
|
+
function normaliseMessage(text , options ) {
|
|
172
172
|
const paths = (text == null ? '' : String(text)).replace(ABSOLUTE_PATH, '<path>');
|
|
173
173
|
return normalise(maskLine(paths, options).text);
|
|
174
174
|
}
|
|
@@ -193,14 +193,14 @@ const REPLACEMENT = '\uFFFD';
|
|
|
193
193
|
* @param {{keepLineBreaks?: boolean}} [options]
|
|
194
194
|
* @returns {T}
|
|
195
195
|
*/
|
|
196
|
-
function printSafe(value
|
|
196
|
+
function printSafe(value , options = {}) {
|
|
197
197
|
if (typeof value === 'string') {
|
|
198
198
|
const spaced = options.keepLineBreaks ? value : value.replace(PRINT_LINE_BREAK, ' ');
|
|
199
199
|
return spaced.replace(PRINT_CONTROL, REPLACEMENT);
|
|
200
200
|
}
|
|
201
|
-
if (Array.isArray(value)) return value.map((item
|
|
201
|
+
if (Array.isArray(value)) return value.map((item ) => printSafe(item, options));
|
|
202
202
|
if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
|
|
203
|
-
const out
|
|
203
|
+
const out = {};
|
|
204
204
|
for (const key of Object.keys(value)) out[key] = printSafe(value[key], options);
|
|
205
205
|
return out;
|
|
206
206
|
}
|
|
@@ -20,7 +20,7 @@ const OUTCOME_STATES = Object.freeze(['consulted-used', 'consulted-nothing-usabl
|
|
|
20
20
|
|
|
21
21
|
/** Five distinct labels (NFR-007). `no-outcome` is derived, never stored. A skipped outcome's
|
|
22
22
|
* text surfaces append its reason: see `outcomeLabel`. */
|
|
23
|
-
const OUTCOME_LABELS
|
|
23
|
+
const OUTCOME_LABELS = Object.freeze({
|
|
24
24
|
'consulted-used': 'consulted, used',
|
|
25
25
|
'consulted-nothing-usable': 'consulted, nothing usable',
|
|
26
26
|
skipped: 'skipped',
|
|
@@ -28,7 +28,7 @@ const OUTCOME_LABELS: Readonly<Record<string, string>> = Object.freeze({
|
|
|
28
28
|
'no-outcome': 'no outcome recorded',
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
const FLAG_TOKEN_STATE
|
|
31
|
+
const FLAG_TOKEN_STATE = Object.freeze({
|
|
32
32
|
used: 'consulted-used',
|
|
33
33
|
'nothing-usable': 'consulted-nothing-usable',
|
|
34
34
|
unavailable: 'unavailable',
|
|
@@ -41,11 +41,11 @@ const CONTENT_LIMIT = 500;
|
|
|
41
41
|
const SCHEMA = 1;
|
|
42
42
|
|
|
43
43
|
// used > nothing usable > unavailable > skipped (IC-008 rule 2).
|
|
44
|
-
const PRECEDENCE
|
|
44
|
+
const PRECEDENCE = Object.freeze({
|
|
45
45
|
'consulted-used': 4, 'consulted-nothing-usable': 3, unavailable: 2, skipped: 1,
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
function outcomeLabel(outcome
|
|
48
|
+
function outcomeLabel(outcome ) {
|
|
49
49
|
const label = OUTCOME_LABELS[outcome.state];
|
|
50
50
|
return outcome.state === 'skipped' && outcome.reason ? `${label}: ${outcome.reason}` : label;
|
|
51
51
|
}
|
|
@@ -54,7 +54,7 @@ function outcomeLabel(outcome: any): string {
|
|
|
54
54
|
* Parse one outcome token (IC-005 flag token). Returns `{ state, reason? }` or `{ error }`.
|
|
55
55
|
* @param {string} token
|
|
56
56
|
*/
|
|
57
|
-
function parseOutcomeToken(token
|
|
57
|
+
function parseOutcomeToken(token ) {
|
|
58
58
|
const text = String(token ?? '').trim();
|
|
59
59
|
if (Object.hasOwn(FLAG_TOKEN_STATE, text)) return { state: FLAG_TOKEN_STATE[text] };
|
|
60
60
|
if (text === 'skipped' || text === 'skipped:') return { error: 'skipped needs a reason: skipped:<reason>' };
|
|
@@ -72,10 +72,10 @@ function parseOutcomeToken(token: any): any {
|
|
|
72
72
|
* @param {string[]|string|undefined} values
|
|
73
73
|
* @returns {{ outcomes: Map<string, {state: string, reason?: string}>, ignored: Array<{value: string, reason: string}> }}
|
|
74
74
|
*/
|
|
75
|
-
function parseMcpOutcomeFlags(values
|
|
76
|
-
const list
|
|
77
|
-
const outcomes = new Map
|
|
78
|
-
const ignored
|
|
75
|
+
function parseMcpOutcomeFlags(values ) {
|
|
76
|
+
const list = values === undefined || values === null ? [] : ([] ).concat(values);
|
|
77
|
+
const outcomes = new Map ();
|
|
78
|
+
const ignored = [];
|
|
79
79
|
for (const raw of list) {
|
|
80
80
|
const value = String(raw);
|
|
81
81
|
const at = value.indexOf('=');
|
|
@@ -94,7 +94,7 @@ function parseMcpOutcomeFlags(values?: string[] | string | null): { outcomes: Ma
|
|
|
94
94
|
|
|
95
95
|
/** Strip a leading `- `, `**...**` emphasis and a line-wide pair of backticks, so
|
|
96
96
|
* `- **Tools used:** a` and `` `Tools used: a` `` both read as `Tools used: a`. */
|
|
97
|
-
function plainLine(line
|
|
97
|
+
function plainLine(line ) {
|
|
98
98
|
let text = line.trim().replace(/^-\s+/, '');
|
|
99
99
|
if (text.startsWith('**')) text = text.slice(2).replace('**', '');
|
|
100
100
|
if (text.startsWith('`Tools used:') && text.endsWith('`')) text = text.slice(1, -1);
|
|
@@ -102,8 +102,8 @@ function plainLine(line: string): string {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/** Split on commas outside parentheses, so a note like `(a, b)` stays with its entry. */
|
|
105
|
-
function splitEntries(body
|
|
106
|
-
const pieces
|
|
105
|
+
function splitEntries(body ) {
|
|
106
|
+
const pieces = [];
|
|
107
107
|
let depth = 0;
|
|
108
108
|
let current = '';
|
|
109
109
|
for (const char of body) {
|
|
@@ -116,7 +116,7 @@ function splitEntries(body: string): string[] {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/** One entry with a trailing ` (note)` and a surrounding pair of backticks removed. */
|
|
119
|
-
function plainEntry(piece
|
|
119
|
+
function plainEntry(piece ) {
|
|
120
120
|
let text = piece.trim().replace(/\s+\([^()]*\)$/, '').trim();
|
|
121
121
|
if (text.length > 1 && text.startsWith('`') && text.endsWith('`')) text = text.slice(1, -1).trim();
|
|
122
122
|
return text;
|
|
@@ -127,7 +127,7 @@ function plainEntry(piece: string): string {
|
|
|
127
127
|
* @param {string} text report text
|
|
128
128
|
* @returns {null | { line: number, raw: string, entries: Array<{name: string, state?: string, reason?: string, raw: string, valid: boolean}> }}
|
|
129
129
|
*/
|
|
130
|
-
function parseToolsUsed(text
|
|
130
|
+
function parseToolsUsed(text ) {
|
|
131
131
|
const lines = String(text ?? '').split('\n');
|
|
132
132
|
let found = -1;
|
|
133
133
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -136,7 +136,7 @@ function parseToolsUsed(text: any): any {
|
|
|
136
136
|
if (found === -1) return null;
|
|
137
137
|
const raw = lines[found].replace(/\s+$/, '').trim();
|
|
138
138
|
const body = plainLine(lines[found]).slice('Tools used:'.length).trim();
|
|
139
|
-
const entries
|
|
139
|
+
const entries = [];
|
|
140
140
|
if (body !== '' && body !== 'none') {
|
|
141
141
|
for (const piece of splitEntries(body)) {
|
|
142
142
|
const entry = plainEntry(piece);
|
|
@@ -154,9 +154,9 @@ function parseToolsUsed(text: any): any {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/** Server ids the registry knows: `mcp` servers plus every provider's `mcp.server`. */
|
|
157
|
-
function knownServerIds(registry
|
|
158
|
-
const ids = new Set
|
|
159
|
-
for (const capability of Object.values(registry?.capabilities || {})
|
|
157
|
+
function knownServerIds(registry ) {
|
|
158
|
+
const ids = new Set ((registry?.mcp || []).map((server ) => server.id));
|
|
159
|
+
for (const capability of Object.values(registry?.capabilities || {}) ) {
|
|
160
160
|
for (const provider of capability?.providers || []) {
|
|
161
161
|
if (provider?.mcp?.server) ids.add(provider.mcp.server);
|
|
162
162
|
}
|
|
@@ -168,17 +168,17 @@ function knownServerIds(registry: any): Set<string> {
|
|
|
168
168
|
* Servers a stage run names (IC-001 meaning). Sorted.
|
|
169
169
|
* @param {{registry: object, skill: string, stageId?: string, taskId?: string, requests?: {list: Function}}} input
|
|
170
170
|
*/
|
|
171
|
-
function namedServers({ registry, skill, stageId, taskId, requests }
|
|
172
|
-
let researchRequested
|
|
173
|
-
const hasRequest = ()
|
|
171
|
+
function namedServers({ registry, skill, stageId, taskId, requests } ) {
|
|
172
|
+
let researchRequested ;
|
|
173
|
+
const hasRequest = () => {
|
|
174
174
|
if (researchRequested === undefined) {
|
|
175
|
-
let list
|
|
175
|
+
let list = [];
|
|
176
176
|
try { list = requests && taskId ? requests.list(taskId) : []; } catch { list = []; }
|
|
177
|
-
researchRequested = Array.isArray(list) && list.some((request
|
|
177
|
+
researchRequested = Array.isArray(list) && list.some((request ) => request && request.stageId === stageId);
|
|
178
178
|
}
|
|
179
179
|
return researchRequested;
|
|
180
180
|
};
|
|
181
|
-
const named = new Set
|
|
181
|
+
const named = new Set ();
|
|
182
182
|
for (const server of registry?.mcp || []) {
|
|
183
183
|
for (const step of server.steps || []) {
|
|
184
184
|
if (step.expectedWhen === 'research-request') {
|
|
@@ -191,7 +191,7 @@ function namedServers({ registry, skill, stageId, taskId, requests }: any): stri
|
|
|
191
191
|
return [...named].sort();
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
const bySortKey = (a
|
|
194
|
+
const bySortKey = (a , b ) => (a < b ? -1 : a > b ? 1 : 0);
|
|
195
195
|
|
|
196
196
|
/**
|
|
197
197
|
* Resolve a stage run's MCP consultations (IC-008).
|
|
@@ -213,11 +213,11 @@ const bySortKey = (a: any, b: any): number => (a < b ? -1 : a > b ? 1 : 0);
|
|
|
213
213
|
function resolveConsultations({
|
|
214
214
|
taskId = null, stageId = null, skill, flags, registry, ledger, requests,
|
|
215
215
|
briefRecords = [], readReport, reach, addCommand, harnessName, since = null,
|
|
216
|
-
}
|
|
217
|
-
const warnings
|
|
218
|
-
const notices
|
|
219
|
-
const evidenceToAdd
|
|
220
|
-
const registered = new Set((registry?.mcp || []).map((server
|
|
216
|
+
} = {}) {
|
|
217
|
+
const warnings = [];
|
|
218
|
+
const notices = [];
|
|
219
|
+
const evidenceToAdd = [];
|
|
220
|
+
const registered = new Set((registry?.mcp || []).map((server ) => server.id));
|
|
221
221
|
const known = knownServerIds(registry);
|
|
222
222
|
const named = namedServers({ registry, skill, stageId, taskId, requests });
|
|
223
223
|
|
|
@@ -232,28 +232,28 @@ function resolveConsultations({
|
|
|
232
232
|
|
|
233
233
|
// Rule 2: Tools used lines in the reports of this run's briefs. Records with no stage are never
|
|
234
234
|
// ingested (design A6).
|
|
235
|
-
const toolsUsed
|
|
236
|
-
const fromReports = new Map
|
|
235
|
+
const toolsUsed = [];
|
|
236
|
+
const fromReports = new Map ();
|
|
237
237
|
const records = (Array.isArray(briefRecords) ? briefRecords : [])
|
|
238
|
-
.filter((record
|
|
239
|
-
const reports
|
|
238
|
+
.filter((record ) => record && stageId && taskId && record.stage === stageId && record.runTaskId === taskId);
|
|
239
|
+
const reports = [];
|
|
240
240
|
for (const record of records) {
|
|
241
241
|
for (const task of record.tasks || []) {
|
|
242
242
|
if (task && typeof task.id === 'string' && typeof task.report === 'string') reports.push({ id: task.id, report: task.report });
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
245
|
reports.sort((a, b) => bySortKey(a.id, b.id) || bySortKey(a.report, b.report));
|
|
246
|
-
const existing = new Map
|
|
247
|
-
const existingFor = (id
|
|
246
|
+
const existing = new Map ();
|
|
247
|
+
const existingFor = (id ) => {
|
|
248
248
|
if (!existing.has(id)) {
|
|
249
|
-
let items
|
|
249
|
+
let items = [];
|
|
250
250
|
try { items = ledger ? ledger.queryEvidence({ taskId: id }) : []; } catch { items = []; }
|
|
251
|
-
existing.set(id, items.filter((item
|
|
251
|
+
existing.set(id, items.filter((item ) => item && item.status === 'active'));
|
|
252
252
|
}
|
|
253
|
-
return existing.get(id)
|
|
253
|
+
return existing.get(id) ;
|
|
254
254
|
};
|
|
255
255
|
for (const { id, report } of reports) {
|
|
256
|
-
let text
|
|
256
|
+
let text = null;
|
|
257
257
|
try { text = typeof readReport === 'function' ? readReport(report) : null; } catch { text = null; }
|
|
258
258
|
if (typeof text !== 'string') {
|
|
259
259
|
toolsUsed.push({ taskId: id, status: 'no-report', report });
|
|
@@ -274,9 +274,9 @@ function resolveConsultations({
|
|
|
274
274
|
}
|
|
275
275
|
const source = { provider: 'dispatched-agent', capability: 'tools-used' };
|
|
276
276
|
const content = parsed.raw.slice(0, CONTENT_LIMIT);
|
|
277
|
-
const duplicate = existingFor(id).find((item
|
|
277
|
+
const duplicate = existingFor(id).find((item ) => item.source?.provider === source.provider
|
|
278
278
|
&& item.source?.capability === source.capability && item.content === content);
|
|
279
|
-
const inBatch = evidenceToAdd.some((item
|
|
279
|
+
const inBatch = evidenceToAdd.some((item ) => item.taskId === id && item.content === content);
|
|
280
280
|
if (duplicate) {
|
|
281
281
|
toolsUsed.push({ taskId: id, status: 'already-recorded', evidenceId: duplicate.id });
|
|
282
282
|
} else if (inBatch) {
|
|
@@ -301,10 +301,10 @@ function resolveConsultations({
|
|
|
301
301
|
|
|
302
302
|
// Rule 3: evidence attributed to a server by `source.provider`, observed during this stage run:
|
|
303
303
|
// after `since` (the previous stage's completion or the run start) when the caller knows it.
|
|
304
|
-
const evidenceServers = new Set
|
|
305
|
-
const inThisStage = (item
|
|
304
|
+
const evidenceServers = new Set ();
|
|
305
|
+
const inThisStage = (item ) => since === null || (typeof item.freshness?.observedAt === 'string' && item.freshness.observedAt > since);
|
|
306
306
|
if (ledger && taskId) {
|
|
307
|
-
let items
|
|
307
|
+
let items = [];
|
|
308
308
|
try { items = ledger.queryEvidence({ taskId }); } catch { items = []; }
|
|
309
309
|
for (const item of items) {
|
|
310
310
|
if (item && item.status === 'active' && known.has(item.source?.provider) && inThisStage(item)) evidenceServers.add(item.source.provider);
|
|
@@ -313,8 +313,8 @@ function resolveConsultations({
|
|
|
313
313
|
|
|
314
314
|
// Rule 4: a brief record for this stage that expected the server and found it unreachable. The
|
|
315
315
|
// brief writes `unreachable` only where every config the harness merges was read (IC-017).
|
|
316
|
-
const unreachable = new Map
|
|
317
|
-
const reachableOn = new Map
|
|
316
|
+
const unreachable = new Map ();
|
|
317
|
+
const reachableOn = new Map ();
|
|
318
318
|
for (const record of records) {
|
|
319
319
|
for (const expected of record.expected || []) {
|
|
320
320
|
if (!expected || typeof expected.server !== 'string') continue;
|
|
@@ -324,16 +324,16 @@ function resolveConsultations({
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
/** Who has a server the run asserted unavailable: the brief's harness, else an installed one. */
|
|
327
|
-
const nameOf = (id
|
|
328
|
-
const contradiction = (server
|
|
327
|
+
const nameOf = (id ) => (typeof harnessName === 'function' && harnessName(id)) || id;
|
|
328
|
+
const contradiction = (server ) => {
|
|
329
329
|
if (reachableOn.has(server)) {
|
|
330
330
|
const id = reachableOn.get(server);
|
|
331
331
|
return id ? nameOf(id) : 'an installed harness';
|
|
332
332
|
}
|
|
333
|
-
let has
|
|
333
|
+
let has = null;
|
|
334
334
|
try { has = reach && typeof reach.anyHarnessHas === 'function' ? reach.anyHarnessHas(server) : null; } catch { has = null; }
|
|
335
335
|
if (has !== true) return null;
|
|
336
|
-
let name
|
|
336
|
+
let name = null;
|
|
337
337
|
try { name = typeof reach.harnessWith === 'function' ? reach.harnessWith(server) : null; } catch { name = null; }
|
|
338
338
|
return name || 'an installed harness';
|
|
339
339
|
};
|
|
@@ -341,14 +341,14 @@ function resolveConsultations({
|
|
|
341
341
|
const candidates = new Set([
|
|
342
342
|
...named, ...parsedFlags.outcomes.keys(), ...fromReports.keys(), ...evidenceServers, ...unreachable.keys(),
|
|
343
343
|
]);
|
|
344
|
-
const outcomes
|
|
344
|
+
const outcomes = [];
|
|
345
345
|
for (const server of [...candidates].sort()) {
|
|
346
|
-
let outcome
|
|
346
|
+
let outcome = null;
|
|
347
347
|
const flag = parsedFlags.outcomes.get(server);
|
|
348
348
|
if (flag) {
|
|
349
349
|
outcome = { server, state: flag.state, ...(flag.reason ? { reason: flag.reason } : {}), source: 'flag' };
|
|
350
350
|
} else if (fromReports.has(server)) {
|
|
351
|
-
const best = (fromReports.get(server)
|
|
351
|
+
const best = (fromReports.get(server) ).reduce((a , b ) => (PRECEDENCE[b.state] > PRECEDENCE[a.state] ? b : a));
|
|
352
352
|
outcome = {
|
|
353
353
|
server, state: best.state, ...(best.reason ? { reason: best.reason } : {}), source: 'tools-used', taskId: best.taskId,
|
|
354
354
|
};
|
|
@@ -17,16 +17,16 @@
|
|
|
17
17
|
const fs = require('node:fs');
|
|
18
18
|
const os = require('node:os');
|
|
19
19
|
const path = require('node:path');
|
|
20
|
-
const { readLedger } = require('../state/index.
|
|
21
|
-
const { resolveAdapter } = require('../adapters/index.
|
|
22
|
-
const { evaluateReach } = require('./reach.
|
|
20
|
+
const { readLedger } = require('../state/index.js');
|
|
21
|
+
const { resolveAdapter } = require('../adapters/index.js');
|
|
22
|
+
const { evaluateReach } = require('./reach.js');
|
|
23
23
|
|
|
24
|
-
const SCOPE_ORDER
|
|
24
|
+
const SCOPE_ORDER = { project: 0, global: 1 };
|
|
25
25
|
|
|
26
26
|
/** Every server id the answer covers: the MCP catalog, then each provider's own MCP server. */
|
|
27
|
-
function serverIdsOf(registry
|
|
28
|
-
const ids = (registry.mcp || []).map((server
|
|
29
|
-
for (const capability of (Object.values(registry.capabilities || {})
|
|
27
|
+
function serverIdsOf(registry ) {
|
|
28
|
+
const ids = (registry.mcp || []).map((server ) => server.id);
|
|
29
|
+
for (const capability of (Object.values(registry.capabilities || {}) )) {
|
|
30
30
|
for (const provider of capability.providers || []) if (provider.mcp?.server) ids.push(provider.mcp.server);
|
|
31
31
|
}
|
|
32
32
|
return [...new Set(ids)];
|
|
@@ -34,16 +34,16 @@ function serverIdsOf(registry: any) {
|
|
|
34
34
|
|
|
35
35
|
/** The MCP file an adapter's discovery could not read and the fixed reason it gives, or null.
|
|
36
36
|
* Reasons come from the adapters (invalid JSON, wrong shape), never from file content. */
|
|
37
|
-
function unreadableDetail(discovery
|
|
38
|
-
for (const [file, read] of (Object.entries(discovery.mcpSnapshot || {})
|
|
39
|
-
for (const [file, read] of (Object.entries(discovery.mcp || {})
|
|
37
|
+
function unreadableDetail(discovery ) {
|
|
38
|
+
for (const [file, read] of (Object.entries(discovery.mcpSnapshot || {}) )) if (read?.ok === false) return { detail: read.reason, file };
|
|
39
|
+
for (const [file, read] of (Object.entries(discovery.mcp || {}) )) if (read?.doc?.ok === false) return { detail: read.doc.reason, file };
|
|
40
40
|
if (discovery.settings?.error) return { detail: 'invalid JSON', ...(discovery.paths?.settings ? { file: discovery.paths.settings } : {}) };
|
|
41
41
|
return null;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/** A server is absent (`not-taken`, `not-selected`) only where the adapter read every source its
|
|
45
45
|
* harness merges (`mcpComplete: true`); anywhere else absence is no proof and the state is unknown. */
|
|
46
|
-
function stateOf(row
|
|
46
|
+
function stateOf(row , id ) {
|
|
47
47
|
if (row.selected.has(id)) return 'selected';
|
|
48
48
|
if (row.configured?.includes(id)) return 'configured';
|
|
49
49
|
if (row.unreadable || row.configured === null || !row.complete) return 'unknown';
|
|
@@ -59,14 +59,14 @@ function stateOf(row: any, id: any) {
|
|
|
59
59
|
* @param {Object} options.adapters adapter registry (`buildAdapterRegistry()`)
|
|
60
60
|
* @returns {{harnesses: Object[], unreadable: Object[]}} plain data; `anyHarnessHas` and `addCommand` ride along unenumerated
|
|
61
61
|
*/
|
|
62
|
-
function mcpReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs, adapters }
|
|
62
|
+
function mcpReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs, adapters } ) {
|
|
63
63
|
const serverIds = serverIdsOf(registry);
|
|
64
|
-
const catalogIds = (registry.mcp || []).map((server
|
|
65
|
-
const harnessOrder = registry.harnesses.map((harness
|
|
64
|
+
const catalogIds = (registry.mcp || []).map((server ) => server.id);
|
|
65
|
+
const harnessOrder = registry.harnesses.map((harness ) => harness.id);
|
|
66
66
|
const { rows: reached } = evaluateReach({ registry, projectRoot, homeDir, fsImpl });
|
|
67
67
|
|
|
68
68
|
const ledgers = new Map();
|
|
69
|
-
const ledgerOf = ({ scope, installRoot }
|
|
69
|
+
const ledgerOf = ({ scope, installRoot } ) => {
|
|
70
70
|
const key = `${scope}\0${installRoot}`;
|
|
71
71
|
if (!ledgers.has(key)) {
|
|
72
72
|
let ledger = null;
|
|
@@ -78,12 +78,12 @@ function mcpReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs,
|
|
|
78
78
|
|
|
79
79
|
const unreadable = [];
|
|
80
80
|
const seen = new Set();
|
|
81
|
-
const rows
|
|
81
|
+
const rows = [];
|
|
82
82
|
for (const { harness: id, scope, installRoot } of reached) {
|
|
83
83
|
const key = `${id}\0${scope}\0${installRoot}`;
|
|
84
84
|
if (seen.has(key)) continue;
|
|
85
85
|
seen.add(key);
|
|
86
|
-
const harness = registry.harnesses.find((item
|
|
86
|
+
const harness = registry.harnesses.find((item ) => item.id === id);
|
|
87
87
|
const ledger = ledgerOf({ scope, installRoot });
|
|
88
88
|
const mcp = harness.capabilities?.mcp;
|
|
89
89
|
const row = {
|
|
@@ -91,7 +91,7 @@ function mcpReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs,
|
|
|
91
91
|
catalog: mcp?.status === 'supported' ? 'taken' : 'not-taken',
|
|
92
92
|
note: mcp?.note ?? mcp?.verification,
|
|
93
93
|
selected: new Set((ledger?.resources || [])
|
|
94
|
-
.filter((resource
|
|
94
|
+
.filter((resource ) => resource.harness === id && resource.kind === 'mcp-server').map((resource ) => resource.identity)),
|
|
95
95
|
configured: null,
|
|
96
96
|
complete: false,
|
|
97
97
|
unreadable: false,
|
|
@@ -104,7 +104,7 @@ function mcpReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs,
|
|
|
104
104
|
row.complete = discovery.mcpComplete === true;
|
|
105
105
|
const found = unreadableDetail(discovery);
|
|
106
106
|
if (found) { row.unreadable = true; unreadable.push({ harness: id, scope, ...found }); }
|
|
107
|
-
} catch (error
|
|
107
|
+
} catch (error ) {
|
|
108
108
|
row.unreadable = true;
|
|
109
109
|
unreadable.push({ harness: id, scope, detail: error.message });
|
|
110
110
|
}
|
|
@@ -115,7 +115,7 @@ function mcpReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs,
|
|
|
115
115
|
|
|
116
116
|
const result = {
|
|
117
117
|
harnesses: rows.map((row) => {
|
|
118
|
-
const entry
|
|
118
|
+
const entry = { id: row.id, scope: row.scope, catalog: row.catalog };
|
|
119
119
|
if (row.catalog === 'not-taken' && row.note) entry.note = row.note;
|
|
120
120
|
entry.servers = Object.fromEntries(serverIds.map((server) => [server, stateOf(row, server)]));
|
|
121
121
|
return entry;
|
|
@@ -126,15 +126,15 @@ function mcpReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs,
|
|
|
126
126
|
/** `true` when a server is selected or configured on some installed harness, `false` only when
|
|
127
127
|
* every installed harness read every source it merges and says it is absent, `null` (unknown)
|
|
128
128
|
* otherwise, including when none is installed. */
|
|
129
|
-
const anyHarnessHas = (server
|
|
129
|
+
const anyHarnessHas = (server ) => {
|
|
130
130
|
const states = rows.map((row) => stateOf(row, server));
|
|
131
131
|
if (states.some((state) => state === 'selected' || state === 'configured')) return true;
|
|
132
132
|
return states.length > 0 && states.every((state) => state === 'not-selected' || state === 'not-taken') ? false : null;
|
|
133
133
|
};
|
|
134
134
|
|
|
135
135
|
/** The command that adds `serverId` for a harness at a scope, or where to configure it by hand. */
|
|
136
|
-
const addCommand = (harnessId
|
|
137
|
-
const harness = registry.harnesses.find((item
|
|
136
|
+
const addCommand = (harnessId , scope , serverId ) => {
|
|
137
|
+
const harness = registry.harnesses.find((item ) => item.id === harnessId);
|
|
138
138
|
const name = harness?.displayName || harnessId;
|
|
139
139
|
if (harness?.capabilities?.mcp?.status !== 'supported') {
|
|
140
140
|
return `configure it in ${name}'s own MCP settings; DoFlow's catalog does not reach it`;
|
|
@@ -144,7 +144,7 @@ function mcpReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs,
|
|
|
144
144
|
}
|
|
145
145
|
const chosen = new Set([serverId]);
|
|
146
146
|
for (const row of rows) if (row.id === harnessId && row.scope === scope) for (const id of row.selected) chosen.add(id);
|
|
147
|
-
return `doflow install${scope === 'global' ? ' -g' : ''} --target ${harnessId} --mcp ${catalogIds.filter((id
|
|
147
|
+
return `doflow install${scope === 'global' ? ' -g' : ''} --target ${harnessId} --mcp ${catalogIds.filter((id ) => chosen.has(id)).join(',')}`;
|
|
148
148
|
};
|
|
149
149
|
|
|
150
150
|
Object.defineProperties(result, {
|