@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.
Files changed (103) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +45 -18
  3. package/bin/adsm-ctx.mjs +44 -0
  4. package/migrations/README.md +5 -1
  5. package/package.json +65 -11
  6. package/skill/SKILL.md +5 -4
  7. package/skill/references/context-lifecycle.md +14 -6
  8. package/skill/references/design-questionnaire.md +13 -1
  9. package/skill/references/document-projections.md +40 -80
  10. package/skill/references/documentation-compression.md +3 -2
  11. package/skill/references/documentation-quality-audit.md +37 -14
  12. package/skill/references/orchestration-model.md +4 -3
  13. package/skill/references/semantic-skins.md +65 -0
  14. package/skill/references/specialized-skills.md +5 -2
  15. package/skill/references/upgrade-policy.md +16 -6
  16. package/skill/templates/ctx/adsm.json +4 -1
  17. package/skill/templates/ctx/docs/AGENTS.md +3 -0
  18. package/skill/templates/ctx/docs/code/testing.md +4 -0
  19. package/skill/templates/work/context/semantic-skin.md +11 -22
  20. package/src/Cli/Command/Design.mjs +17 -0
  21. package/src/Cli/Command/Init.mjs +22 -0
  22. package/src/Cli/Command/InstallSkill.mjs +18 -0
  23. package/src/Cli/Command/Upgrade.mjs +19 -0
  24. package/src/Cli/Command/Validate.mjs +19 -0
  25. package/src/Design/Service.mjs +9 -7
  26. package/src/Shared/Constants.mjs +5 -5
  27. package/src/Shared/Metadata.mjs +1 -0
  28. package/src/Upgrade/Service.mjs +3 -3
  29. package/src/Validate/Service.mjs +217 -259
  30. package/types.d.ts +5 -2
  31. package/.github/workflows/flows-app.yaml +0 -95
  32. package/AGENTS.md +0 -112
  33. package/bin/cli.mjs +0 -55
  34. package/ctx/AGENTS.md +0 -44
  35. package/ctx/README.md +0 -24
  36. package/ctx/adsm.json +0 -5
  37. package/ctx/agent/AGENTS.md +0 -41
  38. package/ctx/agent/flows/.img/flancer32-skill-adsm-ctx-github-flows.png +0 -0
  39. package/ctx/agent/flows/AGENTS.md +0 -37
  40. package/ctx/agent/flows/cfg/issues/labeled/accepted/profile.json +0 -25
  41. package/ctx/agent/flows/cfg/issues/labeled/accepted/prompt.md +0 -104
  42. package/ctx/agent/flows/cfg/issues/labeled/clarification-provided/profile.json +0 -15
  43. package/ctx/agent/flows/cfg/issues/labeled/clarification-provided/prompt.md +0 -63
  44. package/ctx/agent/flows/cfg/issues/opened/admission/profile.json +0 -14
  45. package/ctx/agent/flows/cfg/issues/opened/admission/prompt.md +0 -110
  46. package/ctx/agent/flows/cfg/profile.json +0 -40
  47. package/ctx/agent/flows/cfg/pull_request/closed/merged/profile.json +0 -15
  48. package/ctx/agent/flows/cfg/pull_request/closed/merged/prompt.md +0 -41
  49. package/ctx/agent/flows/cfg/pull_request/labeled/needs-fix/profile.json +0 -25
  50. package/ctx/agent/flows/cfg/pull_request/labeled/needs-fix/prompt.md +0 -79
  51. package/ctx/agent/flows/cfg/pull_request/labeled/ready-for-validation/profile.json +0 -15
  52. package/ctx/agent/flows/cfg/pull_request/labeled/ready-for-validation/prompt.md +0 -72
  53. package/ctx/agent/flows/cfg/pull_request/labeled/verified/profile.json +0 -15
  54. package/ctx/agent/flows/cfg/pull_request/labeled/verified/prompt.md +0 -48
  55. package/ctx/agent/flows/deployment.md +0 -116
  56. package/ctx/agent/flows/labels.md +0 -108
  57. package/ctx/agent/flows/overview.md +0 -82
  58. package/ctx/agent/prompt/cli/ai-intro.md +0 -141
  59. package/ctx/assets/.gitkeep +0 -1
  60. package/ctx/docs/AGENTS.md +0 -121
  61. package/ctx/docs/ai-intro.md +0 -37
  62. package/ctx/docs/architecture/AGENTS.md +0 -98
  63. package/ctx/docs/architecture/behavior.md +0 -68
  64. package/ctx/docs/architecture/checklists.md +0 -48
  65. package/ctx/docs/architecture/constraints.md +0 -53
  66. package/ctx/docs/architecture/constraints.skin.en.md +0 -38
  67. package/ctx/docs/architecture/decisions.md +0 -118
  68. package/ctx/docs/architecture/integration.md +0 -31
  69. package/ctx/docs/architecture/overview.md +0 -76
  70. package/ctx/docs/architecture/state.md +0 -50
  71. package/ctx/docs/architecture/structure.md +0 -38
  72. package/ctx/docs/architecture/supervision.md +0 -71
  73. package/ctx/docs/code/AGENTS.md +0 -51
  74. package/ctx/docs/code/overview.md +0 -53
  75. package/ctx/docs/code/testing.md +0 -26
  76. package/ctx/docs/environment/AGENTS.md +0 -50
  77. package/ctx/docs/environment/overview.md +0 -33
  78. package/ctx/docs/filesystem.md +0 -44
  79. package/ctx/docs/product/AGENTS.md +0 -51
  80. package/ctx/docs/product/domain.md +0 -63
  81. package/ctx/docs/product/domain.skin.en.md +0 -40
  82. package/ctx/docs/product/glossary.md +0 -31
  83. package/ctx/docs/product/overview.md +0 -112
  84. package/ctx/docs/product/overview.skin.en.md +0 -38
  85. package/ctx/docs/product/roles.md +0 -17
  86. package/ctx/docs/product/use-cases.md +0 -15
  87. package/src/Bootstrap/Cli.mjs +0 -62
  88. package/src/Bootstrap.mjs +0 -108
  89. package/test/integration/.gitkeep +0 -1
  90. package/test/integration/cli-help.test.mjs +0 -15
  91. package/test/integration/cli-init-validate.test.mjs +0 -223
  92. package/test/integration/cli-install-skill.test.mjs +0 -41
  93. package/test/unit/Bootstrap/Cli.test.mjs +0 -23
  94. package/test/unit/Bootstrap.test.mjs +0 -53
  95. package/test/unit/Design/Service.test.mjs +0 -11
  96. package/test/unit/Init/Service.test.mjs +0 -51
  97. package/test/unit/Shared/Constants.test.mjs +0 -21
  98. package/test/unit/Shared/CopyDirectory.test.mjs +0 -31
  99. package/test/unit/Shared/Metadata.test.mjs +0 -9
  100. package/test/unit/Shared/Path.test.mjs +0 -11
  101. package/test/unit/Skill/Installer.test.mjs +0 -20
  102. package/test/unit/Upgrade/Service.test.mjs +0 -13
  103. package/test/unit/Validate/Service.test.mjs +0 -760
