create-agdf 0.10.0 → 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 (89) hide show
  1. package/README.md +30 -22
  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/README.md +6 -8
  11. package/generated/.opencode/agdf-interaction-locales.json +2 -0
  12. package/generated/.opencode/contracts/gate-transition.md +4 -2
  13. package/generated/.opencode/contracts/modes.md +12 -1
  14. package/generated/.opencode/skills/agdf-brownfield-analysis/SKILL.md +1 -1
  15. package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +7 -6
  16. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +28 -91
  17. package/generated/plugins/agdf/.claude-plugin/plugin.json +19 -0
  18. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  19. package/generated/plugins/agdf/control/README.md +3 -3
  20. package/generated/plugins/agdf/hooks/session-start.sh +1 -1
  21. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +2 -0
  22. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  23. package/generated/plugins/agdf/meta/contracts/gate-transition.md +4 -2
  24. package/generated/plugins/agdf/meta/contracts/modes.md +12 -1
  25. package/generated/plugins/agdf/runtime/agdf-local.js +11 -0
  26. package/generated/plugins/agdf/runtime/create-agdf/NOTICE +10 -0
  27. package/generated/plugins/agdf/runtime/create-agdf/bin/agdf-validator.js +11 -0
  28. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +418 -0
  29. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +220 -0
  30. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +136 -0
  31. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +75 -0
  32. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +156 -0
  33. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/runtime-context.js +83 -0
  34. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/validation-handlers.js +45 -0
  35. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/delivery-map.js +291 -0
  36. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +265 -0
  37. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +459 -0
  38. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +283 -0
  39. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/required-files.js +9 -0
  40. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +217 -0
  41. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +344 -0
  42. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/verified-change.js +156 -0
  43. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/aggregate.js +18 -0
  44. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/gate-approval-validator.js +49 -0
  45. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/index.js +7 -0
  46. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/legacy-migration.js +95 -0
  47. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +227 -0
  48. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-repository.js +68 -0
  49. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-resolver.js +45 -0
  50. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-writer.js +97 -0
  51. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/candidate-policy.js +99 -0
  52. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +237 -0
  53. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +44 -0
  54. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +53 -0
  55. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/protocol.js +13 -0
  56. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/claude.js +40 -0
  57. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/codex.js +38 -0
  58. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/protocol.js +12 -0
  59. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +39 -0
  60. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/scoring.js +21 -0
  61. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +154 -0
  62. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +161 -0
  63. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +14 -0
  64. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/transports/read-only-guard.js +43 -0
  65. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +590 -0
  66. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/local-validator.js +142 -0
  67. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/validator-application.js +40 -0
  68. package/generated/plugins/agdf/runtime/create-agdf/package.json +66 -0
  69. package/generated/plugins/agdf/runtime/runtime-manifest.json +8 -0
  70. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +1040 -0
  71. package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +1 -1
  72. package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +7 -6
  73. package/generated/plugins/agdf/skills/gate-check/SKILL.md +28 -91
  74. package/lib/cli/application.js +21 -59
  75. package/lib/cli/command-registry.js +9 -11
  76. package/lib/cli/validation-handlers.js +45 -0
  77. package/lib/control-evaluation/doctor.js +1 -1
  78. package/lib/control-evaluation/gate-check.js +7 -7
  79. package/lib/control-evaluation/required-files.js +9 -0
  80. package/lib/installers/local-marketplace.js +322 -0
  81. package/lib/installers/opencode-activation.js +43 -0
  82. package/lib/installers/opencode.js +60 -18
  83. package/lib/installers/plugin-installers.js +102 -26
  84. package/lib/runtime/local-validator.js +142 -0
  85. package/lib/runtime/validator-application.js +40 -0
  86. package/lib/scaffold/plan.js +1 -30
  87. package/lib/scaffold/presentation.js +9 -14
  88. package/opencode-plugin.js +32 -24
  89. 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
