safeword 0.1.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 +129 -0
- package/dist/check-J6DFVBCE.js.map +1 -0
- package/dist/chunk-24OB57NJ.js +78 -0
- package/dist/chunk-24OB57NJ.js.map +1 -0
- package/dist/chunk-DB4CMUFD.js +157 -0
- package/dist/chunk-DB4CMUFD.js.map +1 -0
- package/dist/chunk-UQMQ64CB.js +107 -0
- package/dist/chunk-UQMQ64CB.js.map +1 -0
- package/dist/chunk-W66Z3C5H.js +21 -0
- package/dist/chunk-W66Z3C5H.js.map +1 -0
- package/dist/chunk-WWQ4YRZN.js +7 -0
- package/dist/chunk-WWQ4YRZN.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +34 -0
- package/dist/cli.js.map +1 -0
- package/dist/diff-U4IELWRL.js +163 -0
- package/dist/diff-U4IELWRL.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/reset-XETOHTCK.js +126 -0
- package/dist/reset-XETOHTCK.js.map +1 -0
- package/dist/setup-CLDCHROZ.js +237 -0
- package/dist/setup-CLDCHROZ.js.map +1 -0
- package/dist/upgrade-DOKWRK7J.js +146 -0
- package/dist/upgrade-DOKWRK7J.js.map +1 -0
- package/package.json +50 -0
- package/templates/doc-templates/ticket-template.md +82 -0
- package/templates/hooks/inject-timestamp.sh +7 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HOOK_AGENTS_CHECK,
|
|
3
|
+
HOOK_POST_TOOL,
|
|
4
|
+
HOOK_PRE_COMMIT,
|
|
5
|
+
SAFEWORD_MD,
|
|
6
|
+
SKILL_QUALITY_REVIEWER
|
|
7
|
+
} from "./chunk-DB4CMUFD.js";
|
|
8
|
+
import {
|
|
9
|
+
VERSION
|
|
10
|
+
} from "./chunk-WWQ4YRZN.js";
|
|
11
|
+
import {
|
|
12
|
+
error,
|
|
13
|
+
exists,
|
|
14
|
+
header,
|
|
15
|
+
info,
|
|
16
|
+
listItem,
|
|
17
|
+
readFileSafe,
|
|
18
|
+
success
|
|
19
|
+
} from "./chunk-UQMQ64CB.js";
|
|
20
|
+
|
|
21
|
+
// src/commands/diff.ts
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
function createUnifiedDiff(oldContent, newContent, filename) {
|
|
24
|
+
const oldLines = oldContent.split("\n");
|
|
25
|
+
const newLines = newContent.split("\n");
|
|
26
|
+
const lines = [];
|
|
27
|
+
lines.push(`--- a/${filename}`);
|
|
28
|
+
lines.push(`+++ b/${filename}`);
|
|
29
|
+
let hasChanges = false;
|
|
30
|
+
const maxLines = Math.max(oldLines.length, newLines.length);
|
|
31
|
+
for (let i = 0; i < maxLines; i++) {
|
|
32
|
+
const oldLine = oldLines[i];
|
|
33
|
+
const newLine = newLines[i];
|
|
34
|
+
if (oldLine === newLine) {
|
|
35
|
+
lines.push(` ${oldLine ?? ""}`);
|
|
36
|
+
} else {
|
|
37
|
+
hasChanges = true;
|
|
38
|
+
if (oldLine !== void 0) {
|
|
39
|
+
lines.push(`-${oldLine}`);
|
|
40
|
+
}
|
|
41
|
+
if (newLine !== void 0) {
|
|
42
|
+
lines.push(`+${newLine}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!hasChanges) {
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
lines.splice(2, 0, `@@ -1,${oldLines.length} +1,${newLines.length} @@`);
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
}
|
|
52
|
+
function getFileDiffs(cwd) {
|
|
53
|
+
const safewordDir = join(cwd, ".safeword");
|
|
54
|
+
const diffs = [];
|
|
55
|
+
const files = [
|
|
56
|
+
{ path: ".safeword/SAFEWORD.md", newContent: SAFEWORD_MD },
|
|
57
|
+
{ path: ".safeword/version", newContent: VERSION },
|
|
58
|
+
{ path: ".safeword/hooks/agents-md-check.sh", newContent: HOOK_AGENTS_CHECK },
|
|
59
|
+
{ path: ".safeword/hooks/pre-commit.sh", newContent: HOOK_PRE_COMMIT },
|
|
60
|
+
{ path: ".safeword/hooks/post-tool.sh", newContent: HOOK_POST_TOOL },
|
|
61
|
+
{
|
|
62
|
+
path: ".claude/skills/safeword-quality-reviewer/SKILL.md",
|
|
63
|
+
newContent: SKILL_QUALITY_REVIEWER
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
const fullPath = join(cwd, file.path);
|
|
68
|
+
const currentContent = readFileSafe(fullPath);
|
|
69
|
+
if (currentContent === null) {
|
|
70
|
+
diffs.push({
|
|
71
|
+
path: file.path,
|
|
72
|
+
status: "added",
|
|
73
|
+
newContent: file.newContent
|
|
74
|
+
});
|
|
75
|
+
} else if (currentContent.trim() !== file.newContent.trim()) {
|
|
76
|
+
diffs.push({
|
|
77
|
+
path: file.path,
|
|
78
|
+
status: "modified",
|
|
79
|
+
currentContent,
|
|
80
|
+
newContent: file.newContent
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
diffs.push({
|
|
84
|
+
path: file.path,
|
|
85
|
+
status: "unchanged",
|
|
86
|
+
currentContent,
|
|
87
|
+
newContent: file.newContent
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return diffs;
|
|
92
|
+
}
|
|
93
|
+
async function diff(options) {
|
|
94
|
+
const cwd = process.cwd();
|
|
95
|
+
const safewordDir = join(cwd, ".safeword");
|
|
96
|
+
if (!exists(safewordDir)) {
|
|
97
|
+
error("Not configured. Run `safeword setup` first.");
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
const versionPath = join(safewordDir, "version");
|
|
101
|
+
const projectVersion = readFileSafe(versionPath)?.trim() ?? "unknown";
|
|
102
|
+
header("Safeword Diff");
|
|
103
|
+
info(`Changes from v${projectVersion} \u2192 v${VERSION}`);
|
|
104
|
+
const diffs = getFileDiffs(cwd);
|
|
105
|
+
const added = diffs.filter((d) => d.status === "added");
|
|
106
|
+
const modified = diffs.filter((d) => d.status === "modified");
|
|
107
|
+
const unchanged = diffs.filter((d) => d.status === "unchanged");
|
|
108
|
+
info(
|
|
109
|
+
`
|
|
110
|
+
Summary: ${added.length} added, ${modified.length} modified, ${unchanged.length} unchanged`
|
|
111
|
+
);
|
|
112
|
+
if (added.length > 0) {
|
|
113
|
+
info("\nAdded:");
|
|
114
|
+
for (const file of added) {
|
|
115
|
+
listItem(file.path);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (modified.length > 0) {
|
|
119
|
+
info("\nModified:");
|
|
120
|
+
for (const file of modified) {
|
|
121
|
+
listItem(file.path);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (unchanged.length > 0) {
|
|
125
|
+
info("\nUnchanged:");
|
|
126
|
+
for (const file of unchanged) {
|
|
127
|
+
listItem(file.path);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (options.verbose) {
|
|
131
|
+
header("Detailed Changes");
|
|
132
|
+
for (const file of modified) {
|
|
133
|
+
if (file.currentContent) {
|
|
134
|
+
info(`
|
|
135
|
+
${file.path}:`);
|
|
136
|
+
const diffOutput = createUnifiedDiff(file.currentContent, file.newContent, file.path);
|
|
137
|
+
if (diffOutput) {
|
|
138
|
+
console.log(diffOutput);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const file of added) {
|
|
143
|
+
info(`
|
|
144
|
+
${file.path}: (new file)`);
|
|
145
|
+
const lines = file.newContent.split("\n").slice(0, 10);
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
console.log(`+${line}`);
|
|
148
|
+
}
|
|
149
|
+
if (file.newContent.split("\n").length > 10) {
|
|
150
|
+
console.log("... (truncated)");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (added.length === 0 && modified.length === 0) {
|
|
155
|
+
success("\nNo changes needed - configuration is up to date");
|
|
156
|
+
} else {
|
|
157
|
+
info("\nRun `safeword upgrade` to apply these changes");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
diff
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=diff-U4IELWRL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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":[]}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isGitRepo,
|
|
3
|
+
removeGitHook
|
|
4
|
+
} from "./chunk-24OB57NJ.js";
|
|
5
|
+
import {
|
|
6
|
+
error,
|
|
7
|
+
exists,
|
|
8
|
+
header,
|
|
9
|
+
info,
|
|
10
|
+
listDir,
|
|
11
|
+
listItem,
|
|
12
|
+
readFile,
|
|
13
|
+
readJson,
|
|
14
|
+
remove,
|
|
15
|
+
success,
|
|
16
|
+
writeFile,
|
|
17
|
+
writeJson
|
|
18
|
+
} from "./chunk-UQMQ64CB.js";
|
|
19
|
+
|
|
20
|
+
// src/commands/reset.ts
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
async function reset(options) {
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
const safewordDir = join(cwd, ".safeword");
|
|
25
|
+
if (!exists(safewordDir)) {
|
|
26
|
+
info("Nothing to remove. Project is not configured with safeword.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const isNonInteractive = options.yes || !process.stdin.isTTY;
|
|
30
|
+
if (!isNonInteractive) {
|
|
31
|
+
}
|
|
32
|
+
header("Safeword Reset");
|
|
33
|
+
info("Removing safeword configuration...");
|
|
34
|
+
const removed = [];
|
|
35
|
+
try {
|
|
36
|
+
if (exists(safewordDir)) {
|
|
37
|
+
remove(safewordDir);
|
|
38
|
+
removed.push(".safeword/");
|
|
39
|
+
success("Removed .safeword directory");
|
|
40
|
+
}
|
|
41
|
+
const settingsPath = join(cwd, ".claude", "settings.json");
|
|
42
|
+
if (exists(settingsPath)) {
|
|
43
|
+
info("\nRemoving hooks from .claude/settings.json...");
|
|
44
|
+
const settings = readJson(settingsPath);
|
|
45
|
+
if (settings?.hooks) {
|
|
46
|
+
let modified = false;
|
|
47
|
+
for (const [event, hooks] of Object.entries(settings.hooks)) {
|
|
48
|
+
if (Array.isArray(hooks)) {
|
|
49
|
+
const filtered = hooks.filter(
|
|
50
|
+
(h) => !(typeof h === "object" && h !== null && "command" in h && typeof h.command === "string" && h.command.includes(".safeword"))
|
|
51
|
+
);
|
|
52
|
+
if (filtered.length !== hooks.length) {
|
|
53
|
+
settings.hooks[event] = filtered;
|
|
54
|
+
modified = true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (modified) {
|
|
59
|
+
writeJson(settingsPath, settings);
|
|
60
|
+
removed.push(".claude/settings.json (hooks)");
|
|
61
|
+
success("Removed safeword hooks");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const skillsDir = join(cwd, ".claude", "skills");
|
|
66
|
+
if (exists(skillsDir)) {
|
|
67
|
+
info("\nRemoving safeword skills...");
|
|
68
|
+
const skills = listDir(skillsDir);
|
|
69
|
+
for (const skill of skills) {
|
|
70
|
+
if (skill.startsWith("safeword-")) {
|
|
71
|
+
remove(join(skillsDir, skill));
|
|
72
|
+
removed.push(`.claude/skills/${skill}/`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (removed.some((r) => r.includes("skills"))) {
|
|
76
|
+
success("Removed safeword skills");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (isGitRepo(cwd)) {
|
|
80
|
+
info("\nRemoving git hook markers...");
|
|
81
|
+
removeGitHook(cwd);
|
|
82
|
+
removed.push(".git/hooks/pre-commit (markers)");
|
|
83
|
+
success("Removed git hook markers");
|
|
84
|
+
}
|
|
85
|
+
const agentsMdPath = join(cwd, "AGENTS.md");
|
|
86
|
+
if (exists(agentsMdPath)) {
|
|
87
|
+
info("\nCleaning AGENTS.md...");
|
|
88
|
+
let content = readFile(agentsMdPath);
|
|
89
|
+
const lines = content.split("\n");
|
|
90
|
+
const filteredLines = lines.filter((line) => !line.includes("@./.safeword/SAFEWORD.md"));
|
|
91
|
+
while (filteredLines.length > 0 && filteredLines[0].trim() === "") {
|
|
92
|
+
filteredLines.shift();
|
|
93
|
+
}
|
|
94
|
+
const newContent = filteredLines.join("\n");
|
|
95
|
+
if (newContent !== content) {
|
|
96
|
+
if (newContent.trim() === "") {
|
|
97
|
+
writeFile(agentsMdPath, newContent);
|
|
98
|
+
} else {
|
|
99
|
+
writeFile(agentsMdPath, newContent);
|
|
100
|
+
}
|
|
101
|
+
removed.push("AGENTS.md (link)");
|
|
102
|
+
success("Removed safeword link from AGENTS.md");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
header("Reset Complete");
|
|
106
|
+
if (removed.length > 0) {
|
|
107
|
+
info("\nRemoved:");
|
|
108
|
+
for (const item of removed) {
|
|
109
|
+
listItem(item);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
info("\nPreserved (remove manually if desired):");
|
|
113
|
+
listItem("eslint.config.mjs");
|
|
114
|
+
listItem(".prettierrc");
|
|
115
|
+
listItem("package.json lint/format scripts");
|
|
116
|
+
listItem("ESLint/Prettier devDependencies");
|
|
117
|
+
success("\nSafeword configuration removed");
|
|
118
|
+
} catch (err) {
|
|
119
|
+
error(`Reset failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
reset
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=reset-XETOHTCK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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":[]}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGENTS_MD_LINK,
|
|
3
|
+
HOOK_AGENTS_CHECK,
|
|
4
|
+
HOOK_POST_TOOL,
|
|
5
|
+
HOOK_PRE_COMMIT,
|
|
6
|
+
PRETTIERRC,
|
|
7
|
+
SAFEWORD_MD,
|
|
8
|
+
SETTINGS_HOOKS,
|
|
9
|
+
SKILL_QUALITY_REVIEWER,
|
|
10
|
+
getEslintConfig
|
|
11
|
+
} from "./chunk-DB4CMUFD.js";
|
|
12
|
+
import {
|
|
13
|
+
VERSION
|
|
14
|
+
} from "./chunk-WWQ4YRZN.js";
|
|
15
|
+
import {
|
|
16
|
+
installGitHook,
|
|
17
|
+
isGitRepo
|
|
18
|
+
} from "./chunk-24OB57NJ.js";
|
|
19
|
+
import {
|
|
20
|
+
ensureDir,
|
|
21
|
+
error,
|
|
22
|
+
exists,
|
|
23
|
+
header,
|
|
24
|
+
info,
|
|
25
|
+
listItem,
|
|
26
|
+
makeExecutable,
|
|
27
|
+
readFile,
|
|
28
|
+
readJson,
|
|
29
|
+
success,
|
|
30
|
+
updateJson,
|
|
31
|
+
warn,
|
|
32
|
+
writeFile,
|
|
33
|
+
writeJson
|
|
34
|
+
} from "./chunk-UQMQ64CB.js";
|
|
35
|
+
|
|
36
|
+
// src/commands/setup.ts
|
|
37
|
+
import { join } from "path";
|
|
38
|
+
|
|
39
|
+
// src/utils/project-detector.ts
|
|
40
|
+
function detectProjectType(packageJson) {
|
|
41
|
+
const deps = packageJson.dependencies || {};
|
|
42
|
+
const devDeps = packageJson.devDependencies || {};
|
|
43
|
+
const allDeps = { ...deps, ...devDeps };
|
|
44
|
+
const hasTypescript = "typescript" in allDeps;
|
|
45
|
+
const hasReact = "react" in deps || "react" in devDeps;
|
|
46
|
+
const hasNextJs = "next" in deps;
|
|
47
|
+
const hasAstro = "astro" in deps || "astro" in devDeps;
|
|
48
|
+
const hasElectron = "electron" in deps || "electron" in devDeps;
|
|
49
|
+
return {
|
|
50
|
+
typescript: hasTypescript,
|
|
51
|
+
react: hasReact || hasNextJs,
|
|
52
|
+
// Next.js implies React
|
|
53
|
+
nextjs: hasNextJs,
|
|
54
|
+
astro: hasAstro,
|
|
55
|
+
electron: hasElectron
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/commands/setup.ts
|
|
60
|
+
async function setup(options) {
|
|
61
|
+
const cwd = process.cwd();
|
|
62
|
+
const safewordDir = join(cwd, ".safeword");
|
|
63
|
+
if (exists(safewordDir)) {
|
|
64
|
+
error("Already configured. Run `safeword upgrade` to update.");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
68
|
+
if (!exists(packageJsonPath)) {
|
|
69
|
+
error("No package.json found. Run this command in a Node.js project.");
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const isNonInteractive = options.yes || !process.stdin.isTTY;
|
|
73
|
+
header("Safeword Setup");
|
|
74
|
+
info(`Version: ${VERSION}`);
|
|
75
|
+
const created = [];
|
|
76
|
+
const modified = [];
|
|
77
|
+
try {
|
|
78
|
+
info("\nCreating .safeword directory...");
|
|
79
|
+
ensureDir(safewordDir);
|
|
80
|
+
ensureDir(join(safewordDir, "guides"));
|
|
81
|
+
ensureDir(join(safewordDir, "templates"));
|
|
82
|
+
ensureDir(join(safewordDir, "hooks"));
|
|
83
|
+
writeFile(join(safewordDir, "SAFEWORD.md"), SAFEWORD_MD);
|
|
84
|
+
writeFile(join(safewordDir, "version"), VERSION);
|
|
85
|
+
writeFile(join(safewordDir, "hooks", "agents-md-check.sh"), HOOK_AGENTS_CHECK);
|
|
86
|
+
writeFile(join(safewordDir, "hooks", "pre-commit.sh"), HOOK_PRE_COMMIT);
|
|
87
|
+
writeFile(join(safewordDir, "hooks", "post-tool.sh"), HOOK_POST_TOOL);
|
|
88
|
+
makeExecutable(join(safewordDir, "hooks", "agents-md-check.sh"));
|
|
89
|
+
makeExecutable(join(safewordDir, "hooks", "pre-commit.sh"));
|
|
90
|
+
makeExecutable(join(safewordDir, "hooks", "post-tool.sh"));
|
|
91
|
+
created.push(".safeword/");
|
|
92
|
+
success("Created .safeword directory");
|
|
93
|
+
info("\nConfiguring AGENTS.md...");
|
|
94
|
+
const agentsMdPath = join(cwd, "AGENTS.md");
|
|
95
|
+
if (exists(agentsMdPath)) {
|
|
96
|
+
const content = readFile(agentsMdPath);
|
|
97
|
+
if (!content.includes("@./.safeword/SAFEWORD.md")) {
|
|
98
|
+
writeFile(agentsMdPath, `${AGENTS_MD_LINK}
|
|
99
|
+
|
|
100
|
+
${content}`);
|
|
101
|
+
modified.push("AGENTS.md");
|
|
102
|
+
success("Prepended link to AGENTS.md");
|
|
103
|
+
} else {
|
|
104
|
+
info("AGENTS.md already has safeword link");
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
writeFile(agentsMdPath, `${AGENTS_MD_LINK}
|
|
108
|
+
`);
|
|
109
|
+
created.push("AGENTS.md");
|
|
110
|
+
success("Created AGENTS.md");
|
|
111
|
+
}
|
|
112
|
+
info("\nRegistering Claude Code hooks...");
|
|
113
|
+
const claudeDir = join(cwd, ".claude");
|
|
114
|
+
const settingsPath = join(claudeDir, "settings.json");
|
|
115
|
+
ensureDir(claudeDir);
|
|
116
|
+
try {
|
|
117
|
+
updateJson(settingsPath, (existing) => {
|
|
118
|
+
const hooks = existing?.hooks ?? {};
|
|
119
|
+
for (const [event, newHooks] of Object.entries(SETTINGS_HOOKS)) {
|
|
120
|
+
const existingHooks = hooks[event] ?? [];
|
|
121
|
+
const nonSafewordHooks = existingHooks.filter(
|
|
122
|
+
(h) => typeof h === "object" && h !== null && "command" in h && typeof h.command === "string" && !h.command.includes(".safeword")
|
|
123
|
+
);
|
|
124
|
+
hooks[event] = [...nonSafewordHooks, ...newHooks];
|
|
125
|
+
}
|
|
126
|
+
return { ...existing, hooks };
|
|
127
|
+
});
|
|
128
|
+
if (exists(settingsPath)) {
|
|
129
|
+
modified.push(".claude/settings.json");
|
|
130
|
+
} else {
|
|
131
|
+
created.push(".claude/settings.json");
|
|
132
|
+
}
|
|
133
|
+
success("Registered hooks in .claude/settings.json");
|
|
134
|
+
} catch (err) {
|
|
135
|
+
error(`Failed to register hooks: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
info("\nInstalling skills...");
|
|
139
|
+
const skillsDir = join(claudeDir, "skills", "safeword-quality-reviewer");
|
|
140
|
+
ensureDir(skillsDir);
|
|
141
|
+
writeFile(join(skillsDir, "SKILL.md"), SKILL_QUALITY_REVIEWER);
|
|
142
|
+
created.push(".claude/skills/safeword-quality-reviewer/");
|
|
143
|
+
success("Installed skills");
|
|
144
|
+
info("\nConfiguring linting...");
|
|
145
|
+
const packageJson = readJson(packageJsonPath);
|
|
146
|
+
if (!packageJson) {
|
|
147
|
+
error("Failed to read package.json");
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
const projectType = detectProjectType(packageJson);
|
|
151
|
+
const eslintConfigPath = join(cwd, "eslint.config.mjs");
|
|
152
|
+
if (!exists(eslintConfigPath)) {
|
|
153
|
+
writeFile(eslintConfigPath, getEslintConfig(projectType));
|
|
154
|
+
created.push("eslint.config.mjs");
|
|
155
|
+
success("Created eslint.config.mjs");
|
|
156
|
+
} else {
|
|
157
|
+
info("eslint.config.mjs already exists");
|
|
158
|
+
}
|
|
159
|
+
const prettierrcPath = join(cwd, ".prettierrc");
|
|
160
|
+
if (!exists(prettierrcPath)) {
|
|
161
|
+
writeFile(prettierrcPath, PRETTIERRC);
|
|
162
|
+
created.push(".prettierrc");
|
|
163
|
+
success("Created .prettierrc");
|
|
164
|
+
} else {
|
|
165
|
+
info(".prettierrc already exists");
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const scripts = packageJson.scripts ?? {};
|
|
169
|
+
let scriptsModified = false;
|
|
170
|
+
if (!scripts.lint) {
|
|
171
|
+
scripts.lint = "eslint .";
|
|
172
|
+
scriptsModified = true;
|
|
173
|
+
}
|
|
174
|
+
if (!scripts.format) {
|
|
175
|
+
scripts.format = "prettier --write .";
|
|
176
|
+
scriptsModified = true;
|
|
177
|
+
}
|
|
178
|
+
if (scriptsModified) {
|
|
179
|
+
packageJson.scripts = scripts;
|
|
180
|
+
writeJson(packageJsonPath, packageJson);
|
|
181
|
+
modified.push("package.json");
|
|
182
|
+
success("Added lint and format scripts");
|
|
183
|
+
}
|
|
184
|
+
} catch (err) {
|
|
185
|
+
error(
|
|
186
|
+
`Failed to update package.json: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
187
|
+
);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
info("\nConfiguring git...");
|
|
191
|
+
if (isGitRepo(cwd)) {
|
|
192
|
+
installGitHook(cwd);
|
|
193
|
+
modified.push(".git/hooks/pre-commit");
|
|
194
|
+
success("Installed git pre-commit hook");
|
|
195
|
+
} else if (isNonInteractive) {
|
|
196
|
+
warn("Skipped git initialization (non-interactive mode)");
|
|
197
|
+
warn("Git hooks not installed (no repository)");
|
|
198
|
+
} else {
|
|
199
|
+
warn("Skipped git initialization (no .git directory)");
|
|
200
|
+
warn("Git hooks not installed (no repository)");
|
|
201
|
+
}
|
|
202
|
+
info("\nNote: Install linting dependencies manually:");
|
|
203
|
+
listItem("npm install -D eslint prettier @eslint/js");
|
|
204
|
+
if (projectType.typescript) {
|
|
205
|
+
listItem("npm install -D typescript-eslint");
|
|
206
|
+
}
|
|
207
|
+
if (projectType.react) {
|
|
208
|
+
listItem("npm install -D eslint-plugin-react eslint-plugin-react-hooks");
|
|
209
|
+
}
|
|
210
|
+
header("Setup Complete");
|
|
211
|
+
if (created.length > 0) {
|
|
212
|
+
info("\nCreated:");
|
|
213
|
+
for (const file of created) {
|
|
214
|
+
listItem(file);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (modified.length > 0) {
|
|
218
|
+
info("\nModified:");
|
|
219
|
+
for (const file of modified) {
|
|
220
|
+
listItem(file);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
info("\nNext steps:");
|
|
224
|
+
listItem("Install linting dependencies (see above)");
|
|
225
|
+
listItem("Run `safeword check` to verify setup");
|
|
226
|
+
listItem("Commit the new files to git");
|
|
227
|
+
success(`
|
|
228
|
+
Safeword ${VERSION} installed successfully!`);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
error(`Setup failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
export {
|
|
235
|
+
setup
|
|
236
|
+
};
|
|
237
|
+
//# sourceMappingURL=setup-CLDCHROZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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":[]}
|