safeword 0.52.1 → 0.54.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.
Files changed (97) hide show
  1. package/dist/{check-K6M6MIVS.js → check-IZ42MYCT.js} +10 -10
  2. package/dist/{chunk-MPEK5NNA.js → chunk-G3BDQLVU.js} +7 -7
  3. package/dist/chunk-G3BDQLVU.js.map +1 -0
  4. package/dist/{chunk-WE7ZQLCT.js → chunk-GT7KMCFG.js} +23 -8
  5. package/dist/chunk-GT7KMCFG.js.map +1 -0
  6. package/dist/{chunk-2WUL76K5.js → chunk-HN3ZZETN.js} +2 -2
  7. package/dist/{chunk-MPYFFJBF.js → chunk-I2GV5QKO.js} +10 -3
  8. package/dist/{chunk-MPYFFJBF.js.map → chunk-I2GV5QKO.js.map} +1 -1
  9. package/dist/{chunk-3RM6NYZT.js → chunk-N74UNUZ6.js} +22 -19
  10. package/dist/chunk-N74UNUZ6.js.map +1 -0
  11. package/dist/{chunk-LODQOJEK.js → chunk-PHR2K2Y3.js} +41 -29
  12. package/dist/chunk-PHR2K2Y3.js.map +1 -0
  13. package/dist/{chunk-OJNF2CHC.js → chunk-SW3D7PN7.js} +469 -282
  14. package/dist/chunk-SW3D7PN7.js.map +1 -0
  15. package/dist/{chunk-JLFYAVLP.js → chunk-VVWHQBTU.js} +22 -19
  16. package/dist/chunk-VVWHQBTU.js.map +1 -0
  17. package/dist/{chunk-SD4UB7HJ.js → chunk-VW6VSC5R.js} +55 -33
  18. package/dist/chunk-VW6VSC5R.js.map +1 -0
  19. package/dist/{chunk-QLXFPFIC.js → chunk-XTLCJKGE.js} +5 -5
  20. package/dist/chunk-XTLCJKGE.js.map +1 -0
  21. package/dist/{chunk-SF3CPJUX.js → chunk-YXEKBWNB.js} +12 -12
  22. package/dist/chunk-YXEKBWNB.js.map +1 -0
  23. package/dist/{chunk-IGULTNHR.js → chunk-YXNI7W5D.js} +14 -12
  24. package/dist/chunk-YXNI7W5D.js.map +1 -0
  25. package/dist/cli.js +12 -12
  26. package/dist/{codify-YUGZVCR4.js → codify-2HGAGRPT.js} +14 -12
  27. package/dist/codify-2HGAGRPT.js.map +1 -0
  28. package/dist/{diff-WAQXK32P.js → diff-ZCRAOPAC.js} +33 -32
  29. package/dist/diff-ZCRAOPAC.js.map +1 -0
  30. package/dist/index.js +2 -2
  31. package/dist/{lint-gherkin-KJH3GNJQ.js → lint-gherkin-FLJEH3EJ.js} +2 -2
  32. package/dist/presets/typescript/index.d.ts +11 -11
  33. package/dist/presets/typescript/index.js +2 -2
  34. package/dist/{reset-AT5CGCYX.js → reset-ETS3FAAG.js} +15 -17
  35. package/dist/reset-ETS3FAAG.js.map +1 -0
  36. package/dist/{setup-C3FCNOW7.js → setup-MCRTMRBD.js} +29 -26
  37. package/dist/setup-MCRTMRBD.js.map +1 -0
  38. package/dist/{sync-config-5FCJLGMW.js → sync-config-IDVS7DTC.js} +3 -3
  39. package/dist/{sync-learnings-C7GXSMKZ.js → sync-learnings-PALEDNJ2.js} +3 -3
  40. package/dist/{sync-tickets-SDX4XPVS.js → sync-tickets-O6KTBGNT.js} +4 -4
  41. package/dist/{test-plan-4D3WNSQP.js → test-plan-PV6C5IUO.js} +18 -10
  42. package/dist/test-plan-PV6C5IUO.js.map +1 -0
  43. package/dist/{ticket-new-CVBD3MVR.js → ticket-new-W3YOFGQU.js} +4 -4
  44. package/dist/{ticket-new-CVBD3MVR.js.map → ticket-new-W3YOFGQU.js.map} +1 -1
  45. package/dist/{upgrade-25PKQQCY.js → upgrade-RRO7PLMT.js} +48 -29
  46. package/dist/upgrade-RRO7PLMT.js.map +1 -0
  47. package/package.json +14 -16
  48. package/templates/codex/config.toml +7 -0
  49. package/templates/commands/audit.md +2 -2
  50. package/templates/commands/verify.md +2 -2
  51. package/templates/doc-templates/test-definitions-feature.md +1 -1
  52. package/templates/hooks/lib/ledger-validation.ts +31 -7
  53. package/templates/hooks/lib/lint-config.ts +55 -0
  54. package/templates/hooks/lib/lint.ts +11 -1
  55. package/templates/hooks/lib/quality-state.ts +22 -1
  56. package/templates/hooks/lib/skill-invocation-log.ts +12 -4
  57. package/templates/hooks/lib/update-cache.ts +98 -2
  58. package/templates/hooks/pre-tool-quality.ts +5 -21
  59. package/templates/hooks/record-skill-invocation.ts +7 -1
  60. package/templates/hooks/session-auto-upgrade.ts +193 -48
  61. package/templates/hooks/session-compact-context.ts +4 -15
  62. package/templates/hooks/session-lint-check.ts +11 -3
  63. package/templates/hooks/stop-quality.ts +59 -64
  64. package/templates/hooks/stop-reentry.ts +24 -19
  65. package/templates/skills/audit/SKILL.md +2 -2
  66. package/templates/skills/bdd/SKILL.md +6 -2
  67. package/templates/skills/bdd/TDD.md +13 -3
  68. package/templates/skills/bdd/VERIFY.md +1 -1
  69. package/templates/skills/debug/SKILL.md +29 -15
  70. package/templates/skills/elicit/SKILL.md +4 -0
  71. package/templates/skills/figure-it-out/SKILL.md +6 -0
  72. package/templates/skills/quality-review/SKILL.md +41 -9
  73. package/templates/skills/refactor/SKILL.md +26 -11
  74. package/templates/skills/verify/SKILL.md +2 -2
  75. package/dist/chunk-3RM6NYZT.js.map +0 -1
  76. package/dist/chunk-IGULTNHR.js.map +0 -1
  77. package/dist/chunk-JLFYAVLP.js.map +0 -1
  78. package/dist/chunk-LODQOJEK.js.map +0 -1
  79. package/dist/chunk-MPEK5NNA.js.map +0 -1
  80. package/dist/chunk-OJNF2CHC.js.map +0 -1
  81. package/dist/chunk-QLXFPFIC.js.map +0 -1
  82. package/dist/chunk-SD4UB7HJ.js.map +0 -1
  83. package/dist/chunk-SF3CPJUX.js.map +0 -1
  84. package/dist/chunk-WE7ZQLCT.js.map +0 -1
  85. package/dist/codify-YUGZVCR4.js.map +0 -1
  86. package/dist/diff-WAQXK32P.js.map +0 -1
  87. package/dist/reset-AT5CGCYX.js.map +0 -1
  88. package/dist/setup-C3FCNOW7.js.map +0 -1
  89. package/dist/test-plan-4D3WNSQP.js.map +0 -1
  90. package/dist/upgrade-25PKQQCY.js.map +0 -1
  91. package/templates/hooks/session-update-check.ts +0 -81
  92. /package/dist/{check-K6M6MIVS.js.map → check-IZ42MYCT.js.map} +0 -0
  93. /package/dist/{chunk-2WUL76K5.js.map → chunk-HN3ZZETN.js.map} +0 -0
  94. /package/dist/{lint-gherkin-KJH3GNJQ.js.map → lint-gherkin-FLJEH3EJ.js.map} +0 -0
  95. /package/dist/{sync-config-5FCJLGMW.js.map → sync-config-IDVS7DTC.js.map} +0 -0
  96. /package/dist/{sync-learnings-C7GXSMKZ.js.map → sync-learnings-PALEDNJ2.js.map} +0 -0
  97. /package/dist/{sync-tickets-SDX4XPVS.js.map → sync-tickets-O6KTBGNT.js.map} +0 -0
