skills-doctor 0.6.0 → 0.6.2

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 (36) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +12 -5
  3. package/dist/cli/utils/launch-agent.d.ts +2 -1
  4. package/dist/cli/utils/launch-agent.d.ts.map +1 -1
  5. package/dist/cli/utils/launch-agent.js +1 -1
  6. package/dist/domain/analyze-skill-usage.d.ts.map +1 -1
  7. package/dist/domain/analyze-skill-usage.js +38 -9
  8. package/dist/domain/build-handoff-prompt.d.ts.map +1 -1
  9. package/dist/domain/build-handoff-prompt.js +2 -2
  10. package/dist/domain/build-report.js +6 -5
  11. package/dist/domain/discover-skill-artifacts.js +15 -14
  12. package/dist/domain/discover-skill-roots.d.ts.map +1 -1
  13. package/dist/domain/discover-skill-roots.js +7 -6
  14. package/dist/domain/discover-usage-sources.d.ts.map +1 -1
  15. package/dist/domain/discover-usage-sources.js +34 -26
  16. package/dist/domain/read-codex-disabled-skill-config.js +2 -2
  17. package/dist/domain/rule-catalog.d.ts +55 -0
  18. package/dist/domain/rule-catalog.d.ts.map +1 -1
  19. package/dist/domain/rule-catalog.js +66 -0
  20. package/dist/domain/rules/quality.d.ts +13 -0
  21. package/dist/domain/rules/quality.d.ts.map +1 -1
  22. package/dist/domain/rules/quality.js +300 -25
  23. package/dist/domain/rules/security.d.ts +33 -1
  24. package/dist/domain/rules/security.d.ts.map +1 -1
  25. package/dist/domain/rules/security.js +267 -37
  26. package/dist/domain/scan-skills.d.ts.map +1 -1
  27. package/dist/domain/scan-skills.js +13 -7
  28. package/dist/domain/security/commands.d.ts.map +1 -1
  29. package/dist/domain/security/commands.js +21 -1
  30. package/dist/domain/summarize-findings.js +14 -8
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/package.json +4 -3
  34. package/scripts/build-agentskills-unified-doc.mjs +267 -0
  35. package/skills/skills-doctor/SKILL.md +7 -5
  36. package/skills/skills-doctor/evals/evals.json +33 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.2] - 2026-07-05
10
+
11
+ ### Added
12
+
13
+ - Added static eval file validation for malformed or weak `evals/evals.json` files, including invalid JSON, invalid shape, missing prompts, missing expected output, weak assertions, and missing baseline guidance.
14
+ - Added local/global same-name skill shadowing warnings so users can see when a project skill likely overrides a user-level skill.
15
+ - Added deeper static script-interface findings for missing implementation help support, undocumented structured output, missing stderr diagnostics guidance, and potentially unbounded script output.
16
+
17
+ ### Fixed
18
+
19
+ - Aligned the packaged `skills-doctor` Agent Skill wrapper with scanner standards by using installed CLI commands and adding realistic wrapper evals.
20
+
21
+ ## [0.6.1] - 2026-07-05
22
+
23
+ ### Changed
24
+
25
+ - Updated repair handoff prompts to tell agents that findings are static analyzer diagnostics and should be judged for real issues or false positives before editing.
26
+
27
+ ### Fixed
28
+
29
+ - Cleared React Doctor JSON deep-clone warnings in report-shape tests by using `structuredClone`.
30
+ - Cleared React Doctor warnings for immutable sorting helpers, independent Codex usage pressure reads, and security detector patterns that were mistaken for plugin updater execution.
31
+ - Cleared React Doctor performance warnings by parallelizing independent filesystem reads and combining redundant array passes in scan, usage, report, and rule helpers.
32
+ - Required package-level secret exfiltration findings to connect secret-read and network-egress facts from the same artifact, and included all related chain capabilities in the finding summary.
33
+ - Reduced security false positives for prompt-injection text that appears as negated guidance, quoted examples, anti-patterns, or research notes while preserving operational prompt override, exfiltration, remote execution, and destructive-command findings.
34
+
9
35
  ## [0.6.0] - 2026-07-04
10
36
 
11
37
  ### Added
