@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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
// Harness adapters own native formats and paths. This module intentionally only
|
|
6
6
|
// validates and resolves that boundary; it never reads or writes harness files.
|
|
7
|
-
const { resolveRegistrySource } = require('./resolve-source.
|
|
7
|
+
const { resolveRegistrySource } = require('./resolve-source.js');
|
|
8
8
|
|
|
9
9
|
const ADAPTER_METHODS = Object.freeze(['discover', 'render', 'plan', 'apply', 'remove', 'verify']);
|
|
10
10
|
|
|
@@ -12,12 +12,12 @@ const ADAPTER_METHODS = Object.freeze(['discover', 'render', 'plan', 'apply', 'r
|
|
|
12
12
|
* A3: the Codex shape used to be inlined here, putting per-harness knowledge on the shared side of
|
|
13
13
|
* the boundary); this module only dispatches by adapter name. An adapter gaining a
|
|
14
14
|
* nativeProjection declaration registers its normalizer here. */
|
|
15
|
-
const NATIVE_PROJECTION_NORMALIZERS
|
|
16
|
-
codex: () => require('./codex/native-projection.
|
|
15
|
+
const NATIVE_PROJECTION_NORMALIZERS = Object.freeze({
|
|
16
|
+
codex: () => require('./codex/native-projection.js').normalizeNativeProjection,
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
/** Dispatch a harness's nativeProjection declaration to its adapter's own normalizer. */
|
|
20
|
-
function nativeProjectionFor(registry
|
|
20
|
+
function nativeProjectionFor(registry , harness , mcp , context ) {
|
|
21
21
|
if (!harness.nativeProjection) return {};
|
|
22
22
|
const load = NATIVE_PROJECTION_NORMALIZERS[harness.adapter];
|
|
23
23
|
if (!load) {
|
|
@@ -32,9 +32,9 @@ function nativeProjectionFor(registry: any, harness: any, mcp: any, context: any
|
|
|
32
32
|
* adapter receives. Native serializers may add adapter-specific fields, but
|
|
33
33
|
* cannot infer applicability from a missing projection.
|
|
34
34
|
*/
|
|
35
|
-
function projectAdapterInput({ registry, harness, scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], policies = [], context = {} }
|
|
35
|
+
function projectAdapterInput({ registry, harness, scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], policies = [], context = {} } ) {
|
|
36
36
|
if (!registry || !harness) throw new Error('registry and harness are required for adapter projection');
|
|
37
|
-
const projectAsset = (asset
|
|
37
|
+
const projectAsset = (asset ) => {
|
|
38
38
|
const projection = asset?.projection?.[harness.id];
|
|
39
39
|
if (!projection || typeof projection.renderer !== 'string' || typeof projection.capability !== 'string') {
|
|
40
40
|
throw new Error(`Asset '${asset?.id ?? '?'}' lacks a projection for '${harness.id}'`);
|
|
@@ -62,16 +62,16 @@ function projectAdapterInput({ registry, harness, scope, scopeRoot, assets = [],
|
|
|
62
62
|
return Object.freeze({
|
|
63
63
|
harness, scope, scopeRoot, context,
|
|
64
64
|
assets: Object.freeze(assets.map(projectAsset)),
|
|
65
|
-
mcp: Object.freeze(mcp.map((server
|
|
65
|
+
mcp: Object.freeze(mcp.map((server ) => Object.freeze({ ...server }))),
|
|
66
66
|
// Servers whose existing entries the adapter may take as its own while it holds no MCP row.
|
|
67
|
-
mcpAdoptable: Object.freeze(mcpAdoptable.map((server
|
|
68
|
-
policies: Object.freeze(policies.map((policy
|
|
67
|
+
mcpAdoptable: Object.freeze(mcpAdoptable.map((server ) => Object.freeze({ ...server }))),
|
|
68
|
+
policies: Object.freeze(policies.map((policy ) => Object.freeze({ ...policy }))),
|
|
69
69
|
nativeTargets: Object.freeze({ ...harness.nativeTargets }),
|
|
70
70
|
projection: Object.freeze({ ...projection }),
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
function assertAdapter(adapter
|
|
74
|
+
function assertAdapter(adapter , name = 'adapter') {
|
|
75
75
|
if (!adapter || typeof adapter !== 'object') throw new Error(`${name} must be an object`);
|
|
76
76
|
for (const method of ADAPTER_METHODS) {
|
|
77
77
|
if (typeof adapter[method] !== 'function') throw new Error(`${name} must implement ${method}()`);
|
|
@@ -88,8 +88,8 @@ function createAdapterRegistry(adapters = {}) {
|
|
|
88
88
|
}
|
|
89
89
|
return Object.freeze({
|
|
90
90
|
names: () => [...resolved.keys()],
|
|
91
|
-
has: (name
|
|
92
|
-
get(name
|
|
91
|
+
has: (name ) => resolved.has(name),
|
|
92
|
+
get(name ) {
|
|
93
93
|
const adapter = resolved.get(name);
|
|
94
94
|
if (!adapter) throw new Error(`No registered adapter '${name}'`);
|
|
95
95
|
return adapter;
|
|
@@ -97,7 +97,7 @@ function createAdapterRegistry(adapters = {}) {
|
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
function resolveAdapter(adapterRegistry
|
|
100
|
+
function resolveAdapter(adapterRegistry , harness ) {
|
|
101
101
|
if (!adapterRegistry || typeof adapterRegistry.get !== 'function') throw new Error('adapterRegistry must expose get(name)');
|
|
102
102
|
if (!harness || typeof harness.adapter !== 'string') throw new Error('Harness must declare an adapter');
|
|
103
103
|
return adapterRegistry.get(harness.adapter);
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
// https://kiro.dev/docs/hooks/
|
|
18
18
|
const fs = require('node:fs');
|
|
19
19
|
const path = require('node:path');
|
|
20
|
-
const { planTree, applyTree, removeTree, verifyTree, resolveTransform, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, sourceDirFor } = require('../copy-tree.
|
|
21
|
-
const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.
|
|
22
|
-
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.
|
|
20
|
+
const { planTree, applyTree, removeTree, verifyTree, resolveTransform, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, sourceDirFor } = require('../copy-tree.js');
|
|
21
|
+
const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.js');
|
|
22
|
+
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
|
|
23
23
|
|
|
24
24
|
const HARNESS = 'kiro';
|
|
25
25
|
const MCP_CONTAINER = 'mcpServers';
|
|
@@ -41,13 +41,13 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
41
41
|
// Parity note: Kiro's historical nativePaths() derived even its user-scope root from scopeRoot
|
|
42
42
|
// (the CLI passes $HOME there for -g installs), never from a separate homeDir argument — so the
|
|
43
43
|
// resolver is fed no homeDir and "root" keeps resolving exactly as before.
|
|
44
|
-
function nativePaths({ scope, scopeRoot }
|
|
44
|
+
function nativePaths({ scope, scopeRoot } = {}) {
|
|
45
45
|
return resolveHarnessPaths(declaredPaths, { scope, scopeRoot });
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/** Discovery keeps the MCP files as plan read them (`mcpSnapshot`), which verify compares against,
|
|
49
49
|
* and reports the servers DoFlow owns in Kiro's mcp.json now (`mcpOwned`). */
|
|
50
|
-
function discover({ scope, scopeRoot, mcp = [], mcpAdoptable = [], ledger, fsImpl = fs }
|
|
50
|
+
function discover({ scope, scopeRoot, mcp = [], mcpAdoptable = [], ledger, fsImpl = fs } ) {
|
|
51
51
|
const paths = nativePaths({ scope, scopeRoot });
|
|
52
52
|
const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
|
|
53
53
|
const mcpSnapshot = readMcpFiles({ file: entries.file, ownRows: entries.ownRows, container: MCP_CONTAINER, fsImpl });
|
|
@@ -65,7 +65,7 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
65
65
|
* `kiro-agents`, so nothing reaches this function. It is kept as a pure passthrough only to
|
|
66
66
|
* satisfy the six-function adapter contract every harness must implement identically.
|
|
67
67
|
*/
|
|
68
|
-
function render({ content = '' }
|
|
68
|
+
function render({ content = '' } = {}) {
|
|
69
69
|
return String(content);
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -84,11 +84,11 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
84
84
|
* `kiro-agent-json` layout and `kiro-agents` transform: kiro-cli discovers agents as
|
|
85
85
|
* `.kiro/agents/<name>.json`, so each shared `<name>.md` spec is rendered, not mirrored.
|
|
86
86
|
*/
|
|
87
|
-
function kiroTreeAssets(assets
|
|
88
|
-
return [...copyTreeAssets(assets), ...(assets || []).filter((asset
|
|
87
|
+
function kiroTreeAssets(assets ) {
|
|
88
|
+
return [...copyTreeAssets(assets), ...(assets || []).filter((asset ) => asset.renderer === 'kiro-agents')];
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing, fsImpl = fs }
|
|
91
|
+
function planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing, fsImpl = fs } ) {
|
|
92
92
|
const paths = nativePaths({ scope, scopeRoot });
|
|
93
93
|
const changes = [];
|
|
94
94
|
const conflicts = [];
|
|
@@ -105,10 +105,10 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
105
105
|
// is never deleted on removal, forced or not.
|
|
106
106
|
force: !removing && context?.force === true, });
|
|
107
107
|
treeResults.push(result);
|
|
108
|
-
conflicts.push(...result.conflicts.map((reason
|
|
108
|
+
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
109
109
|
// A hand-edited file at a path DoFlow no longer writes is the user's: it stays, only its
|
|
110
110
|
// ownership row is released.
|
|
111
|
-
notices.push(...result.kept.map((item
|
|
111
|
+
notices.push(...result.kept.map((item ) => `kept hand-edited ${path.relative(paths.root, item.target)}; DoFlow no longer manages it`));
|
|
112
112
|
for (const change of result.changes) {
|
|
113
113
|
changes.push({
|
|
114
114
|
assetId: asset.id, target: change.target, source: change.source, operation: change.operation,
|
|
@@ -124,7 +124,7 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
124
124
|
return { changes, conflicts, notices: [...siblingReplacedNotices(treeResults), ...notices] };
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
function applyCopyTreeAssets(changes
|
|
127
|
+
function applyCopyTreeAssets(changes , { fsImpl = fs } = {}) {
|
|
128
128
|
let applied = 0;
|
|
129
129
|
const grouped = new Map();
|
|
130
130
|
for (const change of changes) {
|
|
@@ -139,13 +139,13 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
139
139
|
return applied;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
function removeCopyTreeAssets(changes
|
|
143
|
-
const treeChanges = changes.filter((change
|
|
144
|
-
.map((change
|
|
142
|
+
function removeCopyTreeAssets(changes , { fsImpl = fs } = {}) {
|
|
143
|
+
const treeChanges = changes.filter((change ) => change.kind === 'copy-tree-file' && change.operation === 'remove' && !change.retained)
|
|
144
|
+
.map((change ) => ({ relPath: change.identity, target: change.target, operation: 'remove', fingerprint: change.fingerprint, pruneGuard: change.pruneGuard }));
|
|
145
145
|
return removeTree({ changes: treeChanges, fsImpl }).removed;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
function verifyCopyTreeAssets({ assets, scope, scopeRoot, context, fsImpl = fs }
|
|
148
|
+
function verifyCopyTreeAssets({ assets, scope, scopeRoot, context, fsImpl = fs } ) {
|
|
149
149
|
const paths = nativePaths({ scope, scopeRoot });
|
|
150
150
|
const statuses = [];
|
|
151
151
|
const resources = [];
|
|
@@ -154,7 +154,7 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
154
154
|
const destDir = copyTreeDestDir(paths.configDir, asset);
|
|
155
155
|
const sourceDir = sourceDirFor(asset, context, fsImpl, 'Kiro');
|
|
156
156
|
const result = verifyTree({ sourceDir, destDir, fsImpl, layout: asset.layout, transform: asset.transform, plannedWrites: context?.plannedWrites });
|
|
157
|
-
conflicts.push(...result.conflicts.map((reason
|
|
157
|
+
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
158
158
|
for (const resource of result.resources) {
|
|
159
159
|
resources.push({
|
|
160
160
|
assetId: asset.id, target: resource.target, ownershipIdentity: `doflow:${HARNESS}:copy-tree:${asset.id}:${resource.relPath}`,
|
|
@@ -177,15 +177,15 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
177
177
|
* registry (core/registry/mcp.json) only ever declares stdio servers today, so only the fields it
|
|
178
178
|
* actually carries are emitted — this never invents a field the registry doesn't supply.
|
|
179
179
|
*/
|
|
180
|
-
function buildServerEntry(server
|
|
180
|
+
function buildServerEntry(server ) {
|
|
181
181
|
if (server.url) return { url: server.url, ...(server.headers ? { headers: server.headers } : {}) };
|
|
182
182
|
return { command: server.command, ...(server.args?.length ? { args: server.args } : {}) };
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
function ownershipIdentity(id
|
|
185
|
+
function ownershipIdentity(id ) { return `doflow:${HARNESS}:mcp-server:${id}`; }
|
|
186
186
|
|
|
187
|
-
function mcpOwnRows(ledger
|
|
188
|
-
return (ledger?.resources || []).filter((row
|
|
187
|
+
function mcpOwnRows(ledger ) {
|
|
188
|
+
return (ledger?.resources || []).filter((row ) => row.harness === HARNESS).flatMap((row ) => {
|
|
189
189
|
if (row.kind === 'mcp-server') {
|
|
190
190
|
return [{ identity: row.identity, target: row.target, fingerprint: row.fingerprint ?? null, ownershipIdentity: row.ownershipIdentity, legacy: false }];
|
|
191
191
|
}
|
|
@@ -196,13 +196,13 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/** Everything the shared entry-ownership rules (../mcp-entries.ts) need from this adapter. */
|
|
199
|
-
function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], ledger, assets = [] }
|
|
200
|
-
const rendered = (servers
|
|
199
|
+
function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], ledger, assets = [] } ) {
|
|
200
|
+
const rendered = (servers ) => servers.map((server ) => ({ id: server.id, entry: buildServerEntry(server) }));
|
|
201
201
|
return {
|
|
202
202
|
file: paths.mcp, selected: rendered(mcp), adoptable: rendered(mcpAdoptable),
|
|
203
203
|
ownRows: mcpOwnRows(ledger),
|
|
204
|
-
foreignRows: (ledger?.resources || []).filter((row
|
|
205
|
-
.map((row
|
|
204
|
+
foreignRows: (ledger?.resources || []).filter((row ) => row.kind === 'mcp-server' && row.harness !== HARNESS)
|
|
205
|
+
.map((row ) => ({ harness: row.harness, identity: row.identity, target: row.target })),
|
|
206
206
|
identityFor: ownershipIdentity, assetId: pseudoAssetId(assets), renderer: MCP_RENDERER, label: 'Kiro MCP',
|
|
207
207
|
};
|
|
208
208
|
}
|
|
@@ -210,9 +210,9 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
210
210
|
/** Kiro has no hooks or mcp asset of its own in core/registry/assets.json (mcp servers come from
|
|
211
211
|
* core/registry/mcp.json, not an asset), so an MCP entry change piggybacks on an asset id
|
|
212
212
|
* this harness actually receives — the same "pseudo-component" technique
|
|
213
|
-
* src/adapters/gemini/index.
|
|
214
|
-
function pseudoAssetId(assets
|
|
215
|
-
return assets.find((asset
|
|
213
|
+
* src/adapters/gemini/index.js#hooksAssetId already uses for its own settings-only component. */
|
|
214
|
+
function pseudoAssetId(assets ) {
|
|
215
|
+
return assets.find((asset ) => asset.id === 'guidance.context-layer')?.id ?? assets[0]?.id;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
// ---- shared adapter contract ----
|
|
@@ -226,11 +226,11 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
226
226
|
* `assets` array; per-file installed status for the hooks tree itself is reported through
|
|
227
227
|
* `copyTree.statuses`/`copyTree.resources`, exactly like it is for skills and agents.
|
|
228
228
|
*/
|
|
229
|
-
function hooksSurface(paths
|
|
229
|
+
function hooksSurface(paths ) {
|
|
230
230
|
return { status: 'supported', target: paths.hooks };
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
function plan({ scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], discovery, context = {}, ledger, fsImpl = fs }
|
|
233
|
+
function plan({ scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], discovery, context = {}, ledger, fsImpl = fs } ) {
|
|
234
234
|
const found = discovery || discover({ scope, scopeRoot, mcp, mcpAdoptable, ledger, fsImpl });
|
|
235
235
|
const changes = [];
|
|
236
236
|
const conflicts = [];
|
|
@@ -253,12 +253,25 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
253
253
|
skills: { status: 'supported', target: found.paths.skills },
|
|
254
254
|
agents: { status: 'supported', target: found.paths.agents },
|
|
255
255
|
hooks: hooksSurface(found.paths),
|
|
256
|
-
mcp: { status: found.mcpSnapshot[found.paths.mcp].ok ? 'supported' : 'blocked', target: found.paths.mcp, selected: mcp.map((item
|
|
256
|
+
mcp: { status: found.mcpSnapshot[found.paths.mcp].ok ? 'supported' : 'blocked', target: found.paths.mcp, selected: mcp.map((item ) => item.id) },
|
|
257
257
|
},
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
/** The hook front doors (IC-037): the scripts and the agent that wires them. */
|
|
262
|
+
function hookDoorAssets(assets ) { return (assets || []).filter((asset ) => asset.id === 'kiro.hooks-scripts' || asset.id === 'kiro.doflow-agent'); }
|
|
263
|
+
|
|
264
|
+
function planHookDoors({ scope, scopeRoot, assets = [], context = {}, ledger, fsImpl = fs } ) {
|
|
265
|
+
const doors = planCopyTreeAssets({ assets: hookDoorAssets(assets), scope, scopeRoot, context, ledger, removing: false, fsImpl });
|
|
266
|
+
return { changes: doors.changes, conflicts: doors.conflicts, prerequisites: [], notices: doors.notices };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function verifyHookDoors({ scope, scopeRoot, assets = [], context = {}, fsImpl = fs } ) {
|
|
270
|
+
const doors = verifyCopyTreeAssets({ assets: hookDoorAssets(assets), scope, scopeRoot, context, fsImpl });
|
|
271
|
+
return { ok: doors.conflicts.length === 0, statuses: doors.statuses, resources: doors.resources, conflicts: doors.conflicts };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function apply({ changes = [], fsImpl = fs } ) {
|
|
262
275
|
writeMcpEntries(changes, { container: MCP_CONTAINER, fsImpl });
|
|
263
276
|
applyCopyTreeAssets(changes, { fsImpl });
|
|
264
277
|
// An update that no longer ships a copy-tree file carries its rows as removals inside the
|
|
@@ -266,12 +279,12 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
266
279
|
removeCopyTreeAssets(changes, { fsImpl });
|
|
267
280
|
}
|
|
268
281
|
|
|
269
|
-
function remove({ changes = [], fsImpl = fs }
|
|
282
|
+
function remove({ changes = [], fsImpl = fs } ) {
|
|
270
283
|
writeMcpEntries(changes, { container: MCP_CONTAINER, fsImpl });
|
|
271
284
|
removeCopyTreeAssets(changes, { fsImpl });
|
|
272
285
|
}
|
|
273
286
|
|
|
274
|
-
function verify({ scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], discovery, ledger, operation, context = {}, fsImpl = fs }
|
|
287
|
+
function verify({ scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], discovery, ledger, operation, context = {}, fsImpl = fs } ) {
|
|
275
288
|
const paths = nativePaths({ scope, scopeRoot });
|
|
276
289
|
const resources = [];
|
|
277
290
|
const conflicts = [];
|
|
@@ -297,7 +310,7 @@ function createKiroAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } =
|
|
|
297
310
|
};
|
|
298
311
|
}
|
|
299
312
|
|
|
300
|
-
return { nativePaths, discover, render, plan, apply, remove, verify, buildServerEntry };
|
|
313
|
+
return { nativePaths, discover, render, plan, apply, remove, verify, planHookDoors, verifyHookDoors, buildServerEntry };
|
|
301
314
|
}
|
|
302
315
|
|
|
303
316
|
const singleton = createKiroAdapter();
|
|
@@ -305,6 +318,7 @@ const singleton = createKiroAdapter();
|
|
|
305
318
|
module.exports = {
|
|
306
319
|
nativePaths: singleton.nativePaths, discover: singleton.discover, render: singleton.render,
|
|
307
320
|
plan: singleton.plan, apply: singleton.apply, remove: singleton.remove, verify: singleton.verify,
|
|
321
|
+
planHookDoors: singleton.planHookDoors, verifyHookDoors: singleton.verifyHookDoors,
|
|
308
322
|
buildServerEntry: singleton.buildServerEntry,
|
|
309
323
|
createKiroAdapter,
|
|
310
324
|
};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// the same reason a file DoFlow cannot read is described by a fixed reason, never parser output.
|
|
15
15
|
const fs = require('node:fs');
|
|
16
16
|
const path = require('node:path');
|
|
17
|
-
const { fingerprint } = require('./copy-tree.
|
|
17
|
+
const { fingerprint } = require('./copy-tree.js');
|
|
18
18
|
|
|
19
19
|
const REASONS = Object.freeze({
|
|
20
20
|
json: 'invalid JSON',
|
|
@@ -22,29 +22,29 @@ const REASONS = Object.freeze({
|
|
|
22
22
|
container: 'server container is not an object',
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
function isObject(value
|
|
25
|
+
function isObject(value ) { return Boolean(value) && typeof value === 'object' && !Array.isArray(value); }
|
|
26
26
|
|
|
27
|
-
function entryFingerprint(value
|
|
27
|
+
function entryFingerprint(value ) { return fingerprint(value); }
|
|
28
28
|
|
|
29
|
-
function fingerprintOrNull(value
|
|
29
|
+
function fingerprintOrNull(value ) { return value === undefined ? null : entryFingerprint(value); }
|
|
30
30
|
|
|
31
31
|
/** Deep equality that ignores key order, so a reformatted entry still equals DoFlow's rendering. */
|
|
32
|
-
function sameValue(a
|
|
32
|
+
function sameValue(a , b ) { return a !== undefined && b !== undefined && entryFingerprint(a) === entryFingerprint(b); }
|
|
33
33
|
|
|
34
|
-
function shortId(id
|
|
34
|
+
function shortId(id ) { const text = String(id); return text.length > 40 ? `${text.slice(0, 37)}...` : text; }
|
|
35
35
|
|
|
36
|
-
function shortPath(file
|
|
36
|
+
function shortPath(file ) { return file.length > 80 ? `...${file.slice(-77)}` : file; }
|
|
37
37
|
|
|
38
38
|
const NOTICES = Object.freeze({
|
|
39
|
-
collision: (id
|
|
40
|
-
released: (id
|
|
41
|
-
untouched: (file
|
|
39
|
+
collision: (id , file ) => `MCP: kept your own entry '${shortId(id)}' in ${path.basename(file)} and did not register DoFlow's; rename or remove yours to let DoFlow manage it.`,
|
|
40
|
+
released: (id , file ) => `MCP: entry '${shortId(id)}' in ${path.basename(file)} was changed outside DoFlow; it is yours now and DoFlow no longer updates or removes it.`,
|
|
41
|
+
untouched: (file ) => `MCP: left ${shortPath(file)} untouched because it cannot be edited safely; DoFlow no longer manages the entries in it.`,
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
function refusal(label
|
|
44
|
+
function refusal(label , file , reason ) { return `${label}: ${file}: ${reason}; DoFlow did not change the file`; }
|
|
45
45
|
|
|
46
46
|
/** Read one MCP file as a JSON object. `servers` is null when the file or its container is absent. */
|
|
47
|
-
function readMcpFile(file
|
|
47
|
+
function readMcpFile(file , container , { fsImpl = fs } = {}) {
|
|
48
48
|
if (!fsImpl.existsSync(file)) return { ok: true, doc: null, servers: null };
|
|
49
49
|
let doc;
|
|
50
50
|
try { doc = JSON.parse(fsImpl.readFileSync(file, 'utf8')); } catch { return { ok: false, reason: REASONS.json }; }
|
|
@@ -55,52 +55,52 @@ function readMcpFile(file: string, container: string, { fsImpl = fs }: { fsImpl?
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/** The `files` map plan and verify take: the scope's MCP file and every file an own row names. */
|
|
58
|
-
function readMcpFiles({ file, ownRows = [], container, fsImpl = fs }
|
|
59
|
-
const targets = new Set([file, ...ownRows.map((row
|
|
58
|
+
function readMcpFiles({ file, ownRows = [], container, fsImpl = fs } ) {
|
|
59
|
+
const targets = new Set([file, ...ownRows.map((row ) => row.target)]);
|
|
60
60
|
return Object.fromEntries([...targets].map((target) => {
|
|
61
61
|
const read = readMcpFile(target, container, { fsImpl });
|
|
62
62
|
return [target, read.ok ? { ok: true, servers: read.servers } : read];
|
|
63
63
|
}));
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function renderedById(selected
|
|
67
|
-
const rendered = new Map
|
|
66
|
+
function renderedById(selected , adoptable ) {
|
|
67
|
+
const rendered = new Map ();
|
|
68
68
|
for (const { id, entry } of [...selected, ...adoptable]) if (!rendered.has(id)) rendered.set(id, entry);
|
|
69
69
|
return rendered;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/** Shared by plan, verify and ownedMcpIds, so the three read one entry the same way. */
|
|
73
|
-
function ownershipView({ file, files, selected = [], adoptable = [], ownRows = [], foreignRows = [] }
|
|
73
|
+
function ownershipView({ file, files, selected = [], adoptable = [], ownRows = [], foreignRows = [] } ) {
|
|
74
74
|
const rendered = renderedById(selected, adoptable);
|
|
75
|
-
const valueOf = (target
|
|
76
|
-
const matches = (row
|
|
75
|
+
const valueOf = (target , id ) => (files[target]?.ok ? files[target].servers?.[id] : undefined);
|
|
76
|
+
const matches = (row , value ) => {
|
|
77
77
|
if (value === undefined) return false;
|
|
78
78
|
const recorded = row.fingerprint ?? null;
|
|
79
79
|
return recorded !== null ? entryFingerprint(value) === recorded : sameValue(value, rendered.get(row.identity));
|
|
80
80
|
};
|
|
81
|
-
const rowsFor = (id
|
|
81
|
+
const rowsFor = (id ) => ownRows.filter((row ) => row.identity === id && row.target === file);
|
|
82
82
|
// A legacy row is consumed alongside the current one, but never preferred over it.
|
|
83
|
-
const rowFor = (id
|
|
83
|
+
const rowFor = (id ) => { const rows = rowsFor(id); return rows.find((row ) => !row.legacy) ?? rows[0] ?? null; };
|
|
84
84
|
const adoptionOpen = ownRows.length === 0;
|
|
85
|
-
const adoptableIds = new Set(adoptable.map((server
|
|
86
|
-
const selectedIds = new Set(selected.map((server
|
|
85
|
+
const adoptableIds = new Set(adoptable.map((server ) => server.id));
|
|
86
|
+
const selectedIds = new Set(selected.map((server ) => server.id));
|
|
87
87
|
/** An equal entry no own row claims is DoFlow's when another harness's row claims it, or when it
|
|
88
88
|
* may be adopted. */
|
|
89
|
-
const claimable = (id
|
|
89
|
+
const claimable = (id , entry ) => {
|
|
90
90
|
const value = valueOf(file, id);
|
|
91
|
-
return sameValue(value, entry) && (foreignRows.some((row
|
|
91
|
+
return sameValue(value, entry) && (foreignRows.some((row ) => row.identity === id && row.target === file)
|
|
92
92
|
|| (adoptionOpen && adoptableIds.has(id)));
|
|
93
93
|
};
|
|
94
94
|
/** Adoptable entries DoFlow takes out because nothing selects them any more. */
|
|
95
95
|
const adoptedRemovals = () => (adoptionOpen && files[file]?.ok
|
|
96
|
-
? adoptable.filter(({ id, entry }
|
|
96
|
+
? adoptable.filter(({ id, entry } ) => !selectedIds.has(id) && sameValue(valueOf(file, id), entry)) : []);
|
|
97
97
|
return { rendered, valueOf, matches, rowsFor, rowFor, claimable, adoptedRemovals };
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
function orderOf(selected
|
|
100
|
+
function orderOf(selected , adoptable ) {
|
|
101
101
|
const ids = [...renderedById(selected, adoptable).keys()];
|
|
102
|
-
const rank = (id
|
|
103
|
-
return (a
|
|
102
|
+
const rank = (id ) => { const index = ids.indexOf(id); return index < 0 ? ids.length : index; };
|
|
103
|
+
return (a , b ) => (a.target < b.target ? -1 : a.target > b.target ? 1
|
|
104
104
|
: rank(a.identity) - rank(b.identity) || (a.identity < b.identity ? -1 : a.identity > b.identity ? 1 : 0));
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -110,17 +110,17 @@ function orderOf(selected: any[], adoptable: any[]): (a: any, b: any) => number
|
|
|
110
110
|
* `foreignRows` other harnesses' rows; `identityFor` composes the adapter's ownership identity.
|
|
111
111
|
*/
|
|
112
112
|
function planMcpEntries({ file, files, selected = [], adoptable = [], ownRows = [], foreignRows = [], removing = false,
|
|
113
|
-
identityFor, assetId, renderer, label }
|
|
113
|
+
identityFor, assetId, renderer, label } ) {
|
|
114
114
|
const view = ownershipView({ file, files, selected, adoptable, ownRows, foreignRows });
|
|
115
|
-
const changes
|
|
116
|
-
const conflicts
|
|
117
|
-
const notices
|
|
118
|
-
const base = (fields
|
|
119
|
-
const write = (operation
|
|
115
|
+
const changes = [];
|
|
116
|
+
const conflicts = [];
|
|
117
|
+
const notices = [];
|
|
118
|
+
const base = (fields ) => ({ assetId, kind: 'mcp-server', sourceVersion: 'registry-v1', projection: { renderer }, ...fields });
|
|
119
|
+
const write = (operation , id , entry ) => base({
|
|
120
120
|
target: file, operation, identity: id, ownershipIdentity: identityFor(id),
|
|
121
121
|
before: fingerprintOrNull(view.valueOf(file, id)), entry, fingerprint: entryFingerprint(entry), afterFingerprint: entryFingerprint(entry),
|
|
122
122
|
});
|
|
123
|
-
const drop = (row
|
|
123
|
+
const drop = (row , release ) => base({
|
|
124
124
|
target: row.target, operation: 'remove', identity: row.identity, ownershipIdentity: row.ownershipIdentity,
|
|
125
125
|
before: fingerprintOrNull(view.valueOf(row.target, row.identity)), fingerprint: row.fingerprint ?? null,
|
|
126
126
|
...(release ? { release: true } : {}),
|
|
@@ -132,7 +132,7 @@ function planMcpEntries({ file, files, selected = [], adoptable = [], ownRows =
|
|
|
132
132
|
else conflicts.push(refusal(label, target, files[target].reason));
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
const consumed = new Set
|
|
135
|
+
const consumed = new Set ();
|
|
136
136
|
if (!removing && files[file]?.ok) {
|
|
137
137
|
for (const { id, entry } of selected) {
|
|
138
138
|
const rows = view.rowsFor(id);
|
|
@@ -184,7 +184,7 @@ function planMcpEntries({ file, files, selected = [], adoptable = [], ownRows =
|
|
|
184
184
|
/** Apply one file's planned changes to the server map read now. Each change goes ahead only while
|
|
185
185
|
* its entry still holds what plan saw, or already holds the change's end state, which makes two
|
|
186
186
|
* harnesses' changes to one shared entry in one run idempotent. Release changes touch nothing. */
|
|
187
|
-
function applyMcpEntries(servers
|
|
187
|
+
function applyMcpEntries(servers , changes ) {
|
|
188
188
|
const next = { ...(servers || {}) };
|
|
189
189
|
for (const change of changes) {
|
|
190
190
|
if (change.release) continue;
|
|
@@ -205,13 +205,13 @@ function applyMcpEntries(servers: any, changes: any[]): any {
|
|
|
205
205
|
* whatever it holds now.
|
|
206
206
|
*/
|
|
207
207
|
function verifyMcpEntries({ file, files, snapshot, selected = [], adoptable = [], ownRows = [], foreignRows = [], removing = false,
|
|
208
|
-
identityFor, assetId, renderer, label, harness, sourceVersion = 'unknown' }
|
|
208
|
+
identityFor, assetId, renderer, label, harness, sourceVersion = 'unknown' } ) {
|
|
209
209
|
const now = ownershipView({ file, files, selected, adoptable, ownRows, foreignRows });
|
|
210
210
|
const then = ownershipView({ file, files: snapshot, selected, adoptable, ownRows, foreignRows });
|
|
211
|
-
const statuses
|
|
212
|
-
const resources
|
|
213
|
-
const conflicts
|
|
214
|
-
const report = (id
|
|
211
|
+
const statuses = [];
|
|
212
|
+
const resources = [];
|
|
213
|
+
const conflicts = [];
|
|
214
|
+
const report = (id , target , ownershipIdentity , status ) => statuses.push({
|
|
215
215
|
harness, assetId, capability: 'mcp', identity: id, target, ownershipIdentity, status,
|
|
216
216
|
});
|
|
217
217
|
|
|
@@ -219,7 +219,7 @@ function verifyMcpEntries({ file, files, snapshot, selected = [], adoptable = []
|
|
|
219
219
|
for (const target of Object.keys(files).filter((item) => !files[item].ok).sort()) conflicts.push(refusal(label, target, files[target].reason));
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
const consumed = new Set
|
|
222
|
+
const consumed = new Set ();
|
|
223
223
|
if (!removing) {
|
|
224
224
|
for (const { id, entry } of selected) {
|
|
225
225
|
for (const row of now.rowsFor(id)) consumed.add(row);
|
|
@@ -239,7 +239,7 @@ function verifyMcpEntries({ file, files, snapshot, selected = [], adoptable = []
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
const leftover = (value
|
|
242
|
+
const leftover = (value , matching , target ) => {
|
|
243
243
|
if (!files[target]?.ok) return 'not-managed';
|
|
244
244
|
if (value === undefined) return 'absent';
|
|
245
245
|
return matching ? 'retained' : 'not-managed';
|
|
@@ -261,29 +261,29 @@ function verifyMcpEntries({ file, files, snapshot, selected = [], adoptable = []
|
|
|
261
261
|
/** The ids DoFlow owns in this harness's MCP file now, in rendering order: those an own row matches,
|
|
262
262
|
* and, while the harness holds no row, adoptable entries equal to DoFlow's rendering. A file that
|
|
263
263
|
* cannot be read contributes none. */
|
|
264
|
-
function ownedMcpIds({ file, files, selected = [], adoptable = [], ownRows = [] }
|
|
264
|
+
function ownedMcpIds({ file, files, selected = [], adoptable = [], ownRows = [] } ) {
|
|
265
265
|
const view = ownershipView({ file, files, selected, adoptable, ownRows });
|
|
266
|
-
const owned = new Set
|
|
266
|
+
const owned = new Set (ownRows.filter((row ) => view.matches(row, view.valueOf(row.target, row.identity))).map((row ) => row.identity));
|
|
267
267
|
if (!ownRows.length) {
|
|
268
268
|
for (const { id, entry } of adoptable) if (sameValue(view.valueOf(file, id), entry)) owned.add(id);
|
|
269
269
|
}
|
|
270
|
-
const order
|
|
270
|
+
const order = [...view.rendered.keys()];
|
|
271
271
|
return [...order.filter((id) => owned.has(id)), ...[...owned].filter((id) => !order.includes(id)).sort()];
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
function isSymlink(file
|
|
274
|
+
function isSymlink(file , fsImpl ) {
|
|
275
275
|
try { return fsImpl.lstatSync(file).isSymbolicLink(); } catch { return false; }
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
/** A symlinked MCP file is written through to the file it points at, so the link stays a link. */
|
|
279
|
-
function writePath(file
|
|
279
|
+
function writePath(file , fsImpl ) {
|
|
280
280
|
if (!isSymlink(file, fsImpl)) return file;
|
|
281
281
|
try { return fsImpl.realpathSync(file); } catch { return path.resolve(path.dirname(file), fsImpl.readlinkSync(file)); }
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
/** Write a JSON document atomically, keeping the existing file's permission bits: an MCP file kept
|
|
285
285
|
* private for its tokens stays so. Creates missing directories; never deletes a file. */
|
|
286
|
-
function writeMcpJson(file
|
|
286
|
+
function writeMcpJson(file , doc , { fsImpl = fs } = {}) {
|
|
287
287
|
const dest = writePath(file, fsImpl);
|
|
288
288
|
fsImpl.mkdirSync(path.dirname(dest), { recursive: true });
|
|
289
289
|
const mode = fsImpl.existsSync(dest) ? fsImpl.statSync(dest).mode & 0o7777 : null;
|
|
@@ -303,14 +303,14 @@ function writeMcpJson(file: string, doc: any, { fsImpl = fs }: { fsImpl?: any }
|
|
|
303
303
|
* every file's next document computed before the first write. An emptied container is kept as `{}`
|
|
304
304
|
* or its member deleted, as the harness expects. Returns the number of files written.
|
|
305
305
|
*/
|
|
306
|
-
function writeMcpEntries(changes
|
|
307
|
-
const byFile = new Map
|
|
306
|
+
function writeMcpEntries(changes , { container, keepEmptyContainer = false, fsImpl = fs } = {}) {
|
|
307
|
+
const byFile = new Map ();
|
|
308
308
|
for (const change of changes) {
|
|
309
309
|
if (change.kind !== 'mcp-server' || change.release) continue;
|
|
310
310
|
if (!byFile.has(change.target)) byFile.set(change.target, []);
|
|
311
|
-
byFile.get(change.target)
|
|
311
|
+
byFile.get(change.target) .push(change);
|
|
312
312
|
}
|
|
313
|
-
const writes
|
|
313
|
+
const writes = [];
|
|
314
314
|
for (const [file, group] of byFile) {
|
|
315
315
|
const read = readMcpFile(file, container, { fsImpl });
|
|
316
316
|
if (!read.ok) throw new Error(`MCP: ${file} changed after planning; nothing was written, re-run the command`);
|