@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.4
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/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/registry/storage.json +87 -83
- package/core/registry/verification.json +1 -1
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
- package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
- package/package.json +4 -2
- package/src/adapters/antigravity/{index.ts → index.js} +60 -60
- package/src/adapters/claude/{index.ts → index.js} +48 -48
- 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} +23 -23
- package/src/adapters/codex/{index.ts → index.js} +98 -98
- 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} +70 -70
- package/src/adapters/gemini/{hooks.ts → hooks.js} +24 -24
- package/src/adapters/gemini/{index.ts → index.js} +41 -41
- 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} +34 -34
- 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/{index.ts → index.js} +30 -30
- 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} +24 -24
- 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} +19 -19
- package/src/cli/{index.ts → index.js} +26 -26
- package/src/cli/{runtime-commands.ts → runtime-commands.js} +48 -48
- 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/{index.ts → index.js} +140 -140
- 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} +60 -60
- 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/{capability-router.ts → capability-router.js} +39 -39
- package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
- package/src/runtime/{claims.ts → claims.js} +30 -30
- package/src/runtime/{cli-result.ts → cli-result.js} +4 -4
- package/src/runtime/{cli.ts → cli.js} +37 -37
- package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
- package/src/runtime/{context-pack.ts → context-pack.js} +20 -20
- package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
- package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +25 -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} +15 -15
- 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} +35 -35
- 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} +40 -40
- package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +19 -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/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} +61 -61
- package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
- package/src/runtime/{task-scope.ts → task-scope.js} +13 -13
- package/src/runtime/{task-state.ts → task-state.js} +15 -15
- 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
|
@@ -15,8 +15,8 @@ const fs = require('node:fs');
|
|
|
15
15
|
const path = require('node:path');
|
|
16
16
|
const crypto = require('node:crypto');
|
|
17
17
|
|
|
18
|
-
const { fingerprint } = require('../copy-tree.
|
|
19
|
-
const { SKELETON, readDocument, insertMember, replaceValue, removeMember, replaceInner, renderNewDocument } = require('./json-members.
|
|
18
|
+
const { fingerprint } = require('../copy-tree.js');
|
|
19
|
+
const { SKELETON, readDocument, insertMember, replaceValue, removeMember, replaceInner, renderNewDocument } = require('./json-members.js');
|
|
20
20
|
|
|
21
21
|
const HARNESS = 'pi';
|
|
22
22
|
const ASSET_ID = 'guidance.codex-pointer';
|
|
@@ -30,47 +30,47 @@ const NOTICES = Object.freeze({
|
|
|
30
30
|
trust: 'MCP: Pi reads .pi/mcp.json only after this project is trusted (/trust or --approve); DoFlow does not grant trust.',
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
function sha256(text
|
|
33
|
+
function sha256(text ) { return crypto.createHash('sha256').update(text).digest('hex'); }
|
|
34
34
|
|
|
35
|
-
function shorten(name
|
|
35
|
+
function shorten(name ) { return name.length > 40 ? `${name.slice(0, 37)}...` : name; }
|
|
36
36
|
|
|
37
|
-
function collisionNotice(key
|
|
37
|
+
function collisionNotice(key , id ) {
|
|
38
38
|
return `MCP: kept your own entry '${shorten(key)}' and did not register DoFlow's '${shorten(id)}'; rename or remove yours to let DoFlow manage it.`;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function releasedNotice(id
|
|
41
|
+
function releasedNotice(id ) {
|
|
42
42
|
return `MCP: entry '${shorten(id)}' was changed outside DoFlow; it is yours now and DoFlow no longer updates or removes it.`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/** Pi treats two server names that differ only in `-` versus `_` as one server. */
|
|
46
|
-
function piServerKey(name
|
|
46
|
+
function piServerKey(name ) { return name.replace(/_/g, '-'); }
|
|
47
47
|
|
|
48
|
-
function refusalFor(file
|
|
48
|
+
function refusalFor(file , reason ) { return `Pi MCP: ${file}: ${reason}; DoFlow did not change the file`; }
|
|
49
49
|
|
|
50
|
-
function untouchedNotice(file
|
|
50
|
+
function untouchedNotice(file ) {
|
|
51
51
|
const shown = file.length > 80 ? `...${file.slice(-77)}` : file;
|
|
52
52
|
return `MCP: left ${shown} untouched because it cannot be edited safely; DoFlow no longer manages the entries in it.`;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/** DoFlow's entry for one catalog server, or the reason Pi cannot take it. */
|
|
56
|
-
function renderPiEntry(server
|
|
56
|
+
function renderPiEntry(server ) {
|
|
57
57
|
if (server.transport !== 'stdio' || typeof server.command !== 'string' || !server.command) {
|
|
58
58
|
return { refusal: `Pi MCP: server '${server.id}' is not a stdio server; DoFlow writes only stdio entries for Pi` };
|
|
59
59
|
}
|
|
60
60
|
if (typeof server.id !== 'string' || !SERVER_NAME.test(server.id)) {
|
|
61
61
|
return { refusal: `Pi MCP: server id '${server.id}' is not a valid Pi server name` };
|
|
62
62
|
}
|
|
63
|
-
const entry
|
|
63
|
+
const entry = { command: server.command };
|
|
64
64
|
if (Array.isArray(server.args) && server.args.length) entry.args = [...server.args];
|
|
65
65
|
if (server.env && typeof server.env === 'object' && Object.keys(server.env).length) entry.env = { ...server.env };
|
|
66
66
|
return { entry };
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function mcpRows(ledger
|
|
70
|
-
return (ledger?.resources || []).filter((row
|
|
69
|
+
function mcpRows(ledger ) {
|
|
70
|
+
return (ledger?.resources || []).filter((row ) => row.harness === HARNESS && row.kind === 'mcp-server');
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function readSnapshotFile(file
|
|
73
|
+
function readSnapshotFile(file , fsImpl ) {
|
|
74
74
|
if (!fsImpl.existsSync(file)) return { exists: false, text: '', hash: sha256(''), doc: ABSENT_DOCUMENT };
|
|
75
75
|
const bytes = fsImpl.readFileSync(file);
|
|
76
76
|
const text = bytes.toString('utf8');
|
|
@@ -82,31 +82,31 @@ function readSnapshotFile(file: string, fsImpl: any): any {
|
|
|
82
82
|
|
|
83
83
|
/** Read the selected server file and every file a ledger row names. Nothing is read when there is
|
|
84
84
|
* neither a selection nor a row, so a run with no Pi MCP work touches no MCP file. */
|
|
85
|
-
function discoverPiMcp({ selected = [], rows = [], file, fsImpl = fs }
|
|
86
|
-
const files = new Set
|
|
85
|
+
function discoverPiMcp({ selected = [], rows = [], file, fsImpl = fs } ) {
|
|
86
|
+
const files = new Set (rows.map((row ) => row.target));
|
|
87
87
|
if (selected.length) files.add(file);
|
|
88
88
|
return Object.fromEntries([...files].map((target) => [target, readSnapshotFile(target, fsImpl)]));
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function memberOf(doc
|
|
91
|
+
function memberOf(doc , key ) { return doc.servers?.members.find((member ) => member.key === key) ?? null; }
|
|
92
92
|
|
|
93
93
|
/** The servers DoFlow owns in Pi's mcp.json now: rows whose entry still holds the value they record. */
|
|
94
|
-
function ownedPiMcpIds({ rows = [], snapshot = {} }
|
|
95
|
-
const owned = rows.filter((row
|
|
94
|
+
function ownedPiMcpIds({ rows = [], snapshot = {} } ) {
|
|
95
|
+
const owned = rows.filter((row ) => {
|
|
96
96
|
const doc = snapshot[row.target]?.doc;
|
|
97
97
|
const member = doc?.ok ? memberOf(doc, row.identity) : null;
|
|
98
98
|
return member !== null && fingerprint(member.value) === row.fingerprint;
|
|
99
99
|
});
|
|
100
|
-
return [...new Set
|
|
100
|
+
return [...new Set (owned.map((row ) => row.identity))];
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function twinOf(doc
|
|
104
|
-
return doc.servers?.members.find((member
|
|
103
|
+
function twinOf(doc , key ) {
|
|
104
|
+
return doc.servers?.members.find((member ) => member.key !== key && piServerKey(member.key) === piServerKey(key)) ?? null;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
function sameValue(a
|
|
107
|
+
function sameValue(a , b ) { return JSON.stringify(a) === JSON.stringify(b); }
|
|
108
108
|
|
|
109
|
-
function baseChange({ target, operation, id, snapshot }
|
|
109
|
+
function baseChange({ target, operation, id, snapshot } ) {
|
|
110
110
|
return {
|
|
111
111
|
assetId: ASSET_ID, target, operation,
|
|
112
112
|
ownershipIdentity: `doflow:${HARNESS}:mcp-server:${id}`,
|
|
@@ -115,36 +115,36 @@ function baseChange({ target, operation, id, snapshot }: any): any {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
function writeChange({ target, operation, id, entry, snapshot }
|
|
118
|
+
function writeChange({ target, operation, id, entry, snapshot } ) {
|
|
119
119
|
return { ...baseChange({ target, operation, id, snapshot }), entry, fingerprint: fingerprint(entry), afterFingerprint: fingerprint(entry) };
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
function releaseChange({ row, snapshot }
|
|
122
|
+
function releaseChange({ row, snapshot } ) {
|
|
123
123
|
const id = row.identity;
|
|
124
124
|
return { ...baseChange({ target: row.target, operation: 'remove', id, snapshot }), release: true, fingerprint: row.fingerprint };
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
function planPiMcp({ selected = [], rows = [], file, scope, removing = false, snapshot = {}, assets = [] }
|
|
128
|
-
const changes
|
|
129
|
-
const conflicts
|
|
130
|
-
const notices
|
|
127
|
+
function planPiMcp({ selected = [], rows = [], file, scope, removing = false, snapshot = {}, assets = [] } ) {
|
|
128
|
+
const changes = [];
|
|
129
|
+
const conflicts = [];
|
|
130
|
+
const notices = [];
|
|
131
131
|
if (!removing && !selected.length && !rows.length) return { changes, conflicts, notices };
|
|
132
132
|
if (removing && !rows.length) return { changes, conflicts, notices };
|
|
133
|
-
if (!assets.some((asset
|
|
133
|
+
if (!assets.some((asset ) => asset.id === ASSET_ID)) {
|
|
134
134
|
return { changes, conflicts: [`Pi MCP needs the ${ASSET_ID} asset to record ownership`], notices };
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// An install or update refuses a file it cannot edit; a removal must still uninstall everything
|
|
138
138
|
// else, so it releases the rows for that file and leaves the file as it is.
|
|
139
|
-
const refused = new Set
|
|
140
|
-
for (const [target, entry] of Object.entries(snapshot)
|
|
139
|
+
const refused = new Set ();
|
|
140
|
+
for (const [target, entry] of Object.entries(snapshot) ) {
|
|
141
141
|
if (entry.doc.ok) continue;
|
|
142
142
|
refused.add(target);
|
|
143
143
|
if (removing) notices.push(untouchedNotice(target));
|
|
144
144
|
else conflicts.push(refusalFor(target, entry.doc.reason));
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
const matched = new Set
|
|
147
|
+
const matched = new Set ();
|
|
148
148
|
if (!removing) {
|
|
149
149
|
if (selected.length) {
|
|
150
150
|
notices.push(NOTICES.surface, NOTICES.extension);
|
|
@@ -154,7 +154,7 @@ function planPiMcp({ selected = [], rows = [], file, scope, removing = false, sn
|
|
|
154
154
|
for (const server of selected) {
|
|
155
155
|
const { entry: desired, refusal } = renderPiEntry(server);
|
|
156
156
|
if (refusal) { conflicts.push(refusal); continue; }
|
|
157
|
-
const row = rows.find((item
|
|
157
|
+
const row = rows.find((item ) => item.identity === server.id && item.target === file);
|
|
158
158
|
if (row) matched.add(row);
|
|
159
159
|
const member = memberOf(doc, server.id);
|
|
160
160
|
const twin = twinOf(doc, server.id);
|
|
@@ -189,21 +189,21 @@ function planPiMcp({ selected = [], rows = [], file, scope, removing = false, sn
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
return { changes: changes.filter((change
|
|
192
|
+
return { changes: changes.filter((change ) => !refused.has(change.target) || (removing && change.release)), conflicts, notices };
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
function isSymlink(file
|
|
195
|
+
function isSymlink(file , fsImpl ) {
|
|
196
196
|
try { return fsImpl.lstatSync(file).isSymbolicLink(); } catch { return false; }
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
/** Where a write to `file` lands: a symlinked mcp.json is written through to the file it points at,
|
|
200
200
|
* so the link stays a link and its target receives the entries. */
|
|
201
|
-
function writePath(file
|
|
201
|
+
function writePath(file , fsImpl ) {
|
|
202
202
|
if (!isSymlink(file, fsImpl)) return file;
|
|
203
203
|
try { return fsImpl.realpathSync(file); } catch { return path.resolve(path.dirname(file), fsImpl.readlinkSync(file)); }
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
function atomicWrite(file
|
|
206
|
+
function atomicWrite(file , content , fsImpl ) {
|
|
207
207
|
const dest = writePath(file, fsImpl);
|
|
208
208
|
fsImpl.mkdirSync(path.dirname(dest), { recursive: true });
|
|
209
209
|
// The rename gives the file the temp file's mode; an mcp.json kept private for its tokens stays so.
|
|
@@ -219,28 +219,28 @@ function atomicWrite(file: string, content: string, fsImpl: any): void {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
/** Replay one file's edits against the text read now, each against a fresh parse of the last result. */
|
|
222
|
-
function editedText(text
|
|
223
|
-
const parse = (current
|
|
222
|
+
function editedText(text , group ) {
|
|
223
|
+
const parse = (current ) => {
|
|
224
224
|
const doc = readDocument(current);
|
|
225
225
|
if (!doc.ok) throw new Error(`Pi MCP: cannot edit the planned file: ${doc.reason}`);
|
|
226
226
|
return doc;
|
|
227
227
|
};
|
|
228
228
|
let next = text;
|
|
229
|
-
for (const change of group.filter((item
|
|
229
|
+
for (const change of group.filter((item ) => item.operation === 'remove')) {
|
|
230
230
|
const doc = parse(next);
|
|
231
231
|
const member = memberOf(doc, change.identity);
|
|
232
232
|
if (member) next = removeMember(next, doc.servers, member);
|
|
233
233
|
}
|
|
234
|
-
for (const change of group.filter((item
|
|
234
|
+
for (const change of group.filter((item ) => item.operation === 'update')) {
|
|
235
235
|
const doc = parse(next);
|
|
236
236
|
const member = memberOf(doc, change.identity);
|
|
237
237
|
if (member) next = replaceValue(next, member, change.entry, doc);
|
|
238
238
|
}
|
|
239
|
-
const creates = group.filter((item
|
|
239
|
+
const creates = group.filter((item ) => item.operation === 'create');
|
|
240
240
|
for (const [index, change] of creates.entries()) {
|
|
241
241
|
const doc = parse(next);
|
|
242
242
|
if (!doc.servers) {
|
|
243
|
-
const entries = Object.fromEntries(creates.slice(index).map((item
|
|
243
|
+
const entries = Object.fromEntries(creates.slice(index).map((item ) => [item.identity, item.entry]));
|
|
244
244
|
return insertMember(next, doc.root, 'mcpServers', entries, doc);
|
|
245
245
|
}
|
|
246
246
|
next = insertMember(next, doc.servers, change.identity, change.entry, doc);
|
|
@@ -251,33 +251,33 @@ function editedText(text: string, group: any[]): string {
|
|
|
251
251
|
/** Undo what DoFlow added around its entries once the last of them is gone: the mcpServers member
|
|
252
252
|
* it inserted, or the inner whitespace of the container it first wrote into. `origin` is recorded
|
|
253
253
|
* on the ledger rows (see originFor); without it the emptied container is left as it is. */
|
|
254
|
-
function restoredText(text
|
|
254
|
+
function restoredText(text , origin ) {
|
|
255
255
|
const doc = readDocument(text);
|
|
256
256
|
if (!doc.ok || !doc.servers || doc.servers.members.length) return text;
|
|
257
257
|
if (origin.created === 'member') return replaceInner(text, doc.servers, origin.inner);
|
|
258
|
-
const next = removeMember(text, doc.root, doc.root.members.find((member
|
|
258
|
+
const next = removeMember(text, doc.root, doc.root.members.find((member ) => member.key === 'mcpServers'));
|
|
259
259
|
if (origin.inner === undefined) return next;
|
|
260
260
|
const after = readDocument(next);
|
|
261
261
|
return after.root.members.length ? next : replaceInner(next, after.root, origin.inner);
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
function applyPiMcp(changes
|
|
265
|
-
const groups = new Map
|
|
264
|
+
function applyPiMcp(changes , { fsImpl = fs } = {}) {
|
|
265
|
+
const groups = new Map ();
|
|
266
266
|
for (const change of changes) {
|
|
267
267
|
if (!groups.has(change.target)) groups.set(change.target, []);
|
|
268
|
-
groups.get(change.target)
|
|
268
|
+
groups.get(change.target) .push(change);
|
|
269
269
|
}
|
|
270
270
|
let applied = 0;
|
|
271
271
|
for (const [target, all] of groups) {
|
|
272
|
-
const group = all.filter((change
|
|
272
|
+
const group = all.filter((change ) => !change.release);
|
|
273
273
|
if (!group.length) continue;
|
|
274
274
|
const exists = fsImpl.existsSync(target);
|
|
275
275
|
const bytes = exists ? fsImpl.readFileSync(target) : Buffer.alloc(0);
|
|
276
276
|
if (sha256(bytes) !== group[0].baseHash) throw new Error(`Pi MCP: ${target} changed after planning; nothing was written, re-run the command`);
|
|
277
277
|
const text = bytes.toString('utf8');
|
|
278
|
-
const creates = group.filter((change
|
|
279
|
-
const origin = group.find((change
|
|
280
|
-
let next = exists ? editedText(text, group) : renderNewDocument(creates.map((change
|
|
278
|
+
const creates = group.filter((change ) => change.operation === 'create');
|
|
279
|
+
const origin = group.find((change ) => change.origin)?.origin;
|
|
280
|
+
let next = exists ? editedText(text, group) : renderNewDocument(creates.map((change ) => [change.identity, change.entry]));
|
|
281
281
|
// Only a file DoFlow created is deleted, and never through a symlink; a user's own file that
|
|
282
282
|
// happens to equal the skeleton stays.
|
|
283
283
|
if (exists && origin?.created === 'file' && next === SKELETON) {
|
|
@@ -291,12 +291,12 @@ function applyPiMcp(changes: any[], { fsImpl = fs }: { fsImpl?: any } = {}): { a
|
|
|
291
291
|
return { applied };
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
function statusFor({ id, target, status }
|
|
294
|
+
function statusFor({ id, target, status } ) {
|
|
295
295
|
return { harness: HARNESS, assetId: ASSET_ID, capability: 'mcp', identity: id, target,
|
|
296
296
|
ownershipIdentity: `doflow:${HARNESS}:mcp-server:${id}`, status };
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
function resourceFor({ id, target, value, origin }
|
|
299
|
+
function resourceFor({ id, target, value, origin } ) {
|
|
300
300
|
return { assetId: ASSET_ID, target, ownershipIdentity: `doflow:${HARNESS}:mcp-server:${id}`, kind: 'mcp-server', identity: id,
|
|
301
301
|
fingerprint: fingerprint(value), sourceVersion: 'registry-v1', projection: { renderer: RENDERER }, ...(origin ? { origin } : {}) };
|
|
302
302
|
}
|
|
@@ -305,15 +305,15 @@ function resourceFor({ id, target, value, origin }: { id: string, target: string
|
|
|
305
305
|
* the whole file, the mcpServers member, or the first member of an empty mcpServers (with the
|
|
306
306
|
* container's original inner whitespace). A row already carrying it passes it on; otherwise it is
|
|
307
307
|
* read from the file as plan saw it. Only whitespace is recorded, never file content. */
|
|
308
|
-
function originFor(target
|
|
309
|
-
const carried = rows.find((row
|
|
308
|
+
function originFor(target , rows , snapshot ) {
|
|
309
|
+
const carried = rows.find((row ) => row.target === target && row.origin)?.origin;
|
|
310
310
|
if (carried) return carried;
|
|
311
311
|
const before = snapshot[target];
|
|
312
312
|
if (!before) return undefined;
|
|
313
313
|
if (!before.exists) return { created: 'file' };
|
|
314
314
|
const { doc, text } = before;
|
|
315
315
|
if (!doc.ok) return undefined;
|
|
316
|
-
const inner = (node
|
|
316
|
+
const inner = (node ) => text.slice(node.open + 1, node.close);
|
|
317
317
|
if (!doc.servers) return doc.root.members.length ? { created: 'key' } : { created: 'key', inner: inner(doc.root) };
|
|
318
318
|
return doc.servers.members.length ? undefined : { created: 'member', inner: inner(doc.servers) };
|
|
319
319
|
}
|
|
@@ -321,26 +321,26 @@ function originFor(target: string, rows: any[], snapshot: any): any {
|
|
|
321
321
|
/** Statuses and ledger resources for the Pi MCP entries. `snapshot` is what plan read before apply:
|
|
322
322
|
* an entry it held that DoFlow did not own then, or held with a value its row no longer matched, is
|
|
323
323
|
* the user's, whatever it holds now. */
|
|
324
|
-
function verifyPiMcp({ selected = [], rows = [], file, snapshot = {}, removing = false, fsImpl = fs }
|
|
325
|
-
const statuses
|
|
326
|
-
const resources
|
|
327
|
-
const conflicts
|
|
328
|
-
const current
|
|
329
|
-
for (const target of new Set
|
|
324
|
+
function verifyPiMcp({ selected = [], rows = [], file, snapshot = {}, removing = false, fsImpl = fs } ) {
|
|
325
|
+
const statuses = [];
|
|
326
|
+
const resources = [];
|
|
327
|
+
const conflicts = [];
|
|
328
|
+
const current = {};
|
|
329
|
+
for (const target of new Set ([...(selected.length ? [file] : []), ...rows.map((row ) => row.target)])) {
|
|
330
330
|
current[target] = readSnapshotFile(target, fsImpl).doc;
|
|
331
331
|
if (!current[target].ok && !removing) conflicts.push(refusalFor(target, current[target].reason));
|
|
332
332
|
}
|
|
333
|
-
const memberNow = (target
|
|
334
|
-
const foreign = (target
|
|
333
|
+
const memberNow = (target , id ) => (current[target]?.ok ? memberOf(current[target], id) : null);
|
|
334
|
+
const foreign = (target , id ) => {
|
|
335
335
|
const before = snapshot[target]?.doc;
|
|
336
336
|
if (!before?.ok) return false;
|
|
337
|
-
const row = rows.find((item
|
|
337
|
+
const row = rows.find((item ) => item.identity === id && item.target === target);
|
|
338
338
|
const member = memberOf(before, id);
|
|
339
339
|
if (!member) return Boolean(twinOf(before, id));
|
|
340
340
|
return !row || fingerprint(member.value) !== row.fingerprint;
|
|
341
341
|
};
|
|
342
342
|
|
|
343
|
-
const verified = new Set
|
|
343
|
+
const verified = new Set ();
|
|
344
344
|
if (!removing) {
|
|
345
345
|
for (const server of selected) {
|
|
346
346
|
const { entry: desired } = renderPiEntry(server);
|
|
@@ -359,7 +359,7 @@ function verifyPiMcp({ selected = [], rows = [], file, snapshot = {}, removing =
|
|
|
359
359
|
const id = row.identity;
|
|
360
360
|
if (verified.has(`${row.target}\u0000${id}`)) continue;
|
|
361
361
|
const member = memberNow(row.target, id);
|
|
362
|
-
let status
|
|
362
|
+
let status ;
|
|
363
363
|
if (!current[row.target].ok) status = 'not-managed';
|
|
364
364
|
else if (!member) status = 'absent';
|
|
365
365
|
else if (foreign(row.target, id) || fingerprint(member.value) !== row.fingerprint) status = 'not-managed';
|
|
@@ -7,7 +7,7 @@ const path = require('node:path');
|
|
|
7
7
|
/** Resolve a registry-declared source path against the repo root, refusing escapes. Shared by the
|
|
8
8
|
* common projection module and by per-adapter native-projection normalizers, which is why it lives
|
|
9
9
|
* in its own module rather than in index.js — a normalizer importing index.js would be a cycle. */
|
|
10
|
-
function resolveRegistrySource(registry
|
|
10
|
+
function resolveRegistrySource(registry , source , label ) {
|
|
11
11
|
if (typeof registry.repoRoot !== 'string' || !registry.repoRoot) throw new Error(`Registry repoRoot is required for ${label}`);
|
|
12
12
|
if (typeof source !== 'string' || !source) throw new Error(`Registry ${label} source is required`);
|
|
13
13
|
const root = path.resolve(registry.repoRoot);
|
|
@@ -4,31 +4,31 @@
|
|
|
4
4
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
5
5
|
const os = require('node:os');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
-
const { resolveTargets, toolDirs } = require('../../install/targets.
|
|
8
|
-
const { resolveContext, printContext } = require('../../install/context.
|
|
7
|
+
const { resolveTargets, toolDirs } = require('../../install/targets.js');
|
|
8
|
+
const { resolveContext, printContext } = require('../../install/context.js');
|
|
9
9
|
const {
|
|
10
10
|
BackupError, DEFAULT_BACKUP_RETENTION, createFileBackup, applyRetention, formatBytes, retentionNote,
|
|
11
|
-
} = require('../../install/backup.
|
|
12
|
-
const { backupSetFromPlan } = require('../../install/backup-set.
|
|
13
|
-
const { ledgerPath } = require('../../state/index.
|
|
14
|
-
const { writeManifest, readInstallManifest } = require('../../install/manifest.
|
|
15
|
-
const { confirm } = require('../../helper/prompt.
|
|
16
|
-
const { sourceCommit } = require('../../helper/git.
|
|
17
|
-
const { chmodHooksExecutable } = require('../../helper/settings-scope.
|
|
18
|
-
const { promptMcpCheckbox, resolveMcpSelections } = require('../../install/mcp.
|
|
19
|
-
const { loadRegistry } = require('../../registry/index.
|
|
20
|
-
const { storageAfterInstall } = require('../../runtime/storage/upkeep.
|
|
21
|
-
const { applyLifecycle, applyMcpIndex, recordMcpOwnership } = require('../../lifecycle/index.
|
|
11
|
+
} = require('../../install/backup.js');
|
|
12
|
+
const { backupSetFromPlan } = require('../../install/backup-set.js');
|
|
13
|
+
const { ledgerPath } = require('../../state/index.js');
|
|
14
|
+
const { writeManifest, readInstallManifest } = require('../../install/manifest.js');
|
|
15
|
+
const { confirm } = require('../../helper/prompt.js');
|
|
16
|
+
const { sourceCommit } = require('../../helper/git.js');
|
|
17
|
+
const { chmodHooksExecutable } = require('../../helper/settings-scope.js');
|
|
18
|
+
const { promptMcpCheckbox, resolveMcpSelections } = require('../../install/mcp.js');
|
|
19
|
+
const { loadRegistry } = require('../../registry/index.js');
|
|
20
|
+
const { storageAfterInstall } = require('../../runtime/storage/upkeep.js');
|
|
21
|
+
const { applyLifecycle, applyMcpIndex, recordMcpOwnership } = require('../../lifecycle/index.js');
|
|
22
22
|
const {
|
|
23
23
|
codexScope, registryLifecycleView, printRegistryLifecycle, printPlanNotices, assertSafeRegistryPlan,
|
|
24
24
|
lockDocument, recordLock,
|
|
25
|
-
} = require('../../lifecycle/view.
|
|
25
|
+
} = require('../../lifecycle/view.js');
|
|
26
26
|
const {
|
|
27
27
|
REPO_ROOT, SCRIPT_DIR, pkg, scopeOf, installPaths, reportRetiredMcp, scopeSelectionState, printMcpSelection, plannedMcpSelections,
|
|
28
28
|
printRecordedMcpOwnership, buildAdapterRegistry, refuseHomeRootedProject, holdRunLock, checkpointRunLock,
|
|
29
|
-
} = require('../shared.
|
|
29
|
+
} = require('../shared.js');
|
|
30
30
|
|
|
31
|
-
function cmdInstall(o
|
|
31
|
+
function cmdInstall(o ) {
|
|
32
32
|
refuseHomeRootedProject(o, 'install');
|
|
33
33
|
const targets = resolveTargets(o.targets);
|
|
34
34
|
const scope = scopeOf(o);
|
|
@@ -117,7 +117,7 @@ function cmdInstall(o: any): void {
|
|
|
117
117
|
stateRoot: lifecycleView.stateRoot, ledger: lifecycleView.ledger });
|
|
118
118
|
for (const target of lifecycleView.plan.targets) {
|
|
119
119
|
if (target.skipped || !target.changes.length) continue;
|
|
120
|
-
const owned = result.ledger.resources.filter((resource
|
|
120
|
+
const owned = result.ledger.resources.filter((resource ) => resource.harness === target.harness).length;
|
|
121
121
|
console.log(`[INFO] ${target.harness}: lifecycle verified (${owned} owned resource(s))`);
|
|
122
122
|
}
|
|
123
123
|
ledgerAfter = result.ledger;
|
|
@@ -167,10 +167,10 @@ function cmdInstall(o: any): void {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
/** Run a backup step; a BackupError stops the command before anything is changed. */
|
|
170
|
-
function backupStep(step
|
|
170
|
+
function backupStep(step ) {
|
|
171
171
|
try {
|
|
172
172
|
return step();
|
|
173
|
-
} catch (error
|
|
173
|
+
} catch (error ) {
|
|
174
174
|
if (!(error instanceof BackupError)) throw error;
|
|
175
175
|
console.error(`[ERROR] Backup failed, nothing was changed: ${error.message}`);
|
|
176
176
|
process.exit(1);
|
|
@@ -178,7 +178,7 @@ function backupStep(step: () => any): any {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
/** Print what a backup step wrote and return its id ('' when there was nothing to back up). */
|
|
181
|
-
function reportBackup(backup
|
|
181
|
+
function reportBackup(backup ) {
|
|
182
182
|
if (!backup) {
|
|
183
183
|
console.error('[INFO] Backup: nothing to back up (no file changes)');
|
|
184
184
|
return '';
|
|
@@ -188,22 +188,22 @@ function reportBackup(backup: any): string {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/** The dry-run backup lines for a set the real run would back up. */
|
|
191
|
-
function printBackupDryRun(set
|
|
191
|
+
function printBackupDryRun(set , destination ) {
|
|
192
192
|
if (set.count === 0) {
|
|
193
193
|
console.log('[DRY] Backup: nothing to back up (no file changes)');
|
|
194
194
|
} else {
|
|
195
195
|
console.log(`[DRY] Would back up ${set.count} file(s), ${formatBytes(set.bytes)} (${set.bytes} bytes; ${set.absent} not present yet) to ${destination}`);
|
|
196
196
|
}
|
|
197
|
-
const mcpOnly = set.excluded.filter((item
|
|
197
|
+
const mcpOnly = set.excluded.filter((item ) => item.reason === 'mcp-entries-only').length;
|
|
198
198
|
if (mcpOnly > 0) console.log(`[DRY] Not backed up: ${mcpOnly} file(s) holding only MCP entries`);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/** Keep the newest backups after a run that changed files; never changes the exit code. */
|
|
202
|
-
function applyBackupRetention({ backupRoot, prune, keep, bid }
|
|
202
|
+
function applyBackupRetention({ backupRoot, prune, keep, bid } ) {
|
|
203
203
|
let retention;
|
|
204
204
|
try {
|
|
205
205
|
retention = applyRetention({ backupRoot, keep, protect: bid ? [bid] : [] });
|
|
206
|
-
} catch (error
|
|
206
|
+
} catch (error ) {
|
|
207
207
|
console.error(`[WARN] Backups: could not apply retention: ${error.message}`);
|
|
208
208
|
return;
|
|
209
209
|
}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// `doflow list-backups` — the backup table for this scope. Takes no project-path positional
|
|
3
3
|
// (rollback's one positional slot is the backup id); scope is -g/--global vs cwd-rooted project.
|
|
4
4
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
5
|
-
const { listBackups } = require('../../install/backup.
|
|
6
|
-
const { installPaths, printBackupTable } = require('../shared.
|
|
5
|
+
const { listBackups } = require('../../install/backup.js');
|
|
6
|
+
const { installPaths, printBackupTable } = require('../shared.js');
|
|
7
7
|
|
|
8
|
-
function cmdListBackups(o
|
|
8
|
+
function cmdListBackups(o ) {
|
|
9
9
|
// rollback/list-backups don't take a project-path positional (their one positional slot is the
|
|
10
10
|
// backup id for rollback) — scope is -g/--global vs default project rooted at cwd.
|
|
11
11
|
const scope = { global: o.global, projectRoot: '.' };
|
|
@@ -14,7 +14,7 @@ function cmdListBackups(o: any): void {
|
|
|
14
14
|
// listBackups now reads the canonical root AND the legacy pre-.doflow one, so the footer must name
|
|
15
15
|
// the roots the rows actually came from: reporting restore points as living in a directory that
|
|
16
16
|
// does not exist on this machine sends a user looking for recovery material to the wrong place.
|
|
17
|
-
const roots = [...new Set(rows.map((r
|
|
17
|
+
const roots = [...new Set(rows.map((r ) => r.backupRoot))];
|
|
18
18
|
printBackupTable(rows, roots.length ? roots.join(', ') : lifecyclePaths.backupRoot);
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -2,31 +2,31 @@
|
|
|
2
2
|
// `doflow reconcile` — converge observed state onto the doflow.lock pin: report drift, then heal
|
|
3
3
|
// it on confirmation. Never re-prompts for MCP; selections ride exactly as pinned.
|
|
4
4
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
5
|
-
const { toolDirs } = require('../../install/targets.
|
|
6
|
-
const { resolveContext, printContext } = require('../../install/context.
|
|
7
|
-
const { writeManifest } = require('../../install/manifest.
|
|
8
|
-
const { confirm } = require('../../helper/prompt.
|
|
9
|
-
const { sourceCommit } = require('../../helper/git.
|
|
10
|
-
const { loadRegistry } = require('../../registry/index.
|
|
11
|
-
const { applyLifecycle, recordMcpOwnership } = require('../../lifecycle/index.
|
|
5
|
+
const { toolDirs } = require('../../install/targets.js');
|
|
6
|
+
const { resolveContext, printContext } = require('../../install/context.js');
|
|
7
|
+
const { writeManifest } = require('../../install/manifest.js');
|
|
8
|
+
const { confirm } = require('../../helper/prompt.js');
|
|
9
|
+
const { sourceCommit } = require('../../helper/git.js');
|
|
10
|
+
const { loadRegistry } = require('../../registry/index.js');
|
|
11
|
+
const { applyLifecycle, recordMcpOwnership } = require('../../lifecycle/index.js');
|
|
12
12
|
const {
|
|
13
13
|
registryLifecycleView, assertSafeRegistryPlan,
|
|
14
|
-
} = require('../../lifecycle/view.
|
|
15
|
-
const { recordedMcpSelections } = require('../../install/mcp.
|
|
14
|
+
} = require('../../lifecycle/view.js');
|
|
15
|
+
const { recordedMcpSelections } = require('../../install/mcp.js');
|
|
16
16
|
const {
|
|
17
17
|
REPO_ROOT, SCRIPT_DIR, pkg, scopeOf, installPaths, scopeSelectionState, printRecordedMcpOwnership, buildAdapterRegistry,
|
|
18
18
|
refuseHomeRootedProject, holdRunLock, checkpointRunLock,
|
|
19
|
-
} = require('../shared.
|
|
19
|
+
} = require('../shared.js');
|
|
20
20
|
|
|
21
21
|
/** Classify desired-vs-observed drift for one lifecycle view. The plan IS the diff: its changes
|
|
22
22
|
* are the operations needed to converge, its conflicts and prerequisites are the drift that must
|
|
23
23
|
* not be silently healed. */
|
|
24
|
-
function reconcileReport(view
|
|
24
|
+
function reconcileReport(view ) {
|
|
25
25
|
const drifts = [];
|
|
26
26
|
const perHarness = new Map();
|
|
27
27
|
for (const target of view.plan.targets) {
|
|
28
28
|
if (target.skipped) continue;
|
|
29
|
-
const entry
|
|
29
|
+
const entry = { create: 0, update: 0, remove: 0 };
|
|
30
30
|
for (const change of target.changes) {
|
|
31
31
|
if (entry[change.operation] !== undefined) entry[change.operation] += 1;
|
|
32
32
|
drifts.push({ harness: target.harness, operation: change.operation, assetId: change.assetId, target: change.target });
|
|
@@ -42,9 +42,9 @@ function reconcileReport(view: any): any {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function printReconcileReport(report
|
|
45
|
+
function printReconcileReport(report , lock ) {
|
|
46
46
|
console.log(`[INFO] Reconciling against doflow.lock (sourceVersion ${lock.sourceVersion ?? 'unknown'})`);
|
|
47
|
-
for (const [harness, counts] of Object.entries
|
|
47
|
+
for (const [harness, counts] of Object.entries (report.perHarness)) {
|
|
48
48
|
const total = counts.create + counts.update + counts.remove;
|
|
49
49
|
console.log(`[INFO] ${harness}: ${total === 0 ? 'clean' : `${total} drift(s) (${counts.create} create, ${counts.update} update, ${counts.remove} remove)`}`);
|
|
50
50
|
}
|
|
@@ -53,7 +53,7 @@ function printReconcileReport(report: any, lock: any): void {
|
|
|
53
53
|
if (report.clean) console.log('[OK] Observed state matches doflow.lock.');
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function cmdReconcile(o
|
|
56
|
+
function cmdReconcile(o ) {
|
|
57
57
|
refuseHomeRootedProject(o, 'reconcile');
|
|
58
58
|
const scope = scopeOf(o);
|
|
59
59
|
const hold = holdRunLock(o, scope, 'reconcile');
|
|
@@ -63,10 +63,10 @@ function cmdReconcile(o: any): void {
|
|
|
63
63
|
const { lock, ledger, manifestServers } = scopeSelectionState(scope);
|
|
64
64
|
// A harness the ledger holds but the lock does not pin is installed and outside reconcile's reach.
|
|
65
65
|
// Said, not converged: the lock records choices, and reconcile has no choice to record for it.
|
|
66
|
-
const pinned = new Set((lock?.targets ?? []).map((entry
|
|
67
|
-
const unpinned = [...new Set((ledger?.resources ?? []).map((resource
|
|
66
|
+
const pinned = new Set((lock?.targets ?? []).map((entry ) => entry.harness));
|
|
67
|
+
const unpinned = [...new Set((ledger?.resources ?? []).map((resource ) => resource.harness))].filter((harness) => !pinned.has(harness)).sort();
|
|
68
68
|
for (const harness of unpinned) {
|
|
69
|
-
const count = ledger.resources.filter((resource
|
|
69
|
+
const count = ledger.resources.filter((resource ) => resource.harness === harness).length;
|
|
70
70
|
console.log(`[WARN] ${harness}: installed (ledger holds ${count} resource(s)) but absent from doflow.lock; reconcile does not converge it.`);
|
|
71
71
|
}
|
|
72
72
|
if (!lock || !lock.targets.length) {
|
|
@@ -76,7 +76,7 @@ function cmdReconcile(o: any): void {
|
|
|
76
76
|
console.log('[INFO] No doflow.lock in this scope — nothing pinned to reconcile against. Run `doflow install` first.');
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
|
-
const targets = lock.targets.map((entry
|
|
79
|
+
const targets = lock.targets.map((entry ) => entry.harness);
|
|
80
80
|
const registry = loadRegistry({ repoRoot: REPO_ROOT });
|
|
81
81
|
printContext(resolveContext({ repoRoot: REPO_ROOT, targets, dirs, sourceCommit: sourceCommit(SCRIPT_DIR), ...scope }));
|
|
82
82
|
// Each harness converges onto its own pinned selection — reconcile never re-prompts and never
|
|
@@ -4,28 +4,28 @@
|
|
|
4
4
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
5
5
|
const os = require('node:os');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
-
const { resolveTargets, toolDirs } = require('../../install/targets.
|
|
8
|
-
const { confirm } = require('../../helper/prompt.
|
|
9
|
-
const { loadRegistry } = require('../../registry/index.
|
|
10
|
-
const { storageForgetScope } = require('../../runtime/storage/upkeep.
|
|
11
|
-
const { removeLifecycle, retentionSummary } = require('../../lifecycle/index.
|
|
7
|
+
const { resolveTargets, toolDirs } = require('../../install/targets.js');
|
|
8
|
+
const { confirm } = require('../../helper/prompt.js');
|
|
9
|
+
const { loadRegistry } = require('../../registry/index.js');
|
|
10
|
+
const { storageForgetScope } = require('../../runtime/storage/upkeep.js');
|
|
11
|
+
const { removeLifecycle, retentionSummary } = require('../../lifecycle/index.js');
|
|
12
12
|
const {
|
|
13
13
|
codexScope, registryLifecycleView, printRegistryLifecycle, LIFECYCLE_HARNESSES,
|
|
14
14
|
assertSafeRegistryPlan, lockDocument, recordLock,
|
|
15
|
-
} = require('../../lifecycle/view.
|
|
16
|
-
const { recordedMcpSelections } = require('../../install/mcp.
|
|
15
|
+
} = require('../../lifecycle/view.js');
|
|
16
|
+
const { recordedMcpSelections } = require('../../install/mcp.js');
|
|
17
17
|
const {
|
|
18
18
|
REPO_ROOT, scopeOf, scopeSelectionState, buildAdapterRegistry, refuseHomeRootedProject, holdRunLock, checkpointRunLock,
|
|
19
|
-
} = require('../shared.
|
|
19
|
+
} = require('../shared.js');
|
|
20
20
|
|
|
21
|
-
function cmdRemove(o
|
|
21
|
+
function cmdRemove(o ) {
|
|
22
22
|
refuseHomeRootedProject(o, 'remove');
|
|
23
23
|
const targets = resolveTargets(o.targets);
|
|
24
24
|
const scope = scopeOf(o);
|
|
25
25
|
const hold = holdRunLock(o, scope, 'remove');
|
|
26
26
|
try {
|
|
27
27
|
const dirs = toolDirs(scope);
|
|
28
|
-
const lifecycleTargets = targets.filter((t
|
|
28
|
+
const lifecycleTargets = targets.filter((t ) => LIFECYCLE_HARNESSES.includes(t));
|
|
29
29
|
if (!lifecycleTargets.length) {
|
|
30
30
|
console.log('[INFO] No lifecycle-owned native resources selected; legacy compatibility assets are never broadly removed.');
|
|
31
31
|
return;
|