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.
- package/.claude/commands/arch-review.md +32 -0
- package/.claude/commands/lint.md +6 -0
- package/.claude/commands/quality-review.md +13 -0
- package/.claude/commands/setup-linting.md +6 -0
- package/.claude/hooks/auto-lint.sh +6 -0
- package/.claude/hooks/auto-quality-review.sh +170 -0
- package/.claude/hooks/check-linting-sync.sh +17 -0
- package/.claude/hooks/inject-timestamp.sh +6 -0
- package/.claude/hooks/question-protocol.sh +12 -0
- package/.claude/hooks/run-linters.sh +8 -0
- package/.claude/hooks/run-quality-review.sh +76 -0
- package/.claude/hooks/version-check.sh +10 -0
- package/.claude/mcp/README.md +96 -0
- package/.claude/mcp/arcade.sample.json +9 -0
- package/.claude/mcp/context7.sample.json +7 -0
- package/.claude/mcp/playwright.sample.json +7 -0
- package/.claude/settings.json +62 -0
- package/.claude/skills/quality-reviewer/SKILL.md +190 -0
- package/.claude/skills/safeword-quality-reviewer/SKILL.md +13 -0
- package/.env.arcade.example +4 -0
- package/.env.example +11 -0
- package/.gitmodules +4 -0
- package/.safeword/SAFEWORD.md +33 -0
- package/.safeword/eslint/eslint-base.mjs +101 -0
- package/.safeword/guides/architecture-guide.md +404 -0
- package/.safeword/guides/code-philosophy.md +174 -0
- package/.safeword/guides/context-files-guide.md +405 -0
- package/.safeword/guides/data-architecture-guide.md +183 -0
- package/.safeword/guides/design-doc-guide.md +165 -0
- package/.safeword/guides/learning-extraction.md +515 -0
- package/.safeword/guides/llm-instruction-design.md +239 -0
- package/.safeword/guides/llm-prompting.md +95 -0
- package/.safeword/guides/tdd-best-practices.md +570 -0
- package/.safeword/guides/test-definitions-guide.md +243 -0
- package/.safeword/guides/testing-methodology.md +573 -0
- package/.safeword/guides/user-story-guide.md +237 -0
- package/.safeword/guides/zombie-process-cleanup.md +214 -0
- package/{templates → .safeword}/hooks/agents-md-check.sh +0 -0
- package/{templates → .safeword}/hooks/post-tool.sh +0 -0
- package/{templates → .safeword}/hooks/pre-commit.sh +0 -0
- package/.safeword/planning/002-user-story-quality-evaluation.md +1840 -0
- package/.safeword/planning/003-langsmith-eval-setup-prompt.md +363 -0
- package/.safeword/planning/004-llm-eval-test-cases.md +3226 -0
- package/.safeword/planning/005-architecture-enforcement-system.md +169 -0
- package/.safeword/planning/006-reactive-fix-prevention-research.md +135 -0
- package/.safeword/planning/011-cli-ux-vision.md +330 -0
- package/.safeword/planning/012-project-structure-cleanup.md +154 -0
- package/.safeword/planning/README.md +39 -0
- package/.safeword/planning/automation-plan-v2.md +1225 -0
- package/.safeword/planning/automation-plan-v3.md +1291 -0
- package/.safeword/planning/automation-plan.md +3058 -0
- package/.safeword/planning/design/005-cli-implementation.md +343 -0
- package/.safeword/planning/design/013-cli-self-contained-templates.md +596 -0
- package/.safeword/planning/design/013a-eslint-plugin-suite.md +256 -0
- package/.safeword/planning/design/013b-implementation-snippets.md +385 -0
- package/.safeword/planning/design/013c-config-isolation-strategy.md +242 -0
- package/.safeword/planning/design/code-philosophy-improvements.md +60 -0
- package/.safeword/planning/mcp-analysis.md +545 -0
- package/.safeword/planning/phase2-subagents-vs-skills-analysis.md +451 -0
- package/.safeword/planning/settings-improvements.md +970 -0
- package/.safeword/planning/test-definitions/005-cli-implementation.md +1301 -0
- package/.safeword/planning/test-definitions/cli-self-contained-templates.md +205 -0
- package/.safeword/planning/user-stories/001-guides-review-user-stories.md +1381 -0
- package/.safeword/planning/user-stories/003-reactive-fix-prevention.md +132 -0
- package/.safeword/planning/user-stories/004-technical-constraints.md +86 -0
- package/.safeword/planning/user-stories/005-cli-implementation.md +311 -0
- package/.safeword/planning/user-stories/cli-self-contained-templates.md +172 -0
- package/.safeword/planning/versioned-distribution.md +740 -0
- package/.safeword/prompts/arch-review.md +43 -0
- package/.safeword/prompts/quality-review.md +11 -0
- package/.safeword/scripts/arch-review.sh +235 -0
- package/.safeword/scripts/check-linting-sync.sh +58 -0
- package/.safeword/scripts/setup-linting.sh +559 -0
- package/.safeword/templates/architecture-template.md +136 -0
- package/.safeword/templates/ci/architecture-check.yml +79 -0
- package/.safeword/templates/design-doc-template.md +127 -0
- package/.safeword/templates/test-definitions-feature.md +100 -0
- package/.safeword/templates/ticket-template.md +74 -0
- package/.safeword/templates/user-stories-template.md +82 -0
- package/.safeword/tickets/001-guides-review-user-stories.md +83 -0
- package/.safeword/tickets/002-architecture-enforcement.md +211 -0
- package/.safeword/tickets/003-reactive-fix-prevention.md +57 -0
- package/.safeword/tickets/004-technical-constraints-in-user-stories.md +39 -0
- package/.safeword/tickets/005-cli-implementation.md +248 -0
- package/.safeword/tickets/006-flesh-out-skills.md +43 -0
- package/.safeword/tickets/007-flesh-out-questioning.md +44 -0
- package/.safeword/tickets/008-upgrade-questioning.md +58 -0
- package/.safeword/tickets/009-naming-conventions.md +41 -0
- package/.safeword/tickets/010-safeword-md-cleanup.md +34 -0
- package/.safeword/tickets/011-cursor-setup.md +86 -0
- package/.safeword/tickets/README.md +73 -0
- package/.safeword/version +1 -0
- package/AGENTS.md +59 -0
- package/CLAUDE.md +12 -0
- package/README.md +347 -0
- package/docs/001-cli-implementation-plan.md +856 -0
- package/docs/elite-dx-implementation-plan.md +1034 -0
- package/framework/README.md +131 -0
- package/framework/mcp/README.md +96 -0
- package/framework/mcp/arcade.sample.json +8 -0
- package/framework/mcp/context7.sample.json +6 -0
- package/framework/mcp/playwright.sample.json +6 -0
- package/framework/scripts/arch-review.sh +235 -0
- package/framework/scripts/check-linting-sync.sh +58 -0
- package/framework/scripts/load-env.sh +49 -0
- package/framework/scripts/setup-claude.sh +223 -0
- package/framework/scripts/setup-linting.sh +559 -0
- package/framework/scripts/setup-quality.sh +477 -0
- package/framework/scripts/setup-safeword.sh +550 -0
- package/framework/templates/ci/architecture-check.yml +78 -0
- package/learnings/ai-sdk-v5-breaking-changes.md +178 -0
- package/learnings/e2e-test-zombie-processes.md +231 -0
- package/learnings/milkdown-crepe-editor-property.md +96 -0
- package/learnings/prosemirror-fragment-traversal.md +119 -0
- package/package.json +19 -43
- package/packages/cli/AGENTS.md +1 -0
- package/packages/cli/ARCHITECTURE.md +279 -0
- package/packages/cli/package.json +51 -0
- package/packages/cli/src/cli.ts +63 -0
- package/packages/cli/src/commands/check.ts +166 -0
- package/packages/cli/src/commands/diff.ts +209 -0
- package/packages/cli/src/commands/reset.ts +190 -0
- package/packages/cli/src/commands/setup.ts +325 -0
- package/packages/cli/src/commands/upgrade.ts +163 -0
- package/packages/cli/src/index.ts +3 -0
- package/packages/cli/src/templates/config.ts +58 -0
- package/packages/cli/src/templates/content.ts +18 -0
- package/packages/cli/src/templates/index.ts +12 -0
- package/packages/cli/src/utils/agents-md.ts +66 -0
- package/packages/cli/src/utils/fs.ts +179 -0
- package/packages/cli/src/utils/git.ts +124 -0
- package/packages/cli/src/utils/hooks.ts +29 -0
- package/packages/cli/src/utils/output.ts +60 -0
- package/packages/cli/src/utils/project-detector.test.ts +185 -0
- package/packages/cli/src/utils/project-detector.ts +44 -0
- package/packages/cli/src/utils/version.ts +28 -0
- package/packages/cli/src/version.ts +6 -0
- package/packages/cli/templates/SAFEWORD.md +776 -0
- package/packages/cli/templates/doc-templates/architecture-template.md +136 -0
- package/packages/cli/templates/doc-templates/design-doc-template.md +134 -0
- package/packages/cli/templates/doc-templates/test-definitions-feature.md +131 -0
- package/packages/cli/templates/doc-templates/ticket-template.md +82 -0
- package/packages/cli/templates/doc-templates/user-stories-template.md +92 -0
- package/packages/cli/templates/guides/architecture-guide.md +423 -0
- package/packages/cli/templates/guides/code-philosophy.md +195 -0
- package/packages/cli/templates/guides/context-files-guide.md +457 -0
- package/packages/cli/templates/guides/data-architecture-guide.md +200 -0
- package/packages/cli/templates/guides/design-doc-guide.md +171 -0
- package/packages/cli/templates/guides/learning-extraction.md +552 -0
- package/packages/cli/templates/guides/llm-instruction-design.md +248 -0
- package/packages/cli/templates/guides/llm-prompting.md +102 -0
- package/packages/cli/templates/guides/tdd-best-practices.md +615 -0
- package/packages/cli/templates/guides/test-definitions-guide.md +334 -0
- package/packages/cli/templates/guides/testing-methodology.md +618 -0
- package/packages/cli/templates/guides/user-story-guide.md +256 -0
- package/packages/cli/templates/guides/zombie-process-cleanup.md +219 -0
- package/packages/cli/templates/hooks/agents-md-check.sh +27 -0
- package/packages/cli/templates/hooks/post-tool.sh +4 -0
- package/packages/cli/templates/hooks/pre-commit.sh +10 -0
- package/packages/cli/templates/prompts/arch-review.md +43 -0
- package/packages/cli/templates/prompts/quality-review.md +10 -0
- package/packages/cli/templates/skills/safeword-quality-reviewer/SKILL.md +207 -0
- package/packages/cli/tests/commands/check.test.ts +129 -0
- package/packages/cli/tests/commands/cli.test.ts +89 -0
- package/packages/cli/tests/commands/diff.test.ts +115 -0
- package/packages/cli/tests/commands/reset.test.ts +310 -0
- package/packages/cli/tests/commands/self-healing.test.ts +170 -0
- package/packages/cli/tests/commands/setup-blocking.test.ts +71 -0
- package/packages/cli/tests/commands/setup-core.test.ts +135 -0
- package/packages/cli/tests/commands/setup-git.test.ts +139 -0
- package/packages/cli/tests/commands/setup-hooks.test.ts +334 -0
- package/packages/cli/tests/commands/setup-linting.test.ts +189 -0
- package/packages/cli/tests/commands/setup-noninteractive.test.ts +80 -0
- package/packages/cli/tests/commands/setup-templates.test.ts +181 -0
- package/packages/cli/tests/commands/upgrade.test.ts +215 -0
- package/packages/cli/tests/helpers.ts +243 -0
- package/packages/cli/tests/npm-package.test.ts +83 -0
- package/packages/cli/tests/technical-constraints.test.ts +96 -0
- package/packages/cli/tsconfig.json +25 -0
- package/packages/cli/tsup.config.ts +11 -0
- package/packages/cli/vitest.config.ts +23 -0
- package/promptfoo.yaml +3270 -0
- package/dist/check-3NGQ4NR5.js +0 -129
- package/dist/check-3NGQ4NR5.js.map +0 -1
- package/dist/chunk-2XWIUEQK.js +0 -190
- package/dist/chunk-2XWIUEQK.js.map +0 -1
- package/dist/chunk-GZRQL3SX.js +0 -146
- package/dist/chunk-GZRQL3SX.js.map +0 -1
- package/dist/chunk-ORQHKDT2.js +0 -10
- package/dist/chunk-ORQHKDT2.js.map +0 -1
- package/dist/chunk-W66Z3C5H.js +0 -21
- package/dist/chunk-W66Z3C5H.js.map +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -34
- package/dist/cli.js.map +0 -1
- package/dist/diff-Y6QTAW4O.js +0 -166
- package/dist/diff-Y6QTAW4O.js.map +0 -1
- package/dist/index.d.ts +0 -11
- package/dist/index.js +0 -7
- package/dist/index.js.map +0 -1
- package/dist/reset-3ACTIYYE.js +0 -143
- package/dist/reset-3ACTIYYE.js.map +0 -1
- package/dist/setup-RR4M334C.js +0 -266
- package/dist/setup-RR4M334C.js.map +0 -1
- package/dist/upgrade-6AR3DHUV.js +0 -134
- package/dist/upgrade-6AR3DHUV.js.map +0 -1
- /package/{templates → framework}/SAFEWORD.md +0 -0
- /package/{templates → framework}/guides/architecture-guide.md +0 -0
- /package/{templates → framework}/guides/code-philosophy.md +0 -0
- /package/{templates → framework}/guides/context-files-guide.md +0 -0
- /package/{templates → framework}/guides/data-architecture-guide.md +0 -0
- /package/{templates → framework}/guides/design-doc-guide.md +0 -0
- /package/{templates → framework}/guides/learning-extraction.md +0 -0
- /package/{templates → framework}/guides/llm-instruction-design.md +0 -0
- /package/{templates → framework}/guides/llm-prompting.md +0 -0
- /package/{templates → framework}/guides/tdd-best-practices.md +0 -0
- /package/{templates → framework}/guides/test-definitions-guide.md +0 -0
- /package/{templates → framework}/guides/testing-methodology.md +0 -0
- /package/{templates → framework}/guides/user-story-guide.md +0 -0
- /package/{templates → framework}/guides/zombie-process-cleanup.md +0 -0
- /package/{templates → framework}/prompts/arch-review.md +0 -0
- /package/{templates → framework}/prompts/quality-review.md +0 -0
- /package/{templates/skills/safeword-quality-reviewer → framework/skills/quality-reviewer}/SKILL.md +0 -0
- /package/{templates/doc-templates → framework/templates}/architecture-template.md +0 -0
- /package/{templates/doc-templates → framework/templates}/design-doc-template.md +0 -0
- /package/{templates/doc-templates → framework/templates}/test-definitions-feature.md +0 -0
- /package/{templates/doc-templates → framework/templates}/ticket-template.md +0 -0
- /package/{templates/doc-templates → framework/templates}/user-stories-template.md +0 -0
- /package/{templates → packages/cli/templates}/commands/arch-review.md +0 -0
- /package/{templates → packages/cli/templates}/commands/lint.md +0 -0
- /package/{templates → packages/cli/templates}/commands/quality-review.md +0 -0
- /package/{templates → packages/cli/templates}/hooks/inject-timestamp.sh +0 -0
- /package/{templates → packages/cli/templates}/lib/common.sh +0 -0
- /package/{templates → packages/cli/templates}/lib/jq-fallback.sh +0 -0
- /package/{templates → packages/cli/templates}/markdownlint.jsonc +0 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# Safeword CLI Architecture
|
|
2
|
+
|
|
3
|
+
**Version:** 1.0
|
|
4
|
+
**Last Updated:** 2025-11-27
|
|
5
|
+
**Status:** Design
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Overview](#overview)
|
|
12
|
+
- [Tech Stack](#tech-stack)
|
|
13
|
+
- [Project Structure](#project-structure)
|
|
14
|
+
- [Key Decisions](#key-decisions)
|
|
15
|
+
- [Best Practices](#best-practices)
|
|
16
|
+
- [Migration Strategy](#migration-strategy)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
The Safeword CLI is a TypeScript Node.js application that replaces bash scripts for project setup, verification, and maintenance. It provides commands for setting up development environments with linting, Claude Code hooks, and configuration management.
|
|
23
|
+
|
|
24
|
+
**Design Philosophy:**
|
|
25
|
+
|
|
26
|
+
- **npx-first**: Always run via npx for latest version
|
|
27
|
+
- **Stateless**: No global config, all state in project files
|
|
28
|
+
- **Idempotent**: Safe to re-run commands (with appropriate guards)
|
|
29
|
+
- **Non-interactive capable**: Full functionality via flags for CI/automation
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Tech Stack
|
|
34
|
+
|
|
35
|
+
| Category | Choice | Rationale |
|
|
36
|
+
| ----------- | ---------------------- | -------------------------------------------------- |
|
|
37
|
+
| Language | TypeScript 5.x | Type safety, IDE support, matches target projects |
|
|
38
|
+
| Runtime | Node.js 18+ | LTS, native fetch, stable fs/promises |
|
|
39
|
+
| Arg parsing | Commander.js | Industry standard, TypeScript support, subcommands |
|
|
40
|
+
| Build | tsup | Fast, zero-config, ESM + CJS output |
|
|
41
|
+
| Testing | Vitest | Fast, TypeScript-native, compatible with Node |
|
|
42
|
+
| Linting | ESLint 9 (flat config) | What we install for users, dogfood ourselves |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Project Structure
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
packages/cli/
|
|
50
|
+
├── src/
|
|
51
|
+
│ ├── cli.ts # Entry point, arg parsing
|
|
52
|
+
│ ├── commands/
|
|
53
|
+
│ │ ├── setup.ts # Setup command
|
|
54
|
+
│ │ ├── check.ts # Health check command
|
|
55
|
+
│ │ ├── upgrade.ts # Upgrade command
|
|
56
|
+
│ │ ├── diff.ts # Diff preview command
|
|
57
|
+
│ │ └── reset.ts # Reset/uninstall command
|
|
58
|
+
│ ├── utils/
|
|
59
|
+
│ │ ├── file-manager.ts # File operations
|
|
60
|
+
│ │ ├── hook-manager.ts # Claude + git hooks
|
|
61
|
+
│ │ ├── prompter.ts # Interactive prompts
|
|
62
|
+
│ │ ├── project-detector.ts # Project type detection
|
|
63
|
+
│ │ ├── linting-setup.ts # ESLint/Prettier config
|
|
64
|
+
│ │ └── output.ts # Colored output, spinners
|
|
65
|
+
│ └── templates/ # Bundled templates (copied to .safeword/)
|
|
66
|
+
├── tests/
|
|
67
|
+
│ ├── commands/ # Integration tests per command
|
|
68
|
+
│ └── utils/ # Unit tests for utilities
|
|
69
|
+
├── ARCHITECTURE.md # This file
|
|
70
|
+
├── package.json
|
|
71
|
+
└── tsconfig.json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Key Decisions
|
|
77
|
+
|
|
78
|
+
### Decision 1: TypeScript over JavaScript
|
|
79
|
+
|
|
80
|
+
**Status:** Active
|
|
81
|
+
**Date:** 2025-11-27
|
|
82
|
+
|
|
83
|
+
| Field | Value |
|
|
84
|
+
| -------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
85
|
+
| What | Write CLI in TypeScript, compile to JavaScript for distribution |
|
|
86
|
+
| Why | Type safety catches bugs early, better IDE support, self-documents interfaces. Target users are TypeScript developers. |
|
|
87
|
+
| Trade-off | Build step required, but tsup makes this trivial |
|
|
88
|
+
| Alternatives | Pure JS (rejected: lose type safety), Bun (rejected: not ubiquitous enough) |
|
|
89
|
+
| Implementation | `packages/cli/src/**/*.ts` |
|
|
90
|
+
|
|
91
|
+
### Decision 2: ESM-only
|
|
92
|
+
|
|
93
|
+
**Status:** Active
|
|
94
|
+
**Date:** 2025-11-27
|
|
95
|
+
|
|
96
|
+
| Field | Value |
|
|
97
|
+
| -------------- | -------------------------------------------------------------------------- |
|
|
98
|
+
| What | Package uses `"type": "module"`, tsup outputs ESM only |
|
|
99
|
+
| Why | Node 18+ has full ESM support, CJS adds bloat for shrinking use case |
|
|
100
|
+
| Trade-off | Won't work with very old tooling that requires CJS |
|
|
101
|
+
| Alternatives | CJS-only (rejected: legacy), dual ESM+CJS (rejected: unnecessary bloat) |
|
|
102
|
+
| Implementation | `tsup.config.ts`: `format: ['esm']` |
|
|
103
|
+
|
|
104
|
+
### Decision 3: Bundle templates in package
|
|
105
|
+
|
|
106
|
+
**Status:** Active
|
|
107
|
+
**Date:** 2025-11-27
|
|
108
|
+
|
|
109
|
+
| Field | Value |
|
|
110
|
+
| -------------- | ----------------------------------------------------------------------- |
|
|
111
|
+
| What | Ship `.safeword/` templates as part of npm package, copy on setup |
|
|
112
|
+
| Why | Works offline, version-locked to CLI, no network dependency for setup |
|
|
113
|
+
| Trade-off | Larger package (~500KB), templates update requires CLI update |
|
|
114
|
+
| Alternatives | Fetch from GitHub (rejected: network dependency, version mismatch risk) |
|
|
115
|
+
| Implementation | `src/templates/` copied to dist, read at runtime |
|
|
116
|
+
|
|
117
|
+
### Decision 4: Commander.js for CLI framework
|
|
118
|
+
|
|
119
|
+
**Status:** Active
|
|
120
|
+
**Date:** 2025-11-27
|
|
121
|
+
|
|
122
|
+
| Field | Value |
|
|
123
|
+
| -------------- | --------------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| What | Use Commander.js for argument parsing, help generation, subcommands |
|
|
125
|
+
| Why | Most popular (40k+ stars), excellent TypeScript support, minimal bundle impact |
|
|
126
|
+
| Trade-off | External dependency, but arg parsing complexity justifies it |
|
|
127
|
+
| Alternatives | yargs (rejected: heavier), meow (rejected: less TypeScript support), manual (rejected: reinventing wheel) |
|
|
128
|
+
| Implementation | `src/cli.ts` |
|
|
129
|
+
|
|
130
|
+
### Decision 5: Vitest for testing
|
|
131
|
+
|
|
132
|
+
**Status:** Active
|
|
133
|
+
**Date:** 2025-11-27
|
|
134
|
+
|
|
135
|
+
| Field | Value |
|
|
136
|
+
| -------------- | ------------------------------------------------------------------------------- |
|
|
137
|
+
| What | Use Vitest for unit and integration tests |
|
|
138
|
+
| Why | Fast, TypeScript-native, watch mode, compatible with Node fs operations |
|
|
139
|
+
| Trade-off | Another test runner to know (but API is Jest-compatible) |
|
|
140
|
+
| Alternatives | Jest (rejected: slower, config heavy), Node test runner (rejected: less mature) |
|
|
141
|
+
| Implementation | `tests/**/*.test.ts` |
|
|
142
|
+
|
|
143
|
+
### Decision 6: No runtime dependencies beyond Commander
|
|
144
|
+
|
|
145
|
+
**Status:** Active
|
|
146
|
+
**Date:** 2025-11-27
|
|
147
|
+
|
|
148
|
+
| Field | Value |
|
|
149
|
+
| -------------- | --------------------------------------------------------------------------------- |
|
|
150
|
+
| What | Minimize runtime deps - only Commander.js, use Node built-ins for everything else |
|
|
151
|
+
| Why | Faster install, fewer security audit concerns, smaller attack surface |
|
|
152
|
+
| Trade-off | May need to implement utilities that libraries provide |
|
|
153
|
+
| Alternatives | Use chalk, ora, inquirer, etc. (rejected: bloat for simple CLI) |
|
|
154
|
+
| Implementation | Use Node readline for prompts, ANSI codes directly for colors |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Best Practices
|
|
159
|
+
|
|
160
|
+
### Error Handling Pattern
|
|
161
|
+
|
|
162
|
+
**What:** Use custom error classes with exit codes
|
|
163
|
+
**Why:** Consistent error handling, testable exit behavior
|
|
164
|
+
**Example:** See `src/utils/errors.ts`
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Define error types
|
|
168
|
+
class SafewordError extends Error {
|
|
169
|
+
constructor(
|
|
170
|
+
message: string,
|
|
171
|
+
public exitCode: number = 1,
|
|
172
|
+
) {
|
|
173
|
+
super(message);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
class SetupError extends SafewordError {}
|
|
178
|
+
class ConfigError extends SafewordError {}
|
|
179
|
+
|
|
180
|
+
// Use in commands
|
|
181
|
+
try {
|
|
182
|
+
await setup(options);
|
|
183
|
+
} catch (e) {
|
|
184
|
+
if (e instanceof SafewordError) {
|
|
185
|
+
console.error(e.message);
|
|
186
|
+
process.exit(e.exitCode);
|
|
187
|
+
}
|
|
188
|
+
throw e; // Re-throw unexpected errors
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### File Operations Pattern
|
|
193
|
+
|
|
194
|
+
**What:** Always use async fs operations, handle errors explicitly
|
|
195
|
+
**Why:** Non-blocking, better error messages
|
|
196
|
+
**Example:** See `src/utils/file-manager.ts`
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
200
|
+
|
|
201
|
+
async function safeWrite(path: string, content: string): Promise<void> {
|
|
202
|
+
await mkdir(dirname(path), { recursive: true });
|
|
203
|
+
await writeFile(path, content, 'utf-8');
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Output Pattern
|
|
208
|
+
|
|
209
|
+
**What:** Use symbols for status, no emoji by default
|
|
210
|
+
**Why:** Works in all terminals, professional appearance
|
|
211
|
+
**Example:** See `src/utils/output.ts`
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const symbols = {
|
|
215
|
+
success: '✓',
|
|
216
|
+
warning: '⚠',
|
|
217
|
+
error: '✗',
|
|
218
|
+
info: '→',
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
function success(msg: string) {
|
|
222
|
+
console.log(`${symbols.success} ${msg}`);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Migration Strategy
|
|
229
|
+
|
|
230
|
+
### From Bash Scripts to CLI
|
|
231
|
+
|
|
232
|
+
**Trigger:** User has existing bash-based safeword setup
|
|
233
|
+
**Steps:**
|
|
234
|
+
|
|
235
|
+
1. User runs `npx safeword upgrade`
|
|
236
|
+
2. CLI detects `.safeword/` exists
|
|
237
|
+
3. CLI overwrites all files with bundled templates
|
|
238
|
+
4. CLI updates hooks in `.claude/settings.json`
|
|
239
|
+
5. CLI preserves user's AGENTS.md content (only updates link)
|
|
240
|
+
|
|
241
|
+
**Rollback:** Run `npx safeword reset`, then re-run old bash scripts
|
|
242
|
+
|
|
243
|
+
### Future: v1 to v2
|
|
244
|
+
|
|
245
|
+
**Trigger:** Breaking changes to template structure or hook format
|
|
246
|
+
**Steps:**
|
|
247
|
+
|
|
248
|
+
1. v2 CLI detects v1 version in `.safeword/version`
|
|
249
|
+
2. Runs migration logic before normal upgrade
|
|
250
|
+
3. Updates version file to v2
|
|
251
|
+
|
|
252
|
+
**Rollback:** Document breaking changes, provide migration guide
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Appendix
|
|
257
|
+
|
|
258
|
+
### References
|
|
259
|
+
|
|
260
|
+
- [Design Doc](../../.safeword/planning/design/005-cli-implementation.md)
|
|
261
|
+
- [User Stories](../../.safeword/planning/user-stories/005-cli-implementation.md)
|
|
262
|
+
- [Test Definitions](../../.safeword/planning/test-definitions/005-cli-implementation.md)
|
|
263
|
+
- [CLI UX Vision](../../.safeword/planning/011-cli-ux-vision.md)
|
|
264
|
+
|
|
265
|
+
### Package.json (key fields)
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"name": "safeword",
|
|
270
|
+
"type": "module",
|
|
271
|
+
"bin": {
|
|
272
|
+
"safeword": "./dist/cli.js"
|
|
273
|
+
},
|
|
274
|
+
"engines": {
|
|
275
|
+
"node": ">=18"
|
|
276
|
+
},
|
|
277
|
+
"files": ["dist", "templates"]
|
|
278
|
+
}
|
|
279
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "safeword",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "CLI for setting up and managing safeword development environments",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"safeword": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"templates"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"test:coverage": "vitest run --coverage",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"lint": "eslint src tests",
|
|
30
|
+
"clean": "rm -rf dist",
|
|
31
|
+
"prepublishOnly": "npm audit --audit-level=high && npm run build && npm test"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"commander": "^12.1.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^20.10.0",
|
|
38
|
+
"tsup": "^8.0.0",
|
|
39
|
+
"typescript": "^5.3.0",
|
|
40
|
+
"vitest": "^2.0.0"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"cli",
|
|
44
|
+
"safeword",
|
|
45
|
+
"developer-experience",
|
|
46
|
+
"linting",
|
|
47
|
+
"claude-code"
|
|
48
|
+
],
|
|
49
|
+
"author": "",
|
|
50
|
+
"license": "MIT"
|
|
51
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { VERSION } from './version.js';
|
|
5
|
+
|
|
6
|
+
const program = new Command();
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name('safeword')
|
|
10
|
+
.description('CLI for setting up and managing safeword development environments')
|
|
11
|
+
.version(VERSION);
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command('setup')
|
|
15
|
+
.description('Set up safeword in the current project')
|
|
16
|
+
.option('-y, --yes', 'Accept all defaults (non-interactive mode)')
|
|
17
|
+
.action(async options => {
|
|
18
|
+
const { setup } = await import('./commands/setup.js');
|
|
19
|
+
await setup(options);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command('check')
|
|
24
|
+
.description('Check project health and versions')
|
|
25
|
+
.option('--offline', 'Skip remote version check')
|
|
26
|
+
.action(async options => {
|
|
27
|
+
const { check } = await import('./commands/check.js');
|
|
28
|
+
await check(options);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
program
|
|
32
|
+
.command('upgrade')
|
|
33
|
+
.description('Upgrade safeword configuration to latest version')
|
|
34
|
+
.action(async () => {
|
|
35
|
+
const { upgrade } = await import('./commands/upgrade.js');
|
|
36
|
+
await upgrade();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command('diff')
|
|
41
|
+
.description('Preview changes that would be made by upgrade')
|
|
42
|
+
.option('-v, --verbose', 'Show full diff output')
|
|
43
|
+
.action(async options => {
|
|
44
|
+
const { diff } = await import('./commands/diff.js');
|
|
45
|
+
await diff(options);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
program
|
|
49
|
+
.command('reset')
|
|
50
|
+
.description('Remove safeword configuration from project')
|
|
51
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
52
|
+
.action(async options => {
|
|
53
|
+
const { reset } = await import('./commands/reset.js');
|
|
54
|
+
await reset(options);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Show help if no arguments provided
|
|
58
|
+
if (process.argv.length === 2) {
|
|
59
|
+
program.help();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Parse arguments
|
|
63
|
+
program.parse();
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check command - Verify project health and configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { VERSION } from '../version.js';
|
|
7
|
+
import { exists, readFile, readFileSafe } from '../utils/fs.js';
|
|
8
|
+
import { info, success, warn, header, keyValue } from '../utils/output.js';
|
|
9
|
+
import { isNewerVersion } from '../utils/version.js';
|
|
10
|
+
|
|
11
|
+
export interface CheckOptions {
|
|
12
|
+
offline?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface HealthStatus {
|
|
16
|
+
configured: boolean;
|
|
17
|
+
projectVersion: string | null;
|
|
18
|
+
cliVersion: string;
|
|
19
|
+
updateAvailable: boolean;
|
|
20
|
+
latestVersion: string | null;
|
|
21
|
+
issues: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check for latest version from npm (with timeout)
|
|
26
|
+
*/
|
|
27
|
+
async function checkLatestVersion(timeout = 3000): Promise<string | null> {
|
|
28
|
+
try {
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
31
|
+
|
|
32
|
+
const response = await fetch('https://registry.npmjs.org/safeword/latest', {
|
|
33
|
+
signal: controller.signal,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
clearTimeout(timeoutId);
|
|
37
|
+
|
|
38
|
+
if (!response.ok) return null;
|
|
39
|
+
|
|
40
|
+
const data = (await response.json()) as { version?: string };
|
|
41
|
+
return data.version ?? null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check project configuration health
|
|
49
|
+
*/
|
|
50
|
+
function checkHealth(cwd: string): HealthStatus {
|
|
51
|
+
const safewordDir = join(cwd, '.safeword');
|
|
52
|
+
const issues: string[] = [];
|
|
53
|
+
|
|
54
|
+
// Check if configured
|
|
55
|
+
if (!exists(safewordDir)) {
|
|
56
|
+
return {
|
|
57
|
+
configured: false,
|
|
58
|
+
projectVersion: null,
|
|
59
|
+
cliVersion: VERSION,
|
|
60
|
+
updateAvailable: false,
|
|
61
|
+
latestVersion: null,
|
|
62
|
+
issues: [],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Read project version
|
|
67
|
+
const versionPath = join(safewordDir, 'version');
|
|
68
|
+
const projectVersion = readFileSafe(versionPath)?.trim() ?? null;
|
|
69
|
+
|
|
70
|
+
// Check for required files
|
|
71
|
+
const requiredFiles = ['SAFEWORD.md', 'version', 'hooks/agents-md-check.sh'];
|
|
72
|
+
|
|
73
|
+
for (const file of requiredFiles) {
|
|
74
|
+
if (!exists(join(safewordDir, file))) {
|
|
75
|
+
issues.push(`Missing: .safeword/${file}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check AGENTS.md link
|
|
80
|
+
const agentsMdPath = join(cwd, 'AGENTS.md');
|
|
81
|
+
if (exists(agentsMdPath)) {
|
|
82
|
+
const content = readFile(agentsMdPath);
|
|
83
|
+
if (!content.includes('@./.safeword/SAFEWORD.md')) {
|
|
84
|
+
issues.push('AGENTS.md missing safeword link');
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
issues.push('AGENTS.md file missing');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check .claude/settings.json
|
|
91
|
+
const settingsPath = join(cwd, '.claude', 'settings.json');
|
|
92
|
+
if (!exists(settingsPath)) {
|
|
93
|
+
issues.push('Missing: .claude/settings.json');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
configured: true,
|
|
98
|
+
projectVersion,
|
|
99
|
+
cliVersion: VERSION,
|
|
100
|
+
updateAvailable: false,
|
|
101
|
+
latestVersion: null,
|
|
102
|
+
issues,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function check(options: CheckOptions): Promise<void> {
|
|
107
|
+
const cwd = process.cwd();
|
|
108
|
+
|
|
109
|
+
header('Safeword Health Check');
|
|
110
|
+
|
|
111
|
+
const health = checkHealth(cwd);
|
|
112
|
+
|
|
113
|
+
// Not configured
|
|
114
|
+
if (!health.configured) {
|
|
115
|
+
info('Not configured. Run `safeword setup` to initialize.');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Show versions
|
|
120
|
+
keyValue('Safeword CLI', `v${health.cliVersion}`);
|
|
121
|
+
keyValue('Project config', health.projectVersion ? `v${health.projectVersion}` : 'unknown');
|
|
122
|
+
|
|
123
|
+
// Check for updates (unless offline)
|
|
124
|
+
if (!options.offline) {
|
|
125
|
+
info('\nChecking for updates...');
|
|
126
|
+
const latestVersion = await checkLatestVersion();
|
|
127
|
+
|
|
128
|
+
if (latestVersion) {
|
|
129
|
+
health.latestVersion = latestVersion;
|
|
130
|
+
health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);
|
|
131
|
+
|
|
132
|
+
if (health.updateAvailable) {
|
|
133
|
+
warn(`Update available: v${latestVersion}`);
|
|
134
|
+
info('Run `npm install -g safeword` to upgrade');
|
|
135
|
+
} else {
|
|
136
|
+
success('CLI is up to date');
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
warn("Couldn't check for updates (offline?)");
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
info('\nSkipped update check (offline mode)');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Check project version vs CLI version
|
|
146
|
+
if (health.projectVersion && isNewerVersion(health.cliVersion, health.projectVersion)) {
|
|
147
|
+
warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);
|
|
148
|
+
info('Consider upgrading the CLI');
|
|
149
|
+
} else if (health.projectVersion && isNewerVersion(health.projectVersion, health.cliVersion)) {
|
|
150
|
+
info(`\nUpgrade available for project config`);
|
|
151
|
+
info(
|
|
152
|
+
`Run \`safeword upgrade\` to update from v${health.projectVersion} to v${health.cliVersion}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Show issues
|
|
157
|
+
if (health.issues.length > 0) {
|
|
158
|
+
header('Issues Found');
|
|
159
|
+
for (const issue of health.issues) {
|
|
160
|
+
warn(issue);
|
|
161
|
+
}
|
|
162
|
+
info('\nRun `safeword upgrade` to repair configuration');
|
|
163
|
+
} else {
|
|
164
|
+
success('\nConfiguration is healthy');
|
|
165
|
+
}
|
|
166
|
+
}
|