safeword 0.6.0 → 0.6.1

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 (40) hide show
  1. package/dist/{check-KTLIM6UR.js → check-INXMFCL5.js} +7 -5
  2. package/dist/{check-KTLIM6UR.js.map → check-INXMFCL5.js.map} +1 -1
  3. package/dist/chunk-6CVTH67L.js +43 -0
  4. package/dist/chunk-6CVTH67L.js.map +1 -0
  5. package/dist/{chunk-KEWQQP4C.js → chunk-75FKNZUM.js} +2 -2
  6. package/dist/{chunk-NI3Z5XHH.js → chunk-ARIAOK2F.js} +2 -34
  7. package/dist/chunk-ARIAOK2F.js.map +1 -0
  8. package/dist/chunk-FRPJITGG.js +35 -0
  9. package/dist/chunk-FRPJITGG.js.map +1 -0
  10. package/dist/chunk-IWWBZVHT.js +274 -0
  11. package/dist/chunk-IWWBZVHT.js.map +1 -0
  12. package/dist/cli.js +9 -5
  13. package/dist/cli.js.map +1 -1
  14. package/dist/{diff-TV24EW3X.js → diff-L7G22MG7.js} +7 -5
  15. package/dist/{diff-TV24EW3X.js.map → diff-L7G22MG7.js.map} +1 -1
  16. package/dist/index.d.ts +4 -0
  17. package/dist/{reset-WJEQTYCX.js → reset-5SRM3P6J.js} +9 -7
  18. package/dist/reset-5SRM3P6J.js.map +1 -0
  19. package/dist/{setup-UYCYH5SE.js → setup-65EVU5OT.js} +179 -75
  20. package/dist/setup-65EVU5OT.js.map +1 -0
  21. package/dist/sync-4XBMKLXS.js +116 -0
  22. package/dist/sync-4XBMKLXS.js.map +1 -0
  23. package/dist/{upgrade-TJZYMNP2.js → upgrade-P3WX3ODU.js} +11 -9
  24. package/dist/{upgrade-TJZYMNP2.js.map → upgrade-P3WX3ODU.js.map} +1 -1
  25. package/package.json +2 -1
  26. package/templates/SAFEWORD.md +4 -4
  27. package/templates/commands/architecture.md +27 -0
  28. package/templates/commands/lint.md +15 -54
  29. package/templates/commands/quality-review.md +16 -13
  30. package/templates/hooks/post-tool-lint.sh +14 -53
  31. package/dist/chunk-74FG33MU.js +0 -211
  32. package/dist/chunk-74FG33MU.js.map +0 -1
  33. package/dist/chunk-NI3Z5XHH.js.map +0 -1
  34. package/dist/reset-WJEQTYCX.js.map +0 -1
  35. package/dist/setup-UYCYH5SE.js.map +0 -1
  36. package/templates/commands/arch-review.md +0 -28
  37. package/templates/hooks/git-pre-commit.sh +0 -18
  38. /package/dist/{chunk-KEWQQP4C.js.map → chunk-75FKNZUM.js.map} +0 -0
  39. /package/templates/prompts/{arch-review.md → architecture.md} +0 -0
  40. /package/templates/prompts/{quality-review.md → review.md} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/diff.ts"],"sourcesContent":["/**\n * Diff command - Preview changes that would be made by upgrade\n */\n\nimport { join } from 'node:path';\nimport { VERSION } from '../version.js';\nimport { exists, readFileSafe, getTemplatesDir } from '../utils/fs.js';\nimport { info, success, error, header, listItem } from '../utils/output.js';\n\nexport interface DiffOptions {\n verbose?: boolean;\n}\n\ninterface FileDiff {\n path: string;\n status: 'added' | 'modified' | 'unchanged';\n currentContent?: string;\n newContent: string;\n}\n\n/**\n * Create a unified diff between two strings\n */\nfunction createUnifiedDiff(oldContent: string, newContent: string, filename: string): string {\n const oldLines = oldContent.split('\\n');\n const newLines = newContent.split('\\n');\n\n const lines: string[] = [];\n lines.push(`--- a/${filename}`);\n lines.push(`+++ b/${filename}`);\n\n // Simple diff - show all changes\n // A real implementation would use a proper diff algorithm\n let hasChanges = false;\n\n const maxLines = Math.max(oldLines.length, newLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const oldLine = oldLines[i];\n const newLine = newLines[i];\n\n if (oldLine === newLine) {\n lines.push(` ${oldLine ?? ''}`);\n } else {\n hasChanges = true;\n if (oldLine !== undefined) {\n lines.push(`-${oldLine}`);\n }\n if (newLine !== undefined) {\n lines.push(`+${newLine}`);\n }\n }\n }\n\n if (!hasChanges) {\n return '';\n }\n\n // Add context marker\n lines.splice(2, 0, `@@ -1,${oldLines.length} +1,${newLines.length} @@`);\n\n return lines.join('\\n');\n}\n\n/**\n * Get all files that would be changed by upgrade\n */\nfunction getFileDiffs(cwd: string): FileDiff[] {\n const templatesDir = getTemplatesDir();\n const diffs: FileDiff[] = [];\n\n // Define files to check (template source -> install destination)\n const files: Array<{ templatePath: string; installPath: string }> = [\n { templatePath: 'SAFEWORD.md', installPath: '.safeword/SAFEWORD.md' },\n { templatePath: 'hooks/agents-md-check.sh', installPath: '.safeword/hooks/agents-md-check.sh' },\n { templatePath: 'hooks/pre-commit.sh', installPath: '.safeword/hooks/pre-commit.sh' },\n { templatePath: 'hooks/post-tool.sh', installPath: '.safeword/hooks/post-tool.sh' },\n { templatePath: 'hooks/inject-timestamp.sh', installPath: '.safeword/hooks/inject-timestamp.sh' },\n { templatePath: 'skills/safeword-quality-reviewer/SKILL.md', installPath: '.claude/skills/safeword-quality-reviewer/SKILL.md' },\n ];\n\n // Add version file (not from templates)\n const versionPath = join(cwd, '.safeword/version');\n const currentVersion = readFileSafe(versionPath);\n if (currentVersion === null) {\n diffs.push({ path: '.safeword/version', status: 'added', newContent: VERSION });\n } else if (currentVersion.trim() !== VERSION) {\n diffs.push({ path: '.safeword/version', status: 'modified', currentContent: currentVersion, newContent: VERSION });\n } else {\n diffs.push({ path: '.safeword/version', status: 'unchanged', currentContent: currentVersion, newContent: VERSION });\n }\n\n for (const file of files) {\n const templateFullPath = join(templatesDir, file.templatePath);\n const installFullPath = join(cwd, file.installPath);\n\n const newContent = readFileSafe(templateFullPath);\n if (newContent === null) continue; // Skip if template doesn't exist\n\n const currentContent = readFileSafe(installFullPath);\n\n if (currentContent === null) {\n diffs.push({\n path: file.installPath,\n status: 'added',\n newContent,\n });\n } else if (currentContent.trim() !== newContent.trim()) {\n diffs.push({\n path: file.installPath,\n status: 'modified',\n currentContent,\n newContent,\n });\n } else {\n diffs.push({\n path: file.installPath,\n status: 'unchanged',\n currentContent,\n newContent,\n });\n }\n }\n\n return diffs;\n}\n\nexport async function diff(options: DiffOptions): Promise<void> {\n const cwd = process.cwd();\n const safewordDir = join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDir)) {\n error('Not configured. Run `safeword setup` first.');\n process.exit(1);\n }\n\n // Read project version\n const versionPath = join(safewordDir, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? 'unknown';\n\n header('Safeword Diff');\n info(`Changes from v${projectVersion} → v${VERSION}`);\n\n const diffs = getFileDiffs(cwd);\n\n const added = diffs.filter(d => d.status === 'added');\n const modified = diffs.filter(d => d.status === 'modified');\n const unchanged = diffs.filter(d => d.status === 'unchanged');\n\n // Summary\n info(\n `\\nSummary: ${added.length} added, ${modified.length} modified, ${unchanged.length} unchanged`,\n );\n\n // List by category\n if (added.length > 0) {\n info('\\nAdded:');\n for (const file of added) {\n listItem(file.path);\n }\n }\n\n if (modified.length > 0) {\n info('\\nModified:');\n for (const file of modified) {\n listItem(file.path);\n }\n }\n\n if (unchanged.length > 0) {\n info('\\nUnchanged:');\n for (const file of unchanged) {\n listItem(file.path);\n }\n }\n\n // Verbose output - show actual diffs\n if (options.verbose) {\n header('Detailed Changes');\n\n for (const file of modified) {\n if (file.currentContent) {\n info(`\\n${file.path}:`);\n const diffOutput = createUnifiedDiff(file.currentContent, file.newContent, file.path);\n if (diffOutput) {\n console.log(diffOutput);\n }\n }\n }\n\n for (const file of added) {\n info(`\\n${file.path}: (new file)`);\n const lines = file.newContent.split('\\n').slice(0, 10);\n for (const line of lines) {\n console.log(`+${line}`);\n }\n if (file.newContent.split('\\n').length > 10) {\n console.log('... (truncated)');\n }\n }\n }\n\n if (added.length === 0 && modified.length === 0) {\n success('\\nNo changes needed - configuration is up to date');\n } else {\n info('\\nRun `safeword upgrade` to apply these changes');\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/commands/diff.ts"],"sourcesContent":["/**\n * Diff command - Preview changes that would be made by upgrade\n */\n\nimport { join } from 'node:path';\nimport { VERSION } from '../version.js';\nimport { exists, readFileSafe, getTemplatesDir } from '../utils/fs.js';\nimport { info, success, error, header, listItem } from '../utils/output.js';\n\nexport interface DiffOptions {\n verbose?: boolean;\n}\n\ninterface FileDiff {\n path: string;\n status: 'added' | 'modified' | 'unchanged';\n currentContent?: string;\n newContent: string;\n}\n\n/**\n * Create a unified diff between two strings\n */\nfunction createUnifiedDiff(oldContent: string, newContent: string, filename: string): string {\n const oldLines = oldContent.split('\\n');\n const newLines = newContent.split('\\n');\n\n const lines: string[] = [];\n lines.push(`--- a/${filename}`);\n lines.push(`+++ b/${filename}`);\n\n // Simple diff - show all changes\n // A real implementation would use a proper diff algorithm\n let hasChanges = false;\n\n const maxLines = Math.max(oldLines.length, newLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const oldLine = oldLines[i];\n const newLine = newLines[i];\n\n if (oldLine === newLine) {\n lines.push(` ${oldLine ?? ''}`);\n } else {\n hasChanges = true;\n if (oldLine !== undefined) {\n lines.push(`-${oldLine}`);\n }\n if (newLine !== undefined) {\n lines.push(`+${newLine}`);\n }\n }\n }\n\n if (!hasChanges) {\n return '';\n }\n\n // Add context marker\n lines.splice(2, 0, `@@ -1,${oldLines.length} +1,${newLines.length} @@`);\n\n return lines.join('\\n');\n}\n\n/**\n * Get all files that would be changed by upgrade\n */\nfunction getFileDiffs(cwd: string): FileDiff[] {\n const templatesDir = getTemplatesDir();\n const diffs: FileDiff[] = [];\n\n // Define files to check (template source -> install destination)\n const files: Array<{ templatePath: string; installPath: string }> = [\n { templatePath: 'SAFEWORD.md', installPath: '.safeword/SAFEWORD.md' },\n { templatePath: 'hooks/agents-md-check.sh', installPath: '.safeword/hooks/agents-md-check.sh' },\n { templatePath: 'hooks/pre-commit.sh', installPath: '.safeword/hooks/pre-commit.sh' },\n { templatePath: 'hooks/post-tool.sh', installPath: '.safeword/hooks/post-tool.sh' },\n { templatePath: 'hooks/inject-timestamp.sh', installPath: '.safeword/hooks/inject-timestamp.sh' },\n { templatePath: 'skills/safeword-quality-reviewer/SKILL.md', installPath: '.claude/skills/safeword-quality-reviewer/SKILL.md' },\n ];\n\n // Add version file (not from templates)\n const versionPath = join(cwd, '.safeword/version');\n const currentVersion = readFileSafe(versionPath);\n if (currentVersion === null) {\n diffs.push({ path: '.safeword/version', status: 'added', newContent: VERSION });\n } else if (currentVersion.trim() !== VERSION) {\n diffs.push({ path: '.safeword/version', status: 'modified', currentContent: currentVersion, newContent: VERSION });\n } else {\n diffs.push({ path: '.safeword/version', status: 'unchanged', currentContent: currentVersion, newContent: VERSION });\n }\n\n for (const file of files) {\n const templateFullPath = join(templatesDir, file.templatePath);\n const installFullPath = join(cwd, file.installPath);\n\n const newContent = readFileSafe(templateFullPath);\n if (newContent === null) continue; // Skip if template doesn't exist\n\n const currentContent = readFileSafe(installFullPath);\n\n if (currentContent === null) {\n diffs.push({\n path: file.installPath,\n status: 'added',\n newContent,\n });\n } else if (currentContent.trim() !== newContent.trim()) {\n diffs.push({\n path: file.installPath,\n status: 'modified',\n currentContent,\n newContent,\n });\n } else {\n diffs.push({\n path: file.installPath,\n status: 'unchanged',\n currentContent,\n newContent,\n });\n }\n }\n\n return diffs;\n}\n\nexport async function diff(options: DiffOptions): Promise<void> {\n const cwd = process.cwd();\n const safewordDir = join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDir)) {\n error('Not configured. Run `safeword setup` first.');\n process.exit(1);\n }\n\n // Read project version\n const versionPath = join(safewordDir, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? 'unknown';\n\n header('Safeword Diff');\n info(`Changes from v${projectVersion} → v${VERSION}`);\n\n const diffs = getFileDiffs(cwd);\n\n const added = diffs.filter(d => d.status === 'added');\n const modified = diffs.filter(d => d.status === 'modified');\n const unchanged = diffs.filter(d => d.status === 'unchanged');\n\n // Summary\n info(\n `\\nSummary: ${added.length} added, ${modified.length} modified, ${unchanged.length} unchanged`,\n );\n\n // List by category\n if (added.length > 0) {\n info('\\nAdded:');\n for (const file of added) {\n listItem(file.path);\n }\n }\n\n if (modified.length > 0) {\n info('\\nModified:');\n for (const file of modified) {\n listItem(file.path);\n }\n }\n\n if (unchanged.length > 0) {\n info('\\nUnchanged:');\n for (const file of unchanged) {\n listItem(file.path);\n }\n }\n\n // Verbose output - show actual diffs\n if (options.verbose) {\n header('Detailed Changes');\n\n for (const file of modified) {\n if (file.currentContent) {\n info(`\\n${file.path}:`);\n const diffOutput = createUnifiedDiff(file.currentContent, file.newContent, file.path);\n if (diffOutput) {\n console.log(diffOutput);\n }\n }\n }\n\n for (const file of added) {\n info(`\\n${file.path}: (new file)`);\n const lines = file.newContent.split('\\n').slice(0, 10);\n for (const line of lines) {\n console.log(`+${line}`);\n }\n if (file.newContent.split('\\n').length > 10) {\n console.log('... (truncated)');\n }\n }\n }\n\n if (added.length === 0 && modified.length === 0) {\n success('\\nNo changes needed - configuration is up to date');\n } else {\n info('\\nRun `safeword upgrade` to apply these changes');\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -10,6 +10,10 @@ interface ProjectType {
10
10
  svelte: boolean;
11
11
  sveltekit: boolean;
12
12
  electron: boolean;
13
+ vitest: boolean;
14
+ playwright: boolean;
15
+ tailwind: boolean;
16
+ publishableLibrary: boolean;
13
17
  }
