safeword 0.2.9 → 0.3.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.
@@ -114,12 +114,20 @@ function getEslintConfig(options) {
114
114
  '{ plugins: { "react-hooks": reactHooks }, rules: reactHooks.configs.recommended.rules }'
115
115
  );
116
116
  }
117
+ if (options.nextjs) {
118
+ imports.push('import nextPlugin from "@next/eslint-plugin-next";');
119
+ configs.push('{ plugins: { "@next/next": nextPlugin }, rules: nextPlugin.configs.recommended.rules }');
120
+ }
121
+ if (options.astro) {
122
+ imports.push('import eslintPluginAstro from "eslint-plugin-astro";');
123
+ configs.push("...eslintPluginAstro.configs.recommended");
124
+ }
117
125
  return `${imports.join("\n")}
118
126
 
119
127
  export default [
120
128
  ${configs.join(",\n ")},
121
129
  {
122
- ignores: ["node_modules/", "dist/", ".next/", "build/"],
130
+ ignores: ["node_modules/", "dist/", ".next/", ".astro/", "build/"],
123
131
  },
124
132
  ];
125
133
  `;
@@ -192,4 +200,4 @@ export {
192
200
  ensureAgentsMdLink,
193
201
  removeAgentsMdLink
194
202
  };
195
- //# sourceMappingURL=chunk-EGFXVTSE.js.map
203
+ //# sourceMappingURL=chunk-TFNBQCKM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/git.ts","../src/utils/hooks.ts","../src/templates/content.ts","../src/templates/config.ts","../src/utils/agents-md.ts"],"sourcesContent":["/**\n * Git utilities for CLI operations\n */\n\nimport { execSync } from 'node:child_process';\nimport { join } from 'node:path';\nimport { exists, readFile, writeFile, ensureDir, makeExecutable } from './fs.js';\n\nconst MARKER_START = '# SAFEWORD_ARCH_CHECK_START';\nconst MARKER_END = '# SAFEWORD_ARCH_CHECK_END';\n\n/**\n * Check if directory is a git repository\n */\nexport function isGitRepo(cwd: string): boolean {\n return exists(join(cwd, '.git'));\n}\n\n/**\n * Initialize a git repository\n */\nexport function initGitRepo(cwd: string): void {\n execSync('git init', { cwd, stdio: 'pipe' });\n}\n\n/**\n * Get the pre-commit hook content to add\n */\nfunction getHookContent(): string {\n return `\n${MARKER_START}\n# Safeword architecture check\n# This section is managed by safeword - do not edit manually\nif [ -f \".safeword/hooks/pre-commit.sh\" ]; then\n bash .safeword/hooks/pre-commit.sh\nfi\n${MARKER_END}\n`;\n}\n\n/**\n * Install safeword markers into pre-commit hook\n */\nexport function installGitHook(cwd: string): void {\n const hooksDir = join(cwd, '.git', 'hooks');\n const hookPath = join(hooksDir, 'pre-commit');\n\n ensureDir(hooksDir);\n\n let content = '';\n\n if (exists(hookPath)) {\n content = readFile(hookPath);\n\n // Check if already has safeword markers\n if (content.includes(MARKER_START)) {\n // Remove existing safeword section and re-add (update)\n content = removeMarkerSection(content);\n }\n } else {\n // Create new hook file with shebang\n content = '#!/bin/bash\\n';\n }\n\n // Add safeword section\n content = content.trimEnd() + '\\n' + getHookContent();\n\n writeFile(hookPath, content);\n makeExecutable(hookPath);\n}\n\n/**\n * Remove safeword markers from pre-commit hook\n */\nexport function removeGitHook(cwd: string): void {\n const hookPath = join(cwd, '.git', 'hooks', 'pre-commit');\n\n if (!exists(hookPath)) return;\n\n let content = readFile(hookPath);\n\n if (!content.includes(MARKER_START)) return;\n\n content = removeMarkerSection(content);\n\n // If only shebang remains, we could delete the file\n // but safer to leave it\n writeFile(hookPath, content);\n}\n\n/**\n * Remove the section between markers (inclusive)\n */\nfunction removeMarkerSection(content: string): string {\n const lines = content.split('\\n');\n const result: string[] = [];\n let inMarkerSection = false;\n\n for (const line of lines) {\n if (line.includes(MARKER_START)) {\n inMarkerSection = true;\n continue;\n }\n if (line.includes(MARKER_END)) {\n inMarkerSection = false;\n continue;\n }\n if (!inMarkerSection) {\n result.push(line);\n }\n }\n\n return result.join('\\n').trim() + '\\n';\n}\n\n/**\n * Check if git hooks have safeword markers\n */\nexport function hasGitHook(cwd: string): boolean {\n const hookPath = join(cwd, '.git', 'hooks', 'pre-commit');\n if (!exists(hookPath)) return false;\n const content = readFile(hookPath);\n return content.includes(MARKER_START);\n}\n","/**\n * Hook utilities for Claude Code settings\n */\n\n/**\n * Type guard to check if a value is a hook object with a command property\n */\nexport function isHookObject(h: unknown): h is { command: string } {\n return (\n typeof h === 'object' &&\n h !== null &&\n 'command' in h &&\n typeof (h as { command: string }).command === 'string'\n );\n}\n\n/**\n * Check if a hook is a safeword hook (command contains '.safeword')\n */\nexport function isSafewordHook(h: unknown): boolean {\n return isHookObject(h) && h.command.includes('.safeword');\n}\n\n/**\n * Filter out safeword hooks from an array of hooks\n */\nexport function filterOutSafewordHooks(hooks: unknown[]): unknown[] {\n return hooks.filter((h) => !isSafewordHook(h));\n}\n","/**\n * Content templates - static string content\n *\n * Note: Most templates (SAFEWORD.md, hooks, skills, guides, etc.) are now\n * file-based in the templates/ directory. This file contains only small\n * string constants that are used inline.\n */\n\nexport const AGENTS_MD_LINK = `**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**\n\nThe SAFEWORD.md file contains core development patterns, workflows, and conventions.\nRead it BEFORE working on any task in this project.\n\n---`;\n\nexport const PRETTIERRC = `{\n \"semi\": true,\n \"singleQuote\": true,\n \"tabWidth\": 2,\n \"trailingComma\": \"es5\",\n \"printWidth\": 100\n}\n`;\n","/**\n * Configuration templates - ESLint config generation and hook settings\n */\n\nexport function getEslintConfig(options: {\n typescript?: boolean;\n react?: boolean;\n nextjs?: boolean;\n astro?: boolean;\n}): string {\n const imports: string[] = ['import js from \"@eslint/js\";'];\n const configs: string[] = ['js.configs.recommended'];\n\n if (options.typescript) {\n imports.push('import tseslint from \"typescript-eslint\";');\n configs.push('...tseslint.configs.recommended');\n }\n\n if (options.react || options.nextjs) {\n imports.push('import react from \"eslint-plugin-react\";');\n imports.push('import reactHooks from \"eslint-plugin-react-hooks\";');\n configs.push('react.configs.flat.recommended');\n configs.push('react.configs.flat[\"jsx-runtime\"]');\n configs.push(\n '{ plugins: { \"react-hooks\": reactHooks }, rules: reactHooks.configs.recommended.rules }',\n );\n }\n\n if (options.nextjs) {\n imports.push('import nextPlugin from \"@next/eslint-plugin-next\";');\n configs.push('{ plugins: { \"@next/next\": nextPlugin }, rules: nextPlugin.configs.recommended.rules }');\n }\n\n if (options.astro) {\n imports.push('import eslintPluginAstro from \"eslint-plugin-astro\";');\n configs.push('...eslintPluginAstro.configs.recommended');\n }\n\n return `${imports.join('\\n')}\n\nexport default [\n ${configs.join(',\\n ')},\n {\n ignores: [\"node_modules/\", \"dist/\", \".next/\", \".astro/\", \"build/\"],\n },\n];\n`;\n}\n\nexport const SETTINGS_HOOKS = {\n SessionStart: [\n {\n command: 'bash .safeword/hooks/agents-md-check.sh',\n description: 'Safeword: Verify AGENTS.md link',\n },\n ],\n UserPromptSubmit: [\n {\n command: 'bash .safeword/hooks/inject-timestamp.sh',\n description: 'Safeword: Inject current timestamp',\n },\n ],\n PostToolUse: [\n {\n command: 'bash .safeword/hooks/post-tool.sh 2>/dev/null || true',\n description: 'Safeword: Post-tool validation',\n },\n ],\n};\n","/**\n * AGENTS.md file utilities\n */\n\nimport { join } from 'node:path';\nimport { exists, readFile, writeFile } from './fs.js';\nimport { AGENTS_MD_LINK } from '../templates/index.js';\n\nconst SAFEWORD_LINK_MARKER = '@./.safeword/SAFEWORD.md';\n\n/**\n * Check if AGENTS.md has the safeword link\n */\nexport function hasAgentsMdLink(cwd: string): boolean {\n const agentsMdPath = join(cwd, 'AGENTS.md');\n if (!exists(agentsMdPath)) return false;\n const content = readFile(agentsMdPath);\n return content.includes(SAFEWORD_LINK_MARKER);\n}\n\n/**\n * Ensure AGENTS.md exists and has the safeword link.\n * Returns 'created' | 'modified' | 'unchanged'\n */\nexport function ensureAgentsMdLink(cwd: string): 'created' | 'modified' | 'unchanged' {\n const agentsMdPath = join(cwd, 'AGENTS.md');\n\n if (!exists(agentsMdPath)) {\n writeFile(agentsMdPath, `${AGENTS_MD_LINK}\\n`);\n return 'created';\n }\n\n const content = readFile(agentsMdPath);\n if (!content.includes(SAFEWORD_LINK_MARKER)) {\n writeFile(agentsMdPath, `${AGENTS_MD_LINK}\\n\\n${content}`);\n return 'modified';\n }\n\n return 'unchanged';\n}\n\n/**\n * Remove safeword link block from AGENTS.md.\n * Returns true if link was removed.\n */\nexport function removeAgentsMdLink(cwd: string): boolean {\n const agentsMdPath = join(cwd, 'AGENTS.md');\n if (!exists(agentsMdPath)) return false;\n\n const content = readFile(agentsMdPath);\n\n // Remove the entire AGENTS_MD_LINK block if present\n let newContent = content.replace(AGENTS_MD_LINK, '');\n\n // Also handle legacy single-line format (filter any remaining lines with marker)\n const lines = newContent.split('\\n').filter((line) => !line.includes(SAFEWORD_LINK_MARKER));\n\n // Remove extra blank lines and separators at the start\n while (lines.length > 0 && (lines[0].trim() === '' || lines[0].trim() === '---')) {\n lines.shift();\n }\n\n newContent = lines.join('\\n');\n\n if (newContent !== content) {\n writeFile(agentsMdPath, newContent);\n return true;\n }\n\n return false;\n}\n"],"mappings":"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","names":["join","join"]}
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { Command } from "commander";
8
8
  var program = new Command();