+ });
@@ -0,0 +1,43 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { pluginDefinition } from "../cli/runtime-context.js";
4
+
5
+ const requiredConfigFields = ["artifact_language", "chat_language", "runtime_language"];
6
+
7
+ export function isOpenCodeLegacySurfacePresent(targetDir) {
8
+ return existsSync(join(targetDir, ".opencode", pluginDefinition.opencode.instructionsFileName))
9
+ && existsSync(join(targetDir, ".opencode", "skills", `${pluginDefinition.opencode.skillPrefix}gate-check`, "SKILL.md"));
10
+ }
11
+
12
+ export function evaluateOpenCodeRepositoryActivation(targetDir) {
13
+ const controlDir = join(targetDir, ".agdf", "control");
14
+ const configPath = join(controlDir, "config.json");
15
+ const legacySurface = isOpenCodeLegacySurfacePresent(targetDir);
16
+ const base = {
17
+ control_dir: controlDir,
18
+ config_path: configPath,
19
+ legacy_surface: legacySurface,
20
+ };
21
+
22
+ if (!existsSync(configPath)) {
23
+ return { ...base, state: "inactive", active: false, diagnostic: "missing_control_config" };
24
+ }
25
+
26
+ try {
27
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
28
+ const valid = config
29
+ && typeof config === "object"
30
+ && !Array.isArray(config)
31
+ && requiredConfigFields.every((field) => typeof config[field] === "string" && config[field].trim());
32
+ if (!valid) return { ...base, state: "invalid_control", active: false, diagnostic: "invalid_control_config" };
33
+ } catch (error) {
34
+ return { ...base, state: "invalid_control", active: false, diagnostic: "invalid_control_json", error: error.message };
35
+ }
36
+
37
+ return {
38
+ ...base,
39
+ state: legacySurface ? "legacy_compatible" : "active",
40
+ active: true,
41
+ diagnostic: "valid_control_config",
42
+ };
43
+ }
@@ -4,6 +4,8 @@ import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import process from "node:process";
6
6
  import { generatedRoot, pluginDefinition } from "../cli/runtime-context.js";
7
+ import { evaluateOpenCodeRepositoryActivation } from "./opencode-activation.js";
8
+ import { resolveLocalValidator } from "../runtime/local-validator.js";
7
9
 
8
10
  const contractModules = ["gate-transition.md", "interaction.md", "modes.md", "quality.md", "context-graph.md", "control-scaffold.md", "closeout.md"];
9
11
  const openCodeSkillNames = pluginDefinition.skillSet.map((skill) => pluginDefinition.opencode.skillPrefix + skill.slug);
@@ -11,6 +13,7 @@ const globalOpenCodeSkillNames = pluginDefinition.skillSet.map((skill) => plugin
11
13
  const globalOpenCodeSkillOwnershipMarker = "<!-- AGDF-GLOBAL-SKILL: ";
12
14
  const globalOpenCodeInstructionsOwnershipMarker = "<!-- AGDF-GLOBAL-INSTRUCTIONS -->";
13
15
  const globalOpenCodeRuntimeContractOwnershipMarker = "<!-- AGDF-GLOBAL-RUNTIME-CONTRACT -->";
16
+ const globalOpenCodeValidatorOwnershipMarker = "// AGDF-GLOBAL-LOCAL-VALIDATOR";
14
17
  const testNpmCliPath = process.env.NODE_ENV === "test" ? process.env.AGDF_TEST_NPM_CLI_PATH || "" : "";
15
18
  const npmCommand = testNpmCliPath ? process.execPath : process.platform === "win32" ? process.execPath : "npm";
16
19
  const npmPrefixArgs = testNpmCliPath ? [testNpmCliPath] : process.platform === "win32" ? [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")] : [];
@@ -118,6 +121,7 @@ function globalOpenCodeConfigPaths(configDir) {
118
121
  runtimeContract: join(configDir, pluginDefinition.opencode.runtimeContractFileName),
119
122
  contracts: join(configDir, "contracts"),
120
123
  skills: join(configDir, "skills"),
124
+ localValidator: join(configDir, "agdf", "bin", "agdf-local.js"),
121
125
  };
122
126
  }
123
127
 
@@ -140,6 +144,7 @@ function assertGlobalOpenCodeSurfaceWritable(configDir) {
140
144
  const paths = globalOpenCodeConfigPaths(configDir);
141
145
  assertGlobalOpenCodeFileWritable(paths.instructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
142
146
  assertGlobalOpenCodeFileWritable(paths.runtimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
147
+ assertGlobalOpenCodeFileWritable(paths.localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
143
148
  for (const moduleName of contractModules) {
144
149
  assertGlobalOpenCodeFileWritable(
145
150
  join(paths.contracts, moduleName),
@@ -169,9 +174,19 @@ function globalOpenCodeBoundary() {
169
174
  "## Global OpenCode Surface Boundary",
170
175
  "",
171
176
  "This skill is globally discoverable, but global plugin or skill presence is not repository governance activation.",
172
- "Before applying AGDF gates, later artefacts or implementation guidance, inspect the current repository for `.opencode/AGDF.md`, `.opencode/skills/agdf-gate-check/SKILL.md` and `.agdf/control/`.",
173
- "If the repository surface is absent, stop and direct the user to `npx --yes @agdf/cli@latest opencode-repo` in this repository.",
174
- "When the repository surface exists, its local instructions, native skills and `.agdf/control/` state are authoritative.",
177
+ "Before applying AGDF gates, later artefacts or implementation guidance, inspect the current repository for valid `.agdf/control/config.json` durable control.",
178
+ "If durable control is missing or invalid, stop and direct the user to `npx --yes @agdf/cli@latest opencode-repo` in this repository.",
179
+ "When durable control is valid, use the global `agdf-global-*` skill surface; existing local OpenCode assets remain a compatibility path.",
180
+ "",
181
+ ].join("\n");
182
+ }
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.",
175
190
  "",
176
191
  ].join("\n");
177
192
  }
@@ -198,14 +213,16 @@ export function installOpenCodeGlobalSurface(configDir) {
198
213
  "AGDF native skills are globally discoverable through OpenCode.",
199
214
  "",
200
215
  globalOpenCodeBoundary(),
201
- "The repository-local `.opencode/AGDF.md`, `.opencode/skills/` and `.agdf/control/` files remain the source of truth for active governance.",
216
+ "Valid `.agdf/control/` is the repository source of truth for active governance; legacy `.opencode/` assets remain supported without being required.",
202
217
  "",
203
218
  toGlobalOpenCodeContent(generatedInstructions),
204
219
  ].join("\n");
205
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`;
206
222
 
207
223
  writeOwnedGlobalOpenCodeFile(paths.instructions, globalInstructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
208
224
  writeOwnedGlobalOpenCodeFile(paths.runtimeContract, globalRuntimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
225
+ writeOwnedGlobalOpenCodeFile(paths.localValidator, localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
209
226
  for (const moduleName of contractModules) {
210
227
  const generatedModule = readFileSync(join(generatedOpenCodeRoot, "contracts", moduleName), "utf8");
211
228
  writeOwnedGlobalOpenCodeFile(
@@ -222,7 +239,7 @@ export function installOpenCodeGlobalSurface(configDir) {
222
239
  const sourcePath = join(generatedOpenCodeRoot, "skills", sourceName, "SKILL.md");
223
240
  const sourceContent = readFileSync(sourcePath, "utf8");
224
241
  const marker = `${globalOpenCodeSkillOwnershipMarker}${skillName} -->`;
225
- 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()}`);
226
243
  writeOwnedGlobalOpenCodeFile(join(paths.skills, skillName, "SKILL.md"), content, marker, "after-frontmatter");
227
244
  }
228
245
 
@@ -231,6 +248,7 @@ export function installOpenCodeGlobalSurface(configDir) {
231
248
  runtimeContract: paths.runtimeContract,
232
249
  contracts: paths.contracts,
233
250
  skills: paths.skills,
251
+ localValidator: paths.localValidator,
234
252
  };
235
253
  }
236
254
 
@@ -243,11 +261,6 @@ function readOpenCodeConfig(configPath) {
243
261
  }
244
262
  }
