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.
- package/CHANGELOG.md +26 -0
- package/README.md +12 -5
- package/dist/cli/utils/launch-agent.d.ts +2 -1
- package/dist/cli/utils/launch-agent.d.ts.map +1 -1
- package/dist/cli/utils/launch-agent.js +1 -1
- package/dist/domain/analyze-skill-usage.d.ts.map +1 -1
- package/dist/domain/analyze-skill-usage.js +38 -9
- package/dist/domain/build-handoff-prompt.d.ts.map +1 -1
- package/dist/domain/build-handoff-prompt.js +2 -2
- package/dist/domain/build-report.js +6 -5
- package/dist/domain/discover-skill-artifacts.js +15 -14
- package/dist/domain/discover-skill-roots.d.ts.map +1 -1
- package/dist/domain/discover-skill-roots.js +7 -6
- package/dist/domain/discover-usage-sources.d.ts.map +1 -1
- package/dist/domain/discover-usage-sources.js +34 -26
- package/dist/domain/read-codex-disabled-skill-config.js +2 -2
- package/dist/domain/rule-catalog.d.ts +55 -0
- package/dist/domain/rule-catalog.d.ts.map +1 -1
- package/dist/domain/rule-catalog.js +66 -0
- package/dist/domain/rules/quality.d.ts +13 -0
- package/dist/domain/rules/quality.d.ts.map +1 -1
- package/dist/domain/rules/quality.js +300 -25
- package/dist/domain/rules/security.d.ts +33 -1
- package/dist/domain/rules/security.d.ts.map +1 -1
- package/dist/domain/rules/security.js +267 -37
- package/dist/domain/scan-skills.d.ts.map +1 -1
- package/dist/domain/scan-skills.js +13 -7
- package/dist/domain/security/commands.d.ts.map +1 -1
- package/dist/domain/security/commands.js +21 -1
- package/dist/domain/summarize-findings.js +14 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +4 -3
- package/scripts/build-agentskills-unified-doc.mjs +267 -0
- package/skills/skills-doctor/SKILL.md +7 -5
- 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 `
|
|
44
|
-
- launch interactive repair handoff through `
|
|
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
|
|
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
|
|
89
|
-
- eval
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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,
|
|
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) =>
|
|
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 =
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
+
entryArtifacts.push(artifact);
|
|
65
65
|
const entryStats = await lstat(artifactPath).catch(() => undefined);
|
|
66
66
|
if (entryStats === undefined)
|
|
67
|
-
|
|
67
|
+
return entryArtifacts;
|
|
68
68
|
if (entryStats.isDirectory()) {
|
|
69
|
-
await walk(artifactPath);
|
|
70
|
-
|
|
69
|
+
entryArtifacts.push(...(await walk(artifactPath)));
|
|
70
|
+
return entryArtifacts;
|
|
71
71
|
}
|
|
72
72
|
if (!entryStats.isSymbolicLink())
|
|
73
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
36
|
-
|
|
37
|
-
if (exists) {
|
|
39
|
+
for (const { candidate, exists } of candidatesWithStatus) {
|
|
40
|
+
if (exists)
|
|
38
41
|
roots.push(candidate);
|
|
39
|
-
|
|
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,
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
readSqlitePressure
|
|
34
|
-
|
|
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
|
|
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
|
-
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
+
if (entry.isDirectory())
|
|
68
|
+
return findJsonlFiles({ ...input, directory: entryPath });
|
|
67
69
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl"))
|
|
68
|
-
|
|
70
|
+
return [];
|
|
69
71
|
const candidate = await fileIfExists(entryPath, input.since, input.diagnostics);
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
99
|
-
names:
|
|
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
|
|
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
|