9
9
  program.name("safeword").description("CLI for setting up and managing safeword development environments").version(VERSION);
10
10
  program.command("setup").description("Set up safeword in the current project").option("-y, --yes", "Accept all defaults (non-interactive mode)").action(async (options) => {
11
- const { setup } = await import("./setup-S5Z7QIOH.js");
11
+ const { setup } = await import("./setup-D3CYQWGK.js");
12
12
  await setup(options);
13
13
  });
14
14
  program.command("check").description("Check project health and versions").option("--offline", "Skip remote version check").action(async (options) => {
@@ -16,7 +16,7 @@ program.command("check").description("Check project health and versions").option
16
16
  await check(options);
17
17
  });
18
18
  program.command("upgrade").description("Upgrade safeword configuration to latest version").action(async () => {
19
- const { upgrade } = await import("./upgrade-JDXMLJGU.js");
19
+ const { upgrade } = await import("./upgrade-RGBXJUU4.js");
20
20
  await upgrade();
21
21
  });
22
22
  program.command("diff").description("Preview changes that would be made by upgrade").option("-v, --verbose", "Show full diff output").action(async (options) => {
@@ -24,7 +24,7 @@ program.command("diff").description("Preview changes that would be made by upgra
24
24
  await diff(options);
25
25
  });
26
26
  program.command("reset").description("Remove safeword configuration from project").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
27
- const { reset } = await import("./reset-SBVTZOSF.js");
27
+ const { reset } = await import("./reset-MGB3ODNQ.js");
28
28
  await reset(options);
29
29
  });
