@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
|
@@ -9,25 +9,25 @@ const fs = require('node:fs');
|
|
|
9
9
|
const os = require('node:os');
|
|
10
10
|
const path = require('node:path');
|
|
11
11
|
const crypto = require('node:crypto');
|
|
12
|
-
const { configPath, fingerprint: configFingerprint, parseToml, planCodexConfig, applyCodexConfig } = require('./config.
|
|
13
|
-
const { renderServer, planCodexMcp, applyCodexMcp, ownedCodexMcpIds, userDefinedCodexMcpIds, serverTableText } = require('./mcp.
|
|
14
|
-
const { agentDirectory, discoverCodexAgents, planCodexAgents, applyCodexAgents } = require('./agents.
|
|
15
|
-
const { planCodexHooks, deployCodexHooks, removeCodexHookScripts } = require('./hooks.
|
|
16
|
-
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices } = require('../copy-tree.
|
|
17
|
-
const { mergeMarkedSection, removeMarkedSection, MARKER_START, MARKER_END } = require('../../helper/marker-merge.
|
|
18
|
-
const { nativeMcpCatalog } = require('../../registry/index.
|
|
19
|
-
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.
|
|
12
|
+
const { configPath, fingerprint: configFingerprint, parseToml, planCodexConfig, applyCodexConfig } = require('./config.js');
|
|
13
|
+
const { renderServer, planCodexMcp, applyCodexMcp, ownedCodexMcpIds, userDefinedCodexMcpIds, serverTableText } = require('./mcp.js');
|
|
14
|
+
const { agentDirectory, discoverCodexAgents, planCodexAgents, applyCodexAgents } = require('./agents.js');
|
|
15
|
+
const { planCodexHooks, deployCodexHooks, removeCodexHookScripts, scriptFingerprint } = require('./hooks.js');
|
|
16
|
+
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices } = require('../copy-tree.js');
|
|
17
|
+
const { mergeMarkedSection, removeMarkedSection, MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
|
|
18
|
+
const { nativeMcpCatalog } = require('../../registry/index.js');
|
|
19
|
+
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
|
|
20
20
|
|
|
21
21
|
const HARNESS = 'codex';
|
|
22
22
|
|
|
23
23
|
/** Every server a capability provider names as its MCP server. */
|
|
24
|
-
function providerMcpServers(registry
|
|
25
|
-
return Object.values
|
|
24
|
+
function providerMcpServers(registry ) {
|
|
25
|
+
return Object.values (registry?.capabilities || {}).flatMap((capability) => (capability.providers || []).map((provider ) => provider.mcp?.server)).filter(Boolean);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/** The servers config.toml defines among `names`, whoever wrote them: the tables DoFlow owns plus the
|
|
29
29
|
* user's own. `null` while the file is absent or cannot be parsed. */
|
|
30
|
-
function configuredCodexMcpIds({ config, scope, fsImpl, mcpRecords, names }
|
|
30
|
+
function configuredCodexMcpIds({ config, scope, fsImpl, mcpRecords, names } ) {
|
|
31
31
|
try {
|
|
32
32
|
if (!fsImpl.existsSync(config)) return null;
|
|
33
33
|
parseToml(fsImpl.readFileSync(config, 'utf8'));
|
|
@@ -44,7 +44,7 @@ function configuredCodexMcpIds({ config, scope, fsImpl, mcpRecords, names }: any
|
|
|
44
44
|
* configured instance so direct callers keep their historical shape.
|
|
45
45
|
*/
|
|
46
46
|
function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } = {}) {
|
|
47
|
-
function normalizeContext({ scope, scopeRoot, projectRoot, codexDir, homeDir }
|
|
47
|
+
function normalizeContext({ scope, scopeRoot, projectRoot, codexDir, homeDir } = {}) {
|
|
48
48
|
if (!['project', 'global', 'user'].includes(scope)) throw new Error(`Unsupported Codex scope '${scope}'`);
|
|
49
49
|
if (scope === 'project') {
|
|
50
50
|
// Logic residue (input normalization, not a path fact): scopeRoot is accepted as the selected
|
|
@@ -65,7 +65,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
65
65
|
|
|
66
66
|
const nativeContext = normalizeContext;
|
|
67
67
|
|
|
68
|
-
function discover(options
|
|
68
|
+
function discover(options , { fsImpl = fs } = {}) {
|
|
69
69
|
const context = nativeContext(options);
|
|
70
70
|
const paths = context.paths;
|
|
71
71
|
const config = paths.configFile;
|
|
@@ -85,9 +85,9 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function render({ registry, asset }
|
|
88
|
+
function render({ registry, asset } ) {
|
|
89
89
|
if (!registry || !asset) throw new Error('render requires registry and asset');
|
|
90
|
-
const harness = registry.harnesses.find((item
|
|
90
|
+
const harness = registry.harnesses.find((item ) => item.id === HARNESS);
|
|
91
91
|
if (!harness) throw new Error('Registry does not define the Codex harness');
|
|
92
92
|
const projection = asset.projection?.[HARNESS];
|
|
93
93
|
if (!projection) return { harness: HARNESS, assetId: asset.id, status: 'not-applicable' };
|
|
@@ -105,20 +105,20 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function assetIdFor(options
|
|
108
|
+
function assetIdFor(options , componentName ) {
|
|
109
109
|
const explicit = options.projection?.assetIds?.[componentName];
|
|
110
110
|
if (explicit) return explicit;
|
|
111
111
|
const assets = options.assets || [];
|
|
112
|
-
if (componentName === 'agents') return assets.find((asset
|
|
113
|
-
return assets.find((asset
|
|
112
|
+
if (componentName === 'agents') return assets.find((asset ) => asset.id === 'agents.shared')?.id ?? assets[0]?.id;
|
|
113
|
+
return assets.find((asset ) => asset.id === 'guidance.codex-pointer')?.id ?? assets[0]?.id ?? `codex.${componentName}`;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
function ownershipKind(componentName
|
|
117
|
-
return ({ config: 'configuration-entry', mcp: 'mcp-server', agents: 'custom-agent', hooks: 'hooks-file' }
|
|
116
|
+
function ownershipKind(componentName ) {
|
|
117
|
+
return ({ config: 'configuration-entry', mcp: 'mcp-server', agents: 'custom-agent', hooks: 'hooks-file' } )[componentName] ?? componentName;
|
|
118
118
|
}
|
|
119
|
-
function ownershipIdentity(componentName
|
|
119
|
+
function ownershipIdentity(componentName , identity ) { return `doflow:codex:${ownershipKind(componentName)}:${identity}`; }
|
|
120
120
|
|
|
121
|
-
function addChanges(output
|
|
121
|
+
function addChanges(output , assetId , component , componentName ) {
|
|
122
122
|
for (const change of component?.changes || []) output.push({
|
|
123
123
|
harness: HARNESS,
|
|
124
124
|
assetId,
|
|
@@ -134,8 +134,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
function sha256(text
|
|
138
|
-
function lifecycleResource({ scope, assetId, kind, identity, target, fingerprint, sourceVersion, selection, recoveryRef, projection }
|
|
137
|
+
function sha256(text ) { return `sha256:${crypto.createHash('sha256').update(text).digest('hex')}`; }
|
|
138
|
+
function lifecycleResource({ scope, assetId, kind, identity, target, fingerprint, sourceVersion, selection, recoveryRef, projection } ) {
|
|
139
139
|
return { harness: HARNESS, scope, assetId, kind, identity, target, ownershipIdentity: `doflow:codex:${kind}:${identity}`,
|
|
140
140
|
fingerprint, sourceVersion: sourceVersion ?? 'unknown', selection: selection ?? true, recoveryRef: recoveryRef ?? null, projection };
|
|
141
141
|
}
|
|
@@ -143,8 +143,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
143
143
|
/** Convert neutral ledger entries to the legacy planner's ownership record.
|
|
144
144
|
* An absolute target must match the exact native file this component will
|
|
145
145
|
* reconcile; a record from another Codex project cannot authorize this plan. */
|
|
146
|
-
function nativeManagedResources(resources
|
|
147
|
-
return (resources || []).flatMap((resource
|
|
146
|
+
function nativeManagedResources(resources , context , { kind, target, directory } = {}) {
|
|
147
|
+
return (resources || []).flatMap((resource ) => {
|
|
148
148
|
if (!resource || resource.kind !== kind) return [];
|
|
149
149
|
// Retain the pre-registry native record shape for direct callers/tests.
|
|
150
150
|
if (!resource.harness && resource.target === HARNESS) {
|
|
@@ -162,36 +162,36 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
162
162
|
/** Whether a row of the OTHER scope claims `target` (for a config or MCP entry: the same entry in
|
|
163
163
|
* that file). A project rooted at $HOME shares `~/.codex`, `~/.agents` and the one ledger with the
|
|
164
164
|
* global install, so its removal must leave whatever the global rows still own, and vice versa. */
|
|
165
|
-
function claimedByOtherScope(resources
|
|
165
|
+
function claimedByOtherScope(resources , context , target , identity ) {
|
|
166
166
|
if (typeof target !== 'string') return false;
|
|
167
|
-
return (resources || []).some((resource
|
|
167
|
+
return (resources || []).some((resource ) => resource?.scope && resource.scope !== context.scope
|
|
168
168
|
&& typeof resource.target === 'string' && path.resolve(resource.target) === path.resolve(target)
|
|
169
169
|
&& (identity === undefined || resource.identity === identity));
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
function claimedRowElsewhere(resources
|
|
172
|
+
function claimedRowElsewhere(resources , context , resource ) {
|
|
173
173
|
return claimedByOtherScope(resources, context, resource.target,
|
|
174
174
|
['configuration-entry', 'config-entry', 'mcp-server'].includes(resource.kind) ? resource.identity : undefined);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
/** The rows a removal may act on: this scope's own, minus any path the other scope still claims. */
|
|
178
|
-
function removableRows(resources
|
|
179
|
-
return (resources || []).filter((resource
|
|
178
|
+
function removableRows(resources , context ) {
|
|
179
|
+
return (resources || []).filter((resource ) => (!resource?.scope || resource.scope === context.scope)
|
|
180
180
|
&& !claimedRowElsewhere(resources, context, resource));
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/** This scope's rows on a path the other scope still claims become retained removals: the
|
|
184
184
|
* lifecycle releases the row and never hands the change to remove(), so nothing is deleted. */
|
|
185
|
-
function retainedRemovals(resources
|
|
186
|
-
return (resources || []).filter((resource
|
|
185
|
+
function retainedRemovals(resources , context ) {
|
|
186
|
+
return (resources || []).filter((resource ) => resource?.harness === HARNESS && resource.scope === context.scope
|
|
187
187
|
&& claimedRowElsewhere(resources, context, resource))
|
|
188
|
-
.map((resource
|
|
188
|
+
.map((resource ) => ({ harness: HARNESS, assetId: resource.assetId, target: resource.target, operation: 'remove',
|
|
189
189
|
ownershipIdentity: resource.ownershipIdentity, kind: resource.kind, identity: resource.identity,
|
|
190
190
|
retained: true, retainedFor: [`${HARNESS} (${context.scope === 'global' ? 'project' : 'global'} scope)`] }));
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
function ownedRemovalPlan(resources
|
|
194
|
-
const entries = (resources || []).filter((resource
|
|
193
|
+
function ownedRemovalPlan(resources , context , kind , directory ) {
|
|
194
|
+
const entries = (resources || []).filter((resource ) => resource?.harness === HARNESS && resource.kind === kind);
|
|
195
195
|
const changes = []; const conflicts = [];
|
|
196
196
|
for (const resource of entries) {
|
|
197
197
|
const target = kind === 'custom-agent'
|
|
@@ -207,38 +207,38 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
207
207
|
return { ok: conflicts.length === 0, status: conflicts.length ? 'conflict' : (changes.length ? 'change' : 'unchanged'), changes, conflicts, directRemove: true };
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
function configResourcesFrom(config
|
|
210
|
+
function configResourcesFrom(config , { scope, sourceVersion, recoveryRef } ) {
|
|
211
211
|
return (config?.managedResources || [])
|
|
212
|
-
.filter((record
|
|
213
|
-
.map((record
|
|
212
|
+
.filter((record ) => record.kind === 'configuration-entry' || record.kind === 'config-entry')
|
|
213
|
+
.map((record ) => lifecycleResource({ scope, assetId: 'guidance.codex-pointer', kind: 'configuration-entry', identity: record.identity,
|
|
214
214
|
target: config.file, fingerprint: record.fingerprint, sourceVersion: record.sourceVersion ?? sourceVersion, recoveryRef,
|
|
215
215
|
projection: { renderer: 'codex-config' } }));
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
function mcpResourcesFrom(mcp
|
|
218
|
+
function mcpResourcesFrom(mcp , { scope, sourceVersion, recoveryRef } ) {
|
|
219
219
|
return (mcp?.managedResources || [])
|
|
220
|
-
.filter((record
|
|
221
|
-
.map((record
|
|
220
|
+
.filter((record ) => record.kind === 'mcp-server')
|
|
221
|
+
.map((record ) => lifecycleResource({ scope, assetId: 'guidance.codex-pointer', kind: 'mcp-server', identity: record.identity,
|
|
222
222
|
target: mcp.file, fingerprint: record.fingerprint, sourceVersion: record.sourceVersion ?? sourceVersion, selection: record.selection, recoveryRef,
|
|
223
223
|
projection: { renderer: 'codex-mcp' } }));
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
function agentResourcesFrom(agents
|
|
226
|
+
function agentResourcesFrom(agents , { scope, sourceVersion, recoveryRef } ) {
|
|
227
227
|
return (agents?.managedResources || [])
|
|
228
|
-
.filter((record
|
|
229
|
-
.map((record
|
|
228
|
+
.filter((record ) => record.kind === 'custom-agent')
|
|
229
|
+
.map((record ) => lifecycleResource({ scope, assetId: 'agents.shared', kind: 'custom-agent', identity: record.identity,
|
|
230
230
|
target: path.join(agents.directory, `${record.identity.replace(/^agent:/, '')}.toml`), fingerprint: record.fingerprint,
|
|
231
231
|
sourceVersion: record.sourceVersion ?? sourceVersion, recoveryRef, projection: { renderer: 'codex-agent' } }));
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
function hooksResourceFrom(hooks
|
|
234
|
+
function hooksResourceFrom(hooks , { scope, sourceVersion, recoveryRef } ) {
|
|
235
235
|
if (!hooks?.applied || !hooks.destination) return [];
|
|
236
236
|
return [lifecycleResource({ scope, assetId: 'guidance.codex-pointer', kind: 'hooks-file', identity: 'hooks.json',
|
|
237
237
|
target: hooks.destination, fingerprint: sha256(fs.readFileSync(hooks.destination, 'utf8')), sourceVersion, recoveryRef,
|
|
238
238
|
projection: { renderer: 'codex-hooks' } })];
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
function resourcesFromApplied({ components, scope, sourceVersion, recoveryRef }
|
|
241
|
+
function resourcesFromApplied({ components, scope, sourceVersion, recoveryRef } ) {
|
|
242
242
|
const shared = { scope, sourceVersion, recoveryRef };
|
|
243
243
|
return [
|
|
244
244
|
...configResourcesFrom(components.config, shared),
|
|
@@ -250,8 +250,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
250
250
|
|
|
251
251
|
// ---- copy-tree assets (rules, skills, agents, templates, scripts, references) ----
|
|
252
252
|
|
|
253
|
-
function codexConfigDir(context
|
|
254
|
-
function sourceDirFor(asset
|
|
253
|
+
function codexConfigDir(context ) { return context.paths.configDir; }
|
|
254
|
+
function sourceDirFor(asset , repoRoot ) {
|
|
255
255
|
if (!asset || typeof asset.source !== 'string') throw new Error('Codex asset requires a source path');
|
|
256
256
|
const root = repoRoot ? path.resolve(repoRoot) : process.cwd();
|
|
257
257
|
const source = path.resolve(root, asset.source);
|
|
@@ -269,16 +269,16 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
269
269
|
// declared in harnesses.json (paths.instructions). The registry declares this asset's codex
|
|
270
270
|
// projection as renderer "codex-agents" (see core/registry/assets.json).
|
|
271
271
|
|
|
272
|
-
function instructionsAsset(assets
|
|
273
|
-
function instructionsPath(context
|
|
272
|
+
function instructionsAsset(assets ) { return (assets || []).find((asset ) => asset?.renderer === 'codex-agents'); }
|
|
273
|
+
function instructionsPath(context ) {
|
|
274
274
|
return context.paths.instructions;
|
|
275
275
|
}
|
|
276
|
-
function sha256Text(text
|
|
276
|
+
function sha256Text(text ) { return crypto.createHash('sha256').update(text).digest('hex'); }
|
|
277
277
|
|
|
278
|
-
function planInstructionsAsset({ assets, context, removing, repoRoot, claimedElsewhere = () => false }
|
|
278
|
+
function planInstructionsAsset({ assets, context, removing, repoRoot, claimedElsewhere = () => false } ) {
|
|
279
279
|
const asset = instructionsAsset(assets);
|
|
280
|
-
const changes
|
|
281
|
-
const conflicts
|
|
280
|
+
const changes = [];
|
|
281
|
+
const conflicts = [];
|
|
282
282
|
if (!asset) return { changes, conflicts };
|
|
283
283
|
const target = instructionsPath(context);
|
|
284
284
|
// Broken markers are a plan conflict, as for Claude: found only while applying, they would stop
|
|
@@ -306,22 +306,22 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
306
306
|
return { changes, conflicts };
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
function applyInstructionsAsset(changes
|
|
309
|
+
function applyInstructionsAsset(changes ) {
|
|
310
310
|
for (const change of changes) {
|
|
311
311
|
if (change.projection?.renderer !== 'codex-agents' || change.operation === 'remove') continue;
|
|
312
312
|
mergeMarkedSection(change.source, change.target);
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
function removeInstructionsAsset(changes
|
|
316
|
+
function removeInstructionsAsset(changes ) {
|
|
317
317
|
for (const change of changes) {
|
|
318
318
|
if (change.projection?.renderer === 'codex-agents' && change.operation === 'remove') removeMarkedSection(change.target);
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
function verifyInstructionsAsset({ assets, context, repoRoot }
|
|
322
|
+
function verifyInstructionsAsset({ assets, context, repoRoot } ) {
|
|
323
323
|
const asset = instructionsAsset(assets);
|
|
324
|
-
const statuses
|
|
324
|
+
const statuses = []; const resources = [];
|
|
325
325
|
if (!asset) return { statuses, resources };
|
|
326
326
|
const target = instructionsPath(context);
|
|
327
327
|
const existing = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : null;
|
|
@@ -335,7 +335,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
335
335
|
return { statuses, resources };
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
function planCopyTreeAssets({ assets, context, neutralResources, removing, repoRoot, sourceVersion, force = false }
|
|
338
|
+
function planCopyTreeAssets({ assets, context, neutralResources, removing, repoRoot, sourceVersion, force = false } ) {
|
|
339
339
|
const changes = [];
|
|
340
340
|
const conflicts = [];
|
|
341
341
|
const treeResults = [];
|
|
@@ -352,10 +352,10 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
352
352
|
// never deleted, forced or not.
|
|
353
353
|
force: !removing && force === true });
|
|
354
354
|
treeResults.push(result);
|
|
355
|
-
conflicts.push(...result.conflicts.map((reason
|
|
355
|
+
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
356
356
|
// A hand-edited file at a path DoFlow no longer writes is the user's: it stays, only its
|
|
357
357
|
// ownership row is released.
|
|
358
|
-
notices.push(...result.kept.map((item
|
|
358
|
+
notices.push(...result.kept.map((item ) => `kept hand-edited ${path.relative(context.paths.root, item.target)}; DoFlow no longer manages it`));
|
|
359
359
|
for (const change of result.changes) {
|
|
360
360
|
changes.push({
|
|
361
361
|
assetId: asset.id, target: change.target, source: change.source, operation: change.operation,
|
|
@@ -370,19 +370,19 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
370
370
|
return { changes, conflicts, notices: [...siblingReplacedNotices(treeResults), ...notices] };
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
function applyCopyTreeAssets(changes
|
|
374
|
-
const treeChanges = changes.filter((change
|
|
375
|
-
.map((change
|
|
373
|
+
function applyCopyTreeAssets(changes ) {
|
|
374
|
+
const treeChanges = changes.filter((change ) => change.projection?.renderer === 'copy-tree' && change.operation !== 'remove')
|
|
375
|
+
.map((change ) => ({ relPath: change.identity, target: change.target, source: change.source, operation: change.operation, fingerprint: change.fingerprint }));
|
|
376
376
|
return applyTree({ changes: treeChanges }).applied;
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
-
function removeCopyTreeAssets(changes
|
|
380
|
-
const treeChanges = changes.filter((change
|
|
381
|
-
.map((change
|
|
379
|
+
function removeCopyTreeAssets(changes ) {
|
|
380
|
+
const treeChanges = changes.filter((change ) => change.projection?.renderer === 'copy-tree' && change.operation === 'remove' && !change.retained)
|
|
381
|
+
.map((change ) => ({ relPath: change.identity, target: change.target, operation: 'remove', fingerprint: change.fingerprint, pruneGuard: change.pruneGuard }));
|
|
382
382
|
return removeTree({ changes: treeChanges }).removed;
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
-
function verifyCopyTreeAssets({ assets, context, repoRoot, sourceVersion, plannedWrites }
|
|
385
|
+
function verifyCopyTreeAssets({ assets, context, repoRoot, sourceVersion, plannedWrites } ) {
|
|
386
386
|
const statuses = [];
|
|
387
387
|
const resources = [];
|
|
388
388
|
const conflicts = [];
|
|
@@ -390,7 +390,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
390
390
|
const destDir = copyTreeDestDir(codexConfigDir(context), asset);
|
|
391
391
|
const sourceDir = sourceDirFor(asset, repoRoot);
|
|
392
392
|
const result = verifyTree({ sourceDir, destDir, layout: asset.layout, plannedWrites });
|
|
393
|
-
conflicts.push(...result.conflicts.map((reason
|
|
393
|
+
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
394
394
|
for (const resource of result.resources) {
|
|
395
395
|
resources.push({
|
|
396
396
|
assetId: asset.id, target: resource.target, ownershipIdentity: `doflow:codex:copy-tree:${asset.id}:${resource.relPath}`,
|
|
@@ -408,11 +408,11 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
408
408
|
* callers may still use legacy option names; lifecycle callers pass `assets`,
|
|
409
409
|
* `mcp`, `policies`, and optionally `projection` for source-bearing surfaces.
|
|
410
410
|
* Native source paths are never guessed from an asset's renderer. */
|
|
411
|
-
function projectedNativeOptions(options
|
|
411
|
+
function projectedNativeOptions(options ) {
|
|
412
412
|
const projection = options.projection || {};
|
|
413
413
|
const configResources = projection.configResources ?? options.configResources ?? options.context?.codexConfigResources;
|
|
414
414
|
const mcpCatalog = projection.mcp?.catalog ?? options.mcpCatalog ?? options.mcp ?? [];
|
|
415
|
-
const selectedMcp = projection.mcp?.selected ?? options.selectedMcp ?? (options.mcp ? options.mcp.map((server
|
|
415
|
+
const selectedMcp = projection.mcp?.selected ?? options.selectedMcp ?? (options.mcp ? options.mcp.map((server ) => server.id) : undefined);
|
|
416
416
|
const agentsSourceDir = projection.agents?.sourceDir ?? options.agentsSourceDir ?? options.context?.codexAgentsSourceDir;
|
|
417
417
|
const hooksSourceFile = projection.hooks?.sourceFile ?? options.hooksSourceFile ?? options.context?.codexHooksSourceFile;
|
|
418
418
|
const hooksSourceDir = projection.hooks?.sourceHooksDir ?? options.hooksSourceDir ?? options.context?.codexHooksSourceDir;
|
|
@@ -421,7 +421,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
421
421
|
return { configResources, mcpCatalog, selectedMcp, agentsSourceDir, hooksSourceFile, hooksSourceDir, hooksConfig, hooksTrusted };
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
-
function planConfigComponent({ native, neutralResources, context, configFile, removing, adopt = false }
|
|
424
|
+
function planConfigComponent({ native, neutralResources, context, configFile, removing, adopt = false } ) {
|
|
425
425
|
if (native.configResources === undefined) return undefined;
|
|
426
426
|
const managedResources = nativeManagedResources(neutralResources, context, { kind: 'configuration-entry', target: configFile });
|
|
427
427
|
const component = planCodexConfig({ file: configFile, scope: context.scope, managedResources, desiredResources: removing ? [] : native.configResources, adopt: !removing && adopt });
|
|
@@ -433,7 +433,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
433
433
|
return component;
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
-
function planMcpComponent({ native, neutralResources, context, configFile, removing, options }
|
|
436
|
+
function planMcpComponent({ native, neutralResources, context, configFile, removing, options } ) {
|
|
437
437
|
if (native.selectedMcp === undefined) return undefined;
|
|
438
438
|
const catalog = nativeMcpCatalog(native.mcpCatalog);
|
|
439
439
|
const managedResources = nativeManagedResources(neutralResources, context, { kind: 'mcp-server', target: configFile });
|
|
@@ -445,9 +445,16 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
445
445
|
/** Codex hooks.json is only replaced when the file on disk still matches the ledger's last-known
|
|
446
446
|
* fingerprint — a foreign edit (or a record the ledger never owned) downgrades an otherwise-clean
|
|
447
447
|
* plan to a conflict rather than silently overwriting it. */
|
|
448
|
-
|
|
448
|
+
/** This scope's ledger rows for the hook scripts deployed beside hooks.json. */
|
|
449
|
+
function scriptRows(neutralResources , context ) {
|
|
450
|
+
return (neutralResources || []).filter((resource ) => resource?.harness === HARNESS && resource.kind === 'hooks-script'
|
|
451
|
+
&& (!resource.scope || resource.scope === context.scope));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function planHooksComponent({ native, neutralResources, context, adopt = false, force = false } ) {
|
|
449
455
|
let hooks = planCodexHooks({ config: native.hooksConfig, sourceFile: native.hooksSourceFile,
|
|
450
|
-
sourceHooksDir: native.hooksSourceDir, trusted: native.hooksTrusted, destinationContext: context
|
|
456
|
+
sourceHooksDir: native.hooksSourceDir, trusted: native.hooksTrusted, destinationContext: context,
|
|
457
|
+
scriptRows: scriptRows(neutralResources, context), force });
|
|
451
458
|
const hooksFile = hooks.destination;
|
|
452
459
|
if (hooks.ok && fs.existsSync(hooksFile)) {
|
|
453
460
|
const [record] = nativeManagedResources(neutralResources, context, { kind: 'hooks-file', target: hooksFile });
|
|
@@ -457,7 +464,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
457
464
|
// conflict — that one is `--force`'s business, not adoption's.
|
|
458
465
|
if (!record && !adopt) {
|
|
459
466
|
hooks = { ...hooks, ok: false, status: 'conflict', changes: [], errors: ['Codex hooks.json exists but is not owned by the neutral ledger'] };
|
|
460
|
-
} else if (record && record.fingerprint !== current) {
|
|
467
|
+
} else if (record && record.fingerprint !== current && !force) {
|
|
461
468
|
hooks = { ...hooks, ok: false, status: 'conflict', changes: [], errors: ['Codex hooks.json was modified outside DoFlow'] };
|
|
462
469
|
}
|
|
463
470
|
}
|
|
@@ -466,16 +473,25 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
466
473
|
|
|
467
474
|
/** Agents/hooks share one removing-vs-planning fork: removal always owns both via the neutral
|
|
468
475
|
* ledger's own records, regardless of what native input the caller provided this run. */
|
|
469
|
-
|
|
476
|
+
/** A removal drops the rows of the hook scripts too. The scripts themselves go by their bytes
|
|
477
|
+
* (removeCodexHookScripts), so each row is only released here. */
|
|
478
|
+
function withScriptReleases(plan , neutralResources , context , extra ) {
|
|
479
|
+
const releases = scriptRows(neutralResources, context).map((row ) => ({ type: 'remove', assetId: row.assetId, identity: row.identity,
|
|
480
|
+
target: row.target, ownershipIdentity: row.ownershipIdentity, kind: row.kind, release: true }));
|
|
481
|
+
return { ...plan, ...extra, changes: [...plan.changes, ...releases],
|
|
482
|
+
status: plan.status === 'unchanged' && releases.length ? 'change' : plan.status };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function planAgentsAndHooksComponents({ native, neutralResources, context, agentsDirectory, removing, options, adopt = false } ) {
|
|
470
486
|
if (removing) {
|
|
471
487
|
return {
|
|
472
488
|
agents: ownedRemovalPlan(neutralResources, context, 'custom-agent', agentsDirectory),
|
|
473
489
|
// The scripts deployed beside hooks.json have no ledger rows; remove() matches them by bytes.
|
|
474
|
-
hooks:
|
|
475
|
-
scriptsDir: native.hooksSourceDir, destinationHooksDir: context.paths.hooksDirectory },
|
|
490
|
+
hooks: withScriptReleases(ownedRemovalPlan(neutralResources, context, 'hooks-file'), neutralResources, context,
|
|
491
|
+
{ scriptsDir: native.hooksSourceDir, destinationHooksDir: context.paths.hooksDirectory }),
|
|
476
492
|
};
|
|
477
493
|
}
|
|
478
|
-
const components
|
|
494
|
+
const components = {};
|
|
479
495
|
if (native.agentsSourceDir !== undefined) {
|
|
480
496
|
const managedResources = nativeManagedResources(neutralResources, context, { kind: 'custom-agent', directory: agentsDirectory });
|
|
481
497
|
// `...context` is the NARROW native context (scope/codexDir/paths) and carries no adopt, so
|
|
@@ -483,7 +499,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
483
499
|
components.agents = planCodexAgents({ ...context, managedResources, sourceDir: native.agentsSourceDir, sourceVersion: options.sourceVersion ?? options.context?.sourceVersion, adopt });
|
|
484
500
|
}
|
|
485
501
|
if (native.hooksSourceFile !== undefined || native.hooksConfig !== undefined) {
|
|
486
|
-
components.hooks = planHooksComponent({ native, neutralResources, context, adopt });
|
|
502
|
+
components.hooks = planHooksComponent({ native, neutralResources, context, adopt, force: options.context?.force === true });
|
|
487
503
|
}
|
|
488
504
|
return components;
|
|
489
505
|
}
|
|
@@ -493,7 +509,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
493
509
|
* its desired input, which prevents a generic plan from accidentally removing
|
|
494
510
|
* an owned configuration entry or MCP server merely because it was omitted.
|
|
495
511
|
*/
|
|
496
|
-
function plan(options
|
|
512
|
+
function plan(options ) {
|
|
497
513
|
const context = nativeContext(options);
|
|
498
514
|
const removing = options.context?.operation === 'remove';
|
|
499
515
|
const ledgerRows = options.managedResources || options.ledger?.resources || [];
|
|
@@ -501,7 +517,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
501
517
|
const native = projectedNativeOptions(options);
|
|
502
518
|
const configFile = context.paths.configFile;
|
|
503
519
|
const agentsDirectory = context.paths.agentsDirectory;
|
|
504
|
-
const components
|
|
520
|
+
const components = {};
|
|
505
521
|
// `options.context` is the lifecycle context; `context` is this adapter's narrow native one.
|
|
506
522
|
// copyTree below already reads force off the former — adopt is read the same way.
|
|
507
523
|
const adopt = options.context?.adopt === true;
|
|
@@ -514,7 +530,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
514
530
|
repoRoot: options.context?.repoRoot, sourceVersion: options.sourceVersion ?? options.context?.sourceVersion,
|
|
515
531
|
force: options.context?.force === true });
|
|
516
532
|
const instructions = planInstructionsAsset({ assets: options.assets, context, removing, repoRoot: options.context?.repoRoot,
|
|
517
|
-
claimedElsewhere: (target
|
|
533
|
+
claimedElsewhere: (target ) => claimedByOtherScope(ledgerRows, context, target) });
|
|
518
534
|
// Two pseudo-components: their changes are already in final adapter shape (unlike the other
|
|
519
535
|
// components' native shape, which addChanges() below converts), so they're appended to `changes`
|
|
520
536
|
// directly rather than run through addChanges. Registering them in `components` still gets their
|
|
@@ -522,8 +538,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
522
538
|
// adapterConflicts() component scan, with no special-casing needed in either.
|
|
523
539
|
components.copyTree = { ok: copyTree.conflicts.length === 0, conflicts: copyTree.conflicts };
|
|
524
540
|
components.instructions = { ok: instructions.conflicts.length === 0, conflicts: instructions.conflicts };
|
|
525
|
-
const failures = Object.entries(components).filter(([, component]
|
|
526
|
-
const changes
|
|
541
|
+
const failures = Object.entries(components).filter(([, component] ) => !component.ok);
|
|
542
|
+
const changes = [];
|
|
527
543
|
addChanges(changes, assetIdFor(options, 'config'), components.config, 'config');
|
|
528
544
|
addChanges(changes, assetIdFor(options, 'mcp'), components.mcp, 'mcp');
|
|
529
545
|
addChanges(changes, assetIdFor(options, 'agents'), components.agents, 'agents');
|
|
@@ -531,7 +547,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
531
547
|
changes.push(...copyTree.changes, ...instructions.changes);
|
|
532
548
|
if (removing) changes.push(...retainedRemovals(ledgerRows, context));
|
|
533
549
|
const requiredNativeResources = [
|
|
534
|
-
...Object.entries(components).flatMap(([component, result]
|
|
550
|
+
...Object.entries(components).flatMap(([component, result] ) => (result.changes || []).map((change ) => ({
|
|
535
551
|
harness: HARNESS, component, target: change.target ?? change.file ?? result.file ?? result.destination ?? result.directory,
|
|
536
552
|
operation: change.operation ?? change.type, identity: change.identity ?? null,
|
|
537
553
|
}))),
|
|
@@ -542,10 +558,29 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
542
558
|
notices: [...copyTree.notices, ...(components.mcp?.notices || [])] };
|
|
543
559
|
}
|
|
544
560
|
|
|
561
|
+
/** The hook front doors (IC-037): hooks.json and the scripts deployed beside it, planned and
|
|
562
|
+
* verified alone. A conflict reaches the lifecycle through `components`, as in plan(). */
|
|
563
|
+
function planHookDoors(options ) {
|
|
564
|
+
const native = projectedNativeOptions(options);
|
|
565
|
+
const hooks = native.hooksSourceFile === undefined && native.hooksConfig === undefined ? null
|
|
566
|
+
: planHooksComponent({ native, neutralResources: options.managedResources || options.ledger?.resources || [], context: nativeContext(options),
|
|
567
|
+
adopt: options.context?.adopt === true, force: options.context?.force === true });
|
|
568
|
+
const changes = [];
|
|
569
|
+
if (hooks) addChanges(changes, assetIdFor(options, 'hooks'), hooks, 'hooks');
|
|
570
|
+
return { changes, components: hooks ? { hooks } : {}, prerequisites: [], notices: [] };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function verifyHookDoors(options = {}) {
|
|
574
|
+
const hooks = verifyHooksResource({ native: projectedNativeOptions(options), context: nativeContext(options), options,
|
|
575
|
+
sourceVersion: options.sourceVersion ?? options.context?.sourceVersion, recoveryRef: options.recoveryRef });
|
|
576
|
+
return { ok: hooks.conflicts.length === 0 && hooks.statuses.every((status ) => status.status !== 'missing' && status.status !== 'conflict'),
|
|
577
|
+
resources: hooks.resources, statuses: hooks.statuses, conflicts: hooks.conflicts };
|
|
578
|
+
}
|
|
579
|
+
|
|
545
580
|
/** Apply only native plans emitted by this adapter. MCP is first because it and
|
|
546
581
|
* config.toml share a file; config is then re-planned over the MCP result so it
|
|
547
582
|
* cannot overwrite an adjacent MCP table. */
|
|
548
|
-
function apply({ changes = [], dryRun = false, scope, sourceVersion, recoveryRef }
|
|
583
|
+
function apply({ changes = [], dryRun = false, scope, sourceVersion, recoveryRef } = {}) {
|
|
549
584
|
const components = new Map();
|
|
550
585
|
for (const change of changes) if (change?.nativeComponent && change.nativePlan) components.set(change.nativeComponent, change.nativePlan);
|
|
551
586
|
let mcp = components.get('mcp');
|
|
@@ -576,23 +611,23 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
576
611
|
/** Removal reuses the same fingerprint-checked native plans. Only config/MCP
|
|
577
612
|
* plans generated with context.operation=remove contain safe removals; hooks
|
|
578
613
|
* and agents are intentionally never blindly deleted. */
|
|
579
|
-
function remove({ changes = [], dryRun = false }
|
|
614
|
+
function remove({ changes = [], dryRun = false } = {}) {
|
|
580
615
|
const result = apply({ changes, dryRun });
|
|
581
616
|
const direct = new Map();
|
|
582
|
-
for (const change of changes) if (change?.nativePlan?.directRemove) direct.set(change.target, change);
|
|
617
|
+
for (const change of changes) if (change?.nativePlan?.directRemove && !change.retained) direct.set(change.target, change);
|
|
583
618
|
for (const change of direct.values()) {
|
|
584
619
|
if (!fs.existsSync(change.target)) continue;
|
|
585
620
|
const current = sha256(fs.readFileSync(change.target, 'utf8'));
|
|
586
621
|
if (current !== change.fingerprint) throw new Error(`Refusing to remove modified Codex resource '${change.identity}'`);
|
|
587
622
|
if (!dryRun) fs.unlinkSync(change.target);
|
|
588
623
|
}
|
|
589
|
-
const hooksPlan = changes.find((change
|
|
624
|
+
const hooksPlan = changes.find((change ) => change?.nativeComponent === 'hooks' && change.nativePlan?.directRemove)?.nativePlan;
|
|
590
625
|
if (hooksPlan) removeCodexHookScripts(hooksPlan, { dryRun });
|
|
591
626
|
if (!dryRun) removeInstructionsAsset(changes);
|
|
592
|
-
return { ...result, removed: changes.filter((change
|
|
627
|
+
return { ...result, removed: changes.filter((change ) => (change.operation ?? change.type) === 'remove').length };
|
|
593
628
|
}
|
|
594
629
|
|
|
595
|
-
function verifyConfigResources({ native, parsed, context, options, sourceVersion, recoveryRef }
|
|
630
|
+
function verifyConfigResources({ native, parsed, context, options, sourceVersion, recoveryRef } ) {
|
|
596
631
|
const statuses = []; const resources = [];
|
|
597
632
|
for (const resource of native.configResources || []) {
|
|
598
633
|
const entry = parsed?.entries.get(resource.identity);
|
|
@@ -607,7 +642,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
607
642
|
|
|
608
643
|
/** A server is recorded only when its table now holds DoFlow's rendering. One discovery found as
|
|
609
644
|
* the user's at plan time is reported `not-managed` and never recorded, even when its bytes match. */
|
|
610
|
-
function verifyMcpResources({ native, configText, context, options, sourceVersion, recoveryRef }
|
|
645
|
+
function verifyMcpResources({ native, configText, context, options, sourceVersion, recoveryRef } ) {
|
|
611
646
|
const statuses = []; const resources = [];
|
|
612
647
|
const catalog = nativeMcpCatalog(native.mcpCatalog);
|
|
613
648
|
const userDefined = new Set(options.discovery?.mcpUserDefined || []);
|
|
@@ -625,8 +660,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
625
660
|
return { statuses, resources };
|
|
626
661
|
}
|
|
627
662
|
|
|
628
|
-
function verifyAgentResources({ native, context, options, sourceVersion, recoveryRef }
|
|
629
|
-
const statuses
|
|
663
|
+
function verifyAgentResources({ native, context, options, sourceVersion, recoveryRef } ) {
|
|
664
|
+
const statuses = []; const resources = [];
|
|
630
665
|
if (!native.agentsSourceDir) return { statuses, resources };
|
|
631
666
|
for (const agent of discoverCodexAgents(native.agentsSourceDir)) {
|
|
632
667
|
const target = path.join(agentDirectory(context), agent.fileName);
|
|
@@ -640,8 +675,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
640
675
|
return { statuses, resources };
|
|
641
676
|
}
|
|
642
677
|
|
|
643
|
-
function verifyHooksResource({ native, context, options, sourceVersion, recoveryRef }
|
|
644
|
-
const statuses
|
|
678
|
+
function verifyHooksResource({ native, context, options, sourceVersion, recoveryRef } ) {
|
|
679
|
+
const statuses = []; const resources = []; const conflicts = [];
|
|
645
680
|
if (!native.hooksSourceFile && !native.hooksConfig) return { statuses, resources, conflicts };
|
|
646
681
|
const hooks = planCodexHooks({ config: native.hooksConfig, sourceFile: native.hooksSourceFile, sourceHooksDir: native.hooksSourceDir,
|
|
647
682
|
trusted: native.hooksTrusted, destinationContext: context });
|
|
@@ -651,14 +686,21 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
651
686
|
if (!hooks.ok) conflicts.push(...hooks.errors);
|
|
652
687
|
if (status === 'managed') resources.push(lifecycleResource({ scope: options.scope, assetId: assetIdFor(options, 'hooks'), kind: 'hooks-file', identity: 'hooks.json',
|
|
653
688
|
target: hooks.destination, fingerprint: sha256(fs.readFileSync(hooks.destination, 'utf8')), sourceVersion, recoveryRef, projection: { renderer: 'codex-hooks' } }));
|
|
689
|
+
if (status === 'managed') {
|
|
690
|
+
for (const name of hooks.scriptNames) {
|
|
691
|
+
resources.push(lifecycleResource({ scope: options.scope, assetId: assetIdFor(options, 'hooks'), kind: 'hooks-script', identity: name,
|
|
692
|
+
target: path.join(hooks.destinationHooksDir, name), fingerprint: scriptFingerprint(fs.readFileSync(path.join(hooks.destinationHooksDir, name))),
|
|
693
|
+
sourceVersion, recoveryRef, projection: { renderer: 'codex-hooks' } }));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
654
696
|
return { statuses, resources, conflicts };
|
|
655
697
|
}
|
|
656
698
|
|
|
657
|
-
function verifyAssetRenderStatuses({ registry, assets }
|
|
658
|
-
return assets.map((asset
|
|
699
|
+
function verifyAssetRenderStatuses({ registry, assets } ) {
|
|
700
|
+
return assets.map((asset ) => render({ registry, asset })).filter((rendered ) => rendered.status !== 'renderable');
|
|
659
701
|
}
|
|
660
702
|
|
|
661
|
-
function verify(options
|
|
703
|
+
function verify(options = {}) {
|
|
662
704
|
const { registry, assets = [], plan: proposed } = options;
|
|
663
705
|
const context = nativeContext(options);
|
|
664
706
|
const native = projectedNativeOptions(options);
|
|
@@ -668,7 +710,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
668
710
|
if (fs.existsSync(context.paths.configFile)) configText = fs.readFileSync(context.paths.configFile, 'utf8');
|
|
669
711
|
let parsed;
|
|
670
712
|
const conflicts = [];
|
|
671
|
-
try { parsed = parseToml(configText); } catch (error
|
|
713
|
+
try { parsed = parseToml(configText); } catch (error ) { conflicts.push(`Malformed Codex config: ${error.message}`); }
|
|
672
714
|
|
|
673
715
|
const shared = { context, options, sourceVersion, recoveryRef };
|
|
674
716
|
const config = verifyConfigResources({ native, parsed, ...shared });
|
|
@@ -685,13 +727,13 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
|
|
|
685
727
|
];
|
|
686
728
|
const resources = [...config.resources, ...mcp.resources, ...agents.resources, ...hooks.resources, ...copyTree.resources, ...instructions.resources];
|
|
687
729
|
conflicts.push(...hooks.conflicts, ...copyTree.conflicts);
|
|
688
|
-
const componentFailures = Object.values
|
|
730
|
+
const componentFailures = Object.values (proposed?.components || {}).filter((component) => !component.ok);
|
|
689
731
|
for (const component of componentFailures) conflicts.push(...(component.conflicts || component.errors || []));
|
|
690
732
|
|
|
691
733
|
return { ok: conflicts.length === 0 && statuses.every((status) => status.status !== 'missing' && status.status !== 'conflict'), resources, statuses, conflicts };
|
|
692
734
|
}
|
|
693
735
|
|
|
694
|
-
return { normalizeContext, nativeContext, projectedNativeOptions, nativeManagedResources, ownershipIdentity, discover, render, plan, apply, remove, verify };
|
|
736
|
+
return { normalizeContext, nativeContext, projectedNativeOptions, nativeManagedResources, ownershipIdentity, discover, render, plan, apply, remove, verify, planHookDoors, verifyHookDoors };
|
|
695
737
|
}
|
|
696
738
|
|
|
697
739
|
const singleton = createCodexAdapter();
|
|
@@ -703,5 +745,6 @@ module.exports = {
|
|
|
703
745
|
ownershipIdentity: singleton.ownershipIdentity,
|
|
704
746
|
discover: singleton.discover, render: singleton.render, plan: singleton.plan, apply: singleton.apply,
|
|
705
747
|
remove: singleton.remove, verify: singleton.verify,
|
|
748
|
+
planHookDoors: singleton.planHookDoors, verifyHookDoors: singleton.verifyHookDoors,
|
|
706
749
|
createCodexAdapter,
|
|
707
750
|
};
|