skills-doctor 0.5.0 → 0.5.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 CHANGED
@@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.2] - 2026-06-20
10
+
11
+ ### Added
12
+
13
+ - Let users choose which unused cleanup candidates to include in cleanup handoff prompts.
14
+
15
+ ### Changed
16
+
17
+ - Include all unused cleanup candidates in reports and handoff selection instead of capping the detected list.
18
+
19
+ ## [0.5.1] - 2026-06-20
20
+
21
+ ### Changed
22
+
23
+ - Store default repair and cleanup handoff reports under the OS temp directory instead of creating `.skills-doctor/reports` in the scanned project.
24
+
9
25
  ## [0.5.0] - 2026-06-20
10
26
 
11
27
  ### Added
package/README.md CHANGED
@@ -104,7 +104,8 @@ available, reads local Codex usage traces from known `~/.codex` paths. It can:
104
104
  3. Let you choose a repair subset: errors, errors plus warnings, all findings,
105
105
  or selected skills.
106
106
  4. Detect local `claude` and `codex` executables.
107
- 5. Write a full report under `.skills-doctor/reports/<timestamp>/`.
107
+ 5. Write a full report under the OS temp directory, for example
108
+ `/tmp/skills-doctor-<uid>/reports/<timestamp>/` on Linux.
108
109
  6. Generate a compact `handoff-prompt.md` tailored to the selected findings or
109
110
  `cleanup-prompt.md` tailored to usage cleanup.
110
111
  7. Preview the launch command.
