renma 0.15.2 → 0.17.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 (126) hide show
  1. package/CHANGELOG.md +54 -1
  2. package/README.md +216 -655
  3. package/architecture.md +523 -0
  4. package/design.md +458 -0
  5. package/dist/agent-skills.d.ts +77 -0
  6. package/dist/agent-skills.d.ts.map +1 -0
  7. package/dist/agent-skills.js +468 -0
  8. package/dist/agent-skills.js.map +1 -0
  9. package/dist/catalog.d.ts.map +1 -1
  10. package/dist/catalog.js +38 -5
  11. package/dist/catalog.js.map +1 -1
  12. package/dist/cli-help.d.ts +13 -11
  13. package/dist/cli-help.d.ts.map +1 -1
  14. package/dist/cli-help.js +36 -11
  15. package/dist/cli-help.js.map +1 -1
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +139 -40
  18. package/dist/cli.js.map +1 -1
  19. package/dist/commands/catalog.d.ts.map +1 -1
  20. package/dist/commands/catalog.js +4 -3
  21. package/dist/commands/catalog.js.map +1 -1
  22. package/dist/commands/graph.d.ts.map +1 -1
  23. package/dist/commands/graph.js +7 -12
  24. package/dist/commands/graph.js.map +1 -1
  25. package/dist/commands/readiness.d.ts.map +1 -1
  26. package/dist/commands/readiness.js +7 -6
  27. package/dist/commands/readiness.js.map +1 -1
  28. package/dist/commands/scaffold.d.ts.map +1 -1
  29. package/dist/commands/scaffold.js +59 -16
  30. package/dist/commands/scaffold.js.map +1 -1
  31. package/dist/commands/suggest-metadata.d.ts +3 -1
  32. package/dist/commands/suggest-metadata.d.ts.map +1 -1
  33. package/dist/commands/suggest-metadata.js +226 -7
  34. package/dist/commands/suggest-metadata.js.map +1 -1
  35. package/dist/commands/suggest-semantic-split.js +3 -3
  36. package/dist/commands/suggest-semantic-split.js.map +1 -1
  37. package/dist/dependency-resolution.d.ts +6 -0
  38. package/dist/dependency-resolution.d.ts.map +1 -0
  39. package/dist/dependency-resolution.js +11 -0
  40. package/dist/dependency-resolution.js.map +1 -0
  41. package/dist/diagnostic-ids.d.ts +29 -0
  42. package/dist/diagnostic-ids.d.ts.map +1 -1
  43. package/dist/diagnostic-ids.js +28 -0
  44. package/dist/diagnostic-ids.js.map +1 -1
  45. package/dist/discovery.d.ts +50 -0
  46. package/dist/discovery.d.ts.map +1 -1
  47. package/dist/discovery.js +183 -25
  48. package/dist/discovery.js.map +1 -1
  49. package/dist/metadata.d.ts +3 -1
  50. package/dist/metadata.d.ts.map +1 -1
  51. package/dist/metadata.js +166 -29
  52. package/dist/metadata.js.map +1 -1
  53. package/dist/model.d.ts +1 -0
  54. package/dist/model.d.ts.map +1 -1
  55. package/dist/report.d.ts.map +1 -1
  56. package/dist/report.js +16 -1
  57. package/dist/report.js.map +1 -1
  58. package/dist/repository-paths.d.ts +13 -0
  59. package/dist/repository-paths.d.ts.map +1 -1
  60. package/dist/repository-paths.js +60 -2
  61. package/dist/repository-paths.js.map +1 -1
  62. package/dist/rules.d.ts.map +1 -1
  63. package/dist/rules.js +97 -107
  64. package/dist/rules.js.map +1 -1
  65. package/dist/scanner.d.ts.map +1 -1
  66. package/dist/scanner.js +3 -1
  67. package/dist/scanner.js.map +1 -1
  68. package/dist/security-diagnostics.d.ts.map +1 -1
  69. package/dist/security-diagnostics.js +98 -35
  70. package/dist/security-diagnostics.js.map +1 -1
  71. package/dist/security-policy-inventory.js +11 -4
  72. package/dist/security-policy-inventory.js.map +1 -1
  73. package/dist/security-policy.d.ts +27 -1
  74. package/dist/security-policy.d.ts.map +1 -1
  75. package/dist/security-policy.js +247 -34
  76. package/dist/security-policy.js.map +1 -1
  77. package/dist/skill-migration.d.ts +30 -0
  78. package/dist/skill-migration.d.ts.map +1 -0
  79. package/dist/skill-migration.js +566 -0
  80. package/dist/skill-migration.js.map +1 -0
  81. package/dist/trust-graph.d.ts.map +1 -1
  82. package/dist/trust-graph.js +7 -11
  83. package/dist/trust-graph.js.map +1 -1
  84. package/dist/types.d.ts +3 -1
  85. package/dist/types.d.ts.map +1 -1
  86. package/dist/yaml-frontmatter.d.ts +26 -0
  87. package/dist/yaml-frontmatter.d.ts.map +1 -0
  88. package/dist/yaml-frontmatter.js +114 -0
  89. package/dist/yaml-frontmatter.js.map +1 -0
  90. package/docs/README.md +55 -0
  91. package/docs/advanced-skill-authoring.md +140 -0
  92. package/docs/agent-skills-compatibility.md +500 -0
  93. package/docs/authoring-guide.md +324 -0
  94. package/docs/context-conflict-diagnostics.md +32 -0
  95. package/docs/context-language-diagnostics.md +99 -0
  96. package/docs/context-lens.md +253 -0
  97. package/docs/context-lifecycle-diagnostics.md +55 -0
  98. package/docs/diagnostics.md +373 -0
  99. package/docs/metadata-budget.md +14 -0
  100. package/docs/repository-context-bom.md +113 -0
  101. package/docs/security-policy.md +338 -0
  102. package/docs/user-manual.md +813 -0
  103. package/examples/context-lens/README.md +101 -0
  104. package/examples/context-lens/contexts/testing/boundary-value-analysis.md +23 -0
  105. package/examples/context-lens/lenses/testing/spec-review-boundary-values.md +29 -0
  106. package/examples/context-lens/lenses/testing/test-design-boundary-values.md +29 -0
  107. package/examples/context-lens/skills/testing/spec-review/SKILL.md +49 -0
  108. package/examples/context-repo/README.md +149 -0
  109. package/examples/context-repo/contexts/domain/payment/idempotency.md +20 -0
  110. package/examples/context-repo/contexts/testing/boundary-value-analysis.md +19 -0
  111. package/examples/context-repo/contexts/testing/negative-testing.md +19 -0
  112. package/examples/context-repo/lenses/testing/spec-review-boundary-values.md +35 -0
  113. package/examples/context-repo/renma.config.json +12 -0
  114. package/examples/context-repo/skills/testing/spec-review/SKILL.md +66 -0
  115. package/examples/context-repo/tools/appium/README.md +14 -0
  116. package/examples/github-actions/renma-ci-report.yml +36 -0
  117. package/examples/interactive-placeholder/README.md +104 -0
  118. package/examples/interactive-placeholder/assets/template.txt +1 -0
  119. package/examples/interactive-placeholder/renma.config.json +6 -0
  120. package/examples/interactive-placeholder/skills/replace-placeholder/SKILL.md +54 -0
  121. package/examples/interactive-placeholder/tools/README.md +48 -0
  122. package/examples/interactive-placeholder/tools/placeholder-demo.mjs +99 -0
  123. package/package.json +11 -1
  124. package/plan-discovery.md +740 -0
  125. package/plan.md +150 -0
  126. package/scripts/verify-package.mjs +97 -0
