safeword 0.6.4 → 0.6.5
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-ICZISZ3R.js → check-OYYSYHFP.js} +3 -3
- package/dist/{chunk-JGXYBPNM.js → chunk-LNSEDZIW.js} +2 -2
- package/dist/{chunk-E5ZC6R5H.js → chunk-ZS3Z3Q37.js} +24 -15
- package/dist/chunk-ZS3Z3Q37.js.map +1 -0
- package/dist/cli.js +6 -6
- package/dist/{diff-FOJDBKKF.js → diff-325TIZ63.js} +3 -3
- package/dist/{reset-JU2E65XN.js → reset-ZGJIKMUW.js} +3 -3
- package/dist/{setup-UKMYK5TE.js → setup-GAMXTFM2.js} +3 -3
- package/dist/{sync-5MOXVTH4.js → sync-BFMXZEHM.js} +2 -2
- package/dist/{upgrade-NSLDFWNR.js → upgrade-X4GREJXN.js} +3 -3
- package/package.json +15 -14
- package/templates/SAFEWORD.md +101 -669
- package/templates/guides/architecture-guide.md +1 -1
- package/templates/guides/cli-reference.md +35 -0
- package/templates/guides/code-philosophy.md +22 -19
- package/templates/guides/context-files-guide.md +2 -2
- package/templates/guides/data-architecture-guide.md +1 -1
- package/templates/guides/design-doc-guide.md +1 -1
- package/templates/guides/{testing-methodology.md → development-workflow.md} +1 -1
- package/templates/guides/learning-extraction.md +1 -1
- package/templates/guides/{llm-instruction-design.md → llm-guide.md} +93 -29
- package/templates/guides/tdd-best-practices.md +2 -2
- package/templates/guides/test-definitions-guide.md +1 -1
- package/templates/guides/user-story-guide.md +1 -1
- package/templates/guides/zombie-process-cleanup.md +1 -1
- package/dist/chunk-E5ZC6R5H.js.map +0 -1
- package/templates/guides/llm-prompting.md +0 -102
- /package/dist/{check-ICZISZ3R.js.map → check-OYYSYHFP.js.map} +0 -0
- /package/dist/{chunk-JGXYBPNM.js.map → chunk-LNSEDZIW.js.map} +0 -0
- /package/dist/{diff-FOJDBKKF.js.map → diff-325TIZ63.js.map} +0 -0
- /package/dist/{reset-JU2E65XN.js.map → reset-ZGJIKMUW.js.map} +0 -0
- /package/dist/{setup-UKMYK5TE.js.map → setup-GAMXTFM2.js.map} +0 -0
- /package/dist/{sync-5MOXVTH4.js.map → sync-BFMXZEHM.js.map} +0 -0
- /package/dist/{upgrade-NSLDFWNR.js.map → upgrade-X4GREJXN.js.map} +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Safeword CLI Reference
|
|
2
|
+
|
|
3
|
+
Commands for managing safeword in projects.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
| Command | Purpose |
|
|
8
|
+
| ----------------------------- | ---------------------------------------------- |
|
|
9
|
+
| `npx safeword@latest setup` | Install safeword in current project |
|
|
10
|
+
| `npx safeword@latest check` | Check project health and versions |
|
|
11
|
+
| `npx safeword@latest upgrade` | Upgrade to latest version |
|
|
12
|
+
| `npx safeword@latest diff` | Preview changes before upgrading |
|
|
13
|
+
| `npx safeword sync` | Sync linting plugins with project dependencies |
|
|
14
|
+
| `npx safeword reset` | Remove safeword from project |
|
|
15
|
+
|
|
16
|
+
## When to Use
|
|
17
|
+
|
|
18
|
+
| Situation | Command |
|
|
19
|
+
| -------------------------- | ----------------------------- |
|
|
20
|
+
| New project setup | `npx safeword@latest setup` |
|
|
21
|
+
| Check if update available | `npx safeword@latest check` |
|
|
22
|
+
| Update after CLI release | `npx safeword@latest upgrade` |
|
|
23
|
+
| See what upgrade changes | `npx safeword@latest diff` |
|
|
24
|
+
| Added/removed framework | `npx safeword sync` |
|
|
25
|
+
| Remove safeword completely | `npx safeword reset --full` |
|
|
26
|
+
|
|
27
|
+
## Options
|
|
28
|
+
|
|
29
|
+
Run `npx safeword <command> --help` for command-specific options.
|
|
30
|
+
|
|
31
|
+
Common flags:
|
|
32
|
+
|
|
33
|
+
- `-y, --yes` - Skip confirmations (setup, reset)
|
|
34
|
+
- `-v, --verbose` - Show detailed output (diff)
|
|
35
|
+
- `-q, --quiet` - Suppress output (sync)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Code Philosophy & Practices
|
|
2
2
|
|
|
3
|
-
**Note:** This file provides instructions for LLM-based coding agents. For comprehensive framework on writing clear, actionable LLM-consumable instructions, see `@.safeword/guides/llm-
|
|
3
|
+
**Note:** This file provides instructions for LLM-based coding agents. For comprehensive framework on writing clear, actionable LLM-consumable instructions, see `@.safeword/guides/llm-guide.md`.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -41,19 +41,21 @@ Examples:
|
|
|
41
41
|
- **Explicit error handling** - NEVER suppress or swallow errors silently
|
|
42
42
|
|
|
43
43
|
**Error handling examples:**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
|
47
|
-
|
|
48
|
-
|
|
|
44
|
+
|
|
45
|
+
| ❌ Bad | ✅ Good |
|
|
46
|
+
| ------------------------------ | ----------------------------------------------------------------------------- |
|
|
47
|
+
| `catch (e) {}` (swallowed) | `catch (e) { throw new Error(\`Failed to read ${filePath}: ${e.message}\`) }` |
|
|
48
|
+
| `catch (e) { console.log(e) }` | `catch (e) { logger.error('Payment failed', { userId, amount, error: e }) }` |
|
|
49
|
+
| Generic "Something went wrong" | "Failed to save user profile: database connection timeout" |
|
|
49
50
|
|
|
50
51
|
**Naming examples:**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
|
52
|
+
|
|
53
|
+
| ❌ Bad | ✅ Good |
|
|
54
|
+
| ------------------ | ------------------------------ |
|
|
55
|
+
| `calcTot` | `calculateTotalWithTax` |
|
|
54
56
|
| `d`, `tmp`, `data` | `userProfile`, `pendingOrders` |
|
|
55
|
-
| `handleClick`
|
|
56
|
-
| `process()`
|
|
57
|
+
| `handleClick` | `submitPaymentForm` |
|
|
58
|
+
| `process()` | `validateAndSaveUser()` |
|
|
57
59
|
|
|
58
60
|
**When to comment:**
|
|
59
61
|
|
|
@@ -63,13 +65,14 @@ Examples:
|
|
|
63
65
|
- ❌ Restating the function name
|
|
64
66
|
|
|
65
67
|
**Bloat examples (avoid these):**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
68
|
+
|
|
69
|
+
| ❌ Bloat | ✅ Instead |
|
|
70
|
+
| ------------------------------------------- | ------------------------- |
|
|
71
|
+
| Utility class for one function | Single function |
|
|
72
|
+
| Factory pattern for simple object | Direct construction |
|
|
73
|
+
| Abstract base class with one implementation | Concrete class |
|
|
74
|
+
| Config file for 2 options | Hardcode or simple params |
|
|
75
|
+
| "Future-proofing" unused code paths | Delete, add when needed |
|
|
73
76
|
|
|
74
77
|
**When to push back:** If a feature request would add >50 lines for a "nice to have", ask: "Is this essential now, or can we add it later?"
|
|
75
78
|
|
|
@@ -106,7 +109,7 @@ Examples:
|
|
|
106
109
|
- ✅ If a test fails, fix the implementation—not the test
|
|
107
110
|
- ✅ If a test seems wrong or requirements changed, explain why and ask before changing it
|
|
108
111
|
|
|
109
|
-
**Workflow:** See `@.safeword/guides/
|
|
112
|
+
**Workflow:** See `@.safeword/guides/development-workflow.md` for comprehensive TDD workflow (RED → GREEN → REFACTOR phases)
|
|
110
113
|
|
|
111
114
|
## Debugging & Troubleshooting
|
|
112
115
|
|
|
@@ -224,7 +224,7 @@ See @README for project overview and @package.json for available npm commands.
|
|
|
224
224
|
|
|
225
225
|
## Coding Standards
|
|
226
226
|
|
|
227
|
-
@.safeword/guides/llm-
|
|
227
|
+
@.safeword/guides/llm-guide.md
|
|
228
228
|
|
|
229
229
|
## Git Workflow
|
|
230
230
|
|
|
@@ -415,7 +415,7 @@ Brief description. Current status.
|
|
|
415
415
|
|
|
416
416
|
**Critical:** These files are instructions consumed by LLMs.
|
|
417
417
|
|
|
418
|
-
**See:** `@.safeword/guides/llm-
|
|
418
|
+
**See:** `@.safeword/guides/llm-guide.md` for 13 core principles for writing LLM-consumable documentation.
|
|
419
419
|
|
|
420
420
|
### Quality Checklist
|
|
421
421
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Context:** How to document data architecture decisions, models, and flows for software projects. Applies LLM instruction design principles for clarity and reliability.
|
|
4
4
|
|
|
5
|
-
**See:** `@.safeword/guides/llm-
|
|
5
|
+
**See:** `@.safeword/guides/llm-guide.md` for comprehensive framework on writing LLM-consumable documentation.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -168,4 +168,4 @@ Before saving, verify:
|
|
|
168
168
|
|
|
169
169
|
**Important:** Design docs are instructions that LLMs read and follow.
|
|
170
170
|
|
|
171
|
-
**See:** `@.safeword/guides/llm-
|
|
171
|
+
**See:** `@.safeword/guides/llm-guide.md` for comprehensive framework on writing clear, actionable documentation that LLMs can reliably follow.
|
|
@@ -614,5 +614,5 @@ npm run test:e2e # E2E tests only
|
|
|
614
614
|
|
|
615
615
|
**Cascading precedence:**
|
|
616
616
|
|
|
617
|
-
1. **Global** (`~/.claude/
|
|
617
|
+
1. **Global** (`~/.claude/development-workflow.md`) - Universal methodology (test type selection, TDD workflow)
|
|
618
618
|
2. **Project** (`tests/SAFEWORD.md`) - Specific stack, commands, patterns
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Extract reusable knowledge from debugging sessions and implementation discoveries. Ensures insights compound across sessions.
|
|
4
4
|
|
|
5
|
-
**LLM Instruction Design:** Learnings are documentation that LLMs read and follow. Apply best practices from `@.safeword/guides/llm-
|
|
5
|
+
**LLM Instruction Design:** Learnings are documentation that LLMs read and follow. Apply best practices from `@.safeword/guides/llm-guide.md` when writing learning files (concrete examples, explicit definitions, MECE principles).
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -1,12 +1,76 @@
|
|
|
1
|
-
#
|
|
1
|
+
# LLM Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This guide covers two related topics:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Part 1: Integration** - How to call LLMs effectively (API calls, structured outputs, caching, testing)
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Part 2: Writing for LLMs** - How to write documentation that LLMs will read and follow (SAFEWORD.md, CLAUDE.md, guides)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Part 1: Integration
|
|
12
|
+
|
|
13
|
+
### Structured Outputs
|
|
14
|
+
|
|
15
|
+
Use JSON mode for predictable LLM responses. Define explicit schemas with validation. Return structured data, not prose.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// ❌ BAD - Prose output
|
|
19
|
+
"The user wants to create a campaign named 'Shadows' with 4 players"
|
|
20
|
+
|
|
21
|
+
// ✅ GOOD - Structured JSON
|
|
22
|
+
{ "intent": "create_campaign", "name": "Shadows", "playerCount": 4 }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Cost Optimization
|
|
26
|
+
|
|
27
|
+
**Prompt Caching (Critical for AI Agents):**
|
|
28
|
+
|
|
29
|
+
- Static rules → System prompt with cache_control: ephemeral (caches for ~5 min, auto-expires)
|
|
30
|
+
- Dynamic data (character state, user input) → User message (no caching)
|
|
31
|
+
- Example: 468-line prompt costs $0.10 without caching, $0.01 with (90% reduction)
|
|
32
|
+
- Cache invalidation: ANY change to cached blocks breaks ALL caches
|
|
33
|
+
- Rule: Change system prompts sparingly; accept one-time cache rebuild cost
|
|
34
|
+
|
|
35
|
+
**Message Architecture:**
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// ✅ GOOD - Cacheable system prompt
|
|
39
|
+
systemPrompt: [
|
|
40
|
+
{ text: STATIC_RULES, cache_control: { type: 'ephemeral' } },
|
|
41
|
+
{ text: STATIC_EXAMPLES, cache_control: { type: 'ephemeral' } },
|
|
42
|
+
];
|
|
43
|
+
userMessage: `Character: ${dynamicState}\nAction: ${userInput}`;
|
|
44
|
+
|
|
45
|
+
// ❌ BAD - Uncacheable (character state in system prompt)
|
|
46
|
+
systemPrompt: `Rules + Character: ${dynamicState}`;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Testing AI Outputs
|
|
50
|
+
|
|
51
|
+
**LLM-as-Judge Pattern:**
|
|
52
|
+
|
|
53
|
+
- Use LLM to evaluate nuanced qualities (narrative tone, reasoning quality)
|
|
54
|
+
- Avoid brittle keyword matching for creative outputs
|
|
55
|
+
- Define rubrics: EXCELLENT / ACCEPTABLE / POOR with criteria
|
|
56
|
+
- Example: "Does the GM's response show collaborative tone?" vs checking for specific words
|
|
57
|
+
|
|
58
|
+
**Evaluation Framework:**
|
|
59
|
+
|
|
60
|
+
- Unit tests: Pure functions (parsing, validation)
|
|
61
|
+
- Integration tests: Agent + real LLM calls (schema compliance)
|
|
62
|
+
- LLM Evals: Judgment quality (position/effect reasoning, atmosphere)
|
|
63
|
+
- Cost awareness: 30 scenarios ≈ $0.15-0.30 per run with caching
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Part 2: Writing for LLMs
|
|
68
|
+
|
|
69
|
+
When creating documentation that LLMs will read and follow (AGENTS.md, CLAUDE.md, testing guides, coding standards), apply these principles:
|
|
70
|
+
|
|
71
|
+
### 1. MECE Principle (Mutually Exclusive, Collectively Exhaustive)
|
|
72
|
+
|
|
73
|
+
Decision trees must have no overlap and cover all cases. LLMs struggle with overlapping categories.
|
|
10
74
|
|
|
11
75
|
```markdown
|
|
12
76
|
❌ BAD - Not mutually exclusive:
|
|
@@ -26,7 +90,7 @@ Problem: A function with database calls could match both
|
|
|
26
90
|
Stops at first match, no ambiguity.
|
|
27
91
|
```
|
|
28
92
|
|
|
29
|
-
|
|
93
|
+
### 2. Explicit Over Implicit
|
|
30
94
|
|
|
31
95
|
Never assume LLMs know what you mean. Define all terms, even "obvious" ones.
|
|
32
96
|
|
|
@@ -41,7 +105,7 @@ Examples needing definition:
|
|
|
41
105
|
- "Pure function" → Input → output, no I/O (define edge cases like Date.now())
|
|
42
106
|
```
|
|
43
107
|
|
|
44
|
-
|
|
108
|
+
### 3. No Contradictions
|
|
45
109
|
|
|
46
110
|
Different sections must align. LLMs don't reconcile conflicting guidance. When updating, grep for related terms and update all references.
|
|
47
111
|
|
|
@@ -57,7 +121,7 @@ Section B: "All critical multi-page user flows have at least one E2E test"
|
|
|
57
121
|
- Definition of "critical" with examples
|
|
58
122
|
```
|
|
59
123
|
|
|
60
|
-
|
|
124
|
+
### 4. Concrete Examples Over Abstract Rules
|
|
61
125
|
|
|
62
126
|
Show, don't just tell. LLMs learn patterns from examples. For every rule, include 2-3 concrete examples showing good vs bad.
|
|
63
127
|
|
|
@@ -78,9 +142,9 @@ expect(calculateDiscount(100, 0.20)).toBe(80)
|
|
|
78
142
|
})
|
|
79
143
|
```
|
|
80
144
|
|
|
81
|
-
|
|
145
|
+
### 5. Edge Cases Must Be Explicit
|
|
82
146
|
|
|
83
|
-
What seems obvious to humans often isn't to LLMs. After stating a rule, add "Edge cases:" section
|
|
147
|
+
What seems obvious to humans often isn't to LLMs. After stating a rule, add "Edge cases:" section.
|
|
84
148
|
|
|
85
149
|
```markdown
|
|
86
150
|
❌ BAD: "Unit test pure functions"
|
|
@@ -94,9 +158,9 @@ Edge cases:
|
|
|
94
158
|
- Mixed pure + I/O → Extract pure part, unit test separately
|
|
95
159
|
```
|
|
96
160
|
|
|
97
|
-
|
|
161
|
+
### 6. Actionable Over Vague
|
|
98
162
|
|
|
99
|
-
|
|
163
|
+
Replace subjective terms with optimization rules + red flags.
|
|
100
164
|
|
|
101
165
|
```markdown
|
|
102
166
|
❌ BAD: "Most tests: Fast, Some tests: Slow"
|
|
@@ -108,9 +172,9 @@ Give LLMs concrete actions, not subjective guidance. Replace subjective terms (m
|
|
|
108
172
|
- Red flag: If you have more E2E tests than integration tests, suite is too slow
|
|
109
173
|
```
|
|
110
174
|
|
|
111
|
-
|
|
175
|
+
### 7. Decision Trees: Sequential Over Parallel
|
|
112
176
|
|
|
113
|
-
Structure decisions as ordered steps, not simultaneous checks.
|
|
177
|
+
Structure decisions as ordered steps, not simultaneous checks.
|
|
114
178
|
|
|
115
179
|
```markdown
|
|
116
180
|
❌ BAD - Parallel branches:
|
|
@@ -118,11 +182,11 @@ Structure decisions as ordered steps, not simultaneous checks. Sequential questi
|
|
|
118
182
|
├─ Multiple components?
|
|
119
183
|
└─ Full user flow?
|
|
120
184
|
|
|
121
|
-
✅ GOOD - Sequential
|
|
185
|
+
✅ GOOD - Sequential:
|
|
122
186
|
Answer questions IN ORDER. Stop at the first match.
|
|
123
187
|
```
|
|
124
188
|
|
|
125
|
-
|
|
189
|
+
### 8. Tie-Breaking Rules
|
|
126
190
|
|
|
127
191
|
When multiple options could apply, tell LLMs how to choose.
|
|
128
192
|
|
|
@@ -134,9 +198,9 @@ Reference in decision trees:
|
|
|
134
198
|
"If multiple seem to apply, use the tie-breaking rule stated above: choose the faster one."
|
|
135
199
|
```
|
|
136
200
|
|
|
137
|
-
|
|
201
|
+
### 9. Lookup Tables for Complex Decisions
|
|
138
202
|
|
|
139
|
-
When decision logic has 3+ branches,
|
|
203
|
+
When decision logic has 3+ branches, provide a reference table.
|
|
140
204
|
|
|
141
205
|
```markdown
|
|
142
206
|
| Bug Type | Unit? | Integration? | E2E? | Best Choice |
|
|
@@ -146,16 +210,16 @@ When decision logic has 3+ branches, nested conditions, or multiple variables to
|
|
|
146
210
|
| CSS layout broken | ❌ | ❌ | ✅ | E2E (only option) |
|
|
147
211
|
```
|
|
148
212
|
|
|
149
|
-
|
|
213
|
+
### 10. Avoid Caveats in Tables
|
|
150
214
|
|
|
151
|
-
Keep patterns clean. Parentheticals break LLM pattern matching.
|
|
215
|
+
Keep patterns clean. Parentheticals break LLM pattern matching.
|
|
152
216
|
|
|
153
217
|
```markdown
|
|
154
218
|
❌ BAD: | State management bug | ❌ NO (if mocked) | ✅ YES |
|
|
155
219
|
✅ GOOD: | State management bug (Zustand, Redux) | ❌ NO | ✅ YES |
|
|
156
220
|
```
|
|
157
221
|
|
|
158
|
-
|
|
222
|
+
### 11. Percentages: Context or None
|
|
159
223
|
|
|
160
224
|
Don't use percentages without adjustment guidance.
|
|
161
225
|
|
|
@@ -167,7 +231,7 @@ Don't use percentages without adjustment guidance.
|
|
|
167
231
|
✅ BEST: "Write as many fast tests as possible. Red flag: More E2E than integration = too slow."
|
|
168
232
|
```
|
|
169
233
|
|
|
170
|
-
|
|
234
|
+
### 12. Specificity in Questions
|
|
171
235
|
|
|
172
236
|
Use precise technical terms, not general descriptions.
|
|
173
237
|
|
|
@@ -178,7 +242,7 @@ Use precise technical terms, not general descriptions.
|
|
|
178
242
|
Note: React Testing Library does NOT require a browser - that's integration testing.
|
|
179
243
|
```
|
|
180
244
|
|
|
181
|
-
|
|
245
|
+
### 13. Re-evaluation Paths
|
|
182
246
|
|
|
183
247
|
When LLMs hit dead ends, provide concrete next steps.
|
|
184
248
|
|
|
@@ -195,13 +259,17 @@ When LLMs hit dead ends, provide concrete next steps.
|
|
|
195
259
|
- Login form → Dashboard → E2E test (multi-page)"
|
|
196
260
|
```
|
|
197
261
|
|
|
198
|
-
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Anti-Patterns
|
|
199
265
|
|
|
200
266
|
❌ **Visual metaphors** - Pyramids, icebergs—LLMs don't process visual information well
|
|
201
267
|
❌ **Undefined jargon** - "Technical debt", "code smell" need definitions
|
|
202
268
|
❌ **Competing guidance** - Multiple decision frameworks that contradict each other
|
|
203
269
|
❌ **Outdated references** - Remove concepts, but forget to update all mentions
|
|
204
270
|
|
|
271
|
+
---
|
|
272
|
+
|
|
205
273
|
## Quality Checklist
|
|
206
274
|
|
|
207
275
|
Before saving/committing LLM-consumable documentation:
|
|
@@ -216,11 +284,7 @@ Before saving/committing LLM-consumable documentation:
|
|
|
216
284
|
- [ ] Complex decisions (3+ branches) have lookup tables
|
|
217
285
|
- [ ] Dead-end paths have re-evaluation steps with examples
|
|
218
286
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
- **MECE (McKinsey):** Mutually exclusive, collectively exhaustive decision trees for reliable LLM decisions
|
|
222
|
-
- **Prompt ambiguity (2025):** "Ambiguity is one of the most common causes of poor LLM output" (Zero-Shot Decision Tree Construction)
|
|
223
|
-
- **Concrete examples (2025):** Structured approaches with concrete examples consistently improve performance over "act as" or "###" techniques
|
|
287
|
+
---
|
|
224
288
|
|
|
225
289
|
## Example: Before and After
|
|
226
290
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Patterns and examples for user stories and test definitions following TDD best practices.
|
|
4
4
|
|
|
5
|
-
**LLM Instruction Design:** These templates create documentation that LLMs read and follow. For comprehensive framework on writing clear, actionable LLM-consumable documentation, see `@.safeword/guides/llm-
|
|
5
|
+
**LLM Instruction Design:** These templates create documentation that LLMs read and follow. For comprehensive framework on writing clear, actionable LLM-consumable documentation, see `@.safeword/guides/llm-guide.md`.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -64,7 +64,7 @@ Patterns and examples for user stories and test definitions following TDD best p
|
|
|
64
64
|
- "Create a design doc for [feature]" → Uses design doc template (2-3 pages)
|
|
65
65
|
- "Update the project architecture doc" → Adds to existing ARCHITECTURE.md
|
|
66
66
|
|
|
67
|
-
**TDD Workflow:** See `@.safeword/guides/
|
|
67
|
+
**TDD Workflow:** See `@.safeword/guides/development-workflow.md` for comprehensive RED → GREEN → REFACTOR workflow with latest best practices
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
@@ -325,7 +325,7 @@ npm run test:e2e -- tests/feature-name.spec.ts --grep "specific test name"
|
|
|
325
325
|
|
|
326
326
|
**Important:** Test definitions are instructions that LLMs read and follow. Apply best practices for clarity.
|
|
327
327
|
|
|
328
|
-
**See:** `@.safeword/guides/llm-
|
|
328
|
+
**See:** `@.safeword/guides/llm-guide.md` for comprehensive framework including:
|
|
329
329
|
|
|
330
330
|
- MECE decision trees (mutually exclusive, collectively exhaustive)
|
|
331
331
|
- Explicit definitions (never assume LLMs know what you mean)
|
|
@@ -221,7 +221,7 @@ After filling out story, mentally check:
|
|
|
221
221
|
|
|
222
222
|
**Core principle:** User stories are instructions that LLMs read and follow. Apply LLM instruction design best practices.
|
|
223
223
|
|
|
224
|
-
**See:** `@.safeword/guides/llm-
|
|
224
|
+
**See:** `@.safeword/guides/llm-guide.md` for comprehensive framework on writing LLM-consumable documentation.
|
|
225
225
|
|
|
226
226
|
**When filling templates:**
|
|
227
227
|
|
|
@@ -26,7 +26,7 @@ When running dev servers and E2E tests across multiple projects, zombie processe
|
|
|
26
26
|
- **Dev port**: Project's configured port (e.g., 3000, 5173, 8080) - manual testing
|
|
27
27
|
- **Test port**: Dev port + 1000 (e.g., 4000, 6173, 9080) - Playwright managed
|
|
28
28
|
|
|
29
|
-
See `
|
|
29
|
+
See `development-workflow.md` → "E2E Testing with Persistent Dev Servers" for full port isolation strategy.
|
|
30
30
|
|
|
31
31
|
**Decision rule:** If unsure which cleanup method to use → port-based first (safest), then project script, then tmux.
|
|
32
32
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/fs.ts","../src/utils/project-detector.ts","../src/templates/content.ts","../src/templates/config.ts","../src/utils/boundaries.ts","../src/utils/install.ts","../src/utils/hooks.ts","../src/schema.ts"],"sourcesContent":["/**\n * File system utilities for CLI operations\n */\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n rmSync,\n rmdirSync,\n readdirSync,\n chmodSync,\n} from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// Get the directory of this module (for locating templates)\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Get path to bundled templates directory.\n * Works in both development (src/) and production (dist/) contexts.\n *\n * Note: We check for SAFEWORD.md to distinguish from src/templates/ which\n * contains TypeScript source files (config.ts, content.ts).\n *\n * Path resolution (bundled with tsup):\n * - From dist/chunk-*.js: __dirname = packages/cli/dist/ → ../templates\n */\nexport function getTemplatesDir(): string {\n const knownTemplateFile = 'SAFEWORD.md';\n\n // Try different relative paths - the bundled code ends up in dist/ directly (flat)\n // while source is in src/utils/\n const candidates = [\n join(__dirname, '..', 'templates'), // From dist/ (flat bundled)\n join(__dirname, '..', '..', 'templates'), // From src/utils/ or dist/utils/\n join(__dirname, 'templates'), // Direct sibling (unlikely but safe)\n ];\n\n for (const candidate of candidates) {\n if (existsSync(join(candidate, knownTemplateFile))) {\n return candidate;\n }\n }\n\n throw new Error('Templates directory not found');\n}\n\n/**\n * Check if a path exists\n */\nexport function exists(path: string): boolean {\n return existsSync(path);\n}\n\n/**\n * Create directory recursively\n */\nexport function ensureDir(path: string): void {\n if (!existsSync(path)) {\n mkdirSync(path, { recursive: true });\n }\n}\n\n/**\n * Read file as string\n */\nexport function readFile(path: string): string {\n return readFileSync(path, 'utf-8');\n}\n\n/**\n * Read file as string, return null if not exists\n */\nexport function readFileSafe(path: string): string | null {\n if (!existsSync(path)) return null;\n return readFileSync(path, 'utf-8');\n}\n\n/**\n * Write file, creating parent directories if needed\n */\nexport function writeFile(path: string, content: string): void {\n ensureDir(dirname(path));\n writeFileSync(path, content);\n}\n\n/**\n * Remove file or directory recursively\n */\nexport function remove(path: string): void {\n if (existsSync(path)) {\n rmSync(path, { recursive: true, force: true });\n }\n}\n\n/**\n * Remove directory only if empty, returns true if removed\n */\nexport function removeIfEmpty(path: string): boolean {\n if (!existsSync(path)) return false;\n try {\n rmdirSync(path); // Non-recursive, throws if not empty\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Make all shell scripts in a directory executable\n */\nexport function makeScriptsExecutable(dirPath: string): void {\n if (!existsSync(dirPath)) return;\n for (const file of readdirSync(dirPath)) {\n if (file.endsWith('.sh')) {\n chmodSync(join(dirPath, file), 0o755);\n }\n }\n}\n\n/**\n * Read JSON file\n */\nexport function readJson<T = unknown>(path: string): T | null {\n const content = readFileSafe(path);\n if (!content) return null;\n try {\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Write JSON file with formatting\n */\nexport function writeJson(path: string, data: unknown): void {\n writeFile(path, JSON.stringify(data, null, 2) + '\\n');\n}\n","/**\n * Project type detection from package.json\n *\n * Detects frameworks and tools used in the project to configure\n * appropriate linting rules.\n */\n\nexport interface PackageJson {\n name?: string;\n version?: string;\n private?: boolean;\n main?: string;\n module?: string;\n exports?: unknown;\n types?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\nexport interface ProjectType {\n typescript: boolean;\n react: boolean;\n nextjs: boolean;\n astro: boolean;\n vue: boolean;\n nuxt: boolean;\n svelte: boolean;\n sveltekit: boolean;\n electron: boolean;\n vitest: boolean;\n playwright: boolean;\n tailwind: boolean;\n publishableLibrary: boolean;\n}\n\n/**\n * Detects project type from package.json contents\n */\nexport function detectProjectType(packageJson: PackageJson): ProjectType {\n const deps = packageJson.dependencies || {};\n const devDeps = packageJson.devDependencies || {};\n const allDeps = { ...deps, ...devDeps };\n\n const hasTypescript = 'typescript' in allDeps;\n const hasReact = 'react' in deps || 'react' in devDeps;\n const hasNextJs = 'next' in deps;\n const hasAstro = 'astro' in deps || 'astro' in devDeps;\n const hasVue = 'vue' in deps || 'vue' in devDeps;\n const hasNuxt = 'nuxt' in deps;\n const hasSvelte = 'svelte' in deps || 'svelte' in devDeps;\n const hasSvelteKit = '@sveltejs/kit' in deps || '@sveltejs/kit' in devDeps;\n const hasElectron = 'electron' in deps || 'electron' in devDeps;\n const hasVitest = 'vitest' in devDeps;\n const hasPlaywright = '@playwright/test' in devDeps;\n const hasTailwind = 'tailwindcss' in allDeps;\n\n // Publishable library: has entry points and is not marked private\n const hasEntryPoints = !!(packageJson.main || packageJson.module || packageJson.exports);\n const isPublishable = hasEntryPoints && packageJson.private !== true;\n\n return {\n typescript: hasTypescript,\n react: hasReact || hasNextJs, // Next.js implies React\n nextjs: hasNextJs,\n astro: hasAstro,\n vue: hasVue || hasNuxt, // Nuxt implies Vue\n nuxt: hasNuxt,\n svelte: hasSvelte || hasSvelteKit, // SvelteKit implies Svelte\n sveltekit: hasSvelteKit,\n electron: hasElectron,\n vitest: hasVitest,\n playwright: hasPlaywright,\n tailwind: hasTailwind,\n publishableLibrary: isPublishable,\n };\n}\n","/**\n * Content templates - static string content\n *\n * Note: Most templates (SAFEWORD.md, hooks, skills, guides, etc.) are now\n * file-based in the templates/ directory. This file contains only small\n * string constants that are used inline.\n */\n\nexport const AGENTS_MD_LINK = `**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**\n\nThe SAFEWORD.md file contains core development patterns, workflows, and conventions.\nRead it BEFORE working on any task in this project.\n\n---`;\n\nexport const PRETTIERRC = `{\n \"semi\": true,\n \"singleQuote\": true,\n \"tabWidth\": 2,\n \"trailingComma\": \"es5\",\n \"printWidth\": 100,\n \"endOfLine\": \"lf\"\n}\n`;\n\n/**\n * lint-staged configuration for pre-commit hooks\n * Runs linters only on staged files for fast commits\n *\n * SYNC: Keep file patterns in sync with post-tool-lint.sh in:\n * packages/cli/templates/hooks/post-tool-lint.sh\n */\nexport const LINT_STAGED_CONFIG = {\n '*.{js,jsx,ts,tsx,mjs,mts,cjs,cts}': ['eslint --fix', 'prettier --write'],\n '*.{vue,svelte,astro}': ['eslint --fix', 'prettier --write'],\n '*.{json,css,scss,html,yaml,yml,graphql}': ['prettier --write'],\n '*.md': ['markdownlint-cli2 --fix', 'prettier --write'],\n};\n","/**\n * Configuration templates - ESLint config generation and hook settings\n *\n * ESLint flat config (v9+) with:\n * - Dynamic framework detection from package.json at runtime\n * - Static imports for base plugins (always installed by safeword)\n * - Dynamic imports for framework plugins (loaded only if framework detected)\n * - defineConfig helper for validation and type checking\n * - eslint-config-prettier last to avoid conflicts\n *\n * See: https://eslint.org/docs/latest/use/configure/configuration-files\n */\n\n/**\n * Generates a dynamic ESLint config that adapts to project frameworks at runtime.\n *\n * The generated config reads package.json to detect frameworks and dynamically\n * imports the corresponding ESLint plugins. This allows the config to be generated\n * once at setup and automatically adapt when frameworks are added or removed.\n *\n * @param options.boundaries - Whether to include architecture boundaries config\n * @returns ESLint config file content as a string\n */\nexport function getEslintConfig(options: { boundaries?: boolean }): string {\n return `import { readFileSync } from \"fs\";\nimport { defineConfig } from \"eslint/config\";\nimport js from \"@eslint/js\";\nimport { importX } from \"eslint-plugin-import-x\";\nimport sonarjs from \"eslint-plugin-sonarjs\";\nimport sdl from \"@microsoft/eslint-plugin-sdl\";\nimport playwright from \"eslint-plugin-playwright\";\nimport eslintConfigPrettier from \"eslint-config-prettier\";\n${options.boundaries ? 'import boundariesConfig from \"./.safeword/eslint-boundaries.config.mjs\";' : ''}\n\n// Read package.json to detect frameworks at runtime\nconst pkg = JSON.parse(readFileSync(\"./package.json\", \"utf8\"));\nconst deps = { ...pkg.dependencies, ...pkg.devDependencies };\n\n// Build dynamic ignores based on detected frameworks\nconst ignores = [\"node_modules/\", \"dist/\", \"build/\", \"coverage/\"];\nif (deps[\"next\"]) ignores.push(\".next/\");\nif (deps[\"astro\"]) ignores.push(\".astro/\");\nif (deps[\"vue\"] || deps[\"nuxt\"]) ignores.push(\".nuxt/\");\nif (deps[\"svelte\"] || deps[\"@sveltejs/kit\"]) ignores.push(\".svelte-kit/\");\n\n// Start with base configs (always loaded)\nconst configs = [\n { ignores },\n js.configs.recommended,\n importX.flatConfigs.recommended,\n sonarjs.configs.recommended,\n ...sdl.configs.recommended,\n];\n\n// TypeScript support (detected from package.json)\nif (deps[\"typescript\"]) {\n const tseslint = await import(\"typescript-eslint\");\n configs.push(importX.flatConfigs.typescript);\n configs.push(...tseslint.default.configs.recommended);\n}\n\n// React/Next.js support\nif (deps[\"react\"] || deps[\"next\"]) {\n const react = await import(\"eslint-plugin-react\");\n const reactHooks = await import(\"eslint-plugin-react-hooks\");\n const jsxA11y = await import(\"eslint-plugin-jsx-a11y\");\n configs.push(react.default.configs.flat.recommended);\n configs.push(react.default.configs.flat[\"jsx-runtime\"]);\n configs.push({\n name: \"react-hooks\",\n plugins: { \"react-hooks\": reactHooks.default },\n rules: reactHooks.default.configs.recommended.rules,\n });\n configs.push(jsxA11y.default.flatConfigs.recommended);\n}\n\n// Next.js plugin\nif (deps[\"next\"]) {\n const nextPlugin = await import(\"@next/eslint-plugin-next\");\n configs.push({\n name: \"nextjs\",\n plugins: { \"@next/next\": nextPlugin.default },\n rules: nextPlugin.default.configs.recommended.rules,\n });\n}\n\n// Astro support\nif (deps[\"astro\"]) {\n const astro = await import(\"eslint-plugin-astro\");\n configs.push(...astro.default.configs.recommended);\n}\n\n// Vue support\nif (deps[\"vue\"] || deps[\"nuxt\"]) {\n const vue = await import(\"eslint-plugin-vue\");\n configs.push(...vue.default.configs[\"flat/recommended\"]);\n}\n\n// Svelte support\nif (deps[\"svelte\"] || deps[\"@sveltejs/kit\"]) {\n const svelte = await import(\"eslint-plugin-svelte\");\n configs.push(...svelte.default.configs.recommended);\n}\n\n// Electron support\nif (deps[\"electron\"]) {\n const electron = await import(\"@electron-toolkit/eslint-config\");\n configs.push(electron.default);\n}\n\n// Vitest support (scoped to test files)\nif (deps[\"vitest\"]) {\n const vitest = await import(\"@vitest/eslint-plugin\");\n configs.push({\n name: \"vitest\",\n files: [\"**/*.test.{js,ts,jsx,tsx}\", \"**/*.spec.{js,ts,jsx,tsx}\", \"**/tests/**\"],\n plugins: { vitest: vitest.default },\n languageOptions: {\n globals: { ...vitest.default.environments.env.globals },\n },\n rules: { ...vitest.default.configs.recommended.rules },\n });\n}\n\n// Playwright for e2e tests (always included - safeword sets up Playwright)\nconfigs.push({\n name: \"playwright\",\n files: [\"**/e2e/**\", \"**/*.e2e.{js,ts,jsx,tsx}\", \"**/playwright/**\"],\n ...playwright.configs[\"flat/recommended\"],\n});\n\n// Architecture boundaries${options.boundaries ? '\\nconfigs.push(boundariesConfig);' : ''}\n\n// eslint-config-prettier must be last to disable conflicting rules\nconfigs.push(eslintConfigPrettier);\n\nexport default defineConfig(configs);\n`;\n}\n\nexport const SETTINGS_HOOKS = {\n SessionStart: [\n {\n hooks: [\n {\n type: 'command',\n command: '\"$CLAUDE_PROJECT_DIR\"/.safeword/hooks/session-verify-agents.sh',\n },\n ],\n },\n {\n hooks: [\n {\n type: 'command',\n command: '\"$CLAUDE_PROJECT_DIR\"/.safeword/hooks/session-version.sh',\n },\n ],\n },\n {\n hooks: [\n {\n type: 'command',\n command: '\"$CLAUDE_PROJECT_DIR\"/.safeword/hooks/session-lint-check.sh',\n },\n ],\n },\n ],\n UserPromptSubmit: [\n {\n hooks: [\n {\n type: 'command',\n command: '\"$CLAUDE_PROJECT_DIR\"/.safeword/hooks/prompt-timestamp.sh',\n },\n ],\n },\n {\n hooks: [\n {\n type: 'command',\n command: '\"$CLAUDE_PROJECT_DIR\"/.safeword/hooks/prompt-questions.sh',\n },\n ],\n },\n ],\n Stop: [\n {\n hooks: [\n {\n type: 'command',\n command: '\"$CLAUDE_PROJECT_DIR\"/.safeword/hooks/stop-quality.sh',\n },\n ],\n },\n ],\n PostToolUse: [\n {\n matcher: 'Write|Edit|MultiEdit|NotebookEdit',\n hooks: [\n {\n type: 'command',\n command: '\"$CLAUDE_PROJECT_DIR\"/.safeword/hooks/post-tool-lint.sh',\n },\n ],\n },\n ],\n};\n","/**\n * Architecture boundaries detection and config generation\n *\n * Auto-detects common architecture directories and generates\n * eslint-plugin-boundaries config with sensible hierarchy rules.\n */\n\nimport { join } from 'node:path';\nimport { exists } from './fs.js';\n\n/**\n * Architecture directories to detect, ordered from bottom to top of hierarchy.\n * Lower items can be imported by higher items, not vice versa.\n */\nconst ARCHITECTURE_DIRS = [\n 'types', // Bottom: can be imported by everything\n 'utils',\n 'lib',\n 'hooks',\n 'services',\n 'components',\n 'features',\n 'modules',\n 'app', // Top: can import everything\n] as const;\n\ntype ArchDir = (typeof ARCHITECTURE_DIRS)[number];\n\n/**\n * Hierarchy rules: what each layer can import\n * Lower layers have fewer import permissions\n */\nconst HIERARCHY: Record<ArchDir, ArchDir[]> = {\n types: [], // types can't import anything (pure type definitions)\n utils: ['types'],\n lib: ['utils', 'types'],\n hooks: ['lib', 'utils', 'types'],\n services: ['lib', 'utils', 'types'],\n components: ['hooks', 'services', 'lib', 'utils', 'types'],\n features: ['components', 'hooks', 'services', 'lib', 'utils', 'types'],\n modules: ['components', 'hooks', 'services', 'lib', 'utils', 'types'],\n app: ['features', 'modules', 'components', 'hooks', 'services', 'lib', 'utils', 'types'],\n};\n\nexport interface DetectedArchitecture {\n directories: ArchDir[];\n inSrc: boolean; // true if dirs are in src/, false if at root\n}\n\n/**\n * Detects architecture directories in the project\n * Always returns a result (even with 0 directories) - boundaries is always configured\n */\nexport function detectArchitecture(projectDir: string): DetectedArchitecture {\n const foundInSrc: ArchDir[] = [];\n const foundAtRoot: ArchDir[] = [];\n\n for (const dir of ARCHITECTURE_DIRS) {\n if (exists(join(projectDir, 'src', dir))) {\n foundInSrc.push(dir);\n }\n if (exists(join(projectDir, dir))) {\n foundAtRoot.push(dir);\n }\n }\n\n // Prefer src/ location if more dirs found there\n const inSrc = foundInSrc.length >= foundAtRoot.length;\n const found = inSrc ? foundInSrc : foundAtRoot;\n\n return { directories: found, inSrc };\n}\n\n/**\n * Generates the boundaries config file content\n */\nexport function generateBoundariesConfig(arch: DetectedArchitecture): string {\n const prefix = arch.inSrc ? 'src/' : '';\n const hasDirectories = arch.directories.length > 0;\n\n // Generate element definitions with mode: 'full' to match from project root only\n const elements = arch.directories\n .map(dir => ` { type: '${dir}', pattern: '${prefix}${dir}/**', mode: 'full' }`)\n .join(',\\n');\n\n // Generate rules (what each layer can import)\n const rules = arch.directories\n .filter(dir => HIERARCHY[dir].length > 0)\n .map(dir => {\n const allowed = HIERARCHY[dir].filter(dep => arch.directories.includes(dep));\n if (allowed.length === 0) return null;\n return ` { from: ['${dir}'], allow: [${allowed.map(d => `'${d}'`).join(', ')}] }`;\n })\n .filter(Boolean)\n .join(',\\n');\n\n const detectedInfo = hasDirectories\n ? `Detected directories: ${arch.directories.join(', ')} (${arch.inSrc ? 'in src/' : 'at root'})`\n : 'No architecture directories detected yet - add types/, utils/, components/, etc.';\n\n // Build elements array content (empty array if no directories)\n const elementsContent = elements || '';\n const rulesContent = rules || '';\n\n return `/**\n * Architecture Boundaries Configuration (AUTO-GENERATED)\n *\n * ${detectedInfo}\n *\n * This enforces import boundaries between architectural layers:\n * - Lower layers (types, utils) cannot import from higher layers (components, features)\n * - Uses 'warn' severity - informative, not blocking\n *\n * Recognized directories (in hierarchy order):\n * types → utils → lib → hooks/services → components → features/modules → app\n *\n * To customize, override in your eslint.config.mjs:\n * rules: { 'boundaries/element-types': ['error', { ... }] }\n */\n\nimport boundaries from 'eslint-plugin-boundaries';\n\nexport default {\n plugins: { boundaries },\n settings: {\n 'boundaries/elements': [\n${elementsContent}\n ],\n },\n rules: {\n 'boundaries/element-types': ['warn', {\n default: 'disallow',\n rules: [\n${rulesContent}\n ],\n }],\n 'boundaries/no-unknown': 'off', // Allow files outside defined elements\n 'boundaries/no-unknown-files': 'off', // Allow non-matching files\n },\n};\n`;\n}\n","/**\n * Shared installation constants\n *\n * These constants are used by schema.ts to define the single source of truth.\n * All functions have been removed - reconcile.ts now handles all operations.\n */\n\n/**\n * Husky pre-commit hook content - includes safeword sync + lint-staged\n * The sync command keeps ESLint plugins aligned with detected frameworks\n */\nexport const HUSKY_PRE_COMMIT_CONTENT = 'npx safeword sync --quiet --stage\\nnpx lint-staged\\n';\n\n/**\n * MCP servers installed by safeword\n */\nexport const MCP_SERVERS = {\n context7: {\n command: 'npx',\n args: ['-y', '@upstash/context7-mcp@latest'],\n },\n playwright: {\n command: 'npx',\n args: ['@playwright/mcp@latest'],\n },\n} as const;\n\n// NOTE: All other constants and functions were removed in the declarative schema refactor.\n// The single source of truth is now SAFEWORD_SCHEMA in src/schema.ts.\n// Operations are handled by reconcile() in src/reconcile.ts.\n","/**\n * Hook utilities for Claude Code settings\n */\n\ninterface HookCommand {\n type: string;\n command: string;\n}\n\ninterface HookEntry {\n matcher?: string;\n hooks: HookCommand[];\n}\n\n/**\n * Type guard to check if a value is a hook entry with hooks array\n */\nexport function isHookEntry(h: unknown): h is HookEntry {\n return (\n typeof h === 'object' &&\n h !== null &&\n 'hooks' in h &&\n Array.isArray((h as HookEntry).hooks)\n );\n}\n\n/**\n * Check if a hook entry contains a safeword hook (command contains '.safeword')\n */\nexport function isSafewordHook(h: unknown): boolean {\n if (!isHookEntry(h)) return false;\n return h.hooks.some(\n (cmd) => typeof cmd.command === 'string' && cmd.command.includes('.safeword'),\n );\n}\n\n/**\n * Filter out safeword hooks from an array of hook entries\n */\nexport function filterOutSafewordHooks(hooks: unknown[]): unknown[] {\n return hooks.filter((h) => !isSafewordHook(h));\n}\n","/**\n * SAFEWORD Schema - Single Source of Truth\n *\n * All files, directories, configurations, and packages managed by safeword\n * are defined here. Commands use this schema via the reconciliation engine.\n *\n * Adding a new file? Add it here and it will be handled by setup/upgrade/reset.\n */\n\nimport { VERSION } from './version.js';\nimport { type ProjectType } from './utils/project-detector.js';\nimport { AGENTS_MD_LINK, PRETTIERRC, LINT_STAGED_CONFIG } from './templates/content.js';\nimport { getEslintConfig, SETTINGS_HOOKS } from './templates/config.js';\nimport { generateBoundariesConfig, detectArchitecture } from './utils/boundaries.js';\nimport { HUSKY_PRE_COMMIT_CONTENT, MCP_SERVERS } from './utils/install.js';\nimport { filterOutSafewordHooks } from './utils/hooks.js';\n\n// ============================================================================\n// Interfaces\n// ============================================================================\n\nexport interface ProjectContext {\n cwd: string;\n projectType: ProjectType;\n devDeps: Record<string, string>;\n isGitRepo: boolean;\n}\n\nexport interface FileDefinition {\n template?: string; // Path in templates/ dir\n content?: string | (() => string); // Static content or factory\n generator?: (ctx: ProjectContext) => string; // Dynamic generator needing context\n}\n\nexport interface ManagedFileDefinition extends FileDefinition {\n // managedFiles: created if missing, updated only if content === current template output\n}\n\nexport interface JsonMergeDefinition {\n keys: string[]; // Dot-notation keys we manage\n conditionalKeys?: Record<string, string[]>; // Keys added based on project type\n merge: (existing: Record<string, unknown>, ctx: ProjectContext) => Record<string, unknown>;\n unmerge: (existing: Record<string, unknown>) => Record<string, unknown>;\n removeFileIfEmpty?: boolean; // Delete file if our keys were the only content\n}\n\nexport interface TextPatchDefinition {\n operation: 'prepend' | 'append';\n content: string;\n marker: string; // Used to detect if already applied & for removal\n createIfMissing: boolean;\n}\n\nexport interface SafewordSchema {\n version: string;\n ownedDirs: string[]; // Fully owned - create on setup, delete on reset\n sharedDirs: string[]; // We add to but don't own\n preservedDirs: string[]; // Created on setup, NOT deleted on reset (user data)\n ownedFiles: Record<string, FileDefinition>; // Overwrite on upgrade (if changed)\n managedFiles: Record<string, ManagedFileDefinition>; // Create if missing, update if safeword content\n jsonMerges: Record<string, JsonMergeDefinition>;\n textPatches: Record<string, TextPatchDefinition>;\n packages: {\n base: string[];\n conditional: Record<string, string[]>;\n };\n}\n\n// ============================================================================\n// SAFEWORD_SCHEMA - The Single Source of Truth\n// ============================================================================\n\nexport const SAFEWORD_SCHEMA: SafewordSchema = {\n version: VERSION,\n\n // Directories fully owned by safeword (created on setup, deleted on reset)\n ownedDirs: [\n '.safeword',\n '.safeword/hooks',\n '.safeword/lib',\n '.safeword/guides',\n '.safeword/templates',\n '.safeword/prompts',\n '.safeword/planning',\n '.safeword/planning/user-stories',\n '.safeword/planning/test-definitions',\n '.safeword/planning/design',\n '.safeword/planning/issues',\n '.husky',\n ],\n\n // Directories we add to but don't own (not deleted on reset)\n sharedDirs: ['.claude', '.claude/skills', '.claude/commands'],\n\n // Created on setup but NOT deleted on reset (preserves user data)\n preservedDirs: ['.safeword/learnings', '.safeword/tickets', '.safeword/tickets/completed'],\n\n // Files owned by safeword (overwritten on upgrade if content changed)\n ownedFiles: {\n // Core files\n '.safeword/SAFEWORD.md': { template: 'SAFEWORD.md' },\n '.safeword/version': { content: () => VERSION },\n '.safeword/eslint-boundaries.config.mjs': {\n generator: ctx => generateBoundariesConfig(detectArchitecture(ctx.cwd)),\n },\n\n // Hooks (7 files)\n '.safeword/hooks/session-verify-agents.sh': { template: 'hooks/session-verify-agents.sh' },\n '.safeword/hooks/session-version.sh': { template: 'hooks/session-version.sh' },\n '.safeword/hooks/session-lint-check.sh': { template: 'hooks/session-lint-check.sh' },\n '.safeword/hooks/prompt-timestamp.sh': { template: 'hooks/prompt-timestamp.sh' },\n '.safeword/hooks/prompt-questions.sh': { template: 'hooks/prompt-questions.sh' },\n '.safeword/hooks/post-tool-lint.sh': { template: 'hooks/post-tool-lint.sh' },\n '.safeword/hooks/stop-quality.sh': { template: 'hooks/stop-quality.sh' },\n\n // Lib (2 files)\n '.safeword/lib/common.sh': { template: 'lib/common.sh' },\n '.safeword/lib/jq-fallback.sh': { template: 'lib/jq-fallback.sh' },\n\n // Guides (13 files)\n '.safeword/guides/architecture-guide.md': { template: 'guides/architecture-guide.md' },\n '.safeword/guides/code-philosophy.md': { template: 'guides/code-philosophy.md' },\n '.safeword/guides/context-files-guide.md': { template: 'guides/context-files-guide.md' },\n '.safeword/guides/data-architecture-guide.md': {\n template: 'guides/data-architecture-guide.md',\n },\n '.safeword/guides/design-doc-guide.md': { template: 'guides/design-doc-guide.md' },\n '.safeword/guides/learning-extraction.md': { template: 'guides/learning-extraction.md' },\n '.safeword/guides/llm-instruction-design.md': { template: 'guides/llm-instruction-design.md' },\n '.safeword/guides/llm-prompting.md': { template: 'guides/llm-prompting.md' },\n '.safeword/guides/tdd-best-practices.md': { template: 'guides/tdd-best-practices.md' },\n '.safeword/guides/test-definitions-guide.md': { template: 'guides/test-definitions-guide.md' },\n '.safeword/guides/testing-methodology.md': { template: 'guides/testing-methodology.md' },\n '.safeword/guides/user-story-guide.md': { template: 'guides/user-story-guide.md' },\n '.safeword/guides/zombie-process-cleanup.md': { template: 'guides/zombie-process-cleanup.md' },\n\n // Templates (5 files)\n '.safeword/templates/architecture-template.md': {\n template: 'doc-templates/architecture-template.md',\n },\n '.safeword/templates/design-doc-template.md': {\n template: 'doc-templates/design-doc-template.md',\n },\n '.safeword/templates/test-definitions-feature.md': {\n template: 'doc-templates/test-definitions-feature.md',\n },\n '.safeword/templates/ticket-template.md': { template: 'doc-templates/ticket-template.md' },\n '.safeword/templates/user-stories-template.md': {\n template: 'doc-templates/user-stories-template.md',\n },\n\n // Prompts (2 files)\n '.safeword/prompts/architecture.md': { template: 'prompts/architecture.md' },\n '.safeword/prompts/quality-review.md': { template: 'prompts/quality-review.md' },\n\n // Claude skills and commands (4 files)\n '.claude/skills/safeword-quality-reviewer/SKILL.md': {\n template: 'skills/safeword-quality-reviewer/SKILL.md',\n },\n '.claude/commands/architecture.md': { template: 'commands/architecture.md' },\n '.claude/commands/lint.md': { template: 'commands/lint.md' },\n '.claude/commands/quality-review.md': { template: 'commands/quality-review.md' },\n\n // Husky (1 file)\n '.husky/pre-commit': { content: HUSKY_PRE_COMMIT_CONTENT },\n },\n\n // Files created if missing, updated only if content matches current template\n managedFiles: {\n 'eslint.config.mjs': {\n generator: () => getEslintConfig({ boundaries: true }),\n },\n '.prettierrc': { content: PRETTIERRC },\n '.markdownlint-cli2.jsonc': { template: 'markdownlint-cli2.jsonc' },\n },\n\n // JSON files where we merge specific keys\n jsonMerges: {\n 'package.json': {\n keys: [\n 'scripts.lint',\n 'scripts.lint:md',\n 'scripts.format',\n 'scripts.format:check',\n 'scripts.knip',\n 'scripts.prepare',\n 'lint-staged',\n ],\n conditionalKeys: {\n publishableLibrary: ['scripts.publint'],\n },\n merge: (existing, ctx) => {\n const scripts = (existing.scripts as Record<string, string>) ?? {};\n const result = { ...existing };\n\n // Add scripts if not present\n if (!scripts.lint) scripts.lint = 'eslint .';\n if (!scripts['lint:md']) scripts['lint:md'] = 'markdownlint-cli2 \"**/*.md\" \"#node_modules\"';\n if (!scripts.format) scripts.format = 'prettier --write .';\n if (!scripts['format:check']) scripts['format:check'] = 'prettier --check .';\n if (!scripts.knip) scripts.knip = 'knip';\n if (!scripts.prepare) scripts.prepare = 'husky || true';\n\n // Conditional: publint for publishable libraries\n if (ctx.projectType.publishableLibrary && !scripts.publint) {\n scripts.publint = 'publint';\n }\n\n result.scripts = scripts;\n\n // Add lint-staged config\n if (!existing['lint-staged']) {\n result['lint-staged'] = LINT_STAGED_CONFIG;\n }\n\n return result;\n },\n unmerge: existing => {\n const result = { ...existing };\n const scripts = { ...((existing.scripts as Record<string, string>) ?? {}) };\n\n // Remove safeword-specific scripts but preserve lint/format (useful standalone)\n delete scripts['lint:md'];\n delete scripts['format:check'];\n delete scripts.knip;\n delete scripts.prepare;\n delete scripts.publint;\n\n if (Object.keys(scripts).length > 0) {\n result.scripts = scripts;\n } else {\n delete result.scripts;\n }\n\n delete result['lint-staged'];\n\n return result;\n },\n },\n\n '.claude/settings.json': {\n keys: ['hooks'],\n merge: existing => {\n // Preserve non-safeword hooks while adding/updating safeword hooks\n const existingHooks = (existing.hooks as Record<string, unknown[]>) ?? {};\n const mergedHooks: Record<string, unknown[]> = { ...existingHooks };\n\n for (const [event, newHooks] of Object.entries(SETTINGS_HOOKS)) {\n const eventHooks = (mergedHooks[event] as unknown[]) ?? [];\n const nonSafewordHooks = filterOutSafewordHooks(eventHooks);\n mergedHooks[event] = [...nonSafewordHooks, ...newHooks];\n }\n\n return { ...existing, hooks: mergedHooks };\n },\n unmerge: existing => {\n // Remove only safeword hooks, preserve custom hooks\n const existingHooks = (existing.hooks as Record<string, unknown[]>) ?? {};\n const cleanedHooks: Record<string, unknown[]> = {};\n\n for (const [event, eventHooks] of Object.entries(existingHooks)) {\n const nonSafewordHooks = filterOutSafewordHooks(eventHooks as unknown[]);\n if (nonSafewordHooks.length > 0) {\n cleanedHooks[event] = nonSafewordHooks;\n }\n }\n\n const result = { ...existing };\n if (Object.keys(cleanedHooks).length > 0) {\n result.hooks = cleanedHooks;\n } else {\n delete result.hooks;\n }\n return result;\n },\n },\n\n '.mcp.json': {\n keys: ['mcpServers.context7', 'mcpServers.playwright'],\n removeFileIfEmpty: true,\n merge: existing => {\n const mcpServers = (existing.mcpServers as Record<string, unknown>) ?? {};\n return {\n ...existing,\n mcpServers: {\n ...mcpServers,\n context7: MCP_SERVERS.context7,\n playwright: MCP_SERVERS.playwright,\n },\n };\n },\n unmerge: existing => {\n const result = { ...existing };\n const mcpServers = { ...((existing.mcpServers as Record<string, unknown>) ?? {}) };\n\n delete mcpServers.context7;\n delete mcpServers.playwright;\n\n if (Object.keys(mcpServers).length > 0) {\n result.mcpServers = mcpServers;\n } else {\n delete result.mcpServers;\n }\n\n return result;\n },\n },\n },\n\n // Text files where we patch specific content\n textPatches: {\n 'AGENTS.md': {\n operation: 'prepend',\n content: AGENTS_MD_LINK,\n marker: '@./.safeword/SAFEWORD.md',\n createIfMissing: true,\n },\n 'CLAUDE.md': {\n operation: 'prepend',\n content: AGENTS_MD_LINK,\n marker: '@./.safeword/SAFEWORD.md',\n createIfMissing: false, // Only patch if exists, don't create (AGENTS.md is primary)\n },\n },\n\n // NPM packages to install\n packages: {\n base: [\n 'eslint',\n 'prettier',\n '@eslint/js',\n 'eslint-plugin-import-x',\n 'eslint-plugin-sonarjs',\n 'eslint-plugin-boundaries',\n 'eslint-plugin-playwright',\n '@microsoft/eslint-plugin-sdl',\n 'eslint-config-prettier',\n 'markdownlint-cli2',\n 'knip',\n 'husky',\n 'lint-staged',\n ],\n conditional: {\n typescript: ['typescript-eslint'],\n react: ['eslint-plugin-react', 'eslint-plugin-react-hooks', 'eslint-plugin-jsx-a11y'],\n nextjs: ['@next/eslint-plugin-next'],\n astro: ['eslint-plugin-astro'],\n vue: ['eslint-plugin-vue'],\n svelte: ['eslint-plugin-svelte'],\n electron: ['@electron-toolkit/eslint-config'],\n vitest: ['@vitest/eslint-plugin'],\n tailwind: ['prettier-plugin-tailwindcss'],\n publishableLibrary: ['publint'],\n },\n },\n};\n"],"mappings":"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","names":["join","join"]}
|