@iceinvein/agent-skills 0.1.27 → 0.1.29

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
@@ -126,6 +126,29 @@ bunx @iceinvein/agent-skills info <skill>
126
126
  | `--activation <mode>` | For skills that support it: `session` (manual `/skill`) or `global` (auto via `SessionStart` hook). Claude Code only. |
127
127
  | `-g, --global` | Install to home directory (available in all projects) |
128
128
 
129
+ ## Updating Skills
130
+
131
+ Each skill is versioned independently (see `skill.json` inside each skill). The CLI tracks what you installed in `.agent-skills.lock` and can pull newer versions from GitHub.
132
+
133
+ ```bash
134
+ bunx @iceinvein/agent-skills update <skill> # update one skill
135
+ bunx @iceinvein/agent-skills update --all # update everything in the lockfile
136
+ bunx @iceinvein/agent-skills list # show installed versions
137
+ ```
138
+
139
+ `update` re-fetches the latest manifest and files from this repo, removes the old install, and re-installs cleanly. Your chosen activation mode (session vs. global) is preserved across updates, as is the set of tools you installed for. Use `-g` to update globally-installed skills.
140
+
141
+ After any command other than `update`, the CLI runs a background freshness check (at most once every 24 hours) and prints a one-line notice if anything is out of date:
142
+
143
+ ```
144
+ some-skill (v0.2.0 → v0.3.0)
145
+ Run: agent-skills update --all
146
+ ```
147
+
148
+ The check is best-effort: it silently skips on network failure and only hits GitHub once per day per project. To force a re-check sooner, delete the `lastUpdateCheck` field in `.agent-skills.lock` (or just run any command after 24 hours have elapsed).
149
+
150
+ To pin to the current version, do nothing: skills are not auto-updated. `update` is opt-in.
151
+
129
152
  ## Activation Modes (Claude Code)
130
153
 
131
154
  Some skills — like `terse` — support activation modes. Pick one at install time:
@@ -159,7 +182,7 @@ Skills install to the **current project** by default. Use `-g` to install to you
159
182
 
160
183
  ## Contributing
161
184
 
162
- - [Releasing](docs/RELEASING.md) how versions are bumped, tagged, and published to npm
185
+ - [Releasing](docs/RELEASING.md): how versions are bumped, tagged, and published to npm
163
186
 
164
187
  ## License
165
188
 
package/dist/cli/index.js CHANGED
@@ -248,6 +248,30 @@ function validateManifest(data) {
248
248
  if (typeof d.install !== "object" || d.install === null) {
249
249
  return { ok: false, error: "Missing 'install' configuration" };
250
250
  }
251
+ if (d.bundle !== undefined) {
252
+ if (typeof d.bundle !== "object" || d.bundle === null) {
253
+ return { ok: false, error: "'bundle' must be an object" };
254
+ }
255
+ const b = d.bundle;
256
+ if (!Array.isArray(b.include) || b.include.length === 0) {
257
+ return { ok: false, error: "'bundle.include' must be a non-empty array" };
258
+ }
259
+ for (const p of b.include) {
260
+ if (typeof p !== "string") {
261
+ return { ok: false, error: "'bundle.include' entries must be strings" };
262
+ }
263
+ }
264
+ if (b.exclude !== undefined) {
265
+ if (!Array.isArray(b.exclude)) {
266
+ return { ok: false, error: "'bundle.exclude' must be an array" };
267
+ }
268
+ for (const p of b.exclude) {
269
+ if (typeof p !== "string") {
270
+ return { ok: false, error: "'bundle.exclude' entries must be strings" };
271
+ }
272
+ }
273
+ }
274
+ }
251
275
  if (d.activation !== undefined) {
252
276
  if (typeof d.activation !== "object" || d.activation === null) {
253
277
  return { ok: false, error: "'activation' must be an object" };
@@ -318,12 +342,87 @@ async function fetchAllSkillFiles(skillName, manifest) {
318
342
  files.set(supportFile, result.content);
319
343
  }
320
344
  }
345
+ if (manifest.bundle) {
346
+ const treeResult = await fetchSkillTree(skillName);
347
+ if (!treeResult.ok)
348
+ return { error: treeResult.error };
349
+ const bundlePaths = resolveBundlePaths(treeResult.entries, manifest.bundle);
350
+ for (const relPath of bundlePaths) {
351
+ if (files.has(relPath))
352
+ continue;
353
+ const result = await fetchSkillFile(skillName, relPath);
354
+ if (!result.ok)
355
+ return { error: result.error };
356
+ files.set(relPath, result.content);
357
+ }
358
+ }
321
359
  return files;
322
360
  }
361
+ async function fetchSkillTree(skillName) {
362
+ const url = `https://api.github.com/repos/${REPO}/git/trees/${BRANCH}?recursive=1`;
363
+ const res = await fetch(url, {
364
+ headers: { Accept: "application/vnd.github+json" }
365
+ });
366
+ if (!res.ok) {
367
+ return { ok: false, error: `Failed to fetch repo tree: HTTP ${res.status}` };
368
+ }
369
+ const data = await res.json();
370
+ if (data.truncated) {
371
+ return { ok: false, error: "GitHub tree response was truncated; skill bundle too large to enumerate" };
372
+ }
373
+ const prefix = `skills/${skillName}/`;
374
+ const entries = data.tree.filter((e) => e.path.startsWith(prefix) && (e.type === "blob" || e.type === "tree")).map((e) => ({ path: e.path.slice(prefix.length), type: e.type }));
375
+ return { ok: true, entries };
376
+ }
377
+ function resolveBundlePaths(entries, bundle) {
378
+ const excludes = bundle.exclude ?? [];
379
+ const isExcluded = (p) => excludes.some((ex) => p === ex || p.startsWith(ex));
380
+ const dirs = new Set;
381
+ const files = new Set;
382
+ for (const entry of entries) {
383
+ if (entry.type === "tree")
384
+ dirs.add(entry.path);
385
+ else
386
+ files.add(entry.path);
387
+ }
388
+ const result = new Set;
389
+ for (const include of bundle.include) {
390
+ if (dirs.has(include)) {
391
+ const prefix = include.endsWith("/") ? include : include + "/";
392
+ for (const f of files) {
393
+ if (f.startsWith(prefix) && !isExcluded(f))
394
+ result.add(f);
395
+ }
396
+ } else if (files.has(include)) {
397
+ if (!isExcluded(include))
398
+ result.add(include);
399
+ }
400
+ }
401
+ return [...result].sort();
402
+ }
323
403
 
324
404
  // src/cli/adapters/claude.ts
325
- import { existsSync as existsSync2, mkdirSync, rmSync, unlinkSync } from "node:fs";
405
+ import { chmodSync, existsSync as existsSync2, mkdirSync, rmSync, statSync, unlinkSync } from "node:fs";
326
406
  import { dirname, join as join2 } from "node:path";
407
+ function shouldBeExecutable(relPath, content) {
408
+ if (relPath.endsWith(".sh"))
409
+ return true;
410
+ if (relPath.startsWith("bin/") || relPath.includes("/bin/"))
411
+ return true;
412
+ if (content.startsWith("#!"))
413
+ return true;
414
+ return false;
415
+ }
416
+ function runScript(scriptPath, cwd) {
417
+ if (!existsSync2(scriptPath))
418
+ return { ok: true, code: 0 };
419
+ const result = Bun.spawnSync(["bash", scriptPath], {
420
+ cwd,
421
+ stdout: "inherit",
422
+ stderr: "inherit"
423
+ });
424
+ return { ok: result.exitCode === 0, code: result.exitCode ?? 1 };
425
+ }
327
426
  function matchesSkillDirective(command, skillName) {
328
427
  return command.includes(`Activate ${skillName} skill`);
329
428
  }
@@ -394,6 +493,21 @@ var claudeAdapter = {
394
493
  }
395
494
  }
