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.
Files changed (87) hide show
  1. package/README.md +21 -13
  2. package/bin/agdf-validator.js +11 -0
  3. package/generated/.agdf/control/README.md +3 -3
  4. package/generated/.github/skills/agdf-brownfield-analysis/SKILL.md +1 -1
  5. package/generated/.github/skills/agdf-delivery-path-search/SKILL.md +7 -6
  6. package/generated/.github/skills/agdf-gate-check/SKILL.md +28 -91
  7. package/generated/.github/skills/agdf-interaction-locales.json +2 -0
  8. package/generated/.github/skills/contracts/gate-transition.md +4 -2
  9. package/generated/.github/skills/contracts/modes.md +12 -1
  10. package/generated/.opencode/agdf-interaction-locales.json +2 -0
  11. package/generated/.opencode/contracts/gate-transition.md +4 -2
  12. package/generated/.opencode/contracts/modes.md +12 -1
  13. package/generated/.opencode/skills/agdf-brownfield-analysis/SKILL.md +1 -1
  14. package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +7 -6
  15. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +28 -91
  16. package/generated/plugins/agdf/.claude-plugin/plugin.json +19 -0
  17. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  18. package/generated/plugins/agdf/control/README.md +3 -3
  19. package/generated/plugins/agdf/hooks/session-start.sh +1 -1
  20. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +2 -0
  21. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  22. package/generated/plugins/agdf/meta/contracts/gate-transition.md +4 -2
  23. package/generated/plugins/agdf/meta/contracts/modes.md +12 -1
  24. package/generated/plugins/agdf/runtime/agdf-local.js +11 -0
  25. package/generated/plugins/agdf/runtime/create-agdf/NOTICE +10 -0
  26. package/generated/plugins/agdf/runtime/create-agdf/bin/agdf-validator.js +11 -0
  27. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +418 -0
  28. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +220 -0
  29. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +136 -0
  30. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +75 -0
  31. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +156 -0
  32. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/runtime-context.js +83 -0
  33. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/validation-handlers.js +45 -0
  34. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/delivery-map.js +291 -0
  35. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +265 -0
  36. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +459 -0
  37. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +283 -0
  38. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/required-files.js +9 -0
  39. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +217 -0
  40. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +344 -0
  41. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/verified-change.js +156 -0
  42. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/aggregate.js +18 -0
  43. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/gate-approval-validator.js +49 -0
  44. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/index.js +7 -0
  45. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/legacy-migration.js +95 -0
  46. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +227 -0
  47. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-repository.js +68 -0
  48. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-resolver.js +45 -0
  49. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-writer.js +97 -0
  50. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/candidate-policy.js +99 -0
  51. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +237 -0
  52. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +44 -0
  53. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +53 -0
  54. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/protocol.js +13 -0
  55. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/claude.js +40 -0
  56. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/codex.js +38 -0
  57. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/protocol.js +12 -0
  58. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +39 -0
  59. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/scoring.js +21 -0
  60. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +154 -0
  61. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +161 -0
  62. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +14 -0
  63. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/transports/read-only-guard.js +43 -0
  64. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +590 -0
  65. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/local-validator.js +142 -0
  66. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/validator-application.js +40 -0
  67. package/generated/plugins/agdf/runtime/create-agdf/package.json +66 -0
  68. package/generated/plugins/agdf/runtime/runtime-manifest.json +8 -0
  69. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +1040 -0
  70. package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +1 -1
  71. package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +7 -6
  72. package/generated/plugins/agdf/skills/gate-check/SKILL.md +28 -91
  73. package/lib/cli/application.js +16 -54
  74. package/lib/cli/command-registry.js +9 -11
  75. package/lib/cli/validation-handlers.js +45 -0
  76. package/lib/control-evaluation/doctor.js +1 -1
  77. package/lib/control-evaluation/gate-check.js +7 -7
  78. package/lib/control-evaluation/required-files.js +9 -0
  79. package/lib/installers/local-marketplace.js +322 -0
  80. package/lib/installers/opencode.js +32 -2
  81. package/lib/installers/plugin-installers.js +102 -26
  82. package/lib/runtime/local-validator.js +142 -0
  83. package/lib/runtime/validator-application.js +40 -0
  84. package/lib/scaffold/plan.js +1 -7
  85. package/lib/scaffold/presentation.js +2 -2
  86. package/opencode-plugin.js +1 -1
  87. 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${globalOpenCodeBoundary()}`);
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, io = console } = {}) {
5
+ export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepareLocalMarketplace, dataRoot } = {}) {
5
6
  const expectedVersion = pluginDefinition.version;
6
7
  const nativeOutput = [];
7
- nativeOutput.push(runPluginPhase(exec, "codex", ["plugin", "marketplace", "add", "arndtgold/ai-native-governance-delivery-framework"], "marketplace", captureOptions()));
8
- nativeOutput.push(runPluginPhase(exec, "codex", ["plugin", "marketplace", "upgrade", "agdf"], "marketplace", captureOptions()));
9
- nativeOutput.push(runPluginPhase(exec, "codex", ["plugin", "add", "agdf", "--marketplace", "agdf"], "plugin_operation", captureOptions()));
10
- const listOutput = runPluginPhase(exec, "codex", ["plugin", "list"], "verification", { encoding: "utf8", stdio: "pipe" });
11
- const installedVersion = pluginVersionFromList(listOutput, "agdf@agdf");
12
- if (installedVersion !== expectedVersion) {
13
- throw lifecycleAdapterError("version", versionMismatchMessage("Codex", "agdf@agdf", expectedVersion, installedVersion, "codex plugin marketplace upgrade agdf && codex plugin add agdf --marketplace agdf"));
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, io = console } = {}) {
31
+ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepareLocalMarketplace, dataRoot } = {}) {
19
32
  const expectedVersion = pluginDefinition.version;
20
33
  const nativeOutput = [];
21
- nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "marketplace", "add", "arndtgold/ai-native-governance-delivery-framework"], "marketplace", captureOptions()));
22
- nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "marketplace", "update", "agdf"], "marketplace", captureOptions()));
23
- const beforeList = runPluginPhase(exec, "claude", ["plugin", "list"], "verification", { encoding: "utf8", stdio: "pipe" });
24
- const alreadyInstalled = pluginListHasPlugin(beforeList, "agdf@agdf");
25
- nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", alreadyInstalled ? "update" : "install", "agdf@agdf"], "plugin_operation", captureOptions()));
26
- const afterList = runPluginPhase(exec, "claude", ["plugin", "list"], "verification", { encoding: "utf8", stdio: "pipe" });
27
- const installedVersion = pluginVersionFromList(afterList, "agdf@agdf");
28
- if (installedVersion && installedVersion !== expectedVersion) {
29
- throw lifecycleAdapterError("version", versionMismatchMessage("Claude Code", "agdf@agdf", expectedVersion, installedVersion, "claude plugin marketplace update agdf && claude plugin update agdf@agdf"));
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
- return {
32
- surface: "claude",
33
- expectedVersion,
34
- installedVersion,
35
- verificationStatus: installedVersion ? "healthy" : "degraded",
36
- evidence: ["claude plugin list", ...(installedVersion ? [] : ["host_did_not_expose_version"])],
37
- nativeOutput: nativeOutput.filter(Boolean).map(String),
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() {