@@ -4,17 +4,17 @@ import {
4
4
  import {
5
5
  checkHealth,
6
6
  reportHealthSummary
7
- } from "./chunk-3RM6NYZT.js";
7
+ } from "./chunk-N74UNUZ6.js";
8
8
  import {
9
9
  syncTickets
10
- } from "./chunk-JLFYAVLP.js";
10
+ } from "./chunk-VVWHQBTU.js";
11
11
  import "./chunk-NHXVS5FL.js";
12
- import "./chunk-IGULTNHR.js";
13
- import "./chunk-QLXFPFIC.js";
14
- import "./chunk-OJNF2CHC.js";
15
- import "./chunk-2WUL76K5.js";
16
- import "./chunk-WE7ZQLCT.js";
17
- import "./chunk-LODQOJEK.js";
12
+ import "./chunk-YXNI7W5D.js";
13
+ import "./chunk-XTLCJKGE.js";
14
+ import "./chunk-SW3D7PN7.js";
15
+ import "./chunk-HN3ZZETN.js";
16
+ import "./chunk-GT7KMCFG.js";
17
+ import "./chunk-PHR2K2Y3.js";
18
18
  import "./chunk-HSC7TELY.js";
19
19
  import {
20
20
  header,
@@ -22,7 +22,7 @@ import {
22
22
  keyValue,
23
23
  success,
24
24
  warn
25
- } from "./chunk-MPYFFJBF.js";
25
+ } from "./chunk-I2GV5QKO.js";
26
26
 
27
27
  // src/commands/check.ts
28
28
  import process from "process";
@@ -110,4 +110,4 @@ async function check(options) {
110
110
  export {
111
111
  check
112
112
  };
113
- //# sourceMappingURL=check-K6M6MIVS.js.map
113
+ //# sourceMappingURL=check-IZ42MYCT.js.map
@@ -4,7 +4,7 @@ import {
4
4
  info,
5
5
  readJson,
6
6
  success
7
- } from "./chunk-MPYFFJBF.js";
7
+ } from "./chunk-I2GV5QKO.js";
8
8
 
9
9
  // src/commands/sync-config.ts
10
10
  import { readFileSync, writeFileSync } from "fs";
@@ -132,7 +132,7 @@ function generateMonorepoRules(workspaces) {
132
132
  }
133
133
  return rules.join(",\n");
134
134
  }
135
- function generateDepCruiseConfigFile(arch) {
135
+ function generateDependencyCruiseConfigFile(arch) {
136
136
  const monorepoRules = arch.workspaces ? generateMonorepoRules(arch.workspaces) : "";
137
137
  const hasMonorepoRules = monorepoRules.length > 0;
138
138
  return String.raw`module.exports = {
@@ -217,7 +217,7 @@ ${monorepoRules},` : ""}
217
217
  };
