safeword 0.2.4 → 0.2.6
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.
- package/dist/check-3NGQ4NR5.js +129 -0
- package/dist/check-3NGQ4NR5.js.map +1 -0
- package/dist/chunk-2XWIUEQK.js +190 -0
- package/dist/chunk-2XWIUEQK.js.map +1 -0
- package/dist/chunk-GZRQL3SX.js +146 -0
- package/dist/chunk-GZRQL3SX.js.map +1 -0
- package/dist/chunk-ORQHKDT2.js +10 -0
- package/dist/chunk-ORQHKDT2.js.map +1 -0
- package/dist/chunk-W66Z3C5H.js +21 -0
- package/dist/chunk-W66Z3C5H.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +34 -0
- package/dist/cli.js.map +1 -0
- package/dist/diff-Y6QTAW4O.js +166 -0
- package/dist/diff-Y6QTAW4O.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/reset-3ACTIYYE.js +143 -0
- package/dist/reset-3ACTIYYE.js.map +1 -0
- package/dist/setup-AIL5RL45.js +276 -0
- package/dist/setup-AIL5RL45.js.map +1 -0
- package/dist/upgrade-6AR3DHUV.js +134 -0
- package/dist/upgrade-6AR3DHUV.js.map +1 -0
- package/package.json +44 -19
- package/{.safeword → templates}/hooks/agents-md-check.sh +0 -0
- package/{.safeword → templates}/hooks/post-tool.sh +0 -0
- package/{.safeword → templates}/hooks/pre-commit.sh +0 -0
- package/.claude/commands/arch-review.md +0 -32
- package/.claude/commands/lint.md +0 -6
- package/.claude/commands/quality-review.md +0 -13
- package/.claude/commands/setup-linting.md +0 -6
- package/.claude/hooks/auto-lint.sh +0 -6
- package/.claude/hooks/auto-quality-review.sh +0 -170
- package/.claude/hooks/check-linting-sync.sh +0 -17
- package/.claude/hooks/inject-timestamp.sh +0 -6
- package/.claude/hooks/question-protocol.sh +0 -12
- package/.claude/hooks/run-linters.sh +0 -8
- package/.claude/hooks/run-quality-review.sh +0 -76
- package/.claude/hooks/version-check.sh +0 -10
- package/.claude/mcp/README.md +0 -96
- package/.claude/mcp/arcade.sample.json +0 -9
- package/.claude/mcp/context7.sample.json +0 -7
- package/.claude/mcp/playwright.sample.json +0 -7
- package/.claude/settings.json +0 -62
- package/.claude/skills/quality-reviewer/SKILL.md +0 -190
- package/.claude/skills/safeword-quality-reviewer/SKILL.md +0 -13
- package/.env.arcade.example +0 -4
- package/.env.example +0 -11
- package/.gitmodules +0 -4
- package/.safeword/SAFEWORD.md +0 -33
- package/.safeword/eslint/eslint-base.mjs +0 -101
- package/.safeword/guides/architecture-guide.md +0 -404
- package/.safeword/guides/code-philosophy.md +0 -174
- package/.safeword/guides/context-files-guide.md +0 -405
- package/.safeword/guides/data-architecture-guide.md +0 -183
- package/.safeword/guides/design-doc-guide.md +0 -165
- package/.safeword/guides/learning-extraction.md +0 -515
- package/.safeword/guides/llm-instruction-design.md +0 -239
- package/.safeword/guides/llm-prompting.md +0 -95
- package/.safeword/guides/tdd-best-practices.md +0 -570
- package/.safeword/guides/test-definitions-guide.md +0 -243
- package/.safeword/guides/testing-methodology.md +0 -573
- package/.safeword/guides/user-story-guide.md +0 -237
- package/.safeword/guides/zombie-process-cleanup.md +0 -214
- package/.safeword/planning/002-user-story-quality-evaluation.md +0 -1840
- package/.safeword/planning/003-langsmith-eval-setup-prompt.md +0 -363
- package/.safeword/planning/004-llm-eval-test-cases.md +0 -3226
- package/.safeword/planning/005-architecture-enforcement-system.md +0 -169
- package/.safeword/planning/006-reactive-fix-prevention-research.md +0 -135
- package/.safeword/planning/011-cli-ux-vision.md +0 -330
- package/.safeword/planning/012-project-structure-cleanup.md +0 -154
- package/.safeword/planning/README.md +0 -39
- package/.safeword/planning/automation-plan-v2.md +0 -1225
- package/.safeword/planning/automation-plan-v3.md +0 -1291
- package/.safeword/planning/automation-plan.md +0 -3058
- package/.safeword/planning/design/005-cli-implementation.md +0 -343
- package/.safeword/planning/design/013-cli-self-contained-templates.md +0 -596
- package/.safeword/planning/design/013a-eslint-plugin-suite.md +0 -256
- package/.safeword/planning/design/013b-implementation-snippets.md +0 -385
- package/.safeword/planning/design/013c-config-isolation-strategy.md +0 -242
- package/.safeword/planning/design/code-philosophy-improvements.md +0 -60
- package/.safeword/planning/mcp-analysis.md +0 -545
- package/.safeword/planning/phase2-subagents-vs-skills-analysis.md +0 -451
- package/.safeword/planning/settings-improvements.md +0 -970
- package/.safeword/planning/test-definitions/005-cli-implementation.md +0 -1301
- package/.safeword/planning/test-definitions/cli-self-contained-templates.md +0 -205
- package/.safeword/planning/user-stories/001-guides-review-user-stories.md +0 -1381
- package/.safeword/planning/user-stories/003-reactive-fix-prevention.md +0 -132
- package/.safeword/planning/user-stories/004-technical-constraints.md +0 -86
- package/.safeword/planning/user-stories/005-cli-implementation.md +0 -311
- package/.safeword/planning/user-stories/cli-self-contained-templates.md +0 -172
- package/.safeword/planning/versioned-distribution.md +0 -740
- package/.safeword/prompts/arch-review.md +0 -43
- package/.safeword/prompts/quality-review.md +0 -11
- package/.safeword/scripts/arch-review.sh +0 -235
- package/.safeword/scripts/check-linting-sync.sh +0 -58
- package/.safeword/scripts/setup-linting.sh +0 -559
- package/.safeword/templates/architecture-template.md +0 -136
- package/.safeword/templates/ci/architecture-check.yml +0 -79
- package/.safeword/templates/design-doc-template.md +0 -127
- package/.safeword/templates/test-definitions-feature.md +0 -100
- package/.safeword/templates/ticket-template.md +0 -74
- package/.safeword/templates/user-stories-template.md +0 -82
- package/.safeword/tickets/001-guides-review-user-stories.md +0 -83
- package/.safeword/tickets/002-architecture-enforcement.md +0 -211
- package/.safeword/tickets/003-reactive-fix-prevention.md +0 -57
- package/.safeword/tickets/004-technical-constraints-in-user-stories.md +0 -39
- package/.safeword/tickets/005-cli-implementation.md +0 -248
- package/.safeword/tickets/006-flesh-out-skills.md +0 -43
- package/.safeword/tickets/007-flesh-out-questioning.md +0 -44
- package/.safeword/tickets/008-upgrade-questioning.md +0 -58
- package/.safeword/tickets/009-naming-conventions.md +0 -41
- package/.safeword/tickets/010-safeword-md-cleanup.md +0 -34
- package/.safeword/tickets/011-cursor-setup.md +0 -86
- package/.safeword/tickets/README.md +0 -73
- package/.safeword/version +0 -1
- package/AGENTS.md +0 -59
- package/CLAUDE.md +0 -12
- package/README.md +0 -347
- package/docs/001-cli-implementation-plan.md +0 -856
- package/docs/elite-dx-implementation-plan.md +0 -1034
- package/framework/README.md +0 -131
- package/framework/mcp/README.md +0 -96
- package/framework/mcp/arcade.sample.json +0 -8
- package/framework/mcp/context7.sample.json +0 -6
- package/framework/mcp/playwright.sample.json +0 -6
- package/framework/scripts/arch-review.sh +0 -235
- package/framework/scripts/check-linting-sync.sh +0 -58
- package/framework/scripts/load-env.sh +0 -49
- package/framework/scripts/setup-claude.sh +0 -223
- package/framework/scripts/setup-linting.sh +0 -559
- package/framework/scripts/setup-quality.sh +0 -477
- package/framework/scripts/setup-safeword.sh +0 -550
- package/framework/templates/ci/architecture-check.yml +0 -78
- package/learnings/ai-sdk-v5-breaking-changes.md +0 -178
- package/learnings/e2e-test-zombie-processes.md +0 -231
- package/learnings/milkdown-crepe-editor-property.md +0 -96
- package/learnings/prosemirror-fragment-traversal.md +0 -119
- package/packages/cli/AGENTS.md +0 -1
- package/packages/cli/ARCHITECTURE.md +0 -279
- package/packages/cli/package.json +0 -51
- package/packages/cli/src/cli.ts +0 -63
- package/packages/cli/src/commands/check.ts +0 -166
- package/packages/cli/src/commands/diff.ts +0 -209
- package/packages/cli/src/commands/reset.ts +0 -190
- package/packages/cli/src/commands/setup.ts +0 -325
- package/packages/cli/src/commands/upgrade.ts +0 -163
- package/packages/cli/src/index.ts +0 -3
- package/packages/cli/src/templates/config.ts +0 -58
- package/packages/cli/src/templates/content.ts +0 -18
- package/packages/cli/src/templates/index.ts +0 -12
- package/packages/cli/src/utils/agents-md.ts +0 -66
- package/packages/cli/src/utils/fs.ts +0 -179
- package/packages/cli/src/utils/git.ts +0 -124
- package/packages/cli/src/utils/hooks.ts +0 -29
- package/packages/cli/src/utils/output.ts +0 -60
- package/packages/cli/src/utils/project-detector.test.ts +0 -185
- package/packages/cli/src/utils/project-detector.ts +0 -44
- package/packages/cli/src/utils/version.ts +0 -28
- package/packages/cli/src/version.ts +0 -6
- package/packages/cli/templates/SAFEWORD.md +0 -776
- package/packages/cli/templates/doc-templates/architecture-template.md +0 -136
- package/packages/cli/templates/doc-templates/design-doc-template.md +0 -134
- package/packages/cli/templates/doc-templates/test-definitions-feature.md +0 -131
- package/packages/cli/templates/doc-templates/ticket-template.md +0 -82
- package/packages/cli/templates/doc-templates/user-stories-template.md +0 -92
- package/packages/cli/templates/guides/architecture-guide.md +0 -423
- package/packages/cli/templates/guides/code-philosophy.md +0 -195
- package/packages/cli/templates/guides/context-files-guide.md +0 -457
- package/packages/cli/templates/guides/data-architecture-guide.md +0 -200
- package/packages/cli/templates/guides/design-doc-guide.md +0 -171
- package/packages/cli/templates/guides/learning-extraction.md +0 -552
- package/packages/cli/templates/guides/llm-instruction-design.md +0 -248
- package/packages/cli/templates/guides/llm-prompting.md +0 -102
- package/packages/cli/templates/guides/tdd-best-practices.md +0 -615
- package/packages/cli/templates/guides/test-definitions-guide.md +0 -334
- package/packages/cli/templates/guides/testing-methodology.md +0 -618
- package/packages/cli/templates/guides/user-story-guide.md +0 -256
- package/packages/cli/templates/guides/zombie-process-cleanup.md +0 -219
- package/packages/cli/templates/hooks/agents-md-check.sh +0 -27
- package/packages/cli/templates/hooks/post-tool.sh +0 -4
- package/packages/cli/templates/hooks/pre-commit.sh +0 -10
- package/packages/cli/templates/prompts/arch-review.md +0 -43
- package/packages/cli/templates/prompts/quality-review.md +0 -10
- package/packages/cli/templates/skills/safeword-quality-reviewer/SKILL.md +0 -207
- package/packages/cli/tests/commands/check.test.ts +0 -129
- package/packages/cli/tests/commands/cli.test.ts +0 -89
- package/packages/cli/tests/commands/diff.test.ts +0 -115
- package/packages/cli/tests/commands/reset.test.ts +0 -310
- package/packages/cli/tests/commands/self-healing.test.ts +0 -170
- package/packages/cli/tests/commands/setup-blocking.test.ts +0 -71
- package/packages/cli/tests/commands/setup-core.test.ts +0 -135
- package/packages/cli/tests/commands/setup-git.test.ts +0 -139
- package/packages/cli/tests/commands/setup-hooks.test.ts +0 -334
- package/packages/cli/tests/commands/setup-linting.test.ts +0 -189
- package/packages/cli/tests/commands/setup-noninteractive.test.ts +0 -80
- package/packages/cli/tests/commands/setup-templates.test.ts +0 -181
- package/packages/cli/tests/commands/upgrade.test.ts +0 -215
- package/packages/cli/tests/helpers.ts +0 -243
- package/packages/cli/tests/npm-package.test.ts +0 -83
- package/packages/cli/tests/technical-constraints.test.ts +0 -96
- package/packages/cli/tsconfig.json +0 -25
- package/packages/cli/tsup.config.ts +0 -11
- package/packages/cli/vitest.config.ts +0 -23
- package/promptfoo.yaml +0 -3270
- /package/{framework → templates}/SAFEWORD.md +0 -0
- /package/{packages/cli/templates → templates}/commands/arch-review.md +0 -0
- /package/{packages/cli/templates → templates}/commands/lint.md +0 -0
- /package/{packages/cli/templates → templates}/commands/quality-review.md +0 -0
- /package/{framework/templates → templates/doc-templates}/architecture-template.md +0 -0
- /package/{framework/templates → templates/doc-templates}/design-doc-template.md +0 -0
- /package/{framework/templates → templates/doc-templates}/test-definitions-feature.md +0 -0
- /package/{framework/templates → templates/doc-templates}/ticket-template.md +0 -0
- /package/{framework/templates → templates/doc-templates}/user-stories-template.md +0 -0
- /package/{framework → templates}/guides/architecture-guide.md +0 -0
- /package/{framework → templates}/guides/code-philosophy.md +0 -0
- /package/{framework → templates}/guides/context-files-guide.md +0 -0
- /package/{framework → templates}/guides/data-architecture-guide.md +0 -0
- /package/{framework → templates}/guides/design-doc-guide.md +0 -0
- /package/{framework → templates}/guides/learning-extraction.md +0 -0
- /package/{framework → templates}/guides/llm-instruction-design.md +0 -0
- /package/{framework → templates}/guides/llm-prompting.md +0 -0
- /package/{framework → templates}/guides/tdd-best-practices.md +0 -0
- /package/{framework → templates}/guides/test-definitions-guide.md +0 -0
- /package/{framework → templates}/guides/testing-methodology.md +0 -0
- /package/{framework → templates}/guides/user-story-guide.md +0 -0
- /package/{framework → templates}/guides/zombie-process-cleanup.md +0 -0
- /package/{packages/cli/templates → templates}/hooks/inject-timestamp.sh +0 -0
- /package/{packages/cli/templates → templates}/lib/common.sh +0 -0
- /package/{packages/cli/templates → templates}/lib/jq-fallback.sh +0 -0
- /package/{packages/cli/templates → templates}/markdownlint.jsonc +0 -0
- /package/{framework → templates}/prompts/arch-review.md +0 -0
- /package/{framework → templates}/prompts/quality-review.md +0 -0
- /package/{framework/skills/quality-reviewer → templates/skills/safeword-quality-reviewer}/SKILL.md +0 -0
|
@@ -1,256 +0,0 @@
|
|
|
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.
|
|
@@ -1,385 +0,0 @@
|
|
|
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
|
-
```
|