396
495
  }
496
+ if (config.bundleRoot && manifest.bundle) {
497
+ const promptPath = manifest.files?.prompt;
498
+ for (const [relPath, content] of files) {
499
+ if (relPath === promptPath)
500
+ continue;
501
+ const targetRel = join2(config.bundleRoot, relPath);
502
+ const targetPath = join2(cwd, targetRel);
503
+ mkdirSync(dirname(targetPath), { recursive: true });
504
+ await Bun.write(targetPath, content);
505
+ if (shouldBeExecutable(relPath, content)) {
506
+ chmodSync(targetPath, 493);
507
+ }
508
+ installed.push(targetRel);
509
+ }
510
+ }
397
511
  if (config.mcpServers) {
398
512
  const settingsPath = join2(cwd, ".claude/settings.json");
399
513
  let settings = {};
@@ -416,22 +530,49 @@ var claudeAdapter = {
416
530
  installed.push(".claude/settings.json");
417
531
  }
418
532
  }
533
+ if (config.postinstall && config.bundleRoot) {
534
+ const scriptPath = join2(cwd, config.bundleRoot, config.postinstall);
535
+ const result = runScript(scriptPath, join2(cwd, config.bundleRoot));
536
+ if (!result.ok) {
537
+ console.error(`postinstall script '${config.postinstall}' exited with code ${result.code}`);
538
+ }
539
+ }
419
540
  return installed;
420
541
  },
421
542
  async remove(cwd, manifest, installedFiles) {
422
543
  const config = manifest.install.claude;
423
544
  if (!config)
424
545
  return;
546
+ if (config.postremove && config.bundleRoot) {
547
+ const scriptPath = join2(cwd, config.bundleRoot, config.postremove);
548
+ const result = runScript(scriptPath, join2(cwd, config.bundleRoot));
549
+ if (!result.ok) {
550
+ console.error(`postremove script '${config.postremove}' exited with code ${result.code}`);
551
+ }
552
+ }
425
553
  for (const file of installedFiles) {
426
554
  if (file === ".claude/settings.json")
427
555
  continue;
428
556
  const fullPath = join2(cwd, file);
429
557
  if (existsSync2(fullPath)) {
430
- unlinkSync(fullPath);
431
- const dir = dirname(fullPath);
432
558
  try {
433
- rmSync(dir, { recursive: false });
559
+ const stat = statSync(fullPath);
560
+ if (stat.isDirectory()) {
561
+ rmSync(fullPath, { recursive: true, force: true });
562
+ } else {
563
+ unlinkSync(fullPath);
564
+ }
434
565
  } catch {}
566
+ let dir = dirname(fullPath);
567
+ const stopAt = config.bundleRoot ? join2(cwd, config.bundleRoot, "..") : cwd;
568
+ while (dir !== stopAt && dir !== "/" && dir.startsWith(cwd)) {
569
+ try {
570
+ rmSync(dir, { recursive: false });
571
+ } catch {
572
+ break;
573
+ }
574
+ dir = dirname(dir);
575
+ }
435
576
  }
436
577
  }
437
578
  if (config.mcpServers) {
@@ -768,8 +909,64 @@ async function fetchAllSkillFiles2(skillName, manifest) {
768
909
  files.set(supportFile, result.content);
769
910
  }
770
911
  }
912
+ if (manifest.bundle) {
913
+ const treeResult = await fetchSkillTree2(skillName);
914
+ if (!treeResult.ok)
915
+ return { error: treeResult.error };
916
+ const bundlePaths = resolveBundlePaths2(treeResult.entries, manifest.bundle);
917
+ for (const relPath of bundlePaths) {
918
+ if (files.has(relPath))
919
+ continue;
920
+ const result = await fetchSkillFile2(skillName, relPath);
921
+ if (!result.ok)
922
+ return { error: result.error };
923
+ files.set(relPath, result.content);
924
+ }
925
+ }
771
926
  return files;
772
927
  }
