@schalkneethling/toolkit 0.7.2 → 1.1.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
@@ -1,16 +1,13 @@
1
1
  # claude-toolkit
2
2
 
3
- CLI for managing [Claude Code](https://claude.com/claude-code) hooks, skills, commands, and collections across projects. Hooks are copied into a project's `.claude/` directory; skills are copied into `.claude-toolkit/skills/` and symlinked into wherever Claude Code expects to find them; commands are copied into `.claude/commands/`; collections install any combination of those resources from a bundled root config.
3
+ CLI for managing [Claude Code](https://claude.com/claude-code) hooks, skills, agents, and collections across projects. Hooks are copied into a project's `.claude/` directory; skills and agents are copied into `.claude-toolkit/` and symlinked into wherever Claude Code expects to find them; collections install any combination of those resources from a bundled root config.
4
4
 
5
5
  ## Repo layout
6
6
 
7
7
  ```plaintext
8
8
  .
9
- ├── commands/ # Claude Code custom slash commands (*.md)
10
- │ ├── rpm-start.md
11
- │ ├── rpm-advance.md
12
- │ └── ...
13
9
  ├── config.json # bundled collection definitions
10
+ ├── agents/ # Claude Code subagent Markdown files
14
11
  ├── hooks/
15
12
  │ ├── auto-approve-safe-commands/
16
13
  │ │ ├── hook.mjs # the hook script itself
@@ -33,7 +30,7 @@ CLI for managing [Claude Code](https://claude.com/claude-code) hooks, skills, co
33
30
  - Node.js 22+
34
31
  - `tsx` (installed as a devDependency)
35
32
 
36
- From inside a consuming project, run the CLI with `tsx /path/to/claude-toolkit/cli/index.ts <command>`, or link it as `toolkit` on your `PATH`.
33
+ From inside a consuming project, run the CLI with `tsx /path/to/claude-toolkit/src/index.ts <command>`, or link it as `toolkit` on your `PATH`.
37
34
 
38
35
  ## Commands
39
36
 
@@ -49,17 +46,17 @@ Copies `skills/<name>/` into `<project>/.claude-toolkit/skills/<name>/` and crea
49
46
  toolkit add skill css-shared-first --link .claude/skills --link docs/skills
50
47
  ```
51
48
 
52
- ### `toolkit add command <name>`
49
+ ### `toolkit add agent <name> [--link <target>]...`
53
50
 
54
- Copies `commands/<name>.md` into `<project>/.claude/commands/<name>.md`. Records the source hash in `.claude/toolkit-manifest.json`.
51
+ Copies `agents/<name>.md` into `<project>/.claude-toolkit/agents/<name>.md` and creates a symlink to that file inside each `--link` target. If no `--link` is given, the default is `.claude/agents`. Repeat `--link` to create symlinks in multiple locations.
55
52
 
56
53
  ```
57
- toolkit add command rpm-start
54
+ toolkit add agent technical-devils-advocate
58
55
  ```
59
56
 
60
57
  ### `toolkit add collections <name>`
61
58
 
62
- Reads the root `config.json`, resolves the named collection, and installs each referenced hook, skill, and command using the same underlying logic as the individual `add` commands.
59
+ Reads the root `config.json`, resolves the named collection, and installs each referenced hook, skill, or agent using the same underlying logic as the individual `add` commands.
63
60
 
64
61
  ```bash
65
62
  toolkit add collections web
@@ -73,9 +70,9 @@ For every entry in `.claude/toolkit-manifest.json`, compares the current source
73
70
  - If the installed file was modified locally (its hash differs from the one recorded in the manifest), warns and skips unless `--force` is passed.
74
71
  - Silent if everything is current.
75
72
 
76
- ### `toolkit list hook` / `toolkit list skill` / `toolkit list command`
73
+ ### `toolkit list hook` / `toolkit list skill` / `toolkit list agent`
77
74
 
78
- Lists available hooks, skills, or commands shipped by this repo, with the current source hash.
75
+ Lists available hooks, skills, or agents shipped by this repo, with the current source hash.
79
76
 
80
77
  ### `toolkit list collections`
81
78
 
@@ -102,6 +99,10 @@ Collections are defined in the repo root `config.json` as an array:
102
99
  {
103
100
  "type": "skill",
104
101
  "src": "skills/semantic-html"
102
+ },
103
+ {
104
+ "type": "agent",
105
+ "src": "agents/technical-devils-advocate.md"
105
106
  }
106
107
  ]
107
108
  }
@@ -109,15 +110,15 @@ Collections are defined in the repo root `config.json` as an array:
109
110
  ```
110
111
 
111
112
  - `name` must be unique.
112
- - `items` may contain `skill`, `hook`, or `command` entries.
113
- - `src` must point to a top-level entry under `skills/`, `hooks/`, or `commands/`.
114
- - Plural `type` values such as `commands` are also accepted for compatibility.
113
+ - `items` may contain `skill`, `hook`, or `agent` entries.
114
+ - `src` must point to a top-level entry under `skills/`, `hooks/`, or `agents/`.
115
+ - Plural `type` values such as `skills` are also accepted for compatibility.
115
116
 
116
117
  ## Versioning
117
118
 
118
- - Each command is hashed over its `.md` file only.
119
119
  - Each hook is hashed over `hook.mjs` only (not the README or `settings-fragment.json`).
120
120
  - Each skill is hashed over every file in the skill directory (sorted by path).
121
+ - Each agent is hashed over its Markdown source file.
121
122
  - SHA-256, truncated to the first 7 hex characters.
122
123
 
123
124
  ## Manifest format
@@ -126,12 +127,6 @@ The CLI writes `<project>/.claude/toolkit-manifest.json`:
126
127
 
127
128
  ```json
128
129
  {
129
- "commands": {
130
- "rpm-start": {
131
- "hash": "b8e2a1f",
132
- "installedAt": "2026-04-18"
133
- }
134
- },
135
130
  "hooks": {
136
131
  "block-dangerous-commands": {
137
132
  "hash": "a3f9c2d",
@@ -144,10 +139,23 @@ The CLI writes `<project>/.claude/toolkit-manifest.json`:
144
139
  "installedAt": "2026-04-18",
145
140
  "linkedTo": [".claude/skills"]
146
141
  }
142
+ },
143
+ "agents": {
144
+ "technical-devils-advocate": {
145
+ "hash": "c110f5e",
146
+ "installedAt": "2026-04-18",
147
+ "linkedTo": [".claude/agents"]
148
+ }
147
149
  }
148
150
  }
149
151
  ```
150
152
 
153
+ ## Bundled agents
154
+
155
+ ### `technical-devils-advocate`
156
+
157
+ A Claude Code project subagent for challenging feature plans, system designs, and implementation approaches before work begins. It surfaces assumptions, risks, edge cases, alternatives, dependencies, scale concerns, and operability questions, then recommends whether to proceed, pivot, or pause.
158
+
151
159
  ## Bundled hooks
152
160
 
153
161
  ### `block-dangerous-commands`
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: technical-devils-advocate
3
+ model: claude-4.6-opus-high-thinking
4
+ description: Technical devil's advocate that challenges feature plans, strategies, and implementation approaches. Use proactively when planning features, designing systems, or proposing solutions. ALWAYS use for plan mode - asks probing questions to uncover risks, edge cases, and alternative approaches before implementation begins.
5
+ ---
6
+
7
+ You are a technical devil's advocate. Your job is to challenge assumptions and stress-test plans before any implementation.
8
+
9
+ When invoked:
10
+
11
+ 1. Receive the feature plan, system design, or proposed solution
12
+ 2. Identify assumptions and unstated constraints
13
+ 3. Ask probing questions that expose weaknesses
14
+ 4. Surface risks, edge cases, and failure modes
15
+ 5. Suggest alternative approaches and tradeoffs
16
+
17
+ Challenge areas:
18
+
19
+ - **Risks**: What could fail? Failure modes? Blast radius?
20
+ - **Edge cases**: Boundary conditions, empty states, error paths, race conditions
21
+ - **Alternatives**: Why this approach vs others? What did we reject and why?
22
+ - **Dependencies**: What breaks if X changes? External assumptions?
23
+ - **Scale**: How does this behave at 10x, 100x? Latency, memory, throughput
24
+ - **Operability**: Monitoring, debugging, rollback, incident response
25
+
26
+ Output format:
27
+
28
+ - Prioritized list of concerns (critical → minor)
29
+ - Specific probing questions the team should answer
30
+ - Alternative approaches worth considering
31
+ - Recommendations: proceed, pivot, or pause
32
+
33
+ Be constructive: challenge to improve the plan, not to block it. Every question should help the team make a better decision.
package/config.json CHANGED
@@ -23,5 +23,22 @@
23
23
  "src": "skills/frontend-security"
24
24
  }
25
25
  ]
26
+ },
27
+ {
28
+ "name": "code-review",
29
+ "items": [
30
+ {
31
+ "type": "skill",
32
+ "src": "skills/code-review"
33
+ },
34
+ {
35
+ "type": "skill",
36
+ "src": "skills/semantic-html"
37
+ },
38
+ {
39
+ "type": "skill",
40
+ "src": "skills/css-coder"
41
+ }
42
+ ]
26
43
  }
27
44
  ]
package/dist/index.mjs CHANGED
@@ -7,23 +7,20 @@ import { fileURLToPath } from "node:url";
7
7
  import { parseArgs } from "node:util";
8
8
  //#region src/index.ts
9
9
  /**
10
- * toolkit — personal CLI for managing Claude Code hooks, skills, and commands.
10
+ * toolkit — personal CLI for managing Claude Code hooks and skills.
11
11
  *
12
12
  * Commands:
13
13
  * toolkit add hook <name>
14
14
  * toolkit add skill <name> [--link <target>...]
15
- * toolkit add command <name>
16
15
  * toolkit add collections <name>
17
16
  * toolkit update [--force]
18
17
  * toolkit list hook
19
18
  * toolkit list skill
20
- * toolkit list command
21
19
  * toolkit list collections
22
20
  */
23
21
  const TOOLKIT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
24
22
  const HOOKS_SRC = join(TOOLKIT_ROOT, "hooks");
25
23
  const SKILLS_SRC = join(TOOLKIT_ROOT, "skills");
26
- const COMMANDS_SRC = join(TOOLKIT_ROOT, "commands");
27
24
  const CONFIG_PATH = join(TOOLKIT_ROOT, "config.json");
28
25
  const PROJECT_ROOT = process.cwd();
29
26
  const CLAUDE_DIR = join(PROJECT_ROOT, ".claude");
@@ -37,20 +34,17 @@ function shortHash(content) {
37
34
  }
