create-agdf 0.13.1 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/generated/.agdf/control/templates/RUN_STATE.md +22 -0
  2. package/generated/.agdf/control/templates/artefacts/OR.md +22 -0
  3. package/generated/.github/skills/agdf-delivery-closeout/SKILL.md +7 -0
  4. package/generated/.github/skills/agdf-interaction-locales.json +2 -4
  5. package/generated/.github/skills/agdf-release-or/SKILL.md +14 -4
  6. package/generated/.github/skills/contracts/closeout.md +49 -0
  7. package/generated/.github/skills/contracts/control-scaffold.md +2 -0
  8. package/generated/.github/skills/contracts/interaction.md +23 -12
  9. package/generated/.opencode/agdf-interaction-locales.json +2 -4
  10. package/generated/.opencode/contracts/closeout.md +49 -0
  11. package/generated/.opencode/contracts/control-scaffold.md +2 -0
  12. package/generated/.opencode/contracts/interaction.md +23 -12
  13. package/generated/.opencode/skills/agdf-delivery-closeout/SKILL.md +7 -0
  14. package/generated/.opencode/skills/agdf-release-or/SKILL.md +14 -4
  15. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  16. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  17. package/generated/plugins/agdf/control/templates/RUN_STATE.md +22 -0
  18. package/generated/plugins/agdf/control/templates/artefacts/OR.md +22 -0
  19. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +2 -4
  20. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  21. package/generated/plugins/agdf/meta/agent-skills-conformance.json +86 -0
  22. package/generated/plugins/agdf/meta/contracts/closeout.md +49 -0
  23. package/generated/plugins/agdf/meta/contracts/control-scaffold.md +2 -0
  24. package/generated/plugins/agdf/meta/contracts/interaction.md +23 -12
  25. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +2 -4
  26. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  27. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/delivery-map.js +13 -3
  28. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +9 -2
  29. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +6 -1
  30. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +16 -1
  31. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/parent-reconciliation.js +284 -0
  32. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +39 -1
  33. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +31 -14
  34. package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
  35. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  36. package/generated/plugins/agdf/scripts/agent-skills-conformance.mjs +543 -0
  37. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +31 -17
  38. package/generated/plugins/agdf/skills/delivery-closeout/SKILL.md +7 -0
  39. package/generated/plugins/agdf/skills/release-or/SKILL.md +14 -4
  40. package/generated/plugins/agdf/submission/openai/availability.md +1 -1
  41. package/generated/plugins/agdf/submission/openai/capability-matrix.json +2 -2
  42. package/generated/plugins/agdf/submission/openai/release-notes.md +1 -1
  43. package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
  44. package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
  45. package/generated/submissions/openai/agdf/meta/agdf-interaction-locales.json +2 -4
  46. package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +1 -1
  47. package/generated/submissions/openai/agdf/meta/agent-skills-conformance.json +86 -0
  48. package/generated/submissions/openai/agdf/meta/contracts/closeout.md +49 -0
  49. package/generated/submissions/openai/agdf/meta/contracts/control-scaffold.md +2 -0
  50. package/generated/submissions/openai/agdf/meta/contracts/interaction.md +23 -12
  51. package/generated/submissions/openai/agdf/skills/delivery-closeout/SKILL.md +7 -0
  52. package/generated/submissions/openai/agdf/skills/release-or/SKILL.md +14 -4
  53. package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
  54. package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +2 -2
  55. package/generated/submissions/openai/agdf/submission/openai/inventory.json +19 -15
  56. package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
  57. package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
  58. package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
  59. package/generated/submissions/openai/agdf/submission/openai/release-notes.md +1 -1
  60. package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
  61. package/lib/control-evaluation/delivery-map.js +13 -3
  62. package/lib/control-evaluation/doctor.js +9 -2
  63. package/lib/control-evaluation/gate-check.js +6 -1
  64. package/lib/control-evaluation/gate-policy.js +16 -1
  65. package/lib/control-evaluation/parent-reconciliation.js +284 -0
  66. package/lib/control-state/run-state-parser.js +39 -1
  67. package/lib/interaction-presentation.js +31 -14
  68. package/lib/proportionality-benchmark/blind-prompt.js +2 -1
  69. package/lib/proportionality-benchmark/contracts.js +16 -7
  70. package/lib/proportionality-benchmark/corpus-loader.js +132 -21
  71. package/lib/proportionality-benchmark/evaluator.js +25 -15
  72. package/lib/proportionality-benchmark/index.js +1 -0
  73. package/lib/proportionality-benchmark/live-recorder.js +32 -10
  74. package/lib/proportionality-benchmark/profiles.js +102 -0
  75. package/lib/proportionality-benchmark/report.js +10 -1
  76. package/lib/proportionality-benchmark/source-fingerprint.js +6 -2
  77. package/lib/release/version-coherence.js +79 -0
  78. package/package.json +7 -3