@@ -300,7 +300,7 @@ const reviewScan = async (report, input) => {
300
300
  ...(report.usage !== undefined && report.usage.topRecommendations.length > 0
301
301
  ? [
302
302
  {
303
- name: "Disable unused skills to reduce context pressure",
303
+ name: "Choose unused skills to disable",
304
304
  value: "cleanup",
305
305
  },
306
306
  { name: "View usage ranking", value: "usage-ranking" },
@@ -350,8 +350,14 @@ const reviewScan = async (report, input) => {
350
350
  };
351
351
  const runCleanupAgentFlow = async (report, input) => {
352
352
  try {
353
+ const selectedRecommendations = await selectCleanupRecommendations(report, input);
354
+ if (selectedRecommendations.length === 0) {
355
+ input.write("Cleanup handoff cancelled.\n");
356
+ return undefined;
357
+ }
353
358
  const handoff = await prepareCleanupHandoff({
354
359
  report,
360
+ recommendations: selectedRecommendations,
355
361
  outputRoot: input.cleanupReportOutputRoot,
356
362
  timestamp: input.cleanupReportTimestamp,
357
363
  });
@@ -427,6 +433,22 @@ const runCleanupAgentFlow = async (report, input) => {
427
433
  throw error;
428
434
  }
429
435
  };
436
+ const selectCleanupRecommendations = async (report, input) => {
437
+ const usage = report.usage;
438
+ if (usage === undefined)
439
+ throw new CliInputError("Usage analysis is required before cleanup.");
440
+ const candidates = usage.recommendations.filter((recommendation) => recommendation.action === "disable-candidate");
441
+ if (candidates.length === 0)
442
+ return [];
443
+ const defaultPaths = new Set(usage.topRecommendations.map((recommendation) => recommendation.skillPath));
444
+ const selectedPaths = new Set(await input.prompts.checkbox("Select unused skills to disable", candidates.map((recommendation) => ({
445
+ name: recommendation.skillName,
446
+ value: recommendation.skillPath,
447
+ description: compactSkillPath(recommendation.skillPath),
448
+ checked: defaultPaths.has(recommendation.skillPath),
449
+ }))));
450
+ return candidates.filter((recommendation) => selectedPaths.has(recommendation.skillPath));
451
+ };
430
452
  const runRepairAgentFlow = async (report, input) => {
431
453
  try {
432
454
  const handoff = await prepareRepairHandoff({
@@ -1,3 +1,4 @@
1
+ import type { SkillCleanupRecommendation } from "../../domain/analyze-skill-usage.js";
1
2
  import type { ScanReport } from "../../domain/build-report.js";
2
3
  import { writeCleanupDirectory } from "../../domain/write-cleanup-directory.js";
3
4
  export type PreparedCleanupHandoff = {
@@ -10,6 +11,7 @@ export type PreparedCleanupHandoff = {
10
11
  };
11
12
  export type PrepareCleanupHandoffInput = {
12
13
  readonly report: ScanReport;
14
+ readonly recommendations?: readonly SkillCleanupRecommendation[] | undefined;
13
15
  readonly outputRoot?: string | undefined;
14
16
  readonly timestamp?: string | undefined;
15
17
  readonly writeDirectory?: typeof writeCleanupDirectory | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"cleanup-handoff-to-agent.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/cleanup-handoff-to-agent.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAK/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAGhF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,qBAAqB,GAAG,SAAS,CAAC;CACpE,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,OAAO,0BAA0B,KAChC,OAAO,CAAC,sBAAsB,CAuChC,CAAC"}
1
+ {"version":3,"file":"cleanup-handoff-to-agent.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/cleanup-handoff-to-agent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AAEtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAK/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAGhF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAC;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,qBAAqB,GAAG,SAAS,CAAC;CACpE,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,OAAO,0BAA0B,KAChC,OAAO,CAAC,sBAAsB,CAgDhC,CAAC"}
@@ -10,6 +10,12 @@ export const prepareCleanupHandoff = async (input) => {
10
10
  if (input.report.usage.topRecommendations.length === 0) {
11
11
  throw new CliInputError("No cleanup recommendations are available.");
12
12
  }
13
+ if (input.recommendations !== undefined && input.recommendations.length === 0) {
14
+ throw new CliInputError("No cleanup recommendations were selected.");
15
+ }
16
+ if (input.recommendations?.some((recommendation) => recommendation.action !== "disable-candidate")) {
17
+ throw new CliInputError("Cleanup handoff can only disable selected unused skills.");
18
+ }
13
19
  const reportResult = await tryWriteCleanupDirectory({
14
20
  report: input.report,
15
21
  outputRoot: input.outputRoot,
@@ -18,6 +24,7 @@ export const prepareCleanupHandoff = async (input) => {
18
24
  });
19
25
  const prompt = buildCleanupHandoffPrompt({
20
26
  report: input.report,
27
+ recommendations: input.recommendations,
21
28
  reportDirectory: reportResult.result?.directory,
22
29
  });
23
30
  let promptPath;
@@ -3,6 +3,7 @@ export type Choice<Value extends string> = {
3
3
  readonly name: string;
4
4
  readonly value: Value;
5
5
  readonly description?: string;
6
+ readonly checked?: boolean;
6
7
  };
7
8
  export type PromptAdapter = {
8
9
  readonly checkbox: <Value extends string>(message: string, choices: readonly Choice<Value>[]) => Promise<Value[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,MAAM,MAAM,CAAC,KAAK,SAAS,MAAM,IAAI;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,SAAS,MAAM,EACtC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,MAAM,CAAC,KAAK,CAAC,EAAE,KAC9B,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChF,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,SAAS,MAAM,EACpC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,MAAM,CAAC,KAAK,CAAC,EAAE,KAC9B,OAAO,CAAC,KAAK,CAAC,CAAC;CACrB,CAAC;AAEF,qBAAa,oBAAqB,SAAQ,aAAa;;CAKtD;AAED,eAAO,MAAM,qBAAqB,EAAE,aAQnC,CAAC"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,MAAM,MAAM,CAAC,KAAK,SAAS,MAAM,IAAI;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,SAAS,MAAM,EACtC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,MAAM,CAAC,KAAK,CAAC,EAAE,KAC9B,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChF,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,SAAS,MAAM,EACpC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,MAAM,CAAC,KAAK,CAAC,EAAE,KAC9B,OAAO,CAAC,KAAK,CAAC,CAAC;CACrB,CAAC;AAEF,qBAAa,oBAAqB,SAAQ,aAAa;;CAKtD;AAED,eAAO,MAAM,qBAAqB,EAAE,aAQnC,CAAC"}
@@ -1,6 +1,8 @@
1
+ import type { SkillCleanupRecommendation } from "./analyze-skill-usage.js";
1
2
  import type { ScanReport } from "./build-report.js";
2
3
  export type BuildCleanupHandoffPromptInput = {
3
4
  readonly report: ScanReport;
5
+ readonly recommendations?: readonly SkillCleanupRecommendation[] | undefined;
4
6
  readonly reportDirectory?: string | undefined;
5
7
  };
6
8
  export declare const buildCleanupHandoffPrompt: (input: BuildCleanupHandoffPromptInput) => string;
@@ -1 +1 @@
1
- {"version":3,"file":"build-cleanup-handoff-prompt.d.ts","sourceRoot":"","sources":["../../src/domain/build-cleanup-handoff-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,mBAAmB,CAAC;AAErE,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,OAAO,8BAA8B,KAAG,MAuDjF,CAAC"}
1
+ {"version":3,"file":"build-cleanup-handoff-prompt.d.ts","sourceRoot":"","sources":["../../src/domain/build-cleanup-handoff-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,mBAAmB,CAAC;AAErE,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAC;IAC7E,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,OAAO,8BAA8B,KAAG,MAyDjF,CAAC"}
@@ -1,5 +1,6 @@
1
1
  export const buildCleanupHandoffPrompt = (input) => {
2
2
  const usage = requireUsage(input.report);
3
+ const recommendations = input.recommendations ?? usage.topRecommendations;
3
4
  const lines = [
4
5
  "Clean up Agent Skills context pressure using the Skills Doctor usage report.",
5
6
  "",
@@ -15,12 +16,12 @@ export const buildCleanupHandoffPrompt = (input) => {
15
16
  else {
16
17
  lines.push("Usage report directory: unavailable; use the inline summary below.");
17
18
  }
18
- lines.push("", "Cleanup rules:", "- Inspect the usage report first.", "- Preserve frequently used and recently used skills.", "- Preserve project-local skills unless there is strong evidence and clear user intent.", "- Do not delete skills.", "- Only disable skills with a `disable-candidate` recommendation.", "- Do not modify skills recommended as keep, review, shorten-description, or merge-candidate.", "- Do not move skill directories.", "- For unused global/plugin skills, disable them the same way Codex `/skills` does: add or update `[[skills.config]]` entries in `~/.codex/config.toml` with the skill `path` and `enabled = false`.", "- Re-enable skills by using Codex `/skills` or removing the matching disabled config entry.", "- Restart Codex or start a fresh session after config changes so the disabled skill list is reloaded.", "- Do not delete skills solely because usage is unknown.", "- Do not expose raw Codex logs or transcript text.", "- Verify by rerunning `npx skills-doctor@latest` after changes.", "", "Unused skills to disable:");
19
- for (const recommendation of usage.topRecommendations.slice(0, 20)) {
19
+ lines.push("", "Cleanup rules:", "- Inspect the usage report first.", "- Preserve frequently used and recently used skills.", "- Preserve project-local skills unless there is strong evidence and clear user intent.", "- Do not delete skills.", "- Only disable skills with a `disable-candidate` recommendation.", "- Disable only the selected skills listed below; leave other cleanup candidates unchanged.", "- Do not modify skills recommended as keep, review, shorten-description, or merge-candidate.", "- Do not move skill directories.", "- For unused global/plugin skills, disable them the same way Codex `/skills` does: add or update `[[skills.config]]` entries in `~/.codex/config.toml` with the skill `path` and `enabled = false`.", "- Re-enable skills by using Codex `/skills` or removing the matching disabled config entry.", "- Restart Codex or start a fresh session after config changes so the disabled skill list is reloaded.", "- Do not delete skills solely because usage is unknown.", "- Do not expose raw Codex logs or transcript text.", "- Verify by rerunning `npx skills-doctor@latest` after changes.", "", "Selected unused skills to disable:");
20
+ for (const recommendation of recommendations) {
20
21
  lines.push(`- ${recommendation.action} ${recommendation.skillName}`, ` Path: ${recommendation.skillPath}`, ` Reason: ${recommendation.reason}`);
21
22
  }
22
- if (usage.topRecommendations.length === 0) {
23
- lines.push("- No unused disable candidates were produced.");
23
+ if (recommendations.length === 0) {
24
+ lines.push("- No unused disable candidates were selected.");
24
25
  }
25
26
  lines.push("", "Make only reversible Codex skills-config disable changes, then stop and report what changed.");
26
27
  return lines.join("\n");
@@ -57,7 +57,6 @@ export type BuildScanReportInput = {
57
57
  export type BuildScanReportUsageInput = {
58
58
  readonly analysis: SkillUsageAnalysis;
59
59
  readonly contextPressure: ContextBudgetPressure;
60
- readonly topRecommendationLimit?: number | undefined;
61
60
  };
62
61
  export declare const buildScanReport: (input: BuildScanReportInput) => ScanReport;
63
62
  //# sourceMappingURL=build-report.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-report.d.ts","sourceRoot":"","sources":["../../src/domain/build-report.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build-report.d.ts","sourceRoot":"","sources":["../../src/domain/build-report.ts"],"names":[],"mappings":"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"}
@@ -58,9 +58,7 @@ const buildReportUsage = (input) => ({
58
58
  pluginContributedSkillCount: input.analysis.pluginContributedSkillCount,
59
59
  skillsByUsage: input.analysis.skillsByUsage,
60
60
  recommendations: input.analysis.recommendations,
61
- topRecommendations: input.analysis.recommendations
62
- .filter((recommendation) => recommendation.action === "disable-candidate")
63
- .slice(0, input.topRecommendationLimit ?? 10),
61
+ topRecommendations: input.analysis.recommendations.filter((recommendation) => recommendation.action === "disable-candidate"),
64
62
  });
65
63
  const countSeverity = (findings, severity) => findings.filter((finding) => finding.severity === severity).length;
66
64
  const countDiagnosticSeverity = (diagnostics, severity) => diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
@@ -0,0 +1,2 @@
1
+ export declare const defaultReportOutputRoot: () => string;
2
+ //# sourceMappingURL=default-report-output-root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-report-output-root.d.ts","sourceRoot":"","sources":["../../src/domain/default-report-output-root.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,uBAAuB,QAAO,MACgB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { tmpdir, userInfo } from "node:os";
2
+ import path from "node:path";
3
+ export const defaultReportOutputRoot = () => path.join(tmpdir(), scopedTempDirectoryName(), "reports");
4
+ const scopedTempDirectoryName = () => {
5
+ const uid = process.getuid?.();
6
+ if (uid !== undefined) {
7
+ return `skills-doctor-${uid}`;
8
+ }
9
+ try {
10
+ return `skills-doctor-${safePathSegment(userInfo().username)}`;
11
+ }
12
+ catch {
13
+ return "skills-doctor-user";
14
+ }
15
+ };
16
+ const safePathSegment = (value) => value
17
+ .toLowerCase()
18
+ .replace(/[^a-z0-9._-]+/g, "-")
19
+ .replace(/^-+|-+$/g, "") || "user";
@@ -1 +1 @@
1
- {"version":3,"file":"summarize-findings.d.ts","sourceRoot":"","sources":["../../src/domain/summarize-findings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEnD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,SAAS,YAAY,EAAE,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACtC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,UAAU,SAAS,OAAO,EAAE,KAAG,cAY/D,CAAC;AAEH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,UAAU,EAClB,UAAS,mBAAwB,KAChC,CAAC,GAAG,CAQN,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,UAAU,EAClB,UAAS,yBAA8B,KACtC,MAwCF,CAAC"}
1
+ {"version":3,"file":"summarize-findings.d.ts","sourceRoot":"","sources":["../../src/domain/summarize-findings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEnD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,SAAS,YAAY,EAAE,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACtC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,UAAU,SAAS,OAAO,EAAE,KAAG,cAY/D,CAAC;AAEH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,UAAU,EAClB,UAAS,mBAAwB,KAChC,CAAC,GAAG,CAQN,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,UAAU,EAClB,UAAS,yBAA8B,KACtC,MAmCF,CAAC"}
@@ -36,11 +36,7 @@ export const renderHumanSummary = (report, options = {}) => {
36
36
  }
37
37
  const cleanupCandidateCount = countCleanupCandidates(report);
38
38
  if (cleanupCandidateCount > 0) {
39
- const shownCount = report.usage.topRecommendations.length;
40
- const shownSuffix = shownCount < cleanupCandidateCount
41
- ? ` (${warning(String(shownCount), shouldColor)} shown in next cleanup batch)`
42
- : "";
43
- lines.push(`${label("Cleanup candidates", shouldColor)}: ${warning(String(cleanupCandidateCount), shouldColor)} enabled unused skills${shownSuffix}`);
39
+ lines.push(`${label("Cleanup candidates", shouldColor)}: ${warning(String(cleanupCandidateCount), shouldColor)} enabled unused skills`);
44
40
  }
45
41
  }
46
42
  return `${lines.join("\n")}\n`;
@@ -1 +1 @@
1
- {"version":3,"file":"write-cleanup-directory.d.ts","sourceRoot":"","sources":["../../src/domain/write-cleanup-directory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,mBAAmB,CAAC;AAErE,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,OAAO,qBAAqB,KAC3B,OAAO,CAAC,sBAAsB,CA+BhC,CAAC"}
1
+ {"version":3,"file":"write-cleanup-directory.d.ts","sourceRoot":"","sources":["../../src/domain/write-cleanup-directory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,mBAAmB,CAAC;AAGrE,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,OAAO,qBAAqB,KAC3B,OAAO,CAAC,sBAAsB,CAgChC,CAAC"}
@@ -1,9 +1,11 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { defaultReportOutputRoot } from "./default-report-output-root.js";
3
4
  export const writeCleanupDirectory = async (input) => {
4
5
  const usage = requireUsage(input.report);
5
- const directory = path.join(input.outputRoot ?? path.join(input.report.directory, ".skills-doctor", "reports"), sanitizeTimestamp(input.timestamp ?? new Date().toISOString()));
6
- await mkdir(directory, { recursive: true });
6
+ const outputRoot = input.outputRoot ?? defaultReportOutputRoot();
7
+ const directory = path.join(outputRoot, sanitizeTimestamp(input.timestamp ?? new Date().toISOString()));
8
+ await mkdir(directory, { recursive: true, mode: 0o700 });
7
9
  const usageJsonPath = path.join(directory, "usage.json");
8
10
  const usageMarkdownPath = path.join(directory, "usage.md");
9
11
  await writeFile(usageJsonPath, `${JSON.stringify({
@@ -1 +1 @@
1
- {"version":3,"file":"write-findings-directory.d.ts","sourceRoot":"","sources":["../../src/domain/write-findings-directory.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;IACnD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,sBAAsB,KAC5B,OAAO,CAAC,uBAAuB,CAgDjC,CAAC"}
1
+ {"version":3,"file":"write-findings-directory.d.ts","sourceRoot":"","sources":["../../src/domain/write-findings-directory.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;IACnD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,sBAAsB,KAC5B,OAAO,CAAC,uBAAuB,CAiDjC,CAAC"}
@@ -1,12 +1,14 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { mkdir, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { defaultReportOutputRoot } from "./default-report-output-root.js";
4
5
  import { groupFindingsByKey } from "./group-findings.js";
5
6
  export const writeFindingsDirectory = async (input) => {
6
7
  const findings = input.findings ?? input.report.findings;
7
- const directory = path.join(input.outputRoot ?? path.join(input.report.directory, ".skills-doctor", "reports"), sanitizeTimestamp(input.timestamp ?? new Date().toISOString()));
8
+ const outputRoot = input.outputRoot ?? defaultReportOutputRoot();
9
+ const directory = path.join(outputRoot, sanitizeTimestamp(input.timestamp ?? new Date().toISOString()));
8
10
  const skillDirectory = path.join(directory, "skills");
9
- await mkdir(skillDirectory, { recursive: true });
11
+ await mkdir(skillDirectory, { recursive: true, mode: 0o700 });
10
12
  const findingsJsonPath = path.join(directory, "findings.json");
11
13
  const findingsMarkdownPath = path.join(directory, "findings.md");
12
14
  const skillReportPaths = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills-doctor",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Interactive CLI that scans and repairs Agent Skills quality issues.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.13",