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.
- package/dist/{check-OYYSYHFP.js → check-JWMAZLUO.js} +73 -57
- package/dist/check-JWMAZLUO.js.map +1 -0
- package/dist/{chunk-ZS3Z3Q37.js → chunk-CO3LARDH.js} +297 -65
- package/dist/chunk-CO3LARDH.js.map +1 -0
- package/dist/{chunk-LNSEDZIW.js → chunk-R4SBJKFJ.js} +159 -152
- package/dist/chunk-R4SBJKFJ.js.map +1 -0
- package/dist/{sync-BFMXZEHM.js → chunk-YNVT2S3D.js} +11 -40
- package/dist/chunk-YNVT2S3D.js.map +1 -0
- package/dist/cli.js +6 -6
- package/dist/{diff-325TIZ63.js → diff-2XITKG3T.js} +51 -53
- package/dist/diff-2XITKG3T.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/{reset-ZGJIKMUW.js → reset-DOGYUFAG.js} +3 -3
- package/dist/{setup-GAMXTFM2.js → setup-UHSXXN6I.js} +17 -20
- package/dist/setup-UHSXXN6I.js.map +1 -0
- package/dist/sync-QPNODJBJ.js +9 -0
- package/dist/sync-QPNODJBJ.js.map +1 -0
- package/dist/{upgrade-X4GREJXN.js → upgrade-GTY3MG6A.js} +10 -7
- package/dist/upgrade-GTY3MG6A.js.map +1 -0
- package/package.json +1 -1
- package/templates/SAFEWORD.md +52 -15
- package/templates/commands/architecture.md +1 -1
- package/templates/commands/lint.md +1 -0
- package/templates/commands/quality-review.md +1 -1
- package/templates/cursor/rules/safeword-core.mdc +5 -0
- package/templates/doc-templates/architecture-template.md +1 -1
- package/templates/doc-templates/task-spec-template.md +151 -0
- package/templates/doc-templates/ticket-template.md +2 -4
- package/templates/guides/architecture-guide.md +2 -2
- package/templates/guides/code-philosophy.md +1 -1
- package/templates/guides/context-files-guide.md +3 -3
- package/templates/guides/design-doc-guide.md +2 -2
- package/templates/guides/development-workflow.md +2 -2
- package/templates/guides/learning-extraction.md +9 -9
- package/templates/guides/tdd-best-practices.md +39 -38
- package/templates/guides/test-definitions-guide.md +15 -14
- package/templates/hooks/cursor/after-file-edit.sh +66 -0
- package/templates/hooks/cursor/stop.sh +50 -0
- package/templates/hooks/post-tool-lint.sh +19 -5
- package/templates/hooks/prompt-questions.sh +1 -1
- package/templates/hooks/session-lint-check.sh +1 -1
- package/templates/hooks/session-verify-agents.sh +1 -1
- package/templates/hooks/session-version.sh +1 -1
- package/templates/hooks/stop-quality.sh +1 -1
- package/templates/markdownlint-cli2.jsonc +18 -19
- package/templates/scripts/bisect-test-pollution.sh +87 -0
- package/templates/scripts/bisect-zombie-processes.sh +129 -0
- package/templates/scripts/lint-md.sh +16 -0
- package/templates/skills/safeword-quality-reviewer/SKILL.md +3 -3
- package/templates/skills/safeword-systematic-debugger/SKILL.md +246 -0
- package/templates/skills/safeword-tdd-enforcer/SKILL.md +221 -0
- package/dist/check-OYYSYHFP.js.map +0 -1
- package/dist/chunk-LNSEDZIW.js.map +0 -1
- package/dist/chunk-ZS3Z3Q37.js.map +0 -1
- package/dist/diff-325TIZ63.js.map +0 -1
- package/dist/setup-GAMXTFM2.js.map +0 -1
- package/dist/sync-BFMXZEHM.js.map +0 -1
- package/dist/upgrade-X4GREJXN.js.map +0 -1
- /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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
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
|
}
|