safeword 0.6.9 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/{check-OYYSYHFP.js → check-QGZJ62PY.js} +73 -57
  2. package/dist/check-QGZJ62PY.js.map +1 -0
  3. package/dist/{chunk-ZS3Z3Q37.js → chunk-QPO3C3FP.js} +285 -65
  4. package/dist/chunk-QPO3C3FP.js.map +1 -0
  5. package/dist/{chunk-LNSEDZIW.js → chunk-YMLVQC4V.js} +159 -152
  6. package/dist/chunk-YMLVQC4V.js.map +1 -0
  7. package/dist/cli.js +6 -6
  8. package/dist/{diff-325TIZ63.js → diff-654SSCFQ.js} +51 -53
  9. package/dist/diff-654SSCFQ.js.map +1 -0
  10. package/dist/index.d.ts +1 -0
  11. package/dist/{reset-ZGJIKMUW.js → reset-IU6AIT7C.js} +3 -3
  12. package/dist/{setup-GAMXTFM2.js → setup-5IQ4KV2M.js} +17 -20
  13. package/dist/setup-5IQ4KV2M.js.map +1 -0
  14. package/dist/{sync-BFMXZEHM.js → sync-YBQEISFI.js} +8 -26
  15. package/dist/sync-YBQEISFI.js.map +1 -0
  16. package/dist/{upgrade-X4GREJXN.js → upgrade-RRTWEQIP.js} +3 -3
  17. package/package.json +1 -1
  18. package/templates/SAFEWORD.md +52 -15
  19. package/templates/commands/architecture.md +1 -1
  20. package/templates/commands/lint.md +1 -0
  21. package/templates/commands/quality-review.md +1 -1
  22. package/templates/cursor/rules/safeword-core.mdc +5 -0
  23. package/templates/doc-templates/architecture-template.md +1 -1
  24. package/templates/doc-templates/task-spec-template.md +151 -0
  25. package/templates/doc-templates/ticket-template.md +2 -4
  26. package/templates/guides/architecture-guide.md +2 -2
  27. package/templates/guides/code-philosophy.md +1 -1
  28. package/templates/guides/context-files-guide.md +3 -3
  29. package/templates/guides/design-doc-guide.md +2 -2
  30. package/templates/guides/development-workflow.md +2 -2
  31. package/templates/guides/learning-extraction.md +9 -9
  32. package/templates/guides/tdd-best-practices.md +39 -38
  33. package/templates/guides/test-definitions-guide.md +15 -14
  34. package/templates/hooks/cursor/after-file-edit.sh +66 -0
  35. package/templates/hooks/cursor/stop.sh +50 -0
  36. package/templates/hooks/post-tool-lint.sh +19 -5
  37. package/templates/hooks/prompt-questions.sh +1 -1
  38. package/templates/hooks/session-lint-check.sh +1 -1
  39. package/templates/hooks/session-verify-agents.sh +1 -1
  40. package/templates/hooks/session-version.sh +1 -1
  41. package/templates/hooks/stop-quality.sh +1 -1
  42. package/templates/markdownlint-cli2.jsonc +18 -19
  43. package/templates/scripts/bisect-test-pollution.sh +87 -0
  44. package/templates/scripts/bisect-zombie-processes.sh +129 -0
  45. package/templates/scripts/lint-md.sh +16 -0
  46. package/templates/skills/safeword-quality-reviewer/SKILL.md +3 -3
  47. package/templates/skills/safeword-systematic-debugger/SKILL.md +246 -0
  48. package/templates/skills/safeword-tdd-enforcer/SKILL.md +221 -0
  49. package/dist/check-OYYSYHFP.js.map +0 -1
  50. package/dist/chunk-LNSEDZIW.js.map +0 -1
  51. package/dist/chunk-ZS3Z3Q37.js.map +0 -1
  52. package/dist/diff-325TIZ63.js.map +0 -1
  53. package/dist/setup-GAMXTFM2.js.map +0 -1
  54. package/dist/sync-BFMXZEHM.js.map +0 -1
  55. /package/dist/{reset-ZGJIKMUW.js.map → reset-IU6AIT7C.js.map} +0 -0
  56. /package/dist/{upgrade-X4GREJXN.js.map → upgrade-RRTWEQIP.js.map} +0 -0
