safeword 0.6.9 → 0.7.1

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 (59) hide show
  1. package/dist/{check-OYYSYHFP.js → check-JWMAZLUO.js} +73 -57
  2. package/dist/check-JWMAZLUO.js.map +1 -0
  3. package/dist/{chunk-ZS3Z3Q37.js → chunk-CO3LARDH.js} +297 -65
  4. package/dist/chunk-CO3LARDH.js.map +1 -0
  5. package/dist/{chunk-LNSEDZIW.js → chunk-R4SBJKFJ.js} +159 -152
  6. package/dist/chunk-R4SBJKFJ.js.map +1 -0
  7. package/dist/{sync-BFMXZEHM.js → chunk-YNVT2S3D.js} +11 -40
  8. package/dist/chunk-YNVT2S3D.js.map +1 -0
  9. package/dist/cli.js +6 -6
  10. package/dist/{diff-325TIZ63.js → diff-2XITKG3T.js} +51 -53
  11. package/dist/diff-2XITKG3T.js.map +1 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/{reset-ZGJIKMUW.js → reset-DOGYUFAG.js} +3 -3
  14. package/dist/{setup-GAMXTFM2.js → setup-UHSXXN6I.js} +17 -20
  15. package/dist/setup-UHSXXN6I.js.map +1 -0
  16. package/dist/sync-QPNODJBJ.js +9 -0
  17. package/dist/sync-QPNODJBJ.js.map +1 -0
  18. package/dist/{upgrade-X4GREJXN.js → upgrade-GTY3MG6A.js} +10 -7
  19. package/dist/upgrade-GTY3MG6A.js.map +1 -0
  20. package/package.json +1 -1
  21. package/templates/SAFEWORD.md +52 -15
  22. package/templates/commands/architecture.md +1 -1
  23. package/templates/commands/lint.md +1 -0
  24. package/templates/commands/quality-review.md +1 -1
  25. package/templates/cursor/rules/safeword-core.mdc +5 -0
  26. package/templates/doc-templates/architecture-template.md +1 -1
  27. package/templates/doc-templates/task-spec-template.md +151 -0
  28. package/templates/doc-templates/ticket-template.md +2 -4
  29. package/templates/guides/architecture-guide.md +2 -2
  30. package/templates/guides/code-philosophy.md +1 -1
  31. package/templates/guides/context-files-guide.md +3 -3
  32. package/templates/guides/design-doc-guide.md +2 -2
  33. package/templates/guides/development-workflow.md +2 -2
  34. package/templates/guides/learning-extraction.md +9 -9
  35. package/templates/guides/tdd-best-practices.md +39 -38
  36. package/templates/guides/test-definitions-guide.md +15 -14
  37. package/templates/hooks/cursor/after-file-edit.sh +66 -0
  38. package/templates/hooks/cursor/stop.sh +50 -0
  39. package/templates/hooks/post-tool-lint.sh +19 -5
  40. package/templates/hooks/prompt-questions.sh +1 -1
  41. package/templates/hooks/session-lint-check.sh +1 -1
  42. package/templates/hooks/session-verify-agents.sh +1 -1
  43. package/templates/hooks/session-version.sh +1 -1
  44. package/templates/hooks/stop-quality.sh +1 -1
  45. package/templates/markdownlint-cli2.jsonc +18 -19
  46. package/templates/scripts/bisect-test-pollution.sh +87 -0
  47. package/templates/scripts/bisect-zombie-processes.sh +129 -0
  48. package/templates/scripts/lint-md.sh +16 -0
  49. package/templates/skills/safeword-quality-reviewer/SKILL.md +3 -3
  50. package/templates/skills/safeword-systematic-debugger/SKILL.md +246 -0
  51. package/templates/skills/safeword-tdd-enforcer/SKILL.md +221 -0
  52. package/dist/check-OYYSYHFP.js.map +0 -1
  53. package/dist/chunk-LNSEDZIW.js.map +0 -1
  54. package/dist/chunk-ZS3Z3Q37.js.map +0 -1
  55. package/dist/diff-325TIZ63.js.map +0 -1
  56. package/dist/setup-GAMXTFM2.js.map +0 -1
  57. package/dist/sync-BFMXZEHM.js.map +0 -1
  58. package/dist/upgrade-X4GREJXN.js.map +0 -1
  59. /package/dist/{reset-ZGJIKMUW.js.map → reset-DOGYUFAG.js.map} +0 -0
