safeword 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/.claude/commands/arch-review.md +32 -0
  2. package/.claude/commands/lint.md +6 -0
  3. package/.claude/commands/quality-review.md +13 -0
  4. package/.claude/commands/setup-linting.md +6 -0
  5. package/.claude/hooks/auto-lint.sh +6 -0
  6. package/.claude/hooks/auto-quality-review.sh +170 -0
  7. package/.claude/hooks/check-linting-sync.sh +17 -0
  8. package/.claude/hooks/inject-timestamp.sh +6 -0
  9. package/.claude/hooks/question-protocol.sh +12 -0
  10. package/.claude/hooks/run-linters.sh +8 -0
  11. package/.claude/hooks/run-quality-review.sh +76 -0
  12. package/.claude/hooks/version-check.sh +10 -0
  13. package/.claude/mcp/README.md +96 -0
  14. package/.claude/mcp/arcade.sample.json +9 -0
  15. package/.claude/mcp/context7.sample.json +7 -0
  16. package/.claude/mcp/playwright.sample.json +7 -0
  17. package/.claude/settings.json +62 -0
  18. package/.claude/skills/quality-reviewer/SKILL.md +190 -0
  19. package/.claude/skills/safeword-quality-reviewer/SKILL.md +13 -0
  20. package/.env.arcade.example +4 -0
  21. package/.env.example +11 -0
  22. package/.gitmodules +4 -0
  23. package/.safeword/SAFEWORD.md +33 -0
  24. package/.safeword/eslint/eslint-base.mjs +101 -0
  25. package/.safeword/guides/architecture-guide.md +404 -0
  26. package/.safeword/guides/code-philosophy.md +174 -0
  27. package/.safeword/guides/context-files-guide.md +405 -0
  28. package/.safeword/guides/data-architecture-guide.md +183 -0
  29. package/.safeword/guides/design-doc-guide.md +165 -0
  30. package/.safeword/guides/learning-extraction.md +515 -0
  31. package/.safeword/guides/llm-instruction-design.md +239 -0
  32. package/.safeword/guides/llm-prompting.md +95 -0
  33. package/.safeword/guides/tdd-best-practices.md +570 -0
  34. package/.safeword/guides/test-definitions-guide.md +243 -0
  35. package/.safeword/guides/testing-methodology.md +573 -0
  36. package/.safeword/guides/user-story-guide.md +237 -0
  37. package/.safeword/guides/zombie-process-cleanup.md +214 -0
  38. package/{templates → .safeword}/hooks/agents-md-check.sh +0 -0
  39. package/{templates → .safeword}/hooks/post-tool.sh +0 -0
  40. package/{templates → .safeword}/hooks/pre-commit.sh +0 -0
  41. package/.safeword/planning/002-user-story-quality-evaluation.md +1840 -0
  42. package/.safeword/planning/003-langsmith-eval-setup-prompt.md +363 -0
  43. package/.safeword/planning/004-llm-eval-test-cases.md +3226 -0
  44. package/.safeword/planning/005-architecture-enforcement-system.md +169 -0
  45. package/.safeword/planning/006-reactive-fix-prevention-research.md +135 -0
  46. package/.safeword/planning/011-cli-ux-vision.md +330 -0
  47. package/.safeword/planning/012-project-structure-cleanup.md +154 -0
  48. package/.safeword/planning/README.md +39 -0
  49. package/.safeword/planning/automation-plan-v2.md +1225 -0
  50. package/.safeword/planning/automation-plan-v3.md +1291 -0
  51. package/.safeword/planning/automation-plan.md +3058 -0
  52. package/.safeword/planning/design/005-cli-implementation.md +343 -0
  53. package/.safeword/planning/design/013-cli-self-contained-templates.md +596 -0
  54. package/.safeword/planning/design/013a-eslint-plugin-suite.md +256 -0
  55. package/.safeword/planning/design/013b-implementation-snippets.md +385 -0
  56. package/.safeword/planning/design/013c-config-isolation-strategy.md +242 -0
  57. package/.safeword/planning/design/code-philosophy-improvements.md +60 -0
  58. package/.safeword/planning/mcp-analysis.md +545 -0
  59. package/.safeword/planning/phase2-subagents-vs-skills-analysis.md +451 -0
  60. package/.safeword/planning/settings-improvements.md +970 -0
  61. package/.safeword/planning/test-definitions/005-cli-implementation.md +1301 -0
  62. package/.safeword/planning/test-definitions/cli-self-contained-templates.md +205 -0
  63. package/.safeword/planning/user-stories/001-guides-review-user-stories.md +1381 -0
  64. package/.safeword/planning/user-stories/003-reactive-fix-prevention.md +132 -0
  65. package/.safeword/planning/user-stories/004-technical-constraints.md +86 -0
  66. package/.safeword/planning/user-stories/005-cli-implementation.md +311 -0
  67. package/.safeword/planning/user-stories/cli-self-contained-templates.md +172 -0
  68. package/.safeword/planning/versioned-distribution.md +740 -0
  69. package/.safeword/prompts/arch-review.md +43 -0
  70. package/.safeword/prompts/quality-review.md +11 -0
  71. package/.safeword/scripts/arch-review.sh +235 -0
  72. package/.safeword/scripts/check-linting-sync.sh +58 -0
  73. package/.safeword/scripts/setup-linting.sh +559 -0
  74. package/.safeword/templates/architecture-template.md +136 -0
  75. package/.safeword/templates/ci/architecture-check.yml +79 -0
  76. package/.safeword/templates/design-doc-template.md +127 -0
  77. package/.safeword/templates/test-definitions-feature.md +100 -0
  78. package/.safeword/templates/ticket-template.md +74 -0
  79. package/.safeword/templates/user-stories-template.md +82 -0
  80. package/.safeword/tickets/001-guides-review-user-stories.md +83 -0
  81. package/.safeword/tickets/002-architecture-enforcement.md +211 -0
  82. package/.safeword/tickets/003-reactive-fix-prevention.md +57 -0
  83. package/.safeword/tickets/004-technical-constraints-in-user-stories.md +39 -0
  84. package/.safeword/tickets/005-cli-implementation.md +248 -0
  85. package/.safeword/tickets/006-flesh-out-skills.md +43 -0
  86. package/.safeword/tickets/007-flesh-out-questioning.md +44 -0
  87. package/.safeword/tickets/008-upgrade-questioning.md +58 -0
  88. package/.safeword/tickets/009-naming-conventions.md +41 -0
  89. package/.safeword/tickets/010-safeword-md-cleanup.md +34 -0
  90. package/.safeword/tickets/011-cursor-setup.md +86 -0
  91. package/.safeword/tickets/README.md +73 -0
  92. package/.safeword/version +1 -0
  93. package/AGENTS.md +59 -0
  94. package/CLAUDE.md +12 -0
  95. package/README.md +347 -0
  96. package/docs/001-cli-implementation-plan.md +856 -0
  97. package/docs/elite-dx-implementation-plan.md +1034 -0
  98. package/framework/README.md +131 -0
  99. package/framework/mcp/README.md +96 -0
  100. package/framework/mcp/arcade.sample.json +8 -0
  101. package/framework/mcp/context7.sample.json +6 -0
  102. package/framework/mcp/playwright.sample.json +6 -0
  103. package/framework/scripts/arch-review.sh +235 -0
  104. package/framework/scripts/check-linting-sync.sh +58 -0
  105. package/framework/scripts/load-env.sh +49 -0
  106. package/framework/scripts/setup-claude.sh +223 -0
  107. package/framework/scripts/setup-linting.sh +559 -0
  108. package/framework/scripts/setup-quality.sh +477 -0
  109. package/framework/scripts/setup-safeword.sh +550 -0
  110. package/framework/templates/ci/architecture-check.yml +78 -0
  111. package/learnings/ai-sdk-v5-breaking-changes.md +178 -0
  112. package/learnings/e2e-test-zombie-processes.md +231 -0
  113. package/learnings/milkdown-crepe-editor-property.md +96 -0
  114. package/learnings/prosemirror-fragment-traversal.md +119 -0
  115. package/package.json +19 -43
  116. package/packages/cli/AGENTS.md +1 -0
  117. package/packages/cli/ARCHITECTURE.md +279 -0
  118. package/packages/cli/package.json +51 -0
  119. package/packages/cli/src/cli.ts +63 -0
  120. package/packages/cli/src/commands/check.ts +166 -0
  121. package/packages/cli/src/commands/diff.ts +209 -0
  122. package/packages/cli/src/commands/reset.ts +190 -0
  123. package/packages/cli/src/commands/setup.ts +325 -0
  124. package/packages/cli/src/commands/upgrade.ts +163 -0
  125. package/packages/cli/src/index.ts +3 -0
  126. package/packages/cli/src/templates/config.ts +58 -0
  127. package/packages/cli/src/templates/content.ts +18 -0
  128. package/packages/cli/src/templates/index.ts +12 -0
  129. package/packages/cli/src/utils/agents-md.ts +66 -0
  130. package/packages/cli/src/utils/fs.ts +179 -0
  131. package/packages/cli/src/utils/git.ts +124 -0
  132. package/packages/cli/src/utils/hooks.ts +29 -0
  133. package/packages/cli/src/utils/output.ts +60 -0
  134. package/packages/cli/src/utils/project-detector.test.ts +185 -0
  135. package/packages/cli/src/utils/project-detector.ts +44 -0
  136. package/packages/cli/src/utils/version.ts +28 -0
  137. package/packages/cli/src/version.ts +6 -0
  138. package/packages/cli/templates/SAFEWORD.md +776 -0
  139. package/packages/cli/templates/doc-templates/architecture-template.md +136 -0
  140. package/packages/cli/templates/doc-templates/design-doc-template.md +134 -0
  141. package/packages/cli/templates/doc-templates/test-definitions-feature.md +131 -0
  142. package/packages/cli/templates/doc-templates/ticket-template.md +82 -0
  143. package/packages/cli/templates/doc-templates/user-stories-template.md +92 -0
  144. package/packages/cli/templates/guides/architecture-guide.md +423 -0
  145. package/packages/cli/templates/guides/code-philosophy.md +195 -0
  146. package/packages/cli/templates/guides/context-files-guide.md +457 -0
  147. package/packages/cli/templates/guides/data-architecture-guide.md +200 -0
  148. package/packages/cli/templates/guides/design-doc-guide.md +171 -0
  149. package/packages/cli/templates/guides/learning-extraction.md +552 -0
  150. package/packages/cli/templates/guides/llm-instruction-design.md +248 -0
  151. package/packages/cli/templates/guides/llm-prompting.md +102 -0
  152. package/packages/cli/templates/guides/tdd-best-practices.md +615 -0
  153. package/packages/cli/templates/guides/test-definitions-guide.md +334 -0
  154. package/packages/cli/templates/guides/testing-methodology.md +618 -0
  155. package/packages/cli/templates/guides/user-story-guide.md +256 -0
  156. package/packages/cli/templates/guides/zombie-process-cleanup.md +219 -0
  157. package/packages/cli/templates/hooks/agents-md-check.sh +27 -0
  158. package/packages/cli/templates/hooks/post-tool.sh +4 -0
  159. package/packages/cli/templates/hooks/pre-commit.sh +10 -0
  160. package/packages/cli/templates/prompts/arch-review.md +43 -0
  161. package/packages/cli/templates/prompts/quality-review.md +10 -0
  162. package/packages/cli/templates/skills/safeword-quality-reviewer/SKILL.md +207 -0
  163. package/packages/cli/tests/commands/check.test.ts +129 -0
  164. package/packages/cli/tests/commands/cli.test.ts +89 -0
  165. package/packages/cli/tests/commands/diff.test.ts +115 -0
  166. package/packages/cli/tests/commands/reset.test.ts +310 -0
  167. package/packages/cli/tests/commands/self-healing.test.ts +170 -0
  168. package/packages/cli/tests/commands/setup-blocking.test.ts +71 -0
  169. package/packages/cli/tests/commands/setup-core.test.ts +135 -0
  170. package/packages/cli/tests/commands/setup-git.test.ts +139 -0
  171. package/packages/cli/tests/commands/setup-hooks.test.ts +334 -0
  172. package/packages/cli/tests/commands/setup-linting.test.ts +189 -0
  173. package/packages/cli/tests/commands/setup-noninteractive.test.ts +80 -0
  174. package/packages/cli/tests/commands/setup-templates.test.ts +181 -0
  175. package/packages/cli/tests/commands/upgrade.test.ts +215 -0
  176. package/packages/cli/tests/helpers.ts +243 -0
  177. package/packages/cli/tests/npm-package.test.ts +83 -0
  178. package/packages/cli/tests/technical-constraints.test.ts +96 -0
  179. package/packages/cli/tsconfig.json +25 -0
  180. package/packages/cli/tsup.config.ts +11 -0
  181. package/packages/cli/vitest.config.ts +23 -0
  182. package/promptfoo.yaml +3270 -0
  183. package/dist/check-3NGQ4NR5.js +0 -129
  184. package/dist/check-3NGQ4NR5.js.map +0 -1
  185. package/dist/chunk-2XWIUEQK.js +0 -190
  186. package/dist/chunk-2XWIUEQK.js.map +0 -1
  187. package/dist/chunk-GZRQL3SX.js +0 -146
  188. package/dist/chunk-GZRQL3SX.js.map +0 -1
  189. package/dist/chunk-ORQHKDT2.js +0 -10
  190. package/dist/chunk-ORQHKDT2.js.map +0 -1
  191. package/dist/chunk-W66Z3C5H.js +0 -21
  192. package/dist/chunk-W66Z3C5H.js.map +0 -1
  193. package/dist/cli.d.ts +0 -1
  194. package/dist/cli.js +0 -34
  195. package/dist/cli.js.map +0 -1
  196. package/dist/diff-Y6QTAW4O.js +0 -166
  197. package/dist/diff-Y6QTAW4O.js.map +0 -1
  198. package/dist/index.d.ts +0 -11
  199. package/dist/index.js +0 -7
  200. package/dist/index.js.map +0 -1
  201. package/dist/reset-3ACTIYYE.js +0 -143
  202. package/dist/reset-3ACTIYYE.js.map +0 -1
  203. package/dist/setup-RR4M334C.js +0 -266
  204. package/dist/setup-RR4M334C.js.map +0 -1
  205. package/dist/upgrade-6AR3DHUV.js +0 -134
  206. package/dist/upgrade-6AR3DHUV.js.map +0 -1
  207. /package/{templates → framework}/SAFEWORD.md +0 -0
  208. /package/{templates → framework}/guides/architecture-guide.md +0 -0
  209. /package/{templates → framework}/guides/code-philosophy.md +0 -0
  210. /package/{templates → framework}/guides/context-files-guide.md +0 -0
  211. /package/{templates → framework}/guides/data-architecture-guide.md +0 -0
  212. /package/{templates → framework}/guides/design-doc-guide.md +0 -0
  213. /package/{templates → framework}/guides/learning-extraction.md +0 -0
  214. /package/{templates → framework}/guides/llm-instruction-design.md +0 -0
  215. /package/{templates → framework}/guides/llm-prompting.md +0 -0
  216. /package/{templates → framework}/guides/tdd-best-practices.md +0 -0
  217. /package/{templates → framework}/guides/test-definitions-guide.md +0 -0
  218. /package/{templates → framework}/guides/testing-methodology.md +0 -0
  219. /package/{templates → framework}/guides/user-story-guide.md +0 -0
  220. /package/{templates → framework}/guides/zombie-process-cleanup.md +0 -0
  221. /package/{templates → framework}/prompts/arch-review.md +0 -0
  222. /package/{templates → framework}/prompts/quality-review.md +0 -0
  223. /package/{templates/skills/safeword-quality-reviewer → framework/skills/quality-reviewer}/SKILL.md +0 -0
  224. /package/{templates/doc-templates → framework/templates}/architecture-template.md +0 -0
  225. /package/{templates/doc-templates → framework/templates}/design-doc-template.md +0 -0
  226. /package/{templates/doc-templates → framework/templates}/test-definitions-feature.md +0 -0
  227. /package/{templates/doc-templates → framework/templates}/ticket-template.md +0 -0
  228. /package/{templates/doc-templates → framework/templates}/user-stories-template.md +0 -0
  229. /package/{templates → packages/cli/templates}/commands/arch-review.md +0 -0
  230. /package/{templates → packages/cli/templates}/commands/lint.md +0 -0
  231. /package/{templates → packages/cli/templates}/commands/quality-review.md +0 -0
  232. /package/{templates → packages/cli/templates}/hooks/inject-timestamp.sh +0 -0
  233. /package/{templates → packages/cli/templates}/lib/common.sh +0 -0
  234. /package/{templates → packages/cli/templates}/lib/jq-fallback.sh +0 -0
  235. /package/{templates → packages/cli/templates}/markdownlint.jsonc +0 -0
