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.
- package/generated/.agdf/control/templates/RUN_STATE.md +22 -0
- package/generated/.agdf/control/templates/artefacts/OR.md +22 -0
- package/generated/.github/skills/agdf-delivery-closeout/SKILL.md +7 -0
- package/generated/.github/skills/agdf-interaction-locales.json +2 -4
- package/generated/.github/skills/agdf-release-or/SKILL.md +14 -4
- package/generated/.github/skills/contracts/closeout.md +49 -0
- package/generated/.github/skills/contracts/control-scaffold.md +2 -0
- package/generated/.github/skills/contracts/interaction.md +23 -12
- package/generated/.opencode/agdf-interaction-locales.json +2 -4
- package/generated/.opencode/contracts/closeout.md +49 -0
- package/generated/.opencode/contracts/control-scaffold.md +2 -0
- package/generated/.opencode/contracts/interaction.md +23 -12
- package/generated/.opencode/skills/agdf-delivery-closeout/SKILL.md +7 -0
- package/generated/.opencode/skills/agdf-release-or/SKILL.md +14 -4
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/control/templates/RUN_STATE.md +22 -0
- package/generated/plugins/agdf/control/templates/artefacts/OR.md +22 -0
- package/generated/plugins/agdf/meta/agdf-interaction-locales.json +2 -4
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/meta/agent-skills-conformance.json +86 -0
- package/generated/plugins/agdf/meta/contracts/closeout.md +49 -0
- package/generated/plugins/agdf/meta/contracts/control-scaffold.md +2 -0
- package/generated/plugins/agdf/meta/contracts/interaction.md +23 -12
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +2 -4
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/delivery-map.js +13 -3
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +9 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +6 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +16 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/parent-reconciliation.js +284 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +39 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +31 -14
- package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/scripts/agent-skills-conformance.mjs +543 -0
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +31 -17
- package/generated/plugins/agdf/skills/delivery-closeout/SKILL.md +7 -0
- package/generated/plugins/agdf/skills/release-or/SKILL.md +14 -4
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +2 -2
- package/generated/plugins/agdf/submission/openai/release-notes.md +1 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/submissions/openai/agdf/meta/agdf-interaction-locales.json +2 -4
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/submissions/openai/agdf/meta/agent-skills-conformance.json +86 -0
- package/generated/submissions/openai/agdf/meta/contracts/closeout.md +49 -0
- package/generated/submissions/openai/agdf/meta/contracts/control-scaffold.md +2 -0
- package/generated/submissions/openai/agdf/meta/contracts/interaction.md +23 -12
- package/generated/submissions/openai/agdf/skills/delivery-closeout/SKILL.md +7 -0
- package/generated/submissions/openai/agdf/skills/release-or/SKILL.md +14 -4
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +2 -2
- package/generated/submissions/openai/agdf/submission/openai/inventory.json +19 -15
- package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
- package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
- package/generated/submissions/openai/agdf/submission/openai/release-notes.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
- package/lib/control-evaluation/delivery-map.js +13 -3
- package/lib/control-evaluation/doctor.js +9 -2
- package/lib/control-evaluation/gate-check.js +6 -1
- package/lib/control-evaluation/gate-policy.js +16 -1
- package/lib/control-evaluation/parent-reconciliation.js +284 -0
- package/lib/control-state/run-state-parser.js +39 -1
- package/lib/interaction-presentation.js +31 -14
- package/lib/proportionality-benchmark/blind-prompt.js +2 -1
- package/lib/proportionality-benchmark/contracts.js +16 -7
- package/lib/proportionality-benchmark/corpus-loader.js +132 -21
- package/lib/proportionality-benchmark/evaluator.js +25 -15
- package/lib/proportionality-benchmark/index.js +1 -0
- package/lib/proportionality-benchmark/live-recorder.js +32 -10
- package/lib/proportionality-benchmark/profiles.js +102 -0
- package/lib/proportionality-benchmark/report.js +10 -1
- package/lib/proportionality-benchmark/source-fingerprint.js +6 -2
- package/lib/release/version-coherence.js +79 -0
- 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")
|
|
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
|