safeword 0.6.3 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/{check-PECCGHEA.js → check-OYYSYHFP.js} +41 -23
  2. package/dist/check-OYYSYHFP.js.map +1 -0
  3. package/dist/chunk-LNSEDZIW.js +454 -0
  4. package/dist/chunk-LNSEDZIW.js.map +1 -0
  5. package/dist/chunk-ZS3Z3Q37.js +729 -0
  6. package/dist/chunk-ZS3Z3Q37.js.map +1 -0
  7. package/dist/cli.js +7 -7
  8. package/dist/cli.js.map +1 -1
  9. package/dist/diff-325TIZ63.js +168 -0
  10. package/dist/diff-325TIZ63.js.map +1 -0
  11. package/dist/reset-ZGJIKMUW.js +74 -0
  12. package/dist/reset-ZGJIKMUW.js.map +1 -0
  13. package/dist/setup-GAMXTFM2.js +103 -0
  14. package/dist/setup-GAMXTFM2.js.map +1 -0
  15. package/dist/{sync-4XBMKLXS.js → sync-BFMXZEHM.js} +33 -32
  16. package/dist/sync-BFMXZEHM.js.map +1 -0
  17. package/dist/upgrade-X4GREJXN.js +73 -0
  18. package/dist/upgrade-X4GREJXN.js.map +1 -0
  19. package/package.json +15 -14
  20. package/templates/SAFEWORD.md +101 -689
  21. package/templates/guides/architecture-guide.md +1 -1
  22. package/templates/guides/cli-reference.md +35 -0
  23. package/templates/guides/code-philosophy.md +22 -19
  24. package/templates/guides/context-files-guide.md +2 -2
  25. package/templates/guides/data-architecture-guide.md +1 -1
  26. package/templates/guides/design-doc-guide.md +1 -1
  27. package/templates/guides/{testing-methodology.md → development-workflow.md} +1 -1
  28. package/templates/guides/learning-extraction.md +1 -1
  29. package/templates/guides/{llm-instruction-design.md → llm-guide.md} +93 -29
  30. package/templates/guides/tdd-best-practices.md +2 -2
  31. package/templates/guides/test-definitions-guide.md +1 -1
  32. package/templates/guides/user-story-guide.md +1 -1
  33. package/templates/guides/zombie-process-cleanup.md +1 -1
  34. package/dist/check-PECCGHEA.js.map +0 -1
  35. package/dist/chunk-6CVTH67L.js +0 -43
  36. package/dist/chunk-6CVTH67L.js.map +0 -1
  37. package/dist/chunk-75FKNZUM.js +0 -15
  38. package/dist/chunk-75FKNZUM.js.map +0 -1
  39. package/dist/chunk-ARIAOK2F.js +0 -110
  40. package/dist/chunk-ARIAOK2F.js.map +0 -1
  41. package/dist/chunk-FRPJITGG.js +0 -35
  42. package/dist/chunk-FRPJITGG.js.map +0 -1
  43. package/dist/chunk-IWWBZVHT.js +0 -274
  44. package/dist/chunk-IWWBZVHT.js.map +0 -1
  45. package/dist/diff-ZACVJKOU.js +0 -171
  46. package/dist/diff-ZACVJKOU.js.map +0 -1
  47. package/dist/reset-5SRM3P6J.js +0 -145
  48. package/dist/reset-5SRM3P6J.js.map +0 -1
  49. package/dist/setup-65EVU5OT.js +0 -437
  50. package/dist/setup-65EVU5OT.js.map +0 -1
  51. package/dist/sync-4XBMKLXS.js.map +0 -1
  52. package/dist/upgrade-P3WX3ODU.js +0 -153
  53. package/dist/upgrade-P3WX3ODU.js.map +0 -1
  54. package/templates/guides/llm-prompting.md +0 -102
  55. /package/templates/prompts/{review.md → quality-review.md} +0 -0
