safeword 0.6.2 → 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 (42) 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 +14 -15
  20. package/templates/SAFEWORD.md +8 -28
  21. package/templates/hooks/stop-quality.sh +21 -9
  22. package/dist/check-PECCGHEA.js.map +0 -1
  23. package/dist/chunk-6CVTH67L.js +0 -43
  24. package/dist/chunk-6CVTH67L.js.map +0 -1
  25. package/dist/chunk-75FKNZUM.js +0 -15
  26. package/dist/chunk-75FKNZUM.js.map +0 -1
  27. package/dist/chunk-ARIAOK2F.js +0 -110
  28. package/dist/chunk-ARIAOK2F.js.map +0 -1
  29. package/dist/chunk-FRPJITGG.js +0 -35
  30. package/dist/chunk-FRPJITGG.js.map +0 -1
  31. package/dist/chunk-IWWBZVHT.js +0 -274
  32. package/dist/chunk-IWWBZVHT.js.map +0 -1
  33. package/dist/diff-ZACVJKOU.js +0 -171
  34. package/dist/diff-ZACVJKOU.js.map +0 -1
  35. package/dist/reset-5SRM3P6J.js +0 -145
  36. package/dist/reset-5SRM3P6J.js.map +0 -1
  37. package/dist/setup-65EVU5OT.js +0 -437
  38. package/dist/setup-65EVU5OT.js.map +0 -1
  39. package/dist/sync-4XBMKLXS.js.map +0 -1
  40. package/dist/upgrade-P3WX3ODU.js +0 -153
  41. package/dist/upgrade-P3WX3ODU.js.map +0 -1
  42. /package/templates/prompts/{review.md → quality-review.md} +0 -0
