safeword 0.10.1 → 0.11.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.
@@ -4,18 +4,20 @@ import {
4
4
  import {
5
5
  SAFEWORD_SCHEMA,
6
6
  createProjectContext,
7
+ reconcile
8
+ } from "./chunk-KL2JTWK6.js";
9
+ import {
10
+ VERSION
11
+ } from "./chunk-ORQHKDT2.js";
12
+ import {
7
13
  exists,
8
14
  header,
9
15
  info,
10
16
  keyValue,
11
17
  readFileSafe,
12
- reconcile,
13
18
  success,
14
19
  warn
15
- } from "./chunk-H7PCVPAC.js";
16
- import {
17
- VERSION
18
- } from "./chunk-ORQHKDT2.js";
20
+ } from "./chunk-DYLHQBW3.js";
19
21
 
20
22
  // src/commands/check.ts
21
23
  import nodePath from "path";
@@ -162,4 +164,4 @@ async function check(options) {
162
164
  export {
163
165
  check
164
166
  };
165
- //# sourceMappingURL=check-IBMCNPDE.js.map
167
+ //# sourceMappingURL=check-EQ3IJPBM.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n *\n * Uses reconcile() with dryRun to detect missing files and configuration issues.\n */\n\nimport nodePath from 'node:path';\n\nimport { reconcile } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { header, info, keyValue, success, warn } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\n/**\n * Check for missing files from write actions\n * @param cwd\n * @param actions\n */\nfunction findMissingFiles(cwd: string, actions: { type: string; path: string }[]): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type === 'write' && !exists(nodePath.join(cwd, action.path))) {\n issues.push(`Missing: ${action.path}`);\n }\n }\n return issues;\n}\n\n/**\n * Check for missing text patch markers\n * @param cwd\n * @param actions\n */\nfunction findMissingPatches(\n cwd: string,\n actions: { type: string; path: string; definition?: { marker: string } }[],\n): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type !== 'text-patch') continue;\n\n const fullPath = nodePath.join(cwd, action.path);\n if (exists(fullPath)) {\n const content = readFileSafe(fullPath) ?? '';\n if (action.definition && !content.includes(action.definition.marker)) {\n issues.push(`${action.path} missing safeword link`);\n }\n } else {\n issues.push(`${action.path} file missing`);\n }\n }\n return issues;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | undefined;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | undefined;\n issues: string[];\n missingPackages: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n * @param timeout\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | undefined> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, 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 undefined;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Check project configuration health using reconcile dryRun\n * @param cwd\n */\nasync function checkHealth(cwd: string): Promise<HealthStatus> {\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDirectory)) {\n return {\n configured: false,\n projectVersion: undefined,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues: [],\n missingPackages: [],\n };\n }\n\n // Read project version\n const versionPath = nodePath.join(safewordDirectory, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? undefined;\n\n // Use reconcile with dryRun to detect issues\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx, { dryRun: true });\n\n // Collect issues from write actions and text patches\n const issues: string[] = [\n ...findMissingFiles(cwd, result.actions),\n ...findMissingPatches(cwd, result.actions),\n ];\n\n // Check for missing .claude/settings.json\n if (!exists(nodePath.join(cwd, '.claude', 'settings.json'))) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues,\n missingPackages: result.packagesToInstall,\n };\n}\n\n/**\n * Check for CLI updates and report status\n * @param health\n */\nasync function reportUpdateStatus(health: HealthStatus): Promise<void> {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (!latestVersion) {\n warn(\"Couldn't check for updates (offline?)\");\n return;\n }\n\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}\n\n/**\n * Compare project version vs CLI version and report\n * @param health\n */\nfunction reportVersionMismatch(health: HealthStatus): void {\n if (!health.projectVersion) return;\n\n if (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 (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\n/**\n * Report issues or success\n * @param health\n */\nfunction reportHealthSummary(health: HealthStatus): void {\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 return;\n }\n\n if (health.missingPackages.length > 0) {\n header('Missing Packages');\n info(`${health.missingPackages.length} linting packages not installed`);\n info('Run `safeword upgrade` to install missing packages');\n return;\n }\n\n success('\\nConfiguration is healthy');\n}\n\n/**\n *\n * @param options\n */\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = await 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('\\nSkipped update check (offline mode)');\n } else {\n await reportUpdateStatus(health);\n }\n\n reportVersionMismatch(health);\n reportHealthSummary(health);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n *\n * Uses reconcile() with dryRun to detect missing files and configuration issues.\n */\n\nimport nodePath from 'node:path';\n\nimport { reconcile } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { header, info, keyValue, success, warn } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\n/**\n * Check for missing files from write actions\n * @param cwd\n * @param actions\n */\nfunction findMissingFiles(cwd: string, actions: { type: string; path: string }[]): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type === 'write' && !exists(nodePath.join(cwd, action.path))) {\n issues.push(`Missing: ${action.path}`);\n }\n }\n return issues;\n}\n\n/**\n * Check for missing text patch markers\n * @param cwd\n * @param actions\n */\nfunction findMissingPatches(\n cwd: string,\n actions: { type: string; path: string; definition?: { marker: string } }[],\n): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type !== 'text-patch') continue;\n\n const fullPath = nodePath.join(cwd, action.path);\n if (exists(fullPath)) {\n const content = readFileSafe(fullPath) ?? '';\n if (action.definition && !content.includes(action.definition.marker)) {\n issues.push(`${action.path} missing safeword link`);\n }\n } else {\n issues.push(`${action.path} file missing`);\n }\n }\n return issues;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | undefined;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | undefined;\n issues: string[];\n missingPackages: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n * @param timeout\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | undefined> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, 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 undefined;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Check project configuration health using reconcile dryRun\n * @param cwd\n */\nasync function checkHealth(cwd: string): Promise<HealthStatus> {\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDirectory)) {\n return {\n configured: false,\n projectVersion: undefined,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues: [],\n missingPackages: [],\n };\n }\n\n // Read project version\n const versionPath = nodePath.join(safewordDirectory, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? undefined;\n\n // Use reconcile with dryRun to detect issues\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx, { dryRun: true });\n\n // Collect issues from write actions and text patches\n const issues: string[] = [\n ...findMissingFiles(cwd, result.actions),\n ...findMissingPatches(cwd, result.actions),\n ];\n\n // Check for missing .claude/settings.json\n if (!exists(nodePath.join(cwd, '.claude', 'settings.json'))) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues,\n missingPackages: result.packagesToInstall,\n };\n}\n\n/**\n * Check for CLI updates and report status\n * @param health\n */\nasync function reportUpdateStatus(health: HealthStatus): Promise<void> {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (!latestVersion) {\n warn(\"Couldn't check for updates (offline?)\");\n return;\n }\n\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}\n\n/**\n * Compare project version vs CLI version and report\n * @param health\n */\nfunction reportVersionMismatch(health: HealthStatus): void {\n if (!health.projectVersion) return;\n\n if (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 (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\n/**\n * Report issues or success\n * @param health\n */\nfunction reportHealthSummary(health: HealthStatus): void {\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 return;\n }\n\n if (health.missingPackages.length > 0) {\n header('Missing Packages');\n info(`${health.missingPackages.length} linting packages not installed`);\n info('Run `safeword upgrade` to install missing packages');\n return;\n }\n\n success('\\nConfiguration is healthy');\n}\n\n/**\n *\n * @param options\n */\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = await 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('\\nSkipped update check (offline mode)');\n } else {\n await reportUpdateStatus(health);\n }\n\n reportVersionMismatch(health);\n reportHealthSummary(health);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAMA,OAAO,cAAc;AAmBrB,SAAS,iBAAiB,KAAa,SAAqD;AAC1F,QAAM,SAAmB,CAAC;AAC1B,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,SAAS,WAAW,CAAC,OAAO,SAAS,KAAK,KAAK,OAAO,IAAI,CAAC,GAAG;AACvE,aAAO,KAAK,YAAY,OAAO,IAAI,EAAE;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,mBACP,KACA,SACU;AACV,QAAM,SAAmB,CAAC;AAC1B,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,SAAS,aAAc;AAElC,UAAM,WAAW,SAAS,KAAK,KAAK,OAAO,IAAI;AAC/C,QAAI,OAAO,QAAQ,GAAG;AACpB,YAAM,UAAU,aAAa,QAAQ,KAAK;AAC1C,UAAI,OAAO,cAAc,CAAC,QAAQ,SAAS,OAAO,WAAW,MAAM,GAAG;AACpE,eAAO,KAAK,GAAG,OAAO,IAAI,wBAAwB;AAAA,MACpD;AAAA,IACF,OAAO;AACL,aAAO,KAAK,GAAG,OAAO,IAAI,eAAe;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAgBA,eAAe,mBAAmB,UAAU,KAAmC;AAC7E,MAAI;AACF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM;AACjC,iBAAW,MAAM;AAAA,IACnB,GAAG,OAAO;AAEV,UAAM,WAAW,MAAM,MAAM,8CAA8C;AAAA,MACzE,QAAQ,WAAW;AAAA,IACrB,CAAC;AAED,iBAAa,SAAS;AAEtB,QAAI,CAAC,SAAS,GAAI,QAAO;AAEzB,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,WAAO,KAAK,WAAW;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAe,YAAY,KAAoC;AAC7D,QAAM,oBAAoB,SAAS,KAAK,KAAK,WAAW;AAGxD,MAAI,CAAC,OAAO,iBAAiB,GAAG;AAC9B,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,eAAe;AAAA,MACf,QAAQ,CAAC;AAAA,MACT,iBAAiB,CAAC;AAAA,IACpB;AAAA,EACF;AAGA,QAAM,cAAc,SAAS,KAAK,mBAAmB,SAAS;AAC9D,QAAM,iBAAiB,aAAa,WAAW,GAAG,KAAK,KAAK;AAG5D,QAAM,MAAM,qBAAqB,GAAG;AACpC,QAAM,SAAS,MAAM,UAAU,iBAAiB,WAAW,KAAK,EAAE,QAAQ,KAAK,CAAC;AAGhF,QAAM,SAAmB;AAAA,IACvB,GAAG,iBAAiB,KAAK,OAAO,OAAO;AAAA,IACvC,GAAG,mBAAmB,KAAK,OAAO,OAAO;AAAA,EAC3C;AAGA,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,WAAW,eAAe,CAAC,GAAG;AAC3D,WAAO,KAAK,gCAAgC;AAAA,EAC9C;AAEA,SAAO;AAAA,IACL,YAAY;AAAA,IACZ;AAAA,IACA,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf;AAAA,IACA,iBAAiB,OAAO;AAAA,EAC1B;AACF;AAMA,eAAe,mBAAmB,QAAqC;AACrE,OAAK,2BAA2B;AAChC,QAAM,gBAAgB,MAAM,mBAAmB;AAE/C,MAAI,CAAC,eAAe;AAClB,SAAK,uCAAuC;AAC5C;AAAA,EACF;AAEA,SAAO,gBAAgB;AACvB,SAAO,kBAAkB,eAAe,OAAO,YAAY,aAAa;AAExE,MAAI,OAAO,iBAAiB;AAC1B,SAAK,sBAAsB,aAAa,EAAE;AAC1C,SAAK,0CAA0C;AAAA,EACjD,OAAO;AACL,YAAQ,mBAAmB;AAAA,EAC7B;AACF;AAMA,SAAS,sBAAsB,QAA4B;AACzD,MAAI,CAAC,OAAO,eAAgB;AAE5B,MAAI,eAAe,OAAO,YAAY,OAAO,cAAc,GAAG;AAC5D,SAAK,oBAAoB,OAAO,cAAc,yBAAyB,OAAO,UAAU,GAAG;AAC3F,SAAK,4BAA4B;AAAA,EACnC,WAAW,eAAe,OAAO,gBAAgB,OAAO,UAAU,GAAG;AACnE,SAAK;AAAA,qCAAwC;AAC7C;AAAA,MACE,4CAA4C,OAAO,cAAc,QAAQ,OAAO,UAAU;AAAA,IAC5F;AAAA,EACF;AACF;AAMA,SAAS,oBAAoB,QAA4B;AACvD,MAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,WAAO,cAAc;AACrB,eAAW,SAAS,OAAO,QAAQ;AACjC,WAAK,KAAK;AAAA,IACZ;AACA,SAAK,kDAAkD;AACvD;AAAA,EACF;AAEA,MAAI,OAAO,gBAAgB,SAAS,GAAG;AACrC,WAAO,kBAAkB;AACzB,SAAK,GAAG,OAAO,gBAAgB,MAAM,iCAAiC;AACtE,SAAK,oDAAoD;AACzD;AAAA,EACF;AAEA,UAAQ,4BAA4B;AACtC;AAMA,eAAsB,MAAM,SAAsC;AAChE,QAAM,MAAM,QAAQ,IAAI;AAExB,SAAO,uBAAuB;AAE9B,QAAM,SAAS,MAAM,YAAY,GAAG;AAGpC,MAAI,CAAC,OAAO,YAAY;AACtB,SAAK,qDAAqD;AAC1D;AAAA,EACF;AAGA,WAAS,gBAAgB,IAAI,OAAO,UAAU,EAAE;AAChD,WAAS,kBAAkB,OAAO,iBAAiB,IAAI,OAAO,cAAc,KAAK,SAAS;AAG1F,MAAI,QAAQ,SAAS;AACnB,SAAK,uCAAuC;AAAA,EAC9C,OAAO;AACL,UAAM,mBAAmB,MAAM;AAAA,EACjC;AAEA,wBAAsB,MAAM;AAC5B,sBAAoB,MAAM;AAC5B;","names":[]}
@@ -0,0 +1,132 @@
1
+ // src/utils/fs.ts
2
+ import {
3
+ chmodSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ rmdirSync,
9
+ rmSync,
10
+ writeFileSync
11
+ } from "fs";
12
+ import nodePath from "path";
13
+ import { fileURLToPath } from "url";
14
+ var __dirname = nodePath.dirname(fileURLToPath(import.meta.url));
15
+ function getTemplatesDirectory() {
16
+ const knownTemplateFile = "SAFEWORD.md";
17
+ const candidates = [
18
+ nodePath.join(__dirname, "..", "templates"),
19
+ // From dist/ (flat bundled)
20
+ nodePath.join(__dirname, "..", "..", "templates"),
21
+ // From src/utils/ or dist/utils/
22
+ nodePath.join(__dirname, "templates")
23
+ // Direct sibling (unlikely but safe)
24
+ ];
25
+ for (const candidate of candidates) {
26
+ if (existsSync(nodePath.join(candidate, knownTemplateFile))) {
27
+ return candidate;
28
+ }
29
+ }
30
+ throw new Error("Templates directory not found");
31
+ }
32
+ function exists(path) {
33
+ return existsSync(path);
34
+ }
35
+ function ensureDirectory(path) {
36
+ if (!existsSync(path)) {
37
+ mkdirSync(path, { recursive: true });
38
+ }
39
+ }
40
+ function readFile(path) {
41
+ return readFileSync(path, "utf8");
42
+ }
43
+ function readFileSafe(path) {
44
+ if (!existsSync(path)) return void 0;
45
+ return readFileSync(path, "utf8");
46
+ }
47
+ function writeFile(path, content) {
48
+ ensureDirectory(nodePath.dirname(path));
49
+ writeFileSync(path, content);
50
+ }
51
+ function remove(path) {
52
+ if (existsSync(path)) {
53
+ rmSync(path, { recursive: true, force: true });
54
+ }
55
+ }
56
+ function removeIfEmpty(path) {
57
+ if (!existsSync(path)) return false;
58
+ try {
59
+ rmdirSync(path);
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ function makeScriptsExecutable(dirPath) {
66
+ if (!existsSync(dirPath)) return;
67
+ for (const file of readdirSync(dirPath)) {
68
+ if (file.endsWith(".sh")) {
69
+ chmodSync(nodePath.join(dirPath, file), 493);
70
+ }
71
+ }
72
+ }
73
+ function readJson(path) {
74
+ const content = readFileSafe(path);
75
+ if (!content) return void 0;
76
+ try {
77
+ return JSON.parse(content);
78
+ } catch {
79
+ return void 0;
80
+ }
81
+ }
82
+ function writeJson(path, data) {
83
+ writeFile(path, `${JSON.stringify(data, void 0, 2)}
84
+ `);
85
+ }
86
+
87
+ // src/utils/output.ts
88
+ function info(message) {
89
+ console.log(message);
90
+ }
91
+ function success(message) {
92
+ console.log(`\u2713 ${message}`);
93
+ }
94
+ function warn(message) {
95
+ console.warn(`\u26A0 ${message}`);
96
+ }
97
+ function error(message) {
98
+ console.error(`\u2717 ${message}`);
99
+ }
100
+ function header(title) {
101
+ console.log(`
102
+ ${title}`);
103
+ console.log("\u2500".repeat(title.length));
104
+ }
105
+ function listItem(item, indent = 2) {
106
+ console.log(`${" ".repeat(indent)}\u2022 ${item}`);
107
+ }
108
+ function keyValue(key, value) {
109
+ console.log(` ${key}: ${value}`);
110
+ }
111
+
112
+ export {
113
+ getTemplatesDirectory,
114
+ exists,
115
+ ensureDirectory,
116
+ readFile,
117
+ readFileSafe,
118
+ writeFile,
119
+ remove,
120
+ removeIfEmpty,
121
+ makeScriptsExecutable,
122
+ readJson,
123
+ writeJson,
124
+ info,
125
+ success,
126
+ warn,
127
+ error,
128
+ header,
129
+ listItem,
130
+ keyValue
131
+ };
132
+ //# sourceMappingURL=chunk-DYLHQBW3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/fs.ts","../src/utils/output.ts"],"sourcesContent":["/**\n * File system utilities for CLI operations\n */\n\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n rmdirSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport nodePath from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// Get the directory of this module (for locating templates)\nconst __dirname = nodePath.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 *\n * Note: We check for SAFEWORD.md to distinguish from src/templates/ which\n * contains TypeScript source files (config.ts, content.ts).\n *\n * Path resolution (bundled with tsup):\n * - From dist/chunk-*.js: __dirname = packages/cli/dist/ → ../templates\n */\nexport function getTemplatesDirectory(): string {\n const knownTemplateFile = 'SAFEWORD.md';\n\n // Try different relative paths - the bundled code ends up in dist/ directly (flat)\n // while source is in src/utils/\n const candidates = [\n nodePath.join(__dirname, '..', 'templates'), // From dist/ (flat bundled)\n nodePath.join(__dirname, '..', '..', 'templates'), // From src/utils/ or dist/utils/\n nodePath.join(__dirname, 'templates'), // Direct sibling (unlikely but safe)\n ];\n\n for (const candidate of candidates) {\n if (existsSync(nodePath.join(candidate, knownTemplateFile))) {\n return candidate;\n }\n }\n\n throw new Error('Templates directory not found');\n}\n\n/**\n * Check if a path exists\n * @param path\n */\nexport function exists(path: string): boolean {\n return existsSync(path);\n}\n\n/**\n * Create directory recursively\n * @param path\n */\nexport function ensureDirectory(path: string): void {\n if (!existsSync(path)) {\n mkdirSync(path, { recursive: true });\n }\n}\n\n/**\n * Read file as string\n * @param path\n */\nexport function readFile(path: string): string {\n return readFileSync(path, 'utf8');\n}\n\n/**\n * Read file as string, return null if not exists\n * @param path\n */\nexport function readFileSafe(path: string): string | undefined {\n if (!existsSync(path)) return undefined;\n return readFileSync(path, 'utf8');\n}\n\n/**\n * Write file, creating parent directories if needed\n * @param path\n * @param content\n */\nexport function writeFile(path: string, content: string): void {\n ensureDirectory(nodePath.dirname(path));\n writeFileSync(path, content);\n}\n\n/**\n * Remove file or directory recursively\n * @param path\n */\nexport function remove(path: string): void {\n if (existsSync(path)) {\n rmSync(path, { recursive: true, force: true });\n }\n}\n\n/**\n * Remove directory only if empty, returns true if removed\n * @param path\n */\nexport function removeIfEmpty(path: string): boolean {\n if (!existsSync(path)) return false;\n try {\n rmdirSync(path); // Non-recursive, throws if not empty\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Make all shell scripts in a directory executable\n * @param dirPath\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(nodePath.join(dirPath, file), 0o755);\n }\n }\n}\n\n/**\n * Read JSON file\n * @param path\n */\nexport function readJson(path: string): unknown {\n const content = readFileSafe(path);\n if (!content) return undefined;\n try {\n return JSON.parse(content) as unknown;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Write JSON file with formatting\n * @param path\n * @param data\n */\nexport function writeJson(path: string, data: unknown): void {\n writeFile(path, `${JSON.stringify(data, undefined, 2)}\\n`);\n}\n","/**\n * Console output utilities for consistent CLI messaging\n */\n\n/**\n * Print info message\n * @param message\n */\nexport function info(message: string): void {\n console.log(message);\n}\n\n/**\n * Print success message\n * @param message\n */\nexport function success(message: string): void {\n console.log(`✓ ${message}`);\n}\n\n/**\n * Print warning message\n * @param message\n */\nexport function warn(message: string): void {\n console.warn(`⚠ ${message}`);\n}\n\n/**\n * Print error message to stderr\n * @param message\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 * @param title\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 * @param item\n * @param indent\n */\nexport function listItem(item: string, indent = 2): void {\n console.log(`${' '.repeat(indent)}• ${item}`);\n}\n\n/**\n * Print key-value pair\n * @param key\n * @param value\n */\nexport function keyValue(key: string, value: string): void {\n console.log(` ${key}: ${value}`);\n}\n"],"mappings":"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","names":[]}
@@ -1,95 +1,22 @@
1
1
  import {
2
2
  VERSION
3
3
  } from "./chunk-ORQHKDT2.js";
4
-
5
- // src/utils/fs.ts
6
4
  import {
7
- chmodSync,
8
- existsSync,
9
- mkdirSync,
10
- readdirSync,
11
- readFileSync,
12
- rmdirSync,
13
- rmSync,
14
- writeFileSync
15
- } from "fs";
16
- import nodePath from "path";
17
- import { fileURLToPath } from "url";
18
- var __dirname = nodePath.dirname(fileURLToPath(import.meta.url));
19
- function getTemplatesDirectory() {
20
- const knownTemplateFile = "SAFEWORD.md";
21
- const candidates = [
22
- nodePath.join(__dirname, "..", "templates"),
23
- // From dist/ (flat bundled)
24
- nodePath.join(__dirname, "..", "..", "templates"),
25
- // From src/utils/ or dist/utils/
26
- nodePath.join(__dirname, "templates")
27
- // Direct sibling (unlikely but safe)
28
- ];
29
- for (const candidate of candidates) {
30
- if (existsSync(nodePath.join(candidate, knownTemplateFile))) {
31
- return candidate;
32
- }
33
- }
34
- throw new Error("Templates directory not found");
35
- }
36
- function exists(path) {
37
- return existsSync(path);
38
- }
39
- function ensureDirectory(path) {
40
- if (!existsSync(path)) {
41
- mkdirSync(path, { recursive: true });
42
- }
43
- }
44
- function readFile(path) {
45
- return readFileSync(path, "utf8");
46
- }
47
- function readFileSafe(path) {
48
- if (!existsSync(path)) return void 0;
49
- return readFileSync(path, "utf8");
50
- }
51
- function writeFile(path, content) {
52
- ensureDirectory(nodePath.dirname(path));
53
- writeFileSync(path, content);
54
- }
55
- function remove(path) {
56
- if (existsSync(path)) {
57
- rmSync(path, { recursive: true, force: true });
58
- }
59
- }
60
- function removeIfEmpty(path) {
61
- if (!existsSync(path)) return false;
62
- try {
63
- rmdirSync(path);
64
- return true;
65
- } catch {
66
- return false;
67
- }
68
- }
69
- function makeScriptsExecutable(dirPath) {
70
- if (!existsSync(dirPath)) return;
71
- for (const file of readdirSync(dirPath)) {
72
- if (file.endsWith(".sh")) {
73
- chmodSync(nodePath.join(dirPath, file), 493);
74
- }
75
- }
76
- }
77
- function readJson(path) {
78
- const content = readFileSafe(path);
79
- if (!content) return void 0;
80
- try {
81
- return JSON.parse(content);
82
- } catch {
83
- return void 0;
84
- }
85
- }
86
- function writeJson(path, data) {
87
- writeFile(path, `${JSON.stringify(data, void 0, 2)}
88
- `);
89
- }
5
+ ensureDirectory,
6
+ exists,
7
+ getTemplatesDirectory,
8
+ makeScriptsExecutable,
9
+ readFile,
10
+ readFileSafe,
11
+ readJson,
12
+ remove,
13
+ removeIfEmpty,
14
+ writeFile,
15
+ writeJson
16
+ } from "./chunk-DYLHQBW3.js";
90
17
 
91
18
  // src/reconcile.ts
92
- import nodePath2 from "path";
19
+ import nodePath from "path";
93
20
  var HUSKY_DIR = ".husky";
94
21
  function shouldSkipForNonGit(path, isGitRepo2) {
95
22
  return path.startsWith(HUSKY_DIR) && !isGitRepo2;
@@ -99,7 +26,7 @@ function planMissingDirectories(directories, cwd, isGitRepo2) {
99
26
  const created = [];
100
27
  for (const dir of directories) {
101
28
  if (shouldSkipForNonGit(dir, isGitRepo2)) continue;
102
- if (!exists(nodePath2.join(cwd, dir))) {
29
+ if (!exists(nodePath.join(cwd, dir))) {
103
30
  actions.push({ type: "mkdir", path: dir });
104
31
  created.push(dir);
105
32
  }
@@ -110,7 +37,7 @@ function planTextPatches(patches, cwd, isGitRepo2) {
110
37
  const actions = [];
111
38
  for (const [filePath, definition] of Object.entries(patches)) {
112
39
  if (shouldSkipForNonGit(filePath, isGitRepo2)) continue;
113
- const content = readFileSafe(nodePath2.join(cwd, filePath)) ?? "";
40
+ const content = readFileSafe(nodePath.join(cwd, filePath)) ?? "";
114
41
  if (!content.includes(definition.marker)) {
115
42
  actions.push({ type: "text-patch", path: filePath, definition });
116
43
  }
@@ -132,7 +59,7 @@ function planManagedFileWrites(files, ctx) {
132
59
  const actions = [];
133
60
  const created = [];
134
61
  for (const [filePath, definition] of Object.entries(files)) {
135
- if (exists(nodePath2.join(ctx.cwd, filePath))) continue;
62
+ if (exists(nodePath.join(ctx.cwd, filePath))) continue;
136
63
  const content = resolveFileContent(definition, ctx);
137
64
  actions.push({ type: "write", path: filePath, content });
138
65
  created.push(filePath);
@@ -145,7 +72,7 @@ function planTextPatchesWithCreation(patches, ctx) {
145
72
  for (const [filePath, definition] of Object.entries(patches)) {
146
73
  if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
147
74
  actions.push({ type: "text-patch", path: filePath, definition });
148
- if (definition.createIfMissing && !exists(nodePath2.join(ctx.cwd, filePath))) {
75
+ if (definition.createIfMissing && !exists(nodePath.join(ctx.cwd, filePath))) {
149
76
  created.push(filePath);
150
77
  }
151
78
  }
@@ -155,7 +82,7 @@ function planExistingDirectoriesRemoval(directories, cwd) {
155
82
  const actions = [];
156
83
  const removed = [];
157
84
  for (const dir of directories) {
158
- if (exists(nodePath2.join(cwd, dir))) {
85
+ if (exists(nodePath.join(cwd, dir))) {
159
86
  actions.push({ type: "rmdir", path: dir });
160
87
  removed.push(dir);
161
88
  }
@@ -166,7 +93,7 @@ function planExistingFilesRemoval(files, cwd) {
166
93
  const actions = [];
167
94
  const removed = [];
168
95
  for (const filePath of files) {
169
- if (exists(nodePath2.join(cwd, filePath))) {
96
+ if (exists(nodePath.join(cwd, filePath))) {
170
97
  actions.push({ type: "rm", path: filePath });
171
98
  removed.push(filePath);
172
99
  }
@@ -210,7 +137,7 @@ function planDeprecatedFilesRemoval(deprecatedFiles, cwd) {
210
137
  const actions = [];
211
138
  const removed = [];
212
139
  for (const filePath of deprecatedFiles) {
213
- if (exists(nodePath2.join(cwd, filePath))) {
140
+ if (exists(nodePath.join(cwd, filePath))) {
214
141
  actions.push({ type: "rm", path: filePath });
215
142
  removed.push(filePath);
216
143
  }
@@ -272,7 +199,7 @@ function computeUpgradePlan(schema, ctx) {
272
199
  wouldCreate.push(...missingDirectories.created);
273
200
  for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {
274
201
  if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
275
- const fullPath = nodePath2.join(ctx.cwd, filePath);
202
+ const fullPath = nodePath.join(ctx.cwd, filePath);
276
203
  const newContent = resolveFileContent(definition, ctx);
277
204
  if (!fileNeedsUpdate(fullPath, newContent)) continue;
278
205
  actions.push({ type: "write", path: filePath, content: newContent });
@@ -283,7 +210,7 @@ function computeUpgradePlan(schema, ctx) {
283
210
  }
284
211
  }
285
212
  for (const [filePath, definition] of Object.entries(schema.managedFiles)) {
286
- const fullPath = nodePath2.join(ctx.cwd, filePath);
213
+ const fullPath = nodePath.join(ctx.cwd, filePath);
287
214
  const newContent = resolveFileContent(definition, ctx);
288
215
  if (!exists(fullPath)) {
289
216
  actions.push({ type: "write", path: filePath, content: newContent });
@@ -341,7 +268,7 @@ function computeUninstallPlan(schema, ctx, full) {
341
268
  actions.push({ type: "json-unmerge", path: filePath, definition });
342
269
  }
343
270
  for (const [filePath, definition] of Object.entries(schema.textPatches)) {
344
- const fullPath = nodePath2.join(ctx.cwd, filePath);
271
+ const fullPath = nodePath.join(ctx.cwd, filePath);
345
272
  if (exists(fullPath)) {
346
273
  const content = readFileSafe(fullPath) ?? "";
347
274
  if (content.includes(definition.marker)) {
@@ -382,17 +309,17 @@ function executePlan(plan, ctx) {
382
309
  }
383
310
  function executeChmod(cwd, paths) {
384
311
  for (const path of paths) {
385
- const fullPath = nodePath2.join(cwd, path);
312
+ const fullPath = nodePath.join(cwd, path);
386
313
  if (exists(fullPath)) makeScriptsExecutable(fullPath);
387
314
  }
388
315
  }
389
316
  function executeRmdir(cwd, path, result) {
390
- if (removeIfEmpty(nodePath2.join(cwd, path))) result.removed.push(path);
317
+ if (removeIfEmpty(nodePath.join(cwd, path))) result.removed.push(path);
391
318
  }
392
319
  function executeAction(action, ctx, result) {
393
320
  switch (action.type) {
394
321
  case "mkdir":
395
- ensureDirectory(nodePath2.join(ctx.cwd, action.path));
322
+ ensureDirectory(nodePath.join(ctx.cwd, action.path));
396
323
  result.created.push(action.path);
397
324
  break;
398
325
  case "rmdir":
@@ -402,7 +329,7 @@ function executeAction(action, ctx, result) {
402
329
  executeWrite(ctx.cwd, action.path, action.content, result);
403
330
  break;
404
331
  case "rm":
405
- remove(nodePath2.join(ctx.cwd, action.path));
332
+ remove(nodePath.join(ctx.cwd, action.path));
406
333
  result.removed.push(action.path);
407
334
  break;
408
335
  case "chmod":
@@ -423,7 +350,7 @@ function executeAction(action, ctx, result) {
423
350
  }
424
351
  }
425
352
  function executeWrite(cwd, path, content, result) {
426
- const fullPath = nodePath2.join(cwd, path);
353
+ const fullPath = nodePath.join(cwd, path);
427
354
  const existed = exists(fullPath);
428
355
  writeFile(fullPath, content);
429
356
  (existed ? result.updated : result.created).push(path);
@@ -431,7 +358,7 @@ function executeWrite(cwd, path, content, result) {
431
358
  function resolveFileContent(definition, ctx) {
432
359
  if (definition.template) {
433
360
  const templatesDirectory = getTemplatesDirectory();
434
- return readFile(nodePath2.join(templatesDirectory, definition.template));
361
+ return readFile(nodePath.join(templatesDirectory, definition.template));
435
362
  }
436
363
  if (definition.content) {
437
364
  return typeof definition.content === "function" ? definition.content() : definition.content;
@@ -469,14 +396,14 @@ function computePackagesToRemove(schema, projectType, installedDevelopmentDeps)
469
396
  return safewordPackages.filter((pkg) => pkg in installedDevelopmentDeps);
470
397
  }
471
398
  function executeJsonMerge(cwd, path, definition, ctx) {
472
- const fullPath = nodePath2.join(cwd, path);
399
+ const fullPath = nodePath.join(cwd, path);
473
400
  const existing = readJson(fullPath) ?? {};
474
401
  const merged = definition.merge(existing, ctx);
475
402
  if (JSON.stringify(existing) === JSON.stringify(merged)) return;
476
403
  writeJson(fullPath, merged);
477
404
  }
478
405
  function executeJsonUnmerge(cwd, path, definition) {
479
- const fullPath = nodePath2.join(cwd, path);
406
+ const fullPath = nodePath.join(cwd, path);
480
407
  if (!exists(fullPath)) return;
481
408
  const existing = readJson(fullPath);
482
409
  if (!existing) return;
@@ -491,14 +418,14 @@ function executeJsonUnmerge(cwd, path, definition) {
491
418
  writeJson(fullPath, unmerged);
492
419
  }
493
420
  function executeTextPatch(cwd, path, definition) {
494
- const fullPath = nodePath2.join(cwd, path);
421
+ const fullPath = nodePath.join(cwd, path);
495
422
  let content = readFileSafe(fullPath) ?? "";
496
423
  if (content.includes(definition.marker)) return;
497
424
  content = definition.operation === "prepend" ? definition.content + content : content + definition.content;
498
425
  writeFile(fullPath, content);
499
426
  }
500
427
  function executeTextUnpatch(cwd, path, definition) {
501
- const fullPath = nodePath2.join(cwd, path);
428
+ const fullPath = nodePath.join(cwd, path);
502
429
  const content = readFileSafe(fullPath);
503
430
  if (!content) return;
504
431
  let unpatched = content.replace(definition.content, "");
@@ -549,13 +476,26 @@ const configs = [
549
476
  ...baseConfig,
550
477
  ];
551
478
 
552
- // Add test configs if testing frameworks detected
479
+ // Add configs for detected tools/frameworks
553
480
  if (deps["vitest"]) {
554
481
  configs.push(...safeword.configs.vitest);
555
482
  }
556
483
  if (deps["playwright"] || deps["@playwright/test"]) {
557
484
  configs.push(...safeword.configs.playwright);
558
485
  }
486
+ if (deps["tailwindcss"]) {
487
+ configs.push(...safeword.configs.tailwind);
488
+ }
489
+ const tanstackQueryPackages = [
490
+ "@tanstack/react-query",
491
+ "@tanstack/vue-query",
492
+ "@tanstack/solid-query",
493
+ "@tanstack/svelte-query",
494
+ "@tanstack/angular-query-experimental",
495
+ ];
496
+ if (tanstackQueryPackages.some(pkg => deps[pkg])) {
497
+ configs.push(...safeword.configs.tanstackQuery);
498
+ }
559
499
 
560
500
  // eslint-config-prettier must be last to disable conflicting rules
561
501
  configs.push(eslintConfigPrettier);
@@ -822,6 +762,7 @@ var SAFEWORD_SCHEMA = {
822
762
  template: "skills/safeword-writing-plans/SKILL.md"
823
763
  },
824
764
  ".claude/commands/architecture.md": { template: "commands/architecture.md" },
765
+ ".claude/commands/audit.md": { template: "commands/audit.md" },
825
766
  ".claude/commands/cleanup-zombies.md": { template: "commands/cleanup-zombies.md" },
826
767
  ".claude/commands/lint.md": { template: "commands/lint.md" },
827
768
  ".claude/commands/quality-review.md": { template: "commands/quality-review.md" },
@@ -845,8 +786,9 @@ var SAFEWORD_SCHEMA = {
845
786
  ".cursor/rules/safeword-writing-plans.mdc": {
846
787
  template: "cursor/rules/safeword-writing-plans.mdc"
847
788
  },
848
- // Cursor commands (4 files - same as Claude)
789
+ // Cursor commands (5 files - same as Claude)
849
790
  ".cursor/commands/architecture.md": { template: "commands/architecture.md" },
791
+ ".cursor/commands/audit.md": { template: "commands/audit.md" },
850
792
  ".cursor/commands/cleanup-zombies.md": { template: "commands/cleanup-zombies.md" },
851
793
  ".cursor/commands/lint.md": { template: "commands/lint.md" },
852
794
  ".cursor/commands/quality-review.md": { template: "commands/quality-review.md" },
@@ -883,6 +825,17 @@ var SAFEWORD_SCHEMA = {
883
825
  2
884
826
  );
885
827
  }
828
+ },
829
+ // Knip config for dead code detection (used by /audit)
830
+ "knip.json": {
831
+ generator: () => JSON.stringify(
832
+ {
833
+ ignore: [".safeword/**"],
834
+ ignoreDependencies: ["eslint-plugin-safeword"]
835
+ },
836
+ void 0,
837
+ 2
838
+ )
886
839
  }
887
840
  },
888
841
  // JSON files where we merge specific keys
@@ -1092,7 +1045,8 @@ var SAFEWORD_SCHEMA = {
1092
1045
  "prettier",
1093
1046
  // Safeword plugin (bundles eslint-config-prettier + all ESLint plugins)
1094
1047
  "eslint-plugin-safeword",
1095
- // Non-ESLint tools
1048
+ // Architecture and dead code tools (used by /audit)
1049
+ "dependency-cruiser",
1096
1050
  "knip"
1097
1051
  ],
1098
1052
  conditional: {
@@ -1107,28 +1061,28 @@ var SAFEWORD_SCHEMA = {
1107
1061
  };
1108
1062
 
1109
1063
  // src/utils/git.ts
1110
- import nodePath3 from "path";
1064
+ import nodePath2 from "path";
1111
1065
  function isGitRepo(cwd) {
1112
- return exists(nodePath3.join(cwd, ".git"));
1066
+ return exists(nodePath2.join(cwd, ".git"));
1113
1067
  }
1114
1068
 
1115
1069
  // src/utils/context.ts
1116
- import nodePath5 from "path";
1070
+ import nodePath4 from "path";
1117
1071
 
1118
1072
  // src/utils/project-detector.ts
1119
- import { readdirSync as readdirSync2 } from "fs";
1120
- import nodePath4 from "path";
1073
+ import { readdirSync } from "fs";
1074
+ import nodePath3 from "path";
1121
1075
  function hasShellScripts(cwd, maxDepth = 4) {
1122
1076
  const excludeDirectories = /* @__PURE__ */ new Set(["node_modules", ".git", ".safeword"]);
1123
1077
  function scan(dir, depth) {
1124
1078
  if (depth > maxDepth) return false;
1125
1079
  try {
1126
- const entries = readdirSync2(dir, { withFileTypes: true });
1080
+ const entries = readdirSync(dir, { withFileTypes: true });
1127
1081
  for (const entry of entries) {
1128
1082
  if (entry.isFile() && entry.name.endsWith(".sh")) {
1129
1083
  return true;
1130
1084
  }
1131
- if (entry.isDirectory() && !excludeDirectories.has(entry.name) && scan(nodePath4.join(dir, entry.name), depth + 1)) {
1085
+ if (entry.isDirectory() && !excludeDirectories.has(entry.name) && scan(nodePath3.join(dir, entry.name), depth + 1)) {
1132
1086
  return true;
1133
1087
  }
1134
1088
  }
@@ -1168,7 +1122,7 @@ function detectProjectType(packageJson, cwd) {
1168
1122
 
1169
1123
  // src/utils/context.ts
1170
1124
  function createProjectContext(cwd) {
1171
- const packageJson = readJson(nodePath5.join(cwd, "package.json"));
1125
+ const packageJson = readJson(nodePath4.join(cwd, "package.json"));
1172
1126
  return {
1173
1127
  cwd,
1174
1128
  projectType: detectProjectType(packageJson ?? {}, cwd),
@@ -1177,45 +1131,10 @@ function createProjectContext(cwd) {
1177
1131
  };
1178
1132
  }
1179
1133
 
1180
- // src/utils/output.ts
1181
- function info(message) {
1182
- console.log(message);
1183
- }
1184
- function success(message) {
1185
- console.log(`\u2713 ${message}`);
1186
- }
1187
- function warn(message) {
1188
- console.warn(`\u26A0 ${message}`);
1189
- }
1190
- function error(message) {
1191
- console.error(`\u2717 ${message}`);
1192
- }
1193
- function header(title) {
1194
- console.log(`
1195
- ${title}`);
1196
- console.log("\u2500".repeat(title.length));
1197
- }
1198
- function listItem(item, indent = 2) {
1199
- console.log(`${" ".repeat(indent)}\u2022 ${item}`);
1200
- }
1201
- function keyValue(key, value) {
1202
- console.log(` ${key}: ${value}`);
1203
- }
1204
-
1205
1134
  export {
1206
- exists,
1207
- readFileSafe,
1208
- writeJson,
1209
1135
  reconcile,
1210
1136
  SAFEWORD_SCHEMA,
1211
1137
  isGitRepo,
1212
- createProjectContext,
1213
- info,
1214
- success,
1215
- warn,
1216
- error,
1217
- header,
1218
- listItem,
1219
- keyValue
1138
+ createProjectContext
1220
1139
  };
1221
- //# sourceMappingURL=chunk-H7PCVPAC.js.map
1140
+ //# sourceMappingURL=chunk-KL2JTWK6.js.map