specpipe 1.0.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 (60) hide show
  1. package/README.md +1319 -0
  2. package/bin/devkit.js +3 -0
  3. package/package.json +61 -0
  4. package/src/cli.js +76 -0
  5. package/src/commands/check.js +33 -0
  6. package/src/commands/diff.js +84 -0
  7. package/src/commands/init-adopt.js +54 -0
  8. package/src/commands/init-agents.js +118 -0
  9. package/src/commands/init-global.js +102 -0
  10. package/src/commands/init.js +311 -0
  11. package/src/commands/list.js +54 -0
  12. package/src/commands/remove.js +133 -0
  13. package/src/commands/upgrade.js +215 -0
  14. package/src/lib/agent-guards.js +100 -0
  15. package/src/lib/agent-install.js +161 -0
  16. package/src/lib/agents.js +280 -0
  17. package/src/lib/claude-global.js +183 -0
  18. package/src/lib/detector.js +93 -0
  19. package/src/lib/hasher.js +21 -0
  20. package/src/lib/installer.js +213 -0
  21. package/src/lib/logger.js +16 -0
  22. package/src/lib/manifest.js +102 -0
  23. package/src/lib/reconcile.js +56 -0
  24. package/templates/.claude/CLAUDE.md +79 -0
  25. package/templates/.claude/hooks/comment-guard.js +126 -0
  26. package/templates/.claude/hooks/file-guard.js +216 -0
  27. package/templates/.claude/hooks/glob-guard.js +104 -0
  28. package/templates/.claude/hooks/path-guard.sh +118 -0
  29. package/templates/.claude/hooks/self-review.sh +27 -0
  30. package/templates/.claude/hooks/sensitive-guard.sh +227 -0
  31. package/templates/.claude/settings.json +68 -0
  32. package/templates/docs/WORKFLOW.md +325 -0
  33. package/templates/docs/specs/.gitkeep +0 -0
  34. package/templates/hooks/specpipe-read-guard.sh +42 -0
  35. package/templates/hooks/specpipe-shell-guard.sh +65 -0
  36. package/templates/rules/specpipe-guards.md +40 -0
  37. package/templates/scripts/test-hooks.sh +66 -0
  38. package/templates/skills/sp-build/SKILL.md +776 -0
  39. package/templates/skills/sp-challenge/SKILL.md +255 -0
  40. package/templates/skills/sp-commit/SKILL.md +174 -0
  41. package/templates/skills/sp-explore/SKILL.md +730 -0
  42. package/templates/skills/sp-fix/SKILL.md +266 -0
  43. package/templates/skills/sp-humanize/SKILL.md +212 -0
  44. package/templates/skills/sp-investigate/SKILL.md +648 -0
  45. package/templates/skills/sp-md-render/SKILL.md +200 -0
  46. package/templates/skills/sp-md-render/components.md +415 -0
  47. package/templates/skills/sp-md-render/template.html +283 -0
  48. package/templates/skills/sp-plan/SKILL.md +947 -0
  49. package/templates/skills/sp-review/SKILL.md +268 -0
  50. package/templates/skills/sp-scaffold/SKILL.md +237 -0
  51. package/templates/skills/sp-scaffold/references/ARCHITECTURE.md.tmpl +228 -0
  52. package/templates/skills/sp-scaffold/references/DESIGN.md.tmpl +113 -0
  53. package/templates/skills/sp-scaffold/references/adr/NNNN-template.md +92 -0
  54. package/templates/skills/sp-scaffold/references/stack-profiles/react.md +36 -0
  55. package/templates/skills/sp-spec-render/SKILL.md +254 -0
  56. package/templates/skills/sp-spec-render/components.md +418 -0
  57. package/templates/skills/sp-spec-render/examples/user-auth.html +749 -0
  58. package/templates/skills/sp-spec-render/examples/user-auth.md +114 -0
  59. package/templates/skills/sp-spec-render/template.html +222 -0
  60. package/templates/skills/sp-voices/SKILL.md +1184 -0
