@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
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
const crypto = require('node:crypto');
|
|
8
8
|
const fs = require('node:fs');
|
|
9
9
|
const path = require('node:path');
|
|
10
|
-
const { mergeMarkedSection, removeMarkedSection, MARKER_START, MARKER_END } = require('../../helper/marker-merge.
|
|
11
|
-
const { selectMcpServers } = require('../../registry/index.
|
|
12
|
-
const { GLOBAL_HOOK_PREFIX, PROJECT_HOOK_PREFIX } = require('../../helper/settings-scope.
|
|
13
|
-
const { mergeSettings, settingsContains, settingsContainsAny, stripManagedSettings } = require('../../helper/settings-merge.
|
|
14
|
-
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, resolveTransform } = require('../copy-tree.
|
|
15
|
-
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.
|
|
16
|
-
const { discoverMcp, planMcp, writeMcp, verifyMcp } = require('./mcp.
|
|
10
|
+
const { mergeMarkedSection, removeMarkedSection, MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
|
|
11
|
+
const { selectMcpServers } = require('../../registry/index.js');
|
|
12
|
+
const { GLOBAL_HOOK_PREFIX, PROJECT_HOOK_PREFIX } = require('../../helper/settings-scope.js');
|
|
13
|
+
const { mergeSettings, settingsContains, settingsContainsAny, stripManagedSettings } = require('../../helper/settings-merge.js');
|
|
14
|
+
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, resolveTransform } = require('../copy-tree.js');
|
|
15
|
+
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
|
|
16
|
+
const { discoverMcp, planMcp, writeMcp, verifyMcp } = require('./mcp.js');
|
|
17
17
|
|
|
18
18
|
const INSTRUCTION_RENDERER = 'claude-instructions';
|
|
19
19
|
const SETTINGS_RENDERER = 'claude-settings';
|
|
@@ -30,11 +30,11 @@ const SETTINGS_RENDERER = 'claude-settings';
|
|
|
30
30
|
*/
|
|
31
31
|
const SETTINGS_FILES = ['settings.json', 'keybindings.json'];
|
|
32
32
|
|
|
33
|
-
function sha256(value
|
|
33
|
+
function sha256(value ) {
|
|
34
34
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function sourcePath(asset
|
|
37
|
+
function sourcePath(asset , context = {}) {
|
|
38
38
|
if (!asset || typeof asset.source !== 'string') throw new Error('Claude asset requires a source path');
|
|
39
39
|
const repoRoot = context.repoRoot ? path.resolve(context.repoRoot) : process.cwd();
|
|
40
40
|
const source = path.resolve(repoRoot, asset.source);
|
|
@@ -44,7 +44,7 @@ function sourcePath(asset: any, context: any = {}) {
|
|
|
44
44
|
return source;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function markerState(file
|
|
47
|
+
function markerState(file ) {
|
|
48
48
|
if (!fs.existsSync(file)) return { exists: false, managed: false, malformed: false };
|
|
49
49
|
const text = fs.readFileSync(file, 'utf8');
|
|
50
50
|
const starts = text.split(MARKER_START).length - 1;
|
|
@@ -53,8 +53,8 @@ function markerState(file: any) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/** Single serialization form for every settings write, so repeated runs are byte-stable. */
|
|
56
|
-
const serializeSettings = (value
|
|
57
|
-
const settingsChangeExtras = (fileName
|
|
56
|
+
const serializeSettings = (value ) => `${JSON.stringify(value, null, 2)}\n`;
|
|
57
|
+
const settingsChangeExtras = (fileName ) => ({ kind: 'settings-file', identity: fileName });
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Native path facts live in core/registry/harnesses.json under this harness's "paths" section and
|
|
@@ -67,18 +67,18 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
67
67
|
// Parity note: Claude's historical nativePaths() resolved BOTH scopes' root from scopeRoot (the
|
|
68
68
|
// CLI passes $HOME there for -g installs) and never consulted homeDir — so the resolver is fed
|
|
69
69
|
// no homeDir and "root" keeps resolving exactly as before.
|
|
70
|
-
function nativePaths({ scope, scopeRoot }
|
|
70
|
+
function nativePaths({ scope, scopeRoot } = {}) {
|
|
71
71
|
return resolveHarnessPaths(declaredPaths, { scope, scopeRoot });
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
function discover({ scope, scopeRoot, mcp = [], mcpAdoptable = [], ledger, registry, homeDir, context = {} }
|
|
74
|
+
function discover({ scope, scopeRoot, mcp = [], mcpAdoptable = [], ledger, registry, homeDir, context = {} } ) {
|
|
75
75
|
const paths = nativePaths({ scope, scopeRoot });
|
|
76
76
|
return {
|
|
77
77
|
paths,
|
|
78
78
|
instructions: markerState(paths.instructions),
|
|
79
79
|
settingsExists: fs.existsSync(paths.settings),
|
|
80
80
|
mcpExists: fs.existsSync(paths.mcp),
|
|
81
|
-
knownMcpServers: registry ? selectMcpServers(registry).map((server
|
|
81
|
+
knownMcpServers: registry ? selectMcpServers(registry).map((server ) => server.id) : [],
|
|
82
82
|
...discoverMcp({ paths, mcp, mcpAdoptable, ledger, scope, scopeRoot, homeDir: context.homeDir || homeDir || undefined }),
|
|
83
83
|
};
|
|
84
84
|
}
|
|
@@ -88,11 +88,11 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
88
88
|
/** Every asset materialised through the copy-tree engine, whatever its declared renderer:
|
|
89
89
|
* verbatim mirrors declare 'copy-tree', transformed projections (output styles) declare their
|
|
90
90
|
* own name so the registry can tell the shapes apart. Routing is on `kind`, which both share. */
|
|
91
|
-
function claudeTreeAssets(assets
|
|
92
|
-
return [...copyTreeAssets(assets), ...(assets || []).filter((asset
|
|
91
|
+
function claudeTreeAssets(assets ) {
|
|
92
|
+
return [...copyTreeAssets(assets), ...(assets || []).filter((asset ) => asset.renderer === 'claude-output-styles')];
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing }
|
|
95
|
+
function planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing } ) {
|
|
96
96
|
const paths = nativePaths({ scope, scopeRoot });
|
|
97
97
|
const changes = [];
|
|
98
98
|
const conflicts = [];
|
|
@@ -109,10 +109,10 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
109
109
|
// is never deleted on removal, forced or not.
|
|
110
110
|
force: !removing && context?.force === true, });
|
|
111
111
|
treeResults.push(result);
|
|
112
|
-
conflicts.push(...result.conflicts.map((reason
|
|
112
|
+
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
113
113
|
// A hand-edited file at a path DoFlow no longer writes is the user's: it stays, only its
|
|
114
114
|
// ownership row is released.
|
|
115
|
-
notices.push(...result.kept.map((item
|
|
115
|
+
notices.push(...result.kept.map((item ) => `kept hand-edited ${path.relative(paths.root, item.target)}; DoFlow no longer manages it`));
|
|
116
116
|
for (const change of result.changes) {
|
|
117
117
|
changes.push({
|
|
118
118
|
assetId: asset.id, target: change.target, source: change.source, operation: change.operation,
|
|
@@ -128,7 +128,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
128
128
|
return { changes, conflicts, notices: [...siblingReplacedNotices(treeResults), ...notices] };
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
function applyCopyTreeAssets(changes
|
|
131
|
+
function applyCopyTreeAssets(changes ) {
|
|
132
132
|
let applied = 0;
|
|
133
133
|
const grouped = new Map();
|
|
134
134
|
for (const change of changes) {
|
|
@@ -143,13 +143,13 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
143
143
|
return applied;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
function removeCopyTreeAssets(changes
|
|
147
|
-
const treeChanges = changes.filter((change
|
|
148
|
-
.map((change
|
|
146
|
+
function removeCopyTreeAssets(changes ) {
|
|
147
|
+
const treeChanges = changes.filter((change ) => change.kind === 'copy-tree-file' && change.operation === 'remove' && !change.retained)
|
|
148
|
+
.map((change ) => ({ relPath: change.identity, target: change.target, operation: 'remove', fingerprint: change.fingerprint, pruneGuard: change.pruneGuard }));
|
|
149
149
|
return removeTree({ changes: treeChanges }).removed;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
function verifyCopyTreeAssets({ assets, scope, scopeRoot, context }
|
|
152
|
+
function verifyCopyTreeAssets({ assets, scope, scopeRoot, context } ) {
|
|
153
153
|
const paths = nativePaths({ scope, scopeRoot });
|
|
154
154
|
const statuses = [];
|
|
155
155
|
const resources = [];
|
|
@@ -158,7 +158,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
158
158
|
const destDir = copyTreeDestDir(paths.configDir, asset);
|
|
159
159
|
const sourceDir = sourcePath(asset, context);
|
|
160
160
|
const result = verifyTree({ sourceDir, destDir, layout: asset.layout, transform: asset.transform, plannedWrites: context?.plannedWrites });
|
|
161
|
-
conflicts.push(...result.conflicts.map((reason
|
|
161
|
+
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
162
162
|
for (const resource of result.resources) {
|
|
163
163
|
resources.push({
|
|
164
164
|
assetId: asset.id, target: resource.target, ownershipIdentity: `doflow:claude:copy-tree:${asset.id}:${resource.relPath}`,
|
|
@@ -174,16 +174,16 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
174
174
|
|
|
175
175
|
// ---- claude-settings asset (settings.json + keybindings.json) ----
|
|
176
176
|
|
|
177
|
-
function settingsAsset(assets
|
|
178
|
-
function settingsTarget(fileName
|
|
177
|
+
function settingsAsset(assets ) { return (assets || []).find((asset ) => asset?.renderer === SETTINGS_RENDERER); }
|
|
178
|
+
function settingsTarget(fileName , paths ) {
|
|
179
179
|
// Per-file dispatch across the two DECLARED settings surfaces (paths.settings / paths.keybindings):
|
|
180
180
|
// residue because the authored file list is content-shaped, not a single declarable destination.
|
|
181
181
|
return fileName === 'settings.json' ? paths.settings : paths.keybindings;
|
|
182
182
|
}
|
|
183
|
-
function settingsContent(fileName
|
|
183
|
+
function settingsContent(fileName , raw , scope ) {
|
|
184
184
|
return fileName === 'settings.json' && scope !== 'global' ? raw.split(GLOBAL_HOOK_PREFIX).join(PROJECT_HOOK_PREFIX) : raw;
|
|
185
185
|
}
|
|
186
|
-
function settingsLedgerResources(ledger
|
|
186
|
+
function settingsLedgerResources(ledger , assetId ) {
|
|
187
187
|
const byFile = new Map();
|
|
188
188
|
for (const resource of ledger?.resources || []) {
|
|
189
189
|
if (resource.harness === 'claude' && resource.assetId === assetId && resource.kind === 'settings-file') byFile.set(resource.identity, resource);
|
|
@@ -194,7 +194,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
194
194
|
|
|
195
195
|
/** Inject (or leave untouched) the statusLine key pointing at the projected script. Refuses to
|
|
196
196
|
* overwrite a user's existing custom status line unless forced: their script is theirs. */
|
|
197
|
-
function withStatusLineKey(rawSettingsJson
|
|
197
|
+
function withStatusLineKey(rawSettingsJson , scriptPath , force ) {
|
|
198
198
|
let parsed;
|
|
199
199
|
try {
|
|
200
200
|
parsed = JSON.parse(rawSettingsJson);
|
|
@@ -207,10 +207,10 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
207
207
|
return serializeSettings(parsed);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
function planSettingsAsset({ assets, scope, scopeRoot, context, ledger, removing }
|
|
210
|
+
function planSettingsAsset({ assets, scope, scopeRoot, context, ledger, removing } ) {
|
|
211
211
|
const asset = settingsAsset(assets);
|
|
212
|
-
const changes
|
|
213
|
-
const conflicts
|
|
212
|
+
const changes = [];
|
|
213
|
+
const conflicts = [];
|
|
214
214
|
if (!asset) return { changes, conflicts };
|
|
215
215
|
const paths = nativePaths({ scope, scopeRoot });
|
|
216
216
|
const sourceDir = sourcePath(asset, context);
|
|
@@ -311,7 +311,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
311
311
|
// other copy-tree/settings write in this codebase — planSettingsAsset's own change detection is
|
|
312
312
|
// fingerprint(sha256)-based, not mtime-based, so this is a consistency convention, not a
|
|
313
313
|
// correctness requirement.
|
|
314
|
-
function applySettingsAsset(changes
|
|
314
|
+
function applySettingsAsset(changes ) {
|
|
315
315
|
let applied = 0;
|
|
316
316
|
for (const change of changes) {
|
|
317
317
|
if (change.projection?.renderer !== SETTINGS_RENDERER || change.operation === 'remove') continue;
|
|
@@ -328,7 +328,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
328
328
|
// is deleted outright only when nothing but schema pointers remains — i.e. DoFlow created it.
|
|
329
329
|
// Previously this rmSync'd unconditionally, which on a user-maintained settings.json would have
|
|
330
330
|
// destroyed their model, permissions, env, and every foreign tool's hooks along with ours.
|
|
331
|
-
function removeSettingsAsset(changes
|
|
331
|
+
function removeSettingsAsset(changes ) {
|
|
332
332
|
let removed = 0;
|
|
333
333
|
for (const change of changes) {
|
|
334
334
|
if (change.projection?.renderer !== SETTINGS_RENDERER || change.operation !== 'remove') continue;
|
|
@@ -347,11 +347,11 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
347
347
|
return removed;
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
function verifySettingsAsset({ assets, scope, scopeRoot, context }
|
|
350
|
+
function verifySettingsAsset({ assets, scope, scopeRoot, context } ) {
|
|
351
351
|
const asset = settingsAsset(assets);
|
|
352
|
-
const statuses
|
|
353
|
-
const resources
|
|
354
|
-
const conflicts
|
|
352
|
+
const statuses = [];
|
|
353
|
+
const resources = [];
|
|
354
|
+
const conflicts = [];
|
|
355
355
|
if (!asset) return { statuses, resources, conflicts };
|
|
356
356
|
const paths = nativePaths({ scope, scopeRoot });
|
|
357
357
|
const sourceDir = sourcePath(asset, context);
|
|
@@ -424,9 +424,9 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
424
424
|
|
|
425
425
|
/** Render native Claude input without writing it.
|
|
426
426
|
* `asset` is the lifecycle-projected shape (flat `renderer`/`capability` fields — see
|
|
427
|
-
* src/adapters/index.
|
|
427
|
+
* src/adapters/index.js#projectAdapterInput), not the raw registry asset's nested
|
|
428
428
|
* `asset.projection.claude`. Every adapter method below shares that same expectation. */
|
|
429
|
-
function render({ asset, scope, scopeRoot, context = {} }
|
|
429
|
+
function render({ asset, scope, scopeRoot, context = {} } ) {
|
|
430
430
|
const paths = nativePaths({ scope, scopeRoot });
|
|
431
431
|
if (asset?.renderer === INSTRUCTION_RENDERER) {
|
|
432
432
|
const source = sourcePath(asset, context);
|
|
@@ -444,7 +444,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
444
444
|
return { type: 'unsupported', assetId: asset?.id, renderer: asset?.renderer ?? null };
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
-
function plan({ assets, scope, scopeRoot, mcp = [], mcpAdoptable = [], discovery, context = {}, ledger }
|
|
447
|
+
function plan({ assets, scope, scopeRoot, mcp = [], mcpAdoptable = [], discovery, context = {}, ledger } ) {
|
|
448
448
|
const changes = [];
|
|
449
449
|
const conflicts = [];
|
|
450
450
|
const paths = nativePaths({ scope, scopeRoot });
|
|
@@ -480,13 +480,26 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
480
480
|
return { changes, conflicts, prerequisites: [], notices: [...copyTree.notices, ...mcpPlan.notices] };
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
-
|
|
483
|
+
/** The hook front doors (IC-037): the one asset that holds the scripts the settings hooks run. */
|
|
484
|
+
function hookDoorAssets(assets ) { return (assets || []).filter((asset ) => asset.id === 'claude.hooks-scripts'); }
|
|
485
|
+
|
|
486
|
+
function planHookDoors({ assets, scope, scopeRoot, context = {}, ledger } ) {
|
|
487
|
+
const doors = planCopyTreeAssets({ assets: hookDoorAssets(assets), scope, scopeRoot, context, ledger, removing: false });
|
|
488
|
+
return { changes: doors.changes, conflicts: doors.conflicts, prerequisites: [], notices: doors.notices };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function verifyHookDoors({ assets, scope, scopeRoot, context = {} } ) {
|
|
492
|
+
const doors = verifyCopyTreeAssets({ assets: hookDoorAssets(assets), scope, scopeRoot, context });
|
|
493
|
+
return { ok: doors.conflicts.length === 0, statuses: doors.statuses, resources: doors.resources, conflicts: doors.conflicts };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function apply({ changes, scope, scopeRoot, context = {} } ) {
|
|
484
497
|
for (const change of changes) {
|
|
485
498
|
if (change.projection?.renderer !== INSTRUCTION_RENDERER) continue;
|
|
486
499
|
// A plan records the resolved source. This keeps apply self-contained when
|
|
487
500
|
// the lifecycle supplies normalized changes rather than the original asset
|
|
488
501
|
// array, while still avoiding any implicit source discovery at write time.
|
|
489
|
-
const source = change.source ?? sourcePath((context.assets || []).find((item
|
|
502
|
+
const source = change.source ?? sourcePath((context.assets || []).find((item ) => item.id === change.assetId), context);
|
|
490
503
|
mergeMarkedSection(source, change.target ?? nativePaths({ scope, scopeRoot }).instructions);
|
|
491
504
|
}
|
|
492
505
|
applyCopyTreeAssets(changes);
|
|
@@ -498,7 +511,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
498
511
|
return { applied: changes.length };
|
|
499
512
|
}
|
|
500
513
|
|
|
501
|
-
function remove({ changes }
|
|
514
|
+
function remove({ changes } ) {
|
|
502
515
|
let removed = 0;
|
|
503
516
|
for (const change of changes) {
|
|
504
517
|
if (change.projection?.renderer === INSTRUCTION_RENDERER && removeMarkedSection(change.target)) removed += 1;
|
|
@@ -509,11 +522,11 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
509
522
|
return { removed };
|
|
510
523
|
}
|
|
511
524
|
|
|
512
|
-
function verify({ assets, scope, scopeRoot, mcp = [], mcpAdoptable = [], discovery, ledger, operation, context = {} }
|
|
525
|
+
function verify({ assets, scope, scopeRoot, mcp = [], mcpAdoptable = [], discovery, ledger, operation, context = {} } ) {
|
|
513
526
|
const paths = nativePaths({ scope, scopeRoot });
|
|
514
527
|
const statuses = [];
|
|
515
528
|
const resources = [];
|
|
516
|
-
const instruction = assets.find((asset
|
|
529
|
+
const instruction = assets.find((asset ) => asset?.renderer === INSTRUCTION_RENDERER);
|
|
517
530
|
const state = markerState(paths.instructions);
|
|
518
531
|
if (instruction) {
|
|
519
532
|
statuses.push({ assetId: instruction.id, status: state.managed ? 'managed' : 'absent', target: paths.instructions });
|
|
@@ -536,7 +549,7 @@ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } =
|
|
|
536
549
|
return { ok: conflicts.length === 0, statuses, resources, conflicts };
|
|
537
550
|
}
|
|
538
551
|
|
|
539
|
-
return { nativePaths, discover, render, plan, apply, remove, verify };
|
|
552
|
+
return { nativePaths, discover, render, plan, apply, remove, verify, planHookDoors, verifyHookDoors };
|
|
540
553
|
}
|
|
541
554
|
|
|
542
555
|
const singleton = createClaudeAdapter();
|
|
@@ -544,5 +557,6 @@ const singleton = createClaudeAdapter();
|
|
|
544
557
|
module.exports = {
|
|
545
558
|
nativePaths: singleton.nativePaths, discover: singleton.discover, render: singleton.render,
|
|
546
559
|
plan: singleton.plan, apply: singleton.apply, remove: singleton.remove, verify: singleton.verify,
|
|
560
|
+
planHookDoors: singleton.planHookDoors, verifyHookDoors: singleton.verifyHookDoors,
|
|
547
561
|
createClaudeAdapter,
|
|
548
562
|
};
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
const fs = require('node:fs');
|
|
11
11
|
const os = require('node:os');
|
|
12
12
|
const path = require('node:path');
|
|
13
|
-
const { nativeMcpCatalog } = require('../../registry/index.
|
|
14
|
-
const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.
|
|
13
|
+
const { nativeMcpCatalog } = require('../../registry/index.js');
|
|
14
|
+
const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.js');
|
|
15
15
|
|
|
16
16
|
const HARNESS = 'claude';
|
|
17
17
|
const MCP_CONTAINER = 'mcpServers';
|
|
@@ -20,39 +20,39 @@ const MCP_RENDERER = 'claude-mcp';
|
|
|
20
20
|
// install carries.
|
|
21
21
|
const MCP_ASSET_ID = 'guidance.core';
|
|
22
22
|
|
|
23
|
-
function ownershipIdentity(id
|
|
23
|
+
function ownershipIdentity(id ) { return `doflow:${HARNESS}:mcp-server:${id}`; }
|
|
24
24
|
|
|
25
25
|
/** Claude Code's own per-server shape, the one the registry's native catalog renders. */
|
|
26
|
-
function rendered(servers
|
|
26
|
+
function rendered(servers ) {
|
|
27
27
|
const { serverDefs } = nativeMcpCatalog(servers);
|
|
28
|
-
return servers.map((server
|
|
28
|
+
return servers.map((server ) => ({ id: server.id, entry: serverDefs[server.id] }));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Everything the shared entry-ownership rules need from this adapter. */
|
|
32
|
-
function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], ledger }
|
|
33
|
-
const rows = (ledger?.resources || []).filter((row
|
|
32
|
+
function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], ledger } ) {
|
|
33
|
+
const rows = (ledger?.resources || []).filter((row ) => row.kind === 'mcp-server');
|
|
34
34
|
return {
|
|
35
35
|
file: paths.mcp, selected: rendered(mcp), adoptable: rendered(mcpAdoptable),
|
|
36
|
-
ownRows: rows.filter((row
|
|
36
|
+
ownRows: rows.filter((row ) => row.harness === HARNESS).map((row ) => ({
|
|
37
37
|
identity: row.identity, target: row.target, fingerprint: row.fingerprint ?? null, ownershipIdentity: row.ownershipIdentity, legacy: false,
|
|
38
38
|
})),
|
|
39
|
-
foreignRows: rows.filter((row
|
|
39
|
+
foreignRows: rows.filter((row ) => row.harness !== HARNESS).map((row ) => ({ harness: row.harness, identity: row.identity, target: row.target })),
|
|
40
40
|
identityFor: ownershipIdentity, assetId: MCP_ASSET_ID, renderer: MCP_RENDERER, label: 'Claude MCP',
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function readFiles(entries
|
|
44
|
+
function readFiles(entries , fsImpl ) {
|
|
45
45
|
return readMcpFiles({ file: entries.file, ownRows: entries.ownRows, container: MCP_CONTAINER, fsImpl });
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/** Server names Claude Code merges into a project from `~/.claude.json` beside the project's own
|
|
49
49
|
* `.mcp.json`: the local scope (`projects[<root>].mcpServers`) and the user scope (`mcpServers`).
|
|
50
50
|
* Read-only and for reach alone; a file that cannot be read contributes nothing (null). */
|
|
51
|
-
function claudeJsonNames({ scopeRoot, homeDir, fsImpl }
|
|
51
|
+
function claudeJsonNames({ scopeRoot, homeDir, fsImpl } ) {
|
|
52
52
|
let doc;
|
|
53
53
|
try { doc = JSON.parse(fsImpl.readFileSync(path.join(homeDir, '.claude.json'), 'utf8')); } catch { return null; }
|
|
54
54
|
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return null;
|
|
55
|
-
const names = (servers
|
|
55
|
+
const names = (servers ) => (servers && typeof servers === 'object' && !Array.isArray(servers) ? Object.keys(servers) : []);
|
|
56
56
|
const projects = doc.projects && typeof doc.projects === 'object' ? doc.projects : {};
|
|
57
57
|
let real = scopeRoot;
|
|
58
58
|
try { real = fsImpl.realpathSync(scopeRoot); } catch { /* the path as given */ }
|
|
@@ -64,7 +64,7 @@ function claudeJsonNames({ scopeRoot, homeDir, fsImpl }: any) {
|
|
|
64
64
|
* (`mcpConfigured`): the scope's file, plus at project scope the local- and user-scope entries in
|
|
65
65
|
* `~/.claude.json`. Claude Code also loads plugin, managed and connector servers DoFlow does not
|
|
66
66
|
* read, so the view is never complete (`mcpComplete: false`) and absence is never proof. */
|
|
67
|
-
function discoverMcp({ paths, mcp, mcpAdoptable, ledger, scope, scopeRoot, homeDir = os.homedir(), fsImpl = fs }
|
|
67
|
+
function discoverMcp({ paths, mcp, mcpAdoptable, ledger, scope, scopeRoot, homeDir = os.homedir(), fsImpl = fs } ) {
|
|
68
68
|
const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
|
|
69
69
|
const mcpSnapshot = readFiles(entries, fsImpl);
|
|
70
70
|
// `null` while nothing could be read; every name otherwise, whoever wrote it.
|
|
@@ -75,17 +75,17 @@ function discoverMcp({ paths, mcp, mcpAdoptable, ledger, scope, scopeRoot, homeD
|
|
|
75
75
|
return { mcpSnapshot, mcpOwned: ownedMcpIds({ ...entries, files: mcpSnapshot }), mcpConfigured, mcpComplete: false };
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function planMcp({ paths, mcp, mcpAdoptable, ledger, snapshot, removing, fsImpl = fs }
|
|
78
|
+
function planMcp({ paths, mcp, mcpAdoptable, ledger, snapshot, removing, fsImpl = fs } ) {
|
|
79
79
|
const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
|
|
80
80
|
return planMcpEntries({ ...entries, files: snapshot ?? readFiles(entries, fsImpl), removing });
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/** An emptied `mcpServers` stays as `{}`, as Claude Code itself leaves it. */
|
|
84
|
-
function writeMcp(changes
|
|
84
|
+
function writeMcp(changes , { fsImpl = fs } = {}) {
|
|
85
85
|
return writeMcpEntries(changes, { container: MCP_CONTAINER, keepEmptyContainer: true, fsImpl });
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function verifyMcp({ paths, mcp, mcpAdoptable, ledger, snapshot, removing, sourceVersion, fsImpl = fs }
|
|
88
|
+
function verifyMcp({ paths, mcp, mcpAdoptable, ledger, snapshot, removing, sourceVersion, fsImpl = fs } ) {
|
|
89
89
|
const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
|
|
90
90
|
const files = readFiles(entries, fsImpl);
|
|
91
91
|
return verifyMcpEntries({ ...entries, files, snapshot: snapshot ?? files, removing, harness: HARNESS, sourceVersion });
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
const crypto = require('node:crypto');
|
|
9
9
|
const fs = require('node:fs');
|
|
10
10
|
const path = require('node:path');
|
|
11
|
-
const { atomicWrite } = require('./config.
|
|
11
|
+
const { atomicWrite } = require('./config.js');
|
|
12
12
|
|
|
13
13
|
const AGENT_DIRECTORY = 'agents';
|
|
14
14
|
const AGENT_KIND = 'custom-agent';
|
|
@@ -23,38 +23,38 @@ const IMPLEMENTATION_ROLES = new Set([
|
|
|
23
23
|
]);
|
|
24
24
|
const ALLOWED_FIELDS = new Set([...REQUIRED_FIELDS, 'sandbox_mode']);
|
|
25
25
|
|
|
26
|
-
function fingerprint(content
|
|
26
|
+
function fingerprint(content ) {
|
|
27
27
|
return `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
function assertAgentName(name
|
|
30
|
+
function assertAgentName(name , label = 'agent name') {
|
|
31
31
|
if (typeof name !== 'string' || !/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
32
32
|
throw new Error(`${label} must be a lowercase kebab-case identifier`);
|
|
33
33
|
}
|
|
34
34
|
return name;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function filenameFor(name
|
|
37
|
+
function filenameFor(name ) {
|
|
38
38
|
assertAgentName(name);
|
|
39
39
|
return `${name}.toml`;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function expectedName(fileName
|
|
42
|
+
function expectedName(fileName ) {
|
|
43
43
|
if (typeof fileName !== 'string' || path.basename(fileName) !== fileName || !fileName.endsWith('.toml')) {
|
|
44
44
|
throw new Error('Agent filename must be a direct .toml filename');
|
|
45
45
|
}
|
|
46
46
|
return assertAgentName(fileName.slice(0, -'.toml'.length), 'Agent filename');
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function parseQuoted(value
|
|
49
|
+
function parseQuoted(value , field , line ) {
|
|
50
50
|
if (!/^"(?:[^"\\\n]|\\.)*"$/.test(value)) throw new Error(`${field} must be a quoted TOML string on line ${line}`);
|
|
51
51
|
try { return JSON.parse(value); } catch { throw new Error(`${field} has an invalid quoted value on line ${line}`); }
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/** Parse the intentionally small TOML contract supported by Codex agent definitions. */
|
|
55
|
-
function parseAgentToml(source
|
|
55
|
+
function parseAgentToml(source , { fileName } = {}) {
|
|
56
56
|
if (typeof source !== 'string') throw new Error('Agent TOML source must be text');
|
|
57
|
-
const values
|
|
57
|
+
const values = {};
|
|
58
58
|
const lines = source.split(/\r?\n/);
|
|
59
59
|
for (let index = 0; index < lines.length; index++) {
|
|
60
60
|
const line = lines[index];
|
|
@@ -64,7 +64,7 @@ function parseAgentToml(source: any, { fileName }: { fileName?: string } = {}):
|
|
|
64
64
|
const field = triple[1];
|
|
65
65
|
if (!ALLOWED_FIELDS.has(field)) throw new Error(`Unsupported agent field '${field}'`);
|
|
66
66
|
if (Object.hasOwn(values, field)) throw new Error(`Duplicate agent field '${field}'`);
|
|
67
|
-
const body
|
|
67
|
+
const body = [];
|
|
68
68
|
let closed = false;
|
|
69
69
|
while (++index < lines.length) {
|
|
70
70
|
if (/^"""\s*$/.test(lines[index])) { closed = true; break; }
|
|
@@ -85,7 +85,7 @@ function parseAgentToml(source: any, { fileName }: { fileName?: string } = {}):
|
|
|
85
85
|
return values;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function validateAgentContract(source
|
|
88
|
+
function validateAgentContract(source , { fileName } = {}) {
|
|
89
89
|
const agent = parseAgentToml(source, { fileName });
|
|
90
90
|
for (const field of REQUIRED_FIELDS) {
|
|
91
91
|
if (typeof agent[field] !== 'string' || !agent[field].trim()) throw new Error(`Agent is missing required '${field}'`);
|
|
@@ -106,7 +106,7 @@ function validateAgentContract(source: any, { fileName }: { fileName?: string }
|
|
|
106
106
|
return Object.freeze({ ...agent, fileName: filenameFor(agent.name) });
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
function agentDirectory({ scope, codexDir, projectRoot, paths }
|
|
109
|
+
function agentDirectory({ scope, codexDir, projectRoot, paths } = {}) {
|
|
110
110
|
// Preferred: the declared path resolved by the adapter (harnesses.json "paths".agentsDirectory).
|
|
111
111
|
if (paths?.agentsDirectory) return paths.agentsDirectory;
|
|
112
112
|
// Legacy derivation for direct callers passing raw scope inputs; kept byte-compatible with the
|
|
@@ -122,19 +122,19 @@ function agentDirectory({ scope, codexDir, projectRoot, paths }: { scope?: strin
|
|
|
122
122
|
throw new Error(`Unsupported Codex agent scope: ${scope}`);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
function destinationFor(directory
|
|
125
|
+
function destinationFor(directory , name ) {
|
|
126
126
|
const file = path.resolve(directory, filenameFor(name));
|
|
127
127
|
const root = `${path.resolve(directory)}${path.sep}`;
|
|
128
128
|
if (!file.startsWith(root)) throw new Error(`Refusing to write agent '${name}' outside the Codex agents directory`);
|
|
129
129
|
return file;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
function discoverCodexAgents(sourceDir
|
|
132
|
+
function discoverCodexAgents(sourceDir , fsImpl = fs) {
|
|
133
133
|
if (!sourceDir) throw new Error('sourceDir is required for Codex agents');
|
|
134
134
|
const directory = path.resolve(sourceDir);
|
|
135
135
|
if (!fsImpl.existsSync(directory)) throw new Error(`Codex agent source directory does not exist: ${sourceDir}`);
|
|
136
|
-
const agents
|
|
137
|
-
for (const entry of fsImpl.readdirSync(directory, { withFileTypes: true }).sort((a
|
|
136
|
+
const agents = [];
|
|
137
|
+
for (const entry of fsImpl.readdirSync(directory, { withFileTypes: true }).sort((a , b ) => a.name.localeCompare(b.name))) {
|
|
138
138
|
if (!entry.name.endsWith('.toml')) continue;
|
|
139
139
|
if (!entry.isFile()) throw new Error(`Codex agent source '${entry.name}' must be a regular file`);
|
|
140
140
|
const name = expectedName(entry.name);
|
|
@@ -145,11 +145,11 @@ function discoverCodexAgents(sourceDir: any, fsImpl: any = fs): any[] {
|
|
|
145
145
|
return agents;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
function isOwnedResource(resource
|
|
148
|
+
function isOwnedResource(resource , scope ) {
|
|
149
149
|
return resource && resource.target === 'codex' && resource.scope === scope && resource.kind === AGENT_KIND && typeof resource.identity === 'string';
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
function resourceFor(agent
|
|
152
|
+
function resourceFor(agent , scope , sourceVersion ) {
|
|
153
153
|
return {
|
|
154
154
|
target: 'codex', scope, kind: AGENT_KIND, identity: `agent:${agent.name}`,
|
|
155
155
|
sourceVersion, fingerprint: agent.fingerprint, selection: true,
|
|
@@ -163,13 +163,13 @@ function resourceFor(agent: any, scope: any, sourceVersion: any): any {
|
|
|
163
163
|
* recording ownership instead of refusing. See planCodexConfig for why this is not `force`: a
|
|
164
164
|
* record that disagrees with the file on disk stays a conflict regardless of `adopt`.
|
|
165
165
|
*/
|
|
166
|
-
function planCodexAgents({ scope, codexDir, projectRoot, sourceDir, sourceVersion = 'unknown', managedResources = [], adopt = false, fsImpl = fs }
|
|
166
|
+
function planCodexAgents({ scope, codexDir, projectRoot, sourceDir, sourceVersion = 'unknown', managedResources = [], adopt = false, fsImpl = fs } ) {
|
|
167
167
|
const directory = agentDirectory({ scope, codexDir, projectRoot });
|
|
168
168
|
const agents = discoverCodexAgents(sourceDir, fsImpl);
|
|
169
|
-
const owned = new Map
|
|
170
|
-
const changes
|
|
171
|
-
const conflicts
|
|
172
|
-
const nextManagedResources = managedResources.filter((resource
|
|
169
|
+
const owned = new Map (managedResources.filter((resource ) => isOwnedResource(resource, scope)).map((resource ) => [resource.identity, resource]));
|
|
170
|
+
const changes = [];
|
|
171
|
+
const conflicts = [];
|
|
172
|
+
const nextManagedResources = managedResources.filter((resource ) => !isOwnedResource(resource, scope));
|
|
173
173
|
for (const agent of agents) {
|
|
174
174
|
const file = destinationFor(directory, agent.name);
|
|
175
175
|
const identity = `agent:${agent.name}`;
|
|
@@ -191,13 +191,13 @@ function planCodexAgents({ scope, codexDir, projectRoot, sourceDir, sourceVersio
|
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
function applyCodexAgents(plan
|
|
194
|
+
function applyCodexAgents(plan , { dryRun = false, fsImpl = fs } = {}) {
|
|
195
195
|
if (!plan.ok || dryRun || plan.status === 'unchanged') return { ...plan, applied: false };
|
|
196
196
|
for (const change of plan.changes) atomicWrite(change.file, change.content, fsImpl);
|
|
197
197
|
return { ...plan, applied: true };
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
function reconcileCodexAgents(options
|
|
200
|
+
function reconcileCodexAgents(options ) {
|
|
201
201
|
return applyCodexAgents(planCodexAgents(options), options);
|
|
202
202
|
}
|
|
203
203
|
|