@delorenj/claude-notifications 2.0.0 → 2.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.
Files changed (206) hide show
  1. package/DO.md +5 -0
  2. package/FIXES-APPLIED.md +195 -0
  3. package/INTEGRATION.md +445 -0
  4. package/LAYOUT-INTEGRATION.md +191 -0
  5. package/QUICK-REFERENCE.md +195 -0
  6. package/README.md +145 -14
  7. package/TASK.md +15 -0
  8. package/ZELLIJ-NOTIFY.md +523 -0
  9. package/_bmad-output/implementation-artifacts/spec-install-multi-cli-hooks.md +241 -0
  10. package/bin/claude-notifications.js +417 -312
  11. package/bin/claude-notify.js +47 -1
  12. package/bin/zellij-notify.js +346 -0
  13. package/bun.lock +35 -0
  14. package/diagnose-zellij.sh +105 -0
  15. package/examples/settings-with-zellij.json +18 -0
  16. package/examples/settings-zellij-only.json +18 -0
  17. package/examples/zellij-notify-examples.sh +143 -0
  18. package/lib/adapters/_stub.js +35 -0
  19. package/lib/adapters/auggie.js +10 -0
  20. package/lib/adapters/claude-code.js +181 -0
  21. package/lib/adapters/codex.js +10 -0
  22. package/lib/adapters/copilot.js +10 -0
  23. package/lib/adapters/gemini.js +10 -0
  24. package/lib/adapters/index.js +240 -0
  25. package/lib/adapters/kimi.js +10 -0
  26. package/lib/adapters/opencode.js +14 -0
  27. package/lib/adapters/vibe.js +10 -0
  28. package/lib/config.js +44 -8
  29. package/lib/tui.js +115 -0
  30. package/lib/zellij.js +248 -0
  31. package/package.json +6 -4
  32. package/postinstall.js +28 -25
  33. package/preuninstall.js +18 -9
  34. package/test/adapters/claude-code.test.js +144 -0
  35. package/test/adapters/patches.test.js +81 -0
  36. package/test/adapters/registry.test.js +89 -0
  37. package/test/adapters/stubs.test.js +46 -0
  38. package/test/cli-json.test.js +79 -0
  39. package/test/helpers/fake-fs.js +59 -0
  40. package/test-integration.sh +113 -0
  41. package/test-notification-plugin.kdl +34 -0
  42. package/test-updated-layout.sh +75 -0
  43. package/test-zellij-cli.sh +72 -0
  44. package/zellij-plugin/.cargo/config.toml +5 -0
  45. package/zellij-plugin/.github/workflows/ci.yml +97 -0
  46. package/zellij-plugin/Cargo.lock +3558 -0
  47. package/zellij-plugin/Cargo.toml +40 -0
  48. package/zellij-plugin/README.md +290 -0
  49. package/zellij-plugin/build.sh +179 -0
  50. package/zellij-plugin/configs/examples/accessibility.kdl +31 -0
  51. package/zellij-plugin/configs/examples/catppuccin.kdl +32 -0
  52. package/zellij-plugin/configs/examples/default.kdl +34 -0
  53. package/zellij-plugin/configs/examples/minimal.kdl +22 -0
  54. package/zellij-plugin/docs/CONFIGURATION.md +191 -0
  55. package/zellij-plugin/docs/INTEGRATION.md +333 -0
  56. package/zellij-plugin/src/animation.rs +451 -0
  57. package/zellij-plugin/src/colors.rs +407 -0
  58. package/zellij-plugin/src/config.rs +664 -0
  59. package/zellij-plugin/src/event_bridge.rs +339 -0
  60. package/zellij-plugin/src/main.rs +420 -0
  61. package/zellij-plugin/src/notification.rs +466 -0
  62. package/zellij-plugin/src/queue.rs +399 -0
  63. package/zellij-plugin/src/renderer.rs +477 -0
  64. package/zellij-plugin/src/state.rs +338 -0
  65. package/zellij-plugin/src/tests.rs +413 -0
  66. package/.claude/checkpoints/1756392335.json +0 -1
  67. package/.claude/checkpoints/1756392341.json +0 -1
  68. package/.claude/checkpoints/1756392347.json +0 -1
  69. package/.claude/checkpoints/1756392376.json +0 -1
  70. package/.claude/checkpoints/1756392377.json +0 -1
  71. package/.claude/checkpoints/1756392386.json +0 -1
  72. package/.claude/checkpoints/1756392387.json +0 -1
  73. package/.claude/checkpoints/1756392398.json +0 -1
  74. package/.claude/checkpoints/1756392400.json +0 -1
  75. package/.claude/checkpoints/1756392427.json +0 -1
  76. package/.claude/checkpoints/1756392428.json +0 -1
  77. package/.claude/checkpoints/1756392486.json +0 -1
  78. package/.claude/checkpoints/1756392488.json +0 -1
  79. package/.claude/checkpoints/1756392558.json +0 -1
  80. package/.claude/checkpoints/1756392559.json +0 -1
  81. package/.claude/checkpoints/summary-session-20250828-105040.md +0 -57
  82. package/.claude/checkpoints/task-1756392207.json +0 -1
  83. package/.claude/checkpoints/task-1756392742.json +0 -1
  84. package/.claude/commands/analysis/COMMAND_COMPLIANCE_REPORT.md +0 -54
  85. package/.claude/commands/analysis/README.md +0 -9
  86. package/.claude/commands/analysis/bottleneck-detect.md +0 -162
  87. package/.claude/commands/analysis/performance-bottlenecks.md +0 -59
  88. package/.claude/commands/analysis/performance-report.md +0 -25
  89. package/.claude/commands/analysis/token-efficiency.md +0 -45
  90. package/.claude/commands/analysis/token-usage.md +0 -25
  91. package/.claude/commands/automation/README.md +0 -9
  92. package/.claude/commands/automation/auto-agent.md +0 -122
  93. package/.claude/commands/automation/self-healing.md +0 -106
  94. package/.claude/commands/automation/session-memory.md +0 -90
  95. package/.claude/commands/automation/smart-agents.md +0 -73
  96. package/.claude/commands/automation/smart-spawn.md +0 -25
  97. package/.claude/commands/automation/workflow-select.md +0 -25
  98. package/.claude/commands/coordination/README.md +0 -9
  99. package/.claude/commands/coordination/agent-spawn.md +0 -25
  100. package/.claude/commands/coordination/init.md +0 -44
  101. package/.claude/commands/coordination/orchestrate.md +0 -43
  102. package/.claude/commands/coordination/spawn.md +0 -45
  103. package/.claude/commands/coordination/swarm-init.md +0 -85
  104. package/.claude/commands/coordination/task-orchestrate.md +0 -25
  105. package/.claude/commands/github/README.md +0 -11
  106. package/.claude/commands/github/code-review-swarm.md +0 -514
  107. package/.claude/commands/github/code-review.md +0 -25
  108. package/.claude/commands/github/github-modes.md +0 -147
  109. package/.claude/commands/github/github-swarm.md +0 -121
  110. package/.claude/commands/github/issue-tracker.md +0 -292
  111. package/.claude/commands/github/issue-triage.md +0 -25
  112. package/.claude/commands/github/multi-repo-swarm.md +0 -519
  113. package/.claude/commands/github/pr-enhance.md +0 -26
  114. package/.claude/commands/github/pr-manager.md +0 -170
  115. package/.claude/commands/github/project-board-sync.md +0 -471
  116. package/.claude/commands/github/release-manager.md +0 -338
  117. package/.claude/commands/github/release-swarm.md +0 -544
  118. package/.claude/commands/github/repo-analyze.md +0 -25
  119. package/.claude/commands/github/repo-architect.md +0 -367
  120. package/.claude/commands/github/swarm-issue.md +0 -482
  121. package/.claude/commands/github/swarm-pr.md +0 -285
  122. package/.claude/commands/github/sync-coordinator.md +0 -301
  123. package/.claude/commands/github/workflow-automation.md +0 -442
  124. package/.claude/commands/hooks/README.md +0 -11
  125. package/.claude/commands/hooks/overview.md +0 -58
  126. package/.claude/commands/hooks/post-edit.md +0 -117
  127. package/.claude/commands/hooks/post-task.md +0 -112
  128. package/.claude/commands/hooks/pre-edit.md +0 -113
  129. package/.claude/commands/hooks/pre-task.md +0 -111
  130. package/.claude/commands/hooks/session-end.md +0 -118
  131. package/.claude/commands/hooks/setup.md +0 -103
  132. package/.claude/commands/memory/README.md +0 -9
  133. package/.claude/commands/memory/memory-persist.md +0 -25
  134. package/.claude/commands/memory/memory-search.md +0 -25
  135. package/.claude/commands/memory/memory-usage.md +0 -25
  136. package/.claude/commands/memory/neural.md +0 -47
  137. package/.claude/commands/memory/usage.md +0 -46
  138. package/.claude/commands/monitoring/README.md +0 -9
  139. package/.claude/commands/monitoring/agent-metrics.md +0 -25
  140. package/.claude/commands/monitoring/agents.md +0 -44
  141. package/.claude/commands/monitoring/real-time-view.md +0 -25
  142. package/.claude/commands/monitoring/status.md +0 -46
  143. package/.claude/commands/monitoring/swarm-monitor.md +0 -25
  144. package/.claude/commands/optimization/README.md +0 -9
  145. package/.claude/commands/optimization/auto-topology.md +0 -62
  146. package/.claude/commands/optimization/cache-manage.md +0 -25
  147. package/.claude/commands/optimization/parallel-execute.md +0 -25
  148. package/.claude/commands/optimization/parallel-execution.md +0 -50
  149. package/.claude/commands/optimization/topology-optimize.md +0 -25
  150. package/.claude/commands/pair/README.md +0 -261
  151. package/.claude/commands/pair/commands.md +0 -546
  152. package/.claude/commands/pair/config.md +0 -510
  153. package/.claude/commands/pair/examples.md +0 -512
  154. package/.claude/commands/pair/modes.md +0 -348
  155. package/.claude/commands/pair/session.md +0 -407
  156. package/.claude/commands/pair/start.md +0 -209
  157. package/.claude/commands/sparc/analyzer.md +0 -52
  158. package/.claude/commands/sparc/architect.md +0 -53
  159. package/.claude/commands/sparc/batch-executor.md +0 -54
  160. package/.claude/commands/sparc/coder.md +0 -54
  161. package/.claude/commands/sparc/debugger.md +0 -54
  162. package/.claude/commands/sparc/designer.md +0 -53
  163. package/.claude/commands/sparc/documenter.md +0 -54
  164. package/.claude/commands/sparc/innovator.md +0 -54
  165. package/.claude/commands/sparc/memory-manager.md +0 -54
  166. package/.claude/commands/sparc/optimizer.md +0 -54
  167. package/.claude/commands/sparc/orchestrator.md +0 -132
  168. package/.claude/commands/sparc/researcher.md +0 -54
  169. package/.claude/commands/sparc/reviewer.md +0 -54
  170. package/.claude/commands/sparc/sparc-modes.md +0 -174
  171. package/.claude/commands/sparc/swarm-coordinator.md +0 -54
  172. package/.claude/commands/sparc/tdd.md +0 -54
  173. package/.claude/commands/sparc/tester.md +0 -54
  174. package/.claude/commands/sparc/workflow-manager.md +0 -54
  175. package/.claude/commands/stream-chain/pipeline.md +0 -121
  176. package/.claude/commands/stream-chain/run.md +0 -70
  177. package/.claude/commands/swarm/analysis.md +0 -95
  178. package/.claude/commands/swarm/development.md +0 -96
  179. package/.claude/commands/swarm/examples.md +0 -168
  180. package/.claude/commands/swarm/maintenance.md +0 -102
  181. package/.claude/commands/swarm/optimization.md +0 -117
  182. package/.claude/commands/swarm/research.md +0 -136
  183. package/.claude/commands/swarm/testing.md +0 -131
  184. package/.claude/commands/training/README.md +0 -9
  185. package/.claude/commands/training/model-update.md +0 -25
  186. package/.claude/commands/training/neural-patterns.md +0 -74
  187. package/.claude/commands/training/neural-train.md +0 -25
  188. package/.claude/commands/training/pattern-learn.md +0 -25
  189. package/.claude/commands/training/specialization.md +0 -63
  190. package/.claude/commands/truth/start.md +0 -143
  191. package/.claude/commands/verify/check.md +0 -50
  192. package/.claude/commands/verify/start.md +0 -128
  193. package/.claude/commands/workflows/README.md +0 -9
  194. package/.claude/commands/workflows/development.md +0 -78
  195. package/.claude/commands/workflows/research.md +0 -63
  196. package/.claude/commands/workflows/workflow-create.md +0 -25
  197. package/.claude/commands/workflows/workflow-execute.md +0 -25
  198. package/.claude/commands/workflows/workflow-export.md +0 -25
  199. package/.claude/config.json +0 -36
  200. package/.claude/settings.json +0 -162
  201. package/.claude-flow/metrics/agent-metrics.json +0 -1
  202. package/.claude-flow/metrics/performance.json +0 -9
  203. package/.claude-flow/metrics/system-metrics.json +0 -230
  204. package/.claude-flow/metrics/task-metrics.json +0 -10
  205. package/FIXES.md +0 -75
  206. package/test-results.md +0 -163