@@ -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 top-level documentation levels under ctx/docs/: product, architecture, environment, and code.",
27
- "3. Add only project-specific substructure that is justified by the project; nested directories are allowed.",
28
- "4. Treat ordinary *.md files as agent-facing operational context documents and add *.skin.<lang>.md files only where human semantic control is needed.",
29
- "5. Place visual, generated, rendered, or auxiliary artifacts under ctx/assets/ instead of ctx/docs/.",
30
- "6. Detect whether specialized ADSM skills are needed for browser, backend, data, deployment, or other areas.",
31
- "7. If a specialized skill is available, use it for that branch after the generic structure is in place.",
32
- "8. If no specialized skill is available, record the specialization gap and avoid inventing details.",
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
  }
@@ -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.1.0";
12
- this.SKIN_WORD_WARN = 160;
13
- this.SKIN_WORD_ERROR = 240;
14
- this.SKIN_LIST_WARN = 12;
15
- this.SKIN_LIST_ERROR = 20;
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([
@@ -20,6 +20,7 @@ export default class Metadata {
20
20
  return {
21
21
  schema: constants.CONTEXT_SCHEMA,
22
22
  version: constants.CONTEXT_VERSION,
23
+ capabilities: [...(constants.CONTEXT_CAPABILITIES ?? [])],
23
24
  };
24
25
  };
25
26
  }
@@ -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 for ADSM context lifecycle.
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
- * Runs the upgrade entry point.
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 currently available for this context.",
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
  };
@@ -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 metadata = readMetadata({constants, fs, pathService, projectRoot});
66
- const projection = inspectProjection({constants, fs, pathService, projectRoot});
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
- if (!metadata.ok) findings.push(metadata.finding);
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: metadata.ok ? metadata.metadata : null,
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
- return {
194
- ok: false,
195
- finding: createFinding({
196
- message: "Missing ctx/adsm.json",
197
- path: "ctx/adsm.json",
198
- ruleId: "metadata-file",
199
- severity: "error",
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
- return {
208
- ok: false,
209
- finding: createFinding({
210
- message: `Unexpected schema: ${parsed.schema}`,
211
- path: "ctx/adsm.json",
212
- ruleId: "metadata-schema",
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
- return {
219
- ok: false,
220
- finding: createFinding({
221
- message: "Missing metadata version",
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-version",
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
- return {ok: true, metadata: parsed};
253
+
254
+ const ok = findings.every((entry) => entry.severity !== "error");
255
+ return {findings, metadata: ok ? parsed : null, ok};
229
256
  } catch (error) {
230
- return {
231
- ok: false,
232
- finding: createFinding({
233
- message: `Invalid metadata JSON: ${error instanceof Error ? error.message : "Unknown error"}`,
234
- path: "ctx/adsm.json",
235
- ruleId: "metadata-json",
236
- severity: "error",
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
- const headings = extractLevelTwoHeadings(text);
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: `Skin agent document mismatch: ${file} references ${normalizedReference} but expected ${expectedReference}`,
755
+ message: "Redundant Agent Document section: skin pairing is derived from its filename",
638
756
  path: file,
639
- ruleId: "skin-agent-document-reference",
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 Fl32_Skill_Adsm_Ctx_Bootstrap = import("./src/Bootstrap.mjs").default;
3
- type Fl32_Skill_Adsm_Ctx_Bootstrap_Cli = import("./src/Bootstrap/Cli.mjs").default;
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;