safeword 0.6.4 → 0.6.6

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 (34) hide show
  1. package/dist/{check-ICZISZ3R.js → check-OYYSYHFP.js} +3 -3
  2. package/dist/{chunk-JGXYBPNM.js → chunk-LNSEDZIW.js} +2 -2
  3. package/dist/{chunk-E5ZC6R5H.js → chunk-ZS3Z3Q37.js} +24 -15
  4. package/dist/chunk-ZS3Z3Q37.js.map +1 -0
  5. package/dist/cli.js +6 -6
  6. package/dist/{diff-FOJDBKKF.js → diff-325TIZ63.js} +3 -3
  7. package/dist/{reset-JU2E65XN.js → reset-ZGJIKMUW.js} +3 -3
  8. package/dist/{setup-UKMYK5TE.js → setup-GAMXTFM2.js} +3 -3
  9. package/dist/{sync-5MOXVTH4.js → sync-BFMXZEHM.js} +2 -2
  10. package/dist/{upgrade-NSLDFWNR.js → upgrade-X4GREJXN.js} +3 -3
  11. package/package.json +15 -14
  12. package/templates/SAFEWORD.md +101 -669
  13. package/templates/guides/architecture-guide.md +1 -1
  14. package/templates/guides/cli-reference.md +35 -0
  15. package/templates/guides/code-philosophy.md +22 -19
  16. package/templates/guides/context-files-guide.md +2 -2
  17. package/templates/guides/data-architecture-guide.md +1 -1
  18. package/templates/guides/design-doc-guide.md +1 -1
  19. package/templates/guides/{testing-methodology.md → development-workflow.md} +1 -1
  20. package/templates/guides/learning-extraction.md +1 -1
  21. package/templates/guides/{llm-instruction-design.md → llm-guide.md} +93 -29
  22. package/templates/guides/tdd-best-practices.md +2 -2
  23. package/templates/guides/test-definitions-guide.md +1 -1
  24. package/templates/guides/user-story-guide.md +1 -1
  25. package/templates/guides/zombie-process-cleanup.md +1 -1
  26. package/dist/chunk-E5ZC6R5H.js.map +0 -1
  27. package/templates/guides/llm-prompting.md +0 -102
  28. /package/dist/{check-ICZISZ3R.js.map → check-OYYSYHFP.js.map} +0 -0
  29. /package/dist/{chunk-JGXYBPNM.js.map → chunk-LNSEDZIW.js.map} +0 -0
  30. /package/dist/{diff-FOJDBKKF.js.map → diff-325TIZ63.js.map} +0 -0
  31. /package/dist/{reset-JU2E65XN.js.map → reset-ZGJIKMUW.js.map} +0 -0
  32. /package/dist/{setup-UKMYK5TE.js.map → setup-GAMXTFM2.js.map} +0 -0
  33. /package/dist/{sync-5MOXVTH4.js.map → sync-BFMXZEHM.js.map} +0 -0
  34. /package/dist/{upgrade-NSLDFWNR.js.map → upgrade-X4GREJXN.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  # Architecture & Design Documentation Guide
2
2
 
3
- **See:** `@.safeword/guides/llm-instruction-design.md` for LLM-consumable documentation principles.
3
+ **See:** `@.safeword/guides/llm-guide.md` for LLM-consumable documentation principles.
4
4
 
5
5
  ---
6
6
 
@@ -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-instruction-design.md`.
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
- | ❌ Bad | ✅ Good |
45
- |--------|---------|
46
- | `catch (e) {}` (swallowed) | `catch (e) { throw new Error(\`Failed to read ${filePath}: ${e.message}\`) }`|
47
- |`catch (e) { console.log(e) }`|`catch (e) { logger.error('Payment failed', { userId, amount, error: e }) }` |
48
- | Generic "Something went wrong" | "Failed to save user profile: database connection timeout" |
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
- | ❌ Bad | ✅ Good |
52
- |--------|---------|
53
- | `calcTot` | `calculateTotalWithTax` |
52
+
53
+ | ❌ Bad | ✅ Good |
54
+ | ------------------ | ------------------------------ |
55
+ | `calcTot` | `calculateTotalWithTax` |
54
56
  | `d`, `tmp`, `data` | `userProfile`, `pendingOrders` |
55
- | `handleClick` | `submitPaymentForm` |
56
- | `process()` | `validateAndSaveUser()` |
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
- | ❌ Bloat | ✅ Instead |
67
- |----------|-----------|
68
- | Utility class for one function | Single function |
69
- | Factory pattern for simple object | Direct construction |
70
- | Abstract base class with one implementation | Concrete class |
71
- | Config file for 2 options | Hardcode or simple params |
72
- | "Future-proofing" unused code paths | Delete, add when needed |
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/testing-methodology.md` for comprehensive TDD workflow (RED → GREEN → REFACTOR phases)
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-prompting.md
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-instruction-design.md` for 13 core principles for writing LLM-consumable documentation.
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-instruction-design.md` for comprehensive framework on writing LLM-consumable documentation.
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-instruction-design.md` for comprehensive framework on writing clear, actionable documentation that LLMs can reliably follow.
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/testing-methodology.md`) - Universal methodology (test type selection, TDD workflow)
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-instruction-design.md` when writing learning files (concrete examples, explicit definitions, MECE principles).
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
- # Writing Instructions for LLMs
1
+ # LLM Guide
2
2
 
3
- **Context:** When creating documentation that LLMs will read and follow (like AGENTS.md, CLAUDE.md, testing guides, coding standards), different best practices apply than when prompting an LLM directly.
3
+ This guide covers two related topics:
4
4
 
5
- ## Core Principles
5
+ **Part 1: Integration** - How to call LLMs effectively (API calls, structured outputs, caching, testing)
6
6
 
7
- **1. MECE Principle (Mutually Exclusive, Collectively Exhaustive)**
7
+ **Part 2: Writing for LLMs** - How to write documentation that LLMs will read and follow (SAFEWORD.md, CLAUDE.md, guides)
8
8
 
9
- Decision trees and categorization must have no overlap and cover all cases. Research shows LLMs struggle with overlapping categories—McKinsey/BCG MECE framework ensures clear decision paths.
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
- **2. Explicit Over Implicit**
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
- **3. No Contradictions**
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
- **4. Concrete Examples Over Abstract Rules**
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
- **5. Edge Cases Must Be Explicit**
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 with common confusing scenarios.
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
- **6. Actionable Over Vague**
161
+ ### 6. Actionable Over Vague
98
162
 
99
- Give LLMs concrete actions, not subjective guidance. Replace subjective terms (most/some/few) with optimization rules + red flags.
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
- **7. Decision Trees: Sequential Over Parallel**
175
+ ### 7. Decision Trees: Sequential Over Parallel
112
176
 
113
- Structure decisions as ordered steps, not simultaneous checks. Sequential questions force the LLM through a deterministic decision path.
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 (see Principle 1 example above)
185
+ ✅ GOOD - Sequential:
122
186
  Answer questions IN ORDER. Stop at the first match.
123
187
  ```
124
188
 
125
- **8. Tie-Breaking Rules**
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
- **9. Lookup Tables for Complex Decisions**
201
+ ### 9. Lookup Tables for Complex Decisions
138
202
 
139
- When decision logic has 3+ branches, nested conditions, or multiple variables to consider, provide a reference table.
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
- **10. Avoid Caveats in Tables**
213
+ ### 10. Avoid Caveats in Tables
150
214
 
151
- Keep patterns clean. Parentheticals break LLM pattern matching. Add separate rows for caveat cases.
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
- **11. Percentages: Context or None**
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
- **12. Specificity in Questions**
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
- **13. Re-evaluation Paths**
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
- ## Anti-Patterns to Avoid
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
- ## Research-Backed Principles
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-instruction-design.md`.
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/testing-methodology.md` for comprehensive RED → GREEN → REFACTOR workflow with latest best practices
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-instruction-design.md` for comprehensive framework including:
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-instruction-design.md` for comprehensive framework on writing LLM-consumable documentation.
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 `testing-methodology.md` → "E2E Testing with Persistent Dev Servers" for full port isolation strategy.
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":";;;;;AAIA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;AAG9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAYjD,SAAS,kBAA0B;AACxC,QAAM,oBAAoB;AAI1B,QAAM,aAAa;AAAA,IACjB,KAAK,WAAW,MAAM,WAAW;AAAA;AAAA,IACjC,KAAK,WAAW,MAAM,MAAM,WAAW;AAAA;AAAA,IACvC,KAAK,WAAW,WAAW;AAAA;AAAA,EAC7B;AAEA,aAAW,aAAa,YAAY;AAClC,QAAI,WAAW,KAAK,WAAW,iBAAiB,CAAC,GAAG;AAClD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,+BAA+B;AACjD;AAKO,SAAS,OAAO,MAAuB;AAC5C,SAAO,WAAW,IAAI;AACxB;AAKO,SAAS,UAAU,MAAoB;AAC5C,MAAI,CAAC,WAAW,IAAI,GAAG;AACrB,cAAU,MAAM,EAAE,WAAW,KAAK,CAAC;AAAA,EACrC;AACF;AAKO,SAAS,SAAS,MAAsB;AAC7C,SAAO,aAAa,MAAM,OAAO;AACnC;AAKO,SAAS,aAAa,MAA6B;AACxD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,SAAO,aAAa,MAAM,OAAO;AACnC;AAKO,SAAS,UAAU,MAAc,SAAuB;AAC7D,YAAU,QAAQ,IAAI,CAAC;AACvB,gBAAc,MAAM,OAAO;AAC7B;AAKO,SAAS,OAAO,MAAoB;AACzC,MAAI,WAAW,IAAI,GAAG;AACpB,WAAO,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC/C;AACF;AAKO,SAAS,cAAc,MAAuB;AACnD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,cAAU,IAAI;AACd,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,sBAAsB,SAAuB;AAC3D,MAAI,CAAC,WAAW,OAAO,EAAG;AAC1B,aAAW,QAAQ,YAAY,OAAO,GAAG;AACvC,QAAI,KAAK,SAAS,KAAK,GAAG;AACxB,gBAAU,KAAK,SAAS,IAAI,GAAG,GAAK;AAAA,IACtC;AAAA,EACF;AACF;AAKO,SAAS,SAAsB,MAAwB;AAC5D,QAAM,UAAU,aAAa,IAAI;AACjC,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,UAAU,MAAc,MAAqB;AAC3D,YAAU,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,IAAI;AACtD;;;ACvGO,SAAS,kBAAkB,aAAuC;AACvE,QAAM,OAAO,YAAY,gBAAgB,CAAC;AAC1C,QAAM,UAAU,YAAY,mBAAmB,CAAC;AAChD,QAAM,UAAU,EAAE,GAAG,MAAM,GAAG,QAAQ;AAEtC,QAAM,gBAAgB,gBAAgB;AACtC,QAAM,WAAW,WAAW,QAAQ,WAAW;AAC/C,QAAM,YAAY,UAAU;AAC5B,QAAM,WAAW,WAAW,QAAQ,WAAW;AAC/C,QAAM,SAAS,SAAS,QAAQ,SAAS;AACzC,QAAM,UAAU,UAAU;AAC1B,QAAM,YAAY,YAAY,QAAQ,YAAY;AAClD,QAAM,eAAe,mBAAmB,QAAQ,mBAAmB;AACnE,QAAM,cAAc,cAAc,QAAQ,cAAc;AACxD,QAAM,YAAY,YAAY;AAC9B,QAAM,gBAAgB,sBAAsB;AAC5C,QAAM,cAAc,iBAAiB;AAGrC,QAAM,iBAAiB,CAAC,EAAE,YAAY,QAAQ,YAAY,UAAU,YAAY;AAChF,QAAM,gBAAgB,kBAAkB,YAAY,YAAY;AAEhE,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,OAAO,YAAY;AAAA;AAAA,IACnB,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,KAAK,UAAU;AAAA;AAAA,IACf,MAAM;AAAA,IACN,QAAQ,aAAa;AAAA;AAAA,IACrB,WAAW;AAAA,IACX,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,oBAAoB;AAAA,EACtB;AACF;;;ACnEO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAOvB,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBnB,IAAM,qBAAqB;AAAA,EAChC,qCAAqC,CAAC,gBAAgB,kBAAkB;AAAA,EACxE,wBAAwB,CAAC,gBAAgB,kBAAkB;AAAA,EAC3D,2CAA2C,CAAC,kBAAkB;AAAA,EAC9D,QAAQ,CAAC,2BAA2B,kBAAkB;AACxD;;;ACdO,SAAS,gBAAgB,SAA2C;AACzE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQP,QAAQ,aAAa,6EAA6E,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAmG1E,QAAQ,aAAa,sCAAsC,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOzF;AAEO,IAAM,iBAAiB;AAAA,EAC5B,cAAc;AAAA,IACZ;AAAA,MACE,OAAO;AAAA,QACL;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,OAAO;AAAA,QACL;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,OAAO;AAAA,QACL;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,kBAAkB;AAAA,IAChB;AAAA,MACE,OAAO;AAAA,QACL;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,OAAO;AAAA,QACL;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM;AAAA,IACJ;AAAA,MACE,OAAO;AAAA,QACL;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX;AAAA,MACE,SAAS;AAAA,MACT,OAAO;AAAA,QACL;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACvMA,SAAS,QAAAA,aAAY;AAOrB,IAAM,oBAAoB;AAAA,EACxB;AAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AACF;AAQA,IAAM,YAAwC;AAAA,EAC5C,OAAO,CAAC;AAAA;AAAA,EACR,OAAO,CAAC,OAAO;AAAA,EACf,KAAK,CAAC,SAAS,OAAO;AAAA,EACtB,OAAO,CAAC,OAAO,SAAS,OAAO;AAAA,EAC/B,UAAU,CAAC,OAAO,SAAS,OAAO;AAAA,EAClC,YAAY,CAAC,SAAS,YAAY,OAAO,SAAS,OAAO;AAAA,EACzD,UAAU,CAAC,cAAc,SAAS,YAAY,OAAO,SAAS,OAAO;AAAA,EACrE,SAAS,CAAC,cAAc,SAAS,YAAY,OAAO,SAAS,OAAO;AAAA,EACpE,KAAK,CAAC,YAAY,WAAW,cAAc,SAAS,YAAY,OAAO,SAAS,OAAO;AACzF;AAWO,SAAS,mBAAmB,YAA0C;AAC3E,QAAM,aAAwB,CAAC;AAC/B,QAAM,cAAyB,CAAC;AAEhC,aAAW,OAAO,mBAAmB;AACnC,QAAI,OAAOC,MAAK,YAAY,OAAO,GAAG,CAAC,GAAG;AACxC,iBAAW,KAAK,GAAG;AAAA,IACrB;AACA,QAAI,OAAOA,MAAK,YAAY,GAAG,CAAC,GAAG;AACjC,kBAAY,KAAK,GAAG;AAAA,IACtB;AAAA,EACF;AAGA,QAAM,QAAQ,WAAW,UAAU,YAAY;AAC/C,QAAM,QAAQ,QAAQ,aAAa;AAEnC,SAAO,EAAE,aAAa,OAAO,MAAM;AACrC;AAKO,SAAS,yBAAyB,MAAoC;AAC3E,QAAM,SAAS,KAAK,QAAQ,SAAS;AACrC,QAAM,iBAAiB,KAAK,YAAY,SAAS;AAGjD,QAAM,WAAW,KAAK,YACnB,IAAI,SAAO,kBAAkB,GAAG,gBAAgB,MAAM,GAAG,GAAG,sBAAsB,EAClF,KAAK,KAAK;AAGb,QAAM,QAAQ,KAAK,YAChB,OAAO,SAAO,UAAU,GAAG,EAAE,SAAS,CAAC,EACvC,IAAI,SAAO;AACV,UAAM,UAAU,UAAU,GAAG,EAAE,OAAO,SAAO,KAAK,YAAY,SAAS,GAAG,CAAC;AAC3E,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,WAAO,qBAAqB,GAAG,eAAe,QAAQ,IAAI,OAAK,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,EACrF,CAAC,EACA,OAAO,OAAO,EACd,KAAK,KAAK;AAEb,QAAM,eAAe,iBACjB,yBAAyB,KAAK,YAAY,KAAK,IAAI,CAAC,KAAK,KAAK,QAAQ,YAAY,SAAS,MAC3F;AAGJ,QAAM,kBAAkB,YAAY;AACpC,QAAM,eAAe,SAAS;AAE9B,SAAO;AAAA;AAAA;AAAA,KAGJ,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBf,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOf,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQd;;;AClIO,IAAM,2BAA2B;AAKjC,IAAM,cAAc;AAAA,EACzB,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM,CAAC,MAAM,8BAA8B;AAAA,EAC7C;AAAA,EACA,YAAY;AAAA,IACV,SAAS;AAAA,IACT,MAAM,CAAC,wBAAwB;AAAA,EACjC;AACF;;;ACRO,SAAS,YAAY,GAA4B;AACtD,SACE,OAAO,MAAM,YACb,MAAM,QACN,WAAW,KACX,MAAM,QAAS,EAAgB,KAAK;AAExC;AAKO,SAAS,eAAe,GAAqB;AAClD,MAAI,CAAC,YAAY,CAAC,EAAG,QAAO;AAC5B,SAAO,EAAE,MAAM;AAAA,IACb,CAAC,QAAQ,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,SAAS,WAAW;AAAA,EAC9E;AACF;AAKO,SAAS,uBAAuB,OAA6B;AAClE,SAAO,MAAM,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;AAC/C;;;AC+BO,IAAM,kBAAkC;AAAA,EAC7C,SAAS;AAAA;AAAA,EAGT,WAAW;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA;AAAA,EAGA,YAAY,CAAC,WAAW,kBAAkB,kBAAkB;AAAA;AAAA,EAG5D,eAAe,CAAC,uBAAuB,qBAAqB,6BAA6B;AAAA;AAAA,EAGzF,YAAY;AAAA;AAAA,IAEV,yBAAyB,EAAE,UAAU,cAAc;AAAA,IACnD,qBAAqB,EAAE,SAAS,MAAM,QAAQ;AAAA,IAC9C,0CAA0C;AAAA,MACxC,WAAW,SAAO,yBAAyB,mBAAmB,IAAI,GAAG,CAAC;AAAA,IACxE;AAAA;AAAA,IAGA,4CAA4C,EAAE,UAAU,iCAAiC;AAAA,IACzF,sCAAsC,EAAE,UAAU,2BAA2B;AAAA,IAC7E,yCAAyC,EAAE,UAAU,8BAA8B;AAAA,IACnF,uCAAuC,EAAE,UAAU,4BAA4B;AAAA,IAC/E,uCAAuC,EAAE,UAAU,4BAA4B;AAAA,IAC/E,qCAAqC,EAAE,UAAU,0BAA0B;AAAA,IAC3E,mCAAmC,EAAE,UAAU,wBAAwB;AAAA;AAAA,IAGvE,2BAA2B,EAAE,UAAU,gBAAgB;AAAA,IACvD,gCAAgC,EAAE,UAAU,qBAAqB;AAAA;AAAA,IAGjE,0CAA0C,EAAE,UAAU,+BAA+B;AAAA,IACrF,uCAAuC,EAAE,UAAU,4BAA4B;AAAA,IAC/E,2CAA2C,EAAE,UAAU,gCAAgC;AAAA,IACvF,+CAA+C;AAAA,MAC7C,UAAU;AAAA,IACZ;AAAA,IACA,wCAAwC,EAAE,UAAU,6BAA6B;AAAA,IACjF,2CAA2C,EAAE,UAAU,gCAAgC;AAAA,IACvF,8CAA8C,EAAE,UAAU,mCAAmC;AAAA,IAC7F,qCAAqC,EAAE,UAAU,0BAA0B;AAAA,IAC3E,0CAA0C,EAAE,UAAU,+BAA+B;AAAA,IACrF,8CAA8C,EAAE,UAAU,mCAAmC;AAAA,IAC7F,2CAA2C,EAAE,UAAU,gCAAgC;AAAA,IACvF,wCAAwC,EAAE,UAAU,6BAA6B;AAAA,IACjF,8CAA8C,EAAE,UAAU,mCAAmC;AAAA;AAAA,IAG7F,gDAAgD;AAAA,MAC9C,UAAU;AAAA,IACZ;AAAA,IACA,8CAA8C;AAAA,MAC5C,UAAU;AAAA,IACZ;AAAA,IACA,mDAAmD;AAAA,MACjD,UAAU;AAAA,IACZ;AAAA,IACA,0CAA0C,EAAE,UAAU,mCAAmC;AAAA,IACzF,gDAAgD;AAAA,MAC9C,UAAU;AAAA,IACZ;AAAA;AAAA,IAGA,qCAAqC,EAAE,UAAU,0BAA0B;AAAA,IAC3E,uCAAuC,EAAE,UAAU,4BAA4B;AAAA;AAAA,IAG/E,qDAAqD;AAAA,MACnD,UAAU;AAAA,IACZ;AAAA,IACA,oCAAoC,EAAE,UAAU,2BAA2B;AAAA,IAC3E,4BAA4B,EAAE,UAAU,mBAAmB;AAAA,IAC3D,sCAAsC,EAAE,UAAU,6BAA6B;AAAA;AAAA,IAG/E,qBAAqB,EAAE,SAAS,yBAAyB;AAAA,EAC3D;AAAA;AAAA,EAGA,cAAc;AAAA,IACZ,qBAAqB;AAAA,MACnB,WAAW,MAAM,gBAAgB,EAAE,YAAY,KAAK,CAAC;AAAA,IACvD;AAAA,IACA,eAAe,EAAE,SAAS,WAAW;AAAA,IACrC,4BAA4B,EAAE,UAAU,0BAA0B;AAAA,EACpE;AAAA;AAAA,EAGA,YAAY;AAAA,IACV,gBAAgB;AAAA,MACd,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,iBAAiB;AAAA,QACf,oBAAoB,CAAC,iBAAiB;AAAA,MACxC;AAAA,MACA,OAAO,CAAC,UAAU,QAAQ;AACxB,cAAM,UAAW,SAAS,WAAsC,CAAC;AACjE,cAAM,SAAS,EAAE,GAAG,SAAS;AAG7B,YAAI,CAAC,QAAQ,KAAM,SAAQ,OAAO;AAClC,YAAI,CAAC,QAAQ,SAAS,EAAG,SAAQ,SAAS,IAAI;AAC9C,YAAI,CAAC,QAAQ,OAAQ,SAAQ,SAAS;AACtC,YAAI,CAAC,QAAQ,cAAc,EAAG,SAAQ,cAAc,IAAI;AACxD,YAAI,CAAC,QAAQ,KAAM,SAAQ,OAAO;AAClC,YAAI,CAAC,QAAQ,QAAS,SAAQ,UAAU;AAGxC,YAAI,IAAI,YAAY,sBAAsB,CAAC,QAAQ,SAAS;AAC1D,kBAAQ,UAAU;AAAA,QACpB;AAEA,eAAO,UAAU;AAGjB,YAAI,CAAC,SAAS,aAAa,GAAG;AAC5B,iBAAO,aAAa,IAAI;AAAA,QAC1B;AAEA,eAAO;AAAA,MACT;AAAA,MACA,SAAS,cAAY;AACnB,cAAM,SAAS,EAAE,GAAG,SAAS;AAC7B,cAAM,UAAU,EAAE,GAAK,SAAS,WAAsC,CAAC,EAAG;AAG1E,eAAO,QAAQ,SAAS;AACxB,eAAO,QAAQ,cAAc;AAC7B,eAAO,QAAQ;AACf,eAAO,QAAQ;AACf,eAAO,QAAQ;AAEf,YAAI,OAAO,KAAK,OAAO,EAAE,SAAS,GAAG;AACnC,iBAAO,UAAU;AAAA,QACnB,OAAO;AACL,iBAAO,OAAO;AAAA,QAChB;AAEA,eAAO,OAAO,aAAa;AAE3B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,yBAAyB;AAAA,MACvB,MAAM,CAAC,OAAO;AAAA,MACd,OAAO,cAAY;AAEjB,cAAM,gBAAiB,SAAS,SAAuC,CAAC;AACxE,cAAM,cAAyC,EAAE,GAAG,cAAc;AAElE,mBAAW,CAAC,OAAO,QAAQ,KAAK,OAAO,QAAQ,cAAc,GAAG;AAC9D,gBAAM,aAAc,YAAY,KAAK,KAAmB,CAAC;AACzD,gBAAM,mBAAmB,uBAAuB,UAAU;AAC1D,sBAAY,KAAK,IAAI,CAAC,GAAG,kBAAkB,GAAG,QAAQ;AAAA,QACxD;AAEA,eAAO,EAAE,GAAG,UAAU,OAAO,YAAY;AAAA,MAC3C;AAAA,MACA,SAAS,cAAY;AAEnB,cAAM,gBAAiB,SAAS,SAAuC,CAAC;AACxE,cAAM,eAA0C,CAAC;AAEjD,mBAAW,CAAC,OAAO,UAAU,KAAK,OAAO,QAAQ,aAAa,GAAG;AAC/D,gBAAM,mBAAmB,uBAAuB,UAAuB;AACvE,cAAI,iBAAiB,SAAS,GAAG;AAC/B,yBAAa,KAAK,IAAI;AAAA,UACxB;AAAA,QACF;AAEA,cAAM,SAAS,EAAE,GAAG,SAAS;AAC7B,YAAI,OAAO,KAAK,YAAY,EAAE,SAAS,GAAG;AACxC,iBAAO,QAAQ;AAAA,QACjB,OAAO;AACL,iBAAO,OAAO;AAAA,QAChB;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,aAAa;AAAA,MACX,MAAM,CAAC,uBAAuB,uBAAuB;AAAA,MACrD,mBAAmB;AAAA,MACnB,OAAO,cAAY;AACjB,cAAM,aAAc,SAAS,cAA0C,CAAC;AACxE,eAAO;AAAA,UACL,GAAG;AAAA,UACH,YAAY;AAAA,YACV,GAAG;AAAA,YACH,UAAU,YAAY;AAAA,YACtB,YAAY,YAAY;AAAA,UAC1B;AAAA,QACF;AAAA,MACF;AAAA,MACA,SAAS,cAAY;AACnB,cAAM,SAAS,EAAE,GAAG,SAAS;AAC7B,cAAM,aAAa,EAAE,GAAK,SAAS,cAA0C,CAAC,EAAG;AAEjF,eAAO,WAAW;AAClB,eAAO,WAAW;AAElB,YAAI,OAAO,KAAK,UAAU,EAAE,SAAS,GAAG;AACtC,iBAAO,aAAa;AAAA,QACtB,OAAO;AACL,iBAAO,OAAO;AAAA,QAChB;AAEA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,aAAa;AAAA,IACX,aAAa;AAAA,MACX,WAAW;AAAA,MACX,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,iBAAiB;AAAA,IACnB;AAAA,IACA,aAAa;AAAA,MACX,WAAW;AAAA,MACX,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,iBAAiB;AAAA;AAAA,IACnB;AAAA,EACF;AAAA;AAAA,EAGA,UAAU;AAAA,IACR,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,YAAY,CAAC,mBAAmB;AAAA,MAChC,OAAO,CAAC,uBAAuB,6BAA6B,wBAAwB;AAAA,MACpF,QAAQ,CAAC,0BAA0B;AAAA,MACnC,OAAO,CAAC,qBAAqB;AAAA,MAC7B,KAAK,CAAC,mBAAmB;AAAA,MACzB,QAAQ,CAAC,sBAAsB;AAAA,MAC/B,UAAU,CAAC,iCAAiC;AAAA,MAC5C,QAAQ,CAAC,uBAAuB;AAAA,MAChC,UAAU,CAAC,6BAA6B;AAAA,MACxC,oBAAoB,CAAC,SAAS;AAAA,IAChC;AAAA,EACF;AACF;","names":["join","join"]}