create-agdf 0.10.1 → 0.10.3
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/README.md +21 -13
- package/bin/agdf-validator.js +11 -0
- package/generated/.agdf/control/README.md +3 -3
- package/generated/.github/skills/agdf-brownfield-analysis/SKILL.md +1 -1
- package/generated/.github/skills/agdf-delivery-path-search/SKILL.md +7 -6
- package/generated/.github/skills/agdf-gate-check/SKILL.md +35 -105
- package/generated/.github/skills/agdf-interaction-locales.json +10 -0
- package/generated/.github/skills/agdf-release-or/SKILL.md +1 -1
- package/generated/.github/skills/contracts/gate-transition.md +4 -2
- package/generated/.github/skills/contracts/interaction.md +23 -0
- package/generated/.github/skills/contracts/modes.md +12 -1
- package/generated/.opencode/agdf-interaction-locales.json +10 -0
- package/generated/.opencode/contracts/gate-transition.md +4 -2
- package/generated/.opencode/contracts/interaction.md +23 -0
- package/generated/.opencode/contracts/modes.md +12 -1
- package/generated/.opencode/skills/agdf-brownfield-analysis/SKILL.md +1 -1
- package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +7 -6
- package/generated/.opencode/skills/agdf-gate-check/SKILL.md +35 -105
- package/generated/.opencode/skills/agdf-release-or/SKILL.md +1 -1
- package/generated/plugins/agdf/.claude-plugin/plugin.json +19 -0
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/control/README.md +3 -3
- package/generated/plugins/agdf/hooks/session-start.sh +1 -1
- package/generated/plugins/agdf/meta/agdf-interaction-locales.json +10 -0
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/meta/contracts/gate-transition.md +4 -2
- package/generated/plugins/agdf/meta/contracts/interaction.md +23 -0
- package/generated/plugins/agdf/meta/contracts/modes.md +12 -1
- package/generated/plugins/agdf/runtime/agdf-local.js +11 -0
- package/generated/plugins/agdf/runtime/create-agdf/NOTICE +10 -0
- package/generated/plugins/agdf/runtime/create-agdf/bin/agdf-validator.js +11 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +426 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +220 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +136 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +75 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +156 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/runtime-context.js +83 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/validation-handlers.js +46 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/delivery-map.js +291 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +265 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +450 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +283 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/required-files.js +9 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +217 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +344 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/verified-change.js +156 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/aggregate.js +18 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/gate-approval-validator.js +49 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/index.js +7 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/legacy-migration.js +95 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +227 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-repository.js +68 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-resolver.js +45 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-writer.js +97 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/candidate-policy.js +99 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +237 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +44 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +53 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/protocol.js +13 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/claude.js +40 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/codex.js +38 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/protocol.js +12 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +39 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/scoring.js +21 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +154 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +161 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +14 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/transports/read-only-guard.js +43 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +670 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/local-validator.js +142 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/validator-application.js +40 -0
- package/generated/plugins/agdf/runtime/create-agdf/package.json +66 -0
- package/generated/plugins/agdf/runtime/runtime-manifest.json +8 -0
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +1055 -0
- package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +1 -1
- package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +7 -6
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +35 -105
- package/generated/plugins/agdf/skills/release-or/SKILL.md +1 -1
- package/lib/cli/application.js +16 -54
- package/lib/cli/command-registry.js +9 -11
- package/lib/cli/validation-handlers.js +46 -0
- package/lib/control-evaluation/doctor.js +1 -1
- package/lib/control-evaluation/gate-check.js +23 -32
- package/lib/control-evaluation/required-files.js +9 -0
- package/lib/installers/local-marketplace.js +322 -0
- package/lib/installers/opencode.js +84 -6
- package/lib/installers/plugin-installers.js +102 -26
- package/lib/interaction-presentation.js +80 -0
- package/lib/runtime/local-validator.js +142 -0
- package/lib/runtime/validator-application.js +40 -0
- package/lib/scaffold/plan.js +1 -7
- package/lib/scaffold/presentation.js +2 -2
- package/opencode-plugin.js +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
cpSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
renameSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
statSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
15
|
+
import process from "node:process";
|
|
16
|
+
import { generatedRoot, packageRoot, pluginDefinition } from "../cli/runtime-context.js";
|
|
17
|
+
|
|
18
|
+
const MARKETPLACE_ID = "agdf";
|
|
19
|
+
const OWNERSHIP_FILE = ".agdf-owned.json";
|
|
20
|
+
const LEGACY_REPOSITORY = "arndtgold/ai-native-governance-delivery-framework";
|
|
21
|
+
|
|
22
|
+
function pathInside(root, candidate) {
|
|
23
|
+
const rel = relative(root, candidate);
|
|
24
|
+
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function digestDirectory(root) {
|
|
28
|
+
const files = [];
|
|
29
|
+
function visit(directory) {
|
|
30
|
+
for (const name of readdirSync(directory).sort()) {
|
|
31
|
+
const path = join(directory, name);
|
|
32
|
+
const stats = statSync(path);
|
|
33
|
+
if (stats.isDirectory()) visit(path);
|
|
34
|
+
else if (stats.isFile()) files.push(path);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
visit(root);
|
|
38
|
+
const hash = createHash("sha256");
|
|
39
|
+
for (const path of files) {
|
|
40
|
+
hash.update(relative(root, path).replaceAll("\\", "/"));
|
|
41
|
+
hash.update("\0");
|
|
42
|
+
hash.update(readFileSync(path));
|
|
43
|
+
hash.update("\0");
|
|
44
|
+
}
|
|
45
|
+
return hash.digest("hex");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readJson(path, label) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error(`${label} is invalid: ${error.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function defaultAgdfDataRoot({ env = process.env, platform = process.platform, home = homedir() } = {}) {
|
|
57
|
+
if (env.AGDF_DATA_DIR) return resolve(env.AGDF_DATA_DIR);
|
|
58
|
+
if (platform === "darwin") return join(home, "Library", "Application Support", "agdf");
|
|
59
|
+
if (platform === "win32") return join(env.LOCALAPPDATA || env.APPDATA || join(home, "AppData", "Local"), "agdf");
|
|
60
|
+
return join(env.XDG_DATA_HOME || join(home, ".local", "share"), "agdf");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function localMarketplaceRoot(options = {}) {
|
|
64
|
+
return join(defaultAgdfDataRoot(options), "marketplaces", MARKETPLACE_ID);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function ownership(root, { allowBuilding = false } = {}) {
|
|
68
|
+
if (!existsSync(join(root, OWNERSHIP_FILE))) {
|
|
69
|
+
throw new Error(`Refusing marketplace path without AGDF ownership marker: ${root}`);
|
|
70
|
+
}
|
|
71
|
+
const marker = readJson(join(root, OWNERSHIP_FILE), "AGDF marketplace ownership marker");
|
|
72
|
+
if (marker.schema_version !== 1 || marker.owner !== "create-agdf" || marker.marketplace_id !== MARKETPLACE_ID) {
|
|
73
|
+
throw new Error(`Refusing marketplace path with invalid AGDF ownership marker: ${root}`);
|
|
74
|
+
}
|
|
75
|
+
if (marker.staging_state !== "ready" && !(allowBuilding && marker.staging_state === "building")) {
|
|
76
|
+
throw new Error(`Refusing marketplace path with invalid AGDF staging state: ${root}`);
|
|
77
|
+
}
|
|
78
|
+
return marker;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function removeOwnedRoot(root, parent, options = {}) {
|
|
82
|
+
if (!existsSync(root)) return;
|
|
83
|
+
if (!pathInside(parent, root) || dirname(root) !== parent) throw new Error(`Refusing unsafe marketplace cleanup path: ${root}`);
|
|
84
|
+
ownership(root, options);
|
|
85
|
+
rmSync(root, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function writeJson(path, value) {
|
|
89
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
90
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function codexMarketplace() {
|
|
94
|
+
return {
|
|
95
|
+
name: MARKETPLACE_ID,
|
|
96
|
+
interface: { displayName: pluginDefinition.displayName },
|
|
97
|
+
plugins: [{
|
|
98
|
+
name: pluginDefinition.id,
|
|
99
|
+
source: { source: "local", path: "./plugins/agdf" },
|
|
100
|
+
policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" },
|
|
101
|
+
category: pluginDefinition.category,
|
|
102
|
+
}],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function claudeMarketplace() {
|
|
107
|
+
return {
|
|
108
|
+
name: MARKETPLACE_ID,
|
|
109
|
+
owner: { name: pluginDefinition.displayName, email: "" },
|
|
110
|
+
metadata: { description: pluginDefinition.description },
|
|
111
|
+
plugins: [{
|
|
112
|
+
name: pluginDefinition.id,
|
|
113
|
+
source: "./plugins/agdf",
|
|
114
|
+
description: pluginDefinition.claudeDescription,
|
|
115
|
+
category: pluginDefinition.category,
|
|
116
|
+
}],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function validateBuiltPlugin(pluginRoot, expectedVersion) {
|
|
121
|
+
const definition = readJson(join(pluginRoot, "meta", "agdf-plugin.definition.json"), "built plugin definition");
|
|
122
|
+
const runtime = readJson(join(pluginRoot, "runtime", "runtime-manifest.json"), "built runtime manifest");
|
|
123
|
+
for (const required of [
|
|
124
|
+
join(pluginRoot, ".codex-plugin", "plugin.json"),
|
|
125
|
+
join(pluginRoot, ".claude-plugin", "plugin.json"),
|
|
126
|
+
join(pluginRoot, "runtime", "agdf-local.js"),
|
|
127
|
+
join(pluginRoot, "scripts", "check-runtime-integrity.mjs"),
|
|
128
|
+
]) {
|
|
129
|
+
if (!existsSync(required)) throw new Error(`Built plugin is incomplete: ${required}`);
|
|
130
|
+
}
|
|
131
|
+
if (definition.version !== expectedVersion || runtime.version !== expectedVersion) {
|
|
132
|
+
throw new Error(`Built plugin version mismatch: expected ${expectedVersion}, definition ${definition.version}, runtime ${runtime.version}`);
|
|
133
|
+
}
|
|
134
|
+
if (!/^[a-f0-9]{64}$/.test(runtime.digest ?? "")) throw new Error("Built plugin runtime digest is invalid.");
|
|
135
|
+
if (digestDirectory(join(pluginRoot, "runtime", "create-agdf")) !== runtime.digest) {
|
|
136
|
+
throw new Error("Built plugin runtime digest does not match its payload.");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function validateMarketplaceRoot(root) {
|
|
141
|
+
const codex = readJson(join(root, ".agents", "plugins", "marketplace.json"), "Codex local marketplace manifest");
|
|
142
|
+
const claude = readJson(join(root, ".claude-plugin", "marketplace.json"), "Claude local marketplace manifest");
|
|
143
|
+
if (JSON.stringify(codex) !== JSON.stringify(codexMarketplace())) {
|
|
144
|
+
throw new Error(`Codex local marketplace manifest is not owned by ${MARKETPLACE_ID}: ${root}`);
|
|
145
|
+
}
|
|
146
|
+
if (JSON.stringify(claude) !== JSON.stringify(claudeMarketplace())) {
|
|
147
|
+
throw new Error(`Claude local marketplace manifest is not owned by ${MARKETPLACE_ID}: ${root}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function recoverInterruptedTransaction(stableRoot, stageRoot, backupRoot, failedRoot) {
|
|
152
|
+
const parent = dirname(stableRoot);
|
|
153
|
+
if (existsSync(backupRoot)) {
|
|
154
|
+
ownership(backupRoot);
|
|
155
|
+
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
156
|
+
if (existsSync(stableRoot)) {
|
|
157
|
+
ownership(stableRoot);
|
|
158
|
+
renameSync(stableRoot, failedRoot);
|
|
159
|
+
}
|
|
160
|
+
renameSync(backupRoot, stableRoot);
|
|
161
|
+
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
162
|
+
}
|
|
163
|
+
if (existsSync(stageRoot)) removeOwnedRoot(stageRoot, parent, { allowBuilding: true });
|
|
164
|
+
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function prepareLocalMarketplace({
|
|
168
|
+
dataRoot = defaultAgdfDataRoot(),
|
|
169
|
+
builtPluginRoot = join(generatedRoot, "plugins", "agdf"),
|
|
170
|
+
expectedVersion = pluginDefinition.version,
|
|
171
|
+
} = {}) {
|
|
172
|
+
dataRoot = resolve(dataRoot);
|
|
173
|
+
builtPluginRoot = resolve(builtPluginRoot);
|
|
174
|
+
const stableRoot = join(dataRoot, "marketplaces", MARKETPLACE_ID);
|
|
175
|
+
const parent = dirname(stableRoot);
|
|
176
|
+
const stageRoot = `${stableRoot}.stage`;
|
|
177
|
+
const backupRoot = `${stableRoot}.backup`;
|
|
178
|
+
const failedRoot = `${stableRoot}.failed`;
|
|
179
|
+
if (!pathInside(dataRoot, stableRoot) || dirname(stableRoot) !== parent) throw new Error(`Unsafe AGDF marketplace root: ${stableRoot}`);
|
|
180
|
+
validateBuiltPlugin(builtPluginRoot, expectedVersion);
|
|
181
|
+
mkdirSync(parent, { recursive: true });
|
|
182
|
+
recoverInterruptedTransaction(stableRoot, stageRoot, backupRoot, failedRoot);
|
|
183
|
+
|
|
184
|
+
const existing = existsSync(stableRoot) ? ownership(stableRoot) : null;
|
|
185
|
+
if (existing) {
|
|
186
|
+
const existingPluginRoot = join(stableRoot, "plugins", MARKETPLACE_ID);
|
|
187
|
+
validateBuiltPlugin(existingPluginRoot, existing.version);
|
|
188
|
+
validateMarketplaceRoot(stableRoot);
|
|
189
|
+
if (digestDirectory(existingPluginRoot) !== existing.plugin_digest) {
|
|
190
|
+
throw new Error(`Refusing tampered AGDF marketplace root: ${stableRoot}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
mkdirSync(stageRoot, { recursive: false });
|
|
194
|
+
try {
|
|
195
|
+
writeJson(join(stageRoot, OWNERSHIP_FILE), {
|
|
196
|
+
schema_version: 1,
|
|
197
|
+
owner: "create-agdf",
|
|
198
|
+
marketplace_id: MARKETPLACE_ID,
|
|
199
|
+
version: expectedVersion,
|
|
200
|
+
plugin_digest: null,
|
|
201
|
+
source_package_version: readJson(join(packageRoot, "package.json"), "create-agdf package manifest").version,
|
|
202
|
+
staging_state: "building",
|
|
203
|
+
});
|
|
204
|
+
const stagedPluginRoot = join(stageRoot, "plugins", MARKETPLACE_ID);
|
|
205
|
+
cpSync(builtPluginRoot, stagedPluginRoot, { recursive: true });
|
|
206
|
+
validateBuiltPlugin(stagedPluginRoot, expectedVersion);
|
|
207
|
+
writeJson(join(stageRoot, ".agents", "plugins", "marketplace.json"), codexMarketplace());
|
|
208
|
+
writeJson(join(stageRoot, ".claude-plugin", "marketplace.json"), claudeMarketplace());
|
|
209
|
+
validateMarketplaceRoot(stageRoot);
|
|
210
|
+
const pluginDigest = digestDirectory(stagedPluginRoot);
|
|
211
|
+
const marker = {
|
|
212
|
+
schema_version: 1,
|
|
213
|
+
owner: "create-agdf",
|
|
214
|
+
marketplace_id: MARKETPLACE_ID,
|
|
215
|
+
version: expectedVersion,
|
|
216
|
+
plugin_digest: pluginDigest,
|
|
217
|
+
source_package_version: readJson(join(packageRoot, "package.json"), "create-agdf package manifest").version,
|
|
218
|
+
staging_state: "ready",
|
|
219
|
+
};
|
|
220
|
+
writeJson(join(stageRoot, OWNERSHIP_FILE), marker);
|
|
221
|
+
|
|
222
|
+
if (existing?.version === expectedVersion && existing?.plugin_digest === pluginDigest) {
|
|
223
|
+
removeOwnedRoot(stageRoot, parent);
|
|
224
|
+
return Object.freeze({
|
|
225
|
+
root: stableRoot,
|
|
226
|
+
pluginRoot: join(stableRoot, "plugins", MARKETPLACE_ID),
|
|
227
|
+
version: expectedVersion,
|
|
228
|
+
digest: pluginDigest,
|
|
229
|
+
changed: false,
|
|
230
|
+
commit() {},
|
|
231
|
+
rollback() {},
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (existsSync(stableRoot)) renameSync(stableRoot, backupRoot);
|
|
236
|
+
try {
|
|
237
|
+
renameSync(stageRoot, stableRoot);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (existsSync(backupRoot) && !existsSync(stableRoot)) renameSync(backupRoot, stableRoot);
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
let closed = false;
|
|
243
|
+
return Object.freeze({
|
|
244
|
+
root: stableRoot,
|
|
245
|
+
pluginRoot: join(stableRoot, "plugins", MARKETPLACE_ID),
|
|
246
|
+
version: expectedVersion,
|
|
247
|
+
digest: pluginDigest,
|
|
248
|
+
changed: true,
|
|
249
|
+
commit() {
|
|
250
|
+
if (closed) return;
|
|
251
|
+
if (existsSync(backupRoot)) removeOwnedRoot(backupRoot, parent);
|
|
252
|
+
closed = true;
|
|
253
|
+
},
|
|
254
|
+
rollback() {
|
|
255
|
+
if (closed) return;
|
|
256
|
+
if (existsSync(backupRoot)) {
|
|
257
|
+
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
258
|
+
if (existsSync(stableRoot)) renameSync(stableRoot, failedRoot);
|
|
259
|
+
renameSync(backupRoot, stableRoot);
|
|
260
|
+
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
261
|
+
} else if (existsSync(stableRoot)) {
|
|
262
|
+
removeOwnedRoot(stableRoot, parent);
|
|
263
|
+
}
|
|
264
|
+
closed = true;
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (existsSync(stageRoot)) removeOwnedRoot(stageRoot, parent, { allowBuilding: true });
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function normalizedLegacySource(value) {
|
|
274
|
+
if (typeof value !== "string") return "";
|
|
275
|
+
return value.trim().toLowerCase()
|
|
276
|
+
.replace(/^git\+/, "")
|
|
277
|
+
.replace(/^https?:\/\/github\.com\//, "")
|
|
278
|
+
.replace(/^github:/, "")
|
|
279
|
+
.replace(/\.git$/, "")
|
|
280
|
+
.replace(/\/$/, "");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function sourceForEntry(entry) {
|
|
284
|
+
const source = entry?.marketplaceSource?.source
|
|
285
|
+
?? entry?.source?.source
|
|
286
|
+
?? (entry?.source === "directory" ? entry?.path : undefined)
|
|
287
|
+
?? (entry?.source === "github" ? entry?.repo : undefined)
|
|
288
|
+
?? entry?.repo
|
|
289
|
+
?? entry?.path
|
|
290
|
+
?? entry?.source
|
|
291
|
+
?? entry?.url
|
|
292
|
+
?? "";
|
|
293
|
+
return typeof source === "string" ? source : "";
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function classifyMarketplaceList(surface, output, stableRoot) {
|
|
297
|
+
let parsed;
|
|
298
|
+
try {
|
|
299
|
+
parsed = typeof output === "string" ? JSON.parse(output) : output;
|
|
300
|
+
} catch {
|
|
301
|
+
return { state: "unknown", source: "", reason: "invalid_json" };
|
|
302
|
+
}
|
|
303
|
+
const entries = surface === "codex" ? parsed?.marketplaces : parsed;
|
|
304
|
+
if (!Array.isArray(entries)) return { state: "unknown", source: "", reason: "invalid_shape" };
|
|
305
|
+
const matches = entries.filter((entry) => entry?.name === MARKETPLACE_ID);
|
|
306
|
+
if (matches.length === 0) return { state: "absent", source: "" };
|
|
307
|
+
if (matches.length !== 1) return { state: "unknown", source: "", reason: "duplicate_name" };
|
|
308
|
+
const entry = matches[0];
|
|
309
|
+
const source = sourceForEntry(entry);
|
|
310
|
+
const sourceType = entry?.marketplaceSource?.sourceType ?? entry?.source?.sourceType ?? "";
|
|
311
|
+
if ((sourceType === "local" || isAbsolute(source)) && resolve(source) === resolve(stableRoot)) {
|
|
312
|
+
return { state: "owned_local_current", source };
|
|
313
|
+
}
|
|
314
|
+
if (normalizedLegacySource(source) === LEGACY_REPOSITORY) return { state: "legacy_github", source };
|
|
315
|
+
return { state: "conflict", source, reason: source ? "foreign_source" : "source_missing" };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export const localMarketplaceConstants = Object.freeze({
|
|
319
|
+
id: MARKETPLACE_ID,
|
|
320
|
+
ownershipFile: OWNERSHIP_FILE,
|
|
321
|
+
legacyRepository: LEGACY_REPOSITORY,
|
|
322
|
+
});
|
|
@@ -5,6 +5,7 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { generatedRoot, pluginDefinition } from "../cli/runtime-context.js";
|
|
7
7
|
import { evaluateOpenCodeRepositoryActivation } from "./opencode-activation.js";
|
|
8
|
+
import { resolveLocalValidator } from "../runtime/local-validator.js";
|
|
8
9
|
|
|
9
10
|
const contractModules = ["gate-transition.md", "interaction.md", "modes.md", "quality.md", "context-graph.md", "control-scaffold.md", "closeout.md"];
|
|
10
11
|
const openCodeSkillNames = pluginDefinition.skillSet.map((skill) => pluginDefinition.opencode.skillPrefix + skill.slug);
|
|
@@ -12,6 +13,8 @@ const globalOpenCodeSkillNames = pluginDefinition.skillSet.map((skill) => plugin
|
|
|
12
13
|
const globalOpenCodeSkillOwnershipMarker = "<!-- AGDF-GLOBAL-SKILL: ";
|
|
13
14
|
const globalOpenCodeInstructionsOwnershipMarker = "<!-- AGDF-GLOBAL-INSTRUCTIONS -->";
|
|
14
15
|
const globalOpenCodeRuntimeContractOwnershipMarker = "<!-- AGDF-GLOBAL-RUNTIME-CONTRACT -->";
|
|
16
|
+
const globalOpenCodeValidatorOwnershipMarker = "// AGDF-GLOBAL-LOCAL-VALIDATOR";
|
|
17
|
+
const globalOpenCodeValidatorPackageOwner = "create-agdf";
|
|
15
18
|
const testNpmCliPath = process.env.NODE_ENV === "test" ? process.env.AGDF_TEST_NPM_CLI_PATH || "" : "";
|
|
16
19
|
const npmCommand = testNpmCliPath ? process.execPath : process.platform === "win32" ? process.execPath : "npm";
|
|
17
20
|
const npmPrefixArgs = testNpmCliPath ? [testNpmCliPath] : process.platform === "win32" ? [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")] : [];
|
|
@@ -72,16 +75,23 @@ export function installOpenCodeGlobalPlugin(configDir) {
|
|
|
72
75
|
nextConfig.instructions = [...(nextConfig.instructions ?? [])];
|
|
73
76
|
if (!nextConfig.instructions.includes("AGDF.md")) nextConfig.instructions.push("AGDF.md");
|
|
74
77
|
|
|
78
|
+
const canonicalPermissions = pluginDefinition.opencode.permissions;
|
|
75
79
|
if (nextConfig.permission === undefined) {
|
|
76
|
-
nextConfig.permission =
|
|
80
|
+
nextConfig.permission = JSON.parse(JSON.stringify(canonicalPermissions));
|
|
77
81
|
} else if (nextConfig.permission && typeof nextConfig.permission === "object" && !Array.isArray(nextConfig.permission)) {
|
|
78
82
|
nextConfig.permission = { ...nextConfig.permission };
|
|
79
|
-
|
|
83
|
+
for (const permissionName of ["question", "edit", "bash"]) {
|
|
84
|
+
if (nextConfig.permission[permissionName] === undefined) {
|
|
85
|
+
nextConfig.permission[permissionName] = canonicalPermissions[permissionName];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
80
88
|
if (nextConfig.permission.skill === undefined) {
|
|
81
|
-
nextConfig.permission.skill = {
|
|
89
|
+
nextConfig.permission.skill = { ...canonicalPermissions.skill };
|
|
82
90
|
} else if (nextConfig.permission.skill && typeof nextConfig.permission.skill === "object" && !Array.isArray(nextConfig.permission.skill)) {
|
|
83
91
|
nextConfig.permission.skill = { ...nextConfig.permission.skill };
|
|
84
|
-
|
|
92
|
+
for (const [skillPattern, decision] of Object.entries(canonicalPermissions.skill)) {
|
|
93
|
+
if (nextConfig.permission.skill[skillPattern] === undefined) nextConfig.permission.skill[skillPattern] = decision;
|
|
94
|
+
}
|
|
85
95
|
}
|
|
86
96
|
}
|
|
87
97
|
|
|
@@ -119,9 +129,36 @@ function globalOpenCodeConfigPaths(configDir) {
|
|
|
119
129
|
runtimeContract: join(configDir, pluginDefinition.opencode.runtimeContractFileName),
|
|
120
130
|
contracts: join(configDir, "contracts"),
|
|
121
131
|
skills: join(configDir, "skills"),
|
|
132
|
+
localValidator: join(configDir, "agdf", "bin", "agdf-local.js"),
|
|
133
|
+
localValidatorPackage: join(configDir, "agdf", "package.json"),
|
|
122
134
|
};
|
|
123
135
|
}
|
|
124
136
|
|
|
137
|
+
function readGlobalOpenCodeValidatorPackage(path) {
|
|
138
|
+
if (!existsSync(path)) return null;
|
|
139
|
+
let existing;
|
|
140
|
+
try {
|
|
141
|
+
existing = JSON.parse(readFileSync(path, "utf8"));
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
return existing;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function globalOpenCodeValidatorPackageIsValid(path) {
|
|
149
|
+
const manifest = readGlobalOpenCodeValidatorPackage(path);
|
|
150
|
+
return manifest?.type === "module"
|
|
151
|
+
&& manifest?.agdf?.owner === globalOpenCodeValidatorPackageOwner
|
|
152
|
+
&& manifest?.agdf?.surface === "opencode-global-validator";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function assertGlobalOpenCodeValidatorPackageWritable(path) {
|
|
156
|
+
if (!existsSync(path)) return;
|
|
157
|
+
if (!globalOpenCodeValidatorPackageIsValid(path)) {
|
|
158
|
+
throw new Error(`Refusing to overwrite unowned global OpenCode validator package: ${path}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
125
162
|
function globalOpenCodeOwnershipMarkerIsValid(content, marker, placement) {
|
|
126
163
|
const lines = content.split(/\r?\n/);
|
|
127
164
|
if (placement === "first-line") return lines[0] === marker;
|
|
@@ -141,6 +178,8 @@ function assertGlobalOpenCodeSurfaceWritable(configDir) {
|
|
|
141
178
|
const paths = globalOpenCodeConfigPaths(configDir);
|
|
142
179
|
assertGlobalOpenCodeFileWritable(paths.instructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
|
|
143
180
|
assertGlobalOpenCodeFileWritable(paths.runtimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
|
|
181
|
+
assertGlobalOpenCodeFileWritable(paths.localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
|
|
182
|
+
assertGlobalOpenCodeValidatorPackageWritable(paths.localValidatorPackage);
|
|
144
183
|
for (const moduleName of contractModules) {
|
|
145
184
|
assertGlobalOpenCodeFileWritable(
|
|
146
185
|
join(paths.contracts, moduleName),
|
|
@@ -177,6 +216,16 @@ function globalOpenCodeBoundary() {
|
|
|
177
216
|
].join("\n");
|
|
178
217
|
}
|
|
179
218
|
|
|
219
|
+
function globalOpenCodeActivationGuard() {
|
|
220
|
+
return [
|
|
221
|
+
"## Repository Activation Guard",
|
|
222
|
+
"",
|
|
223
|
+
"Apply AGDF governance only when this repository has valid `.agdf/control/config.json` durable control.",
|
|
224
|
+
"If it is missing or invalid, stop and direct the user to `npx --yes @agdf/cli@latest opencode-repo`; the global installation alone is not activation.",
|
|
225
|
+
"",
|
|
226
|
+
].join("\n");
|
|
227
|
+
}
|
|
228
|
+
|
|
180
229
|
function toGlobalOpenCodeContent(content) {
|
|
181
230
|
let next = content;
|
|
182
231
|
for (const skill of pluginDefinition.skillSet) {
|
|
@@ -188,6 +237,7 @@ function toGlobalOpenCodeContent(content) {
|
|
|
188
237
|
}
|
|
189
238
|
|
|
190
239
|
export function installOpenCodeGlobalSurface(configDir) {
|
|
240
|
+
assertGlobalOpenCodeSurfaceWritable(configDir);
|
|
191
241
|
const paths = globalOpenCodeConfigPaths(configDir);
|
|
192
242
|
const generatedOpenCodeRoot = join(generatedRoot, ".opencode");
|
|
193
243
|
const generatedInstructions = readFileSync(join(generatedOpenCodeRoot, pluginDefinition.opencode.instructionsFileName), "utf8");
|
|
@@ -204,9 +254,21 @@ export function installOpenCodeGlobalSurface(configDir) {
|
|
|
204
254
|
toGlobalOpenCodeContent(generatedInstructions),
|
|
205
255
|
].join("\n");
|
|
206
256
|
const globalRuntimeContract = `${globalOpenCodeRuntimeContractOwnershipMarker}\n${generatedRuntimeContract}`;
|
|
257
|
+
const localValidator = `${globalOpenCodeValidatorOwnershipMarker}\nimport process from "node:process";\nimport { fileURLToPath } from "node:url";\nimport { runLocalValidator } from "../../node_modules/create-agdf/lib/runtime/local-validator.js";\n\nconst ownedPackageRoot = fileURLToPath(new URL("../../node_modules/create-agdf", import.meta.url));\nprocess.exitCode = runLocalValidator({ ownedPackageRoot, expectedVersion: ${JSON.stringify(pluginDefinition.version)}, surface: "opencode" }, process.argv.slice(2));\n`;
|
|
207
258
|
|
|
208
259
|
writeOwnedGlobalOpenCodeFile(paths.instructions, globalInstructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
|
|
209
260
|
writeOwnedGlobalOpenCodeFile(paths.runtimeContract, globalRuntimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
|
|
261
|
+
writeOwnedGlobalOpenCodeFile(paths.localValidator, localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
|
|
262
|
+
mkdirSync(dirname(paths.localValidatorPackage), { recursive: true });
|
|
263
|
+
writeFileSync(paths.localValidatorPackage, `${JSON.stringify({
|
|
264
|
+
name: "agdf-opencode-validator-runtime",
|
|
265
|
+
private: true,
|
|
266
|
+
type: "module",
|
|
267
|
+
agdf: {
|
|
268
|
+
owner: globalOpenCodeValidatorPackageOwner,
|
|
269
|
+
surface: "opencode-global-validator",
|
|
270
|
+
},
|
|
271
|
+
}, null, 2)}\n`, "utf8");
|
|
210
272
|
for (const moduleName of contractModules) {
|
|
211
273
|
const generatedModule = readFileSync(join(generatedOpenCodeRoot, "contracts", moduleName), "utf8");
|
|
212
274
|
writeOwnedGlobalOpenCodeFile(
|
|
@@ -223,7 +285,7 @@ export function installOpenCodeGlobalSurface(configDir) {
|
|
|
223
285
|
const sourcePath = join(generatedOpenCodeRoot, "skills", sourceName, "SKILL.md");
|
|
224
286
|
const sourceContent = readFileSync(sourcePath, "utf8");
|
|
225
287
|
const marker = `${globalOpenCodeSkillOwnershipMarker}${skillName} -->`;
|
|
226
|
-
const content = toGlobalOpenCodeContent(sourceContent).replace(/^(---[\s\S]*?\n---\n)/, `$1${marker}\n\n${
|
|
288
|
+
const content = toGlobalOpenCodeContent(sourceContent).replace(/^(---[\s\S]*?\n---\n)/, `$1${marker}\n\n${globalOpenCodeActivationGuard()}`);
|
|
227
289
|
writeOwnedGlobalOpenCodeFile(join(paths.skills, skillName, "SKILL.md"), content, marker, "after-frontmatter");
|
|
228
290
|
}
|
|
229
291
|
|
|
@@ -232,6 +294,8 @@ export function installOpenCodeGlobalSurface(configDir) {
|
|
|
232
294
|
runtimeContract: paths.runtimeContract,
|
|
233
295
|
contracts: paths.contracts,
|
|
234
296
|
skills: paths.skills,
|
|
297
|
+
localValidator: paths.localValidator,
|
|
298
|
+
localValidatorPackage: paths.localValidatorPackage,
|
|
235
299
|
};
|
|
236
300
|
}
|
|
237
301
|
|
|
@@ -309,6 +373,12 @@ function evaluateGlobalOpenCodeSurface(configDir) {
|
|
|
309
373
|
const paths = globalOpenCodeConfigPaths(configDir);
|
|
310
374
|
const skillCount = globalOpenCodeSkillNames.filter((skillName) => existsSync(join(paths.skills, skillName, "SKILL.md"))).length;
|
|
311
375
|
const contractCount = contractModules.filter((moduleName) => existsSync(join(paths.contracts, moduleName))).length;
|
|
376
|
+
const validatorResolution = resolveLocalValidator({
|
|
377
|
+
ownedPackageRoot: join(configDir, "node_modules", pluginDefinition.opencode.npmPackage),
|
|
378
|
+
runtimeRoot: join(configDir, "agdf"),
|
|
379
|
+
expectedVersion: pluginDefinition.version,
|
|
380
|
+
surface: "opencode",
|
|
381
|
+
});
|
|
312
382
|
return {
|
|
313
383
|
path: paths.skills,
|
|
314
384
|
instructions: paths.instructions,
|
|
@@ -318,11 +388,19 @@ function evaluateGlobalOpenCodeSurface(configDir) {
|
|
|
318
388
|
skill_count: skillCount,
|
|
319
389
|
expected_contract_count: contractModules.length,
|
|
320
390
|
contract_count: contractCount,
|
|
391
|
+
local_validator: {
|
|
392
|
+
path: paths.localValidator,
|
|
393
|
+
present: existsSync(paths.localValidator),
|
|
394
|
+
...validatorResolution.envelope,
|
|
395
|
+
},
|
|
321
396
|
present: existsSync(paths.skills),
|
|
322
397
|
complete: existsSync(paths.instructions)
|
|
323
398
|
&& existsSync(paths.runtimeContract)
|
|
324
399
|
&& contractCount === contractModules.length
|
|
325
|
-
&& skillCount === openCodeSkillNames.length
|
|
400
|
+
&& skillCount === openCodeSkillNames.length
|
|
401
|
+
&& existsSync(paths.localValidator)
|
|
402
|
+
&& globalOpenCodeValidatorPackageIsValid(paths.localValidatorPackage)
|
|
403
|
+
&& validatorResolution.envelope.machine_validation === "owned_version_matched",
|
|
326
404
|
};
|
|
327
405
|
}
|
|
328
406
|
|
|
@@ -1,41 +1,117 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { pluginDefinition } from "../cli/runtime-context.js";
|
|
3
|
+
import { classifyMarketplaceList, prepareLocalMarketplace } from "./local-marketplace.js";
|
|
3
4
|
|
|
4
|
-
export function installCodexGlobalPlugin({ exec = execFileSync,
|
|
5
|
+
export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepareLocalMarketplace, dataRoot } = {}) {
|
|
5
6
|
const expectedVersion = pluginDefinition.version;
|
|
6
7
|
const nativeOutput = [];
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const transaction = prepare({ expectedVersion, ...(dataRoot ? { dataRoot } : {}) });
|
|
9
|
+
const migration = { state: "unknown", source: "", addedLocal: false, removedLegacy: false };
|
|
10
|
+
try {
|
|
11
|
+
const marketplaceOutput = runPluginPhase(exec, "codex", ["plugin", "marketplace", "list", "--json"], "marketplace", captureOptions());
|
|
12
|
+
migrateMarketplace({ surface: "codex", exec, output: marketplaceOutput, root: transaction.root, nativeOutput, migration });
|
|
13
|
+
nativeOutput.push(runPluginPhase(exec, "codex", ["plugin", "add", "agdf", "--marketplace", "agdf"], "plugin_operation", captureOptions()));
|
|
14
|
+
const listOutput = runPluginPhase(exec, "codex", ["plugin", "list"], "verification", captureOptions());
|
|
15
|
+
const installedVersion = pluginVersionFromList(listOutput, "agdf@agdf");
|
|
16
|
+
if (installedVersion !== expectedVersion) {
|
|
17
|
+
throw lifecycleAdapterError("version", versionMismatchMessage("Codex", "agdf@agdf", expectedVersion, installedVersion, "npx --yes @agdf/cli@latest codex"));
|
|
18
|
+
}
|
|
19
|
+
transaction.commit();
|
|
20
|
+
return {
|
|
21
|
+
surface: "codex", operation: migration.state === "owned_local_current" ? "update" : "install", expectedVersion, installedVersion, verificationStatus: "healthy",
|
|
22
|
+
evidence: ["durable_local_marketplace", `marketplace:${migration.state}`, "codex plugin list"],
|
|
23
|
+
nativeOutput: nativeOutput.filter(Boolean).map(String),
|
|
24
|
+
};
|
|
25
|
+
} catch (error) {
|
|
26
|
+
recoverMarketplace({ surface: "codex", exec, migration, transaction, error });
|
|
27
|
+
throw error;
|
|
14
28
|
}
|
|
15
|
-
return { surface: "codex", expectedVersion, installedVersion, verificationStatus: "healthy", evidence: ["codex plugin list"], nativeOutput: nativeOutput.filter(Boolean).map(String) };
|
|
16
29
|
}
|
|
17
30
|
|
|
18
|
-
export function installClaudeGlobalPlugin({ exec = execFileSync,
|
|
31
|
+
export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepareLocalMarketplace, dataRoot } = {}) {
|
|
19
32
|
const expectedVersion = pluginDefinition.version;
|
|
20
33
|
const nativeOutput = [];
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
const transaction = prepare({ expectedVersion, ...(dataRoot ? { dataRoot } : {}) });
|
|
35
|
+
const migration = { state: "unknown", source: "", addedLocal: false, removedLegacy: false };
|
|
36
|
+
try {
|
|
37
|
+
const marketplaceOutput = runPluginPhase(exec, "claude", ["plugin", "marketplace", "list", "--json"], "marketplace", captureOptions());
|
|
38
|
+
migrateMarketplace({ surface: "claude", exec, output: marketplaceOutput, root: transaction.root, nativeOutput, migration });
|
|
39
|
+
nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "marketplace", "update", "agdf"], "marketplace", captureOptions()));
|
|
40
|
+
const beforeList = runPluginPhase(exec, "claude", ["plugin", "list"], "verification", captureOptions());
|
|
41
|
+
const alreadyInstalled = pluginListHasPlugin(beforeList, "agdf@agdf");
|
|
42
|
+
nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", alreadyInstalled ? "update" : "install", "agdf@agdf"], "plugin_operation", captureOptions()));
|
|
43
|
+
const afterList = runPluginPhase(exec, "claude", ["plugin", "list"], "verification", captureOptions());
|
|
44
|
+
const installedVersion = pluginVersionFromList(afterList, "agdf@agdf");
|
|
45
|
+
if (installedVersion && installedVersion !== expectedVersion) {
|
|
46
|
+
throw lifecycleAdapterError("version", versionMismatchMessage("Claude Code", "agdf@agdf", expectedVersion, installedVersion, "npx --yes @agdf/cli@latest claude"));
|
|
47
|
+
}
|
|
48
|
+
transaction.commit();
|
|
49
|
+
return {
|
|
50
|
+
surface: "claude",
|
|
51
|
+
operation: migration.state === "owned_local_current" ? "update" : "install",
|
|
52
|
+
expectedVersion,
|
|
53
|
+
installedVersion,
|
|
54
|
+
verificationStatus: installedVersion ? "healthy" : "degraded",
|
|
55
|
+
evidence: ["durable_local_marketplace", `marketplace:${migration.state}`, "claude plugin list", ...(installedVersion ? [] : ["host_did_not_expose_version"])],
|
|
56
|
+
nativeOutput: nativeOutput.filter(Boolean).map(String),
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
recoverMarketplace({ surface: "claude", exec, migration, transaction, error });
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function migrateMarketplace({ surface, exec, output, root, nativeOutput, migration }) {
|
|
65
|
+
const classification = classifyMarketplaceList(surface, output, root);
|
|
66
|
+
Object.assign(migration, classification);
|
|
67
|
+
if (["conflict", "unknown"].includes(classification.state)) {
|
|
68
|
+
throw lifecycleAdapterError("marketplace", `Refusing to replace non-AGDF marketplace registration agdf (${classification.reason || classification.state}).`, classification);
|
|
30
69
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
70
|
+
const executable = surface === "claude" ? "claude" : "codex";
|
|
71
|
+
if (classification.state === "legacy_github") {
|
|
72
|
+
const args = surface === "claude"
|
|
73
|
+
? ["plugin", "marketplace", "remove", "agdf", "--scope", "user"]
|
|
74
|
+
: ["plugin", "marketplace", "remove", "agdf", "--json"];
|
|
75
|
+
nativeOutput.push(runPluginPhase(exec, executable, args, "marketplace", captureOptions()));
|
|
76
|
+
migration.removedLegacy = true;
|
|
77
|
+
}
|
|
78
|
+
if (["absent", "legacy_github"].includes(classification.state)) {
|
|
79
|
+
const args = surface === "claude"
|
|
80
|
+
? ["plugin", "marketplace", "add", root, "--scope", "user"]
|
|
81
|
+
: ["plugin", "marketplace", "add", root, "--json"];
|
|
82
|
+
nativeOutput.push(runPluginPhase(exec, executable, args, "marketplace", captureOptions()));
|
|
83
|
+
migration.addedLocal = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function recoverMarketplace({ surface, exec, migration, transaction, error }) {
|
|
88
|
+
const executable = surface === "claude" ? "claude" : "codex";
|
|
89
|
+
const recovery = [];
|
|
90
|
+
const attempt = (args) => {
|
|
91
|
+
try {
|
|
92
|
+
exec(executable, args, captureOptions());
|
|
93
|
+
recovery.push({ args, status: "restored" });
|
|
94
|
+
} catch (rollbackError) {
|
|
95
|
+
recovery.push({ args, status: "failed", message: commandErrorText(rollbackError) });
|
|
96
|
+
}
|
|
38
97
|
};
|
|
98
|
+
if (migration?.addedLocal) {
|
|
99
|
+
attempt(surface === "claude"
|
|
100
|
+
? ["plugin", "marketplace", "remove", "agdf", "--scope", "user"]
|
|
101
|
+
: ["plugin", "marketplace", "remove", "agdf", "--json"]);
|
|
102
|
+
}
|
|
103
|
+
if (migration?.removedLegacy && migration.source) {
|
|
104
|
+
attempt(surface === "claude"
|
|
105
|
+
? ["plugin", "marketplace", "add", migration.source, "--scope", "user"]
|
|
106
|
+
: ["plugin", "marketplace", "add", migration.source, "--json"]);
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
transaction.rollback();
|
|
110
|
+
recovery.push({ filesystem: transaction.root, status: "restored" });
|
|
111
|
+
} catch (rollbackError) {
|
|
112
|
+
recovery.push({ filesystem: transaction.root, status: "failed", message: rollbackError.message });
|
|
113
|
+
}
|
|
114
|
+
error.evidence = { ...(error.evidence ?? {}), rollback: recovery };
|
|
39
115
|
}
|
|
40
116
|
|
|
41
117
|
function captureOptions() {
|