245
263
 
246
- function isOpenCodeRepositorySurfacePresent(targetDir) {
247
- return existsSync(join(targetDir, ".opencode", pluginDefinition.opencode.instructionsFileName))
248
- && existsSync(join(targetDir, ".opencode", "skills", `${pluginDefinition.opencode.skillPrefix}gate-check`, "SKILL.md"));
249
- }
250
-
251
264
  function resolveOpenCodePackage(configDir) {
252
265
  const packageName = pluginDefinition.opencode.npmPackage;
253
266
  try {
@@ -313,6 +326,12 @@ function evaluateGlobalOpenCodeSurface(configDir) {
313
326
  const paths = globalOpenCodeConfigPaths(configDir);
314
327
  const skillCount = globalOpenCodeSkillNames.filter((skillName) => existsSync(join(paths.skills, skillName, "SKILL.md"))).length;
315
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
+ });
316
335
  return {
317
336
  path: paths.skills,
318
337
  instructions: paths.instructions,
@@ -322,11 +341,18 @@ function evaluateGlobalOpenCodeSurface(configDir) {
322
341
  skill_count: skillCount,
323
342
  expected_contract_count: contractModules.length,
324
343
  contract_count: contractCount,
344
+ local_validator: {
345
+ path: paths.localValidator,
346
+ present: existsSync(paths.localValidator),
347
+ ...validatorResolution.envelope,
348
+ },
325
349
  present: existsSync(paths.skills),
326
350
  complete: existsSync(paths.instructions)
327
351
  && existsSync(paths.runtimeContract)
328
352
  && contractCount === contractModules.length
329
- && skillCount === openCodeSkillNames.length,
353
+ && skillCount === openCodeSkillNames.length
354
+ && existsSync(paths.localValidator)
355
+ && validatorResolution.envelope.machine_validation === "owned_version_matched",
330
356
  };
331
357
  }
332
358
 
@@ -343,8 +369,9 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
343
369
  version: process.env.AGDF_PLUGIN_VERSION || "",
344
370
  control_dir: process.env.AGDF_CONTROL_DIR || "",
345
371
  repository_surface: process.env.AGDF_OPENCODE_REPOSITORY_SURFACE === "1",
372
+ repository_activation: process.env.AGDF_OPENCODE_REPOSITORY_ACTIVATION || "",
346
373
  };
