@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
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
// https://opencode.ai/docs/rules
|
|
17
17
|
const fs = require('node:fs');
|
|
18
18
|
const path = require('node:path');
|
|
19
|
-
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, sharedTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, readJson, sourceDirFor, resolveTransform } = require('../copy-tree.
|
|
20
|
-
const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.
|
|
19
|
+
const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, sharedTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, readJson, sourceDirFor, resolveTransform } = require('../copy-tree.js');
|
|
20
|
+
const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.js');
|
|
21
21
|
|
|
22
22
|
// Only the marker constants: marker-merge.js reads and writes files itself, which cannot be used
|
|
23
23
|
// from plan(), whose contract is to compute changes without touching disk. The gemini adapter
|
|
24
24
|
// solves this the same way, with a pure managedInstruction() over strings.
|
|
25
|
-
const { MARKER_START, MARKER_END } = require('../../helper/marker-merge.
|
|
26
|
-
const { destructiveCommandGlobs } = require('../../helper/guardrails.
|
|
27
|
-
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.
|
|
25
|
+
const { MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
|
|
26
|
+
const { destructiveCommandGlobs } = require('../../helper/guardrails.js');
|
|
27
|
+
const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
|
|
28
28
|
|
|
29
29
|
const HARNESS = 'opencode';
|
|
30
30
|
/**
|
|
@@ -48,7 +48,7 @@ const MCP_RENDERER = 'opencode-mcp';
|
|
|
48
48
|
* historical shape.
|
|
49
49
|
*/
|
|
50
50
|
function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } = {}) {
|
|
51
|
-
function nativePaths({ scope, scopeRoot, homeDir }
|
|
51
|
+
function nativePaths({ scope, scopeRoot, homeDir } ) {
|
|
52
52
|
return resolveHarnessPaths(declaredPaths, { scope, scopeRoot, homeDir });
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -57,20 +57,20 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
57
57
|
* `opencode.json` are read from the repo root, but the skills tree is namespaced under
|
|
58
58
|
* `.opencode/` per design.md's native-surfaces table. Global scope has no such split —
|
|
59
59
|
* `~/.config/opencode` already IS the skills root. Declared as `treeRoot` in harnesses.json. */
|
|
60
|
-
function copyTreeConfigDir(paths
|
|
60
|
+
function copyTreeConfigDir(paths ) {
|
|
61
61
|
return paths.treeRoot;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** Agents live under `.opencode/agents/` at project scope and `~/.config/opencode/agents/`
|
|
65
65
|
* globally — a different root from the skills tree in both scopes, so they get their own
|
|
66
66
|
* destination resolution rather than reusing copyTreeConfigDir. Declared as `agentsDirectory`. */
|
|
67
|
-
function agentsDestDir(paths
|
|
67
|
+
function agentsDestDir(paths ) {
|
|
68
68
|
return paths.agentsDirectory;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/** Discovery keeps opencode.json as plan read it (`mcpSnapshot`), which verify compares against,
|
|
72
72
|
* and reports the servers DoFlow owns in its `mcp` object now (`mcpOwned`). */
|
|
73
|
-
function discover({ scope, scopeRoot, mcp = [], mcpAdoptable = [], ledger, context = {}, fsImpl = fs }
|
|
73
|
+
function discover({ scope, scopeRoot, mcp = [], mcpAdoptable = [], ledger, context = {}, fsImpl = fs } ) {
|
|
74
74
|
const paths = nativePaths({ scope, scopeRoot, homeDir: context.homeDir });
|
|
75
75
|
const instruction = fsImpl.existsSync(paths.instruction) ? fsImpl.readFileSync(paths.instruction, 'utf8') : null;
|
|
76
76
|
const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
|
|
@@ -81,7 +81,7 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
81
81
|
return { paths, instruction, config: readJson(paths.config, { fsImpl }), mcpSnapshot, mcpOwned: ownedMcpIds({ ...entries, files: mcpSnapshot }), mcpConfigured };
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function render({ content = '' }
|
|
84
|
+
function render({ content = '' } = {}) {
|
|
85
85
|
return `${MARKER_START}\n${String(content).trimEnd()}\n${MARKER_END}\n`;
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -90,17 +90,17 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
90
90
|
* every config it discovers — replacing would silently drop a user's own entries. Skills are
|
|
91
91
|
* deliberately absent here: they are a materialised directory now, discovered natively, not a
|
|
92
92
|
* registered path. The `mcp` object is left to the entry changes. */
|
|
93
|
-
function mergeConfig(existing
|
|
93
|
+
function mergeConfig(existing ) {
|
|
94
94
|
const next = { ...(existing || {}) };
|
|
95
|
-
const union = (current
|
|
95
|
+
const union = (current , add ) => [...new Set([...(Array.isArray(current) ? current : []), ...add])];
|
|
96
96
|
next.instructions = union(next.instructions, [INSTRUCTION_FILE]);
|
|
97
97
|
return next;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/** Remove only what mergeConfig added, leaving every user key and every unrelated array entry. */
|
|
101
|
-
function unmergeConfig(existing
|
|
101
|
+
function unmergeConfig(existing ) {
|
|
102
102
|
const next = { ...(existing || {}) };
|
|
103
|
-
const without = (current
|
|
103
|
+
const without = (current , drop ) => (Array.isArray(current) ? current.filter((v) => !drop.includes(v)) : []);
|
|
104
104
|
next.instructions = without(next.instructions, [INSTRUCTION_FILE]);
|
|
105
105
|
if (!next.instructions.length) delete next.instructions;
|
|
106
106
|
return next;
|
|
@@ -116,12 +116,12 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
116
116
|
* comment-free glob we can re-derive) are touched; a user's own bash rules survive untouched.
|
|
117
117
|
* Entries are namespaced by value, not by key, so ownership is decided by re-deriving the list
|
|
118
118
|
* from the single authored source rather than by trusting whatever happens to be present. */
|
|
119
|
-
function guardrailBashRules(context
|
|
119
|
+
function guardrailBashRules(context ) {
|
|
120
120
|
const globs = destructiveCommandGlobs({ repoRoot: context.repoRoot });
|
|
121
|
-
return Object.fromEntries(globs.map((glob
|
|
121
|
+
return Object.fromEntries(globs.map((glob ) => [glob, 'deny']));
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
function mergeGuardrails(existing
|
|
124
|
+
function mergeGuardrails(existing , context ) {
|
|
125
125
|
const next = { ...(existing || {}) };
|
|
126
126
|
const bash = { ...guardrailBashRules(context), ...((next.permission?.bash ?? {})) };
|
|
127
127
|
// Keep user rules, but make sure ours win for identical globs (they are the stricter value).
|
|
@@ -129,7 +129,7 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
129
129
|
return next;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
function stripGuardrails(existing
|
|
132
|
+
function stripGuardrails(existing , context ) {
|
|
133
133
|
if (!existing?.permission?.bash) return existing;
|
|
134
134
|
const ours = new Set(Object.keys(guardrailBashRules(context)));
|
|
135
135
|
const bash = Object.fromEntries(Object.entries(existing.permission.bash).filter(([glob]) => !ours.has(glob)));
|
|
@@ -141,7 +141,7 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
141
141
|
return rest;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
function managedInstruction(existing
|
|
144
|
+
function managedInstruction(existing , rendered ) {
|
|
145
145
|
if (existing === null) return { ok: true, operation: 'create', content: rendered };
|
|
146
146
|
const start = existing.indexOf(MARKER_START);
|
|
147
147
|
const end = existing.indexOf(MARKER_END);
|
|
@@ -153,7 +153,7 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
153
153
|
|
|
154
154
|
/** Strip DoFlow's span, leaving the rest of a shared AGENTS.md intact. Returns null when there is
|
|
155
155
|
* nothing to do, so remove() can skip the write entirely. */
|
|
156
|
-
function strippedInstruction(existing
|
|
156
|
+
function strippedInstruction(existing ) {
|
|
157
157
|
if (typeof existing !== 'string') return null;
|
|
158
158
|
const start = existing.indexOf(MARKER_START);
|
|
159
159
|
const end = existing.indexOf(MARKER_END);
|
|
@@ -168,11 +168,11 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
168
168
|
/** `agents.shared` projects onto OpenCode through its own renderer name (its frontmatter is
|
|
169
169
|
* transformed into OpenCode's markdown-agent vocabulary) but materialises with exactly the same
|
|
170
170
|
* engine and change records as every other copy-tree asset. */
|
|
171
|
-
function opencodeTreeAssets(assets
|
|
172
|
-
return [...copyTreeAssets(assets), ...(assets || []).filter((asset
|
|
171
|
+
function opencodeTreeAssets(assets ) {
|
|
172
|
+
return [...copyTreeAssets(assets), ...(assets || []).filter((asset ) => asset.renderer === 'opencode-agents')];
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
function planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing, fsImpl = fs }
|
|
175
|
+
function planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing, fsImpl = fs } ) {
|
|
176
176
|
const paths = nativePaths({ scope, scopeRoot, homeDir: context.homeDir });
|
|
177
177
|
const treeConfigDir = copyTreeConfigDir(paths);
|
|
178
178
|
const changes = [];
|
|
@@ -190,10 +190,10 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
190
190
|
// is never deleted on removal, forced or not.
|
|
191
191
|
force: !removing && context?.force === true, });
|
|
192
192
|
treeResults.push(result);
|
|
193
|
-
conflicts.push(...result.conflicts.map((reason
|
|
193
|
+
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
194
194
|
// A hand-edited file at a path DoFlow no longer writes is the user's: it stays, only its
|
|
195
195
|
// ownership row is released.
|
|
196
|
-
notices.push(...result.kept.map((item
|
|
196
|
+
notices.push(...result.kept.map((item ) => `kept hand-edited ${path.relative(paths.root, item.target)}; DoFlow no longer manages it`));
|
|
197
197
|
for (const change of result.changes) {
|
|
198
198
|
changes.push({
|
|
199
199
|
assetId: asset.id, target: change.target, source: change.source, operation: change.operation,
|
|
@@ -209,7 +209,7 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
209
209
|
return { changes, conflicts, notices: [...siblingReplacedNotices(treeResults), ...notices] };
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
function applyCopyTreeAssets(changes
|
|
212
|
+
function applyCopyTreeAssets(changes , { fsImpl = fs } = {}) {
|
|
213
213
|
let applied = 0;
|
|
214
214
|
const grouped = new Map();
|
|
215
215
|
for (const change of changes) {
|
|
@@ -224,13 +224,13 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
224
224
|
return applied;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
function removeCopyTreeAssets(changes
|
|
228
|
-
const treeChanges = changes.filter((change
|
|
229
|
-
.map((change
|
|
227
|
+
function removeCopyTreeAssets(changes , { fsImpl = fs } = {}) {
|
|
228
|
+
const treeChanges = changes.filter((change ) => change.kind === 'copy-tree-file' && change.operation === 'remove' && !change.retained)
|
|
229
|
+
.map((change ) => ({ relPath: change.identity, target: change.target, operation: 'remove', fingerprint: change.fingerprint, pruneGuard: change.pruneGuard }));
|
|
230
230
|
return removeTree({ changes: treeChanges, fsImpl }).removed;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
function verifyCopyTreeAssets({ assets, scope, scopeRoot, context, fsImpl = fs }
|
|
233
|
+
function verifyCopyTreeAssets({ assets, scope, scopeRoot, context, fsImpl = fs } ) {
|
|
234
234
|
const paths = nativePaths({ scope, scopeRoot, homeDir: context.homeDir });
|
|
235
235
|
const treeConfigDir = copyTreeConfigDir(paths);
|
|
236
236
|
const statuses = [];
|
|
@@ -240,7 +240,7 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
240
240
|
const destDir = asset.renderer === 'opencode-agents' ? agentsDestDir(paths) : sharedTreeDestDir(paths.root, asset.nativeDir) ?? copyTreeDestDir(treeConfigDir, asset);
|
|
241
241
|
const sourceDir = sourceDirFor(asset, context, fsImpl, 'OpenCode');
|
|
242
242
|
const result = verifyTree({ sourceDir, destDir, fsImpl, layout: asset.layout, transform: asset.transform, plannedWrites: context?.plannedWrites });
|
|
243
|
-
conflicts.push(...result.conflicts.map((reason
|
|
243
|
+
conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
|
|
244
244
|
for (const resource of result.resources) {
|
|
245
245
|
resources.push({
|
|
246
246
|
assetId: asset.id, target: resource.target, ownershipIdentity: `doflow:${HARNESS}:copy-tree:${asset.id}:${resource.relPath}`,
|
|
@@ -259,45 +259,45 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
259
259
|
/** OpenCode's settings-merge change has no asset of its own in core/registry/assets.json (it's a
|
|
260
260
|
* registration side-effect of the instructions/mcp assets, not a projected asset), so it piggybacks
|
|
261
261
|
* on a real asset id already routed to this harness — the same "pseudo-component" technique
|
|
262
|
-
* src/adapters/gemini/index.
|
|
262
|
+
* src/adapters/gemini/index.js#hooksAssetId and src/adapters/kiro/index.js#pseudoAssetId use. The
|
|
263
263
|
* lifecycle layer validates every change's assetId against the harness's actual asset list, so an
|
|
264
264
|
* invented id like the literal string 'opencode.config' is rejected as unknown. */
|
|
265
|
-
function pseudoAssetId(assets
|
|
266
|
-
return assets.find((asset
|
|
265
|
+
function pseudoAssetId(assets ) {
|
|
266
|
+
return assets.find((asset ) => asset.capability === 'instructions')?.id ?? assets[0]?.id;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
// ---- MCP entries (opencode.json `mcp`) ----
|
|
270
270
|
|
|
271
271
|
/** OpenCode's own server shape: a local server whose command and arguments form one array. */
|
|
272
|
-
function buildServerEntry(server
|
|
272
|
+
function buildServerEntry(server ) {
|
|
273
273
|
return { type: 'local', command: [server.command, ...(server.args || [])], enabled: true };
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
function ownershipIdentity(id
|
|
276
|
+
function ownershipIdentity(id ) { return `doflow:${HARNESS}:mcp-server:${id}`; }
|
|
277
277
|
|
|
278
278
|
/** Everything the shared entry-ownership rules (../mcp-entries.ts) need from this adapter. */
|
|
279
|
-
function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], ledger, assets = [] }
|
|
280
|
-
const rendered = (servers
|
|
281
|
-
const rows = (ledger?.resources || []).filter((row
|
|
279
|
+
function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], ledger, assets = [] } ) {
|
|
280
|
+
const rendered = (servers ) => servers.map((server ) => ({ id: server.id, entry: buildServerEntry(server) }));
|
|
281
|
+
const rows = (ledger?.resources || []).filter((row ) => row.kind === 'mcp-server');
|
|
282
282
|
return {
|
|
283
283
|
file: paths.config, selected: rendered(mcp), adoptable: rendered(mcpAdoptable),
|
|
284
|
-
ownRows: rows.filter((row
|
|
284
|
+
ownRows: rows.filter((row ) => row.harness === HARNESS).map((row ) => ({
|
|
285
285
|
identity: row.identity, target: row.target, fingerprint: row.fingerprint ?? null, ownershipIdentity: row.ownershipIdentity, legacy: false,
|
|
286
286
|
})),
|
|
287
|
-
foreignRows: rows.filter((row
|
|
287
|
+
foreignRows: rows.filter((row ) => row.harness !== HARNESS).map((row ) => ({ harness: row.harness, identity: row.identity, target: row.target })),
|
|
288
288
|
identityFor: ownershipIdentity, assetId: pseudoAssetId(assets), renderer: MCP_RENDERER, label: 'OpenCode MCP',
|
|
289
289
|
};
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
/** opencode.json cannot be edited safely while removing: its registration row is released with
|
|
293
293
|
* the entry rows, so the removal completes and leaves the file as it is. */
|
|
294
|
-
function releasedConfigRow({ assets, paths, ledger }
|
|
295
|
-
const owned = (ledger?.resources || []).some((row
|
|
294
|
+
function releasedConfigRow({ assets, paths, ledger } ) {
|
|
295
|
+
const owned = (ledger?.resources || []).some((row ) => row.harness === HARNESS && row.ownershipIdentity === `${HARNESS}:config:registration` && row.target === paths.config);
|
|
296
296
|
return owned ? [{ assetId: pseudoAssetId(assets), target: paths.config, operation: 'remove', release: true,
|
|
297
297
|
ownershipIdentity: `${HARNESS}:config:registration`, harness: HARNESS, projection: { renderer: 'opencode-config' } }] : [];
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
function plan({ scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], discovery, context = {}, ledger, fsImpl = fs }
|
|
300
|
+
function plan({ scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], discovery, context = {}, ledger, fsImpl = fs } ) {
|
|
301
301
|
const found = discovery || discover({ scope, scopeRoot, mcp, mcpAdoptable, ledger, context, fsImpl });
|
|
302
302
|
const changes = [];
|
|
303
303
|
const conflicts = [];
|
|
@@ -306,7 +306,7 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
306
306
|
// below, under a fixed reason that never quotes the file.
|
|
307
307
|
const configReadable = found.mcpSnapshot[found.paths.config].ok;
|
|
308
308
|
|
|
309
|
-
const guidance = assets.find((asset
|
|
309
|
+
const guidance = assets.find((asset ) => asset.capability === 'instructions');
|
|
310
310
|
if (guidance) {
|
|
311
311
|
const rendered = render({ content: fsImpl.readFileSync(path.resolve(context.repoRoot, guidance.source), 'utf8') });
|
|
312
312
|
const outcome = removing
|
|
@@ -333,7 +333,7 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
333
333
|
// Tagged 'remove' during a remove operation (rather than always 'update'/'create') so this
|
|
334
334
|
// adapter's own apply()/remove() dispatch — which routes strictly on `operation` — writes
|
|
335
335
|
// the unmerged file during removal instead of silently skipping it (the same fix
|
|
336
|
-
// src/adapters/copilot/index.
|
|
336
|
+
// src/adapters/copilot/index.js's analogous mcp-merge change already carries).
|
|
337
337
|
operation: removing ? 'remove' : (found.config.exists ? 'update' : 'create'), content: `${JSON.stringify(next, null, 2)}\n`,
|
|
338
338
|
ownershipIdentity: `${HARNESS}:config:registration`, fingerprint: fingerprint(next),
|
|
339
339
|
harness: HARNESS, projection: { renderer: 'opencode-config' } });
|
|
@@ -355,14 +355,14 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
355
355
|
return { changes, conflicts, notices: [...copyTree.notices, ...mcpPlan.notices], paths: found.paths };
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
-
function writeChange(change
|
|
358
|
+
function writeChange(change , fsImpl ) {
|
|
359
359
|
fsImpl.mkdirSync(path.dirname(change.target), { recursive: true });
|
|
360
360
|
fsImpl.writeFileSync(change.target, change.content, 'utf8');
|
|
361
361
|
}
|
|
362
362
|
|
|
363
363
|
// The config change is written first; the entry changes then go to a fresh read of the same file,
|
|
364
364
|
// so they apply on top of it.
|
|
365
|
-
function apply({ changes = [], fsImpl = fs }
|
|
365
|
+
function apply({ changes = [], fsImpl = fs } ) {
|
|
366
366
|
let applied = 0;
|
|
367
367
|
for (const change of changes) {
|
|
368
368
|
if (change.operation === 'remove') continue;
|
|
@@ -378,7 +378,7 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
378
378
|
return { applied };
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
-
function remove({ changes = [], fsImpl = fs }
|
|
381
|
+
function remove({ changes = [], fsImpl = fs } ) {
|
|
382
382
|
let removed = 0;
|
|
383
383
|
for (const change of changes) {
|
|
384
384
|
if (change.operation !== 'remove') continue;
|
|
@@ -392,13 +392,13 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
392
392
|
return { removed };
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
function verify({ scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], discovery, ledger, operation, context = {}, fsImpl = fs }
|
|
395
|
+
function verify({ scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], discovery, ledger, operation, context = {}, fsImpl = fs } ) {
|
|
396
396
|
const found = discover({ scope, scopeRoot, mcp, mcpAdoptable, ledger, context, fsImpl });
|
|
397
397
|
const removing = (operation ?? context.operation) === 'remove';
|
|
398
398
|
const statuses = [];
|
|
399
399
|
const resources = [];
|
|
400
400
|
const conflicts = [];
|
|
401
|
-
const instructionsAssetId = assets.find((asset
|
|
401
|
+
const instructionsAssetId = assets.find((asset ) => asset.capability === 'instructions')?.id ?? pseudoAssetId(assets);
|
|
402
402
|
|
|
403
403
|
const hasSection = typeof found.instruction === 'string'
|
|
404
404
|
&& found.instruction.includes(MARKER_START) && found.instruction.includes(MARKER_END);
|
|
@@ -445,7 +445,7 @@ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS]
|
|
|
445
445
|
|
|
446
446
|
const copyTree = verifyCopyTreeAssets({ assets, scope, scopeRoot, context, fsImpl });
|
|
447
447
|
resources.push(...copyTree.resources);
|
|
448
|
-
statuses.push(...copyTree.statuses.map((status
|
|
448
|
+
statuses.push(...copyTree.statuses.map((status ) => ({ harness: HARNESS, ...status })));
|
|
449
449
|
conflicts.push(...copyTree.conflicts);
|
|
450
450
|
|
|
451
451
|
return { ok: conflicts.length === 0 && !statuses.some((s) => s.status === 'invalid' || s.status === 'conflict'), resources, statuses, conflicts };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { fingerprint } = require('../copy-tree.js');
|
|
8
|
+
|
|
9
|
+
const ASSET_ID = 'pi.guard-extension';
|
|
10
|
+
const EXTENSION_NAME = 'doflow-guard.js';
|
|
11
|
+
const OWNERSHIP = 'pi:guard-extension';
|
|
12
|
+
const TEMPLATE = 'core/harnesses/pi/extensions/doflow-guard.js';
|
|
13
|
+
const BRIDGE_SUFFIX = ['.doflow', 'shared', 'hooks', 'bridge', 'bridge.sh'];
|
|
14
|
+
|
|
15
|
+
function userAgentDir(scopeRoot , env ) {
|
|
16
|
+
const override = env?.PI_CODING_AGENT_DIR;
|
|
17
|
+
return typeof override === 'string' && override.trim() ? path.resolve(override.trim()) : path.join(scopeRoot, '.pi', 'agent');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function targetPath(scopeRoot , env , scope = 'user') {
|
|
21
|
+
return scope === 'project'
|
|
22
|
+
? path.join(scopeRoot, '.pi', 'extensions', EXTENSION_NAME)
|
|
23
|
+
: path.join(userAgentDir(scopeRoot, env), 'extensions', EXTENSION_NAME);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderExtension({ repoRoot, scopeRoot, target, fsImpl = fs } ) {
|
|
27
|
+
const templatePath = path.join(repoRoot, TEMPLATE);
|
|
28
|
+
const template = fsImpl.readFileSync(templatePath, 'utf8');
|
|
29
|
+
const bridgePath = path.join(scopeRoot, ...BRIDGE_SUFFIX);
|
|
30
|
+
const relative = path.relative(path.dirname(target), bridgePath).split(path.sep).join('/');
|
|
31
|
+
const rendered = template.replace("const BRIDGE = new URL('../../shared/hooks/bridge/bridge.sh', import.meta.url);",
|
|
32
|
+
`const BRIDGE = new URL(${JSON.stringify(relative)}, import.meta.url);`);
|
|
33
|
+
if (rendered === template) throw new Error('Pi extension bridge path template was not found');
|
|
34
|
+
return rendered;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function planPiHooks({ scope, scopeRoot, assets = [], ledger, context = {}, adopt = false, env = process.env, fsImpl = fs } ) {
|
|
38
|
+
if (scope !== 'user' && scope !== 'global' && scope !== 'project') return { changes: [], conflicts: [], notices: [] };
|
|
39
|
+
const asset = assets.find((row ) => row.id === ASSET_ID);
|
|
40
|
+
if (!asset) return { changes: [], conflicts: [], notices: [] };
|
|
41
|
+
const computedTarget = targetPath(scopeRoot, env, scope);
|
|
42
|
+
const removing = context.operation === 'remove';
|
|
43
|
+
const ownedRows = (ledger?.resources || []).filter((row ) => row.harness === 'pi' && row.assetId === ASSET_ID
|
|
44
|
+
&& row.ownershipIdentity === OWNERSHIP);
|
|
45
|
+
const exactOwned = ownedRows.find((row ) => row.target === computedTarget);
|
|
46
|
+
const owned = exactOwned || (removing && ownedRows.length === 1 ? ownedRows[0] : undefined);
|
|
47
|
+
const target = removing && owned ? owned.target : computedTarget;
|
|
48
|
+
const exists = fsImpl.existsSync(target);
|
|
49
|
+
const notices = [];
|
|
50
|
+
if (removing) {
|
|
51
|
+
if (!owned) return { changes: [], conflicts: [], notices };
|
|
52
|
+
if (!exists) return { changes: [{ assetId: ASSET_ID, target, operation: 'remove', release: true,
|
|
53
|
+
ownershipIdentity: OWNERSHIP, harness: 'pi', projection: { renderer: 'pi-guard-extension' } }], conflicts: [], notices };
|
|
54
|
+
const current = fsImpl.readFileSync(target, 'utf8');
|
|
55
|
+
if (fingerprint(current) !== owned.fingerprint) {
|
|
56
|
+
notices.push(`kept hand-edited ${target}; DoFlow no longer manages it`);
|
|
57
|
+
return { changes: [{ assetId: ASSET_ID, target, operation: 'remove', release: true,
|
|
58
|
+
ownershipIdentity: OWNERSHIP, harness: 'pi', projection: { renderer: 'pi-guard-extension' } }], conflicts: [], notices };
|
|
59
|
+
}
|
|
60
|
+
return { changes: [{ assetId: ASSET_ID, target, operation: 'remove', content: null,
|
|
61
|
+
ownershipIdentity: OWNERSHIP, harness: 'pi', projection: { renderer: 'pi-guard-extension' } }], conflicts: [], notices };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const content = renderExtension({ repoRoot: context.repoRoot, scopeRoot, target, fsImpl });
|
|
65
|
+
if (!exists) return { changes: [{ assetId: ASSET_ID, target, operation: 'create', content,
|
|
66
|
+
ownershipIdentity: OWNERSHIP, harness: 'pi', fingerprint: fingerprint(content),
|
|
67
|
+
projection: { renderer: 'pi-guard-extension' } }], conflicts: [], notices };
|
|
68
|
+
if (!owned && !adopt) {
|
|
69
|
+
return { changes: [], conflicts: [`Pi extension already exists and is not owned by DoFlow: ${target}`], notices };
|
|
70
|
+
}
|
|
71
|
+
const current = fsImpl.readFileSync(target, 'utf8');
|
|
72
|
+
if (owned && fingerprint(current) !== owned.fingerprint) {
|
|
73
|
+
return { changes: [], conflicts: [`Pi extension was modified outside DoFlow: ${target}`], notices };
|
|
74
|
+
}
|
|
75
|
+
if (current === content) return { changes: [], conflicts: [], notices };
|
|
76
|
+
return { changes: [{ assetId: ASSET_ID, target, operation: 'update', content,
|
|
77
|
+
ownershipIdentity: OWNERSHIP, harness: 'pi', fingerprint: fingerprint(content),
|
|
78
|
+
projection: { renderer: 'pi-guard-extension' } }], conflicts: [], notices };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function verifyPiHooks({ scope, scopeRoot, assets = [], ledger, context = {}, adopt = false, env = process.env, fsImpl = fs, pathEnv = process.env.PATH } ) {
|
|
82
|
+
if (scope !== 'user' && scope !== 'global' && scope !== 'project') return { statuses: [], resources: [], conflicts: [] };
|
|
83
|
+
const asset = assets.find((row ) => row.id === ASSET_ID);
|
|
84
|
+
if (!asset) return { statuses: [], resources: [], conflicts: [] };
|
|
85
|
+
const target = targetPath(scopeRoot, env, scope);
|
|
86
|
+
const owned = (ledger?.resources || []).find((row ) => row.harness === 'pi' && row.assetId === ASSET_ID
|
|
87
|
+
&& row.ownershipIdentity === OWNERSHIP && row.target === target);
|
|
88
|
+
const bridgePath = path.join(scopeRoot, ...BRIDGE_SUFFIX);
|
|
89
|
+
const present = fsImpl.existsSync(target);
|
|
90
|
+
const content = present ? fsImpl.readFileSync(target, 'utf8') : null;
|
|
91
|
+
const actualFingerprint = content === null ? null : fingerprint(content);
|
|
92
|
+
const fingerprintMatches = !!owned && actualFingerprint === owned.fingerprint;
|
|
93
|
+
const plannedWrite = (context?.plannedWrites || []).includes(target);
|
|
94
|
+
const bridgeMatches = !!content && content.includes(JSON.stringify(path.relative(path.dirname(target), bridgePath).split(path.sep).join('/')));
|
|
95
|
+
const commandOnPath = (command ) => String(pathEnv || '').split(path.delimiter)
|
|
96
|
+
.some((dir) => fsImpl.existsSync(path.join(dir, command)));
|
|
97
|
+
const status = present && !owned && !plannedWrite && !adopt ? 'conflict'
|
|
98
|
+
: (present && (fingerprintMatches || plannedWrite) && bridgeMatches && commandOnPath('bash') && commandOnPath('jq') ? 'managed' : 'absent');
|
|
99
|
+
const resources = status === 'managed' ? [{ assetId: ASSET_ID, target, ownershipIdentity: OWNERSHIP,
|
|
100
|
+
fingerprint: actualFingerprint, sourceVersion: context.sourceVersion ?? 'unknown',
|
|
101
|
+
projection: { renderer: 'pi-guard-extension' } }] : [];
|
|
102
|
+
const statuses = [{ harness: 'pi', assetId: ASSET_ID, capability: 'hooks', status, target,
|
|
103
|
+
ownershipIdentity: OWNERSHIP, fingerprint: actualFingerprint, fingerprintMatches,
|
|
104
|
+
bridgePath, bridgeExists: fsImpl.existsSync(bridgePath), bash: commandOnPath('bash'), jq: commandOnPath('jq'),
|
|
105
|
+
prerequisites: scope === 'project' ? ['project-trust'] : [], piCodingAgentDir: typeof env?.PI_CODING_AGENT_DIR === 'string' && env.PI_CODING_AGENT_DIR.trim() ? env.PI_CODING_AGENT_DIR.trim() : null }];
|
|
106
|
+
const conflicts = status === 'conflict' ? [`Pi extension is not owned by DoFlow: ${target}`] : [];
|
|
107
|
+
return { statuses, resources, conflicts };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function removePiHooks(changes , { fsImpl = fs } = {}) {
|
|
111
|
+
let removed = 0;
|
|
112
|
+
for (const change of changes) {
|
|
113
|
+
if (change.projection?.renderer !== 'pi-guard-extension' || change.operation !== 'remove' || change.release) continue;
|
|
114
|
+
if (fsImpl.existsSync(change.target)) { fsImpl.unlinkSync(change.target); removed += 1; }
|
|
115
|
+
}
|
|
116
|
+
return removed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = { ASSET_ID, EXTENSION_NAME, OWNERSHIP, userAgentDir, targetPath, renderExtension, planPiHooks, verifyPiHooks, removePiHooks };
|