14
18
 
15
19
  export { type ProjectType, VERSION };
@@ -1,19 +1,21 @@
1
1
  import {
2
2
  filterOutSafewordHooks,
3
3
  removeAgentsMdLink
4
- } from "./chunk-74FG33MU.js";
4
+ } from "./chunk-IWWBZVHT.js";
5
5
  import {
6
6
  error,
7
- exists,
8
7
  header,
9
8
  info,
10
- listDir,
11
9
  listItem,
10
+ success
11
+ } from "./chunk-FRPJITGG.js";
12
+ import {
13
+ exists,
14
+ listDir,
12
15
  readJson,
13
16
  remove,
14
- success,
15
17
  writeJson
16
- } from "./chunk-NI3Z5XHH.js";
18
+ } from "./chunk-ARIAOK2F.js";
17
19
 
18
20
  // src/commands/reset.ts
19
21
  import { join } from "path";
@@ -73,7 +75,7 @@ async function reset(options) {
73
75
  }
74
76
  }
75
77
  const commandsDir = join(cwd, ".claude", "commands");
76
- const safewordCommands = ["quality-review.md", "arch-review.md", "lint.md"];
78
+ const safewordCommands = ["review.md", "architecture.md", "lint.md"];
77
79
  if (exists(commandsDir)) {
78
80
  info("\nRemoving safeword commands...");
79
81
  let commandsRemoved = false;
@@ -140,4 +142,4 @@ async function reset(options) {
140
142
  export {
141
143
  reset
142
144
  };
143
- //# sourceMappingURL=reset-WJEQTYCX.js.map
145
+ //# sourceMappingURL=reset-5SRM3P6J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/reset.ts"],"sourcesContent":["/**\n * Reset command - Remove safeword configuration from project\n */\n\nimport { join } from 'node:path';\nimport { exists, remove, readJson, writeJson, listDir } from '../utils/fs.js';\nimport { info, success, error, header, listItem } from '../utils/output.js';\nimport { filterOutSafewordHooks } from '../utils/hooks.js';\nimport { removeAgentsMdLink } from '../utils/agents-md.js';\n\nexport interface ResetOptions {\n yes?: boolean;\n}\n\nexport async function reset(options: ResetOptions): Promise<void> {\n const cwd = process.cwd();\n const safewordDir = join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDir)) {\n info('Nothing to remove. Project is not configured with safeword.');\n return;\n }\n\n const isNonInteractive = options.yes || !process.stdin.isTTY;\n\n // Confirmation (in interactive mode without --yes)\n if (!isNonInteractive) {\n // In a real implementation, we'd prompt here\n // For now, non-TTY mode auto-confirms\n }\n\n header('Safeword Reset');\n info('Removing safeword configuration...');\n\n const removed: string[] = [];\n\n try {\n // 1. Remove .safeword directory\n if (exists(safewordDir)) {\n remove(safewordDir);\n removed.push('.safeword/');\n success('Removed .safeword directory');\n }\n\n // 2. Remove safeword hooks from .claude/settings.json\n const settingsPath = join(cwd, '.claude', 'settings.json');\n\n if (exists(settingsPath)) {\n info('\\nRemoving hooks from .claude/settings.json...');\n\n interface SettingsJson {\n hooks?: Record<string, unknown[]>;\n [key: string]: unknown;\n }\n\n const settings = readJson<SettingsJson>(settingsPath);\n\n if (settings?.hooks) {\n let modified = false;\n\n for (const [event, hooks] of Object.entries(settings.hooks)) {\n if (Array.isArray(hooks)) {\n const filtered = filterOutSafewordHooks(hooks);\n if (filtered.length !== hooks.length) {\n settings.hooks[event] = filtered;\n modified = true;\n }\n }\n }\n\n if (modified) {\n writeJson(settingsPath, settings);\n removed.push('.claude/settings.json (hooks)');\n success('Removed safeword hooks');\n }\n }\n }\n\n // 3. Remove safeword skills\n const skillsDir = join(cwd, '.claude', 'skills');\n\n if (exists(skillsDir)) {\n info('\\nRemoving safeword skills...');\n\n const skills = listDir(skillsDir);\n for (const skill of skills) {\n if (skill.startsWith('safeword-')) {\n remove(join(skillsDir, skill));\n removed.push(`.claude/skills/${skill}/`);\n }\n }\n\n if (removed.some(r => r.includes('skills'))) {\n success('Removed safeword skills');\n }\n }\n\n // 3.5. Remove safeword slash commands\n const commandsDir = join(cwd, '.claude', 'commands');\n const safewordCommands = ['review.md', 'architecture.md', 'lint.md'];\n\n if (exists(commandsDir)) {\n info('\\nRemoving safeword commands...');\n\n let commandsRemoved = false;\n for (const cmd of safewordCommands) {\n const cmdPath = join(commandsDir, cmd);\n if (exists(cmdPath)) {\n remove(cmdPath);\n removed.push(`.claude/commands/${cmd}`);\n commandsRemoved = true;\n }\n }\n\n if (commandsRemoved) {\n success('Removed safeword commands');\n }\n }\n\n // 3.6. Remove MCP servers from .mcp.json\n const mcpConfigPath = join(cwd, '.mcp.json');\n\n if (exists(mcpConfigPath)) {\n info('\\nRemoving MCP servers...');\n\n interface McpConfig {\n mcpServers?: Record<string, unknown>;\n [key: string]: unknown;\n }\n\n const mcpConfig = readJson<McpConfig>(mcpConfigPath);\n\n if (mcpConfig?.mcpServers) {\n // Remove safeword MCP servers\n delete mcpConfig.mcpServers.context7;\n delete mcpConfig.mcpServers.playwright;\n\n // If no servers left, remove the file\n if (Object.keys(mcpConfig.mcpServers).length === 0) {\n remove(mcpConfigPath);\n removed.push('.mcp.json');\n } else {\n writeJson(mcpConfigPath, mcpConfig);\n removed.push('.mcp.json (context7, playwright)');\n }\n\n success('Removed MCP servers');\n }\n }\n\n // 4. Remove Husky directory\n const huskyDir = join(cwd, '.husky');\n if (exists(huskyDir)) {\n info('\\nRemoving Husky hooks...');\n remove(huskyDir);\n removed.push('.husky/');\n success('Removed Husky hooks');\n }\n\n // 5. Remove link from AGENTS.md\n info('\\nCleaning AGENTS.md...');\n if (removeAgentsMdLink(cwd)) {\n removed.push('AGENTS.md (link)');\n success('Removed safeword link from AGENTS.md');\n }\n\n // Print summary\n header('Reset Complete');\n\n if (removed.length > 0) {\n info('\\nRemoved:');\n for (const item of removed) {\n listItem(item);\n }\n }\n\n // Note about preserved linting\n info('\\nPreserved (remove manually if desired):');\n listItem('eslint.config.mjs');\n listItem('.prettierrc');\n listItem('.markdownlint-cli2.jsonc');\n listItem('package.json (scripts, lint-staged config)');\n listItem('devDependencies (eslint, prettier, husky, lint-staged, etc.)');\n\n success('\\nSafeword configuration removed');\n } catch (err) {\n error(`Reset failed: ${err instanceof Error ? err.message : 'Unknown error'}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isGitRepo
3
- } from "./chunk-KEWQQP4C.js";
3
+ } from "./chunk-75FKNZUM.js";
4
4
  import {
5
5
  VERSION
6
6
  } from "./chunk-ORQHKDT2.js";