package/README.md CHANGED
@@ -40,13 +40,13 @@ Skills Doctor is also available as an agent-facing skill wrapper at `skills/skil
40
40
 
41
41
  Use the `skills-doctor` skill when people ask for an agent workflow to:
42
42
 
43
- - audit skills with `npx skills-doctor@latest --json` for deterministic, machine-readable output.
44
- - launch interactive repair handoff through `npx skills-doctor@latest` after explicit consent.
43
+ - audit skills with `skills-doctor --json` for deterministic, machine-readable output.
44
+ - launch interactive repair handoff through `skills-doctor` after explicit consent.
45
45
 
46
46
  The CLI is the primary source of rule logic and output shape.
47
47
  The skill wrapper is a convenience entrypoint so agents can discover and invoke the same scanner from within skill workflows.
48
48
  When installed from npm, the wrapper is shipped at `node_modules/skills-doctor/skills/skills-doctor/SKILL.md`.
49
- Copy or reference that file from an agent skill directory when you want an agent to discover the workflow, but keep scans delegated to `npx skills-doctor@latest` or the installed `skills-doctor` binary.
49
+ Copy or reference that file from an agent skill directory when you want an agent to discover the workflow, but keep scans delegated to the installed `skills-doctor` binary.
50
50
 
51
51
  ## What It Scans
52
52
 
@@ -85,8 +85,9 @@ the Agent Skills standards from <https://agentskills.io/home>, including:
85
85
  - non-generic skill bodies with concrete workflow structure
86
86
  - progressive disclosure for large or referenced material
87
87
  - referenced `references/`, `scripts/`, and `assets/` files
88
- - script guidance that is non-interactive and reproducible
89
- - eval guidance for non-trivial skills
88
+ - script guidance that is non-interactive, reproducible, documented, and bounded
89
+ - eval file quality for non-trivial skills, including JSON shape, realistic prompts, expected outputs, assertions, and baseline guidance
90
+ - local/global same-name skills where project-level skills likely shadow user-level skills
90
91
  - divergent same-name skills across Claude and Codex/agents roots
91
92
  - suspicious security patterns across the skill package, including prompt
92
93
  override, permission bypass, secret access, exfiltration chains, destructive
@@ -230,6 +231,12 @@ Codex prompts or assistant transcript text.
230
231
 
231
232
  ## Release Checklist
232
233
 
234
+ Refresh the consolidated upstream Agent Skills reference when source docs change:
235
+
236
+ ```bash
237
+ node scripts/build-agentskills-unified-doc.mjs
238
+ ```
239
+
233
240
  Before tagging a release:
234
241
 
235
242
  ```bash
@@ -1,5 +1,5 @@
1
1
  import type { PromptAdapter } from "./prompts.js";
2
- export declare const REPAIR_AGENT_IDS: readonly ["claude", "codex"];
2
+ declare const REPAIR_AGENT_IDS: readonly ["claude", "codex"];
3
3
  export type RepairAgentId = (typeof REPAIR_AGENT_IDS)[number];