@@ -0,0 +1,543 @@
1
+ import {
2
+ existsSync,
3
+ readFileSync,
4
+ readdirSync,
5
+ realpathSync,
6
+ statSync,
7
+ } from "node:fs";
8
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
9
+
10
+ const FINDING_ORDER = ["code", "classification", "severity", "skillPath", "resource", "message", "remediation"];
11
+ const VALID_CLASSIFICATIONS = new Set(["standard_strict", "upstream_advisory", "agdf_policy"]);
12
+ const RELATIVE_MARKDOWN_TOKEN = /`((?:\.\.?\/)+[^`\r\n]+\.md)`/g;
13
+ const RESOURCE_BULLET = /^\s*-\s+`([^`\r\n]+\.md)`\s*$/;
14
+
15
+ function readJson(path) {
16
+ return JSON.parse(readFileSync(path, "utf8"));
17
+ }
18
+
19
+ function isFile(path) {
20
+ try { return statSync(path).isFile(); } catch { return false; }
21
+ }
22
+
23
+ function isDirectory(path) {
24
+ try { return statSync(path).isDirectory(); } catch { return false; }
25
+ }
26
+
27
+ function codePointLength(value) {
28
+ return [...value].length;
29
+ }
30
+
31
+ function within(root, candidate) {
32
+ const path = relative(root, candidate);
33
+ return path === "" || (!path.startsWith(`..${sep}`) && path !== ".." && !isAbsolute(path));
34
+ }
35
+
36
+ function stablePath(root, path) {
37
+ const value = relative(root, path).replaceAll("\\", "/");
38
+ return value || ".";
39
+ }
40
+
41
+ function finding({ code, classification, severity = "error", skillPath = "", resource = "", message, remediation }) {
42
+ return Object.fromEntries(FINDING_ORDER.map((key) => [key, ({
43
+ code,
44
+ classification,
45
+ severity,
46
+ skillPath,
47
+ resource,
48
+ message,
49
+ remediation,
50
+ })[key]]));
51
+ }
52
+
53
+ function policyFinding(code, message, remediation = "Correct the canonical Agent Skills conformance policy.") {
54
+ return finding({ code, classification: "agdf_policy", message, remediation });
55
+ }
56
+
57
+ function validatePolicy(policy) {
58
+ const findings = [];
59
+ if (policy?.schemaVersion !== 1) findings.push(policyFinding("AGDF_SKILL_POLICY_SCHEMA_UNSUPPORTED", "Conformance policy schemaVersion must be 1."));
60
+ if (typeof policy?.policyVersion !== "string" || !policy.policyVersion) findings.push(policyFinding("AGDF_SKILL_POLICY_VERSION_MISSING", "Conformance policyVersion must be a non-empty string."));
61
+ if (policy?.baseline?.id !== "agent-skills-core-format" || !policy?.baseline?.specificationUrl || !policy?.baseline?.reviewedAt) {
62
+ findings.push(policyFinding("AGDF_SKILL_POLICY_BASELINE_INVALID", "Conformance policy must declare the reviewed Agent Skills core-format baseline."));
63
+ }
64
+ if (!Array.isArray(policy?.classifications)
65
+ || policy.classifications.length !== VALID_CLASSIFICATIONS.size
66
+ || new Set(policy.classifications).size !== VALID_CLASSIFICATIONS.size
67
+ || policy.classifications.some((value) => !VALID_CLASSIFICATIONS.has(value))) {
68
+ findings.push(policyFinding("AGDF_SKILL_POLICY_CLASSIFICATIONS_INVALID", "Conformance policy must declare the three supported finding classifications exactly once."));
69
+ }
70
+ for (const field of ["requiredFields", "allowedFields", "scalarStyles"]) {
71
+ if (!Array.isArray(policy?.frontmatterProfile?.[field]) || policy.frontmatterProfile[field].length === 0) {
72
+ findings.push(policyFinding("AGDF_SKILL_POLICY_FRONTMATTER_PROFILE_INVALID", `frontmatterProfile.${field} must be a non-empty array.`));
73
+ }
74
+ }
75
+ try {
76
+ new RegExp(policy?.strictRules?.namePattern);
77
+ } catch {
78
+ findings.push(policyFinding("AGDF_SKILL_POLICY_NAME_PATTERN_INVALID", "strictRules.namePattern must be a valid regular expression."));
79
+ }
80
+ if (!Number.isInteger(policy?.strictRules?.nameMaxCodePoints) || policy.strictRules.nameMaxCodePoints < 1
81
+ || !Number.isInteger(policy?.strictRules?.descriptionMaxCodePoints) || policy.strictRules.descriptionMaxCodePoints < 1
82
+ || !Number.isInteger(policy?.advisoryRules?.skillMdMaxLines) || policy.advisoryRules.skillMdMaxLines < 1) {
83
+ findings.push(policyFinding("AGDF_SKILL_POLICY_LIMIT_INVALID", "Strict and advisory limits must be positive integers."));
84
+ }
85
+ if (typeof policy?.agdfRules?.descriptionStartsWith !== "string" || !policy.agdfRules.descriptionStartsWith
86
+ || policy?.agdfRules?.requiresRuntimeContractResource !== true) {
87
+ findings.push(policyFinding("AGDF_SKILL_POLICY_LOCAL_RULES_INVALID", "AGDF-owned description and Runtime Contract rules must be explicit."));
88
+ }
89
+ if (!policy?.surfaces || typeof policy.surfaces !== "object" || Array.isArray(policy.surfaces)) {
90
+ findings.push(policyFinding("AGDF_SKILL_POLICY_SURFACES_INVALID", "Conformance policy must declare surface descriptors."));
91
+ }
92
+ return findings;
93
+ }
94
+
95
+ function parseScalar(raw) {
96
+ if (raw === "") return { ok: true, value: "", style: "plain" };
97
+ if (raw.startsWith('"')) {
98
+ if (!raw.endsWith('"') || raw.length < 2) return { ok: false };
99
+ try {
100
+ const value = JSON.parse(raw);
101
+ return typeof value === "string" ? { ok: true, value, style: "double_quoted" } : { ok: false };
102
+ } catch {
103
+ return { ok: false };
104
+ }
105
+ }
106
+ if (raw.startsWith("'")) {
107
+ if (!raw.endsWith("'") || raw.length < 2) return { ok: false };
108
+ const inner = raw.slice(1, -1);
109
+ if (/(^|[^'])'(?!')/.test(inner)) return { ok: false };
110
+ return { ok: true, value: inner.replaceAll("''", "'"), style: "single_quoted" };
111
+ }
112
+ if (/^[#\[\]{}&*!|>@`]/.test(raw)
113
+ || /^(?:~|null|true|false)$/i.test(raw)
114
+ || /^[-+]?(?:0|[1-9][0-9_]*)(?:\.[0-9_]+)?(?:e[-+]?[0-9]+)?$/i.test(raw)
115
+ || /^0(?:x[0-9a-f_]+|o[0-7_]+)$/i.test(raw)
116
+ || /^[-+]?\.(?:inf|nan)$/i.test(raw)
117
+ || raw.includes(": ")
118
+ || raw.includes(" #")) return { ok: false };
119
+ return { ok: true, value: raw, style: "plain" };
120
+ }
121
+
122
+ function parseFrontmatter(content, skillPath, policy) {
123
+ const findings = [];
124
+ const lines = content.split(/\r?\n/);
125
+ if (lines[0] !== "---") {
126
+ findings.push(finding({
127
+ code: "AGENT_SKILLS_FRONTMATTER_MISSING",
128
+ classification: "standard_strict",
129
+ skillPath,
130
+ message: "SKILL.md must begin with YAML frontmatter.",
131
+ remediation: "Add an opening and closing --- frontmatter block at the start of SKILL.md.",
132
+ }));
133
+ return { fields: new Map(), findings };
134
+ }
135
+ const end = lines.indexOf("---", 1);
136
+ if (end < 0) {
137
+ findings.push(finding({
138
+ code: "AGENT_SKILLS_FRONTMATTER_UNCLOSED",
139
+ classification: "standard_strict",
140
+ skillPath,
141
+ message: "SKILL.md frontmatter has no closing delimiter.",
142
+ remediation: "Close the initial YAML frontmatter block with an exact --- line.",
143
+ }));
144
+ return { fields: new Map(), findings };
145
+ }
146
+
147
+ const fields = new Map();
148
+ const allowedFields = new Set(policy.frontmatterProfile.allowedFields);
149
+ const allowedStyles = new Set(policy.frontmatterProfile.scalarStyles);
150
+ for (const line of lines.slice(1, end)) {
151
+ if (line === "" || /^\s*#/.test(line)) continue;
152
+ const match = line.match(/^([A-Za-z][A-Za-z0-9_-]*):(?:\s+(.*)|\s*)$/);
153
+ if (!match) {
154
+ findings.push(finding({
155
+ code: "AGDF_SKILL_FRONTMATTER_PROFILE_UNSUPPORTED",
156
+ classification: "agdf_policy",
157
+ skillPath,
158
+ message: "Frontmatter uses syntax outside the dependency-free AGDF scalar profile.",
159
+ remediation: "Use unique, unindented top-level scalar fields with plain, single-quoted, or double-quoted values.",
160
+ }));
161
+ continue;
162
+ }
163
+ const [, key, rawValue = ""] = match;
164
+ if (fields.has(key)) {
165
+ findings.push(finding({
166
+ code: "AGDF_SKILL_FRONTMATTER_DUPLICATE_FIELD",
167
+ classification: "agdf_policy",
168
+ skillPath,
169
+ message: `Frontmatter field ${key} is declared more than once.`,
170
+ remediation: `Keep exactly one ${key} field.`,
171
+ }));
172
+ continue;
173
+ }
174
+ if (!allowedFields.has(key)) {
175
+ findings.push(finding({
176
+ code: "AGDF_SKILL_FRONTMATTER_FIELD_UNSUPPORTED",
177
+ classification: "agdf_policy",
178
+ skillPath,
179
+ message: `Frontmatter field ${key} is outside the approved AGDF profile.`,
180
+ remediation: "Remove the field or revise the AGDF conformance policy through governance.",
181
+ }));
182
+ }
183
+ const parsed = parseScalar(rawValue.trim());
184
+ if (!parsed.ok || !allowedStyles.has(parsed.style)) {
185
+ findings.push(finding({
186
+ code: "AGDF_SKILL_FRONTMATTER_SCALAR_INVALID",
187
+ classification: "agdf_policy",
188
+ skillPath,
189
+ message: `Frontmatter field ${key} is not a valid supported scalar.`,
190
+ remediation: "Use a valid plain, single-quoted, or JSON-compatible double-quoted scalar.",
191
+ }));
192
+ continue;
193
+ }
194
+ fields.set(key, parsed.value);
195
+ }
196
+ return { fields, findings };
197
+ }
198
+
199
+ function runtimeContractSection(content) {
200
+ const heading = content.match(/^## Runtime Contract\s*\r?\n/m);
201
+ if (!heading || heading.index === undefined) return "";
202
+ const body = content.slice(heading.index + heading[0].length);
203
+ const nextHeading = body.search(/^##\s/m);
204
+ return nextHeading < 0 ? body : body.slice(0, nextHeading);
205
+ }
206
+
207
+ function declaredResources(content) {
208
+ const section = runtimeContractSection(content);
209
+ const bulletResources = section.split(/\r?\n/).map((line) => line.match(RESOURCE_BULLET)?.[1]).filter(Boolean);
210
+ return [...new Set([...dependencyTokens(section), ...bulletResources])];
211
+ }
212
+
213
+ function dependencyTokens(content) {
214
+ return [...content.matchAll(RELATIVE_MARKDOWN_TOKEN)].map((match) => match[1]);
215
+ }
216
+
217
+ function withoutFencedCode(content) {
218
+ return content
219
+ .replace(/^[ \t]{0,3}`{3,}[^\r\n]*\r?\n[\s\S]*?^[ \t]{0,3}`{3,}\s*$/gm, "")
220
+ .replace(/^[ \t]{0,3}~{3,}[^\r\n]*\r?\n[\s\S]*?^[ \t]{0,3}~{3,}\s*$/gm, "");
221
+ }
222
+
223
+ function validateResource({ token, skillDir, skillPath, surfaceRoot, sharedRoots }) {
224
+ const base = { skillPath, resource: token };
225
+ if (!token || isAbsolute(token) || /^[a-z][a-z0-9+.-]*:/i.test(token) || token.includes("\\") || token.split("/").includes("")) {
226
+ return { finding: finding({
227
+ ...base,
228
+ code: "AGDF_SKILL_RESOURCE_PATH_UNSAFE",
229
+ classification: "agdf_policy",
230
+ message: "Declared resource path is not a safe portable relative path.",
231
+ remediation: "Use a forward-slash relative path within the declared surface boundary.",
232
+ }) };
233
+ }
234
+ const target = resolve(skillDir, token);
235
+ if (!within(surfaceRoot, target)) {
236
+ return { finding: finding({
237
+ ...base,
238
+ code: "AGDF_SKILL_RESOURCE_TRAVERSAL",
239
+ classification: "agdf_policy",
240
+ message: "Declared resource escapes the surface root lexically.",
241
+ remediation: "Keep the resource inside the skill root or an approved shared resource root.",
242
+ }) };
243
+ }
244
+ if (!isFile(target)) {
245
+ return { finding: finding({
246
+ ...base,
247
+ code: "AGDF_SKILL_RESOURCE_UNRESOLVED",
248
+ classification: "agdf_policy",
249
+ message: "Declared resource does not resolve to a file.",
250
+ remediation: "Correct the resource path or restore the canonical file.",
251
+ }) };
252
+ }
253
+ let physicalSurfaceRoot;
254
+ let physicalSkillRoot;
255
+ let physicalTarget;
256
+ let physicalSharedRoots;
257
+ try {
258
+ physicalSurfaceRoot = realpathSync(surfaceRoot);
259
+ physicalSkillRoot = realpathSync(skillDir);
260
+ physicalTarget = realpathSync(target);
261
+ physicalSharedRoots = sharedRoots.map((path) => realpathSync(path));
262
+ } catch {
263
+ return { finding: finding({
264
+ ...base,
265
+ code: "AGDF_SKILL_RESOURCE_PHYSICAL_RESOLUTION_FAILED",
266
+ classification: "agdf_policy",
267
+ message: "Declared resource boundary could not be resolved physically.",
268
+ remediation: "Restore readable surface, skill and shared-root paths, then rerun validation.",
269
+ }) };
270
+ }
271
+ if (!within(physicalSurfaceRoot, physicalTarget)) {
272
+ return { finding: finding({
273
+ ...base,
274
+ code: "AGDF_SKILL_RESOURCE_SYMLINK_ESCAPE",
275
+ classification: "agdf_policy",
276
+ message: "Declared resource resolves outside the surface root.",
277
+ remediation: "Remove the escaping symlink or point at a file inside the approved surface.",
278
+ }) };
279
+ }
280
+ if (within(physicalSkillRoot, physicalTarget)) return { classification: "skill_local" };
281
+ for (const sharedRoot of physicalSharedRoots) {
282
+ if (within(sharedRoot, physicalTarget)) return { classification: "plugin_scoped" };
283
+ }
284
+ return { finding: finding({
285
+ ...base,
286
+ code: "AGDF_SKILL_RESOURCE_SCOPE_UNDECLARED",
287
+ classification: "agdf_policy",
288
+ message: "Declared resource is outside the skill and every approved shared root.",
289
+ remediation: "Move the resource into an approved root or revise the single conformance policy owner.",
290
+ }) };
291
+ }
292
+
293
+ function loadInputs({ pluginRoot, policyPath, definitionPath }) {
294
+ const findings = [];
295
+ let policy = null;
296
+ let definition = null;
297
+ try {
298
+ policy = readJson(policyPath ?? join(pluginRoot, "meta", "agent-skills-conformance.json"));
299
+ } catch {
300
+ findings.push(policyFinding("AGDF_SKILL_POLICY_UNREADABLE", "Conformance policy must be present and readable JSON."));
301
+ }
302
+ try {
303
+ definition = readJson(definitionPath ?? join(pluginRoot, "meta", "agdf-plugin.definition.json"));
304
+ } catch {
305
+ findings.push(policyFinding("AGDF_SKILL_DEFINITION_UNREADABLE", "Canonical plugin definition must be present and readable JSON.", "Restore the canonical plugin definition."));
306
+ }
307
+ if (policy) findings.push(...validatePolicy(policy));
308
+ if (!Array.isArray(definition?.skillSet) || definition.skillSet.length === 0) {
309
+ findings.push(policyFinding("AGDF_SKILL_INVENTORY_INVALID", "Canonical plugin definition must own a non-empty skillSet.", "Correct agdf-plugin.definition.json instead of adding another inventory."));
310
+ }
311
+ return { policy, definition, findings };
312
+ }
313
+
314
+ export function validateAgentSkillsConformance({ pluginRoot, surfaceRoot = pluginRoot, surface = "source", policyPath, definitionPath } = {}) {
315
+ pluginRoot = resolve(pluginRoot ?? ".");
316
+ surfaceRoot = resolve(surfaceRoot ?? pluginRoot);
317
+ const { policy, definition, findings } = loadInputs({ pluginRoot, policyPath, definitionPath });
318
+ const result = {
319
+ surface,
320
+ policyVersion: policy?.policyVersion ?? "unknown",
321
+ inspectedSkillCount: 0,
322
+ findings,
323
+ skills: [],
324
+ blocking: true,
325
+ };
326
+ if (findings.some(({ severity }) => severity === "error")) return result;
327
+
328
+ const descriptor = policy.surfaces[surface];
329
+ if (!descriptor || typeof descriptor.skillsRoot !== "string"
330
+ || !Array.isArray(descriptor.sharedResourceRoots)
331
+ || typeof descriptor.skillPrefixOwner !== "string"
332
+ || !Array.isArray(descriptor.ignoredSkillEntries)) {
333
+ findings.push(policyFinding("AGDF_SKILL_SURFACE_DESCRIPTOR_INVALID", `Surface descriptor ${surface} is missing or invalid.`));
334
+ return result;
335
+ }
336
+ if (!isDirectory(surfaceRoot)) {
337
+ findings.push(policyFinding("AGDF_SKILL_SURFACE_ROOT_INVALID", `Surface root for ${surface} is not a directory.`));
338
+ return result;
339
+ }
340
+ const skillsRoot = resolve(surfaceRoot, descriptor.skillsRoot);
341
+ const sharedRoots = descriptor.sharedResourceRoots.map((path) => resolve(surfaceRoot, path));
342
+ if (!within(surfaceRoot, skillsRoot) || sharedRoots.some((path) => !within(surfaceRoot, path))) {
343
+ findings.push(policyFinding("AGDF_SKILL_SURFACE_BOUNDARY_INVALID", `Surface descriptor ${surface} escapes its root.`));
344
+ return result;
345
+ }
346
+ if (!isDirectory(skillsRoot) || sharedRoots.some((path) => !isDirectory(path))) {
347
+ findings.push(policyFinding("AGDF_SKILL_SURFACE_LAYOUT_MISSING", `Surface ${surface} is missing its skill or shared resource root.`));
348
+ return result;
349
+ }
350
+ const allowedIgnoredEntries = new Set(sharedRoots.map((path) => relative(skillsRoot, path).split(sep)[0]).filter((entry) => entry && entry !== ".."));
351
+ if (new Set(descriptor.ignoredSkillEntries).size !== descriptor.ignoredSkillEntries.length
352
+ || descriptor.ignoredSkillEntries.some((entry) => !allowedIgnoredEntries.has(entry))) {
353
+ findings.push(policyFinding("AGDF_SKILL_SURFACE_IGNORES_INVALID", `Surface ${surface} may ignore only declared shared-root entries.`));
354
+ return result;
355
+ }
356
+
357
+ const prefix = definition?.[descriptor.skillPrefixOwner]?.skillPrefix;
358
+ if (typeof prefix !== "string") {
359
+ findings.push(policyFinding("AGDF_SKILL_SURFACE_PREFIX_INVALID", `Surface ${surface} cannot derive its skill prefix from the canonical plugin definition.`));
360
+ return result;
361
+ }
362
+ const expected = definition.skillSet.map(({ slug }) => `${prefix}${slug}`).sort();
363
+ if (new Set(expected).size !== expected.length || expected.some((name) => !name)) {
364
+ findings.push(policyFinding("AGDF_SKILL_INVENTORY_DUPLICATE", "Canonical skill inventory produces empty or duplicate surface names.", "Correct agdf-plugin.definition.json."));
365
+ return result;
366
+ }
367
+ const ignored = new Set(descriptor.ignoredSkillEntries);
368
+ let skillEntries;
369
+ try {
370
+ skillEntries = readdirSync(skillsRoot);
371
+ } catch {
372
+ findings.push(policyFinding("AGDF_SKILL_SURFACE_SKILLS_UNREADABLE", `Surface ${surface} skill root cannot be read.`));
373
+ return result;
374
+ }
375
+ const actual = skillEntries
376
+ .filter((entry) => !ignored.has(entry) && isDirectory(join(skillsRoot, entry)))
377
+ .sort();
378
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
379
+ findings.push(finding({
380
+ code: "AGENT_SKILLS_INVENTORY_MISMATCH",
381
+ classification: "standard_strict",
382
+ message: `Surface ${surface} skill directories do not match the canonical inventory. Expected ${expected.join(", ")}; got ${actual.join(", ")}.`,
383
+ remediation: "Align the canonical plugin definition, source skills and generated projection.",
384
+ }));
385
+ }
386
+
387
+ for (const expectedName of expected) {
388
+ const skillDir = join(skillsRoot, expectedName);
389
+ const path = join(skillDir, "SKILL.md");
390
+ const skillPath = stablePath(surfaceRoot, path);
391
+ if (existsSync(skillDir)) {
392
+ try {
393
+ if (!within(realpathSync(surfaceRoot), realpathSync(skillDir))) {
394
+ findings.push(finding({
395
+ code: "AGDF_SKILL_DIRECTORY_SYMLINK_ESCAPE",
396
+ classification: "agdf_policy",
397
+ skillPath,
398
+ message: "Canonical skill directory resolves outside the surface root.",
399
+ remediation: "Replace the escaping symlink with a skill directory inside the approved surface.",
400
+ }));
401
+ continue;
402
+ }
403
+ } catch {
404
+ findings.push(finding({
405
+ code: "AGDF_SKILL_DIRECTORY_UNREADABLE",
406
+ classification: "agdf_policy",
407
+ skillPath,
408
+ message: "Canonical skill directory cannot be resolved physically.",
409
+ remediation: "Restore a readable skill directory inside the approved surface.",
410
+ }));
411
+ continue;
412
+ }
413
+ }
414
+ if (!isFile(path)) {
415
+ findings.push(finding({
416
+ code: "AGENT_SKILLS_FILE_MISSING",
417
+ classification: "standard_strict",
418
+ skillPath,
419
+ message: "Canonical skill directory must contain SKILL.md.",
420
+ remediation: "Restore the canonical SKILL.md file.",
421
+ }));
422
+ continue;
423
+ }
424
+ result.inspectedSkillCount += 1;
425
+ let content;
426
+ try {
427
+ content = readFileSync(path, "utf8");
428
+ } catch {
429
+ findings.push(finding({
430
+ code: "AGENT_SKILLS_FILE_UNREADABLE",
431
+ classification: "standard_strict",
432
+ skillPath,
433
+ message: "Canonical SKILL.md cannot be read.",
434
+ remediation: "Restore a readable canonical SKILL.md file.",
435
+ }));
436
+ continue;
437
+ }
438
+ const parsed = parseFrontmatter(content, skillPath, policy);
439
+ findings.push(...parsed.findings);
440
+ for (const field of policy.frontmatterProfile.requiredFields) {
441
+ if (!parsed.fields.has(field) || parsed.fields.get(field) === "") {
442
+ findings.push(finding({
443
+ code: `AGENT_SKILLS_${field.toUpperCase()}_MISSING`,
444
+ classification: "standard_strict",
445
+ skillPath,
446
+ message: `Required frontmatter field ${field} is missing or empty.`,
447
+ remediation: `Add a non-empty ${field} scalar to the frontmatter.`,
448
+ }));
449
+ }
450
+ }
451
+ const name = parsed.fields.get("name") ?? "";
452
+ const description = parsed.fields.get("description") ?? "";
453
+ let namePattern = null;
454
+ try { namePattern = new RegExp(policy.strictRules.namePattern); } catch {}
455
+ if (name && (codePointLength(name) > policy.strictRules.nameMaxCodePoints || !namePattern?.test(name))) {
456
+ findings.push(finding({
457
+ code: "AGENT_SKILLS_NAME_INVALID",
458
+ classification: "standard_strict",
459
+ skillPath,
460
+ message: "Skill name violates the declared Agent Skills name constraints.",
461
+ remediation: "Use 1-64 lowercase alphanumeric or single-hyphen-separated characters.",
462
+ }));
463
+ }
464
+ if (name && policy.strictRules.nameMustMatchParent && name !== expectedName) {
465
+ findings.push(finding({
466
+ code: "AGENT_SKILLS_NAME_PARENT_MISMATCH",
467
+ classification: "standard_strict",
468
+ skillPath,
469
+ message: `Skill name ${name} does not match parent directory ${expectedName}.`,
470
+ remediation: "Align the frontmatter name and canonical surface directory.",
471
+ }));
472
+ }
473
+ if (description && codePointLength(description) > policy.strictRules.descriptionMaxCodePoints) {
474
+ findings.push(finding({
475
+ code: "AGENT_SKILLS_DESCRIPTION_INVALID",
476
+ classification: "standard_strict",
477
+ skillPath,
478
+ message: "Skill description exceeds the declared Agent Skills limit.",
479
+ remediation: "Shorten description to at most 1024 Unicode code points.",
480
+ }));
481
+ }
482
+ if (description && !description.toLowerCase().startsWith(policy.agdfRules.descriptionStartsWith.toLowerCase())) {
483
+ findings.push(finding({
484
+ code: "AGDF_SKILL_DESCRIPTION_PREFIX_INVALID",
485
+ classification: "agdf_policy",
486
+ skillPath,
487
+ message: `Skill description must start with ${policy.agdfRules.descriptionStartsWith}.`,
488
+ remediation: "Restore the AGDF discovery-oriented description convention.",
489
+ }));
490
+ }
491
+ const lineCount = content.split(/\r?\n/).length;
492
+ if (lineCount > policy.advisoryRules.skillMdMaxLines) {
493
+ findings.push(finding({
494
+ code: "AGENT_SKILLS_LENGTH_ADVISORY",
495
+ classification: "upstream_advisory",
496
+ severity: "warning",
497
+ skillPath,
498
+ message: `SKILL.md has ${lineCount} lines; upstream guidance recommends at most ${policy.advisoryRules.skillMdMaxLines}.`,
499
+ remediation: "Consider moving detail into linked resources; no strict conformance failure is asserted.",
500
+ }));
501
+ }
502
+
503
+ const declarations = declaredResources(content);
504
+ if (policy.agdfRules.requiresRuntimeContractResource && declarations.length === 0) {
505
+ findings.push(finding({
506
+ code: "AGDF_SKILL_RUNTIME_CONTRACT_RESOURCE_MISSING",
507
+ classification: "agdf_policy",
508
+ skillPath,
509
+ message: "AGDF skill must declare at least one focused Runtime Contract resource.",
510
+ remediation: "Declare the applicable focused contract module in the Runtime Contract section.",
511
+ }));
512
+ }
513
+ const declarationSet = new Set(declarations);
514
+ for (const token of new Set(dependencyTokens(withoutFencedCode(content)))) {
515
+ if (!declarationSet.has(token)) {
516
+ findings.push(finding({
517
+ code: "AGDF_SKILL_RESOURCE_UNDECLARED_INLINE",
518
+ classification: "agdf_policy",
519
+ skillPath,
520
+ resource: token,
521
+ message: "Dependency-shaped relative Markdown path is not declared in the Runtime Contract resource list.",
522
+ remediation: "Declare the resource in the Runtime Contract bullet list or remove the dependency-shaped token.",
523
+ }));
524
+ }
525
+ }
526
+ const portability = new Set();
527
+ for (const token of declarations) {
528
+ const resourceResult = validateResource({ token, skillDir, skillPath, surfaceRoot, sharedRoots });
529
+ if (resourceResult.finding) findings.push(resourceResult.finding);
530
+ if (resourceResult.classification) portability.add(resourceResult.classification);
531
+ }
532
+ result.skills.push({
533
+ name: expectedName,
534
+ path: skillPath,
535
+ portability: portability.has("plugin_scoped") ? "plugin_scoped" : "skill_local",
536
+ resources: declarations,
537
+ });
538
+ }
539
+
540
+ findings.sort((left, right) => FINDING_ORDER.map((key) => String(left[key]).localeCompare(String(right[key]))).find((value) => value !== 0) ?? 0);
541
+ result.blocking = findings.some(({ severity }) => severity === "error");
542
+ return result;
543
+ }
@@ -84,6 +84,14 @@ function resolveLayout() {
84
84
 
85
85
  const { mode: validationMode, repoRoot, pluginRoot } = resolveLayout();
86
86
  const sourceMode = validationMode === "source";
87
+ let validateAgentSkillsConformance;
88
+ try {
89
+ ({ validateAgentSkillsConformance } = await import("./agent-skills-conformance.mjs"));
90
+ } catch {
91
+ console.error("[agdf-runtime-integrity] FAIL");
92
+ console.error("- AGDF_AGENT_SKILLS_VALIDATOR_MISSING: scripts/agent-skills-conformance.mjs must be present and loadable");
93
+ process.exit(1);
94
+ }
87
95
  const marketplacePath = sourceMode ? join(repoRoot, ".claude-plugin", "marketplace.json") : null;
88
96
  const codexPluginPath = join(pluginRoot, ".codex-plugin", "plugin.json");
89
97
  const claudePluginPath = join(pluginRoot, ".claude-plugin", "plugin.json");
@@ -403,7 +411,9 @@ if (!runtimeContract.includes("first eligible native-attempt") || !runtimeContra
403
411
  if (!runtimeContract.includes("`attempted_not_applied`") || !runtimeContract.includes("`unsafe_to_wait`")) {
404
412
  failures.push("Runtime Contract must define visible fallback attempt outcomes");
405
413
  }
406
- if (!runtimeContract.includes("### Interaction Locale Contract") || !runtimeContract.includes("an incomplete pack is unsupported and must fail to English as a unit")) {
414
+ if (!runtimeContract.includes("### Interaction Locale Contract")
415
+ || !runtimeContract.includes("an unsupported requested locale must fail to English as a complete unit")
416
+ || !runtimeContract.includes("an incomplete or invalid registry fails closed")) {
407
417
  failures.push("Runtime Contract must define deterministic chat-locale resolution with English fallback");
408
418
  }
409
419
 
@@ -494,6 +504,11 @@ if (sourceMode && isFile(interactionPresentationPath)) {
494
504
  if (!interactionPresentation.includes("export function renderScopeClassificationCard")) {
495
505
  failures.push("interaction-presentation.js must export renderScopeClassificationCard");
496
506
  }
507
+ if (!interactionPresentation.includes("SCOPE_CLASSIFICATION_LIMITS")
508
+ || !interactionPresentation.includes("maxCodePointsPerField: 240")
509
+ || !interactionPresentation.includes('mode !== "quick_task"')) {
510
+ failures.push("interaction-presentation.js must enforce the bounded Quick Task-only scope classification contract");
511
+ }
497
512
  if (!interactionPresentation.includes("export function renderTaskTargetOrientation")) {
498
513
  failures.push("interaction-presentation.js must export renderTaskTargetOrientation");
499
514
  }
@@ -503,6 +518,9 @@ for (const locale of ["en", "de"]) {
503
518
  if (!pack?.scopeClassification?.title || !pack?.scopeClassification?.mode?.quick_task || !pack?.scopeClassification?.challenge) {
504
519
  failures.push(`Interaction locale ${locale} missing scopeClassification section keys`);
505
520
  }
521
+ if (Object.hasOwn(pack?.scopeClassification?.mode ?? {}, "verified_change")) {
522
+ failures.push(`Interaction locale ${locale} must not expose Verified Change as a scope classification card mode`);
523
+ }
506
524
  if (!pack?.taskTargetResolution?.title
507
525
  || !pack?.taskTargetResolution?.primaryTarget
508
526
  || !pack?.taskTargetResolution?.reasonCodes?.target_content_mismatch
@@ -646,6 +664,18 @@ if (interactionLocales) {
646
664
  }
647
665
  }
648
666
 
667
+ const agentSkillsConformance = validateAgentSkillsConformance({
668
+ pluginRoot,
669
+ surfaceRoot: pluginRoot,
670
+ surface: sourceMode ? "source" : "plugin",
671
+ });
672
+ for (const conformanceFinding of agentSkillsConformance.findings) {
673
+ const detail = [conformanceFinding.skillPath, conformanceFinding.resource].filter(Boolean).join(" -> ");
674
+ const rendered = `${conformanceFinding.code} [${conformanceFinding.classification}]${detail ? ` ${detail}` : ""}: ${conformanceFinding.message}`;
675
+ if (conformanceFinding.severity === "error") failures.push(rendered);
676
+ else console.warn(`[agdf-runtime-integrity] advisory: ${rendered}`);
677
+ }
678
+
649
679
  if (isFile(pagesSkillsPath)) {
650
680
  failures.push("Pages must not maintain a duplicate skill catalogue; use the canonical plugin definition and handbook");
651
681
  }
@@ -961,22 +991,6 @@ for (const skill of expectedSkills) {
961
991
 
962
992
  const skillMd = read(skillPath);
963
993
  const helpMd = read(helpPath);
964
- const frontmatter = skillMd.match(/^---\r?\n([\s\S]*?)\r?\n---/);
965
- if (!frontmatter) {
966
- failures.push(`${skill}/SKILL.md missing YAML frontmatter`);
967
- } else {
968
- if (!frontmatter[1].includes(`name: ${skill}`)) failures.push(`${skill}/SKILL.md frontmatter name mismatch`);
969
- const descriptionLine = frontmatter[1].split(/\r?\n/).find((line) => line.startsWith("description:"));
970
- const descriptionValue = descriptionLine?.slice("description:".length).trim() ?? "";
971
- const unquotedDescription = descriptionValue.replace(/^(["'])(.*)\1$/, "$2");
972
- if (!/^Use this skill/i.test(unquotedDescription)) failures.push(`${skill}/SKILL.md description should be English and start with "Use this skill"`);
973
- if (!/^["']/.test(descriptionValue) && descriptionValue.includes(": ")) {
974
- failures.push(`${skill}/SKILL.md description contains an unquoted colon-space sequence that Claude Code rejects as YAML`);
975
- }
976
- }
977
- if (!skillMd.includes("../../meta/contracts/")) {
978
- failures.push(`${skill}/SKILL.md missing focused runtime contract module reference`);
979
- }
980
994
  if (skill === "gate-check") {
981
995
  for (const required of [
982
996
  "## Native Interaction Path",
@@ -57,6 +57,7 @@ Use what is available:
57
57
  - rollout or migration notes
58
58
  - open technical risks or documentation gaps
59
59
  - Context Graph impact from OR/QA/review
60
+ - Parent reconciliation projection already reported by OR
60
61
 
61
62
  If code-change status is unclear, do not claim a commit handoff.
62
63
 
@@ -87,6 +88,11 @@ If code-change status is unclear, do not claim a commit handoff.
87
88
  - monitoring/runtime verification
88
89
  - Context Graph reconciliation
89
90
  7. If `Approval: UAT` exists, code changes were delivered, and no Context Graph gap remains, actively offer the commit but do not run it.
91
+ 8. If OR reports an `open` Parent reconciliation, retain its named Parent and one next action in the
92
+ handoff while preserving an otherwise valid commit offer. This coordination warning is not a
93
+ Child gate failure.
94
+ 9. Consume Parent reconciliation from the OR only. Do not inspect sibling runs, infer Parentage,
95
+ reevaluate evidence or mutate a Parent.
90
96
 
91
97
  ## Output
92
98
  For code-changing runs:
@@ -116,3 +122,4 @@ This skill must not:
116
122
  - execute commit, push, or PR
117
123
  - invent a commit handoff for non-code runs
118
124
  - imply new gate approvals
125
+ - rediscover, reclassify or repair Parent reconciliation outside the OR