@@ -82,7 +82,7 @@ Patterns and examples for user stories and test definitions following TDD best p
82
82
 
83
83
  ### Standard Format (Recommended)
84
84
 
85
- ```
85
+ ````text
86
86
  As a [role/persona]
87
87
  I want [capability/feature]
88
88
  So that [business value/benefit]
@@ -94,44 +94,44 @@ Acceptance Criteria:
94
94
 
95
95
  Out of Scope:
96
96
  - [What this story explicitly does NOT include]
97
- ```
97
+ ```text
98
98
 
99
99
  ### Given-When-Then Format (Behavior-Focused)
100
100
 
101
- ```
101
+ ```text
102
102
  Given [initial context/state]
103
103
  When [action/event occurs]
104
104
  Then [expected outcome]
105
105
 
106
106
  And [additional context/outcome]
107
107
  But [exception/edge case]
108
- ```
108
+ ```text
109
109
 
110
110
  **Filled example:**
111
111
 
112
- ```
112
+ ```text
113
113
  Given I am an authenticated API user
114
114
  When I POST to /api/campaigns with valid JSON
115
115
  Then I receive a 201 Created response with campaign ID
116
116
  And the campaign appears in my GET /api/campaigns list
117
117
  But invalid JSON returns 400 with descriptive error messages
118
- ```
118
+ ```text
119
119
 
120
120
  ### Job Story Format (Outcome-Focused)
121
121
 
122
- ```
122
+ ```text
123
123
  When [situation/context]
124
124
  I want to [motivation/job-to-be-done]
125
125
  So I can [expected outcome]
126
- ```
126
+ ```text
127
127
 
128
128
  **Filled example:**
129
129
 
130
- ```
130
+ ```text
131
131
  When I'm debugging a failing test
132
132
  I want to see the exact LLM prompt and response
133
133
  So I can identify whether the issue is prompt engineering or code logic
134
- ```
134
+ ```text
135
135
 
136
136
  ---
137
137
 
@@ -141,7 +141,7 @@ So I can identify whether the issue is prompt engineering or code logic
141
141
 
142
142
  **Web App Feature:**
143
143
 
144
- ```
144
+ ```text
145
145
  As a user with multiple campaigns
146
146
  I want to switch between campaigns without reloading the page
147
147
  So that I can quickly compare game states
@@ -155,21 +155,21 @@ Acceptance Criteria:
155
155
  Out of Scope:
156
156
  - Campaign merging/deletion (separate story)
157
157
  - Multi-campaign view (future epic)
158
- ```
158
+ ```text
159
159
 
160
160
  **API Feature:**
161
161
 
162
- ```
162
+ ```text
163
163
  Given I am an authenticated API user
164
164
  When I POST to /api/campaigns with valid JSON
165
165
  Then I receive a 201 Created response with campaign ID
166
166
  And the campaign appears in my GET /api/campaigns list
167
167
  But invalid JSON returns 400 with descriptive error messages
168
- ```
168
+ ```text
169
169
 
170
170
  **CLI Feature:**
171
171
 
172
- ```
172
+ ```text
173
173
  When I'm debugging a failing test
174
174
  I want to see the exact LLM prompt and response
175
175
  So I can identify whether the issue is prompt engineering or code logic
@@ -179,11 +179,11 @@ Acceptance Criteria:
179
179
  - Response JSON is pretty-printed with syntax highlighting
180
180
  - Token count and cost are displayed
181
181
  - Works with all agent types (rules, narrative, character)
182
- ```
182
+ ```text
183
183
 
184
184
  **With Technical Constraints:**
185
185
 
186
- ```
186
+ ```text
187
187
  As a user with multiple campaigns
188
188
  I want to switch between campaigns without reloading the page
189
189
  So that I can quickly compare game states
@@ -203,17 +203,17 @@ Compatibility:
203
203
 
204
204
  Data:
205
205
  - [ ] Campaign data persists across browser sessions
206
- ```
206
+ ```text
207
207
 
208
208
  ### ❌ BAD Examples (Anti-Patterns)
209
209
 
210
210
  **Too Vague:**
211
211
 
212
- ```
212
+ ```text
213
213
  As a user
214
214
  I want the app to work better
215
215
  So that I'm happy
216
- ```
216
+ ```text
217
217
 
218
218
  - ❌ No specific role
219
219
  - ❌ "Work better" is not measurable
@@ -221,11 +221,11 @@ So that I'm happy
221
221
 
222
222
  **Too Technical (Implementation Details):**
223
223
 
224
- ```
224
+ ```text
225
225
  As a developer
226
226
  I want to refactor the CharacterStore to use Immer
227
227
  So that state mutations are prevented
228
- ```
228
+ ```text
229
229
 
230
230
  - ❌ This is a technical task, not a user story
231
231
  - ❌ Users don't care about Immer
@@ -233,21 +233,21 @@ So that state mutations are prevented
233
233
 
234
234
  **Missing "So That" (No Value):**
235
235
 
236
- ```
236
+ ```text
237
237
  As a GM
238
238
  I want to roll dice
239
- ```
239
+ ```text
240
240
 
241
241
  - ❌ No business value stated
242
242
  - ❌ Why does the GM need this?
243
243
 
244
244
  **Multiple Features in One Story:**
245
245
 
246
- ```
246
+ ```text
247
247
  As a player
248
248
  I want to create characters, manage inventory, and track relationships
249
249
  So that I can play the game
250
- ```
250
+ ```text
251
251
 
252
252
  - ❌ 3+ separate features bundled together
253
253
  - ❌ Cannot be completed in one sprint
@@ -291,7 +291,7 @@ describe('[Unit/Module Name]', () => {
291
291
  });
292
292
  });
293
293
  });
294
- ```
294
+ ```text
295
295
 
296
296
  ### Integration Test Template
297
297
 
@@ -342,7 +342,7 @@ describe('[Feature Name] Integration', () => {
342
342
  expect(await getInventory('sword')).toBe(1); // Not decremented
343
343
  });
344
344
  });
345
- ```
345
+ ```text
346
346
 
347
347
  ### E2E Test Template (Playwright/Cypress)
348
348
 
@@ -368,7 +368,7 @@ test.describe('[User Journey Name]', () => {
368
368
  await expect(page).toHaveURL(/\/characters\/create/);
369
369
  });
370
370
  });
371
- ```
371
+ ```text
372
372
 
373
373
  ---
374
374
 
@@ -383,7 +383,7 @@ it('should return risky position when outnumbered 3-to-1');
383
383
  it('should cache LLM responses for 5 minutes to reduce costs');
384
384
  it('should preserve armor state after reducing harm from L2 to L1');
385
385
  it('should throw ValidationError when dice pool is negative');
386
- ```
386
+ ```text
387
387
 
388
388
  **❌ BAD - Vague or Implementation-Focused:**
389
389
 
@@ -392,7 +392,7 @@ it('works correctly'); // What does "correctly" mean?
392
392
  it('tests the function'); // Obvious, not descriptive
393
393
  it('should call setState'); // Implementation detail
394
394
  it('scenario 1'); // No context
395
- ```
395
+ ```text
396
396
 
397
397
  **How to rename:**
398
398
 
@@ -418,7 +418,7 @@ it('should calculate critical success on 6', () => {
418
418
  expect(outcome).toBe('critical');
419
419
  expect(outcome.highestDie).toBe(6);
420
420
  });
421
- ```
421
+ ```text
422
422
 
423
423
  ### Test Independence
424
424
 
@@ -436,7 +436,7 @@ it('test A', () => {
436
436
  it('test B', () => {
437
437
  /* uses separate gameState */
438
438
  });
439
- ```
439
+ ```text
440
440
 
441
441
  **❌ BAD - Shared State:**
442
442
 
@@ -448,7 +448,7 @@ it('test A', () => {
448
448
  it('test B', () => {
449
449
  expect(sharedState.foo).toBe('bar');
450
450
  }); // Depends on test A!
451
- ```
451
+ ```text
452
452
 
453
453
  ### What to Test
454
454
 
@@ -480,7 +480,7 @@ export function getFormattedTimestamp(event) {
480
480
  return _formatDateInternal(event.createdAt);
481
481
  }
482
482
  // Test getFormattedTimestamp, not _formatDateInternal
483
- ```
483
+ ```text
484
484
 
485
485
  ### Test Data Builders
486
486
 
@@ -504,7 +504,7 @@ it('should increase stress when resisting', () => {
504
504
  const character = buildCharacter({ stress: 3 });
505
505
  // Test uses character with stress=3
506
506
  });
507
- ```
507
+ ```text
508
508
 
509
509
  ---
510
510
 
@@ -547,7 +547,7 @@ tests:
547
547
  EXCELLENT: Asks open-ended questions, invites player creativity
548
548
  ACCEPTABLE: Acknowledges player action, minimal collaboration
549
549
  POOR: Dictates outcomes without player input
550
- ```
550
+ ```text
551
551
 
552
552
  ### Integration Test with Real LLM
553
553
 
@@ -570,7 +570,7 @@ describe('Rules Agent Integration', () => {
570
570
  expect(response.consequences).toContain('severe harm');
571
571
  });
572
572
  });
573
- ```
573
+ ```text
574
574
 
575
575
  ---
576
576
 
@@ -613,3 +613,4 @@ Before writing a story, verify it passes all six criteria:
613
613
  - **Unit:** Single function/module logic (fast, cheap, low-level confidence)
614
614
 
615
615
  **Ratio guidance:** 70% unit, 20% integration, 10% E2E (adjust based on project)
616
+ ````
@@ -61,21 +61,21 @@ Use these consistently:
61
61
 
62
62
  **✅ GOOD - Clear, actionable steps:**
63
63
 
64
- ```
64
+ ````text
65
65
  **Steps**:
