safeword 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{check-PECCGHEA.js → check-OYYSYHFP.js} +41 -23
- package/dist/check-OYYSYHFP.js.map +1 -0
- package/dist/chunk-LNSEDZIW.js +454 -0
- package/dist/chunk-LNSEDZIW.js.map +1 -0
- package/dist/chunk-ZS3Z3Q37.js +729 -0
- package/dist/chunk-ZS3Z3Q37.js.map +1 -0
- package/dist/cli.js +7 -7
- package/dist/cli.js.map +1 -1
- package/dist/diff-325TIZ63.js +168 -0
- package/dist/diff-325TIZ63.js.map +1 -0
- package/dist/reset-ZGJIKMUW.js +74 -0
- package/dist/reset-ZGJIKMUW.js.map +1 -0
- package/dist/setup-GAMXTFM2.js +103 -0
- package/dist/setup-GAMXTFM2.js.map +1 -0
- package/dist/{sync-4XBMKLXS.js → sync-BFMXZEHM.js} +33 -32
- package/dist/sync-BFMXZEHM.js.map +1 -0
- package/dist/upgrade-X4GREJXN.js +73 -0
- package/dist/upgrade-X4GREJXN.js.map +1 -0
- package/package.json +15 -14
- package/templates/SAFEWORD.md +101 -689
- package/templates/guides/architecture-guide.md +1 -1
- package/templates/guides/cli-reference.md +35 -0
- package/templates/guides/code-philosophy.md +22 -19
- package/templates/guides/context-files-guide.md +2 -2
- package/templates/guides/data-architecture-guide.md +1 -1
- package/templates/guides/design-doc-guide.md +1 -1
- package/templates/guides/{testing-methodology.md → development-workflow.md} +1 -1
- package/templates/guides/learning-extraction.md +1 -1
- package/templates/guides/{llm-instruction-design.md → llm-guide.md} +93 -29
- package/templates/guides/tdd-best-practices.md +2 -2
- package/templates/guides/test-definitions-guide.md +1 -1
- package/templates/guides/user-story-guide.md +1 -1
- package/templates/guides/zombie-process-cleanup.md +1 -1
- package/dist/check-PECCGHEA.js.map +0 -1
- package/dist/chunk-6CVTH67L.js +0 -43
- package/dist/chunk-6CVTH67L.js.map +0 -1
- package/dist/chunk-75FKNZUM.js +0 -15
- package/dist/chunk-75FKNZUM.js.map +0 -1
- package/dist/chunk-ARIAOK2F.js +0 -110
- package/dist/chunk-ARIAOK2F.js.map +0 -1
- package/dist/chunk-FRPJITGG.js +0 -35
- package/dist/chunk-FRPJITGG.js.map +0 -1
- package/dist/chunk-IWWBZVHT.js +0 -274
- package/dist/chunk-IWWBZVHT.js.map +0 -1
- package/dist/diff-ZACVJKOU.js +0 -171
- package/dist/diff-ZACVJKOU.js.map +0 -1
- package/dist/reset-5SRM3P6J.js +0 -145
- package/dist/reset-5SRM3P6J.js.map +0 -1
- package/dist/setup-65EVU5OT.js +0 -437
- package/dist/setup-65EVU5OT.js.map +0 -1
- package/dist/sync-4XBMKLXS.js.map +0 -1
- package/dist/upgrade-P3WX3ODU.js +0 -153
- package/dist/upgrade-P3WX3ODU.js.map +0 -1
- package/templates/guides/llm-prompting.md +0 -102
- /package/templates/prompts/{review.md → quality-review.md} +0 -0
|
@@ -2,20 +2,22 @@ import {
|
|
|
2
2
|
isNewerVersion
|
|
3
3
|
} from "./chunk-W66Z3C5H.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
} from "./chunk-ORQHKDT2.js";
|
|
7
|
-
import {
|
|
5
|
+
createProjectContext,
|
|
8
6
|
header,
|
|
9
7
|
info,
|
|
10
8
|
keyValue,
|
|
9
|
+
reconcile,
|
|
11
10
|
success,
|
|
12
11
|
warn
|
|
13
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LNSEDZIW.js";
|
|
14
13
|
import {
|
|
14
|
+
SAFEWORD_SCHEMA,
|
|
15
15
|
exists,
|
|
16
|
-
readFile,
|
|
17
16
|
readFileSafe
|
|
18
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-ZS3Z3Q37.js";
|
|
18
|
+
import {
|
|
19
|
+
VERSION
|
|
20
|
+
} from "./chunk-ORQHKDT2.js";
|
|
19
21
|
|
|
20
22
|
// src/commands/check.ts
|
|
21
23
|
import { join } from "path";
|
|
@@ -34,9 +36,8 @@ async function checkLatestVersion(timeout = 3e3) {
|
|
|
34
36
|
return null;
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
|
-
function checkHealth(cwd) {
|
|
39
|
+
async function checkHealth(cwd) {
|
|
38
40
|
const safewordDir = join(cwd, ".safeword");
|
|
39
|
-
const issues = [];
|
|
40
41
|
if (!exists(safewordDir)) {
|
|
41
42
|
return {
|
|
42
43
|
configured: false,
|
|
@@ -44,25 +45,37 @@ function checkHealth(cwd) {
|
|
|
44
45
|
cliVersion: VERSION,
|
|
45
46
|
updateAvailable: false,
|
|
46
47
|
latestVersion: null,
|
|
47
|
-
issues: []
|
|
48
|
+
issues: [],
|
|
49
|
+
missingPackages: []
|
|
48
50
|
};
|
|
49
51
|
}
|
|
50
52
|
const versionPath = join(safewordDir, "version");
|
|
51
53
|
const projectVersion = readFileSafe(versionPath)?.trim() ?? null;
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const ctx = createProjectContext(cwd);
|
|
55
|
+
const result = await reconcile(SAFEWORD_SCHEMA, "upgrade", ctx, { dryRun: true });
|
|
56
|
+
const issues = [];
|
|
57
|
+
const writeActions = result.actions.filter((a) => a.type === "write");
|
|
58
|
+
for (const action of writeActions) {
|
|
59
|
+
if (action.type === "write") {
|
|
60
|
+
const fullPath = join(cwd, action.path);
|
|
61
|
+
if (!exists(fullPath)) {
|
|
62
|
+
issues.push(`Missing: ${action.path}`);
|
|
63
|
+
}
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
const textPatchActions = result.actions.filter((a) => a.type === "text-patch");
|
|
67
|
+
for (const action of textPatchActions) {
|
|
68
|
+
if (action.type === "text-patch") {
|
|
69
|
+
const fullPath = join(cwd, action.path);
|
|
70
|
+
if (!exists(fullPath)) {
|
|
71
|
+
issues.push(`${action.path} file missing`);
|
|
72
|
+
} else {
|
|
73
|
+
const content = readFileSafe(fullPath) ?? "";
|
|
74
|
+
if (!content.includes(action.definition.marker)) {
|
|
75
|
+
issues.push(`${action.path} missing safeword link`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
63
78
|
}
|
|
64
|
-
} else {
|
|
65
|
-
issues.push("AGENTS.md file missing");
|
|
66
79
|
}
|
|
67
80
|
const settingsPath = join(cwd, ".claude", "settings.json");
|
|
68
81
|
if (!exists(settingsPath)) {
|
|
@@ -74,13 +87,14 @@ function checkHealth(cwd) {
|
|
|
74
87
|
cliVersion: VERSION,
|
|
75
88
|
updateAvailable: false,
|
|
76
89
|
latestVersion: null,
|
|
77
|
-
issues
|
|
90
|
+
issues,
|
|
91
|
+
missingPackages: result.packagesToInstall
|
|
78
92
|
};
|
|
79
93
|
}
|
|
80
94
|
async function check(options) {
|
|
81
95
|
const cwd = process.cwd();
|
|
82
96
|
header("Safeword Health Check");
|
|
83
|
-
const health = checkHealth(cwd);
|
|
97
|
+
const health = await checkHealth(cwd);
|
|
84
98
|
if (!health.configured) {
|
|
85
99
|
info("Not configured. Run `safeword setup` to initialize.");
|
|
86
100
|
return;
|
|
@@ -121,6 +135,10 @@ Upgrade available for project config`);
|
|
|
121
135
|
warn(issue);
|
|
122
136
|
}
|
|
123
137
|
info("\nRun `safeword upgrade` to repair configuration");
|
|
138
|
+
} else if (health.missingPackages.length > 0) {
|
|
139
|
+
header("Missing Packages");
|
|
140
|
+
info(`${health.missingPackages.length} linting packages not installed`);
|
|
141
|
+
info("Run `safeword sync` to install missing packages");
|
|
124
142
|
} else {
|
|
125
143
|
success("\nConfiguration is healthy");
|
|
126
144
|
}
|
|
@@ -128,4 +146,4 @@ Upgrade available for project config`);
|
|
|
128
146
|
export {
|
|
129
147
|
check
|
|
130
148
|
};
|
|
131
|
-
//# sourceMappingURL=check-
|
|
149
|
+
//# sourceMappingURL=check-OYYSYHFP.js.map
|
|
@@ -0,0 +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 { join } from 'node:path';\nimport { VERSION } from '../version.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { info, success, warn, header, keyValue } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { reconcile } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | null;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | null;\n issues: string[];\n missingPackages: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch('https://registry.npmjs.org/safeword/latest', {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) return null;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check project configuration health using reconcile dryRun\n */\nasync function checkHealth(cwd: string): Promise<HealthStatus> {\n const safewordDir = join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDir)) {\n return {\n configured: false,\n projectVersion: null,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues: [],\n missingPackages: [],\n };\n }\n\n // Read project version\n const versionPath = join(safewordDir, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? null;\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 const issues: string[] = [];\n\n // Check for missing owned files (write actions indicate missing/changed files)\n const writeActions = result.actions.filter(a => a.type === 'write');\n for (const action of writeActions) {\n if (action.type === 'write') {\n const fullPath = join(cwd, action.path);\n if (!exists(fullPath)) {\n issues.push(`Missing: ${action.path}`);\n }\n }\n }\n\n // Check for missing text patches (e.g., AGENTS.md link)\n const textPatchActions = result.actions.filter(a => a.type === 'text-patch');\n for (const action of textPatchActions) {\n if (action.type === 'text-patch') {\n const fullPath = join(cwd, action.path);\n if (!exists(fullPath)) {\n issues.push(`${action.path} file missing`);\n } else {\n const content = readFileSafe(fullPath) ?? '';\n if (!content.includes(action.definition.marker)) {\n issues.push(`${action.path} missing safeword link`);\n }\n }\n }\n }\n\n // Check for missing .claude/settings.json\n const settingsPath = join(cwd, '.claude', 'settings.json');\n if (!exists(settingsPath)) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues,\n missingPackages: result.packagesToInstall,\n };\n}\n\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = 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('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (latestVersion) {\n health.latestVersion = latestVersion;\n health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);\n\n if (health.updateAvailable) {\n warn(`Update available: v${latestVersion}`);\n info('Run `npm install -g safeword` to upgrade');\n } else {\n success('CLI is up to date');\n }\n } else {\n warn(\"Couldn't check for updates (offline?)\");\n }\n } else {\n info('\\nSkipped update check (offline mode)');\n }\n\n // Check project version vs CLI version\n if (health.projectVersion && isNewerVersion(health.cliVersion, health.projectVersion)) {\n warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);\n info('Consider upgrading the CLI');\n } else if (health.projectVersion && isNewerVersion(health.projectVersion, health.cliVersion)) {\n info(`\\nUpgrade available for project config`);\n info(\n `Run \\`safeword upgrade\\` to update from v${health.projectVersion} to v${health.cliVersion}`,\n );\n }\n\n // Show issues\n if (health.issues.length > 0) {\n header('Issues Found');\n for (const issue of health.issues) {\n warn(issue);\n }\n info('\\nRun `safeword upgrade` to repair configuration');\n } else if (health.missingPackages.length > 0) {\n header('Missing Packages');\n info(`${health.missingPackages.length} linting packages not installed`);\n info('Run `safeword sync` to install missing packages');\n } else {\n success('\\nConfiguration is healthy');\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectProjectType,
|
|
3
|
+
ensureDir,
|
|
4
|
+
exists,
|
|
5
|
+
getTemplatesDir,
|
|
6
|
+
makeScriptsExecutable,
|
|
7
|
+
readFile,
|
|
8
|
+
readFileSafe,
|
|
9
|
+
readJson,
|
|
10
|
+
remove,
|
|
11
|
+
removeIfEmpty,
|
|
12
|
+
writeFile,
|
|
13
|
+
writeJson
|
|
14
|
+
} from "./chunk-ZS3Z3Q37.js";
|
|
15
|
+
|
|
16
|
+
// src/utils/output.ts
|
|
17
|
+
function info(message) {
|
|
18
|
+
console.log(message);
|
|
19
|
+
}
|
|
20
|
+
function success(message) {
|
|
21
|
+
console.log(`\u2713 ${message}`);
|
|
22
|
+
}
|
|
23
|
+
function warn(message) {
|
|
24
|
+
console.warn(`\u26A0 ${message}`);
|
|
25
|
+
}
|
|
26
|
+
function error(message) {
|
|
27
|
+
console.error(`\u2717 ${message}`);
|
|
28
|
+
}
|
|
29
|
+
function header(title) {
|
|
30
|
+
console.log(`
|
|
31
|
+
${title}`);
|
|
32
|
+
console.log("\u2500".repeat(title.length));
|
|
33
|
+
}
|
|
34
|
+
function listItem(item, indent = 2) {
|
|
35
|
+
console.log(`${" ".repeat(indent)}\u2022 ${item}`);
|
|
36
|
+
}
|
|
37
|
+
function keyValue(key, value) {
|
|
38
|
+
console.log(` ${key}: ${value}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/utils/git.ts
|
|
42
|
+
import { join } from "path";
|
|
43
|
+
function isGitRepo(cwd) {
|
|
44
|
+
return exists(join(cwd, ".git"));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/utils/context.ts
|
|
48
|
+
import { join as join2 } from "path";
|
|
49
|
+
function createProjectContext(cwd) {
|
|
50
|
+
const packageJson = readJson(join2(cwd, "package.json"));
|
|
51
|
+
return {
|
|
52
|
+
cwd,
|
|
53
|
+
projectType: detectProjectType(packageJson ?? {}),
|
|
54
|
+
devDeps: packageJson?.devDependencies ?? {},
|
|
55
|
+
isGitRepo: isGitRepo(cwd)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/reconcile.ts
|
|
60
|
+
import { join as join3 } from "path";
|
|
61
|
+
var HUSKY_DIR = ".husky";
|
|
62
|
+
async function reconcile(schema, mode, ctx, options) {
|
|
63
|
+
const dryRun = options?.dryRun ?? false;
|
|
64
|
+
const plan = computePlan(schema, mode, ctx);
|
|
65
|
+
if (dryRun) {
|
|
66
|
+
return {
|
|
67
|
+
actions: plan.actions,
|
|
68
|
+
applied: false,
|
|
69
|
+
created: plan.wouldCreate,
|
|
70
|
+
updated: plan.wouldUpdate,
|
|
71
|
+
removed: plan.wouldRemove,
|
|
72
|
+
packagesToInstall: plan.packagesToInstall,
|
|
73
|
+
packagesToRemove: plan.packagesToRemove
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const result = executePlan(plan, ctx);
|
|
77
|
+
return {
|
|
78
|
+
actions: plan.actions,
|
|
79
|
+
applied: true,
|
|
80
|
+
created: result.created,
|
|
81
|
+
updated: result.updated,
|
|
82
|
+
removed: result.removed,
|
|
83
|
+
packagesToInstall: plan.packagesToInstall,
|
|
84
|
+
packagesToRemove: plan.packagesToRemove
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function computePlan(schema, mode, ctx) {
|
|
88
|
+
switch (mode) {
|
|
89
|
+
case "install":
|
|
90
|
+
return computeInstallPlan(schema, ctx);
|
|
91
|
+
case "upgrade":
|
|
92
|
+
return computeUpgradePlan(schema, ctx);
|
|
93
|
+
case "uninstall":
|
|
94
|
+
return computeUninstallPlan(schema, ctx, false);
|
|
95
|
+
case "uninstall-full":
|
|
96
|
+
return computeUninstallPlan(schema, ctx, true);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function computeInstallPlan(schema, ctx) {
|
|
100
|
+
const actions = [];
|
|
101
|
+
const wouldCreate = [];
|
|
102
|
+
const allDirs = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];
|
|
103
|
+
for (const dir of allDirs) {
|
|
104
|
+
if (dir.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;
|
|
105
|
+
const fullPath = join3(ctx.cwd, dir);
|
|
106
|
+
if (!exists(fullPath)) {
|
|
107
|
+
actions.push({ type: "mkdir", path: dir });
|
|
108
|
+
wouldCreate.push(dir);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const [filePath, def] of Object.entries(schema.ownedFiles)) {
|
|
112
|
+
if (filePath.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;
|
|
113
|
+
const content = resolveFileContent(def, ctx);
|
|
114
|
+
actions.push({ type: "write", path: filePath, content });
|
|
115
|
+
wouldCreate.push(filePath);
|
|
116
|
+
}
|
|
117
|
+
for (const [filePath, def] of Object.entries(schema.managedFiles)) {
|
|
118
|
+
const fullPath = join3(ctx.cwd, filePath);
|
|
119
|
+
if (!exists(fullPath)) {
|
|
120
|
+
const content = resolveFileContent(def, ctx);
|
|
121
|
+
actions.push({ type: "write", path: filePath, content });
|
|
122
|
+
wouldCreate.push(filePath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const chmodPaths = [".safeword/hooks", ".safeword/lib"];
|
|
126
|
+
if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);
|
|
127
|
+
actions.push({ type: "chmod", paths: chmodPaths });
|
|
128
|
+
for (const [filePath, def] of Object.entries(schema.jsonMerges)) {
|
|
129
|
+
actions.push({ type: "json-merge", path: filePath, definition: def });
|
|
130
|
+
}
|
|
131
|
+
for (const [filePath, def] of Object.entries(schema.textPatches)) {
|
|
132
|
+
actions.push({ type: "text-patch", path: filePath, definition: def });
|
|
133
|
+
if (def.createIfMissing && !exists(join3(ctx.cwd, filePath))) {
|
|
134
|
+
wouldCreate.push(filePath);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const packagesToInstall = computePackagesToInstall(
|
|
138
|
+
schema,
|
|
139
|
+
ctx.projectType,
|
|
140
|
+
ctx.devDeps,
|
|
141
|
+
ctx.isGitRepo
|
|
142
|
+
);
|
|
143
|
+
return {
|
|
144
|
+
actions,
|
|
145
|
+
wouldCreate,
|
|
146
|
+
wouldUpdate: [],
|
|
147
|
+
wouldRemove: [],
|
|
148
|
+
packagesToInstall,
|
|
149
|
+
packagesToRemove: []
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function computeUpgradePlan(schema, ctx) {
|
|
153
|
+
const actions = [];
|
|
154
|
+
const wouldCreate = [];
|
|
155
|
+
const wouldUpdate = [];
|
|
156
|
+
const allDirs = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];
|
|
157
|
+
for (const dir of allDirs) {
|
|
158
|
+
if (dir.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;
|
|
159
|
+
const fullPath = join3(ctx.cwd, dir);
|
|
160
|
+
if (!exists(fullPath)) {
|
|
161
|
+
actions.push({ type: "mkdir", path: dir });
|
|
162
|
+
wouldCreate.push(dir);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const [filePath, def] of Object.entries(schema.ownedFiles)) {
|
|
166
|
+
if (filePath.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;
|
|
167
|
+
const fullPath = join3(ctx.cwd, filePath);
|
|
168
|
+
const newContent = resolveFileContent(def, ctx);
|
|
169
|
+
if (fileNeedsUpdate(fullPath, newContent)) {
|
|
170
|
+
actions.push({ type: "write", path: filePath, content: newContent });
|
|
171
|
+
if (exists(fullPath)) {
|
|
172
|
+
wouldUpdate.push(filePath);
|
|
173
|
+
} else {
|
|
174
|
+
wouldCreate.push(filePath);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const [filePath, def] of Object.entries(schema.managedFiles)) {
|
|
179
|
+
const fullPath = join3(ctx.cwd, filePath);
|
|
180
|
+
const newContent = resolveFileContent(def, ctx);
|
|
181
|
+
if (!exists(fullPath)) {
|
|
182
|
+
actions.push({ type: "write", path: filePath, content: newContent });
|
|
183
|
+
wouldCreate.push(filePath);
|
|
184
|
+
} else {
|
|
185
|
+
const currentContent = readFileSafe(fullPath);
|
|
186
|
+
if (currentContent?.trim() === newContent.trim()) {
|
|
187
|
+
} else {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const chmodPaths = [".safeword/hooks", ".safeword/lib"];
|
|
192
|
+
if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);
|
|
193
|
+
actions.push({ type: "chmod", paths: chmodPaths });
|
|
194
|
+
for (const [filePath, def] of Object.entries(schema.jsonMerges)) {
|
|
195
|
+
actions.push({ type: "json-merge", path: filePath, definition: def });
|
|
196
|
+
}
|
|
197
|
+
for (const [filePath, def] of Object.entries(schema.textPatches)) {
|
|
198
|
+
const fullPath = join3(ctx.cwd, filePath);
|
|
199
|
+
const content = readFileSafe(fullPath) ?? "";
|
|
200
|
+
if (!content.includes(def.marker)) {
|
|
201
|
+
actions.push({ type: "text-patch", path: filePath, definition: def });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const packagesToInstall = computePackagesToInstall(
|
|
205
|
+
schema,
|
|
206
|
+
ctx.projectType,
|
|
207
|
+
ctx.devDeps,
|
|
208
|
+
ctx.isGitRepo
|
|
209
|
+
);
|
|
210
|
+
return {
|
|
211
|
+
actions,
|
|
212
|
+
wouldCreate,
|
|
213
|
+
wouldUpdate,
|
|
214
|
+
wouldRemove: [],
|
|
215
|
+
packagesToInstall,
|
|
216
|
+
packagesToRemove: []
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function computeUninstallPlan(schema, ctx, full) {
|
|
220
|
+
const actions = [];
|
|
221
|
+
const wouldRemove = [];
|
|
222
|
+
const dirsToCleanup = /* @__PURE__ */ new Set();
|
|
223
|
+
for (const filePath of Object.keys(schema.ownedFiles)) {
|
|
224
|
+
const fullPath = join3(ctx.cwd, filePath);
|
|
225
|
+
if (exists(fullPath)) {
|
|
226
|
+
actions.push({ type: "rm", path: filePath });
|
|
227
|
+
wouldRemove.push(filePath);
|
|
228
|
+
if (filePath.startsWith(".claude/")) {
|
|
229
|
+
const parentDir = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
230
|
+
if (parentDir && parentDir !== ".claude" && parentDir !== ".claude/skills" && parentDir !== ".claude/commands") {
|
|
231
|
+
dirsToCleanup.add(parentDir);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
for (const dir of dirsToCleanup) {
|
|
237
|
+
const fullPath = join3(ctx.cwd, dir);
|
|
238
|
+
if (exists(fullPath)) {
|
|
239
|
+
actions.push({ type: "rmdir", path: dir });
|
|
240
|
+
wouldRemove.push(dir);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (const [filePath, def] of Object.entries(schema.jsonMerges)) {
|
|
244
|
+
actions.push({ type: "json-unmerge", path: filePath, definition: def });
|
|
245
|
+
}
|
|
246
|
+
for (const [filePath, def] of Object.entries(schema.textPatches)) {
|
|
247
|
+
const fullPath = join3(ctx.cwd, filePath);
|
|
248
|
+
if (exists(fullPath)) {
|
|
249
|
+
const content = readFileSafe(fullPath) ?? "";
|
|
250
|
+
if (content.includes(def.marker)) {
|
|
251
|
+
actions.push({ type: "text-unpatch", path: filePath, definition: def });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const preservedDirsToRemove = [...schema.preservedDirs].reverse();
|
|
256
|
+
for (const dir of preservedDirsToRemove) {
|
|
257
|
+
const fullPath = join3(ctx.cwd, dir);
|
|
258
|
+
if (exists(fullPath)) {
|
|
259
|
+
actions.push({ type: "rmdir", path: dir });
|
|
260
|
+
wouldRemove.push(dir);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const dirsToRemove = [...schema.ownedDirs].reverse();
|
|
264
|
+
for (const dir of dirsToRemove) {
|
|
265
|
+
const fullPath = join3(ctx.cwd, dir);
|
|
266
|
+
if (exists(fullPath)) {
|
|
267
|
+
actions.push({ type: "rmdir", path: dir });
|
|
268
|
+
wouldRemove.push(dir);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (full) {
|
|
272
|
+
for (const filePath of Object.keys(schema.managedFiles)) {
|
|
273
|
+
const fullPath = join3(ctx.cwd, filePath);
|
|
274
|
+
if (exists(fullPath)) {
|
|
275
|
+
actions.push({ type: "rm", path: filePath });
|
|
276
|
+
wouldRemove.push(filePath);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const packagesToRemove = full ? computePackagesToRemove(schema, ctx.projectType, ctx.devDeps) : [];
|
|
281
|
+
return {
|
|
282
|
+
actions,
|
|
283
|
+
wouldCreate: [],
|
|
284
|
+
wouldUpdate: [],
|
|
285
|
+
wouldRemove,
|
|
286
|
+
packagesToInstall: [],
|
|
287
|
+
packagesToRemove
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function executePlan(plan, ctx) {
|
|
291
|
+
const created = [];
|
|
292
|
+
const updated = [];
|
|
293
|
+
const removed = [];
|
|
294
|
+
for (const action of plan.actions) {
|
|
295
|
+
switch (action.type) {
|
|
296
|
+
case "mkdir": {
|
|
297
|
+
const fullPath = join3(ctx.cwd, action.path);
|
|
298
|
+
ensureDir(fullPath);
|
|
299
|
+
created.push(action.path);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case "rmdir": {
|
|
303
|
+
const fullPath = join3(ctx.cwd, action.path);
|
|
304
|
+
if (removeIfEmpty(fullPath)) {
|
|
305
|
+
removed.push(action.path);
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case "write": {
|
|
310
|
+
const fullPath = join3(ctx.cwd, action.path);
|
|
311
|
+
const existed = exists(fullPath);
|
|
312
|
+
writeFile(fullPath, action.content);
|
|
313
|
+
if (existed) {
|
|
314
|
+
updated.push(action.path);
|
|
315
|
+
} else {
|
|
316
|
+
created.push(action.path);
|
|
317
|
+
}
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
case "rm": {
|
|
321
|
+
const fullPath = join3(ctx.cwd, action.path);
|
|
322
|
+
remove(fullPath);
|
|
323
|
+
removed.push(action.path);
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case "chmod": {
|
|
327
|
+
for (const path of action.paths) {
|
|
328
|
+
const fullPath = join3(ctx.cwd, path);
|
|
329
|
+
if (exists(fullPath)) {
|
|
330
|
+
makeScriptsExecutable(fullPath);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
case "json-merge": {
|
|
336
|
+
executeJsonMerge(ctx.cwd, action.path, action.definition, ctx);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
case "json-unmerge": {
|
|
340
|
+
executeJsonUnmerge(ctx.cwd, action.path, action.definition);
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
case "text-patch": {
|
|
344
|
+
executeTextPatch(ctx.cwd, action.path, action.definition);
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
case "text-unpatch": {
|
|
348
|
+
executeTextUnpatch(ctx.cwd, action.path, action.definition);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return { created, updated, removed };
|
|
354
|
+
}
|
|
355
|
+
function resolveFileContent(def, ctx) {
|
|
356
|
+
if (def.template) {
|
|
357
|
+
const templatesDir = getTemplatesDir();
|
|
358
|
+
return readFile(join3(templatesDir, def.template));
|
|
359
|
+
}
|
|
360
|
+
if (def.content) {
|
|
361
|
+
return typeof def.content === "function" ? def.content() : def.content;
|
|
362
|
+
}
|
|
363
|
+
if (def.generator) {
|
|
364
|
+
return def.generator(ctx);
|
|
365
|
+
}
|
|
366
|
+
throw new Error("FileDefinition must have template, content, or generator");
|
|
367
|
+
}
|
|
368
|
+
function fileNeedsUpdate(installedPath, newContent) {
|
|
369
|
+
if (!exists(installedPath)) return true;
|
|
370
|
+
const currentContent = readFileSafe(installedPath);
|
|
371
|
+
return currentContent?.trim() !== newContent.trim();
|
|
372
|
+
}
|
|
373
|
+
var GIT_ONLY_PACKAGES = ["husky", "lint-staged"];
|
|
374
|
+
function computePackagesToInstall(schema, projectType, installedDevDeps, isGitRepo2 = true) {
|
|
375
|
+
let needed = [...schema.packages.base];
|
|
376
|
+
if (!isGitRepo2) {
|
|
377
|
+
needed = needed.filter((pkg) => !GIT_ONLY_PACKAGES.includes(pkg));
|
|
378
|
+
}
|
|
379
|
+
for (const [key, deps] of Object.entries(schema.packages.conditional)) {
|
|
380
|
+
if (projectType[key]) {
|
|
381
|
+
needed.push(...deps);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return needed.filter((pkg) => !(pkg in installedDevDeps));
|
|
385
|
+
}
|
|
386
|
+
function computePackagesToRemove(schema, projectType, installedDevDeps) {
|
|
387
|
+
const safewordPackages = [...schema.packages.base];
|
|
388
|
+
for (const [key, deps] of Object.entries(schema.packages.conditional)) {
|
|
389
|
+
if (projectType[key]) {
|
|
390
|
+
safewordPackages.push(...deps);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return safewordPackages.filter((pkg) => pkg in installedDevDeps);
|
|
394
|
+
}
|
|
395
|
+
function executeJsonMerge(cwd, path, def, ctx) {
|
|
396
|
+
const fullPath = join3(cwd, path);
|
|
397
|
+
const existing = readJson(fullPath) ?? {};
|
|
398
|
+
const merged = def.merge(existing, ctx);
|
|
399
|
+
writeJson(fullPath, merged);
|
|
400
|
+
}
|
|
401
|
+
function executeJsonUnmerge(cwd, path, def) {
|
|
402
|
+
const fullPath = join3(cwd, path);
|
|
403
|
+
if (!exists(fullPath)) return;
|
|
404
|
+
const existing = readJson(fullPath);
|
|
405
|
+
if (!existing) return;
|
|
406
|
+
const unmerged = def.unmerge(existing);
|
|
407
|
+
if (def.removeFileIfEmpty) {
|
|
408
|
+
const remainingKeys = Object.keys(unmerged).filter(
|
|
409
|
+
(k) => unmerged[k] !== void 0 && unmerged[k] !== null
|
|
410
|
+
);
|
|
411
|
+
if (remainingKeys.length === 0) {
|
|
412
|
+
remove(fullPath);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
writeJson(fullPath, unmerged);
|
|
417
|
+
}
|
|
418
|
+
function executeTextPatch(cwd, path, def) {
|
|
419
|
+
const fullPath = join3(cwd, path);
|
|
420
|
+
let content = readFileSafe(fullPath) ?? "";
|
|
421
|
+
if (content.includes(def.marker)) return;
|
|
422
|
+
if (def.operation === "prepend") {
|
|
423
|
+
content = def.content + content;
|
|
424
|
+
} else {
|
|
425
|
+
content = content + def.content;
|
|
426
|
+
}
|
|
427
|
+
writeFile(fullPath, content);
|
|
428
|
+
}
|
|
429
|
+
function executeTextUnpatch(cwd, path, def) {
|
|
430
|
+
const fullPath = join3(cwd, path);
|
|
431
|
+
const content = readFileSafe(fullPath);
|
|
432
|
+
if (!content) return;
|
|
433
|
+
let unpatched = content.replace(def.content, "");
|
|
434
|
+
if (unpatched === content && content.includes(def.marker)) {
|
|
435
|
+
const lines = content.split("\n");
|
|
436
|
+
const filtered = lines.filter((line) => !line.includes(def.marker));
|
|
437
|
+
unpatched = filtered.join("\n").replace(/^\n+/, "");
|
|
438
|
+
}
|
|
439
|
+
writeFile(fullPath, unpatched);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export {
|
|
443
|
+
info,
|
|
444
|
+
success,
|
|
445
|
+
warn,
|
|
446
|
+
error,
|
|
447
|
+
header,
|
|
448
|
+
listItem,
|
|
449
|
+
keyValue,
|
|
450
|
+
isGitRepo,
|
|
451
|
+
createProjectContext,
|
|
452
|
+
reconcile
|
|
453
|
+
};
|
|
454
|
+
//# sourceMappingURL=chunk-LNSEDZIW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/output.ts","../src/utils/git.ts","../src/utils/context.ts","../src/reconcile.ts"],"sourcesContent":["/**\n * Console output utilities for consistent CLI messaging\n */\n\n/**\n * Print info message\n */\nexport function info(message: string): void {\n console.log(message);\n}\n\n/**\n * Print success message\n */\nexport function success(message: string): void {\n console.log(`✓ ${message}`);\n}\n\n/**\n * Print warning message\n */\nexport function warn(message: string): void {\n console.warn(`⚠ ${message}`);\n}\n\n/**\n * Print error message to stderr\n */\nexport function error(message: string): void {\n console.error(`✗ ${message}`);\n}\n\n/**\n * Print a blank line\n */\nexport function blank(): void {\n console.log('');\n}\n\n/**\n * Print a section header\n */\nexport function header(title: string): void {\n console.log(`\\n${title}`);\n console.log('─'.repeat(title.length));\n}\n\n/**\n * Print a list item\n */\nexport function listItem(item: string, indent = 2): void {\n console.log(`${' '.repeat(indent)}• ${item}`);\n}\n\n/**\n * Print key-value pair\n */\nexport function keyValue(key: string, value: string): void {\n console.log(` ${key}: ${value}`);\n}\n","/**\n * Git utilities for CLI operations\n */\n\nimport { join } from 'node:path';\nimport { exists } from './fs.js';\n\n/**\n * Check if directory is a git repository\n */\nexport function isGitRepo(cwd: string): boolean {\n return exists(join(cwd, '.git'));\n}\n","/**\n * Project Context Utilities\n *\n * Shared helpers for creating ProjectContext objects used by reconcile().\n */\n\nimport { join } from 'node:path';\nimport { readJson } from './fs.js';\nimport { isGitRepo } from './git.js';\nimport { detectProjectType, type PackageJson } from './project-detector.js';\nimport type { ProjectContext } from '../schema.js';\n\n/**\n * Create a ProjectContext from the current working directory.\n *\n * Reads package.json and detects project type for use with reconcile().\n */\nexport function createProjectContext(cwd: string): ProjectContext {\n const packageJson = readJson<PackageJson>(join(cwd, 'package.json'));\n\n return {\n cwd,\n projectType: detectProjectType(packageJson ?? {}),\n devDeps: packageJson?.devDependencies ?? {},\n isGitRepo: isGitRepo(cwd),\n };\n}\n","/**\n * Reconciliation Engine\n *\n * Computes and executes plans based on SAFEWORD_SCHEMA and project state.\n * This is the single source of truth for all file/dir/config operations.\n */\n\nimport { join } from 'node:path';\nimport {\n exists,\n ensureDir,\n writeFile,\n readFile,\n readFileSafe,\n readJson,\n writeJson,\n remove,\n removeIfEmpty,\n makeScriptsExecutable,\n getTemplatesDir,\n} from './utils/fs.js';\nimport type {\n SafewordSchema,\n ProjectContext,\n FileDefinition,\n JsonMergeDefinition,\n TextPatchDefinition,\n} from './schema.js';\nimport type { ProjectType } from './utils/project-detector.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst HUSKY_DIR = '.husky';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ReconcileMode = 'install' | 'upgrade' | 'uninstall' | 'uninstall-full';\n\nexport type Action =\n | { type: 'mkdir'; path: string }\n | { type: 'rmdir'; path: string }\n | { type: 'write'; path: string; content: string }\n | { type: 'rm'; path: string }\n | { type: 'chmod'; paths: string[] }\n | { type: 'json-merge'; path: string; definition: JsonMergeDefinition }\n | { type: 'json-unmerge'; path: string; definition: JsonMergeDefinition }\n | { type: 'text-patch'; path: string; definition: TextPatchDefinition }\n | { type: 'text-unpatch'; path: string; definition: TextPatchDefinition };\n\nexport interface ReconcileResult {\n actions: Action[];\n applied: boolean;\n created: string[];\n updated: string[];\n removed: string[];\n packagesToInstall: string[];\n packagesToRemove: string[];\n}\n\nexport interface ReconcileOptions {\n dryRun?: boolean;\n}\n\n// ============================================================================\n// Main reconcile function\n// ============================================================================\n\nexport async function reconcile(\n schema: SafewordSchema,\n mode: ReconcileMode,\n ctx: ProjectContext,\n options?: ReconcileOptions,\n): Promise<ReconcileResult> {\n const dryRun = options?.dryRun ?? false;\n\n const plan = computePlan(schema, mode, ctx);\n\n if (dryRun) {\n return {\n actions: plan.actions,\n applied: false,\n created: plan.wouldCreate,\n updated: plan.wouldUpdate,\n removed: plan.wouldRemove,\n packagesToInstall: plan.packagesToInstall,\n packagesToRemove: plan.packagesToRemove,\n };\n }\n\n const result = executePlan(plan, ctx);\n\n return {\n actions: plan.actions,\n applied: true,\n created: result.created,\n updated: result.updated,\n removed: result.removed,\n packagesToInstall: plan.packagesToInstall,\n packagesToRemove: plan.packagesToRemove,\n };\n}\n\n// ============================================================================\n// Plan computation\n// ============================================================================\n\ninterface ReconcilePlan {\n actions: Action[];\n wouldCreate: string[];\n wouldUpdate: string[];\n wouldRemove: string[];\n packagesToInstall: string[];\n packagesToRemove: string[];\n}\n\nfunction computePlan(\n schema: SafewordSchema,\n mode: ReconcileMode,\n ctx: ProjectContext,\n): ReconcilePlan {\n switch (mode) {\n case 'install':\n return computeInstallPlan(schema, ctx);\n case 'upgrade':\n return computeUpgradePlan(schema, ctx);\n case 'uninstall':\n return computeUninstallPlan(schema, ctx, false);\n case 'uninstall-full':\n return computeUninstallPlan(schema, ctx, true);\n }\n}\n\nfunction computeInstallPlan(schema: SafewordSchema, ctx: ProjectContext): ReconcilePlan {\n const actions: Action[] = [];\n const wouldCreate: string[] = [];\n\n // 1. Create all directories (skip .husky if not a git repo)\n const allDirs = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];\n for (const dir of allDirs) {\n // Skip .husky in non-git repos\n if (dir.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;\n\n const fullPath = join(ctx.cwd, dir);\n if (!exists(fullPath)) {\n actions.push({ type: 'mkdir', path: dir });\n wouldCreate.push(dir);\n }\n }\n\n // 2. Write all owned files (skip .husky files if not a git repo)\n for (const [filePath, def] of Object.entries(schema.ownedFiles)) {\n // Skip .husky files in non-git repos\n if (filePath.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;\n\n const content = resolveFileContent(def, ctx);\n actions.push({ type: 'write', path: filePath, content });\n wouldCreate.push(filePath);\n }\n\n // 3. Write managed files (only if missing)\n for (const [filePath, def] of Object.entries(schema.managedFiles)) {\n const fullPath = join(ctx.cwd, filePath);\n if (!exists(fullPath)) {\n const content = resolveFileContent(def, ctx);\n actions.push({ type: 'write', path: filePath, content });\n wouldCreate.push(filePath);\n }\n }\n\n // 4. chmod hook/lib directories (only .husky if git repo)\n const chmodPaths = ['.safeword/hooks', '.safeword/lib'];\n if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);\n actions.push({ type: 'chmod', paths: chmodPaths });\n\n // 5. JSON merges\n for (const [filePath, def] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-merge', path: filePath, definition: def });\n }\n\n // 6. Text patches\n for (const [filePath, def] of Object.entries(schema.textPatches)) {\n actions.push({ type: 'text-patch', path: filePath, definition: def });\n if (def.createIfMissing && !exists(join(ctx.cwd, filePath))) {\n wouldCreate.push(filePath);\n }\n }\n\n // 7. Compute packages to install (husky/lint-staged skipped if no git repo)\n const packagesToInstall = computePackagesToInstall(\n schema,\n ctx.projectType,\n ctx.devDeps,\n ctx.isGitRepo,\n );\n\n return {\n actions,\n wouldCreate,\n wouldUpdate: [],\n wouldRemove: [],\n packagesToInstall,\n packagesToRemove: [],\n };\n}\n\nfunction computeUpgradePlan(schema: SafewordSchema, ctx: ProjectContext): ReconcilePlan {\n const actions: Action[] = [];\n const wouldCreate: string[] = [];\n const wouldUpdate: string[] = [];\n\n // 1. Ensure directories exist (skip .husky if not a git repo)\n const allDirs = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];\n for (const dir of allDirs) {\n // Skip .husky in non-git repos\n if (dir.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;\n\n const fullPath = join(ctx.cwd, dir);\n if (!exists(fullPath)) {\n actions.push({ type: 'mkdir', path: dir });\n wouldCreate.push(dir);\n }\n }\n\n // 2. Update owned files if content changed (skip .husky files if not a git repo)\n for (const [filePath, def] of Object.entries(schema.ownedFiles)) {\n // Skip .husky files in non-git repos\n if (filePath.startsWith(HUSKY_DIR) && !ctx.isGitRepo) continue;\n\n const fullPath = join(ctx.cwd, filePath);\n const newContent = resolveFileContent(def, ctx);\n\n if (fileNeedsUpdate(fullPath, newContent)) {\n actions.push({ type: 'write', path: filePath, content: newContent });\n if (exists(fullPath)) {\n wouldUpdate.push(filePath);\n } else {\n wouldCreate.push(filePath);\n }\n }\n }\n\n // 3. Update managed files only if content matches current template\n for (const [filePath, def] of Object.entries(schema.managedFiles)) {\n const fullPath = join(ctx.cwd, filePath);\n const newContent = resolveFileContent(def, ctx);\n\n if (!exists(fullPath)) {\n // Missing - create it\n actions.push({ type: 'write', path: filePath, content: newContent });\n wouldCreate.push(filePath);\n } else {\n // Exists - only update if user hasn't modified it\n // For upgrade, we need to compare against what safeword would generate\n // If the current content matches our template, user hasn't customized it\n const currentContent = readFileSafe(fullPath);\n if (currentContent?.trim() === newContent.trim()) {\n // Content matches - no update needed (already current)\n } else {\n // Content differs - check if it was originally from safeword\n // For now, we don't update managed files in upgrade mode\n // unless they match our previous template exactly\n // This is conservative - user may have customized\n }\n }\n }\n\n // 4. chmod (only .husky if git repo)\n const chmodPaths = ['.safeword/hooks', '.safeword/lib'];\n if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);\n actions.push({ type: 'chmod', paths: chmodPaths });\n\n // 5. JSON merges (always apply to ensure keys are present)\n for (const [filePath, def] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-merge', path: filePath, definition: def });\n }\n\n // 6. Text patches (only if marker missing)\n for (const [filePath, def] of Object.entries(schema.textPatches)) {\n const fullPath = join(ctx.cwd, filePath);\n const content = readFileSafe(fullPath) ?? '';\n if (!content.includes(def.marker)) {\n actions.push({ type: 'text-patch', path: filePath, definition: def });\n }\n }\n\n // 7. Compute packages to install (husky/lint-staged skipped if no git repo)\n const packagesToInstall = computePackagesToInstall(\n schema,\n ctx.projectType,\n ctx.devDeps,\n ctx.isGitRepo,\n );\n\n return {\n actions,\n wouldCreate,\n wouldUpdate,\n wouldRemove: [],\n packagesToInstall,\n packagesToRemove: [],\n };\n}\n\nfunction computeUninstallPlan(\n schema: SafewordSchema,\n ctx: ProjectContext,\n full: boolean,\n): ReconcilePlan {\n const actions: Action[] = [];\n const wouldRemove: string[] = [];\n\n // 1. Remove all owned files\n const dirsToCleanup = new Set<string>();\n for (const filePath of Object.keys(schema.ownedFiles)) {\n const fullPath = join(ctx.cwd, filePath);\n if (exists(fullPath)) {\n actions.push({ type: 'rm', path: filePath });\n wouldRemove.push(filePath);\n // Track parent dir for cleanup (for .claude/* skill dirs)\n if (filePath.startsWith('.claude/')) {\n const parentDir = filePath.substring(0, filePath.lastIndexOf('/'));\n if (\n parentDir &&\n parentDir !== '.claude' &&\n parentDir !== '.claude/skills' &&\n parentDir !== '.claude/commands'\n ) {\n dirsToCleanup.add(parentDir);\n }\n }\n }\n }\n // Clean up empty parent directories (like .claude/skills/safeword-*)\n for (const dir of dirsToCleanup) {\n const fullPath = join(ctx.cwd, dir);\n if (exists(fullPath)) {\n actions.push({ type: 'rmdir', path: dir });\n wouldRemove.push(dir);\n }\n }\n\n // 2. JSON unmerges\n for (const [filePath, def] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-unmerge', path: filePath, definition: def });\n }\n\n // 3. Text unpatches\n for (const [filePath, def] of Object.entries(schema.textPatches)) {\n const fullPath = join(ctx.cwd, filePath);\n if (exists(fullPath)) {\n const content = readFileSafe(fullPath) ?? '';\n if (content.includes(def.marker)) {\n actions.push({ type: 'text-unpatch', path: filePath, definition: def });\n }\n }\n }\n\n // 4. Remove preserved directories first (reverse order)\n // These will only be removed if empty (no user content)\n const preservedDirsToRemove = [...schema.preservedDirs].reverse();\n for (const dir of preservedDirsToRemove) {\n const fullPath = join(ctx.cwd, dir);\n if (exists(fullPath)) {\n actions.push({ type: 'rmdir', path: dir });\n wouldRemove.push(dir);\n }\n }\n\n // 5. Remove owned directories (reverse order)\n // Reverse order ensures children are removed before parents\n const dirsToRemove = [...schema.ownedDirs].reverse();\n for (const dir of dirsToRemove) {\n const fullPath = join(ctx.cwd, dir);\n if (exists(fullPath)) {\n actions.push({ type: 'rmdir', path: dir });\n wouldRemove.push(dir);\n }\n }\n\n // 6. Full uninstall: remove managed files\n if (full) {\n for (const filePath of Object.keys(schema.managedFiles)) {\n const fullPath = join(ctx.cwd, filePath);\n if (exists(fullPath)) {\n actions.push({ type: 'rm', path: filePath });\n wouldRemove.push(filePath);\n }\n }\n }\n\n // 7. Compute packages to remove (full only)\n const packagesToRemove = full\n ? computePackagesToRemove(schema, ctx.projectType, ctx.devDeps)\n : [];\n\n return {\n actions,\n wouldCreate: [],\n wouldUpdate: [],\n wouldRemove,\n packagesToInstall: [],\n packagesToRemove,\n };\n}\n\n// ============================================================================\n// Plan execution\n// ============================================================================\n\ninterface ExecutionResult {\n created: string[];\n updated: string[];\n removed: string[];\n}\n\nfunction executePlan(plan: ReconcilePlan, ctx: ProjectContext): ExecutionResult {\n const created: string[] = [];\n const updated: string[] = [];\n const removed: string[] = [];\n\n for (const action of plan.actions) {\n switch (action.type) {\n case 'mkdir': {\n const fullPath = join(ctx.cwd, action.path);\n ensureDir(fullPath);\n created.push(action.path);\n break;\n }\n\n case 'rmdir': {\n const fullPath = join(ctx.cwd, action.path);\n // Use removeIfEmpty to preserve directories with user content\n // This will only succeed if the directory is empty\n if (removeIfEmpty(fullPath)) {\n removed.push(action.path);\n }\n break;\n }\n\n case 'write': {\n const fullPath = join(ctx.cwd, action.path);\n const existed = exists(fullPath);\n writeFile(fullPath, action.content);\n if (existed) {\n updated.push(action.path);\n } else {\n created.push(action.path);\n }\n break;\n }\n\n case 'rm': {\n const fullPath = join(ctx.cwd, action.path);\n remove(fullPath);\n removed.push(action.path);\n break;\n }\n\n case 'chmod': {\n for (const path of action.paths) {\n const fullPath = join(ctx.cwd, path);\n if (exists(fullPath)) {\n makeScriptsExecutable(fullPath);\n }\n }\n break;\n }\n\n case 'json-merge': {\n executeJsonMerge(ctx.cwd, action.path, action.definition, ctx);\n break;\n }\n\n case 'json-unmerge': {\n executeJsonUnmerge(ctx.cwd, action.path, action.definition);\n break;\n }\n\n case 'text-patch': {\n executeTextPatch(ctx.cwd, action.path, action.definition);\n break;\n }\n\n case 'text-unpatch': {\n executeTextUnpatch(ctx.cwd, action.path, action.definition);\n break;\n }\n }\n }\n\n return { created, updated, removed };\n}\n\n// ============================================================================\n// Helper functions\n// ============================================================================\n\nfunction resolveFileContent(def: FileDefinition, ctx: ProjectContext): string {\n if (def.template) {\n const templatesDir = getTemplatesDir();\n return readFile(join(templatesDir, def.template));\n }\n\n if (def.content) {\n return typeof def.content === 'function' ? def.content() : def.content;\n }\n\n if (def.generator) {\n return def.generator(ctx);\n }\n\n throw new Error('FileDefinition must have template, content, or generator');\n}\n\nfunction fileNeedsUpdate(installedPath: string, newContent: string): boolean {\n if (!exists(installedPath)) return true;\n const currentContent = readFileSafe(installedPath);\n return currentContent?.trim() !== newContent.trim();\n}\n\n// Packages that require git repo\nconst GIT_ONLY_PACKAGES = ['husky', 'lint-staged'];\n\nexport function computePackagesToInstall(\n schema: SafewordSchema,\n projectType: ProjectType,\n installedDevDeps: Record<string, string>,\n isGitRepo = true,\n): string[] {\n let needed = [...schema.packages.base];\n\n // Filter out git-only packages when not in a git repo\n if (!isGitRepo) {\n needed = needed.filter(pkg => !GIT_ONLY_PACKAGES.includes(pkg));\n }\n\n for (const [key, deps] of Object.entries(schema.packages.conditional)) {\n if (projectType[key as keyof ProjectType]) {\n needed.push(...deps);\n }\n }\n\n return needed.filter(pkg => !(pkg in installedDevDeps));\n}\n\nfunction computePackagesToRemove(\n schema: SafewordSchema,\n projectType: ProjectType,\n installedDevDeps: Record<string, string>,\n): string[] {\n const safewordPackages = [...schema.packages.base];\n\n for (const [key, deps] of Object.entries(schema.packages.conditional)) {\n if (projectType[key as keyof ProjectType]) {\n safewordPackages.push(...deps);\n }\n }\n\n // Only remove packages that are actually installed\n return safewordPackages.filter(pkg => pkg in installedDevDeps);\n}\n\nfunction executeJsonMerge(\n cwd: string,\n path: string,\n def: JsonMergeDefinition,\n ctx: ProjectContext,\n): void {\n const fullPath = join(cwd, path);\n const existing = readJson<Record<string, unknown>>(fullPath) ?? {};\n const merged = def.merge(existing, ctx);\n writeJson(fullPath, merged);\n}\n\nfunction executeJsonUnmerge(cwd: string, path: string, def: JsonMergeDefinition): void {\n const fullPath = join(cwd, path);\n if (!exists(fullPath)) return;\n\n const existing = readJson<Record<string, unknown>>(fullPath);\n if (!existing) return;\n\n const unmerged = def.unmerge(existing);\n\n // Check if file should be removed\n if (def.removeFileIfEmpty) {\n const remainingKeys = Object.keys(unmerged).filter(\n k => unmerged[k] !== undefined && unmerged[k] !== null,\n );\n if (remainingKeys.length === 0) {\n remove(fullPath);\n return;\n }\n }\n\n writeJson(fullPath, unmerged);\n}\n\nfunction executeTextPatch(cwd: string, path: string, def: TextPatchDefinition): void {\n const fullPath = join(cwd, path);\n let content = readFileSafe(fullPath) ?? '';\n\n // Check if already patched\n if (content.includes(def.marker)) return;\n\n // Apply patch\n if (def.operation === 'prepend') {\n content = def.content + content;\n } else {\n content = content + def.content;\n }\n\n writeFile(fullPath, content);\n}\n\nfunction executeTextUnpatch(cwd: string, path: string, def: TextPatchDefinition): void {\n const fullPath = join(cwd, path);\n const content = readFileSafe(fullPath);\n if (!content) return;\n\n // Remove the patched content\n // First try to remove the full content block\n let unpatched = content.replace(def.content, '');\n\n // If full content wasn't found but marker exists, remove lines containing the marker\n if (unpatched === content && content.includes(def.marker)) {\n // Remove lines containing the marker\n const lines = content.split('\\n');\n const filtered = lines.filter(line => !line.includes(def.marker));\n unpatched = filtered.join('\\n').replace(/^\\n+/, ''); // Remove leading empty lines\n }\n\n writeFile(fullPath, unpatched);\n}\n"],"mappings":"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","names":["join","join","join","join","isGitRepo"]}
|