safeword 0.1.0 → 0.2.0
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-J6DFVBCE.js → check-FHNTC46G.js} +2 -2
- package/dist/check-FHNTC46G.js.map +1 -0
- package/dist/chunk-2XWIUEQK.js +190 -0
- package/dist/chunk-2XWIUEQK.js.map +1 -0
- package/dist/{chunk-UQMQ64CB.js → chunk-GZRQL3SX.js} +41 -2
- package/dist/chunk-GZRQL3SX.js.map +1 -0
- package/dist/cli.js +5 -5
- package/dist/{diff-U4IELWRL.js → diff-SPJ7BJBG.js} +31 -28
- package/dist/diff-SPJ7BJBG.js.map +1 -0
- package/dist/{reset-XETOHTCK.js → reset-3ACTIYYE.js} +44 -27
- package/dist/reset-3ACTIYYE.js.map +1 -0
- package/dist/{setup-CLDCHROZ.js → setup-ANC3NUOC.js} +76 -47
- package/dist/setup-ANC3NUOC.js.map +1 -0
- package/dist/{upgrade-DOKWRK7J.js → upgrade-3KVLLNDF.js} +37 -49
- package/dist/upgrade-3KVLLNDF.js.map +1 -0
- package/package.json +1 -1
- package/templates/SAFEWORD.md +776 -0
- package/templates/commands/arch-review.md +24 -0
- package/templates/commands/lint.md +11 -0
- package/templates/commands/quality-review.md +23 -0
- package/templates/doc-templates/architecture-template.md +136 -0
- package/templates/doc-templates/design-doc-template.md +134 -0
- package/templates/doc-templates/test-definitions-feature.md +131 -0
- package/templates/doc-templates/user-stories-template.md +92 -0
- package/templates/guides/architecture-guide.md +423 -0
- package/templates/guides/code-philosophy.md +195 -0
- package/templates/guides/context-files-guide.md +457 -0
- package/templates/guides/data-architecture-guide.md +200 -0
- package/templates/guides/design-doc-guide.md +171 -0
- package/templates/guides/learning-extraction.md +552 -0
- package/templates/guides/llm-instruction-design.md +248 -0
- package/templates/guides/llm-prompting.md +102 -0
- package/templates/guides/tdd-best-practices.md +615 -0
- package/templates/guides/test-definitions-guide.md +334 -0
- package/templates/guides/testing-methodology.md +618 -0
- package/templates/guides/user-story-guide.md +256 -0
- package/templates/guides/zombie-process-cleanup.md +219 -0
- package/templates/hooks/agents-md-check.sh +27 -0
- package/templates/hooks/inject-timestamp.sh +2 -3
- package/templates/hooks/post-tool.sh +4 -0
- package/templates/hooks/pre-commit.sh +10 -0
- package/templates/lib/common.sh +26 -0
- package/templates/lib/jq-fallback.sh +20 -0
- package/templates/markdownlint.jsonc +25 -0
- package/templates/prompts/arch-review.md +43 -0
- package/templates/prompts/quality-review.md +10 -0
- package/templates/skills/safeword-quality-reviewer/SKILL.md +207 -0
- package/dist/check-J6DFVBCE.js.map +0 -1
- package/dist/chunk-24OB57NJ.js +0 -78
- package/dist/chunk-24OB57NJ.js.map +0 -1
- package/dist/chunk-DB4CMUFD.js +0 -157
- package/dist/chunk-DB4CMUFD.js.map +0 -1
- package/dist/chunk-UQMQ64CB.js.map +0 -1
- package/dist/diff-U4IELWRL.js.map +0 -1
- package/dist/reset-XETOHTCK.js.map +0 -1
- package/dist/setup-CLDCHROZ.js.map +0 -1
- package/dist/upgrade-DOKWRK7J.js.map +0 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: quality-reviewer
|
|
3
|
+
description: Deep code quality review with web research. Use when user explicitly requests verification against latest docs ('double check against latest', 'verify versions', 'check security'), needs deeper analysis beyond automatic hook, or is working on projects without SAFEWORD.md/CLAUDE.md. Fetches current documentation (WebFetch), checks latest versions (WebSearch), and provides deep analysis (performance, security, alternatives).
|
|
4
|
+
allowed-tools: '*'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Quality Reviewer
|
|
8
|
+
|
|
9
|
+
Deep quality review with web research to verify code against the latest ecosystem state.
|
|
10
|
+
|
|
11
|
+
**Primary differentiator**: Web research (WebSearch, WebFetch) to verify against current versions, documentation, and best practices.
|
|
12
|
+
|
|
13
|
+
**Triggers**:
|
|
14
|
+
|
|
15
|
+
- **Explicit web research request**: "double check against latest docs", "verify we're using latest version", "check for security issues"
|
|
16
|
+
- **Deep dive needed**: User wants analysis beyond automatic hook (performance, architecture alternatives, trade-offs)
|
|
17
|
+
- **No SAFEWORD.md/CLAUDE.md**: Projects without context files (automatic hook won't run, manual review needed)
|
|
18
|
+
- **Pre-change review**: User wants review before making changes (automatic hook only triggers after changes)
|
|
19
|
+
- **Model-invoked**: Claude determines web research would be valuable
|
|
20
|
+
|
|
21
|
+
**Relationship to automatic quality hook**:
|
|
22
|
+
|
|
23
|
+
- **Automatic hook**: Fast quality check using existing knowledge + project context (guaranteed, runs on every change)
|
|
24
|
+
- **This skill**: Deep review with web research when verification against current ecosystem is needed (on-demand, 2-3 min)
|
|
25
|
+
|
|
26
|
+
## Review Protocol
|
|
27
|
+
|
|
28
|
+
### 1. Identify What Changed
|
|
29
|
+
|
|
30
|
+
Understand context:
|
|
31
|
+
|
|
32
|
+
- What files were just modified?
|
|
33
|
+
- What problem is being solved?
|
|
34
|
+
- What was the implementation approach?
|
|
35
|
+
|
|
36
|
+
### 2. Read Project Standards
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
ls CLAUDE.md SAFEWORD.md ARCHITECTURE.md .claude/
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Read relevant standards:
|
|
43
|
+
|
|
44
|
+
- `CLAUDE.md` or `SAFEWORD.md` - Project-specific guidelines
|
|
45
|
+
- `ARCHITECTURE.md` - Architectural principles
|
|
46
|
+
- `@./.safeword/guides/code-philosophy.md` - Core coding principles
|
|
47
|
+
|
|
48
|
+
### 3. Evaluate Correctness
|
|
49
|
+
|
|
50
|
+
**Will it work?**
|
|
51
|
+
|
|
52
|
+
- Does the logic make sense?
|
|
53
|
+
- Are there obvious bugs?
|
|
54
|
+
|
|
55
|
+
**Edge cases:**
|
|
56
|
+
|
|
57
|
+
- Empty inputs, null/undefined, boundary conditions (0, -1, max)?
|
|
58
|
+
- Concurrent access, network failures?
|
|
59
|
+
|
|
60
|
+
**Error handling:**
|
|
61
|
+
|
|
62
|
+
- Are errors caught appropriately?
|
|
63
|
+
- Helpful error messages?
|
|
64
|
+
- Cleanup handled (resources, connections)?
|
|
65
|
+
|
|
66
|
+
**Logic errors:**
|
|
67
|
+
|
|
68
|
+
- Off-by-one errors, race conditions, wrong assumptions?
|
|
69
|
+
|
|
70
|
+
### 4. Evaluate Anti-Bloat
|
|
71
|
+
|
|
72
|
+
- Are all dependencies necessary? Could we use stdlib/built-ins?
|
|
73
|
+
- Are abstractions solving real problems or imaginary ones?
|
|
74
|
+
- YAGNI: Is this feature actually needed now?
|
|
75
|
+
|
|
76
|
+
### 5. Evaluate Elegance
|
|
77
|
+
|
|
78
|
+
- Is the code easy to understand?
|
|
79
|
+
- Are names clear and descriptive?
|
|
80
|
+
- Is the intent obvious?
|
|
81
|
+
- Will this be easy to change later?
|
|
82
|
+
|
|
83
|
+
### 6. Check Standards Compliance
|
|
84
|
+
|
|
85
|
+
**Project standards** (from CLAUDE.md/SAFEWORD.md/ARCHITECTURE.md):
|
|
86
|
+
|
|
87
|
+
- Does it follow established patterns?
|
|
88
|
+
- Does it violate any documented principles?
|
|
89
|
+
|
|
90
|
+
**Library best practices:**
|
|
91
|
+
|
|
92
|
+
- Are we using libraries correctly?
|
|
93
|
+
- Are we following official documentation?
|
|
94
|
+
|
|
95
|
+
### 7. Verify Latest Versions ⭐ **PRIMARY VALUE**
|
|
96
|
+
|
|
97
|
+
**CRITICAL**: This is your main differentiator from automatic hook. ALWAYS check versions.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
WebSearch: "[library name] latest stable version 2025"
|
|
101
|
+
WebSearch: "[library name] security vulnerabilities"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Flag if outdated:**
|
|
105
|
+
|
|
106
|
+
- Major versions behind → WARN (e.g., React 17 when 19 is stable)
|
|
107
|
+
- Minor versions behind → NOTE (e.g., React 19.0.0 when 19.1.0 is stable)
|
|
108
|
+
- Security vulnerabilities → CRITICAL (must upgrade)
|
|
109
|
+
- Using latest → Confirm
|
|
110
|
+
|
|
111
|
+
**Common libraries**: React, TypeScript, Vite, Next.js, Node.js, Vitest, Playwright, Jest, esbuild
|
|
112
|
+
|
|
113
|
+
**Check even if dependencies didn't change** - User might be using outdated patterns.
|
|
114
|
+
|
|
115
|
+
### 8. Verify Latest Documentation ⭐ **PRIMARY VALUE**
|
|
116
|
+
|
|
117
|
+
**CRITICAL**: This is your main differentiator from automatic hook. ALWAYS verify against current docs.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
WebFetch: https://react.dev (for React)
|
|
121
|
+
WebFetch: https://vitejs.dev (for Vite)
|
|
122
|
+
WebFetch: https://www.electronjs.org/docs (for Electron)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Look for:**
|
|
126
|
+
|
|
127
|
+
- Are we using deprecated APIs?
|
|
128
|
+
- Are there newer, better patterns?
|
|
129
|
+
- Did the library's recommendations change since training data?
|
|
130
|
+
|
|
131
|
+
**Cache results**: If you checked docs recently in this session, don't re-fetch.
|
|
132
|
+
|
|
133
|
+
## Output Format
|
|
134
|
+
|
|
135
|
+
**Simple question** ("is it correct?"):
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
**Correctness:** ✓ Logic is sound, edge cases handled, no obvious errors.
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Full review** ("double check and critique"):
|
|
142
|
+
|
|
143
|
+
```markdown
|
|
144
|
+
## Quality Review
|
|
145
|
+
|
|
146
|
+
**Correctness:** [✓/⚠️/❌] [Brief assessment]
|
|
147
|
+
**Anti-Bloat:** [✓/⚠️/❌] [Brief assessment]
|
|
148
|
+
**Elegance:** [✓/⚠️/❌] [Brief assessment]
|
|
149
|
+
**Standards:** [✓/⚠️/❌] [Brief assessment]
|
|
150
|
+
**Versions:** [✓/⚠️/❌] [Latest version check with WebSearch]
|
|
151
|
+
**Documentation:** [✓/⚠️/❌] [Current docs check with WebFetch]
|
|
152
|
+
|
|
153
|
+
**Verdict:** [APPROVE / REQUEST CHANGES / NEEDS DISCUSSION]
|
|
154
|
+
|
|
155
|
+
**Critical issues:** [List or "None"]
|
|
156
|
+
**Suggested improvements:** [List or "None"]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Use structured format for "double check"/"critique". Use brief format for specific questions.
|
|
160
|
+
|
|
161
|
+
## Example: Full Review
|
|
162
|
+
|
|
163
|
+
```markdown
|
|
164
|
+
## Quality Review
|
|
165
|
+
|
|
166
|
+
**Correctness:** ✓ Logic sound, edge cases covered, error handling adequate
|
|
167
|
+
**Anti-Bloat:** ✓ Minimal dependencies, appropriate abstractions
|
|
168
|
+
**Elegance:** ✓ Clear code, good naming, well-structured
|
|
169
|
+
**Standards:** ✓ Follows CLAUDE.md patterns
|
|
170
|
+
**Versions:** ✓ React 19.0.0 (latest stable), TypeScript 5.7.2 (latest)
|
|
171
|
+
**Documentation:** ✓ Using current React patterns per https://react.dev
|
|
172
|
+
|
|
173
|
+
**Verdict:** APPROVE - Production ready
|
|
174
|
+
|
|
175
|
+
**Critical issues:** None
|
|
176
|
+
**Suggested improvements:** None
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Critical Reminders
|
|
180
|
+
|
|
181
|
+
1. **Primary value: Web research** - Use WebSearch/WebFetch to verify against current ecosystem (versions, docs, security)
|
|
182
|
+
2. **Complement automatic hook** - Hook does fast check with existing knowledge, you do deep dive with web research
|
|
183
|
+
3. **Explicit triggers matter** - "double check against latest docs", "verify versions", "check security" = invoke web research
|
|
184
|
+
4. **Projects without SAFEWORD.md** - Automatic hook won't run, you're the only quality check
|
|
185
|
+
5. **Always check latest docs** - Verify patterns are current, not outdated (WebFetch)
|
|
186
|
+
6. **Always verify versions** - Flag outdated dependencies (WebSearch)
|
|
187
|
+
7. **Be thorough but concise** - Cover all areas but keep explanations brief
|
|
188
|
+
8. **Provide actionable feedback** - Specific line numbers, concrete suggestions
|
|
189
|
+
9. **Clear verdict** - Always end with APPROVE/REQUEST CHANGES/NEEDS DISCUSSION
|
|
190
|
+
10. **Separate critical vs nice-to-have** - User needs to know what's blocking vs optional
|
|
191
|
+
|
|
192
|
+
## Non-Obvious Edge Cases
|
|
193
|
+
|
|
194
|
+
**User requests review after automatic hook ran:**
|
|
195
|
+
|
|
196
|
+
- Acknowledge hook ran: "The automatic quality hook already did a fast check. I'll now do deeper analysis with web research..."
|
|
197
|
+
- Focus on what automatic hook doesn't do: fetch latest docs, verify versions, security checks, performance analysis
|
|
198
|
+
|
|
199
|
+
**WebSearch/WebFetch fails:**
|
|
200
|
+
|
|
201
|
+
- Continue review without version/docs checks
|
|
202
|
+
- Note: "Couldn't verify latest versions/docs, skipping that check"
|
|
203
|
+
|
|
204
|
+
**Project has no CLAUDE.md/SAFEWORD.md:**
|
|
205
|
+
|
|
206
|
+
- Use `@./.safeword/guides/code-philosophy.md` as fallback
|
|
207
|
+
- Note: "No project-specific standards found, using general best practices"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n */\n\nimport { join } from 'node:path';\nimport { VERSION } from '../version.js';\nimport { exists, readFile, readFileSafe } from '../utils/fs.js';\nimport { info, success, warn, error, header, keyValue } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | null;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | null;\n issues: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch('https://registry.npmjs.org/safeword/latest', {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) return null;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check project configuration health\n */\nfunction checkHealth(cwd: string): HealthStatus {\n const safewordDir = join(cwd, '.safeword');\n const issues: string[] = [];\n\n // Check if configured\n if (!exists(safewordDir)) {\n return {\n configured: false,\n projectVersion: null,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues: [],\n };\n }\n\n // Read project version\n const versionPath = join(safewordDir, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? null;\n\n // Check for required files\n const requiredFiles = ['SAFEWORD.md', 'version', 'hooks/agents-md-check.sh'];\n\n for (const file of requiredFiles) {\n if (!exists(join(safewordDir, file))) {\n issues.push(`Missing: .safeword/${file}`);\n }\n }\n\n // Check AGENTS.md link\n const agentsMdPath = join(cwd, 'AGENTS.md');\n if (exists(agentsMdPath)) {\n const content = readFile(agentsMdPath);\n if (!content.includes('@./.safeword/SAFEWORD.md')) {\n issues.push('AGENTS.md missing safeword link');\n }\n } else {\n issues.push('AGENTS.md file missing');\n }\n\n // Check .claude/settings.json\n const settingsPath = join(cwd, '.claude', 'settings.json');\n if (!exists(settingsPath)) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues,\n };\n}\n\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = checkHealth(cwd);\n\n // Not configured\n if (!health.configured) {\n info('Not configured. Run `safeword setup` to initialize.');\n return;\n }\n\n // Show versions\n keyValue('Safeword CLI', `v${health.cliVersion}`);\n keyValue('Project config', health.projectVersion ? `v${health.projectVersion}` : 'unknown');\n\n // Check for updates (unless offline)\n if (!options.offline) {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (latestVersion) {\n health.latestVersion = latestVersion;\n health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);\n\n if (health.updateAvailable) {\n warn(`Update available: v${latestVersion}`);\n info('Run `npm install -g safeword` to upgrade');\n } else {\n success('CLI is up to date');\n }\n } else {\n warn(\"Couldn't check for updates (offline?)\");\n }\n } else {\n info('\\nSkipped update check (offline mode)');\n }\n\n // Check project version vs CLI version\n if (health.projectVersion && isNewerVersion(health.cliVersion, health.projectVersion)) {\n warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);\n info('Consider upgrading the CLI');\n } else if (health.projectVersion && isNewerVersion(health.projectVersion, health.cliVersion)) {\n info(`\\nUpgrade available for project config`);\n info(\n `Run \\`safeword upgrade\\` to update from v${health.projectVersion} to v${health.cliVersion}`,\n );\n }\n\n // Show issues\n if (health.issues.length > 0) {\n header('Issues Found');\n for (const issue of health.issues) {\n warn(issue);\n }\n info('\\nRun `safeword upgrade` to repair configuration');\n } else {\n success('\\nConfiguration is healthy');\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-24OB57NJ.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ensureDir,
|
|
3
|
-
exists,
|
|
4
|
-
makeExecutable,
|
|
5
|
-
readFile,
|
|
6
|
-
writeFile
|
|
7
|
-
} from "./chunk-UQMQ64CB.js";
|
|
8
|
-
|
|
9
|
-
// src/utils/git.ts
|
|
10
|
-
import { execSync } from "child_process";
|
|
11
|
-
import { join } from "path";
|
|
12
|
-
var MARKER_START = "# SAFEWORD_ARCH_CHECK_START";
|
|
13
|
-
var MARKER_END = "# SAFEWORD_ARCH_CHECK_END";
|
|
14
|
-
function isGitRepo(cwd) {
|
|
15
|
-
return exists(join(cwd, ".git"));
|
|
16
|
-
}
|
|
17
|
-
function getHookContent() {
|
|
18
|
-
return `
|
|
19
|
-
${MARKER_START}
|
|
20
|
-
# Safeword architecture check
|
|
21
|
-
# This section is managed by safeword - do not edit manually
|
|
22
|
-
if [ -f ".safeword/hooks/pre-commit.sh" ]; then
|
|
23
|
-
bash .safeword/hooks/pre-commit.sh
|
|
24
|
-
fi
|
|
25
|
-
${MARKER_END}
|
|
26
|
-
`;
|
|
27
|
-
}
|
|
28
|
-
function installGitHook(cwd) {
|
|
29
|
-
const hooksDir = join(cwd, ".git", "hooks");
|
|
30
|
-
const hookPath = join(hooksDir, "pre-commit");
|
|
31
|
-
ensureDir(hooksDir);
|
|
32
|
-
let content = "";
|
|
33
|
-
if (exists(hookPath)) {
|
|
34
|
-
content = readFile(hookPath);
|
|
35
|
-
if (content.includes(MARKER_START)) {
|
|
36
|
-
content = removeMarkerSection(content);
|
|
37
|
-
}
|
|
38
|
-
} else {
|
|
39
|
-
content = "#!/bin/bash\n";
|
|
40
|
-
}
|
|
41
|
-
content = content.trimEnd() + "\n" + getHookContent();
|
|
42
|
-
writeFile(hookPath, content);
|
|
43
|
-
makeExecutable(hookPath);
|
|
44
|
-
}
|
|
45
|
-
function removeGitHook(cwd) {
|
|
46
|
-
const hookPath = join(cwd, ".git", "hooks", "pre-commit");
|
|
47
|
-
if (!exists(hookPath)) return;
|
|
48
|
-
let content = readFile(hookPath);
|
|
49
|
-
if (!content.includes(MARKER_START)) return;
|
|
50
|
-
content = removeMarkerSection(content);
|
|
51
|
-
writeFile(hookPath, content);
|
|
52
|
-
}
|
|
53
|
-
function removeMarkerSection(content) {
|
|
54
|
-
const lines = content.split("\n");
|
|
55
|
-
const result = [];
|
|
56
|
-
let inMarkerSection = false;
|
|
57
|
-
for (const line of lines) {
|
|
58
|
-
if (line.includes(MARKER_START)) {
|
|
59
|
-
inMarkerSection = true;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (line.includes(MARKER_END)) {
|
|
63
|
-
inMarkerSection = false;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (!inMarkerSection) {
|
|
67
|
-
result.push(line);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return result.join("\n").trim() + "\n";
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export {
|
|
74
|
-
isGitRepo,
|
|
75
|
-
installGitHook,
|
|
76
|
-
removeGitHook
|
|
77
|
-
};
|
|
78
|
-
//# sourceMappingURL=chunk-24OB57NJ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/git.ts"],"sourcesContent":["/**\n * Git utilities for CLI operations\n */\n\nimport { execSync } from 'node:child_process';\nimport { join } from 'node:path';\nimport { exists, readFile, writeFile, ensureDir, makeExecutable } from './fs.js';\n\nconst MARKER_START = '# SAFEWORD_ARCH_CHECK_START';\nconst MARKER_END = '# SAFEWORD_ARCH_CHECK_END';\n\n/**\n * Check if directory is a git repository\n */\nexport function isGitRepo(cwd: string): boolean {\n return exists(join(cwd, '.git'));\n}\n\n/**\n * Initialize a git repository\n */\nexport function initGitRepo(cwd: string): void {\n execSync('git init', { cwd, stdio: 'pipe' });\n}\n\n/**\n * Get the pre-commit hook content to add\n */\nfunction getHookContent(): string {\n return `\n${MARKER_START}\n# Safeword architecture check\n# This section is managed by safeword - do not edit manually\nif [ -f \".safeword/hooks/pre-commit.sh\" ]; then\n bash .safeword/hooks/pre-commit.sh\nfi\n${MARKER_END}\n`;\n}\n\n/**\n * Install safeword markers into pre-commit hook\n */\nexport function installGitHook(cwd: string): void {\n const hooksDir = join(cwd, '.git', 'hooks');\n const hookPath = join(hooksDir, 'pre-commit');\n\n ensureDir(hooksDir);\n\n let content = '';\n\n if (exists(hookPath)) {\n content = readFile(hookPath);\n\n // Check if already has safeword markers\n if (content.includes(MARKER_START)) {\n // Remove existing safeword section and re-add (update)\n content = removeMarkerSection(content);\n }\n } else {\n // Create new hook file with shebang\n content = '#!/bin/bash\\n';\n }\n\n // Add safeword section\n content = content.trimEnd() + '\\n' + getHookContent();\n\n writeFile(hookPath, content);\n makeExecutable(hookPath);\n}\n\n/**\n * Remove safeword markers from pre-commit hook\n */\nexport function removeGitHook(cwd: string): void {\n const hookPath = join(cwd, '.git', 'hooks', 'pre-commit');\n\n if (!exists(hookPath)) return;\n\n let content = readFile(hookPath);\n\n if (!content.includes(MARKER_START)) return;\n\n content = removeMarkerSection(content);\n\n // If only shebang remains, we could delete the file\n // but safer to leave it\n writeFile(hookPath, content);\n}\n\n/**\n * Remove the section between markers (inclusive)\n */\nfunction removeMarkerSection(content: string): string {\n const lines = content.split('\\n');\n const result: string[] = [];\n let inMarkerSection = false;\n\n for (const line of lines) {\n if (line.includes(MARKER_START)) {\n inMarkerSection = true;\n continue;\n }\n if (line.includes(MARKER_END)) {\n inMarkerSection = false;\n continue;\n }\n if (!inMarkerSection) {\n result.push(line);\n }\n }\n\n return result.join('\\n').trim() + '\\n';\n}\n\n/**\n * Check if git hooks have safeword markers\n */\nexport function hasGitHook(cwd: string): boolean {\n const hookPath = join(cwd, '.git', 'hooks', 'pre-commit');\n if (!exists(hookPath)) return false;\n const content = readFile(hookPath);\n return content.includes(MARKER_START);\n}\n"],"mappings":";;;;;;;;;AAIA,SAAS,gBAAgB;AACzB,SAAS,YAAY;AAGrB,IAAM,eAAe;AACrB,IAAM,aAAa;AAKZ,SAAS,UAAU,KAAsB;AAC9C,SAAO,OAAO,KAAK,KAAK,MAAM,CAAC;AACjC;AAYA,SAAS,iBAAyB;AAChC,SAAO;AAAA,EACP,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMZ,UAAU;AAAA;AAEZ;AAKO,SAAS,eAAe,KAAmB;AAChD,QAAM,WAAW,KAAK,KAAK,QAAQ,OAAO;AAC1C,QAAM,WAAW,KAAK,UAAU,YAAY;AAE5C,YAAU,QAAQ;AAElB,MAAI,UAAU;AAEd,MAAI,OAAO,QAAQ,GAAG;AACpB,cAAU,SAAS,QAAQ;AAG3B,QAAI,QAAQ,SAAS,YAAY,GAAG;AAElC,gBAAU,oBAAoB,OAAO;AAAA,IACvC;AAAA,EACF,OAAO;AAEL,cAAU;AAAA,EACZ;AAGA,YAAU,QAAQ,QAAQ,IAAI,OAAO,eAAe;AAEpD,YAAU,UAAU,OAAO;AAC3B,iBAAe,QAAQ;AACzB;AAKO,SAAS,cAAc,KAAmB;AAC/C,QAAM,WAAW,KAAK,KAAK,QAAQ,SAAS,YAAY;AAExD,MAAI,CAAC,OAAO,QAAQ,EAAG;AAEvB,MAAI,UAAU,SAAS,QAAQ;AAE/B,MAAI,CAAC,QAAQ,SAAS,YAAY,EAAG;AAErC,YAAU,oBAAoB,OAAO;AAIrC,YAAU,UAAU,OAAO;AAC7B;AAKA,SAAS,oBAAoB,SAAyB;AACpD,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,SAAmB,CAAC;AAC1B,MAAI,kBAAkB;AAEtB,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,SAAS,YAAY,GAAG;AAC/B,wBAAkB;AAClB;AAAA,IACF;AACA,QAAI,KAAK,SAAS,UAAU,GAAG;AAC7B,wBAAkB;AAClB;AAAA,IACF;AACA,QAAI,CAAC,iBAAiB;AACpB,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,OAAO,KAAK,IAAI,EAAE,KAAK,IAAI;AACpC;","names":[]}
|
package/dist/chunk-DB4CMUFD.js
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
// src/templates/content.ts
|
|
2
|
-
var SAFEWORD_MD = `# SAFEWORD Configuration
|
|
3
|
-
|
|
4
|
-
This directory contains safeword configuration for AI coding agents.
|
|
5
|
-
|
|
6
|
-
## Structure
|
|
7
|
-
|
|
8
|
-
- \`SAFEWORD.md\` - This file (main configuration)
|
|
9
|
-
- \`guides/\` - Reference documentation
|
|
10
|
-
- \`templates/\` - Document templates
|
|
11
|
-
- \`hooks/\` - Claude Code hook scripts
|
|
12
|
-
- \`version\` - Installed safeword version
|
|
13
|
-
|
|
14
|
-
## Usage
|
|
15
|
-
|
|
16
|
-
The AGENTS.md file in your project root should reference this configuration:
|
|
17
|
-
|
|
18
|
-
\`\`\`markdown
|
|
19
|
-
**\u26A0\uFE0F ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**
|
|
20
|
-
\`\`\`
|
|
21
|
-
|
|
22
|
-
This ensures AI agents read the safeword configuration before any other context.
|
|
23
|
-
|
|
24
|
-
## Customization
|
|
25
|
-
|
|
26
|
-
You can customize the guides and templates, but note that running \`safeword upgrade\`
|
|
27
|
-
will overwrite changes. Keep customizations in separate files if needed.
|
|
28
|
-
|
|
29
|
-
## Commands
|
|
30
|
-
|
|
31
|
-
- \`safeword check\` - Verify configuration health
|
|
32
|
-
- \`safeword upgrade\` - Update to latest templates
|
|
33
|
-
- \`safeword diff\` - Preview upgrade changes
|
|
34
|
-
- \`safeword reset\` - Remove safeword configuration
|
|
35
|
-
`;
|
|
36
|
-
var AGENTS_MD_LINK = "**\u26A0\uFE0F ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**";
|
|
37
|
-
var PRETTIERRC = `{
|
|
38
|
-
"semi": true,
|
|
39
|
-
"singleQuote": true,
|
|
40
|
-
"tabWidth": 2,
|
|
41
|
-
"trailingComma": "es5",
|
|
42
|
-
"printWidth": 100
|
|
43
|
-
}
|
|
44
|
-
`;
|
|
45
|
-
var HOOK_AGENTS_CHECK = `#!/bin/bash
|
|
46
|
-
# Safeword AGENTS.md self-healing hook
|
|
47
|
-
# Ensures the AGENTS.md link is always present
|
|
48
|
-
|
|
49
|
-
LINK='**\u26A0\uFE0F ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**'
|
|
50
|
-
|
|
51
|
-
if [ ! -d ".safeword" ]; then
|
|
52
|
-
# Not a safeword project, skip
|
|
53
|
-
exit 0
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
if [ ! -f "AGENTS.md" ]; then
|
|
57
|
-
# AGENTS.md doesn't exist, create it
|
|
58
|
-
echo "$LINK" > AGENTS.md
|
|
59
|
-
echo "SAFEWORD: Created AGENTS.md with safeword link"
|
|
60
|
-
exit 0
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
# Check if link is present
|
|
64
|
-
if ! grep -q "@./.safeword/SAFEWORD.md" AGENTS.md; then
|
|
65
|
-
# Link missing, prepend it
|
|
66
|
-
CONTENT=$(cat AGENTS.md)
|
|
67
|
-
echo -e "$LINK\\n\\n$CONTENT" > AGENTS.md
|
|
68
|
-
echo "SAFEWORD: Restored AGENTS.md link (was removed)"
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
exit 0
|
|
72
|
-
`;
|
|
73
|
-
var HOOK_PRE_COMMIT = `#!/bin/bash
|
|
74
|
-
# Safeword pre-commit hook
|
|
75
|
-
# Runs architecture checks before commit
|
|
76
|
-
|
|
77
|
-
# Run linting if available
|
|
78
|
-
if [ -f "package.json" ] && grep -q '"lint"' package.json; then
|
|
79
|
-
npm run lint --silent || exit 1
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
exit 0
|
|
83
|
-
`;
|
|
84
|
-
var HOOK_POST_TOOL = `#!/bin/bash
|
|
85
|
-
# Safeword post-tool hook
|
|
86
|
-
# Placeholder for post-tool validations
|
|
87
|
-
exit 0
|
|
88
|
-
`;
|
|
89
|
-
var SKILL_QUALITY_REVIEWER = `# Quality Reviewer Skill
|
|
90
|
-
|
|
91
|
-
This skill provides deep code quality review with web research capabilities.
|
|
92
|
-
|
|
93
|
-
## Usage
|
|
94
|
-
|
|
95
|
-
Use when user explicitly requests verification against latest docs or needs deeper analysis.
|
|
96
|
-
|
|
97
|
-
## Capabilities
|
|
98
|
-
|
|
99
|
-
- Fetches current documentation (WebFetch)
|
|
100
|
-
- Checks latest versions (WebSearch)
|
|
101
|
-
- Provides deep analysis (performance, security, alternatives)
|
|
102
|
-
`;
|
|
103
|
-
|
|
104
|
-
// src/templates/config.ts
|
|
105
|
-
function getEslintConfig(options) {
|
|
106
|
-
const imports = ['import js from "@eslint/js";'];
|
|
107
|
-
const configs = ["js.configs.recommended"];
|
|
108
|
-
if (options.typescript) {
|
|
109
|
-
imports.push('import tseslint from "typescript-eslint";');
|
|
110
|
-
configs.push("...tseslint.configs.recommended");
|
|
111
|
-
}
|
|
112
|
-
if (options.react || options.nextjs) {
|
|
113
|
-
imports.push('import react from "eslint-plugin-react";');
|
|
114
|
-
imports.push('import reactHooks from "eslint-plugin-react-hooks";');
|
|
115
|
-
configs.push("react.configs.flat.recommended");
|
|
116
|
-
configs.push('react.configs.flat["jsx-runtime"]');
|
|
117
|
-
configs.push(
|
|
118
|
-
'{ plugins: { "react-hooks": reactHooks }, rules: reactHooks.configs.recommended.rules }'
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
return `${imports.join("\n")}
|
|
122
|
-
|
|
123
|
-
export default [
|
|
124
|
-
${configs.join(",\n ")},
|
|
125
|
-
{
|
|
126
|
-
ignores: ["node_modules/", "dist/", ".next/", "build/"],
|
|
127
|
-
},
|
|
128
|
-
];
|
|
129
|
-
`;
|
|
130
|
-
}
|
|
131
|
-
var SETTINGS_HOOKS = {
|
|
132
|
-
SessionStart: [
|
|
133
|
-
{
|
|
134
|
-
command: "bash .safeword/hooks/agents-md-check.sh",
|
|
135
|
-
description: "Safeword: Verify AGENTS.md link"
|
|
136
|
-
}
|
|
137
|
-
],
|
|
138
|
-
PostToolUse: [
|
|
139
|
-
{
|
|
140
|
-
command: "bash .safeword/hooks/post-tool.sh 2>/dev/null || true",
|
|
141
|
-
description: "Safeword: Post-tool validation"
|
|
142
|
-
}
|
|
143
|
-
]
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
export {
|
|
147
|
-
SAFEWORD_MD,
|
|
148
|
-
AGENTS_MD_LINK,
|
|
149
|
-
PRETTIERRC,
|
|
150
|
-
HOOK_AGENTS_CHECK,
|
|
151
|
-
HOOK_PRE_COMMIT,
|
|
152
|
-
HOOK_POST_TOOL,
|
|
153
|
-
SKILL_QUALITY_REVIEWER,
|
|
154
|
-
getEslintConfig,
|
|
155
|
-
SETTINGS_HOOKS
|
|
156
|
-
};
|
|
157
|
-
//# sourceMappingURL=chunk-DB4CMUFD.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/templates/content.ts","../src/templates/config.ts"],"sourcesContent":["/**\n * Content templates - markdown, scripts, and static content\n */\n\nexport const SAFEWORD_MD = `# SAFEWORD Configuration\n\nThis directory contains safeword configuration for AI coding agents.\n\n## Structure\n\n- \\`SAFEWORD.md\\` - This file (main configuration)\n- \\`guides/\\` - Reference documentation\n- \\`templates/\\` - Document templates\n- \\`hooks/\\` - Claude Code hook scripts\n- \\`version\\` - Installed safeword version\n\n## Usage\n\nThe AGENTS.md file in your project root should reference this configuration:\n\n\\`\\`\\`markdown\n**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**\n\\`\\`\\`\n\nThis ensures AI agents read the safeword configuration before any other context.\n\n## Customization\n\nYou can customize the guides and templates, but note that running \\`safeword upgrade\\`\nwill overwrite changes. Keep customizations in separate files if needed.\n\n## Commands\n\n- \\`safeword check\\` - Verify configuration health\n- \\`safeword upgrade\\` - Update to latest templates\n- \\`safeword diff\\` - Preview upgrade changes\n- \\`safeword reset\\` - Remove safeword configuration\n`;\n\nexport const AGENTS_MD_LINK = '**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**';\n\nexport const PRETTIERRC = `{\n \"semi\": true,\n \"singleQuote\": true,\n \"tabWidth\": 2,\n \"trailingComma\": \"es5\",\n \"printWidth\": 100\n}\n`;\n\nexport const HOOK_AGENTS_CHECK = `#!/bin/bash\n# Safeword AGENTS.md self-healing hook\n# Ensures the AGENTS.md link is always present\n\nLINK='**⚠️ ALWAYS READ FIRST: @./.safeword/SAFEWORD.md**'\n\nif [ ! -d \".safeword\" ]; then\n # Not a safeword project, skip\n exit 0\nfi\n\nif [ ! -f \"AGENTS.md\" ]; then\n # AGENTS.md doesn't exist, create it\n echo \"$LINK\" > AGENTS.md\n echo \"SAFEWORD: Created AGENTS.md with safeword link\"\n exit 0\nfi\n\n# Check if link is present\nif ! grep -q \"@./.safeword/SAFEWORD.md\" AGENTS.md; then\n # Link missing, prepend it\n CONTENT=$(cat AGENTS.md)\n echo -e \"$LINK\\\\n\\\\n$CONTENT\" > AGENTS.md\n echo \"SAFEWORD: Restored AGENTS.md link (was removed)\"\nfi\n\nexit 0\n`;\n\nexport const HOOK_PRE_COMMIT = `#!/bin/bash\n# Safeword pre-commit hook\n# Runs architecture checks before commit\n\n# Run linting if available\nif [ -f \"package.json\" ] && grep -q '\"lint\"' package.json; then\n npm run lint --silent || exit 1\nfi\n\nexit 0\n`;\n\nexport const HOOK_POST_TOOL = `#!/bin/bash\n# Safeword post-tool hook\n# Placeholder for post-tool validations\nexit 0\n`;\n\nexport const SKILL_QUALITY_REVIEWER = `# Quality Reviewer Skill\n\nThis skill provides deep code quality review with web research capabilities.\n\n## Usage\n\nUse when user explicitly requests verification against latest docs or needs deeper analysis.\n\n## Capabilities\n\n- Fetches current documentation (WebFetch)\n- Checks latest versions (WebSearch)\n- Provides deep analysis (performance, security, alternatives)\n`;\n","/**\n * Configuration templates - ESLint config generation and hook settings\n */\n\nexport function getEslintConfig(options: {\n typescript?: boolean;\n react?: boolean;\n nextjs?: boolean;\n}): string {\n const imports: string[] = ['import js from \"@eslint/js\";'];\n const configs: string[] = ['js.configs.recommended'];\n\n if (options.typescript) {\n imports.push('import tseslint from \"typescript-eslint\";');\n configs.push('...tseslint.configs.recommended');\n }\n\n if (options.react || options.nextjs) {\n imports.push('import react from \"eslint-plugin-react\";');\n imports.push('import reactHooks from \"eslint-plugin-react-hooks\";');\n configs.push('react.configs.flat.recommended');\n configs.push('react.configs.flat[\"jsx-runtime\"]');\n configs.push(\n '{ plugins: { \"react-hooks\": reactHooks }, rules: reactHooks.configs.recommended.rules }',\n );\n }\n\n return `${imports.join('\\n')}\n\nexport default [\n ${configs.join(',\\n ')},\n {\n ignores: [\"node_modules/\", \"dist/\", \".next/\", \"build/\"],\n },\n];\n`;\n}\n\nexport const SETTINGS_HOOKS = {\n SessionStart: [\n {\n command: 'bash .safeword/hooks/agents-md-check.sh',\n description: 'Safeword: Verify AGENTS.md link',\n },\n ],\n PostToolUse: [\n {\n command: 'bash .safeword/hooks/post-tool.sh 2>/dev/null || true',\n description: 'Safeword: Post-tool validation',\n },\n ],\n};\n"],"mappings":";AAIO,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCpB,IAAM,iBAAiB;AAEvB,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASnB,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6B1B,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYxB,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAMvB,IAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC7F/B,SAAS,gBAAgB,SAIrB;AACT,QAAM,UAAoB,CAAC,8BAA8B;AACzD,QAAM,UAAoB,CAAC,wBAAwB;AAEnD,MAAI,QAAQ,YAAY;AACtB,YAAQ,KAAK,2CAA2C;AACxD,YAAQ,KAAK,iCAAiC;AAAA,EAChD;AAEA,MAAI,QAAQ,SAAS,QAAQ,QAAQ;AACnC,YAAQ,KAAK,0CAA0C;AACvD,YAAQ,KAAK,qDAAqD;AAClE,YAAQ,KAAK,gCAAgC;AAC7C,YAAQ,KAAK,mCAAmC;AAChD,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAEA,SAAO,GAAG,QAAQ,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,IAG1B,QAAQ,KAAK,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAMzB;AAEO,IAAM,iBAAiB;AAAA,EAC5B,cAAc;AAAA,IACZ;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX;AAAA,MACE,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/fs.ts","../src/utils/output.ts"],"sourcesContent":["/**\n * File system utilities for CLI operations\n */\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n rmSync,\n readdirSync,\n statSync,\n chmodSync,\n} from 'node:fs';\nimport { join, dirname } from 'node:path';\n\n/**\n * Check if a path exists\n */\nexport function exists(path: string): boolean {\n return existsSync(path);\n}\n\n/**\n * Check if path is a directory\n */\nexport function isDirectory(path: string): boolean {\n return existsSync(path) && statSync(path).isDirectory();\n}\n\n/**\n * Create directory recursively\n */\nexport function ensureDir(path: string): void {\n if (!existsSync(path)) {\n mkdirSync(path, { recursive: true });\n }\n}\n\n/**\n * Read file as string\n */\nexport function readFile(path: string): string {\n return readFileSync(path, 'utf-8');\n}\n\n/**\n * Read file as string, return null if not exists\n */\nexport function readFileSafe(path: string): string | null {\n if (!existsSync(path)) return null;\n return readFileSync(path, 'utf-8');\n}\n\n/**\n * Write file, creating parent directories if needed\n */\nexport function writeFile(path: string, content: string): void {\n ensureDir(dirname(path));\n writeFileSync(path, content);\n}\n\n/**\n * Remove file or directory recursively\n */\nexport function remove(path: string): void {\n if (existsSync(path)) {\n rmSync(path, { recursive: true, force: true });\n }\n}\n\n/**\n * List files in directory\n */\nexport function listDir(path: string): string[] {\n if (!existsSync(path)) return [];\n return readdirSync(path);\n}\n\n/**\n * Copy directory recursively\n */\nexport function copyDir(src: string, dest: string): void {\n ensureDir(dest);\n const entries = readdirSync(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = join(src, entry.name);\n const destPath = join(dest, entry.name);\n\n if (entry.isDirectory()) {\n copyDir(srcPath, destPath);\n } else {\n writeFileSync(destPath, readFileSync(srcPath));\n }\n }\n}\n\n/**\n * Make file executable\n */\nexport function makeExecutable(path: string): void {\n chmodSync(path, 0o755);\n}\n\n/**\n * Read JSON file\n */\nexport function readJson<T = unknown>(path: string): T | null {\n const content = readFileSafe(path);\n if (!content) return null;\n try {\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Write JSON file with formatting\n */\nexport function writeJson(path: string, data: unknown): void {\n writeFile(path, JSON.stringify(data, null, 2) + '\\n');\n}\n\n/**\n * Update JSON file, merging with existing content\n */\nexport function updateJson<T extends Record<string, unknown>>(\n path: string,\n updater: (existing: T | null) => T,\n): void {\n const existing = readJson<T>(path);\n const updated = updater(existing);\n writeJson(path, updated);\n}\n","/**\n * Console output utilities for consistent CLI messaging\n */\n\n/**\n * Print info message\n */\nexport function info(message: string): void {\n console.log(message);\n}\n\n/**\n * Print success message\n */\nexport function success(message: string): void {\n console.log(`✓ ${message}`);\n}\n\n/**\n * Print warning message\n */\nexport function warn(message: string): void {\n console.warn(`⚠ ${message}`);\n}\n\n/**\n * Print error message to stderr\n */\nexport function error(message: string): void {\n console.error(`✗ ${message}`);\n}\n\n/**\n * Print a blank line\n */\nexport function blank(): void {\n console.log('');\n}\n\n/**\n * Print a section header\n */\nexport function header(title: string): void {\n console.log(`\\n${title}`);\n console.log('─'.repeat(title.length));\n}\n\n/**\n * Print a list item\n */\nexport function listItem(item: string, indent = 2): void {\n console.log(`${' '.repeat(indent)}• ${item}`);\n}\n\n/**\n * Print key-value pair\n */\nexport function keyValue(key: string, value: string): void {\n console.log(` ${key}: ${value}`);\n}\n"],"mappings":";AAIA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,MAAM,eAAe;AAKvB,SAAS,OAAO,MAAuB;AAC5C,SAAO,WAAW,IAAI;AACxB;AAYO,SAAS,UAAU,MAAoB;AAC5C,MAAI,CAAC,WAAW,IAAI,GAAG;AACrB,cAAU,MAAM,EAAE,WAAW,KAAK,CAAC;AAAA,EACrC;AACF;AAKO,SAAS,SAAS,MAAsB;AAC7C,SAAO,aAAa,MAAM,OAAO;AACnC;AAKO,SAAS,aAAa,MAA6B;AACxD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,SAAO,aAAa,MAAM,OAAO;AACnC;AAKO,SAAS,UAAU,MAAc,SAAuB;AAC7D,YAAU,QAAQ,IAAI,CAAC;AACvB,gBAAc,MAAM,OAAO;AAC7B;AAKO,SAAS,OAAO,MAAoB;AACzC,MAAI,WAAW,IAAI,GAAG;AACpB,WAAO,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC/C;AACF;AAKO,SAAS,QAAQ,MAAwB;AAC9C,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO,CAAC;AAC/B,SAAO,YAAY,IAAI;AACzB;AAwBO,SAAS,eAAe,MAAoB;AACjD,YAAU,MAAM,GAAK;AACvB;AAKO,SAAS,SAAsB,MAAwB;AAC5D,QAAM,UAAU,aAAa,IAAI;AACjC,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,UAAU,MAAc,MAAqB;AAC3D,YAAU,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,IAAI;AACtD;AAKO,SAAS,WACd,MACA,SACM;AACN,QAAM,WAAW,SAAY,IAAI;AACjC,QAAM,UAAU,QAAQ,QAAQ;AAChC,YAAU,MAAM,OAAO;AACzB;;;AChIO,SAAS,KAAK,SAAuB;AAC1C,UAAQ,IAAI,OAAO;AACrB;AAKO,SAAS,QAAQ,SAAuB;AAC7C,UAAQ,IAAI,UAAK,OAAO,EAAE;AAC5B;AAKO,SAAS,KAAK,SAAuB;AAC1C,UAAQ,KAAK,UAAK,OAAO,EAAE;AAC7B;AAKO,SAAS,MAAM,SAAuB;AAC3C,UAAQ,MAAM,UAAK,OAAO,EAAE;AAC9B;AAYO,SAAS,OAAO,OAAqB;AAC1C,UAAQ,IAAI;AAAA,EAAK,KAAK,EAAE;AACxB,UAAQ,IAAI,SAAI,OAAO,MAAM,MAAM,CAAC;AACtC;AAKO,SAAS,SAAS,MAAc,SAAS,GAAS;AACvD,UAAQ,IAAI,GAAG,IAAI,OAAO,MAAM,CAAC,UAAK,IAAI,EAAE;AAC9C;AAKO,SAAS,SAAS,KAAa,OAAqB;AACzD,UAAQ,IAAI,KAAK,GAAG,KAAK,KAAK,EAAE;AAClC;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/diff.ts"],"sourcesContent":["/**\n * Diff command - Preview changes that would be made by upgrade\n */\n\nimport { join } from 'node:path';\nimport { VERSION } from '../version.js';\nimport { exists, readFile, readFileSafe } from '../utils/fs.js';\nimport { info, success, warn, error, header, listItem } from '../utils/output.js';\nimport {\n SAFEWORD_MD,\n HOOK_AGENTS_CHECK,\n HOOK_PRE_COMMIT,\n HOOK_POST_TOOL,\n SKILL_QUALITY_REVIEWER,\n} from '../templates/index.js';\n\nexport interface DiffOptions {\n verbose?: boolean;\n}\n\ninterface FileDiff {\n path: string;\n status: 'added' | 'modified' | 'unchanged';\n currentContent?: string;\n newContent: string;\n}\n\n/**\n * Create a unified diff between two strings\n */\nfunction createUnifiedDiff(oldContent: string, newContent: string, filename: string): string {\n const oldLines = oldContent.split('\\n');\n const newLines = newContent.split('\\n');\n\n const lines: string[] = [];\n lines.push(`--- a/${filename}`);\n lines.push(`+++ b/${filename}`);\n\n // Simple diff - show all changes\n // A real implementation would use a proper diff algorithm\n let hasChanges = false;\n\n const maxLines = Math.max(oldLines.length, newLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const oldLine = oldLines[i];\n const newLine = newLines[i];\n\n if (oldLine === newLine) {\n lines.push(` ${oldLine ?? ''}`);\n } else {\n hasChanges = true;\n if (oldLine !== undefined) {\n lines.push(`-${oldLine}`);\n }\n if (newLine !== undefined) {\n lines.push(`+${newLine}`);\n }\n }\n }\n\n if (!hasChanges) {\n return '';\n }\n\n // Add context marker\n lines.splice(2, 0, `@@ -1,${oldLines.length} +1,${newLines.length} @@`);\n\n return lines.join('\\n');\n}\n\n/**\n * Get all files that would be changed by upgrade\n */\nfunction getFileDiffs(cwd: string): FileDiff[] {\n const safewordDir = join(cwd, '.safeword');\n const diffs: FileDiff[] = [];\n\n // Define files to check\n const files: Array<{ path: string; newContent: string }> = [\n { path: '.safeword/SAFEWORD.md', newContent: SAFEWORD_MD },\n { path: '.safeword/version', newContent: VERSION },\n { path: '.safeword/hooks/agents-md-check.sh', newContent: HOOK_AGENTS_CHECK },\n { path: '.safeword/hooks/pre-commit.sh', newContent: HOOK_PRE_COMMIT },\n { path: '.safeword/hooks/post-tool.sh', newContent: HOOK_POST_TOOL },\n {\n path: '.claude/skills/safeword-quality-reviewer/SKILL.md',\n newContent: SKILL_QUALITY_REVIEWER,\n },\n ];\n\n for (const file of files) {\n const fullPath = join(cwd, file.path);\n const currentContent = readFileSafe(fullPath);\n\n if (currentContent === null) {\n diffs.push({\n path: file.path,\n status: 'added',\n newContent: file.newContent,\n });\n } else if (currentContent.trim() !== file.newContent.trim()) {\n diffs.push({\n path: file.path,\n status: 'modified',\n currentContent,\n newContent: file.newContent,\n });\n } else {\n diffs.push({\n path: file.path,\n status: 'unchanged',\n currentContent,\n newContent: file.newContent,\n });\n }\n }\n\n return diffs;\n}\n\nexport async function diff(options: DiffOptions): Promise<void> {\n const cwd = process.cwd();\n const safewordDir = join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDir)) {\n error('Not configured. Run `safeword setup` first.');\n process.exit(1);\n }\n\n // Read project version\n const versionPath = join(safewordDir, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? 'unknown';\n\n header('Safeword Diff');\n info(`Changes from v${projectVersion} → v${VERSION}`);\n\n const diffs = getFileDiffs(cwd);\n\n const added = diffs.filter(d => d.status === 'added');\n const modified = diffs.filter(d => d.status === 'modified');\n const unchanged = diffs.filter(d => d.status === 'unchanged');\n\n // Summary\n info(\n `\\nSummary: ${added.length} added, ${modified.length} modified, ${unchanged.length} unchanged`,\n );\n\n // List by category\n if (added.length > 0) {\n info('\\nAdded:');\n for (const file of added) {\n listItem(file.path);\n }\n }\n\n if (modified.length > 0) {\n info('\\nModified:');\n for (const file of modified) {\n listItem(file.path);\n }\n }\n\n if (unchanged.length > 0) {\n info('\\nUnchanged:');\n for (const file of unchanged) {\n listItem(file.path);\n }\n }\n\n // Verbose output - show actual diffs\n if (options.verbose) {\n header('Detailed Changes');\n\n for (const file of modified) {\n if (file.currentContent) {\n info(`\\n${file.path}:`);\n const diffOutput = createUnifiedDiff(file.currentContent, file.newContent, file.path);\n if (diffOutput) {\n console.log(diffOutput);\n }\n }\n }\n\n for (const file of added) {\n info(`\\n${file.path}: (new file)`);\n const lines = file.newContent.split('\\n').slice(0, 10);\n for (const line of lines) {\n console.log(`+${line}`);\n }\n if (file.newContent.split('\\n').length > 10) {\n console.log('... (truncated)');\n }\n }\n }\n\n if (added.length === 0 && modified.length === 0) {\n success('\\nNo changes needed - configuration is up to date');\n } else {\n info('\\nRun `safeword upgrade` to apply these changes');\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/reset.ts"],"sourcesContent":["/**\n * Reset command - Remove safeword configuration from project\n */\n\nimport { join } from 'node:path';\nimport { readdirSync } from 'node:fs';\nimport { exists, remove, readFile, writeFile, readJson, writeJson, listDir } from '../utils/fs.js';\nimport { info, success, warn, error, header, listItem } from '../utils/output.js';\nimport { isGitRepo, removeGitHook } from '../utils/git.js';\nimport { AGENTS_MD_LINK } from '../templates/index.js';\n\nexport interface ResetOptions {\n yes?: boolean;\n}\n\nexport async function reset(options: ResetOptions): Promise<void> {\n const cwd = process.cwd();\n const safewordDir = join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDir)) {\n info('Nothing to remove. Project is not configured with safeword.');\n return;\n }\n\n const isNonInteractive = options.yes || !process.stdin.isTTY;\n\n // Confirmation (in interactive mode without --yes)\n if (!isNonInteractive) {\n // In a real implementation, we'd prompt here\n // For now, non-TTY mode auto-confirms\n }\n\n header('Safeword Reset');\n info('Removing safeword configuration...');\n\n const removed: string[] = [];\n\n try {\n // 1. Remove .safeword directory\n if (exists(safewordDir)) {\n remove(safewordDir);\n removed.push('.safeword/');\n success('Removed .safeword directory');\n }\n\n // 2. Remove safeword hooks from .claude/settings.json\n const settingsPath = join(cwd, '.claude', 'settings.json');\n\n if (exists(settingsPath)) {\n info('\\nRemoving hooks from .claude/settings.json...');\n\n interface SettingsJson {\n hooks?: Record<string, unknown[]>;\n [key: string]: unknown;\n }\n\n const settings = readJson<SettingsJson>(settingsPath);\n\n if (settings?.hooks) {\n let modified = false;\n\n for (const [event, hooks] of Object.entries(settings.hooks)) {\n if (Array.isArray(hooks)) {\n const filtered = hooks.filter(\n (h: unknown) =>\n !(\n typeof h === 'object' &&\n h !== null &&\n 'command' in h &&\n typeof (h as { command: string }).command === 'string' &&\n (h as { command: string }).command.includes('.safeword')\n ),\n );\n\n if (filtered.length !== hooks.length) {\n settings.hooks[event] = filtered;\n modified = true;\n }\n }\n }\n\n if (modified) {\n writeJson(settingsPath, settings);\n removed.push('.claude/settings.json (hooks)');\n success('Removed safeword hooks');\n }\n }\n }\n\n // 3. Remove safeword skills\n const skillsDir = join(cwd, '.claude', 'skills');\n\n if (exists(skillsDir)) {\n info('\\nRemoving safeword skills...');\n\n const skills = listDir(skillsDir);\n for (const skill of skills) {\n if (skill.startsWith('safeword-')) {\n remove(join(skillsDir, skill));\n removed.push(`.claude/skills/${skill}/`);\n }\n }\n\n if (removed.some(r => r.includes('skills'))) {\n success('Removed safeword skills');\n }\n }\n\n // 4. Remove git hook markers\n if (isGitRepo(cwd)) {\n info('\\nRemoving git hook markers...');\n removeGitHook(cwd);\n removed.push('.git/hooks/pre-commit (markers)');\n success('Removed git hook markers');\n }\n\n // 5. Remove link from AGENTS.md\n const agentsMdPath = join(cwd, 'AGENTS.md');\n\n if (exists(agentsMdPath)) {\n info('\\nCleaning AGENTS.md...');\n\n let content = readFile(agentsMdPath);\n\n // Remove the safeword link line\n const lines = content.split('\\n');\n const filteredLines = lines.filter(line => !line.includes('@./.safeword/SAFEWORD.md'));\n\n // Remove extra blank lines at the start\n while (filteredLines.length > 0 && filteredLines[0].trim() === '') {\n filteredLines.shift();\n }\n\n const newContent = filteredLines.join('\\n');\n\n if (newContent !== content) {\n if (newContent.trim() === '') {\n // File would be empty, but we keep it as user might have other content\n writeFile(agentsMdPath, newContent);\n } else {\n writeFile(agentsMdPath, newContent);\n }\n removed.push('AGENTS.md (link)');\n success('Removed safeword link from AGENTS.md');\n }\n }\n\n // Print summary\n header('Reset Complete');\n\n if (removed.length > 0) {\n info('\\nRemoved:');\n for (const item of removed) {\n listItem(item);\n }\n }\n\n // Note about preserved linting\n info('\\nPreserved (remove manually if desired):');\n listItem('eslint.config.mjs');\n listItem('.prettierrc');\n listItem('package.json lint/format scripts');\n listItem('ESLint/Prettier devDependencies');\n\n success('\\nSafeword configuration removed');\n } catch (err) {\n error(`Reset failed: ${err instanceof Error ? err.message : 'Unknown error'}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/setup.ts","../src/utils/project-detector.ts"],"sourcesContent":["/**\n * Setup command - Initialize safeword in a project\n */\n\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { VERSION } from '../version.js';\nimport {\n exists,\n ensureDir,\n writeFile,\n readFile,\n readJson,\n writeJson,\n updateJson,\n makeExecutable,\n} from '../utils/fs.js';\nimport { info, success, warn, error, header, listItem } from '../utils/output.js';\nimport { isGitRepo, initGitRepo, installGitHook } from '../utils/git.js';\nimport { detectProjectType } from '../utils/project-detector.js';\nimport {\n SAFEWORD_MD,\n AGENTS_MD_LINK,\n PRETTIERRC,\n getEslintConfig,\n HOOK_AGENTS_CHECK,\n HOOK_PRE_COMMIT,\n HOOK_POST_TOOL,\n SETTINGS_HOOKS,\n SKILL_QUALITY_REVIEWER,\n} from '../templates/index.js';\n\nexport interface SetupOptions {\n yes?: boolean;\n}\n\ninterface PackageJson {\n name?: string;\n version?: string;\n scripts?: Record<string, string>;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\nexport async function setup(options: SetupOptions): Promise<void> {\n const cwd = process.cwd();\n const safewordDir = join(cwd, '.safeword');\n\n // Check if already configured\n if (exists(safewordDir)) {\n error('Already configured. Run `safeword upgrade` to update.');\n process.exit(1);\n }\n\n // Check for package.json\n const packageJsonPath = join(cwd, 'package.json');\n if (!exists(packageJsonPath)) {\n error('No package.json found. Run this command in a Node.js project.');\n process.exit(1);\n }\n\n const isNonInteractive = options.yes || !process.stdin.isTTY;\n\n header('Safeword Setup');\n info(`Version: ${VERSION}`);\n\n // Track created files for summary\n const created: string[] = [];\n const modified: string[] = [];\n\n try {\n // 1. Create .safeword directory structure\n info('\\nCreating .safeword directory...');\n\n ensureDir(safewordDir);\n ensureDir(join(safewordDir, 'guides'));\n ensureDir(join(safewordDir, 'templates'));\n ensureDir(join(safewordDir, 'hooks'));\n\n writeFile(join(safewordDir, 'SAFEWORD.md'), SAFEWORD_MD);\n writeFile(join(safewordDir, 'version'), VERSION);\n\n // Create hook scripts\n writeFile(join(safewordDir, 'hooks', 'agents-md-check.sh'), HOOK_AGENTS_CHECK);\n writeFile(join(safewordDir, 'hooks', 'pre-commit.sh'), HOOK_PRE_COMMIT);\n writeFile(join(safewordDir, 'hooks', 'post-tool.sh'), HOOK_POST_TOOL);\n\n makeExecutable(join(safewordDir, 'hooks', 'agents-md-check.sh'));\n makeExecutable(join(safewordDir, 'hooks', 'pre-commit.sh'));\n makeExecutable(join(safewordDir, 'hooks', 'post-tool.sh'));\n\n created.push('.safeword/');\n success('Created .safeword directory');\n\n // 2. Handle AGENTS.md\n info('\\nConfiguring AGENTS.md...');\n const agentsMdPath = join(cwd, 'AGENTS.md');\n\n if (exists(agentsMdPath)) {\n const content = readFile(agentsMdPath);\n if (!content.includes('@./.safeword/SAFEWORD.md')) {\n writeFile(agentsMdPath, `${AGENTS_MD_LINK}\\n\\n${content}`);\n modified.push('AGENTS.md');\n success('Prepended link to AGENTS.md');\n } else {\n info('AGENTS.md already has safeword link');\n }\n } else {\n writeFile(agentsMdPath, `${AGENTS_MD_LINK}\\n`);\n created.push('AGENTS.md');\n success('Created AGENTS.md');\n }\n\n // 3. Register Claude Code hooks\n info('\\nRegistering Claude Code hooks...');\n\n const claudeDir = join(cwd, '.claude');\n const settingsPath = join(claudeDir, 'settings.json');\n\n ensureDir(claudeDir);\n\n try {\n updateJson<{ hooks?: Record<string, unknown[]> }>(settingsPath, existing => {\n const hooks = existing?.hooks ?? {};\n\n // Merge hooks, preserving existing ones\n for (const [event, newHooks] of Object.entries(SETTINGS_HOOKS)) {\n const existingHooks = (hooks[event] as unknown[]) ?? [];\n\n // Filter out any existing safeword hooks\n const nonSafewordHooks = existingHooks.filter(\n (h: unknown) =>\n typeof h === 'object' &&\n h !== null &&\n 'command' in h &&\n typeof (h as { command: string }).command === 'string' &&\n !(h as { command: string }).command.includes('.safeword'),\n );\n\n // Add safeword hooks\n hooks[event] = [...nonSafewordHooks, ...newHooks];\n }\n\n return { ...existing, hooks };\n });\n\n if (exists(settingsPath)) {\n modified.push('.claude/settings.json');\n } else {\n created.push('.claude/settings.json');\n }\n success('Registered hooks in .claude/settings.json');\n } catch (err) {\n error(`Failed to register hooks: ${err instanceof Error ? err.message : 'Unknown error'}`);\n process.exit(1);\n }\n\n // 4. Copy skills\n info('\\nInstalling skills...');\n\n const skillsDir = join(claudeDir, 'skills', 'safeword-quality-reviewer');\n ensureDir(skillsDir);\n writeFile(join(skillsDir, 'SKILL.md'), SKILL_QUALITY_REVIEWER);\n\n created.push('.claude/skills/safeword-quality-reviewer/');\n success('Installed skills');\n\n // 5. Setup linting\n info('\\nConfiguring linting...');\n\n const packageJson = readJson<PackageJson>(packageJsonPath);\n if (!packageJson) {\n error('Failed to read package.json');\n process.exit(1);\n }\n\n const projectType = detectProjectType(packageJson);\n\n // Create ESLint config\n const eslintConfigPath = join(cwd, 'eslint.config.mjs');\n if (!exists(eslintConfigPath)) {\n writeFile(eslintConfigPath, getEslintConfig(projectType));\n created.push('eslint.config.mjs');\n success('Created eslint.config.mjs');\n } else {\n info('eslint.config.mjs already exists');\n }\n\n // Create Prettier config\n const prettierrcPath = join(cwd, '.prettierrc');\n if (!exists(prettierrcPath)) {\n writeFile(prettierrcPath, PRETTIERRC);\n created.push('.prettierrc');\n success('Created .prettierrc');\n } else {\n info('.prettierrc already exists');\n }\n\n // Add scripts to package.json\n try {\n const scripts = packageJson.scripts ?? {};\n let scriptsModified = false;\n\n if (!scripts.lint) {\n scripts.lint = 'eslint .';\n scriptsModified = true;\n }\n\n if (!scripts.format) {\n scripts.format = 'prettier --write .';\n scriptsModified = true;\n }\n\n if (scriptsModified) {\n packageJson.scripts = scripts;\n writeJson(packageJsonPath, packageJson);\n modified.push('package.json');\n success('Added lint and format scripts');\n }\n } catch (err) {\n error(\n `Failed to update package.json: ${err instanceof Error ? err.message : 'Unknown error'}`,\n );\n process.exit(1);\n }\n\n // 6. Handle git repository\n info('\\nConfiguring git...');\n\n if (isGitRepo(cwd)) {\n installGitHook(cwd);\n modified.push('.git/hooks/pre-commit');\n success('Installed git pre-commit hook');\n } else if (isNonInteractive) {\n warn('Skipped git initialization (non-interactive mode)');\n warn('Git hooks not installed (no repository)');\n } else {\n // Interactive mode - would prompt here\n // For now, skip in all cases\n warn('Skipped git initialization (no .git directory)');\n warn('Git hooks not installed (no repository)');\n }\n\n // 7. Install dependencies\n info('\\nNote: Install linting dependencies manually:');\n listItem('npm install -D eslint prettier @eslint/js');\n if (projectType.typescript) {\n listItem('npm install -D typescript-eslint');\n }\n if (projectType.react) {\n listItem('npm install -D eslint-plugin-react eslint-plugin-react-hooks');\n }\n\n // Print summary\n header('Setup Complete');\n\n if (created.length > 0) {\n info('\\nCreated:');\n for (const file of created) {\n listItem(file);\n }\n }\n\n if (modified.length > 0) {\n info('\\nModified:');\n for (const file of modified) {\n listItem(file);\n }\n }\n\n info('\\nNext steps:');\n listItem('Install linting dependencies (see above)');\n listItem('Run `safeword check` to verify setup');\n listItem('Commit the new files to git');\n\n success(`\\nSafeword ${VERSION} installed successfully!`);\n } catch (err) {\n error(`Setup failed: ${err instanceof Error ? err.message : 'Unknown error'}`);\n process.exit(1);\n }\n}\n","/**\n * Project type detection from package.json\n *\n * Detects frameworks and tools used in the project to configure\n * appropriate linting rules.\n */\n\nexport interface PackageJson {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\nexport interface ProjectType {\n typescript: boolean;\n react: boolean;\n nextjs: boolean;\n astro: boolean;\n electron: boolean;\n}\n\n/**\n * Detects project type from package.json contents\n */\nexport function detectProjectType(packageJson: PackageJson): ProjectType {\n const deps = packageJson.dependencies || {};\n const devDeps = packageJson.devDependencies || {};\n const allDeps = { ...deps, ...devDeps };\n\n const hasTypescript = 'typescript' in allDeps;\n const hasReact = 'react' in deps || 'react' in devDeps;\n const hasNextJs = 'next' in deps;\n const hasAstro = 'astro' in deps || 'astro' in devDeps;\n const hasElectron = 'electron' in deps || 'electron' in devDeps;\n\n return {\n typescript: hasTypescript,\n react: hasReact || hasNextJs, // Next.js implies React\n nextjs: hasNextJs,\n astro: hasAstro,\n electron: hasElectron,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/upgrade.ts"],"sourcesContent":["/**\n * Upgrade command - Update safeword configuration to latest version\n */\n\nimport { join } from 'node:path';\nimport { VERSION } from '../version.js';\nimport {\n exists,\n ensureDir,\n writeFile,\n readFile,\n readFileSafe,\n readJson,\n updateJson,\n makeExecutable,\n} from '../utils/fs.js';\nimport { info, success, warn, error, header, listItem } from '../utils/output.js';\nimport { isGitRepo, installGitHook } from '../utils/git.js';\nimport { compareVersions } from '../utils/version.js';\nimport {\n SAFEWORD_MD,\n AGENTS_MD_LINK,\n HOOK_AGENTS_CHECK,\n HOOK_PRE_COMMIT,\n HOOK_POST_TOOL,\n SETTINGS_HOOKS,\n SKILL_QUALITY_REVIEWER,\n} from '../templates/index.js';\n\nexport async function upgrade(): Promise<void> {\n const cwd = process.cwd();\n const safewordDir = join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDir)) {\n error('Not configured. Run `safeword setup` first.');\n process.exit(1);\n }\n\n // Read project version\n const versionPath = join(safewordDir, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? '0.0.0';\n\n // Check for downgrade\n if (compareVersions(VERSION, projectVersion) < 0) {\n error(`CLI v${VERSION} is older than project v${projectVersion}.`);\n error('Update the CLI first: npm install -g safeword');\n process.exit(1);\n }\n\n header('Safeword Upgrade');\n info(`Upgrading from v${projectVersion} to v${VERSION}`);\n\n const updated: string[] = [];\n const unchanged: string[] = [];\n\n try {\n // 1. Update .safeword directory\n info('\\nUpdating .safeword directory...');\n\n ensureDir(join(safewordDir, 'guides'));\n ensureDir(join(safewordDir, 'templates'));\n ensureDir(join(safewordDir, 'hooks'));\n\n // Update core files\n writeFile(join(safewordDir, 'SAFEWORD.md'), SAFEWORD_MD);\n writeFile(join(safewordDir, 'version'), VERSION);\n updated.push('.safeword/SAFEWORD.md');\n updated.push('.safeword/version');\n\n // Update hook scripts\n writeFile(join(safewordDir, 'hooks', 'agents-md-check.sh'), HOOK_AGENTS_CHECK);\n writeFile(join(safewordDir, 'hooks', 'pre-commit.sh'), HOOK_PRE_COMMIT);\n writeFile(join(safewordDir, 'hooks', 'post-tool.sh'), HOOK_POST_TOOL);\n\n makeExecutable(join(safewordDir, 'hooks', 'agents-md-check.sh'));\n makeExecutable(join(safewordDir, 'hooks', 'pre-commit.sh'));\n makeExecutable(join(safewordDir, 'hooks', 'post-tool.sh'));\n\n updated.push('.safeword/hooks/');\n success('Updated .safeword directory');\n\n // 2. Verify AGENTS.md link\n info('\\nVerifying AGENTS.md...');\n const agentsMdPath = join(cwd, 'AGENTS.md');\n\n if (exists(agentsMdPath)) {\n const content = readFile(agentsMdPath);\n if (!content.includes('@./.safeword/SAFEWORD.md')) {\n writeFile(agentsMdPath, `${AGENTS_MD_LINK}\\n\\n${content}`);\n updated.push('AGENTS.md');\n success('Restored link to AGENTS.md');\n } else {\n unchanged.push('AGENTS.md');\n info('AGENTS.md link is present');\n }\n } else {\n writeFile(agentsMdPath, `${AGENTS_MD_LINK}\\n`);\n updated.push('AGENTS.md');\n success('Created AGENTS.md');\n }\n\n // 3. Update Claude Code hooks\n info('\\nUpdating Claude Code hooks...');\n\n const claudeDir = join(cwd, '.claude');\n const settingsPath = join(claudeDir, 'settings.json');\n\n ensureDir(claudeDir);\n\n updateJson<{ hooks?: Record<string, unknown[]> }>(settingsPath, existing => {\n const hooks = existing?.hooks ?? {};\n\n // Merge hooks, preserving non-safeword hooks\n for (const [event, newHooks] of Object.entries(SETTINGS_HOOKS)) {\n const existingHooks = (hooks[event] as unknown[]) ?? [];\n\n // Filter out existing safeword hooks\n const nonSafewordHooks = existingHooks.filter(\n (h: unknown) =>\n typeof h === 'object' &&\n h !== null &&\n 'command' in h &&\n typeof (h as { command: string }).command === 'string' &&\n !(h as { command: string }).command.includes('.safeword'),\n );\n\n // Add safeword hooks\n hooks[event] = [...nonSafewordHooks, ...newHooks];\n }\n\n return { ...existing, hooks };\n });\n\n updated.push('.claude/settings.json');\n success('Updated hooks in .claude/settings.json');\n\n // 4. Update skills\n info('\\nUpdating skills...');\n\n const skillsDir = join(claudeDir, 'skills', 'safeword-quality-reviewer');\n ensureDir(skillsDir);\n writeFile(join(skillsDir, 'SKILL.md'), SKILL_QUALITY_REVIEWER);\n\n updated.push('.claude/skills/safeword-quality-reviewer/');\n success('Updated skills');\n\n // 5. Update git hooks if repo exists\n if (isGitRepo(cwd)) {\n info('\\nUpdating git hooks...');\n installGitHook(cwd);\n updated.push('.git/hooks/pre-commit');\n success('Updated git pre-commit hook');\n }\n\n // Print summary\n header('Upgrade Complete');\n\n info(`\\nVersion: v${projectVersion} → v${VERSION}`);\n\n if (updated.length > 0) {\n info('\\nUpdated:');\n for (const file of updated) {\n listItem(file);\n }\n }\n\n if (unchanged.length > 0) {\n info('\\nUnchanged:');\n for (const file of unchanged) {\n listItem(file);\n }\n }\n\n success(`\\nSafeword upgraded to v${VERSION}`);\n } catch (err) {\n error(`Upgrade failed: ${err instanceof Error ? err.message : 'Unknown error'}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
|