@iceinvein/agent-skills 0.1.37 → 0.1.39

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.
Files changed (79) hide show
  1. package/package.json +1 -1
  2. package/skills/bounded-context-auditor/SKILL.md +15 -3
  3. package/skills/bounded-context-auditor/skill.json +1 -1
  4. package/skills/codebase-architecture/SKILL.md +4 -4
  5. package/skills/codebase-architecture/skill.json +1 -1
  6. package/skills/cognitive-load-auditor/SKILL.md +11 -9
  7. package/skills/cognitive-load-auditor/skill.json +1 -1
  8. package/skills/cohesion-analyzer/SKILL.md +1 -1
  9. package/skills/cohesion-analyzer/skill.json +1 -1
  10. package/skills/composability-auditor/SKILL.md +5 -5
  11. package/skills/composability-auditor/skill.json +1 -1
  12. package/skills/contract-enforcer/SKILL.md +5 -5
  13. package/skills/contract-enforcer/skill.json +1 -1
  14. package/skills/coupling-auditor/SKILL.md +2 -2
  15. package/skills/coupling-auditor/skill.json +1 -1
  16. package/skills/cover-letter/SKILL.md +18 -20
  17. package/skills/cover-letter/skill.json +7 -2
  18. package/skills/cover-letter-audit/SKILL.md +20 -20
  19. package/skills/cover-letter-audit/skill.json +7 -2
  20. package/skills/cover-letter-persona/SKILL.md +13 -13
  21. package/skills/cover-letter-persona/skill.json +7 -2
  22. package/skills/cover-letter-rewrite/SKILL.md +18 -16
  23. package/skills/cover-letter-rewrite/skill.json +7 -2
  24. package/skills/cover-letter-write/SKILL.md +25 -20
  25. package/skills/cover-letter-write/skill.json +7 -2
  26. package/skills/cqs-auditor/SKILL.md +19 -47
  27. package/skills/cqs-auditor/skill.json +1 -1
  28. package/skills/demeter-enforcer/SKILL.md +5 -5
  29. package/skills/demeter-enforcer/skill.json +1 -1
  30. package/skills/dependency-direction-auditor/SKILL.md +1 -1
  31. package/skills/dependency-direction-auditor/skill.json +1 -1
  32. package/skills/design-review/SKILL.md +6 -2
  33. package/skills/design-review/skill.json +1 -1
  34. package/skills/error-strategist/SKILL.md +3 -3
  35. package/skills/error-strategist/skill.json +1 -1
  36. package/skills/event-design-reviewer/SKILL.md +3 -3
  37. package/skills/event-design-reviewer/skill.json +1 -1
  38. package/skills/evolution-analyzer/SKILL.md +4 -3
  39. package/skills/evolution-analyzer/skill.json +1 -1
  40. package/skills/gestalt-reviewer/SKILL.md +8 -4
  41. package/skills/gestalt-reviewer/skill.json +1 -1
  42. package/skills/idempotency-guardian/SKILL.md +6 -6
  43. package/skills/idempotency-guardian/skill.json +1 -1
  44. package/skills/improve-my-codebase/CATALOGUE-FIELDS.md +2 -2
  45. package/skills/improve-my-codebase/SKILL.md +68 -27
  46. package/skills/improve-my-codebase/skill.json +1 -1
  47. package/skills/index.json +33 -33
  48. package/skills/integration-pattern-auditor/SKILL.md +2 -2
  49. package/skills/integration-pattern-auditor/skill.json +1 -1
  50. package/skills/magpie/README.md +3 -5
  51. package/skills/magpie/SKILL.md +39 -536
  52. package/skills/magpie/package.json +1 -1
  53. package/skills/magpie/references/critic.md +58 -0
  54. package/skills/magpie/references/peer-review.md +84 -0
  55. package/skills/magpie/references/specialists.md +391 -0
  56. package/skills/magpie/scripts/__tests__/helper.test.ts +40 -0
  57. package/skills/magpie/scripts/__tests__/skill-lint.test.ts +116 -28
  58. package/skills/magpie/scripts/__tests__/status-cmd.test.ts +13 -0
  59. package/skills/magpie/scripts/helper.js +24 -13
  60. package/skills/magpie/scripts/status-cmd.ts +10 -1
  61. package/skills/magpie/skill.json +2 -1
  62. package/skills/module-secret-auditor/SKILL.md +8 -5
  63. package/skills/module-secret-auditor/skill.json +1 -1
  64. package/skills/port-adapter-auditor/SKILL.md +3 -3
  65. package/skills/port-adapter-auditor/skill.json +1 -1
  66. package/skills/rams-design-audit/SKILL.md +4 -2
  67. package/skills/rams-design-audit/skill.json +1 -1
  68. package/skills/seam-finder/SKILL.md +2 -2
  69. package/skills/seam-finder/skill.json +1 -1
  70. package/skills/simplicity-razor/SKILL.md +4 -4
  71. package/skills/simplicity-razor/skill.json +1 -1
  72. package/skills/temporal-coupling-detector/SKILL.md +2 -2
  73. package/skills/temporal-coupling-detector/skill.json +1 -1
  74. package/skills/terse/SKILL.md +12 -7
  75. package/skills/terse/skill.json +1 -1
  76. package/skills/type-driven-designer/SKILL.md +8 -8
  77. package/skills/type-driven-designer/skill.json +1 -1
  78. package/skills/unidirectional-flow-enforcer/SKILL.md +2 -2
  79. package/skills/unidirectional-flow-enforcer/skill.json +1 -1
