safeword 0.5.0 → 0.5.2

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.
@@ -71,11 +71,14 @@ function removeMarkerSection(content) {
71
71
  }
72
72
 
73
73
  // src/utils/hooks.ts
74
- function isHookObject(h) {
75
- return typeof h === "object" && h !== null && "command" in h && typeof h.command === "string";
74
+ function isHookEntry(h) {
75
+ return typeof h === "object" && h !== null && "hooks" in h && Array.isArray(h.hooks);
76
76
  }
77
77
  function isSafewordHook(h) {
78
- return isHookObject(h) && h.command.includes(".safeword");
78
+ if (!isHookEntry(h)) return false;
79
+ return h.hooks.some(
80
+ (cmd) => typeof cmd.command === "string" && cmd.command.includes(".safeword")
81
+ );
79
82
  }
80
83
  function filterOutSafewordHooks(hooks) {
81
84
  return hooks.filter((h) => !isSafewordHook(h));
@@ -148,39 +151,67 @@ export default [
148
151
  var SETTINGS_HOOKS = {
149
152
  SessionStart: [
150
153
  {
151
- command: "bash .safeword/hooks/session-verify-agents.sh",
152
- description: "Safeword: Verify AGENTS.md link"
154
+ hooks: [
155
+ {
156
+ type: "command",
157
+ command: "bash .safeword/hooks/session-verify-agents.sh"
158
+ }
159
+ ]
153
160
  },
154
161
  {
155
- command: "bash .safeword/hooks/session-version.sh",
156
- description: "Safeword: Display version info"
162
+ hooks: [
163
+ {
164
+ type: "command",
165
+ command: "bash .safeword/hooks/session-version.sh"
166
+ }
167
+ ]
157
168
  },
158
169
  {
159
- command: "bash .safeword/hooks/session-lint-check.sh",
160
- description: "Safeword: Check lint config"
170
+ hooks: [
171
+ {
172
+ type: "command",
173
+ command: "bash .safeword/hooks/session-lint-check.sh"
174
+ }
175
+ ]
161
176
  }
162
177
  ],
163
178
  UserPromptSubmit: [
164
179
  {
165
- command: "bash .safeword/hooks/prompt-timestamp.sh",
166
- description: "Safeword: Inject current timestamp"
180
+ hooks: [
181
+ {
182
+ type: "command",
183
+ command: "bash .safeword/hooks/prompt-timestamp.sh"
184
+ }
185
+ ]
167
186
  },
168
187
  {
169
- command: "bash .safeword/hooks/prompt-questions.sh",
170
- description: "Safeword: Question protocol guidance"
188
+ hooks: [
189
+ {
190
+ type: "command",
191
+ command: "bash .safeword/hooks/prompt-questions.sh"
192
+ }
193
+ ]
171
194
  }
172
195
  ],
173
196
  Stop: [
174
197
  {
175
- command: "bash .safeword/hooks/stop-quality.sh",
176
- description: "Safeword: Quality review reminder"
198
+ hooks: [
199
+ {
200
+ type: "command",
201
+ command: "bash .safeword/hooks/stop-quality.sh"
202
+ }
203
+ ]
177
204
  }
178
205
  ],
179
206
  PostToolUse: [
180
207
  {
181
208
  matcher: "Write|Edit|MultiEdit|NotebookEdit",
182
- command: "bash .safeword/hooks/post-tool-lint.sh",
183
- description: "Safeword: Auto-lint changed files"
209
+ hooks: [
210
+ {
211
+ type: "command",
212
+ command: "bash .safeword/hooks/post-tool-lint.sh"
213
+ }
214
+ ]
184
215
  }
185
216
  ]
186
217
  };
@@ -232,4 +263,4 @@ export {
232
263
  ensureAgentsMdLink,
233
264
  removeAgentsMdLink
234
265
  };
235
- //# sourceMappingURL=chunk-SEF2PNTV.js.map
266
+ //# sourceMappingURL=chunk-Z7MWRHVP.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 pre-commit linting\n# This section is managed by safeword - do not edit manually\nif [ -f \".safeword/hooks/git-pre-commit.sh\" ]; then\n bash .safeword/hooks/git-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\ninterface HookCommand {\n type: string;\n command: string;\n}\n\ninterface HookEntry {\n matcher?: string;\n hooks: HookCommand[];\n}\n\n/**\n * Type guard to check if a value is a hook entry with hooks array\n */\nexport function isHookEntry(h: unknown): h is HookEntry {\n return (\n typeof h === 'object' &&\n h !== null &&\n 'hooks' in h &&\n Array.isArray((h as HookEntry).hooks)\n );\n}\n\n/**\n * Check if a hook entry contains a safeword hook (command contains '.safeword')\n */\nexport function isSafewordHook(h: unknown): boolean {\n if (!isHookEntry(h)) return false;\n return h.hooks.some(\n (cmd) => typeof cmd.command === 'string' && cmd.command.includes('.safeword'),\n );\n}\n\n/**\n * Filter out safeword hooks from an array of hook entries\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 vue?: boolean;\n svelte?: boolean;\n}): string {\n const imports: string[] = ['import js from \"@eslint/js\";'];\n const configs: string[] = ['js.configs.recommended'];\n const ignores: string[] = ['node_modules/', 'dist/', 'build/'];\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 ignores.push('.next/');\n }\n\n if (options.astro) {\n imports.push('import eslintPluginAstro from \"eslint-plugin-astro\";');\n configs.push('...eslintPluginAstro.configs.recommended');\n ignores.push('.astro/');\n }\n\n if (options.vue) {\n imports.push('import pluginVue from \"eslint-plugin-vue\";');\n configs.push('...pluginVue.configs[\"flat/recommended\"]');\n ignores.push('.nuxt/');\n }\n\n if (options.svelte) {\n imports.push('import eslintPluginSvelte from \"eslint-plugin-svelte\";');\n configs.push('...eslintPluginSvelte.configs[\"flat/recommended\"]');\n ignores.push('.svelte-kit/');\n }\n\n return `${imports.join('\\n')}\n\nexport default [\n ${configs.join(',\\n ')},\n {\n ignores: [${ignores.map(i => `\"${i}\"`).join(', ')}],\n },\n];\n`;\n}\n\nexport const SETTINGS_HOOKS = {\n SessionStart: [\n {\n hooks: [\n {\n type: 'command',\n command: 'bash .safeword/hooks/session-verify-agents.sh',\n },\n ],\n },\n {\n hooks: [\n {\n type: 'command',\n command: 'bash .safeword/hooks/session-version.sh',\n },\n ],\n },\n {\n hooks: [\n {\n type: 'command',\n command: 'bash .safeword/hooks/session-lint-check.sh',\n },\n ],\n },\n ],\n UserPromptSubmit: [\n {\n hooks: [\n {\n type: 'command',\n command: 'bash .safeword/hooks/prompt-timestamp.sh',\n },\n ],\n },\n {\n hooks: [\n {\n type: 'command',\n command: 'bash .safeword/hooks/prompt-questions.sh',\n },\n ],\n },\n ],\n Stop: [\n {\n hooks: [\n {\n type: 'command',\n command: 'bash .safeword/hooks/stop-quality.sh',\n },\n ],\n },\n ],\n PostToolUse: [\n {\n matcher: 'Write|Edit|MultiEdit|NotebookEdit',\n hooks: [\n {\n type: 'command',\n command: 'bash .safeword/hooks/post-tool-lint.sh',\n },\n ],\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-L5YSYPSD.js");
11
+ const { setup } = await import("./setup-U35LUMIF.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-HZ7O3WDQ.js");
19
+ const { upgrade } = await import("./upgrade-GQKC2ZKF.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-LG5SI5EC.js");
27
+ const { reset } = await import("./reset-YPSS7BAB.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-SEF2PNTV.js";
6
+ } from "./chunk-Z7MWRHVP.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-LG5SI5EC.js.map
143
+ //# sourceMappingURL=reset-YPSS7BAB.js.map
@@ -9,7 +9,7 @@ import {
9
9
  getEslintConfig,
10
10
  installGitHook,
11
11
  isGitRepo
12
- } from "./chunk-SEF2PNTV.js";
12
+ } from "./chunk-Z7MWRHVP.js";
13
13
  import {
14
14
  copyDir,
15
15
  copyFile,
@@ -295,4 +295,4 @@ Safeword ${VERSION} installed successfully!`);
295
295
  export {
296
296
  setup
297
297
  };
298
- //# sourceMappingURL=setup-L5YSYPSD.js.map
298
+ //# sourceMappingURL=setup-U35LUMIF.js.map
@@ -10,7 +10,7 @@ import {
10
10
  filterOutSafewordHooks,
11
11
  installGitHook,
12
12
  isGitRepo
13
- } from "./chunk-SEF2PNTV.js";
13
+ } from "./chunk-Z7MWRHVP.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-HZ7O3WDQ.js.map
134
+ //# sourceMappingURL=upgrade-GQKC2ZKF.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safeword",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "CLI for setting up and managing safeword development environments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,15 +1,73 @@
1
1
  ---
2
- description: Run ESLint and Prettier on the project, fix auto-fixable issues
2
+ description: Run all linters and formatters on the project
3
3
  ---
4
4
 
5
5
  # Lint
6
6
 
7
- Run project linting and fix issues.
7
+ Run the full linting and formatting suite on the project.
8
8
 
9
- ## Usage
9
+ ## Instructions
10
10
 
11
+ Run the following commands based on what's available in the project. Check for each tool before running.
12
+
13
+ ### JavaScript/TypeScript (if package.json exists)
14
+
15
+ ```bash
16
+ # Prettier (format all supported files)
17
+ npx prettier --write "**/*.{js,jsx,ts,tsx,mjs,cjs,vue,svelte,astro,json,css,scss,html,yaml,yml,graphql,md}" --ignore-path .gitignore 2>/dev/null || true
18
+
19
+ # ESLint (lint and fix JS/TS files)
20
+ npx eslint --fix "**/*.{js,jsx,ts,tsx,mjs,cjs,vue,svelte,astro}" --ignore-path .gitignore 2>&1 || true
21
+ ```
22
+
23
+ ### Markdown (if .md files exist)
24
+
25
+ ```bash
26
+ # markdownlint
27
+ npx markdownlint-cli2 --fix "**/*.md" "#node_modules" 2>&1 || true
28
+ ```
29
+
30
+ ### Python (if .py files exist and ruff is installed)
31
+
32
+ ```bash
33
+ # Check if ruff is available
34
+ if command -v ruff &> /dev/null; then
35
+ ruff format .
36
+ ruff check --fix .
37
+ fi
11
38
  ```
12
- /lint
39
+
40
+ ### Go (if .go files exist)
41
+
42
+ ```bash
43
+ # Check if go is available
44
+ if command -v go &> /dev/null; then
45
+ go fmt ./...
46
+ # Run go vet for static analysis
47
+ go vet ./... 2>&1 || true
48
+ fi
49
+ ```
50
+
51
+ ### Rust (if Cargo.toml exists)
52
+
53
+ ```bash
54
+ # Check if cargo is available
55
+ if command -v cargo &> /dev/null; then
56
+ cargo fmt
57
+ # Run clippy for lints
58
+ cargo clippy --fix --allow-dirty --allow-staged 2>&1 || true
59
+ fi
13
60
  ```
14
61
 
15
- Runs ESLint and Prettier on the project. Auto-fixes what it can, reports remaining issues.
62
+ ### TypeScript Type Checking (if tsconfig.json exists)
63
+
64
+ ```bash
65
+ npx tsc --noEmit 2>&1 || true
66
+ ```
67
+
68
+ ## Summary
69
+
70
+ After running, report:
71
+ 1. Number of files formatted/fixed
72
+ 2. Any remaining errors that couldn't be auto-fixed
73
+ 3. Type errors (if TypeScript)
@@ -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 pre-commit linting\n# This section is managed by safeword - do not edit manually\nif [ -f \".safeword/hooks/git-pre-commit.sh\" ]; then\n bash .safeword/hooks/git-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 vue?: boolean;\n svelte?: boolean;\n}): string {\n const imports: string[] = ['import js from \"@eslint/js\";'];\n const configs: string[] = ['js.configs.recommended'];\n const ignores: string[] = ['node_modules/', 'dist/', 'build/'];\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 ignores.push('.next/');\n }\n\n if (options.astro) {\n imports.push('import eslintPluginAstro from \"eslint-plugin-astro\";');\n configs.push('...eslintPluginAstro.configs.recommended');\n ignores.push('.astro/');\n }\n\n if (options.vue) {\n imports.push('import pluginVue from \"eslint-plugin-vue\";');\n configs.push('...pluginVue.configs[\"flat/recommended\"]');\n ignores.push('.nuxt/');\n }\n\n if (options.svelte) {\n imports.push('import eslintPluginSvelte from \"eslint-plugin-svelte\";');\n configs.push('...eslintPluginSvelte.configs[\"flat/recommended\"]');\n ignores.push('.svelte-kit/');\n }\n\n return `${imports.join('\\n')}\n\nexport default [\n ${configs.join(',\\n ')},\n {\n ignores: [${ignores.map(i => `\"${i}\"`).join(', ')}],\n },\n];\n`;\n}\n\nexport const SETTINGS_HOOKS = {\n SessionStart: [\n {\n command: 'bash .safeword/hooks/session-verify-agents.sh',\n description: 'Safeword: Verify AGENTS.md link',\n },\n {\n command: 'bash .safeword/hooks/session-version.sh',\n description: 'Safeword: Display version info',\n },\n {\n command: 'bash .safeword/hooks/session-lint-check.sh',\n description: 'Safeword: Check lint config',\n },\n ],\n UserPromptSubmit: [\n {\n command: 'bash .safeword/hooks/prompt-timestamp.sh',\n description: 'Safeword: Inject current timestamp',\n },\n {\n command: 'bash .safeword/hooks/prompt-questions.sh',\n description: 'Safeword: Question protocol guidance',\n },\n ],\n Stop: [\n {\n command: 'bash .safeword/hooks/stop-quality.sh',\n description: 'Safeword: Quality review reminder',\n },\n ],\n PostToolUse: [\n {\n matcher: 'Write|Edit|MultiEdit|NotebookEdit',\n command: 'bash .safeword/hooks/post-tool-lint.sh',\n description: 'Safeword: Auto-lint changed files',\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"]}