38
35
  function readManifest() {
39
36
  if (!existsSync(MANIFEST_PATH)) return {
40
- commands: {},
41
37
  hooks: {},
42
38
  skills: {}
43
39
  };
44
40
  try {
45
41
  const parsed = JSON.parse(readFileSync(MANIFEST_PATH, "utf8"));
46
42
  return {
47
- commands: parsed.commands ?? {},
48
43
  hooks: parsed.hooks ?? {},
49
44
  skills: parsed.skills ?? {}
50
45
  };
51
46
  } catch {
52
47
  return {
53
- commands: {},
54
48
  hooks: {},
55
49
  skills: {}
56
50
  };
@@ -72,9 +66,6 @@ function deepMerge(target, source) {
72
66
  }
73
67
  return source;
74
68
  }
75
- function hashCommandSource(name) {
76
- return shortHash(readFileSync(join(COMMANDS_SRC, `${name}.md`)));
77
- }
78
69
  function hashHookSource(name) {
79
70
  return shortHash(readFileSync(join(HOOKS_SRC, name, "hook.mjs")));
80
71
  }
@@ -131,7 +122,6 @@ function sanitizeName(name, kind) {
131
122
  return name;
132
123
  }
133
124
  function normalizeCollectionItemType(type, collectionName) {
134
- if (type === "command" || type === "commands") return "command";
135
125
  if (type === "hook" || type === "hooks") return "hook";
136
126
  if (type === "skill" || type === "skills") return "skill";
137
127
  throw new Error(`Collection "${collectionName}" has unsupported item type "${type}"`);
@@ -142,10 +132,6 @@ function resolveSourcePath(src, kind, collectionName) {
142
132
  return sourcePath;
143
133
  }
144
134
  function inferItemNameFromSource(type, sourcePath, collectionName) {
145
- if (type === "command") {
146
- if (dirname(sourcePath) !== COMMANDS_SRC || !sourcePath.startsWith(COMMANDS_SRC + sep) || !sourcePath.endsWith(".md")) throw new Error(`Collection "${collectionName}" command source must point to a markdown file directly under commands/: ${relative(TOOLKIT_ROOT, sourcePath)}`);
147
- return basename(sourcePath, ".md");
148
- }
149
135
  const expectedRoot = type === "hook" ? HOOKS_SRC : SKILLS_SRC;
150
136
  if (dirname(sourcePath) !== expectedRoot || !sourcePath.startsWith(expectedRoot + sep)) throw new Error(`Collection "${collectionName}" ${type} source must point to a top-level entry under ${relative(TOOLKIT_ROOT, expectedRoot)}/: ${relative(TOOLKIT_ROOT, sourcePath)}`);
151
137
  return basename(sourcePath);
@@ -200,31 +186,6 @@ function resolveCollection(name) {
200
186
  }
201
187
  return [...deduped.values()];
202
188
  }
203
- function installCommand(name, src) {
204
- if (!existsSync(src)) {
205
- console.error(`Command not found: ${name}`);
206
- process.exit(1);
207
- }
208
- const commandsDir = join(CLAUDE_DIR, "commands");
209
- mkdirSync(commandsDir, { recursive: true });
210
- const dest = resolve(commandsDir, `${name}.md`);
211
- if (!dest.startsWith(commandsDir + sep)) {
212
- console.error("Invalid command name");
213
- process.exit(1);
214
- }
215
- writeFileSync(dest, readFileSync(src));
216
- const manifest = readManifest();
217
- manifest.commands[name] = {
218
- hash: hashCommandSource(name),
219
- installedAt: today()
220
- };
221
- writeManifest(manifest);
222
- console.log(`Installed command: ${name} → ${relative(PROJECT_ROOT, dest)}`);
223
- }
224
- function addCommand(name) {
225
- name = sanitizeName(name, "command");
226
- installCommand(name, join(COMMANDS_SRC, `${name}.md`));
227
- }
228
189
  function installHook(name, srcDir) {
229
190
  if (!existsSync(srcDir)) {
230
191
  console.error(`Hook not found: ${name}`);
@@ -298,11 +259,6 @@ function addCollection(name) {
298
259
  if (!existsSync(item.sourcePath)) throw new Error(`Collection "${item.collection}" references missing ${item.type} source: ${relative(TOOLKIT_ROOT, item.sourcePath)}`);
299
260
  const itemStats = statSync(item.sourcePath);
300
261
  const actualKind = itemStats.isFile() ? "file" : itemStats.isDirectory() ? "directory" : "other";
301
- if (item.type === "command") {
302
- if (!itemStats.isFile()) throw new Error(`Collection "${item.collection}" expected command source "${item.sourcePath}" to be a file, found ${actualKind}`);
303
- installCommand(item.sourceName, item.sourcePath);
304
- continue;
305
- }
306
262
  if (item.type === "hook") {
307
263
  if (!itemStats.isDirectory()) throw new Error(`Collection "${item.collection}" expected hook source "${item.sourcePath}" to be a directory, found ${actualKind}`);
308
264
  installHook(item.sourceName, item.sourcePath);
@@ -368,36 +324,9 @@ async function update(force) {
368
324
  linkedTo: entry.linkedTo
369
325
  };
370
326
  }
371
- for (const [name, entry] of Object.entries(manifest.commands)) {
372
- const src = join(COMMANDS_SRC, `${name}.md`);
373
- if (!existsSync(src)) continue;
374
- const sourceHash = hashCommandSource(name);
375
- if (sourceHash === entry.hash) continue;
376
- changed = true;
377
- console.log(`\n~ command: ${name} (${entry.hash} → ${sourceHash})`);
378
- if (!(force || await confirm(`Update command "${name}"?`))) continue;
379
- writeFileSync(join(CLAUDE_DIR, "commands", `${name}.md`), readFileSync(src));
380
- manifest.commands[name] = {
381
- hash: sourceHash,
382
- installedAt: today()
383
- };
384
- }
385
327
  if (changed) writeManifest(manifest);
386
328
  }
387
329
  function list(kind) {
388
- if (kind === "command") {
389
- if (!existsSync(COMMANDS_SRC)) {
390
- console.log("(no commands available)");
391
- return;
392
- }
393
- const files = readdirSync(COMMANDS_SRC).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
394
- if (files.length === 0) {
395
- console.log("(no commands available)");
396
- return;
397
- }
398
- for (const name of files) console.log(`${name} ${hashCommandSource(name)}`);
399
- return;
400
- }
401
330
  const dir = kind === "hook" ? HOOKS_SRC : SKILLS_SRC;
402
331
  if (!existsSync(dir)) {
403
332
  console.log(`(no ${kind}s available)`);
@@ -425,12 +354,10 @@ function usage() {
425
354
  console.error(`Usage:
426
355
  toolkit add hook <name>
427
356
  toolkit add skill <name> [--link <target>]...
428
- toolkit add command <name>
429
357
  toolkit add collections <name>
430
358
  toolkit update [--force]
431
359
  toolkit list hook
432
360
  toolkit list skill
433
- toolkit list command
434
361
  toolkit list collections`);
435
362
  process.exit(1);
436
363
  }
@@ -460,11 +387,6 @@ async function main() {
460
387
  addSkill(name, links ? links : []);
461
388
  return;
462
389
  }
463
- if (command === "add" && resource === "command") {
464
- if (!name) usage();
465
- addCommand(name);
466
- return;
467
- }
468
390
  if (command === "add" && (resource === "collection" || resource === "collections")) {
469
391
  if (!name) usage();
470
392
  addCollection(name);
@@ -474,7 +396,7 @@ async function main() {
474
396
  await update(force);
475
397
  return;
476
398
  }
477
- if (command === "list" && (resource === "hook" || resource === "skill" || resource === "command")) {
399
+ if (command === "list" && (resource === "hook" || resource === "skill")) {
478
400
  list(resource);
479
401
  return;
480
402
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * toolkit — personal CLI for managing Claude Code hooks, skills, and commands.\n *\n * Commands:\n * toolkit add hook <name>\n * toolkit add skill <name> [--link <target>...]\n * toolkit add command <name>\n * toolkit add collections <name>\n * toolkit update [--force]\n * toolkit list hook\n * toolkit list skill\n * toolkit list command\n * toolkit list collections\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n cpSync,\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n symlinkSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { createInterface } from \"node:readline/promises\";\nimport { basename, dirname, join, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseArgs } from \"node:util\";\n\nconst TOOLKIT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst HOOKS_SRC = join(TOOLKIT_ROOT, \"hooks\");\nconst SKILLS_SRC = join(TOOLKIT_ROOT, \"skills\");\nconst COMMANDS_SRC = join(TOOLKIT_ROOT, \"commands\");\nconst CONFIG_PATH = join(TOOLKIT_ROOT, \"config.json\");\n\nconst PROJECT_ROOT = process.cwd();\nconst CLAUDE_DIR = join(PROJECT_ROOT, \".claude\");\nconst TOOLKIT_DIR = join(PROJECT_ROOT, \".claude-toolkit\");\nconst MANIFEST_PATH = join(CLAUDE_DIR, \"toolkit-manifest.json\");\n\ntype HookEntry = { hash: string; installedAt: string };\ntype SkillEntry = { hash: string; installedAt: string; linkedTo: string[] };\ntype CommandEntry = { hash: string; installedAt: string };\ntype Manifest = {\n commands: Record<string, CommandEntry>;\n hooks: Record<string, HookEntry>;\n skills: Record<string, SkillEntry>;\n};\ntype CollectionItemKind = \"command\" | \"hook\" | \"skill\";\ntype CollectionItemConfig = {\n type: CollectionItemKind | `${CollectionItemKind}s`;\n src: string;\n};\ntype CollectionConfig = {\n name: string;\n items: CollectionItemConfig[];\n};\ntype ResolvedCollectionItem = {\n collection: string;\n sourcePath: string;\n sourceName: string;\n type: CollectionItemKind;\n};\n\n// ---------- helpers ----------\n\nfunction today(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nfunction shortHash(content: string | Buffer): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 7);\n}\n\nfunction readManifest(): Manifest {\n if (!existsSync(MANIFEST_PATH)) {\n return { commands: {}, hooks: {}, skills: {} };\n }\n\n try {\n const parsed = JSON.parse(readFileSync(MANIFEST_PATH, \"utf8\")) as Partial<Manifest>;\n return {\n commands: parsed.commands ?? {},\n hooks: parsed.hooks ?? {},\n skills: parsed.skills ?? {},\n };\n } catch {\n return { commands: {}, hooks: {}, skills: {} };\n }\n}\n\nfunction writeManifest(m: Manifest): void {\n mkdirSync(CLAUDE_DIR, { recursive: true });\n writeFileSync(MANIFEST_PATH, JSON.stringify(m, null, 2) + \"\\n\");\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction deepMerge<T>(target: T, source: T): T {\n if (Array.isArray(target) && Array.isArray(source)) {\n return [...target, ...source] as T;\n }\n if (isPlainObject(target) && isPlainObject(source)) {\n const out: Record<string, unknown> = { ...target };\n for (const [k, v] of Object.entries(source)) {\n out[k] = k in out ? deepMerge(out[k], v) : v;\n }\n return out as T;\n }\n return source;\n}\n\nfunction hashCommandSource(name: string): string {\n const p = join(COMMANDS_SRC, `${name}.md`);\n return shortHash(readFileSync(p));\n}\n\nfunction hashHookSource(name: string): string {\n const p = join(HOOKS_SRC, name, \"hook.mjs\");\n return shortHash(readFileSync(p));\n}\n\nfunction hashSkillSource(name: string): string {\n const dir = join(SKILLS_SRC, name);\n const files = collectFiles(dir).sort();\n const h = createHash(\"sha256\");\n for (const f of files) {\n h.update(relative(dir, f));\n h.update(\"\\0\");\n h.update(readFileSync(f));\n h.update(\"\\0\");\n }\n return h.digest(\"hex\").slice(0, 7);\n}\n\nfunction collectFiles(dir: string): string[] {\n const out: string[] = [];\n if (!existsSync(dir)) {\n return out;\n }\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === \".gitkeep\") {\n continue;\n }\n\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(...collectFiles(full));\n } else if (entry.isFile()) {\n out.push(full);\n }\n }\n return out;\n}\n\nasync function confirm(question: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();\n rl.close();\n return answer === \"y\" || answer === \"yes\";\n}\n\nfunction diffLines(oldStr: string, newStr: string): string {\n const a = oldStr.split(\"\\n\");\n const b = newStr.split(\"\\n\");\n const out: string[] = [];\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n if (a[i] === b[i]) {\n continue;\n }\n\n if (a[i] !== undefined) {\n out.push(`- ${a[i]}`);\n }\n\n if (b[i] !== undefined) {\n out.push(`+ ${b[i]}`);\n }\n }\n return out.join(\"\\n\");\n}\n\n// ---------- commands ----------\n\nfunction sanitizeName(name: string, kind: string): string {\n name = basename(name);\n if (!name) {\n console.error(`Invalid ${kind} name`);\n process.exit(1);\n }\n return name;\n}\n\nfunction normalizeCollectionItemType(\n type: CollectionItemConfig[\"type\"],\n collectionName: string,\n): CollectionItemKind {\n if (type === \"command\" || type === \"commands\") {\n return \"command\";\n }\n if (type === \"hook\" || type === \"hooks\") {\n return \"hook\";\n }\n if (type === \"skill\" || type === \"skills\") {\n return \"skill\";\n }\n\n throw new Error(`Collection \"${collectionName}\" has unsupported item type \"${type}\"`);\n}\n\nfunction resolveSourcePath(src: string, kind: string, collectionName: string): string {\n const sourcePath = resolve(TOOLKIT_ROOT, src);\n if (!sourcePath.startsWith(TOOLKIT_ROOT + sep)) {\n throw new Error(\n `Collection \"${collectionName}\" ${kind} source must stay within the toolkit root: ${src}`,\n );\n }\n return sourcePath;\n}\n\nfunction inferItemNameFromSource(\n type: CollectionItemKind,\n sourcePath: string,\n collectionName: string,\n): string {\n if (type === \"command\") {\n if (\n dirname(sourcePath) !== COMMANDS_SRC ||\n !sourcePath.startsWith(COMMANDS_SRC + sep) ||\n !sourcePath.endsWith(\".md\")\n ) {\n throw new Error(\n `Collection \"${collectionName}\" command source must point to a markdown file directly under commands/: ${relative(TOOLKIT_ROOT, sourcePath)}`,\n );\n }\n return basename(sourcePath, \".md\");\n }\n\n const expectedRoot = type === \"hook\" ? HOOKS_SRC : SKILLS_SRC;\n if (dirname(sourcePath) !== expectedRoot || !sourcePath.startsWith(expectedRoot + sep)) {\n throw new Error(\n `Collection \"${collectionName}\" ${type} source must point to a top-level entry under ${relative(TOOLKIT_ROOT, expectedRoot)}/: ${relative(TOOLKIT_ROOT, sourcePath)}`,\n );\n }\n\n return basename(sourcePath);\n}\n\nfunction readCollectionsConfig(): CollectionConfig[] {\n if (!existsSync(CONFIG_PATH)) {\n throw new Error(`Collections config not found: ${relative(TOOLKIT_ROOT, CONFIG_PATH)}`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(CONFIG_PATH, \"utf8\"));\n } catch (error) {\n throw new Error(\n `Invalid collections config in ${relative(TOOLKIT_ROOT, CONFIG_PATH)}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n if (!Array.isArray(parsed)) {\n throw new Error(\"Collections config must be an array\");\n }\n\n const names = new Set<string>();\n return parsed.map((entry, index) => {\n if (!isPlainObject(entry)) {\n throw new Error(`Collection at index ${index} must be an object`);\n }\n\n const { name, items } = entry;\n if (typeof name !== \"string\" || name.trim().length === 0) {\n throw new Error(`Collection at index ${index} must have a non-empty name`);\n }\n if (names.has(name)) {\n throw new Error(`Duplicate collection name: ${name}`);\n }\n names.add(name);\n\n if (!Array.isArray(items)) {\n throw new Error(`Collection \"${name}\" must have an items array`);\n }\n\n const validatedItems = items.map((item, itemIndex) => {\n if (!isPlainObject(item)) {\n throw new Error(`Collection \"${name}\" item at index ${itemIndex} must be an object`);\n }\n if (typeof item.type !== \"string\" || item.type.trim().length === 0) {\n throw new Error(\n `Collection \"${name}\" item at index ${itemIndex} must have a non-empty type`,\n );\n }\n if (typeof item.src !== \"string\" || item.src.trim().length === 0) {\n throw new Error(\n `Collection \"${name}\" item at index ${itemIndex} must have a non-empty src`,\n );\n }\n\n return {\n type: item.type as CollectionItemConfig[\"type\"],\n src: item.src,\n };\n });\n\n return {\n name,\n items: validatedItems,\n };\n });\n}\n\nfunction resolveCollection(name: string): ResolvedCollectionItem[] {\n const collectionName = sanitizeName(name, \"collection\");\n const collections = readCollectionsConfig();\n const collection = collections.find((entry) => entry.name === collectionName);\n\n if (!collection) {\n throw new Error(`Collection not found: ${collectionName}`);\n }\n\n const deduped = new Map<string, ResolvedCollectionItem>();\n\n for (const item of collection.items) {\n const type = normalizeCollectionItemType(item.type, collection.name);\n const sourcePath = resolveSourcePath(item.src, type, collection.name);\n const sourceName = inferItemNameFromSource(type, sourcePath, collection.name);\n const key = `${type}:${sourceName}`;\n\n if (!deduped.has(key)) {\n deduped.set(key, {\n collection: collection.name,\n sourcePath,\n sourceName,\n type,\n });\n }\n }\n\n return [...deduped.values()];\n}\n\nfunction installCommand(name: string, src: string): void {\n if (!existsSync(src)) {\n console.error(`Command not found: ${name}`);\n process.exit(1);\n }\n\n const commandsDir = join(CLAUDE_DIR, \"commands\");\n mkdirSync(commandsDir, { recursive: true });\n const dest = resolve(commandsDir, `${name}.md`);\n if (!dest.startsWith(commandsDir + sep)) {\n console.error(\"Invalid command name\");\n process.exit(1);\n }\n writeFileSync(dest, readFileSync(src));\n\n const manifest = readManifest();\n manifest.commands[name] = {\n hash: hashCommandSource(name),\n installedAt: today(),\n };\n writeManifest(manifest);\n\n console.log(`Installed command: ${name} → ${relative(PROJECT_ROOT, dest)}`);\n}\n\nfunction addCommand(name: string): void {\n name = sanitizeName(name, \"command\");\n installCommand(name, join(COMMANDS_SRC, `${name}.md`));\n}\n\nfunction installHook(name: string, srcDir: string): void {\n if (!existsSync(srcDir)) {\n console.error(`Hook not found: ${name}`);\n process.exit(1);\n }\n\n const hookSrc = join(srcDir, \"hook.mjs\");\n const fragmentPath = join(srcDir, \"settings-fragment.json\");\n\n const hooksDir = join(CLAUDE_DIR, \"hooks\");\n mkdirSync(hooksDir, { recursive: true });\n const destHook = resolve(hooksDir, `${name}.mjs`);\n if (!destHook.startsWith(hooksDir + sep)) {\n console.error(\"Invalid hook name\");\n process.exit(1);\n }\n writeFileSync(destHook, readFileSync(hookSrc));\n\n if (existsSync(fragmentPath)) {\n const fragment = JSON.parse(readFileSync(fragmentPath, \"utf8\"));\n const settingsPath = join(CLAUDE_DIR, \"settings.json\");\n const current = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, \"utf8\")) : {};\n const merged = deepMerge(current, fragment);\n writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + \"\\n\");\n }\n\n const manifest = readManifest();\n manifest.hooks[name] = { hash: hashHookSource(name), installedAt: today() };\n writeManifest(manifest);\n\n console.log(`Installed hook: ${name} → ${relative(PROJECT_ROOT, destHook)}`);\n}\n\nfunction addHook(name: string): void {\n name = sanitizeName(name, \"hook\");\n installHook(name, join(HOOKS_SRC, name));\n}\n\nfunction installSkill(name: string, srcDir: string, links: string[]): void {\n if (!existsSync(srcDir) || !statSync(srcDir).isDirectory()) {\n console.error(`Skill not found: ${name}`);\n process.exit(1);\n }\n\n const destDir = resolve(TOOLKIT_DIR, \"skills\", name);\n if (!destDir.startsWith(join(TOOLKIT_DIR, \"skills\") + sep)) {\n console.error(\"Invalid skill name\");\n process.exit(1);\n }\n mkdirSync(dirname(destDir), { recursive: true });\n cpSync(srcDir, destDir, { recursive: true });\n\n const resolvedLinks = links.length > 0 ? links : [join(\".claude\", \"skills\")];\n for (const link of resolvedLinks) {\n const linkDir = resolve(PROJECT_ROOT, link);\n mkdirSync(linkDir, { recursive: true });\n\n const linkPath = join(linkDir, name);\n if (existsSync(linkPath) || lstatExists(linkPath)) {\n unlinkSync(linkPath);\n }\n\n const relTarget = relative(linkDir, destDir);\n symlinkSync(relTarget, linkPath, \"dir\");\n }\n\n const manifest = readManifest();\n manifest.skills[name] = {\n hash: hashSkillSource(name),\n installedAt: today(),\n linkedTo: resolvedLinks,\n };\n writeManifest(manifest);\n\n console.log(`Installed skill: ${name} → ${relative(PROJECT_ROOT, destDir)}`);\n for (const l of resolvedLinks) {\n console.log(` linked: ${join(l, name)}`);\n }\n}\n\nfunction addSkill(name: string, links: string[]): void {\n name = sanitizeName(name, \"skill\");\n installSkill(name, join(SKILLS_SRC, name), links);\n}\n\nfunction addCollection(name: string): void {\n const items = resolveCollection(name);\n for (const item of items) {\n if (!existsSync(item.sourcePath)) {\n throw new Error(\n `Collection \"${item.collection}\" references missing ${item.type} source: ${relative(TOOLKIT_ROOT, item.sourcePath)}`,\n );\n }\n\n const itemStats = statSync(item.sourcePath);\n const actualKind = itemStats.isFile()\n ? \"file\"\n : itemStats.isDirectory()\n ? \"directory\"\n : \"other\";\n\n if (item.type === \"command\") {\n if (!itemStats.isFile()) {\n throw new Error(\n `Collection \"${item.collection}\" expected command source \"${item.sourcePath}\" to be a file, found ${actualKind}`,\n );\n }\n installCommand(item.sourceName, item.sourcePath);\n continue;\n }\n\n if (item.type === \"hook\") {\n if (!itemStats.isDirectory()) {\n throw new Error(\n `Collection \"${item.collection}\" expected hook source \"${item.sourcePath}\" to be a directory, found ${actualKind}`,\n );\n }\n installHook(item.sourceName, item.sourcePath);\n continue;\n }\n\n if (!itemStats.isDirectory()) {\n throw new Error(\n `Collection \"${item.collection}\" expected skill source \"${item.sourcePath}\" to be a directory, found ${actualKind}`,\n );\n }\n installSkill(item.sourceName, item.sourcePath, []);\n }\n}\n\nfunction lstatExists(p: string): boolean {\n try {\n lstatSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function update(force: boolean): Promise<void> {\n const manifest = readManifest();\n let changed = false;\n\n for (const [name, entry] of Object.entries(manifest.hooks)) {\n const srcDir = join(HOOKS_SRC, name);\n if (!existsSync(srcDir)) {\n continue;\n }\n\n const sourceHash = hashHookSource(name);\n const installedPath = join(CLAUDE_DIR, \"hooks\", `${name}.mjs`);\n const installedHash = existsSync(installedPath) ? shortHash(readFileSync(installedPath)) : null;\n\n const sourceChanged = sourceHash !== entry.hash;\n const locallyModified = installedHash !== null && installedHash !== entry.hash;\n\n if (!sourceChanged && !locallyModified) {\n continue;\n }\n\n changed = true;\n\n if (locallyModified && !force) {\n console.warn(\n `! hook \"${name}\" was modified locally (installed=${installedHash}, manifest=${entry.hash}). Use --force to overwrite.`,\n );\n continue;\n }\n\n if (sourceChanged) {\n const oldSrc = existsSync(installedPath) ? readFileSync(installedPath, \"utf8\") : \"\";\n const newSrc = readFileSync(join(srcDir, \"hook.mjs\"), \"utf8\");\n console.log(`\\n~ hook: ${name} (${entry.hash} → ${sourceHash})`);\n console.log(diffLines(oldSrc, newSrc));\n const ok = force || (await confirm(`Update hook \"${name}\"?`));\n\n if (!ok) {\n continue;\n }\n\n writeFileSync(installedPath, newSrc);\n manifest.hooks[name] = { hash: sourceHash, installedAt: today() };\n }\n }\n\n for (const [name, entry] of Object.entries(manifest.skills)) {\n const srcDir = join(SKILLS_SRC, name);\n if (!existsSync(srcDir)) {\n continue;\n }\n\n const sourceHash = hashSkillSource(name);\n if (sourceHash === entry.hash) {\n continue;\n }\n\n changed = true;\n console.log(`\\n~ skill: ${name} (${entry.hash} → ${sourceHash})`);\n const ok = force || (await confirm(`Update skill \"${name}\"?`));\n if (!ok) {\n continue;\n }\n\n const destDir = join(TOOLKIT_DIR, \"skills\", name);\n cpSync(srcDir, destDir, { recursive: true, force: true });\n manifest.skills[name] = {\n hash: sourceHash,\n installedAt: today(),\n linkedTo: entry.linkedTo,\n };\n }\n\n for (const [name, entry] of Object.entries(manifest.commands)) {\n const src = join(COMMANDS_SRC, `${name}.md`);\n if (!existsSync(src)) {\n continue;\n }\n\n const sourceHash = hashCommandSource(name);\n if (sourceHash === entry.hash) {\n continue;\n }\n\n changed = true;\n console.log(`\\n~ command: ${name} (${entry.hash} → ${sourceHash})`);\n const ok = force || (await confirm(`Update command \"${name}\"?`));\n if (!ok) {\n continue;\n }\n\n const dest = join(CLAUDE_DIR, \"commands\", `${name}.md`);\n writeFileSync(dest, readFileSync(src));\n manifest.commands[name] = { hash: sourceHash, installedAt: today() };\n }\n\n if (changed) {\n writeManifest(manifest);\n }\n}\n\nfunction list(kind: \"hook\" | \"skill\" | \"command\"): void {\n if (kind === \"command\") {\n if (!existsSync(COMMANDS_SRC)) {\n console.log(\"(no commands available)\");\n return;\n }\n const files = readdirSync(COMMANDS_SRC)\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => f.replace(/\\.md$/, \"\"));\n if (files.length === 0) {\n console.log(\"(no commands available)\");\n return;\n }\n for (const name of files) {\n console.log(`${name} ${hashCommandSource(name)}`);\n }\n return;\n }\n\n const dir = kind === \"hook\" ? HOOKS_SRC : SKILLS_SRC;\n if (!existsSync(dir)) {\n console.log(`(no ${kind}s available)`);\n return;\n }\n const entries = readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory() || (kind === \"skill\" && e.isSymbolicLink()))\n .map((e) => e.name);\n\n if (entries.length === 0) {\n console.log(`(no ${kind}s available)`);\n return;\n }\n\n for (const name of entries) {\n const hash = kind === \"hook\" ? hashHookSource(name) : hashSkillSource(name);\n console.log(`${name} ${hash}`);\n }\n}\n\nfunction listCollections(): void {\n const collections = readCollectionsConfig();\n if (collections.length === 0) {\n console.log(\"(no collections available)\");\n return;\n }\n\n for (const collection of collections) {\n console.log(`${collection.name} ${collection.items.length} item(s)`);\n }\n}\n\n// ---------- argv ----------\n\nfunction usage(): never {\n console.error(\n `Usage:\n toolkit add hook <name>\n toolkit add skill <name> [--link <target>]...\n toolkit add command <name>\n toolkit add collections <name>\n toolkit update [--force]\n toolkit list hook\n toolkit list skill\n toolkit list command\n toolkit list collections`,\n );\n process.exit(1);\n}\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n options: {\n force: {\n default: false,\n type: \"boolean\",\n },\n links: {\n multiple: true,\n type: \"string\",\n },\n },\n allowPositionals: true,\n });\n\n const { force, links } = values;\n const [command, resource, name] = positionals;\n\n if (command === \"add\" && resource === \"hook\") {\n if (!name) {\n usage();\n }\n\n addHook(name);\n return;\n }\n\n if (command === \"add\" && resource === \"skill\") {\n if (!name) {\n usage();\n }\n\n addSkill(name, links ? links : []);\n return;\n }\n\n if (command === \"add\" && resource === \"command\") {\n if (!name) {\n usage();\n }\n\n addCommand(name);\n return;\n }\n\n if (command === \"add\" && (resource === \"collection\" || resource === \"collections\")) {\n if (!name) {\n usage();\n }\n\n addCollection(name);\n return;\n }\n\n if (command === \"update\") {\n await update(force);\n return;\n }\n\n if (\n command === \"list\" &&\n (resource === \"hook\" || resource === \"skill\" || resource === \"command\")\n ) {\n list(resource as \"hook\" | \"skill\" | \"command\");\n return;\n }\n\n if (command === \"list\" && (resource === \"collection\" || resource === \"collections\")) {\n listCollections();\n return;\n }\n\n usage();\n}\n\nmain().catch((err) => {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * toolkit — personal CLI for managing Claude Code hooks and skills.\n *\n * Commands:\n * toolkit add hook <name>\n * toolkit add skill <name> [--link <target>...]\n * toolkit add collections <name>\n * toolkit update [--force]\n * toolkit list hook\n * toolkit list skill\n * toolkit list collections\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n cpSync,\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n symlinkSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { createInterface } from \"node:readline/promises\";\nimport { basename, dirname, join, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseArgs } from \"node:util\";\n\nconst TOOLKIT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst HOOKS_SRC = join(TOOLKIT_ROOT, \"hooks\");\nconst SKILLS_SRC = join(TOOLKIT_ROOT, \"skills\");\nconst CONFIG_PATH = join(TOOLKIT_ROOT, \"config.json\");\n\nconst PROJECT_ROOT = process.cwd();\nconst CLAUDE_DIR = join(PROJECT_ROOT, \".claude\");\nconst TOOLKIT_DIR = join(PROJECT_ROOT, \".claude-toolkit\");\nconst MANIFEST_PATH = join(CLAUDE_DIR, \"toolkit-manifest.json\");\n\ntype HookEntry = { hash: string; installedAt: string };\ntype SkillEntry = { hash: string; installedAt: string; linkedTo: string[] };\ntype Manifest = {\n hooks: Record<string, HookEntry>;\n skills: Record<string, SkillEntry>;\n};\ntype CollectionItemKind = \"hook\" | \"skill\";\ntype CollectionItemConfig = {\n type: CollectionItemKind | `${CollectionItemKind}s`;\n src: string;\n};\ntype CollectionConfig = {\n name: string;\n items: CollectionItemConfig[];\n};\ntype ResolvedCollectionItem = {\n collection: string;\n sourcePath: string;\n sourceName: string;\n type: CollectionItemKind;\n};\n\n// ---------- helpers ----------\n\nfunction today(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nfunction shortHash(content: string | Buffer): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 7);\n}\n\nfunction readManifest(): Manifest {\n if (!existsSync(MANIFEST_PATH)) {\n return { hooks: {}, skills: {} };\n }\n\n try {\n const parsed = JSON.parse(readFileSync(MANIFEST_PATH, \"utf8\")) as Partial<Manifest>;\n return {\n hooks: parsed.hooks ?? {},\n skills: parsed.skills ?? {},\n };\n } catch {\n return { hooks: {}, skills: {} };\n }\n}\n\nfunction writeManifest(m: Manifest): void {\n mkdirSync(CLAUDE_DIR, { recursive: true });\n writeFileSync(MANIFEST_PATH, JSON.stringify(m, null, 2) + \"\\n\");\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction deepMerge<T>(target: T, source: T): T {\n if (Array.isArray(target) && Array.isArray(source)) {\n return [...target, ...source] as T;\n }\n if (isPlainObject(target) && isPlainObject(source)) {\n const out: Record<string, unknown> = { ...target };\n for (const [k, v] of Object.entries(source)) {\n out[k] = k in out ? deepMerge(out[k], v) : v;\n }\n return out as T;\n }\n return source;\n}\n\nfunction hashHookSource(name: string): string {\n const p = join(HOOKS_SRC, name, \"hook.mjs\");\n return shortHash(readFileSync(p));\n}\n\nfunction hashSkillSource(name: string): string {\n const dir = join(SKILLS_SRC, name);\n const files = collectFiles(dir).sort();\n const h = createHash(\"sha256\");\n for (const f of files) {\n h.update(relative(dir, f));\n h.update(\"\\0\");\n h.update(readFileSync(f));\n h.update(\"\\0\");\n }\n return h.digest(\"hex\").slice(0, 7);\n}\n\nfunction collectFiles(dir: string): string[] {\n const out: string[] = [];\n if (!existsSync(dir)) {\n return out;\n }\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === \".gitkeep\") {\n continue;\n }\n\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(...collectFiles(full));\n } else if (entry.isFile()) {\n out.push(full);\n }\n }\n return out;\n}\n\nasync function confirm(question: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();\n rl.close();\n return answer === \"y\" || answer === \"yes\";\n}\n\nfunction diffLines(oldStr: string, newStr: string): string {\n const a = oldStr.split(\"\\n\");\n const b = newStr.split(\"\\n\");\n const out: string[] = [];\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n if (a[i] === b[i]) {\n continue;\n }\n\n if (a[i] !== undefined) {\n out.push(`- ${a[i]}`);\n }\n\n if (b[i] !== undefined) {\n out.push(`+ ${b[i]}`);\n }\n }\n return out.join(\"\\n\");\n}\n\n// ---------- resources ----------\n\nfunction sanitizeName(name: string, kind: string): string {\n name = basename(name);\n if (!name) {\n console.error(`Invalid ${kind} name`);\n process.exit(1);\n }\n return name;\n}\n\nfunction normalizeCollectionItemType(\n type: CollectionItemConfig[\"type\"],\n collectionName: string,\n): CollectionItemKind {\n if (type === \"hook\" || type === \"hooks\") {\n return \"hook\";\n }\n if (type === \"skill\" || type === \"skills\") {\n return \"skill\";\n }\n\n throw new Error(`Collection \"${collectionName}\" has unsupported item type \"${type}\"`);\n}\n\nfunction resolveSourcePath(src: string, kind: string, collectionName: string): string {\n const sourcePath = resolve(TOOLKIT_ROOT, src);\n if (!sourcePath.startsWith(TOOLKIT_ROOT + sep)) {\n throw new Error(\n `Collection \"${collectionName}\" ${kind} source must stay within the toolkit root: ${src}`,\n );\n }\n return sourcePath;\n}\n\nfunction inferItemNameFromSource(\n type: CollectionItemKind,\n sourcePath: string,\n collectionName: string,\n): string {\n const expectedRoot = type === \"hook\" ? HOOKS_SRC : SKILLS_SRC;\n if (dirname(sourcePath) !== expectedRoot || !sourcePath.startsWith(expectedRoot + sep)) {\n throw new Error(\n `Collection \"${collectionName}\" ${type} source must point to a top-level entry under ${relative(TOOLKIT_ROOT, expectedRoot)}/: ${relative(TOOLKIT_ROOT, sourcePath)}`,\n );\n }\n\n return basename(sourcePath);\n}\n\nfunction readCollectionsConfig(): CollectionConfig[] {\n if (!existsSync(CONFIG_PATH)) {\n throw new Error(`Collections config not found: ${relative(TOOLKIT_ROOT, CONFIG_PATH)}`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(CONFIG_PATH, \"utf8\"));\n } catch (error) {\n throw new Error(\n `Invalid collections config in ${relative(TOOLKIT_ROOT, CONFIG_PATH)}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n if (!Array.isArray(parsed)) {\n throw new Error(\"Collections config must be an array\");\n }\n\n const names = new Set<string>();\n return parsed.map((entry, index) => {\n if (!isPlainObject(entry)) {\n throw new Error(`Collection at index ${index} must be an object`);\n }\n\n const { name, items } = entry;\n if (typeof name !== \"string\" || name.trim().length === 0) {\n throw new Error(`Collection at index ${index} must have a non-empty name`);\n }\n if (names.has(name)) {\n throw new Error(`Duplicate collection name: ${name}`);\n }\n names.add(name);\n\n if (!Array.isArray(items)) {\n throw new Error(`Collection \"${name}\" must have an items array`);\n }\n\n const validatedItems = items.map((item, itemIndex) => {\n if (!isPlainObject(item)) {\n throw new Error(`Collection \"${name}\" item at index ${itemIndex} must be an object`);\n }\n if (typeof item.type !== \"string\" || item.type.trim().length === 0) {\n throw new Error(\n `Collection \"${name}\" item at index ${itemIndex} must have a non-empty type`,\n );\n }\n if (typeof item.src !== \"string\" || item.src.trim().length === 0) {\n throw new Error(\n `Collection \"${name}\" item at index ${itemIndex} must have a non-empty src`,\n );\n }\n\n return {\n type: item.type as CollectionItemConfig[\"type\"],\n src: item.src,\n };\n });\n\n return {\n name,\n items: validatedItems,\n };\n });\n}\n\nfunction resolveCollection(name: string): ResolvedCollectionItem[] {\n const collectionName = sanitizeName(name, \"collection\");\n const collections = readCollectionsConfig();\n const collection = collections.find((entry) => entry.name === collectionName);\n\n if (!collection) {\n throw new Error(`Collection not found: ${collectionName}`);\n }\n\n const deduped = new Map<string, ResolvedCollectionItem>();\n\n for (const item of collection.items) {\n const type = normalizeCollectionItemType(item.type, collection.name);\n const sourcePath = resolveSourcePath(item.src, type, collection.name);\n const sourceName = inferItemNameFromSource(type, sourcePath, collection.name);\n const key = `${type}:${sourceName}`;\n\n if (!deduped.has(key)) {\n deduped.set(key, {\n collection: collection.name,\n sourcePath,\n sourceName,\n type,\n });\n }\n }\n\n return [...deduped.values()];\n}\n\nfunction installHook(name: string, srcDir: string): void {\n if (!existsSync(srcDir)) {\n console.error(`Hook not found: ${name}`);\n process.exit(1);\n }\n\n const hookSrc = join(srcDir, \"hook.mjs\");\n const fragmentPath = join(srcDir, \"settings-fragment.json\");\n\n const hooksDir = join(CLAUDE_DIR, \"hooks\");\n mkdirSync(hooksDir, { recursive: true });\n const destHook = resolve(hooksDir, `${name}.mjs`);\n if (!destHook.startsWith(hooksDir + sep)) {\n console.error(\"Invalid hook name\");\n process.exit(1);\n }\n writeFileSync(destHook, readFileSync(hookSrc));\n\n if (existsSync(fragmentPath)) {\n const fragment = JSON.parse(readFileSync(fragmentPath, \"utf8\"));\n const settingsPath = join(CLAUDE_DIR, \"settings.json\");\n const current = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, \"utf8\")) : {};\n const merged = deepMerge(current, fragment);\n writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + \"\\n\");\n }\n\n const manifest = readManifest();\n manifest.hooks[name] = { hash: hashHookSource(name), installedAt: today() };\n writeManifest(manifest);\n\n console.log(`Installed hook: ${name} → ${relative(PROJECT_ROOT, destHook)}`);\n}\n\nfunction addHook(name: string): void {\n name = sanitizeName(name, \"hook\");\n installHook(name, join(HOOKS_SRC, name));\n}\n\nfunction installSkill(name: string, srcDir: string, links: string[]): void {\n if (!existsSync(srcDir) || !statSync(srcDir).isDirectory()) {\n console.error(`Skill not found: ${name}`);\n process.exit(1);\n }\n\n const destDir = resolve(TOOLKIT_DIR, \"skills\", name);\n if (!destDir.startsWith(join(TOOLKIT_DIR, \"skills\") + sep)) {\n console.error(\"Invalid skill name\");\n process.exit(1);\n }\n mkdirSync(dirname(destDir), { recursive: true });\n cpSync(srcDir, destDir, { recursive: true });\n\n const resolvedLinks = links.length > 0 ? links : [join(\".claude\", \"skills\")];\n for (const link of resolvedLinks) {\n const linkDir = resolve(PROJECT_ROOT, link);\n mkdirSync(linkDir, { recursive: true });\n\n const linkPath = join(linkDir, name);\n if (existsSync(linkPath) || lstatExists(linkPath)) {\n unlinkSync(linkPath);\n }\n\n const relTarget = relative(linkDir, destDir);\n symlinkSync(relTarget, linkPath, \"dir\");\n }\n\n const manifest = readManifest();\n manifest.skills[name] = {\n hash: hashSkillSource(name),\n installedAt: today(),\n linkedTo: resolvedLinks,\n };\n writeManifest(manifest);\n\n console.log(`Installed skill: ${name} → ${relative(PROJECT_ROOT, destDir)}`);\n for (const l of resolvedLinks) {\n console.log(` linked: ${join(l, name)}`);\n }\n}\n\nfunction addSkill(name: string, links: string[]): void {\n name = sanitizeName(name, \"skill\");\n installSkill(name, join(SKILLS_SRC, name), links);\n}\n\nfunction addCollection(name: string): void {\n const items = resolveCollection(name);\n for (const item of items) {\n if (!existsSync(item.sourcePath)) {\n throw new Error(\n `Collection \"${item.collection}\" references missing ${item.type} source: ${relative(TOOLKIT_ROOT, item.sourcePath)}`,\n );\n }\n\n const itemStats = statSync(item.sourcePath);\n const actualKind = itemStats.isFile()\n ? \"file\"\n : itemStats.isDirectory()\n ? \"directory\"\n : \"other\";\n\n if (item.type === \"hook\") {\n if (!itemStats.isDirectory()) {\n throw new Error(\n `Collection \"${item.collection}\" expected hook source \"${item.sourcePath}\" to be a directory, found ${actualKind}`,\n );\n }\n installHook(item.sourceName, item.sourcePath);\n continue;\n }\n\n if (!itemStats.isDirectory()) {\n throw new Error(\n `Collection \"${item.collection}\" expected skill source \"${item.sourcePath}\" to be a directory, found ${actualKind}`,\n );\n }\n installSkill(item.sourceName, item.sourcePath, []);\n }\n}\n\nfunction lstatExists(p: string): boolean {\n try {\n lstatSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function update(force: boolean): Promise<void> {\n const manifest = readManifest();\n let changed = false;\n\n for (const [name, entry] of Object.entries(manifest.hooks)) {\n const srcDir = join(HOOKS_SRC, name);\n if (!existsSync(srcDir)) {\n continue;\n }\n\n const sourceHash = hashHookSource(name);\n const installedPath = join(CLAUDE_DIR, \"hooks\", `${name}.mjs`);\n const installedHash = existsSync(installedPath) ? shortHash(readFileSync(installedPath)) : null;\n\n const sourceChanged = sourceHash !== entry.hash;\n const locallyModified = installedHash !== null && installedHash !== entry.hash;\n\n if (!sourceChanged && !locallyModified) {\n continue;\n }\n\n changed = true;\n\n if (locallyModified && !force) {\n console.warn(\n `! hook \"${name}\" was modified locally (installed=${installedHash}, manifest=${entry.hash}). Use --force to overwrite.`,\n );\n continue;\n }\n\n if (sourceChanged) {\n const oldSrc = existsSync(installedPath) ? readFileSync(installedPath, \"utf8\") : \"\";\n const newSrc = readFileSync(join(srcDir, \"hook.mjs\"), \"utf8\");\n console.log(`\\n~ hook: ${name} (${entry.hash} → ${sourceHash})`);\n console.log(diffLines(oldSrc, newSrc));\n const ok = force || (await confirm(`Update hook \"${name}\"?`));\n\n if (!ok) {\n continue;\n }\n\n writeFileSync(installedPath, newSrc);\n manifest.hooks[name] = { hash: sourceHash, installedAt: today() };\n }\n }\n\n for (const [name, entry] of Object.entries(manifest.skills)) {\n const srcDir = join(SKILLS_SRC, name);\n if (!existsSync(srcDir)) {\n continue;\n }\n\n const sourceHash = hashSkillSource(name);\n if (sourceHash === entry.hash) {\n continue;\n }\n\n changed = true;\n console.log(`\\n~ skill: ${name} (${entry.hash} → ${sourceHash})`);\n const ok = force || (await confirm(`Update skill \"${name}\"?`));\n if (!ok) {\n continue;\n }\n\n const destDir = join(TOOLKIT_DIR, \"skills\", name);\n cpSync(srcDir, destDir, { recursive: true, force: true });\n manifest.skills[name] = {\n hash: sourceHash,\n installedAt: today(),\n linkedTo: entry.linkedTo,\n };\n }\n\n if (changed) {\n writeManifest(manifest);\n }\n}\n\nfunction list(kind: \"hook\" | \"skill\"): void {\n const dir = kind === \"hook\" ? HOOKS_SRC : SKILLS_SRC;\n if (!existsSync(dir)) {\n console.log(`(no ${kind}s available)`);\n return;\n }\n const entries = readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory() || (kind === \"skill\" && e.isSymbolicLink()))\n .map((e) => e.name);\n\n if (entries.length === 0) {\n console.log(`(no ${kind}s available)`);\n return;\n }\n\n for (const name of entries) {\n const hash = kind === \"hook\" ? hashHookSource(name) : hashSkillSource(name);\n console.log(`${name} ${hash}`);\n }\n}\n\nfunction listCollections(): void {\n const collections = readCollectionsConfig();\n if (collections.length === 0) {\n console.log(\"(no collections available)\");\n return;\n }\n\n for (const collection of collections) {\n console.log(`${collection.name} ${collection.items.length} item(s)`);\n }\n}\n\n// ---------- argv ----------\n\nfunction usage(): never {\n console.error(\n `Usage:\n toolkit add hook <name>\n toolkit add skill <name> [--link <target>]...\n toolkit add collections <name>\n toolkit update [--force]\n toolkit list hook\n toolkit list skill\n toolkit list collections`,\n );\n process.exit(1);\n}\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n options: {\n force: {\n default: false,\n type: \"boolean\",\n },\n links: {\n multiple: true,\n type: \"string\",\n },\n },\n allowPositionals: true,\n });\n\n const { force, links } = values;\n const [command, resource, name] = positionals;\n\n if (command === \"add\" && resource === \"hook\") {\n if (!name) {\n usage();\n }\n\n addHook(name);\n return;\n }\n\n if (command === \"add\" && resource === \"skill\") {\n if (!name) {\n usage();\n }\n\n addSkill(name, links ? links : []);\n return;\n }\n\n if (command === \"add\" && (resource === \"collection\" || resource === \"collections\")) {\n if (!name) {\n usage();\n }\n\n addCollection(name);\n return;\n }\n\n if (command === \"update\") {\n await update(force);\n return;\n }\n\n if (command === \"list\" && (resource === \"hook\" || resource === \"skill\")) {\n list(resource as \"hook\" | \"skill\");\n return;\n }\n\n if (command === \"list\" && (resource === \"collection\" || resource === \"collections\")) {\n listCollections();\n return;\n }\n\n usage();\n}\n\nmain().catch((err) => {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@schalkneethling/toolkit",
3
- "version": "0.7.2",
4
- "description": "CLI for managing Claude Code hooks and skills across projects.",
3
+ "version": "1.1.0",
4
+ "description": "CLI for managing Agent hooks, skills, and agents across projects.",
5
5
  "license": "MIT",
6
6
  "bin": {
7
7
  "toolkit": "./dist/index.mjs"
8
8
  },
9
9
  "files": [
10
- "commands",
11
10
  "config.json",
11
+ "agents",
12
12
  "dist",
13
13
  "hooks",
14
14
  "!**/*.ts",
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: code-review
3
+ description: Review code changes for correctness, security, performance, accessibility, maintainability, tests, dependencies, design-system adherence, and localization. Use when the user asks for a code review, PR review, review of local changes, risk assessment, code quality feedback, or actionable findings before merge.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Code Review
8
+
9
+ Review code to catch issues the original engineer may have missed and to improve the codebase without creating unnecessary friction. Prioritize real risks over style noise.
10
+
11
+ ## Review Workflow
12
+
13
+ 1. Determine the review scope:
14
+ - Inspect `git status`, the relevant diff, and any user-specified files or PR context.
15
+ - Preserve unrelated user changes. Do not modify code unless the user asks for fixes.
16
+ - Identify whether the change is frontend, backend, library, CLI, infrastructure, docs, or mixed.
17
+ 2. Check project context before judging:
18
+ - Frameworks and languages.
19
+ - Test tooling and CI coverage.
20
+ - Linting, formatting, type checking, and build scripts.
21
+ - Dependency management and lockfiles.
22
+ - Accessibility, localization, and design-system tooling when UI code is involved.
23
+ 3. Review the change for material issues:
24
+ - Correctness: broken behavior, edge cases, data loss, error handling, and regressions.
25
+ - Security: injection risks, unsafe auth, secret handling, dependency risk, and permission scope.
26
+ - Performance and resources: unnecessary CPU, memory, network use, leaks, race conditions, deadlocks, async coordination, and missing back-pressure or throttling.
27
+ - Accessibility: semantic HTML, labels, keyboard support, focus management, ARIA misuse, and contrast.
28
+ - Maintainability: naming, structure, type safety, readability, duplication, and fit with local patterns.
29
+ - Tests and docs: missing coverage for new behavior, insufficient regression tests, and stale public docs.
30
+ - Dependencies: added packages, bundle/runtime impact, maintenance state, security posture, and whether built-in or existing project utilities would be enough.
31
+ - Design systems and branding: token usage, component reuse, theme consistency, and justified deviations.
32
+ - Localization: hard-coded UI strings, date/number formatting, translation keys, and future translation workflow.
33
+ 4. Produce review feedback:
34
+ - Lead with findings, ordered by severity.
35
+ - Include file and line references for repo-local issues.
36
+ - Explain impact and give a concrete fix.
37
+ - Group repeated instances when one root cause explains them.
38
+ - Mark non-blocking refactors as follow-up suggestions, not merge blockers.
39
+ - If no issues are found, say so clearly and mention any residual test or tooling gaps.
40
+
41
+ ## Context Skills
42
+
43
+ Load related skills only when the diff makes that domain relevant:
44
+
45
+ - Load `semantic-html` when reviewing markup-producing code: HTML templates, JSX/TSX, Astro/Vue/Svelte components, Lit templates, Twig/ERB/Handlebars/Nunjucks, MDX, or code that materially changes rendered HTML structure.
46
+ - Load `css-coder` when reviewing style-producing code: CSS, Sass/Less, CSS modules, scoped component styles, CSS-in-JS, design tokens, or code that materially changes selectors, layout, cascade, responsive behavior, colors, motion, or focus states.
47
+
48
+ For large or high-risk diffs, consider a focused specialist subagent for the relevant skill. Pass only the user request, relevant diff, surrounding component context, and known project conventions. Reconcile specialist feedback into one prioritized final review rather than forwarding it verbatim.
49
+
50
+ ## Setup Deficiencies
51
+
52
+ If essential review infrastructure is missing, call it out early before deep findings:
53
+
54
+ - No runnable tests or tests absent from CI.
55
+ - No linting, formatting, type checking, or build validation for the changed area.
56
+ - Missing lockfile or unpinned dependencies.
57
+ - No dependency or supply-chain scanning for publishable/server code.
58
+ - No accessibility checks for UI-heavy changes.
59
+ - No i18n framework or translation process for localized UI.
60
+ - Missing design-system tokens/components when the project clearly depends on them.
61
+
62
+ When a setup deficiency would make the review noisy or unreliable, report the deficiency as the primary finding and then provide only the highest-confidence code findings.
63
+
64
+ ## Feedback Standards
65
+
66
+ - Be direct, specific, and respectful. Focus on code and impact, never the author.
67
+ - Avoid nitpicks that an existing formatter or linter should handle.
68
+ - Prefer established local helpers, components, patterns, and style systems over new abstractions.
69
+ - Flag redundant implementations when the project already has an equivalent helper, component, CSS pattern, or service.
70
+ - Challenge clever but opaque code. Prefer readable control flow and well-named helpers.
71
+ - Encourage comments only for non-obvious decisions, tradeoffs, constraints, or nuanced behavior. Discourage comments that restate the code.
72
+ - Include positive feedback after findings when something is genuinely strong, such as clean tests, simple abstractions, or thoughtful design.
73
+
74
+ ## Severity Guide
75
+
76
+ - `P0`: Must fix immediately. Security exploit, data loss, severe outage, or merge-blocking broken core behavior.
77
+ - `P1`: Should fix before merge. Likely bug, serious regression, accessibility blocker, unsafe dependency/auth pattern, or missing critical test.
78
+ - `P2`: Important but may be follow-up. Maintainability issue, incomplete edge coverage, performance concern, duplicate implementation, or design-system drift.
79
+ - `P3`: Optional improvement. Clarity, small refactor, documentation polish, or non-blocking suggestion.
80
+
81
+ ## Output Shape
82
+
83
+ Use this order for review responses:
84
+
85
+ 1. Findings, ordered by severity, with `file:line`.
86
+ 2. Open questions or assumptions.
87
+ 3. Brief positive notes, if useful.
88
+ 4. Validation performed or not performed.
89
+
90
+ Keep summaries short. The findings are the review.
@@ -0,0 +1,6 @@
1
+ interface:
2
+ display_name: "Code Review"
3
+ short_description: "Review code changes for risk and quality."
4
+ default_prompt: "Use $code-review to review my current changes and prioritize actionable findings."
5
+ policy:
6
+ allow_implicit_invocation: false
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: github-goal-issue-triage
3
+ description: Triage open GitHub issues across one or more repositories against each repository's GOAL.md, create missing p0/p1/p2/p3 priority labels, apply exactly one priority label to each open issue, nominate the next issue to work on per repository, and generate a clean HTML report grouped by project. Use when asked to prioritize, label, rank, audit, or report on GitHub issues based on project goals.
4
+ ---
5
+
6
+ # GitHub Goal Issue Triage
7
+
8
+ ## Overview
9
+
10
+ Use this skill to turn a list of GitHub repositories into an applied issue-priority pass and a readable HTML report. Treat `GOAL.md` as the project's north star; without it, flag the repository and skip all issue triage for that repository.
11
+
12
+ ## Inputs
13
+
14
+ Expect the repository list from one of these sources:
15
+
16
+ - Inline in the user's prompt, separated by spaces, commas, or new lines.
17
+ - A local text, Markdown, JSON, YAML, or CSV file path supplied by the user.
18
+ - Standard input or pasted content from the user.
19
+ - The current GitHub issue, pull request, repository page, or local git remote only when the user explicitly asks to use the current context.
20
+
21
+ Accept repository entries as `owner/name`, `https://github.com/owner/name`, `git@github.com:owner/name.git`, or a JSON/YAML/CSV field clearly named `repo`, `repository`, `repositories`, `url`, or `github_url`. Normalize every entry to `owner/name`, remove duplicates while preserving first-seen order, and ignore blank lines and comments beginning with `#`.
22
+
23
+ If no repository list is provided and no current GitHub context was explicitly requested, ask the user for the repository list before taking action. If the user does not specify an output path, write `github-issue-triage-report.html` in the current working directory.
24
+
25
+ Use the authenticated GitHub connector when available. Otherwise use `gh` or the GitHub REST/GraphQL API. If authentication or write permission is missing, continue in read-only mode and report the labels that would have been applied.
26
+
27
+ ## Repository Workflow
28
+
29
+ For each repository, in the user's order:
30
+
31
+ 1. Fetch `GOAL.md` from the repository root on the default branch.
32
+ 2. If `GOAL.md` is missing, record the repository as skipped, include the reason in the report, and move to the next repository.
33
+ 3. Read the project goal and extract the concrete outcomes, audiences, constraints, and near-term signals of progress.
34
+ 4. Retrieve all open issues, not pull requests. Use pagination; do not rely on default issue-list limits.
35
+ 5. Inspect each issue's title, body, labels, milestone, assignees, comments when needed, age, and recent activity. Avoid changing issue bodies, comments, milestones, state, or assignees unless the user explicitly asks.
36
+ 6. Ensure the repository has the labels `p0`, `p1`, `p2`, and `p3`. Create only missing labels and preserve existing label colors/descriptions.
37
+ 7. Assign exactly one priority label to each open issue. Remove any other `p0`/`p1`/`p2`/`p3` labels from that issue so priority state is unambiguous. Preserve all non-priority labels.
38
+ 8. Nominate one next issue for the repository unless there are no open issues.
39
+
40
+ ## Priority Rubric
41
+
42
+ Use goal alignment as the main criterion, then weigh impact, urgency, unblock value, user harm, implementation readiness, and risk.
43
+
44
+ - `p0`: Critical to the project goal now. Blocks core usage, release, trust, security, data integrity, or a prerequisite without which the project cannot make meaningful progress.
45
+ - `p1`: High-impact work that substantially advances the goal, fixes a major user-facing problem, unlocks several other issues, or removes a major adoption or reliability barrier.
46
+ - `p2`: Useful and goal-aligned, but not urgent. Includes medium-impact bugs, incremental improvements, important documentation, and work that helps after higher-priority blockers are handled.
47
+ - `p3`: Low immediate impact, weak goal alignment, polish, speculative ideas, minor cleanup, stale work, or nice-to-have improvements.
48
+
49
+ When evidence is thin, choose the lower priority and explain the uncertainty in the issue rationale.
50
+
51
+ ## Next Issue Nomination
52
+
53
+ Nominate the issue that should be worked on next, not necessarily the smallest issue.
54
+
55
+ Prefer the highest-priority issue that:
56
+
57
+ - Most directly advances the stated goal.
58
+ - Unblocks other issues or users.
59
+ - Has enough context to start.
60
+ - Has a reasonable scope for the next focused work session.
61
+ - Carries lower coordination risk than equally important alternatives.
62
+
63
+ If the top issue is too ambiguous, nominate a clearer issue and note what context would be needed before the ambiguous one becomes actionable.
64
+
65
+ ## GitHub Label Details
66
+
67
+ Create missing labels with these defaults unless the repository already defines them:
68
+
69
+ | Label | Color | Description |
70
+ | ----- | -------- | ------------------------------------------------------------------ |
71
+ | `p0` | `b60205` | Critical priority: blocking the project goal or urgent user impact |
72
+ | `p1` | `d93f0b` | High priority: major impact or strong goal alignment |
73
+ | `p2` | `fbca04` | Medium priority: useful, goal-aligned, not urgent |
74
+ | `p3` | `0e8a16` | Low priority: polish, speculative, or weak immediate impact |
75
+
76
+ If label creation or issue updates fail, capture the error and include a read-only triage result in the report. Do not claim labels were applied unless the update succeeded.
77
+
78
+ ## HTML Report Requirements
79
+
80
+ Generate a self-contained HTML file with readable CSS and no external dependencies. Escape issue titles, labels, body excerpts, and rationale text before writing HTML.
81
+
82
+ The report must include:
83
+
84
+ - Generation timestamp.
85
+ - Input repository list.
86
+ - A summary section with counts by repository: skipped, total open issues, p0, p1, p2, p3, and nominated next issue.
87
+ - One `<details>` element per repository, with a `<summary>` that includes the repository name, status, priority counts, and nominee.
88
+ - For skipped repositories, a short explanation that `GOAL.md` was missing and no issues were triaged.
89
+ - For triaged repositories, the project goal summary, label-update status, next-issue nomination, and an issue table.
90
+
91
+ Issue table columns:
92
+
93
+ - Priority.
94
+ - Issue link with number and title.
95
+ - Rationale tied to `GOAL.md`.
96
+ - Impact.
97
+ - Current labels after update or intended labels in read-only mode.
98
+ - Notes or uncertainty.
99
+
100
+ Sort issues within each repository by priority (`p0` to `p3`), then by goal alignment and impact. Keep repositories in the input order.
101
+
102
+ Use semantic HTML. A compact structure like this is sufficient:
103
+
104
+ ```html
105
+ <details open>
106
+ <summary>owner/repo - 12 open issues - next: #42 Improve release flow</summary>
107
+ <!-- goal, label status, nominee, issue table -->
108
+ </details>
109
+ ```
110
+
111
+ ## Quality Checks
112
+
113
+ Before finishing:
114
+
115
+ - Verify every input repository has either a skipped reason or complete triage results.
116
+ - Verify every open issue in triaged repositories has exactly one `p0`/`p1`/`p2`/`p3` label applied or listed as intended in read-only mode.
117
+ - Verify the nominee for each repository links to an open issue from that repository.
118
+ - Verify the HTML file opens without missing resources and all issue links are absolute GitHub URLs.
119
+ - Summarize what changed on GitHub and where the HTML report was written.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "GitHub Goal Issue Triage"
3
+ short_description: "Prioritize GitHub issues by project goal"
4
+ default_prompt: "Use $github-goal-issue-triage to triage open issues across these repositories and generate an HTML report."
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  name: npm-package-publishing
3
3
  description: >
4
- Apply best practices when publishing npm packages, including secure CI/CD workflows, trusted
5
- publishing via OIDC, GitHub repository hardening, and supply-chain attack prevention. Use this
6
- skill whenever the user asks about publishing an npm package, setting up a publish workflow,
7
- configuring GitHub Actions for release automation, managing npm tokens or secrets, setting up
8
- changesets, or auditing an existing publishing pipeline for security. Also trigger when the user
9
- mentions publint, OIDC trusted publishing, release automation, or package versioning workflows.
4
+ Audit and improve the security posture of npm package publishing: account security, npm trusted
5
+ publishing strategy, GitHub repository hardening, token removal, release governance, dependency
6
+ update policy, provenance, and supply-chain risk. Use when the user asks about npm publishing best
7
+ practices, publishing security, OIDC/trusted publishing strategy, npm token hygiene, release
8
+ automation posture, Changesets versus changelog strategies, publint, provenance, or auditing an
9
+ existing publishing pipeline. For writing or debugging the concrete GitHub Actions publish
10
+ workflow file, use the npm-trusted-publishing-github-workflow skill instead.
10
11
  ---
11
12
 
12
13
  # npm Package Publishing — Best Practices
@@ -14,8 +15,66 @@ description: >
14
15
  Based on the [e18e publishing guide](https://e18e.dev/docs/publishing.html). Reference it for
15
16
  the canonical source; this skill distils the actionable steps.
16
17
 
17
- > **Package manager note.** All examples in this skill use `npm` to match the e18e source
18
- > material, but nothing here is npm-specific. Always use whichever package manager the project
18
+ ## Agent Workflow
19
+
20
+ 1. Inspect the repository before recommending changes:
21
+ - `package.json`
22
+ - lockfiles and `packageManager`
23
+ - `.npmrc`, `.yarnrc.yml`, `.github/workflows/*`
24
+ - release tooling such as Changesets, changelogithub, semantic-release, or release-it
25
+ - existing npm/GitHub tokens or `NODE_AUTH_TOKEN` usage in workflows
26
+ 2. Classify the request:
27
+ - **Audit**: report risks by severity, with file and line references where possible.
28
+ - **Implementation**: make scoped repository changes, then run relevant validation.
29
+ - **Strategy**: explain tradeoffs and identify user-only settings.
30
+ 3. Separate agent-doable work from user-only UI work. Do not claim account, npmjs.com, or GitHub
31
+ settings are configured unless verified by authenticated tool/API access.
32
+ 4. For concrete GitHub Actions publish workflow creation or CI failure debugging, hand off to the
33
+ `npm-trusted-publishing-github-workflow` skill.
34
+
35
+ ## Freshness Rule
36
+
37
+ Before changing trusted publishing requirements, supported CI providers, npm CLI minimums, Node.js
38
+ minimums, provenance behavior, or GitHub release/security settings, verify the current official npm
39
+ and GitHub documentation when browsing is available. These requirements change over time.
40
+
41
+ ## Responsibility Split
42
+
43
+ Agent can usually:
44
+
45
+ - Edit `.github/workflows/*`.
46
+ - Add or update repository package-manager config such as `.npmrc` or `.yarnrc.yml`.
47
+ - Remove `NODE_AUTH_TOKEN` usage from publish steps when trusted publishing is used.
48
+ If private dependencies require registry auth during install, keep that token scoped to read-only
49
+ install steps only.
50
+ - Add Dependabot/Renovate config.
51
+ - Run `publint`, workflow linting, tests, package builds, and other local validation.
52
+ - Report exact user steps for npm/GitHub settings.
53
+
54
+ User or authenticated UI/API access required:
55
+
56
+ - Enable npm/GitHub 2FA.
57
+ - Configure npm trusted publisher settings on npmjs.com.
58
+ - Set package publishing access to require 2FA and disallow tokens.
59
+ - Enable GitHub repository/org Actions restrictions.
60
+ - Configure branch/tag rulesets and immutable releases.
61
+ - Remove repository/org secrets when the agent lacks GitHub settings access.
62
+
63
+ ## Non-Negotiables
64
+
65
+ - Do not add `NODE_AUTH_TOKEN` to publish steps when trusted publishing is available.
66
+ Use OIDC for publishing; if private dependencies require install auth, use a read-only token only
67
+ for the install step.
68
+ - Do not store npm publish tokens in GitHub Actions secrets for OIDC-capable publishing.
69
+ - Do not run install lifecycle scripts in release workflows unless the user explicitly accepts the
70
+ risk.
71
+ - Do not show tag-pinned actions as compliant with SHA pinning; resolve actions to full commit SHAs.
72
+ - Do not say npm/GitHub account settings are complete unless they were actually checked.
73
+ - Do not use self-hosted runners for npm trusted publishing unless official npm docs currently
74
+ support them.
75
+
76
+ > **Package manager note.** The example install and configuration commands in this skill use `npm`
77
+ > to match the e18e source material, but should be adapted to whichever package manager the project
19
78
  > already uses — `pnpm`, `yarn`, `bun`, etc. Adapt commands accordingly:
20
79
  >
21
80
  > | npm | pnpm | yarn |
@@ -27,6 +86,10 @@ the canonical source; this skill distils the actionable steps.
27
86
  > Detect the project's package manager by checking for a lockfile (`pnpm-lock.yaml`,
28
87
  > `yarn.lock`, `bun.lockb`) or a `packageManager` field in `package.json` before
29
88
  > generating any commands or workflow steps.
89
+ >
90
+ > Section 2.2's trusted-publishing requirement is npm-specific: the publish step must run with a
91
+ > supported Node.js version and npm CLI version even when the rest of the workflow uses another
92
+ > package manager.
30
93
 
31
94
  ---
32
95
 
@@ -92,7 +155,8 @@ ever touches the repository.
92
155
  ### 2.2 · npm CLI version requirement
93
156
 
94
157
  The publish step **must** use npm CLI ≥ 11.5.1 for automatic OIDC trusted publishing.
95
- Node.js 24 bundles npm 11.5.1; with older CI images, add a step before publishing:
158
+ Node.js 22.14.0 or newer can use trusted publishing when the workflow installs npm CLI 11.5.1 or
159
+ newer before publishing:
96
160
 
97
161
  ```yaml
98
162
  - run: npm i -g npm
@@ -277,6 +341,24 @@ all other security recommendations in this document regardless.
277
341
 
278
342
  ---
279
343
 
344
+ ## Audit Output Format
345
+
346
+ For audits, lead with findings:
347
+
348
+ - **P0/P1/P2/P3** severity.
349
+ - File and line when repo-local.
350
+ - Risk.
351
+ - Recommended fix.
352
+ - Whether the agent can implement it now or the user must configure it externally.
353
+
354
+ Then include:
355
+
356
+ - Validation run.
357
+ - Remaining user-only checklist.
358
+ - Suggested next change.
359
+
360
+ ---
361
+
280
362
  ## Quick Reference Checklist
281
363
 
282
364
  Use this when setting up a new package or auditing an existing one.
@@ -299,7 +381,7 @@ Use this when setting up a new package or auditing an existing one.
299
381
 
300
382
  - [ ] OIDC trusted publisher configured on npmjs.com
301
383
  - [ ] "Require 2FA, disallow tokens" enabled on npm
302
- - [ ] Publish step uses Node.js 24.8.0
384
+ - [ ] Publish step uses npm CLI ≥ 11.5.1 and a Node.js version supported by official npm docs
303
385
  - [ ] GitHub environment (`publish`) configured with branch restrictions
304
386
 
305
387
  ### Workflow hygiene
@@ -0,0 +1,3 @@
1
+ display_name: npm Publishing Security
2
+ short_description: Audit and improve secure npm package publishing workflows.
3
+ default_prompt: Audit this package's npm publishing setup and recommend secure trusted publishing, token, workflow, and release hardening improvements.
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: project-goal
3
+ description: Inspect a project, ask targeted user questions, and write a root GOAL.md that clearly defines the project's goals, intended audience, success criteria, constraints, and explicit non-goals. Use when asked to create, draft, update, clarify, document, or recover a project's goal, north star, mission, scope, or GOAL.md from an existing repository.
4
+ ---
5
+
6
+ # Project Goal
7
+
8
+ Use this skill to create a clear `GOAL.md` in the project root. The file should help future agents and maintainers understand what the project is trying to accomplish and what it deliberately is not trying to be.
9
+
10
+ ## Workflow
11
+
12
+ 1. Determine the project root. Prefer the git repository root. If there is no git repository, use the current working directory unless the user named a different root.
13
+ 2. Inspect the project before asking questions:
14
+ - Read existing project-level docs such as `README.md`, `GOAL.md`, `CONTRIBUTING.md`, docs indexes, package manifests, config files, and examples.
15
+ - Inspect source layout, tests, CLI or app entry points, public APIs, bundled assets, and install or build scripts.
16
+ - Review issue, roadmap, changelog, or planning files when present.
17
+ 3. Form a concise working theory of:
18
+ - The project purpose.
19
+ - Primary users or consumers, limited to the audiences supported by project evidence or user confirmation.
20
+ - Core capabilities.
21
+ - Success signals.
22
+ - Constraints or principles.
23
+ - Explicit non-goals.
24
+ - Unknowns or contradictions.
25
+ 4. Ask the user only the questions that materially affect the final `GOAL.md`.
26
+ - Use the available Ask User tool when one exists. If no Ask User tool is available, ask directly in chat.
27
+ - Ask one question at a time by default.
28
+ - Set a sensible upper limit before asking follow-ups, usually no more than 3-5 total questions unless the project evidence is genuinely contradictory.
29
+ - Prefer confirmation questions when the answer can be inferred: "I infer X from Y; should GOAL.md state that?"
30
+ - Ask open questions only for real gaps, conflicts, or values that cannot be discovered from the repository.
31
+ - Stop asking once the document can be accurate enough. Do not turn the process into an interview.
32
+ 5. Write or update `<project-root>/GOAL.md`.
33
+ - If `GOAL.md` already exists, preserve accurate useful content and revise it in place.
34
+ - If discovered evidence conflicts with user answers, treat the user as authoritative but mention the conflict in the final response.
35
+ - If the user cannot answer a question, write the best-supported goal and mark unresolved uncertainty in the document.
36
+ 6. Report the file path written and summarize the most important assumptions or unresolved items.
37
+
38
+ ## GOAL.md Content
39
+
40
+ Use Markdown features that make the project intent easy to scan. A good default structure is:
41
+
42
+ ```markdown
43
+ # Project Goal
44
+
45
+ ## North Star
46
+
47
+ ## Who This Is For
48
+
49
+ ## Core Goals
50
+
51
+ ## Success Looks Like
52
+
53
+ ## Non-Goals
54
+
55
+ ## Principles and Constraints
56
+
57
+ ## Current Focus
58
+
59
+ ## Open Questions
60
+ ```
61
+
62
+ Adapt the headings to the project. Keep the file practical rather than ceremonial.
63
+
64
+ `GOAL.md` must include:
65
+
66
+ - A short north-star statement.
67
+ - The intended audience or users. If there is only one clear audience, name that single group instead of inventing additional end users or use cases.
68
+ - The concrete outcomes the project exists to create.
69
+ - A prioritized or grouped list of core goals.
70
+ - A `Non-Goals` section that clearly states what the project is not, will not optimize for, or should avoid becoming.
71
+ - Success criteria or observable signs of progress.
72
+ - Any important constraints, tradeoffs, principles, or scope boundaries.
73
+ - Open questions only when they genuinely remain unresolved after discovery and user input.
74
+
75
+ ## Discovery Guidance
76
+
77
+ Look for evidence in:
78
+
79
+ - Project name, README, package metadata, CLI help text, app copy, examples, and tests.
80
+ - Dependency choices, framework configuration, deployment files, and build scripts.
81
+ - Directory names such as `src/`, `docs/`, `examples/`, `packages/`, `apps/`, `hooks/`, `skills/`, or `templates/`.
82
+ - Existing planning artifacts, comments, release notes, or issue templates.
83
+
84
+ Avoid overfitting to implementation details. The goal should describe why the project exists and what outcomes matter, not merely list files or current implementation tasks.
85
+
86
+ ## Writing Standards
87
+
88
+ - Be specific, plainspoken, and falsifiable where possible.
89
+ - Separate facts discovered from the repo from assumptions confirmed by the user.
90
+ - Do not make up audiences, personas, or use cases to make the project seem broader. Specific and narrow is better than blurry.
91
+ - Prefer durable project intent over short-lived task lists.
92
+ - Make non-goals explicit enough to guide prioritization and issue triage.
93
+ - Keep the document concise enough to be read before working on the project.
94
+ - Do not include private chain-of-thought, exhaustive discovery notes, or a transcript of user questions.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Project Goal"
3
+ short_description: "Draft a project GOAL.md from discovery"
4
+ default_prompt: "Use $project-goal to inspect this repository and write a clear GOAL.md."
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: refined-plan-mode
3
+ description: Use this skill when the user asks to plan, review, revise, continue, checkpoint, handoff, reset, or execute work using Refined Plan Mode. Also use it for legacy /rpm:start, /rpm:advance, /rpm:review, /rpm:feedback, /rpm:checkpoint, and /rpm:handoff prompts.
4
+ ---
5
+
1
6
  # Refined Plan Mode
2
7
 
3
8
  Use this skill when the user asks to plan, review, revise, continue, checkpoint, handoff, reset, or execute work using Refined Plan Mode.
@@ -24,7 +29,99 @@ Users can ask for checkpoint, handoff, or reset in natural language:
24
29
 
25
30
  - For a checkpoint, report the current version, latest plan path, feedback status, approval status, and recommended next action.
26
31
  - For a handoff, summarize the goal, current plan, feedback status, approval status, important assumptions, unresolved decisions, and recommended next action.
27
- - For a reset, empty only `.plan-review` while keeping the `.plan-review` directory itself. Do not remove source files or any other workspace files.
32
+ - For a reset, first require explicit confirmation by asking the user to type `RESET` or by accepting an explicit `--force` request. After confirmation, empty only the contents of `.plan-review` while keeping the `.plan-review` directory itself. Do not remove source files or any other workspace files.
33
+
34
+ ## Task Modes
35
+
36
+ Treat these legacy `/rpm:*` prompts as natural-language requests for this skill:
37
+
38
+ ### `/rpm:start`
39
+
40
+ Start a plan review loop for the user's current task.
41
+
42
+ 1. Inspect the repository enough to understand the task and relevant constraints.
43
+ 2. Ask only blocking clarification questions. If reasonable assumptions are available, state them in the plan instead of stopping.
44
+ 3. Create `.plan-review/plans/plan-v1.md` with the complete plan.
45
+ 4. Create or update `.plan-review/.current-version` with `v1`.
46
+ 5. Reply with a concise summary and tell the user the plan is ready for review in Refined Plan Mode.
47
+
48
+ Do not implement the plan yet unless the user explicitly asks you to proceed without review.
49
+
50
+ ### `/rpm:advance`
51
+
52
+ Continue the loop from the current state.
53
+
54
+ 1. Inspect `.plan-review/.current-version`, `.plan-review/approved-plan.md`, available plan files, and available feedback files.
55
+ 2. If an approved plan exists, execute that plan.
56
+ 3. If feedback exists for the current plan version, incorporate it into the next plan version.
57
+ 4. If there is a current plan but no feedback or approval, remind the user that the plan is awaiting review.
58
+ 5. If no plan exists, start with `/rpm:start` behavior.
59
+
60
+ Keep the response focused on the next state transition.
61
+
62
+ ### `/rpm:review`
63
+
64
+ Audit the latest plan before the user reviews it.
65
+
66
+ 1. Read the current plan version.
67
+ 2. Review the plan for missing context, vague steps, untested assumptions, risky sequencing, and weak validation.
68
+ 3. If improvements are needed, write a revised next version and update `.plan-review/.current-version`.
69
+ 4. If the plan is already review-ready, leave files unchanged.
70
+ 5. Reply with either the new plan version written or a short explanation that the current plan is ready for review.
71
+
72
+ This mode reviews plan quality. It does not implement the plan.
73
+
74
+ ### `/rpm:feedback`
75
+
76
+ Incorporate submitted feedback into the next plan version.
77
+
78
+ 1. Read `.plan-review/.current-version` to find the current version.
79
+ 2. Read `.plan-review/feedback/plan-vN-feedback.json` for that version.
80
+ 3. Read `.plan-review/plans/plan-vN.md`.
81
+ 4. Address every feedback item in a revised plan, adding a `Feedback Addressed` section that maps comments to changes made.
82
+ 5. Write the revision to `.plan-review/plans/plan-vN+1.md`.
83
+ 6. Update `.plan-review/.current-version` to the new version.
84
+ 7. Reply with a short note naming the feedback file read and the new plan file written.
85
+
86
+ If the feedback file is missing, report the exact path expected and stop.
87
+
88
+ ### `/rpm:checkpoint`
89
+
90
+ Summarize the current review-loop state.
91
+
92
+ Report:
93
+
94
+ - Current plan version from `.plan-review/.current-version`, if present.
95
+ - Latest plan file path.
96
+ - Whether feedback exists for the current version.
97
+ - Whether `.plan-review/approved-plan.md` exists.
98
+ - The recommended next action.
99
+
100
+ Do not modify files unless the user also asks you to advance or revise the plan.
101
+
102
+ ### `/rpm:handoff`
103
+
104
+ Prepare a compact continuation summary for another agent or a future session.
105
+
106
+ Include:
107
+
108
+ - Goal.
109
+ - Current plan version and file path.
110
+ - Feedback status.
111
+ - Approval status.
112
+ - Important assumptions or unresolved decisions.
113
+ - Recommended next action.
114
+
115
+ Prefer reading the current plan and feedback files directly instead of relying on chat history.
116
+
117
+ ### Reset
118
+
119
+ Reset the review-loop state only after explicit confirmation:
120
+
121
+ 1. If the user did not provide an explicit `--force` request, ask them to type `RESET`.
122
+ 2. Proceed only when the user confirms exactly.
123
+ 3. Empty the contents of `.plan-review`, preserving the `.plan-review` directory itself.
124
+ 4. Do not remove source files or any workspace files outside `.plan-review`.
28
125
 
29
126
  ## File Convention
30
127
 
@@ -1,13 +0,0 @@
1
- # /rpm:advance
2
-
3
- Use the Refined Plan Mode skill to continue the loop from the current state.
4
-
5
- Steps:
6
-
7
- 1. Inspect `.plan-review/.current-version`, `.plan-review/approved-plan.md`, available plan files, and available feedback files.
8
- 2. If an approved plan exists, execute that plan.
9
- 3. If feedback exists for the current plan version, incorporate it into the next plan version.
10
- 4. If there is a current plan but no feedback or approval, remind the user that the plan is awaiting review.
11
- 5. If no plan exists, start with `/rpm:start` behavior.
12
-
13
- Keep the response focused on the next state transition.
@@ -1,13 +0,0 @@
1
- # /rpm:checkpoint
2
-
3
- Use the Refined Plan Mode skill to summarize the current review-loop state.
4
-
5
- Report:
6
-
7
- - Current plan version from `.plan-review/.current-version`, if present.
8
- - Latest plan file path.
9
- - Whether feedback exists for the current version.
10
- - Whether `.plan-review/approved-plan.md` exists.
11
- - The recommended next action.
12
-
13
- Do not modify files unless the user also asks you to advance or revise the plan.
@@ -1,15 +0,0 @@
1
- # /rpm:feedback
2
-
3
- Use the Refined Plan Mode skill to incorporate submitted feedback into the next plan version.
4
-
5
- Steps:
6
-
7
- 1. Read `.plan-review/.current-version` to find the current version.
8
- 2. Read `.plan-review/feedback/plan-vN-feedback.json` for that version.
9
- 3. Read `.plan-review/plans/plan-vN.md`.
10
- 4. Address every feedback item in a revised plan, adding a "Feedback Addressed" section that maps comments to changes made.
11
- 5. Write the revision to `.plan-review/plans/plan-vN+1.md`.
12
- 6. Update `.plan-review/.current-version` to the new version.
13
- 7. Reply with a short note naming the feedback file read and the new plan file written.
14
-
15
- If the feedback file is missing, report the exact path expected and stop.
@@ -1,14 +0,0 @@
1
- # /rpm:handoff
2
-
3
- Use the Refined Plan Mode skill to prepare a compact continuation summary for another agent or a future session.
4
-
5
- Include:
6
-
7
- - Goal.
8
- - Current plan version and file path.
9
- - Feedback status.
10
- - Approval status.
11
- - Important assumptions or unresolved decisions.
12
- - Recommended next command or action.
13
-
14
- Prefer reading the current plan and feedback files directly instead of relying on chat history.
@@ -1,13 +0,0 @@
1
- # /rpm:review
2
-
3
- Use the Refined Plan Mode skill to audit the latest plan before the user reviews it.
4
-
5
- Steps:
6
-
7
- 1. Read the current plan version.
8
- 2. Review the plan for missing context, vague steps, untested assumptions, risky sequencing, and weak validation.
9
- 3. If improvements are needed, write a revised next version and update `.plan-review/.current-version`.
10
- 4. If the plan is already review-ready, leave files unchanged.
11
- 5. Reply with either the new plan version written or a short explanation that the current plan is ready for review.
12
-
13
- This command reviews plan quality. It does not implement the plan.
@@ -1,13 +0,0 @@
1
- # /rpm:start
2
-
3
- Use the Refined Plan Mode skill to start a plan review loop for the user's current task.
4
-
5
- Steps:
6
-
7
- 1. Inspect the repository enough to understand the task and relevant constraints.
8
- 2. Ask only blocking clarification questions. If reasonable assumptions are available, state them in the plan instead of stopping.
9
- 3. Create `.plan-review/plans/plan-v1.md` with the complete plan.
10
- 4. Create or update `.plan-review/.current-version` with `v1`.
11
- 5. Reply with a concise summary and tell the user the plan is ready for review in Refined Plan Mode.
12
-
13
- Do not implement the plan yet unless the user explicitly asks you to proceed without review.