safeword 0.5.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{check-3NGQ4NR5.js → check-INXMFCL5.js} +7 -5
- package/dist/{check-3NGQ4NR5.js.map → check-INXMFCL5.js.map} +1 -1
- package/dist/chunk-6CVTH67L.js +43 -0
- package/dist/chunk-6CVTH67L.js.map +1 -0
- package/dist/chunk-75FKNZUM.js +15 -0
- package/dist/chunk-75FKNZUM.js.map +1 -0
- package/dist/{chunk-GZRQL3SX.js → chunk-ARIAOK2F.js} +2 -38
- package/dist/chunk-ARIAOK2F.js.map +1 -0
- package/dist/chunk-FRPJITGG.js +35 -0
- package/dist/chunk-FRPJITGG.js.map +1 -0
- package/dist/chunk-IWWBZVHT.js +274 -0
- package/dist/chunk-IWWBZVHT.js.map +1 -0
- package/dist/cli.js +9 -5
- package/dist/cli.js.map +1 -1
- package/dist/{diff-Y6QTAW4O.js → diff-L7G22MG7.js} +7 -5
- package/dist/{diff-Y6QTAW4O.js.map → diff-L7G22MG7.js.map} +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/{reset-YPSS7BAB.js → reset-5SRM3P6J.js} +19 -17
- package/dist/reset-5SRM3P6J.js.map +1 -0
- package/dist/setup-65EVU5OT.js +437 -0
- package/dist/setup-65EVU5OT.js.map +1 -0
- package/dist/sync-4XBMKLXS.js +116 -0
- package/dist/sync-4XBMKLXS.js.map +1 -0
- package/dist/{upgrade-GQKC2ZKF.js → upgrade-P3WX3ODU.js} +34 -15
- package/dist/upgrade-P3WX3ODU.js.map +1 -0
- package/package.json +2 -1
- package/templates/SAFEWORD.md +4 -4
- package/templates/commands/architecture.md +27 -0
- package/templates/commands/lint.md +15 -54
- package/templates/commands/quality-review.md +16 -13
- package/templates/hooks/post-tool-lint.sh +14 -53
- package/dist/chunk-GZRQL3SX.js.map +0 -1
- package/dist/chunk-Z7MWRHVP.js +0 -266
- package/dist/chunk-Z7MWRHVP.js.map +0 -1
- package/dist/reset-YPSS7BAB.js.map +0 -1
- package/dist/setup-U35LUMIF.js +0 -298
- package/dist/setup-U35LUMIF.js.map +0 -1
- package/dist/upgrade-GQKC2ZKF.js.map +0 -1
- package/templates/commands/arch-review.md +0 -28
- package/templates/hooks/git-pre-commit.sh +0 -18
- /package/templates/{markdownlint.jsonc → markdownlint-cli2.jsonc} +0 -0
- /package/templates/prompts/{arch-review.md → architecture.md} +0 -0
- /package/templates/prompts/{quality-review.md → review.md} +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectProjectType
|
|
3
|
+
} from "./chunk-6CVTH67L.js";
|
|
4
|
+
import {
|
|
5
|
+
exists,
|
|
6
|
+
readJson
|
|
7
|
+
} from "./chunk-ARIAOK2F.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/sync.ts
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
import { execSync } from "child_process";
|
|
12
|
+
function getRequiredPlugins(projectType) {
|
|
13
|
+
const plugins = [
|
|
14
|
+
// Always required (base plugins)
|
|
15
|
+
"eslint",
|
|
16
|
+
"@eslint/js",
|
|
17
|
+
"eslint-plugin-import-x",
|
|
18
|
+
"eslint-plugin-sonarjs",
|
|
19
|
+
"@microsoft/eslint-plugin-sdl",
|
|
20
|
+
"eslint-config-prettier",
|
|
21
|
+
"eslint-plugin-boundaries",
|
|
22
|
+
"eslint-plugin-playwright"
|
|
23
|
+
];
|
|
24
|
+
if (projectType.typescript) {
|
|
25
|
+
plugins.push("typescript-eslint");
|
|
26
|
+
}
|
|
27
|
+
if (projectType.react || projectType.nextjs) {
|
|
28
|
+
plugins.push("eslint-plugin-react", "eslint-plugin-react-hooks", "eslint-plugin-jsx-a11y");
|
|
29
|
+
}
|
|
30
|
+
if (projectType.nextjs) {
|
|
31
|
+
plugins.push("@next/eslint-plugin-next");
|
|
32
|
+
}
|
|
33
|
+
if (projectType.astro) {
|
|
34
|
+
plugins.push("eslint-plugin-astro");
|
|
35
|
+
}
|
|
36
|
+
if (projectType.vue) {
|
|
37
|
+
plugins.push("eslint-plugin-vue");
|
|
38
|
+
}
|
|
39
|
+
if (projectType.svelte) {
|
|
40
|
+
plugins.push("eslint-plugin-svelte");
|
|
41
|
+
}
|
|
42
|
+
if (projectType.electron) {
|
|
43
|
+
plugins.push("@electron-toolkit/eslint-config");
|
|
44
|
+
}
|
|
45
|
+
if (projectType.vitest) {
|
|
46
|
+
plugins.push("@vitest/eslint-plugin");
|
|
47
|
+
}
|
|
48
|
+
return plugins;
|
|
49
|
+
}
|
|
50
|
+
function getMissingPackages(required, installed) {
|
|
51
|
+
return required.filter((pkg) => !(pkg in installed));
|
|
52
|
+
}
|
|
53
|
+
async function sync(options = {}) {
|
|
54
|
+
const cwd = process.cwd();
|
|
55
|
+
const safewordDir = join(cwd, ".safeword");
|
|
56
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
57
|
+
if (!exists(safewordDir)) {
|
|
58
|
+
if (!options.quiet) {
|
|
59
|
+
console.error("Not a safeword project. Run `safeword setup` first.");
|
|
60
|
+
}
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
if (!exists(packageJsonPath)) {
|
|
64
|
+
if (!options.quiet) {
|
|
65
|
+
console.error("No package.json found.");
|
|
66
|
+
}
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const packageJson = readJson(packageJsonPath);
|
|
70
|
+
if (!packageJson) {
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const projectType = detectProjectType(packageJson);
|
|
74
|
+
const devDeps = packageJson.devDependencies || {};
|
|
75
|
+
const requiredPlugins = getRequiredPlugins(projectType);
|
|
76
|
+
const missingPlugins = getMissingPackages(requiredPlugins, devDeps);
|
|
77
|
+
if (missingPlugins.length === 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!options.quiet) {
|
|
81
|
+
console.log(`Installing missing ESLint plugins: ${missingPlugins.join(", ")}`);
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
execSync(`npm install -D ${missingPlugins.join(" ")}`, {
|
|
85
|
+
cwd,
|
|
86
|
+
stdio: options.quiet ? "pipe" : "inherit"
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const pluginList = missingPlugins.join(" ");
|
|
90
|
+
console.error(`
|
|
91
|
+
\u2717 Failed to install ESLint plugins
|
|
92
|
+
`);
|
|
93
|
+
console.error(`Your project needs: ${pluginList}`);
|
|
94
|
+
console.error(`
|
|
95
|
+
Run manually when online:`);
|
|
96
|
+
console.error(` npm install -D ${pluginList}
|
|
97
|
+
`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
if (options.stage) {
|
|
101
|
+
try {
|
|
102
|
+
execSync("git add package.json package-lock.json", {
|
|
103
|
+
cwd,
|
|
104
|
+
stdio: "pipe"
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!options.quiet) {
|
|
110
|
+
console.log(`\u2713 Installed ${missingPlugins.length} ESLint plugin(s)`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
sync
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=sync-4XBMKLXS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/sync.ts"],"sourcesContent":["/**\n * Sync command - Keep linting plugins in sync with project dependencies\n *\n * Detects frameworks in package.json and ensures the corresponding ESLint plugins\n * are installed. Designed to be called from Husky pre-commit hook.\n *\n * Behavior:\n * - Fast exit when nothing needs to change\n * - Installs missing plugins\n * - Optionally stages modified files (--stage flag for pre-commit)\n * - Clear error message if installation fails\n */\n\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { exists, readJson } from '../utils/fs.js';\nimport { detectProjectType, type PackageJson, type ProjectType } from '../utils/project-detector.js';\n\nexport interface SyncOptions {\n quiet?: boolean;\n stage?: boolean;\n}\n\n/**\n * Maps project type flags to their required ESLint plugin packages\n */\nfunction getRequiredPlugins(projectType: ProjectType): string[] {\n const plugins: string[] = [\n // Always required (base plugins)\n 'eslint',\n '@eslint/js',\n 'eslint-plugin-import-x',\n 'eslint-plugin-sonarjs',\n '@microsoft/eslint-plugin-sdl',\n 'eslint-config-prettier',\n 'eslint-plugin-boundaries',\n 'eslint-plugin-playwright',\n ];\n\n // Framework plugins (detected at runtime by dynamic ESLint config)\n if (projectType.typescript) {\n plugins.push('typescript-eslint');\n }\n if (projectType.react || projectType.nextjs) {\n plugins.push('eslint-plugin-react', 'eslint-plugin-react-hooks', 'eslint-plugin-jsx-a11y');\n }\n if (projectType.nextjs) {\n plugins.push('@next/eslint-plugin-next');\n }\n if (projectType.astro) {\n plugins.push('eslint-plugin-astro');\n }\n if (projectType.vue) {\n plugins.push('eslint-plugin-vue');\n }\n if (projectType.svelte) {\n plugins.push('eslint-plugin-svelte');\n }\n if (projectType.electron) {\n plugins.push('@electron-toolkit/eslint-config');\n }\n if (projectType.vitest) {\n plugins.push('@vitest/eslint-plugin');\n }\n\n return plugins;\n}\n\n/**\n * Check which packages are missing from devDependencies\n */\nfunction getMissingPackages(required: string[], installed: Record<string, string>): string[] {\n return required.filter(pkg => !(pkg in installed));\n}\n\n/**\n * Sync linting configuration with current project dependencies\n */\nexport async function sync(options: SyncOptions = {}): Promise<void> {\n const cwd = process.cwd();\n const safewordDir = join(cwd, '.safeword');\n const packageJsonPath = join(cwd, 'package.json');\n\n // Must be in a safeword project\n if (!exists(safewordDir)) {\n if (!options.quiet) {\n console.error('Not a safeword project. Run `safeword setup` first.');\n }\n process.exit(1);\n }\n\n if (!exists(packageJsonPath)) {\n if (!options.quiet) {\n console.error('No package.json found.');\n }\n process.exit(1);\n }\n\n const packageJson = readJson<PackageJson>(packageJsonPath);\n if (!packageJson) {\n process.exit(1);\n }\n\n // Detect current project type\n const projectType = detectProjectType(packageJson);\n const devDeps = packageJson.devDependencies || {};\n\n // Check for missing plugins\n const requiredPlugins = getRequiredPlugins(projectType);\n const missingPlugins = getMissingPackages(requiredPlugins, devDeps);\n\n // Fast exit if nothing to install\n if (missingPlugins.length === 0) {\n return;\n }\n\n // Install missing plugins\n if (!options.quiet) {\n console.log(`Installing missing ESLint plugins: ${missingPlugins.join(', ')}`);\n }\n\n try {\n execSync(`npm install -D ${missingPlugins.join(' ')}`, {\n cwd,\n stdio: options.quiet ? 'pipe' : 'inherit',\n });\n } catch (error) {\n // Clear error message for network/install failures\n const pluginList = missingPlugins.join(' ');\n console.error(`\\n✗ Failed to install ESLint plugins\\n`);\n console.error(`Your project needs: ${pluginList}`);\n console.error(`\\nRun manually when online:`);\n console.error(` npm install -D ${pluginList}\\n`);\n process.exit(1);\n }\n\n // Stage modified files if --stage flag is set (for pre-commit hook)\n if (options.stage) {\n try {\n execSync('git add package.json package-lock.json', {\n cwd,\n stdio: 'pipe',\n });\n } catch {\n // Not in a git repo or git add failed - ignore\n }\n }\n\n if (!options.quiet) {\n console.log(`✓ Installed ${missingPlugins.length} ESLint plugin(s)`);\n }\n}\n"],"mappings":";;;;;;;;;AAaA,SAAS,YAAY;AACrB,SAAS,gBAAgB;AAYzB,SAAS,mBAAmB,aAAoC;AAC9D,QAAM,UAAoB;AAAA;AAAA,IAExB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,MAAI,YAAY,YAAY;AAC1B,YAAQ,KAAK,mBAAmB;AAAA,EAClC;AACA,MAAI,YAAY,SAAS,YAAY,QAAQ;AAC3C,YAAQ,KAAK,uBAAuB,6BAA6B,wBAAwB;AAAA,EAC3F;AACA,MAAI,YAAY,QAAQ;AACtB,YAAQ,KAAK,0BAA0B;AAAA,EACzC;AACA,MAAI,YAAY,OAAO;AACrB,YAAQ,KAAK,qBAAqB;AAAA,EACpC;AACA,MAAI,YAAY,KAAK;AACnB,YAAQ,KAAK,mBAAmB;AAAA,EAClC;AACA,MAAI,YAAY,QAAQ;AACtB,YAAQ,KAAK,sBAAsB;AAAA,EACrC;AACA,MAAI,YAAY,UAAU;AACxB,YAAQ,KAAK,iCAAiC;AAAA,EAChD;AACA,MAAI,YAAY,QAAQ;AACtB,YAAQ,KAAK,uBAAuB;AAAA,EACtC;AAEA,SAAO;AACT;AAKA,SAAS,mBAAmB,UAAoB,WAA6C;AAC3F,SAAO,SAAS,OAAO,SAAO,EAAE,OAAO,UAAU;AACnD;AAKA,eAAsB,KAAK,UAAuB,CAAC,GAAkB;AACnE,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,cAAc,KAAK,KAAK,WAAW;AACzC,QAAM,kBAAkB,KAAK,KAAK,cAAc;AAGhD,MAAI,CAAC,OAAO,WAAW,GAAG;AACxB,QAAI,CAAC,QAAQ,OAAO;AAClB,cAAQ,MAAM,qDAAqD;AAAA,IACrE;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,OAAO,eAAe,GAAG;AAC5B,QAAI,CAAC,QAAQ,OAAO;AAClB,cAAQ,MAAM,wBAAwB;AAAA,IACxC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,cAAc,SAAsB,eAAe;AACzD,MAAI,CAAC,aAAa;AAChB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,cAAc,kBAAkB,WAAW;AACjD,QAAM,UAAU,YAAY,mBAAmB,CAAC;AAGhD,QAAM,kBAAkB,mBAAmB,WAAW;AACtD,QAAM,iBAAiB,mBAAmB,iBAAiB,OAAO;AAGlE,MAAI,eAAe,WAAW,GAAG;AAC/B;AAAA,EACF;AAGA,MAAI,CAAC,QAAQ,OAAO;AAClB,YAAQ,IAAI,sCAAsC,eAAe,KAAK,IAAI,CAAC,EAAE;AAAA,EAC/E;AAEA,MAAI;AACF,aAAS,kBAAkB,eAAe,KAAK,GAAG,CAAC,IAAI;AAAA,MACrD;AAAA,MACA,OAAO,QAAQ,QAAQ,SAAS;AAAA,IAClC,CAAC;AAAA,EACH,SAAS,OAAO;AAEd,UAAM,aAAa,eAAe,KAAK,GAAG;AAC1C,YAAQ,MAAM;AAAA;AAAA,CAAwC;AACtD,YAAQ,MAAM,uBAAuB,UAAU,EAAE;AACjD,YAAQ,MAAM;AAAA,0BAA6B;AAC3C,YAAQ,MAAM,oBAAoB,UAAU;AAAA,CAAI;AAChD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,MAAI,QAAQ,OAAO;AACjB,QAAI;AACF,eAAS,0CAA0C;AAAA,QACjD;AAAA,QACA,OAAO;AAAA,MACT,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ,OAAO;AAClB,YAAQ,IAAI,oBAAe,eAAe,MAAM,mBAAmB;AAAA,EACrE;AACF;","names":[]}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isGitRepo
|
|
3
|
+
} from "./chunk-75FKNZUM.js";
|
|
1
4
|
import {
|
|
2
5
|
compareVersions
|
|
3
6
|
} from "./chunk-W66Z3C5H.js";
|
|
@@ -7,29 +10,30 @@ import {
|
|
|
7
10
|
import {
|
|
8
11
|
SETTINGS_HOOKS,
|
|
9
12
|
ensureAgentsMdLink,
|
|
10
|
-
filterOutSafewordHooks
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
filterOutSafewordHooks
|
|
14
|
+
} from "./chunk-IWWBZVHT.js";
|
|
15
|
+
import {
|
|
16
|
+
error,
|
|
17
|
+
header,
|
|
18
|
+
info,
|
|
19
|
+
listItem,
|
|
20
|
+
success
|
|
21
|
+
} from "./chunk-FRPJITGG.js";
|
|
14
22
|
import {
|
|
15
23
|
copyDir,
|
|
16
24
|
copyFile,
|
|
17
25
|
ensureDir,
|
|
18
|
-
error,
|
|
19
26
|
exists,
|
|
20
27
|
getTemplatesDir,
|
|
21
|
-
header,
|
|
22
|
-
info,
|
|
23
|
-
listItem,
|
|
24
28
|
makeScriptsExecutable,
|
|
25
29
|
readFileSafe,
|
|
26
|
-
success,
|
|
27
30
|
updateJson,
|
|
28
31
|
writeFile
|
|
29
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-ARIAOK2F.js";
|
|
30
33
|
|
|
31
34
|
// src/commands/upgrade.ts
|
|
32
35
|
import { join } from "path";
|
|
36
|
+
import { execSync } from "child_process";
|
|
33
37
|
async function upgrade() {
|
|
34
38
|
const cwd = process.cwd();
|
|
35
39
|
const safewordDir = join(cwd, ".safeword");
|
|
@@ -101,10 +105,25 @@ async function upgrade() {
|
|
|
101
105
|
updated.push(".claude/commands/");
|
|
102
106
|
success("Updated skills and commands");
|
|
103
107
|
if (isGitRepo(cwd)) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
const huskyDir = join(cwd, ".husky");
|
|
109
|
+
if (exists(huskyDir)) {
|
|
110
|
+
info("\nUpdating Husky pre-commit hook...");
|
|
111
|
+
const huskyPreCommit = join(huskyDir, "pre-commit");
|
|
112
|
+
writeFile(huskyPreCommit, "npx lint-staged\n");
|
|
113
|
+
updated.push(".husky/pre-commit");
|
|
114
|
+
success("Updated Husky pre-commit hook");
|
|
115
|
+
} else {
|
|
116
|
+
info("\nInitializing Husky...");
|
|
117
|
+
try {
|
|
118
|
+
execSync("npx husky init", { cwd, stdio: "pipe" });
|
|
119
|
+
const huskyPreCommit = join(cwd, ".husky", "pre-commit");
|
|
120
|
+
writeFile(huskyPreCommit, "npx lint-staged\n");
|
|
121
|
+
updated.push(".husky/pre-commit");
|
|
122
|
+
success("Initialized Husky with lint-staged");
|
|
123
|
+
} catch {
|
|
124
|
+
info("Husky not initialized (run: npx husky init)");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
108
127
|
}
|
|
109
128
|
header("Upgrade Complete");
|
|
110
129
|
info(`
|
|
@@ -131,4 +150,4 @@ Safeword upgraded to v${VERSION}`);
|
|
|
131
150
|
export {
|
|
132
151
|
upgrade
|
|
133
152
|
};
|
|
134
|
-
//# sourceMappingURL=upgrade-
|
|
153
|
+
//# sourceMappingURL=upgrade-P3WX3ODU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 readFileSafe,\n updateJson,\n copyDir,\n copyFile,\n getTemplatesDir,\n makeScriptsExecutable,\n} from '../utils/fs.js';\nimport { info, success, error, header, listItem } from '../utils/output.js';\nimport { isGitRepo } from '../utils/git.js';\nimport { execSync } from 'node:child_process';\nimport { compareVersions } from '../utils/version.js';\nimport { filterOutSafewordHooks } from '../utils/hooks.js';\nimport { ensureAgentsMdLink } from '../utils/agents-md.js';\nimport { SETTINGS_HOOKS } 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 const templatesDir = getTemplatesDir();\n\n // 1. Update .safeword directory\n info('\\nUpdating .safeword directory...');\n\n // Update core files from templates\n copyFile(join(templatesDir, 'SAFEWORD.md'), join(safewordDir, 'SAFEWORD.md'));\n writeFile(join(safewordDir, 'version'), VERSION);\n updated.push('.safeword/SAFEWORD.md');\n updated.push('.safeword/version');\n\n // Update guides, templates, prompts from templates\n copyDir(join(templatesDir, 'guides'), join(safewordDir, 'guides'));\n copyDir(join(templatesDir, 'doc-templates'), join(safewordDir, 'templates'));\n copyDir(join(templatesDir, 'prompts'), join(safewordDir, 'prompts'));\n\n // Update lib scripts and make executable\n copyDir(join(templatesDir, 'lib'), join(safewordDir, 'lib'));\n makeScriptsExecutable(join(safewordDir, 'lib'));\n\n // Update hook scripts and make executable\n copyDir(join(templatesDir, 'hooks'), join(safewordDir, 'hooks'));\n makeScriptsExecutable(join(safewordDir, 'hooks'));\n\n updated.push('.safeword/guides/');\n updated.push('.safeword/templates/');\n updated.push('.safeword/prompts/');\n updated.push('.safeword/hooks/');\n success('Updated .safeword directory');\n\n // 2. Verify AGENTS.md link\n info('\\nVerifying AGENTS.md...');\n const agentsMdResult = ensureAgentsMdLink(cwd);\n if (agentsMdResult === 'created') {\n updated.push('AGENTS.md');\n success('Created AGENTS.md');\n } else if (agentsMdResult === 'modified') {\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\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 existing non-safeword hooks\n for (const [event, newHooks] of Object.entries(SETTINGS_HOOKS)) {\n const existingHooks = (hooks[event] as unknown[]) ?? [];\n const nonSafewordHooks = filterOutSafewordHooks(existingHooks);\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 and commands\n info('\\nUpdating skills and commands...');\n\n copyDir(join(templatesDir, 'skills'), join(claudeDir, 'skills'));\n copyDir(join(templatesDir, 'commands'), join(claudeDir, 'commands'));\n\n updated.push('.claude/skills/');\n updated.push('.claude/commands/');\n success('Updated skills and commands');\n\n // 5. Update Husky hooks if repo exists\n if (isGitRepo(cwd)) {\n const huskyDir = join(cwd, '.husky');\n if (exists(huskyDir)) {\n info('\\nUpdating Husky pre-commit hook...');\n const huskyPreCommit = join(huskyDir, 'pre-commit');\n writeFile(huskyPreCommit, 'npx lint-staged\\n');\n updated.push('.husky/pre-commit');\n success('Updated Husky pre-commit hook');\n } else {\n // Initialize Husky if not present\n info('\\nInitializing Husky...');\n try {\n execSync('npx husky init', { cwd, stdio: 'pipe' });\n const huskyPreCommit = join(cwd, '.husky', 'pre-commit');\n writeFile(huskyPreCommit, 'npx lint-staged\\n');\n updated.push('.husky/pre-commit');\n success('Initialized Husky with lint-staged');\n } catch {\n info('Husky not initialized (run: npx husky init)');\n }\n }\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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safeword",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "CLI for setting up and managing safeword development environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"build": "tsup",
|
|
24
24
|
"dev": "tsup --watch",
|
|
25
25
|
"test": "vitest run",
|
|
26
|
+
"test:e2e": "vitest run tests/e2e/",
|
|
26
27
|
"test:watch": "vitest",
|
|
27
28
|
"test:coverage": "vitest run --coverage",
|
|
28
29
|
"typecheck": "tsc --noEmit",
|
package/templates/SAFEWORD.md
CHANGED
|
@@ -77,7 +77,7 @@ bash ./framework/scripts/setup-quality.sh # Quality review only
|
|
|
77
77
|
- `.safeword/SAFEWORD.md` - Global patterns and workflows (copied from this file)
|
|
78
78
|
- `.safeword/guides/` - Reference documentation
|
|
79
79
|
- `.claude/hooks/` - Hook scripts (with version comments)
|
|
80
|
-
- `.claude/commands/` - Slash commands (`/lint`, `/
|
|
80
|
+
- `.claude/commands/` - Slash commands (`/lint`, `/review`, `/architecture`)
|
|
81
81
|
- `.claude/settings.json` - Hook configuration (appends to existing)
|
|
82
82
|
- `SAFEWORD.md` or `CLAUDE.md` - Project context file with ALWAYS trigger for @./.safeword/SAFEWORD.md
|
|
83
83
|
- If CLAUDE.md exists → prepends trigger
|
|
@@ -375,11 +375,11 @@ last_modified: 2025-11-24T19:09:00Z
|
|
|
375
375
|
- Running semantic architecture review on code changes
|
|
376
376
|
- Setting up pre-commit hook for architecture enforcement
|
|
377
377
|
- Setting up CI workflow for PR architecture checks
|
|
378
|
-
- Understanding
|
|
378
|
+
- Understanding architecture review verdicts (clean/minor/refactor_needed)
|
|
379
379
|
- Checking for: god modules, leaky abstractions, misplaced logic, tight coupling
|
|
380
380
|
|
|
381
|
-
**
|
|
382
|
-
**Prompt:** `.safeword/prompts/
|
|
381
|
+
**Command:** `/architecture`
|
|
382
|
+
**Prompt:** `.safeword/prompts/architecture.md`
|
|
383
383
|
**CI Template:** `.safeword/templates/ci/architecture-check.yml`
|
|
384
384
|
|
|
385
385
|
---
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Review code against architecture guidelines and layer boundaries
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Architecture Review
|
|
6
|
+
|
|
7
|
+
Review code changes against project architecture guidelines and dependency boundaries.
|
|
8
|
+
|
|
9
|
+
## Instructions
|
|
10
|
+
|
|
11
|
+
When the user invokes this command:
|
|
12
|
+
|
|
13
|
+
1. **Read architecture docs** - Check `.safeword/guides/architecture-guide.md` if it exists
|
|
14
|
+
2. **Identify layers** - Map the project's architectural layers (types, utils, services, components, etc.)
|
|
15
|
+
3. **Check boundaries** - Verify imports follow the hierarchy:
|
|
16
|
+
- Lower layers cannot import from higher layers
|
|
17
|
+
- `types` → `utils` → `lib` → `hooks/services` → `components` → `features` → `app`
|
|
18
|
+
4. **Validate dependencies** - Ensure dependency directions are correct
|
|
19
|
+
5. **Report violations** - List any boundary violations with specific file:line references
|
|
20
|
+
|
|
21
|
+
## Example Usage
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
/architecture
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then: "Review my recent changes" or "Check if the new auth module follows our architecture"
|
|
@@ -1,73 +1,34 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Run
|
|
2
|
+
description: Run linters and formatters to fix code style issues
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Lint
|
|
6
6
|
|
|
7
|
-
Run the full linting and formatting suite
|
|
7
|
+
Run the full linting and formatting suite using the npm scripts configured by safeword.
|
|
8
8
|
|
|
9
9
|
## Instructions
|
|
10
10
|
|
|
11
|
-
Run
|
|
12
|
-
|
|
13
|
-
### JavaScript/TypeScript (if package.json exists)
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
# Prettier (format all supported files)
|
|
17
|
-
npx prettier --write "**/*.{js,jsx,ts,tsx,mjs,cjs,vue,svelte,astro,json,css,scss,html,yaml,yml,graphql,md}" --ignore-path .gitignore 2>/dev/null || true
|
|
18
|
-
|
|
19
|
-
# ESLint (lint and fix JS/TS files)
|
|
20
|
-
npx eslint --fix "**/*.{js,jsx,ts,tsx,mjs,cjs,vue,svelte,astro}" --ignore-path .gitignore 2>&1 || true
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### Markdown (if .md files exist)
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
# markdownlint
|
|
27
|
-
npx markdownlint-cli2 --fix "**/*.md" "#node_modules" 2>&1 || true
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Python (if .py files exist and ruff is installed)
|
|
11
|
+
Run these npm scripts in order. Each script is configured in package.json by `safeword setup`.
|
|
31
12
|
|
|
32
13
|
```bash
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
ruff format .
|
|
36
|
-
ruff check --fix .
|
|
37
|
-
fi
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Go (if .go files exist)
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
# Check if go is available
|
|
44
|
-
if command -v go &> /dev/null; then
|
|
45
|
-
go fmt ./...
|
|
46
|
-
# Run go vet for static analysis
|
|
47
|
-
go vet ./... 2>&1 || true
|
|
48
|
-
fi
|
|
49
|
-
```
|
|
14
|
+
# 1. ESLint - fix code quality issues
|
|
15
|
+
npm run lint 2>&1 || true
|
|
50
16
|
|
|
51
|
-
|
|
17
|
+
# 2. Prettier - format all files
|
|
18
|
+
npm run format 2>&1 || true
|
|
52
19
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if command -v cargo &> /dev/null; then
|
|
56
|
-
cargo fmt
|
|
57
|
-
# Run clippy for lints
|
|
58
|
-
cargo clippy --fix --allow-dirty --allow-staged 2>&1 || true
|
|
59
|
-
fi
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### TypeScript Type Checking (if tsconfig.json exists)
|
|
20
|
+
# 3. Markdownlint - fix markdown issues
|
|
21
|
+
npm run lint:md 2>&1 || true
|
|
63
22
|
|
|
64
|
-
|
|
65
|
-
npx tsc --noEmit 2>&1 || true
|
|
23
|
+
# 4. TypeScript type check (if tsconfig.json exists)
|
|
24
|
+
[ -f tsconfig.json ] && npx tsc --noEmit 2>&1 || true
|
|
66
25
|
```
|
|
67
26
|
|
|
68
27
|
## Summary
|
|
69
28
|
|
|
70
29
|
After running, report:
|
|
71
|
-
1.
|
|
72
|
-
2. Any
|
|
30
|
+
1. Any ESLint errors that couldn't be auto-fixed
|
|
31
|
+
2. Any formatting issues
|
|
73
32
|
3. Type errors (if TypeScript)
|
|
33
|
+
|
|
34
|
+
Note: File patterns are defined in the npm scripts (single source of truth), not here.
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Deep code
|
|
2
|
+
description: Deep code review with web research against latest docs and versions
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Quality Review
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Perform a deep code review with web research to verify against latest documentation.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Instructions
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- Need deeper analysis beyond automatic hook
|
|
13
|
-
- Working on projects without SAFEWORD.md/CLAUDE.md
|
|
11
|
+
When the user invokes this command:
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
1. **Identify the scope** - Ask what code to review if not specified
|
|
14
|
+
2. **Fetch current docs** - Use WebFetch/WebSearch for libraries being used
|
|
15
|
+
3. **Check versions** - Verify dependencies are current and secure
|
|
16
|
+
4. **Analyze deeply** - Look for:
|
|
17
|
+
- Performance issues
|
|
18
|
+
- Security vulnerabilities
|
|
19
|
+
- Deprecated APIs
|
|
20
|
+
- Better alternatives
|
|
21
|
+
- Missing error handling
|
|
22
|
+
5. **Report findings** - Provide actionable recommendations
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
2. Checks latest versions (WebSearch)
|
|
19
|
-
3. Provides deep analysis (performance, security, alternatives)
|
|
20
|
-
|
|
21
|
-
## Usage
|
|
24
|
+
## Example Usage
|
|
22
25
|
|
|
23
26
|
```
|
|
24
27
|
/quality-review
|
|
25
28
|
```
|
|
26
29
|
|
|
27
|
-
Then
|
|
30
|
+
Then: "Review the authentication implementation" or "Check if my React hooks are following best practices"
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Safeword: Auto-lint changed files (PostToolUse)
|
|
3
3
|
# Silently auto-fixes, only outputs unfixable errors
|
|
4
|
+
#
|
|
5
|
+
# SYNC: Keep file patterns in sync with LINT_STAGED_CONFIG in:
|
|
6
|
+
# packages/cli/src/templates/content.ts
|
|
7
|
+
#
|
|
8
|
+
# This hook is intentionally simple - ESLint's config handles
|
|
9
|
+
# framework-specific rules (React, Vue, Svelte, Astro, etc.)
|
|
4
10
|
|
|
5
11
|
# Require jq for JSON parsing
|
|
6
12
|
command -v jq &> /dev/null || exit 0
|
|
@@ -14,71 +20,26 @@ file=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.notebook_path
|
|
|
14
20
|
# Change to project directory
|
|
15
21
|
[ -n "$CLAUDE_PROJECT_DIR" ] && cd "$CLAUDE_PROJECT_DIR"
|
|
16
22
|
|
|
17
|
-
# Determine
|
|
23
|
+
# Determine linter based on file extension
|
|
18
24
|
case "$file" in
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
npx prettier --write "$file" 2>/dev/null
|
|
22
|
-
|
|
23
|
-
# ESLint --fix (capture unfixable errors)
|
|
25
|
+
# JS/TS and framework files - ESLint first (fix code), then Prettier (format)
|
|
26
|
+
*.js|*.jsx|*.ts|*.tsx|*.mjs|*.mts|*.cjs|*.cts|*.vue|*.svelte|*.astro)
|
|
24
27
|
errors=$(npx eslint --fix "$file" 2>&1)
|
|
25
|
-
|
|
26
|
-
if [ $exit_code -ne 0 ] && [ -n "$errors" ]; then
|
|
27
|
-
echo "$errors"
|
|
28
|
-
fi
|
|
29
|
-
;;
|
|
30
|
-
|
|
31
|
-
*.vue|*.svelte)
|
|
32
|
-
# Vue/Svelte: Prettier + ESLint (requires eslint-plugin-vue or eslint-plugin-svelte)
|
|
28
|
+
[ $? -ne 0 ] && [ -n "$errors" ] && echo "$errors"
|
|
33
29
|
npx prettier --write "$file" 2>/dev/null
|
|
34
|
-
|
|
35
|
-
# ESLint --fix (capture unfixable errors)
|
|
36
|
-
errors=$(npx eslint --fix "$file" 2>&1)
|
|
37
|
-
exit_code=$?
|
|
38
|
-
if [ $exit_code -ne 0 ] && [ -n "$errors" ]; then
|
|
39
|
-
echo "$errors"
|
|
40
|
-
fi
|
|
41
30
|
;;
|
|
42
31
|
|
|
32
|
+
# Markdown - markdownlint first, then Prettier
|
|
43
33
|
*.md)
|
|
44
|
-
# Markdownlint (capture unfixable errors)
|
|
45
34
|
errors=$(npx markdownlint-cli2 --fix "$file" 2>&1)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
echo "$errors"
|
|
49
|
-
fi
|
|
35
|
+
[ $? -ne 0 ] && [ -n "$errors" ] && echo "$errors"
|
|
36
|
+
npx prettier --write "$file" 2>/dev/null
|
|
50
37
|
;;
|
|
51
38
|
|
|
39
|
+
# Other supported formats - prettier only
|
|
52
40
|
*.json|*.css|*.scss|*.html|*.yaml|*.yml|*.graphql)
|
|
53
|
-
# Prettier only (silent, ignore errors)
|
|
54
41
|
npx prettier --write "$file" 2>/dev/null
|
|
55
42
|
;;
|
|
56
|
-
|
|
57
|
-
*.py)
|
|
58
|
-
# Python: ruff format + ruff check --fix
|
|
59
|
-
if command -v ruff &> /dev/null; then
|
|
60
|
-
ruff format "$file" 2>/dev/null
|
|
61
|
-
errors=$(ruff check --fix "$file" 2>&1)
|
|
62
|
-
exit_code=$?
|
|
63
|
-
if [ $exit_code -ne 0 ] && [ -n "$errors" ]; then
|
|
64
|
-
echo "$errors"
|
|
65
|
-
fi
|
|
66
|
-
fi
|
|
67
|
-
;;
|
|
68
|
-
|
|
69
|
-
*.go)
|
|
70
|
-
# Go: gofmt
|
|
71
|
-
if command -v gofmt &> /dev/null; then
|
|
72
|
-
gofmt -w "$file" 2>/dev/null
|
|
73
|
-
fi
|
|
74
|
-
;;
|
|
75
|
-
|
|
76
|
-
*.rs)
|
|
77
|
-
# Rust: rustfmt
|
|
78
|
-
if command -v rustfmt &> /dev/null; then
|
|
79
|
-
rustfmt "$file" 2>/dev/null
|
|
80
|
-
fi
|
|
81
|
-
;;
|
|
82
43
|
esac
|
|
83
44
|
|
|
84
45
|
exit 0
|
|
@@ -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 copyFileSync,\n} from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// Get the directory of this module (for locating templates)\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Get path to bundled templates directory.\n * Works in both development (src/) and production (dist/) contexts.\n */\nexport function getTemplatesDir(): string {\n // When running from dist/, __dirname is packages/cli/dist/\n // Templates are at packages/cli/templates/ (one level up)\n const fromDist = join(__dirname, '..', 'templates');\n\n // Fallback path for edge cases\n const fallback = join(__dirname, '..', '..', 'templates');\n\n if (existsSync(fromDist)) return fromDist;\n if (existsSync(fallback)) return fallback;\n\n throw new Error('Templates directory not found');\n}\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 a single file\n */\nexport function copyFile(src: string, dest: string): void {\n ensureDir(dirname(dest));\n copyFileSync(src, dest);\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 copyFileSync(srcPath, destPath);\n }\n }\n}\n\n/**\n * Make file executable\n */\nexport function makeExecutable(path: string): void {\n chmodSync(path, 0o755);\n}\n\n/**\n * Make all shell scripts in a directory executable\n */\nexport function makeScriptsExecutable(dirPath: string): void {\n if (!existsSync(dirPath)) return;\n for (const file of readdirSync(dirPath)) {\n if (file.endsWith('.sh')) {\n chmodSync(join(dirPath, file), 0o755);\n }\n }\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":"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","names":[]}
|