@@ -0,0 +1,246 @@
1
+ ---
2
+ name: systematic-debugger
3
+ description: Four-phase debugging framework that ensures root cause identification before fixes. Use when encountering bugs, test failures, unexpected behavior, or when previous fix attempts failed. Enforces investigate-first discipline ('debug this', 'fix this error', 'test is failing', 'not working').
4
+ allowed-tools: '*'
5
+ ---
6
+
7
+ # Systematic Debugger
8
+
9
+ Find root cause before fixing. Symptom fixes are failure.
10
+
11
+ **Iron Law:** NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
12
+
13
+ ## When to Use
14
+
15
+ Answer IN ORDER. Stop at first match:
16
+
17
+ 1. Bug, error, or test failure? → Use this skill
18
+ 2. Unexpected behavior? → Use this skill
19
+ 3. Previous fix didn't work? → Use this skill (especially important)
20
+ 4. Performance problem? → Use this skill
21
+ 5. None of above? → Skip this skill
22
+
23
+ **Use especially when:**
24
+
25
+ - Under time pressure (emergencies make guessing tempting)
26
+ - "Quick fix" seems obvious (red flag)
27
+ - Already tried 1+ fixes that didn't work
28
+
29
+ ## The Four Phases
30
+
31
+ Complete each phase before proceeding.
32
+
33
+ ### Phase 1: Root Cause Investigation
34
+
35
+ **BEFORE attempting ANY fix:**
36
+
37
+ **1. Read Error Messages Completely**
38
+
39
+ ```text
40
+ Don't skip past errors. They often contain the exact solution.
41
+ - Full stack trace (note line numbers, file paths)
42
+ - Error codes and messages
43
+ - Warnings that preceded the error
44
+ ```
45
+
46
+ **2. Reproduce Consistently**
47
+
48
+ | Can reproduce? | Action |
49
+ | --------------- | ---------------------------------------------------- |
50
+ | Yes, every time | Proceed to step 3 |
51
+ | Sometimes | Gather more data - when does it happen vs not? |
52
+ | Never | Cannot debug what you cannot reproduce - gather logs |
53
+
54
+ **3. Check Recent Changes**
55
+
56
+ ```bash
57
+ git diff HEAD~5 # Recent code changes
58
+ git log --oneline -10 # Recent commits
59
+ ```
60
+
61
+ What changed that could cause this? Dependencies? Config? Environment?
62
+
63
+ **4. Trace Data Flow (Root Cause Tracing)**
64
+
65
+ When error is deep in call stack:
66
+
67
+ ```text
68
+ Symptom: Error at line 50 in utils.js
69
+ ↑ Called by handler.js:120
70
+ ↑ Called by router.js:45
71
+ ↑ Called by app.js:10 ← ROOT CAUSE: bad input here
72
+ ```
73
+
74
+ **Technique:**
75
+
76
+ 1. Find where error occurs (symptom)
77
+ 2. Ask: "What called this with bad data?"
78
+ 3. Trace up until you find the SOURCE
79
+ 4. Fix at source, not at symptom
80
+
81
+ **5. Multi-Component Systems**
82
+
83
+ When system has multiple layers (API → service → database):
84
+
85
+ ```bash
86
+ # Log at EACH boundary before proposing fixes
87
+ echo "=== Layer 1 (API): request=$REQUEST ==="
88
+ echo "=== Layer 2 (Service): input=$INPUT ==="
89
+ echo "=== Layer 3 (DB): query=$QUERY ==="
90
+ ```
91
+
92
+ Run once to find WHERE it breaks. Then investigate that layer.
93
+
94
+ ### Phase 2: Pattern Analysis
95
+
96
+ **1. Find Working Examples**
97
+
98
+ Locate similar working code in same codebase. What works that's similar?
99
+
100
+ **2. Identify Differences**
101
+
102
+ | Working code | Broken code | Could this matter? |
103
+ | ---------------- | -------------- | ------------------ |
104
+ | Uses async/await | Uses callbacks | Yes - timing |
105
+ | Validates input | No validation | Yes - bad data |
106
+
107
+ List ALL differences. Don't assume "that can't matter."
108
+
109
+ ### Phase 3: Hypothesis Testing
110
+
111
+ **1. Form Single Hypothesis**
112
+
113
+ Write it down: "I think X is the root cause because Y"
114
+
115
+ Be specific:
116
+
117
+ - ❌ "Something's wrong with the database"
118
+ - ✅ "Connection pool exhausted because connections aren't released in error path"
119
+
120
+ **2. Test Minimally**
121
+
122
+ | Rule | Why |
123
+ | ------------------------ | ---------------------- |
124
+ | ONE change at a time | Isolate what works |
125
+ | Smallest possible change | Avoid side effects |
126
+ | Don't bundle fixes | Can't tell what helped |
127
+
128
+ **3. Evaluate Result**
129
+
130
+ | Result | Action |
131
+ | --------------- | --------------------------------------- |
132
+ | Fixed | Phase 4 (verify) |
133
+ | Not fixed | NEW hypothesis (return to 3.1) |
134
+ | Partially fixed | Found one issue, continue investigating |
135
+
136
+ ### Phase 4: Implementation
137
+
138
+ **1. Create Failing Test**
139
+
140
+ Before fixing, write test that fails due to the bug:
141
+
142
+ ```javascript
143
+ it('handles empty input without crashing', () => {
144
+ // This test should FAIL before fix, PASS after
145
+ expect(() => processData('')).not.toThrow();
146
+ });
147
+ ```
148
+
149
+ **2. Implement Fix**
150
+
151
+ - Address ROOT CAUSE identified in Phase 1
152
+ - ONE change
153
+ - No "while I'm here" improvements
154
+
155
+ **3. Verify**
156
+
157
+ - [ ] New test passes
158
+ - [ ] Existing tests still pass
159
+ - [ ] Issue actually resolved (not just test passing)
160
+
161
+ **4. If Fix Doesn't Work**
162
+
163
+ | Fix attempts | Action |
164
+ | ------------ | ---------------------------------------- |
165
+ | 1-2 | Return to Phase 1 with new information |
166
+ | 3+ | STOP - Question architecture (see below) |
167
+
168
+ **5. After 3+ Failed Fixes: Question Architecture**
169
+
170
+ Pattern indicating architectural problem:
171
+
172
+ - Each fix reveals new coupling/shared state
173
+ - Fixes require "massive refactoring"
174
+ - Each fix creates new symptoms elsewhere
175
+
176
+ **STOP and ask:**
177
+
178
+ - Is this pattern fundamentally sound?
179
+ - Should we refactor vs. continue patching?
180
+ - Discuss with user before more fix attempts
181
+
182
+ ## Red Flags - STOP Immediately
183
+
184
+ If you catch yourself thinking:
185
+
186
+ | Thought | Reality |
187
+ | ---------------------------------------------- | --------------------------------- |
188
+ | "Quick fix for now, investigate later" | Investigate NOW or you never will |
189
+ | "Just try changing X" | That's guessing, not debugging |
190
+ | "I'll add multiple fixes and test" | Can't isolate what worked |
191
+ | "I don't fully understand but this might work" | You need to understand first |
192
+ | "One more fix attempt" (after 2+ failures) | 3+ failures = wrong approach |
193
+
194
+ **ALL mean: STOP. Return to Phase 1.**
195
+
196
+ ## Finding Test Pollution
197
+
198
+ When tests pass individually but fail together (test isolation problem, tests affect each other, tests leave files behind), use bisection:
199
+
200
+ ```bash
201
+ ./.safeword/scripts/bisect-test-pollution.sh '.git' '*.test.ts' src
202
+ ```
203
+
204
+ See: @./.safeword/scripts/bisect-test-pollution.sh
205
+
206
+ ## Debug Logging
207
+
208
+ When adding diagnostic logging:
209
+
210
+ ```javascript
211
+ // ❌ BAD
212
+ console.log('here');
213
+ console.log(data);
214
+
215
+ // ✅ GOOD
216
+ console.log('validateUser', { expected: 'admin', actual: user.role });
217
+ console.log('processOrder', JSON.stringify({ input, output }, null, 2));
218
+ ```
219
+
220
+ Log **expected vs actual**. Remove after fixing.
221
+
222
+ ## Quick Reference
223
+
224
+ | Phase | Key Question | Success Criteria |
225
+ | ----------------- | ------------------------------------- | ---------------------------------- |
226
+ | 1. Root Cause | "WHY is this happening?" | Understand cause, not just symptom |
227
+ | 2. Pattern | "What's different from working code?" | Identified key differences |
228
+ | 3. Hypothesis | "Is my theory correct?" | Confirmed or formed new theory |
229
+ | 4. Implementation | "Does the fix work?" | Test passes, issue resolved |
230
+
231
+ ## Finding Zombie Process Spawners
232
+
233
+ When tests leave processes behind (playwright browsers not cleaned up, port stays in use, zombie node processes, chromium accumulating), use bisection to find the culprit:
234
+
235
+ ```bash
236
+ ./.safeword/scripts/bisect-zombie-processes.sh 'chromium' '*.test.ts' tests
237
+ ./.safeword/scripts/bisect-zombie-processes.sh 'playwright' '*.spec.ts' e2e
238
+ ```
239
+
240
+ See: @./.safeword/scripts/bisect-zombie-processes.sh
241
+
242
+ ## Related Resources
243
+
244
+ - Process cleanup guide: @./.safeword/guides/zombie-process-cleanup.md
245
+ - Debug logging style: @./.safeword/guides/code-philosophy.md
246
+ - TDD for fix verification: @./.safeword/guides/tdd-best-practices.md
@@ -0,0 +1,221 @@
1
+ ---
2
+ name: tdd-enforcer
3
+ description: Use when implementing features, fixing bugs, or making code changes. Ensures scope is defined before coding, then enforces RED → GREEN → REFACTOR test discipline. Triggers: 'implement', 'add', 'build', 'create', 'fix', 'change', 'feature', 'bug'.
4
+ allowed-tools: '*'
5
+ ---
6
+
7
+ # TDD Enforcer
8
+
9
+ Scope work before coding. Write tests before implementation.
10
+
11
+ **Iron Law:** NO IMPLEMENTATION UNTIL SCOPE IS DEFINED AND TEST FAILS
12
+
13
+ ## When to Use
14
+
15
+ Answer IN ORDER. Stop at first match:
16
+
17
+ 1. Implementing new feature? → Use this skill
18
+ 2. Fixing bug? → Use this skill
19
+ 3. Adding enhancement? → Use this skill
20
+ 4. Refactoring? → Use this skill
21
+ 5. Research/investigation only? → Skip this skill
22
+
23
+ ---
24
+
25
+ ## Phase 0: TRIAGE
26
+
27
+ **Purpose:** Determine work level and ensure scope exists.
28
+
29
+ ### Step 1: Identify Level
30
+
31
+ Answer IN ORDER. Stop at first match:
32
+
33
+ | Question | If Yes → |
34
+ | ---------------------------------------- | -------------- |
35
+ | User-facing feature with business value? | **L2 Feature** |
36
+ | Bug, improvement, internal, or refactor? | **L1 Task** |
37
+ | Typo, config, or trivial change? | **L0 Micro** |
38
+
39
+ ### Step 2: Check/Create Artifacts
40
+
41
+ | Level | Required Artifacts | Test Location |
42
+ | ------ | --------------------------------------------------------------- | ------------------------------- |
43
+ | **L2** | Feature Spec + Test Definitions (+ Design Doc if 3+ components) | `test-definitions/feature-*.md` |
44
+ | **L1** | Task Spec | Inline in spec |
45
+ | **L0** | Task Spec (minimal) | Existing tests |
46
+
47
+ **Locations:**
48
+
49
+ - Specs: `.safeword/planning/specs/`
50
+ - Test definitions: `.safeword/planning/test-definitions/`
51
+
52
+ **Templates:**
53
+
54
+ - L2 Feature: @./.safeword/templates/user-stories-template.md
55
+ - L1/L0 Task: @./.safeword/templates/task-spec-template.md
56
+ - Test Definitions: @./.safeword/templates/test-definitions-feature.md
57
+
58
+ ### Exit Criteria
59
+
60
+ - [ ] Level identified (L0/L1/L2)
61
+ - [ ] Spec exists with "Out of Scope" defined
62
+ - [ ] L2: Test definitions file exists
63
+ - [ ] L1: Test scenarios in spec
64
+ - [ ] L0: Existing test coverage confirmed
65
+
66
+ ---
67
+
68
+ ## Phase 1: RED
69
+
70
+ **Iron Law:** NO IMPLEMENTATION UNTIL TEST FAILS FOR THE RIGHT REASON
71
+
72
+ **Protocol:**
73
+
74
+ 1. Pick ONE test from spec (L1) or test definitions (L2)
75
+ 2. Write test code
76
+ 3. Run test
77
+ 4. Verify: fails because behavior missing (not syntax error)
78
+ 5. Commit: `test: [behavior]`
79
+
80
+ **For L0:** No new test needed. Confirm existing tests pass, then proceed to Phase 2.
81
+
82
+ **Exit Criteria:**
83
+
84
+ - [ ] Test written and executed
85
+ - [ ] Test fails for RIGHT reason (behavior missing)
86
+ - [ ] Committed: `test: [behavior]`
87
+
88
+ **Red Flags → STOP:**
89
+
90
+ | Flag | Action |
91
+ | ----------------------- | -------------------------------- |
92
+ | Test passes immediately | Rewrite - you're testing nothing |
93
+ | Syntax error | Fix syntax, not behavior |
94
+ | Wrote implementation | Delete it, return to test |
95
+ | Multiple tests | Pick ONE |
96
+
97
+ ---
98
+
99
+ ## Phase 2: GREEN
100
+
101
+ **Iron Law:** ONLY WRITE CODE THE TEST REQUIRES
102
+
103
+ **Protocol:**
104
+
105
+ 1. Write minimal code to pass test
106
+ 2. Run test → verify pass
107
+ 3. Commit: `feat:` or `fix:`
108
+
109
+ **Exit Criteria:**
110
+
111
+ - [ ] Test passes
112
+ - [ ] No extra code
113
+ - [ ] No hardcoded/mock values
114
+ - [ ] Committed
115
+
116
+ **Red Flags → STOP:**
117
+
118
+ | Flag | Action |
119
+ | ------------------- | -------------------------------------- |
120
+ | "Just in case" code | Delete it |
121
+ | Multiple functions | Delete extras |
122
+ | Refactoring | Stop - that's Phase 3 |
123
+ | Test still fails | Debug (→ systematic-debugger if stuck) |
124
+ | Hardcoded value | Implement real logic (see below) |
125
+
126
+ ### Anti-Pattern: Mock Implementations
127
+
128
+ LLMs sometimes hardcode values to pass tests. This is not TDD.
129
+
130
+ ```typescript
131
+ // ❌ BAD - Hardcoded to pass test
132
+ function calculateDiscount(amount, tier) {
133
+ return 80; // Passes test but isn't real
134
+ }
135
+
136
+ // ✅ GOOD - Actual logic
137
+ function calculateDiscount(amount, tier) {
138
+ if (tier === 'VIP') return amount * 0.8;
139
+ return amount;
140
+ }
141
+ ```
142
+
143
+ Fix mocks immediately. The next test cycle will catch them, but they're technical debt.
144
+
145
+ ---
146
+
147
+ ## Phase 3: REFACTOR
148
+
149
+ **Protocol:**
150
+
151
+ 1. Tests pass before changes
152
+ 2. Improve code (rename, extract, dedupe)
153
+ 3. Tests pass after changes
154
+ 4. Commit if changed: `refactor: [improvement]`
155
+
156
+ **Exit Criteria:**
157
+
158
+ - [ ] Tests still pass
159
+ - [ ] Code cleaner (or no changes needed)
160
+ - [ ] Committed (if changed)
161
+
162
+ **NOT Allowed:** New behavior, changing assertions, adding tests.
163
+
164
+ ---
165
+
166
+ ## Phase 4: ITERATE
167
+
168
+ ```text
169
+ More tests in spec/test-definitions?
170
+ ├─ Yes → Return to Phase 1
171
+ └─ No → All "Done When" / AC checked?
172
+ ├─ Yes → Complete
173
+ └─ No → Update spec, return to Phase 0
174
+ ```
175
+
176
+ For L2: Update test definition status (✅/⏭️/❌/🔴) as tests pass.
177
+
178
+ ---
179
+
180
+ ## Quick Reference
181
+
182
+ | Phase | Key Question | Gate |
183
+ | ----------- | -------------------------------- | ----------------------------- |
184
+ | 0. TRIAGE | What level? Is scope defined? | Spec exists with boundaries |
185
+ | 1. RED | Does test fail for right reason? | Test fails (behavior missing) |
186
+ | 2. GREEN | Does minimal code pass? | Test passes, no extras |
187
+ | 3. REFACTOR | Is code clean? | Tests still pass |
188
+ | 4. ITERATE | More tests? | All done → complete |
189
+
190
+ ---
191
+
192
+ ## Examples
193
+
194
+ **L2 Feature** ("Add VIP discount"):
195
+ Phase 0: L2 → create spec + test defs → Phase 1: write test → FAIL → commit → Phase 2: implement → PASS → commit → Phase 3: clean up → Phase 4: more tests? → repeat
196
+
197
+ **L1 Bug** ("Fix login timeout"):
198
+ Phase 0: L1 → create task spec → Phase 1: write failing test → commit → Phase 2: fix → PASS → commit → Phase 3: clean up if needed → Phase 4: done
199
+
200
+ **L0 Micro** ("Fix typo"):
201
+ Phase 0: L0 → create minimal spec → Phase 1: no new test (existing tests cover) → Phase 2: fix typo → tests PASS → commit → done
202
+
203
+ **Why L0 needs a spec:** "Fix typo" can become "refactor error handling" without explicit "Out of Scope".
204
+
205
+ ---
206
+
207
+ ## Integration
208
+
209
+ | Scenario | Handoff |
210
+ | ----------------------- | --------------------- |
211
+ | Test fails unexpectedly | → systematic-debugger |
212
+ | Review needed | → quality-reviewer |
213
+ | Scope expanding | → Update spec first |
214
+
215
+ ---
216
+
217
+ ## Related
218
+
219
+ - @./.safeword/guides/test-definitions-guide.md
220
+ - @./.safeword/guides/tdd-best-practices.md
221
+ - @./.safeword/guides/development-workflow.md
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n *\n * Uses reconcile() with dryRun to detect missing files and configuration issues.\n */\n\nimport { join } from 'node:path';\nimport { VERSION } from '../version.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { info, success, warn, header, keyValue } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { reconcile } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | null;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | null;\n issues: string[];\n missingPackages: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch('https://registry.npmjs.org/safeword/latest', {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) return null;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check project configuration health using reconcile dryRun\n */\nasync function checkHealth(cwd: string): Promise<HealthStatus> {\n const safewordDir = join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDir)) {\n return {\n configured: false,\n projectVersion: null,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues: [],\n missingPackages: [],\n };\n }\n\n // Read project version\n const versionPath = join(safewordDir, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? null;\n\n // Use reconcile with dryRun to detect issues\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx, { dryRun: true });\n\n const issues: string[] = [];\n\n // Check for missing owned files (write actions indicate missing/changed files)\n const writeActions = result.actions.filter(a => a.type === 'write');\n for (const action of writeActions) {\n if (action.type === 'write') {\n const fullPath = join(cwd, action.path);\n if (!exists(fullPath)) {\n issues.push(`Missing: ${action.path}`);\n }\n }\n }\n\n // Check for missing text patches (e.g., AGENTS.md link)\n const textPatchActions = result.actions.filter(a => a.type === 'text-patch');\n for (const action of textPatchActions) {\n if (action.type === 'text-patch') {\n const fullPath = join(cwd, action.path);\n if (!exists(fullPath)) {\n issues.push(`${action.path} file missing`);\n } else {\n const content = readFileSafe(fullPath) ?? '';\n if (!content.includes(action.definition.marker)) {\n issues.push(`${action.path} missing safeword link`);\n }\n }\n }\n }\n\n // Check for missing .claude/settings.json\n const settingsPath = join(cwd, '.claude', 'settings.json');\n if (!exists(settingsPath)) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues,\n missingPackages: result.packagesToInstall,\n };\n}\n\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = await checkHealth(cwd);\n\n // Not configured\n if (!health.configured) {\n info('Not configured. Run `safeword setup` to initialize.');\n return;\n }\n\n // Show versions\n keyValue('Safeword CLI', `v${health.cliVersion}`);\n keyValue('Project config', health.projectVersion ? `v${health.projectVersion}` : 'unknown');\n\n // Check for updates (unless offline)\n if (!options.offline) {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (latestVersion) {\n health.latestVersion = latestVersion;\n health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);\n\n if (health.updateAvailable) {\n warn(`Update available: v${latestVersion}`);\n info('Run `npm install -g safeword` to upgrade');\n } else {\n success('CLI is up to date');\n }\n } else {\n warn(\"Couldn't check for updates (offline?)\");\n }\n } else {\n info('\\nSkipped update check (offline mode)');\n }\n\n // Check project version vs CLI version\n if (health.projectVersion && isNewerVersion(health.cliVersion, health.projectVersion)) {\n warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);\n info('Consider upgrading the CLI');\n } else if (health.projectVersion && isNewerVersion(health.projectVersion, health.cliVersion)) {\n info(`\\nUpgrade available for project config`);\n info(\n `Run \\`safeword upgrade\\` to update from v${health.projectVersion} to v${health.cliVersion}`,\n );\n }\n\n // Show issues\n if (health.issues.length > 0) {\n header('Issues Found');\n for (const issue of health.issues) {\n warn(issue);\n }\n info('\\nRun `safeword upgrade` to repair configuration');\n } else if (health.missingPackages.length > 0) {\n header('Missing Packages');\n info(`${health.missingPackages.length} linting packages not installed`);\n info('Run `safeword sync` to install missing packages');\n } else {\n success('\\nConfiguration is healthy');\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/output.ts","../src/utils/git.ts","../src/utils/context.ts","../src/reconcile.ts"],"sourcesContent":["/**\n * Console output utilities for consistent CLI messaging\n */\n\n/**\n * Print info message\n */\nexport function info(message: string): void {\n console.log(message);\n}\n\n/**\n * Print success message\n */\nexport function success(message: string): void {\n console.log(`✓ ${message}`);\n}\n\n/**\n * Print warning message\n */\nexport function warn(message: string): void {\n console.warn(`⚠ ${message}`);\n}\n\n/**\n * Print error message to stderr\n */\nexport function error(message: string): void {\n console.error(`✗ ${message}`);\n}\n\n/**\n * Print a blank line\n */\nexport function blank(): void {\n console.log('');\n}\n\n/**\n * Print a section header\n */\nexport function header(title: string): void {\n console.log(`\\n${title}`);\n console.log('─'.repeat(title.length));\n}\n\n/**\n * Print a list item\n */\nexport function listItem(item: string, indent = 2): void {\n console.log(`${' '.repeat(indent)}• ${item}`);\n}\n\n/**\n * Print key-value pair\n */\nexport function keyValue(key: string, value: string): void {\n console.log(` ${key}: ${value}`);\n}\n","/**\n * Git utilities for CLI operations\n */\n\nimport { join } from 'node:path';\nimport { exists } from './fs.js';\n\n/**\n * Check if directory is a git repository\n */\nexport function isGitRepo(cwd: string): boolean {\n return exists(join(cwd, '.git'));\n}\n","/**\n * Project Context Utilities\n *\n * Shared helpers for creating ProjectContext objects used by reconcile().\n */\n\nimport { join } from 'node:path';\nimport { readJson } from './fs.js';\nimport { isGitRepo } from './git.js';\nimport { detectProjectType, type PackageJson } from './project-detector.js';\nimport type { ProjectContext } from '../schema.js';\n\n/**\n * Create a ProjectContext from the current working directory.\n *\n * Reads package.json and detects project type for use with reconcile().\n */\nexport function createProjectContext(cwd: string): ProjectContext {\n const packageJson = readJson<PackageJson>(join(cwd, 'package.json'));\n\n return {\n cwd,\n projectType: detectProjectType(packageJson ?? {}),\n devDeps: packageJson?.devDependencies ?? {},\n isGitRepo: isGitRepo(cwd),\n };\n}\n","/**\n * Reconciliation Engine\n *\n * Computes and executes plans based on SAFEWORD_SCHEMA and project state.\n * This is the single source of truth for all file/dir/config operations.\n */\n\nimport { join } from 'node:path';\nimport {\n exists,\n ensureDir,\n writeFile,\n readFile,\n readFileSafe,\n readJson,\n writeJson,\n remove,\n removeIfEmpty,\n makeScriptsExecutable,\n getTemplatesDir,\n} from './utils/fs.js';\nimport type {\n SafewordSchema,\n ProjectContext,\n FileDefinition,\n JsonMergeDefinition,\n TextPatchDefinition,\n} from './schema.js';\nimport type { ProjectType } from './utils/project-detector.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst HUSKY_DIR = '.husky';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ReconcileMode = 'install' | 'upgrade' | 'uninstall' | 'uninstall-full';\n\nexport type Action =\n | { type: 'mkdir'; path: string }\n | { type: 'rmdir'; path: string }\n | { type: 'write'; path: string; content: string }\n | { type: 'rm'; path: string }\n | { type: 'chmod'; paths: string[] }\n | { type: 'json-merge'; path: string; definition: JsonMergeDefinition }\n | { type: 'json-unmerge'; path: string; definition: JsonMergeDefinition }\n | { type: 'text-patch'; path: string; definition: TextPatchDefinition }\n | { type: 'text-unpatch'; path: string; definition: TextPatchDefinition };\n\nexport interface ReconcileResult {\n actions: Action[];\n applied: boolean;\n created: string[];\n updated: string[];\n removed: string[];\n packagesToInstall: string[];\n packagesToRemove: string[];\n}\n\nexport interface ReconcileOptions {\n dryRun?: boolean;\n}\n\n// ============================================================================\n// Main reconcile function\n// ============================================================================\n\nexport async function reconcile(\n schema: SafewordSchema,\n mode: ReconcileMode,\n ctx: ProjectContext,\n options?: ReconcileOptions,\n): Promise<ReconcileResult> {\n const dryRun = options?.dryRun ?? false;\n\n const plan = computePlan(schema, mode, ctx);\n\n if (dryRun) {\n return {\n actions: plan.actions,\n applied: false,\n created: plan.wouldCreate,\n updated: plan.wouldUpdate,\n removed: plan.wouldRemove,\n packagesToInstall: plan.packagesToInstall,\n packagesToRemove: plan.packagesToRemove,\n };\n }\n\n const result = executePlan(plan, ctx);\n\n return {\n actions: plan.actions,\n applied: true,\n created: result.created,\n updated: result.updated,\n removed: result.removed,\n packagesToInstall: plan.packagesToInstall,\n packagesToRemove: plan.packagesToRemove,\n };\n}\n\n// ============================================================================\n// Plan computation\n// ============================================================================\n\ninterface ReconcilePlan {\n actions: Action[];\n wouldCreate: string[];\n wouldUpdate: string[];\n wouldRemove: string[];\n packagesToInstall: string[];\n packagesToRemove: string[];\n}\n\nfunction computePlan(\n schema: SafewordSchema,\n mode: ReconcileMode,\n ctx: ProjectContext,\n): ReconcilePlan {\n switch (mode) {\n case 'install':\n return computeInstallPlan(schema, ctx);\n case 'upgrade':\n return computeUpgradePlan(schema, ctx);\n case 'uninstall':\n return computeUninstallPlan(schema, ctx, false);\n case 'uninstall-full':\n return computeUninstallPlan(schema, ctx, true);\n }\n}\n\nfunction computeInstallPlan(schema: SafewordSchema, ctx: ProjectContext): ReconcilePlan {\n const actions: Action[] = [];\n const wouldCreate: string[] = [];\n\n // 1. Create all directories (skip .husky if not a git repo)\n const allDirs = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];\n for (const dir of allDirs) {\n // Skip .husky in non-git repos\n if (dir.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;\n\n const fullPath = join(ctx.cwd, dir);\n if (!exists(fullPath)) {\n actions.push({ type: 'mkdir', path: dir });\n wouldCreate.push(dir);\n }\n }\n\n // 2. Write all owned files (skip .husky files if not a git repo)\n for (const [filePath, def] of Object.entries(schema.ownedFiles)) {\n // Skip .husky files in non-git repos\n if (filePath.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;\n\n const content = resolveFileContent(def, ctx);\n actions.push({ type: 'write', path: filePath, content });\n wouldCreate.push(filePath);\n }\n\n // 3. Write managed files (only if missing)\n for (const [filePath, def] of Object.entries(schema.managedFiles)) {\n const fullPath = join(ctx.cwd, filePath);\n if (!exists(fullPath)) {\n const content = resolveFileContent(def, ctx);\n actions.push({ type: 'write', path: filePath, content });\n wouldCreate.push(filePath);\n }\n }\n\n // 4. chmod hook/lib directories (only .husky if git repo)\n const chmodPaths = ['.safeword/hooks', '.safeword/lib'];\n if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);\n actions.push({ type: 'chmod', paths: chmodPaths });\n\n // 5. JSON merges\n for (const [filePath, def] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-merge', path: filePath, definition: def });\n }\n\n // 6. Text patches\n for (const [filePath, def] of Object.entries(schema.textPatches)) {\n actions.push({ type: 'text-patch', path: filePath, definition: def });\n if (def.createIfMissing && !exists(join(ctx.cwd, filePath))) {\n wouldCreate.push(filePath);\n }\n }\n\n // 7. Compute packages to install (husky/lint-staged skipped if no git repo)\n const packagesToInstall = computePackagesToInstall(\n schema,\n ctx.projectType,\n ctx.devDeps,\n ctx.isGitRepo,\n );\n\n return {\n actions,\n wouldCreate,\n wouldUpdate: [],\n wouldRemove: [],\n packagesToInstall,\n packagesToRemove: [],\n };\n}\n\nfunction computeUpgradePlan(schema: SafewordSchema, ctx: ProjectContext): ReconcilePlan {\n const actions: Action[] = [];\n const wouldCreate: string[] = [];\n const wouldUpdate: string[] = [];\n\n // 1. Ensure directories exist (skip .husky if not a git repo)\n const allDirs = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];\n for (const dir of allDirs) {\n // Skip .husky in non-git repos\n if (dir.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;\n\n const fullPath = join(ctx.cwd, dir);\n if (!exists(fullPath)) {\n actions.push({ type: 'mkdir', path: dir });\n wouldCreate.push(dir);\n }\n }\n\n // 2. Update owned files if content changed (skip .husky files if not a git repo)\n for (const [filePath, def] of Object.entries(schema.ownedFiles)) {\n // Skip .husky files in non-git repos\n if (filePath.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;\n\n const fullPath = join(ctx.cwd, filePath);\n const newContent = resolveFileContent(def, ctx);\n\n if (fileNeedsUpdate(fullPath, newContent)) {\n actions.push({ type: 'write', path: filePath, content: newContent });\n if (exists(fullPath)) {\n wouldUpdate.push(filePath);\n } else {\n wouldCreate.push(filePath);\n }\n }\n }\n\n // 3. Update managed files only if content matches current template\n for (const [filePath, def] of Object.entries(schema.managedFiles)) {\n const fullPath = join(ctx.cwd, filePath);\n const newContent = resolveFileContent(def, ctx);\n\n if (!exists(fullPath)) {\n // Missing - create it\n actions.push({ type: 'write', path: filePath, content: newContent });\n wouldCreate.push(filePath);\n } else {\n // Exists - only update if user hasn't modified it\n // For upgrade, we need to compare against what safeword would generate\n // If the current content matches our template, user hasn't customized it\n const currentContent = readFileSafe(fullPath);\n if (currentContent?.trim() === newContent.trim()) {\n // Content matches - no update needed (already current)\n } else {\n // Content differs - check if it was originally from safeword\n // For now, we don't update managed files in upgrade mode\n // unless they match our previous template exactly\n // This is conservative - user may have customized\n }\n }\n }\n\n // 4. chmod (only .husky if git repo)\n const chmodPaths = ['.safeword/hooks', '.safeword/lib'];\n if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);\n actions.push({ type: 'chmod', paths: chmodPaths });\n\n // 5. JSON merges (always apply to ensure keys are present)\n for (const [filePath, def] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-merge', path: filePath, definition: def });\n }\n\n // 6. Text patches (only if marker missing)\n for (const [filePath, def] of Object.entries(schema.textPatches)) {\n const fullPath = join(ctx.cwd, filePath);\n const content = readFileSafe(fullPath) ?? '';\n if (!content.includes(def.marker)) {\n actions.push({ type: 'text-patch', path: filePath, definition: def });\n }\n }\n\n // 7. Compute packages to install (husky/lint-staged skipped if no git repo)\n const packagesToInstall = computePackagesToInstall(\n schema,\n ctx.projectType,\n ctx.devDeps,\n ctx.isGitRepo,\n );\n\n return {\n actions,\n wouldCreate,\n wouldUpdate,\n wouldRemove: [],\n packagesToInstall,\n packagesToRemove: [],\n };\n}\n\nfunction computeUninstallPlan(\n schema: SafewordSchema,\n ctx: ProjectContext,\n full: boolean,\n): ReconcilePlan {\n const actions: Action[] = [];\n const wouldRemove: string[] = [];\n\n // 1. Remove all owned files\n const dirsToCleanup = new Set<string>();\n for (const filePath of Object.keys(schema.ownedFiles)) {\n const fullPath = join(ctx.cwd, filePath);\n if (exists(fullPath)) {\n actions.push({ type: 'rm', path: filePath });\n wouldRemove.push(filePath);\n // Track parent dir for cleanup (for .claude/* skill dirs)\n if (filePath.startsWith('.claude/')) {\n const parentDir = filePath.substring(0, filePath.lastIndexOf('/'));\n if (\n parentDir &&\n parentDir !== '.claude' &&\n parentDir !== '.claude/skills' &&\n parentDir !== '.claude/commands'\n ) {\n dirsToCleanup.add(parentDir);\n }\n }\n }\n }\n // Clean up empty parent directories (like .claude/skills/safeword-*)\n for (const dir of dirsToCleanup) {\n const fullPath = join(ctx.cwd, dir);\n if (exists(fullPath)) {\n actions.push({ type: 'rmdir', path: dir });\n wouldRemove.push(dir);\n }\n }\n\n // 2. JSON unmerges\n for (const [filePath, def] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-unmerge', path: filePath, definition: def });\n }\n\n // 3. Text unpatches\n for (const [filePath, def] of Object.entries(schema.textPatches)) {\n const fullPath = join(ctx.cwd, filePath);\n if (exists(fullPath)) {\n const content = readFileSafe(fullPath) ?? '';\n if (content.includes(def.marker)) {\n actions.push({ type: 'text-unpatch', path: filePath, definition: def });\n }\n }\n }\n\n // 4. Remove preserved directories first (reverse order)\n // These will only be removed if empty (no user content)\n const preservedDirsToRemove = [...schema.preservedDirs].reverse();\n for (const dir of preservedDirsToRemove) {\n const fullPath = join(ctx.cwd, dir);\n if (exists(fullPath)) {\n actions.push({ type: 'rmdir', path: dir });\n wouldRemove.push(dir);\n }\n }\n\n // 5. Remove owned directories (reverse order)\n // Reverse order ensures children are removed before parents\n const dirsToRemove = [...schema.ownedDirs].reverse();\n for (const dir of dirsToRemove) {\n const fullPath = join(ctx.cwd, dir);\n if (exists(fullPath)) {\n actions.push({ type: 'rmdir', path: dir });\n wouldRemove.push(dir);\n }\n }\n\n // 6. Full uninstall: remove managed files\n if (full) {\n for (const filePath of Object.keys(schema.managedFiles)) {\n const fullPath = join(ctx.cwd, filePath);\n if (exists(fullPath)) {\n actions.push({ type: 'rm', path: filePath });\n wouldRemove.push(filePath);\n }\n }\n }\n\n // 7. Compute packages to remove (full only)\n const packagesToRemove = full\n ? computePackagesToRemove(schema, ctx.projectType, ctx.devDeps)\n : [];\n\n return {\n actions,\n wouldCreate: [],\n wouldUpdate: [],\n wouldRemove,\n packagesToInstall: [],\n packagesToRemove,\n };\n}\n\n// ============================================================================\n// Plan execution\n// ============================================================================\n\ninterface ExecutionResult {\n created: string[];\n updated: string[];\n removed: string[];\n}\n\nfunction executePlan(plan: ReconcilePlan, ctx: ProjectContext): ExecutionResult {\n const created: string[] = [];\n const updated: string[] = [];\n const removed: string[] = [];\n\n for (const action of plan.actions) {\n switch (action.type) {\n case 'mkdir': {\n const fullPath = join(ctx.cwd, action.path);\n ensureDir(fullPath);\n created.push(action.path);\n break;\n }\n\n case 'rmdir': {\n const fullPath = join(ctx.cwd, action.path);\n // Use removeIfEmpty to preserve directories with user content\n // This will only succeed if the directory is empty\n if (removeIfEmpty(fullPath)) {\n removed.push(action.path);\n }\n break;\n }\n\n case 'write': {\n const fullPath = join(ctx.cwd, action.path);\n const existed = exists(fullPath);\n writeFile(fullPath, action.content);\n if (existed) {\n updated.push(action.path);\n } else {\n created.push(action.path);\n }\n break;\n }\n\n case 'rm': {\n const fullPath = join(ctx.cwd, action.path);\n remove(fullPath);\n removed.push(action.path);\n break;\n }\n\n case 'chmod': {\n for (const path of action.paths) {\n const fullPath = join(ctx.cwd, path);\n if (exists(fullPath)) {\n makeScriptsExecutable(fullPath);\n }\n }\n break;\n }\n\n case 'json-merge': {\n executeJsonMerge(ctx.cwd, action.path, action.definition, ctx);\n break;\n }\n\n case 'json-unmerge': {\n executeJsonUnmerge(ctx.cwd, action.path, action.definition);\n break;\n }\n\n case 'text-patch': {\n executeTextPatch(ctx.cwd, action.path, action.definition);\n break;\n }\n\n case 'text-unpatch': {\n executeTextUnpatch(ctx.cwd, action.path, action.definition);\n break;\n }\n }\n }\n\n return { created, updated, removed };\n}\n\n// ============================================================================\n// Helper functions\n// ============================================================================\n\nfunction resolveFileContent(def: FileDefinition, ctx: ProjectContext): string {\n if (def.template) {\n const templatesDir = getTemplatesDir();\n return readFile(join(templatesDir, def.template));\n }\n\n if (def.content) {\n return typeof def.content === 'function' ? def.content() : def.content;\n }\n\n if (def.generator) {\n return def.generator(ctx);\n }\n\n throw new Error('FileDefinition must have template, content, or generator');\n}\n\nfunction fileNeedsUpdate(installedPath: string, newContent: string): boolean {\n if (!exists(installedPath)) return true;\n const currentContent = readFileSafe(installedPath);\n return currentContent?.trim() !== newContent.trim();\n}\n\n// Packages that require git repo\nconst GIT_ONLY_PACKAGES = ['husky', 'lint-staged'];\n\nexport function computePackagesToInstall(\n schema: SafewordSchema,\n projectType: ProjectType,\n installedDevDeps: Record<string, string>,\n isGitRepo = true,\n): string[] {\n let needed = [...schema.packages.base];\n\n // Filter out git-only packages when not in a git repo\n if (!isGitRepo) {\n needed = needed.filter(pkg => !GIT_ONLY_PACKAGES.includes(pkg));\n }\n\n for (const [key, deps] of Object.entries(schema.packages.conditional)) {\n if (projectType[key as keyof ProjectType]) {\n needed.push(...deps);\n }\n }\n\n return needed.filter(pkg => !(pkg in installedDevDeps));\n}\n\nfunction computePackagesToRemove(\n schema: SafewordSchema,\n projectType: ProjectType,\n installedDevDeps: Record<string, string>,\n): string[] {\n const safewordPackages = [...schema.packages.base];\n\n for (const [key, deps] of Object.entries(schema.packages.conditional)) {\n if (projectType[key as keyof ProjectType]) {\n safewordPackages.push(...deps);\n }\n }\n\n // Only remove packages that are actually installed\n return safewordPackages.filter(pkg => pkg in installedDevDeps);\n}\n\nfunction executeJsonMerge(\n cwd: string,\n path: string,\n def: JsonMergeDefinition,\n ctx: ProjectContext,\n): void {\n const fullPath = join(cwd, path);\n const existing = readJson<Record<string, unknown>>(fullPath) ?? {};\n const merged = def.merge(existing, ctx);\n writeJson(fullPath, merged);\n}\n\nfunction executeJsonUnmerge(cwd: string, path: string, def: JsonMergeDefinition): void {\n const fullPath = join(cwd, path);\n if (!exists(fullPath)) return;\n\n const existing = readJson<Record<string, unknown>>(fullPath);\n if (!existing) return;\n\n const unmerged = def.unmerge(existing);\n\n // Check if file should be removed\n if (def.removeFileIfEmpty) {\n const remainingKeys = Object.keys(unmerged).filter(\n k => unmerged[k] !== undefined && unmerged[k] !== null,\n );\n if (remainingKeys.length === 0) {\n remove(fullPath);\n return;\n }\n }\n\n writeJson(fullPath, unmerged);\n}\n\nfunction executeTextPatch(cwd: string, path: string, def: TextPatchDefinition): void {\n const fullPath = join(cwd, path);\n let content = readFileSafe(fullPath) ?? '';\n\n // Check if already patched\n if (content.includes(def.marker)) return;\n\n // Apply patch\n if (def.operation === 'prepend') {\n content = def.content + content;\n } else {\n content = content + def.content;\n }\n\n writeFile(fullPath, content);\n}\n\nfunction executeTextUnpatch(cwd: string, path: string, def: TextPatchDefinition): void {\n const fullPath = join(cwd, path);\n const content = readFileSafe(fullPath);\n if (!content) return;\n\n // Remove the patched content\n // First try to remove the full content block\n let unpatched = content.replace(def.content, '');\n\n // If full content wasn't found but marker exists, remove lines containing the marker\n if (unpatched === content && content.includes(def.marker)) {\n // Remove lines containing the marker\n const lines = content.split('\\n');\n const filtered = lines.filter(line => !line.includes(def.marker));\n unpatched = filtered.join('\\n').replace(/^\\n+/, ''); // Remove leading empty lines\n }\n\n writeFile(fullPath, unpatched);\n}\n"],"mappings":"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","names":["join","join","join","join","isGitRepo"]}