218
218
  `;
219
219
  }
220
- function generateDepCruiseMainConfig() {
220
+ function generateDependencyCruiseMainConfig() {
221
221
  return `/**
222
222
  * Dependency Cruiser Configuration
223
223
  *
@@ -249,12 +249,12 @@ function syncConfigCore(cwd, arch) {
249
249
  createdMainConfig: false
250
250
  };
251
251
  const generatedConfigPath = nodePath3.join(safewordDirectory, "depcruise-config.cjs");
252
- const generatedConfig = generateDepCruiseConfigFile(arch);
252
+ const generatedConfig = generateDependencyCruiseConfigFile(arch);
253
253
  writeFileSync(generatedConfigPath, generatedConfig);
254
254
  result.generatedConfig = true;
255
255
  const mainConfigPath = nodePath3.join(cwd, ".dependency-cruiser.cjs");
256
256
  if (!exists(mainConfigPath)) {
257
- const mainConfig = generateDepCruiseMainConfig();
257
+ const mainConfig = generateDependencyCruiseMainConfig();
258
258
  writeFileSync(mainConfigPath, mainConfig);
259
259
  result.createdMainConfig = true;
260
260
  }
@@ -273,7 +273,7 @@ function checkConfig(cwd, arch) {
273
273
  if (!exists(generatedConfigPath)) {
274
274
  return { matches: false, reason: "missing" };
275
275
  }
276
- const generated = generateDepCruiseConfigFile(arch);
276
+ const generated = generateDependencyCruiseConfigFile(arch);
277
277
  const onDisk = readFileSync(generatedConfigPath, "utf8");
278
278
  return generated === onDisk ? { matches: true } : { matches: false, reason: "drifted" };
279
279
  }
@@ -312,4 +312,4 @@ export {
312
312
  hasArchitectureDetected,
313
313
  syncConfig
314
314
  };
315
- //# sourceMappingURL=chunk-MPEK5NNA.js.map
315
+ //# sourceMappingURL=chunk-G3BDQLVU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/sync-config.ts","../src/utils/boundaries.ts","../src/utils/depcruise-config.ts"],"sourcesContent":["/**\n * Sync Config command - Regenerate depcruise config from current project structure\n *\n * Default mode writes config to disk. `--check` mode reports drift without writing —\n * used by `/audit` to detect stale config without polluting the working tree.\n */\n\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { detectArchitecture } from '../utils/boundaries.js';\nimport {\n type DependencyCruiseArchitecture,\n detectWorkspaces,\n generateDependencyCruiseConfigFile,\n generateDependencyCruiseMainConfig,\n} from '../utils/depcruise-config.js';\nimport { exists } from '../utils/fs.js';\nimport { error, info, success } from '../utils/output.js';\n\ninterface SyncConfigResult {\n generatedConfig: boolean;\n createdMainConfig: boolean;\n}\n\n/**\n * Core sync logic - writes depcruise configs to disk\n * Can be called from setup or as standalone command\n */\nexport function syncConfigCore(cwd: string, arch: DependencyCruiseArchitecture): SyncConfigResult {\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n const result: SyncConfigResult = {\n generatedConfig: false,\n createdMainConfig: false,\n };\n\n // Generate and write .safeword/depcruise-config.cjs (CJS for compatibility)\n const generatedConfigPath = nodePath.join(safewordDirectory, 'depcruise-config.cjs');\n const generatedConfig = generateDependencyCruiseConfigFile(arch);\n writeFileSync(generatedConfigPath, generatedConfig);\n result.generatedConfig = true;\n\n // Create main config if not exists (self-healing)\n // Use .cjs extension to work in ESM projects (type: \"module\")\n const mainConfigPath = nodePath.join(cwd, '.dependency-cruiser.cjs');\n if (!exists(mainConfigPath)) {\n const mainConfig = generateDependencyCruiseMainConfig();\n writeFileSync(mainConfigPath, mainConfig);\n result.createdMainConfig = true;\n }\n\n return result;\n}\n\n/**\n * Build full architecture info by combining detected layers with workspaces\n */\nexport function buildArchitecture(cwd: string): DependencyCruiseArchitecture {\n const arch = detectArchitecture(cwd);\n const workspaces = detectWorkspaces(cwd);\n return { ...arch, workspaces };\n}\n\n/**\n * Check if architecture was detected (layers, monorepo structure, or workspaces)\n */\nexport function hasArchitectureDetected(arch: DependencyCruiseArchitecture): boolean {\n return arch.elements.length > 0 || arch.isMonorepo || (arch.workspaces?.length ?? 0) > 0;\n}\n\n/**\n * Check if generated config matches on-disk content. No writes.\n * Returns { matches: true } when bytes are byte-equal.\n * Returns { matches: false, reason } when on-disk is missing or differs.\n */\nfunction checkConfig(\n cwd: string,\n arch: DependencyCruiseArchitecture,\n): { matches: true } | { matches: false; reason: 'missing' | 'drifted' } {\n const generatedConfigPath = nodePath.join(cwd, '.safeword', 'depcruise-config.cjs');\n if (!exists(generatedConfigPath)) {\n return { matches: false, reason: 'missing' };\n }\n const generated = generateDependencyCruiseConfigFile(arch);\n const onDisk = readFileSync(generatedConfigPath, 'utf8');\n return generated === onDisk ? { matches: true } : { matches: false, reason: 'drifted' };\n}\n\n/**\n * CLI command: Sync depcruise config with current project structure\n */\n\nexport async function syncConfig(options: { check?: boolean } = {}): Promise<void> {\n // Public CLI command contract is Promise<void>; body is sync today but the\n // signature reserves room for async I/O. Token await keeps the contract honest.\n await Promise.resolve();\n const cwd = process.cwd();\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n // Check if .safeword exists\n if (!exists(safewordDirectory)) {\n error('Not configured. Run `safeword setup` first.');\n process.exit(1);\n }\n\n const arch = buildArchitecture(cwd);\n\n if (options.check) {\n const result = checkConfig(cwd, arch);\n if (result.matches) {\n success('Config in sync');\n return;\n }\n const message =\n result.reason === 'missing'\n ? 'Missing .safeword/depcruise-config.cjs — run `safeword sync-config` to generate it.'\n : 'Stale .safeword/depcruise-config.cjs — run `safeword sync-config` to refresh.';\n error(message);\n process.exit(1);\n }\n\n const result = syncConfigCore(cwd, arch);\n\n if (result.generatedConfig) {\n info('Generated .safeword/depcruise-config.cjs');\n }\n if (result.createdMainConfig) {\n info('Created .dependency-cruiser.cjs');\n }\n\n success('Config synced');\n}\n","/**\n * Architecture boundaries detection\n *\n * Auto-detects common architecture directories for use by\n * dependency-cruiser layer enforcement.\n *\n * Supports:\n * - Standard projects (src/utils, utils/)\n * - Monorepos (packages/*, apps/*)\n * - Various naming conventions (helpers, shared, core, etc.)\n */\n\nimport { readdirSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { exists } from './fs.js';\n\n/**\n * Architecture layer definitions with alternative names.\n * Each layer maps to equivalent directory names.\n * Order defines hierarchy: earlier = lower layer.\n */\nconst ARCHITECTURE_LAYERS = [\n // Layer 0: Pure types (no imports)\n { layer: 'types', dirs: ['types', 'interfaces', 'schemas'] },\n // Layer 1: Utilities (only types)\n { layer: 'utils', dirs: ['utils', 'helpers', 'shared', 'common', 'core'] },\n // Layer 2: Libraries (types, utils)\n { layer: 'lib', dirs: ['lib', 'libraries'] },\n // Layer 3: State & logic (types, utils, lib)\n { layer: 'hooks', dirs: ['hooks', 'composables'] },\n { layer: 'services', dirs: ['services', 'api', 'stores', 'state'] },\n // Layer 4: UI components (all above)\n { layer: 'components', dirs: ['components', 'ui'] },\n // Layer 5: Features (all above)\n { layer: 'features', dirs: ['features', 'modules', 'domains'] },\n // Layer 6: Entry points (can import everything)\n { layer: 'app', dirs: ['app', 'pages', 'views', 'routes', 'commands'] },\n] as const;\n\ntype Layer = (typeof ARCHITECTURE_LAYERS)[number]['layer'];\n\ninterface DetectedElement {\n layer: Layer;\n pattern: string; // glob pattern for boundaries config\n location: string; // human-readable location\n}\n\nexport interface DetectedArchitecture {\n elements: DetectedElement[];\n isMonorepo: boolean;\n}\n\n/**\n * Find monorepo package directories\n * @param projectDirectory\n */\nfunction findMonorepoPackages(projectDirectory: string): string[] {\n const packages: string[] = [];\n\n // Check common monorepo patterns\n const monorepoRoots = ['packages', 'apps', 'libs', 'modules'];\n\n for (const root of monorepoRoots) {\n const rootPath = nodePath.join(projectDirectory, root);\n if (!exists(rootPath)) continue;\n\n try {\n const entries = readdirSync(rootPath, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && !entry.name.startsWith('.')) {\n packages.push(nodePath.join(root, entry.name));\n }\n }\n } catch {\n // Directory not readable, skip\n }\n }\n\n return packages;\n}\n\n/**\n * Check if a layer already exists for this path prefix\n * @param elements\n * @param layer\n * @param pathPrefix\n */\nfunction hasLayerForPrefix(elements: DetectedElement[], layer: Layer, pathPrefix: string): boolean {\n return elements.some(\n element => element.layer === layer && element.pattern.startsWith(pathPrefix),\n );\n}\n\n/**\n * Scan a single search path for architecture layers\n * @param projectDirectory\n * @param searchPath\n * @param pathPrefix\n * @param elements\n */\n/**\n * A `features/` directory holding Gherkin `.feature` files is safeword's BDD\n * acceptance lane (scaffolded by setup, ticket 102b), not a feature-sliced\n * architecture layer — without this guard every safeword project would\n * \"detect\" architecture and get depcruise configs it doesn't need.\n */\nfunction isGherkinDirectory(fullPath: string): boolean {\n try {\n return readdirSync(fullPath).some(entry => entry.endsWith('.feature'));\n } catch {\n return false;\n }\n}\n\nfunction scanSearchPath(\n projectDirectory: string,\n searchPath: string,\n pathPrefix: string,\n elements: DetectedElement[],\n): void {\n for (const layerDefinition of ARCHITECTURE_LAYERS) {\n for (const dirName of layerDefinition.dirs) {\n const fullPath = nodePath.join(projectDirectory, searchPath, dirName);\n if (\n exists(fullPath) &&\n !isGherkinDirectory(fullPath) &&\n !hasLayerForPrefix(elements, layerDefinition.layer, pathPrefix)\n ) {\n elements.push({\n layer: layerDefinition.layer,\n pattern: `${pathPrefix}${dirName}/**`,\n location: `${pathPrefix}${dirName}`,\n });\n }\n }\n }\n}\n\n/**\n * Scan a directory for architecture layers\n * @param projectDirectory\n * @param basePath\n */\nfunction scanForLayers(projectDirectory: string, basePath: string): DetectedElement[] {\n const elements: DetectedElement[] = [];\n const prefix = basePath ? `${basePath}/` : '';\n\n // Check src/ and root level\n scanSearchPath(projectDirectory, nodePath.join(basePath, 'src'), `${prefix}src/`, elements);\n scanSearchPath(projectDirectory, basePath, prefix, elements);\n\n return elements;\n}\n\n/**\n * Detects architecture directories in the project\n * Handles both standard projects and monorepos\n * @param projectDirectory\n */\nexport function detectArchitecture(projectDirectory: string): DetectedArchitecture {\n const elements: DetectedElement[] = [];\n\n // First, check for monorepo packages\n const packages = findMonorepoPackages(projectDirectory);\n const isMonorepo = packages.length > 0;\n\n if (isMonorepo) {\n // Scan each package\n for (const pkg of packages) {\n elements.push(...scanForLayers(projectDirectory, pkg));\n }\n }\n\n // Also scan root level (works for both monorepo root and standard projects)\n elements.push(...scanForLayers(projectDirectory, ''));\n\n // Deduplicate by pattern\n const seen = new Set<string>();\n const uniqueElements = elements.filter(element => {\n if (seen.has(element.pattern)) return false;\n seen.add(element.pattern);\n return true;\n });\n\n return { elements: uniqueElements, isMonorepo };\n}\n","/**\n * Dependency-cruiser config generator\n *\n * Generates dependency-cruiser configuration from detected architecture.\n * Used by `safeword sync-config` command and `/audit` slash command.\n */\n\nimport nodePath from 'node:path';\n\nimport type { DetectedArchitecture } from './boundaries.js';\nimport { readJson } from './fs.js';\n\nexport interface DependencyCruiseArchitecture extends DetectedArchitecture {\n workspaces?: string[];\n}\n\ninterface PackageJson {\n workspaces?: string[] | { packages?: string[] };\n}\n\n/**\n * Detect workspaces from package.json\n * Supports both array format and object format (yarn workspaces)\n */\nexport function detectWorkspaces(cwd: string): string[] | undefined {\n const packageJsonPath = nodePath.join(cwd, 'package.json');\n const packageJson = readJson(packageJsonPath) as PackageJson | undefined;\n\n if (!packageJson?.workspaces) return undefined;\n\n // Handle both formats: string[] or { packages: string[] }\n const workspaces = Array.isArray(packageJson.workspaces)\n ? packageJson.workspaces\n : packageJson.workspaces.packages;\n\n return workspaces && workspaces.length > 0 ? workspaces : undefined;\n}\n\n/**\n * Generate monorepo hierarchy rules based on workspace patterns\n */\nfunction generateMonorepoRules(workspaces: string[]): string {\n const rules: string[] = [];\n\n const hasLibs = workspaces.some(w => w.startsWith('libs'));\n const hasPackages = workspaces.some(w => w.startsWith('packages'));\n const hasApps = workspaces.some(w => w.startsWith('apps'));\n\n // libs cannot import packages or apps\n if (hasLibs && (hasPackages || hasApps)) {\n rules.push(` {\n name: 'libs-cannot-import-packages-or-apps',\n severity: 'error',\n from: { path: '^libs/' },\n to: { path: '^(packages|apps)/' },\n }`);\n }\n\n // packages cannot import apps\n if (hasPackages && hasApps) {\n rules.push(` {\n name: 'packages-cannot-import-apps',\n severity: 'error',\n from: { path: '^packages/' },\n to: { path: '^apps/' },\n }`);\n }\n\n return rules.join(',\\n');\n}\n\n/**\n * Generate .safeword/depcruise-config.cjs content (forbidden rules + options)\n */\nexport function generateDependencyCruiseConfigFile(arch: DependencyCruiseArchitecture): string {\n const monorepoRules = arch.workspaces ? generateMonorepoRules(arch.workspaces) : '';\n const hasMonorepoRules = monorepoRules.length > 0;\n\n return String.raw`module.exports = {\n forbidden: [\n // =========================================================================\n // ERROR RULES (block on violations)\n // =========================================================================\n {\n name: 'no-circular',\n // Runtime cycles cause initialization-order bugs and make code hard to reason about.\n // Type-only edges (import type) are erased at compile time and cannot cause runtime\n // cycles - TypeScript designed import type for exactly this case, and depcruise\n // documents viaOnly + dependencyTypesNot: ['type-only'] as the canonical opt-in.\n comment: 'Circular dependencies cause runtime issues and make code hard to reason about',\n severity: 'error',\n from: {},\n to: { circular: true, viaOnly: { dependencyTypesNot: ['type-only'] } },\n },\n {\n name: 'no-deprecated-deps',\n comment: 'Deprecated npm packages should be replaced - they may have security issues or be unmaintained',\n severity: 'error',\n from: {},\n to: { dependencyTypes: ['deprecated'] },\n },${hasMonorepoRules ? `\\n${monorepoRules},` : ''}\n\n // =========================================================================\n // WARNING RULES (flag issues but don't block)\n // =========================================================================\n {\n name: 'no-dev-deps-in-src',\n comment: 'Production code should not import devDependencies - may cause runtime failures',\n severity: 'warn',\n from: {\n path: ['^src', '^packages/[^/]+/src'],\n pathNot: '\\\\.test\\\\.[tj]sx?$',\n },\n to: { dependencyTypes: ['npm-dev'] },\n },\n {\n name: 'no-orphans',\n comment: 'Orphan modules are not imported anywhere - may be dead code',\n severity: 'warn',\n from: {\n orphan: true,\n pathNot: [\n // Entry points\n '(^|/)index\\\\.[tj]sx?$',\n '(^|/)main\\\\.[tj]sx?$',\n '(^|/)cli\\\\.[tj]s$',\n '(^|/)cucumber\\\\.mjs$',\n '\\\\.config\\\\.[tj]s$',\n '\\\\.config\\\\.mjs$',\n // Test files\n '\\\\.test\\\\.[tj]sx?$',\n '\\\\.spec\\\\.[tj]sx?$',\n '/tests/',\n '/__tests__/',\n // Astro/Next.js pages and content\n '/src/content/',\n '/src/pages/',\n '/app/',\n ],\n },\n to: {},\n },\n ],\n options: {\n doNotFollow: { path: ['node_modules', '.safeword'] },\n exclude: {\n path: ['node_modules', 'dist', 'build', 'coverage', '\\\\.d\\\\.ts$'],\n },\n tsPreCompilationDeps: true,\n tsConfig: { fileName: 'tsconfig.json' },\n enhancedResolveOptions: {\n extensions: ['.ts', '.tsx', '.js', '.jsx'],\n exportsFields: ['exports'],\n conditionNames: ['import', 'require', 'node', 'default'],\n },\n },\n};\n`;\n}\n\n/**\n * Generate .dependency-cruiser.js (main config that imports generated)\n */\nexport function generateDependencyCruiseMainConfig(): string {\n return `/**\n * Dependency Cruiser Configuration\n *\n * Imports auto-generated rules from .safeword/depcruise-config.cjs\n * ADD YOUR CUSTOM RULES BELOW the spread operator.\n */\n\nconst generated = require('./.safeword/depcruise-config.cjs');\n\nmodule.exports = {\n forbidden: [\n ...generated.forbidden,\n // ADD YOUR CUSTOM RULES BELOW:\n // { name: 'no-legacy', from: { path: 'legacy/' }, to: { path: 'new/' } },\n ],\n options: {\n ...generated.options,\n // Your overrides here\n },\n};\n`;\n}\n"],"mappings":"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","names":["nodePath","nodePath","nodePath","nodePath","result"]}
@@ -3,7 +3,7 @@ import {
3
3
  listItem,
4
4
  success,
5
5
  warn
6
- } from "./chunk-MPYFFJBF.js";
6
+ } from "./chunk-I2GV5QKO.js";
7
7
 
