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.
Files changed (94) 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 +35 -105
  7. package/generated/.github/skills/agdf-interaction-locales.json +10 -0
  8. package/generated/.github/skills/agdf-release-or/SKILL.md +1 -1
  9. package/generated/.github/skills/contracts/gate-transition.md +4 -2
  10. package/generated/.github/skills/contracts/interaction.md +23 -0
  11. package/generated/.github/skills/contracts/modes.md +12 -1
  12. package/generated/.opencode/agdf-interaction-locales.json +10 -0
  13. package/generated/.opencode/contracts/gate-transition.md +4 -2
  14. package/generated/.opencode/contracts/interaction.md +23 -0
  15. package/generated/.opencode/contracts/modes.md +12 -1
  16. package/generated/.opencode/skills/agdf-brownfield-analysis/SKILL.md +1 -1
  17. package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +7 -6
  18. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +35 -105
  19. package/generated/.opencode/skills/agdf-release-or/SKILL.md +1 -1
  20. package/generated/plugins/agdf/.claude-plugin/plugin.json +19 -0
  21. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  22. package/generated/plugins/agdf/control/README.md +3 -3
  23. package/generated/plugins/agdf/hooks/session-start.sh +1 -1
  24. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +10 -0
  25. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  26. package/generated/plugins/agdf/meta/contracts/gate-transition.md +4 -2
  27. package/generated/plugins/agdf/meta/contracts/interaction.md +23 -0
  28. package/generated/plugins/agdf/meta/contracts/modes.md +12 -1
  29. package/generated/plugins/agdf/runtime/agdf-local.js +11 -0
  30. package/generated/plugins/agdf/runtime/create-agdf/NOTICE +10 -0
  31. package/generated/plugins/agdf/runtime/create-agdf/bin/agdf-validator.js +11 -0
  32. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +426 -0
  33. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +220 -0
  34. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +136 -0
  35. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +75 -0
  36. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +156 -0
  37. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/runtime-context.js +83 -0
  38. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/validation-handlers.js +46 -0
  39. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/delivery-map.js +291 -0
  40. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +265 -0
  41. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +450 -0
  42. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +283 -0
  43. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/required-files.js +9 -0
  44. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +217 -0
  45. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +344 -0
  46. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/verified-change.js +156 -0
  47. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/aggregate.js +18 -0
  48. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/gate-approval-validator.js +49 -0
  49. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/index.js +7 -0
  50. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/legacy-migration.js +95 -0
  51. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +227 -0
  52. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-repository.js +68 -0
  53. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-resolver.js +45 -0
  54. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-writer.js +97 -0
  55. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/candidate-policy.js +99 -0
  56. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +237 -0
  57. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +44 -0
  58. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +53 -0
  59. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/protocol.js +13 -0
  60. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/claude.js +40 -0
  61. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/codex.js +38 -0
  62. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/protocol.js +12 -0
  63. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +39 -0
  64. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/scoring.js +21 -0
  65. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +154 -0
  66. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +161 -0
  67. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +14 -0
  68. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/transports/read-only-guard.js +43 -0
  69. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +670 -0
  70. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/local-validator.js +142 -0
  71. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/validator-application.js +40 -0
  72. package/generated/plugins/agdf/runtime/create-agdf/package.json +66 -0
  73. package/generated/plugins/agdf/runtime/runtime-manifest.json +8 -0
  74. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +1055 -0
  75. package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +1 -1
  76. package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +7 -6
  77. package/generated/plugins/agdf/skills/gate-check/SKILL.md +35 -105
  78. package/generated/plugins/agdf/skills/release-or/SKILL.md +1 -1
  79. package/lib/cli/application.js +16 -54
  80. package/lib/cli/command-registry.js +9 -11
  81. package/lib/cli/validation-handlers.js +46 -0
  82. package/lib/control-evaluation/doctor.js +1 -1
  83. package/lib/control-evaluation/gate-check.js +23 -32
  84. package/lib/control-evaluation/required-files.js +9 -0
  85. package/lib/installers/local-marketplace.js +322 -0
  86. package/lib/installers/opencode.js +84 -6
  87. package/lib/installers/plugin-installers.js +102 -26
  88. package/lib/interaction-presentation.js +80 -0
  89. package/lib/runtime/local-validator.js +142 -0
  90. package/lib/runtime/validator-application.js +40 -0
  91. package/lib/scaffold/plan.js +1 -7
  92. package/lib/scaffold/presentation.js +2 -2
  93. package/opencode-plugin.js +1 -1
  94. 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 = { question: "allow", skill: { "agdf-*": "allow" } };
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
- if (nextConfig.permission.question === undefined) nextConfig.permission.question = "allow";
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 = { "agdf-*": "allow" };
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
- if (nextConfig.permission.skill["agdf-*"] === undefined) nextConfig.permission.skill["agdf-*"] = "allow";
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${globalOpenCodeBoundary()}`);
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, 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() {