66
66
  1. Toggle AI pane visible
67
67
  2. Get bounding box for AI pane
68
68
  3. Get bounding box for Editor pane
69
69
  4. Compare X coordinates
70
- ```
70
+ ```text
71
71
 
72
72
  **❌ BAD - Vague or incomplete:**
73
73
 
74
- ```
74
+ ```text
75
75
  **Steps**:
76
76
  1. Check panes
77
77
  2. Verify order
78
- ```
78
+ ```text
79
79
 
80
80
  ---
81
81
 
@@ -83,20 +83,20 @@ Use these consistently:
83
83
 
84
84
  **✅ GOOD - Specific, testable assertions:**
85
85
 
86
- ```
86
+ ```text
87
87
  **Expected**:
88
88
  - AI pane X coordinate < Editor pane X coordinate
89
89
  - Explorer pane X coordinate > Editor pane X coordinate
90
90
  - All coordinates are positive numbers
91
- ```
91
+ ```text
92
92
 
93
93
  **❌ BAD - Vague expectations:**
94
94
 
95
- ```
95
+ ```text
96
96
  **Expected**:
97
97
  - Panes are in correct order
98
98
  - Everything works
99
- ```
99
+ ```text
100
100
 
101
101
  ---
102
102
 
@@ -131,13 +131,13 @@ Use these consistently:
131
131
 