928
+ async function fetchSkillTree2(skillName) {
929
+ const url = `https://api.github.com/repos/${REPO2}/git/trees/${BRANCH2}?recursive=1`;
930
+ const res = await fetch(url, {
931
+ headers: { Accept: "application/vnd.github+json" }
932
+ });
933
+ if (!res.ok) {
934
+ return { ok: false, error: `Failed to fetch repo tree: HTTP ${res.status}` };
935
+ }
936
+ const data = await res.json();
937
+ if (data.truncated) {
938
+ return { ok: false, error: "GitHub tree response was truncated; skill bundle too large to enumerate" };
939
+ }
940
+ const prefix = `skills/${skillName}/`;
941
+ const entries = data.tree.filter((e) => e.path.startsWith(prefix) && (e.type === "blob" || e.type === "tree")).map((e) => ({ path: e.path.slice(prefix.length), type: e.type }));
942
+ return { ok: true, entries };
943
+ }
944
+ function resolveBundlePaths2(entries, bundle) {
945
+ const excludes = bundle.exclude ?? [];
946
+ const isExcluded = (p) => excludes.some((ex) => p === ex || p.startsWith(ex));
947
+ const dirs = new Set;
948
+ const files = new Set;
949
+ for (const entry of entries) {
950
+ if (entry.type === "tree")
951
+ dirs.add(entry.path);
952
+ else
953
+ files.add(entry.path);
954
+ }
955
+ const result = new Set;
956
+ for (const include of bundle.include) {
957
+ if (dirs.has(include)) {
958
+ const prefix = include.endsWith("/") ? include : include + "/";
959
+ for (const f of files) {
960
+ if (f.startsWith(prefix) && !isExcluded(f))
961
+ result.add(f);
962
+ }
963
+ } else if (files.has(include)) {
964
+ if (!isExcluded(include))
965
+ result.add(include);
966
+ }
967
+ }
968
+ return [...result].sort();
969
+ }
773
970
 
774
971
  // src/cli/commands/remove.ts
775
972
  async function removeSkill(cwd, skillName) {
@@ -1081,6 +1278,18 @@ async function checkForUpdates(cwd) {
1081
1278
  }
1082
1279
  }
1083
1280
 
1281
+ // src/cli/lockfile.ts
1282
+ import { join as join8 } from "node:path";
1283
+ var LOCKFILE_NAME2 = ".agent-skills.lock";
1284
+ async function readLockfile2(cwd) {
1285
+ const path = join8(cwd, LOCKFILE_NAME2);
1286
+ const file = Bun.file(path);
1287
+ if (!await file.exists()) {
1288
+ return { skills: {} };
1289
+ }
1290
+ return file.json();
1291
+ }
1292
+
1084
1293
  // src/cli/types.ts
1085
1294
  var TOOL_NAMES2 = ["claude", "cursor", "codex", "gemini"];
1086
1295
 
@@ -2953,8 +3162,19 @@ async function pickActivation(skillName, modes) {
2953
3162
 
2954
3163
  // src/cli/index.ts
2955
3164
  import { mkdirSync as mkdirSync4 } from "fs";
2956
- import { join as join8 } from "path";
3165
+ import { join as join9 } from "path";
2957
3166
  import { homedir } from "os";
3167
+ async function otherScopeSkillCount(currentDir) {
3168
+ const home = homedir();
3169
+ const otherDir = currentDir === home ? process.cwd() : home;
3170
+ if (otherDir === currentDir)
3171
+ return { scope: "global", count: 0 };
3172
+ const lockfile = await readLockfile2(otherDir);
3173
+ return {
3174
+ scope: otherDir === home ? "global" : "local",
3175
+ count: Object.keys(lockfile.skills).length
3176
+ };
3177
+ }
2958
3178
  function resolveInstallDir(flags) {
2959
3179
  if (flags.global !== undefined || flags.g !== undefined) {
2960
3180
  return homedir();
@@ -3046,7 +3266,7 @@ async function resolveTargetTools(installDir, isGlobal, flags) {
3046
3266
  for (const tool of picked) {
3047
3267
  const dir = TOOL_DIRS[tool];
3048
3268
  if (dir)
3049
- mkdirSync4(join8(installDir, dir), { recursive: true });
3269
+ mkdirSync4(join9(installDir, dir), { recursive: true });
3050
3270
  }
3051
3271
  return picked;
3052
3272
  }
@@ -3160,18 +3380,25 @@ Installing ${names.length} skills...
3160
3380
  `);
3161
3381
  const results = await updateAllSkills(updateDir);
3162
3382
  if (results.length === 0) {
3163
- console.log("No skills installed.");
3164
- break;
3165
- }
3166
- for (const r of results) {
3167
- if (!r.ok) {
3168
- console.error(` \u2717 ${r.name}: ${r.error}`);
3169
- } else if (r.from === r.to) {
3170
- console.log(` \u2298 ${r.name} already up to date (v${r.to})`);
3171
- } else {
3172
- console.log(` \u2713 ${r.name} v${r.from} \u2192 v${r.to}`);
3383
+ console.log("No skills installed in this scope.");
3384
+ } else {
3385
+ for (const r of results) {
3386
+ if (!r.ok) {
3387
+ console.error(` \u2717 ${r.name}: ${r.error}`);
3388
+ } else if (r.from === r.to) {
3389
+ console.log(` \u2298 ${r.name} already up to date (v${r.to})`);
3390
+ } else {
3391
+ console.log(` \u2713 ${r.name} v${r.from} \u2192 v${r.to}`);
3392
+ }
3173
3393
  }
3174
3394
  }
3395
+ const other = await otherScopeSkillCount(updateDir);
3396
+ if (other.count > 0) {
3397
+ const command2 = other.scope === "global" ? "agent-skills update --all -g" : "agent-skills update --all";
3398
+ const noun = other.count === 1 ? "skill" : "skills";
3399
+ console.log(`
3400
+ Note: ${other.count} ${noun} installed in the ${other.scope} scope. Run \`${command2}\` to update those too.`);
3401
+ }
3175
3402
  break;
3176
3403
  }
