@spardutti/claude-skills 1.24.1 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,6 +79,7 @@ Portable slash commands for common git workflows. Installed to `.claude/commands
79
79
  | `/release` | Release flow — dev→main PR with semver, changelog, tag, and GitHub release |
80
80
  | `/refactor` | Detect size/complexity/duplication/coupling issues via 4 parallel Haiku subagents, then refactor |
81
81
  | `/deep-review` | Multi-agent deep code review — 5 parallel Sonnet subagents catch guard bypasses, lost async state, wrong-table queries, dead references, protocol violations |
82
+ | `/plan-feature` | Integration-first feature planning — 3 parallel Haiku subagents scan for reusable code, established patterns, and touch points before producing a short integration plan |
82
83
 
83
84
  ## Quick Start
84
85
 
@@ -1,65 +1,121 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
3
 
4
- const SKILL_EVAL_BLOCK = `
5
- skill_evaluation:
6
- mandatory: true
7
- rule: |
8
- BEFORE writing ANY code, you MUST:
9
- 1. List EVERY skill from the system-reminder's available skills section
10
- 2. For each skill, write: [skill-name] ACTIVATE / SKIP — [one-line reason]
11
- 3. Call Skill(name) for every skill marked ACTIVATE
12
- 4. Only THEN proceed to implementation
13
- If you skip this evaluation, your response is INCOMPLETE and WRONG.`;
14
-
15
- const FILE_SIZE_BLOCK = `
16
- ## File Size Enforcement
4
+ const SKILL_BODY = `## Skills
5
+
6
+ BEFORE writing ANY code, you MUST:
7
+
8
+ 1. List EVERY skill available: check \`.claude/skills/\` (project) and \`~/.claude/skills/\` (global). The system-reminder's available-skills section is a hint, not the source of truth — if it's missing or empty, still check the directories.
9
+ 2. For each skill, write: [skill-name] ACTIVATE / SKIP — [one-line reason]
10
+ 3. Call Skill(name) for every skill marked ACTIVATE
11
+ 4. Only THEN proceed to implementation
12
+
13
+ If you skip this evaluation, your response is INCOMPLETE and WRONG.`;
14
+
15
+ const FILE_SIZE_BODY = `## File Size Enforcement
17
16
 
18
17
  - **Never write a file longer than 200 lines of code.** If a file would exceed 200 lines, split it into smaller modules before writing.
19
18
  - This rule applies during skill evaluation: if the code you're about to write would exceed 200 lines in any single file, refactor into multiple files first.
20
19
  - Skill evaluation must check this limit as part of every ACTIVATE decision.`;
21
20
 
22
- const EVAL_MARKER = "skill_evaluation:";
23
- const FILE_SIZE_MARKER = "## File Size Enforcement";
21
+ const BLOCKS = [
22
+ {
23
+ id: "skill-evaluation",
24
+ body: SKILL_BODY,
25
+ legacyHeadings: ["## Skills"],
26
+ legacyYamlMarker: "skill_evaluation:",
27
+ },
28
+ {
29
+ id: "file-size",
30
+ body: FILE_SIZE_BODY,
31
+ legacyHeadings: ["## File Size Enforcement"],
32
+ },
33
+ ];
24
34
 