@@ -1,437 +0,0 @@
1
- import {
2
- isGitRepo
3
- } from "./chunk-75FKNZUM.js";
4
- import {
5
- VERSION
6
- } from "./chunk-ORQHKDT2.js";
7
- import {
8
- LINT_STAGED_CONFIG,
9
- PRETTIERRC,
10
- SETTINGS_HOOKS,
11
- ensureAgentsMdLink,
12
- filterOutSafewordHooks,
13
- getEslintConfig
14
- } from "./chunk-IWWBZVHT.js";
15
- import {
16
- error,
17
- header,
18
- info,
19
- listItem,
20
- success,
21
- warn
22
- } from "./chunk-FRPJITGG.js";
23
- import {
24
- detectProjectType
25
- } from "./chunk-6CVTH67L.js";
26
- import {
27
- copyDir,
28
- copyFile,
29
- ensureDir,
30
- exists,
31
- getTemplatesDir,
32
- makeScriptsExecutable,
33
- readJson,
34
- updateJson,
35
- writeFile,
36
- writeJson
37
- } from "./chunk-ARIAOK2F.js";
38
-
39
- // src/commands/setup.ts
40
- import { join as join2, basename } from "path";
41
-
42
- // src/utils/boundaries.ts
43
- import { join } from "path";
44
- var ARCHITECTURE_DIRS = [
45
- "types",
46
- // Bottom: can be imported by everything
47
- "utils",
48
- "lib",
49
- "hooks",
50
- "services",
51
- "components",
52
- "features",
53
- "modules",
54
- "app"
55
- // Top: can import everything
56
- ];
57
- var HIERARCHY = {
58
- types: [],
59
- // types can't import anything (pure type definitions)
60
- utils: ["types"],
61
- lib: ["utils", "types"],
62
- hooks: ["lib", "utils", "types"],
63
- services: ["lib", "utils", "types"],
64
- components: ["hooks", "services", "lib", "utils", "types"],
65
- features: ["components", "hooks", "services", "lib", "utils", "types"],
66
- modules: ["components", "hooks", "services", "lib", "utils", "types"],
67
- app: ["features", "modules", "components", "hooks", "services", "lib", "utils", "types"]
68
- };
69
- function detectArchitecture(projectDir) {
70
- const foundInSrc = [];
71
- const foundAtRoot = [];
72
- for (const dir of ARCHITECTURE_DIRS) {
73
- if (exists(join(projectDir, "src", dir))) {
74
- foundInSrc.push(dir);
75
- }
76
- if (exists(join(projectDir, dir))) {
77
- foundAtRoot.push(dir);
78
- }
79
- }
80
- const inSrc = foundInSrc.length >= foundAtRoot.length;
81
- const found = inSrc ? foundInSrc : foundAtRoot;
82
- return { directories: found, inSrc };
83
- }
84
- function generateBoundariesConfig(arch) {
85
- const prefix = arch.inSrc ? "src/" : "";
86
- const hasDirectories = arch.directories.length > 0;
87
- const elements = arch.directories.map((dir) => ` { type: '${dir}', pattern: '${prefix}${dir}/**', mode: 'full' }`).join(",\n");
88
- const rules = arch.directories.filter((dir) => HIERARCHY[dir].length > 0).map((dir) => {
89
- const allowed = HIERARCHY[dir].filter((dep) => arch.directories.includes(dep));
90
- if (allowed.length === 0) return null;
91
- return ` { from: ['${dir}'], allow: [${allowed.map((d) => `'${d}'`).join(", ")}] }`;
92
- }).filter(Boolean).join(",\n");
93
- const detectedInfo = hasDirectories ? `Detected directories: ${arch.directories.join(", ")} (${arch.inSrc ? "in src/" : "at root"})` : "No architecture directories detected yet - add types/, utils/, components/, etc.";
94
- const elementsContent = elements || "";
95
- const rulesContent = rules || "";
96
- return `/**
97
- * Architecture Boundaries Configuration (AUTO-GENERATED)
98
- *
99
- * ${detectedInfo}
100
- *
101
- * This enforces import boundaries between architectural layers:
102
- * - Lower layers (types, utils) cannot import from higher layers (components, features)
103
- * - Uses 'warn' severity - informative, not blocking
104
- *
105
- * Recognized directories (in hierarchy order):
106
- * types \u2192 utils \u2192 lib \u2192 hooks/services \u2192 components \u2192 features/modules \u2192 app
107
- *
108
- * To customize, override in your eslint.config.mjs:
109
- * rules: { 'boundaries/element-types': ['error', { ... }] }
110
- */
111
-
112
- import boundaries from 'eslint-plugin-boundaries';
113
-
114
- export default {
115
- plugins: { boundaries },
116
- settings: {
117
- 'boundaries/elements': [
118
- ${elementsContent}
119
- ],
120
- },
121
- rules: {
122
- 'boundaries/element-types': ['warn', {
123
- default: 'disallow',
124
- rules: [
125
- ${rulesContent}
126
- ],
127
- }],
128
- 'boundaries/no-unknown': 'off', // Allow files outside defined elements
129
- 'boundaries/no-unknown-files': 'off', // Allow non-matching files
130
- },
131
- };
132
- `;
133
- }
134
-
135
- // src/commands/setup.ts
136
- import { execSync } from "child_process";
137
- async function setup(options) {
138
- const cwd = process.cwd();
139
- const safewordDir = join2(cwd, ".safeword");
140
- if (exists(safewordDir)) {
141
- error("Already configured. Run `safeword upgrade` to update.");
142
- process.exit(1);
143
- }
144
- const packageJsonPath = join2(cwd, "package.json");
145
- let packageJsonCreated = false;
146
- if (!exists(packageJsonPath)) {
147
- const dirName = basename(cwd) || "project";
148
- const defaultPackageJson = {
149
- name: dirName,
150
- version: "0.1.0",
151
- scripts: {}
152
- };
153
- writeJson(packageJsonPath, defaultPackageJson);
154
- packageJsonCreated = true;
155
- }
156
- const isNonInteractive = options.yes || !process.stdin.isTTY;
157
- header("Safeword Setup");
158
- info(`Version: ${VERSION}`);
159
- if (packageJsonCreated) {
160
- info("Created package.json (none found)");
161
- }
162
- const created = packageJsonCreated ? ["package.json"] : [];
163
- const modified = [];
164
- try {
165
- const templatesDir = getTemplatesDir();
166
- info("\nCreating .safeword directory...");
167
- ensureDir(safewordDir);
168
- ensureDir(join2(safewordDir, "learnings"));
169
- ensureDir(join2(safewordDir, "planning", "user-stories"));
170
- ensureDir(join2(safewordDir, "planning", "design"));
171
- ensureDir(join2(safewordDir, "tickets", "completed"));
172
- copyFile(join2(templatesDir, "SAFEWORD.md"), join2(safewordDir, "SAFEWORD.md"));
173
- writeFile(join2(safewordDir, "version"), VERSION);
174
- copyDir(join2(templatesDir, "guides"), join2(safewordDir, "guides"));
175
- copyDir(join2(templatesDir, "doc-templates"), join2(safewordDir, "templates"));
176
- copyDir(join2(templatesDir, "prompts"), join2(safewordDir, "prompts"));
177
- copyDir(join2(templatesDir, "lib"), join2(safewordDir, "lib"));
178
- makeScriptsExecutable(join2(safewordDir, "lib"));
179
- copyDir(join2(templatesDir, "hooks"), join2(safewordDir, "hooks"));
180
- makeScriptsExecutable(join2(safewordDir, "hooks"));
181
- created.push(".safeword/");
182
- success("Created .safeword directory");
183
- info("\nConfiguring AGENTS.md...");
184
- const agentsMdResult = ensureAgentsMdLink(cwd);
185
- if (agentsMdResult === "created") {
186
- created.push("AGENTS.md");
187
- success("Created AGENTS.md");
188
- } else if (agentsMdResult === "modified") {
189
- modified.push("AGENTS.md");
190
- success("Prepended link to AGENTS.md");
191
- } else {
192
- info("AGENTS.md already has safeword link");
193
- }
194
- info("\nRegistering Claude Code hooks...");
195
- const claudeDir = join2(cwd, ".claude");
196
- const settingsPath = join2(claudeDir, "settings.json");
197
- ensureDir(claudeDir);
198
- try {
199
- updateJson(settingsPath, (existing) => {
200
- const hooks = existing?.hooks ?? {};
201
- for (const [event, newHooks] of Object.entries(SETTINGS_HOOKS)) {
202
- const existingHooks = hooks[event] ?? [];
203
- const nonSafewordHooks = filterOutSafewordHooks(existingHooks);
204
- hooks[event] = [...nonSafewordHooks, ...newHooks];
205
- }
206
- return { ...existing, hooks };
207
- });
208
- if (exists(settingsPath)) {
209
- modified.push(".claude/settings.json");
210
- } else {
211
- created.push(".claude/settings.json");
212
- }
213
- success("Registered hooks in .claude/settings.json");
214
- } catch (err) {
215
- error(`Failed to register hooks: ${err instanceof Error ? err.message : "Unknown error"}`);
216
- process.exit(1);
217
- }
218
- info("\nInstalling skills...");
219
- const skillsDir = join2(claudeDir, "skills");
220
- copyDir(join2(templatesDir, "skills"), skillsDir);
221
- created.push(".claude/skills/safeword-quality-reviewer/");
222
- success("Installed skills");
223
- info("\nInstalling slash commands...");
224
- const commandsDir = join2(claudeDir, "commands");
225
- copyDir(join2(templatesDir, "commands"), commandsDir);
226
- created.push(".claude/commands/");
227
- success("Installed slash commands");
228
- info("\nConfiguring MCP servers...");
229
- const mcpConfigPath = join2(cwd, ".mcp.json");
230
- updateJson(mcpConfigPath, (existing) => {
231
- const mcpServers = existing?.mcpServers ?? {};
232
- mcpServers.context7 = {
233
- command: "npx",
234
- args: ["-y", "@upstash/context7-mcp@latest"]
235
- };
236
- mcpServers.playwright = {
237
- command: "npx",
238
- args: ["@playwright/mcp@latest"]
239
- };
240
- return { ...existing, mcpServers };
241
- });
242
- if (exists(mcpConfigPath)) {
243
- modified.push(".mcp.json");
244
- } else {
245
- created.push(".mcp.json");
246
- }
247
- success("Configured MCP servers");
248
- info("\nConfiguring linting...");
249
- const packageJson = readJson(packageJsonPath);
250
- if (!packageJson) {
251
- error("Failed to read package.json");
252
- process.exit(1);
253
- }
254
- const projectType = detectProjectType(packageJson);
255
- const architecture = detectArchitecture(cwd);
256
- const eslintConfigPath = join2(cwd, "eslint.config.mjs");
257
- if (!exists(eslintConfigPath)) {
258
- writeFile(eslintConfigPath, getEslintConfig({ boundaries: true }));
259
- created.push("eslint.config.mjs");
260
- success("Created eslint.config.mjs (dynamic - adapts to framework changes)");
261
- } else {
262
- info("eslint.config.mjs already exists");
263
- }
264
- const boundariesConfigPath = join2(safewordDir, "eslint-boundaries.config.mjs");
265
- writeFile(boundariesConfigPath, generateBoundariesConfig(architecture));
266
- if (architecture.directories.length > 0) {
267
- info(`Detected architecture: ${architecture.directories.join(", ")} (${architecture.inSrc ? "in src/" : "at root"})`);
268
- } else {
269
- info("No architecture directories detected yet (boundaries ready when you add them)");
270
- }
271
- success("Created .safeword/eslint-boundaries.config.mjs");
272
- const prettierrcPath = join2(cwd, ".prettierrc");
273
- if (!exists(prettierrcPath)) {
274
- writeFile(prettierrcPath, PRETTIERRC);
275
- created.push(".prettierrc");
276
- success("Created .prettierrc");
277
- } else {
278
- info(".prettierrc already exists");
279
- }
280
- const markdownlintPath = join2(cwd, ".markdownlint-cli2.jsonc");
281
- if (!exists(markdownlintPath)) {
282
- copyFile(join2(templatesDir, "markdownlint-cli2.jsonc"), markdownlintPath);
283
- created.push(".markdownlint-cli2.jsonc");
284
- success("Created .markdownlint-cli2.jsonc");
285
- } else {
286
- info(".markdownlint-cli2.jsonc already exists");
287
- }
288
- try {
289
- const scripts = packageJson.scripts ?? {};
290
- let packageJsonModified = false;
291
- if (!scripts.lint) {
292
- scripts.lint = "eslint .";
293
- packageJsonModified = true;
294
- }
295
- if (!scripts["lint:md"]) {
296
- scripts["lint:md"] = 'markdownlint-cli2 "**/*.md" "#node_modules"';
297
- packageJsonModified = true;
298
- }
299
- if (!scripts.format) {
300
- scripts.format = "prettier --write .";
301
- packageJsonModified = true;
302
- }
303
- if (!scripts["format:check"]) {
304
- scripts["format:check"] = "prettier --check .";
305
- packageJsonModified = true;
306
- }
307
- if (!scripts.knip) {
308
- scripts.knip = "knip";
309
- packageJsonModified = true;
310
- }
311
- if (projectType.publishableLibrary && !scripts.publint) {
312
- scripts.publint = "publint";
313
- packageJsonModified = true;
314
- }
315
- if (!scripts.prepare) {
316
- scripts.prepare = "husky || true";
317
- packageJsonModified = true;
318
- }
319
- if (!packageJson["lint-staged"]) {
320
- packageJson["lint-staged"] = LINT_STAGED_CONFIG;
321
- packageJsonModified = true;
322
- }
323
- if (packageJsonModified) {
324
- packageJson.scripts = scripts;
325
- writeJson(packageJsonPath, packageJson);
326
- modified.push("package.json");
327
- success("Added lint scripts and lint-staged config");
328
- }
329
- } catch (err) {
330
- error(
331
- `Failed to update package.json: ${err instanceof Error ? err.message : "Unknown error"}`
332
- );
333
- process.exit(1);
334
- }
335
- info("\nInstalling linting dependencies...");
336
- const devDeps = [
337
- "eslint",
338
- "prettier",
339
- "@eslint/js",
340
- "eslint-plugin-import-x",
341
- "eslint-plugin-sonarjs",
342
- "@microsoft/eslint-plugin-sdl",
343
- "eslint-config-prettier",
344
- "markdownlint-cli2",
345
- "knip",
346
- "husky",
347
- "lint-staged"
348
- ];
349
- if (projectType.typescript) {
350
- devDeps.push("typescript-eslint");
351
- }
352
- if (projectType.react || projectType.nextjs) {
353
- devDeps.push("eslint-plugin-react", "eslint-plugin-react-hooks", "eslint-plugin-jsx-a11y");
354
- }
355
- if (projectType.nextjs) {
356
- devDeps.push("@next/eslint-plugin-next");
357
- }
358
- if (projectType.astro) {
359
- devDeps.push("eslint-plugin-astro");
360
- }
361
- if (projectType.vue) {
362
- devDeps.push("eslint-plugin-vue");
363
- }
364
- if (projectType.svelte) {
365
- devDeps.push("eslint-plugin-svelte");
366
- }
367
- devDeps.push("eslint-plugin-boundaries");
368
- if (projectType.electron) {
369
- devDeps.push("@electron-toolkit/eslint-config");
370
- }
371
- if (projectType.vitest) {
372
- devDeps.push("@vitest/eslint-plugin");
373
- }
374
- devDeps.push("eslint-plugin-playwright");
375
- if (projectType.tailwind) {
376
- devDeps.push("prettier-plugin-tailwindcss");
377
- }
378
- if (projectType.publishableLibrary) {
379
- devDeps.push("publint");
380
- }
381
- try {
382
- const installCmd = `npm install -D ${devDeps.join(" ")}`;
383
- info(`Running: ${installCmd}`);
384
- execSync(installCmd, { cwd, stdio: "inherit" });
385
- success("Installed linting dependencies");
386
- } catch {
387
- warn("Failed to install dependencies. Run manually:");
388
- listItem(`npm install -D ${devDeps.join(" ")}`);
389
- }
390
- info("\nConfiguring git hooks with Husky...");
391
- if (isGitRepo(cwd)) {
392
- try {
393
- const huskyDir = join2(cwd, ".husky");
394
- ensureDir(huskyDir);
395
- const huskyPreCommit = join2(huskyDir, "pre-commit");
396
- writeFile(huskyPreCommit, "npx safeword sync --quiet --stage\nnpx lint-staged\n");
397
- makeScriptsExecutable(huskyDir);
398
- created.push(".husky/pre-commit");
399
- success("Configured Husky with lint-staged pre-commit hook");
400
- } catch {
401
- warn("Failed to setup Husky. Run manually:");
402
- listItem("mkdir -p .husky");
403
- listItem('echo "npx lint-staged" > .husky/pre-commit');
404
- }
405
- } else if (isNonInteractive) {
406
- warn("Skipped Husky setup (no git repository)");
407
- } else {
408
- warn("Skipped Husky setup (no .git directory)");
409
- info("Initialize git and run safeword setup again to enable pre-commit hooks");
410
- }
411
- header("Setup Complete");
412
- if (created.length > 0) {
413
- info("\nCreated:");
414
- for (const file of created) {
415
- listItem(file);
416
- }
417
- }
418
- if (modified.length > 0) {
419
- info("\nModified:");
420
- for (const file of modified) {
421
- listItem(file);
422
- }
423
- }
424
- info("\nNext steps:");
425
- listItem("Run `safeword check` to verify setup");
426
- listItem("Commit the new files to git");
427
- success(`
428
- Safeword ${VERSION} installed successfully!`);
429
- } catch (err) {
430
- error(`Setup failed: ${err instanceof Error ? err.message : "Unknown error"}`);
431
- process.exit(1);
432
- }
433
- }
434
- export {
435
- setup
436
- };
437
- //# sourceMappingURL=setup-65EVU5OT.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/setup.ts","../src/utils/boundaries.ts"],"sourcesContent":["/**\n * Setup command - Initialize safeword in a project\n */\n\nimport { join, basename } from 'node:path';\nimport { VERSION } from '../version.js';\nimport {\n exists,\n ensureDir,\n writeFile,\n readJson,\n writeJson,\n updateJson,\n copyDir,\n copyFile,\n getTemplatesDir,\n makeScriptsExecutable,\n} from '../utils/fs.js';\nimport { info, success, warn, error, header, listItem } from '../utils/output.js';\nimport { isGitRepo } from '../utils/git.js';\nimport { detectProjectType } from '../utils/project-detector.js';\nimport { filterOutSafewordHooks } from '../utils/hooks.js';\nimport { ensureAgentsMdLink } from '../utils/agents-md.js';\nimport { PRETTIERRC, LINT_STAGED_CONFIG, getEslintConfig, SETTINGS_HOOKS } from '../templates/index.js';\nimport { detectArchitecture, generateBoundariesConfig } from '../utils/boundaries.js';\nimport { execSync } from 'node:child_process';\n\nexport interface SetupOptions {\n yes?: boolean;\n}\n\ninterface PackageJson {\n name?: string;\n version?: string;\n scripts?: Record<string, string>;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n 'lint-staged'?: Record<string, string[]>;\n}\n\nexport async function setup(options: SetupOptions): Promise<void> {\n const cwd = process.cwd();\n const safewordDir = join(cwd, '.safeword');\n\n // Check if already configured\n if (exists(safewordDir)) {\n error('Already configured. Run `safeword upgrade` to update.');\n process.exit(1);\n }\n\n // Check for package.json, create if missing\n const packageJsonPath = join(cwd, 'package.json');\n let packageJsonCreated = false;\n if (!exists(packageJsonPath)) {\n const dirName = basename(cwd) || 'project';\n const defaultPackageJson: PackageJson = {\n name: dirName,\n version: '0.1.0',\n scripts: {},\n };\n writeJson(packageJsonPath, defaultPackageJson);\n packageJsonCreated = true;\n }\n\n const isNonInteractive = options.yes || !process.stdin.isTTY;\n\n header('Safeword Setup');\n info(`Version: ${VERSION}`);\n\n if (packageJsonCreated) {\n info('Created package.json (none found)');\n }\n\n // Track created files for summary\n const created: string[] = packageJsonCreated ? ['package.json'] : [];\n const modified: string[] = [];\n\n try {\n const templatesDir = getTemplatesDir();\n\n // 1. Create .safeword directory structure and copy templates\n info('\\nCreating .safeword directory...');\n\n ensureDir(safewordDir);\n ensureDir(join(safewordDir, 'learnings'));\n ensureDir(join(safewordDir, 'planning', 'user-stories'));\n ensureDir(join(safewordDir, 'planning', 'design'));\n ensureDir(join(safewordDir, 'tickets', 'completed'));\n\n // Copy full SAFEWORD.md from templates\n copyFile(join(templatesDir, 'SAFEWORD.md'), join(safewordDir, 'SAFEWORD.md'));\n writeFile(join(safewordDir, 'version'), VERSION);\n\n // Copy methodology guides\n copyDir(join(templatesDir, 'guides'), join(safewordDir, 'guides'));\n\n // Copy document templates (to 'templates' to match links in SAFEWORD.md)\n copyDir(join(templatesDir, 'doc-templates'), join(safewordDir, 'templates'));\n\n // Copy review prompts\n copyDir(join(templatesDir, 'prompts'), join(safewordDir, 'prompts'));\n\n // Copy lib scripts and make executable\n copyDir(join(templatesDir, 'lib'), join(safewordDir, 'lib'));\n makeScriptsExecutable(join(safewordDir, 'lib'));\n\n // Copy hook scripts and make executable\n copyDir(join(templatesDir, 'hooks'), join(safewordDir, 'hooks'));\n makeScriptsExecutable(join(safewordDir, 'hooks'));\n\n created.push('.safeword/');\n success('Created .safeword directory');\n\n // 2. Handle AGENTS.md\n info('\\nConfiguring AGENTS.md...');\n const agentsMdResult = ensureAgentsMdLink(cwd);\n if (agentsMdResult === 'created') {\n created.push('AGENTS.md');\n success('Created AGENTS.md');\n } else if (agentsMdResult === 'modified') {\n modified.push('AGENTS.md');\n success('Prepended link to AGENTS.md');\n } else {\n info('AGENTS.md already has safeword link');\n }\n\n // 3. Register Claude Code hooks\n info('\\nRegistering Claude Code hooks...');\n\n const claudeDir = join(cwd, '.claude');\n const settingsPath = join(claudeDir, 'settings.json');\n\n ensureDir(claudeDir);\n\n try {\n updateJson<{ hooks?: Record<string, unknown[]> }>(settingsPath, existing => {\n const hooks = existing?.hooks ?? {};\n\n // Merge hooks, preserving existing non-safeword hooks\n for (const [event, newHooks] of Object.entries(SETTINGS_HOOKS)) {\n const existingHooks = (hooks[event] as unknown[]) ?? [];\n const nonSafewordHooks = filterOutSafewordHooks(existingHooks);\n hooks[event] = [...nonSafewordHooks, ...newHooks];\n }\n\n return { ...existing, hooks };\n });\n\n if (exists(settingsPath)) {\n modified.push('.claude/settings.json');\n } else {\n created.push('.claude/settings.json');\n }\n success('Registered hooks in .claude/settings.json');\n } catch (err) {\n error(`Failed to register hooks: ${err instanceof Error ? err.message : 'Unknown error'}`);\n process.exit(1);\n }\n\n // 4. Copy skills\n info('\\nInstalling skills...');\n\n const skillsDir = join(claudeDir, 'skills');\n copyDir(join(templatesDir, 'skills'), skillsDir);\n\n created.push('.claude/skills/safeword-quality-reviewer/');\n success('Installed skills');\n\n // 5. Copy slash commands\n info('\\nInstalling slash commands...');\n\n const commandsDir = join(claudeDir, 'commands');\n copyDir(join(templatesDir, 'commands'), commandsDir);\n\n created.push('.claude/commands/');\n success('Installed slash commands');\n\n // 6. Setup MCP servers\n info('\\nConfiguring MCP servers...');\n\n const mcpConfigPath = join(cwd, '.mcp.json');\n\n updateJson<{ mcpServers?: Record<string, unknown> }>(mcpConfigPath, existing => {\n const mcpServers = existing?.mcpServers ?? {};\n\n // Add safeword MCP servers (context7 and playwright)\n mcpServers.context7 = {\n command: 'npx',\n args: ['-y', '@upstash/context7-mcp@latest'],\n };\n mcpServers.playwright = {\n command: 'npx',\n args: ['@playwright/mcp@latest'],\n };\n\n return { ...existing, mcpServers };\n });\n\n if (exists(mcpConfigPath)) {\n modified.push('.mcp.json');\n } else {\n created.push('.mcp.json');\n }\n success('Configured MCP servers');\n\n // 7. Setup linting\n info('\\nConfiguring linting...');\n\n const packageJson = readJson<PackageJson>(packageJsonPath);\n if (!packageJson) {\n error('Failed to read package.json');\n process.exit(1);\n }\n\n const projectType = detectProjectType(packageJson);\n\n // Detect architecture boundaries (always configured, rules depend on detected dirs)\n const architecture = detectArchitecture(cwd);\n\n // Create dynamic ESLint config (detects frameworks from package.json at runtime)\n const eslintConfigPath = join(cwd, 'eslint.config.mjs');\n if (!exists(eslintConfigPath)) {\n writeFile(eslintConfigPath, getEslintConfig({ boundaries: true }));\n created.push('eslint.config.mjs');\n success('Created eslint.config.mjs (dynamic - adapts to framework changes)');\n } else {\n info('eslint.config.mjs already exists');\n }\n\n // Always create boundaries config (rules depend on detected architecture dirs)\n const boundariesConfigPath = join(safewordDir, 'eslint-boundaries.config.mjs');\n writeFile(boundariesConfigPath, generateBoundariesConfig(architecture));\n if (architecture.directories.length > 0) {\n info(`Detected architecture: ${architecture.directories.join(', ')} (${architecture.inSrc ? 'in src/' : 'at root'})`);\n } else {\n info('No architecture directories detected yet (boundaries ready when you add them)');\n }\n success('Created .safeword/eslint-boundaries.config.mjs');\n\n // Create Prettier config\n const prettierrcPath = join(cwd, '.prettierrc');\n if (!exists(prettierrcPath)) {\n writeFile(prettierrcPath, PRETTIERRC);\n created.push('.prettierrc');\n success('Created .prettierrc');\n } else {\n info('.prettierrc already exists');\n }\n\n // Create markdownlint config (using cli2 preferred filename)\n const markdownlintPath = join(cwd, '.markdownlint-cli2.jsonc');\n if (!exists(markdownlintPath)) {\n copyFile(join(templatesDir, 'markdownlint-cli2.jsonc'), markdownlintPath);\n created.push('.markdownlint-cli2.jsonc');\n success('Created .markdownlint-cli2.jsonc');\n } else {\n info('.markdownlint-cli2.jsonc already exists');\n }\n\n // Add scripts and lint-staged config to package.json\n try {\n const scripts = packageJson.scripts ?? {};\n let packageJsonModified = false;\n\n if (!scripts.lint) {\n scripts.lint = 'eslint .';\n packageJsonModified = true;\n }\n\n if (!scripts['lint:md']) {\n scripts['lint:md'] = 'markdownlint-cli2 \"**/*.md\" \"#node_modules\"';\n packageJsonModified = true;\n }\n\n if (!scripts.format) {\n scripts.format = 'prettier --write .';\n packageJsonModified = true;\n }\n\n if (!scripts['format:check']) {\n scripts['format:check'] = 'prettier --check .';\n packageJsonModified = true;\n }\n\n if (!scripts.knip) {\n scripts.knip = 'knip';\n packageJsonModified = true;\n }\n\n // Add publint script for publishable libraries\n if (projectType.publishableLibrary && !scripts.publint) {\n scripts.publint = 'publint';\n packageJsonModified = true;\n }\n\n // Add prepare script for Husky (runs on npm install)\n // The || true fallback prevents npm install --production from failing\n // when husky (a devDependency) isn't installed\n if (!scripts.prepare) {\n scripts.prepare = 'husky || true';\n packageJsonModified = true;\n }\n\n // Add lint-staged config\n if (!packageJson['lint-staged']) {\n packageJson['lint-staged'] = LINT_STAGED_CONFIG;\n packageJsonModified = true;\n }\n\n if (packageJsonModified) {\n packageJson.scripts = scripts;\n writeJson(packageJsonPath, packageJson);\n modified.push('package.json');\n success('Added lint scripts and lint-staged config');\n }\n } catch (err) {\n error(\n `Failed to update package.json: ${err instanceof Error ? err.message : 'Unknown error'}`,\n );\n process.exit(1);\n }\n\n // 8. Install dependencies\n info('\\nInstalling linting dependencies...');\n\n // Build the list of packages to install\n const devDeps: string[] = [\n 'eslint',\n 'prettier',\n '@eslint/js',\n 'eslint-plugin-import-x',\n 'eslint-plugin-sonarjs',\n '@microsoft/eslint-plugin-sdl',\n 'eslint-config-prettier',\n 'markdownlint-cli2',\n 'knip',\n 'husky',\n 'lint-staged',\n ];\n\n if (projectType.typescript) {\n devDeps.push('typescript-eslint');\n }\n if (projectType.react || projectType.nextjs) {\n devDeps.push('eslint-plugin-react', 'eslint-plugin-react-hooks', 'eslint-plugin-jsx-a11y');\n }\n if (projectType.nextjs) {\n devDeps.push('@next/eslint-plugin-next');\n }\n if (projectType.astro) {\n devDeps.push('eslint-plugin-astro');\n }\n if (projectType.vue) {\n devDeps.push('eslint-plugin-vue');\n }\n if (projectType.svelte) {\n devDeps.push('eslint-plugin-svelte');\n }\n // Always install boundaries - configured only when 3+ architecture directories exist\n devDeps.push('eslint-plugin-boundaries');\n if (projectType.electron) {\n devDeps.push('@electron-toolkit/eslint-config');\n }\n if (projectType.vitest) {\n devDeps.push('@vitest/eslint-plugin');\n }\n // Always include Playwright - safeword sets up e2e testing with Playwright\n devDeps.push('eslint-plugin-playwright');\n\n // Tailwind: use official Prettier plugin for class sorting\n if (projectType.tailwind) {\n devDeps.push('prettier-plugin-tailwindcss');\n }\n\n // Publishable libraries: validate package.json for npm publishing\n if (projectType.publishableLibrary) {\n devDeps.push('publint');\n }\n\n try {\n const installCmd = `npm install -D ${devDeps.join(' ')}`;\n info(`Running: ${installCmd}`);\n execSync(installCmd, { cwd, stdio: 'inherit' });\n success('Installed linting dependencies');\n } catch {\n warn('Failed to install dependencies. Run manually:');\n listItem(`npm install -D ${devDeps.join(' ')}`);\n }\n\n // 9. Setup Husky for git hooks (manually, not using husky init which overwrites prepare script)\n info('\\nConfiguring git hooks with Husky...');\n\n if (isGitRepo(cwd)) {\n try {\n // Create .husky directory and pre-commit hook manually\n // (husky init unconditionally sets prepare script, which we don't want)\n const huskyDir = join(cwd, '.husky');\n ensureDir(huskyDir);\n\n // Create pre-commit hook that syncs linting plugins and runs lint-staged\n const huskyPreCommit = join(huskyDir, 'pre-commit');\n writeFile(huskyPreCommit, 'npx safeword sync --quiet --stage\\nnpx lint-staged\\n');\n\n // Make hook executable (required for git hooks on Unix)\n makeScriptsExecutable(huskyDir);\n\n created.push('.husky/pre-commit');\n success('Configured Husky with lint-staged pre-commit hook');\n } catch {\n warn('Failed to setup Husky. Run manually:');\n listItem('mkdir -p .husky');\n listItem('echo \"npx lint-staged\" > .husky/pre-commit');\n }\n } else if (isNonInteractive) {\n warn('Skipped Husky setup (no git repository)');\n } else {\n warn('Skipped Husky setup (no .git directory)');\n info('Initialize git and run safeword setup again to enable pre-commit hooks');\n }\n\n // Print summary\n header('Setup Complete');\n\n if (created.length > 0) {\n info('\\nCreated:');\n for (const file of created) {\n listItem(file);\n }\n }\n\n if (modified.length > 0) {\n info('\\nModified:');\n for (const file of modified) {\n listItem(file);\n }\n }\n\n info('\\nNext steps:');\n listItem('Run `safeword check` to verify setup');\n listItem('Commit the new files to git');\n\n success(`\\nSafeword ${VERSION} installed successfully!`);\n } catch (err) {\n error(`Setup failed: ${err instanceof Error ? err.message : 'Unknown error'}`);\n process.exit(1);\n }\n}\n","/**\n * Architecture boundaries detection and config generation\n *\n * Auto-detects common architecture directories and generates\n * eslint-plugin-boundaries config with sensible hierarchy rules.\n */\n\nimport { join } from 'node:path';\nimport { exists } from './fs.js';\n\n/**\n * Architecture directories to detect, ordered from bottom to top of hierarchy.\n * Lower items can be imported by higher items, not vice versa.\n */\nconst ARCHITECTURE_DIRS = [\n 'types', // Bottom: can be imported by everything\n 'utils',\n 'lib',\n 'hooks',\n 'services',\n 'components',\n 'features',\n 'modules',\n 'app', // Top: can import everything\n] as const;\n\ntype ArchDir = (typeof ARCHITECTURE_DIRS)[number];\n\n/**\n * Hierarchy rules: what each layer can import\n * Lower layers have fewer import permissions\n */\nconst HIERARCHY: Record<ArchDir, ArchDir[]> = {\n types: [], // types can't import anything (pure type definitions)\n utils: ['types'],\n lib: ['utils', 'types'],\n hooks: ['lib', 'utils', 'types'],\n services: ['lib', 'utils', 'types'],\n components: ['hooks', 'services', 'lib', 'utils', 'types'],\n features: ['components', 'hooks', 'services', 'lib', 'utils', 'types'],\n modules: ['components', 'hooks', 'services', 'lib', 'utils', 'types'],\n app: ['features', 'modules', 'components', 'hooks', 'services', 'lib', 'utils', 'types'],\n};\n\nexport interface DetectedArchitecture {\n directories: ArchDir[];\n inSrc: boolean; // true if dirs are in src/, false if at root\n}\n\n/**\n * Detects architecture directories in the project\n * Always returns a result (even with 0 directories) - boundaries is always configured\n */\nexport function detectArchitecture(projectDir: string): DetectedArchitecture {\n const foundInSrc: ArchDir[] = [];\n const foundAtRoot: ArchDir[] = [];\n\n for (const dir of ARCHITECTURE_DIRS) {\n if (exists(join(projectDir, 'src', dir))) {\n foundInSrc.push(dir);\n }\n if (exists(join(projectDir, dir))) {\n foundAtRoot.push(dir);\n }\n }\n\n // Prefer src/ location if more dirs found there\n const inSrc = foundInSrc.length >= foundAtRoot.length;\n const found = inSrc ? foundInSrc : foundAtRoot;\n\n return { directories: found, inSrc };\n}\n\n/**\n * Generates the boundaries config file content\n */\nexport function generateBoundariesConfig(arch: DetectedArchitecture): string {\n const prefix = arch.inSrc ? 'src/' : '';\n const hasDirectories = arch.directories.length > 0;\n\n // Generate element definitions with mode: 'full' to match from project root only\n const elements = arch.directories\n .map(dir => ` { type: '${dir}', pattern: '${prefix}${dir}/**', mode: 'full' }`)\n .join(',\\n');\n\n // Generate rules (what each layer can import)\n const rules = arch.directories\n .filter(dir => HIERARCHY[dir].length > 0)\n .map(dir => {\n const allowed = HIERARCHY[dir].filter(dep => arch.directories.includes(dep));\n if (allowed.length === 0) return null;\n return ` { from: ['${dir}'], allow: [${allowed.map(d => `'${d}'`).join(', ')}] }`;\n })\n .filter(Boolean)\n .join(',\\n');\n\n const detectedInfo = hasDirectories\n ? `Detected directories: ${arch.directories.join(', ')} (${arch.inSrc ? 'in src/' : 'at root'})`\n : 'No architecture directories detected yet - add types/, utils/, components/, etc.';\n\n // Build elements array content (empty array if no directories)\n const elementsContent = elements || '';\n const rulesContent = rules || '';\n\n return `/**\n * Architecture Boundaries Configuration (AUTO-GENERATED)\n *\n * ${detectedInfo}\n *\n * This enforces import boundaries between architectural layers:\n * - Lower layers (types, utils) cannot import from higher layers (components, features)\n * - Uses 'warn' severity - informative, not blocking\n *\n * Recognized directories (in hierarchy order):\n * types → utils → lib → hooks/services → components → features/modules → app\n *\n * To customize, override in your eslint.config.mjs:\n * rules: { 'boundaries/element-types': ['error', { ... }] }\n */\n\nimport boundaries from 'eslint-plugin-boundaries';\n\nexport default {\n plugins: { boundaries },\n settings: {\n 'boundaries/elements': [\n${elementsContent}\n ],\n },\n rules: {\n 'boundaries/element-types': ['warn', {\n default: 'disallow',\n rules: [\n${rulesContent}\n ],\n }],\n 'boundaries/no-unknown': 'off', // Allow files outside defined elements\n 'boundaries/no-unknown-files': 'off', // Allow non-matching files\n },\n};\n`;\n}\n"],"mappings":"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","names":["join","join"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/sync.ts"],"sourcesContent":["/**\n * Sync command - Keep linting plugins in sync with project dependencies\n *\n * Detects frameworks in package.json and ensures the corresponding ESLint plugins\n * are installed. Designed to be called from Husky pre-commit hook.\n *\n * Behavior:\n * - Fast exit when nothing needs to change\n * - Installs missing plugins\n * - Optionally stages modified files (--stage flag for pre-commit)\n * - Clear error message if installation fails\n */\n\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { exists, readJson } from '../utils/fs.js';\nimport { detectProjectType, type PackageJson, type ProjectType } from '../utils/project-detector.js';\n\nexport interface SyncOptions {\n quiet?: boolean;\n stage?: boolean;\n}\n\n/**\n * Maps project type flags to their required ESLint plugin packages\n */\nfunction getRequiredPlugins(projectType: ProjectType): string[] {\n const plugins: string[] = [\n // Always required (base plugins)\n 'eslint',\n '@eslint/js',\n 'eslint-plugin-import-x',\n 'eslint-plugin-sonarjs',\n '@microsoft/eslint-plugin-sdl',\n 'eslint-config-prettier',\n 'eslint-plugin-boundaries',\n 'eslint-plugin-playwright',\n ];\n\n // Framework plugins (detected at runtime by dynamic ESLint config)\n if (projectType.typescript) {\n plugins.push('typescript-eslint');\n }\n if (projectType.react || projectType.nextjs) {\n plugins.push('eslint-plugin-react', 'eslint-plugin-react-hooks', 'eslint-plugin-jsx-a11y');\n }\n if (projectType.nextjs) {\n plugins.push('@next/eslint-plugin-next');\n }\n if (projectType.astro) {\n plugins.push('eslint-plugin-astro');\n }\n if (projectType.vue) {\n plugins.push('eslint-plugin-vue');\n }\n if (projectType.svelte) {\n plugins.push('eslint-plugin-svelte');\n }\n if (projectType.electron) {\n plugins.push('@electron-toolkit/eslint-config');\n }\n if (projectType.vitest) {\n plugins.push('@vitest/eslint-plugin');\n }\n\n return plugins;\n}\n\n/**\n * Check which packages are missing from devDependencies\n */\nfunction getMissingPackages(required: string[], installed: Record<string, string>): string[] {\n return required.filter(pkg => !(pkg in installed));\n}\n\n/**\n * Sync linting configuration with current project dependencies\n */\nexport async function sync(options: SyncOptions = {}): Promise<void> {\n const cwd = process.cwd();\n const safewordDir = join(cwd, '.safeword');\n const packageJsonPath = join(cwd, 'package.json');\n\n // Must be in a safeword project\n if (!exists(safewordDir)) {\n if (!options.quiet) {\n console.error('Not a safeword project. Run `safeword setup` first.');\n }\n process.exit(1);\n }\n\n if (!exists(packageJsonPath)) {\n if (!options.quiet) {\n console.error('No package.json found.');\n }\n process.exit(1);\n }\n\n const packageJson = readJson<PackageJson>(packageJsonPath);\n if (!packageJson) {\n process.exit(1);\n }\n\n // Detect current project type\n const projectType = detectProjectType(packageJson);\n const devDeps = packageJson.devDependencies || {};\n\n // Check for missing plugins\n const requiredPlugins = getRequiredPlugins(projectType);\n const missingPlugins = getMissingPackages(requiredPlugins, devDeps);\n\n // Fast exit if nothing to install\n if (missingPlugins.length === 0) {\n return;\n }\n\n // Install missing plugins\n if (!options.quiet) {\n console.log(`Installing missing ESLint plugins: ${missingPlugins.join(', ')}`);\n }\n\n try {\n execSync(`npm install -D ${missingPlugins.join(' ')}`, {\n cwd,\n stdio: options.quiet ? 'pipe' : 'inherit',\n });\n } catch (error) {\n // Clear error message for network/install failures\n const pluginList = missingPlugins.join(' ');\n console.error(`\\n✗ Failed to install ESLint plugins\\n`);\n console.error(`Your project needs: ${pluginList}`);\n console.error(`\\nRun manually when online:`);\n console.error(` npm install -D ${pluginList}\\n`);\n process.exit(1);\n }\n\n // Stage modified files if --stage flag is set (for pre-commit hook)\n if (options.stage) {\n try {\n execSync('git add package.json package-lock.json', {\n cwd,\n stdio: 'pipe',\n });\n } catch {\n // Not in a git repo or git add failed - ignore\n }\n }\n\n if (!options.quiet) {\n console.log(`✓ Installed ${missingPlugins.length} ESLint plugin(s)`);\n }\n}\n"],"mappings":";;;;;;;;;AAaA,SAAS,YAAY;AACrB,SAAS,gBAAgB;AAYzB,SAAS,mBAAmB,aAAoC;AAC9D,QAAM,UAAoB;AAAA;AAAA,IAExB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,MAAI,YAAY,YAAY;AAC1B,YAAQ,KAAK,mBAAmB;AAAA,EAClC;AACA,MAAI,YAAY,SAAS,YAAY,QAAQ;AAC3C,YAAQ,KAAK,uBAAuB,6BAA6B,wBAAwB;AAAA,EAC3F;AACA,MAAI,YAAY,QAAQ;AACtB,YAAQ,KAAK,0BAA0B;AAAA,EACzC;AACA,MAAI,YAAY,OAAO;AACrB,YAAQ,KAAK,qBAAqB;AAAA,EACpC;AACA,MAAI,YAAY,KAAK;AACnB,YAAQ,KAAK,mBAAmB;AAAA,EAClC;AACA,MAAI,YAAY,QAAQ;AACtB,YAAQ,KAAK,sBAAsB;AAAA,EACrC;AACA,MAAI,YAAY,UAAU;AACxB,YAAQ,KAAK,iCAAiC;AAAA,EAChD;AACA,MAAI,YAAY,QAAQ;AACtB,YAAQ,KAAK,uBAAuB;AAAA,EACtC;AAEA,SAAO;AACT;AAKA,SAAS,mBAAmB,UAAoB,WAA6C;AAC3F,SAAO,SAAS,OAAO,SAAO,EAAE,OAAO,UAAU;AACnD;AAKA,eAAsB,KAAK,UAAuB,CAAC,GAAkB;AACnE,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,cAAc,KAAK,KAAK,WAAW;AACzC,QAAM,kBAAkB,KAAK,KAAK,cAAc;AAGhD,MAAI,CAAC,OAAO,WAAW,GAAG;AACxB,QAAI,CAAC,QAAQ,OAAO;AAClB,cAAQ,MAAM,qDAAqD;AAAA,IACrE;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,OAAO,eAAe,GAAG;AAC5B,QAAI,CAAC,QAAQ,OAAO;AAClB,cAAQ,MAAM,wBAAwB;AAAA,IACxC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,cAAc,SAAsB,eAAe;AACzD,MAAI,CAAC,aAAa;AAChB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,cAAc,kBAAkB,WAAW;AACjD,QAAM,UAAU,YAAY,mBAAmB,CAAC;AAGhD,QAAM,kBAAkB,mBAAmB,WAAW;AACtD,QAAM,iBAAiB,mBAAmB,iBAAiB,OAAO;AAGlE,MAAI,eAAe,WAAW,GAAG;AAC/B;AAAA,EACF;AAGA,MAAI,CAAC,QAAQ,OAAO;AAClB,YAAQ,IAAI,sCAAsC,eAAe,KAAK,IAAI,CAAC,EAAE;AAAA,EAC/E;AAEA,MAAI;AACF,aAAS,kBAAkB,eAAe,KAAK,GAAG,CAAC,IAAI;AAAA,MACrD;AAAA,MACA,OAAO,QAAQ,QAAQ,SAAS;AAAA,IAClC,CAAC;AAAA,EACH,SAAS,OAAO;AAEd,UAAM,aAAa,eAAe,KAAK,GAAG;AAC1C,YAAQ,MAAM;AAAA;AAAA,CAAwC;AACtD,YAAQ,MAAM,uBAAuB,UAAU,EAAE;AACjD,YAAQ,MAAM;AAAA,0BAA6B;AAC3C,YAAQ,MAAM,oBAAoB,UAAU;AAAA,CAAI;AAChD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,MAAI,QAAQ,OAAO;AACjB,QAAI;AACF,eAAS,0CAA0C;AAAA,QACjD;AAAA,QACA,OAAO;AAAA,MACT,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ,OAAO;AAClB,YAAQ,IAAI,oBAAe,eAAe,MAAM,mBAAmB;AAAA,EACrE;AACF;","names":[]}
@@ -1,153 +0,0 @@
1
- import {
2
- isGitRepo
3
- } from "./chunk-75FKNZUM.js";
4
- import {
5
- compareVersions
6
- } from "./chunk-W66Z3C5H.js";
7
- import {
8
- VERSION
9
- } from "./chunk-ORQHKDT2.js";
10
- import {
11
- SETTINGS_HOOKS,
12
- ensureAgentsMdLink,
13
- filterOutSafewordHooks
14
- } from "./chunk-IWWBZVHT.js";
15
- import {
16
- error,
17
- header,
18
- info,
19
- listItem,
20
- success
21
- } from "./chunk-FRPJITGG.js";
22
- import {
23
- copyDir,
24
- copyFile,
25
- ensureDir,
26
- exists,
27
- getTemplatesDir,
28
- makeScriptsExecutable,
29
- readFileSafe,
30
- updateJson,
31
- writeFile
32
- } from "./chunk-ARIAOK2F.js";
33
-
34
- // src/commands/upgrade.ts
35
- import { join } from "path";
36
- import { execSync } from "child_process";
37
- async function upgrade() {
38
- const cwd = process.cwd();
39
- const safewordDir = join(cwd, ".safeword");
40
- if (!exists(safewordDir)) {
41
- error("Not configured. Run `safeword setup` first.");
42
- process.exit(1);
43
- }
44
- const versionPath = join(safewordDir, "version");
45
- const projectVersion = readFileSafe(versionPath)?.trim() ?? "0.0.0";
46
- if (compareVersions(VERSION, projectVersion) < 0) {
47
- error(`CLI v${VERSION} is older than project v${projectVersion}.`);
48
- error("Update the CLI first: npm install -g safeword");
49
- process.exit(1);
50
- }
51
- header("Safeword Upgrade");
52
- info(`Upgrading from v${projectVersion} to v${VERSION}`);
53
- const updated = [];
54
- const unchanged = [];
55
- try {
56
- const templatesDir = getTemplatesDir();
57
- info("\nUpdating .safeword directory...");
58
- copyFile(join(templatesDir, "SAFEWORD.md"), join(safewordDir, "SAFEWORD.md"));
59
- writeFile(join(safewordDir, "version"), VERSION);
60
- updated.push(".safeword/SAFEWORD.md");
61
- updated.push(".safeword/version");
62
- copyDir(join(templatesDir, "guides"), join(safewordDir, "guides"));
63
- copyDir(join(templatesDir, "doc-templates"), join(safewordDir, "templates"));
64
- copyDir(join(templatesDir, "prompts"), join(safewordDir, "prompts"));
65
- copyDir(join(templatesDir, "lib"), join(safewordDir, "lib"));
66
- makeScriptsExecutable(join(safewordDir, "lib"));
67
- copyDir(join(templatesDir, "hooks"), join(safewordDir, "hooks"));
68
- makeScriptsExecutable(join(safewordDir, "hooks"));
69
- updated.push(".safeword/guides/");
70
- updated.push(".safeword/templates/");
71
- updated.push(".safeword/prompts/");
72
- updated.push(".safeword/hooks/");
73
- success("Updated .safeword directory");
74
- info("\nVerifying AGENTS.md...");
75
- const agentsMdResult = ensureAgentsMdLink(cwd);
76
- if (agentsMdResult === "created") {
77
- updated.push("AGENTS.md");
78
- success("Created AGENTS.md");
79
- } else if (agentsMdResult === "modified") {
80
- updated.push("AGENTS.md");
81
- success("Restored link to AGENTS.md");
82
- } else {
83
- unchanged.push("AGENTS.md");
84
- info("AGENTS.md link is present");
85
- }
86
- info("\nUpdating Claude Code hooks...");
87
- const claudeDir = join(cwd, ".claude");
88
- const settingsPath = join(claudeDir, "settings.json");
89
- ensureDir(claudeDir);
90
- updateJson(settingsPath, (existing) => {
91
- const hooks = existing?.hooks ?? {};
92
- for (const [event, newHooks] of Object.entries(SETTINGS_HOOKS)) {
93
- const existingHooks = hooks[event] ?? [];
94
- const nonSafewordHooks = filterOutSafewordHooks(existingHooks);
95
- hooks[event] = [...nonSafewordHooks, ...newHooks];
96
- }
97
- return { ...existing, hooks };
98
- });
99
- updated.push(".claude/settings.json");
100
- success("Updated hooks in .claude/settings.json");
101
- info("\nUpdating skills and commands...");
102
- copyDir(join(templatesDir, "skills"), join(claudeDir, "skills"));
103
- copyDir(join(templatesDir, "commands"), join(claudeDir, "commands"));
104
- updated.push(".claude/skills/");
105
- updated.push(".claude/commands/");
106
- success("Updated skills and commands");
107
- if (isGitRepo(cwd)) {
108
- const huskyDir = join(cwd, ".husky");
109
- if (exists(huskyDir)) {
110
- info("\nUpdating Husky pre-commit hook...");
111
- const huskyPreCommit = join(huskyDir, "pre-commit");
112
- writeFile(huskyPreCommit, "npx lint-staged\n");
113
- updated.push(".husky/pre-commit");
114
- success("Updated Husky pre-commit hook");
115
- } else {
116
- info("\nInitializing Husky...");
117
- try {
118
- execSync("npx husky init", { cwd, stdio: "pipe" });
119
- const huskyPreCommit = join(cwd, ".husky", "pre-commit");
120
- writeFile(huskyPreCommit, "npx lint-staged\n");
121
- updated.push(".husky/pre-commit");
122
- success("Initialized Husky with lint-staged");
123
- } catch {
124
- info("Husky not initialized (run: npx husky init)");
125
- }
126
- }
127
- }
128
- header("Upgrade Complete");
129
- info(`
130
- Version: v${projectVersion} \u2192 v${VERSION}`);
131
- if (updated.length > 0) {
132
- info("\nUpdated:");
133
- for (const file of updated) {
134
- listItem(file);
135
- }
136
- }
137
- if (unchanged.length > 0) {
138
- info("\nUnchanged:");
139
- for (const file of unchanged) {
140
- listItem(file);
141
- }
142
- }
143
- success(`
144
- Safeword upgraded to v${VERSION}`);
145
- } catch (err) {
146
- error(`Upgrade failed: ${err instanceof Error ? err.message : "Unknown error"}`);
147
- process.exit(1);
148
- }
149
- }
150
- export {
151
- upgrade
152
- };
153
- //# sourceMappingURL=upgrade-P3WX3ODU.js.map