132
132
  **Example:**
133
133
 
134
- ```
134
+ ```text
135
135
  **Total**: 20 tests
136
136
  **Passing**: 9 tests (45%)
137
137
  **Skipped**: 4 tests (20%)
138
138
  **Not Implemented**: 7 tests (35%)
139
139
  **Failing**: 0 tests
140
- ```
140
+ ```text
141
141
 
142
142
  ---
143
143
 
@@ -169,7 +169,7 @@ npm run test:e2e -- tests/feature-name.spec.ts
169
169
 
170
170
  # Run specific test
171
171
  npm run test:e2e -- tests/feature-name.spec.ts --grep "specific test name"
172
- ```
172
+ ```text
173
173
 
174
174
  ---
175
175
 
@@ -246,7 +246,7 @@ npm run test:e2e -- tests/feature-name.spec.ts --grep "specific test name"
246
246
  - P95 response time < 200ms
247
247
  - No requests timeout
248
248
  - No 5xx errors under load
249
- ```
249
+ ```text
250
250
 
251
251
  **❌ BAD - Vague, untestable:**
252
252
 
@@ -255,7 +255,7 @@ npm run test:e2e -- tests/feature-name.spec.ts --grep "specific test name"
255
255
 
256
256
  **Steps**: Check if fast
257
257
  **Expected**: Good performance