30
30
  if (process.argv.length === 2) {
@@ -3,7 +3,7 @@ import {
3
3
  isGitRepo,
4
4
  removeAgentsMdLink,
5
5
  removeGitHook
6
- } from "./chunk-EGFXVTSE.js";
6
+ } from "./chunk-TFNBQCKM.js";
7
7
  import {
8
8
  error,
9
9
  exists,
@@ -140,4 +140,4 @@ async function reset(options) {
140
140
  export {
141
141
  reset
142
142
  };
143
- //# sourceMappingURL=reset-SBVTZOSF.js.map
143
+ //# sourceMappingURL=reset-MGB3ODNQ.js.map
@@ -9,7 +9,7 @@ import {
9
9
  getEslintConfig,
10
10
  installGitHook,
11
11
  isGitRepo
12
- } from "./chunk-EGFXVTSE.js";
12
+ } from "./chunk-TFNBQCKM.js";
13
13
  import {
14
14
  copyDir,
15
15
  copyFile,
@@ -243,9 +243,15 @@ async function setup(options) {
243
243
  if (projectType.typescript) {
244
244
  listItem("npm install -D typescript-eslint");
245
245
  }
246
- if (projectType.react) {
246
+ if (projectType.react && !projectType.nextjs) {
247
247
  listItem("npm install -D eslint-plugin-react eslint-plugin-react-hooks");
248
248
  }
249
+ if (projectType.nextjs) {
250
+ listItem("npm install -D eslint-plugin-react eslint-plugin-react-hooks @next/eslint-plugin-next");
251
+ }
252
+ if (projectType.astro) {
253
+ listItem("npm install -D eslint-plugin-astro");
254
+ }
249
255
  header("Setup Complete");
250
256
  if (created.length > 0) {
251
257
  info("\nCreated:");
@@ -273,4 +279,4 @@ Safeword ${VERSION} installed successfully!`);
273
279
  export {
274
280
  setup
275
281
  };
276
- //# sourceMappingURL=setup-S5Z7QIOH.js.map
282
+ //# sourceMappingURL=setup-D3CYQWGK.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/setup.ts","../src/utils/project-detector.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, installGitHook } 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, getEslintConfig, SETTINGS_HOOKS } from '../templates/index.js';\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}\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 // Create ESLint config\n const eslintConfigPath = join(cwd, 'eslint.config.mjs');\n if (!exists(eslintConfigPath)) {\n writeFile(eslintConfigPath, getEslintConfig(projectType));\n created.push('eslint.config.mjs');\n success('Created eslint.config.mjs');\n } else {\n info('eslint.config.mjs already exists');\n }\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\n const markdownlintPath = join(cwd, '.markdownlint.jsonc');\n if (!exists(markdownlintPath)) {\n copyFile(join(templatesDir, 'markdownlint.jsonc'), markdownlintPath);\n created.push('.markdownlint.jsonc');\n success('Created .markdownlint.jsonc');\n } else {\n info('.markdownlint.jsonc already exists');\n }\n\n // Add scripts to package.json\n try {\n const scripts = packageJson.scripts ?? {};\n let scriptsModified = false;\n\n if (!scripts.lint) {\n scripts.lint = 'eslint .';\n scriptsModified = true;\n }\n\n if (!scripts['lint:md']) {\n scripts['lint:md'] = 'markdownlint-cli2 \"**/*.md\" \"#node_modules\"';\n scriptsModified = true;\n }\n\n if (!scripts.format) {\n scripts.format = 'prettier --write .';\n scriptsModified = true;\n }\n\n if (!scripts['format:check']) {\n scripts['format:check'] = 'prettier --check .';\n scriptsModified = true;\n }\n\n if (scriptsModified) {\n packageJson.scripts = scripts;\n writeJson(packageJsonPath, packageJson);\n modified.push('package.json');\n success('Added lint and format scripts');\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. Handle git repository\n info('\\nConfiguring git...');\n\n if (isGitRepo(cwd)) {\n installGitHook(cwd);\n modified.push('.git/hooks/pre-commit');\n success('Installed git pre-commit hook');\n } else if (isNonInteractive) {\n warn('Skipped git initialization (non-interactive mode)');\n warn('Git hooks not installed (no repository)');\n } else {\n // Interactive mode - would prompt here\n // For now, skip in all cases\n warn('Skipped git initialization (no .git directory)');\n warn('Git hooks not installed (no repository)');\n }\n\n // 9. Note about dependencies\n info('\\nNote: Install linting dependencies manually:');\n listItem('npm install -D eslint prettier @eslint/js');\n if (projectType.typescript) {\n listItem('npm install -D typescript-eslint');\n }\n if (projectType.react) {\n listItem('npm install -D eslint-plugin-react eslint-plugin-react-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('Install linting dependencies (see above)');\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 * Project type detection from package.json\n *\n * Detects frameworks and tools used in the project to configure\n * appropriate linting rules.\n */\n\nexport interface PackageJson {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\nexport interface ProjectType {\n typescript: boolean;\n react: boolean;\n nextjs: boolean;\n astro: boolean;\n electron: boolean;\n}\n\n/**\n * Detects project type from package.json contents\n */\nexport function detectProjectType(packageJson: PackageJson): ProjectType {\n const deps = packageJson.dependencies || {};\n const devDeps = packageJson.devDependencies || {};\n const allDeps = { ...deps, ...devDeps };\n\n const hasTypescript = 'typescript' in allDeps;\n const hasReact = 'react' in deps || 'react' in devDeps;\n const hasNextJs = 'next' in deps;\n const hasAstro = 'astro' in deps || 'astro' in devDeps;\n const hasElectron = 'electron' in deps || 'electron' in devDeps;\n\n return {\n typescript: hasTypescript,\n react: hasReact || hasNextJs, // Next.js implies React\n nextjs: hasNextJs,\n astro: hasAstro,\n electron: hasElectron,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/commands/setup.ts","../src/utils/project-detector.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, installGitHook } 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, getEslintConfig, SETTINGS_HOOKS } from '../templates/index.js';\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}\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 // Create ESLint config\n const eslintConfigPath = join(cwd, 'eslint.config.mjs');\n if (!exists(eslintConfigPath)) {\n writeFile(eslintConfigPath, getEslintConfig(projectType));\n created.push('eslint.config.mjs');\n success('Created eslint.config.mjs');\n } else {\n info('eslint.config.mjs already exists');\n }\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\n const markdownlintPath = join(cwd, '.markdownlint.jsonc');\n if (!exists(markdownlintPath)) {\n copyFile(join(templatesDir, 'markdownlint.jsonc'), markdownlintPath);\n created.push('.markdownlint.jsonc');\n success('Created .markdownlint.jsonc');\n } else {\n info('.markdownlint.jsonc already exists');\n }\n\n // Add scripts to package.json\n try {\n const scripts = packageJson.scripts ?? {};\n let scriptsModified = false;\n\n if (!scripts.lint) {\n scripts.lint = 'eslint .';\n scriptsModified = true;\n }\n\n if (!scripts['lint:md']) {\n scripts['lint:md'] = 'markdownlint-cli2 \"**/*.md\" \"#node_modules\"';\n scriptsModified = true;\n }\n\n if (!scripts.format) {\n scripts.format = 'prettier --write .';\n scriptsModified = true;\n }\n\n if (!scripts['format:check']) {\n scripts['format:check'] = 'prettier --check .';\n scriptsModified = true;\n }\n\n if (scriptsModified) {\n packageJson.scripts = scripts;\n writeJson(packageJsonPath, packageJson);\n modified.push('package.json');\n success('Added lint and format scripts');\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. Handle git repository\n info('\\nConfiguring git...');\n\n if (isGitRepo(cwd)) {\n installGitHook(cwd);\n modified.push('.git/hooks/pre-commit');\n success('Installed git pre-commit hook');\n } else if (isNonInteractive) {\n warn('Skipped git initialization (non-interactive mode)');\n warn('Git hooks not installed (no repository)');\n } else {\n // Interactive mode - would prompt here\n // For now, skip in all cases\n warn('Skipped git initialization (no .git directory)');\n warn('Git hooks not installed (no repository)');\n }\n\n // 9. Note about dependencies\n info('\\nNote: Install linting dependencies manually:');\n listItem('npm install -D eslint prettier @eslint/js');\n if (projectType.typescript) {\n listItem('npm install -D typescript-eslint');\n }\n if (projectType.react && !projectType.nextjs) {\n listItem('npm install -D eslint-plugin-react eslint-plugin-react-hooks');\n }\n if (projectType.nextjs) {\n listItem('npm install -D eslint-plugin-react eslint-plugin-react-hooks @next/eslint-plugin-next');\n }\n if (projectType.astro) {\n listItem('npm install -D eslint-plugin-astro');\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('Install linting dependencies (see above)');\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 * Project type detection from package.json\n *\n * Detects frameworks and tools used in the project to configure\n * appropriate linting rules.\n */\n\nexport interface PackageJson {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\nexport interface ProjectType {\n typescript: boolean;\n react: boolean;\n nextjs: boolean;\n astro: boolean;\n electron: boolean;\n}\n\n/**\n * Detects project type from package.json contents\n */\nexport function detectProjectType(packageJson: PackageJson): ProjectType {\n const deps = packageJson.dependencies || {};\n const devDeps = packageJson.devDependencies || {};\n const allDeps = { ...deps, ...devDeps };\n\n const hasTypescript = 'typescript' in allDeps;\n const hasReact = 'react' in deps || 'react' in devDeps;\n const hasNextJs = 'next' in deps;\n const hasAstro = 'astro' in deps || 'astro' in devDeps;\n const hasElectron = 'electron' in deps || 'electron' in devDeps;\n\n return {\n typescript: hasTypescript,\n react: hasReact || hasNextJs, // Next.js implies React\n nextjs: hasNextJs,\n astro: hasAstro,\n electron: hasElectron,\n };\n}\n"],"mappings":"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","names":[]}
@@ -10,7 +10,7 @@ import {
10
10
  filterOutSafewordHooks,
11
11
  installGitHook,
12
12
  isGitRepo
13
- } from "./chunk-EGFXVTSE.js";
13
+ } from "./chunk-TFNBQCKM.js";
14
14
  import {
15
15
  copyDir,
16
16
  copyFile,
@@ -131,4 +131,4 @@ Safeword upgraded to v${VERSION}`);
131
131
  export {
132
132
  upgrade
133
133
  };
134
- //# sourceMappingURL=upgrade-JDXMLJGU.js.map
134
+ //# sourceMappingURL=upgrade-RGBXJUU4.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safeword",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "CLI for setting up and managing safeword development environments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/git.ts","../src/utils/hooks.ts","../src/templates/content.ts","../src/templates/config.ts","../src/utils/agents-md.ts"],"sourcesContent":["/**\n * Git utilities for CLI operations\n */\n\nimport { execSync } from 'node:child_process';\nimport { join } from 'node:path';\nimport { exists, readFile, writeFile, ensureDir, makeExecutable } from './fs.js';\n\nconst MARKER_START = '# SAFEWORD_ARCH_CHECK_START';\nconst MARKER_END = '# SAFEWORD_ARCH_CHECK_END';\n\n/**\n * Check if directory is a git repository\n */\nexport function isGitRepo(cwd: string): boolean {\n return exists(join(cwd, '.git'));\n}\n\n/**\n * Initialize a git repository\n */\nexport function initGitRepo(cwd: string): void {\n execSync('git init', { cwd, stdio: 'pipe' });\n}\n\n/**\n * Get the pre-commit hook content to add\n */\nfunction getHookContent(): string {\n return `\n${MARKER_START}\n# Safeword architecture check\n# This section is managed by safeword - do not edit manually\nif [ -f \".safeword/hooks/pre-commit.sh\" ]; then\n bash .safeword/hooks/pre-commit.sh\nfi\n${MARKER_END}\n`;\n}\n\n/**\n * Install safeword markers into pre-commit hook\n */\nexport function installGitHook(cwd: string): void {\n const hooksDir = join(cwd, '.git', 'hooks');\n const hookPath = join(hooksDir, 'pre-commit');\n\n ensureDir(hooksDir);\n\n let content = '';\n\n if (exists(hookPath)) {\n content = readFile(hookPath);\n\n // Check if already has safeword markers\n if (content.includes(MARKER_START)) {\n // Remove existing safeword section and re-add (update)\n content = removeMarkerSection(content);\n }\n } else {\n // Create new hook file with shebang\n content = '#!/bin/bash\\n';\n }\n\n // Add safeword section\n content = content.trimEnd() + '\\n' + getHookContent();\n\n writeFile(hookPath, content);\n makeExecutable(hookPath);\n}\n\n/**\n * Remove safeword markers from pre-commit hook\n */\nexport function removeGitHook(cwd: string): void {\n const hookPath = join(cwd, '.git', 'hooks', 'pre-commit');\n\n if (!exists(hookPath)) return;\n\n let content = readFile(hookPath);\n\n if (!content.includes(MARKER_START)) return;\n\n content = removeMarkerSection(content);\n\n // If only shebang remains, we could delete the file\n // but safer to leave it\n writeFile(hookPath, content);\n}\n\n/**\n * Remove the section between markers (inclusive)\n */\nfunction removeMarkerSection(content: string): string {\n const lines = content.split('\\n');\n const result: string[] = [];\n let inMarkerSection = false;\n\n for (const line of lines) {\n if (line.includes(MARKER_START)) {\n inMarkerSection = true;\n continue;\n }\n if (line.includes(MARKER_END)) {\n inMarkerSection = false;\n continue;\n }\n if (!inMarkerSection) {\n result.push(line);\n }\n }\n\n return result.join('\\n').trim() + '\\n';\n}\n\n/**\n * Check if git hooks have safeword markers\n */\nexport function hasGitHook(cwd: string): boolean {\n const hookPath = join(cwd, '.git', 'hooks', 'pre-commit');\n if (!exists(hookPath)) return false;\n const content = readFile(hookPath);\n return content.includes(MARKER_START);\n}\n","/**\n * Hook utilities for Claude Code settings\n */\n\n/**\n * Type guard to check if a value is a hook object with a command property\n */\nexport function isHookObject(h: unknown): h is { command: string } {\n return (\n typeof h === 'object' &&\n h !== null &&\n 'command' in h &&\n typeof (h as { command: string }).command === 'string'\n );\n}\n\n/**\n * Check if a hook is a safeword hook (command contains '.safeword')\n */\nexport function isSafewordHook(h: unknown): boolean {\n return isHookObject(h) && h.command.includes('.safeword');\n}\n\n/**\n * Filter out safeword hooks from an array of hooks\n */\nexport function filterOutSafewordHooks(hooks: unknown[]): unknown[] {\n return hooks.filter((h) => !isSafewordHook(h));\n}\n","/**\n * Content templates - static string content\n *\n * Note: Most templates (SAFEWORD.md, hooks, skills, guides, etc.) are now\n * file-based in the templates/ directory. This file contains only small\n * string constants that are used inline.\n */\n\nexport const AGENTS_MD_LINK = `**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**\n\nThe SAFEWORD.md file contains core development patterns, workflows, and conventions.\nRead it BEFORE working on any task in this project.\n\n---`;\n\nexport const PRETTIERRC = `{\n \"semi\": true,\n \"singleQuote\": true,\n \"tabWidth\": 2,\n \"trailingComma\": \"es5\",\n \"printWidth\": 100\n}\n`;\n","/**\n * Configuration templates - ESLint config generation and hook settings\n */\n\nexport function getEslintConfig(options: {\n typescript?: boolean;\n react?: boolean;\n nextjs?: boolean;\n}): string {\n const imports: string[] = ['import js from \"@eslint/js\";'];\n const configs: string[] = ['js.configs.recommended'];\n\n if (options.typescript) {\n imports.push('import tseslint from \"typescript-eslint\";');\n configs.push('...tseslint.configs.recommended');\n }\n\n if (options.react || options.nextjs) {\n imports.push('import react from \"eslint-plugin-react\";');\n imports.push('import reactHooks from \"eslint-plugin-react-hooks\";');\n configs.push('react.configs.flat.recommended');\n configs.push('react.configs.flat[\"jsx-runtime\"]');\n configs.push(\n '{ plugins: { \"react-hooks\": reactHooks }, rules: reactHooks.configs.recommended.rules }',\n );\n }\n\n return `${imports.join('\\n')}\n\nexport default [\n ${configs.join(',\\n ')},\n {\n ignores: [\"node_modules/\", \"dist/\", \".next/\", \"build/\"],\n },\n];\n`;\n}\n\nexport const SETTINGS_HOOKS = {\n SessionStart: [\n {\n command: 'bash .safeword/hooks/agents-md-check.sh',\n description: 'Safeword: Verify AGENTS.md link',\n },\n ],\n UserPromptSubmit: [\n {\n command: 'bash .safeword/hooks/inject-timestamp.sh',\n description: 'Safeword: Inject current timestamp',\n },\n ],\n PostToolUse: [\n {\n command: 'bash .safeword/hooks/post-tool.sh 2>/dev/null || true',\n description: 'Safeword: Post-tool validation',\n },\n ],\n};\n","/**\n * AGENTS.md file utilities\n */\n\nimport { join } from 'node:path';\nimport { exists, readFile, writeFile } from './fs.js';\nimport { AGENTS_MD_LINK } from '../templates/index.js';\n\nconst SAFEWORD_LINK_MARKER = '@./.safeword/SAFEWORD.md';\n\n/**\n * Check if AGENTS.md has the safeword link\n */\nexport function hasAgentsMdLink(cwd: string): boolean {\n const agentsMdPath = join(cwd, 'AGENTS.md');\n if (!exists(agentsMdPath)) return false;\n const content = readFile(agentsMdPath);\n return content.includes(SAFEWORD_LINK_MARKER);\n}\n\n/**\n * Ensure AGENTS.md exists and has the safeword link.\n * Returns 'created' | 'modified' | 'unchanged'\n */\nexport function ensureAgentsMdLink(cwd: string): 'created' | 'modified' | 'unchanged' {\n const agentsMdPath = join(cwd, 'AGENTS.md');\n\n if (!exists(agentsMdPath)) {\n writeFile(agentsMdPath, `${AGENTS_MD_LINK}\\n`);\n return 'created';\n }\n\n const content = readFile(agentsMdPath);\n if (!content.includes(SAFEWORD_LINK_MARKER)) {\n writeFile(agentsMdPath, `${AGENTS_MD_LINK}\\n\\n${content}`);\n return 'modified';\n }\n\n return 'unchanged';\n}\n\n/**\n * Remove safeword link block from AGENTS.md.\n * Returns true if link was removed.\n */\nexport function removeAgentsMdLink(cwd: string): boolean {\n const agentsMdPath = join(cwd, 'AGENTS.md');\n if (!exists(agentsMdPath)) return false;\n\n const content = readFile(agentsMdPath);\n\n // Remove the entire AGENTS_MD_LINK block if present\n let newContent = content.replace(AGENTS_MD_LINK, '');\n\n // Also handle legacy single-line format (filter any remaining lines with marker)\n const lines = newContent.split('\\n').filter((line) => !line.includes(SAFEWORD_LINK_MARKER));\n\n // Remove extra blank lines and separators at the start\n while (lines.length > 0 && (lines[0].trim() === '' || lines[0].trim() === '---')) {\n lines.shift();\n }\n\n newContent = lines.join('\\n');\n\n if (newContent !== content) {\n writeFile(agentsMdPath, newContent);\n return true;\n }\n\n return false;\n}\n"],"mappings":"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","names":["join","join"]}