renma 0.16.0 → 0.18.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 +157 -1
- package/README.md +219 -748
- package/architecture.md +533 -0
- package/design.md +469 -0
- package/dist/agent-skills.d.ts +1 -1
- package/dist/agent-skills.d.ts.map +1 -1
- package/dist/agent-skills.js +28 -8
- package/dist/agent-skills.js.map +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +120 -12
- package/dist/catalog.js.map +1 -1
- package/dist/cli-help.d.ts +15 -9
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +32 -9
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +162 -40
- package/dist/cli.js.map +1 -1
- package/dist/commands/bom.d.ts +6 -3
- package/dist/commands/bom.d.ts.map +1 -1
- package/dist/commands/bom.js +27 -7
- package/dist/commands/bom.js.map +1 -1
- package/dist/commands/catalog.d.ts +1 -1
- package/dist/commands/catalog.d.ts.map +1 -1
- package/dist/commands/catalog.js +11 -4
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/ci-report.d.ts.map +1 -1
- package/dist/commands/ci-report.js +18 -5
- package/dist/commands/ci-report.js.map +1 -1
- package/dist/commands/diff.js +8 -2
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/graph.d.ts +6 -2
- package/dist/commands/graph.d.ts.map +1 -1
- package/dist/commands/graph.js +30 -14
- package/dist/commands/graph.js.map +1 -1
- package/dist/commands/inspect.d.ts.map +1 -1
- package/dist/commands/inspect.js +2 -0
- package/dist/commands/inspect.js.map +1 -1
- package/dist/commands/ownership.d.ts +4 -3
- package/dist/commands/ownership.d.ts.map +1 -1
- package/dist/commands/ownership.js +27 -23
- package/dist/commands/ownership.js.map +1 -1
- package/dist/commands/readiness.d.ts +2 -1
- package/dist/commands/readiness.d.ts.map +1 -1
- package/dist/commands/readiness.js +105 -36
- package/dist/commands/readiness.js.map +1 -1
- package/dist/commands/scaffold.d.ts +3 -0
- package/dist/commands/scaffold.d.ts.map +1 -1
- package/dist/commands/scaffold.js +28 -2
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/suggest-metadata.d.ts.map +1 -1
- package/dist/commands/suggest-metadata.js +56 -8
- package/dist/commands/suggest-metadata.js.map +1 -1
- package/dist/commands/suggest-semantic-split.js +3 -3
- package/dist/commands/suggest-semantic-split.js.map +1 -1
- package/dist/commands/trust-graph.d.ts.map +1 -1
- package/dist/commands/trust-graph.js +12 -0
- package/dist/commands/trust-graph.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -3
- package/dist/config.js.map +1 -1
- package/dist/dependency-resolution.d.ts +6 -0
- package/dist/dependency-resolution.d.ts.map +1 -0
- package/dist/dependency-resolution.js +11 -0
- package/dist/dependency-resolution.js.map +1 -0
- package/dist/diagnostic-ids.d.ts +8 -0
- package/dist/diagnostic-ids.d.ts.map +1 -1
- package/dist/diagnostic-ids.js +8 -0
- package/dist/diagnostic-ids.js.map +1 -1
- package/dist/discovery.d.ts +33 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +178 -80
- package/dist/discovery.js.map +1 -1
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +15 -0
- package/dist/markdown.js.map +1 -1
- package/dist/model.d.ts +17 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +4 -1
- package/dist/model.js.map +1 -1
- package/dist/quality-profile.d.ts +91 -0
- package/dist/quality-profile.d.ts.map +1 -0
- package/dist/quality-profile.js +91 -0
- package/dist/quality-profile.js.map +1 -0
- package/dist/repeated-context.d.ts.map +1 -1
- package/dist/repeated-context.js +38 -83
- package/dist/repeated-context.js.map +1 -1
- package/dist/repository-boundary.d.ts +30 -0
- package/dist/repository-boundary.d.ts.map +1 -0
- package/dist/repository-boundary.js +116 -0
- package/dist/repository-boundary.js.map +1 -0
- package/dist/repository-evidence.d.ts +2 -0
- package/dist/repository-evidence.d.ts.map +1 -1
- package/dist/repository-evidence.js +6 -4
- package/dist/repository-evidence.js.map +1 -1
- package/dist/repository-paths.d.ts +19 -2
- package/dist/repository-paths.d.ts.map +1 -1
- package/dist/repository-paths.js +123 -14
- package/dist/repository-paths.js.map +1 -1
- package/dist/rules.d.ts +2 -0
- package/dist/rules.d.ts.map +1 -1
- package/dist/rules.js +322 -256
- package/dist/rules.js.map +1 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +5 -1
- package/dist/scanner.js.map +1 -1
- package/dist/security-diagnostics.d.ts.map +1 -1
- package/dist/security-diagnostics.js +95 -30
- package/dist/security-diagnostics.js.map +1 -1
- package/dist/security-diff.d.ts +5 -2
- package/dist/security-diff.d.ts.map +1 -1
- package/dist/security-diff.js +20 -4
- package/dist/security-diff.js.map +1 -1
- package/dist/security-policy-inventory.d.ts +15 -3
- package/dist/security-policy-inventory.d.ts.map +1 -1
- package/dist/security-policy-inventory.js +123 -26
- package/dist/security-policy-inventory.js.map +1 -1
- package/dist/security-policy.d.ts +7 -0
- package/dist/security-policy.d.ts.map +1 -1
- package/dist/security-policy.js +70 -7
- package/dist/security-policy.js.map +1 -1
- package/dist/security-posture.d.ts.map +1 -1
- package/dist/security-posture.js +2 -1
- package/dist/security-posture.js.map +1 -1
- package/dist/skill-migration.js +2 -0
- package/dist/skill-migration.js.map +1 -1
- package/dist/static-support.d.ts +13 -0
- package/dist/static-support.d.ts.map +1 -0
- package/dist/static-support.js +284 -0
- package/dist/static-support.js.map +1 -0
- package/dist/token-estimator.d.ts +21 -0
- package/dist/token-estimator.d.ts.map +1 -0
- package/dist/token-estimator.js +84 -0
- package/dist/token-estimator.js.map +1 -0
- package/dist/trust-graph.d.ts +3 -3
- package/dist/trust-graph.d.ts.map +1 -1
- package/dist/trust-graph.js +76 -21
- package/dist/trust-graph.js.map +1 -1
- package/dist/types.d.ts +7 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +61 -0
- package/docs/advanced-skill-authoring.md +147 -0
- package/docs/agent-skills-compatibility.md +508 -0
- package/docs/authoring-guide.md +382 -0
- package/docs/context-conflict-diagnostics.md +32 -0
- package/docs/context-language-diagnostics.md +99 -0
- package/docs/context-lens.md +253 -0
- package/docs/context-lifecycle-diagnostics.md +55 -0
- package/docs/diagnostics.md +406 -0
- package/docs/metadata-budget.md +14 -0
- package/docs/quality-profile.md +123 -0
- package/docs/repository-context-bom.md +172 -0
- package/docs/schemas/repository-context-bom-v2.schema.json +648 -0
- package/docs/schemas/trust-graph-v2.schema.json +301 -0
- package/docs/security-policy.md +354 -0
- package/docs/trust-graph.md +47 -0
- package/docs/user-manual.md +842 -0
- package/examples/context-lens/README.md +101 -0
- package/examples/context-lens/contexts/testing/boundary-value-analysis.md +23 -0
- package/examples/context-lens/lenses/testing/spec-review-boundary-values.md +29 -0
- package/examples/context-lens/lenses/testing/test-design-boundary-values.md +29 -0
- package/examples/context-lens/skills/testing/spec-review/SKILL.md +49 -0
- package/examples/context-repo/README.md +148 -0
- package/examples/context-repo/contexts/domain/payment/idempotency.md +27 -0
- package/examples/context-repo/contexts/testing/boundary-value-analysis.md +26 -0
- package/examples/context-repo/contexts/testing/negative-testing.md +26 -0
- package/examples/context-repo/lenses/testing/spec-review-boundary-values.md +35 -0
- package/examples/context-repo/renma.config.json +12 -0
- package/examples/context-repo/skills/testing/spec-review/SKILL.md +71 -0
- package/examples/context-repo/tools/appium/README.md +14 -0
- package/examples/github-actions/renma-ci-report.yml +36 -0
- package/examples/interactive-placeholder/README.md +104 -0
- package/examples/interactive-placeholder/assets/template.txt +1 -0
- package/examples/interactive-placeholder/renma.config.json +6 -0
- package/examples/interactive-placeholder/skills/replace-placeholder/SKILL.md +54 -0
- package/examples/interactive-placeholder/tools/README.md +48 -0
- package/examples/interactive-placeholder/tools/placeholder-demo.mjs +99 -0
- package/package.json +10 -1
- package/plan-discovery.md +749 -0
- package/plan.md +90 -0
- package/scripts/verify-package.mjs +104 -0
package/dist/rules.js
CHANGED
|
@@ -2,95 +2,52 @@ import path from "node:path";
|
|
|
2
2
|
import { resolvedAgentSkillDescription } from "./agent-skills.js";
|
|
3
3
|
import { addDaysIsoDate, isIsoDate, parseDayDuration, todayIsoDate, } from "./freshness.js";
|
|
4
4
|
import { DIAGNOSTIC_IDS } from "./diagnostic-ids.js";
|
|
5
|
-
import {
|
|
5
|
+
import { classifyRepositorySkillEntrypointPath, classifyRepositorySkillPath, logicalSkillDirectory, normalizeRepositoryRelativePath, } from "./discovery.js";
|
|
6
|
+
import { helperScriptPath, resolveHelperScriptPath, } from "./repository-paths.js";
|
|
6
7
|
import { parseAssetMetadata } from "./metadata.js";
|
|
7
8
|
import { runRuleRegistry } from "./rule-engine.js";
|
|
9
|
+
import { DEFAULT_QUALITY_PROFILE } from "./quality-profile.js";
|
|
10
|
+
import { estimateTokens, markdownBody } from "./token-estimator.js";
|
|
11
|
+
import { staticSupportReferences } from "./static-support.js";
|
|
8
12
|
const SECRET_PATTERN = /\b(?:password|passwd|token|api[_-]?key|secret|credential|private[_-]?key)\b\s*[:=]\s*["']?([A-Za-z0-9_./+=-]{8,})/i;
|
|
9
13
|
const PRIVATE_KEY_PATTERN = /-----BEGIN (?:RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----/;
|
|
10
14
|
const REMOTE_PATTERN = /\b(?:curl|wget)\b.*(?:\|\s*(?:sh|bash)|\b(?:example\.com|prod|production|--insecure|-k)\b)|\b(?:ssh|scp)\b.*\b(?:example\.com|prod|production|root@|--insecure|-k|StrictHostKeyChecking=no|UserKnownHostsFile=\/dev\/null)\b/i;
|
|
11
15
|
const ENV_COPY_PATTERN = /\b(?:process\.env|env)\b.*\b(?:spawn|exec|execFile|system|subprocess|child_process)\b|\b(?:spawn|exec|execFile|system|subprocess|child_process)\b.*\b(?:process\.env|env)\b/i;
|
|
12
16
|
const USER_LOCAL_PATH_PATTERN = /(?:^|[^a-z0-9_])(?:\/Users\/[^\s/\\]+|\/home\/[^\s/\\]+|[A-Za-z]:\\Users\\[^\s\\]+)(?:\/|$)/iu;
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const REUSABLE_CONTEXT_MIN_LINES = 24;
|
|
16
|
-
const REUSABLE_CONTEXT_MIN_TOKENS = 180;
|
|
17
|
+
const QUALITY = DEFAULT_QUALITY_PROFILE;
|
|
18
|
+
const MAX_LOCAL_SUPPORT_REFERENCE_HOPS = QUALITY.agentSkills.recommendedReferenceDepth + 1;
|
|
17
19
|
const REQUIRED_INPUTS_PATTERN = /\b(?:required inputs?|inputs|input requirements?|required information|prerequisites?|required context|required files|required permissions?|permission requirements?|environment requirements?|before running,\s*provide|before you begin,\s*provide|the user must provide|needs the following|target files|permissions required|environment required)\b|\brequires:/;
|
|
18
20
|
const COMPLETION_CRITERIA_PATTERN = /\b(?:completion criteria|completion checklist|success criteria|success requirements|done criteria|done when|definition of done|acceptance criteria|deliverables?|final response|final answer|expected outcomes?|expected results?|expected output|required output|output requirements?|report should include|patch should include|when complete|workflow is complete|the workflow is complete after|task is complete|counts as complete|completion requirements?|stop when|do not finish until)\b/;
|
|
19
|
-
const REUSABLE_CONTEXT_MIN_SIGNALS = 3;
|
|
20
|
-
const SUPPORT_SHARED_CONTEXT_MIN_LINES = 18;
|
|
21
|
-
const SUPPORT_SHARED_CONTEXT_MIN_TOKENS = 140;
|
|
22
|
-
const SUPPORT_SHARED_CONTEXT_MIN_HEADINGS = 2;
|
|
23
|
-
const SUPPORT_SHARED_CONTEXT_MIN_PHRASES = 2;
|
|
24
21
|
const REUSABLE_CONTEXT_HEADING_PATTERNS = [
|
|
25
|
-
[/\
|
|
26
|
-
[/\
|
|
27
|
-
[/\bconfiguration\b/i, "Configuration"],
|
|
28
|
-
[/\benvironment\b/i, "Environment"],
|
|
29
|
-
[/\bprerequisites?\b/i, "Prerequisites"],
|
|
30
|
-
[/\bplatform\b/i, "Platform"],
|
|
31
|
-
[/\btroubleshooting\b/i, "Troubleshooting"],
|
|
22
|
+
[/\bplatform facts?\b/i, "Platform Facts"],
|
|
23
|
+
[/\breusable troubleshooting\b/i, "Reusable Troubleshooting"],
|
|
32
24
|
[/\bknown issues?\b/i, "Known Issues"],
|
|
33
|
-
[/\blimitations?\b/i, "Limitations"],
|
|
34
|
-
[/\bbest practices?\b/i, "Best Practices"],
|
|
35
|
-
[/\btesting heuristics?\b/i, "Testing Heuristics"],
|
|
36
|
-
[/\btest strategy\b/i, "Test Strategy"],
|
|
37
|
-
[/\bverification\b/i, "Verification"],
|
|
38
|
-
[/\bexamples?\b/i, "Examples"],
|
|
39
|
-
[/\bedge cases?\b/i, "Edge Cases"],
|
|
40
|
-
[/\brisks?\b/i, "Risks"],
|
|
41
25
|
[/\bdomain rules?\b/i, "Domain Rules"],
|
|
42
|
-
[/\
|
|
43
|
-
[/\
|
|
26
|
+
[/\bcompatibility (?:matrix|matrices)\b/i, "Compatibility Matrix"],
|
|
27
|
+
[/\bproduct polic(?:y|ies)\b/i, "Product Policy"],
|
|
28
|
+
[/\bshared testing heuristics?\b/i, "Shared Testing Heuristics"],
|
|
44
29
|
];
|
|
45
30
|
const REUSABLE_CONTEXT_PHRASE_PATTERNS = [
|
|
46
|
-
[/\buse this when\b/i, "use this when"],
|
|
47
31
|
[/\bknown issue\b/i, "known issue"],
|
|
48
|
-
[/\
|
|
49
|
-
[/\btroubleshooting\b/i, "troubleshooting"],
|
|
50
|
-
[/\bflaky\b/i, "flaky"],
|
|
51
|
-
[/\bretry\b/i, "retry"],
|
|
32
|
+
[/\breusable troubleshooting\b/i, "reusable troubleshooting"],
|
|
52
33
|
[/\bplatform-specific\b/i, "platform-specific"],
|
|
53
|
-
[/\
|
|
54
|
-
[/\
|
|
55
|
-
[/\
|
|
56
|
-
[/\bbest practice\b/i, "best practice"],
|
|
57
|
-
[/\bdo not\b/i, "do not"],
|
|
58
|
-
[/\bavoid\b/i, "avoid"],
|
|
59
|
-
[/\balways\b/i, "always"],
|
|
60
|
-
[/\bnever\b/i, "never"],
|
|
34
|
+
[/\bsource of truth\b/i, "source of truth"],
|
|
35
|
+
[/\bused by (?:multiple|several) skills\b/i, "cross-Skill use"],
|
|
36
|
+
[/\bshared (?:rule|policy|heuristic|knowledge)\b/i, "shared knowledge"],
|
|
61
37
|
];
|
|
62
38
|
const SUPPORT_SHARED_CONTEXT_HEADING_PATTERNS = [
|
|
63
39
|
...REUSABLE_CONTEXT_HEADING_PATTERNS,
|
|
64
|
-
[/\bdecision logic\b/i, "Decision Logic"],
|
|
65
|
-
[/\bsafety notes?\b/i, "Safety Notes"],
|
|
66
|
-
[/\bvalidation\b/i, "Validation"],
|
|
67
|
-
[/\boperating model\b/i, "Operating Model"],
|
|
68
40
|
[/\bpolicy\b/i, "Policy"],
|
|
69
|
-
[/\bguidelines?\b/i, "Guidelines"],
|
|
70
|
-
[/\bprocedures?\b/i, "Procedure"],
|
|
71
|
-
[/\bchecklist\b/i, "Checklist"],
|
|
72
41
|
[/\bcompatibility\b/i, "Compatibility"],
|
|
73
|
-
[/\
|
|
42
|
+
[/\bsource of truth\b/i, "Source of Truth"],
|
|
74
43
|
];
|
|
75
44
|
const SUPPORT_SHARED_CONTEXT_PHRASE_PATTERNS = [
|
|
76
|
-
[/\bmust\b/i, "must"],
|
|
77
|
-
[/\bshould\b/i, "should"],
|
|
78
|
-
[/\balways\b/i, "always"],
|
|
79
|
-
[/\bnever\b/i, "never"],
|
|
80
|
-
[/\bavoid\b/i, "avoid"],
|
|
81
|
-
[/\bprefer\b/i, "prefer"],
|
|
82
|
-
[/\bdo not\b/i, "do not"],
|
|
83
|
-
[/\brequired\b/i, "required"],
|
|
84
|
-
[/\brecommended\b/i, "recommended"],
|
|
85
45
|
[/\bknown issue\b/i, "known issue"],
|
|
86
|
-
[/\
|
|
87
|
-
[/\
|
|
88
|
-
[/\
|
|
89
|
-
[/\
|
|
90
|
-
[/\
|
|
91
|
-
[/\bbest practice\b/i, "best practice"],
|
|
92
|
-
[/\bvalidate\b/i, "validate"],
|
|
93
|
-
[/\bverify\b/i, "verify"],
|
|
46
|
+
[/\breusable troubleshooting\b/i, "reusable troubleshooting"],
|
|
47
|
+
[/\bsource of truth\b/i, "source of truth"],
|
|
48
|
+
[/\bused by (?:multiple|several) skills\b/i, "cross-Skill use"],
|
|
49
|
+
[/\bindependent (?:owner|ownership|lifecycle)\b/i, "independent ownership"],
|
|
50
|
+
[/\bshared (?:rule|policy|heuristic|knowledge)\b/i, "shared knowledge"],
|
|
94
51
|
];
|
|
95
52
|
const NON_SEMANTIC_CONTEXT_PATH_SEGMENTS = new Set([
|
|
96
53
|
"promoted",
|
|
@@ -113,15 +70,10 @@ const NON_SEMANTIC_CONTEXT_PATH_SEGMENTS = new Set([
|
|
|
113
70
|
"candidate",
|
|
114
71
|
"candidates",
|
|
115
72
|
]);
|
|
116
|
-
const CONTEXT_TOKEN_LIMITS =
|
|
117
|
-
context: 1200,
|
|
118
|
-
profile: 500,
|
|
119
|
-
reference: 800,
|
|
120
|
-
example: 800,
|
|
121
|
-
};
|
|
73
|
+
const CONTEXT_TOKEN_LIMITS = QUALITY.contentTokenWarn;
|
|
122
74
|
/** Run all deterministic rules and return findings in stable source order. */
|
|
123
75
|
export function runRules(documents, config, catalog, options = {}) {
|
|
124
|
-
const findings = runRuleRegistry(documents, rulesForEvaluationDate(evaluationDay(options.evaluationDate), options.repositoryPaths), catalog, config);
|
|
76
|
+
const findings = runRuleRegistry(documents, rulesForEvaluationDate(evaluationDay(options.evaluationDate), options.repositoryPaths, options.repositoryPathStates), catalog, config);
|
|
125
77
|
return findings.sort((a, b) => {
|
|
126
78
|
const byPath = a.evidence.path.localeCompare(b.evidence.path);
|
|
127
79
|
if (byPath !== 0)
|
|
@@ -129,11 +81,11 @@ export function runRules(documents, config, catalog, options = {}) {
|
|
|
129
81
|
return a.evidence.startLine - b.evidence.startLine;
|
|
130
82
|
});
|
|
131
83
|
}
|
|
132
|
-
function rulesForEvaluationDate(evaluationDate, repositoryPaths) {
|
|
84
|
+
function rulesForEvaluationDate(evaluationDate, repositoryPaths, repositoryPathStates) {
|
|
133
85
|
return [
|
|
134
86
|
{
|
|
135
87
|
id: "strict-layout-policy",
|
|
136
|
-
run: (context) => strictLayoutPolicyFindings(context.documents, context.
|
|
88
|
+
run: (context) => strictLayoutPolicyFindings(context.documents, context.catalog, repositoryPaths),
|
|
137
89
|
},
|
|
138
90
|
{
|
|
139
91
|
id: "security",
|
|
@@ -152,7 +104,7 @@ function rulesForEvaluationDate(evaluationDate, repositoryPaths) {
|
|
|
152
104
|
},
|
|
153
105
|
{
|
|
154
106
|
id: "skill-local-support-reachability",
|
|
155
|
-
run: ({ documents }) => skillLocalSupportReachabilityFindings(documents),
|
|
107
|
+
run: ({ documents }) => skillLocalSupportReachabilityFindings(documents, repositoryPaths, repositoryPathStates),
|
|
156
108
|
},
|
|
157
109
|
{
|
|
158
110
|
id: "support-asset-shared-context-candidate",
|
|
@@ -645,33 +597,51 @@ function shapeFindings(document) {
|
|
|
645
597
|
const description = document.artifact.kind === "skill"
|
|
646
598
|
? (resolvedAgentSkillDescription(document) ?? "")
|
|
647
599
|
: (document.metadata.description ?? "");
|
|
648
|
-
|
|
600
|
+
// Skill budgets measure only Markdown after frontmatter. Agent Skills loads
|
|
601
|
+
// metadata separately, and Renma applies independent metadata budgets.
|
|
602
|
+
const bodyTokenCount = estimateTokens(markdownBody(document.artifact.content));
|
|
649
603
|
if (!description) {
|
|
650
604
|
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."));
|
|
651
605
|
}
|
|
652
606
|
else if (document.artifact.kind === "skill" &&
|
|
653
|
-
|
|
654
|
-
|
|
607
|
+
QUALITY.descriptionMinChars > 0 &&
|
|
608
|
+
description.length < QUALITY.descriptionMinChars) {
|
|
609
|
+
findings.push(documentFinding(document, DIAGNOSTIC_IDS.QUAL_SHORT_DESCRIPTION, "Skill description is too short for routing clarity", "quality", "low", `Expand frontmatter description to at least ${QUALITY.descriptionMinChars} characters with usage routing guidance.`));
|
|
655
610
|
}
|
|
656
|
-
const reusableContextFinding = reusableContextCandidateFinding(document,
|
|
611
|
+
const reusableContextFinding = reusableContextCandidateFinding(document, bodyTokenCount);
|
|
657
612
|
if (reusableContextFinding)
|
|
658
613
|
findings.push(reusableContextFinding);
|
|
659
|
-
if (document.artifact.kind === "skill" &&
|
|
660
|
-
|
|
661
|
-
|
|
614
|
+
if (document.artifact.kind === "skill" &&
|
|
615
|
+
bodyTokenCount > QUALITY.skillTokenWarn) {
|
|
616
|
+
const limit = bodyTokenCount > QUALITY.skillTokenStrongWarn
|
|
617
|
+
? QUALITY.skillTokenStrongWarn
|
|
618
|
+
: QUALITY.skillTokenWarn;
|
|
619
|
+
const severity = bodyTokenCount > QUALITY.skillTokenStrongWarn ? "medium" : "low";
|
|
620
|
+
findings.push(documentFinding(document, DIAGNOSTIC_IDS.QUAL_SKILL_TOKEN_BUDGET, "Skill body exceeds advisory token budget", "quality", severity, "Review progressive disclosure without deleting workflow steps. Keep selection boundaries, read conditions, ordered workflow, constraints, and completion criteria in SKILL.md. Move Skill-specific conditional detail to references/, deterministic repeated implementation to scripts/, output material to assets/, and independently owned cross-Skill knowledge to contexts/.", {
|
|
621
|
+
whyItMatters: "Long Skill bodies can make activated workflows harder to navigate. Size is advisory evidence only; Agent Skills recommends staying under 5,000 tokens and Renma adds an earlier low review point at 2,000 estimated tokens.",
|
|
662
622
|
constraints: [
|
|
663
623
|
"Do not introduce runtime context resolution.",
|
|
664
624
|
"Do not create prompt packages.",
|
|
665
625
|
"Do not make Renma responsible for selecting context.",
|
|
666
|
-
"Preserve
|
|
667
|
-
"
|
|
626
|
+
"Preserve ordered workflow steps, constraints, and completion criteria.",
|
|
627
|
+
"Choose a destination by semantic ownership, not by size alone.",
|
|
668
628
|
],
|
|
669
629
|
verificationSteps: [
|
|
670
630
|
"Run renma scan.",
|
|
671
631
|
"Run any project-specific validation checks that apply to this repository.",
|
|
672
|
-
"Confirm
|
|
632
|
+
"Confirm any moved material remains statically reachable and semantically owned by its destination.",
|
|
673
633
|
],
|
|
674
|
-
llmHint: "
|
|
634
|
+
llmHint: "Review the Skill for progressive disclosure. Keep core workflow in SKILL.md; use references/ for local detail, scripts/ for deterministic implementation, assets/ for output resources, and contexts/ only for independently owned shared knowledge.",
|
|
635
|
+
details: {
|
|
636
|
+
measured: bodyTokenCount,
|
|
637
|
+
limit,
|
|
638
|
+
unit: "estimated_tokens",
|
|
639
|
+
profile: QUALITY.profile,
|
|
640
|
+
measurement: "markdown_body_after_frontmatter",
|
|
641
|
+
source: limit === QUALITY.skillTokenStrongWarn
|
|
642
|
+
? "agent_skills_recommendation_and_renma_severity"
|
|
643
|
+
: "renma_quality_policy",
|
|
644
|
+
},
|
|
675
645
|
}));
|
|
676
646
|
}
|
|
677
647
|
if (document.artifact.kind === "skill" &&
|
|
@@ -731,17 +701,25 @@ function shapeFindings(document) {
|
|
|
731
701
|
if (!/verify|validation|test|confirm result|expected output/.test(text)) {
|
|
732
702
|
findings.push(documentFinding(document, DIAGNOSTIC_IDS.QUAL_MISSING_VERIFICATION, "Skill lacks verification guidance", "quality", "medium", "State how to verify success with a command, check, or observable result."));
|
|
733
703
|
}
|
|
734
|
-
if (document.headings.length <
|
|
735
|
-
|
|
736
|
-
findings.push(documentFinding(document, DIAGNOSTIC_IDS.QUAL_LOW_HEADING_DENSITY, "Long instruction file has few headings", "structure", "low", "
|
|
704
|
+
if (document.headings.length < QUALITY.lowHeadingDensityMinHeadings &&
|
|
705
|
+
bodyTokenCount >= QUALITY.lowHeadingDensityMinTokens) {
|
|
706
|
+
findings.push(documentFinding(document, DIAGNOSTIC_IDS.QUAL_LOW_HEADING_DENSITY, "Long instruction file has few headings", "structure", "low", "Add task-oriented headings so agents can navigate the body reliably.", {
|
|
707
|
+
details: {
|
|
708
|
+
measured: bodyTokenCount,
|
|
709
|
+
limit: QUALITY.lowHeadingDensityMinTokens,
|
|
710
|
+
unit: "estimated_tokens",
|
|
711
|
+
profile: QUALITY.profile,
|
|
712
|
+
measurement: "markdown_body_after_frontmatter",
|
|
713
|
+
},
|
|
714
|
+
}));
|
|
737
715
|
}
|
|
738
716
|
return findings;
|
|
739
717
|
}
|
|
740
|
-
function reusableContextCandidateFinding(document,
|
|
718
|
+
function reusableContextCandidateFinding(document, estimatedTokens) {
|
|
741
719
|
if (document.artifact.kind !== "skill")
|
|
742
720
|
return undefined;
|
|
743
|
-
if (document.lines.length <
|
|
744
|
-
|
|
721
|
+
if (document.lines.length < QUALITY.reusableContextCandidate.minLines &&
|
|
722
|
+
estimatedTokens < QUALITY.reusableContextCandidate.minTokens)
|
|
745
723
|
return undefined;
|
|
746
724
|
const headingMatches = document.headings.flatMap((heading) => REUSABLE_CONTEXT_HEADING_PATTERNS.filter(([pattern]) => pattern.test(heading.text)).map(([, label]) => ({
|
|
747
725
|
label,
|
|
@@ -765,7 +743,10 @@ function reusableContextCandidateFinding(document, tokenCount) {
|
|
|
765
743
|
];
|
|
766
744
|
const phraseLabels = [...new Set(phraseMatches.map((match) => match.label))];
|
|
767
745
|
const signalCount = new Set([...headingLabels, ...phraseLabels]).size;
|
|
768
|
-
|
|
746
|
+
// All patterns above are reusable-knowledge evidence. Ordinary workflow
|
|
747
|
+
// headings such as Verification, Examples, Steps, Risks, and Constraints are
|
|
748
|
+
// intentionally excluded and cannot qualify a Skill by themselves.
|
|
749
|
+
if (signalCount < QUALITY.reusableContextCandidate.minSignals)
|
|
769
750
|
return undefined;
|
|
770
751
|
const evidenceLine = headingMatches[0]?.line ??
|
|
771
752
|
phraseMatches[0]?.line ??
|
|
@@ -783,37 +764,51 @@ function reusableContextCandidateFinding(document, tokenCount) {
|
|
|
783
764
|
: undefined,
|
|
784
765
|
].filter((part) => Boolean(part));
|
|
785
766
|
return {
|
|
786
|
-
id: DIAGNOSTIC_IDS.
|
|
787
|
-
title: "Skill may
|
|
767
|
+
id: DIAGNOSTIC_IDS.QUAL_SKILL_MIXED_RESPONSIBILITY,
|
|
768
|
+
title: "Skill may mix its workflow with reusable knowledge",
|
|
788
769
|
category: "maintenance",
|
|
789
770
|
severity: "low",
|
|
790
771
|
confidence: "medium",
|
|
791
772
|
evidence: evidence(document, evidenceLine, evidenceParts.join("; ")),
|
|
792
773
|
whyItMatters: "Reusable setup notes, troubleshooting, platform guidance, testing heuristics, or domain rules are easier to own, review, and reuse when they live in shared context assets instead of only inside one skill.",
|
|
793
|
-
remediation: "Review the matched headings and phrases.
|
|
774
|
+
remediation: "Review the matched headings and phrases. Promote content to contexts/ only when it needs cross-Skill reuse, independent ownership, lifecycle, or source-of-truth status; otherwise keep Skill-specific detail in SKILL.md or references/.",
|
|
794
775
|
constraints: [
|
|
795
776
|
"Do not make Renma select runtime context.",
|
|
796
777
|
"Do not assemble prompt packages.",
|
|
797
778
|
"Do not automatically rewrite or split skills.",
|
|
798
|
-
"Preserve SKILL.md as
|
|
779
|
+
"Preserve SKILL.md as a focused workflow entrypoint.",
|
|
799
780
|
"Give extracted context assets stable metadata such as id, owner, and status.",
|
|
800
781
|
],
|
|
801
782
|
verificationSteps: [
|
|
802
783
|
"Run renma scan.",
|
|
803
784
|
"Confirm the advisory is resolved or intentionally accepted after reusable knowledge is represented as shared context assets.",
|
|
804
785
|
],
|
|
805
|
-
llmHint: "
|
|
786
|
+
llmHint: "Check whether the matched knowledge is used across Skills or needs independent ownership. Use contexts/ only for shared knowledge; keep Skill-local procedures and edge cases in SKILL.md or references/.",
|
|
787
|
+
details: {
|
|
788
|
+
measured: estimatedTokens,
|
|
789
|
+
limit: QUALITY.reusableContextCandidate.minTokens,
|
|
790
|
+
unit: "estimated_tokens",
|
|
791
|
+
lines: document.lines.length,
|
|
792
|
+
minLines: QUALITY.reusableContextCandidate.minLines,
|
|
793
|
+
distinctSignals: signalCount,
|
|
794
|
+
minSignals: QUALITY.reusableContextCandidate.minSignals,
|
|
795
|
+
profile: QUALITY.profile,
|
|
796
|
+
measurement: "markdown_body_after_frontmatter",
|
|
797
|
+
},
|
|
806
798
|
};
|
|
807
799
|
}
|
|
808
800
|
function supportSharedContextCandidateFindings(document) {
|
|
809
801
|
if (document.artifact.kind !== "reference")
|
|
810
802
|
return [];
|
|
811
|
-
|
|
803
|
+
const skillPath = classifyRepositorySkillPath(document.artifact.path);
|
|
804
|
+
if (skillPath?.kind !== "support" ||
|
|
805
|
+
skillPath.supportDirectory !== "references" ||
|
|
806
|
+
!document.artifact.path.endsWith(".md")) {
|
|
812
807
|
return [];
|
|
813
808
|
}
|
|
814
|
-
const
|
|
815
|
-
if (document.lines.length <
|
|
816
|
-
|
|
809
|
+
const estimatedTokens = estimateTokens(markdownBody(document.artifact.content));
|
|
810
|
+
if (document.lines.length < QUALITY.sharedSupportCandidate.minLines &&
|
|
811
|
+
estimatedTokens < QUALITY.sharedSupportCandidate.minTokens) {
|
|
817
812
|
return [];
|
|
818
813
|
}
|
|
819
814
|
const contentLineIndexes = markdownBodyLineIndexes(document);
|
|
@@ -848,8 +843,10 @@ function supportSharedContextCandidateFindings(document) {
|
|
|
848
843
|
];
|
|
849
844
|
});
|
|
850
845
|
const sourceSignals = [...headingMatches, ...phraseMatches];
|
|
851
|
-
if (headingMatches.
|
|
852
|
-
|
|
846
|
+
if (new Set(headingMatches.map((match) => match.label)).size <
|
|
847
|
+
QUALITY.sharedSupportCandidate.minHeadings ||
|
|
848
|
+
new Set(phraseMatches.map((match) => match.label)).size <
|
|
849
|
+
QUALITY.sharedSupportCandidate.minPhrases) {
|
|
853
850
|
return [];
|
|
854
851
|
}
|
|
855
852
|
const evidenceMatches = sourceSignals
|
|
@@ -893,6 +890,19 @@ function supportSharedContextCandidateFindings(document) {
|
|
|
893
890
|
"Confirm reusable source-of-truth knowledge lives in contexts/ and skill-local references only contain local support guidance.",
|
|
894
891
|
],
|
|
895
892
|
llmHint: "Search the repository for similar headings, filenames, repeated procedures, commands, constraints, or overlapping guidance. If this support file appears reusable, propose a first-class context asset under contexts/, move the reusable details without losing information, keep truly local notes in the skill directory, and update declared context references.",
|
|
893
|
+
details: {
|
|
894
|
+
measured: estimatedTokens,
|
|
895
|
+
limit: QUALITY.sharedSupportCandidate.minTokens,
|
|
896
|
+
unit: "estimated_tokens",
|
|
897
|
+
lines: document.lines.length,
|
|
898
|
+
minLines: QUALITY.sharedSupportCandidate.minLines,
|
|
899
|
+
headings: new Set(headingMatches.map((match) => match.label)).size,
|
|
900
|
+
minHeadings: QUALITY.sharedSupportCandidate.minHeadings,
|
|
901
|
+
phrases: new Set(phraseMatches.map((match) => match.label)).size,
|
|
902
|
+
minPhrases: QUALITY.sharedSupportCandidate.minPhrases,
|
|
903
|
+
profile: QUALITY.profile,
|
|
904
|
+
measurement: "full_file",
|
|
905
|
+
},
|
|
896
906
|
},
|
|
897
907
|
];
|
|
898
908
|
}
|
|
@@ -1061,8 +1071,11 @@ function skillReferencesSupersededAssetFindings(documents) {
|
|
|
1061
1071
|
});
|
|
1062
1072
|
}
|
|
1063
1073
|
function isSkillLocalReference(document) {
|
|
1074
|
+
const skillPath = classifyRepositorySkillPath(document.artifact.path);
|
|
1064
1075
|
return (document.artifact.kind === "reference" &&
|
|
1065
|
-
|
|
1076
|
+
skillPath?.kind === "support" &&
|
|
1077
|
+
skillPath.supportDirectory === "references" &&
|
|
1078
|
+
document.artifact.path.endsWith(".md"));
|
|
1066
1079
|
}
|
|
1067
1080
|
function sharedContextTargets(document) {
|
|
1068
1081
|
const operationalMetadata = parseAssetMetadata(document).metadata;
|
|
@@ -1072,13 +1085,15 @@ function sharedContextTargets(document) {
|
|
|
1072
1085
|
].filter((target, index, targets) => /^contexts?\//u.test(target) && targets.indexOf(target) === index);
|
|
1073
1086
|
}
|
|
1074
1087
|
function parentSkillPath(referencePath) {
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1088
|
+
const skillPath = classifyRepositorySkillPath(referencePath);
|
|
1089
|
+
return skillPath?.kind === "support"
|
|
1090
|
+
? `${skillPath.skillDirectory}/SKILL.md`
|
|
1091
|
+
: undefined;
|
|
1079
1092
|
}
|
|
1080
1093
|
function skillReferenceLine(skill, referencePath) {
|
|
1081
|
-
const skillDir =
|
|
1094
|
+
const skillDir = logicalSkillDirectory(skill.artifact.path);
|
|
1095
|
+
if (!skillDir)
|
|
1096
|
+
return undefined;
|
|
1082
1097
|
const relativePath = path.posix.relative(skillDir, referencePath);
|
|
1083
1098
|
const referencedTokens = [referencePath, relativePath];
|
|
1084
1099
|
for (const index of markdownBodyLineIndexes(skill)) {
|
|
@@ -1201,16 +1216,17 @@ function contextBudgetFindings(document) {
|
|
|
1201
1216
|
return [];
|
|
1202
1217
|
}
|
|
1203
1218
|
const limit = CONTEXT_TOKEN_LIMITS[document.artifact.kind];
|
|
1204
|
-
const
|
|
1205
|
-
if (
|
|
1219
|
+
const estimatedTokens = estimateTokens(document.artifact.content);
|
|
1220
|
+
if (estimatedTokens <= limit)
|
|
1206
1221
|
return [];
|
|
1207
1222
|
return [
|
|
1208
|
-
documentFinding(document, DIAGNOSTIC_IDS.QUAL_SUPPORT_ASSET_TOKEN_BUDGET, "Support asset exceeds token guidance", "quality", "low", `
|
|
1209
|
-
whyItMatters: "
|
|
1223
|
+
documentFinding(document, DIAGNOSTIC_IDS.QUAL_SUPPORT_ASSET_TOKEN_BUDGET, "Support asset exceeds token guidance", "quality", "low", `Review whether this ${document.artifact.kind} asset still has one coherent scope. Size alone does not require a split. Consider headings, read conditions, reference depth, mixed responsibilities, and maintained duplication before using \`renma suggest-semantic-split ${document.artifact.path}\`.`, {
|
|
1224
|
+
whyItMatters: "Large content assets deserve a low-advisory coherence review, but an intentionally focused asset can remain intact above the threshold.",
|
|
1210
1225
|
constraints: [
|
|
1211
1226
|
"Do not introduce runtime context resolution.",
|
|
1212
1227
|
"Do not create prompt packages.",
|
|
1213
|
-
"
|
|
1228
|
+
"Do not split based on size alone.",
|
|
1229
|
+
"Preserve concrete procedural steps losslessly if a semantic split is chosen.",
|
|
1214
1230
|
"Keep static references from the parent file or SKILL.md to every split part.",
|
|
1215
1231
|
],
|
|
1216
1232
|
verificationSteps: [
|
|
@@ -1218,7 +1234,14 @@ function contextBudgetFindings(document) {
|
|
|
1218
1234
|
"Run the repository-specific validation or test command, if one exists.",
|
|
1219
1235
|
"Confirm the finding is resolved or reduced and every split part remains reachable.",
|
|
1220
1236
|
],
|
|
1221
|
-
llmHint: "
|
|
1237
|
+
llmHint: "Review scope, headings, read conditions, reference depth, responsibilities, and duplication. Propose a semantic split only when those signals justify it.",
|
|
1238
|
+
details: {
|
|
1239
|
+
measured: estimatedTokens,
|
|
1240
|
+
limit,
|
|
1241
|
+
unit: "estimated_tokens",
|
|
1242
|
+
profile: QUALITY.profile,
|
|
1243
|
+
measurement: "full_file",
|
|
1244
|
+
},
|
|
1222
1245
|
}),
|
|
1223
1246
|
];
|
|
1224
1247
|
}
|
|
@@ -1232,19 +1255,31 @@ function profileFindings(document) {
|
|
|
1232
1255
|
documentFinding(document, DIAGNOSTIC_IDS.PROF_MISSING_BASE, "Profile overlay does not declare its base skill", "structure", "medium", "Declare the base skill or compatibility target so routing conflicts are auditable."),
|
|
1233
1256
|
];
|
|
1234
1257
|
}
|
|
1235
|
-
function skillLocalSupportReachabilityFindings(documents) {
|
|
1258
|
+
function skillLocalSupportReachabilityFindings(documents, repositoryPaths, repositoryPathStates) {
|
|
1236
1259
|
const skills = documents.filter((document) => document.artifact.kind === "skill");
|
|
1237
1260
|
return skills.flatMap((skill) => {
|
|
1238
|
-
const skillDir =
|
|
1239
|
-
|
|
1240
|
-
document.artifact.path.startsWith(`${skillDir}/`));
|
|
1241
|
-
if (localSupportDocs.length === 0)
|
|
1261
|
+
const skillDir = logicalSkillDirectory(skill.artifact.path);
|
|
1262
|
+
if (!skillDir)
|
|
1242
1263
|
return [];
|
|
1264
|
+
const localSupportDocs = documents.filter((document) => {
|
|
1265
|
+
if (!["profile", "reference", "example", "script", "asset"].includes(document.artifact.kind)) {
|
|
1266
|
+
return false;
|
|
1267
|
+
}
|
|
1268
|
+
const classified = classifyRepositorySkillPath(document.artifact.path);
|
|
1269
|
+
return (classified?.kind === "support" && classified.skillDirectory === skillDir);
|
|
1270
|
+
});
|
|
1271
|
+
const localSupportPaths = repositoryPaths
|
|
1272
|
+
? [...repositoryPaths].filter((candidate) => {
|
|
1273
|
+
const classified = classifyRepositorySkillPath(candidate);
|
|
1274
|
+
return (classified?.kind === "support" &&
|
|
1275
|
+
classified.skillDirectory === skillDir);
|
|
1276
|
+
})
|
|
1277
|
+
: localSupportDocs.map((document) => document.artifact.path);
|
|
1243
1278
|
const findings = [];
|
|
1244
1279
|
const text = skill.artifact.content.toLowerCase();
|
|
1245
|
-
const hasLocalSupportGuidance = /support file|local support|context route|context map|mixin|profiles?\/|references?\/|examples?\/|load .*?(?:profile|reference|example)|reference .*?(?:profile|reference|example)/.test(text);
|
|
1246
|
-
if (!hasLocalSupportGuidance) {
|
|
1247
|
-
findings.push(documentFinding(skill, DIAGNOSTIC_IDS.SUPPORT_MISSING_REACHABILITY_GUIDANCE, "Skill has local support files but no reachability guidance", "structure", "medium", "Add local support file reachability guidance so the top-level skill declares when profiles, references, examples, or
|
|
1280
|
+
const hasLocalSupportGuidance = /support file|local support|context route|context map|mixin|profiles?\/|references?\/|examples?\/|scripts?\/|assets?\/|load .*?(?:profile|reference|example|script|asset)|(?:read|run|reference) .*?(?:profile|reference|example|script|asset)/.test(text);
|
|
1281
|
+
if (localSupportDocs.length > 0 && !hasLocalSupportGuidance) {
|
|
1282
|
+
findings.push(documentFinding(skill, DIAGNOSTIC_IDS.SUPPORT_MISSING_REACHABILITY_GUIDANCE, "Skill has local support files but no reachability guidance", "structure", "medium", "Add local support file reachability guidance so the top-level skill declares when profiles, references, examples, scripts, or assets are reachable. If support content was split into ordered parts, reference the index or all parts in order. Preserve original concrete steps. Do not delete, summarize, or merge away procedural steps.", {
|
|
1248
1283
|
whyItMatters: "Local support files should be statically discoverable from the skill so humans and LLM coding agents can tell which repository evidence belongs to the skill without relying on runtime context selection.",
|
|
1249
1284
|
constraints: [
|
|
1250
1285
|
"Do not introduce runtime context resolution.",
|
|
@@ -1255,14 +1290,15 @@ function skillLocalSupportReachabilityFindings(documents) {
|
|
|
1255
1290
|
verificationSteps: [
|
|
1256
1291
|
"Run renma scan.",
|
|
1257
1292
|
"Run any project-specific validation checks that apply to this repository.",
|
|
1258
|
-
"Confirm each local profile, reference, or
|
|
1293
|
+
"Confirm each local profile, reference, example, script, or asset is reachable from SKILL.md or from a referenced parent support file.",
|
|
1259
1294
|
],
|
|
1260
|
-
llmHint: "Add concise reachability guidance in SKILL.md that references local profiles, references, examples, or ordered support indexes without adding runtime routing behavior.",
|
|
1295
|
+
llmHint: "Add concise reachability guidance in SKILL.md that references local profiles, references, examples, scripts, assets, or ordered support indexes without adding runtime routing behavior.",
|
|
1261
1296
|
}));
|
|
1262
1297
|
}
|
|
1263
|
-
const
|
|
1298
|
+
const reachabilityDepth = localSupportReachabilityDepth(skill, skillDir, localSupportDocs, localSupportPaths);
|
|
1264
1299
|
for (const document of localSupportDocs) {
|
|
1265
|
-
|
|
1300
|
+
const depth = reachabilityDepth.get(document.artifact.path);
|
|
1301
|
+
if (depth === undefined) {
|
|
1266
1302
|
findings.push(documentFinding(document, localSupportUnreachableRuleId(document.artifact.kind), "Local support file is not reachable from the skill", "structure", "low", "Reference this local support file from SKILL.md or from a referenced parent support file with clear reachability guidance. If this file is a split part, ensure the parent skill references the index or all ordered parts so preserved details remain reachable. Do not delete, summarize, or merge away procedural steps just to satisfy the check.", {
|
|
1267
1303
|
whyItMatters: "Unreachable local support files can drift outside review and be missed by humans or LLM coding agents. Reachability should be static repository evidence, not runtime context assembly.",
|
|
1268
1304
|
constraints: [
|
|
@@ -1279,36 +1315,126 @@ function skillLocalSupportReachabilityFindings(documents) {
|
|
|
1279
1315
|
llmHint: "Update SKILL.md or a referenced support index to mention this file by path, basename, or clear title so the static reachability graph can find it.",
|
|
1280
1316
|
}));
|
|
1281
1317
|
}
|
|
1318
|
+
else if (depth > MAX_LOCAL_SUPPORT_REFERENCE_HOPS) {
|
|
1319
|
+
findings.push(documentFinding(document, DIAGNOSTIC_IDS.SUPPORT_DEEP_REFERENCE_CHAIN, "Skill-local resource is behind a deep reference chain", "structure", "low", "Reference this resource directly from SKILL.md or through one directly referenced index/reference. Agent Skills recommends shallow file-reference chains.", {
|
|
1320
|
+
details: {
|
|
1321
|
+
measured: depth,
|
|
1322
|
+
limit: MAX_LOCAL_SUPPORT_REFERENCE_HOPS,
|
|
1323
|
+
unit: "static_reference_hops",
|
|
1324
|
+
profile: QUALITY.profile,
|
|
1325
|
+
},
|
|
1326
|
+
}));
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
const missingPathSources = [
|
|
1330
|
+
skill,
|
|
1331
|
+
...localSupportDocs.filter((document) => (reachabilityDepth.get(document.artifact.path) ?? 99) <=
|
|
1332
|
+
MAX_LOCAL_SUPPORT_REFERENCE_HOPS),
|
|
1333
|
+
];
|
|
1334
|
+
const reportedMissingPaths = new Set();
|
|
1335
|
+
const reportedSymlinkBoundaries = new Set();
|
|
1336
|
+
for (const source of missingPathSources) {
|
|
1337
|
+
for (const reference of localSupportPathReferences(source, skillDir, localSupportPaths)) {
|
|
1338
|
+
if (reference.relative.startsWith("examples/") &&
|
|
1339
|
+
!path.posix.basename(reference.relative).includes(".")) {
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
const symlinkBoundary = referencedSymlinkBoundary(reference.target, repositoryPathStates);
|
|
1343
|
+
if (symlinkBoundary) {
|
|
1344
|
+
const key = `${source.artifact.path}:${symlinkBoundary}`;
|
|
1345
|
+
if (reportedSymlinkBoundaries.has(key))
|
|
1346
|
+
continue;
|
|
1347
|
+
reportedSymlinkBoundaries.add(key);
|
|
1348
|
+
findings.push(findingAt(source, DIAGNOSTIC_IDS.SUPPORT_SYMLINK_PATH, "Skill-local resource path is an unusable symbolic link", "structure", "medium", reference.line, reference.raw, `Replace ${reference.relative} with a regular repository file or directory; Renma never follows symbolic links.`, {
|
|
1349
|
+
details: {
|
|
1350
|
+
sourcePath: source.artifact.path,
|
|
1351
|
+
target: reference.target,
|
|
1352
|
+
state: "symlink",
|
|
1353
|
+
symlinkBoundary,
|
|
1354
|
+
profile: QUALITY.profile,
|
|
1355
|
+
},
|
|
1356
|
+
}));
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
if (repositoryPathExists(repositoryPaths, reference.target, repositoryPathStates) ||
|
|
1360
|
+
repositoryPathExists(repositoryPaths, reference.relative, repositoryPathStates) ||
|
|
1361
|
+
reportedMissingPaths.has(`${source.artifact.path}:${reference.target}`))
|
|
1362
|
+
continue;
|
|
1363
|
+
reportedMissingPaths.add(`${source.artifact.path}:${reference.target}`);
|
|
1364
|
+
findings.push(findingAt(source, DIAGNOSTIC_IDS.SUPPORT_MISSING_PATH, "Skill-local resource path does not exist", "structure", "medium", reference.line, reference.raw, `Create ${reference.relative} under the Skill root or correct the relative path.`, {
|
|
1365
|
+
details: {
|
|
1366
|
+
target: reference.target,
|
|
1367
|
+
profile: QUALITY.profile,
|
|
1368
|
+
},
|
|
1369
|
+
}));
|
|
1370
|
+
}
|
|
1282
1371
|
}
|
|
1283
1372
|
return findings;
|
|
1284
1373
|
});
|
|
1285
1374
|
}
|
|
1286
|
-
function
|
|
1287
|
-
const
|
|
1375
|
+
function repositoryPathExists(repositoryPaths, candidate, repositoryPathStates) {
|
|
1376
|
+
const state = repositoryPathStates?.get(candidate);
|
|
1377
|
+
if (state !== undefined)
|
|
1378
|
+
return state !== "absent";
|
|
1379
|
+
if (!repositoryPaths)
|
|
1380
|
+
return false;
|
|
1381
|
+
if (repositoryPaths.has(candidate))
|
|
1382
|
+
return true;
|
|
1383
|
+
const directoryPrefix = `${candidate.replace(/\/$/, "")}/`;
|
|
1384
|
+
for (const filePath of repositoryPaths) {
|
|
1385
|
+
if (filePath.startsWith(directoryPrefix))
|
|
1386
|
+
return true;
|
|
1387
|
+
}
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
function referencedSymlinkBoundary(candidate, repositoryPathStates) {
|
|
1391
|
+
if (!repositoryPathStates)
|
|
1392
|
+
return undefined;
|
|
1393
|
+
const segments = candidate.split("/");
|
|
1394
|
+
for (let length = 1; length <= segments.length; length += 1) {
|
|
1395
|
+
const prefix = segments.slice(0, length).join("/");
|
|
1396
|
+
if (repositoryPathStates.get(prefix) === "symlink")
|
|
1397
|
+
return prefix;
|
|
1398
|
+
}
|
|
1399
|
+
return undefined;
|
|
1400
|
+
}
|
|
1401
|
+
function localSupportReachabilityDepth(skill, skillDirectory, localSupportDocs, candidatePaths) {
|
|
1402
|
+
const reachable = new Map();
|
|
1403
|
+
const references = new Map([skill, ...localSupportDocs].map((document) => [
|
|
1404
|
+
document.artifact.path,
|
|
1405
|
+
new Set(staticSupportReferences(document, skillDirectory, candidatePaths).map((reference) => reference.targetPath)),
|
|
1406
|
+
]));
|
|
1288
1407
|
let changed = true;
|
|
1289
1408
|
while (changed) {
|
|
1290
1409
|
changed = false;
|
|
1291
1410
|
for (const document of localSupportDocs) {
|
|
1292
1411
|
if (reachable.has(document.artifact.path))
|
|
1293
1412
|
continue;
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1413
|
+
if (references.get(skill.artifact.path)?.has(document.artifact.path)) {
|
|
1414
|
+
reachable.set(document.artifact.path, 1);
|
|
1415
|
+
changed = true;
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
const parent = localSupportDocs
|
|
1419
|
+
.filter((candidate) => reachable.has(candidate.artifact.path))
|
|
1420
|
+
.sort((left, right) => (reachable.get(left.artifact.path) ?? 0) -
|
|
1421
|
+
(reachable.get(right.artifact.path) ?? 0))
|
|
1422
|
+
.find((candidate) => references.get(candidate.artifact.path)?.has(document.artifact.path));
|
|
1423
|
+
if (parent) {
|
|
1424
|
+
reachable.set(document.artifact.path, (reachable.get(parent.artifact.path) ?? 0) + 1);
|
|
1300
1425
|
changed = true;
|
|
1301
1426
|
}
|
|
1302
1427
|
}
|
|
1303
1428
|
}
|
|
1304
1429
|
return reachable;
|
|
1305
1430
|
}
|
|
1306
|
-
function
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1431
|
+
function localSupportPathReferences(document, skillDir, candidatePaths) {
|
|
1432
|
+
return staticSupportReferences(document, skillDir, candidatePaths).map((reference) => ({
|
|
1433
|
+
raw: reference.raw,
|
|
1434
|
+
relative: reference.relativePath,
|
|
1435
|
+
target: reference.targetPath,
|
|
1436
|
+
line: reference.line,
|
|
1437
|
+
}));
|
|
1312
1438
|
}
|
|
1313
1439
|
function markdownBodyLineIndexes(document) {
|
|
1314
1440
|
if (document.lines[0]?.trim() !== "---") {
|
|
@@ -1347,14 +1473,13 @@ function matchingLineFindings(document, matcher) {
|
|
|
1347
1473
|
];
|
|
1348
1474
|
});
|
|
1349
1475
|
}
|
|
1350
|
-
function strictLayoutPolicyFindings(documents,
|
|
1476
|
+
function strictLayoutPolicyFindings(documents, catalog, repositoryPaths) {
|
|
1351
1477
|
const findings = [];
|
|
1352
1478
|
const paths = repositoryPaths ??
|
|
1353
1479
|
new Set(documents.map((document) => document.artifact.path));
|
|
1354
1480
|
for (const document of documents) {
|
|
1355
|
-
findings.push(...disallowedSkillAssetFindings(document, config));
|
|
1356
1481
|
findings.push(...thinSkillLayoutFindings(document));
|
|
1357
|
-
findings.push(...helperCommandFindings(document, paths
|
|
1482
|
+
findings.push(...helperCommandFindings(document, paths));
|
|
1358
1483
|
findings.push(...layoutConsistencyFindings(document));
|
|
1359
1484
|
findings.push(...contextRootFindings(document));
|
|
1360
1485
|
findings.push(...helperRootFindings(document));
|
|
@@ -1364,80 +1489,40 @@ function strictLayoutPolicyFindings(documents, config, catalog, repositoryPaths)
|
|
|
1364
1489
|
}
|
|
1365
1490
|
return findings;
|
|
1366
1491
|
}
|
|
1367
|
-
function disallowedSkillAssetFindings(document, config) {
|
|
1368
|
-
const match = document.artifact.path.match(/^skills\/([^/]+)\/(references|profiles|examples|scripts)\/(.+)$/);
|
|
1369
|
-
if (!match)
|
|
1370
|
-
return [];
|
|
1371
|
-
const [, skillName = "", assetRoot = "", rest = ""] = match;
|
|
1372
|
-
const target = canonicalSkillAssetTarget(config, skillName, assetRoot, rest);
|
|
1373
|
-
return [
|
|
1374
|
-
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.`, {
|
|
1375
|
-
whyItMatters: "Strict layout keeps skills as thin entrypoints, shared context under contexts/, and executable helpers under tools/ so agents can migrate repositories deterministically.",
|
|
1376
|
-
verificationSteps: [
|
|
1377
|
-
`Move ${document.artifact.path} to ${target}.`,
|
|
1378
|
-
"Run renma scan and renma readiness again.",
|
|
1379
|
-
],
|
|
1380
|
-
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.`,
|
|
1381
|
-
}),
|
|
1382
|
-
];
|
|
1383
|
-
}
|
|
1384
|
-
function canonicalSkillAssetTarget(config, skillName, assetRoot, rest) {
|
|
1385
|
-
const workflow = canonicalWorkflowName(config, skillName);
|
|
1386
|
-
if (assetRoot === "scripts")
|
|
1387
|
-
return helperAssetPath(config, workflow, `scripts/${rest}`);
|
|
1388
|
-
return contextAssetPath(config, workflow, `${assetRoot}/${rest}`);
|
|
1389
|
-
}
|
|
1390
|
-
function canonicalWorkflowName(config, skillName) {
|
|
1391
|
-
return config.layout.workflowAliases[skillName] ?? skillName;
|
|
1392
|
-
}
|
|
1393
1492
|
function thinSkillLayoutFindings(document) {
|
|
1394
|
-
if (
|
|
1493
|
+
if (classifyRepositorySkillEntrypointPath(document.artifact.path)?.kind !==
|
|
1494
|
+
"canonical")
|
|
1395
1495
|
return [];
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
];
|
|
1402
|
-
if (wordCount > 450 ||
|
|
1403
|
-
procedureSignals.some((pattern) => pattern.test(document.artifact.content))) {
|
|
1404
|
-
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.", {
|
|
1405
|
-
whyItMatters: "Strict layout expects skills/*/SKILL.md to route agents, not own canonical references, profiles, examples, scripts, or detailed procedures.",
|
|
1406
|
-
verificationSteps: [
|
|
1407
|
-
"Confirm SKILL.md contains routing guidance and required context references only.",
|
|
1408
|
-
"Run renma readiness and check layout.skills_thin.",
|
|
1409
|
-
],
|
|
1410
|
-
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.",
|
|
1411
|
-
}));
|
|
1412
|
-
}
|
|
1413
|
-
const command = firstExecutableCommand(document);
|
|
1414
|
-
if (command) {
|
|
1415
|
-
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.", {
|
|
1416
|
-
whyItMatters: "Executable setup commands in SKILL.md make the entrypoint procedural and harder to migrate to the strict three-root layout.",
|
|
1417
|
-
verificationSteps: [
|
|
1418
|
-
"Move the command guidance to a context/reference/procedure.",
|
|
1419
|
-
"If the command invokes a helper script, ensure it points under tools/**.",
|
|
1420
|
-
],
|
|
1421
|
-
}));
|
|
1422
|
-
}
|
|
1423
|
-
return findings;
|
|
1496
|
+
// Agent Skills explicitly permits step-by-step instructions and command
|
|
1497
|
+
// examples in SKILL.md. Focused-workflow quality is evaluated by dedicated
|
|
1498
|
+
// routing, input, completion, progressive-disclosure, and mixed-knowledge
|
|
1499
|
+
// rules instead of the removed 0.17 thin-router heuristic.
|
|
1500
|
+
return [];
|
|
1424
1501
|
}
|
|
1425
|
-
function helperCommandFindings(document, paths
|
|
1502
|
+
function helperCommandFindings(document, paths) {
|
|
1426
1503
|
const findings = [];
|
|
1427
1504
|
for (const command of executableCommands(document)) {
|
|
1428
|
-
const
|
|
1429
|
-
if (!
|
|
1505
|
+
const commandPath = helperScriptPath(command.command);
|
|
1506
|
+
if (!commandPath)
|
|
1430
1507
|
continue;
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
],
|
|
1508
|
+
const resolution = resolveHelperScriptPath(document.artifact.path, commandPath);
|
|
1509
|
+
if (resolution.kind === "unscoped")
|
|
1510
|
+
continue;
|
|
1511
|
+
if (resolution.kind === "unsafe") {
|
|
1512
|
+
findings.push(unresolvedHelperCommandFinding(document, command, commandPath, {
|
|
1513
|
+
details: { unsafePath: true },
|
|
1438
1514
|
}));
|
|
1439
1515
|
continue;
|
|
1440
1516
|
}
|
|
1517
|
+
const scriptPath = resolution.path;
|
|
1518
|
+
const skillPath = classifyRepositorySkillPath(scriptPath);
|
|
1519
|
+
if (skillPath?.kind === "support" &&
|
|
1520
|
+
skillPath.supportDirectory === "scripts") {
|
|
1521
|
+
if (!paths.has(scriptPath)) {
|
|
1522
|
+
findings.push(unresolvedHelperCommandFinding(document, command, scriptPath));
|
|
1523
|
+
}
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1441
1526
|
if (scriptPath.includes("/scripts/") && !scriptPath.startsWith("tools/")) {
|
|
1442
1527
|
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/**.", {
|
|
1443
1528
|
whyItMatters: "Helper commands should resolve to non-context helper assets in tools/** so contexts remain LLM-readable guidance.",
|
|
@@ -1445,9 +1530,7 @@ function helperCommandFindings(document, paths, config) {
|
|
|
1445
1530
|
continue;
|
|
1446
1531
|
}
|
|
1447
1532
|
if (scriptPath.startsWith("tools/") && !paths.has(scriptPath)) {
|
|
1448
|
-
findings.push(
|
|
1449
|
-
whyItMatters: "Agents need helper commands in markdown procedures to resolve deterministically before running them.",
|
|
1450
|
-
}));
|
|
1533
|
+
findings.push(unresolvedHelperCommandFinding(document, command, scriptPath));
|
|
1451
1534
|
}
|
|
1452
1535
|
}
|
|
1453
1536
|
return findings;
|
|
@@ -1467,10 +1550,6 @@ function layoutConsistencyFindings(document) {
|
|
|
1467
1550
|
pattern: /Each skill includes a self-improvement prompt/i,
|
|
1468
1551
|
message: "Describe self-improvement prompts as loaded context/reference/procedure workflow prompts instead of SKILL.md-only content.",
|
|
1469
1552
|
},
|
|
1470
|
-
{
|
|
1471
|
-
pattern: /skills\/[^/\s`]+\/(?:references|profiles|examples|scripts)\//,
|
|
1472
|
-
message: "Update docs to point to canonical contexts/** assets or tools/** helper scripts instead of skill-local support directories.",
|
|
1473
|
-
},
|
|
1474
1553
|
{
|
|
1475
1554
|
pattern: /\]\(contexts\/tools\//,
|
|
1476
1555
|
message: "Use backtick repo-root paths for contexts/tools/... references instead of file-relative Markdown links.",
|
|
@@ -1481,10 +1560,10 @@ function layoutConsistencyFindings(document) {
|
|
|
1481
1560
|
if (!match)
|
|
1482
1561
|
continue;
|
|
1483
1562
|
const line = lineForOffset(text, match.index ?? 0);
|
|
1484
|
-
findings.push(findingAt(document, DIAGNOSTIC_IDS.DOCS_LAYOUT_INCONSISTENT, "Repository docs
|
|
1485
|
-
whyItMatters: "README.md and AGENTS.md should
|
|
1563
|
+
findings.push(findingAt(document, DIAGNOSTIC_IDS.DOCS_LAYOUT_INCONSISTENT, "Repository docs contradict the supported layout", "maintenance", "low", line, lineText(document, line), stale.message, {
|
|
1564
|
+
whyItMatters: "README.md and AGENTS.md should describe the current repository model without treating valid Skill-local support as stale.",
|
|
1486
1565
|
verificationSteps: [
|
|
1487
|
-
"
|
|
1566
|
+
"Confirm docs distinguish canonical Skill roots and valid local support from governed contexts/** assets and shared tools/** helpers.",
|
|
1488
1567
|
"Run renma scan again.",
|
|
1489
1568
|
],
|
|
1490
1569
|
}));
|
|
@@ -1502,6 +1581,11 @@ function contextRootFindings(document) {
|
|
|
1502
1581
|
return [];
|
|
1503
1582
|
}
|
|
1504
1583
|
function helperRootFindings(document) {
|
|
1584
|
+
const skillPath = classifyRepositorySkillPath(document.artifact.path);
|
|
1585
|
+
if (skillPath?.kind === "support" &&
|
|
1586
|
+
skillPath.supportDirectory === "scripts") {
|
|
1587
|
+
return [];
|
|
1588
|
+
}
|
|
1505
1589
|
if (document.artifact.path.includes("/scripts/") &&
|
|
1506
1590
|
!document.artifact.path.startsWith("tools/")) {
|
|
1507
1591
|
return [
|
|
@@ -1522,7 +1606,7 @@ function declaredDependencyLayoutFindings(catalog, paths) {
|
|
|
1522
1606
|
continue;
|
|
1523
1607
|
}
|
|
1524
1608
|
if (target.startsWith("contexts/") ||
|
|
1525
|
-
target
|
|
1609
|
+
normalizeRepositoryRelativePath(target) !== undefined ||
|
|
1526
1610
|
target.startsWith("tools/")) {
|
|
1527
1611
|
continue;
|
|
1528
1612
|
}
|
|
@@ -1536,7 +1620,7 @@ function declaredDependencyLayoutFindings(catalog, paths) {
|
|
|
1536
1620
|
severity: "low",
|
|
1537
1621
|
confidence: "medium",
|
|
1538
1622
|
evidence: metadataFindingEvidence(source.sourcePath, target),
|
|
1539
|
-
whyItMatters: "Declared context references should resolve through canonical contexts/**, skills/**, or tools/**
|
|
1623
|
+
whyItMatters: "Declared context references should resolve through canonical contexts/**, skills/**, .agents/skills/**, or tools/** repository paths.",
|
|
1540
1624
|
remediation: "Rewrite declared required or optional context dependency values to canonical repo-root paths without changing the Skill's operational metadata format.",
|
|
1541
1625
|
verificationSteps: ["Run renma graph and confirm all edges resolve."],
|
|
1542
1626
|
details: {
|
|
@@ -1558,30 +1642,18 @@ function executableCommands(document) {
|
|
|
1558
1642
|
}))
|
|
1559
1643
|
.filter(({ command }) => /^(node|bash|sh|python|python3)\s+/.test(command)));
|
|
1560
1644
|
}
|
|
1561
|
-
function
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
if (namespace)
|
|
1574
|
-
return `contexts/tools/${namespace}/${workflow}/${rest}`;
|
|
1575
|
-
return `contexts/${workflow}/${rest}`;
|
|
1576
|
-
}
|
|
1577
|
-
function helperAssetPath(config, workflow, rest) {
|
|
1578
|
-
const namespace = config.layout.toolNamespace;
|
|
1579
|
-
if (namespace)
|
|
1580
|
-
return `tools/${namespace}/${workflow}/${rest}`;
|
|
1581
|
-
return `tools/${workflow}/${rest}`;
|
|
1582
|
-
}
|
|
1583
|
-
function isCanonicalSkillEntrypoint(pathValue) {
|
|
1584
|
-
return /^skills\/[^/]+\/SKILL\.md$/.test(pathValue);
|
|
1645
|
+
function unresolvedHelperCommandFinding(document, command, scriptPath, details = {}) {
|
|
1646
|
+
const unsafePath = details.details?.unsafePath === true;
|
|
1647
|
+
return findingAt(document, DIAGNOSTIC_IDS.PATH_HELPER_COMMAND_UNRESOLVED, "Helper command target does not resolve", "structure", "medium", command.line, command.command, unsafePath
|
|
1648
|
+
? "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 `..`."
|
|
1649
|
+
: `Create \`${scriptPath}\` or update this command to the correct local helper path.`, {
|
|
1650
|
+
whyItMatters: "Agents need shared tools/** helpers and Skill-local scripts to resolve deterministically before running them.",
|
|
1651
|
+
verificationSteps: [
|
|
1652
|
+
"Confirm the command resolves to an existing tools/** helper or to scripts/** inside its owning Skill.",
|
|
1653
|
+
"Run renma readiness and check paths.helper_commands.",
|
|
1654
|
+
],
|
|
1655
|
+
...details,
|
|
1656
|
+
});
|
|
1585
1657
|
}
|
|
1586
1658
|
function findingAt(document, id, title, category, severity, line, snippet, remediation, details = {}) {
|
|
1587
1659
|
return {
|
|
@@ -1599,9 +1671,6 @@ function lineText(document, line) {
|
|
|
1599
1671
|
function isRepoPathLike(value) {
|
|
1600
1672
|
return /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)+$/.test(value);
|
|
1601
1673
|
}
|
|
1602
|
-
function words(text) {
|
|
1603
|
-
return text.match(/[A-Za-z0-9_./+=-]+|[^\sA-Za-z0-9_./+=-]/g) ?? [];
|
|
1604
|
-
}
|
|
1605
1674
|
function finding(id, title, category, severity, document, remediation, details = {}) {
|
|
1606
1675
|
return {
|
|
1607
1676
|
id,
|
|
@@ -1656,15 +1725,12 @@ function localSupportUnreachableRuleId(kind) {
|
|
|
1656
1725
|
return DIAGNOSTIC_IDS.SUPPORT_UNREACHABLE_PROFILE;
|
|
1657
1726
|
if (kind === "example")
|
|
1658
1727
|
return DIAGNOSTIC_IDS.SUPPORT_UNREACHABLE_EXAMPLE;
|
|
1728
|
+
if (kind === "script")
|
|
1729
|
+
return DIAGNOSTIC_IDS.SUPPORT_UNREACHABLE_SCRIPT;
|
|
1730
|
+
if (kind === "asset")
|
|
1731
|
+
return DIAGNOSTIC_IDS.SUPPORT_UNREACHABLE_ASSET;
|
|
1659
1732
|
return DIAGNOSTIC_IDS.SUPPORT_UNREACHABLE_REFERENCE;
|
|
1660
1733
|
}
|
|
1661
|
-
function escapeRegExp(value) {
|
|
1662
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1663
|
-
}
|
|
1664
|
-
function approximateTokenCount(text) {
|
|
1665
|
-
const matches = text.match(/[A-Za-z0-9_./+=-]+|[^\sA-Za-z0-9_./+=-]/g);
|
|
1666
|
-
return matches?.length ?? 0;
|
|
1667
|
-
}
|
|
1668
1734
|
function isPlaceholder(line) {
|
|
1669
1735
|
return /(?:example|placeholder|your_|<[^>]+>|\$\{[^}]+})/i.test(line);
|
|
1670
1736
|
}
|