8
8
  // src/utils/install.ts
9
9
  import { execFileSync } from "child_process";
@@ -16,7 +16,14 @@ var PM_COMMANDS = {
16
16
  pnpm: { install: "add", uninstall: "remove" },
17
17
  bun: { install: "add", uninstall: "remove" }
18
18
  };
19
+ function isPnpmWorkspace(cwd) {
20
+ return existsSync(path.join(cwd, "pnpm-workspace.yaml"));
21
+ }
22
+ function pnpmWorkspaceFlags(pm, cwd) {
23
+ return pm === "pnpm" && isPnpmWorkspace(cwd) ? ["-w"] : [];
24
+ }
19
25
  function detectPackageManager(cwd) {
26
+ if (isPnpmWorkspace(cwd)) return "pnpm";
20
27
  if (existsSync(path.join(cwd, "bun.lockb")) || existsSync(path.join(cwd, "bun.lock")))
21
28
  return "bun";
22
29
  if (existsSync(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
@@ -25,21 +32,30 @@ function detectPackageManager(cwd) {
25
32
  if (process.versions.bun) return "bun";
26
33
  return "npm";
27
34
  }
28
- function getUninstallCommand(pm, packages) {
35
+ function getUninstallCommand(packages, cwd) {
36
+ const pm = detectPackageManager(cwd);
29
37
  const { uninstall } = PM_COMMANDS[pm];
30
- return `${pm} ${uninstall} ${packages.join(" ")}`;
38
+ const extraFlags = pnpmWorkspaceFlags(pm, cwd);
39
+ const flagString = extraFlags.length > 0 ? ` ${extraFlags.join(" ")}` : "";
40
+ return `${pm} ${uninstall}${flagString} ${packages.join(" ")}`;
31
41
  }
32
42
  function installDependencies(cwd, packages, label = "packages") {
33
43
  if (packages.length === 0) return;
34
44
  if (process.env.SAFEWORD_SKIP_INSTALL) return;
35
45
  const pm = detectPackageManager(cwd);
36
46
  const { install } = PM_COMMANDS[pm];
37
- const displayCommand = `${pm} ${install} ${DEV_FLAG} ${packages.join(" ")}`;
47
+ const extraFlags = pnpmWorkspaceFlags(pm, cwd);
48
+ const flagString = extraFlags.length > 0 ? ` ${extraFlags.join(" ")}` : "";
49
+ const displayCommand = `${pm} ${install} ${DEV_FLAG}${flagString} ${packages.join(" ")}`;
38
50
  info(`
39
51
  Installing ${label}...`);
40
52
  info(`Running: ${displayCommand}`);
41
53
  try {
42
- execFileSync(pm, [install, DEV_FLAG, ...packages], { cwd, stdio: "pipe", timeout: 12e4 });
54
+ execFileSync(pm, [install, DEV_FLAG, ...extraFlags, ...packages], {
55
+ cwd,
56
+ stdio: "pipe",
57
+ timeout: 12e4
58
+ });
43
59
  success(`Installed ${label}`);
44
60
  } catch {
45
61
  warn(`Failed to install ${label}. Run manually:`);
@@ -48,8 +64,7 @@ Installing ${label}...`);
48
64
  }
49
65
  var MCP_SERVERS = {
50
66
  context7: {
51
- command: "bunx",
52
- args: ["@upstash/context7-mcp@latest"]
67
+ url: "https://mcp.context7.com/mcp"
53
68
  },
54
69
  playwright: {
55
70
  command: "bunx",
@@ -63,4 +78,4 @@ export {
63
78
  installDependencies,
64
79
  MCP_SERVERS
65
80
  };
66
- //# sourceMappingURL=chunk-WE7ZQLCT.js.map
81
+ //# sourceMappingURL=chunk-GT7KMCFG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/install.ts"],"sourcesContent":["/**\n * Shared installation utilities\n *\n * Package manager detection and MCP server constants.\n * Operations are handled by reconcile() in src/reconcile.ts.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\n\nimport { info, listItem, success, warn } from './output.js';\n\ntype PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';\n\n/** Dev-dependency flag, shared across all package managers. */\nconst DEV_FLAG = '-D';\n\n/**\n * Package manager command definitions.\n * Single source of truth for install/uninstall args across all managers.\n */\nconst PM_COMMANDS: Record<PackageManager, { install: string; uninstall: string }> = {\n npm: { install: 'install', uninstall: 'uninstall' },\n yarn: { install: 'add', uninstall: 'remove' },\n pnpm: { install: 'add', uninstall: 'remove' },\n bun: { install: 'add', uninstall: 'remove' },\n};\n\nfunction isPnpmWorkspace(cwd: string): boolean {\n return existsSync(path.join(cwd, 'pnpm-workspace.yaml'));\n}\n\nfunction pnpmWorkspaceFlags(pm: PackageManager, cwd: string): string[] {\n return pm === 'pnpm' && isPnpmWorkspace(cwd) ? ['-w'] : [];\n}\n\n/**\n * Detect package manager by lockfile and workspace config.\n * pnpm-workspace.yaml takes priority over bun.lockb — catalog: protocol\n * requires pnpm even when a bun lockfile also exists.\n */\nexport function detectPackageManager(cwd: string): PackageManager {\n if (isPnpmWorkspace(cwd)) return 'pnpm';\n if (existsSync(path.join(cwd, 'bun.lockb')) || existsSync(path.join(cwd, 'bun.lock')))\n return 'bun';\n if (existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn';\n if (existsSync(path.join(cwd, 'package-lock.json'))) return 'npm';\n // No lockfile found — fall back to current runtime (bun) or npm\n if (process.versions.bun) return 'bun';\n return 'npm';\n}\n\nexport function getUninstallCommand(packages: string[], cwd: string): string {\n const pm = detectPackageManager(cwd);\n const { uninstall } = PM_COMMANDS[pm];\n const extraFlags = pnpmWorkspaceFlags(pm, cwd);\n const flagString = extraFlags.length > 0 ? ` ${extraFlags.join(' ')}` : '';\n return `${pm} ${uninstall}${flagString} ${packages.join(' ')}`;\n}\n\nexport function installDependencies(cwd: string, packages: string[], label = 'packages'): void {\n if (packages.length === 0) return;\n if (process.env.SAFEWORD_SKIP_INSTALL) return;\n\n const pm = detectPackageManager(cwd);\n const { install } = PM_COMMANDS[pm];\n // pnpm workspaces require -w to install at the workspace root\n const extraFlags = pnpmWorkspaceFlags(pm, cwd);\n const flagString = extraFlags.length > 0 ? ` ${extraFlags.join(' ')}` : '';\n const displayCommand = `${pm} ${install} ${DEV_FLAG}${flagString} ${packages.join(' ')}`;\n\n info(`\\nInstalling ${label}...`);\n info(`Running: ${displayCommand}`);\n\n try {\n execFileSync(pm, [install, DEV_FLAG, ...extraFlags, ...packages], {\n cwd,\n stdio: 'pipe',\n timeout: 120_000,\n });\n success(`Installed ${label}`);\n } catch {\n warn(`Failed to install ${label}. Run manually:`);\n listItem(displayCommand);\n }\n}\n\n/**\n * MCP servers installed by safeword\n */\nexport const MCP_SERVERS = {\n context7: {\n url: 'https://mcp.context7.com/mcp',\n },\n playwright: {\n command: 'bunx',\n args: ['@playwright/mcp@latest'],\n },\n} as const;\n"],"mappings":";;;;;;;;AAOA,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,OAAO,UAAU;AAOjB,IAAM,WAAW;AAMjB,IAAM,cAA8E;AAAA,EAClF,KAAK,EAAE,SAAS,WAAW,WAAW,YAAY;AAAA,EAClD,MAAM,EAAE,SAAS,OAAO,WAAW,SAAS;AAAA,EAC5C,MAAM,EAAE,SAAS,OAAO,WAAW,SAAS;AAAA,EAC5C,KAAK,EAAE,SAAS,OAAO,WAAW,SAAS;AAC7C;AAEA,SAAS,gBAAgB,KAAsB;AAC7C,SAAO,WAAW,KAAK,KAAK,KAAK,qBAAqB,CAAC;AACzD;AAEA,SAAS,mBAAmB,IAAoB,KAAuB;AACrE,SAAO,OAAO,UAAU,gBAAgB,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC;AAC3D;AAOO,SAAS,qBAAqB,KAA6B;AAChE,MAAI,gBAAgB,GAAG,EAAG,QAAO;AACjC,MAAI,WAAW,KAAK,KAAK,KAAK,WAAW,CAAC,KAAK,WAAW,KAAK,KAAK,KAAK,UAAU,CAAC;AAClF,WAAO;AACT,MAAI,WAAW,KAAK,KAAK,KAAK,gBAAgB,CAAC,EAAG,QAAO;AACzD,MAAI,WAAW,KAAK,KAAK,KAAK,WAAW,CAAC,EAAG,QAAO;AACpD,MAAI,WAAW,KAAK,KAAK,KAAK,mBAAmB,CAAC,EAAG,QAAO;AAE5D,MAAI,QAAQ,SAAS,IAAK,QAAO;AACjC,SAAO;AACT;AAEO,SAAS,oBAAoB,UAAoB,KAAqB;AAC3E,QAAM,KAAK,qBAAqB,GAAG;AACnC,QAAM,EAAE,UAAU,IAAI,YAAY,EAAE;AACpC,QAAM,aAAa,mBAAmB,IAAI,GAAG;AAC7C,QAAM,aAAa,WAAW,SAAS,IAAI,IAAI,WAAW,KAAK,GAAG,CAAC,KAAK;AACxE,SAAO,GAAG,EAAE,IAAI,SAAS,GAAG,UAAU,IAAI,SAAS,KAAK,GAAG,CAAC;AAC9D;AAEO,SAAS,oBAAoB,KAAa,UAAoB,QAAQ,YAAkB;AAC7F,MAAI,SAAS,WAAW,EAAG;AAC3B,MAAI,QAAQ,IAAI,sBAAuB;AAEvC,QAAM,KAAK,qBAAqB,GAAG;AACnC,QAAM,EAAE,QAAQ,IAAI,YAAY,EAAE;AAElC,QAAM,aAAa,mBAAmB,IAAI,GAAG;AAC7C,QAAM,aAAa,WAAW,SAAS,IAAI,IAAI,WAAW,KAAK,GAAG,CAAC,KAAK;AACxE,QAAM,iBAAiB,GAAG,EAAE,IAAI,OAAO,IAAI,QAAQ,GAAG,UAAU,IAAI,SAAS,KAAK,GAAG,CAAC;AAEtF,OAAK;AAAA,aAAgB,KAAK,KAAK;AAC/B,OAAK,YAAY,cAAc,EAAE;AAEjC,MAAI;AACF,iBAAa,IAAI,CAAC,SAAS,UAAU,GAAG,YAAY,GAAG,QAAQ,GAAG;AAAA,MAChE;AAAA,MACA,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AACD,YAAQ,aAAa,KAAK,EAAE;AAAA,EAC9B,QAAQ;AACN,SAAK,qBAAqB,KAAK,iBAAiB;AAChD,aAAS,cAAc;AAAA,EACzB;AACF;AAKO,IAAM,cAAc;AAAA,EACzB,UAAU;AAAA,IACR,KAAK;AAAA,EACP;AAAA,EACA,YAAY;AAAA,IACV,SAAS;AAAA,IACT,MAAM,CAAC,wBAAwB;AAAA,EACjC;AACF;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  isDirectory,
3
3
  readFileSafe
4
- } from "./chunk-MPYFFJBF.js";
4
+ } from "./chunk-I2GV5QKO.js";
5
5
 
6
6
  // src/utils/configured-paths.ts
7
7
  import nodePath from "path";
@@ -117,4 +117,4 @@ export {
117
117
  defaultConfiguredPath,
118
118
  resolveConfiguredPath
119
119
  };
120
- //# sourceMappingURL=chunk-2WUL76K5.js.map
120
+ //# sourceMappingURL=chunk-HN3ZZETN.js.map
@@ -150,9 +150,9 @@ function makeScriptsExecutable(dirPath) {
150
150
  }
151
151
  }
152
152
  function readJson(path) {
153
- const content = readFileSafe(path);
154
- if (!content) return void 0;
155
153
  try {
154
+ const content = readFileSafe(path);
155
+ if (!content) return void 0;
156
156
  return JSON.parse(content);
157
157
  } catch {
158
158
  return void 0;
@@ -188,6 +188,12 @@ ${title}`);
188
188
  function listItem(item, indent = 2) {
189
189
  console.log(`${" ".repeat(indent)}\u2022 ${item}`);
190
190
  }
191
+ function printReconcileWarnings(warnings) {
192
+ if (warnings.length === 0) return;
193
+ warn(`
194
+ ${warnings.length} config(s) could not be updated:`);
195
+ for (const message of warnings) listItem(message);
196
+ }
191
197
  function keyValue(key, value) {
192
198
  console.log(` ${key}: ${value}`);
193
199
  }
@@ -214,6 +220,7 @@ export {
214
220
  error,
215
221
  header,
216
222
  listItem,
223
+ printReconcileWarnings,
217
224
  keyValue
218
225
  };
219
- //# sourceMappingURL=chunk-MPYFFJBF.js.map
226
+ //# sourceMappingURL=chunk-I2GV5QKO.js.map
@@ -1 +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 statSync,\n writeFileSync,\n} from 'node:fs';\nimport nodePath from 'node:path';\n\n// Get the directory of this module (for locating templates)\nconst __dirname = import.meta.dirname;\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 */\n/** True when `path` exists and is a directory (a file is not). */\nexport function isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function exists(path: string): boolean {\n return existsSync(path);\n}\n\n/**\n * Directories to exclude when scanning subdirectories for language manifests.\n * These contain vendored/generated files that would cause false positives.\n */\nconst SUBDIRECTORY_EXCLUDE = new Set([\n 'node_modules',\n '.git',\n '.safeword',\n 'vendor',\n 'dist',\n 'build',\n 'target',\n 'coverage',\n 'dbt_packages',\n 'out',\n '.next',\n '.nuxt',\n '.output',\n '__pycache__',\n '.venv',\n 'venv',\n]);\n\n/**\n * Check if a file exists anywhere in the project tree.\n * Recursively scans subdirectories, skipping excluded directories.\n *\n * @param cwd - Project root directory\n * @param filename - File to search for (e.g., 'pyproject.toml')\n * @returns true if found anywhere in the project tree\n */\nexport function existsInTree(cwd: string, filename: string): boolean {\n return findInTree(cwd, filename) !== undefined;\n}\n\n/**\n * Find a file anywhere in the project tree.\n * Recursively scans subdirectories, skipping excluded directories.\n * Root is checked first; deeper matches use depth-first traversal.\n *\n * @param cwd - Project root directory\n * @param filename - File to search for (e.g., 'pyproject.toml')\n * @param maxDepth - Maximum directory depth to scan (default: 10)\n * @returns Directory path where file was found, or undefined\n */\nexport function findInTree(cwd: string, filename: string, maxDepth = 10): string | undefined {\n // Check root first\n if (existsSync(nodePath.join(cwd, filename))) {\n return cwd;\n }\n\n return scanTreeForFile(cwd, filename, 1, maxDepth);\n}\n\n/** Return scannable subdirectory paths (excludes hidden dirs and SUBDIRECTORY_EXCLUDE). */\nfunction getScannableSubdirectories(directory: string): string[] {\n try {\n return readdirSync(directory, { withFileTypes: true })\n .filter(\n entry =>\n entry.isDirectory() &&\n !entry.name.startsWith('.') &&\n !SUBDIRECTORY_EXCLUDE.has(entry.name),\n )\n .map(entry => nodePath.join(directory, entry.name));\n } catch {\n return [];\n }\n}\n\nfunction scanTreeForFile(\n directory: string,\n filename: string,\n depth: number,\n maxDepth: number,\n): string | undefined {\n if (depth > maxDepth) return undefined;\n\n const subdirectories = getScannableSubdirectories(directory);\n\n // Check all children at this level first\n for (const subdirectory of subdirectories) {\n if (existsSync(nodePath.join(subdirectory, filename))) {\n return subdirectory;\n }\n }\n\n // Then recurse deeper\n for (const subdirectory of subdirectories) {\n const result = scanTreeForFile(subdirectory, filename, depth + 1, maxDepth);\n if (result !== undefined) return result;\n }\n\n return undefined;\n}\n\n/**\n * Index multiple files in a single tree walk — the multi-file analogue of\n * `findInTree`. Returns a Map from each requested filename to the directory of\n * its **shallowest** occurrence (root-first, level-order — same ordering as\n * `findInTree`), omitting names not found. One traversal regardless of how many\n * filenames are requested, so callers that probe many manifests stay O(one walk)\n * even on large/deep monorepos. Stops early once every name is located.\n */\nexport function indexFilesInTree(\n cwd: string,\n filenames: Iterable<string>,\n maxDepth = 10,\n): Map<string, string> {\n const wanted = [...filenames];\n const found = new Map<string, string>();\n // Cursor instead of Array.shift() so the BFS stays O(dirs), not O(dirs²) on\n // wide/deep monorepos — the very repos this single-walk index exists to serve.\n const queue: { directory: string; depth: number }[] = [{ directory: cwd, depth: 0 }];\n\n let head = 0;\n while (head < queue.length) {\n const item = queue[head];\n head += 1;\n if (item === undefined) break;\n for (const name of wanted) {\n if (!found.has(name) && existsSync(nodePath.join(item.directory, name))) {\n found.set(name, item.directory);\n }\n }\n if (found.size === wanted.length) break;\n if (item.depth >= maxDepth) continue;\n for (const subdirectory of getScannableSubdirectories(item.directory)) {\n queue.push({ directory: subdirectory, depth: item.depth + 1 });\n }\n }\n return found;\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 * Compose a glyph-prefixed line, hoisting any leading newlines ABOVE the glyph\n * so blank-line spacing renders before the marker instead of orphaning it on\n * its own line (ticket 469YSR). `('✓', '\\nFoo')` → `'\\n✓ Foo'`.\n * @param glyph the status glyph (✓ / ⚠ / ✗)\n * @param message the message, which may start with newline(s) for spacing\n */\nexport function formatGlyphLine(glyph: string, message: string): string {\n const leadingNewlines = /^\\n*/.exec(message)?.[0] ?? '';\n return `${leadingNewlines}${glyph} ${message.slice(leadingNewlines.length)}`;\n}\n\n/**\n * Print success message\n * @param message\n */\nexport function success(message: string): void {\n console.log(formatGlyphLine('✓', message));\n}\n\n/**\n * Print warning message\n * @param message\n */\nexport function warn(message: string): void {\n console.warn(formatGlyphLine('⚠', message));\n}\n\n/**\n * Print error message to stderr\n * @param message\n */\nexport function error(message: string): void {\n console.error(formatGlyphLine('✗', message));\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
+ {"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 statSync,\n writeFileSync,\n} from 'node:fs';\nimport nodePath from 'node:path';\n\n// Get the directory of this module (for locating templates)\nconst __dirname = import.meta.dirname;\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 */\n/** True when `path` exists and is a directory (a file is not). */\nexport function isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function exists(path: string): boolean {\n return existsSync(path);\n}\n\n/**\n * Directories to exclude when scanning subdirectories for language manifests.\n * These contain vendored/generated files that would cause false positives.\n */\nconst SUBDIRECTORY_EXCLUDE = new Set([\n 'node_modules',\n '.git',\n '.safeword',\n 'vendor',\n 'dist',\n 'build',\n 'target',\n 'coverage',\n 'dbt_packages',\n 'out',\n '.next',\n '.nuxt',\n '.output',\n '__pycache__',\n '.venv',\n 'venv',\n]);\n\n/**\n * Check if a file exists anywhere in the project tree.\n * Recursively scans subdirectories, skipping excluded directories.\n *\n * @param cwd - Project root directory\n * @param filename - File to search for (e.g., 'pyproject.toml')\n * @returns true if found anywhere in the project tree\n */\nexport function existsInTree(cwd: string, filename: string): boolean {\n return findInTree(cwd, filename) !== undefined;\n}\n\n/**\n * Find a file anywhere in the project tree.\n * Recursively scans subdirectories, skipping excluded directories.\n * Root is checked first; deeper matches use depth-first traversal.\n *\n * @param cwd - Project root directory\n * @param filename - File to search for (e.g., 'pyproject.toml')\n * @param maxDepth - Maximum directory depth to scan (default: 10)\n * @returns Directory path where file was found, or undefined\n */\nexport function findInTree(cwd: string, filename: string, maxDepth = 10): string | undefined {\n // Check root first\n if (existsSync(nodePath.join(cwd, filename))) {\n return cwd;\n }\n\n return scanTreeForFile(cwd, filename, 1, maxDepth);\n}\n\n/** Return scannable subdirectory paths (excludes hidden dirs and SUBDIRECTORY_EXCLUDE). */\nfunction getScannableSubdirectories(directory: string): string[] {\n try {\n return readdirSync(directory, { withFileTypes: true })\n .filter(\n entry =>\n entry.isDirectory() &&\n !entry.name.startsWith('.') &&\n !SUBDIRECTORY_EXCLUDE.has(entry.name),\n )\n .map(entry => nodePath.join(directory, entry.name));\n } catch {\n return [];\n }\n}\n\nfunction scanTreeForFile(\n directory: string,\n filename: string,\n depth: number,\n maxDepth: number,\n): string | undefined {\n if (depth > maxDepth) return undefined;\n\n const subdirectories = getScannableSubdirectories(directory);\n\n // Check all children at this level first\n for (const subdirectory of subdirectories) {\n if (existsSync(nodePath.join(subdirectory, filename))) {\n return subdirectory;\n }\n }\n\n // Then recurse deeper\n for (const subdirectory of subdirectories) {\n const result = scanTreeForFile(subdirectory, filename, depth + 1, maxDepth);\n if (result !== undefined) return result;\n }\n\n return undefined;\n}\n\n/**\n * Index multiple files in a single tree walk — the multi-file analogue of\n * `findInTree`. Returns a Map from each requested filename to the directory of\n * its **shallowest** occurrence (root-first, level-order — same ordering as\n * `findInTree`), omitting names not found. One traversal regardless of how many\n * filenames are requested, so callers that probe many manifests stay O(one walk)\n * even on large/deep monorepos. Stops early once every name is located.\n */\nexport function indexFilesInTree(\n cwd: string,\n filenames: Iterable<string>,\n maxDepth = 10,\n): Map<string, string> {\n const wanted = [...filenames];\n const found = new Map<string, string>();\n // Cursor instead of Array.shift() so the BFS stays O(dirs), not O(dirs²) on\n // wide/deep monorepos — the very repos this single-walk index exists to serve.\n const queue: { directory: string; depth: number }[] = [{ directory: cwd, depth: 0 }];\n\n let head = 0;\n while (head < queue.length) {\n const item = queue[head];\n head += 1;\n if (item === undefined) break;\n for (const name of wanted) {\n if (!found.has(name) && existsSync(nodePath.join(item.directory, name))) {\n found.set(name, item.directory);\n }\n }\n if (found.size === wanted.length) break;\n if (item.depth >= maxDepth) continue;\n for (const subdirectory of getScannableSubdirectories(item.directory)) {\n queue.push({ directory: subdirectory, depth: item.depth + 1 });\n }\n }\n return found;\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 and parse a JSON file, best-effort: returns `undefined` when no JSON\n * value can be obtained from the path — whether it is absent, empty, unparseable\n * (e.g. JSONC comments or malformed), or unreadable as a file at all. The read is\n * inside the `try` so a directory at the path (`EISDIR`) or a permission error\n * (`EACCES`) degrades to `undefined` like a parse failure, instead of throwing\n * uncaught and crashing callers (e.g. reconcile's jsonMerge). All callers already\n * treat the result as `T | undefined`.\n * @param path\n */\nexport function readJson(path: string): unknown {\n try {\n const content = readFileSafe(path);\n if (!content) return undefined;\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 * Compose a glyph-prefixed line, hoisting any leading newlines ABOVE the glyph\n * so blank-line spacing renders before the marker instead of orphaning it on\n * its own line (ticket 469YSR). `('✓', '\\nFoo')` → `'\\n✓ Foo'`.\n * @param glyph the status glyph (✓ / ⚠ / ✗)\n * @param message the message, which may start with newline(s) for spacing\n */\nexport function formatGlyphLine(glyph: string, message: string): string {\n const leadingNewlines = /^\\n*/.exec(message)?.[0] ?? '';\n return `${leadingNewlines}${glyph} ${message.slice(leadingNewlines.length)}`;\n}\n\n/**\n * Print success message\n * @param message\n */\nexport function success(message: string): void {\n console.log(formatGlyphLine('✓', message));\n}\n\n/**\n * Print warning message\n * @param message\n */\nexport function warn(message: string): void {\n console.warn(formatGlyphLine('⚠', message));\n}\n\n/**\n * Print error message to stderr\n * @param message\n */\nexport function error(message: string): void {\n console.error(formatGlyphLine('✗', message));\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 the non-fatal config-merge warnings collected during reconcile (e.g. a\n * jsonMerge target that exists but does not parse, so the merge was skipped).\n * No-op when empty. Shared by `setup` and `upgrade` so the heading stays in sync.\n * @param warnings\n */\nexport function printReconcileWarnings(warnings: string[]): void {\n if (warnings.length === 0) return;\n warn(`\\n${warnings.length} config(s) could not be updated:`);\n for (const message of warnings) listItem(message);\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":[]}
@@ -2,7 +2,7 @@ import {
2
2
  findDanglingDependencies,
3
3
  findTicketsInCycles,
4
4
  readTickets
5
- } from "./chunk-JLFYAVLP.js";
5
+ } from "./chunk-VVWHQBTU.js";
6
6
  import {
7
7
  formatTicketReference
8
8
  } from "./chunk-NHXVS5FL.js";
@@ -11,25 +11,25 @@ import {
11
11
  buildCoverageReportFromFeature,
12
12
  computeSkipMask,
13
13
  stripInlineComments
14
- } from "./chunk-IGULTNHR.js";
14
+ } from "./chunk-YXNI7W5D.js";
15
15
  import {
16
16
  FeatureParseError,
17
17
  findFeatureLineageIssues,
18
18
  findFeatureSourcePath
19
- } from "./chunk-QLXFPFIC.js";
19
+ } from "./chunk-XTLCJKGE.js";
20
20
  import {
21
21
  SAFEWORD_SCHEMA,
22
22
  createProjectContext,
23
23
  getMissingPacks,
24
24
  reconcile
25
- } from "./chunk-OJNF2CHC.js";
25
+ } from "./chunk-SW3D7PN7.js";
26
26
  import {
27
27
  defaultConfiguredPath,
28
28
  readConfiguredDocumentationSources,
29
29
  readConfiguredPath,
30
30
  resolveConfiguredPath,
31
31
  resolveTicketsDirectory
32
- } from "./chunk-2WUL76K5.js";
32
+ } from "./chunk-HN3ZZETN.js";
33
33
  import {
34
34
  VERSION
35
35
  } from "./chunk-HSC7TELY.js";