258
- ```
258
+ ```text
259
259
 
260
260
  ### When to Skip Constraint Tests
261
261
 
@@ -291,7 +291,7 @@ npm run test:e2e -- tests/feature-name.spec.ts --grep "specific test name"
291
291
  - After first toggle: AI pane visible
292
292
  - After second toggle: AI pane hidden
293
293
  - Toggle action triggers state change in uiStore
294
- ```
294
+ ```text
295
295
 
296
296
  ---
297
297
 
@@ -332,3 +332,4 @@ npm run test:e2e -- tests/feature-name.spec.ts --grep "specific test name"
332
332
  - Concrete examples over abstract rules
333
333
  - Edge cases must be explicit
334
334
  - Actionable over vague language
335
+ ````
@@ -0,0 +1,66 @@
1
+ #!/bin/bash
2
+ # Safeword: Cursor adapter for afterFileEdit
3
+ # Auto-lints changed files, only outputs unfixable errors
4
+ # Sets marker file for stop hook to trigger quality review
5
+
6
+ # Require jq for JSON parsing
7
+ command -v jq &> /dev/null || exit 0
8
+
9
+ input=$(cat)
10
+
11
+ # Get workspace root and file path from Cursor's JSON format
12
+ workspace=$(echo "$input" | jq -r '.workspace_roots[0] // empty' 2>/dev/null)
13
+ file=$(echo "$input" | jq -r '.file_path // empty' 2>/dev/null)
14
+ conv_id=$(echo "$input" | jq -r '.conversation_id // "default"' 2>/dev/null)
15
+
16
+ # Exit silently if no file
17
+ [ -z "$file" ] || [ ! -f "$file" ] && exit 0
18
+
19
+ # Change to workspace directory
20
+ [ -n "$workspace" ] && cd "$workspace" || true
21
+
22
+ # Check for .safeword directory
23
+ if [ ! -d ".safeword" ]; then
24
+ exit 0
25
+ fi
26
+
27
+ # Set marker file for stop hook to know edits were made
28
+ touch "/tmp/safeword-cursor-edited-${conv_id}"
29
+
30
+ # Determine linter based on file extension
31
+ case "$file" in
32
+ # JS/TS and framework files - ESLint first (fix code), then Prettier (format)
33
+ *.js|*.jsx|*.ts|*.tsx|*.mjs|*.mts|*.cjs|*.cts|*.vue|*.svelte|*.astro)
34
+ if ! errors=$(npx eslint --fix "$file" 2>&1); then
35
+ [ -n "$errors" ] && echo "$errors"
36
+ fi
37
+ npx prettier --write "$file" 2>/dev/null
38
+ ;;
39
+
40
+ # Markdown - markdownlint first, then Prettier
41
+ *.md)
42
+ if ! errors=$(npx markdownlint-cli2 --fix "$file" 2>&1); then
43
+ [ -n "$errors" ] && echo "$errors"
44
+ fi
45
+ npx prettier --write "$file" 2>/dev/null
46
+ ;;
47
+
48
+ # Other supported formats - prettier only
49
+ *.json|*.css|*.scss|*.html|*.yaml|*.yml|*.graphql)
50
+ npx prettier --write "$file" 2>/dev/null
51
+ ;;
52
+
53
+ # Shell scripts - shellcheck (if available), then Prettier (if plugin installed)
54
+ *.sh)
55
+ if [ -f node_modules/.bin/shellcheck ] || command -v shellcheck &> /dev/null; then
56
+ if ! errors=$(npx shellcheck "$file" 2>&1); then
57
+ [ -n "$errors" ] && echo "$errors"
58
+ fi
59
+ fi
60
+ if [ -d node_modules/prettier-plugin-sh ]; then
61
+ npx prettier --write "$file" 2>/dev/null
62
+ fi
63
+ ;;
64
+ esac
65
+
66
+ exit 0
@@ -0,0 +1,50 @@
1
+ #!/bin/bash
2
+ # Safeword: Cursor adapter for stop hook
3
+ # Checks for marker file from afterFileEdit to determine if files were modified
4
+ # Uses followup_message to inject quality review prompt into conversation
5
+
6
+ # Require jq for JSON parsing
7
+ command -v jq &> /dev/null || { echo '{}'; exit 0; }
8
+
9
+ input=$(cat)
10
+
11
+ # Get workspace root
12
+ workspace=$(echo "$input" | jq -r '.workspace_roots[0] // empty' 2>/dev/null)
13
+ [ -n "$workspace" ] && cd "$workspace" || true
14
+
15
+ # Check for .safeword directory
16
+ if [ ! -d ".safeword" ]; then
17
+ echo '{}'
18
+ exit 0
19
+ fi
20
+
21
+ # Check status - only proceed on completed (not aborted/error)
22
+ status=$(echo "$input" | jq -r '.status // empty' 2>/dev/null)
23
+ if [ "$status" != "completed" ]; then
24
+ echo '{}'
25
+ exit 0
26
+ fi
27
+
28
+ # Get loop_count to prevent infinite review loops
29
+ # When review is triggered, agent runs again with loop_count >= 1
30
+ loop_count=$(echo "$input" | jq -r '.loop_count // 0' 2>/dev/null)
31
+
32
+ if [ "$loop_count" -ge 1 ]; then
33
+ echo '{}'
34
+ exit 0
35
+ fi
36
+
37
+ # Check if any file edits occurred in this session by looking for recent .safeword marker
38
+ # This is a heuristic: if afterFileEdit ran recently, work was done
39
+ marker_file="/tmp/safeword-cursor-edited-$(echo "$input" | jq -r '.conversation_id // "default"' 2>/dev/null)"
40
+
41
+ if [ -f "$marker_file" ]; then
42
+ rm -f "$marker_file" # Clean up marker
43
+ cat << 'EOF'
44
+ {
45
+ "followup_message": "SAFEWORD Quality Review:\n\nDouble check and critique your work again just in case.\nAssume you've never seen it before.\n\n- Is it correct?\n- Is it elegant?\n- Does it follow latest docs/best practices?\n- Ask me any non-obvious questions.\n- Avoid bloat."
46
+ }
47
+ EOF
48
+ else
49
+ echo '{}'
50
+ fi
@@ -18,21 +18,23 @@ file=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.notebook_path
18
18
  [ -z "$file" ] || [ ! -f "$file" ] && exit 0
