create-agdf 0.10.1 → 0.10.2
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 +28 -91
- package/generated/.github/skills/agdf-interaction-locales.json +2 -0
- package/generated/.github/skills/contracts/gate-transition.md +4 -2
- package/generated/.github/skills/contracts/modes.md +12 -1
- package/generated/.opencode/agdf-interaction-locales.json +2 -0
- package/generated/.opencode/contracts/gate-transition.md +4 -2
- 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 +28 -91
- 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 +2 -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/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 +418 -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 +45 -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 +459 -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 +590 -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 +1040 -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 +28 -91
- package/lib/cli/application.js +16 -54
- package/lib/cli/command-registry.js +9 -11
- package/lib/cli/validation-handlers.js +45 -0
- package/lib/control-evaluation/doctor.js +1 -1
- package/lib/control-evaluation/gate-check.js +7 -7
- package/lib/control-evaluation/required-files.js +9 -0
- package/lib/installers/local-marketplace.js +322 -0
- package/lib/installers/opencode.js +32 -2
- package/lib/installers/plugin-installers.js +102 -26
- 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,7 @@ 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";
|
|
15
17
|
const testNpmCliPath = process.env.NODE_ENV === "test" ? process.env.AGDF_TEST_NPM_CLI_PATH || "" : "";
|
|
16
18
|
const npmCommand = testNpmCliPath ? process.execPath : process.platform === "win32" ? process.execPath : "npm";
|
|
17
19
|
const npmPrefixArgs = testNpmCliPath ? [testNpmCliPath] : process.platform === "win32" ? [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")] : [];
|
|
@@ -119,6 +121,7 @@ function globalOpenCodeConfigPaths(configDir) {
|
|
|
119
121
|
runtimeContract: join(configDir, pluginDefinition.opencode.runtimeContractFileName),
|
|
120
122
|
contracts: join(configDir, "contracts"),
|
|
121
123
|
skills: join(configDir, "skills"),
|
|
124
|
+
localValidator: join(configDir, "agdf", "bin", "agdf-local.js"),
|
|
122
125
|
};
|
|
123
126
|
}
|
|
124
127
|
|
|
@@ -141,6 +144,7 @@ function assertGlobalOpenCodeSurfaceWritable(configDir) {
|
|
|
141
144
|
const paths = globalOpenCodeConfigPaths(configDir);
|
|
142
145
|
assertGlobalOpenCodeFileWritable(paths.instructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
|
|
143
146
|
assertGlobalOpenCodeFileWritable(paths.runtimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
|
|
147
|
+
assertGlobalOpenCodeFileWritable(paths.localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
|
|
144
148
|
for (const moduleName of contractModules) {
|
|
145
149
|
assertGlobalOpenCodeFileWritable(
|
|
146
150
|
join(paths.contracts, moduleName),
|
|
@@ -177,6 +181,16 @@ function globalOpenCodeBoundary() {
|
|
|
177
181
|
].join("\n");
|
|
178
182
|
}
|
|
179
183
|
|
|
184
|
+
function globalOpenCodeActivationGuard() {
|
|
185
|
+
return [
|
|
186
|
+
"## Repository Activation Guard",
|
|
187
|
+
"",
|
|
188
|
+
"Apply AGDF governance only when this repository has valid `.agdf/control/config.json` durable control.",
|
|
189
|
+
"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.",
|
|
190
|
+
"",
|
|
191
|
+
].join("\n");
|
|
192
|
+
}
|
|
193
|
+
|
|
180
194
|
function toGlobalOpenCodeContent(content) {
|
|
181
195
|
let next = content;
|
|
182
196
|
for (const skill of pluginDefinition.skillSet) {
|
|
@@ -204,9 +218,11 @@ export function installOpenCodeGlobalSurface(configDir) {
|
|
|
204
218
|
toGlobalOpenCodeContent(generatedInstructions),
|
|
205
219
|
].join("\n");
|
|
206
220
|
const globalRuntimeContract = `${globalOpenCodeRuntimeContractOwnershipMarker}\n${generatedRuntimeContract}`;
|
|
221
|
+
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
222
|
|
|
208
223
|
writeOwnedGlobalOpenCodeFile(paths.instructions, globalInstructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
|
|
209
224
|
writeOwnedGlobalOpenCodeFile(paths.runtimeContract, globalRuntimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
|
|
225
|
+
writeOwnedGlobalOpenCodeFile(paths.localValidator, localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
|
|
210
226
|
for (const moduleName of contractModules) {
|
|
211
227
|
const generatedModule = readFileSync(join(generatedOpenCodeRoot, "contracts", moduleName), "utf8");
|
|
212
228
|
writeOwnedGlobalOpenCodeFile(
|
|
@@ -223,7 +239,7 @@ export function installOpenCodeGlobalSurface(configDir) {
|
|
|
223
239
|
const sourcePath = join(generatedOpenCodeRoot, "skills", sourceName, "SKILL.md");
|
|
224
240
|
const sourceContent = readFileSync(sourcePath, "utf8");
|
|
225
241
|
const marker = `${globalOpenCodeSkillOwnershipMarker}${skillName} -->`;
|
|
226
|
-
const content = toGlobalOpenCodeContent(sourceContent).replace(/^(---[\s\S]*?\n---\n)/, `$1${marker}\n\n${
|
|
242
|
+
const content = toGlobalOpenCodeContent(sourceContent).replace(/^(---[\s\S]*?\n---\n)/, `$1${marker}\n\n${globalOpenCodeActivationGuard()}`);
|
|
227
243
|
writeOwnedGlobalOpenCodeFile(join(paths.skills, skillName, "SKILL.md"), content, marker, "after-frontmatter");
|
|
228
244
|
}
|
|
229
245
|
|
|
@@ -232,6 +248,7 @@ export function installOpenCodeGlobalSurface(configDir) {
|
|
|
232
248
|
runtimeContract: paths.runtimeContract,
|
|
233
249
|
contracts: paths.contracts,
|
|
234
250
|
skills: paths.skills,
|
|
251
|
+
localValidator: paths.localValidator,
|
|
235
252
|
};
|
|
236
253
|
}
|
|
237
254
|
|
|
@@ -309,6 +326,12 @@ function evaluateGlobalOpenCodeSurface(configDir) {
|
|
|
309
326
|
const paths = globalOpenCodeConfigPaths(configDir);
|
|
310
327
|
const skillCount = globalOpenCodeSkillNames.filter((skillName) => existsSync(join(paths.skills, skillName, "SKILL.md"))).length;
|
|
311
328
|
const contractCount = contractModules.filter((moduleName) => existsSync(join(paths.contracts, moduleName))).length;
|
|
329
|
+
const validatorResolution = resolveLocalValidator({
|
|
330
|
+
ownedPackageRoot: join(configDir, "node_modules", pluginDefinition.opencode.npmPackage),
|
|
331
|
+
runtimeRoot: join(configDir, "agdf"),
|
|
332
|
+
expectedVersion: pluginDefinition.version,
|
|
333
|
+
surface: "opencode",
|
|
334
|
+
});
|
|
312
335
|
return {
|
|
313
336
|
path: paths.skills,
|
|
314
337
|
instructions: paths.instructions,
|
|
@@ -318,11 +341,18 @@ function evaluateGlobalOpenCodeSurface(configDir) {
|
|
|
318
341
|
skill_count: skillCount,
|
|
319
342
|
expected_contract_count: contractModules.length,
|
|
320
343
|
contract_count: contractCount,
|
|
344
|
+
local_validator: {
|
|
345
|
+
path: paths.localValidator,
|
|
346
|
+
present: existsSync(paths.localValidator),
|
|
347
|
+
...validatorResolution.envelope,
|
|
348
|
+
},
|
|
321
349
|
present: existsSync(paths.skills),
|
|
322
350
|
complete: existsSync(paths.instructions)
|
|
323
351
|
&& existsSync(paths.runtimeContract)
|
|
324
352
|
&& contractCount === contractModules.length
|
|
325
|
-
&& skillCount === openCodeSkillNames.length
|
|
353
|
+
&& skillCount === openCodeSkillNames.length
|
|
354
|
+
&& existsSync(paths.localValidator)
|
|
355
|
+
&& validatorResolution.envelope.machine_validation === "owned_version_matched",
|
|
326
356
|
};
|
|
327
357
|
}
|
|
328
358
|
|
|
@@ -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() {
|