@@ -0,0 +1,256 @@
1
+ # ESLint Plugin Suite
2
+
3
+ **Parent:** [013-cli-self-contained-templates.md](./013-cli-self-contained-templates.md)
4
+ **Date:** 2025-11-29
5
+
6
+ ---
7
+
8
+ Safeword sets up a comprehensive linting configuration. The plugin list varies by detected project type.
9
+
10
+ ## Core Plugins (Always Installed)
11
+
12
+ | Plugin | Purpose | npm Package |
13
+ |--------|---------|-------------|
14
+ | SonarJS | Code smells, complexity, bugs | `eslint-plugin-sonarjs` |
15
+ | Microsoft SDL | Security Development Lifecycle (includes eslint-plugin-security rules) | `@microsoft/eslint-plugin-sdl` |
16
+
17
+ **Note:** We use `@microsoft/eslint-plugin-sdl` instead of `eslint-plugin-security` because [SDL is a superset](https://github.com/microsoft/eslint-plugin-sdl) that includes security rules plus additional framework-specific rules (React, Angular, TypeScript). Using both would be redundant.
18
+
19
+ ## Architecture Enforcement (Auto-Detected)
20
+
21
+ | Plugin | npm Package | Mode |
22
+ |--------|-------------|------|
23
+ | Boundaries | `eslint-plugin-boundaries` | Auto-detect dirs, generate config, warn severity |
24
+
25
+ Safeword auto-generates boundaries config when it detects 3+ architecture directories:
26
+
27
+ **Detection logic:**
28
+ ```typescript
29
+ const architectureDirs = ['app', 'components', 'lib', 'utils', 'hooks', 'services', 'types', 'features', 'modules'];
30
+
31
+ // Check both root and src/ prefix
32
+ const found = architectureDirs.filter(d =>
33
+ existsSync(join(projectDir, d)) || existsSync(join(projectDir, 'src', d))
34
+ );
35
+
36
+ if (found.length >= 3) {
37
+ generateBoundariesConfig(projectDir, found);
38
+ }
39
+ ```
40
+
41
+ **Generated hierarchy** (from most to least restrictive):
42
+ ```
43
+ types → can be imported by: everything
44
+ utils → can import: types
45
+ lib → can import: utils, types
46
+ hooks → can import: lib, utils, types
47
+ services → can import: lib, utils, types
48
+ components → can import: hooks, services, lib, utils, types
49
+ features → can import: components, hooks, services, lib, utils, types
50
+ modules → can import: components, hooks, services, lib, utils, types
51
+ app → can import: everything
52
+ ```
53
+
54
+ **Key design decisions:**
55
+ - Uses `warn` severity (not `error`) - informative, not blocking
56
+ - Config placed in `.safeword/eslint-boundaries.config.js` (safeword owns)
57
+ - User can override in their `eslint.config.js` (takes precedence)
58
+ - Setup prints: "Detected architecture boundaries - review `.safeword/eslint-boundaries.config.js`"
59
+
60
+ **Generated config example:**
61
+ ```javascript
62
+ // .safeword/eslint-boundaries.config.js (AUTO-GENERATED)
63
+ import boundaries from 'eslint-plugin-boundaries';
64
+
65
+ // Detected directories: components, lib, utils, types (in src/)
66
+ export default {
67
+ plugins: { boundaries },
68
+ settings: {
69
+ 'boundaries/elements': [
70
+ { type: 'components', pattern: 'src/components/**' },
71
+ { type: 'lib', pattern: 'src/lib/**' },
72
+ { type: 'utils', pattern: 'src/utils/**' },
73
+ { type: 'types', pattern: 'src/types/**' },
74
+ ],
75
+ },
76
+ rules: {
77
+ 'boundaries/element-types': ['warn', {
78
+ default: 'disallow',
79
+ rules: [
80
+ { from: 'components', allow: ['lib', 'utils', 'types'] },
81
+ { from: 'lib', allow: ['utils', 'types'] },
82
+ { from: 'utils', allow: ['types'] },
83
+ // types can be imported by anything (no restriction)
84
+ ],
85
+ }],
86
+ },
87
+ };
88
+ ```
89
+
90
+ **User override** (in their `eslint.config.js`):
91
+ ```javascript
92
+ export default defineConfig([
93
+ { extends: [safeword] },
94
+
95
+ // Override boundaries with custom architecture
96
+ {
97
+ settings: {
98
+ 'boundaries/elements': [
99
+ // Your custom architecture
100
+ ],
101
+ },
102
+ rules: {
103
+ 'boundaries/element-types': ['error', { /* stricter rules */ }],
104
+ },
105
+ },
106
+ ]);
107
+ ```
108
+
109
+ ## Framework-Specific Plugins (Auto-Detected)
110
+
111
+ | Framework | Plugin | npm Package | Detection |
112
+ |-----------|--------|-------------|-----------|
113
+ | React | React rules | `eslint-plugin-react` | `react` in dependencies |
114
+ | React | Hooks rules | `eslint-plugin-react-hooks` | `react` in dependencies |
115
+ | Next.js | Next.js rules | `@next/eslint-plugin-next` | `next` in dependencies |
116
+ | Astro | Astro rules | `eslint-plugin-astro` | `astro` in dependencies |
117
+ | Electron | Electron config | `@electron-toolkit/eslint-config` | `electron` in dependencies |
118
+
119
+ ## Testing Plugins (Auto-Detected)
120
+
121
+ | Framework | Plugin | npm Package | Detection |
122
+ |-----------|--------|-------------|-----------|
123
+ | Vitest | Vitest rules | `@vitest/eslint-plugin` | `vitest` in devDependencies |
124
+ | Playwright | Playwright rules | `eslint-plugin-playwright` | `@playwright/test` in devDependencies |
125
+
126
+ **Note:** Testing plugins use `files` patterns to scope rules to test files only:
127
+ - Vitest: `['**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}', '**/tests/**']`
128
+ - Playwright: `['**/e2e/**', '**/*.e2e.{ts,tsx}', '**/playwright/**']`
129
+
130
+ Patterns are detected from `playwright.config.ts` if present, otherwise defaults are used.
131
+
132
+ ## Database Plugins (Auto-Detected)
133
+
134
+ | ORM/Database | Plugin | npm Package | Detection |
135
+ |--------------|--------|-------------|-----------|
136
+ | Drizzle ORM | Drizzle rules | `eslint-plugin-drizzle` | `drizzle-orm` in dependencies |
137
+
138
+ **Available but not auto-installed** (too niche for most projects):
139
+ - `eslint-plugin-sql` - SQL linting in template literals (for raw SQL usage)
140
+ - `eslint-plugin-sql-template` - Enforces `sql` tag to prevent injection
141
+
142
+ ## Container Linting (Separate Tool)
143
+
144
+ Dockerfile linting is handled by [Hadolint](https://github.com/hadolint/hadolint), not ESLint:
145
+
146
+ ```bash
147
+ # Installation
148
+ brew install hadolint # macOS
149
+ scoop install hadolint # Windows
150
+ docker pull hadolint/hadolint # Docker
151
+
152
+ # Usage
153
+ hadolint Dockerfile
154
+ ```
155
+
156
+ **Safeword integration:** The `safeword setup` command adds Hadolint to pre-commit hooks if Dockerfile is detected in project root. Requires Hadolint to be installed on the system.
157
+
158
+ ## Plugin Detection Logic
159
+
160
+ ```typescript
161
+ function detectPlugins(projectDir: string): string[] {
162
+ const pkg = readPackageJson(projectDir);
163
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
164
+ const devDeps = pkg.devDependencies || {};
165
+ const plugins: string[] = [];
166
+
167
+ // Core (always)
168
+ plugins.push('eslint-plugin-sonarjs');
169
+ plugins.push('@microsoft/eslint-plugin-sdl'); // Superset of eslint-plugin-security
170
+
171
+ // Framework detection
172
+ if (deps['react']) {
173
+ plugins.push('eslint-plugin-react');
174
+ plugins.push('eslint-plugin-react-hooks');
175
+ }
176
+ if (deps['next']) {
177
+ plugins.push('@next/eslint-plugin-next');
178
+ }
179
+ if (deps['astro']) {
180
+ plugins.push('eslint-plugin-astro');
181
+ }
182
+ if (deps['electron']) {
183
+ plugins.push('@electron-toolkit/eslint-config');
184
+ }
185
+
186
+ // Testing detection
187
+ if (devDeps['vitest']) {
188
+ plugins.push('@vitest/eslint-plugin');
189
+ }
190
+ if (devDeps['@playwright/test']) {
191
+ plugins.push('eslint-plugin-playwright');
192
+ }
193
+
194
+ // Database detection (only Drizzle - raw SQL plugins are niche)
195
+ if (deps['drizzle-orm']) {
196
+ plugins.push('eslint-plugin-drizzle');
197
+ }
198
+
199
+ // Architecture boundaries (auto-detected if 3+ dirs found)
200
+ if (detectArchitectureFolders(projectDir)) {
201
+ plugins.push('eslint-plugin-boundaries');
202
+ }
203
+
204
+ return plugins;
205
+ }
206
+
207
+ function detectArchitectureFolders(projectDir: string): boolean {
208
+ const architectureDirs = ['app', 'components', 'lib', 'utils', 'hooks', 'services', 'types', 'features', 'modules'];
209
+
210
+ const found = architectureDirs.filter(d =>
211
+ existsSync(join(projectDir, d)) || existsSync(join(projectDir, 'src', d))
212
+ );
213
+ return found.length >= 3;
214
+ }
215
+ ```
216
+
217
+ ## ESLint Config Generation
218
+
219
+ The generated `.safeword/eslint.config.js` dynamically includes plugins based on detection:
220
+
221
+ ```javascript
222
+ // .safeword/eslint.config.js (SAFEWORD OWNS)
223
+ // ⚠️ AUTO-GENERATED BY SAFEWORD - DO NOT EDIT
224
+ // Customize in your root eslint.config.js instead
225
+
226
+ import js from '@eslint/js';
227
+ import tseslint from 'typescript-eslint';
228
+ import sonarjs from 'eslint-plugin-sonarjs';
229
+ import sdl from '@microsoft/eslint-plugin-sdl';
230
+ // Framework imports added based on detection (react, next, vitest, etc.)
231
+ // Boundaries import added if 3+ architecture dirs detected
232
+
233
+ export default [
234
+ // Base (always included)
235
+ js.configs.recommended,
236
+ ...tseslint.configs.recommended,
237
+ sonarjs.configs.recommended,
238
+ ...sdl.configs.recommended,
239
+
240
+ // Framework configs (conditionally included based on package.json)
241
+ // ...react configs if react detected
242
+ // ...next configs if next detected
243
+ // ...vitest configs if vitest detected
244
+ // ...playwright configs if @playwright/test detected
245
+
246
+ // Boundaries (conditionally included if 3+ architecture dirs found)
247
+ // boundariesConfig,
248
+
249
+ // Global ignores
250
+ { ignores: ['node_modules/', 'dist/', '.next/', 'build/'] },
251
+ ];
252
+ ```
253
+
254
+ **Implementation note:** The CLI generates this file dynamically based on detected dependencies and directory structure. Only detected plugins are imported and included.
255
+
256
+ **Note:** Vite doesn't need ESLint rules - use `vite-plugin-eslint2` for dev server integration.
@@ -0,0 +1,385 @@
1
+ # Implementation Snippets
2
+
3
+ **Parent:** [013-cli-self-contained-templates.md](./013-cli-self-contained-templates.md)
4
+ **Date:** 2025-11-29
5
+
6
+ Reference code snippets for implementing the CLI. Copy/adapt as needed.
7
+
8
+ ---
9
+
10
+ ## Template Reader (src/utils/templates.ts)
11
+
12
+ ```typescript
13
+ import { readFileSync, cpSync } from 'node:fs';
14
+ import { join, dirname } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const TEMPLATES_DIR = join(__dirname, '..', 'templates');
19
+
20
+ export function getTemplate(relativePath: string): string {
21
+ return readFileSync(join(TEMPLATES_DIR, relativePath), 'utf-8');
22
+ }
23
+
24
+ export function copyTemplateDir(src: string, dest: string): void {
25
+ cpSync(join(TEMPLATES_DIR, src), dest, { recursive: true });
26
+ }
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Setup/Upgrade Usage
32
+
33
+ ```typescript
34
+ import { getTemplate, copyTemplateDir } from '../utils/templates.js';
35
+
36
+ // Copy templates to .safeword/
37
+ writeFile(join(safewordDir, 'SAFEWORD.md'), getTemplate('safeword/SAFEWORD.md'));
38
+ copyTemplateDir('safeword/guides', join(safewordDir, 'guides'));
39
+ copyTemplateDir('safeword/doc-templates', join(safewordDir, 'doc-templates'));
40
+ copyTemplateDir('safeword/prompts', join(safewordDir, 'prompts'));
41
+ copyTemplateDir('safeword/hooks', join(safewordDir, 'hooks'));
42
+ copyTemplateDir('safeword/lib', join(safewordDir, 'lib'));
43
+ copyTemplateDir('safeword/git', join(safewordDir, 'git'));
44
+
45
+ // Copy templates to .claude/
46
+ copyTemplateDir('claude/commands', join(claudeDir, 'commands'));
47
+ copyTemplateDir('claude/skills', join(claudeDir, 'skills'));
48
+
49
+ // Create empty planning directories
50
+ ensureDir(join(safewordDir, 'planning', 'user-stories'));
51
+ ensureDir(join(safewordDir, 'planning', 'design'));
52
+ ensureDir(join(safewordDir, 'tickets', 'completed'));
53
+ ensureDir(join(safewordDir, 'learnings'));
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Merge settings.json
59
+
60
+ ```typescript
61
+ import { SETTINGS_HOOKS } from '../config/hooks.js';
62
+
63
+ function mergeSettingsJson(claudeDir: string): void {
64
+ const settingsPath = join(claudeDir, 'settings.json');
65
+ let existing = existsSync(settingsPath)
66
+ ? JSON.parse(readFileSync(settingsPath, 'utf-8'))
67
+ : {};
68
+
69
+ const merged = {
70
+ ...existing,
71
+ hooks: mergeHooks(existing.hooks || {}, SETTINGS_HOOKS),
72
+ };
73
+ writeFileSync(settingsPath, JSON.stringify(merged, null, 2));
74
+ }
75
+
76
+ function mergeHooks(existing: object, safeword: object): object {
77
+ const result = { ...existing };
78
+ for (const [event, hooks] of Object.entries(safeword)) {
79
+ result[event] = [...(result[event] || []), ...hooks];
80
+ }
81
+ return result;
82
+ }
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Linting Setup (Preserve Existing)
88
+
89
+ ```typescript
90
+ function setupLinting(projectDir: string): void {
91
+ const eslintPath = join(projectDir, 'eslint.config.js');
92
+ const prettierPath = join(projectDir, '.prettierrc');
93
+
94
+ if (!existsSync(eslintPath) && !existsSync(join(projectDir, '.eslintrc.js'))) {
95
+ writeFileSync(eslintPath, getEslintConfig(detectProjectType(projectDir)));
96
+ }
97
+
98
+ if (!existsSync(prettierPath) && !existsSync(join(projectDir, '.prettierrc.js'))) {
99
+ writeFileSync(prettierPath, JSON.stringify({ semi: true, singleQuote: true }, null, 2));
100
+ }
101
+
102
+ addLintScriptsIfMissing(projectDir);
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Error Handling
109
+
110
+ ```typescript
111
+ // src/utils/errors.ts
112
+ export class SafewordError extends Error {
113
+ constructor(message: string, public fatal: boolean = true) {
114
+ super(message);
115
+ this.name = 'SafewordError';
116
+ }
117
+ }
118
+
119
+ export function handleError(error: SafewordError, context: string): void {
120
+ if (error.fatal) {
121
+ console.error(`❌ Fatal: ${error.message}`);
122
+ process.exit(1);
123
+ } else {
124
+ console.warn(`⚠️ Warning: ${error.message} (continuing...)`);
125
+ }
126
+ }
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Rollback
132
+
133
+ ```typescript
134
+ // src/utils/rollback.ts
135
+ export async function withRollback<T>(
136
+ operation: () => Promise<T>,
137
+ backup: () => Promise<void>,
138
+ restore: () => Promise<void>
139
+ ): Promise<T> {
140
+ await backup();
141
+ try {
142
+ return await operation();
143
+ } catch (error) {
144
+ console.error('❌ Operation failed, rolling back...');
145
+ await restore();
146
+ throw error;
147
+ }
148
+ }
149
+
150
+ // Usage
151
+ await withRollback(
152
+ () => installSafeword(options),
153
+ () => backupExisting('.safeword', '.claude'),
154
+ () => restoreBackup('.safeword', '.claude')
155
+ );
156
+ ```
157
+
158
+ Backup locations: `.safeword.backup/`, `.claude.backup/` (deleted on success)
159
+
160
+ ---
161
+
162
+ ## ESLint Plugin Installation
163
+
164
+ ```typescript
165
+ // src/commands/init.ts
166
+ async function installEslintPlugins(detected: DetectedStack): Promise<void> {
167
+ const plugins: string[] = ['@eslint/js'];
168
+
169
+ if (detected.typescript) plugins.push('typescript-eslint');
170
+ if (detected.react) plugins.push('eslint-plugin-react', 'eslint-plugin-react-hooks');
171
+ if (detected.security) plugins.push('@microsoft/eslint-plugin-sdl');
172
+ if (detected.playwright) plugins.push('eslint-plugin-playwright');
173
+ if (detected.vitest) plugins.push('@vitest/eslint-plugin');
174
+ if (detected.boundaries) plugins.push('eslint-plugin-boundaries');
175
+
176
+ const pm = detectPackageManager();
177
+ const installCmd = {
178
+ npm: `npm install -D ${plugins.join(' ')}`,
179
+ yarn: `yarn add -D ${plugins.join(' ')}`,
180
+ pnpm: `pnpm add -D ${plugins.join(' ')}`,
181
+ bun: `bun add -D ${plugins.join(' ')}`,
182
+ }[pm];
183
+
184
+ console.log(`📦 Installing ESLint plugins: ${plugins.join(', ')}`);
185
+ execSync(installCmd, { stdio: 'inherit' });
186
+ }
187
+ ```
188
+
189
+ ---
190
+
191
+ ## MCP Config Setup
192
+
193
+ ```typescript
194
+ // src/commands/init.ts
195
+ async function setupMcpConfigs(): Promise<void> {
196
+ const mcpPath = '.mcp.json';
197
+ const samplePath = '.mcp.json.sample';
198
+
199
+ let mcpConfig: { mcpServers: Record<string, unknown> } = { mcpServers: {} };
200
+ try {
201
+ const existing = await fs.readFile(mcpPath, 'utf-8');
202
+ mcpConfig = JSON.parse(existing);
203
+ } catch { /* file doesn't exist */ }
204
+
205
+ // Auto-activate free servers (merge, don't overwrite)
206
+ mcpConfig.mcpServers['context7'] ??= {
207
+ command: 'npx',
208
+ args: ['-y', '@upstash/context7-mcp@latest'],
209
+ };
210
+ mcpConfig.mcpServers['playwright'] ??= {
211
+ command: 'npx',
212
+ args: ['@playwright/mcp@latest'],
213
+ };
214
+
215
+ await fs.writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2));
216
+
217
+ // Sample for API-key servers
218
+ const sampleExists = await fs.access(samplePath).then(() => true).catch(() => false);
219
+ if (!sampleExists) {
220
+ const sample = {
221
+ mcpServers: {
222
+ arcade: {
223
+ type: 'http',
224
+ url: 'https://mcp.arcade.dev',
225
+ headers: { Authorization: 'Bearer YOUR_ARCADE_API_KEY' },
226
+ },
227
+ },
228
+ };
229
+ await fs.writeFile(samplePath, JSON.stringify(sample, null, 2));
230
+ }
231
+
232
+ console.log('📡 MCP: Context7 ✓, Playwright ✓ (see .mcp.json.sample for Arcade)');
233
+ }
234
+ ```
235
+
236
+ ---
237
+
238
+ ## MCP Cleanup (Reset)
239
+
240
+ ```typescript
241
+ // src/commands/reset.ts
242
+ async function cleanupMcpConfig(): Promise<void> {
243
+ const mcpPath = '.mcp.json';
244
+ try {
245
+ const content = await fs.readFile(mcpPath, 'utf-8');
246
+ const config = JSON.parse(content);
247
+
248
+ delete config.mcpServers?.['context7'];
249
+ delete config.mcpServers?.['playwright'];
250
+
251
+ if (Object.keys(config.mcpServers || {}).length === 0) {
252
+ await fs.unlink(mcpPath);
253
+ } else {
254
+ await fs.writeFile(mcpPath, JSON.stringify(config, null, 2));
255
+ }
256
+ } catch { /* file doesn't exist */ }
257
+
258
+ await fs.unlink('.mcp.json.sample').catch(() => {});
259
+ }
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Hook Output (hookSpecificOutput)
265
+
266
+ ```bash
267
+ # .safeword/hooks/session-verify-agents.sh
268
+ #!/bin/bash
269
+ if [[ -f "AGENTS.md" ]]; then
270
+ jq -n '{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "AGENTS.md verified"}}'
271
+ else
272
+ jq -n '{"hookSpecificOutput": {"hookEventName": "SessionStart", "blockReason": "AGENTS.md not found - run safeword init"}}'
273
+ exit 2
274
+ fi
275
+ ```
276
+
277
+ ```bash
278
+ # .safeword/hooks/prompt-timestamp.sh
279
+ #!/bin/bash
280
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
281
+ jq -n --arg ts "$TIMESTAMP" \
282
+ '{"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": ("Timestamp: " + $ts)}}'
283
+ ```
284
+
285
+ ```bash
286
+ # .safeword/hooks/post-tool-lint.sh
287
+ #!/bin/bash
288
+ LINT_OUTPUT=$(npm run lint 2>&1 || true)
289
+ if echo "$LINT_OUTPUT" | grep -q "error"; then
290
+ jq -n '{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "Lint errors found - please fix"}}'
291
+ else
292
+ jq -n '{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "Lint passed"}}'
293
+ fi
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Slash Command Templates
299
+
300
+ ```typescript
301
+ // src/config/commands.ts
302
+ export const COMMANDS = {
303
+ 'arch-review.md': `---
304
+ description: Run architecture review on current changes
305
+ allowed-tools: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"]
306
+ ---
307
+
308
+ Review the architecture of the current changes against the project's architecture guide.
309
+ Focus on: layering violations, dependency direction, and interface contracts.
310
+ `,
311
+ 'quality-review.md': `---
312
+ description: Deep code quality review with web research
313
+ allowed-tools: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"]
314
+ ---
315
+
316
+ Perform a comprehensive quality review of the current changes.
317
+ Check for security issues, performance concerns, and best practice violations.
318
+ `,
319
+ };
320
+ ```
321
+
322
+ ---
323
+
324
+ ## Testing Examples
325
+
326
+ ### Unit Tests
327
+
328
+ ```typescript
329
+ // tests/unit/detect.test.ts
330
+ describe('detectStack', () => {
331
+ it('detects TypeScript from tsconfig.json', async () => {
332
+ const result = await detectStack('/mock/project');
333
+ expect(result.typescript).toBe(true);
334
+ });
335
+
336
+ it('detects boundaries architecture (3+ dirs)', async () => {
337
+ const result = await detectStack('/mock/layered-project');
338
+ expect(result.boundaries).toBe(true);
339
+ });
340
+ });
341
+ ```
342
+
343
+ ### Integration Tests
344
+
345
+ ```typescript
346
+ // tests/integration/init.test.ts
347
+ describe('safeword init', () => {
348
+ const testDir = '/tmp/safeword-test-project';
349
+
350
+ beforeEach(async () => {
351
+ await fs.mkdir(testDir, { recursive: true });
352
+ await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ name: 'test', type: 'module' }));
353
+ });
354
+
355
+ afterEach(async () => {
356
+ await fs.rm(testDir, { recursive: true, force: true });
357
+ });
358
+
359
+ it('creates .safeword directory', async () => {
360
+ execSync('npx safeword init', { cwd: testDir });
361
+ const exists = await fs.access(path.join(testDir, '.safeword')).then(() => true).catch(() => false);
362
+ expect(exists).toBe(true);
363
+ });
364
+ });
365
+ ```
366
+
367
+ ### LLM Evals (promptfoo)
368
+
369
+ ```yaml
370
+ # promptfoo.yaml
371
+ providers:
372
+ - id: anthropic:messages:claude-sonnet-4-20250514
373
+ config:
374
+ temperature: 0
375
+
376
+ tests:
377
+ - description: "init creates correct structure for React + TS project"
378
+ vars:
379
+ project_type: "React + TypeScript"
380
+ assert:
381
+ - type: contains
382
+ value: ".safeword/"
383
+ - type: contains
384
+ value: "eslint-plugin-react"
385
+ ```