package/dist/rules.js CHANGED
@@ -1,7 +1,10 @@
1
1
  import path from "node:path";
2
+ import { resolvedAgentSkillDescription } from "./agent-skills.js";
2
3
  import { addDaysIsoDate, isIsoDate, parseDayDuration, todayIsoDate, } from "./freshness.js";
3
4
  import { DIAGNOSTIC_IDS } from "./diagnostic-ids.js";
4
- import { helperScriptPath } from "./repository-paths.js";
5
+ import { classifyRepositorySkillEntrypointPath, classifyRepositorySkillPath, normalizeRepositoryRelativePath, } from "./discovery.js";
6
+ import { helperScriptPath, resolveHelperScriptPath, } from "./repository-paths.js";
7
+ import { parseAssetMetadata } from "./metadata.js";
5
8
  import { runRuleRegistry } from "./rule-engine.js";
6
9
  const SECRET_PATTERN = /\b(?:password|passwd|token|api[_-]?key|secret|credential|private[_-]?key)\b\s*[:=]\s*["']?([A-Za-z0-9_./+=-]{8,})/i;
7
10
  const PRIVATE_KEY_PATTERN = /-----BEGIN (?:RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----/;
@@ -131,7 +134,7 @@ function rulesForEvaluationDate(evaluationDate, repositoryPaths) {
131
134
  return [
132
135
  {
133
136
  id: "strict-layout-policy",
134
- run: (context) => strictLayoutPolicyFindings(context.documents, context.config, context.catalog, repositoryPaths),
137
+ run: (context) => strictLayoutPolicyFindings(context.documents, context.catalog, repositoryPaths),
135
138
  },
136
139
  {
137
140
  id: "security",
@@ -434,7 +437,7 @@ function orphanedContextLensFindings(entries, resolver) {
434
437
  confidence: "medium",
435
438
  evidence: metadataFindingEvidence(entry.sourcePath, "Active context lens has no incoming requires_lens or optional_lens references from skills."),
436
439
  whyItMatters: "Context lenses are easier to review when a skill declares how the purpose-oriented interpretation is used. An unreferenced active lens may be newly created, intentionally staged, or stale.",
437
- remediation: "If the lens should be used, reference it from a skill with requires_lens or optional_lens. If it is not ready or no longer needed, update its lifecycle status after review.",
440
+ remediation: "If the lens should be used, reference it from a canonical Skill with metadata.renma.requires-lens or metadata.renma.optional-lens JSON-array metadata. Pre-0.16-only Skills use requires_lens or optional_lens only during migration. If the lens is not ready or no longer needed, update its lifecycle status after review.",
438
441
  constraints: [
439
442
  "Do not make Renma select runtime lenses.",
440
443
  "Do not rank or retrieve lenses semantically.",
@@ -446,7 +449,7 @@ function orphanedContextLensFindings(entries, resolver) {
446
449
  "Run renma catalog.",
447
450
  "Run renma graph focused on the lens or owning skill.",
448
451
  ],
449
- llmHint: `Search for skills that should declare "${entry.id}" in requires_lens or optional_lens. Do not add runtime selection logic or prompt assembly.`,
452
+ llmHint: `Search for Skills that should declare "${entry.id}" as a required or optional lens. For canonical Skills, update metadata.renma.requires-lens or metadata.renma.optional-lens as a JSON-array string; use requires_lens or optional_lens only for pre-0.16 migration inputs. Do not add runtime selection logic or prompt assembly.`,
450
453
  details: {
451
454
  assetId: entry.id,
452
455
  sourcePath: entry.sourcePath,
@@ -640,7 +643,9 @@ function shapeFindings(document) {
640
643
  return [];
641
644
  const text = document.artifact.content.toLowerCase();
642
645
  const findings = [];
643
- const description = document.metadata.description ?? "";
646
+ const description = document.artifact.kind === "skill"
647
+ ? (resolvedAgentSkillDescription(document) ?? "")
648
+ : (document.metadata.description ?? "");
644
649
  const tokenCount = approximateTokenCount(document.artifact.content);
645
650
  if (!description) {
646
651
  findings.push(documentFinding(document, DIAGNOSTIC_IDS.QUAL_MISSING_DESCRIPTION, "Skill is missing an explicit description", "quality", "medium", "Add frontmatter description so agents can route to the skill intentionally."));
@@ -804,7 +809,10 @@ function reusableContextCandidateFinding(document, tokenCount) {
804
809
  function supportSharedContextCandidateFindings(document) {
805
810
  if (document.artifact.kind !== "reference")
806
811
  return [];
807
- if (!/^skills\/[^/]+\/references\/.+\.md$/u.test(document.artifact.path)) {
812
+ const skillPath = classifyRepositorySkillPath(document.artifact.path);
813
+ if (skillPath?.kind !== "support" ||
814
+ skillPath.supportDirectory !== "references" ||
815
+ !document.artifact.path.endsWith(".md")) {
808
816
  return [];
809
817
  }
810
818
  const tokenCount = approximateTokenCount(document.artifact.content);
@@ -936,7 +944,8 @@ function contextPathNonSemanticFindings(document) {
936
944
  function skillContextReferenceNotDeclaredFindings(document) {
937
945
  if (document.artifact.kind !== "skill")
938
946
  return [];
939
- const declaredContexts = new Set(listMetadataValue(document.metadata.requires_context));
947
+ const operationalMetadata = parseAssetMetadata(document).metadata;
948
+ const declaredContexts = new Set(operationalMetadata.requiresContext);
940
949
  const bodyLineIndexes = markdownBodyLineIndexes(document);
941
950
  const matches = new Map();
942
951
  for (const index of bodyLineIndexes) {
@@ -961,7 +970,7 @@ function skillContextReferenceNotDeclaredFindings(document) {
961
970
  confidence: "high",
962
971
  evidence: evidence(document, match.line, match.text),
963
972
  whyItMatters: "Declared context references make skill/context relationships visible to catalog, graph, and validation reports. If a skill only mentions a context in prose, humans may see the dependency but repository tooling cannot validate it.",
964
- remediation: "Add the referenced shared context asset to the skill metadata using requires_context, or remove the prose reference if it is no longer needed.",
973
+ remediation: "Add the referenced shared context asset to metadata.renma.requires-context as a JSON-array string, or remove the prose reference if it is no longer needed. Pre-0.16 requires_context is migration input only and is not operational in Renma 0.16.0.",
965
974
  constraints: [
966
975
  "Do not select runtime context.",
967
976
  "Do not assemble prompt packages.",
@@ -973,9 +982,9 @@ function skillContextReferenceNotDeclaredFindings(document) {
973
982
  "Run renma catalog.",
974
983
  "Confirm the skill/context relationship appears in metadata and catalog output.",
975
984
  ],
976
- llmHint: `Find context paths mentioned in the skill body and add them to requires_context using the metadata syntax currently supported by Renma. Missing declaration: ${referencedPath}`,
985
+ llmHint: `Find context paths mentioned in the Skill body and add the missing declaration using metadata.renma.requires-context as a JSON-array string. Pre-0.16 requires_context is accepted only by suggest-metadata and is not operational. Missing declaration: ${referencedPath}`,
977
986
  details: {
978
- source: metadataText(document.metadata.id) ?? document.artifact.path,
987
+ source: operationalMetadata.id ?? document.artifact.path,
979
988
  target: referencedPath,
980
989
  referenceKind: "requires_context",
981
990
  sourcePath: document.artifact.path,
@@ -1002,6 +1011,7 @@ function skillReferencesSupersededAssetFindings(documents) {
1002
1011
  const skill = skillsByPath.get(skillPath);
1003
1012
  if (!skill)
1004
1013
  return [];
1014
+ const skillMetadata = parseAssetMetadata(skill).metadata;
1005
1015
  const referencedFrom = skillReferenceLine(skill, document.artifact.path);
1006
1016
  if (!referencedFrom)
1007
1017
  return [];
@@ -1042,7 +1052,7 @@ function skillReferencesSupersededAssetFindings(documents) {
1042
1052
  ],
1043
1053
  llmHint: "Inspect the deprecated local support file and its superseded_by or canonical_context metadata. If the shared context asset is now canonical, update skill guidance and metadata to reference the shared context directly. Keep the local reference only if it contains truly local notes or is intentionally preserved as a compatibility shim.",
1044
1054
  details: {
1045
- source: metadataText(skill.metadata.id) ?? skill.artifact.path,
1055
+ source: skillMetadata.id ?? skill.artifact.path,
1046
1056
  target: metadataText(document.metadata.id) ?? document.artifact.path,
1047
1057
  referenceKind: "body_reference",
1048
1058
  sourcePath: skill.artifact.path,
@@ -1055,20 +1065,24 @@ function skillReferencesSupersededAssetFindings(documents) {
1055
1065
  });
1056
1066
  }
1057
1067
  function isSkillLocalReference(document) {
1068
+ const skillPath = classifyRepositorySkillPath(document.artifact.path);
1058
1069
  return (document.artifact.kind === "reference" &&
1059
- /^skills\/[^/]+\/references\/.+\.md$/u.test(document.artifact.path));
1070
+ skillPath?.kind === "support" &&
1071
+ skillPath.supportDirectory === "references" &&
1072
+ document.artifact.path.endsWith(".md"));
1060
1073
  }
1061
1074
  function sharedContextTargets(document) {
1075
+ const operationalMetadata = parseAssetMetadata(document).metadata;
1062
1076
  return [
1063
- ...listMetadataValue(document.metadata.superseded_by),
1077
+ ...operationalMetadata.supersededBy,
1064
1078
  ...listMetadataValue(document.metadata.canonical_context),
1065
1079
  ].filter((target, index, targets) => /^contexts?\//u.test(target) && targets.indexOf(target) === index);
1066
1080
  }
1067
1081
  function parentSkillPath(referencePath) {
1068
- const segments = referencePath.split("/");
1069
- if (segments.length < 4 || segments[0] !== "skills")
1070
- return undefined;
1071
- return `skills/${segments[1]}/SKILL.md`;
1082
+ const skillPath = classifyRepositorySkillPath(referencePath);
1083
+ return skillPath?.kind === "support"
1084
+ ? `${skillPath.skillDirectory}/SKILL.md`
1085
+ : undefined;
1072
1086
  }
1073
1087
  function skillReferenceLine(skill, referencePath) {
1074
1088
  const skillDir = path.posix.dirname(skill.artifact.path);
@@ -1084,18 +1098,23 @@ function skillReferenceLine(skill, referencePath) {
1084
1098
  }
1085
1099
  function assetReferencesSupersededAssetFindings(documents) {
1086
1100
  const supersededAssets = documents
1087
- .map((document) => ({
1088
- document,
1089
- canonicalTargets: sharedContextTargets(document),
1090
- }))
1091
- .filter(({ document, canonicalTargets }) => metadataText(document.metadata.status) === "deprecated" ||
1092
- metadataText(document.metadata.status) === "archived" ||
1101
+ .map((document) => {
1102
+ const metadata = parseAssetMetadata(document).metadata;
1103
+ return {
1104
+ document,
1105
+ metadata,
1106
+ canonicalTargets: sharedContextTargets(document),
1107
+ };
1108
+ })
1109
+ .filter(({ metadata, canonicalTargets }) => metadata.status === "deprecated" ||
1110
+ metadata.status === "archived" ||
1093
1111
  canonicalTargets.length > 0)
1094
1112
  .filter(({ canonicalTargets }) => canonicalTargets.length > 0);
1095
1113
  return documents.flatMap((referencingDocument) => {
1096
1114
  if (referencingDocument.artifact.kind === "skill")
1097
1115
  return [];
1098
- return supersededAssets.flatMap(({ document, canonicalTargets }) => {
1116
+ const referencingMetadata = parseAssetMetadata(referencingDocument).metadata;
1117
+ return supersededAssets.flatMap(({ document, metadata, canonicalTargets }) => {
1099
1118
  if (document.artifact.path === referencingDocument.artifact.path) {
1100
1119
  return [];
1101
1120
  }
@@ -1139,13 +1158,12 @@ function assetReferencesSupersededAssetFindings(documents) {
1139
1158
  ],
1140
1159
  llmHint: "Inspect the referenced deprecated asset and its superseded_by or canonical context metadata. If the canonical shared context is the intended source of truth, update this asset to reference that context directly. Keep the superseded file only when it serves a deliberate compatibility or migration role.",
1141
1160
  details: {
1142
- source: metadataText(referencingDocument.metadata.id) ??
1143
- referencingDocument.artifact.path,
1144
- target: metadataText(document.metadata.id) ?? document.artifact.path,
1161
+ source: referencingMetadata.id ?? referencingDocument.artifact.path,
1162
+ target: metadata.id ?? document.artifact.path,
1145
1163
  referenceKind: "body_reference",
1146
1164
  sourcePath: referencingDocument.artifact.path,
1147
1165
  targetPath: document.artifact.path,
1148
- targetStatus: metadataText(document.metadata.status),
1166
+ targetStatus: metadata.status,
1149
1167
  replacementTargets: canonicalTargets,
1150
1168
  },
1151
1169
  },
@@ -1336,14 +1354,13 @@ function matchingLineFindings(document, matcher) {
1336
1354
  ];
1337
1355
  });
1338
1356
  }
1339
- function strictLayoutPolicyFindings(documents, config, catalog, repositoryPaths) {
1357
+ function strictLayoutPolicyFindings(documents, catalog, repositoryPaths) {
1340
1358
  const findings = [];
1341
1359
  const paths = repositoryPaths ??
1342
1360
  new Set(documents.map((document) => document.artifact.path));
1343
1361
  for (const document of documents) {
1344
- findings.push(...disallowedSkillAssetFindings(document, config));
1345
1362
  findings.push(...thinSkillLayoutFindings(document));
1346
- findings.push(...helperCommandFindings(document, paths, config));
1363
+ findings.push(...helperCommandFindings(document, paths));
1347
1364
  findings.push(...layoutConsistencyFindings(document));
1348
1365
  findings.push(...contextRootFindings(document));
1349
1366
  findings.push(...helperRootFindings(document));
@@ -1353,34 +1370,9 @@ function strictLayoutPolicyFindings(documents, config, catalog, repositoryPaths)
1353
1370
  }
1354
1371
  return findings;
1355
1372
  }
1356
- function disallowedSkillAssetFindings(document, config) {
1357
- const match = document.artifact.path.match(/^skills\/([^/]+)\/(references|profiles|examples|scripts)\/(.+)$/);
1358
- if (!match)
1359
- return [];
1360
- const [, skillName = "", assetRoot = "", rest = ""] = match;
1361
- const target = canonicalSkillAssetTarget(config, skillName, assetRoot, rest);
1362
- return [
1363
- documentFinding(document, DIAGNOSTIC_IDS.LAYOUT_DISALLOWED_SKILL_ASSET, `Disallowed skill-local ${assetRoot} asset`, "structure", assetRoot === "scripts" ? "medium" : "low", `Move this file to \`${target}\` and update every repo-local reference to the new canonical path.`, {
1364
- whyItMatters: "Strict layout keeps skills as thin entrypoints, shared context under contexts/, and executable helpers under tools/ so agents can migrate repositories deterministically.",
1365
- verificationSteps: [
1366
- `Move ${document.artifact.path} to ${target}.`,
1367
- "Run renma scan and renma readiness again.",
1368
- ],
1369
- llmHint: `Move ${document.artifact.path} to ${target}, then rewrite references from the old path to the new path. Do not leave a compatibility shim unless another Renma finding requires it.`,
1370
- }),
1371
- ];
1372
- }
1373
- function canonicalSkillAssetTarget(config, skillName, assetRoot, rest) {
1374
- const workflow = canonicalWorkflowName(config, skillName);
1375
- if (assetRoot === "scripts")
1376
- return helperAssetPath(config, workflow, `scripts/${rest}`);
1377
- return contextAssetPath(config, workflow, `${assetRoot}/${rest}`);
1378
- }
1379
- function canonicalWorkflowName(config, skillName) {
1380
- return config.layout.workflowAliases[skillName] ?? skillName;
1381
- }
1382
1373
  function thinSkillLayoutFindings(document) {
1383
- if (!isCanonicalSkillEntrypoint(document.artifact.path))
1374
+ if (classifyRepositorySkillEntrypointPath(document.artifact.path)?.kind !==
1375
+ "canonical")
1384
1376
  return [];
1385
1377
  const findings = [];
1386
1378
  const wordCount = words(document.artifact.content).length;
@@ -1391,42 +1383,50 @@ function thinSkillLayoutFindings(document) {
1391
1383
  if (wordCount > 450 ||
1392
1384
  procedureSignals.some((pattern) => pattern.test(document.artifact.content))) {
1393
1385
  findings.push(documentFinding(document, DIAGNOSTIC_IDS.LAYOUT_SKILL_NOT_THIN, "Skill entrypoint contains procedure content", "structure", wordCount > 700 ? "medium" : "low", "Move reusable procedure content into contexts/** and keep SKILL.md as a concise router that points to the required context assets.", {
1394
- whyItMatters: "Strict layout expects skills/*/SKILL.md to route agents, not own canonical references, profiles, examples, scripts, or detailed procedures.",
1386
+ whyItMatters: "Canonical Skill entrypoints should route agents instead of owning reusable knowledge, helper implementations, or detailed procedures.",
1395
1387
  verificationSteps: [
1396
1388
  "Confirm SKILL.md contains routing guidance and required context references only.",
1397
1389
  "Run renma readiness and check layout.skills_thin.",
1398
1390
  ],
1399
- llmHint: "Extract long procedure sections into contexts/**, add requires_context or optional_context references, and keep SKILL.md focused on when to use or not use the skill.",
1391
+ llmHint: "Extract long procedure sections into contexts/**, add metadata.renma.requires-context or metadata.renma.optional-context JSON-array strings, and keep SKILL.md focused on when to use or not use the Skill. Pre-0.16 top-level references are migration input only.",
1400
1392
  }));
1401
1393
  }
1402
1394
  const command = firstExecutableCommand(document);
1403
1395
  if (command) {
1404
- findings.push(findingAt(document, DIAGNOSTIC_IDS.LAYOUT_SKILL_EXECUTABLE_COMMAND, "Skill entrypoint contains executable command", "structure", "low", command.line, command.command, "Move executable setup commands into contexts/** procedures or tools/** helper documentation, and keep SKILL.md as a router.", {
1405
- whyItMatters: "Executable setup commands in SKILL.md make the entrypoint procedural and harder to migrate to the strict three-root layout.",
1396
+ findings.push(findingAt(document, DIAGNOSTIC_IDS.LAYOUT_SKILL_EXECUTABLE_COMMAND, "Skill entrypoint contains executable command", "structure", "low", command.line, command.command, "Move executable command detail into Skill-local references or governed contexts/** procedures, and keep SKILL.md as a router.", {
1397
+ whyItMatters: "Executable setup commands in SKILL.md make the entrypoint procedural instead of a concise routing layer.",
1406
1398
  verificationSteps: [
1407
1399
  "Move the command guidance to a context/reference/procedure.",
1408
- "If the command invokes a helper script, ensure it points under tools/**.",
1400
+ "If the command invokes a helper script, ensure it resolves to local scripts/** in the owning Skill or a shared tools/** helper.",
1409
1401
  ],
1410
1402
  }));
1411
1403
  }
1412
1404
  return findings;
1413
1405
  }
1414
- function helperCommandFindings(document, paths, config) {
1406
+ function helperCommandFindings(document, paths) {
1415
1407
  const findings = [];
1416
1408
  for (const command of executableCommands(document)) {
1417
- const scriptPath = helperScriptPath(command.command);
1418
- if (!scriptPath)
1409
+ const commandPath = helperScriptPath(command.command);
1410
+ if (!commandPath)
1419
1411
  continue;
1420
- if (/^skills\/[^/]+\/scripts\//.test(scriptPath)) {
1421
- findings.push(findingAt(document, DIAGNOSTIC_IDS.PATH_HELPER_COMMAND_SKILL_SCRIPTS, "Helper command points to skill-local scripts", "structure", "medium", command.line, command.command, `Move the helper script to \`${canonicalHelperTarget(config, scriptPath)}\` and update this command to use the tools/** path.`, {
1422
- whyItMatters: "Strict layout keeps executable helper assets under tools/**, not under skills/**.",
1423
- verificationSteps: [
1424
- `Confirm ${canonicalHelperTarget(config, scriptPath)} exists.`,
1425
- "Run renma scan and renma readiness again.",
1426
- ],
1412
+ const resolution = resolveHelperScriptPath(document.artifact.path, commandPath);
1413
+ if (resolution.kind === "unscoped")
1414
+ continue;
1415
+ if (resolution.kind === "unsafe") {
1416
+ findings.push(unresolvedHelperCommandFinding(document, command, commandPath, {
1417
+ details: { unsafePath: true },
1427
1418
  }));
1428
1419
  continue;
1429
1420
  }
1421
+ const scriptPath = resolution.path;
1422
+ const skillPath = classifyRepositorySkillPath(scriptPath);
1423
+ if (skillPath?.kind === "support" &&
1424
+ skillPath.supportDirectory === "scripts") {
1425
+ if (!paths.has(scriptPath)) {
1426
+ findings.push(unresolvedHelperCommandFinding(document, command, scriptPath));
1427
+ }
1428
+ continue;
1429
+ }
1430
1430
  if (scriptPath.includes("/scripts/") && !scriptPath.startsWith("tools/")) {
1431
1431
  findings.push(findingAt(document, DIAGNOSTIC_IDS.PATH_HELPER_COMMAND_NON_TOOLS, "Helper command does not use tools root", "structure", "low", command.line, command.command, "Update helper script commands to reference scripts under tools/**.", {
1432
1432
  whyItMatters: "Helper commands should resolve to non-context helper assets in tools/** so contexts remain LLM-readable guidance.",
@@ -1434,9 +1434,7 @@ function helperCommandFindings(document, paths, config) {
1434
1434
  continue;
1435
1435
  }
1436
1436
  if (scriptPath.startsWith("tools/") && !paths.has(scriptPath)) {
1437
- findings.push(findingAt(document, DIAGNOSTIC_IDS.PATH_HELPER_COMMAND_UNRESOLVED, "Helper command target does not resolve", "structure", "medium", command.line, command.command, `Create \`${scriptPath}\` or update this command to the correct tools/** helper path.`, {
1438
- whyItMatters: "Agents need helper commands in markdown procedures to resolve deterministically before running them.",
1439
- }));
1437
+ findings.push(unresolvedHelperCommandFinding(document, command, scriptPath));
1440
1438
  }
1441
1439
  }
1442
1440
  return findings;
@@ -1456,10 +1454,6 @@ function layoutConsistencyFindings(document) {
1456
1454
  pattern: /Each skill includes a self-improvement prompt/i,
1457
1455
  message: "Describe self-improvement prompts as loaded context/reference/procedure workflow prompts instead of SKILL.md-only content.",
1458
1456
  },
1459
- {
1460
- pattern: /skills\/[^/\s`]+\/(?:references|profiles|examples|scripts)\//,
1461
- message: "Update docs to point to canonical contexts/** assets or tools/** helper scripts instead of skill-local support directories.",
1462
- },
1463
1457
  {
1464
1458
  pattern: /\]\(contexts\/tools\//,
1465
1459
  message: "Use backtick repo-root paths for contexts/tools/... references instead of file-relative Markdown links.",
@@ -1470,10 +1464,10 @@ function layoutConsistencyFindings(document) {
1470
1464
  if (!match)
1471
1465
  continue;
1472
1466
  const line = lineForOffset(text, match.index ?? 0);
1473
- findings.push(findingAt(document, DIAGNOSTIC_IDS.DOCS_LAYOUT_INCONSISTENT, "Repository docs describe a non-canonical layout", "maintenance", "low", line, lineText(document, line), stale.message, {
1474
- whyItMatters: "README.md and AGENTS.md should teach agents the same strict three-root layout that Renma enforces.",
1467
+ findings.push(findingAt(document, DIAGNOSTIC_IDS.DOCS_LAYOUT_INCONSISTENT, "Repository docs contradict the supported layout", "maintenance", "low", line, lineText(document, line), stale.message, {
1468
+ whyItMatters: "README.md and AGENTS.md should describe the current repository model without treating valid Skill-local support as stale.",
1475
1469
  verificationSteps: [
1476
- "Update README.md and AGENTS.md to mention skills/, contexts/, and tools/ canonical roots.",
1470
+ "Confirm docs distinguish canonical Skill roots and valid local support from governed contexts/** assets and shared tools/** helpers.",
1477
1471
  "Run renma scan again.",
1478
1472
  ],
1479
1473
  }));
@@ -1491,6 +1485,11 @@ function contextRootFindings(document) {
1491
1485
  return [];
1492
1486
  }
1493
1487
  function helperRootFindings(document) {
1488
+ const skillPath = classifyRepositorySkillPath(document.artifact.path);
1489
+ if (skillPath?.kind === "support" &&
1490
+ skillPath.supportDirectory === "scripts") {
1491
+ return [];
1492
+ }
1494
1493
  if (document.artifact.path.includes("/scripts/") &&
1495
1494
  !document.artifact.path.startsWith("tools/")) {
1496
1495
  return [
@@ -1511,7 +1510,7 @@ function declaredDependencyLayoutFindings(catalog, paths) {
1511
1510
  continue;
1512
1511
  }
1513
1512
  if (target.startsWith("contexts/") ||
1514
- target.startsWith("skills/") ||
1513
+ normalizeRepositoryRelativePath(target) !== undefined ||
1515
1514
  target.startsWith("tools/")) {
1516
1515
  continue;
1517
1516
  }
@@ -1525,8 +1524,8 @@ function declaredDependencyLayoutFindings(catalog, paths) {
1525
1524
  severity: "low",
1526
1525
  confidence: "medium",
1527
1526
  evidence: metadataFindingEvidence(source.sourcePath, target),
1528
- whyItMatters: "Declared context references should resolve through canonical contexts/**, skills/**, or tools/** repo paths.",
1529
- remediation: "Rewrite declared requires_context or optional_context values to canonical repo-root paths.",
1527
+ whyItMatters: "Declared context references should resolve through canonical contexts/**, skills/**, .agents/skills/**, or tools/** repository paths.",
1528
+ remediation: "Rewrite declared required or optional context dependency values to canonical repo-root paths without changing the Skill's operational metadata format.",
1530
1529
  verificationSteps: ["Run renma graph and confirm all edges resolve."],
1531
1530
  details: {
1532
1531
  source: dependency.from,
@@ -1550,27 +1549,18 @@ function executableCommands(document) {
1550
1549
  function firstExecutableCommand(document) {
1551
1550
  return executableCommands(document)[0];
1552
1551
  }
1553
- function canonicalHelperTarget(config, scriptPath) {
1554
- const parts = scriptPath.split("/");
1555
- const skillName = parts[1] ?? "unknown";
1556
- const rest = parts.slice(3).join("/");
1557
- const workflow = canonicalWorkflowName(config, skillName);
1558
- return helperAssetPath(config, workflow, `scripts/${rest}`);
1559
- }
1560
- function contextAssetPath(config, workflow, rest) {
1561
- const namespace = config.layout.toolNamespace;
1562
- if (namespace)
1563
- return `contexts/tools/${namespace}/${workflow}/${rest}`;
1564
- return `contexts/${workflow}/${rest}`;
1565
- }
1566
- function helperAssetPath(config, workflow, rest) {
1567
- const namespace = config.layout.toolNamespace;
1568
- if (namespace)
1569
- return `tools/${namespace}/${workflow}/${rest}`;
1570
- return `tools/${workflow}/${rest}`;
1571
- }
1572
- function isCanonicalSkillEntrypoint(pathValue) {
1573
- return /^skills\/[^/]+\/SKILL\.md$/.test(pathValue);
1552
+ function unresolvedHelperCommandFinding(document, command, scriptPath, details = {}) {
1553
+ const unsafePath = details.details?.unsafePath === true;
1554
+ return findingAt(document, DIAGNOSTIC_IDS.PATH_HELPER_COMMAND_UNRESOLVED, "Helper command target does not resolve", "structure", "medium", command.line, command.command, unsafePath
1555
+ ? "Update this command to a path inside the owning Skill's scripts/** directory or to a repository-root tools/** helper; do not escape the Skill with `..`."
1556
+ : `Create \`${scriptPath}\` or update this command to the correct local helper path.`, {
1557
+ whyItMatters: "Agents need shared tools/** helpers and Skill-local scripts to resolve deterministically before running them.",
1558
+ verificationSteps: [
1559
+ "Confirm the command resolves to an existing tools/** helper or to scripts/** inside its owning Skill.",
1560
+ "Run renma readiness and check paths.helper_commands.",
1561
+ ],
1562
+ ...details,
1563
+ });
1574
1564
  }
1575
1565
  function findingAt(document, id, title, category, severity, line, snippet, remediation, details = {}) {
1576
1566
  return {