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,1040 @@
1
+ import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
2
+ import { spawnSync } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join, resolve } from "node:path";
5
+
6
+ const scriptPluginRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
7
+
8
+ function isFile(path) {
9
+ if (!path) return false;
10
+ try {
11
+ return statSync(path).isFile();
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+
17
+ function isDirectory(path) {
18
+ if (!path) return false;
19
+ try {
20
+ return statSync(path).isDirectory();
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+
26
+ function samePath(left, right) {
27
+ try {
28
+ return realpathSync(left) === realpathSync(right);
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ function hasPluginLayout(root) {
35
+ return [
36
+ ".codex-plugin/plugin.json",
37
+ ".claude-plugin/plugin.json",
38
+ "meta/agdf-plugin.definition.json",
39
+ "meta/agdf-runtime-contract.md",
40
+ "hooks/hooks.json",
41
+ "scripts/check-runtime-integrity.mjs",
42
+ ].every((path) => isFile(join(root, path)))
43
+ && ["skills", "control"].every((path) => isDirectory(join(root, path)));
44
+ }
45
+
46
+ function hasSourceLayout(root) {
47
+ return hasPluginLayout(join(root, "plugin"))
48
+ && [
49
+ ".claude-plugin/marketplace.json",
50
+ "agdf/package.json",
51
+ "create-agdf/package.json",
52
+ "pages/package.json",
53
+ "LICENSE",
54
+ ].every((path) => isFile(join(root, path)));
55
+ }
56
+
57
+ function invalidLayout(root) {
58
+ console.error("[agdf-runtime-integrity] FAIL");
59
+ console.error(`- AGDF_RUNTIME_INTEGRITY_LAYOUT_INVALID: ${root}`);
60
+ process.exit(1);
61
+ }
62
+
63
+ function classifyLayout(root) {
64
+ const source = hasSourceLayout(root);
65
+ const installed = hasPluginLayout(root);
66
+ if (source === installed) invalidLayout(root);
67
+ return source
68
+ ? { mode: "source", repoRoot: root, pluginRoot: join(root, "plugin") }
69
+ : { mode: "installed", repoRoot: null, pluginRoot: root };
70
+ }
71
+
72
+ function resolveLayout() {
73
+ if (process.env.AGDF_RUNTIME_INTEGRITY_ROOT) {
74
+ return classifyLayout(resolve(process.env.AGDF_RUNTIME_INTEGRITY_ROOT));
75
+ }
76
+
77
+ const parentRoot = dirname(scriptPluginRoot);
78
+ const sourceIntent = samePath(join(parentRoot, "plugin"), scriptPluginRoot)
79
+ && (isFile(join(parentRoot, "package.json"))
80
+ || isDirectory(join(parentRoot, "create-agdf"))
81
+ || isDirectory(join(parentRoot, "agdf")));
82
+ return classifyLayout(sourceIntent ? parentRoot : scriptPluginRoot);
83
+ }
84
+
85
+ const { mode: validationMode, repoRoot, pluginRoot } = resolveLayout();
86
+ const sourceMode = validationMode === "source";
87
+ const marketplacePath = sourceMode ? join(repoRoot, ".claude-plugin", "marketplace.json") : null;
88
+ const codexPluginPath = join(pluginRoot, ".codex-plugin", "plugin.json");
89
+ const claudePluginPath = join(pluginRoot, ".claude-plugin", "plugin.json");
90
+ const pluginDefinitionPath = join(pluginRoot, "meta", "agdf-plugin.definition.json");
91
+ const agentRouterPath = join(pluginRoot, "meta", "agdf-agent-router.md");
92
+ const runtimeContractPath = join(pluginRoot, "meta", "agdf-runtime-contract.md");
93
+ const contractsDir = join(pluginRoot, "meta", "contracts");
94
+ const contractModules = [
95
+ "gate-transition.md",
96
+ "interaction.md",
97
+ "modes.md",
98
+ "quality.md",
99
+ "context-graph.md",
100
+ "control-scaffold.md",
101
+ "closeout.md",
102
+ ];
103
+ const interactionLocalesPath = join(pluginRoot, "meta", "agdf-interaction-locales.json");
104
+ const gateCheckSkillPath = join(pluginRoot, "skills", "gate-check", "SKILL.md");
105
+ const brownfieldSkillPath = join(pluginRoot, "skills", "brownfield-analysis", "SKILL.md");
106
+ const localRuntimeManifestPath = join(pluginRoot, "runtime", "runtime-manifest.json");
107
+ const localRuntimeEntrypointPath = join(pluginRoot, "runtime", "agdf-local.js");
108
+ const hooksConfigPath = join(pluginRoot, "hooks", "hooks.json");
109
+ const sessionStartHookPath = join(pluginRoot, "hooks", "session-start.sh");
110
+ const codexComposerIconPath = join(pluginRoot, "assets", "agdf-icon.svg");
111
+ const codexLogoPath = join(pluginRoot, "assets", "agdf-logo.svg");
112
+ const agdfPackagePath = sourceMode ? join(repoRoot, "agdf", "package.json") : null;
113
+ const createAgdfPackagePath = sourceMode ? join(repoRoot, "create-agdf", "package.json") : null;
114
+ const pagesPackagePath = sourceMode ? join(repoRoot, "pages", "package.json") : null;
115
+ const pagesSiteDataPath = sourceMode ? join(repoRoot, "pages", "src", "data", "site.ts") : null;
116
+ const pagesSkillsPath = sourceMode ? join(repoRoot, "pages", "src", "data", "skills.ts") : null;
117
+ const pagesIndexPath = sourceMode ? join(repoRoot, "pages", "src", "pages", "index.astro") : null;
118
+ const syncPackageAssetsPath = sourceMode ? join(repoRoot, "create-agdf", "scripts", "sync-package-assets.js") : null;
119
+ const createAgdfOpenCodeInstallerPath = sourceMode ? join(repoRoot, "create-agdf", "lib", "installers", "opencode.js") : null;
120
+ const activeRunStatePath = sourceMode ? join(repoRoot, ".agdf", "control", "AGDF_RUN.md") : null;
121
+ const rootLicensePath = sourceMode ? join(repoRoot, "LICENSE") : null;
122
+ const pluginLicensePath = join(pluginRoot, "LICENSE");
123
+ const controlRoot = join(pluginRoot, "control");
124
+ const skillRoot = join(pluginRoot, "skills");
125
+
126
+ let expectedSkills = [];
127
+
128
+ const expectedControlFiles = [
129
+ "README.md",
130
+ "templates/RUN_STATE.md",
131
+ "templates/AGDF_RUN.md",
132
+ "templates/MASTER_BACKLOG.md",
133
+ "templates/SOT_REGISTRY.md",
134
+ "templates/CONTEXT_GRAPH.md",
135
+ "templates/AGENT_QUALITY_CONTRACTS.json",
136
+ "templates/artefacts/UR.md",
137
+ "templates/artefacts/BROWNFIELD_REVIEW.md",
138
+ "templates/artefacts/VERIFIED_CHANGE.md",
139
+ "templates/artefacts/PRD.md",
140
+ "templates/artefacts/SD.md",
141
+ "templates/artefacts/TP.md",
142
+ "templates/artefacts/QA_REPORT.md",
143
+ "templates/artefacts/OR.md",
144
+ ];
145
+
146
+ const allowedGermanFragments = [
147
+ "Freigabe:",
148
+ "freigabefähig",
149
+ ];
150
+
151
+ const germanRuntimePatterns = [
152
+ /\bNutze\b/i,
153
+ /\bVerwende\b/i,
154
+ /\bZweck\b/i,
155
+ /\bBeschreibung\b/i,
156
+ /\bArbeitsablauf\b/i,
157
+ /\bNaechster\b/i,
158
+ /\bNächster\b/i,
159
+ /\bQualitaetsausblick\b/i,
160
+ /\bQualitätsausblick\b/i,
161
+ /\bfreigegeben\b/i,
162
+ /\bzulaessig\b/i,
163
+ /\bzulässig\b/i,
164
+ /\bPrueft\b/i,
165
+ /\bPrüft\b/i,
166
+ /\bErzeugt\b/i,
167
+ /\bBestimmt\b/i,
168
+ /\bRekonstruiert\b/i,
169
+ /\bLiefert\b/i,
170
+ ];
171
+
172
+ const failures = [];
173
+
174
+ function read(path) {
175
+ return readFileSync(path, "utf8");
176
+ }
177
+
178
+ function readAllContracts() {
179
+ return contractModules
180
+ .map((name) => {
181
+ const path = join(contractsDir, name);
182
+ return isFile(path) ? read(path) : "";
183
+ })
184
+ .join("\n\n");
185
+ }
186
+
187
+ function readJson(path, label) {
188
+ try {
189
+ return JSON.parse(read(path));
190
+ } catch (error) {
191
+ failures.push(`${label} must be readable JSON`);
192
+ return null;
193
+ }
194
+ }
195
+
196
+ function stripAllowedGerman(content) {
197
+ let next = content;
198
+ for (const fragment of allowedGermanFragments) {
199
+ next = next.split(fragment).join("");
200
+ }
201
+ return next;
202
+ }
203
+
204
+ function sectionAfterHeading(content, heading) {
205
+ return content.match(new RegExp(`## ${heading}\\r?\\n([\\s\\S]*?)(?=\\r?\\n## )`))?.[1] ?? "";
206
+ }
207
+
208
+ function fieldValue(content, field) {
209
+ return content.match(new RegExp(`^- ${field}:\\s*(.*)$`, "m"))?.[1]?.trim() ?? "";
210
+ }
211
+
212
+ function noneLike(value) {
213
+ const normalized = value.replaceAll("`", "").trim().toLowerCase();
214
+ return normalized === "" || normalized === "none" || normalized === "none yet" || normalized === "n/a";
215
+ }
216
+
217
+ function hasPendingContextGraphAction(value) {
218
+ const normalized = value.replaceAll("`", "").toLowerCase();
219
+ return /\b(link|update|create|resolve_drift|promote|reassess)\b/.test(normalized) || normalized.includes("after uat");
220
+ }
221
+
222
+ function assertContextGraphReconciliation(label, content, { allowTemplatePlaceholders = false } = {}) {
223
+ const impact = fieldValue(content, "context_graph_impact");
224
+ const refs = fieldValue(content, "context_graph_refs");
225
+ const reconciliation = fieldValue(content, "context_graph_reconciliation").replaceAll("`", "").trim();
226
+ const requiredAction = fieldValue(content, "context_graph_required_action");
227
+
228
+ if (!content.includes("context_graph_reconciliation")) {
229
+ failures.push(`${label} missing context_graph_reconciliation`);
230
+ return;
231
+ }
232
+
233
+ if (allowTemplatePlaceholders && reconciliation.includes("|")) return;
234
+
235
+ if (hasPendingContextGraphAction(requiredAction) && noneLike(refs) && reconciliation !== "open_gap") {
236
+ failures.push(`${label} has pending Context Graph action without concrete refs or open_gap reconciliation`);
237
+ }
238
+
239
+ if (reconciliation === "resolved" && noneLike(refs) && impact.replaceAll("`", "").trim() !== "none") {
240
+ failures.push(`${label} marks Context Graph reconciliation resolved without concrete refs`);
241
+ }
242
+ }
243
+
244
+ function assertFile(path, label) {
245
+ if (!isFile(path)) {
246
+ failures.push(`${label} missing`);
247
+ }
248
+ }
249
+
250
+ function skillNameForSurface(skill, surface) {
251
+ const prefix = pluginDefinition?.[surface]?.skillPrefix ?? "";
252
+ return `${prefix}${skill.slug}`;
253
+ }
254
+
255
+ function routingRowForSurface(skill, surface) {
256
+ return `| \`${skillNameForSurface(skill, surface)}\` | ${skill.useFor} | ${skill.boundary} |`;
257
+ }
258
+
259
+ function assertRouterMatchesDefinition(pathLabel, content, surface) {
260
+ if (!pluginDefinition) return;
261
+
262
+ for (const skill of pluginDefinition.skillSet ?? []) {
263
+ const expectedRow = routingRowForSurface(skill, surface);
264
+ if (!content.includes(expectedRow)) {
265
+ failures.push(`${pathLabel} missing canonical ${surface} routing row: ${expectedRow}`);
266
+ }
267
+ }
268
+ }
269
+
270
+ assertFile(runtimeContractPath, "runtime contract");
271
+ for (const moduleName of contractModules) {
272
+ assertFile(join(contractsDir, moduleName), `runtime contract module ${moduleName}`);
273
+ }
274
+ assertFile(interactionLocalesPath, "interaction locale registry");
275
+ assertFile(pluginDefinitionPath, "canonical AGDF plugin definition");
276
+ if (sourceMode) {
277
+ if (isDirectory(join(pluginRoot, "runtime"))) failures.push("source plugin must not contain generated runtime");
278
+ } else {
279
+ assertFile(localRuntimeManifestPath, "surface-local runtime manifest");
280
+ assertFile(localRuntimeEntrypointPath, "surface-local runtime entrypoint");
281
+ }
282
+ assertFile(agentRouterPath, "canonical AGDF agent router");
283
+ assertFile(codexPluginPath, "Codex plugin manifest");
284
+ assertFile(claudePluginPath, "Claude plugin manifest");
285
+ assertFile(codexComposerIconPath, "Codex plugin composer icon");
286
+ assertFile(codexLogoPath, "Codex plugin logo");
287
+ assertFile(hooksConfigPath, "Codex plugin default hooks config");
288
+ assertFile(sessionStartHookPath, "AGDF SessionStart hook");
289
+ assertFile(join(controlRoot, "README.md"), "AGDF control scaffold README");
290
+ assertFile(pluginLicensePath, "plugin LICENSE");
291
+
292
+ if (sourceMode) {
293
+ assertFile(marketplacePath, "plugin marketplace");
294
+ assertFile(agdfPackagePath, "agdf CLI package manifest");
295
+ assertFile(createAgdfPackagePath, "create-agdf package manifest");
296
+ assertFile(pagesPackagePath, "Pages package manifest");
297
+ assertFile(pagesSiteDataPath, "Pages site data");
298
+ assertFile(pagesSkillsPath, "Pages skill data");
299
+ assertFile(pagesIndexPath, "Pages index");
300
+ assertFile(syncPackageAssetsPath, "create-agdf package asset sync");
301
+ assertFile(rootLicensePath, "root LICENSE");
302
+
303
+ if (isFile(rootLicensePath) && isFile(pluginLicensePath) && read(rootLicensePath) !== read(pluginLicensePath)) {
304
+ failures.push("plugin/LICENSE must be byte-identical to the root LICENSE");
305
+ }
306
+ }
307
+
308
+ const pluginDefinition = isFile(pluginDefinitionPath) ? readJson(pluginDefinitionPath, "canonical AGDF plugin definition") : null;
309
+ const localRuntimeManifest = isFile(localRuntimeManifestPath) ? readJson(localRuntimeManifestPath, "surface-local runtime manifest") : null;
310
+ if (pluginDefinition && localRuntimeManifest) {
311
+ if (localRuntimeManifest.version !== pluginDefinition.version) failures.push("surface-local runtime version must match the plugin definition");
312
+ if (localRuntimeManifest.entrypoint !== "create-agdf/bin/agdf-validator.js") failures.push("surface-local runtime manifest must own the focused validator entrypoint");
313
+ if (!/^[a-f0-9]{64}$/.test(localRuntimeManifest.digest ?? "")) failures.push("surface-local runtime manifest must include a deterministic SHA-256 digest");
314
+ }
315
+ for (const forbiddenRuntimeDirectory of ["installers", "lifecycle", "scaffold"]) {
316
+ if (isDirectory(join(pluginRoot, "runtime", "create-agdf", "lib", forbiddenRuntimeDirectory))) {
317
+ failures.push(`surface-local runtime must exclude non-validation ${forbiddenRuntimeDirectory} modules`);
318
+ }
319
+ }
320
+ for (const requiredRuntimeFile of sourceMode ? [] : [
321
+ "runtime/create-agdf/lib/runtime/validator-application.js",
322
+ "runtime/create-agdf/lib/cli/validation-handlers.js",
323
+ ]) assertFile(join(pluginRoot, requiredRuntimeFile), `focused surface-local runtime module ${requiredRuntimeFile}`);
324
+ if (!sourceMode && isFile(localRuntimeEntrypointPath)) {
325
+ const probe = spawnSync(process.execPath, [localRuntimeEntrypointPath, "--resolve-only", "--json"], { encoding: "utf8" });
326
+ let report = null;
327
+ try { report = JSON.parse(probe.stdout); } catch {}
328
+ if (probe.status !== 0 || report?.machine_validation !== "owned_version_matched" || report?.registry_access !== false) {
329
+ failures.push("surface-local runtime must pass exact-version digest resolution without registry access");
330
+ }
331
+ }
332
+ const runtimeContract = readAllContracts();
333
+ const interactionLocales = isFile(interactionLocalesPath) ? readJson(interactionLocalesPath, "interaction locale registry") : null;
334
+ const gateCheckSkill = isFile(gateCheckSkillPath) ? read(gateCheckSkillPath) : "";
335
+ const brownfieldSkill = isFile(brownfieldSkillPath) ? read(brownfieldSkillPath) : "";
336
+ if (!brownfieldSkill.includes("Persist the completed review and its decision, scope reason, evidence and required next gate in the same internal operation")) {
337
+ failures.push("brownfield-analysis must own atomic post-UR review and routing persistence");
338
+ }
339
+ const codexPlugin = isFile(codexPluginPath) ? readJson(codexPluginPath, "Codex plugin manifest") : null;
340
+ const claudePlugin = isFile(claudePluginPath) ? readJson(claudePluginPath, "Claude plugin manifest") : null;
341
+ const agdfPackage = isFile(agdfPackagePath) ? readJson(agdfPackagePath, "agdf CLI package manifest") : null;
342
+ const createAgdfPackage = isFile(createAgdfPackagePath) ? readJson(createAgdfPackagePath, "create-agdf package manifest") : null;
343
+ const pagesPackage = isFile(pagesPackagePath) ? readJson(pagesPackagePath, "Pages package manifest") : null;
344
+
345
+ if (!runtimeContract.includes("first eligible native-attempt") || !runtimeContract.includes("does not trigger a second")) {
346
+ failures.push("Native Interaction Contract must require a single first native attempt with no retry");
347
+ }
348
+ if (!runtimeContract.includes("`attempted_not_applied`") || !runtimeContract.includes("`unsafe_to_wait`")) {
349
+ failures.push("Runtime Contract must define visible fallback attempt outcomes");
350
+ }
351
+ if (!runtimeContract.includes("### Interaction Locale Contract") || !runtimeContract.includes("an incomplete pack is unsupported and must fail to English as a unit")) {
352
+ failures.push("Runtime Contract must define deterministic chat-locale resolution with English fallback");
353
+ }
354
+
355
+ const gateCheckOperationalBoundaries = [
356
+ "Select exactly one run and evaluate its current gate.",
357
+ "Confirm that the required durable artefact is present and ready.",
358
+ "Consume the canonical `approval_presentation` verbatim",
359
+ "obtain deliberate input through the contract-selected native or exact-text path",
360
+ "Revalidate the same run, gate and revision immediately after the response and before persistence.",
361
+ "Persist only a currently valid exact approval through the existing control-state workflow.",
362
+ ];
363
+ if (!gateCheckSkill.includes("../../meta/contracts/interaction.md")) {
364
+ failures.push("gate-check must load the focused interaction contract");
365
+ }
366
+ for (const required of gateCheckOperationalBoundaries) {
367
+ if (!gateCheckSkill.includes(required)) failures.push(`gate-check operational boundary missing: ${required}`);
368
+ }
369
+ for (const duplicatedPolicy of ["Surface behavior:", "Make exactly one native-attempt", "Interaction Locale Contract", "decorated_label_only"]) {
370
+ if (gateCheckSkill.includes(duplicatedPolicy)) failures.push(`gate-check must not duplicate normative interaction policy: ${duplicatedPolicy}`);
371
+ }
372
+
373
+ for (const gate of ["UR", "PRD", "SD", "TP", "QA", "UAT"]) {
374
+ for (const locale of ["en", "de"]) {
375
+ if (!interactionLocales?.locales?.[locale]?.gateActionTitles?.[gate]) {
376
+ failures.push(`Interaction locale ${locale} missing neutral decision gate title for ${gate}`);
377
+ }
378
+ if (!interactionLocales?.locales?.[locale]?.gateRequiredDecisions?.[gate]) failures.push(`Interaction locale ${locale} missing required decision copy for ${gate}`);
379
+ }
380
+ }
381
+ for (const locale of ["en", "de"]) {
382
+ const pack = interactionLocales?.locales?.[locale];
383
+ if (!pack?.statusCard?.breadcrumbFulfilled || !pack?.statusCard?.breadcrumbCurrent || !pack?.statusCard?.breadcrumbOpen || !pack?.statusCard?.breadcrumbSeparator) {
384
+ failures.push(`Interaction locale ${locale} missing breadcrumb keys in statusCard`);
385
+ }
386
+ if (!pack?.gateTitles?.["Compact Delivery"]) failures.push(`Interaction locale ${locale} missing Compact Delivery gate title`);
387
+ if (!pack?.primary?.narration?.gateSatisfied || !pack?.primary?.narration?.noAction || !pack?.primary?.narration?.gates) {
388
+ failures.push(`Interaction locale ${locale} missing narration keys in primary`);
389
+ }
390
+ }
391
+ for (const [surface, expectedTransport] of [["codex", "exact_option_value"], ["claude", "exact_option_value"], ["opencode", "exact_option_value"], ["fallback", "exact_option_value"]]) {
392
+ const surfaceContract = pluginDefinition?.interactions?.surfaces?.[surface];
393
+ if (surfaceContract?.approvalValueTransport !== expectedTransport || surfaceContract?.waitSafety !== "deliberate_no_auto_resolution" || surfaceContract?.authorizationPath !== "exact_text" || Object.hasOwn(surfaceContract ?? {}, "canonicalValueTransport")) {
394
+ failures.push(`${surface} interaction adapter must declare fail-closed canonical value transport and exact-text authorization path`);
395
+ }
396
+ }
397
+ for (const required of [
398
+ "Before presenting `gate_approval` for any user gate",
399
+ "one immediately preceding assistant message",
400
+ "one immutable,\nnon-authorizing presentation snapshot",
401
+ "### Human Decision Presentation Contract",
402
+ "Never guess a path or emit a broken link",
403
+ "A localized label, description, option position, recommendation style or host action never authorizes a gate",
404
+ ]) {
405
+ if (!runtimeContract.includes(required)) failures.push(`interaction contract ownership boundary missing: ${required}`);
406
+ }
407
+ const gateTransitionCard = sectionAfterHeading(runtimeContract, "Gate Transition Card");
408
+ for (const pattern of [
409
+ "| Status |",
410
+ "- allowed_now:",
411
+ "Diagnostic code:",
412
+ "Evidence:",
413
+ "Question:",
414
+ "Next user gate: Brownfield Analysis",
415
+ ]) {
416
+ if (gateTransitionCard.includes(pattern)) failures.push(`Gate Transition Card must not render approval-time pattern: ${pattern}`);
417
+ }
418
+
419
+ if (pluginDefinition) {
420
+ if (pluginDefinition.id !== "agdf") failures.push("canonical AGDF plugin definition id must be agdf");
421
+ if (pluginDefinition.displayName !== "AI Governance & Delivery Framework") failures.push("canonical AGDF plugin definition must use the speaking display name");
422
+ if (pluginDefinition.codex?.skillPrefix !== "") failures.push("canonical AGDF plugin definition Codex skill prefix must be empty to avoid agdf:agdf-* plugin labels");
423
+ if (pluginDefinition.claude?.skillPrefix !== "") failures.push("canonical AGDF plugin definition Claude Code skill prefix must be empty to avoid agdf:agdf-* plugin labels");
424
+ if (pluginDefinition.codex?.agentRouter !== "meta/agdf-agent-router.md") failures.push("canonical AGDF plugin definition Codex agent router must point to meta/agdf-agent-router.md");
425
+ if (pluginDefinition.codex?.composerIcon !== "./assets/agdf-icon.svg") failures.push("canonical AGDF plugin definition Codex composer icon must point to ./assets/agdf-icon.svg");
426
+ if (pluginDefinition.codex?.logo !== "./assets/agdf-logo.svg") failures.push("canonical AGDF plugin definition Codex logo must point to ./assets/agdf-logo.svg");
427
+ if (pluginDefinition.claude?.agentRouter !== "meta/agdf-agent-router.md") failures.push("canonical AGDF plugin definition Claude agent router must point to meta/agdf-agent-router.md");
428
+ if (pluginDefinition.copilot?.skillPrefix !== "agdf-") failures.push("canonical AGDF plugin definition Copilot skill prefix must be agdf-");
429
+ if (pluginDefinition.opencode?.skillPrefix !== "agdf-") failures.push("canonical AGDF plugin definition OpenCode skill prefix must be agdf-");
430
+ if (pluginDefinition.opencode?.globalSkillPrefix !== "agdf-global-") failures.push("canonical AGDF plugin definition OpenCode global skill prefix must be agdf-global-");
431
+ if (pluginDefinition.opencode?.runtimeContractFileName !== "agdf-runtime-contract.md") failures.push("canonical AGDF plugin definition OpenCode runtime contract filename must be agdf-runtime-contract.md");
432
+ if (pluginDefinition.opencode?.instructionsFileName !== "AGDF.md") failures.push("canonical AGDF plugin definition OpenCode instructions filename must be AGDF.md");
433
+ if (pluginDefinition.opencode?.permissions?.question !== "allow") failures.push("canonical AGDF plugin definition OpenCode permissions must allow the native question tool");
434
+ if (pluginDefinition.opencode?.permissions?.edit !== "ask" || pluginDefinition.opencode?.permissions?.bash !== "ask") failures.push("canonical AGDF plugin definition OpenCode permissions must ask before edit and bash");
435
+ if (pluginDefinition.interactions?.fallback !== "exact_text") failures.push("canonical AGDF plugin definition interactions must declare exact_text fallback");
436
+ if (pluginDefinition.interactions?.localeRegistry !== "meta/agdf-interaction-locales.json" || pluginDefinition.interactions?.fallbackLocale !== "en") {
437
+ failures.push("canonical AGDF plugin definition must declare the locale registry and English fallback");
438
+ }
439
+ if (JSON.stringify(pluginDefinition.interactions?.optionOrder) !== JSON.stringify(["approve", "revise", "decline", "cancel"])) {
440
+ failures.push("canonical AGDF plugin definition must preserve stable interaction option order");
441
+ }
442
+ if (JSON.stringify(pluginDefinition.interactions?.outcomes) !== JSON.stringify(["approve", "revise", "decline", "cancel", "no_response", "timeout", "empty", "invalid", "stale"])) {
443
+ failures.push("canonical AGDF plugin definition must preserve distinct interaction outcomes");
444
+ }
445
+ for (const [surface, adapter, safety] of [
446
+ ["codex", "request_user_input", "omit_auto_resolution"],
447
+ ["claude", "AskUserQuestion", "no_timeout_or_hook_supplied_answer"],
448
+ ["opencode", "question", "preserve_explicit_deny"],
449
+ ["fallback", "exact_text", "wait_for_deliberate_user_input"],
450
+ ]) {
451
+ const interaction = pluginDefinition.interactions?.surfaces?.[surface];
452
+ if (interaction?.questionAdapter !== adapter || interaction?.gateSafety !== safety || !interaction?.technicalPermissionOwner) {
453
+ failures.push(`canonical AGDF plugin definition ${surface} interaction mapping must declare adapter, gate safety and technical permission owner`);
454
+ }
455
+ const expectedOutcomes = surface === "fallback" ? ["approve", "revise", "decline", "cancel"] : ["approve", "revise", "decline"];
456
+ if (JSON.stringify(interaction?.explicitOutcomes) !== JSON.stringify(expectedOutcomes)) {
457
+ failures.push(`canonical AGDF plugin definition ${surface} interaction mapping must preserve stable explicit outcome order`);
458
+ }
459
+ }
460
+ if (!Array.isArray(pluginDefinition.skillSet) || pluginDefinition.skillSet.length === 0) {
461
+ failures.push("canonical AGDF plugin definition must declare the workflow skill set");
462
+ } else {
463
+ for (const skill of pluginDefinition.skillSet) {
464
+ if (!skill?.slug) failures.push("canonical AGDF plugin definition skill set entries must declare slug");
465
+ if (!skill?.useFor) failures.push(`canonical AGDF plugin definition skill ${skill?.slug ?? "<unknown>"} must declare useFor`);
466
+ if (!skill?.boundary) failures.push(`canonical AGDF plugin definition skill ${skill?.slug ?? "<unknown>"} must declare boundary`);
467
+ }
468
+ }
469
+ if (pluginDefinition.marketplaces?.repository?.name !== "agdf-repo") failures.push("canonical AGDF plugin definition repository marketplace name must be agdf-repo");
470
+ if (pluginDefinition.marketplaces?.repository?.displayName !== "This repository") failures.push("canonical AGDF plugin definition repository marketplace display name must be This repository");
471
+ expectedSkills = (pluginDefinition.skillSet ?? [])
472
+ .map((skill) => `${pluginDefinition.codex?.skillPrefix ?? ""}${skill?.slug ?? ""}`)
473
+ .sort();
474
+ }
475
+
476
+ if (interactionLocales) {
477
+ if (interactionLocales.schemaVersion !== 1 || interactionLocales.fallbackLocale !== "en") failures.push("interaction locale registry must use schemaVersion 1 and English fallback");
478
+ if (JSON.stringify(interactionLocales.optionOrder) !== JSON.stringify(["approve", "revise", "decline", "cancel"])) failures.push("interaction locale registry must preserve stable option order");
479
+ const englishKeys = JSON.stringify(Object.keys(interactionLocales.locales?.en?.statusCard ?? {}).sort());
480
+ for (const locale of ["en", "de"]) {
481
+ const pack = interactionLocales.locales?.[locale];
482
+ if (!pack) {
483
+ failures.push(`interaction locale registry missing initial ${locale} pack`);
484
+ continue;
485
+ }
486
+ if (JSON.stringify(Object.keys(pack.statusCard ?? {}).sort()) !== englishKeys) failures.push(`interaction locale registry ${locale} status-card keys are incomplete`);
487
+ for (const gate of ["UR", "PRD", "SD", "TP", "QA", "UAT"]) {
488
+ if (!pack.gateTitles?.[gate]) failures.push(`interaction locale registry ${locale} missing gate title ${gate}`);
489
+ }
490
+ for (const key of ["reviseLabel", "reviseDescription", "declineLabel", "declineDescription", "cancelLabel", "cancelDescription"]) {
491
+ if (!pack.interaction?.[key]) failures.push(`interaction locale registry ${locale} missing interaction copy ${key}`);
492
+ }
493
+ }
494
+ }
495
+
496
+ if (pluginDefinition && isFile(pagesSkillsPath)) {
497
+ const pageSkillNames = [...read(pagesSkillsPath).matchAll(/\bname:\s*"([^"]+)"/g)]
498
+ .map((match) => match[1])
499
+ .sort();
500
+ const canonicalSkillNames = (pluginDefinition.skillSet ?? []).map((skill) => skill.slug).sort();
501
+ if (JSON.stringify(pageSkillNames) !== JSON.stringify(canonicalSkillNames)) {
502
+ failures.push(`Pages skill data must match canonical skillSet exactly: expected ${canonicalSkillNames.join(", ")}, got ${pageSkillNames.join(", ")}`);
503
+ }
504
+ }
505
+
506
+ if (isFile(pagesIndexPath) && !read(pagesIndexPath).includes("{skills.length} Core Workflow Skills")) {
507
+ failures.push("Pages skill heading must derive its count from skills.length");
508
+ }
509
+
510
+ if (codexPlugin && pluginDefinition) {
511
+ if (codexPlugin.name !== pluginDefinition.id) failures.push("Codex plugin manifest name must match canonical AGDF plugin definition");
512
+ if (codexPlugin.version !== pluginDefinition.version) failures.push("Codex plugin manifest version must match canonical AGDF plugin definition");
513
+ if (codexPlugin.description !== pluginDefinition.description) failures.push("Codex plugin manifest description must match canonical AGDF plugin definition");
514
+ if (codexPlugin.homepage !== pluginDefinition.homepage) failures.push("Codex plugin manifest homepage must match canonical AGDF plugin definition");
515
+ if (codexPlugin.repository !== pluginDefinition.repository) failures.push("Codex plugin manifest repository must match canonical AGDF plugin definition");
516
+ if (codexPlugin.license !== pluginDefinition.license) failures.push("Codex plugin manifest license must match canonical AGDF plugin definition");
517
+ if (JSON.stringify(codexPlugin.keywords) !== JSON.stringify(pluginDefinition.keywords)) failures.push("Codex plugin manifest keywords must match canonical AGDF plugin definition");
518
+ if (codexPlugin.author?.name !== pluginDefinition.author?.name || codexPlugin.author?.url !== pluginDefinition.author?.url) failures.push("Codex plugin manifest author must match canonical AGDF plugin definition");
519
+ if (codexPlugin.skills !== pluginDefinition.codex?.skills) failures.push("Codex plugin manifest must point skills to canonical AGDF skills path");
520
+ if (codexPlugin.interface?.displayName !== pluginDefinition.displayName) failures.push("Codex plugin display name must match canonical AGDF plugin definition");
521
+ if (codexPlugin.interface?.shortDescription !== pluginDefinition.shortDescription) failures.push("Codex plugin short description must match canonical AGDF plugin definition");
522
+ if (codexPlugin.interface?.longDescription !== pluginDefinition.longDescription) failures.push("Codex plugin long description must match canonical AGDF plugin definition");
523
+ if (codexPlugin.interface?.developerName !== pluginDefinition.developerName) failures.push("Codex plugin developer name must match canonical AGDF plugin definition");
524
+ if (codexPlugin.interface?.category !== pluginDefinition.category) failures.push("Codex plugin category must match canonical AGDF plugin definition");
525
+ if (codexPlugin.interface?.websiteURL !== pluginDefinition.homepage) failures.push("Codex plugin website URL must match canonical AGDF plugin definition");
526
+ if (codexPlugin.interface?.composerIcon !== pluginDefinition.codex?.composerIcon) failures.push("Codex plugin composer icon must match canonical AGDF plugin definition");
527
+ if (codexPlugin.interface?.logo !== pluginDefinition.codex?.logo) failures.push("Codex plugin logo must match canonical AGDF plugin definition");
528
+ if (codexPlugin.interface?.brandColor !== pluginDefinition.brandColor) failures.push("Codex plugin brand color must match canonical AGDF plugin definition");
529
+ if (JSON.stringify(codexPlugin.interface?.capabilities) !== JSON.stringify(pluginDefinition.codex?.capabilities)) failures.push("Codex plugin capabilities must match canonical AGDF plugin definition");
530
+ if (JSON.stringify(codexPlugin.interface?.defaultPrompt) !== JSON.stringify(pluginDefinition.codex?.defaultPrompt)) failures.push("Codex plugin default prompts must match canonical AGDF plugin definition");
531
+ if (codexPlugin.hooks !== `./${pluginDefinition.codex?.hooks}`) failures.push("Codex plugin manifest hooks must explicitly point to ./hooks/hooks.json");
532
+ }
533
+
534
+ if (claudePlugin && pluginDefinition) {
535
+ if (claudePlugin.name !== pluginDefinition.id) failures.push("Claude plugin manifest name must match canonical AGDF plugin definition");
536
+ if (claudePlugin.version !== pluginDefinition.version) failures.push("Claude plugin manifest version must match canonical AGDF plugin definition");
537
+ if (claudePlugin.description !== pluginDefinition.claudeDescription) failures.push("Claude plugin manifest description must match canonical AGDF plugin definition");
538
+ if (claudePlugin.homepage !== pluginDefinition.homepage) failures.push("Claude plugin manifest homepage must match canonical AGDF plugin definition");
539
+ if (claudePlugin.repository !== pluginDefinition.repository) failures.push("Claude plugin manifest repository must match canonical AGDF plugin definition");
540
+ if (claudePlugin.license !== pluginDefinition.license) failures.push("Claude plugin manifest license must match canonical AGDF plugin definition");
541
+ if (JSON.stringify(claudePlugin.keywords) !== JSON.stringify(pluginDefinition.keywords)) failures.push("Claude plugin manifest keywords must match canonical AGDF plugin definition");
542
+ if (claudePlugin.author?.name !== pluginDefinition.author?.name || claudePlugin.author?.url !== pluginDefinition.author?.url) failures.push("Claude plugin manifest author must match canonical AGDF plugin definition");
543
+ }
544
+
545
+ if (createAgdfPackage && pluginDefinition && createAgdfPackage.version !== pluginDefinition.version) {
546
+ failures.push("create-agdf package version must match canonical AGDF plugin definition");
547
+ }
548
+
549
+ if (agdfPackage && pluginDefinition) {
550
+ if (agdfPackage.name !== "@agdf/cli") failures.push("agdf CLI package name must be @agdf/cli");
551
+ if (agdfPackage.version !== pluginDefinition.version) failures.push("agdf CLI package version must match canonical AGDF plugin definition");
552
+ if (agdfPackage.bin?.agdf !== "./bin/agdf.js") failures.push("agdf CLI package must expose the agdf binary");
553
+ if (agdfPackage.dependencies?.["create-agdf"] !== pluginDefinition.version) failures.push("agdf CLI package must depend on the matching create-agdf version");
554
+ }
555
+
556
+ if (pagesPackage && pluginDefinition && pagesPackage.version !== pluginDefinition.version) {
557
+ failures.push("Pages package version must match canonical AGDF plugin definition");
558
+ }
559
+
560
+ if (pluginDefinition && isFile(pagesSiteDataPath) && !read(pagesSiteDataPath).includes(`version: "${pluginDefinition.version}"`)) {
561
+ failures.push("Pages site data version must match canonical AGDF plugin definition");
562
+ }
563
+
564
+ if (isFile(syncPackageAssetsPath)) {
565
+ const syncPackageAssets = read(syncPackageAssetsPath);
566
+ if (!syncPackageAssets.includes("writeOpenCodeConfig") || !syncPackageAssets.includes("writeOpenCodeSkill")) {
567
+ failures.push("create-agdf package asset sync must generate the OpenCode config and native skills");
568
+ }
569
+ if (!pluginDefinition?.opencode?.npmPackage) {
570
+ failures.push("canonical AGDF plugin definition must declare the OpenCode npm package");
571
+ }
572
+ if (!syncPackageAssets.includes("toOpenCodeInstructionsRouter")) {
573
+ failures.push("OpenCode instructions must be rendered from the canonical AGDF router");
574
+ }
575
+ }
576
+
577
+ if (isFile(createAgdfOpenCodeInstallerPath)) {
578
+ const createAgdfOpenCodeInstaller = read(createAgdfOpenCodeInstallerPath);
579
+ if (!createAgdfOpenCodeInstaller.includes("globalOpenCodeBoundary") || !createAgdfOpenCodeInstaller.includes("globalOpenCodeSkillOwnershipMarker")) {
580
+ failures.push("OpenCode global native-surface ownership and fail-closed boundary must remain in the canonical installer path");
581
+ }
582
+ }
583
+
584
+ const openCodeNpmPluginPath = sourceMode ? join(repoRoot, "create-agdf", "opencode-plugin.js") : null;
585
+ if (sourceMode && isFile(openCodeNpmPluginPath)) {
586
+ const openCodeNpmPlugin = read(openCodeNpmPluginPath);
587
+ if (!openCodeNpmPlugin.includes("experimental.session.compacting") || !openCodeNpmPlugin.includes("AGDF_CONTROL_DIR")) {
588
+ failures.push("OpenCode npm plugin must preserve AGDF runtime context hooks");
589
+ }
590
+ }
591
+
592
+ const hooksConfig = isFile(hooksConfigPath) ? readJson(hooksConfigPath, "Codex plugin hooks config") : null;
593
+ if (hooksConfig) {
594
+ const sessionStartGroups = hooksConfig.hooks?.SessionStart;
595
+ if (!Array.isArray(sessionStartGroups) || sessionStartGroups.length === 0) failures.push("Codex plugin hooks config must define SessionStart hooks");
596
+ const sessionStartCommands = sessionStartGroups?.flatMap((group) => group?.hooks ?? []) ?? [];
597
+ if (!sessionStartCommands.some((hook) => hook?.type === "command" && String(hook?.command ?? "").includes("session-start.sh"))) {
598
+ failures.push("Codex plugin SessionStart hooks must load session-start.sh");
599
+ }
600
+ const sessionStartCommandText = sessionStartCommands.map((hook) => String(hook?.command ?? "")).join("\n");
601
+ if (!sessionStartCommandText.includes("PLUGIN_ROOT")) failures.push("Codex plugin SessionStart hook command must use PLUGIN_ROOT");
602
+ if (sessionStartCommandText.includes("/plugins/cache/*/")) failures.push("Codex plugin SessionStart hook command must not use cache wildcards");
603
+ }
604
+
605
+ if (isFile(sessionStartHookPath)) {
606
+ const sessionStartHook = read(sessionStartHookPath);
607
+ if (!sessionStartHook.includes("agdf-agent-router.md")) failures.push("AGDF SessionStart hook must load the canonical agent router");
608
+ if (!sessionStartHook.includes("agdf-constitution.md")) failures.push("AGDF SessionStart hook must load the AGDF constitution");
609
+ if (!sessionStartHook.includes("agdf-runtime-contract.md")) failures.push("AGDF SessionStart hook must expose the canonical runtime contract source");
610
+ if (!sessionStartHook.includes("Do not print the full router or constitution unless the user asks for them")) {
611
+ failures.push("AGDF SessionStart hook must avoid flooding the chat with full router or constitution text");
612
+ }
613
+ if (!sessionStartHook.includes(".agdf/control/config.json") || !sessionStartHook.includes("artefacts=") || !sessionStartHook.includes("chat=")) {
614
+ failures.push("AGDF SessionStart hook must report the project language config hint compactly");
615
+ }
616
+ if (!sessionStartHook.includes("Language policy: write durable AGDF artefacts in")) {
617
+ failures.push("AGDF SessionStart hook must turn language config into an explicit artefact/chat language instruction");
618
+ }
619
+ if (sessionStartHook.includes('cat "$ROUTER"') || sessionStartHook.includes('cat "$CONSTITUTION"')) {
620
+ failures.push("AGDF SessionStart hook must not print full router or constitution files by default");
621
+ }
622
+ }
623
+
624
+ if (isFile(agentRouterPath)) {
625
+ const agentRouter = read(agentRouterPath);
626
+ assertRouterMatchesDefinition("plugin/meta/agdf-agent-router.md", agentRouter, "codex");
627
+ if (!agentRouter.includes("Default entry rule: a new user intent to build, add, change, extend, refactor or otherwise deliver something starts with `gate-check`")) {
628
+ failures.push("plugin/meta/agdf-agent-router.md must state the gate-check default entry rule for new build/change intents");
629
+ }
630
+ if (!agentRouter.includes("Do not choose `brownfield-analysis` as the first primary skill for a fresh")) {
631
+ failures.push("plugin/meta/agdf-agent-router.md must prevent brownfield-analysis from bypassing gate-check on fresh build/change prompts");
632
+ }
633
+ if (!agentRouter.includes("For Quick Task Mode, close with the Runtime Contract mini-output only")) {
634
+ failures.push("plugin/meta/agdf-agent-router.md must keep Quick Task closeout lighter than relevant-run closeout");
635
+ }
636
+ for (const skill of pluginDefinition?.skillSet ?? []) {
637
+ const copilotName = skillNameForSurface(skill, "copilot");
638
+ if (agentRouter.includes(`\`${copilotName}\``) || agentRouter.includes(`/${copilotName}`)) {
639
+ failures.push(`plugin/meta/agdf-agent-router.md must not use Copilot-prefixed skill name ${copilotName}`);
640
+ }
641
+ }
642
+ }
643
+
644
+ if (isFile(runtimeContractPath)) {
645
+ const runtimeContract = readAllContracts();
646
+ for (const required of [
647
+ "## Native Interaction Contract",
648
+ "`clarification`: asks for missing intent",
649
+ "`tool_permission`: requests host-owned authority",
650
+ "`gate_approval`: requests deliberate user input",
651
+ "auto_resolution: forbidden for gate_approval",
652
+ "response_origin: deliberate_user_input for gate_approval",
653
+ "re-run canonical gate evaluation against the same `run_id` and expected gate immediately before persistence",
654
+ "Host permission, plan approval, native question presentation, timeout/default behavior, hook output and agent messages never carry AGDF gate authority by themselves",
655
+ ]) {
656
+ if (!runtimeContract.includes(required)) failures.push(`runtime contract Native Interaction Contract missing: ${required}`);
657
+ }
658
+ for (const adapter of ["request_user_input", "AskUserQuestion", "permission.question", "Exact textual approvals remain canonical"]) {
659
+ if (!runtimeContract.includes(adapter)) failures.push(`runtime contract Native Interaction Contract missing surface/fallback mapping: ${adapter}`);
660
+ }
661
+ if (!runtimeContract.includes("Approval of one user gate permits work on the next allowed gate artefact or required internal step only")) {
662
+ failures.push("runtime contract must state that one gate approval only permits the next gate artefact or required internal step");
663
+ }
664
+ if (!runtimeContract.includes("`Approval: UR` permits Brownfield Review after G-00 first. The review records its Mode/Slice Decision in the same internal operation")) {
665
+ failures.push("runtime contract must state that Approval: UR permits Brownfield Review before Mode/Slice Decision");
666
+ }
667
+ if (!runtimeContract.includes("PRD, SD and TP depth is chosen after Brownfield Review through the Mode/Slice Decision")) {
668
+ failures.push("runtime contract must state that gate depth is chosen after Brownfield Review");
669
+ }
670
+ if (!runtimeContract.includes("The Mode/Slice Decision must be visible before any PRD shortcut, Quick Task execution or implementation")) {
671
+ failures.push("runtime contract must require visible Mode/Slice Decision before later work");
672
+ }
673
+ if (!runtimeContract.includes("A Mode/Slice Decision without scope reason and evidence is not recorded")) {
674
+ failures.push("runtime contract must treat unevidenced Mode/Slice Decision as missing");
675
+ }
676
+ if (!runtimeContract.includes("Missing control files or missing current-state fields do not forbid the agent from preparing the current allowed artefact")) {
677
+ failures.push("runtime contract must allow constructive artefact drafting when control state is missing");
678
+ }
679
+ if (!runtimeContract.includes("For a fresh request, the default allowed work is to draft a minimal UR in the response")) {
680
+ failures.push("runtime contract must keep fresh missing-control requests lightweight");
681
+ }
682
+ if (!runtimeContract.includes("Initialize or write `.agdf/control/` only when the user explicitly asks for durable AGDF control state")) {
683
+ failures.push("runtime contract must prevent default full scaffold writes for fresh requests");
684
+ }
685
+ if (!runtimeContract.includes("`config.json` stores project language preferences")) {
686
+ failures.push("runtime contract must define AGDF language preference config");
687
+ }
688
+ if (!runtimeContract.includes("## Gate Transition Model")) {
689
+ failures.push("runtime contract must own the canonical gate transition model");
690
+ }
691
+ if (!runtimeContract.includes("Skills may reference it, but must not carry a second complete copy")) {
692
+ failures.push("runtime contract must forbid duplicated complete gate transition tables");
693
+ }
694
+ if (!runtimeContract.includes("## Quick Task Output")) {
695
+ failures.push("runtime contract must define Quick Task output");
696
+ }
697
+ if (!runtimeContract.includes("Do not add a separate `Quality outlook` line for pure Quick Tasks")) {
698
+ failures.push("runtime contract must prevent Quick Task closeout from requiring Quality outlook");
699
+ }
700
+ if (!runtimeContract.includes("## Chat Output Discipline")) {
701
+ failures.push("runtime contract must define compact chat output discipline");
702
+ }
703
+ if (!runtimeContract.includes("Do not paste full control files, full artefact bodies, full templates or full generated reports into the chat")) {
704
+ failures.push("runtime contract must prevent full artefact bodies from flooding chat");
705
+ }
706
+ if (!runtimeContract.includes("## Relevant Run")) {
707
+ failures.push("runtime contract must define relevant run for OR scope");
708
+ }
709
+ if (!runtimeContract.includes("## Brownfield Modes")) {
710
+ failures.push("runtime contract must define Brownfield modes");
711
+ }
712
+ if (!runtimeContract.includes("requires a durable UR in `.agdf/control/` or a linked authoritative repository SoT")) {
713
+ failures.push("runtime contract must require a durable UR for new product semantics or functional change");
714
+ }
715
+ if (!runtimeContract.includes("UR, PRD, SD, TP and QA report approvals require durable artefacts")) {
716
+ failures.push("runtime contract must require durable artefacts for UR, PRD, SD, TP and QA report approvals");
717
+ }
718
+ if (!runtimeContract.includes("approval and durable artefact presence are separate requirements")) {
719
+ failures.push("runtime contract must separate approval text from durable artefact presence");
720
+ }
721
+ if (!runtimeContract.includes("AGDF is agent-native first and CLI-verifiable by design")) {
722
+ failures.push("runtime contract must state the agent-native and CLI-verifiable operating model");
723
+ }
724
+ if (!runtimeContract.includes("Helper commands are deterministic proof and automation interfaces, not the normal-work ritual")) {
725
+ failures.push("runtime contract must classify helper commands as proof and automation interfaces");
726
+ }
727
+ if (!runtimeContract.includes("`delivery-map --json` is the machine-readable delivery picture")) {
728
+ failures.push("runtime contract must define delivery-map as the machine-readable delivery picture");
729
+ }
730
+ if (!runtimeContract.includes("Missing relationship evidence in the Artefact Chain is at least `revise`")) {
731
+ failures.push("runtime contract must state the delivery-map relationship evidence rule");
732
+ }
733
+ if (!runtimeContract.includes("## Run Status Card")) {
734
+ failures.push("runtime contract must define the Run Status Card");
735
+ }
736
+ if (!runtimeContract.includes("`quality_outlook` is quality direction")) {
737
+ failures.push("runtime contract must distinguish quality_outlook from next_step");
738
+ }
739
+ if (!runtimeContract.includes("do not expose snake_case keys as the visible")) {
740
+ failures.push("runtime contract must keep the human-facing Run Status Card readable");
741
+ }
742
+ if (!runtimeContract.includes("## Source Precedence")) {
743
+ failures.push("runtime contract must define source precedence");
744
+ }
745
+ if (!runtimeContract.includes("A branch name or uncommitted workspace delta is never sufficient scope proof by itself")) {
746
+ failures.push("runtime contract must state branch/workspace evidence limits");
747
+ }
748
+ if (!runtimeContract.includes("## Workstate And Scope Ambiguity")) {
749
+ failures.push("runtime contract must define multi-scope fail-closed behavior");
750
+ }
751
+ if (!runtimeContract.includes("## Knowledge Persistence Decision")) {
752
+ failures.push("runtime contract must define knowledge persistence decisions");
753
+ }
754
+ if (!runtimeContract.includes("## Bug Lightweight Track")) {
755
+ failures.push("runtime contract must define the Bug Lightweight Track");
756
+ }
757
+ if (!runtimeContract.includes("## Domain Guardrail Packs")) {
758
+ failures.push("runtime contract must define domain guardrail packs");
759
+ }
760
+ }
761
+
762
+ if (sourceMode) {
763
+ const marketplace = isFile(marketplacePath) ? readJson(marketplacePath, "plugin marketplace") : null;
764
+ const agdfMarketplaceEntry = marketplace?.plugins?.find((plugin) => plugin?.name === "agdf");
765
+ if (!agdfMarketplaceEntry) {
766
+ failures.push("plugin marketplace must expose agdf");
767
+ } else {
768
+ if (agdfMarketplaceEntry.source !== "./plugin/") failures.push("plugin marketplace agdf source must point to ./plugin/");
769
+ if (Object.hasOwn(agdfMarketplaceEntry, "policy")) failures.push("Claude plugin marketplace agdf entry must not use unsupported policy field");
770
+ if (agdfMarketplaceEntry.category !== "Productivity") failures.push("plugin marketplace agdf category must be Productivity");
771
+ }
772
+ }
773
+
774
+ const actualSkills = readdirSync(skillRoot)
775
+ .filter((entry) => statSync(join(skillRoot, entry)).isDirectory())
776
+ .sort();
777
+
778
+ if (JSON.stringify(actualSkills) !== JSON.stringify(expectedSkills)) {
779
+ failures.push(`skill set mismatch: expected ${expectedSkills.join(", ")}, got ${actualSkills.join(", ")}`);
780
+ }
781
+
782
+ for (const skill of expectedSkills) {
783
+ const skillPath = join(skillRoot, skill, "SKILL.md");
784
+ const helpPath = join(skillRoot, skill, "help.md");
785
+ assertFile(skillPath, `${skill}/SKILL.md`);
786
+ assertFile(helpPath, `${skill}/help.md`);
787
+ if (!isFile(skillPath) || !isFile(helpPath)) continue;
788
+
789
+ const skillMd = read(skillPath);
790
+ const helpMd = read(helpPath);
791
+ const frontmatter = skillMd.match(/^---\r?\n([\s\S]*?)\r?\n---/);
792
+ if (!frontmatter) {
793
+ failures.push(`${skill}/SKILL.md missing YAML frontmatter`);
794
+ } else {
795
+ if (!frontmatter[1].includes(`name: ${skill}`)) failures.push(`${skill}/SKILL.md frontmatter name mismatch`);
796
+ const descriptionLine = frontmatter[1].split(/\r?\n/).find((line) => line.startsWith("description:"));
797
+ const descriptionValue = descriptionLine?.slice("description:".length).trim() ?? "";
798
+ const unquotedDescription = descriptionValue.replace(/^(["'])(.*)\1$/, "$2");
799
+ if (!/^Use this skill/i.test(unquotedDescription)) failures.push(`${skill}/SKILL.md description should be English and start with "Use this skill"`);
800
+ if (!/^["']/.test(descriptionValue) && descriptionValue.includes(": ")) {
801
+ failures.push(`${skill}/SKILL.md description contains an unquoted colon-space sequence that Claude Code rejects as YAML`);
802
+ }
803
+ }
804
+ if (!skillMd.includes("../../meta/contracts/")) {
805
+ failures.push(`${skill}/SKILL.md missing focused runtime contract module reference`);
806
+ }
807
+ if (skill === "gate-check") {
808
+ for (const required of [
809
+ "## Native Interaction Path",
810
+ "complete normative owner for interaction kinds, locale",
811
+ "Select exactly one run and evaluate its current gate",
812
+ "Confirm that the required durable artefact is present and ready",
813
+ "Revalidate the same run, gate and revision immediately after the response and before persistence",
814
+ "Persist only a currently valid exact approval",
815
+ ]) {
816
+ if (!skillMd.includes(required)) failures.push(`gate-check native interaction guidance missing: ${required}`);
817
+ }
818
+ if (!skillMd.includes("If `Approval: UR` is present, do not say implementation is the next step.")) {
819
+ failures.push("gate-check must prevent implementation immediately after Approval: UR");
820
+ }
821
+ if (skillMd.includes("## Gate Transitions") || skillMd.includes("## Gate Order") || skillMd.includes("| State | Current gate or step | Allowed | Forbidden | Missing approval |")) {
822
+ failures.push("gate-check must not duplicate the Runtime Contract gate transition table");
823
+ }
824
+ if (!skillMd.includes("The canonical gate order and transition model live only in `../../meta/contracts/gate-transition.md`")) {
825
+ failures.push("gate-check must point to gate-transition.md as gate transition SoT");
826
+ }
827
+ for (const label of ["Status", "Current gate", "Allowed now", "Blocked by", "Missing approval", "Next step", "Quality outlook"]) {
828
+ if (!skillMd.includes(`| ${label} |`)) failures.push(`gate-check must render Run Status Card label: ${label}`);
829
+ }
830
+ if (!skillMd.includes("Next gate after approval") || !skillMd.includes("Allowed after approval")) {
831
+ failures.push("gate-check must render post-approval Run Status Card fields when an approval is missing");
832
+ }
833
+ if (!skillMd.includes("A decision value without scope reason and evidence is still missing")) {
834
+ failures.push("gate-check must require evidenced Mode/Slice Decision before later work");
835
+ }
836
+ if (!skillMd.includes("Missing or incomplete control state must not push setup work back to the user")) {
837
+ failures.push("gate-check must make missing control state constructive for current artefact drafting");
838
+ }
839
+ if (!skillMd.includes("For a fresh request, draft the current minimal artefact in the response")) {
840
+ failures.push("gate-check must keep fresh missing-control requests lightweight");
841
+ }
842
+ if (!skillMd.includes("Do not write a full control scaffold unless durable control state was explicitly requested")) {
843
+ failures.push("gate-check must prevent full control scaffold writes by default");
844
+ }
845
+ if (!skillMd.includes("do not paste full file bodies into the chat")) {
846
+ failures.push("gate-check must keep control artefact content out of chat by default");
847
+ }
848
+ if (!skillMd.includes("requires a durable UR in `.agdf/control/` or a linked authoritative repository SoT")) {
849
+ failures.push("gate-check must require durable UR persistence for new product semantics or functional change");
850
+ }
851
+ if (!skillMd.includes("Approval text and durable artefact presence are separate requirements for UR, PRD, SD, TP and QA report decisions")) {
852
+ failures.push("gate-check must separate approval text from durable artefact presence for UR, PRD, SD, TP and QA report decisions");
853
+ }
854
+ if (!skillMd.includes("node <surface-local-agdf> delivery-map --json")) {
855
+ failures.push("gate-check must expose the machine-readable delivery-map command");
856
+ }
857
+ if (!skillMd.includes("This skill is the primary operating path for gate judgement")) {
858
+ failures.push("gate-check must state that the skill is the primary operating path before helper commands");
859
+ }
860
+ if (!skillMd.includes("not a required ritual for normal work")) {
861
+ failures.push("gate-check must state that CLI reports are not a required ritual for normal work");
862
+ }
863
+ if (!skillMd.includes("The CLI reports are validators and JSON evidence, not the primary user experience")) {
864
+ failures.push("gate-check must classify CLI reports as validators, not the primary workflow");
865
+ }
866
+ }
867
+
868
+ for (const [pathLabel, content] of [
869
+ [`${skill}/SKILL.md`, skillMd],
870
+ [`${skill}/help.md`, helpMd],
871
+ ]) {
872
+ const normalized = stripAllowedGerman(content);
873
+ for (const pattern of germanRuntimePatterns) {
874
+ if (pattern.test(normalized)) failures.push(`${pathLabel} contains German runtime wording matching ${pattern}`);
875
+ }
876
+ }
877
+ }
878
+
879
+ for (const [pathLabel, content] of [
880
+ ["plugin/meta/agdf-agent-router.md", read(agentRouterPath)],
881
+ ["plugin/meta/agdf-runtime-contract.md", read(runtimeContractPath)],
882
+ ["plugin/meta/contracts/*.md", readAllContracts()],
883
+ ]) {
884
+ const normalized = stripAllowedGerman(content);
885
+ for (const pattern of germanRuntimePatterns) {
886
+ if (pattern.test(normalized)) failures.push(`${pathLabel} contains German runtime wording matching ${pattern}`);
887
+ }
888
+ }
889
+
890
+ for (const relativePath of expectedControlFiles) {
891
+ assertFile(join(controlRoot, relativePath), `plugin/control/${relativePath}`);
892
+ }
893
+
894
+ const runTemplatePath = join(controlRoot, "templates", "AGDF_RUN.md");
895
+ const contextGraphTemplatePath = join(controlRoot, "templates", "CONTEXT_GRAPH.md");
896
+ const sotRegistryTemplatePath = join(controlRoot, "templates", "SOT_REGISTRY.md");
897
+ const qualityContractsPath = join(controlRoot, "templates", "AGENT_QUALITY_CONTRACTS.json");
898
+ const brownfieldReviewTemplatePath = join(controlRoot, "templates", "artefacts", "BROWNFIELD_REVIEW.md");
899
+ const verifiedChangeTemplatePath = join(controlRoot, "templates", "artefacts", "VERIFIED_CHANGE.md");
900
+ const orTemplatePath = join(controlRoot, "templates", "artefacts", "OR.md");
901
+ const backlogTemplatePath = join(controlRoot, "templates", "MASTER_BACKLOG.md");
902
+
903
+ if (isFile(runTemplatePath)) {
904
+ const runTemplate = read(runTemplatePath);
905
+ for (const required of ["current_gate", "next allowed action", "Missing Evidence", "Mode / Slice Decision", "transparency_note", "Artefact Chain", "context_graph_impact", "context_graph_reconciliation", "quality_outlook"]) {
906
+ if (!runTemplate.includes(required)) failures.push(`AGDF_RUN.md missing control field: ${required}`);
907
+ }
908
+ assertContextGraphReconciliation("AGDF_RUN.md template", runTemplate, { allowTemplatePlaceholders: true });
909
+ const runStatusCard = sectionAfterHeading(runTemplate, "Run Status Card");
910
+ for (const label of ["Status", "Current gate", "Allowed now", "Blocked by", "Missing approval", "Next step", "Quality outlook"]) {
911
+ if (!runStatusCard.includes(`| ${label} |`)) failures.push(`AGDF_RUN.md Run Status Card missing readable label: ${label}`);
912
+ }
913
+ for (const extraLabel of ["Mode", "Mode / slice", "Forbidden now", "Evidence", "Next skill"]) {
914
+ if (runStatusCard.includes(`| ${extraLabel} |`)) failures.push(`AGDF_RUN.md Run Status Card is not compact: ${extraLabel}`);
915
+ }
916
+ for (const rawField of ["allowed_now", "forbidden_now", "blocking_condition", "next_skill", "next_step", "quality_outlook"]) {
917
+ if (runStatusCard.includes(`- ${rawField}:`)) failures.push(`AGDF_RUN.md Run Status Card exposes raw field: ${rawField}`);
918
+ }
919
+ }
920
+
921
+ if (isFile(orTemplatePath)) {
922
+ const orTemplate = read(orTemplatePath);
923
+ const orStatusCard = sectionAfterHeading(orTemplate, "Run Status Card");
924
+ for (const label of ["Status", "Current gate", "Allowed now", "Blocked by", "Missing approval", "Next step", "Quality outlook"]) {
925
+ if (!orStatusCard.includes(`| ${label} |`)) failures.push(`OR.md Run Status Card missing readable label: ${label}`);
926
+ }
927
+ for (const extraLabel of ["Mode", "Mode / slice", "Forbidden now", "Evidence", "Next skill"]) {
928
+ if (orStatusCard.includes(`| ${extraLabel} |`)) failures.push(`OR.md Run Status Card is not compact: ${extraLabel}`);
929
+ }
930
+ for (const rawField of ["allowed_now", "forbidden_now", "blocking_condition", "next_skill", "next_step", "quality_outlook"]) {
931
+ if (orStatusCard.includes(`- ${rawField}:`)) failures.push(`OR.md Run Status Card exposes raw field: ${rawField}`);
932
+ }
933
+ if ((orTemplate.match(/Quality outlook/g) ?? []).length !== 1) {
934
+ failures.push("OR.md must present Quality outlook exactly once in the Run Status Card");
935
+ }
936
+ }
937
+
938
+ if (isFile(backlogTemplatePath)) {
939
+ const backlogTemplate = read(backlogTemplatePath);
940
+ for (const required of [
941
+ "| Priority | Key | Work item | Status | Artefacts | Current spec | Next step |",
942
+ "| Key | Work item | Final status | Historical record | Outcome |",
943
+ "document-relative Markdown link",
944
+ ]) {
945
+ if (!backlogTemplate.includes(required)) failures.push(`MASTER_BACKLOG.md missing human-readable backlog contract: ${required}`);
946
+ }
947
+ if (backlogTemplate.includes("| Prio | Key | Title | Status | UR |")) {
948
+ failures.push("MASTER_BACKLOG.md must not use the legacy wide layout as its canonical template");
949
+ }
950
+ }
951
+
952
+ if (isFile(contextGraphTemplatePath)) {
953
+ const contextGraphTemplate = read(contextGraphTemplatePath);
954
+ for (const required of ["Admission Rules", "exit criterion", "Relationship Language", "evidenced_by"]) {
955
+ if (!contextGraphTemplate.includes(required)) failures.push(`CONTEXT_GRAPH.md missing control concept: ${required}`);
956
+ }
957
+ }
958
+
959
+ if (isFile(sotRegistryTemplatePath)) {
960
+ const sotRegistryTemplate = read(sotRegistryTemplatePath);
961
+ if (!sotRegistryTemplate.includes("Each domain should have one primary owner")) {
962
+ failures.push("SOT_REGISTRY.md must state one primary owner per domain");
963
+ }
964
+ }
965
+
966
+ if (isFile(brownfieldReviewTemplatePath)) {
967
+ const brownfieldReviewTemplate = read(brownfieldReviewTemplatePath);
968
+ for (const required of ["Existing-System View", "Reuse And Parallel-Structure Risk", "Mode / Slice Decision", "transparency_note", "Next Permissible Step"]) {
969
+ if (!brownfieldReviewTemplate.includes(required)) failures.push(`BROWNFIELD_REVIEW.md missing control field: ${required}`);
970
+ }
971
+ }
972
+
973
+ if (isFile(verifiedChangeTemplatePath)) {
974
+ const verifiedChangeTemplate = read(verifiedChangeTemplatePath);
975
+ for (const required of ["related_ur", "canonical_owner", "allowed_source_paths", "allowed_derived_paths", "baseline_tracked_paths", "baseline_untracked_paths", "escalation_target", "validation_commands"]) {
976
+ if (!new RegExp(`^- ${required}:`, "m").test(verifiedChangeTemplate)) failures.push(`VERIFIED_CHANGE.md missing control field: ${required}`);
977
+ }
978
+ if (!/^## Mini-Closeout$/m.test(verifiedChangeTemplate)) failures.push("VERIFIED_CHANGE.md missing control field: Mini-Closeout");
979
+ }
980
+
981
+ if (isFile(orTemplatePath)) {
982
+ const orTemplate = read(orTemplatePath);
983
+ for (const required of ["Report mode", "OR-lite | OR-full", "Next Permissible Step", "Context Graph Impact", "context_graph_reconciliation", "OR does not approve later gates"]) {
984
+ if (!orTemplate.includes(required)) failures.push(`OR.md missing control field: ${required}`);
985
+ }
986
+ assertContextGraphReconciliation("OR.md template", orTemplate, { allowTemplatePlaceholders: true });
987
+ }
988
+
989
+ if (isFile(activeRunStatePath)) {
990
+ assertContextGraphReconciliation(".agdf/control/AGDF_RUN.md", read(activeRunStatePath));
991
+ }
992
+
993
+ assertContextGraphReconciliation("Context Graph open-gap fixture", [
994
+ "- context_graph_impact: link_only",
995
+ "- context_graph_refs: none",
996
+ "- context_graph_reconciliation: open_gap",
997
+ "- context_graph_required_action: promote after UAT",
998
+ ].join("\n"));
999
+
1000
+ const fixtureFailureCount = failures.length;
1001
+ assertContextGraphReconciliation("Context Graph invalid fixture", [
1002
+ "- context_graph_impact: link_only",
1003
+ "- context_graph_refs: none",
1004
+ "- context_graph_reconciliation: not_applicable",
1005
+ "- context_graph_required_action: promote after UAT",
1006
+ ].join("\n"));
1007
+ const invalidFixtureFailure = failures.pop();
1008
+ if (!invalidFixtureFailure?.includes("Context Graph invalid fixture")) {
1009
+ failures.push("Context Graph invalid fixture did not fail as expected");
1010
+ }
1011
+ if (failures.length !== fixtureFailureCount) {
1012
+ failures.push("Context Graph invalid fixture changed unrelated failure state");
1013
+ }
1014
+
1015
+ const qualityContracts = isFile(qualityContractsPath) ? readJson(qualityContractsPath, "AGDF control quality contracts") : null;
1016
+ if (qualityContracts) {
1017
+ const contracts = Array.isArray(qualityContracts.contracts) ? qualityContracts.contracts : [];
1018
+ const requiredCodes = ["AGDF_GATE_BYPASS", "AGDF_QA_WITHOUT_TASK_PLAN_REVIEW", "AGDF_PARALLEL_SOT"];
1019
+ for (const code of requiredCodes) {
1020
+ if (!contracts.some((contract) => contract?.code === code)) {
1021
+ failures.push(`AGENT_QUALITY_CONTRACTS.json missing ${code}`);
1022
+ }
1023
+ }
1024
+ for (const contract of contracts) {
1025
+ if (!["block", "revise", "warn"].includes(contract?.impact)) {
1026
+ failures.push(`AGENT_QUALITY_CONTRACTS.json contract ${contract?.code ?? "<unknown>"} has invalid impact`);
1027
+ }
1028
+ if (!Array.isArray(contract?.required_evidence) || contract.required_evidence.length === 0) {
1029
+ failures.push(`AGENT_QUALITY_CONTRACTS.json contract ${contract?.code ?? "<unknown>"} missing required evidence`);
1030
+ }
1031
+ }
1032
+ }
1033
+
1034
+ if (failures.length > 0) {
1035
+ console.error("[agdf-runtime-integrity] FAIL");
1036
+ for (const failure of failures) console.error(`- ${failure}`);
1037
+ process.exit(1);
1038
+ }
1039
+
1040
+ console.log(`[agdf-runtime-integrity] ok (mode=${validationMode}; ${expectedSkills.length} skills and ${expectedControlFiles.length} control files checked)`);