@@ -42,7 +42,7 @@ import {
42
42
  readFileSafe,
43
43
  success,
44
44
  warn
45
- } from "./chunk-MPYFFJBF.js";
45
+ } from "./chunk-I2GV5QKO.js";
46
46
 
47
47
  // src/health.ts
48
48
  import { readdirSync as readdirSync2 } from "fs";
@@ -85,7 +85,7 @@ function groupByLine(entries, pick) {
85
85
  }
86
86
  function findDuplicates(grouped, kind) {
87
87
  const issues = [];
88
- for (const [value, lines] of grouped.entries()) {
88
+ for (const [value, lines] of grouped) {
89
89
  if (lines.length <= 1) continue;
90
90
  for (const line of lines) {
91
91
  const others = lines.filter((other) => other !== line).join(", ");
@@ -99,15 +99,18 @@ function findDuplicates(grouped, kind) {
99
99
  function groupAliasesByLine(entries) {
100
100
  const grouped = /* @__PURE__ */ new Map();
101
101
  for (const entry of entries) {
102
- for (const alias of entry.aliases) {
103
- if (alias.length === 0) continue;
104
- const lines = grouped.get(alias) ?? [];
105
- lines.push(entry.lineNumber);
106
- grouped.set(alias, lines);
107
- }
102
+ addEntryAliasesToGroup(entry, grouped);
108
103
  }
109
104
  return grouped;
110
105
  }
106
+ function addEntryAliasesToGroup(entry, grouped) {
107
+ for (const alias of entry.aliases) {
108
+ if (alias.length === 0) continue;
109
+ const lines = grouped.get(alias) ?? [];
110
+ lines.push(entry.lineNumber);
111
+ grouped.set(alias, lines);
112
+ }
113
+ }
111
114
  function findAliasShadowingTerms(entries) {
112
115
  const termLines = /* @__PURE__ */ new Map();
113
116
  for (const entry of entries) {
@@ -220,7 +223,7 @@ function parseGlossary(content) {
220
223
  let current;
221
224
  let activeField;
222
225
  for (const [index, line] of lines.entries()) {
223
- if (skip[index]) continue;
226
+ if (skip[index] === true) continue;
224
227
  const headerName = parseTermHeader(line);
225
228
  if (headerName !== void 0) {
226
229
  if (current) entries.push(current);
@@ -280,7 +283,7 @@ function parsePersonas(content) {
280
283
  const personas = [];
281
284
  let current;
282
285
  for (const [index, line] of lines.entries()) {
283
- if (skip[index]) continue;
286
+ if (skip[index] === true) continue;
284
287
  const header2 = parseHeaderLine(line);
285
288
  if (header2) {
286
289
  if (current) personas.push(current);
@@ -459,15 +462,15 @@ function findArchitectureAdvisories(cwd) {
459
462
  });
460
463
  }
461
464
  function archAlignmentHasContent(implPlanContent) {
462
- let inSection = false;
465
+ let isInSection = false;
463
466
  const body = [];
464
467
  for (const raw of implPlanContent.split("\n")) {
465
468
  const line = raw.trim();
466
469
  if (line.startsWith("## ")) {
467
- inSection = line.slice(3).trim().toLowerCase() === "arch alignment";
470
+ isInSection = line.slice(3).trim().toLowerCase() === "arch alignment";
468
471
  continue;
469
472
  }
470
- if (inSection && line !== "") body.push(line);
473
+ if (isInSection && line !== "") body.push(line);
471
474
  }
472
475
  if (body.length === 0) return false;
473
476
  return !(body.length === 1 && (body[0] ?? "").toLowerCase().startsWith("skip:"));
@@ -698,4 +701,4 @@ export {
698
701
  checkHealth,
699
702
  reportHealthSummary
700
703
  };
701
- //# sourceMappingURL=chunk-3RM6NYZT.js.map
704
+ //# sourceMappingURL=chunk-N74UNUZ6.js.map