19
19
 
20
20
  # Change to project directory
21
- [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR"
21
+ [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR" || true
22
22
 
23
23
  # Determine linter based on file extension
24
24
  case "$file" in
25
25
  # JS/TS and framework files - ESLint first (fix code), then Prettier (format)
26
26
  *.js|*.jsx|*.ts|*.tsx|*.mjs|*.mts|*.cjs|*.cts|*.vue|*.svelte|*.astro)
27
- errors=$(npx eslint --fix "$file" 2>&1)
28
- [ $? -ne 0 ] && [ -n "$errors" ] && echo "$errors"
27
+ if ! errors=$(npx eslint --fix "$file" 2>&1); then
28
+ [ -n "$errors" ] && echo "$errors"
29
+ fi
29
30
  npx prettier --write "$file" 2>/dev/null
30
31
  ;;
31
32
 
32
33
  # Markdown - markdownlint first, then Prettier
33
34
  *.md)
34
- errors=$(npx markdownlint-cli2 --fix "$file" 2>&1)
35
- [ $? -ne 0 ] && [ -n "$errors" ] && echo "$errors"
35
+ if ! errors=$(npx markdownlint-cli2 --fix "$file" 2>&1); then
36
+ [ -n "$errors" ] && echo "$errors"
37
+ fi
36
38
  npx prettier --write "$file" 2>/dev/null
37
39
  ;;
38
40
 
@@ -40,6 +42,18 @@ case "$file" in
40
42
  *.json|*.css|*.scss|*.html|*.yaml|*.yml|*.graphql)
