safeword 0.8.6 → 0.8.9

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 (39) hide show
  1. package/dist/{check-I2J6THGQ.js → check-QMAGWUOA.js} +24 -22
  2. package/dist/check-QMAGWUOA.js.map +1 -0
  3. package/dist/{chunk-DES5CSPH.js → chunk-4URRFBUS.js} +10 -10
  4. package/dist/chunk-4URRFBUS.js.map +1 -0
  5. package/dist/{chunk-DXT6TWW4.js → chunk-CLSGXTOL.js} +232 -435
  6. package/dist/chunk-CLSGXTOL.js.map +1 -0
  7. package/dist/{chunk-W66Z3C5H.js → chunk-FJYRWU2V.js} +5 -5
  8. package/dist/chunk-FJYRWU2V.js.map +1 -0
  9. package/dist/{chunk-VXKJ5ZIV.js → chunk-KQ6BLN6W.js} +172 -155
  10. package/dist/chunk-KQ6BLN6W.js.map +1 -0
  11. package/dist/cli.js +6 -6
  12. package/dist/cli.js.map +1 -1
  13. package/dist/{diff-4YFDNEZB.js → diff-2T7UDES7.js} +12 -12
  14. package/dist/diff-2T7UDES7.js.map +1 -0
  15. package/dist/index.d.ts +2 -2
  16. package/dist/{reset-QVERBAQJ.js → reset-QRXG7KZZ.js} +8 -8
  17. package/dist/reset-QRXG7KZZ.js.map +1 -0
  18. package/dist/{setup-ZSMZ7HZG.js → setup-QUUJ7SH3.js} +8 -8
  19. package/dist/setup-QUUJ7SH3.js.map +1 -0
  20. package/dist/sync-ISBJ7X2T.js +9 -0
  21. package/dist/{upgrade-WILVVHUY.js → upgrade-FALAUUKE.js} +22 -10
  22. package/dist/upgrade-FALAUUKE.js.map +1 -0
  23. package/package.json +15 -14
  24. package/templates/SAFEWORD.md +4 -2
  25. package/templates/commands/cleanup-zombies.md +48 -0
  26. package/templates/guides/zombie-process-cleanup.md +40 -24
  27. package/templates/scripts/cleanup-zombies.sh +222 -0
  28. package/templates/scripts/lint-md.sh +0 -0
  29. package/dist/check-I2J6THGQ.js.map +0 -1
  30. package/dist/chunk-DES5CSPH.js.map +0 -1
  31. package/dist/chunk-DXT6TWW4.js.map +0 -1
  32. package/dist/chunk-VXKJ5ZIV.js.map +0 -1
  33. package/dist/chunk-W66Z3C5H.js.map +0 -1
  34. package/dist/diff-4YFDNEZB.js.map +0 -1
  35. package/dist/reset-QVERBAQJ.js.map +0 -1
  36. package/dist/setup-ZSMZ7HZG.js.map +0 -1
  37. package/dist/sync-VQW5DSTV.js +0 -9
  38. package/dist/upgrade-WILVVHUY.js.map +0 -1
  39. /package/dist/{sync-VQW5DSTV.js.map → sync-ISBJ7X2T.js.map} +0 -0
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  detectProjectType,
3
- ensureDir,
3
+ ensureDirectory,
4
4
  exists,
5
- getTemplatesDir,
5
+ getTemplatesDirectory,
6
6
  makeScriptsExecutable,
7
7
  readFile,
8
8
  readFileSafe,
@@ -11,84 +11,42 @@ import {
11
11
  removeIfEmpty,
12
12
  writeFile,
13
13
  writeJson
14
- } from "./chunk-DXT6TWW4.js";
15
-
16
- // src/utils/output.ts
17
- function info(message) {
18
- console.log(message);
19
- }
20
- function success(message) {
21
- console.log(`\u2713 ${message}`);
22
- }
23
- function warn(message) {
24
- console.warn(`\u26A0 ${message}`);
25
- }
26
- function error(message) {
27
- console.error(`\u2717 ${message}`);
28
- }
29
- function header(title) {
30
- console.log(`
31
- ${title}`);
32
- console.log("\u2500".repeat(title.length));
33
- }
34
- function listItem(item, indent = 2) {
35
- console.log(`${" ".repeat(indent)}\u2022 ${item}`);
36
- }
37
- function keyValue(key, value) {
38
- console.log(` ${key}: ${value}`);
39
- }
40
-
41
- // src/utils/git.ts
42
- import { join } from "path";
43
- function isGitRepo(cwd) {
44
- return exists(join(cwd, ".git"));
45
- }
46
-
47
- // src/utils/context.ts
48
- import { join as join2 } from "path";
49
- function createProjectContext(cwd) {
50
- const packageJson = readJson(join2(cwd, "package.json"));
51
- return {
52
- cwd,
53
- projectType: detectProjectType(packageJson ?? {}, cwd),
54
- devDeps: packageJson?.devDependencies ?? {},
55
- isGitRepo: isGitRepo(cwd)
56
- };
57
- }
14
+ } from "./chunk-CLSGXTOL.js";
58
15
 
59
16
  // src/reconcile.ts
60
- import { join as join3 } from "path";
17
+ import nodePath from "path";
61
18
  var HUSKY_DIR = ".husky";
62
19
  function shouldSkipForNonGit(path, isGitRepo2) {
63
20
  return path.startsWith(HUSKY_DIR) && !isGitRepo2;
64
21
  }