@@ -11,69 +11,137 @@ import {
11
11
  ensureAgentsMdLink,
12
12
  filterOutSafewordHooks,
13
13
  getEslintConfig
14
- } from "./chunk-74FG33MU.js";
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";
15
26
  import {
16
27
  copyDir,
17
28
  copyFile,
18
29
  ensureDir,
19
- error,
20
30
  exists,
21
31
  getTemplatesDir,
22
- header,
23
- info,
24
- listItem,
25
32
  makeScriptsExecutable,
26
33
  readJson,
27
- success,
28
34
  updateJson,
29
- warn,
30
35
  writeFile,
31
36
  writeJson
32
- } from "./chunk-NI3Z5XHH.js";
37
+ } from "./chunk-ARIAOK2F.js";
33
38
 
34
39
  // src/commands/setup.ts
35
- import { join, basename } from "path";
40
+ import { join as join2, basename } from "path";
36
41
 
37
- // src/utils/project-detector.ts
38
- function detectProjectType(packageJson) {
39
- const deps = packageJson.dependencies || {};
40
- const devDeps = packageJson.devDependencies || {};
41
- const allDeps = { ...deps, ...devDeps };
42
- const hasTypescript = "typescript" in allDeps;
43
- const hasReact = "react" in deps || "react" in devDeps;
44
- const hasNextJs = "next" in deps;
45
- const hasAstro = "astro" in deps || "astro" in devDeps;
46
- const hasVue = "vue" in deps || "vue" in devDeps;
47
- const hasNuxt = "nuxt" in deps;
48
- const hasSvelte = "svelte" in deps || "svelte" in devDeps;
49
- const hasSvelteKit = "@sveltejs/kit" in deps || "@sveltejs/kit" in devDeps;
50
- const hasElectron = "electron" in deps || "electron" in devDeps;
51
- return {
52
- typescript: hasTypescript,
53
- react: hasReact || hasNextJs,
54
- // Next.js implies React
55
- nextjs: hasNextJs,
56
- astro: hasAstro,
57
- vue: hasVue || hasNuxt,
58
- // Nuxt implies Vue
59
- nuxt: hasNuxt,
60
- svelte: hasSvelte || hasSvelteKit,
61
- // SvelteKit implies Svelte
62
- sveltekit: hasSvelteKit,
63
- electron: hasElectron
64
- };
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
+ `;
65
133
  }
66
134
 
67
135
  // src/commands/setup.ts
68
136
  import { execSync } from "child_process";
69
137
  async function setup(options) {
70
138
  const cwd = process.cwd();
71
- const safewordDir = join(cwd, ".safeword");
139
+ const safewordDir = join2(cwd, ".safeword");
72
140
  if (exists(safewordDir)) {
73
141
  error("Already configured. Run `safeword upgrade` to update.");
74
142
  process.exit(1);
75
143
  }
76
- const packageJsonPath = join(cwd, "package.json");
144
+ const packageJsonPath = join2(cwd, "package.json");
77
145
  let packageJsonCreated = false;
78
146
  if (!exists(packageJsonPath)) {
79
147
  const dirName = basename(cwd) || "project";
@@ -97,19 +165,19 @@ async function setup(options) {
97
165
  const templatesDir = getTemplatesDir();
98
166
  info("\nCreating .safeword directory...");
99
167
  ensureDir(safewordDir);
100
- ensureDir(join(safewordDir, "learnings"));
101
- ensureDir(join(safewordDir, "planning", "user-stories"));
102
- ensureDir(join(safewordDir, "planning", "design"));
103
- ensureDir(join(safewordDir, "tickets", "completed"));
104
- copyFile(join(templatesDir, "SAFEWORD.md"), join(safewordDir, "SAFEWORD.md"));
105
- writeFile(join(safewordDir, "version"), VERSION);
106
- copyDir(join(templatesDir, "guides"), join(safewordDir, "guides"));
107
- copyDir(join(templatesDir, "doc-templates"), join(safewordDir, "templates"));
108
- copyDir(join(templatesDir, "prompts"), join(safewordDir, "prompts"));
109
- copyDir(join(templatesDir, "lib"), join(safewordDir, "lib"));
110
- makeScriptsExecutable(join(safewordDir, "lib"));
111
- copyDir(join(templatesDir, "hooks"), join(safewordDir, "hooks"));
112
- makeScriptsExecutable(join(safewordDir, "hooks"));
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"));
113
181
  created.push(".safeword/");
114
182
  success("Created .safeword directory");
115
183
  info("\nConfiguring AGENTS.md...");
@@ -124,8 +192,8 @@ async function setup(options) {
124
192
  info("AGENTS.md already has safeword link");
125
193
  }
126
194
  info("\nRegistering Claude Code hooks...");
127
- const claudeDir = join(cwd, ".claude");
128
- const settingsPath = join(claudeDir, "settings.json");
195
+ const claudeDir = join2(cwd, ".claude");
196
+ const settingsPath = join2(claudeDir, "settings.json");
129
197
  ensureDir(claudeDir);
130
198
  try {
131
199
  updateJson(settingsPath, (existing) => {
@@ -148,17 +216,17 @@ async function setup(options) {
148
216
  process.exit(1);
149
217
  }
150
218
  info("\nInstalling skills...");
151
- const skillsDir = join(claudeDir, "skills");
152
- copyDir(join(templatesDir, "skills"), skillsDir);
219
+ const skillsDir = join2(claudeDir, "skills");
220
+ copyDir(join2(templatesDir, "skills"), skillsDir);
153
221
  created.push(".claude/skills/safeword-quality-reviewer/");
154
222
  success("Installed skills");
155
223
  info("\nInstalling slash commands...");
156
- const commandsDir = join(claudeDir, "commands");
157
- copyDir(join(templatesDir, "commands"), commandsDir);
224
+ const commandsDir = join2(claudeDir, "commands");
225
+ copyDir(join2(templatesDir, "commands"), commandsDir);
158
226
  created.push(".claude/commands/");
159
227
  success("Installed slash commands");
160
228
  info("\nConfiguring MCP servers...");
161
- const mcpConfigPath = join(cwd, ".mcp.json");
229
+ const mcpConfigPath = join2(cwd, ".mcp.json");
162
230
  updateJson(mcpConfigPath, (existing) => {
163
231
  const mcpServers = existing?.mcpServers ?? {};
164
232
  mcpServers.context7 = {
@@ -184,15 +252,24 @@ async function setup(options) {
184
252
  process.exit(1);
185
253
  }
186
254
  const projectType = detectProjectType(packageJson);
187
- const eslintConfigPath = join(cwd, "eslint.config.mjs");
255
+ const architecture = detectArchitecture(cwd);
256
+ const eslintConfigPath = join2(cwd, "eslint.config.mjs");
188
257
  if (!exists(eslintConfigPath)) {
189
- writeFile(eslintConfigPath, getEslintConfig(projectType));
258
+ writeFile(eslintConfigPath, getEslintConfig({ boundaries: true }));
190
259
  created.push("eslint.config.mjs");
191
- success("Created eslint.config.mjs");
260
+ success("Created eslint.config.mjs (dynamic - adapts to framework changes)");
192
261
  } else {
193
262
  info("eslint.config.mjs already exists");
194
263
  }
195
- const prettierrcPath = join(cwd, ".prettierrc");
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");
196
273
  if (!exists(prettierrcPath)) {
197
274
  writeFile(prettierrcPath, PRETTIERRC);
198
275
  created.push(".prettierrc");
@@ -200,9 +277,9 @@ async function setup(options) {
200
277
  } else {
201
278
  info(".prettierrc already exists");
202
279
  }
203
- const markdownlintPath = join(cwd, ".markdownlint-cli2.jsonc");
280
+ const markdownlintPath = join2(cwd, ".markdownlint-cli2.jsonc");
204
281
  if (!exists(markdownlintPath)) {
205
- copyFile(join(templatesDir, "markdownlint-cli2.jsonc"), markdownlintPath);
282
+ copyFile(join2(templatesDir, "markdownlint-cli2.jsonc"), markdownlintPath);
206
283
  created.push(".markdownlint-cli2.jsonc");
207
284
  success("Created .markdownlint-cli2.jsonc");
208
285
  } else {
@@ -227,8 +304,16 @@ async function setup(options) {
227
304
  scripts["format:check"] = "prettier --check .";
228
305
  packageJsonModified = true;
229
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
+ }
230
315
  if (!scripts.prepare) {
231
- scripts.prepare = "husky";
316
+ scripts.prepare = "husky || true";
232
317
  packageJsonModified = true;
233
318
  }
234
319
  if (!packageJson["lint-staged"]) {
@@ -252,8 +337,12 @@ async function setup(options) {
252
337
  "eslint",
253
338
  "prettier",
254
339
  "@eslint/js",
340
+ "eslint-plugin-import-x",
341
+ "eslint-plugin-sonarjs",
342
+ "@microsoft/eslint-plugin-sdl",
255
343
  "eslint-config-prettier",
256
344
  "markdownlint-cli2",
345
+ "knip",
257
346
  "husky",
258
347
  "lint-staged"
259
348
  ];
@@ -261,7 +350,7 @@ async function setup(options) {
261
350
  devDeps.push("typescript-eslint");
262
351
  }
263
352
  if (projectType.react || projectType.nextjs) {
264
- devDeps.push("eslint-plugin-react", "eslint-plugin-react-hooks");
353
+ devDeps.push("eslint-plugin-react", "eslint-plugin-react-hooks", "eslint-plugin-jsx-a11y");
265
354
  }
266
355
  if (projectType.nextjs) {
267
356
  devDeps.push("@next/eslint-plugin-next");
@@ -275,25 +364,40 @@ async function setup(options) {
275
364
  if (projectType.svelte) {
276
365
  devDeps.push("eslint-plugin-svelte");
277
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
+ }
278
381
  try {
279
382
  const installCmd = `npm install -D ${devDeps.join(" ")}`;
280
383
  info(`Running: ${installCmd}`);
281
384
  execSync(installCmd, { cwd, stdio: "inherit" });
282
385
  success("Installed linting dependencies");
283
- } catch (err) {
386
+ } catch {
284
387
  warn("Failed to install dependencies. Run manually:");
285
388
  listItem(`npm install -D ${devDeps.join(" ")}`);
286
389
  }
287
390
  info("\nConfiguring git hooks with Husky...");
288
391
  if (isGitRepo(cwd)) {
289
392
  try {
290
- const huskyDir = join(cwd, ".husky");
393
+ const huskyDir = join2(cwd, ".husky");
291
394
  ensureDir(huskyDir);
292
- const huskyPreCommit = join(huskyDir, "pre-commit");
293
- writeFile(huskyPreCommit, "npx lint-staged\n");
395
+ const huskyPreCommit = join2(huskyDir, "pre-commit");
396
+ writeFile(huskyPreCommit, "npx safeword sync --quiet --stage\nnpx lint-staged\n");
397
+ makeScriptsExecutable(huskyDir);
294
398
  created.push(".husky/pre-commit");
295
399
  success("Configured Husky with lint-staged pre-commit hook");
296
- } catch (err) {
400
+ } catch {
297
401
  warn("Failed to setup Husky. Run manually:");
298
402
  listItem("mkdir -p .husky");
299
403
  listItem('echo "npx lint-staged" > .husky/pre-commit');
@@ -302,7 +406,7 @@ async function setup(options) {
302
406
  warn("Skipped Husky setup (no git repository)");
303
407
  } else {
304
408
  warn("Skipped Husky setup (no .git directory)");
305
- info('Initialize git and run: mkdir -p .husky && echo "npx lint-staged" > .husky/pre-commit');
409
+ info("Initialize git and run safeword setup again to enable pre-commit hooks");
306
410
  }
307
411
  header("Setup Complete");
308
412
  if (created.length > 0) {
@@ -330,4 +434,4 @@ Safeword ${VERSION} installed successfully!`);
330
434
  export {
331
435
  setup
332
436
  };
333
- //# sourceMappingURL=setup-UYCYH5SE.js.map
437
+ //# sourceMappingURL=setup-65EVU5OT.js.map
@@ -0,0 +1 @@
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"]}