41
43
  npx prettier --write "$file" 2>/dev/null
42
44
  ;;
45
+
46
+ # Shell scripts - shellcheck (if available), then Prettier (if plugin installed)
47
+ *.sh)
48
+ if [ -f node_modules/.bin/shellcheck ] || command -v shellcheck &> /dev/null; then
49
+ if ! errors=$(npx shellcheck "$file" 2>&1); then
50
+ [ -n "$errors" ] && echo "$errors"
51
+ fi
52
+ fi
53
+ if [ -d node_modules/prettier-plugin-sh ]; then
54
+ npx prettier --write "$file" 2>/dev/null
55
+ fi
56
+ ;;
43
57
  esac
44
58
 
45
59
  exit 0
@@ -3,7 +3,7 @@
3
3
  # Reminds Claude to ask 1-5 clarifying questions for ambiguous tasks
4
4
 
5
5
  # Change to project directory if set
6
- [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR"
6
+ [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR" || true
7
7
 
8
8
  if [ ! -d ".safeword" ]; then
9
9
  exit 0
@@ -3,7 +3,7 @@
3
3
  # Warns if ESLint or Prettier configs are missing or out of sync
4
4
 
5
5
  # Change to project directory if set
6
- [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR"
6
+ [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR" || true
7
7
 
8
8
  if [ ! -d ".safeword" ]; then
9
9
  exit 0
@@ -5,7 +5,7 @@
5
5
  LINK='**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**'
6
6
 
7
7
  # Change to project directory if set
8
- [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR"
8
+ [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR" || true
9
9
 
10
10
  if [ ! -d ".safeword" ]; then
11
11
  # Not a safeword project, skip silently
@@ -3,7 +3,7 @@
3
3
  # Shows current safeword version and confirms hooks are active
4
4
 
5
5
  # Change to project directory if set
6
- [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR"
6
+ [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR" || true
7
7
 
8
8
  if [ ! -d ".safeword" ]; then
9
9
  exit 0
@@ -5,7 +5,7 @@
5
5
  # Looks for {"proposedChanges": ..., "madeChanges": ...} JSON blob
6
6
 
7
7
  # Change to project directory if set
8
- [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR"
8
+ [ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR" || true
9
9
 
10
10
  # Check for .safeword directory
11
11
  if [ ! -d ".safeword" ]; then
@@ -1,25 +1,24 @@
1
1
  {
2
2
  // Safeword markdownlint configuration
3
- "default": true,
3
+ // Philosophy: Only enforce rules that affect rendering or LLM comprehension
4
+ // Cosmetic/stylistic rules disabled - LLMs don't care about line length or bullet style
5
+ "config": {
6
+ "default": false,
4
7
 
5
- // Allow inline HTML (needed for some markdown features)
6
- "MD033": false,
8
+ // RENDERING-CRITICAL (broken markdown if violated)
9
+ "MD011": true, // Reversed link syntax [text](url) not (text)[url]
10
+ "MD018": true, // Space required after # in headings
11
+ "MD037": true, // No spaces inside emphasis markers **like this**
12
+ "MD038": true, // No spaces inside code spans `like this`
13
+ "MD042": true, // No empty links [text]()
14
+ "MD056": true, // Table column count must be consistent
7
15
 
8
- // Allow multiple headings with same content
9
- "MD024": {
10
- "siblings_only": true
16
+ // LLM-IMPORTANT (structure clarity for tokenization)
17
+ "MD001": true, // Heading levels increment by one (h1 -> h2, not h1 -> h3)
18
+ "MD022": true, // Blank lines around headings
19
+ "MD031": true, // Blank lines around fenced code blocks
20
+ "MD032": true, // Blank lines around lists
21
+ "MD040": true, // Code fence language specified (helps LLM understand code context)
22
+ "MD058": true, // Blank lines around tables
11
23
  },
12
-
13
- // Line length - be lenient
14
- "MD013": {
15
- "line_length": 120,
16
- "code_blocks": false,
17
- "tables": false
18
- },
19
-
20
- // Allow trailing punctuation in headings
21
- "MD026": false,
22
-
23
- // Allow emphasis as heading
24
- "MD036": false
25
24
  }