347
- const repositorySurface = isOpenCodeRepositorySurfacePresent(targetDir);
374
+ const repositoryActivation = evaluateOpenCodeRepositoryActivation(targetDir);
348
375
  const gateCheckSkillPath = join(targetDir, ".opencode", "skills", `${pluginDefinition.opencode.skillPrefix}gate-check`, "SKILL.md");
349
376
 
350
377
  const findings = [];
@@ -360,7 +387,11 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
360
387
  );
361
388
  }
362
389
  if (!sessionSignals.active) findings.push("No active AGDF OpenCode session signal is visible in this process.");
363
- if (!repositorySurface) findings.push("Current repository does not contain the AGDF OpenCode surface.");
390
+ if (!repositoryActivation.active) {
391
+ findings.push(repositoryActivation.state === "invalid_control"
392
+ ? "Current repository has invalid AGDF durable control configuration."
393
+ : "Current repository does not contain valid AGDF durable control configuration.");
394
+ }
364
395
 
365
396
  return {
366
397
  schema_version: "1",
@@ -383,21 +414,30 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
383
414
  },
384
415
  global_native_surface: globalNativeSurface,
385
416
  session: sessionSignals,
417
+ repository_activation: repositoryActivation.state,
418
+ repository_control: {
419
+ path: repositoryActivation.config_path,
420
+ diagnostic: repositoryActivation.diagnostic,
421
+ ...(repositoryActivation.error ? { error: repositoryActivation.error } : {}),
422
+ },
386
423
  repository_surface: {
387
424
  path: targetDir,
388
- present: repositorySurface,
425
+ present: repositoryActivation.active,
426
+ legacy_present: repositoryActivation.legacy_surface,
389
427
  instructions: join(targetDir, ".opencode", pluginDefinition.opencode.instructionsFileName),
390
428
  gate_check_agent: gateCheckSkillPath,
391
429
  gate_check_skill: gateCheckSkillPath,
392
430
  },
393
- visible_entrypoint: repositorySurface ? `${pluginDefinition.opencode.skillPrefix}gate-check (native skill)` : "none until opencode-repo is installed for this repository",
431
+ visible_entrypoint: repositoryActivation.active ? `${pluginDefinition.opencode.globalSkillPrefix}gate-check (native skill)` : "none until durable AGDF control is configured for this repository",
394
432
  findings,
395
433
  next_step: packageVersionStatus !== "current"
396
434
  ? "Run npx --yes @agdf/cli@latest opencode to install or repair the OpenCode package version."
397
435
  : !globalNativeSurface.complete
398
436
  ? "Run npx --yes @agdf/cli@latest opencode to install or repair the global native OpenCode skill surface."
399
- : repositorySurface
400
- ? `Restart OpenCode if needed, then load ${pluginDefinition.opencode.skillPrefix}gate-check through the native skill tool for new build/change intent.`
437
+ : repositoryActivation.state === "invalid_control"
438
+ ? "Repair .agdf/control/config.json so it contains valid artifact_language, chat_language and runtime_language values."
439
+ : repositoryActivation.active
440
+ ? `Restart OpenCode if needed, then load ${pluginDefinition.opencode.globalSkillPrefix}gate-check through the native skill tool for new build/change intent.`
401
441
  : "Run npx --yes @agdf/cli@latest opencode-repo in repositories where AGDF governance should be active and reviewable.",
402
442
  };
403
443
  }
@@ -431,7 +471,9 @@ export function printOpenCodeStatus(report, json, io = console) {
431
471
  io.log(`Session active signal: ${report.session.active ? "yes" : "no"}`);
432
472
  if (report.session.version) io.log(`Session plugin version: ${report.session.version}`);
433
473
  if (report.session.control_dir) io.log(`Session control dir: ${report.session.control_dir}`);
434
- io.log(`Repository surface: ${report.repository_surface.present ? "present" : "missing"}`);
474
+ io.log(`Repository activation: ${report.repository_activation}`);
475
+ io.log(`Repository control: ${report.repository_control.path} (${report.repository_control.diagnostic})`);
476
+ io.log(`Legacy repository surface: ${report.repository_surface.legacy_present ? "present" : "absent"}`);
435
477
  io.log(`Visible entrypoint: ${report.visible_entrypoint}`);
436
478
  io.log(`Next step: ${report.next_step}`);
437
479