@@ -0,0 +1,729 @@
1
+ import {
2
+ VERSION
3
+ } from "./chunk-ORQHKDT2.js";
4
+
5
+ // src/utils/fs.ts
6
+ import {
7
+ existsSync,
8
+ mkdirSync,
9
+ readFileSync,
10
+ writeFileSync,
11
+ rmSync,
12
+ rmdirSync,
13
+ readdirSync,
14
+ chmodSync
15
+ } from "fs";
16
+ import { join, dirname } from "path";
17
+ import { fileURLToPath } from "url";
18
+ var __dirname = dirname(fileURLToPath(import.meta.url));
19
+ function getTemplatesDir() {
20
+ const knownTemplateFile = "SAFEWORD.md";
21
+ const candidates = [
22
+ join(__dirname, "..", "templates"),
23
+ // From dist/ (flat bundled)
24
+ join(__dirname, "..", "..", "templates"),
25
+ // From src/utils/ or dist/utils/
26
+ join(__dirname, "templates")
27
+ // Direct sibling (unlikely but safe)
28
+ ];
29
+ for (const candidate of candidates) {
30
+ if (existsSync(join(candidate, knownTemplateFile))) {
31
+ return candidate;
32
+ }
33
+ }
34
+ throw new Error("Templates directory not found");
35
+ }
36
+ function exists(path) {
37
+ return existsSync(path);
38
+ }
39
+ function ensureDir(path) {
40
+ if (!existsSync(path)) {
41
+ mkdirSync(path, { recursive: true });
42
+ }
43
+ }
44
+ function readFile(path) {
45
+ return readFileSync(path, "utf-8");
46
+ }
47
+ function readFileSafe(path) {
48
+ if (!existsSync(path)) return null;
49
+ return readFileSync(path, "utf-8");
50
+ }
51
+ function writeFile(path, content) {
52
+ ensureDir(dirname(path));
53
+ writeFileSync(path, content);
54
+ }
55
+ function remove(path) {
56
+ if (existsSync(path)) {
57
+ rmSync(path, { recursive: true, force: true });
58
+ }
59
+ }
60
+ function removeIfEmpty(path) {
61
+ if (!existsSync(path)) return false;
62
+ try {
63
+ rmdirSync(path);
64
+ return true;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+ function makeScriptsExecutable(dirPath) {
70
+ if (!existsSync(dirPath)) return;
71
+ for (const file of readdirSync(dirPath)) {
72
+ if (file.endsWith(".sh")) {
73
+ chmodSync(join(dirPath, file), 493);
74
+ }
75
+ }
76
+ }
77
+ function readJson(path) {
78
+ const content = readFileSafe(path);
79
+ if (!content) return null;
80
+ try {
81
+ return JSON.parse(content);
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+ function writeJson(path, data) {
87
+ writeFile(path, JSON.stringify(data, null, 2) + "\n");
88
+ }
89
+
90
+ // src/utils/project-detector.ts
91
+ function detectProjectType(packageJson) {
92
+ const deps = packageJson.dependencies || {};
93
+ const devDeps = packageJson.devDependencies || {};
94
+ const allDeps = { ...deps, ...devDeps };
95
+ const hasTypescript = "typescript" in allDeps;
96
+ const hasReact = "react" in deps || "react" in devDeps;
97
+ const hasNextJs = "next" in deps;
98
+ const hasAstro = "astro" in deps || "astro" in devDeps;
99
+ const hasVue = "vue" in deps || "vue" in devDeps;
100
+ const hasNuxt = "nuxt" in deps;
101
+ const hasSvelte = "svelte" in deps || "svelte" in devDeps;
102
+ const hasSvelteKit = "@sveltejs/kit" in deps || "@sveltejs/kit" in devDeps;
103
+ const hasElectron = "electron" in deps || "electron" in devDeps;
104
+ const hasVitest = "vitest" in devDeps;
105
+ const hasPlaywright = "@playwright/test" in devDeps;
106
+ const hasTailwind = "tailwindcss" in allDeps;
107
+ const hasEntryPoints = !!(packageJson.main || packageJson.module || packageJson.exports);
108
+ const isPublishable = hasEntryPoints && packageJson.private !== true;
109
+ return {
110
+ typescript: hasTypescript,
111
+ react: hasReact || hasNextJs,
112
+ // Next.js implies React
113
+ nextjs: hasNextJs,
114
+ astro: hasAstro,
115
+ vue: hasVue || hasNuxt,
116
+ // Nuxt implies Vue
117
+ nuxt: hasNuxt,
118
+ svelte: hasSvelte || hasSvelteKit,
119
+ // SvelteKit implies Svelte
120
+ sveltekit: hasSvelteKit,
121
+ electron: hasElectron,
122
+ vitest: hasVitest,
123
+ playwright: hasPlaywright,
124
+ tailwind: hasTailwind,
125
+ publishableLibrary: isPublishable
126
+ };
127
+ }
128
+
129
+ // src/templates/content.ts
130
+ var AGENTS_MD_LINK = `**\u26A0\uFE0F ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**
131
+
132
+ The SAFEWORD.md file contains core development patterns, workflows, and conventions.
133
+ Read it BEFORE working on any task in this project.
134
+
135
+ ---`;
136
+ function getPrettierConfig(projectType) {
137
+ const config = {
138
+ semi: true,
139
+ singleQuote: true,
140
+ tabWidth: 2,
141
+ trailingComma: "es5",
142
+ printWidth: 100,
143
+ endOfLine: "lf"
144
+ };
145
+ const plugins = [];
146
+ if (projectType.astro) plugins.push("prettier-plugin-astro");
147
+ if (projectType.svelte) plugins.push("prettier-plugin-svelte");
148
+ if (projectType.tailwind) plugins.push("prettier-plugin-tailwindcss");
149
+ if (plugins.length > 0) {
150
+ config.plugins = plugins;
151
+ }
152
+ return JSON.stringify(config, null, 2) + "\n";
153
+ }
154
+ var LINT_STAGED_CONFIG = {
155
+ "*.{js,jsx,ts,tsx,mjs,mts,cjs,cts}": ["eslint --fix", "prettier --write"],
156
+ "*.{vue,svelte,astro}": ["eslint --fix", "prettier --write"],
157
+ "*.{json,css,scss,html,yaml,yml,graphql}": ["prettier --write"],
158
+ "*.md": ["markdownlint-cli2 --fix", "prettier --write"]
159
+ };
160
+
161
+ // src/templates/config.ts
162
+ function getEslintConfig(options) {
163
+ return `import { readFileSync } from "fs";
164
+ import { defineConfig } from "eslint/config";
165
+ import js from "@eslint/js";
166
+ import { importX } from "eslint-plugin-import-x";
167
+ import sonarjs from "eslint-plugin-sonarjs";
168
+ import sdl from "@microsoft/eslint-plugin-sdl";
169
+ import playwright from "eslint-plugin-playwright";
170
+ import eslintConfigPrettier from "eslint-config-prettier";
171
+ ${options.boundaries ? 'import boundariesConfig from "./.safeword/eslint-boundaries.config.mjs";' : ""}
172
+
173
+ // Read package.json to detect frameworks at runtime
174
+ const pkg = JSON.parse(readFileSync("./package.json", "utf8"));
175
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
176
+
177
+ // Build dynamic ignores based on detected frameworks
178
+ const ignores = ["node_modules/", "dist/", "build/", "coverage/"];
179
+ if (deps["next"]) ignores.push(".next/");
180
+ if (deps["astro"]) ignores.push(".astro/");
181
+ if (deps["vue"] || deps["nuxt"]) ignores.push(".nuxt/");
182
+ if (deps["svelte"] || deps["@sveltejs/kit"]) ignores.push(".svelte-kit/");
183
+
184
+ // Start with base configs (always loaded)
185
+ const configs = [
186
+ { ignores },
187
+ js.configs.recommended,
188
+ importX.flatConfigs.recommended,
189
+ sonarjs.configs.recommended,
190
+ ...sdl.configs.recommended,
191
+ ];
192
+
193
+ // TypeScript support (detected from package.json)
194
+ if (deps["typescript"]) {
195
+ const tseslint = await import("typescript-eslint");
196
+ configs.push(importX.flatConfigs.typescript);
197
+ configs.push(...tseslint.default.configs.recommended);
198
+ }
199
+
200
+ // React/Next.js support
201
+ if (deps["react"] || deps["next"]) {
202
+ const react = await import("eslint-plugin-react");
203
+ const reactHooks = await import("eslint-plugin-react-hooks");
204
+ const jsxA11y = await import("eslint-plugin-jsx-a11y");
205
+ configs.push(react.default.configs.flat.recommended);
206
+ configs.push(react.default.configs.flat["jsx-runtime"]);
207
+ configs.push({
208
+ name: "react-hooks",
209
+ plugins: { "react-hooks": reactHooks.default },
210
+ rules: reactHooks.default.configs.recommended.rules,
211
+ });
212
+ configs.push(jsxA11y.default.flatConfigs.recommended);
213
+ }
214
+
215
+ // Next.js plugin
216
+ if (deps["next"]) {
217
+ const nextPlugin = await import("@next/eslint-plugin-next");
218
+ configs.push({
219
+ name: "nextjs",
220
+ plugins: { "@next/next": nextPlugin.default },
221
+ rules: nextPlugin.default.configs.recommended.rules,
222
+ });
223
+ }
224
+
225
+ // Astro support
226
+ if (deps["astro"]) {
227
+ const astro = await import("eslint-plugin-astro");
228
+ configs.push(...astro.default.configs.recommended);
229
+ }
230
+
231
+ // Vue support
232
+ if (deps["vue"] || deps["nuxt"]) {
233
+ const vue = await import("eslint-plugin-vue");
234
+ configs.push(...vue.default.configs["flat/recommended"]);
235
+ }
236
+
237
+ // Svelte support
238
+ if (deps["svelte"] || deps["@sveltejs/kit"]) {
239
+ const svelte = await import("eslint-plugin-svelte");
240
+ configs.push(...svelte.default.configs.recommended);
241
+ }
242
+
243
+ // Electron support
244
+ if (deps["electron"]) {
245
+ const electron = await import("@electron-toolkit/eslint-config");
246
+ configs.push(electron.default);
247
+ }
248
+
249
+ // Vitest support (scoped to test files)
250
+ if (deps["vitest"]) {
251
+ const vitest = await import("@vitest/eslint-plugin");
252
+ configs.push({
253
+ name: "vitest",
254
+ files: ["**/*.test.{js,ts,jsx,tsx}", "**/*.spec.{js,ts,jsx,tsx}", "**/tests/**"],
255
+ plugins: { vitest: vitest.default },
256
+ languageOptions: {
257
+ globals: { ...vitest.default.environments.env.globals },
258
+ },
259
+ rules: { ...vitest.default.configs.recommended.rules },
260
+ });
261
+ }
262
+
263
+ // Playwright for e2e tests (always included - safeword sets up Playwright)
264
+ configs.push({
265
+ name: "playwright",
266
+ files: ["**/e2e/**", "**/*.e2e.{js,ts,jsx,tsx}", "**/playwright/**"],
267
+ ...playwright.configs["flat/recommended"],
268
+ });
269
+
270
+ // Architecture boundaries${options.boundaries ? "\nconfigs.push(boundariesConfig);" : ""}
271
+
272
+ // eslint-config-prettier must be last to disable conflicting rules
273
+ configs.push(eslintConfigPrettier);
274
+
275
+ export default defineConfig(configs);
276
+ `;
277
+ }
278
+ var SETTINGS_HOOKS = {
279
+ SessionStart: [
280
+ {
281
+ hooks: [
282
+ {
283
+ type: "command",
284
+ command: '"$CLAUDE_PROJECT_DIR"/.safeword/hooks/session-verify-agents.sh'
285
+ }
286
+ ]
287
+ },
288
+ {
289
+ hooks: [
290
+ {
291
+ type: "command",
292
+ command: '"$CLAUDE_PROJECT_DIR"/.safeword/hooks/session-version.sh'
293
+ }
294
+ ]
295
+ },
296
+ {
297
+ hooks: [
298
+ {
299
+ type: "command",
300
+ command: '"$CLAUDE_PROJECT_DIR"/.safeword/hooks/session-lint-check.sh'
301
+ }
302
+ ]
303
+ }
304
+ ],
305
+ UserPromptSubmit: [
306
+ {
307
+ hooks: [
308
+ {
309
+ type: "command",
310
+ command: '"$CLAUDE_PROJECT_DIR"/.safeword/hooks/prompt-timestamp.sh'
311
+ }
312
+ ]
313
+ },
314
+ {
315
+ hooks: [
316
+ {
317
+ type: "command",
318
+ command: '"$CLAUDE_PROJECT_DIR"/.safeword/hooks/prompt-questions.sh'
319
+ }
320
+ ]
321
+ }
322
+ ],
323
+ Stop: [
324
+ {
325
+ hooks: [
326
+ {
327
+ type: "command",
328
+ command: '"$CLAUDE_PROJECT_DIR"/.safeword/hooks/stop-quality.sh'
329
+ }
330
+ ]
331
+ }
332
+ ],
333
+ PostToolUse: [
334
+ {
335
+ matcher: "Write|Edit|MultiEdit|NotebookEdit",
336
+ hooks: [
337
+ {
338
+ type: "command",
339
+ command: '"$CLAUDE_PROJECT_DIR"/.safeword/hooks/post-tool-lint.sh'
340
+ }
341
+ ]
342
+ }
343
+ ]
344
+ };
345
+
346
+ // src/utils/boundaries.ts
347
+ import { join as join2 } from "path";
348
+ var ARCHITECTURE_DIRS = [
349
+ "types",
350
+ // Bottom: can be imported by everything
351
+ "utils",
352
+ "lib",
353
+ "hooks",
354
+ "services",
355
+ "components",
356
+ "features",
357
+ "modules",
358
+ "app"
359
+ // Top: can import everything
360
+ ];
361
+ var HIERARCHY = {
362
+ types: [],
363
+ // types can't import anything (pure type definitions)
364
+ utils: ["types"],
365
+ lib: ["utils", "types"],
366
+ hooks: ["lib", "utils", "types"],
367
+ services: ["lib", "utils", "types"],
368
+ components: ["hooks", "services", "lib", "utils", "types"],
369
+ features: ["components", "hooks", "services", "lib", "utils", "types"],
370
+ modules: ["components", "hooks", "services", "lib", "utils", "types"],
371
+ app: ["features", "modules", "components", "hooks", "services", "lib", "utils", "types"]
372
+ };
373
+ function detectArchitecture(projectDir) {
374
+ const foundInSrc = [];
375
+ const foundAtRoot = [];
376
+ for (const dir of ARCHITECTURE_DIRS) {
377
+ if (exists(join2(projectDir, "src", dir))) {
378
+ foundInSrc.push(dir);
379
+ }
380
+ if (exists(join2(projectDir, dir))) {
381
+ foundAtRoot.push(dir);
382
+ }
383
+ }
384
+ const inSrc = foundInSrc.length >= foundAtRoot.length;
385
+ const found = inSrc ? foundInSrc : foundAtRoot;
386
+ return { directories: found, inSrc };
387
+ }
388
+ function generateBoundariesConfig(arch) {
389
+ const prefix = arch.inSrc ? "src/" : "";
390
+ const hasDirectories = arch.directories.length > 0;
391
+ const elements = arch.directories.map((dir) => ` { type: '${dir}', pattern: '${prefix}${dir}/**', mode: 'full' }`).join(",\n");
392
+ const rules = arch.directories.filter((dir) => HIERARCHY[dir].length > 0).map((dir) => {
393
+ const allowed = HIERARCHY[dir].filter((dep) => arch.directories.includes(dep));
394
+ if (allowed.length === 0) return null;
395
+ return ` { from: ['${dir}'], allow: [${allowed.map((d) => `'${d}'`).join(", ")}] }`;
396
+ }).filter(Boolean).join(",\n");
397
+ const detectedInfo = hasDirectories ? `Detected directories: ${arch.directories.join(", ")} (${arch.inSrc ? "in src/" : "at root"})` : "No architecture directories detected yet - add types/, utils/, components/, etc.";
398
+ const elementsContent = elements || "";
399
+ const rulesContent = rules || "";
400
+ return `/**
401
+ * Architecture Boundaries Configuration (AUTO-GENERATED)
402
+ *
403
+ * ${detectedInfo}
404
+ *
405
+ * This enforces import boundaries between architectural layers:
406
+ * - Lower layers (types, utils) cannot import from higher layers (components, features)
407
+ * - Uses 'warn' severity - informative, not blocking
408
+ *
409
+ * Recognized directories (in hierarchy order):
410
+ * types \u2192 utils \u2192 lib \u2192 hooks/services \u2192 components \u2192 features/modules \u2192 app
411
+ *
412
+ * To customize, override in your eslint.config.mjs:
413
+ * rules: { 'boundaries/element-types': ['error', { ... }] }
414
+ */
415
+
416
+ import boundaries from 'eslint-plugin-boundaries';
417
+
418
+ export default {
419
+ plugins: { boundaries },
420
+ settings: {
421
+ 'boundaries/elements': [
422
+ ${elementsContent}
423
+ ],
424
+ },
425
+ rules: {
426
+ 'boundaries/element-types': ['warn', {
427
+ default: 'disallow',
428
+ rules: [
429
+ ${rulesContent}
430
+ ],
431
+ }],
432
+ 'boundaries/no-unknown': 'off', // Allow files outside defined elements
433
+ 'boundaries/no-unknown-files': 'off', // Allow non-matching files
434
+ },
435
+ };
436
+ `;
437
+ }
438
+
439
+ // src/utils/install.ts
440
+ var HUSKY_PRE_COMMIT_CONTENT = "npx safeword sync --quiet --stage\nnpx lint-staged\n";
441
+ var MCP_SERVERS = {
442
+ context7: {
443
+ command: "npx",
444
+ args: ["-y", "@upstash/context7-mcp@latest"]
445
+ },
446
+ playwright: {
447
+ command: "npx",
448
+ args: ["@playwright/mcp@latest"]
449
+ }
450
+ };
451
+
452
+ // src/utils/hooks.ts
453
+ function isHookEntry(h) {
454
+ return typeof h === "object" && h !== null && "hooks" in h && Array.isArray(h.hooks);
455
+ }
456
+ function isSafewordHook(h) {
457
+ if (!isHookEntry(h)) return false;
458
+ return h.hooks.some(
459
+ (cmd) => typeof cmd.command === "string" && cmd.command.includes(".safeword")
460
+ );
461
+ }
462
+ function filterOutSafewordHooks(hooks) {
463
+ return hooks.filter((h) => !isSafewordHook(h));
464
+ }
465
+
466
+ // src/schema.ts
467
+ var SAFEWORD_SCHEMA = {
468
+ version: VERSION,
469
+ // Directories fully owned by safeword (created on setup, deleted on reset)
470
+ ownedDirs: [
471
+ ".safeword",
472
+ ".safeword/hooks",
473
+ ".safeword/lib",
474
+ ".safeword/guides",
475
+ ".safeword/templates",
476
+ ".safeword/prompts",
477
+ ".safeword/planning",
478
+ ".safeword/planning/user-stories",
479
+ ".safeword/planning/test-definitions",
480
+ ".safeword/planning/design",
481
+ ".safeword/planning/issues",
482
+ ".husky"
483
+ ],
484
+ // Directories we add to but don't own (not deleted on reset)
485
+ sharedDirs: [".claude", ".claude/skills", ".claude/commands"],
486
+ // Created on setup but NOT deleted on reset (preserves user data)
487
+ preservedDirs: [".safeword/learnings", ".safeword/tickets", ".safeword/tickets/completed"],
488
+ // Files owned by safeword (overwritten on upgrade if content changed)
489
+ ownedFiles: {
490
+ // Core files
491
+ ".safeword/SAFEWORD.md": { template: "SAFEWORD.md" },
492
+ ".safeword/version": { content: () => VERSION },
493
+ ".safeword/eslint-boundaries.config.mjs": {
494
+ generator: (ctx) => generateBoundariesConfig(detectArchitecture(ctx.cwd))
495
+ },
496
+ // Hooks (7 files)
497
+ ".safeword/hooks/session-verify-agents.sh": { template: "hooks/session-verify-agents.sh" },
498
+ ".safeword/hooks/session-version.sh": { template: "hooks/session-version.sh" },
499
+ ".safeword/hooks/session-lint-check.sh": { template: "hooks/session-lint-check.sh" },
500
+ ".safeword/hooks/prompt-timestamp.sh": { template: "hooks/prompt-timestamp.sh" },
501
+ ".safeword/hooks/prompt-questions.sh": { template: "hooks/prompt-questions.sh" },
502
+ ".safeword/hooks/post-tool-lint.sh": { template: "hooks/post-tool-lint.sh" },
503
+ ".safeword/hooks/stop-quality.sh": { template: "hooks/stop-quality.sh" },
504
+ // Lib (2 files)
505
+ ".safeword/lib/common.sh": { template: "lib/common.sh" },
506
+ ".safeword/lib/jq-fallback.sh": { template: "lib/jq-fallback.sh" },
507
+ // Guides (13 files)
508
+ ".safeword/guides/architecture-guide.md": { template: "guides/architecture-guide.md" },
509
+ ".safeword/guides/cli-reference.md": { template: "guides/cli-reference.md" },
510
+ ".safeword/guides/code-philosophy.md": { template: "guides/code-philosophy.md" },
511
+ ".safeword/guides/context-files-guide.md": { template: "guides/context-files-guide.md" },
512
+ ".safeword/guides/data-architecture-guide.md": {
513
+ template: "guides/data-architecture-guide.md"
514
+ },
515
+ ".safeword/guides/design-doc-guide.md": { template: "guides/design-doc-guide.md" },
516
+ ".safeword/guides/development-workflow.md": { template: "guides/development-workflow.md" },
517
+ ".safeword/guides/learning-extraction.md": { template: "guides/learning-extraction.md" },
518
+ ".safeword/guides/llm-guide.md": { template: "guides/llm-guide.md" },
519
+ ".safeword/guides/tdd-best-practices.md": { template: "guides/tdd-best-practices.md" },
520
+ ".safeword/guides/test-definitions-guide.md": { template: "guides/test-definitions-guide.md" },
521
+ ".safeword/guides/user-story-guide.md": { template: "guides/user-story-guide.md" },
522
+ ".safeword/guides/zombie-process-cleanup.md": { template: "guides/zombie-process-cleanup.md" },
523
+ // Templates (5 files)
524
+ ".safeword/templates/architecture-template.md": {
525
+ template: "doc-templates/architecture-template.md"
526
+ },
527
+ ".safeword/templates/design-doc-template.md": {
528
+ template: "doc-templates/design-doc-template.md"
529
+ },
530
+ ".safeword/templates/test-definitions-feature.md": {
531
+ template: "doc-templates/test-definitions-feature.md"
532
+ },
533
+ ".safeword/templates/ticket-template.md": { template: "doc-templates/ticket-template.md" },
534
+ ".safeword/templates/user-stories-template.md": {
535
+ template: "doc-templates/user-stories-template.md"
536
+ },
537
+ // Prompts (2 files)
538
+ ".safeword/prompts/architecture.md": { template: "prompts/architecture.md" },
539
+ ".safeword/prompts/quality-review.md": { template: "prompts/quality-review.md" },
540
+ // Claude skills and commands (4 files)
541
+ ".claude/skills/safeword-quality-reviewer/SKILL.md": {
542
+ template: "skills/safeword-quality-reviewer/SKILL.md"
543
+ },
544
+ ".claude/commands/architecture.md": { template: "commands/architecture.md" },
545
+ ".claude/commands/lint.md": { template: "commands/lint.md" },
546
+ ".claude/commands/quality-review.md": { template: "commands/quality-review.md" },
547
+ // Husky (1 file)
548
+ ".husky/pre-commit": { content: HUSKY_PRE_COMMIT_CONTENT }
549
+ },
550
+ // Files created if missing, updated only if content matches current template
551
+ managedFiles: {
552
+ "eslint.config.mjs": {
553
+ generator: () => getEslintConfig({ boundaries: true })
554
+ },
555
+ ".prettierrc": { generator: (ctx) => getPrettierConfig(ctx.projectType) },
556
+ ".markdownlint-cli2.jsonc": { template: "markdownlint-cli2.jsonc" }
557
+ },
558
+ // JSON files where we merge specific keys
559
+ jsonMerges: {
560
+ "package.json": {
561
+ keys: [
562
+ "scripts.lint",
563
+ "scripts.lint:md",
564
+ "scripts.format",
565
+ "scripts.format:check",
566
+ "scripts.knip",
567
+ "scripts.prepare",
568
+ "lint-staged"
569
+ ],
570
+ conditionalKeys: {
571
+ publishableLibrary: ["scripts.publint"]
572
+ },
573
+ merge: (existing, ctx) => {
574
+ const scripts = existing.scripts ?? {};
575
+ const result = { ...existing };
576
+ if (!scripts.lint) scripts.lint = "eslint .";
577
+ if (!scripts["lint:md"]) scripts["lint:md"] = 'markdownlint-cli2 "**/*.md" "#node_modules"';
578
+ if (!scripts.format) scripts.format = "prettier --write .";
579
+ if (!scripts["format:check"]) scripts["format:check"] = "prettier --check .";
580
+ if (!scripts.knip) scripts.knip = "knip";
581
+ if (!scripts.prepare) scripts.prepare = "husky || true";
582
+ if (ctx.projectType.publishableLibrary && !scripts.publint) {
583
+ scripts.publint = "publint";
584
+ }
585
+ result.scripts = scripts;
586
+ if (!existing["lint-staged"]) {
587
+ result["lint-staged"] = LINT_STAGED_CONFIG;
588
+ }
589
+ return result;
590
+ },
591
+ unmerge: (existing) => {
592
+ const result = { ...existing };
593
+ const scripts = { ...existing.scripts ?? {} };
594
+ delete scripts["lint:md"];
595
+ delete scripts["format:check"];
596
+ delete scripts.knip;
597
+ delete scripts.prepare;
598
+ delete scripts.publint;
599
+ if (Object.keys(scripts).length > 0) {
600
+ result.scripts = scripts;
601
+ } else {
602
+ delete result.scripts;
603
+ }
604
+ delete result["lint-staged"];
605
+ return result;
606
+ }
607
+ },
608
+ ".claude/settings.json": {
609
+ keys: ["hooks"],
610
+ merge: (existing) => {
611
+ const existingHooks = existing.hooks ?? {};
612
+ const mergedHooks = { ...existingHooks };
613
+ for (const [event, newHooks] of Object.entries(SETTINGS_HOOKS)) {
614
+ const eventHooks = mergedHooks[event] ?? [];
615
+ const nonSafewordHooks = filterOutSafewordHooks(eventHooks);
616
+ mergedHooks[event] = [...nonSafewordHooks, ...newHooks];
617
+ }
618
+ return { ...existing, hooks: mergedHooks };
619
+ },
620
+ unmerge: (existing) => {
621
+ const existingHooks = existing.hooks ?? {};
622
+ const cleanedHooks = {};
623
+ for (const [event, eventHooks] of Object.entries(existingHooks)) {
624
+ const nonSafewordHooks = filterOutSafewordHooks(eventHooks);
625
+ if (nonSafewordHooks.length > 0) {
626
+ cleanedHooks[event] = nonSafewordHooks;
627
+ }
628
+ }
629
+ const result = { ...existing };
630
+ if (Object.keys(cleanedHooks).length > 0) {
631
+ result.hooks = cleanedHooks;
632
+ } else {
633
+ delete result.hooks;
634
+ }
635
+ return result;
636
+ }
637
+ },
638
+ ".mcp.json": {
639
+ keys: ["mcpServers.context7", "mcpServers.playwright"],
640
+ removeFileIfEmpty: true,
641
+ merge: (existing) => {
642
+ const mcpServers = existing.mcpServers ?? {};
643
+ return {
644
+ ...existing,
645
+ mcpServers: {
646
+ ...mcpServers,
647
+ context7: MCP_SERVERS.context7,
648
+ playwright: MCP_SERVERS.playwright
649
+ }
650
+ };
651
+ },
652
+ unmerge: (existing) => {
653
+ const result = { ...existing };
654
+ const mcpServers = { ...existing.mcpServers ?? {} };
655
+ delete mcpServers.context7;
656
+ delete mcpServers.playwright;
657
+ if (Object.keys(mcpServers).length > 0) {
658
+ result.mcpServers = mcpServers;
659
+ } else {
660
+ delete result.mcpServers;
661
+ }
662
+ return result;
663
+ }
664
+ }
665
+ },
666
+ // Text files where we patch specific content
667
+ textPatches: {
668
+ "AGENTS.md": {
669
+ operation: "prepend",
670
+ content: AGENTS_MD_LINK,
671
+ marker: "@./.safeword/SAFEWORD.md",
672
+ createIfMissing: true
673
+ },
674
+ "CLAUDE.md": {
675
+ operation: "prepend",
676
+ content: AGENTS_MD_LINK,
677
+ marker: "@./.safeword/SAFEWORD.md",
678
+ createIfMissing: false
679
+ // Only patch if exists, don't create (AGENTS.md is primary)
680
+ }
681
+ },
682
+ // NPM packages to install
683
+ packages: {
684
+ base: [
685
+ "eslint",
686
+ "prettier",
687
+ "@eslint/js",
688
+ "eslint-plugin-import-x",
689
+ "eslint-plugin-sonarjs",
690
+ "eslint-plugin-boundaries",
691
+ "eslint-plugin-playwright",
692
+ "@microsoft/eslint-plugin-sdl",
693
+ "eslint-config-prettier",
694
+ "markdownlint-cli2",
695
+ "knip",
696
+ "husky",
697
+ "lint-staged"
698
+ ],
699
+ conditional: {
700
+ typescript: ["typescript-eslint"],
701
+ react: ["eslint-plugin-react", "eslint-plugin-react-hooks", "eslint-plugin-jsx-a11y"],
702
+ nextjs: ["@next/eslint-plugin-next"],
703
+ astro: ["eslint-plugin-astro", "prettier-plugin-astro"],
704
+ vue: ["eslint-plugin-vue"],
705
+ svelte: ["eslint-plugin-svelte", "prettier-plugin-svelte"],
706
+ electron: ["@electron-toolkit/eslint-config"],
707
+ vitest: ["@vitest/eslint-plugin"],
708
+ tailwind: ["prettier-plugin-tailwindcss"],
709
+ publishableLibrary: ["publint"]
710
+ }
711
+ }
712
+ };
713
+
714
+ export {
715
+ getTemplatesDir,
716
+ exists,
717
+ ensureDir,
718
+ readFile,
719
+ readFileSafe,
720
+ writeFile,
721
+ remove,
722
+ removeIfEmpty,
723
+ makeScriptsExecutable,
724
+ readJson,
725
+ writeJson,
726
+ detectProjectType,
727
+ SAFEWORD_SCHEMA
728
+ };
729
+ //# sourceMappingURL=chunk-ZS3Z3Q37.js.map