65
- function planMissingDirs(dirs, cwd, isGitRepo2) {
22
+ function planMissingDirectories(directories, cwd, isGitRepo2) {
66
23
  const actions = [];
67
24
  const created = [];
68
- for (const dir of dirs) {
25
+ for (const dir of directories) {
69
26
  if (shouldSkipForNonGit(dir, isGitRepo2)) continue;
70
- if (!exists(join3(cwd, dir))) {
27
+ if (!exists(nodePath.join(cwd, dir))) {
71
28
  actions.push({ type: "mkdir", path: dir });
72
29
  created.push(dir);
73
30
  }
74
31
  }
75
32
  return { actions, created };
76
33
  }
77
- function planTextPatches(patches, cwd) {
34
+ function planTextPatches(patches, cwd, isGitRepo2) {
78
35
  const actions = [];
79
- for (const [filePath, def] of Object.entries(patches)) {
80
- const content = readFileSafe(join3(cwd, filePath)) ?? "";
81
- if (!content.includes(def.marker)) {
82
- actions.push({ type: "text-patch", path: filePath, definition: def });
36
+ for (const [filePath, definition] of Object.entries(patches)) {
37
+ if (shouldSkipForNonGit(filePath, isGitRepo2)) continue;
38
+ const content = readFileSafe(nodePath.join(cwd, filePath)) ?? "";
39
+ if (!content.includes(definition.marker)) {
40
+ actions.push({ type: "text-patch", path: filePath, definition });
83
41
  }
84
42
  }
85
43
  return actions;
86
44
  }
87
- function planExistingDirsRemoval(dirs, cwd) {
45
+ function planExistingDirectoriesRemoval(directories, cwd) {
88
46
  const actions = [];
89
47
  const removed = [];
90
- for (const dir of dirs) {
91
- if (exists(join3(cwd, dir))) {
48
+ for (const dir of directories) {
49
+ if (exists(nodePath.join(cwd, dir))) {
92
50
  actions.push({ type: "rmdir", path: dir });
93
51
  removed.push(dir);
94
52
  }
@@ -99,20 +57,20 @@ function planExistingFilesRemoval(files, cwd) {
99
57
  const actions = [];
100
58
  const removed = [];
101
59
  for (const filePath of files) {
102
- if (exists(join3(cwd, filePath))) {
60
+ if (exists(nodePath.join(cwd, filePath))) {
103
61
  actions.push({ type: "rm", path: filePath });
104
62
  removed.push(filePath);
105
63
  }
106
64
  }
107
65
  return { actions, removed };
108
66
  }
109
- function getClaudeParentDirForCleanup(filePath) {
110
- if (!filePath.startsWith(".claude/")) return null;
111
- const parentDir = filePath.slice(0, Math.max(0, filePath.lastIndexOf("/")));
112
- if (!parentDir || parentDir === ".claude" || parentDir === ".claude/skills" || parentDir === ".claude/commands") {
113
- return null;
67
+ function getClaudeParentDirectoryForCleanup(filePath) {
68
+ if (!filePath.startsWith(".claude/")) return void 0;
69
+ const parentDirectory = filePath.slice(0, Math.max(0, filePath.lastIndexOf("/")));
70
+ if (!parentDirectory || parentDirectory === ".claude" || parentDirectory === ".claude/skills" || parentDirectory === ".claude/commands") {
71
+ return void 0;
114
72
  }
115
- return parentDir;
73
+ return parentDirectory;
116
74
  }
117
75
  async function reconcile(schema, mode, ctx, options) {
118
76
  const dryRun = options?.dryRun ?? false;
@@ -143,7 +101,7 @@ function planDeprecatedFilesRemoval(deprecatedFiles, cwd) {
143
101
  const actions = [];
144
102
  const removed = [];
145
103
  for (const filePath of deprecatedFiles) {
146
- if (exists(join3(cwd, filePath))) {
104
+ if (exists(nodePath.join(cwd, filePath))) {
147
105
  actions.push({ type: "rm", path: filePath });
148
106
  removed.push(filePath);
149
107
  }
@@ -164,45 +122,55 @@ function computePlan(schema, mode, ctx) {
164
122
  case "uninstall-full": {
165
123
  return computeUninstallPlan(schema, ctx, true);
166
124
  }
125
+ default: {
126
+ const _exhaustiveCheck = mode;
127
+ return _exhaustiveCheck;
128
+ }
167
129
  }
168
130
  }
169
131
  function computeInstallPlan(schema, ctx) {
170
132
  const actions = [];
171
133
  const wouldCreate = [];
172
- const allDirs = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];
173
- const missingDirs = planMissingDirs(allDirs, ctx.cwd, ctx.isGitRepo);
174
- actions.push(...missingDirs.actions);
175
- wouldCreate.push(...missingDirs.created);
176
- for (const [filePath, def] of Object.entries(schema.ownedFiles)) {
134
+ const allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];
135
+ const missingDirectories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);
136
+ actions.push(...missingDirectories.actions);
137
+ wouldCreate.push(...missingDirectories.created);
138
+ for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {
177
139
  if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
178
- const content = resolveFileContent(def, ctx);
140
+ const content = resolveFileContent(definition, ctx);
179
141
  actions.push({ type: "write", path: filePath, content });
180
142
  wouldCreate.push(filePath);
181
143
  }
182
- for (const [filePath, def] of Object.entries(schema.managedFiles)) {
183
- const fullPath = join3(ctx.cwd, filePath);
144
+ for (const [filePath, definition] of Object.entries(schema.managedFiles)) {
145
+ const fullPath = nodePath.join(ctx.cwd, filePath);
184
146
  if (!exists(fullPath)) {
185
- const content = resolveFileContent(def, ctx);
147
+ const content = resolveFileContent(definition, ctx);
186
148
  actions.push({ type: "write", path: filePath, content });
187
149
  wouldCreate.push(filePath);
188
150
  }
189
151
  }
190
- const chmodPaths = [".safeword/hooks", ".safeword/hooks/cursor", ".safeword/lib"];
152
+ const chmodPaths = [
153
+ ".safeword/hooks",
154
+ ".safeword/hooks/cursor",
155
+ ".safeword/lib",
156
+ ".safeword/scripts"
157
+ ];
191
158
  if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);
192
159
  actions.push({ type: "chmod", paths: chmodPaths });
193
- for (const [filePath, def] of Object.entries(schema.jsonMerges)) {
194
- actions.push({ type: "json-merge", path: filePath, definition: def });
160
+ for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {
161
+ actions.push({ type: "json-merge", path: filePath, definition });
195
162
  }
196
- for (const [filePath, def] of Object.entries(schema.textPatches)) {
197
- actions.push({ type: "text-patch", path: filePath, definition: def });
198
- if (def.createIfMissing && !exists(join3(ctx.cwd, filePath))) {
163
+ for (const [filePath, definition] of Object.entries(schema.textPatches)) {
164
+ if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
165
+ actions.push({ type: "text-patch", path: filePath, definition });
166
+ if (definition.createIfMissing && !exists(nodePath.join(ctx.cwd, filePath))) {
199
167
  wouldCreate.push(filePath);
200
168
  }
201
169
  }
202
170
  const packagesToInstall = computePackagesToInstall(
203
171
  schema,
204
172
  ctx.projectType,
205
- ctx.devDeps,
173
+ ctx.developmentDeps,
206
174
  ctx.isGitRepo
207
175
  );
208
176
  return {
@@ -218,14 +186,14 @@ function computeUpgradePlan(schema, ctx) {
218
186
  const actions = [];
219
187
  const wouldCreate = [];
220
188
  const wouldUpdate = [];
221
- const allDirs = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];
222
- const missingDirs = planMissingDirs(allDirs, ctx.cwd, ctx.isGitRepo);
223
- actions.push(...missingDirs.actions);
224
- wouldCreate.push(...missingDirs.created);
225
- for (const [filePath, def] of Object.entries(schema.ownedFiles)) {
189
+ const allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];
190
+ const missingDirectories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);
191
+ actions.push(...missingDirectories.actions);
192
+ wouldCreate.push(...missingDirectories.created);
193
+ for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {
226
194
  if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
227
- const fullPath = join3(ctx.cwd, filePath);
228
- const newContent = resolveFileContent(def, ctx);
195
+ const fullPath = nodePath.join(ctx.cwd, filePath);
196
+ const newContent = resolveFileContent(definition, ctx);
229
197
  if (!fileNeedsUpdate(fullPath, newContent)) continue;
230
198
  actions.push({ type: "write", path: filePath, content: newContent });
231
199
  if (exists(fullPath)) {
@@ -234,9 +202,9 @@ function computeUpgradePlan(schema, ctx) {
234
202
  wouldCreate.push(filePath);
235
203
  }
236
204
  }
237
- for (const [filePath, def] of Object.entries(schema.managedFiles)) {
238
- const fullPath = join3(ctx.cwd, filePath);
239
- const newContent = resolveFileContent(def, ctx);
205
+ for (const [filePath, definition] of Object.entries(schema.managedFiles)) {
206
+ const fullPath = nodePath.join(ctx.cwd, filePath);
207
+ const newContent = resolveFileContent(definition, ctx);
240
208
  if (!exists(fullPath)) {
241
209
  actions.push({ type: "write", path: filePath, content: newContent });
242
210
  wouldCreate.push(filePath);
@@ -245,26 +213,34 @@ function computeUpgradePlan(schema, ctx) {
245
213
  const deprecatedFiles = planDeprecatedFilesRemoval(schema.deprecatedFiles, ctx.cwd);
246
214
  actions.push(...deprecatedFiles.actions);
247
215
  const wouldRemove = deprecatedFiles.removed;
248
- const chmodPathsUpgrade = [".safeword/hooks", ".safeword/hooks/cursor", ".safeword/lib"];
249
- if (ctx.isGitRepo) chmodPathsUpgrade.push(HUSKY_DIR);
216
+ const deprecatedDirectories = planExistingDirectoriesRemoval(schema.deprecatedDirs, ctx.cwd);
217
+ actions.push(...deprecatedDirectories.actions);
218
+ wouldRemove.push(...deprecatedDirectories.removed);
219
+ const chmodPathsUpgrade = [
220
+ ".safeword/hooks",
221
+ ".safeword/hooks/cursor",
222
+ ".safeword/lib",
223
+ ".safeword/scripts"
224
+ ];
250
225
  actions.push({ type: "chmod", paths: chmodPathsUpgrade });
251
- for (const [filePath, def] of Object.entries(schema.jsonMerges)) {
252
- actions.push({ type: "json-merge", path: filePath, definition: def });
226
+ for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {
227
+ actions.push({ type: "json-merge", path: filePath, definition });
253
228
  }
254
- actions.push(...planTextPatches(schema.textPatches, ctx.cwd));
229
+ actions.push(...planTextPatches(schema.textPatches, ctx.cwd, ctx.isGitRepo));
255
230
  const packagesToInstall = computePackagesToInstall(
256
231
  schema,
257
232
  ctx.projectType,
258
- ctx.devDeps,
233
+ ctx.developmentDeps,
259
234
  ctx.isGitRepo
260
235
  );
236
+ const packagesToRemove = schema.deprecatedPackages.filter((pkg) => pkg in ctx.developmentDeps);
261
237
  return {
262
238
  actions,
263
239
  wouldCreate,
264
240
  wouldUpdate,
265
241
  wouldRemove,
266
242
  packagesToInstall,
267
- packagesToRemove: []
243
+ packagesToRemove
268
244
  };
269
245
  }
270
246
  function computeUninstallPlan(schema, ctx, full) {
@@ -273,30 +249,30 @@ function computeUninstallPlan(schema, ctx, full) {
273
249
  const ownedFiles = planExistingFilesRemoval(Object.keys(schema.ownedFiles), ctx.cwd);
274
250
  actions.push(...ownedFiles.actions);
275
251
  wouldRemove.push(...ownedFiles.removed);
276
- const dirsToCleanup = /* @__PURE__ */ new Set();
252
+ const directoriesToCleanup = /* @__PURE__ */ new Set();
277
253
  for (const filePath of ownedFiles.removed) {
278
- const parentDir = getClaudeParentDirForCleanup(filePath);
279
- if (parentDir) dirsToCleanup.add(parentDir);
254
+ const parentDirectory = getClaudeParentDirectoryForCleanup(filePath);
255
+ if (parentDirectory) directoriesToCleanup.add(parentDirectory);
280
256
  }
281
- const cleanupDirs = planExistingDirsRemoval([...dirsToCleanup], ctx.cwd);
282
- actions.push(...cleanupDirs.actions);
283
- wouldRemove.push(...cleanupDirs.removed);
284
- for (const [filePath, def] of Object.entries(schema.jsonMerges)) {
285
- actions.push({ type: "json-unmerge", path: filePath, definition: def });
257
+ const cleanupDirectories = planExistingDirectoriesRemoval([...directoriesToCleanup], ctx.cwd);
258
+ actions.push(...cleanupDirectories.actions);
259
+ wouldRemove.push(...cleanupDirectories.removed);
260
+ for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {
261
+ actions.push({ type: "json-unmerge", path: filePath, definition });
286
262
  }
287
- for (const [filePath, def] of Object.entries(schema.textPatches)) {
288
- const fullPath = join3(ctx.cwd, filePath);
263
+ for (const [filePath, definition] of Object.entries(schema.textPatches)) {
264
+ const fullPath = nodePath.join(ctx.cwd, filePath);
289
265
  if (exists(fullPath)) {
290
266
  const content = readFileSafe(fullPath) ?? "";
291
- if (content.includes(def.marker)) {
292
- actions.push({ type: "text-unpatch", path: filePath, definition: def });
267
+ if (content.includes(definition.marker)) {
268
+ actions.push({ type: "text-unpatch", path: filePath, definition });
293
269
  }
294
270
  }
295
271
  }
296
- const preserved = planExistingDirsRemoval(schema.preservedDirs.toReversed(), ctx.cwd);
272
+ const preserved = planExistingDirectoriesRemoval(schema.preservedDirs.toReversed(), ctx.cwd);
297
273
  actions.push(...preserved.actions);
298
274
  wouldRemove.push(...preserved.removed);
299
- const owned = planExistingDirsRemoval(schema.ownedDirs.toReversed(), ctx.cwd);
275
+ const owned = planExistingDirectoriesRemoval(schema.ownedDirs.toReversed(), ctx.cwd);
300
276
  actions.push(...owned.actions);
301
277
  wouldRemove.push(...owned.removed);
302
278
  if (full) {
@@ -304,7 +280,7 @@ function computeUninstallPlan(schema, ctx, full) {
304
280
  actions.push(...managed.actions);
305
281
  wouldRemove.push(...managed.removed);
306
282
  }
307
- const packagesToRemove = full ? computePackagesToRemove(schema, ctx.projectType, ctx.devDeps) : [];
283
+ const packagesToRemove = full ? computePackagesToRemove(schema, ctx.projectType, ctx.developmentDeps) : [];
308
284
  return {
309
285
  actions,
310
286
  wouldCreate: [],
@@ -327,12 +303,12 @@ function executePlan(plan, ctx) {
327
303
  function executeAction(action, ctx, result) {
328
304
  switch (action.type) {
329
305
  case "mkdir": {
330
- ensureDir(join3(ctx.cwd, action.path));
306
+ ensureDirectory(nodePath.join(ctx.cwd, action.path));
331
307
  result.created.push(action.path);
332
308
  break;
333
309
  }
334
310
  case "rmdir": {
335
- if (removeIfEmpty(join3(ctx.cwd, action.path))) {
311
+ if (removeIfEmpty(nodePath.join(ctx.cwd, action.path))) {
336
312
  result.removed.push(action.path);
337
313
  }
338
314
  break;
@@ -342,13 +318,13 @@ function executeAction(action, ctx, result) {
342
318
  break;
343
319
  }
344
320
  case "rm": {
345
- remove(join3(ctx.cwd, action.path));
321
+ remove(nodePath.join(ctx.cwd, action.path));
346
322
  result.removed.push(action.path);
347
323
  break;
348
324
  }
349
325
  case "chmod": {
350
326
  for (const path of action.paths) {
351
- const fullPath = join3(ctx.cwd, path);
327
+ const fullPath = nodePath.join(ctx.cwd, path);
352
328
  if (exists(fullPath)) makeScriptsExecutable(fullPath);
353
329
  }
354
330
  break;
@@ -372,21 +348,21 @@ function executeAction(action, ctx, result) {
372
348
  }
373
349
  }
374
350
  function executeWrite(cwd, path, content, result) {
375
- const fullPath = join3(cwd, path);
351
+ const fullPath = nodePath.join(cwd, path);
376
352
  const existed = exists(fullPath);
377
353
  writeFile(fullPath, content);
378
354
  (existed ? result.updated : result.created).push(path);
379
355
  }
380
- function resolveFileContent(def, ctx) {
381
- if (def.template) {
382
- const templatesDir = getTemplatesDir();
383
- return readFile(join3(templatesDir, def.template));
356
+ function resolveFileContent(definition, ctx) {
357
+ if (definition.template) {
358
+ const templatesDirectory = getTemplatesDirectory();
359
+ return readFile(nodePath.join(templatesDirectory, definition.template));
384
360
  }
385
- if (def.content) {
386
- return typeof def.content === "function" ? def.content() : def.content;
361
+ if (definition.content) {
362
+ return typeof definition.content === "function" ? definition.content() : definition.content;
387
363
  }
388
- if (def.generator) {
389
- return def.generator(ctx);
364
+ if (definition.generator) {
365
+ return definition.generator(ctx);
390
366
  }
391
367
  throw new Error("FileDefinition must have template, content, or generator");
392
368
  }
@@ -396,7 +372,7 @@ function fileNeedsUpdate(installedPath, newContent) {
396
372
  return currentContent?.trim() !== newContent.trim();
397
373
  }
398
374
  var GIT_ONLY_PACKAGES = /* @__PURE__ */ new Set(["husky", "lint-staged"]);
399
- function computePackagesToInstall(schema, projectType, installedDevDeps, isGitRepo2 = true) {
375
+ function computePackagesToInstall(schema, projectType, installedDevelopmentDeps, isGitRepo2 = true) {
400
376
  let needed = [...schema.packages.base];
401
377
  if (!isGitRepo2) {
402
378
  needed = needed.filter((pkg) => !GIT_ONLY_PACKAGES.has(pkg));
@@ -406,34 +382,32 @@ function computePackagesToInstall(schema, projectType, installedDevDeps, isGitRe
406
382
  needed.push(...deps);
407
383
  }
408
384
  }
409
- return needed.filter((pkg) => !(pkg in installedDevDeps));
385
+ return needed.filter((pkg) => !(pkg in installedDevelopmentDeps));
410
386
  }
411
- function computePackagesToRemove(schema, projectType, installedDevDeps) {
387
+ function computePackagesToRemove(schema, projectType, installedDevelopmentDeps) {
412
388
  const safewordPackages = [...schema.packages.base];
413
389
  for (const [key, deps] of Object.entries(schema.packages.conditional)) {
414
390
  if (projectType[key]) {
415
391
  safewordPackages.push(...deps);
416
392
  }
417
393
  }
418
- return safewordPackages.filter((pkg) => pkg in installedDevDeps);
394
+ return safewordPackages.filter((pkg) => pkg in installedDevelopmentDeps);
419
395
  }
420
- function executeJsonMerge(cwd, path, def, ctx) {
421
- const fullPath = join3(cwd, path);
396
+ function executeJsonMerge(cwd, path, definition, ctx) {
397
+ const fullPath = nodePath.join(cwd, path);
422
398
  const existing = readJson(fullPath) ?? {};
423
- const merged = def.merge(existing, ctx);
399
+ const merged = definition.merge(existing, ctx);
424
400
  if (JSON.stringify(existing) === JSON.stringify(merged)) return;
425
401
  writeJson(fullPath, merged);
426
402
  }
427
- function executeJsonUnmerge(cwd, path, def) {
428
- const fullPath = join3(cwd, path);
403
+ function executeJsonUnmerge(cwd, path, definition) {
404
+ const fullPath = nodePath.join(cwd, path);
429
405
  if (!exists(fullPath)) return;
430
406
  const existing = readJson(fullPath);
431
407
  if (!existing) return;
432
- const unmerged = def.unmerge(existing);
433
- if (def.removeFileIfEmpty) {
434
- const remainingKeys = Object.keys(unmerged).filter(
435
- (k) => unmerged[k] !== void 0 && unmerged[k] !== null
436
- );
408
+ const unmerged = definition.unmerge(existing);
409
+ if (definition.removeFileIfEmpty) {
410
+ const remainingKeys = Object.keys(unmerged).filter((k) => unmerged[k] !== void 0);
437
411
  if (remainingKeys.length === 0) {
438
412
  remove(fullPath);
439
413
  return;
@@ -441,36 +415,79 @@ function executeJsonUnmerge(cwd, path, def) {
441
415
  }
442
416
  writeJson(fullPath, unmerged);
443
417
  }
444
- function executeTextPatch(cwd, path, def) {
445
- const fullPath = join3(cwd, path);
418
+ function executeTextPatch(cwd, path, definition) {
419
+ const fullPath = nodePath.join(cwd, path);
446
420
  let content = readFileSafe(fullPath) ?? "";
447
- if (content.includes(def.marker)) return;
448
- content = def.operation === "prepend" ? def.content + content : content + def.content;
421
+ if (content.includes(definition.marker)) return;
422
+ content = definition.operation === "prepend" ? definition.content + content : content + definition.content;
449
423
  writeFile(fullPath, content);
450
424
  }
451
- function executeTextUnpatch(cwd, path, def) {
452
- const fullPath = join3(cwd, path);
425
+ function executeTextUnpatch(cwd, path, definition) {
426
+ const fullPath = nodePath.join(cwd, path);
453
427
  const content = readFileSafe(fullPath);
454
428
  if (!content) return;
455
- let unpatched = content.replace(def.content, "");
456
- if (unpatched === content && content.includes(def.marker)) {
429
+ let unpatched = content.replace(definition.content, "");
430
+ if (unpatched === content && content.includes(definition.marker)) {
457
431
  const lines = content.split("\n");
458
- const filtered = lines.filter((line) => !line.includes(def.marker));
432
+ const filtered = lines.filter((line) => !line.includes(definition.marker));
459
433
  unpatched = filtered.join("\n").replace(/^\n+/, "");
460
434
  }
461
435
  writeFile(fullPath, unpatched);
462
436
  }
463
437
 
438
+ // src/utils/git.ts
439
+ import nodePath2 from "path";
440
+ function isGitRepo(cwd) {
441
+ return exists(nodePath2.join(cwd, ".git"));
442
+ }
443
+
444
+ // src/utils/context.ts
445
+ import nodePath3 from "path";
446
+ function createProjectContext(cwd) {
447
+ const packageJson = readJson(nodePath3.join(cwd, "package.json"));
448
+ return {
449
+ cwd,
450
+ projectType: detectProjectType(packageJson ?? {}, cwd),
451
+ developmentDeps: packageJson?.devDependencies ?? {},
452
+ isGitRepo: isGitRepo(cwd)
453
+ };
454
+ }
455
+
456
+ // src/utils/output.ts
457
+ function info(message) {
458
+ console.log(message);
459
+ }
460
+ function success(message) {
461
+ console.log(`\u2713 ${message}`);
462
+ }
463
+ function warn(message) {
464
+ console.warn(`\u26A0 ${message}`);
465
+ }
466
+ function error(message) {
467
+ console.error(`\u2717 ${message}`);
468
+ }
469
+ function header(title) {
470
+ console.log(`
471
+ ${title}`);
472
+ console.log("\u2500".repeat(title.length));
473
+ }
474
+ function listItem(item, indent = 2) {
475
+ console.log(`${" ".repeat(indent)}\u2022 ${item}`);
476
+ }
477
+ function keyValue(key, value) {
478
+ console.log(` ${key}: ${value}`);
479
+ }
480
+
464
481
  export {
482
+ reconcile,
483
+ isGitRepo,
484
+ createProjectContext,
465
485
  info,
466
486
  success,
467
487
  warn,
468
488
  error,
469
489
  header,
470
490
  listItem,
471
- keyValue,
472
- isGitRepo,
473
- createProjectContext,
474
- reconcile
491
+ keyValue
475
492
  };
476
- //# sourceMappingURL=chunk-VXKJ5ZIV.js.map
493
+ //# sourceMappingURL=chunk-KQ6BLN6W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reconcile.ts","../src/utils/git.ts","../src/utils/context.ts","../src/utils/output.ts"],"sourcesContent":["/**\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 nodePath from 'node:path';\n\nimport type {\n FileDefinition,\n JsonMergeDefinition,\n ProjectContext,\n SafewordSchema,\n TextPatchDefinition,\n} from './schema.js';\nimport {\n ensureDirectory,\n exists,\n getTemplatesDirectory,\n makeScriptsExecutable,\n readFile,\n readFileSafe,\n readJson,\n remove,\n removeIfEmpty,\n writeFile,\n writeJson,\n} from './utils/fs.js';\nimport type { ProjectType } from './utils/project-detector.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst HUSKY_DIR = '.husky';\n\n/**\n * Check if path should be skipped in non-git repos (husky files)\n * @param path\n * @param isGitRepo\n */\nfunction shouldSkipForNonGit(path: string, isGitRepo: boolean): boolean {\n return path.startsWith(HUSKY_DIR) && !isGitRepo;\n}\n\n/**\n * Plan mkdir actions for directories that don't exist\n * @param dirs\n * @param cwd\n * @param isGitRepo\n */\nfunction planMissingDirectories(\n directories: string[],\n cwd: string,\n isGitRepo: boolean,\n): { actions: Action[]; created: string[] } {\n const actions: Action[] = [];\n const created: string[] = [];\n for (const dir of directories) {\n if (shouldSkipForNonGit(dir, isGitRepo)) continue;\n if (!exists(nodePath.join(cwd, dir))) {\n actions.push({ type: 'mkdir', path: dir });\n created.push(dir);\n }\n }\n return { actions, created };\n}\n\n/**\n * Plan text-patch actions for files missing the marker\n * @param patches\n * @param cwd\n * @param isGitRepo\n */\nfunction planTextPatches(\n patches: Record<string, TextPatchDefinition>,\n cwd: string,\n isGitRepo: boolean,\n): Action[] {\n const actions: Action[] = [];\n for (const [filePath, definition] of Object.entries(patches)) {\n if (shouldSkipForNonGit(filePath, isGitRepo)) continue;\n const content = readFileSafe(nodePath.join(cwd, filePath)) ?? '';\n if (!content.includes(definition.marker)) {\n actions.push({ type: 'text-patch', path: filePath, definition });\n }\n }\n return actions;\n}\n\n/**\n * Plan rmdir actions for directories that exist\n * @param dirs\n * @param cwd\n */\nfunction planExistingDirectoriesRemoval(\n directories: string[],\n cwd: string,\n): { actions: Action[]; removed: string[] } {\n const actions: Action[] = [];\n const removed: string[] = [];\n for (const dir of directories) {\n if (exists(nodePath.join(cwd, dir))) {\n actions.push({ type: 'rmdir', path: dir });\n removed.push(dir);\n }\n }\n return { actions, removed };\n}\n\n/**\n * Plan rm actions for files that exist\n * @param files\n * @param cwd\n */\nfunction planExistingFilesRemoval(\n files: string[],\n cwd: string,\n): { actions: Action[]; removed: string[] } {\n const actions: Action[] = [];\n const removed: string[] = [];\n for (const filePath of files) {\n if (exists(nodePath.join(cwd, filePath))) {\n actions.push({ type: 'rm', path: filePath });\n removed.push(filePath);\n }\n }\n return { actions, removed };\n}\n\n/**\n * Check if a .claude path needs parent dir cleanup\n * @param filePath\n */\nfunction getClaudeParentDirectoryForCleanup(filePath: string): string | undefined {\n if (!filePath.startsWith('.claude/')) return undefined;\n const parentDirectory = filePath.slice(0, Math.max(0, filePath.lastIndexOf('/')));\n if (\n !parentDirectory ||\n parentDirectory === '.claude' ||\n parentDirectory === '.claude/skills' ||\n parentDirectory === '.claude/commands'\n ) {\n return undefined;\n }\n return parentDirectory;\n}\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\n/**\n *\n * @param schema\n * @param mode\n * @param ctx\n * @param options\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\n/**\n *\n * @param deprecatedFiles\n * @param cwd\n */\nfunction planDeprecatedFilesRemoval(\n deprecatedFiles: string[],\n cwd: string,\n): { actions: Action[]; removed: string[] } {\n const actions: Action[] = [];\n const removed: string[] = [];\n for (const filePath of deprecatedFiles) {\n if (exists(nodePath.join(cwd, filePath))) {\n actions.push({ type: 'rm', path: filePath });\n removed.push(filePath);\n }\n }\n return { actions, removed };\n}\n\n/**\n *\n * @param schema\n * @param mode\n * @param ctx\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 }\n case 'upgrade': {\n return computeUpgradePlan(schema, ctx);\n }\n case 'uninstall': {\n return computeUninstallPlan(schema, ctx, false);\n }\n case 'uninstall-full': {\n return computeUninstallPlan(schema, ctx, true);\n }\n default: {\n // Exhaustive check - TypeScript ensures all cases are handled\n const _exhaustiveCheck: never = mode;\n return _exhaustiveCheck;\n }\n }\n}\n\n/**\n *\n * @param schema\n * @param ctx\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 allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];\n const missingDirectories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);\n actions.push(...missingDirectories.actions);\n wouldCreate.push(...missingDirectories.created);\n\n // 2. Write all owned files (skip .husky files if not a git repo)\n for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {\n if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;\n\n const content = resolveFileContent(definition, 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, definition] of Object.entries(schema.managedFiles)) {\n const fullPath = nodePath.join(ctx.cwd, filePath);\n if (!exists(fullPath)) {\n const content = resolveFileContent(definition, ctx);\n actions.push({ type: 'write', path: filePath, content });\n wouldCreate.push(filePath);\n }\n }\n\n // 4. chmod hook/lib/scripts directories (only .husky if git repo)\n const chmodPaths = [\n '.safeword/hooks',\n '.safeword/hooks/cursor',\n '.safeword/lib',\n '.safeword/scripts',\n ];\n if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);\n actions.push({ type: 'chmod', paths: chmodPaths });\n\n // 5. JSON merges\n for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-merge', path: filePath, definition });\n }\n\n // 6. Text patches (skip .husky files in non-git repos)\n for (const [filePath, definition] of Object.entries(schema.textPatches)) {\n if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;\n actions.push({ type: 'text-patch', path: filePath, definition });\n if (definition.createIfMissing && !exists(nodePath.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.developmentDeps,\n ctx.isGitRepo,\n );\n\n return {\n actions,\n wouldCreate,\n wouldUpdate: [],\n wouldRemove: [],\n packagesToInstall,\n packagesToRemove: [],\n };\n}\n\n/**\n *\n * @param schema\n * @param ctx\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 allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];\n const missingDirectories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);\n actions.push(...missingDirectories.actions);\n wouldCreate.push(...missingDirectories.created);\n\n // 2. Update owned files if content changed (skip .husky files if not a git repo)\n for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {\n if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;\n\n const fullPath = nodePath.join(ctx.cwd, filePath);\n const newContent = resolveFileContent(definition, ctx);\n\n if (!fileNeedsUpdate(fullPath, newContent)) continue;\n\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 // 3. Update managed files only if content matches current template\n for (const [filePath, definition] of Object.entries(schema.managedFiles)) {\n const fullPath = nodePath.join(ctx.cwd, filePath);\n const newContent = resolveFileContent(definition, ctx);\n\n if (!exists(fullPath)) {\n // Missing - create it\n actions.push({ type: 'write', path: filePath, content: newContent });\n wouldCreate.push(filePath);\n }\n // If file exists, don't update during upgrade - user may have customized it\n }\n\n // 4. Remove deprecated files (renamed or removed in newer versions)\n const deprecatedFiles = planDeprecatedFilesRemoval(schema.deprecatedFiles, ctx.cwd);\n actions.push(...deprecatedFiles.actions);\n const wouldRemove = deprecatedFiles.removed;\n\n // 4b. Remove deprecated directories (no longer managed by safeword)\n const deprecatedDirectories = planExistingDirectoriesRemoval(schema.deprecatedDirs, ctx.cwd);\n actions.push(...deprecatedDirectories.actions);\n wouldRemove.push(...deprecatedDirectories.removed);\n\n // 5. chmod\n const chmodPathsUpgrade = [\n '.safeword/hooks',\n '.safeword/hooks/cursor',\n '.safeword/lib',\n '.safeword/scripts',\n ];\n actions.push({ type: 'chmod', paths: chmodPathsUpgrade });\n\n // 6. JSON merges (always apply to ensure keys are present)\n for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-merge', path: filePath, definition });\n }\n\n // 7. Text patches (only if marker missing, skip .husky in non-git repos)\n actions.push(...planTextPatches(schema.textPatches, ctx.cwd, ctx.isGitRepo));\n\n // 8. Compute packages to install (husky/lint-staged skipped if no git repo)\n const packagesToInstall = computePackagesToInstall(\n schema,\n ctx.projectType,\n ctx.developmentDeps,\n ctx.isGitRepo,\n );\n\n // 9. Compute deprecated packages to remove (only those actually installed)\n const packagesToRemove = schema.deprecatedPackages.filter(pkg => pkg in ctx.developmentDeps);\n\n return {\n actions,\n wouldCreate,\n wouldUpdate,\n wouldRemove,\n packagesToInstall,\n packagesToRemove,\n };\n}\n\n/**\n *\n * @param schema\n * @param ctx\n * @param full\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 and track parent dirs for cleanup\n const ownedFiles = planExistingFilesRemoval(Object.keys(schema.ownedFiles), ctx.cwd);\n actions.push(...ownedFiles.actions);\n wouldRemove.push(...ownedFiles.removed);\n\n // Collect parent dirs that need cleanup (for .claude/* skill dirs)\n const directoriesToCleanup = new Set<string>();\n for (const filePath of ownedFiles.removed) {\n const parentDirectory = getClaudeParentDirectoryForCleanup(filePath);\n if (parentDirectory) directoriesToCleanup.add(parentDirectory);\n }\n const cleanupDirectories = planExistingDirectoriesRemoval([...directoriesToCleanup], ctx.cwd);\n actions.push(...cleanupDirectories.actions);\n wouldRemove.push(...cleanupDirectories.removed);\n\n // 2. JSON unmerges\n for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-unmerge', path: filePath, definition });\n }\n\n // 3. Text unpatches\n for (const [filePath, definition] of Object.entries(schema.textPatches)) {\n const fullPath = nodePath.join(ctx.cwd, filePath);\n if (exists(fullPath)) {\n const content = readFileSafe(fullPath) ?? '';\n if (content.includes(definition.marker)) {\n actions.push({ type: 'text-unpatch', path: filePath, definition });\n }\n }\n }\n\n // 4. Remove preserved directories first (reverse order, only if empty)\n const preserved = planExistingDirectoriesRemoval(schema.preservedDirs.toReversed(), ctx.cwd);\n actions.push(...preserved.actions);\n wouldRemove.push(...preserved.removed);\n\n // 5. Remove owned directories (reverse order ensures children before parents)\n const owned = planExistingDirectoriesRemoval(schema.ownedDirs.toReversed(), ctx.cwd);\n actions.push(...owned.actions);\n wouldRemove.push(...owned.removed);\n\n // 6. Full uninstall: remove managed files\n if (full) {\n const managed = planExistingFilesRemoval(Object.keys(schema.managedFiles), ctx.cwd);\n actions.push(...managed.actions);\n wouldRemove.push(...managed.removed);\n }\n\n // 7. Compute packages to remove (full only)\n const packagesToRemove = full\n ? computePackagesToRemove(schema, ctx.projectType, ctx.developmentDeps)\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\n/**\n *\n * @param plan\n * @param ctx\n */\nfunction executePlan(plan: ReconcilePlan, ctx: ProjectContext): ExecutionResult {\n const created: string[] = [];\n const updated: string[] = [];\n const removed: string[] = [];\n const result = { created, updated, removed };\n\n for (const action of plan.actions) {\n executeAction(action, ctx, result);\n }\n\n return result;\n}\n\n/**\n *\n * @param action\n * @param ctx\n * @param result\n */\nfunction executeAction(action: Action, ctx: ProjectContext, result: ExecutionResult): void {\n switch (action.type) {\n case 'mkdir': {\n ensureDirectory(nodePath.join(ctx.cwd, action.path));\n result.created.push(action.path);\n break;\n }\n\n case 'rmdir': {\n // Use removeIfEmpty to preserve directories with user content\n if (removeIfEmpty(nodePath.join(ctx.cwd, action.path))) {\n result.removed.push(action.path);\n }\n break;\n }\n\n case 'write': {\n executeWrite(ctx.cwd, action.path, action.content, result);\n break;\n }\n\n case 'rm': {\n remove(nodePath.join(ctx.cwd, action.path));\n result.removed.push(action.path);\n break;\n }\n\n case 'chmod': {\n for (const path of action.paths) {\n const fullPath = nodePath.join(ctx.cwd, path);\n if (exists(fullPath)) makeScriptsExecutable(fullPath);\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/**\n *\n * @param cwd\n * @param path\n * @param content\n * @param result\n */\nfunction executeWrite(cwd: string, path: string, content: string, result: ExecutionResult): void {\n const fullPath = nodePath.join(cwd, path);\n const existed = exists(fullPath);\n writeFile(fullPath, content);\n (existed ? result.updated : result.created).push(path);\n}\n\n// ============================================================================\n// Helper functions\n// ============================================================================\n\n/**\n *\n * @param definition\n * @param ctx\n */\nfunction resolveFileContent(definition: FileDefinition, ctx: ProjectContext): string {\n if (definition.template) {\n const templatesDirectory = getTemplatesDirectory();\n return readFile(nodePath.join(templatesDirectory, definition.template));\n }\n\n if (definition.content) {\n return typeof definition.content === 'function' ? definition.content() : definition.content;\n }\n\n if (definition.generator) {\n return definition.generator(ctx);\n }\n\n throw new Error('FileDefinition must have template, content, or generator');\n}\n\n/**\n *\n * @param installedPath\n * @param newContent\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 = new Set(['husky', 'lint-staged']);\n\n/**\n *\n * @param schema\n * @param projectType\n * @param installedDevDeps\n * @param isGitRepo\n */\nexport function computePackagesToInstall(\n schema: SafewordSchema,\n projectType: ProjectType,\n installedDevelopmentDeps: 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.has(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 installedDevelopmentDeps));\n}\n\n/**\n *\n * @param schema\n * @param projectType\n * @param installedDevDeps\n */\nfunction computePackagesToRemove(\n schema: SafewordSchema,\n projectType: ProjectType,\n installedDevelopmentDeps: 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 installedDevelopmentDeps);\n}\n\n/**\n *\n * @param cwd\n * @param path\n * @param definition\n * @param ctx\n */\nfunction executeJsonMerge(\n cwd: string,\n path: string,\n definition: JsonMergeDefinition,\n ctx: ProjectContext,\n): void {\n const fullPath = nodePath.join(cwd, path);\n const existing = readJson<Record<string, unknown>>(fullPath) ?? {};\n const merged = definition.merge(existing, ctx);\n\n // Skip write if content is unchanged (avoids formatting churn)\n if (JSON.stringify(existing) === JSON.stringify(merged)) return;\n\n writeJson(fullPath, merged);\n}\n\n/**\n *\n * @param cwd\n * @param path\n * @param definition\n */\nfunction executeJsonUnmerge(cwd: string, path: string, definition: JsonMergeDefinition): void {\n const fullPath = nodePath.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 = definition.unmerge(existing);\n\n // Check if file should be removed\n if (definition.removeFileIfEmpty) {\n const remainingKeys = Object.keys(unmerged).filter(k => unmerged[k] !== undefined);\n if (remainingKeys.length === 0) {\n remove(fullPath);\n return;\n }\n }\n\n writeJson(fullPath, unmerged);\n}\n\n/**\n *\n * @param cwd\n * @param path\n * @param definition\n */\nfunction executeTextPatch(cwd: string, path: string, definition: TextPatchDefinition): void {\n const fullPath = nodePath.join(cwd, path);\n let content = readFileSafe(fullPath) ?? '';\n\n // Check if already patched\n if (content.includes(definition.marker)) return;\n\n // Apply patch\n content =\n definition.operation === 'prepend'\n ? definition.content + content\n : content + definition.content;\n\n writeFile(fullPath, content);\n}\n\n/**\n *\n * @param cwd\n * @param path\n * @param definition\n */\nfunction executeTextUnpatch(cwd: string, path: string, definition: TextPatchDefinition): void {\n const fullPath = nodePath.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(definition.content, '');\n\n // If full content wasn't found but marker exists, remove lines containing the marker\n if (unpatched === content && content.includes(definition.marker)) {\n // Remove lines containing the marker\n const lines = content.split('\\n');\n const filtered = lines.filter(line => !line.includes(definition.marker));\n unpatched = filtered.join('\\n').replace(/^\\n+/, ''); // Remove leading empty lines\n }\n\n writeFile(fullPath, unpatched);\n}\n","/**\n * Git utilities for CLI operations\n */\n\nimport nodePath from 'node:path';\n\nimport { exists } from './fs.js';\n\n/**\n * Check if directory is a git repository\n * @param cwd\n */\nexport function isGitRepo(cwd: string): boolean {\n return exists(nodePath.join(cwd, '.git'));\n}\n","/**\n * Project Context Utilities\n *\n * Shared helpers for creating ProjectContext objects used by reconcile().\n */\n\nimport nodePath from 'node:path';\n\nimport type { ProjectContext } from '../schema.js';\nimport { readJson } from './fs.js';\nimport { isGitRepo } from './git.js';\nimport { detectProjectType, type PackageJson } from './project-detector.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 * @param cwd\n */\nexport function createProjectContext(cwd: string): ProjectContext {\n const packageJson = readJson<PackageJson>(nodePath.join(cwd, 'package.json'));\n\n return {\n cwd,\n projectType: detectProjectType(packageJson ?? {}, cwd),\n developmentDeps: packageJson?.devDependencies ?? {},\n isGitRepo: isGitRepo(cwd),\n };\n}\n","/**\n * Console output utilities for consistent CLI messaging\n */\n\n/**\n * Print info message\n * @param message\n */\nexport function info(message: string): void {\n console.log(message);\n}\n\n/**\n * Print success message\n * @param message\n */\nexport function success(message: string): void {\n console.log(`✓ ${message}`);\n}\n\n/**\n * Print warning message\n * @param message\n */\nexport function warn(message: string): void {\n console.warn(`⚠ ${message}`);\n}\n\n/**\n * Print error message to stderr\n * @param message\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 * @param title\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 * @param item\n * @param indent\n */\nexport function listItem(item: string, indent = 2): void {\n console.log(`${' '.repeat(indent)}• ${item}`);\n}\n\n/**\n * Print key-value pair\n * @param key\n * @param value\n */\nexport function keyValue(key: string, value: string): void {\n console.log(` ${key}: ${value}`);\n}\n"],"mappings":"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","names":["isGitRepo","nodePath","nodePath","nodePath","nodePath"]}