3177
3404
  const skillName = args[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iceinvein/agent-skills",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Install agent skills into AI coding tools",
5
5
  "author": "iceinvein",
6
6
  "license": "MIT",
@@ -19,6 +19,7 @@
19
19
  "skill:bump": "bun run src/cli/index.ts bump",
20
20
  "skill:bump:all": "bun run src/cli/index.ts bump --all",
21
21
  "skill:bump:check": "bun run src/cli/index.ts bump --all --dry-run",
22
+ "build:index": "bun run scripts/build-index.ts",
22
23
  "release": "bash scripts/release.sh"
23
24
  },
24
25
  "keywords": [
package/skills/index.json CHANGED
@@ -1,18 +1,12 @@
1
1
  [
2
2
  {
3
- "name": "design-review",
4
- "description": "Brooks-inspired design integrity reviewtests conceptual integrity, constraint exploitation, removal discipline, and scope control",
5
- "type": "prompt",
6
- "version": "1.0.0",
7
- "applies": ["any"],
8
- "quick": false
9
- },
10
- {
11
- "name": "codebase-architecture",
12
- "description": "Architecture review for existing codebases or structured design for new projects, with patterns reference",
3
+ "name": "bounded-context-auditor",
4
+ "description": "Evans-inspired bounded context analysisdetect linguistic fractures, draw context maps, identify leaking language and shared model pollution",
13
5
  "type": "prompt",
14
6
  "version": "1.0.0",
15
- "applies": ["any", "architecture"],
7
+ "applies": [
8
+ "domain"
9
+ ],
16
10
  "quick": false
17
11
  },
18
12
  {
@@ -22,107 +16,124 @@
22
16
  "version": "1.0.0"
23
17
  },
24
18
  {
25
- "name": "contract-enforcer",
26
- "description": "Meyer-inspired Design by Contract preconditions, postconditions, invariants, and failure contracts for non-trivial functions",
19
+ "name": "codebase-architecture",
20
+ "description": "Architecture review for existing codebases or structured design for new projects, with patterns reference",
27
21
  "type": "prompt",
28
22
  "version": "1.0.0",
29
- "applies": ["any"],
23
+ "applies": [
24
+ "any",
25
+ "architecture"
26
+ ],
30
27
  "quick": false
31
28
  },
32
29
  {
33
- "name": "complexity-accountant",
34
- "description": "Ousterhout-inspired complexity analysis — deep vs shallow modules, complexity budget, justified abstractions",
30
+ "name": "cognitive-load-auditor",
31
+ "description": "Jeff Johnson-inspired cognitive load analysis — evaluate UI against Miller's Law, Hick's Law, Fitts's Law, and working memory limits",
35
32
  "type": "prompt",
36
33
  "version": "1.0.0",
37
- "applies": ["any"],
34
+ "applies": [
35
+ "ui"
36
+ ],
38
37
  "quick": false
39
38
  },
40
39
  {
41
- "name": "module-secret-auditor",
42
- "description": "Parnas-inspired information hiding analysis — module boundaries drawn by change-reason, not by noun or technical layer",
43
- "type": "prompt",
44
- "version": "1.0.0",
45
- "applies": ["architecture"],
46
- "quick": true
47
- },
48
- {
49
- "name": "seam-finder",
50
- "description": "Feathers-inspired legacy code modification — find seams, make minimal incisions, preserve existing behavior",
40
+ "name": "cohesion-analyzer",
41
+ "description": "Constantine & Yourdon-inspired cohesion analysis — classify module focus on the 7-level spectrum, find split lines, reduce mixed responsibilities",
51
42
  "type": "prompt",
52
43
  "version": "1.0.0",
53
- "applies": ["legacy"],
44
+ "applies": [
45
+ "architecture"
46
+ ],
54
47
  "quick": true
55
48
  },
56
49
  {
57
- "name": "simplicity-razor",
58
- "description": "Hickey-inspired simplicity analysis — simple vs easy, complecting detection, strand decomposition",
50
+ "name": "complexity-accountant",
51
+ "description": "Ousterhout-inspired complexity analysis — deep vs shallow modules, complexity budget, justified abstractions",
59
52
  "type": "prompt",
60
53
  "version": "1.0.0",
61
- "applies": ["any"],
54
+ "applies": [
55
+ "any"
56
+ ],
62
57
  "quick": false
63
58
  },
64
59
  {
65
- "name": "coupling-auditor",
66
- "description": "Constantine & Yourdon-inspired coupling analysis — classify, measure, and reduce interdependence between modules",
60
+ "name": "composability-auditor",
61
+ "description": "Unix Philosophy-inspired composability analysis — identify reuse blockers, decompose self-sufficient units into composable pieces with standard interfaces",
67
62
  "type": "prompt",
68
63
  "version": "1.0.0",
69
- "applies": ["architecture"],
70
- "quick": true
64
+ "applies": [
65
+ "any"
66
+ ],
67
+ "quick": false
71
68
  },
72
69
  {
73
- "name": "evolution-analyzer",
74
- "description": "Lehman-inspired software evolution analysistrajectory assessment, debt visibility, and change impact on system health",
70
+ "name": "contract-enforcer",
71
+ "description": "Meyer-inspired Design by Contractpreconditions, postconditions, invariants, and failure contracts for non-trivial functions",
75
72
  "type": "prompt",
76
73
  "version": "1.0.0",
77
- "applies": ["any"],
74
+ "applies": [
75
+ "any"
76
+ ],
78
77
  "quick": false
79
78
  },
80
79
  {
81
- "name": "error-strategist",
82
- "description": "Duffy & Abrahams-inspired error handling — classify errors (bug/recoverable/fatal), assign safety guarantees, design recovery boundaries",
80
+ "name": "coupling-auditor",
81
+ "description": "Constantine & Yourdon-inspired coupling analysis — classify, measure, and reduce interdependence between modules",
83
82
  "type": "prompt",
84
83
  "version": "1.0.0",
85
- "applies": ["errors"],
86
- "quick": false
84
+ "applies": [
85
+ "architecture"
86
+ ],
87
+ "quick": true
87
88
  },
88
89
  {
89
- "name": "rams-design-audit",
90
- "description": "Dieter Rams-inspired design auditevery visual element must earn its presence, less but better, clarity through restraint",
90
+ "name": "cqs-auditor",
91
+ "description": "Meyer-inspired Command-Query Separationclassify functions as commands or queries, detect mixed violations, separate side effects from return values",
91
92
  "type": "prompt",
92
93
  "version": "1.0.0",
93
- "applies": ["ui"],
94
+ "applies": [
95
+ "architecture"
96
+ ],
94
97
  "quick": true
95
98
  },
96
99
  {
97
- "name": "cognitive-load-auditor",
98
- "description": "Jeff Johnson-inspired cognitive load analysis — evaluate UI against Miller's Law, Hick's Law, Fitts's Law, and working memory limits",
100
+ "name": "demeter-enforcer",
101
+ "description": "Lieberherr-inspired Law of Demeter analysis — detect chain violations, parameter drilling, and hidden traversal; fix with tell-don't-ask or parameter narrowing",
99
102
  "type": "prompt",
100
103
  "version": "1.0.0",
101
- "applies": ["ui"],
102
- "quick": false
104
+ "applies": [
105
+ "architecture"
106
+ ],
107
+ "quick": true
103
108
  },
104
109
  {
105
- "name": "gestalt-reviewer",
106
- "description": "Gestalt-inspired visual perception auditproximity, similarity, closure, continuity, and figure-ground analysis for UI layouts",
110
+ "name": "dependency-direction-auditor",
111
+ "description": "Martin-inspired dependency direction analysistrace imports across layers, classify violations by severity, recommend inversion",
107
112
  "type": "prompt",
108
113
  "version": "1.0.0",
109
- "applies": ["ui"],
114
+ "applies": [
115
+ "architecture"
116
+ ],
110
117
  "quick": true
111
118
  },
112
119
  {
113
- "name": "integration-pattern-auditor",
114
- "description": "Hohpe & Woolf-inspired messaging analysisname the integration pattern, verify delivery guarantees, identify missing infrastructure",
120
+ "name": "design-review",
121
+ "description": "Brooks-inspired design integrity review tests conceptual integrity, constraint exploitation, removal discipline, and scope control",
115
122
  "type": "prompt",
116
123
  "version": "1.0.0",
117
- "applies": ["integration"],
124
+ "applies": [
125
+ "any"
126
+ ],
118
127
  "quick": false
119
128
  },
120
129
  {
121
- "name": "unidirectional-flow-enforcer",
122
- "description": "Elm Architecture-inspired data flow analysis enforce unidirectional state flow, detect bidirectional mutations, trace state lifecycle in UI applications",
130
+ "name": "error-strategist",
131
+ "description": "Duffy & Abrahams-inspired error handlingclassify errors (bug/recoverable/fatal), assign safety guarantees, design recovery boundaries",
123
132
  "type": "prompt",
124
133
  "version": "1.0.0",
125
- "applies": ["ui"],
134
+ "applies": [
135
+ "errors"
136
+ ],
126
137
  "quick": false
127
138
  },
128
139
  {
@@ -130,129 +141,148 @@
130
141
  "description": "Evans/Vernon/Dahan-inspired event design — domain-meaningful naming, fat payloads, schema evolution, and the domain expert test",
131
142
  "type": "prompt",
132
143
  "version": "1.0.0",
133
- "applies": ["integration", "domain"],
144
+ "applies": [
145
+ "integration",
146
+ "domain"
147
+ ],
134
148
  "quick": false
135
149
  },
136
150
  {
137
- "name": "dependency-direction-auditor",
138
- "description": "Martin-inspired dependency direction analysis — trace imports across layers, classify violations by severity, recommend inversion",
139
- "type": "prompt",
140
- "version": "1.0.0",
141
- "applies": ["architecture"],
142
- "quick": true
143
- },
144
- {
145
- "name": "cohesion-analyzer",
146
- "description": "Constantine & Yourdon-inspired cohesion analysis — classify module focus on the 7-level spectrum, find split lines, reduce mixed responsibilities",
151
+ "name": "evolution-analyzer",
152
+ "description": "Lehman-inspired software evolution analysis — trajectory assessment, debt visibility, and change impact on system health",
147
153
  "type": "prompt",
148
154
  "version": "1.0.0",
149
- "applies": ["architecture"],
150
- "quick": true
155
+ "applies": [
156
+ "any"
157
+ ],
158
+ "quick": false
151
159
  },
152
160
  {
153
- "name": "demeter-enforcer",
154
- "description": "Lieberherr-inspired Law of Demeter analysis detect chain violations, parameter drilling, and hidden traversal; fix with tell-don't-ask or parameter narrowing",
161
+ "name": "gestalt-reviewer",
162
+ "description": "Gestalt-inspired visual perception auditproximity, similarity, closure, continuity, and figure-ground analysis for UI layouts",
155
163
  "type": "prompt",
156
164
  "version": "1.0.0",
157
- "applies": ["architecture"],
165
+ "applies": [
166
+ "ui"
167
+ ],
158
168
  "quick": true
159
169
  },
160
170
  {
161
- "name": "bounded-context-auditor",
162
- "description": "Evans-inspired bounded context analysis — detect linguistic fractures, draw context maps, identify leaking language and shared model pollution",
171
+ "name": "idempotency-guardian",
172
+ "description": "Helland-inspired idempotency analysis — classify mutation points, check protection mechanisms, evaluate side effect safety for retry-safe systems",
163
173
  "type": "prompt",
164
174
  "version": "1.0.0",
165
- "applies": ["domain"],
175
+ "applies": [
176
+ "integration"
177
+ ],
166
178
  "quick": false
167
179
  },
168
180
  {
169
- "name": "port-adapter-auditor",
170
- "description": "Cockburn-inspired hexagonal architecture analysis identify ports and adapters, classify boundary health, ensure core testability and swappability",
181
+ "name": "improve-my-codebase",
182
+ "description": "Orchestrator skill that runs every applicable audit skill in parallel and produces a prioritized, convergence-ranked improvement report",
171
183
  "type": "prompt",
172
- "version": "1.0.0",
173
- "applies": ["architecture"],
174
- "quick": false
184
+ "version": "1.0.1"
175
185
  },
176
186
  {
177
- "name": "idempotency-guardian",
178
- "description": "Helland-inspired idempotency analysis — classify mutation points, check protection mechanisms, evaluate side effect safety for retry-safe systems",
187
+ "name": "integration-pattern-auditor",
188
+ "description": "Hohpe & Woolf-inspired messaging analysis — name the integration pattern, verify delivery guarantees, identify missing infrastructure",
179
189
  "type": "prompt",
180
190
  "version": "1.0.0",
181
- "applies": ["integration"],
191
+ "applies": [
192
+ "integration"
193
+ ],
182
194
  "quick": false
183
195
  },
184
196
  {
185
- "name": "type-driven-designer",
186
- "description": "Wlaschin & Minsky-inspired type design make illegal states unrepresentable through branded types, discriminated unions, and domain-encoded constraints",
197
+ "name": "magpie",
198
+ "description": "Interactive PR review pipeline. Runs five parallel specialist subagents (security, bugs, performance, code-smells, architecture), dedupes findings, applies a critic rubric, peer-reviews via codex exec, and serves an interactive HTML report for selecting findings to post via gh. Bundles a Bun CLI installed onto PATH via the skill's postinstall step. Use when the user asks to review a GitHub pull request.",
187
199
  "type": "prompt",
188
- "version": "1.0.0",
189
- "applies": ["any"],
190
- "quick": false
200
+ "version": "0.3.0"
191
201
  },
192
202
  {
193
- "name": "cqs-auditor",
194
- "description": "Meyer-inspired Command-Query Separationclassify functions as commands or queries, detect mixed violations, separate side effects from return values",
203
+ "name": "module-secret-auditor",
204
+ "description": "Parnas-inspired information hiding analysis module boundaries drawn by change-reason, not by noun or technical layer",
195
205
  "type": "prompt",
196
206
  "version": "1.0.0",
197
- "applies": ["architecture"],
207
+ "applies": [
208
+ "architecture"
209
+ ],
198
210
  "quick": true
199
211
  },
200
212
  {
201
- "name": "composability-auditor",
202
- "description": "Unix Philosophy-inspired composability analysis — identify reuse blockers, decompose self-sufficient units into composable pieces with standard interfaces",
213
+ "name": "port-adapter-auditor",
214
+ "description": "Cockburn-inspired hexagonal architecture analysis — identify ports and adapters, classify boundary health, ensure core testability and swappability",
203
215
  "type": "prompt",
204
216
  "version": "1.0.0",
205
- "applies": ["any"],
217
+ "applies": [
218
+ "architecture"
219
+ ],
206
220
  "quick": false
207
221
  },
208
222
  {
209
- "name": "temporal-coupling-detector",
210
- "description": "Hidden ordering dependency analysisdetect two-phase init, method order dependencies, invisible preconditions, and resource lifecycle violations; fix with types, parameters, and factory patterns",
223
+ "name": "rams-design-audit",
224
+ "description": "Dieter Rams-inspired design auditevery visual element must earn its presence, less but better, clarity through restraint",
211
225
  "type": "prompt",
212
226
  "version": "1.0.0",
213
- "applies": ["any"],
227
+ "applies": [
228
+ "ui"
229
+ ],
214
230
  "quick": true
215
231
  },
216
232
  {
217
- "name": "terse",
218
- "description": "Professional output compressionproper grammar, no fluff, ~50-60% fewer tokens. Three levels: clean, tight, sharp.",
219
- "type": "prompt",
220
- "version": "1.1.0"
221
- },
222
- {
223
- "name": "cover-letter",
224
- "description": "Cover letter suite router. Routes to write, audit, rewrite, or persona subcommands; shares conventions for file I/O, output formats, and writing principles.",
233
+ "name": "seam-finder",
234
+ "description": "Feathers-inspired legacy code modification find seams, make minimal incisions, preserve existing behavior",
225
235
  "type": "prompt",
226
- "version": "1.0.0"
236
+ "version": "1.0.0",
237
+ "applies": [
238
+ "legacy"
239
+ ],
240
+ "quick": true
227
241
  },
228
242
  {
229
- "name": "cover-letter-write",
230
- "description": "Generate a cover letter from resume + job description (PDF, DOCX, markdown, URL). Outputs markdown, DOCX, and PDF. Enforces human prose, evidence alignment, and JD coverage without keyword stuffing.",
243
+ "name": "simplicity-razor",
244
+ "description": "Hickey-inspired simplicity analysis simple vs easy, complecting detection, strand decomposition",
231
245
  "type": "prompt",
232
- "version": "1.0.0"
246
+ "version": "1.0.0",
247
+ "applies": [
248
+ "any"
249
+ ],
250
+ "quick": false
233
251
  },
234
252
  {
235
- "name": "cover-letter-audit",
236
- "description": "Score a cover letter on 100 points across content, structure, voice, and correctness. Detects AI-generated-content signals (burstiness, AI phrases, TTR, em-dashes) and produces prioritized fixes.",
253
+ "name": "temporal-coupling-detector",
254
+ "description": "Hidden ordering dependency analysis detect two-phase init, method order dependencies, invisible preconditions, and resource lifecycle violations; fix with types, parameters, and factory patterns",
237
255
  "type": "prompt",
238
- "version": "1.0.0"
256
+ "version": "1.0.0",
257
+ "applies": [
258
+ "any"
259
+ ],
260
+ "quick": true
239
261
  },
240
262
  {
241
- "name": "cover-letter-rewrite",
242
- "description": "Revise an existing cover letter with audit-driven targeted fixes. Focus modes: humanize, align, tighten, structure, tone. Preserves voice where it already works.",
263
+ "name": "terse",
264
+ "description": "Professional output compression. Cuts ~20-30% of output tokens with proper grammar and semantic accuracy. Three levels: clean, tight, sharp.",
243
265
  "type": "prompt",
244
- "version": "1.0.0"
266
+ "version": "1.2.0"
245
267
  },
246
268
  {
247
- "name": "cover-letter-persona",
248
- "description": "Manage reusable writing personas for cover letters using the NNGroup 4-dimension tone framework. Locks in tone, sentence style, and vocabulary across applications.",
269
+ "name": "type-driven-designer",
270
+ "description": "Wlaschin & Minsky-inspired type design make illegal states unrepresentable through branded types, discriminated unions, and domain-encoded constraints",
249
271
  "type": "prompt",
250
- "version": "1.0.0"
272
+ "version": "1.0.0",
273
+ "applies": [
274
+ "any"
275
+ ],
276
+ "quick": false
251
277
  },
252
278
  {
253
- "name": "improve-my-codebase",
254
- "description": "Orchestrator skill that runs every applicable audit skill in parallel and produces a prioritized, convergence-ranked improvement report",
279
+ "name": "unidirectional-flow-enforcer",
280
+ "description": "Elm Architecture-inspired data flow analysis enforce unidirectional state flow, detect bidirectional mutations, trace state lifecycle in UI applications",
255
281
  "type": "prompt",
256
- "version": "1.0.0"
282
+ "version": "1.0.0",
283
+ "applies": [
284
+ "ui"
285
+ ],
286
+ "quick": false
257
287
  }
258
288
  ]
@@ -15,15 +15,13 @@ Given a GitHub PR number, dispatches five specialist subagents in parallel (secu
15
15
 
16
16
  ## Install
17
17
 
18
- This skill ships in two parts: the prompt (SKILL.md) and a companion Bun CLI (bin + scripts). Both are needed, and `install.sh` handles both in one step.
19
-
20
18
  ```
21
- ./install.sh
19
+ bunx @iceinvein/agent-skills install magpie -g
22
20
  ```
23
21
 
24
- That symlinks the full skill source tree (bin/, scripts/, templates/, fixtures/, SKILL.md, skill.json) into `~/.claude/skills/magpie/`, then symlinks `bin/magpie` onto your PATH (preferring `/usr/local/bin`, falling back to `~/.local/bin`). Re-run any time to refresh the links.
22
+ This skill ships in two parts: the prompt (SKILL.md) and a companion Bun CLI (bin + scripts). The agent-skills installer writes both into `~/.claude/skills/magpie/` and then runs the bundled `install.sh` as a postinstall step, which symlinks `bin/magpie` onto your PATH (preferring `/usr/local/bin`, falling back to `~/.local/bin`). Removing the skill with `agent-skills remove magpie -g` runs `uninstall.sh` first to undo the PATH symlink.
25
23
 
26
- Once an agent-skills registry entry exists for `magpie`, the prompt half can also be installed via `bunx @iceinvein/agent-skills install magpie`, but the CLI still requires `./install.sh` (or an equivalent manual symlink) because the registry only ships SKILL.md.
24
+ If you cloned this repo and want to run from source, you can also invoke `./install.sh` directly: it does the PATH-link step against the local source tree.
27
25
 
28
26
  ## Use
29
27
 
@@ -57,11 +55,12 @@ The fixture lives at `fixtures/example-pr/` (pr.json + findings.final.json + pos
57
55
 
58
56
  - `SKILL.md` is the agent-facing prompt; installed by the agent-skills CLI.
59
57
  - `skill.json` is the agent-skills manifest.
60
- - `bin/magpie` is the CLI invoked by the agent during stages; installed by `install.sh`.
58
+ - `bin/magpie` is the CLI invoked by the agent during stages; symlinked onto PATH by `install.sh`.
61
59
  - `scripts/` holds the implementation (server, dedupe, render, setup, cleanup, etc.).
62
60
  - `templates/styles.css` is the HTML report stylesheet.
63
- - `fixtures/` holds canned PR data for tests.
64
- - `install.sh` symlinks the skill directory into `~/.claude/skills/`.
61
+ - `fixtures/` holds canned PR data for tests (not shipped to users via the registry).
62
+ - `install.sh` is run as a postinstall step by the agent-skills installer; symlinks `bin/magpie` onto PATH and records the location.
63
+ - `uninstall.sh` is run as a postremove step; removes the PATH symlink if it still points back into this bundle.
65
64
 
66
65
  ## Design docs
67
66
 
@@ -1,25 +1,20 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
+ # This script is run by agent-skills as a postinstall step after the magpie
5
+ # skill bundle has been written to disk. Its only job is to put the magpie
6
+ # CLI on PATH and record where it linked, so uninstall.sh can clean up.
7
+
4
8
  SOURCE_DIR="$(cd "$(dirname "$0")" && pwd)"
5
- TARGET="$HOME/.claude/skills/magpie"
9
+ BIN_SOURCE="$SOURCE_DIR/bin/magpie"
10
+ STATE_FILE="$SOURCE_DIR/.installed-cli-path"
6
11
 
7
- if [ -e "$TARGET" ] && [ ! -L "$TARGET" ]; then
8
- echo "Refusing to overwrite non-symlink at $TARGET"
9
- echo "Remove or move it manually, then re-run."
12
+ if [ ! -f "$BIN_SOURCE" ]; then
13
+ echo "magpie postinstall: bin/magpie missing at $BIN_SOURCE" >&2
10
14
  exit 1
11
15
  fi
12
16
 
13
- mkdir -p "$HOME/.claude/skills"
14
- ln -snf "$SOURCE_DIR" "$TARGET"
15
-
16
- echo "Installed magpie skill at $TARGET"
17
-
18
- # Install the magpie CLI onto PATH. Try /usr/local/bin first (no sudo
19
- # needed if the user owns it, common on Homebrew Macs), fall back to
20
- # ~/.local/bin (which most modern shells already have on PATH; we print a
21
- # nudge if it isn't).
22
- BIN_SOURCE="$SOURCE_DIR/bin/magpie"
17
+ chmod +x "$BIN_SOURCE" || true
23
18
 
24
19
  install_link() {
25
20
  local dest_dir="$1"
@@ -28,11 +23,9 @@ install_link() {
28
23
  if [ ! -d "$dest_dir" ]; then
29
24
  mkdir -p "$dest_dir" 2>/dev/null || return 1
30
25
  fi
31
-
32
26
  if [ ! -w "$dest_dir" ]; then
33
27
  return 1
34
28
  fi
35
-
36
29
  if [ -e "$dest" ] && [ ! -L "$dest" ]; then
37
30
  echo " skipping $dest_dir (a non-symlink magpie already exists there)"
38
31
  return 2
@@ -40,23 +33,21 @@ install_link() {
40
33
 
41
34
  ln -snf "$BIN_SOURCE" "$dest"
42
35
  echo " linked $dest -> $BIN_SOURCE"
43
- CLI_INSTALL_DIR="$dest_dir"
36
+ printf '%s\n' "$dest" > "$STATE_FILE"
44
37
  return 0
45
38
  }
46
39
 
47
- echo ""
48
40
  echo "Installing magpie CLI on PATH:"
49
41
 
50
42
  CLI_INSTALL_DIR=""
51
43
  if install_link "/usr/local/bin"; then
52
- :
44
+ CLI_INSTALL_DIR="/usr/local/bin"
53
45
  elif install_link "$HOME/.local/bin"; then
54
- :
46
+ CLI_INSTALL_DIR="$HOME/.local/bin"
55
47
  else
56
48
  echo " Could not write to /usr/local/bin or ~/.local/bin."
57
- echo " Add this to your shell rc instead:"
49
+ echo " Add this alias to your shell rc instead:"
58
50
  echo " alias magpie='bun $BIN_SOURCE.ts'"
59
- CLI_INSTALL_DIR=""
60
51
  fi
61
52
 
62
53
  if [ -n "$CLI_INSTALL_DIR" ]; then
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "magpie",
3
- "version": "0.2.1",
4
- "description": "Interactive PR review pipeline. Runs five parallel specialist subagents (security, bugs, performance, code-smells, architecture), dedupes findings, applies a critic rubric, peer-reviews via codex exec, and serves an interactive HTML report for selecting findings to post via gh. Bundles a Bun CLI installed separately via the skill's install.sh. Use when the user asks to review a GitHub pull request.",
3
+ "version": "0.3.0",
4
+ "description": "Interactive PR review pipeline. Runs five parallel specialist subagents (security, bugs, performance, code-smells, architecture), dedupes findings, applies a critic rubric, peer-reviews via codex exec, and serves an interactive HTML report for selecting findings to post via gh. Bundles a Bun CLI installed onto PATH via the skill's postinstall step. Use when the user asks to review a GitHub pull request.",
5
5
  "author": "iceinvein",
6
6
  "type": "prompt",
7
7
  "tools": [
@@ -10,9 +10,28 @@
10
10
  "files": {
11
11
  "prompt": "SKILL.md"
12
12
  },
13
+ "bundle": {
14
+ "include": [
15
+ "bin",
16
+ "scripts",
17
+ "templates",
18
+ "install.sh",
19
+ "uninstall.sh",
20
+ "package.json",
21
+ "bun.lock",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "exclude": [
26
+ "scripts/__tests__/"
27
+ ]
28
+ },
13
29
  "install": {
14
30
  "claude": {
15
- "prompt": ".claude/skills/magpie/SKILL.md"
31
+ "prompt": ".claude/skills/magpie/SKILL.md",
32
+ "bundleRoot": ".claude/skills/magpie",
33
+ "postinstall": "install.sh",
34
+ "postremove": "uninstall.sh"
16
35
  }
17
36
  }
18
37
  }
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Run by agent-skills as a postremove step before the magpie skill bundle is
5
+ # deleted. Reads the path recorded by install.sh and removes the PATH symlink
6
+ # if it still points back into this bundle.
7
+
8
+ SOURCE_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ STATE_FILE="$SOURCE_DIR/.installed-cli-path"
10
+
11
+ if [ ! -f "$STATE_FILE" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ CLI_PATH="$(head -n 1 "$STATE_FILE")"
16
+ [ -z "$CLI_PATH" ] && exit 0
17
+
18
+ if [ -L "$CLI_PATH" ]; then
19
+ TARGET="$(readlink "$CLI_PATH")"
20
+ case "$TARGET" in
21
+ "$SOURCE_DIR"/*)
22
+ rm -f "$CLI_PATH"
23
+ echo "Removed magpie CLI symlink at $CLI_PATH"
24
+ ;;
25
+ *)
26
+ echo "magpie uninstall: leaving $CLI_PATH alone (points to $TARGET, not this bundle)"
27
+ ;;
28
+ esac
29
+ fi
30
+
31
+ rm -f "$STATE_FILE"