@flancer32/skill-adsm-ctx 0.2.1 → 0.4.0
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/CHANGELOG.md +30 -0
- package/README.md +45 -18
- package/bin/adsm-ctx.mjs +44 -0
- package/migrations/README.md +5 -1
- package/package.json +65 -11
- package/skill/SKILL.md +5 -4
- package/skill/references/context-lifecycle.md +14 -6
- package/skill/references/design-questionnaire.md +13 -1
- package/skill/references/document-projections.md +40 -80
- package/skill/references/documentation-compression.md +3 -2
- package/skill/references/documentation-quality-audit.md +37 -14
- package/skill/references/orchestration-model.md +4 -3
- package/skill/references/semantic-skins.md +65 -0
- package/skill/references/specialized-skills.md +5 -2
- package/skill/references/upgrade-policy.md +16 -6
- package/skill/templates/ctx/adsm.json +4 -1
- package/skill/templates/ctx/docs/AGENTS.md +3 -0
- package/skill/templates/ctx/docs/code/testing.md +4 -0
- package/skill/templates/work/context/semantic-skin.md +11 -22
- package/src/Cli/Command/Design.mjs +17 -0
- package/src/Cli/Command/Init.mjs +22 -0
- package/src/Cli/Command/InstallSkill.mjs +18 -0
- package/src/Cli/Command/Upgrade.mjs +19 -0
- package/src/Cli/Command/Validate.mjs +19 -0
- package/src/Design/Service.mjs +9 -7
- package/src/Shared/Constants.mjs +5 -5
- package/src/Shared/Metadata.mjs +1 -0
- package/src/Upgrade/Service.mjs +3 -3
- package/src/Validate/Service.mjs +217 -259
- package/types.d.ts +5 -2
- package/.github/workflows/flows-app.yaml +0 -95
- package/AGENTS.md +0 -112
- package/bin/cli.mjs +0 -55
- package/ctx/AGENTS.md +0 -44
- package/ctx/README.md +0 -24
- package/ctx/adsm.json +0 -5
- package/ctx/agent/AGENTS.md +0 -41
- package/ctx/agent/flows/.img/flancer32-skill-adsm-ctx-github-flows.png +0 -0
- package/ctx/agent/flows/AGENTS.md +0 -37
- package/ctx/agent/flows/cfg/issues/labeled/accepted/profile.json +0 -25
- package/ctx/agent/flows/cfg/issues/labeled/accepted/prompt.md +0 -104
- package/ctx/agent/flows/cfg/issues/labeled/clarification-provided/profile.json +0 -15
- package/ctx/agent/flows/cfg/issues/labeled/clarification-provided/prompt.md +0 -63
- package/ctx/agent/flows/cfg/issues/opened/admission/profile.json +0 -14
- package/ctx/agent/flows/cfg/issues/opened/admission/prompt.md +0 -110
- package/ctx/agent/flows/cfg/profile.json +0 -40
- package/ctx/agent/flows/cfg/pull_request/closed/merged/profile.json +0 -15
- package/ctx/agent/flows/cfg/pull_request/closed/merged/prompt.md +0 -41
- package/ctx/agent/flows/cfg/pull_request/labeled/needs-fix/profile.json +0 -25
- package/ctx/agent/flows/cfg/pull_request/labeled/needs-fix/prompt.md +0 -79
- package/ctx/agent/flows/cfg/pull_request/labeled/ready-for-validation/profile.json +0 -15
- package/ctx/agent/flows/cfg/pull_request/labeled/ready-for-validation/prompt.md +0 -72
- package/ctx/agent/flows/cfg/pull_request/labeled/verified/profile.json +0 -15
- package/ctx/agent/flows/cfg/pull_request/labeled/verified/prompt.md +0 -48
- package/ctx/agent/flows/deployment.md +0 -116
- package/ctx/agent/flows/labels.md +0 -108
- package/ctx/agent/flows/overview.md +0 -82
- package/ctx/agent/prompt/cli/ai-intro.md +0 -141
- package/ctx/assets/.gitkeep +0 -1
- package/ctx/docs/AGENTS.md +0 -121
- package/ctx/docs/ai-intro.md +0 -37
- package/ctx/docs/architecture/AGENTS.md +0 -98
- package/ctx/docs/architecture/behavior.md +0 -68
- package/ctx/docs/architecture/checklists.md +0 -48
- package/ctx/docs/architecture/constraints.md +0 -53
- package/ctx/docs/architecture/constraints.skin.en.md +0 -38
- package/ctx/docs/architecture/decisions.md +0 -118
- package/ctx/docs/architecture/integration.md +0 -31
- package/ctx/docs/architecture/overview.md +0 -76
- package/ctx/docs/architecture/state.md +0 -50
- package/ctx/docs/architecture/structure.md +0 -38
- package/ctx/docs/architecture/supervision.md +0 -71
- package/ctx/docs/code/AGENTS.md +0 -51
- package/ctx/docs/code/overview.md +0 -53
- package/ctx/docs/code/testing.md +0 -26
- package/ctx/docs/environment/AGENTS.md +0 -50
- package/ctx/docs/environment/overview.md +0 -33
- package/ctx/docs/filesystem.md +0 -44
- package/ctx/docs/product/AGENTS.md +0 -51
- package/ctx/docs/product/domain.md +0 -63
- package/ctx/docs/product/domain.skin.en.md +0 -40
- package/ctx/docs/product/glossary.md +0 -31
- package/ctx/docs/product/overview.md +0 -112
- package/ctx/docs/product/overview.skin.en.md +0 -38
- package/ctx/docs/product/roles.md +0 -17
- package/ctx/docs/product/use-cases.md +0 -15
- package/src/Bootstrap/Cli.mjs +0 -62
- package/src/Bootstrap.mjs +0 -108
- package/test/integration/.gitkeep +0 -1
- package/test/integration/cli-help.test.mjs +0 -15
- package/test/integration/cli-init-validate.test.mjs +0 -223
- package/test/integration/cli-install-skill.test.mjs +0 -41
- package/test/unit/Bootstrap/Cli.test.mjs +0 -23
- package/test/unit/Bootstrap.test.mjs +0 -53
- package/test/unit/Design/Service.test.mjs +0 -11
- package/test/unit/Init/Service.test.mjs +0 -51
- package/test/unit/Shared/Constants.test.mjs +0 -21
- package/test/unit/Shared/CopyDirectory.test.mjs +0 -31
- package/test/unit/Shared/Metadata.test.mjs +0 -9
- package/test/unit/Shared/Path.test.mjs +0 -11
- package/test/unit/Skill/Installer.test.mjs +0 -20
- package/test/unit/Upgrade/Service.test.mjs +0 -13
- package/test/unit/Validate/Service.test.mjs +0 -760
package/src/Design/Service.mjs
CHANGED
|
@@ -23,13 +23,15 @@ export default class Service {
|
|
|
23
23
|
"",
|
|
24
24
|
"Recommended agent workflow:",
|
|
25
25
|
"1. Identify the project class from existing repository signals or user input.",
|
|
26
|
-
"2. Keep ADSM's four
|
|
27
|
-
"3. Add only
|
|
28
|
-
"4.
|
|
29
|
-
"5.
|
|
30
|
-
"6.
|
|
31
|
-
"7.
|
|
32
|
-
"8.
|
|
26
|
+
"2. Keep ADSM's four ordered declarative documentation levels under ctx/docs/: product, architecture, environment, and code.",
|
|
27
|
+
"3. Add ctx/docs/verification/ only when a durable verification specification is justified; it is cross-cutting, not a fifth level.",
|
|
28
|
+
"4. Place each verification specification under the category of its primary declaration target and link secondary targets instead of duplicating it.",
|
|
29
|
+
"5. Add only project-specific substructure that is justified by the project; nested directories are allowed.",
|
|
30
|
+
"6. Treat ordinary *.md files as agent-facing operational context documents and add *.skin.<lang>.md files only where human semantic control is needed.",
|
|
31
|
+
"7. Place visual, generated, rendered, or auxiliary artifacts under ctx/assets/ instead of ctx/docs/.",
|
|
32
|
+
"8. Detect whether specialized ADSM skills are needed for browser, backend, data, deployment, verification execution, monitoring, or other areas.",
|
|
33
|
+
"9. If a specialized skill is available, use it for that branch after the generic structure is in place.",
|
|
34
|
+
"10. If no specialized skill is available, record the specialization gap and avoid inventing details.",
|
|
33
35
|
].join("\n");
|
|
34
36
|
};
|
|
35
37
|
}
|
package/src/Shared/Constants.mjs
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
export default class Constants {
|
|
9
9
|
constructor() {
|
|
10
10
|
this.CONTEXT_SCHEMA = "adsm-context";
|
|
11
|
-
this.CONTEXT_VERSION = "0.
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
11
|
+
this.CONTEXT_VERSION = "0.2.0";
|
|
12
|
+
this.VERIFICATION_CAPABILITY = "verification-layer";
|
|
13
|
+
this.CONTEXT_CAPABILITIES = Object.freeze([this.VERIFICATION_CAPABILITY]);
|
|
14
|
+
this.VERIFICATION_BLOCK = "verification";
|
|
15
|
+
this.VERIFICATION_TARGETS = Object.freeze(["product", "architecture", "environment", "code"]);
|
|
16
16
|
this.DOC_LEVEL_MAX_DIRECTORIES = 9;
|
|
17
17
|
this.DOC_LEVEL_MAX_FILES = 9;
|
|
18
18
|
this.REQUIRED_PATHS = Object.freeze([
|
package/src/Shared/Metadata.mjs
CHANGED
package/src/Upgrade/Service.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @namespace Fl32_Skill_Adsm_Ctx_Upgrade_Service
|
|
5
|
-
* @description Provides the baseline non-destructive upgrade entry point
|
|
5
|
+
* @description Provides the baseline non-destructive upgrade entry point without automatic migrations.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export default class Service {
|
|
@@ -13,7 +13,7 @@ export default class Service {
|
|
|
13
13
|
*/
|
|
14
14
|
constructor({fs, pathService}) {
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Checks for context metadata and reports the current migration availability.
|
|
17
17
|
*
|
|
18
18
|
* @param {object} params
|
|
19
19
|
* @param {string} params.targetPath
|
|
@@ -31,7 +31,7 @@ export default class Service {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
return {
|
|
34
|
-
message: "No migrations are
|
|
34
|
+
message: "No automatic migrations are available. Verification Layer adoption is optional and must not create empty scaffolding.",
|
|
35
35
|
ok: true,
|
|
36
36
|
};
|
|
37
37
|
};
|
package/src/Validate/Service.mjs
CHANGED
|
@@ -5,14 +5,6 @@
|
|
|
5
5
|
* @description Validates baseline ADSM context structure, metadata, and documentation projection shape.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const REQUIRED_SKIN_SECTIONS = Object.freeze([
|
|
9
|
-
"Purpose",
|
|
10
|
-
"Mental Model",
|
|
11
|
-
"Scope",
|
|
12
|
-
"Invariants",
|
|
13
|
-
"Agent Document",
|
|
14
|
-
]);
|
|
15
|
-
|
|
16
8
|
const LINK_PATTERN = /\[[^\]]+\]\(([^)]+)\)/g;
|
|
17
9
|
const EXPLICIT_SKIN_FILENAME = /^(.*)\.skin\.([a-z]{2}(?:-[A-Za-z0-9]+)?)\.md$/;
|
|
18
10
|
const LEGACY_SKIN_FILENAME = /^(.*)\.skin\.md$/;
|
|
@@ -39,7 +31,7 @@ export default class Validate {
|
|
|
39
31
|
* ruleId: string,
|
|
40
32
|
* severity: "error" | "warning"
|
|
41
33
|
* }[],
|
|
42
|
-
* metadata: {schema: string, version: string, initializedAt?: string} | null,
|
|
34
|
+
* metadata: {capabilities?: string[], schema: string, version: string, initializedAt?: string} | null,
|
|
43
35
|
* missingPaths: string[],
|
|
44
36
|
* ok: boolean,
|
|
45
37
|
* projectRoot: string,
|
|
@@ -48,7 +40,8 @@ export default class Validate {
|
|
|
48
40
|
* bodies: string[],
|
|
49
41
|
* orphanSkins: string[],
|
|
50
42
|
* pairs: {body: string, skin: string}[],
|
|
51
|
-
* skins: string[]
|
|
43
|
+
* skins: string[],
|
|
44
|
+
* verification: {categories: string[], present: boolean}
|
|
52
45
|
* },
|
|
53
46
|
* warningCount: number
|
|
54
47
|
* }>}
|
|
@@ -62,10 +55,16 @@ export default class Validate {
|
|
|
62
55
|
ruleId: "required-path",
|
|
63
56
|
severity: "error",
|
|
64
57
|
}));
|
|
65
|
-
const
|
|
66
|
-
const projection = inspectProjection({
|
|
58
|
+
const metadataResult = readMetadata({constants, fs, pathService, projectRoot});
|
|
59
|
+
const projection = inspectProjection({
|
|
60
|
+
constants,
|
|
61
|
+
fs,
|
|
62
|
+
metadata: metadataResult.metadata,
|
|
63
|
+
pathService,
|
|
64
|
+
projectRoot,
|
|
65
|
+
});
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
findings.push(...metadataResult.findings);
|
|
69
68
|
findings.push(...projection.findings);
|
|
70
69
|
|
|
71
70
|
const errorCount = findings.filter((entry) => entry.severity === "error").length;
|
|
@@ -74,7 +73,7 @@ export default class Validate {
|
|
|
74
73
|
return {
|
|
75
74
|
errorCount,
|
|
76
75
|
findings,
|
|
77
|
-
metadata:
|
|
76
|
+
metadata: metadataResult.ok ? metadataResult.metadata : null,
|
|
78
77
|
missingPaths,
|
|
79
78
|
ok: errorCount === 0,
|
|
80
79
|
projectRoot,
|
|
@@ -188,54 +187,80 @@ function parseSkinFilename(file) {
|
|
|
188
187
|
* }}
|
|
189
188
|
*/
|
|
190
189
|
function readMetadata({constants, fs, pathService, projectRoot}) {
|
|
190
|
+
const findings = [];
|
|
191
191
|
const metadataPath = pathService.join(projectRoot, "ctx", "adsm.json");
|
|
192
192
|
if (!fs.existsSync(metadataPath)) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}),
|
|
201
|
-
};
|
|
193
|
+
findings.push(createFinding({
|
|
194
|
+
message: "Missing ctx/adsm.json",
|
|
195
|
+
path: "ctx/adsm.json",
|
|
196
|
+
ruleId: "metadata-file",
|
|
197
|
+
severity: "error",
|
|
198
|
+
}));
|
|
199
|
+
return {findings, metadata: null, ok: false};
|
|
202
200
|
}
|
|
203
201
|
|
|
204
202
|
try {
|
|
205
203
|
const parsed = JSON.parse(fs.readFileSync(metadataPath, "utf8"));
|
|
206
204
|
if (parsed.schema !== constants.CONTEXT_SCHEMA) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
severity: "error",
|
|
214
|
-
}),
|
|
215
|
-
};
|
|
205
|
+
findings.push(createFinding({
|
|
206
|
+
message: `Unexpected schema: ${parsed.schema}`,
|
|
207
|
+
path: "ctx/adsm.json",
|
|
208
|
+
ruleId: "metadata-schema",
|
|
209
|
+
severity: "error",
|
|
210
|
+
}));
|
|
216
211
|
}
|
|
217
212
|
if (!parsed.version) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
213
|
+
findings.push(createFinding({
|
|
214
|
+
message: "Missing metadata version",
|
|
215
|
+
path: "ctx/adsm.json",
|
|
216
|
+
ruleId: "metadata-version",
|
|
217
|
+
severity: "error",
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (Object.hasOwn(parsed, "capabilities")) {
|
|
222
|
+
if (!Array.isArray(parsed.capabilities)) {
|
|
223
|
+
findings.push(createFinding({
|
|
224
|
+
message: "Metadata capabilities must be an array of unique strings",
|
|
222
225
|
path: "ctx/adsm.json",
|
|
223
|
-
ruleId: "metadata-
|
|
226
|
+
ruleId: "metadata-capabilities",
|
|
224
227
|
severity: "error",
|
|
225
|
-
})
|
|
226
|
-
}
|
|
228
|
+
}));
|
|
229
|
+
} else {
|
|
230
|
+
const seen = new Set();
|
|
231
|
+
for (const capability of parsed.capabilities) {
|
|
232
|
+
if (typeof capability !== "string" || capability.length === 0 || seen.has(capability)) {
|
|
233
|
+
findings.push(createFinding({
|
|
234
|
+
message: "Metadata capabilities must be an array of unique non-empty strings",
|
|
235
|
+
path: "ctx/adsm.json",
|
|
236
|
+
ruleId: "metadata-capabilities",
|
|
237
|
+
severity: "error",
|
|
238
|
+
}));
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
seen.add(capability);
|
|
242
|
+
if (!(constants.CONTEXT_CAPABILITIES ?? []).includes(capability)) {
|
|
243
|
+
findings.push(createFinding({
|
|
244
|
+
message: `Unknown context capability: ${capability}`,
|
|
245
|
+
path: "ctx/adsm.json",
|
|
246
|
+
ruleId: "metadata-capability-unknown",
|
|
247
|
+
severity: "warning",
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
227
252
|
}
|
|
228
|
-
|
|
253
|
+
|
|
254
|
+
const ok = findings.every((entry) => entry.severity !== "error");
|
|
255
|
+
return {findings, metadata: ok ? parsed : null, ok};
|
|
229
256
|
} catch (error) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}),
|
|
238
|
-
};
|
|
257
|
+
findings.push(createFinding({
|
|
258
|
+
message: `Invalid metadata JSON: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
259
|
+
path: "ctx/adsm.json",
|
|
260
|
+
ruleId: "metadata-json",
|
|
261
|
+
severity: "error",
|
|
262
|
+
}));
|
|
263
|
+
return {findings, metadata: null, ok: false};
|
|
239
264
|
}
|
|
240
265
|
}
|
|
241
266
|
|
|
@@ -261,7 +286,7 @@ function readMetadata({constants, fs, pathService, projectRoot}) {
|
|
|
261
286
|
* }
|
|
262
287
|
* }}
|
|
263
288
|
*/
|
|
264
|
-
function inspectProjection({constants, fs, pathService, projectRoot}) {
|
|
289
|
+
function inspectProjection({constants, fs, metadata, pathService, projectRoot}) {
|
|
265
290
|
const docsRoot = pathService.join(projectRoot, "ctx", "docs");
|
|
266
291
|
const assetsRoot = pathService.join(projectRoot, "ctx", "assets");
|
|
267
292
|
const findings = [];
|
|
@@ -311,6 +336,26 @@ function inspectProjection({constants, fs, pathService, projectRoot}) {
|
|
|
311
336
|
pathService,
|
|
312
337
|
projectRoot,
|
|
313
338
|
}));
|
|
339
|
+
const verification = inspectVerificationBlock({
|
|
340
|
+
constants,
|
|
341
|
+
docsEntryMap,
|
|
342
|
+
fs,
|
|
343
|
+
pathService,
|
|
344
|
+
projectRoot,
|
|
345
|
+
});
|
|
346
|
+
findings.push(...verification.findings);
|
|
347
|
+
if (
|
|
348
|
+
verification.summary.present
|
|
349
|
+
&& metadata
|
|
350
|
+
&& !metadata.capabilities?.includes(constants.VERIFICATION_CAPABILITY)
|
|
351
|
+
) {
|
|
352
|
+
findings.push(createFinding({
|
|
353
|
+
message: `Verification block is present without declared ${constants.VERIFICATION_CAPABILITY} capability`,
|
|
354
|
+
path: "ctx/adsm.json",
|
|
355
|
+
ruleId: "verification-capability-undocumented",
|
|
356
|
+
severity: "warning",
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
314
359
|
|
|
315
360
|
const markdownFiles = [];
|
|
316
361
|
for (const file of listFiles({fs, pathService, root: docsRoot, prefix: "ctx/docs"})) {
|
|
@@ -373,10 +418,129 @@ function inspectProjection({constants, fs, pathService, projectRoot}) {
|
|
|
373
418
|
orphanSkins,
|
|
374
419
|
pairs,
|
|
375
420
|
skins,
|
|
421
|
+
verification: verification.summary,
|
|
376
422
|
},
|
|
377
423
|
};
|
|
378
424
|
}
|
|
379
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Inspects optional Verification Layer adoption without treating it as a fifth
|
|
428
|
+
* declarative documentation level.
|
|
429
|
+
*
|
|
430
|
+
* @param {object} params
|
|
431
|
+
* @param {Fl32_Skill_Adsm_Ctx_Shared_Constants} params.constants
|
|
432
|
+
* @param {Map<string, {name: string, isDirectory: () => boolean, isFile: () => boolean}>} params.docsEntryMap
|
|
433
|
+
* @param {typeof import("node:fs")} params.fs
|
|
434
|
+
* @param {Fl32_Skill_Adsm_Ctx_Shared_Path} params.pathService
|
|
435
|
+
* @param {string} params.projectRoot
|
|
436
|
+
* @returns {{
|
|
437
|
+
* findings: {message: string, path: string, ruleId: string, severity: "error" | "warning"}[],
|
|
438
|
+
* summary: {categories: string[], present: boolean}
|
|
439
|
+
* }}
|
|
440
|
+
*/
|
|
441
|
+
function inspectVerificationBlock({constants, docsEntryMap, fs, pathService, projectRoot}) {
|
|
442
|
+
const findings = [];
|
|
443
|
+
const summary = {categories: [], present: false};
|
|
444
|
+
const entry = docsEntryMap.get(constants.VERIFICATION_BLOCK);
|
|
445
|
+
if (!entry) return {findings, summary};
|
|
446
|
+
|
|
447
|
+
summary.present = true;
|
|
448
|
+
const verificationPath = `ctx/docs/${constants.VERIFICATION_BLOCK}`;
|
|
449
|
+
if (!entry.isDirectory()) {
|
|
450
|
+
findings.push(createFinding({
|
|
451
|
+
message: `Verification block is not a directory: ${verificationPath}`,
|
|
452
|
+
path: verificationPath,
|
|
453
|
+
ruleId: "verification-block",
|
|
454
|
+
severity: "error",
|
|
455
|
+
}));
|
|
456
|
+
return {findings, summary};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
let entries = [];
|
|
460
|
+
try {
|
|
461
|
+
entries = listEntries({
|
|
462
|
+
fs,
|
|
463
|
+
root: pathService.join(projectRoot, ...verificationPath.split("/")),
|
|
464
|
+
});
|
|
465
|
+
} catch (error) {
|
|
466
|
+
findings.push(createFinding({
|
|
467
|
+
message: `Unreadable verification block (${error instanceof Error ? error.message : "Unknown error"})`,
|
|
468
|
+
path: verificationPath,
|
|
469
|
+
ruleId: "verification-block-readable",
|
|
470
|
+
severity: "error",
|
|
471
|
+
}));
|
|
472
|
+
return {findings, summary};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const agentsEntry = entries.find((candidate) => candidate.name === "AGENTS.md");
|
|
476
|
+
if (!agentsEntry?.isFile()) {
|
|
477
|
+
findings.push(createFinding({
|
|
478
|
+
message: `Verification block requires ${verificationPath}/AGENTS.md`,
|
|
479
|
+
path: `${verificationPath}/AGENTS.md`,
|
|
480
|
+
ruleId: "verification-agents",
|
|
481
|
+
severity: "error",
|
|
482
|
+
}));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const targets = constants.VERIFICATION_TARGETS ?? constants.DOC_LEVELS;
|
|
486
|
+
for (const child of entries) {
|
|
487
|
+
if (child.name === "AGENTS.md") continue;
|
|
488
|
+
if (!targets.includes(child.name)) {
|
|
489
|
+
if (child.isDirectory()) {
|
|
490
|
+
findings.push(createFinding({
|
|
491
|
+
message: `Unsupported verification category: ${child.name}`,
|
|
492
|
+
path: `${verificationPath}/${child.name}`,
|
|
493
|
+
ruleId: "verification-category",
|
|
494
|
+
severity: "error",
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (!child.isDirectory()) {
|
|
501
|
+
findings.push(createFinding({
|
|
502
|
+
message: `Verification category is not a directory: ${child.name}`,
|
|
503
|
+
path: `${verificationPath}/${child.name}`,
|
|
504
|
+
ruleId: "verification-category",
|
|
505
|
+
severity: "error",
|
|
506
|
+
}));
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
summary.categories.push(child.name);
|
|
511
|
+
const categoryPath = `${verificationPath}/${child.name}`;
|
|
512
|
+
const documents = listFiles({
|
|
513
|
+
fs,
|
|
514
|
+
pathService,
|
|
515
|
+
prefix: categoryPath,
|
|
516
|
+
root: pathService.join(projectRoot, ...categoryPath.split("/")),
|
|
517
|
+
}).filter((file) => (
|
|
518
|
+
file.endsWith(".md")
|
|
519
|
+
&& !file.endsWith("/AGENTS.md")
|
|
520
|
+
&& !parseSkinFilename(file)
|
|
521
|
+
));
|
|
522
|
+
if (documents.length === 0) {
|
|
523
|
+
findings.push(createFinding({
|
|
524
|
+
message: `Verification category has no durable specification: ${categoryPath}`,
|
|
525
|
+
path: categoryPath,
|
|
526
|
+
ruleId: "verification-empty-category",
|
|
527
|
+
severity: "error",
|
|
528
|
+
}));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (summary.categories.length === 0) {
|
|
533
|
+
findings.push(createFinding({
|
|
534
|
+
message: `Verification block has no populated primary-target category: ${verificationPath}`,
|
|
535
|
+
path: verificationPath,
|
|
536
|
+
ruleId: "verification-empty-block",
|
|
537
|
+
severity: "error",
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {findings, summary};
|
|
542
|
+
}
|
|
543
|
+
|
|
380
544
|
/**
|
|
381
545
|
* Warns when a documentation level exceeds the recommended direct-entry capacity
|
|
382
546
|
* and requires its local AGENTS.md to make an intentional exception explicit.
|
|
@@ -586,129 +750,17 @@ function validateSkinDocument({bodySet, constants, file, skinInfo, text}) {
|
|
|
586
750
|
severity: "error",
|
|
587
751
|
}));
|
|
588
752
|
}
|
|
589
|
-
|
|
590
|
-
for (const section of REQUIRED_SKIN_SECTIONS) {
|
|
591
|
-
if (!headings.includes(section)) {
|
|
592
|
-
findings.push(createFinding({
|
|
593
|
-
message: `Missing skin section "${section}": ${file}`,
|
|
594
|
-
path: file,
|
|
595
|
-
ruleId: "skin-required-sections",
|
|
596
|
-
severity: "error",
|
|
597
|
-
}));
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const scopeText = extractSectionBody({sectionName: "Scope", text});
|
|
602
|
-
if (scopeText) {
|
|
603
|
-
if (!/^Includes:\s*$/m.test(scopeText)) {
|
|
604
|
-
findings.push(createFinding({
|
|
605
|
-
message: `Missing Includes: block in Scope: ${file}`,
|
|
606
|
-
path: file,
|
|
607
|
-
ruleId: "skin-scope-includes-excludes",
|
|
608
|
-
severity: "error",
|
|
609
|
-
}));
|
|
610
|
-
}
|
|
611
|
-
if (!/^Excludes:\s*$/m.test(scopeText)) {
|
|
612
|
-
findings.push(createFinding({
|
|
613
|
-
message: `Missing Excludes: block in Scope: ${file}`,
|
|
614
|
-
path: file,
|
|
615
|
-
ruleId: "skin-scope-includes-excludes",
|
|
616
|
-
severity: "error",
|
|
617
|
-
}));
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const declaredReference = extractAgentDocumentReference(text);
|
|
622
|
-
if (!declaredReference) {
|
|
623
|
-
findings.push(createFinding({
|
|
624
|
-
message: `Missing Agent Document reference: ${file}`,
|
|
625
|
-
path: file,
|
|
626
|
-
ruleId: "skin-agent-document-reference",
|
|
627
|
-
severity: "error",
|
|
628
|
-
}));
|
|
629
|
-
findings.push(...validateSkinMetrics({constants, file, text}));
|
|
630
|
-
return findings;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
const expectedReference = skinInfo.body;
|
|
634
|
-
const normalizedReference = normalizeAgentDocumentReference({reference: declaredReference, skinFile: file});
|
|
635
|
-
if (normalizedReference !== expectedReference) {
|
|
753
|
+
if (/^#{1,6}\s+Agent Document\s*#*\s*$/mi.test(text)) {
|
|
636
754
|
findings.push(createFinding({
|
|
637
|
-
message:
|
|
755
|
+
message: "Redundant Agent Document section: skin pairing is derived from its filename",
|
|
638
756
|
path: file,
|
|
639
|
-
ruleId: "skin-agent-document-
|
|
757
|
+
ruleId: "skin-redundant-agent-document-section",
|
|
640
758
|
severity: "error",
|
|
641
759
|
}));
|
|
642
760
|
}
|
|
643
|
-
if (!bodySet.has(normalizedReference)) {
|
|
644
|
-
findings.push(createFinding({
|
|
645
|
-
message: `Missing paired agent document: ${file} references ${normalizedReference}`,
|
|
646
|
-
path: file,
|
|
647
|
-
ruleId: "skin-paired-agent-document",
|
|
648
|
-
severity: "error",
|
|
649
|
-
}));
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
findings.push(...validateSkinMetrics({constants, file, text}));
|
|
653
761
|
return findings;
|
|
654
762
|
}
|
|
655
763
|
|
|
656
|
-
/**
|
|
657
|
-
* @param {object} params
|
|
658
|
-
* @param {Fl32_Skill_Adsm_Ctx_Shared_Constants} params.constants
|
|
659
|
-
* @param {string} params.file
|
|
660
|
-
* @param {string} params.text
|
|
661
|
-
* @returns {{message: string, path: string, ruleId: string, severity: "error" | "warning"}[]}
|
|
662
|
-
*/
|
|
663
|
-
function validateSkinMetrics({constants, file, text}) {
|
|
664
|
-
const findings = [];
|
|
665
|
-
const metricText = extractSkinMetricText(text);
|
|
666
|
-
const wordCount = countWords(metricText);
|
|
667
|
-
const listCount = countMeaningfulListItems(metricText);
|
|
668
|
-
|
|
669
|
-
if (wordCount > constants.SKIN_WORD_ERROR) {
|
|
670
|
-
findings.push(createFinding({
|
|
671
|
-
message: `Skin word count ${wordCount} exceeds limit ${constants.SKIN_WORD_ERROR}`,
|
|
672
|
-
path: file,
|
|
673
|
-
ruleId: "skin-size-word-count",
|
|
674
|
-
severity: "error",
|
|
675
|
-
}));
|
|
676
|
-
} else if (wordCount > constants.SKIN_WORD_WARN) {
|
|
677
|
-
findings.push(createFinding({
|
|
678
|
-
message: `Skin word count ${wordCount} exceeds warning threshold ${constants.SKIN_WORD_WARN}`,
|
|
679
|
-
path: file,
|
|
680
|
-
ruleId: "skin-size-word-count",
|
|
681
|
-
severity: "warning",
|
|
682
|
-
}));
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
if (listCount > constants.SKIN_LIST_ERROR) {
|
|
686
|
-
findings.push(createFinding({
|
|
687
|
-
message: `Skin list count ${listCount} exceeds limit ${constants.SKIN_LIST_ERROR}`,
|
|
688
|
-
path: file,
|
|
689
|
-
ruleId: "skin-size-list-count",
|
|
690
|
-
severity: "error",
|
|
691
|
-
}));
|
|
692
|
-
} else if (listCount > constants.SKIN_LIST_WARN) {
|
|
693
|
-
findings.push(createFinding({
|
|
694
|
-
message: `Skin list count ${listCount} exceeds warning threshold ${constants.SKIN_LIST_WARN}`,
|
|
695
|
-
path: file,
|
|
696
|
-
ruleId: "skin-size-list-count",
|
|
697
|
-
severity: "warning",
|
|
698
|
-
}));
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
return findings;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* @param {string} text
|
|
706
|
-
* @returns {string[]}
|
|
707
|
-
*/
|
|
708
|
-
function extractLevelTwoHeadings(text) {
|
|
709
|
-
return [...text.matchAll(/^##\s+(.+)$/gm)].map((match) => match[1].trim());
|
|
710
|
-
}
|
|
711
|
-
|
|
712
764
|
/**
|
|
713
765
|
* @param {object} params
|
|
714
766
|
* @param {string} params.sectionName
|
|
@@ -731,46 +783,6 @@ function extractSectionBody({sectionName, text}) {
|
|
|
731
783
|
return body.join("\n");
|
|
732
784
|
}
|
|
733
785
|
|
|
734
|
-
/**
|
|
735
|
-
* @param {string} text
|
|
736
|
-
* @returns {string | null}
|
|
737
|
-
*/
|
|
738
|
-
function extractAgentDocumentReference(text) {
|
|
739
|
-
const lines = text.split("\n");
|
|
740
|
-
let insideSection = false;
|
|
741
|
-
for (const rawLine of lines) {
|
|
742
|
-
const line = rawLine.trim();
|
|
743
|
-
if (!insideSection) {
|
|
744
|
-
if (line === "## Agent Document") insideSection = true;
|
|
745
|
-
continue;
|
|
746
|
-
}
|
|
747
|
-
if (line.startsWith("## ")) break;
|
|
748
|
-
if (line.length > 0) return line;
|
|
749
|
-
}
|
|
750
|
-
return null;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
* @param {object} params
|
|
755
|
-
* @param {string} params.reference
|
|
756
|
-
* @param {string} params.skinFile
|
|
757
|
-
* @returns {string}
|
|
758
|
-
*/
|
|
759
|
-
function normalizeAgentDocumentReference({reference, skinFile}) {
|
|
760
|
-
let value = reference.trim();
|
|
761
|
-
const codeMatch = value.match(/^`(.+)`$/);
|
|
762
|
-
if (codeMatch) value = codeMatch[1].trim();
|
|
763
|
-
const linkMatch = value.match(/^\[[^\]]+\]\(([^)]+)\)$/);
|
|
764
|
-
if (linkMatch) value = linkMatch[1].trim();
|
|
765
|
-
if (value.startsWith("./")) value = value.slice(2);
|
|
766
|
-
if (value.startsWith("ctx/docs/")) return value;
|
|
767
|
-
|
|
768
|
-
const skinParts = skinFile.split("/");
|
|
769
|
-
skinParts.pop();
|
|
770
|
-
skinParts.push(value);
|
|
771
|
-
return normalizeRelativeSegments(skinParts).join("/");
|
|
772
|
-
}
|
|
773
|
-
|
|
774
786
|
/**
|
|
775
787
|
* @param {object} params
|
|
776
788
|
* @param {string} params.file
|
|
@@ -818,60 +830,6 @@ function normalizeRelativeSegments(parts) {
|
|
|
818
830
|
return result;
|
|
819
831
|
}
|
|
820
832
|
|
|
821
|
-
/**
|
|
822
|
-
* @param {string} text
|
|
823
|
-
* @returns {string}
|
|
824
|
-
*/
|
|
825
|
-
function stripFencedCodeBlocks(text) {
|
|
826
|
-
return text.replace(/```[\s\S]*?```/g, "");
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
* Extracts semantic skin content while excluding metadata, headings, and the
|
|
831
|
-
* Agent Document reference.
|
|
832
|
-
*
|
|
833
|
-
* @param {string} text
|
|
834
|
-
* @returns {string}
|
|
835
|
-
*/
|
|
836
|
-
function extractSkinMetricText(text) {
|
|
837
|
-
return ["Purpose", "Mental Model", "Scope", "Invariants"]
|
|
838
|
-
.map((sectionName) => extractSectionBody({sectionName, text}))
|
|
839
|
-
.join("\n")
|
|
840
|
-
.replace(/^\s{0,3}#{1,6}\s+.*$/gm, "")
|
|
841
|
-
.replace(/^\s*(?:Includes|Excludes):\s*$/gm, "");
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
/**
|
|
845
|
-
* @param {string} text
|
|
846
|
-
* @returns {string}
|
|
847
|
-
*/
|
|
848
|
-
function stripSkinPresentation(text) {
|
|
849
|
-
return stripFencedCodeBlocks(text)
|
|
850
|
-
.split("\n")
|
|
851
|
-
.filter((line) => !/^#/.test(line.trim()))
|
|
852
|
-
.filter((line) => !/^- (?:Path|Changed): `[^`]+`$/.test(line.trim()))
|
|
853
|
-
.join("\n");
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* @param {string} text
|
|
858
|
-
* @returns {number}
|
|
859
|
-
*/
|
|
860
|
-
function countWords(text) {
|
|
861
|
-
const matches = stripFencedCodeBlocks(text).match(/(?:\p{L}[\p{L}\p{M}\p{N}'’_-]*|\p{N}+)/gu);
|
|
862
|
-
return matches ? matches.length : 0;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
/**
|
|
866
|
-
* @param {string} text
|
|
867
|
-
* @returns {number}
|
|
868
|
-
*/
|
|
869
|
-
function countMeaningfulListItems(text) {
|
|
870
|
-
return [...stripFencedCodeBlocks(text).matchAll(/^\s*(?:[-*+]|\d+[.)])\s+(.+)$/gmu)]
|
|
871
|
-
.filter((match) => countWords(match[1]) > 0)
|
|
872
|
-
.length;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
833
|
export const __deps__ = Object.freeze({
|
|
876
834
|
constants: "Fl32_Skill_Adsm_Ctx_Shared_Constants$",
|
|
877
835
|
fs: "node:fs",
|
package/types.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
declare global {
|
|
2
|
-
type
|
|
3
|
-
type
|
|
2
|
+
type Fl32_Skill_Adsm_Ctx_Cli_Command_Design = import("./src/Cli/Command/Design.mjs").default;
|
|
3
|
+
type Fl32_Skill_Adsm_Ctx_Cli_Command_Init = import("./src/Cli/Command/Init.mjs").default;
|
|
4
|
+
type Fl32_Skill_Adsm_Ctx_Cli_Command_InstallSkill = import("./src/Cli/Command/InstallSkill.mjs").default;
|
|
5
|
+
type Fl32_Skill_Adsm_Ctx_Cli_Command_Upgrade = import("./src/Cli/Command/Upgrade.mjs").default;
|
|
6
|
+
type Fl32_Skill_Adsm_Ctx_Cli_Command_Validate = import("./src/Cli/Command/Validate.mjs").default;
|
|
4
7
|
type Fl32_Skill_Adsm_Ctx_Design_Service = import("./src/Design/Service.mjs").default;
|
|
5
8
|
type Fl32_Skill_Adsm_Ctx_Init_Service = import("./src/Init/Service.mjs").default;
|
|
6
9
|
type Fl32_Skill_Adsm_Ctx_Shared_Constants = import("./src/Shared/Constants.mjs").default;
|