@@ -0,0 +1,68 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Bash",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/path-guard.sh"
10
+ },
11
+ {
12
+ "type": "command",
13
+ "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/sensitive-guard.sh"
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "matcher": "Read|Write|Edit|MultiEdit|Grep",
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/sensitive-guard.sh"
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "matcher": "Edit|MultiEdit",
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/comment-guard.js"
32
+ }
33
+ ]
34
+ },
35
+ {
36
+ "matcher": "Glob",
37
+ "hooks": [
38
+ {
39
+ "type": "command",
40
+ "command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/glob-guard.js"
41
+ }
42
+ ]
43
+ }
44
+ ],
45
+ "PostToolUse": [
46
+ {
47
+ "matcher": "Write|Edit|MultiEdit",
48
+ "hooks": [
49
+ {
50
+ "type": "command",
51
+ "command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/file-guard.js"
52
+ }
53
+ ]
54
+ }
55
+ ],
56
+ "Stop": [
57
+ {
58
+ "matcher": "",
59
+ "hooks": [
60
+ {
61
+ "type": "command",
62
+ "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/self-review.sh"
63
+ }
64
+ ]
65
+ }
66
+ ]
67
+ }
68
+ }
@@ -0,0 +1,325 @@
1
+ # Development Workflow Reference
2
+
3
+ > Spec-first development: every change follows SPEC (with acceptance scenarios) → CODE + TESTS → BUILD PASS.
4
+
5
+ ---
6
+
7
+ ## 1. Workflow Types
8
+
9
+ ### New Project (Greenfield)
10
+
11
+ When: Brand-new project — no codebase yet (empty repo, no package manager / `src/`).
12
+
13
+ ```
14
+ Step 1 → /sp-explore "what you're building"
15
+ Detects greenfield → ALSO decides app-type + stack (versions researched,
16
+ current — not recalled from memory) and emits a Bootstrap Brief.
17
+ Output: docs/explore/<feature>.md (with ## Bootstrap Brief)
18
+
19
+ Step 2 → /sp-scaffold
20
+ Reads the Bootstrap Brief → generator-first runnable skeleton:
21
+ core/ + ONE pattern-demonstrating module + co-located tests.
22
+ Smoke-gated (install → build → start/smoke must be GREEN, ≥1 real test —
23
+ this resolves TEST_CMD for /sp-build). Writes ARCHITECTURE.md + ADRs.
24
+ Hands off ONLY when it actually runs; otherwise BLOCKED.
25
+
26
+ Step 3 → /sp-plan → /sp-build
27
+ Normal New Feature flow, now on a runnable base. /sp-build's Foundation
28
+ Gate confirms the harness exists before the first RED.
29
+ ```
30
+
31
+ ### Explore Before Planning
32
+
33
+ When: Requirements are unclear, multiple approaches are possible, or it's a brownfield feature with existing code to understand first.
34
+
35
+ ```
36
+ Step 1 → /sp-explore "feature description"
37
+ Clarifies: why the feature is needed, desired behavior, boundaries,
38
+ edge cases, business rules, permissions, UI expectations.
39
+ Asks questions as a Client Technical Lead — one topic at a time.
40
+ Output: docs/explore/<feature>.md
41
+
42
+ Step 2 → /sp-plan "feature description"
43
+ Auto-detects docs/explore/<feature>.md → skips redundant codebase
44
+ discovery, uses explore findings as direct input for the spec.
45
+ Continue with the normal New Feature flow from Step 2.
46
+ ```
47
+
48
+ ### New Feature
49
+
50
+ When: Building something that doesn't exist yet (no code, no spec).
51
+
52
+ ```
53
+ Step 1 → /sp-plan "description of feature"
54
+ Generates: docs/specs/<feature>/<feature>.md (spec with acceptance scenarios)
55
+ Runs Scope Challenge: reuse check, complexity smell (8+ files = flag),
56
+ framework built-in search, distribution check.
57
+ Adds "What Already Exists" and "Not in Scope" sections to the spec.
58
+ Answers validation questions with effort scales (human: X / CC: Y).
59
+ At the end, suggests /sp-spec-render if you want a scannable HTML view.
60
+ Review before proceeding.
61
+
62
+ Step 1.5 → (Optional) /sp-spec-render <feature>
63
+ Generates <feature>.html next to the .md — sidebar TOC, story cards,
64
+ collapsible Given/When/Then, dark/light theme. Useful when the spec
65
+ is long and you want to scan it visually or share it with stakeholders.
66
+ Source .md remains canonical; .html is regenerable, never hand-edit.
67
+ For non-spec markdown (investigation, explore, RFC, retro, README),
68
+ use /sp-md-render <file.md> instead — same idea, generic content.
69
+
70
+ Step 2 → (Optional) /sp-challenge docs/specs/<feature>/<feature>.md
71
+ Adversarial review: spawns hostile reviewers to find flaws.
72
+ Recommended for complex features, auth, data pipelines.
73
+ Skip for simple CRUD or small features.
74
+
75
+ Step 3 → Implement in chunks. After each chunk:
76
+ /sp-build
77
+ Checks 8 edge case categories (null, empty, invalid types, boundary,
78
+ error paths, race conditions, large data, special chars).
79
+ Draws Coverage Map before writing tests: traces code paths + user flows,
80
+ marks [GAP], [GAP][→E2E], [GAP][→EVAL] (with pass@1/pass@3 guidance).
81
+ Regression rule enforced.
82
+ Repeat until chunk is green.
83
+
84
+ Step 4 → /sp-review (before merge)
85
+ Checks API/Backend patterns (rate limiting, timeouts, CORS, error leakage).
86
+ Extra layer for AI-generated code: regressions, trust boundaries, cost escalation.
87
+
88
+ Step 4.5 → (Optional) /sp-voices
89
+ Multi-LLM second opinion: sends the diff (or spec) to 2–3 different LLMs,
90
+ synthesizes consensus + disagreements. Use when: high-stakes change
91
+ (auth/payment/data), mixed-confidence findings from /sp-review, or
92
+ you want cross-model verification before merge. Skip for routine changes.
93
+
94
+ Step 5 → /sp-commit
95
+ ```
96
+
97
+ ### Update Existing Feature
98
+
99
+ When: Changing behavior, adding options, refactoring logic.
100
+
101
+ ```
102
+ Step 1 → /sp-plan docs/specs/<feature>/<feature>.md "description of changes"
103
+ Mode C handles everything: snapshot → classification → change report → apply.
104
+ Do NOT manually edit the spec before running /sp-plan — it creates the
105
+ snapshot first, then applies changes. Manual edits bypass snapshot protection.
106
+ At the end, suggests /sp-spec-render to refresh <feature>.html if you
107
+ have an HTML view (it's stale after this update).
108
+
109
+ Step 2 → Implement code changes.
110
+ /sp-build
111
+ Fix until green.
112
+
113
+ Step 4 → /sp-review → /sp-commit
114
+ ```
115
+
116
+ ### Bug Fix
117
+
118
+ When: Something is broken and needs fixing.
119
+
120
+ ```
121
+ Step 0 → (OPTIONAL) /sp-investigate "description of the bug"
122
+ Use ONLY when: bug is complex, ambiguous, production outage, data
123
+ corruption, regression with unclear cause, or user wants diagnosis
124
+ before any code change. Skip for trivial/obvious bugs.
125
+ Read-only: traces data flow, maps blast radius, lists hypotheses
126
+ with confidence levels. Writes docs/investigate/<slug>-<date>.md.
127
+ No code changes — hands off the report to /sp-fix.
128
+
129
+ Step 1 → /sp-fix "description" (or /sp-fix docs/investigate/<slug>-<date>.md)
130
+ Auto-detects investigation file if passed → skips redundant discovery.
131
+ Draws Bug Path Diagram to confirm hypothesis ([GAP] must be locatable).
132
+ Regression rule: if diff broke existing behavior with no test → CRITICAL test required.
133
+ Writes failing test → fixes code → confirms green → runs full suite.
134
+
135
+ Step 2 → /sp-commit
136
+
137
+ Optional → If the bug reveals an undocumented edge case, update the spec.
138
+ ```
139
+
140
+ ### Remove Feature
141
+
142
+ When: Deleting a feature, removing deprecated code.
143
+
144
+ ```
145
+ Step 1 → /sp-plan docs/specs/<feature>/<feature>.md "remove stories S-XXX"
146
+ Mode C creates a snapshot (removing stories = M2 = Major),
147
+ then marks stories and AS as removed in the spec.
148
+ (Or if removing the entire feature: archive the directory.)
149
+
150
+ Step 2 → Delete production code and related test code.
151
+
152
+ Step 3 → Run the full test suite with the project's native test command.
153
+ Fix any cascading breakage.
154
+
155
+ Step 4 → /sp-commit
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 2. Decision Tree
161
+
162
+ Use this to decide which workflow to follow:
163
+
164
+ ```
165
+ Is there a runnable project yet (package manager / src / build)?
166
+ ├─ No → New Project (Greenfield). /sp-explore (greenfield) → /sp-scaffold → then /sp-plan.
167
+ └─ Yes ↓
168
+
169
+ Is this a brand new feature (no existing spec or code)?
170
+ ├─ Yes
171
+ │ ├─ Are requirements clear and approach decided?
172
+ │ │ ├─ Yes → New Feature workflow. Start with /sp-plan.
173
+ │ │ │ └─ Is the feature complex (auth, data pipeline, multi-service)?
174
+ │ │ │ ├─ Yes → Run /sp-challenge after /sp-plan, before coding.
175
+ │ │ │ └─ No → Skip /sp-challenge, go straight to implementation.
176
+ │ │ └─ No → Explore Before Planning. Start with /sp-explore.
177
+ │ │ Then /sp-plan using the explore output.
178
+ └─ No
179
+ ├─ Is this a bug fix?
180
+ │ ├─ Yes → Bug Fix workflow.
181
+ │ │ ├─ Complex / outage / ambiguous cause / data corruption?
182
+ │ │ │ ├─ Yes → /sp-investigate first, then /sp-fix.
183
+ │ │ │ └─ No → /sp-fix directly.
184
+ │ └─ No
185
+ │ ├─ Are you removing/deprecating code?
186
+ │ │ ├─ Yes → Remove Feature workflow.
187
+ │ │ └─ No → Update Feature workflow. Start with /sp-plan.
188
+ │ │
189
+ │ └─ Is the change very small (< 5 lines, behavior unchanged)?
190
+ │ └─ Yes → Skip spec update. Just /sp-build and /sp-commit.
191
+ ```
192
+
193
+ ---
194
+
195
+ ## 3. Prompt Templates
196
+
197
+ Copy-paste these when working with your agent.
198
+
199
+ ### Template A — Implement + Test Together
200
+
201
+ ```
202
+ I just implemented [brief description].
203
+ Files changed: [list files]
204
+
205
+ Based on:
206
+ - Spec: docs/specs/<feature>/<feature>.md (section §X)
207
+ - Acceptance scenarios: docs/specs/<feature>/<feature>.md (section ## Stories)
208
+
209
+ Write tests for the part I just implemented.
210
+ Only tests related to this change — not the entire feature.
211
+ Build and run until all pass.
212
+ If the spec seems incomplete, note what's missing but don't change it.
213
+ ```
214
+
215
+ ### Template B — Update Feature + Tests
216
+
217
+ ```
218
+ I'm about to change [description of change].
219
+ Affected files: [list]
220
+
221
+ 1. /sp-plan docs/specs/<feature>/<feature>.md "description of changes"
222
+ (handles snapshot + spec update + acceptance scenarios)
223
+ 2. Implement the code change
224
+ 3. Update tests to match
225
+ 4. Build and run → fix until green
226
+ ```
227
+
228
+ ### Template C — Test-First Bug Fix
229
+
230
+ ```
231
+ Bug: [description]
232
+ Steps to reproduce: [steps]
233
+ Expected: [correct behavior]
234
+ Actual: [broken behavior]
235
+
236
+ 1. Write a test that reproduces this bug (must fail currently)
237
+ 2. Fix the production code to make the test pass
238
+ 3. Run the full test suite — nothing else should break
239
+ 4. Update the spec if this is an undocumented edge case
240
+ ```
241
+
242
+ ### Template D — Remove Feature
243
+
244
+ ```
245
+ Removing: [feature name]
246
+ Files to delete: [list]
247
+
248
+ 1. /sp-plan docs/specs/<feature>/<feature>.md "remove stories S-XXX, S-YYY"
249
+ (handles snapshot + marks stories and AS as removed)
250
+ 2. Delete production code
251
+ 3. Delete test code
252
+ 4. Run full test suite → fix cascading breaks
253
+ ```
254
+
255
+ ---
256
+
257
+ ## 4. Token Cost Guide
258
+
259
+ | Workflow | Estimated Tokens | When |
260
+ |----------|-----------------|------|
261
+ | `/sp-explore` | 10–20k | Before /sp-plan when requirements are unclear |
262
+ | `/sp-scaffold` | 15–40k + real install/build time | Greenfield only — once, to stand up a runnable skeleton before the first spec |
263
+ | `/sp-build` (incremental) | 5–10k | Daily, after each code chunk |
264
+ | `/sp-investigate` (complex bug) | 8–15k | OPTIONAL before /sp-fix — complex/outage only |
265
+ | `/sp-fix` (single bug) | 3–5k | As bugs arise |
266
+ | `/sp-commit` | 2–4k | Each commit |
267
+ | `/sp-review` (diff-based) | 10–20k | Before merge |
268
+ | `/sp-plan` (new feature) | 20–40k | Start of new feature |
269
+ | `/sp-challenge` (adversarial) | 15–30k | After /sp-plan, for complex features |
270
+ | `/sp-spec-render` (HTML view) | 3–8k | User-invoked after `/sp-plan` if HTML view wanted, or to refresh stale `.html` |
271
+ | `/sp-md-render` (HTML view, any md) | 3–8k | User-invoked for non-spec markdown — investigation, explore, RFC, retro, README |
272
+ | `/sp-voices` (multi-LLM review) | 10–30k + external API cost | Optional — after /sp-review for high-stakes changes |
273
+ | `/sp-humanize` (rephrase text) | 2–6k | User-invoked — rephrase plan/notes/AI output into send-ready text. Outside the dev cycle |
274
+ | Full audit (manual) | 100k+ | Before release, quarterly |
275
+
276
+ **Rule of thumb:** Daily work uses templates + `/sp-build` → low token cost.
277
+ Save `/sp-plan` and full audits for significant milestones.
278
+
279
+ ---
280
+
281
+ ## 5. CI Integration Checklist
282
+
283
+ Use this as a PR review checklist (enforce manually or via CI):
284
+
285
+ - [ ] **Spec updated?** If production behavior changed, `docs/specs/<feature>/` should have changes.
286
+ - [ ] **Acceptance scenarios updated?** If spec behavior changed, AS in spec should reflect it.
287
+ - [ ] **Tests pass?** The project's test command exits 0.
288
+ - [ ] **No dead tests?** Removed production code → removed corresponding tests.
289
+ - [ ] **Coverage not decreased?** (Optional, per-team decision.)
290
+ - [ ] **No secrets in diff?** No API keys, tokens, passwords in committed code.
291
+ - [ ] **Commit messages conventional?** `type(scope): description` format.
292
+
293
+ ---
294
+
295
+ ## 6. Spec-Test-Code Sync Rules
296
+
297
+ | Change | Must Also Update |
298
+ |--------|-----------------|
299
+ | Production code behavior changed | Spec (including acceptance scenarios) + tests |
300
+ | Spec updated | Acceptance scenarios + tests (if behavior changed) |
301
+ | Code removed | Remove related tests. Mark spec and AS as removed. |
302
+ | Bug fix | Add test. Update spec if edge case was undocumented. |
303
+
304
+ **Never acceptable:**
305
+ - Code changed, spec not updated (spec drift)
306
+ - Code changed, tests not updated (untested code)
307
+ - Spec changed, acceptance scenarios or tests not updated (AS drift)
308
+ - Code removed, dead tests remain (orphaned tests)
309
+
310
+ **Acceptable shortcut** for changes under 5 lines with no behavior change:
311
+ - Code + tests together, skip spec update (same PR).
312
+
313
+ ---
314
+
315
+ ## 7. Common Pitfalls
316
+
317
+ | Pitfall | Symptom | Prevention |
318
+ |---------|---------|------------|
319
+ | **Spec drift** | Code does X, spec says Y | Always update spec before coding |
320
+ | **Dead tests** | Tests pass but test removed functionality | Delete tests when removing features |
321
+ | **Over-testing** | 50 tests for simple CRUD, slow suite | Focus on behavior, not implementation details |
322
+ | **Mock abuse** | Tests pass with mocks, fail in production | Use real implementations; mock only external services |
323
+ | **Big-bang testing** | All tests written after all code is done | Test incrementally after each chunk |
324
+ | **Ignoring flaky tests** | Tests pass sometimes, fail sometimes | Fix immediately — flaky tests erode trust |
325
+ | **Testing private methods** | Tests break on refactor | Test public API and behavior only |
File without changes
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ # specpipe-read-guard.sh — blocking pre-file-read hook (enforced guardrail).
3
+ #
4
+ # For agents whose pre-read payload puts the path at .file_path (Cursor
5
+ # beforeReadFile) or .tool_input.file_path (Claude/Codex Read). Blocks (exit 2)
6
+ # reads of secret files; allows *.example / *.sample / *.template.
7
+ #
8
+ # Exit codes: 0 = allow, 2 = block (reason on stderr).
9
+ set -euo pipefail
10
+
11
+ INPUT=$(cat)
12
+ [[ -z "$INPUT" ]] && exit 0
13
+
14
+ extract_path() {
15
+ if command -v node &>/dev/null; then
16
+ printf '%s' "$1" | node -e "
17
+ try {
18
+ const d = JSON.parse(require('fs').readFileSync(0,'utf-8'));
19
+ const p = d.file_path ?? d.tool_input?.file_path ?? d.path;
20
+ if (typeof p === 'string') process.stdout.write(p);
21
+ } catch {}
22
+ " 2>/dev/null
23
+ else
24
+ printf '%s' "$1" | grep -oE '\"file_path\"[[:space:]]*:[[:space:]]*\"[^\"]*\"' | head -1 | sed -E 's/.*:[[:space:]]*\"//;s/\"$//'
25
+ fi
26
+ }
27
+
28
+ P=$(extract_path "$INPUT") || exit 0
29
+ [[ -z "$P" ]] && exit 0
30
+
31
+ # Allow example/template variants.
32
+ case "$P" in
33
+ *.example|*.sample|*.template) exit 0 ;;
34
+ esac
35
+
36
+ SECRET="(\.env)($|\.[A-Za-z0-9]+$)|\.(pem|key|p12|pfx|keystore)$|id_(rsa|ed25519|ecdsa)$|(credentials|secrets?)\.(json|ya?ml|toml|txt)$"
37
+ if printf '%s\n' "$P" | grep -qiE "$SECRET"; then
38
+ echo "Blocked: '$P' is a secret file. Use its .example variant, or ask the user first." >&2
39
+ exit 2
40
+ fi
41
+
42
+ exit 0
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ # specpipe-shell-guard.sh — blocking pre-shell/pre-tool hook (enforced guardrail).
3
+ #
4
+ # Portable across agents whose hook payload puts the shell command at either
5
+ # .tool_input.command (Codex PreToolUse, Claude PreToolUse)
6
+ # .command (Cursor beforeShellExecution)
7
+ # Blocks (exit 2) commands that explore wasteful directories or touch secrets.
8
+ #
9
+ # Exit codes: 0 = allow, 2 = block (reason on stderr). Exit 2 is the portable
10
+ # block primitive honored by Claude, Codex, and Cursor.
11
+ set -euo pipefail
12
+
13
+ INPUT=$(cat)
14
+ [[ -z "$INPUT" ]] && exit 0
15
+
16
+ extract_command() {
17
+ if command -v node &>/dev/null; then
18
+ printf '%s' "$1" | node -e "
19
+ try {
20
+ const d = JSON.parse(require('fs').readFileSync(0,'utf-8'));
21
+ const c = d.tool_input?.command ?? d.command;
22
+ if (typeof c === 'string') process.stdout.write(c);
23
+ } catch {}
24
+ " 2>/dev/null
25
+ else
26
+ printf '%s' "$1" | grep -oE '\"command\"[[:space:]]*:[[:space:]]*\"[^\"]*\"' | head -1 | sed -E 's/.*:[[:space:]]*\"//;s/\"$//'
27
+ fi
28
+ }
29
+
30
+ COMMAND=$(extract_command "$INPUT") || exit 0
31
+ [[ -z "$COMMAND" ]] && exit 0
32
+
33
+ SEP="[/\\\\]"
34
+
35
+ # Secrets: block reading/copying credential files (allow *.example / *.sample).
36
+ SECRET="(^|[ /\\\\\"'])(\.env)($|[ /\\\\\"'.])"
37
+ SECRET+="|(^|[ /\\\\])\.env\.[A-Za-z0-9]+"
38
+ SECRET+="|\.(pem|key|p12|pfx|keystore)(\b|$)"
39
+ SECRET+="|(^|[ /\\\\])id_(rsa|ed25519|ecdsa)"
40
+ SECRET+="|(credentials|secrets?)\.(json|ya?ml|toml|txt)"
41
+ if printf '%s\n' "$COMMAND" | grep -qiE '(^|[ |;&`(])(cat|less|more|head|tail|bat|cp|nano|vi|vim|grep|rg|strings|xxd|od|base64)([ ])'; then
42
+ CLEAN=$(printf '%s\n' "$COMMAND" | sed -E 's/\.env\.(example|sample|template)//g')
43
+ if printf '%s\n' "$CLEAN" | grep -qiE "$SECRET"; then
44
+ echo "Blocked: command accesses a secret file (.env / key / credentials). Use .env.example, or ask the user first." >&2
45
+ exit 2
46
+ fi
47
+ fi
48
+
49
+ # Wasteful directories: only when an exploration verb is present.
50
+ EXPLORE="(^|[[:space:]|;&\`(])(ls|ll|la|find|cat|head|tail|less|more|wc|stat|du|tree|bat|od|xxd|hexdump|nl)([[:space:]]|$)"
51
+ printf '%s\n' "$COMMAND" | grep -qE "$EXPLORE" || exit 0
52
+
53
+ BLOCKED="(^|[ /\\\\])node_modules(${SEP}|$| )"
54
+ BLOCKED+="|(__pycache__)|\.git${SEP}(objects|refs)"
55
+ BLOCKED+="|(^|[ /\\\\])dist${SEP}|(^|[ /\\\\])build${SEP}|\.next${SEP}"
56
+ BLOCKED+="|(^|[ /\\\\])vendor(${SEP}|$| )|(^|[ /\\\\])target${SEP}"
57
+ BLOCKED+="|(^|[ /\\\\])\.venv${SEP}|(^|[ /\\\\])venv${SEP}|\.pytest_cache${SEP}|\.cache(${SEP}|$| )"
58
+ CLEAN=$(printf '%s\n' "$COMMAND" | sed -E "s|node_modules[/\\]\.bin[/\\][^[:space:]]*||g")
59
+ if printf '%s\n' "$CLEAN" | grep -qE "$BLOCKED"; then
60
+ M=$(printf '%s\n' "$COMMAND" | grep -oE "$BLOCKED" | head -1)
61
+ echo "Blocked: command explores '$M' — a large/generated directory. Use scoped paths or Grep." >&2
62
+ exit 2
63
+ fi
64
+
65
+ exit 0
@@ -0,0 +1,40 @@
1
+ These are the always-on operating rules for working in this repository with specpipe.
2
+ On Claude Code the guardrails are enforced by hooks; on other agents this whole document
3
+ is an always-on rule you must self-enforce.
4
+
5
+ ## Spec-first cycle
6
+
7
+ Every change follows: **SPEC (with acceptance scenarios) → CODE + TESTS → BUILD PASS**.
8
+
9
+ - Specs live in `docs/specs/<feature>/<feature>.md`; acceptance scenarios (Given/When/Then)
10
+ are embedded under `## Stories`.
11
+ - Never write code before the spec exists. Never auto-modify a spec from code.
12
+ - The spec is the source of truth — if code contradicts it, the code is wrong.
13
+
14
+ ## Guardrails
15
+
16
+ - **Don't explore large directories.** Never grep/list/read inside `node_modules/`,
17
+ build/dist artifacts, or `.git/` internals — scope to specific paths.
18
+ - **Never touch secrets.** Do not read or write `.env*`, private keys, credentials, or
19
+ token stores. Respect any `.agentignore` patterns.
20
+ - **Never drop real code.** Don't replace implementation with placeholder comments like
21
+ `// ... existing code ...`. Reproduce the full code when editing.
22
+ - **Avoid broad globs.** No `**/*.ts` at the project root; scope globs to a directory.
23
+ - **Keep files focused.** Don't let a source file grow past a few hundred lines — split.
24
+
25
+ ## Testing
26
+
27
+ - Run the project's native test command (`npx vitest run`, `pytest`, `cargo test`,
28
+ `go test ./...`, `swift test`, …). Compile/typecheck before running tests.
29
+ - Max 3 fix loops on a failure, then stop and report.
30
+ - **Never edit production code to make a test pass** — ask first.
31
+ - No mocks/fakes/stubs to pass builds; real implementations only. Test doubles are for
32
+ external services (APIs, DBs) that can't run locally.
33
+
34
+ ## Conventions
35
+
36
+ - Commits: conventional — `type(scope): description` (`feat`, `fix`, `docs`, `refactor`,
37
+ `test`, `chore`, `perf`, `build`, `ci`).
38
+ - File names: kebab-case, descriptive enough to understand purpose from the path.
39
+ - Never `git push --force` to `main`/`master`; never commit `.env`, certs, or keys.
40
+ - Self-review before finishing: tests pass, no secrets, no debug code, matches the spec.
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bash
2
+ # test-hooks.sh — Hook unit tests
3
+ # Usage: bash scripts/test-hooks.sh [--filter PATTERN]
4
+ # Exit: 0 = all pass, 1 = failures
5
+
6
+ set -uo pipefail
7
+
8
+ FILTER=""
9
+ while [[ $# -gt 0 ]]; do
10
+ case "$1" in
11
+ --filter) FILTER="${2:-}"; shift 2 ;;
12
+ *) FILTER="$1"; shift ;;
13
+ esac
14
+ done
15
+
16
+ HOOKS_DIR="$(cd "$(dirname "$0")/../.claude/hooks" && pwd)"
17
+ PASS=0; FAIL=0
18
+
19
+ # Encode a string as a JSON string literal using node
20
+ json_str() { node -e "process.stdout.write(JSON.stringify(process.argv[1]))" -- "$1"; }
21
+
22
+ bash_payload() { printf '{"tool_name":"Bash","tool_input":{"command":%s}}' "$(json_str "$1")"; }
23
+
24
+ run() {
25
+ local name="$1" hook="$2" payload="$3" expected_exit="$4"
26
+ if [[ -n "$FILTER" && "$name" != *"$FILTER"* ]]; then return 0; fi
27
+ local actual_exit=0
28
+ printf '%s' "$payload" | bash "$HOOKS_DIR/$hook" >/dev/null 2>&1 || actual_exit=$?
29
+ if [[ "$actual_exit" == "$expected_exit" ]]; then
30
+ echo "[PASS] $name"
31
+ PASS=$((PASS + 1))
32
+ else
33
+ echo "[FAIL] $name (expected exit $expected_exit, got $actual_exit)"
34
+ FAIL=$((FAIL + 1))
35
+ fi
36
+ }
37
+
38
+ # ── path-guard.sh ──────────────────────────────────────────────────────────────
39
+
40
+ # Should BLOCK: reading/listing blocked dirs
41
+ run "pg: ls dist/" path-guard.sh "$(bash_payload 'ls dist/')" 2
42
+ run "pg: cat dist/file" path-guard.sh "$(bash_payload 'cat dist/bundle.js')" 2
43
+ run "pg: find dist" path-guard.sh "$(bash_payload 'find dist/ -name "*.js"')" 2
44
+ run "pg: head dist file" path-guard.sh "$(bash_payload 'head -20 dist/server.js')" 2
45
+ run "pg: ls node_modules" path-guard.sh "$(bash_payload 'ls node_modules/')" 2
46
+ run "pg: cat node_modules" path-guard.sh "$(bash_payload 'cat node_modules/lodash/index.js')" 2
47
+ run "pg: ls build/" path-guard.sh "$(bash_payload 'ls build/')" 2
48
+ run "pg: wc dist file" path-guard.sh "$(bash_payload 'wc -l dist/bundle.js')" 2
49
+
50
+ # Should ALLOW: existence/permission checks and variable assignments (no file content read)
51
+ # Regression: dist/ path as intermediate component in binary check — path-guard.sh:55 blocked this
52
+ run "pg: allow -x binary check" \
53
+ path-guard.sh \
54
+ "$(bash_payload 'B=~/.claude/skills/gstack/browse/dist/browse
55
+ if [ -x "$B" ]; then echo "READY: $B"; else echo "NEEDS_SETUP"; fi')" \
56
+ 0
57
+ run "pg: allow -f check" path-guard.sh "$(bash_payload '[ -f /usr/local/dist/bin/tool ] && echo ok')" 0
58
+ run "pg: allow path assign" path-guard.sh "$(bash_payload 'TOOL=~/.claude/dist/mytool; "$TOOL" --version')" 0
59
+ run "pg: allow git command" path-guard.sh "$(bash_payload 'git rev-parse --show-toplevel')" 0
60
+ run "pg: allow empty" path-guard.sh '{}' 0
61
+
62
+ # ── End ───────────────────────────────────────────────────────────────────────
63
+
64
+ echo ""
65
+ echo "Results: $PASS passed, $FAIL failed"
66
+ [[ "$FAIL" -eq 0 ]]