25
- export async function setupClaudeMd(targetDir = process.cwd()) {
26
- const resolved = resolve(targetDir);
27
- const claudeMdPath = join(resolved, "CLAUDE.md");
35
+ function wrap(id, body) {
36
+ return `<!-- claude-skills:${id}:start -->\n${body}\n<!-- claude-skills:${id}:end -->`;
37
+ }
28
38
 
29
- let existing = "";
30
- try {
31
- existing = await readFile(claudeMdPath, "utf-8");
32
- } catch {
33
- // File doesn't exist will create
34
- }
39
+ function spliceSentinels(content, id, replacement) {
40
+ const start = `<!-- claude-skills:${id}:start -->`;
41
+ const end = `<!-- claude-skills:${id}:end -->`;
42
+ const startIdx = content.indexOf(start);
43
+ if (startIdx === -1) return null;
44
+ const endIdx = content.indexOf(end, startIdx);
45
+ if (endIdx === -1) return null;
46
+ return content.slice(0, startIdx) + replacement + content.slice(endIdx + end.length);
47
+ }
35
48
 
36
- const hasEval = existing.includes(EVAL_MARKER);
37
- const hasFileSize = existing.includes(FILE_SIZE_MARKER);
49
+ function spliceLegacyHeading(content, heading, replacement) {
50
+ const lines = content.split("\n");
51
+ const startIdx = lines.findIndex((l) => l.trim() === heading);
52
+ if (startIdx === -1) return null;
53
+ let endIdx = lines.length;
54
+ for (let i = startIdx + 1; i < lines.length; i++) {
55
+ if (lines[i].startsWith("## ")) {
56
+ endIdx = i;
57
+ break;
58
+ }
59
+ }
60
+ return joinSplice(lines, startIdx, endIdx, replacement);
61
+ }
38
62
 
39
- if (hasEval && hasFileSize) {
40
- console.log(" CLAUDE.md already has skill_evaluation and file size rules — skipped.");
41
- return;
63
+ function spliceLegacyYaml(content, marker, replacement) {
64
+ const lines = content.split("\n");
65
+ const startIdx = lines.findIndex((l) => l.startsWith(marker));
66
+ if (startIdx === -1) return null;
67
+ let endIdx = lines.length;
68
+ for (let i = startIdx + 1; i < lines.length; i++) {
69
+ const line = lines[i];
70
+ if (line.length > 0 && !/^\s/.test(line)) {
71
+ endIdx = i;
72
+ break;
73
+ }
42
74
  }
75
+ return joinSplice(lines, startIdx, endIdx, replacement);
76
+ }
43
77
 
44
- let content = existing.trimEnd();
78
+ function joinSplice(lines, startIdx, endIdx, replacement) {
79
+ const before = lines.slice(0, startIdx).join("\n").replace(/\s+$/, "");
80
+ const after = lines.slice(endIdx).join("\n").replace(/^\s+/, "");
81
+ const parts = [before, replacement, after].filter((s) => s.length > 0);
82
+ return parts.join("\n\n");
83
+ }
45
84
 
46
- if (!hasEval) {
47
- content = content.length > 0
48
- ? content + "\n" + SKILL_EVAL_BLOCK
49
- : SKILL_EVAL_BLOCK.trimStart();
85
+ function applyBlock(content, block) {
86
+ const wrapped = wrap(block.id, block.body);
87
+ let next = spliceSentinels(content, block.id, wrapped);
88
+ if (next !== null) return { content: next, action: `replaced ${block.id}` };
89
+ for (const heading of block.legacyHeadings ?? []) {
90
+ next = spliceLegacyHeading(content, heading, wrapped);
91
+ if (next !== null) return { content: next, action: `migrated ${block.id}` };
50
92
  }
51
-
52
- if (!hasFileSize) {
53
- content = content + "\n" + FILE_SIZE_BLOCK;
93
+ if (block.legacyYamlMarker) {
94
+ next = spliceLegacyYaml(content, block.legacyYamlMarker, wrapped);
95
+ if (next !== null) return { content: next, action: `migrated ${block.id}` };
54
96
  }
97
+ const base = content.replace(/\s+$/, "");
98
+ const merged = base.length > 0 ? base + "\n\n" + wrapped : wrapped;
99
+ return { content: merged, action: `added ${block.id}` };
100
+ }
101
+
102
+ export async function setupClaudeMd(targetDir = process.cwd()) {
103
+ const claudeMdPath = join(resolve(targetDir), "CLAUDE.md");
55
104
 
56
- await writeFile(claudeMdPath, content + "\n", { mode: 0o644 });
105
+ let content = "";
106
+ try {
107
+ content = await readFile(claudeMdPath, "utf-8");
108
+ } catch {
109
+ // File doesn't exist — will create
110
+ }
57
111
 
58
- if (!hasEval && !hasFileSize) {
59
- console.log(" CLAUDE.md updated with skill_evaluation and file size rules.");
60
- } else if (!hasEval) {
61
- console.log(" CLAUDE.md updated with skill_evaluation block.");
62
- } else {
63
- console.log(" CLAUDE.md updated with file size enforcement rule.");
112
+ const actions = [];
113
+ for (const block of BLOCKS) {
114
+ const result = applyBlock(content, block);
115
+ content = result.content;
116
+ actions.push(result.action);
64
117
  }
118
+
119
+ await writeFile(claudeMdPath, content.replace(/\s+$/, "") + "\n", { mode: 0o644 });
120
+ console.log(` CLAUDE.md: ${actions.join(", ")}.`);
65
121
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spardutti/claude-skills",
3
- "version": "1.24.1",
3
+ "version": "1.26.0",
4
4
  "description": "CLI to install Claude Code skills from the claude-skills collection",
5
5
  "type": "module",
6
6
  "bin": {