4
4
  export type CommandInvocation = {
5
5
  readonly command: string;
@@ -30,4 +30,5 @@ export type SpawnInvocationOptions = {
30
30
  };
31
31
  export declare const buildRepairAgentSpawnInvocation: (agentId: RepairAgentId, prompt: string, options?: SpawnInvocationOptions) => CommandInvocation;
32
32
  export declare const launchRepairAgent: RepairAgentLauncher;
33
+ export {};
33
34
  //# sourceMappingURL=launch-agent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"launch-agent.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/launch-agent.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,eAAO,MAAM,gBAAgB,8BAA+B,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AACrF,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,KACR,OAAO,CAAC,MAAM,CAAC,CAAC;AAiBrB,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CAC3D,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,UAAS,yBAA8B,KACtC,OAAO,CAAC,SAAS,WAAW,EAAE,CAUhC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CAC3D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,OAAO,sBAAsB,KAC5B,OAAO,CAAC,WAAW,GAAG,SAAS,CA2CjC,CAAC;AAEF,eAAO,MAAM,0BAA0B,GACrC,SAAS,aAAa,EACtB,QAAQ,MAAM,KACb,iBAMF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,SAAS,aAAa,KAAG,MAGjE,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;CACjD,CAAC;AAEF,eAAO,MAAM,+BAA+B,GAC1C,SAAS,aAAa,EACtB,QAAQ,MAAM,EACd,UAAS,sBAA2B,KACnC,iBAWF,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,mBAO/B,CAAC"}
1
+ {"version":3,"file":"launch-agent.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/launch-agent.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,QAAA,MAAM,gBAAgB,8BAA+B,CAAC;AAEtD,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AACrF,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,KACR,OAAO,CAAC,MAAM,CAAC,CAAC;AAiBrB,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CAC3D,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,UAAS,yBAA8B,KACtC,OAAO,CAAC,SAAS,WAAW,EAAE,CAUhC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CAC3D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,OAAO,sBAAsB,KAC5B,OAAO,CAAC,WAAW,GAAG,SAAS,CA2CjC,CAAC;AAEF,eAAO,MAAM,0BAA0B,GACrC,SAAS,aAAa,EACtB,QAAQ,MAAM,KACb,iBAMF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,SAAS,aAAa,KAAG,MAGjE,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;CACjD,CAAC;AAEF,eAAO,MAAM,+BAA+B,GAC1C,SAAS,aAAa,EACtB,QAAQ,MAAM,EACd,UAAS,sBAA2B,KACnC,iBAWF,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,mBAO/B,CAAC"}
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { BackToMainMenuError, CliInputError } from "./handle-error.js";
5
5
  import { isCommandAvailable } from "./is-command-available.js";
6
6
  import { BACK_TO_MAIN_MENU_VALUE, backToMainMenuChoice } from "./prompts.js";
7
- export const REPAIR_AGENT_IDS = ["claude", "codex"];
7
+ const REPAIR_AGENT_IDS = ["claude", "codex"];
8
8
  const REPAIR_AGENT_CONFIG = {
9
9
  claude: {
10
10
  id: "claude",
@@ -1 +1 @@
1
- {"version":3,"file":"analyze-skill-usage.d.ts","sourceRoot":"","sources":["../../src/domain/analyze-skill-usage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"analyze-skill-usage.d.ts","sourceRoot":"","sources":["../../src/domain/analyze-skill-usage.ts"],"names":[],"mappings":"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"}
@@ -11,18 +11,36 @@ export const analyzeSkillUsage = async (input) => {
11
11
  const phraseMap = buildPhraseMap(catalog);
12
12
  const events = [];
13
13
  let readableSourceCount = 0;
14
- for (const sourcePath of sourcePaths) {
15
- const content = await readFile(sourcePath, "utf8").catch((error) => {
14
+ const sourceContents = await Promise.all(sourcePaths.map(async (sourcePath) => {
15
+ try {
16
+ return { sourcePath, content: await readFile(sourcePath, "utf8") };
17
+ }
18
+ catch (error) {
19
+ return {
20
+ sourcePath,
21
+ diagnostic: {
22
+ code: "usage-source-unreadable",
23
+ severity: "warning",
24
+ message: error instanceof Error ? error.message : `Unable to read ${sourcePath}`,
25
+ path: sourcePath,
26
+ },
27
+ };
28
+ }
29
+ }));
30
+ for (const { sourcePath, content, diagnostic } of sourceContents) {
31
+ if (diagnostic !== undefined) {
32
+ diagnostics.push(diagnostic);
33
+ continue;
34
+ }
35
+ if (content === undefined) {
16
36
  diagnostics.push({
17
37
  code: "usage-source-unreadable",
18
38
  severity: "warning",
19
- message: error instanceof Error ? error.message : `Unable to read ${sourcePath}`,
39
+ message: `Unable to read ${sourcePath}`,
20
40
  path: sourcePath,
21
41
  });
22
- return undefined;
23
- });
24
- if (content === undefined)
25
42
  continue;
43
+ }
26
44
  readableSourceCount += 1;
27
45
  events.push(...parseUsageSource({ sourcePath, content, aliasMap, phraseMap, diagnostics }));
28
46
  }
@@ -302,8 +320,14 @@ const extractAssistantText = (record) => {
302
320
  const collectText = (value) => {
303
321
  if (typeof value === "string")
304
322
  return value;
305
- if (Array.isArray(value))
306
- return value.map(collectText).filter(Boolean).join("\n");
323
+ if (Array.isArray(value)) {
324
+ return value
325
+ .flatMap((item) => {
326
+ const text = collectText(item);
327
+ return text ? [text] : [];
328
+ })
329
+ .join("\n");
330
+ }
307
331
  if (!isRecord(value))
308
332
  return "";
309
333
  return collectText(value.text ?? value.content);
@@ -404,7 +428,12 @@ const duplicateSkillNames = (catalog) => {
404
428
  for (const skill of catalog) {
405
429
  counts.set(skill.skillName, (counts.get(skill.skillName) ?? 0) + 1);
406
430
  }
407
- return new Set([...counts.entries()].filter(([, count]) => count > 1).map(([skillName]) => skillName));
431
+ const duplicateNames = new Set();
432
+ for (const [skillName, count] of counts) {
433
+ if (count > 1)
434
+ duplicateNames.add(skillName);
435
+ }
436
+ return duplicateNames;
408
437
  };
409
438
  const compareUsageSummaries = (left, right) => {
410
439
  const usageDifference = right.usageCount - left.usageCount;
@@ -1 +1 @@
1
- {"version":3,"file":"build-handoff-prompt.d.ts","sourceRoot":"","sources":["../../src/domain/build-handoff-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,YAAY,CAAC;AAKrD,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,uBAAuB,KAAG,MA2EnE,CAAC"}
1
+ {"version":3,"file":"build-handoff-prompt.d.ts","sourceRoot":"","sources":["../../src/domain/build-handoff-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,YAAY,CAAC;AAKrD,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,uBAAuB,KAAG,MA8EnE,CAAC"}
@@ -20,7 +20,7 @@ export const buildHandoffPrompt = (input) => {
20
20
  else {
21
21
  lines.push("Full findings report: unavailable; use the inline findings below.");
22
22
  }
23
- lines.push("", "Repair rules grounded in docs/SKILLS_SPEC.md:", "- Preserve unrelated user changes and existing skill intent.", "- Edit skill files directly; do not invent new requirements outside the skill spec.", "- Keep SKILL.md concise and move large examples, scripts, and references into progressively disclosed files.", "- Ensure frontmatter has a valid kebab-case name and a specific trigger-oriented description.", "- Make referenced resources/scripts real, deterministic, non-interactive by default, and safe for agents.", "- Verify by rerunning `skills-doctor` or an equivalent scan after edits.", "", "Top grouped findings:");
23
+ lines.push("", "Repair rules grounded in docs/SKILLS_SPEC.md:", "- Treat findings as static analyzer diagnostics, not definitive proof that the skill is broken or malicious.", "- Use judgment before editing: inspect the cited files, evidence, confidence, rationale, and counterevidence, then decide whether each finding is real, partially applicable, or a false positive.", "- Fix real issues with the smallest skill-spec-aligned change; leave likely false positives or intentionally acceptable patterns unchanged and explain that decision in your final report.", "- Preserve unrelated user changes and existing skill intent.", "- Edit skill files directly; do not invent new requirements outside the skill spec.", "- Keep SKILL.md concise and move large examples, scripts, and references into progressively disclosed files.", "- Ensure frontmatter has a valid kebab-case name and a specific trigger-oriented description.", "- Make referenced resources/scripts real, deterministic, non-interactive by default, and safe for agents.", "- Verify by rerunning `skills-doctor` or an equivalent scan after edits.", "", "Top grouped findings:");
24
24
  for (const [index, group] of groups.entries()) {
25
25
  lines.push("", `${index + 1}. ${group.skillLabel} (${group.findings.length} finding${group.findings.length === 1 ? "" : "s"})`, ` File: ${group.skillPath}`);
26
26
  for (const finding of group.findings.slice(0, MAX_INLINE_FINDINGS_PER_GROUP)) {
@@ -56,7 +56,7 @@ const groupFindingsBySkill = (findings) => {
56
56
  right.findings.length - left.findings.length ||
57
57
  left.skillPath.localeCompare(right.skillPath));
58
58
  };
59
- const sortFindings = (findings) => [...findings].sort((left, right) => severityScore(right) - severityScore(left) ||
59
+ const sortFindings = (findings) => findings.toSorted((left, right) => severityScore(right) - severityScore(left) ||
60
60
  left.skillPath.localeCompare(right.skillPath) ||
61
61
  left.ruleId.localeCompare(right.ruleId));
62
62
  const severityScore = (finding) => {
@@ -11,6 +11,11 @@ export const buildScanReport = (input) => {
11
11
  const diagnosticErrorCount = countDiagnosticSeverity(input.scan.diagnostics, "error");
12
12
  const hasErrorDiagnostics = diagnosticErrorCount > 0;
13
13
  const qualityFindingsBySkillPath = indexFindingsBySkillPath(qualityFindings);
14
+ const diagnosticErrorCodes = [];
15
+ for (const diagnostic of input.scan.diagnostics) {
16
+ if (diagnostic.severity === "error")
17
+ diagnosticErrorCodes.push(diagnostic.code);
18
+ }
14
19
  return {
15
20
  schemaVersion: 1,
16
21
  ok: errorCount === 0 && !hasErrorDiagnostics,
@@ -28,11 +33,7 @@ export const buildScanReport = (input) => {
28
33
  errorCount,
29
34
  warningCount,
30
35
  adviceCount,
31
- score: calculateScore(scoreFindings, {
32
- diagnosticErrorCodes: input.scan.diagnostics
33
- .filter((diagnostic) => diagnostic.severity === "error")
34
- .map((diagnostic) => diagnostic.code),
35
- }),
36
+ score: calculateScore(scoreFindings, { diagnosticErrorCodes }),
36
37
  skills: input.scan.skills.map((skill) => {
37
38
  const skillFindings = qualityFindingsBySkillPath.get(skill.skillPath) ?? [];
38
39
  return {
@@ -43,46 +43,47 @@ const readRootSymlinkArtifact = async (skill, rootRealPath) => {
43
43
  };
44
44
  };
45
45
  const walkSkillArtifacts = async (skillDir, rootRealPath) => {
46
- const artifacts = [];
47
46
  const visitedDirectories = new Set();
48
47
  const walk = async (directoryPath) => {
49
48
  const directoryRealPath = await realpath(directoryPath).catch(() => undefined);
50
49
  if (directoryRealPath !== undefined) {
51
50
  if (visitedDirectories.has(directoryRealPath))
52
- return;
51
+ return [];
53
52
  visitedDirectories.add(directoryRealPath);
54
53
  }
55
54
  const entries = await readdir(directoryPath, { withFileTypes: true }).catch(() => []);
56
- const sortedEntries = [...entries].sort((left, right) => left.name.localeCompare(right.name));
57
- for (const entry of sortedEntries) {
55
+ const sortedEntries = entries.toSorted((left, right) => left.name.localeCompare(right.name));
56
+ const artifactsByEntry = await Promise.all(sortedEntries.map(async (entry) => {
58
57
  if (IGNORED_DIRECTORY_NAMES.has(entry.name))
59
- continue;
58
+ return [];
59
+ const entryArtifacts = [];
60
60
  const artifactPath = path.join(directoryPath, entry.name);
61
61
  const relativePath = normalizeRelativePath(path.relative(skillDir, artifactPath));
62
62
  const artifact = await readArtifact(skillDir, rootRealPath, artifactPath, relativePath);
63
63
  if (artifact !== undefined)
64
- artifacts.push(artifact);
64
+ entryArtifacts.push(artifact);
65
65
  const entryStats = await lstat(artifactPath).catch(() => undefined);
66
66
  if (entryStats === undefined)
67
- continue;
67
+ return entryArtifacts;
68
68
  if (entryStats.isDirectory()) {
69
- await walk(artifactPath);
70
- continue;
69
+ entryArtifacts.push(...(await walk(artifactPath)));
70
+ return entryArtifacts;
71
71
  }
72
72
  if (!entryStats.isSymbolicLink())
73
- continue;
73
+ return entryArtifacts;
74
74
  const targetRealPath = await realpath(artifactPath).catch(() => undefined);
75
75
  if (targetRealPath !== undefined &&
76
76
  rootRealPath !== undefined &&
77
77
  isPathInside(rootRealPath, targetRealPath)) {
78
78
  const targetStats = await stat(artifactPath).catch(() => undefined);
79
79
  if (targetStats?.isDirectory() === true)
80
- await walk(artifactPath);
80
+ entryArtifacts.push(...(await walk(artifactPath)));
81
81
  }
82
- }
82
+ return entryArtifacts;
83
+ }));
84
+ return artifactsByEntry.flat();
83
85
  };
84
- await walk(skillDir);
85
- return artifacts;
86
+ return walk(skillDir);
86
87
  };
87
88
  const readArtifact = async (skillDir, rootRealPath, artifactPath, relativePath) => {
88
89
  const stats = await lstat(artifactPath).catch(() => undefined);
@@ -1 +1 @@
1
- {"version":3,"file":"discover-skill-roots.d.ts","sourceRoot":"","sources":["../../src/domain/discover-skill-roots.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExE,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;CAC7C,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,OAAO,uBAAuB,KAC7B,OAAO,CAAC,wBAAwB,CAmDlC,CAAC"}
1
+ {"version":3,"file":"discover-skill-roots.d.ts","sourceRoot":"","sources":["../../src/domain/discover-skill-roots.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExE,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;CAC7C,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,OAAO,uBAAuB,KAC7B,OAAO,CAAC,wBAAwB,CAqDlC,CAAC"}
@@ -30,15 +30,16 @@ export const discoverSkillRoots = async (input) => {
30
30
  source: "custom",
31
31
  })),
32
32
  ];
33
+ const candidatesWithStatus = await Promise.all(candidates.map(async (candidate) => ({
34
+ candidate,
35
+ exists: await isDirectory(candidate.rootPath),
36
+ })));
33
37
  const roots = [];
34
38
  const diagnostics = [];
35
- for (const candidate of candidates) {
36
- const exists = await isDirectory(candidate.rootPath);
37
- if (exists) {
39
+ for (const { candidate, exists } of candidatesWithStatus) {
40
+ if (exists)
38
41
  roots.push(candidate);
39
- continue;
40
- }
41
- if (candidate.source === "custom") {
42
+ else if (candidate.source === "custom") {
42
43
  diagnostics.push({
43
44
  code: "skill-root-not-found",
44
45
  severity: "warning",
@@ -1 +1 @@
1
- {"version":3,"file":"discover-usage-sources.d.ts","sourceRoot":"","sources":["../../src/domain/discover-usage-sources.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAEzE,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,yBAAyB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,yBAAyB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,EAAE;IAC5C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;CACtB,KAAK,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CAAC;AAE3C,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,uBAAuB,GAAG,SAAS,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;IAC5C,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC;CACjD,CAAC;AAaF,eAAO,MAAM,oBAAoB,GAC/B,QAAO,yBAA8B,KACpC,OAAO,CAAC,0BAA0B,CA4CpC,CAAC"}
1
+ {"version":3,"file":"discover-usage-sources.d.ts","sourceRoot":"","sources":["../../src/domain/discover-usage-sources.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAEzE,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,yBAAyB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,yBAAyB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,EAAE;IAC5C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;CACtB,KAAK,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CAAC;AAE3C,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,uBAAuB,GAAG,SAAS,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;IAC5C,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC;CACjD,CAAC;AAaF,eAAO,MAAM,oBAAoB,GAC/B,QAAO,yBAA8B,KACpC,OAAO,CAAC,0BAA0B,CAiDpC,CAAC"}
@@ -22,17 +22,22 @@ export const discoverUsageSources = async (input = {}) => {
22
22
  ...sessionFiles.map((candidate) => candidate.filePath),
23
23
  ...(historyFile === undefined ? [] : [historyFile.filePath]),
24
24
  ];
25
- const jsonlPressure = await detectJsonlPressure({
26
- files: [...sessionFiles, ...(historyFile === undefined ? [] : [historyFile])],
27
- maxFileBytes,
28
- diagnostics,
29
- });
30
- const sqlitePressure = await readSqlitePressure({
31
- sqlitePath,
32
- since,
33
- readSqlitePressure: input.readSqlitePressure,
34
- diagnostics,
35
- });
25
+ const jsonlDiagnostics = [];
26
+ const sqliteDiagnostics = [];
27
+ const [jsonlPressure, sqlitePressure] = await Promise.all([
28
+ detectJsonlPressure({
29
+ files: [...sessionFiles, ...(historyFile === undefined ? [] : [historyFile])],
30
+ maxFileBytes,
31
+ diagnostics: jsonlDiagnostics,
32
+ }),
33
+ readSqlitePressure({
34
+ sqlitePath,
35
+ since,
36
+ readSqlitePressure: input.readSqlitePressure,
37
+ diagnostics: sqliteDiagnostics,
38
+ }),
39
+ ]);
40
+ diagnostics.push(...jsonlDiagnostics, ...sqliteDiagnostics);
36
41
  return {
37
42
  usageSourcePaths,
38
43
  diagnostics,
@@ -57,20 +62,16 @@ const findJsonlFiles = async (input) => {
57
62
  });
58
63
  if (entries === undefined)
59
64
  return [];
60
- const files = [];
61
- for (const entry of entries) {
65
+ const filesByEntry = await Promise.all(entries.map(async (entry) => {
62
66
  const entryPath = path.join(input.directory, entry.name);
63
- if (entry.isDirectory()) {
64
- files.push(...(await findJsonlFiles({ ...input, directory: entryPath })));
65
- continue;
66
- }
67
+ if (entry.isDirectory())
68
+ return findJsonlFiles({ ...input, directory: entryPath });
67
69
  if (!entry.isFile() || !entry.name.endsWith(".jsonl"))
68
- continue;
70
+ return [];
69
71
  const candidate = await fileIfExists(entryPath, input.since, input.diagnostics);
70
- if (candidate !== undefined)
71
- files.push(candidate);
72
- }
73
- return files.sort(compareCandidateFiles);
72
+ return candidate === undefined ? [] : [candidate];
73
+ }));
74
+ return filesByEntry.flat().sort(compareCandidateFiles);
74
75
  };
75
76
  const fileIfExists = async (filePath, since, diagnostics) => {
76
77
  const stats = await stat(filePath).catch((error) => {
@@ -100,9 +101,7 @@ const fileIfExists = async (filePath, since, diagnostics) => {
100
101
  return { filePath, modifiedAt: stats.mtime };
101
102
  };
102
103
  const detectJsonlPressure = async (input) => {
103
- let warningCount = 0;
104
- const warningTimestamps = [];
105
- for (const file of input.files) {
104
+ const pressureByFile = await Promise.all(input.files.map(async (file) => {
106
105
  const content = await readTail(file.filePath, input.maxFileBytes).catch((error) => {
107
106
  input.diagnostics.push({
108
107
  code: "usage-source-unreadable",
@@ -113,7 +112,9 @@ const detectJsonlPressure = async (input) => {
113
112
  return undefined;
114
113
  });
115
114
  if (content === undefined)
116
- continue;
115
+ return { warningCount: 0, warningTimestamps: [] };
116
+ let warningCount = 0;
117
+ const warningTimestamps = [];
117
118
  for (const line of content.split(/\r?\n/)) {
118
119
  if (!line.includes(CONTEXT_BUDGET_WARNING))
119
120
  continue;
@@ -122,6 +123,13 @@ const detectJsonlPressure = async (input) => {
122
123
  if (timestamp !== undefined)
123
124
  warningTimestamps.push(timestamp);
124
125
  }
126
+ return { warningCount, warningTimestamps };
127
+ }));
128
+ let warningCount = 0;
129
+ const warningTimestamps = [];
130
+ for (const pressure of pressureByFile) {
131
+ warningCount += pressure.warningCount;
132
+ warningTimestamps.push(...pressure.warningTimestamps);
125
133
  }
126
134
  return {
127
135
  warningCount,
@@ -95,8 +95,8 @@ export const parseCodexDisabledSkillConfig = (content) => {
95
95
  }
96
96
  flush();
97
97
  return {
98
- paths: [...disabledPaths].sort((left, right) => left.localeCompare(right)),
99
- names: [...disabledNames].sort((left, right) => left.localeCompare(right)),
98
+ paths: Array.from(disabledPaths).toSorted((left, right) => left.localeCompare(right)),
99
+ names: Array.from(disabledNames).toSorted((left, right) => left.localeCompare(right)),
100
100
  };
101
101
  };
102
102
  export const normalizeSkillPath = (skillPath) => path.resolve(skillPath);
@@ -181,6 +181,26 @@ export declare const ruleCatalog: readonly [{
181
181
  readonly severity: "warning";
182
182
  readonly categories: readonly ["scripts"];
183
183
  readonly description: "Referenced script has risky operations without safety flags.";
184
+ }, {
185
+ readonly ruleId: "script-implementation-without-help";
186
+ readonly severity: "advice";
187
+ readonly categories: readonly ["scripts"];
188
+ readonly description: "Skill guidance mentions --help but the referenced script lacks apparent help support.";
189
+ }, {
190
+ readonly ruleId: "script-output-contract-missing";
191
+ readonly severity: "advice";
192
+ readonly categories: readonly ["scripts"];
193
+ readonly description: "Structured script output is not documented in nearby skill guidance.";
194
+ }, {
195
+ readonly ruleId: "script-diagnostics-channel-missing";
196
+ readonly severity: "advice";
197
+ readonly categories: readonly ["scripts"];
198
+ readonly description: "Structured-output script guidance does not separate diagnostics from data.";
199
+ }, {
200
+ readonly ruleId: "script-output-unbounded";
201
+ readonly severity: "advice";
202
+ readonly categories: readonly ["scripts"];
203
+ readonly description: "Script guidance or implementation suggests large output without bounds.";
184
204
  }, {
185
205
  readonly ruleId: "unpinned-package-runner";
186
206
  readonly severity: "advice";
@@ -201,6 +221,41 @@ export declare const ruleCatalog: readonly [{
201
221
  readonly severity: "advice";
202
222
  readonly categories: readonly ["evals"];
203
223
  readonly description: "Non-trivial skill lacks evals/evals.json.";
224
+ }, {
225
+ readonly ruleId: "invalid-evals-json";
226
+ readonly severity: "warning";
227
+ readonly categories: readonly ["evals"];
228
+ readonly description: "evals/evals.json is unreadable or not valid JSON.";
229
+ }, {
230
+ readonly ruleId: "invalid-evals-shape";
231
+ readonly severity: "warning";
232
+ readonly categories: readonly ["evals"];
233
+ readonly description: "evals/evals.json lacks required root fields or valid eval case shape.";
234
+ }, {
235
+ readonly ruleId: "eval-missing-prompt";
236
+ readonly severity: "warning";
237
+ readonly categories: readonly ["evals"];
238
+ readonly description: "An eval case lacks a non-empty realistic prompt.";
239
+ }, {
240
+ readonly ruleId: "eval-missing-expected-output";
241
+ readonly severity: "warning";
242
+ readonly categories: readonly ["evals"];
243
+ readonly description: "An eval case lacks a concrete expected_output.";
244
+ }, {
245
+ readonly ruleId: "eval-weak-assertions";
246
+ readonly severity: "warning";
247
+ readonly categories: readonly ["evals"];
248
+ readonly description: "Eval assertions are empty, malformed, or too vague.";
249
+ }, {
250
+ readonly ruleId: "eval-missing-baseline-guidance";
251
+ readonly severity: "advice";
252
+ readonly categories: readonly ["evals"];
253
+ readonly description: "Mature eval material lacks baseline or previous-version comparison guidance.";
254
+ }, {
255
+ readonly ruleId: "local-global-skill-shadowing";
256
+ readonly severity: "warning";
257
+ readonly categories: readonly ["portability"];
258
+ readonly description: "A global skill is likely shadowed by a same-name project-local skill.";
204
259
  }, {
205
260
  readonly ruleId: "SKILL001_PROMPT_OVERRIDE";
206
261
  readonly severity: "warning";
@@ -1 +1 @@
1
- {"version":3,"file":"rule-catalog.d.ts","sourceRoot":"","sources":["../../src/domain/rule-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAErF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6YwB,CAAC"}
1
+ {"version":3,"file":"rule-catalog.d.ts","sourceRoot":"","sources":["../../src/domain/rule-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAErF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgdwB,CAAC"}
@@ -209,6 +209,30 @@ export const ruleCatalog = [
209
209
  categories: ["scripts"],
210
210
  description: "Referenced script has risky operations without safety flags.",
211
211
  },
212
+ {
213
+ ruleId: "script-implementation-without-help",
214
+ severity: "advice",
215
+ categories: ["scripts"],
216
+ description: "Skill guidance mentions --help but the referenced script lacks apparent help support.",
217
+ },
218
+ {
219
+ ruleId: "script-output-contract-missing",
220
+ severity: "advice",
221
+ categories: ["scripts"],
222
+ description: "Structured script output is not documented in nearby skill guidance.",
223
+ },
224
+ {
225
+ ruleId: "script-diagnostics-channel-missing",
226
+ severity: "advice",
227
+ categories: ["scripts"],
228
+ description: "Structured-output script guidance does not separate diagnostics from data.",
229
+ },
230
+ {
231
+ ruleId: "script-output-unbounded",
232
+ severity: "advice",
233
+ categories: ["scripts"],
234
+ description: "Script guidance or implementation suggests large output without bounds.",
235
+ },
212
236
  {
213
237
  ruleId: "unpinned-package-runner",
214
238
  severity: "advice",
@@ -233,6 +257,48 @@ export const ruleCatalog = [
233
257
  categories: ["evals"],
234
258
  description: "Non-trivial skill lacks evals/evals.json.",
235
259
  },
260
+ {
261
+ ruleId: "invalid-evals-json",
262
+ severity: "warning",
263
+ categories: ["evals"],
264
+ description: "evals/evals.json is unreadable or not valid JSON.",
265
+ },
266
+ {
267
+ ruleId: "invalid-evals-shape",
268
+ severity: "warning",
269
+ categories: ["evals"],
270
+ description: "evals/evals.json lacks required root fields or valid eval case shape.",
271
+ },
272
+ {
273
+ ruleId: "eval-missing-prompt",
274
+ severity: "warning",
275
+ categories: ["evals"],
276
+ description: "An eval case lacks a non-empty realistic prompt.",
277
+ },
278
+ {
279
+ ruleId: "eval-missing-expected-output",
280
+ severity: "warning",
281
+ categories: ["evals"],
282
+ description: "An eval case lacks a concrete expected_output.",
283
+ },
284
+ {
285
+ ruleId: "eval-weak-assertions",
286
+ severity: "warning",
287
+ categories: ["evals"],
288
+ description: "Eval assertions are empty, malformed, or too vague.",
289
+ },
290
+ {
291
+ ruleId: "eval-missing-baseline-guidance",
292
+ severity: "advice",
293
+ categories: ["evals"],
294
+ description: "Mature eval material lacks baseline or previous-version comparison guidance.",
295
+ },
296
+ {
297
+ ruleId: "local-global-skill-shadowing",
298
+ severity: "warning",
299
+ categories: ["portability"],
300
+ description: "A global skill is likely shadowed by a same-name project-local skill.",
301
+ },
236
302
  {
237
303
  ruleId: "SKILL001_PROMPT_OVERRIDE",
238
304
  severity: "warning",
@@ -4,6 +4,19 @@ export type QualityRuleOptions = {
4
4
  readonly resourceExists?: ((skill: SkillRecord, referencePath: string) => Promise<boolean>) | undefined;
5
5
  readonly resourceStatus?: ((skill: SkillRecord, referencePath: string) => Promise<ResourceStatus>) | undefined;
6
6
  readonly evalsExist?: ((skill: SkillRecord) => Promise<boolean>) | undefined;
7
+ readonly inspectEvals?: ((skill: SkillRecord) => Promise<EvalInspection>) | undefined;
8
+ };
9
+ export type EvalInspection = {
10
+ readonly status: "missing";
11
+ } | {
12
+ readonly status: "unreadable";
13
+ readonly message: string;
14
+ } | {
15
+ readonly status: "invalid-json";
16
+ readonly message: string;
17
+ } | {
18
+ readonly status: "valid";
19
+ readonly value: unknown;
7
20
  };
8
21
  export declare const validateQualityRules: (skills: readonly SkillRecord[], options?: QualityRuleOptions) => Promise<Finding[]>;
9
22
  //# sourceMappingURL=quality.d.ts.map