@@ -0,0 +1,241 @@
1
+ ---
2
+ title: 'Interactive multi-CLI notification hook installer'
3
+ type: 'feature'
4
+ created: '2026-04-14'
5
+ status: 'done'
6
+ baseline_commit: 'b9f82b54f3e8f85549006cd1d89bff7a4cf1e03e'
7
+ review_iterations: 1
8
+ context:
9
+ - '{project-root}/bin/claude-notifications.js'
10
+ - '{project-root}/README.md'
11
+ ---
12
+
13
+ <frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
14
+
15
+ ## Intent
16
+
17
+ **Problem:** The current installer only targets Claude Code via hardcoded paths and blind JSON writes in `bin/claude-notifications.js:38-107`. It cannot detect other agent CLIs (gemini, opencode, auggie, copilot, kimi, vibe, codex), has no user-facing selection, and is idempotency-brittle (duplicate Stop hooks, no PATH validation, no clean rollback).
18
+
19
+ **Approach:** Replace the single-target installer with a per-CLI adapter architecture behind a terminal UI. On `claude-notifications install`, the app probes `$PATH` for each known agent CLI, renders a checkbox TUI listing detected ones (with unsupported CLIs shown disabled and annotated), and on confirm invokes the matching adapter to idempotently write/remove a `Notification`-event hook pointing at `claude-notify`. Non-TTY invocations fall back to `--cli=<list>` flags driving the same adapters.
20
+
21
+ ## Boundaries & Constraints
22
+
23
+ **Always:**
24
+ - Adapter-per-CLI module under `lib/adapters/<cli>.js` exposing `{ id, label, detect(), supportsHooks, configPath(), install(ctx), uninstall(ctx), status(ctx) }`. Registry in `lib/adapters/index.js`.
25
+ - Detection uses `which` (already a dep); never shell-out to the CLI itself.
26
+ - Hook writes are idempotent — read existing config, upsert by a stable marker (`"source": "claude-notifications"`), never blind-push.
27
+ - Validate `claude-notify` resolves in `$PATH` before writing any adapter hook; abort with actionable error if missing.
28
+ - Preserve existing Claude Code behavior byte-for-byte when the user selects only Claude Code (backward compatible).
29
+ - Dry-run flag (`--dry-run`) prints planned diffs without writing.
30
+
31
+ **Ask First:**
32
+ - Adding any new runtime dependency beyond the TUI library.
33
+ - Writing to CLI config files outside the user's home directory.
34
+ - Changing the on-disk shape of `~/.config/claude-notifications/settings.json`.
35
+
36
+ **Never:**
37
+ - Do not install hooks into CLIs with `supportsHooks: false` (show them in the TUI as disabled with a reason).
38
+ - Do not delete config keys we didn't write.
39
+ - Do not introduce a web UI, Electron, or non-terminal UX.
40
+ - Do not bundle hooks into the npm `postinstall` step unattended — installation is always explicit (postinstall only prints a hint).
41
+
42
+ ## I/O & Edge-Case Matrix
43
+
44
+ | Scenario | Input / State | Expected Output / Behavior | Error Handling |
45
+ |----------|---------------|---------------------------|----------------|
46
+ | Happy path, multi-CLI | `claude-notify` in PATH; claude + opencode detected; user toggles both | TUI shows both checked by default; on confirm, each adapter upserts Notification hook; summary lists 2 installed, 0 skipped | N/A |
47
+ | Unsupported CLI detected | gemini in PATH but no hook system | Row shown disabled with reason "no hook API"; cannot toggle on | N/A |
48
+ | Idempotent re-run | Hook already present with our marker | Adapter reports "already installed"; no file write | N/A |
49
+ | Non-TTY (CI/pipe) | `--cli=claude,opencode` flag | Skip TUI, run adapters directly, emit structured stdout | Exit 2 if any adapter fails, continue-on-error with `--keep-going` |
50
+ | `claude-notify` not in PATH | User ran installer from source without `npm link` | Abort before writing any adapter; print install fix | Exit 1 |
51
+ | CLI detected but config file missing | Fresh install of CLI, no config yet | Adapter creates minimal config with only our hook block | N/A |
52
+ | Uninstall | `claude-notifications uninstall` | Each adapter removes only blocks matching our marker; reports restored state | Warn if external edits made our block unremovable |
53
+
54
+ </frozen-after-approval>
55
+
56
+ ## Code Map
57
+
58
+ - `bin/claude-notifications.js` -- CLI entry; gains `install` / `uninstall` / `status` subcommands that dispatch to registry + TUI
59
+ - `lib/adapters/index.js` -- NEW. Adapter registry + shared helpers (JSON read/merge with marker, config path resolution)
60
+ - `lib/adapters/claude-code.js` -- NEW. Migrates existing `updateClaudeCodeConfig` logic behind the adapter interface, keyed on `Notification` event
61
+ - `lib/adapters/opencode.js` -- NEW. Opencode hook/config writer (verify hook surface during impl; if absent, set `supportsHooks: false` and move to unsupported list)
62
+ - `lib/adapters/{gemini,auggie,copilot,kimi,vibe,codex}.js` -- NEW stubs. `detect()` real, `supportsHooks: false` until hook surface is verified per CLI. Explicit reason string on each.
63
+ - `lib/tui.js` -- NEW. `@clack/prompts` wrapper: `renderSelector(detections) → {selectedIds, cancelled}`.
64
+ - `lib/config.js` -- Minor: expose `notifyBinaryPath()` helper used by all adapters for PATH validation.
65
+ - `postinstall.js` -- Change to print a one-line hint instead of auto-invoking install.
66
+ - `preuninstall.js` -- Route through new uninstall subcommand (adapter-aware).
67
+ - `package.json` -- Add `@clack/prompts` dependency; ensure `bin.claude-notifications` entry still points at updated script.
68
+ - `README.md` -- Document new flow (separate task near end).
69
+
70
+ ## Tasks & Acceptance
71
+
72
+ **Execution:**
73
+ - [x] `lib/adapters/index.js` -- Adapter interface JSDoc, registry, marker-based upsert/remove helpers, `detectAll` -- foundation others depend on
74
+ - [x] `lib/adapters/claude-code.js` -- Adapter interface; writes Notification + Stop hooks with marker; supports modern `~/.claude/settings.json` and legacy `config.json` paths
75
+ - [x] `lib/adapters/opencode.js` -- `supportsHooks: false` via `_stub` factory pending hook API verification
76
+ - [x] `lib/adapters/gemini.js` `auggie.js` `copilot.js` `kimi.js` `vibe.js` `codex.js` + `lib/adapters/_stub.js` -- detection stubs via shared factory
77
+ - [x] `lib/tui.js` -- `@clack/prompts` multi-select, unsupported rows shown as a note
78
+ - [x] `bin/claude-notifications.js` -- install|uninstall|status|sounds|test subcommands with flags `--cli`, `--dry-run`, `--keep-going`, `--non-interactive`, `--json`
79
+ - [x] `lib/config.js` -- `notifyBinaryPath()` exported
80
+ - [x] `postinstall.js` `preuninstall.js` -- postinstall no longer auto-writes hooks (prints hint); preuninstall routes through adapter-aware uninstall
81
+ - [x] `package.json` -- `@clack/prompts` added; `engines.node >= 18` for native `node --test`; test script updated
82
+ - [x] Tests: `test/adapters/{registry,claude-code,stubs}.test.js` + `test/helpers/fake-fs.js` -- 43 tests covering happy / idempotent / unsupported / missing-config / malformed-JSON / legacy-path / dry-run / foreign-entry-preservation / marker-removal
83
+ - [x] `README.md` -- new install flow, command reference, adapter extension guide
84
+
85
+ **Acceptance Criteria:**
86
+ - Given `claude-notify` is on PATH and `claude` + `opencode` are installed, when the user runs `claude-notifications install` and confirms both, then both CLI config files contain a single hook block tagged with our marker and `claude-notifications status` reports both as `installed`.
87
+ - Given a hook is already installed, when the user re-runs `install`, then no config file is rewritten (mtime unchanged) and the TUI shows the row as "already installed".
88
+ - Given a CLI with `supportsHooks: false` is detected, when the TUI renders, then its row is disabled with a visible reason and cannot be toggled on.
89
+ - Given the installer runs in a non-TTY with `--cli=claude`, when it completes, then it exits 0 and stdout contains a parseable `installed: [claude]` line.
90
+ - Given the user runs `claude-notifications uninstall`, when adapters execute, then only blocks tagged with our marker are removed and foreign hook entries remain intact.
91
+ - Given `claude-notify` is not in `$PATH`, when `install` runs, then the program exits non-zero before any adapter writes, with a message pointing to the fix.
92
+
93
+ ## Design Notes
94
+
95
+ **Marker pattern for idempotency:**
96
+ Each adapter injects a JSON object carrying `{ "source": "claude-notifications", "version": <pkg.version> }` alongside the hook config. Upsert logic: find first element where `source === "claude-notifications"`, replace it in-place; otherwise append. Uninstall filters out exactly those elements. This avoids the current `bin/claude-notifications.js:83-87` fragility where presence check is by command string and any user edit breaks it.
97
+
98
+ **Per-CLI hook-surface honesty:**
99
+ Only Claude Code's hook system is well-documented today. Rather than fabricate hooks for CLIs whose plugin surface we haven't verified, stubs set `supportsHooks: false` with a reason string. As each CLI's hook API is confirmed, flip the flag in that adapter only. This keeps the TUI truthful and prevents silent no-ops.
100
+
101
+ **TUI choice:**
102
+ `@clack/prompts` picked over `inquirer`/`enquirer` for smaller footprint, ESM-first, and native multi-select with disabled rows. Single new dep.
103
+
104
+ ## Spec Change Log
105
+
106
+ ### Iteration 1 — review pass (all findings classified as patch; no loopback)
107
+
108
+ **Findings summary (deduped across 3 reviewers):**
109
+ - Acceptance auditor: AC 4 wording uses `--cli=claude` (shorthand) — canonical id per Code Map is `claude-code`. Reviewer flagged this as critical spec violation; resolved by interpreting `claude` as shorthand (Code Map is authoritative) and by adding an explicit `installed: [ids]` line to stdout so the "parseable" intent is satisfied literally.
110
+ - Blind hunter (10 findings): `--keep-going` wrongly suppressed exit code; partial uninstall wiped shared sounds; `nonInteractive` auto-tripped on pipes; `writeJson` not atomic; `makeMarker` spread order; `process.exit(1)` before drain; legacy `config.json` precedence bug; uninstall aborted on one malformed config; `parseArgs` silently dropped positionals; `sox execSync` shell-injection via `$HOME`/`$TMPDIR`.
111
+ - Edge case hunter (12 findings): marker `version` caused rewrite-storms on `npm update`; UTF-8 BOM broke `readJsonSafe`; root-level JSON array silently corrupted; preuninstall unhandled rejection; plus duplicates of blind-hunter findings.
112
+
113
+ **Amendments (non-frozen only):**
114
+ - Verification: updated example command to `--cli=claude-code`; documented the new `installed: [ids]` stdout line.
115
+ - Design Notes: clarified that marker `version` is stored for diagnostics but excluded from the idempotency diff so routine package upgrades don't trigger unnecessary writes.
116
+
117
+ **Known-bad state avoided:**
118
+ - CI runs silently passing while adapter writes fail
119
+ - `claude-notify` hook left pointing at a deleted sound file after partial uninstall
120
+ - `~/.claude/settings.json` truncated by an interrupted write
121
+ - Hooks written to stale `~/.claude/config.json` while live Claude Code reads `settings.json`
122
+ - Shell-injection surface reachable via environment-controlled paths in the sox pipeline
123
+
124
+ **KEEP (must survive any future re-derivation):**
125
+ - Marker-based idempotency (`source: "claude-notifications"`) as the single authority for "we own this"
126
+ - Adapter interface shape: `{ id, label, binary, supportsHooks, detect, configPath, install, uninstall, status }`
127
+ - `_stub.js` factory for unsupported CLIs — honesty over fabrication
128
+ - Per-adapter module files under `lib/adapters/` (not a monolithic switch)
129
+ - Zero test-framework dependency (`node --test`)
130
+ - `@clack/prompts` as the only TUI dep
131
+
132
+ **Deferred (pre-existing or within-spec; not fixed this iteration):**
133
+ - Warn-on-uninstall when a marker block's body was user-edited (spec says "remove only blocks matching our marker"; current behavior is within spec).
134
+ - Symlink-transparent writes to `settings.json`. Standard Node fs behavior; surprising but not a bug.
135
+ - Shell alias/function detection fallback when `which` returns null. Standard `which` limitation.
136
+
137
+ ## Verification
138
+
139
+ **Commands:**
140
+ - `node bin/claude-notifications.js install --dry-run --non-interactive --cli=claude-code` -- expected: exit 0, prints `✓ claude-code: would write <path>` plus a trailing `installed: [claude-code]` line; no files changed.
141
+ - `node bin/claude-notifications.js status` -- expected: table listing each adapter with detected/installed state.
142
+ - `node bin/claude-notifications.js status --json` -- expected: structured JSON array, one object per adapter.
143
+ - `npm test` -- expected: all adapter tests pass; coverage of I/O matrix rows.
144
+
145
+ **Manual checks:**
146
+ - Run `claude-notifications install` in a real terminal; verify the TUI renders, unsupported rows are shown as a separate note and cannot be toggled on, confirming the selection writes `source: "claude-notifications"` entries to the target config only.
147
+ - Run `claude-notifications install --cli=claude-code` twice; verify the second run reports `already installed` and the config mtime does not change.
148
+ - Run `claude-notifications install --cli=claude-code --keep-going` with a deliberately broken adapter (e.g. set the target config to `not json`); verify process exit code is 2 despite `--keep-going`.
149
+
150
+ ## Suggested Review Order
151
+
152
+ **Adapter protocol (start here)**
153
+
154
+ - Foundation: registry, marker protocol, atomic writes, BOM/array-guard parse.
155
+ [`index.js:135`](../../lib/adapters/index.js#L135)
156
+
157
+ - Marker factory — spread order prevents callers from overriding `source`/`version`.
158
+ [`index.js:154`](../../lib/adapters/index.js#L154)
159
+
160
+ - Upsert logic — version excluded from diff so `npm update` doesn't rewrite every config.
161
+ [`index.js:111`](../../lib/adapters/index.js#L111)
162
+
163
+ **Claude Code adapter**
164
+
165
+ - Modern `settings.json` preferred unconditionally; legacy `config.json` only when modern is absent.
166
+ [`claude-code.js:23`](../../lib/adapters/claude-code.js#L23)
167
+
168
+ - Install writes `Notification` + `Stop` hook blocks, idempotent via marker.
169
+ [`claude-code.js:94`](../../lib/adapters/claude-code.js#L94)
170
+
171
+ - Uninstall removes only marker-tagged entries, preserves foreign hooks.
172
+ [`claude-code.js:115`](../../lib/adapters/claude-code.js#L115)
173
+
174
+ **Unsupported-CLI honesty pattern**
175
+
176
+ - Factory returns inert install/uninstall with a reason, so the TUI can surface without fabricating hook integrations.
177
+ [`_stub.js:9`](../../lib/adapters/_stub.js#L9)
178
+
179
+ - Opencode uses the factory pending hook-API verification.
180
+ [`opencode.js:7`](../../lib/adapters/opencode.js#L7)
181
+
182
+ **CLI orchestration**
183
+
184
+ - `parseArgs` — rejects unknown positionals and bad `--cli` form via typed error; `nonInteractive` defaults to false.
185
+ [`claude-notifications.js:32`](../../bin/claude-notifications.js#L32)
186
+
187
+ - `doInstall` — PATH preflight, id resolution, TTY check, adapter loop.
188
+ [`claude-notifications.js:151`](../../bin/claude-notifications.js#L151)
189
+
190
+ - `doUninstall` — best-effort loop, full-vs-partial sounds-dir gate.
191
+ [`claude-notifications.js:196`](../../bin/claude-notifications.js#L196)
192
+
193
+ - `reportResults` — `installed: [ids]` line for AC 4 scripted parsing; `--keep-going` no longer suppresses exit code.
194
+ [`claude-notifications.js:278`](../../bin/claude-notifications.js#L278)
195
+
196
+ - Top-level error handler — `process.exitCode` instead of `process.exit`, so pending I/O drains.
197
+ [`claude-notifications.js:358`](../../bin/claude-notifications.js#L358)
198
+
199
+ **Presentation**
200
+
201
+ - TUI: selectable set = installed + supportsHooks; unsupported-but-detected rows shown as a separate note.
202
+ [`tui.js:40`](../../lib/tui.js#L40)
203
+
204
+ **Side-effect hardening**
205
+
206
+ - Sound generation uses `execFileSync` with argv, no shell-injection surface.
207
+ [`claude-notifications.js:105`](../../bin/claude-notifications.js#L105)
208
+
209
+ - postinstall: prints a hint only; no more surprise writes on `npm install`.
210
+ [`postinstall.js:1`](../../postinstall.js#L1)
211
+
212
+ - preuninstall: routes through adapter-aware uninstall subcommand.
213
+ [`preuninstall.js:1`](../../preuninstall.js#L1)
214
+
215
+ **Tests**
216
+
217
+ - Registry + marker + upsert/remove invariants.
218
+ [`registry.test.js:1`](../../test/adapters/registry.test.js#L1)
219
+
220
+ - Claude Code: happy path, idempotency, foreign-entry preservation, legacy path fallback.
221
+ [`claude-code.test.js:1`](../../test/adapters/claude-code.test.js#L1)
222
+
223
+ - Stubs: interface shape and inert install for every unsupported adapter.
224
+ [`stubs.test.js:1`](../../test/adapters/stubs.test.js#L1)
225
+
226
+ - Patch regressions: marker hijack, version-only diff, BOM, non-object root, legacy precedence.
227
+ [`patches.test.js:1`](../../test/adapters/patches.test.js#L1)
228
+
229
+ **Config/deps**
230
+
231
+ - `notifyBinaryPath()` helper — single source of truth for `claude-notify` PATH check.
232
+ [`config.js:144`](../../lib/config.js#L144)
233
+
234
+ - `@clack/prompts` dep, `engines.node >= 18`, `node --test` wiring.
235
+ [`package.json:11`](../../package.json#L11)
236
+
237
+ - User-facing install flow and adapter-extension guide.
238
+ [`README.md:7`](../../README.md#L7)
239
+
240
+ **Manual checks:**
241
+ - Run `claude-notifications install` in a real terminal; verify TUI renders, disabled rows unselectable, confirm triggers writes only to selected CLI configs, and that `jq` on the target config shows exactly one block with `"source": "claude-notifications"`.