safeword 0.6.3 → 0.6.4

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