@@ -2,10 +2,35 @@ import { expect, test } from 'bun:test'
2
2
  import { readFile } from 'node:fs/promises'
3
3
 
4
4
  const SKILL = new URL('../../SKILL.md', import.meta.url).pathname
5
+ const SKILL_JSON = new URL('../../skill.json', import.meta.url).pathname
6
+ const ref = (name: string) => new URL(`../../references/${name}`, import.meta.url).pathname
5
7
  const FOCUSES = ['security', 'bugs', 'performance', 'code-smells', 'architecture'] as const
6
8
 
7
- test('SKILL.md has a specialist block for every focus', async () => {
9
+ test('every references/ path SKILL.md cites exists on disk', async () => {
8
10
  const text = await readFile(SKILL, 'utf8')
11
+ const cited = [...text.matchAll(/references\/[a-z0-9-]+\.md/g)].map((m) => m[0])
12
+ expect(cited.length).toBeGreaterThan(0)
13
+ for (const rel of new Set(cited)) {
14
+ const body = await readFile(new URL(`../../${rel}`, import.meta.url).pathname, 'utf8')
15
+ expect(body.length).toBeGreaterThan(0)
16
+ }
17
+ })
18
+
19
+ test('references/ ships in the install bundle', async () => {
20
+ // resolveBundlePaths silently drops include entries that match nothing, so a
21
+ // missing entry here would install a SKILL.md whose prompts are all 404s.
22
+ const manifest = JSON.parse(await readFile(SKILL_JSON, 'utf8')) as {
23
+ bundle: { include: string[] }
24
+ }
25
+ const covers = (p: string) =>
26
+ manifest.bundle.include.some((inc) => inc === p || p.startsWith(`${inc}/`))
27
+ for (const name of ['specialists.md', 'critic.md', 'peer-review.md']) {
28
+ expect(covers(`references/${name}`)).toBe(true)
29
+ }
30
+ })
31
+
32
+ test('references/specialists.md has a block for every focus', async () => {
33
+ const text = await readFile(ref('specialists.md'), 'utf8')
9
34
  for (const focus of FOCUSES) {
10
35
  const tag = `magpie-specialist-${focus}`
11
36
  const fence = `\`\`\`${tag}`
@@ -13,23 +38,69 @@ test('SKILL.md has a specialist block for every focus', async () => {
13
38
  const start = text.indexOf(fence)
14
39
  const end = text.indexOf('```', start + tag.length + 3)
15
40
  const block = text.slice(start, end)
16
- expect(block).toContain('orchestrator')
41
+ expect(block).toContain('Output Contract')
17
42
  expect(block).toContain(focus)
18
43
  }
19
44
  })
20
45
 
21
- test('SKILL.md §4 specifies the findings file path', async () => {
22
- const text = await readFile(SKILL, 'utf8')
23
- // The file-write instruction lives once, in the orchestrator template before §5.
24
- const orchestrator = text.slice(0, text.indexOf('```magpie-specialist-'))
25
- expect(orchestrator).toMatch(/findings\/<focus>\.json/)
26
- expect(orchestrator).toMatch(/Write findings to/i)
46
+ test('references/specialists.md carries the output contract next to the blocks', async () => {
47
+ const text = await readFile(ref('specialists.md'), 'utf8')
48
+ // The contract has to travel with the prompts: the orchestrator assembles
49
+ // both into one subagent prompt from this single file.
50
+ const contract = text.slice(0, text.indexOf('```magpie-specialist-'))
51
+ expect(contract).toContain('## Output Contract')
52
+ expect(contract).toMatch(/findings\/<focus>\.json/)
53
+ expect(contract).toMatch(/Write findings to/i)
54
+ // Severity, impact, likelihood, confidence, action enums must all be listed.
55
+ expect(contract).toMatch(/"blocker".*"high".*"medium".*"low"/)
56
+ expect(contract).toMatch(/"critical".*"high".*"medium".*"low"/)
57
+ expect(contract).toMatch(/"likely".*"possible".*"edge-case".*"unknown"/)
58
+ expect(contract).toMatch(/"must-fix".*"should-fix".*"consider".*"optional"/)
59
+ expect(contract).toMatch(/"impact"[\s\S]*"likelihood"[\s\S]*"confidence"[\s\S]*"action"/)
60
+ expect(contract).toMatch(/"body"[\s\S]*"startLine"[\s\S]*"endLine"/)
61
+ // Anti-patterns flagged explicitly so subagents don't repeat the JSON-shape mistakes.
62
+ expect(contract).toMatch(/NOT "lines"/)
63
+ expect(contract).toMatch(/NOT "recommendation"/)
27
64
  })
28
65
 
29
- test('SKILL.md has the critic and peer-review blocks', async () => {
30
- const text = await readFile(SKILL, 'utf8')
66
+ test('references/critic.md holds the rubric and both placeholders', async () => {
67
+ const text = await readFile(ref('critic.md'), 'utf8')
31
68
  expect(text).toContain('```magpie-critic')
69
+ expect(text).toContain('<<DEDUPED_FINDINGS_COMPACT>>')
70
+ expect(text).toContain('<<DIFF_EXCERPT>>')
71
+ expect(text).toContain('review-critic')
72
+ })
73
+
74
+ test('references/peer-review.md holds the prompt and the Claude preamble', async () => {
75
+ const text = await readFile(ref('peer-review.md'), 'utf8')
32
76
  expect(text).toContain('```magpie-peer-review')
77
+ expect(text).toContain('```magpie-peer-review-claude-preamble')
78
+ expect(text).toContain('review-peer-review')
79
+ for (const ph of ['<<PRIMARY_PROVIDER>>', '<<PEER_PROVIDER>>', '<<KEPT_FINDINGS_COMPACT>>']) {
80
+ expect(text).toContain(ph)
81
+ }
82
+ })
83
+
84
+ test('SKILL.md sends each stage to the reference file it needs', async () => {
85
+ const text = await readFile(SKILL, 'utf8')
86
+ const section = (heading: string) => {
87
+ const start = text.indexOf(heading)
88
+ expect(start).toBeGreaterThan(-1)
89
+ const next = text.indexOf('\n### ', start + heading.length)
90
+ return text.slice(start, next === -1 ? undefined : next)
91
+ }
92
+ expect(section('### 3. Specialists')).toContain('references/specialists.md')
93
+ expect(section('### 5. Critic')).toContain('references/critic.md')
94
+ expect(section('### 6. Peer review')).toContain('references/peer-review.md')
95
+ })
96
+
97
+ test('SKILL.md no longer inlines the prompt bodies it moved out', async () => {
98
+ const text = await readFile(SKILL, 'utf8')
99
+ for (const tag of ['magpie-specialist-', 'magpie-critic', 'magpie-peer-review']) {
100
+ expect(text).not.toContain(`\`\`\`${tag}`)
101
+ }
102
+ // The walkthrough is the always-read part; keep it small enough to be cheap.
103
+ expect(text.split(/\s+/).length).toBeLessThan(2600)
33
104
  })
34
105
 
35
106
  test('styles.css declares a prefers-color-scheme:dark block that overrides core tokens', async () => {
@@ -104,25 +175,42 @@ test('severity chips and submit button no longer hardcode `color: white` (must f
104
175
  }
105
176
  })
106
177
 
107
- test('SKILL.md §4 inlines the full specialist schema with enum vocabularies', async () => {
178
+ test('SKILL.md checks for a resumable run before minting a new run id', async () => {
108
179
  const text = await readFile(SKILL, 'utf8')
109
- // The schema block sits inside the specialist orchestrator template (§4),
110
- // before the specialist blocks begin (the first `magpie-specialist-` tag).
111
- const schemaCutoff = text.indexOf('```magpie-specialist-')
112
- expect(schemaCutoff).toBeGreaterThan(-1)
113
- const orchestrator = text.slice(0, schemaCutoff)
114
- // Severity, impact, likelihood, confidence, action enums must all be listed.
115
- expect(orchestrator).toMatch(/"blocker".*"high".*"medium".*"low"/)
116
- expect(orchestrator).toMatch(/"critical".*"high".*"medium".*"low"/)
117
- expect(orchestrator).toMatch(/"likely".*"possible".*"edge-case".*"unknown"/)
118
- expect(orchestrator).toMatch(/"must-fix".*"should-fix".*"consider".*"optional"/)
119
- // The risk field must be documented as an object with the four required keys.
120
- expect(orchestrator).toMatch(/"impact"[\s\S]*"likelihood"[\s\S]*"confidence"[\s\S]*"action"/)
121
- // suggestion shape: body/startLine/endLine.
122
- expect(orchestrator).toMatch(/"body"[\s\S]*"startLine"[\s\S]*"endLine"/)
123
- // Anti-patterns flagged explicitly so subagents don't repeat the JSON-shape mistakes.
124
- expect(orchestrator).toMatch(/NOT "lines"/)
125
- expect(orchestrator).toMatch(/NOT "recommendation"/)
180
+ // §0 always computed a fresh `pr-<n>-<epoch>` id, so the resume section
181
+ // (which keyed off that brand-new directory) could never fire.
182
+ const setupSection = text.slice(0, text.indexOf('### 1. Setup'))
183
+ expect(setupSection).toContain('magpie --list-runs')
184
+ expect(setupSection).toMatch(/active/i)
185
+ })
186
+
187
+ test('SKILL.md does not gate resume on state/server-info', async () => {
188
+ const text = await readFile(SKILL, 'utf8')
189
+ const start = text.indexOf('## Resuming a crashed run')
190
+ expect(start).toBeGreaterThan(-1)
191
+ const section = text.slice(start, text.indexOf('## Aborting', start))
192
+ // server-info is deleted when the server idles out, so a resumable run
193
+ // fails that check. log.jsonl is the durable signal.
194
+ expect(section).toContain('log.jsonl')
195
+ expect(section).not.toMatch(/if .*server-info.* exists/i)
196
+ // Resuming must restart the server; the old one is gone.
197
+ expect(section).toContain('magpie serve')
198
+ // `context` is a no-op stage: say so, or the agent stalls on it.
199
+ expect(section).toContain('context')
200
+ })
201
+
202
+ test('SKILL.md names the report buttons that actually exist', async () => {
203
+ const text = await readFile(SKILL, 'utf8')
204
+ const actionBar = await readFile(
205
+ new URL('../render-action-bar.ts', import.meta.url).pathname,
206
+ 'utf8',
207
+ )
208
+ for (const label of ['Post Selected', 'Post Recommended']) {
209
+ expect(actionBar).toContain(label)
210
+ expect(text).toContain(label)
211
+ }
212
+ // The old label was never rendered anywhere.
213
+ expect(text).not.toContain('Post to PR')
126
214
  })
127
215
 
128
216
  test('SKILL.md has the stage walkthrough', async () => {
@@ -31,6 +31,19 @@ test('empty log reports nothing completed', async () => {
31
31
  expect(result.next).toBe('setup')
32
32
  })
33
33
 
34
+ test('a skipped stage advances the resume pointer past it', async () => {
35
+ // `context` has no work in the pipeline; SKILL.md tells the orchestrator to
36
+ // log it as skipped. If `skipped` did not advance the pointer, a resume
37
+ // would be sent back to a stage that has no step to run.
38
+ await writeFile(
39
+ join(runDir, 'log.jsonl'),
40
+ `${JSON.stringify({ stage: 'setup', status: 'done' })}\n${JSON.stringify({ stage: 'context', status: 'skipped' })}\n`,
41
+ )
42
+ const result = await runStatus(runDir)
43
+ expect(result.lastCompleted).toBe('context')
44
+ expect(result.next).toBe('specialists')
45
+ })
46
+
34
47
  test('error stage halts progression', async () => {
35
48
  await writeFile(
36
49
  join(runDir, 'log.jsonl'),
@@ -352,6 +352,23 @@
352
352
  }
353
353
  }
354
354
 
355
+ function isSelected(id) {
356
+ return findCheckboxes(id).some((cb) => cb.checked && !cb.disabled)
357
+ }
358
+
359
+ // Assigning `cb.checked` in script fires no change event, so any programmatic
360
+ // selection has to emit its own /events record. `state/events` is the only
361
+ // channel the orchestrator can read when the user types `post` in the
362
+ // terminal instead of using the in-page buttons; a silent set drops the
363
+ // finding from that post.
364
+ function setCheckedAndNotify(id, checked) {
365
+ const before = isSelected(id)
366
+ setChecked(id, checked)
367
+ const after = isSelected(id)
368
+ if (after === before) return
369
+ post({ type: after ? 'select' : 'deselect', findingId: id, timestamp: Date.now() })
370
+ }
371
+
355
372
  // ---------------------------------------------------------------------------
356
373
  // Bulk selection
357
374
  // ---------------------------------------------------------------------------
@@ -367,8 +384,8 @@
367
384
  if (el.getAttribute('data-posted') === 'true') continue
368
385
  ids.add(el.getAttribute('data-finding-id'))
369
386
  }
370
- for (const id of ids) setChecked(id, true)
371
- updateSelectedCount()
387
+ for (const id of ids) setCheckedAndNotify(id, true)
388
+ recountSelected()
372
389
  }
373
390
 
374
391
  function handleSelectRecommended() {
@@ -378,8 +395,8 @@
378
395
  if (el.getAttribute('data-posted') === 'true') continue
379
396
  ids.add(el.getAttribute('data-finding-id'))
380
397
  }
381
- for (const id of ids) setChecked(id, true)
382
- updateSelectedCount()
398
+ for (const id of ids) setCheckedAndNotify(id, true)
399
+ recountSelected()
383
400
  }
384
401
 
385
402
  // ---------------------------------------------------------------------------
@@ -399,14 +416,8 @@
399
416
  if (cbs.length === 0) return
400
417
  const cb = cbs[0]
401
418
  if (cb.disabled) return
402
- const next = !cb.checked
403
- setChecked(id, next)
404
- post({
405
- type: next ? 'select' : 'deselect',
406
- findingId: id,
407
- timestamp: Date.now(),
408
- })
409
- updateSelectedCount()
419
+ setCheckedAndNotify(id, !cb.checked)
420
+ recountSelected()
410
421
  }
411
422
 
412
423
  // ---------------------------------------------------------------------------
@@ -535,7 +546,7 @@
535
546
  if (cb.disabled) continue
536
547
  cb.checked = target.checked
537
548
  }
538
- updateSelectedCount()
549
+ recountSelected()
539
550
  post({
540
551
  type: target.checked ? 'select' : 'deselect',
541
552
  findingId: id,
@@ -13,6 +13,12 @@ const ORDER = [
13
13
  ] as const
14
14
 
15
15
  export type StatusResult = {
16
+ /**
17
+ * Highest stage the log says is behind us. A stage logged `skipped` counts:
18
+ * `context` has no work in the pipeline and is always logged that way, so
19
+ * treating it as unfinished would send a resume back to a stage that has no
20
+ * step to run.
21
+ */
16
22
  lastCompleted: (typeof ORDER)[number] | null
17
23
  next: (typeof ORDER)[number] | 'cleanup'
18
24
  error: string | null
@@ -35,7 +41,10 @@ export async function runStatus(runDir: string): Promise<StatusResult> {
35
41
  error = stage
36
42
  break
37
43
  }
38
- if (status === 'done' && (ORDER as readonly string[]).includes(stage)) {
44
+ if (
45
+ (status === 'done' || status === 'skipped') &&
46
+ (ORDER as readonly string[]).includes(stage)
47
+ ) {
39
48
  lastCompleted = stage as StatusResult['lastCompleted']
40
49
  }
41
50
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magpie",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
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 (falling back to a Claude second opinion when codex is unavailable), 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",
@@ -13,6 +13,7 @@
13
13
  "bundle": {
14
14
  "include": [
15
15
  "bin",
16
+ "references",
16
17
  "scripts",
17
18
  "templates",
18
19
  "fixtures/example-pr",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: module-secret-auditor
3
- description: Use when creating new files, directories, or modules, when reviewing project structure, when a change ripples across 3+ directories, or when the user asks "where should this code live?" or "how should I structure this?". Trigger on structural decisions and reorganizations.
3
+ description: Use when creating new modules or directories, drawing or questioning module boundaries, when a change ripples across 3+ directories, or when the user asks "where should this code live?" or "how should I structure this?". Trigger on structural decisions and reorganizations. NOT for a module doing too many unrelated things (cohesion-analyzer), domain-language boundaries (bounded-context-auditor), naming conventions, code style, or adding code to existing well-bounded modules.
4
4
  ---
5
5
 
6
6
  # Module Secret Auditor
@@ -69,17 +69,20 @@ When proposing structure, organize around change-reasons, not around nouns or te
69
69
 
70
70
  Instead of:
71
71
  ```
72
- models/user.ts
73
- services/userService.ts
72
+ models/user.ts — user fields for every feature; changed by all of them
73
+ services/userService.ts — session logic + notification prefs + billing address in one file
74
74
  controllers/userController.ts
75
75
  repositories/userRepository.ts
76
76
  ```
77
77
 
78
- Propose:
78
+ Propose (same code, re-homed by which decision changes it):
79
79
  ```
80
80
  auth/ — secret: how identity is verified and sessions are managed
81
- pricing/ — secret: how costs are calculated and discounts applied
81
+ (absorbs the session/token logic from userService + userController)
82
82
  notifications/ — secret: how and when users are notified, via which channels
83
+ (absorbs the notification-preference logic from userService)
84
+ billing/ — secret: how billing details are stored and validated
85
+ (absorbs the billing fields from user.ts + userRepository)
83
86
  ```
84
87
 
85
88
  Each directory answers: **"If this decision changes, only this directory is affected."**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "module-secret-auditor",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Parnas-inspired information hiding analysis: module boundaries drawn by change-reason, not by noun or technical layer",
5
5
  "author": "iceinvein",
6
6
  "type": "prompt",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: port-adapter-auditor
3
- description: Use when business logic is tangled with database queries, HTTP handling, or third-party SDK calls, when testing a feature requires standing up real infrastructure, or when swapping an infrastructure dependency would require rewriting business logic. Trigger on "I can't test this without a database", "swapping the email provider means rewriting half the service", or when reviewing boundaries between core and infrastructure. NOT for dependency direction between layers, internal module structure within the core, or API contract design.
3
+ description: Use when business logic is tangled with database queries, HTTP handling, or third-party SDK calls, when testing a feature requires standing up real infrastructure, or when swapping an infrastructure dependency would mean rewriting business logic. Trigger on "I can't test this without a database" or "swapping the email provider means rewriting half the service". NOT for dependency direction between layers (dependency-direction-auditor), internal core structure, or API contract design.
4
4
  ---
5
5
 
6
6
  # Port-Adapter Auditor
@@ -288,7 +288,7 @@ Decision engine. The agent prioritizes driven ports (outbound) first because the
288
288
 
289
289
  **Adapters can depend on libraries; ports cannot.** An adapter can import Stripe SDK, ORM libraries, HTTP frameworks. A port cannot. Ports are library-agnostic.
290
290
 
291
- **Beware ports that mirror adapters.** If your port looks exactly like your adapter's public interface, the port hasn't abstracted anything. It's just a pass-through. The port should translate between domain language and adapter language.
291
+ **Beware ports that mirror adapters.** If your port looks exactly like your adapter's public interface, the port hasn't abstracted anything. It's just a pass-through. The port should be expressed in domain terms so the adapter has something to translate; a port that mirrors the SDK's surface has abstracted nothing.
292
292
 
293
293
  ## Common Mistakes
294
294
 
@@ -298,7 +298,7 @@ Decision engine. The agent prioritizes driven ports (outbound) first because the
298
298
  | Port returns infrastructure types | `OrderRepository.getById()` returns an ORM entity. Should return a domain Order. Adapter translates. |
299
299
  | Adapter does business logic | Business logic belongs in core. Adapter translates and delegates. If you find logic in an adapter, move it to core. |
300
300
  | One giant service (no ports) | Even monoliths need internal hexagons. Define ports between core and persistence, core and messaging, etc. |
301
- | Testing adapters instead of core | Don't write tests for adapters (they test the library). Write tests for core with mock adapters. Adapter tests are integration tests, not unit tests. |
301
+ | Testing adapters instead of core | Don't unit-test adapters with mocks (that just tests the library). Write unit tests for core with mock adapters; cover adapters with integration tests against the real dependency. |
302
302
  | Ports for internal collaboration | Ports are for external systems (database, API, messaging). Internal communication between core modules uses dependency injection, not ports. |
303
303
  | Circular imports | Core imports adapter to construct it (bad). Use a composition root outside core to wire dependencies. Core only imports ports. |
304
304
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "port-adapter-auditor",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Cockburn-inspired hexagonal architecture analysis: identify ports and adapters, classify boundary health, ensure core testability and swappability",
5
5
  "author": "iceinvein",
6
6
  "type": "prompt",
@@ -18,7 +18,7 @@ A design evaluation framework based on Dieter Rams' *Ten Principles for Good Des
18
18
  - When decorative elements (gradients, shadows, borders, icons) are present
19
19
  - Evaluating whether a redesign actually improved or just rearranged
20
20
 
21
- **Not for:** Functional logic review, accessibility audits (WCAG compliance), performance optimization, or backend architecture. This skill evaluates *visual design quality*, not correctness.
21
+ **Not for:** Functional logic review, accessibility audits (WCAG compliance), performance optimization, or backend architecture. This skill evaluates *visual design quality*, not correctness. For option overload and memory demands use `cognitive-load-auditor`; for spacing and visual grouping use `gestalt-reviewer` — the three compose on a full UI review.
22
22
 
23
23
  ## The Process
24
24
 
@@ -39,7 +39,7 @@ Apply this ruthlessly to:
39
39
 
40
40
  ### 2. Evaluate Against Rams' Principles
41
41
 
42
- Score the interface against the principles most relevant to digital UI:
42
+ Evaluate the interface against the principles most relevant to digital UI (a pass/fail-with-evidence judgment per principle; there is no numeric score):
43
43
 
44
44
  **Is it innovative?**
45
45
  - Does the design solve the interaction problem in a way that serves the user, or does it follow a template without questioning whether the template fits?
@@ -112,6 +112,8 @@ RAMS AUDIT: User settings page
112
112
 
113
113
  Decision engine. After generating UI code, the agent runs the Rams audit on its own output. The audit identifies specific elements to remove and states to add. The human sees the audit alongside the component and can override any cut.
114
114
 
115
+ **Acquiring the UI when auditing something the agent didn't just write:** prefer a rendered view (screenshot the user provides, or a browser/screenshot tool if one is available); otherwise audit the component source and stylesheets directly and say which mode the audit ran in (source-only audits can judge decoration, states, and element count, but not rendered visual weight).
116
+
115
117
  When the human has explicitly requested a visual style that conflicts with Rams (e.g., "make it more playful," "add some personality"), the agent acknowledges the tension and adapts the audit to evaluate within the requested style — Rams doesn't mandate austerity, but even playful design should be intentional, not random.
116
118
 
117
119
  ## Anti-Patterns
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rams-design-audit",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Dieter Rams-inspired design audit: every visual element must earn its presence, less but better, clarity through restraint",
5
5
  "author": "iceinvein",
6
6
  "type": "prompt",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: seam-finder
3
- description: Use when modifying existing code the agent didn't write, when the agent's instinct is to rewrite or heavily refactor, when adding tests to untested code, or when working in a codebase with minimal coverage. Trigger on "change this behavior", "add a feature to this", or any modification of legacy or unfamiliar code. NOT for greenfield code or code the agent just wrote in this session.
3
+ description: Use when changing code that has minimal or no test coverage, when the agent's instinct is to rewrite or heavily refactor unfamiliar code, or when adding tests to untested code. Trigger on "carefully change this legacy code", "this has no tests", or modifications where breakage would be hard to detect. NOT for greenfield code, code the agent just wrote in this session, or well-tested code where the suite catches regressions.
4
4
  ---
5
5
 
6
6
  # Seam Finder
@@ -30,7 +30,7 @@ Before modifying existing code, survey the available seams:
30
30
  - Look for: constructor parameters, function arguments that accept interfaces, injected services, callback parameters, strategy patterns already in place
31
31
  - This is the most common seam in object-oriented code. If a dependency is passed in, you can substitute it.
32
32
 
33
- **Preprocessing seam** — Can the inputs be transformed before they reach this code?
33
+ **Input seam** (an adaptation of Feathers' preprocessing seam, which in the book is the build-time preprocessor; the modern equivalent is transforming inputs) — Can the inputs be transformed before they reach this code?
34
34
  - Look for: middleware chains, interceptors, data transformation layers, event handlers, input normalization steps
35
35
  - The code doesn't change — its inputs do. This is safe because the original code path is untouched.
36
36
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seam-finder",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Feathers-inspired legacy code modification: find seams, make minimal incisions, preserve existing behavior",
5
5
  "author": "iceinvein",
6
6
  "type": "prompt",
@@ -136,11 +136,11 @@ SIMPLICITY: Using Redux + Redux Toolkit + RTK Query for app state
136
136
 
137
137
  | Term | Meaning | Example |
138
138
  |------|---------|---------|
139
- | **Simple** | Not interleaved; one braid | A pure function: data in, data out |
139
+ | **Simple** | Not interleaved; one strand, one fold | A pure function: data in, data out |
140
140
  | **Easy** | Near at hand; familiar | An ORM with convention-over-configuration |
141
- | **Complex** | Interleaved; multiple braids | A function that fetches, caches, transforms, and logs |
142
- | **Complecting** | The act of braiding together | Adding "just one more concern" to an existing module |
143
- | **Decomplecting** | The act of separating braids | Splitting a god module into composable parts |
141
+ | **Complex** | Interleaved; strands braided together | A function that fetches, caches, transforms, and logs |
142
+ | **Complecting** | The act of braiding strands together | Adding "just one more concern" to an existing module |
143
+ | **Decomplecting** | The act of separating strands | Splitting a god module into composable parts |
144
144
  | **Compose** | Combining simple things | Piping focused functions together |
145
145
  | **Artifact** | What you build | Code, modules, services |
146
146
  | **Construct** | Tools you use to build | Languages, libraries, patterns |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simplicity-razor",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Hickey-inspired simplicity analysis: simple vs easy, complecting detection, strand decomposition",
5
5
  "author": "iceinvein",
6
6
  "type": "prompt",
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: temporal-coupling-detector
3
- description: Use when code breaks because functions were called in the wrong order, when objects must be "initialized" before use but nothing prevents using them uninitialized, when setup/teardown sequences exist and forgetting a step causes silent corruption, or when parallelizing sequential code causes mysterious failures. Trigger on "why does this work in the test but fail in production?", "it worked until we changed the call order", or when reviewing multi-step object setup. NOT for intentional pipelines with type-enforced ordering, framework lifecycle hooks managed by the framework, or database transactions.
3
+ description: Use when code breaks because functions were called in the wrong order, when objects must be "initialized" but nothing prevents using them uninitialized, or when parallelizing sequential code causes mysterious failures. Trigger on "works in the test, fails in production", "it worked until we changed the call order", or when reviewing multi-step object setup. NOT for pipelines with type-enforced ordering, framework-managed lifecycle hooks, or database transactions.
4
4
  ---
5
5
 
6
6
  # Temporal Coupling Detector
7
7
 
8
- One of the most insidious forms of coupling — ordering dependencies required but invisible. If `init()` before `process()` is required but nothing enforces it, every caller must just know. The cost isn't immediately obvious: a test passes because the setup happens to occur in the right order. Code works in development, fails in production because initialization runs on a different thread. A refactor parallelizes sequential operations and introduces a race condition no one saw. Fowler and Beck call this a code smell; Kent Beck recommends encoding ordering into the type system so it cannot be violated.
8
+ One of the most insidious forms of coupling — ordering dependencies required but invisible. If `init()` before `process()` is required but nothing enforces it, every caller must just know. The cost isn't immediately obvious: a test passes because the setup happens to occur in the right order. Code works in development, fails in production because initialization runs on a different thread. A refactor parallelizes sequential operations and introduces a race condition no one saw. Hunt and Thomas named temporal coupling in *The Pragmatic Programmer*; Mark Seemann's design-smell treatment popularized the fix this skill teaches: encode the ordering into the type system so it cannot be violated.
9
9
 
10
10
  **Core principle:** If code must execute in a specific order, that ordering must be enforced by the design — through types, parameters, or API structure — not by documentation or convention. Convention fails at scale. Types prevent failure.
11
11
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temporal-coupling-detector",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "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",
5
5
  "author": "iceinvein",
6
6
  "type": "prompt",
@@ -1,10 +1,12 @@
1
1
  ---
2
2
  name: terse
3
3
  description: >
4
- Professional output compression. Cuts ~20-30% of output tokens while keeping proper grammar,
5
- readable prose, and semantic accuracy. Three intensity levels: clean, tight (default), sharp.
6
- Always-on from session start. Switch with /terse clean|tight|sharp.
7
- Off with "stop terse" or "normal mode".
4
+ Use when the user invokes /terse or asks for compressed, low-token output.
5
+ Professional output compression: cuts ~20-30% of output tokens while keeping proper
6
+ grammar, readable prose, and semantic accuracy. Three intensity levels: clean, tight
7
+ (default), sharp. Once active it applies to every response until "stop terse" or
8
+ "normal mode". Installed with global activation (SessionStart hook) it is on from
9
+ session start; with the default session install it activates when invoked.
8
10
  argument-hint: "[clean|tight|sharp]"
9
11
  ---
10
12
 
@@ -23,7 +25,7 @@ Terse compresses HOW the answer is delivered. It must never change WHAT the answ
23
25
 
24
26
  ACTIVE EVERY RESPONSE. No filler drift after many turns. Still active if unsure. Off only: "stop terse" / "normal mode".
25
27
 
26
- Active level: **$ARGUMENTS[0]** (default to **tight** if no argument provided). Switch anytime: `/terse clean|tight|sharp`.
28
+ Active level: **$1** (default to **tight** if no argument provided). Switch anytime: `/terse clean|tight|sharp`.
27
29
 
28
30
  ## What to Eliminate
29
31
 
@@ -83,7 +85,9 @@ If context is needed between tool calls, state the *finding* or *decision*, not
83
85
 
84
86
  These add no information. Remove on sight regardless of level:
85
87
 
86
- just, really, basically, actually, simply, essentially, honestly, certainly, definitely, sure, of course, happy to, absolutely, great question, that's a great point, as mentioned, it's worth noting that, it should be noted, in order to (use "to"), as well as (use "and"), due to the fact that (use "because"), at this point in time (use "now"), utilize (use "use"), demonstrate (use "show"), implement a solution for (use "fix"), investigate (use "check")
88
+ just, really, basically, actually, simply, essentially, honestly, certainly, definitely, sure, of course, happy to, absolutely, great question, that's a great point, as mentioned, it's worth noting that, it should be noted
89
+
90
+ (Word swaps like utilize→use or in order to→to are `tight`-level substitutions, listed under that level; `clean` keeps natural word choice and only drops the pure filler above.)
87
91
 
88
92
  ## Intensity Levels
89
93
 
@@ -102,6 +106,7 @@ Everything in `clean`, plus shorter synonyms, shorter sentences, and targeted cu
102
106
 
103
107
  Word cuts:
104
108
  - Shorter synonyms: big not extensive, fix not implement, use not utilize, show not demonstrate, check not investigate, need not requirement, start not initialize, end not terminate, send not transmit
109
+ - Shorter phrases: "to" not "in order to", "and" not "as well as", "because" not "due to the fact that", "now" not "at this point in time", "fix" not "implement a solution for"
105
110
  - Strip transition phrases on sight: "however", "additionally", "furthermore", "moreover", "that said", "in other words", "it's also worth mentioning", "on the other hand", "as a result", "with that in mind". Just start the next sentence.
106
111
  - Replace "this means that" with a dash or colon. Replace "the reason is that" with "because".
107
112
  - One idea per sentence. Split compound sentences.
@@ -110,7 +115,7 @@ Content cuts:
110
115
  - Direct answer first, then explanation if needed.
111
116
  - One example per point. Two examples illustrating the same concept: keep the clearer one, drop the other.
112
117
  - If the answer would work without the last paragraph, drop the last paragraph.
113
- - Do not restructure or add formatting that wasn't in the uncompressed answer. Only cut.
118
+ - Do not restructure or add formatting that wasn't in the uncompressed answer. Only cut. (This rule is `tight`-specific; `sharp` explicitly adds structural compression below.)
114
119
 
115
120
  ### `sharp`
116
121
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terse",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Professional output compression. Cuts ~20-30% of output tokens with proper grammar and semantic accuracy. Three levels: clean, tight, sharp.",
5
5
  "author": "iceinvein",
6
6
  "type": "prompt",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: type-driven-designer
3
- description: Use when primitive types (string, number, boolean) represent domain concepts, when nullable fields have ambiguous meaning, when invalid combinations of fields are structurally possible but logically impossible, or when validation happens far from construction. Trigger on "why is this email field just a string?", "how can status be 'shipped' with no tracking number?", or when reviewing domain model types. NOT for performance-critical inner loops where type wrappers add overhead, dynamic/untyped languages, or API boundary serialization shapes.
3
+ description: Use when primitives (string, number, boolean) represent domain concepts, when nullable fields have ambiguous meaning, when invalid field combinations are structurally possible, or when validation happens far from construction. Trigger on "why is this email just a string?", "how can status be 'shipped' with no tracking number?", or when reviewing domain model types. Works in any language with a type layer (TypeScript, Rust, Python + Pydantic). NOT for hot inner loops or API wire formats.
4
4
  ---
5
5
 
6
6
  # Type-Driven Designer
@@ -160,13 +160,13 @@ type PaymentStatus =
160
160
  | { status: "completed"; amount: number; transactionId: string; completedAt: Date }
161
161
  | { status: "failed"; amount: number; reason: string; failedAt: Date };
162
162
 
163
- function processPayment(payment: PaymentStatus): PaymentStatus {
164
- if (payment.status === "pending") {
165
- // Only pending payments can be processed
166
- return { status: "processing", amount: payment.amount, gatewayId: "..." };
167
- }
168
- // Can't process already-completed or failed payments — type system enforces it
169
- throw new Error(`Cannot process ${payment.status} payment`);
163
+ type PendingPayment = Extract<PaymentStatus, { status: "pending" }>;
164
+ type ProcessingPayment = Extract<PaymentStatus, { status: "processing" }>;
165
+
166
+ // Accepts ONLY pending payments — passing a completed or failed payment
167
+ // is a compile error, not a runtime throw. The type system enforces it.
168
+ function processPayment(payment: PendingPayment): ProcessingPayment {
169
+ return { status: "processing", amount: payment.amount, gatewayId: "..." };
170
170
  }
171
171
  ```
172
172
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "type-driven-designer",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Wlaschin & Minsky-inspired type design: make illegal states unrepresentable through branded types, discriminated unions, and domain-encoded constraints",
5
5
  "author": "iceinvein",
6
6
  "type": "prompt",