oh-my-customcode 1.1.91 → 1.1.93
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/README.md +1 -1
- package/dist/cli/index.js +118 -2
- package/dist/index.js +118 -2
- package/package.json +3 -2
- package/templates/.claude/hooks/hooks.json +46 -46
- package/templates/.claude/hooks/scripts/rule-deletion-guard.sh +1 -1
- package/templates/.claude/hooks/scripts/schema-validator.sh +5 -1
- package/templates/.claude/hooks/skill-count-reminder.sh +5 -1
- package/templates/.claude/rules/MAY-optimization.md +2 -0
- package/templates/.claude/rules/MUST-enforcement-policy.md +1 -1
- package/templates/.claude/settings.json +46 -46
- package/templates/.claude/skills/pipeline/workflows/auto-dev.yaml +56 -10
- package/templates/guides/claude-code/15-version-compatibility.md +149 -0
- package/templates/guides/hook-data-flow/README.md +4 -2
- package/templates/manifest.json +1 -1
- package/templates/workflows/auto-dev.yaml +56 -10
package/README.md
CHANGED
|
@@ -249,7 +249,7 @@ oh-my-customcode ships 42 lifecycle hook scripts covering security, drift detect
|
|
|
249
249
|
|
|
250
250
|
Most hooks are advisory (exit 0) — they warn but never block. A small number of hard-block hooks (e.g. `stage-blocker`, `rule-deletion-guard`) reject the tool call outright (exit 2). oh-my-customcode's own governance rules (`.claude/rules/`) follow an **advisory-first enforcement model**: prompt-based guidance is the default, and a rule is only promoted to a blocking hook after repeated, observed violations. Rules also retire — clauses tied to since-fixed platform bugs or that go unused for two minor releases are wrapped in HTML comments (still readable via the source file, invisible to the agent's context) rather than left to accumulate indefinitely.
|
|
251
251
|
|
|
252
|
-
The hook source of truth is `.claude/hooks/hooks.json`; `omcustom init` compiles it into the `hooks` block of `.claude/settings.json` (via `src/core/hooks-settings.ts`), which is the file Claude Code actually loads.
|
|
252
|
+
The hook source of truth is `.claude/hooks/hooks.json`; `omcustom init` compiles it into the `hooks` block of `.claude/settings.json` (via `src/core/hooks-settings.ts`), which is the file Claude Code actually loads. Hook commands are anchored as `bash "${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/<path>"`, so hooks keep working when the session's working directory moves to a subdirectory. Contributors: do not hand-edit the `hooks` blocks in settings; edit `hooks.json` and run `bun run sync:hooks` (also run at the end of `bun run build`) to regenerate them. Existing installs need no manual step: `omcustom update` (full or hooks component) and `omcustom init --backup` automatically migrate old relative hook commands in `.claude/settings.local.json`, and custom hooks are left untouched.
|
|
253
253
|
|
|
254
254
|
---
|
|
255
255
|
|
package/dist/cli/index.js
CHANGED
|
@@ -217,7 +217,7 @@ var init_package = __esm(() => {
|
|
|
217
217
|
workspaces: [
|
|
218
218
|
"packages/*"
|
|
219
219
|
],
|
|
220
|
-
version: "1.1.
|
|
220
|
+
version: "1.1.93",
|
|
221
221
|
description: "Batteries-included agent harness for Claude Code",
|
|
222
222
|
type: "module",
|
|
223
223
|
bin: {
|
|
@@ -241,7 +241,8 @@ var init_package = __esm(() => {
|
|
|
241
241
|
},
|
|
242
242
|
scripts: {
|
|
243
243
|
dev: "bun run src/cli/index.ts",
|
|
244
|
-
build: "bun build src/cli/index.ts --outdir dist/cli --target node && bun build src/index.ts --outdir dist --target node && bun run scripts/sync-source-lockfile.ts",
|
|
244
|
+
build: "bun build src/cli/index.ts --outdir dist/cli --target node && bun build src/index.ts --outdir dist --target node && bun run scripts/sync-source-lockfile.ts && bun run scripts/sync-hooks-settings.ts",
|
|
245
|
+
"sync:hooks": "bun run scripts/sync-hooks-settings.ts",
|
|
245
246
|
test: "bun test tests/ packages/eval-core/",
|
|
246
247
|
"test:unit": "bun test tests/unit",
|
|
247
248
|
"test:integration": "bun test tests/integration",
|
|
@@ -15615,6 +15616,8 @@ var MESSAGES = {
|
|
|
15615
15616
|
"install.entry_md_skipped": "{{entry}} skipped ({{reason}})",
|
|
15616
15617
|
"install.lockfile_generated": "Lockfile generated ({{files}} files tracked)",
|
|
15617
15618
|
"install.lockfile_failed": "Failed to generate lockfile: {{error}}",
|
|
15619
|
+
"install.hook_commands_migrated": "Migrated {{rewritten}} hook command(s) in settings.local.json",
|
|
15620
|
+
"install.hook_commands_migration_failed": "Failed to migrate hook commands in settings.local.json: {{error}}",
|
|
15618
15621
|
"lockfile.not_found": "Lockfile not found: {{path}}",
|
|
15619
15622
|
"lockfile.invalid_version": "Invalid lockfile version: {{path}}",
|
|
15620
15623
|
"lockfile.invalid_structure": "Invalid lockfile structure: {{path}}",
|
|
@@ -15636,6 +15639,8 @@ var MESSAGES = {
|
|
|
15636
15639
|
"update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
|
|
15637
15640
|
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}} updated: {{hint}}",
|
|
15638
15641
|
"update.namespace_synced": "Namespace synced: {{file}} ({{component}})",
|
|
15642
|
+
"update.hook_commands_migrated": "Migrated {{count}} hook command(s) to CLAUDE_PROJECT_DIR-anchored paths",
|
|
15643
|
+
"update.hook_commands_migration_failed": "Failed to migrate hook commands in {{path}}",
|
|
15639
15644
|
"config.load_failed": "Failed to load config: {{error}}",
|
|
15640
15645
|
"config.not_found": "Config not found at {{path}}, using defaults",
|
|
15641
15646
|
"config.saved": "Config saved to {{path}}",
|
|
@@ -15661,6 +15666,8 @@ var MESSAGES = {
|
|
|
15661
15666
|
"install.entry_md_skipped": "{{entry}} 건너뜀 ({{reason}})",
|
|
15662
15667
|
"install.lockfile_generated": "잠금 파일 생성 완료 ({{files}}개 파일 추적)",
|
|
15663
15668
|
"install.lockfile_failed": "잠금 파일 생성 실패: {{error}}",
|
|
15669
|
+
"install.hook_commands_migrated": "settings.local.json의 훅 명령 {{rewritten}}개를 마이그레이션했습니다",
|
|
15670
|
+
"install.hook_commands_migration_failed": "settings.local.json의 훅 명령 마이그레이션에 실패했습니다: {{error}}",
|
|
15664
15671
|
"lockfile.not_found": "잠금 파일 없음: {{path}}",
|
|
15665
15672
|
"lockfile.invalid_version": "잠금 파일 버전 유효하지 않음: {{path}}",
|
|
15666
15673
|
"lockfile.invalid_structure": "잠금 파일 구조 유효하지 않음: {{path}}",
|
|
@@ -15682,6 +15689,8 @@ var MESSAGES = {
|
|
|
15682
15689
|
"update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
|
|
15683
15690
|
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}}) 업데이트됨: {{hint}}",
|
|
15684
15691
|
"update.namespace_synced": "네임스페이스 동기화: {{file}} ({{component}})",
|
|
15692
|
+
"update.hook_commands_migrated": "CLAUDE_PROJECT_DIR 기준 경로로 훅 명령 {{count}}개를 마이그레이션했습니다",
|
|
15693
|
+
"update.hook_commands_migration_failed": "{{path}}의 훅 명령 마이그레이션에 실패했습니다",
|
|
15685
15694
|
"config.load_failed": "설정 로드 실패: {{error}}",
|
|
15686
15695
|
"config.not_found": "{{path}}에 설정 없음, 기본값 사용",
|
|
15687
15696
|
"config.saved": "설정 저장: {{path}}",
|
|
@@ -17349,6 +17358,52 @@ function getDefaultWorkflow() {
|
|
|
17349
17358
|
};
|
|
17350
17359
|
}
|
|
17351
17360
|
|
|
17361
|
+
// src/core/hook-command-migration.ts
|
|
17362
|
+
var OLD_HOOK_COMMAND = /^(bash )?(?:\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)$/;
|
|
17363
|
+
function rewriteRelativeHookCommand(command) {
|
|
17364
|
+
const match = OLD_HOOK_COMMAND.exec(command);
|
|
17365
|
+
if (match === null) {
|
|
17366
|
+
return null;
|
|
17367
|
+
}
|
|
17368
|
+
const interpreter = match[1] ?? "";
|
|
17369
|
+
return `${interpreter}"\${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/${match[2]}"`;
|
|
17370
|
+
}
|
|
17371
|
+
function isRecord(value) {
|
|
17372
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17373
|
+
}
|
|
17374
|
+
function rewriteNode(node, counter) {
|
|
17375
|
+
if (Array.isArray(node)) {
|
|
17376
|
+
return node.map((item) => rewriteNode(item, counter));
|
|
17377
|
+
}
|
|
17378
|
+
if (!isRecord(node)) {
|
|
17379
|
+
return node;
|
|
17380
|
+
}
|
|
17381
|
+
const out = {};
|
|
17382
|
+
for (const [key, value] of Object.entries(node)) {
|
|
17383
|
+
if (key === "command" && typeof value === "string") {
|
|
17384
|
+
const rewritten = rewriteRelativeHookCommand(value);
|
|
17385
|
+
if (rewritten !== null) {
|
|
17386
|
+
counter.count++;
|
|
17387
|
+
out[key] = rewritten;
|
|
17388
|
+
continue;
|
|
17389
|
+
}
|
|
17390
|
+
}
|
|
17391
|
+
out[key] = rewriteNode(value, counter);
|
|
17392
|
+
}
|
|
17393
|
+
return out;
|
|
17394
|
+
}
|
|
17395
|
+
function migrateHookCommands(settings) {
|
|
17396
|
+
if (!isRecord(settings.hooks)) {
|
|
17397
|
+
return { settings, rewritten: 0 };
|
|
17398
|
+
}
|
|
17399
|
+
const counter = { count: 0 };
|
|
17400
|
+
const hooks = rewriteNode(settings.hooks, counter);
|
|
17401
|
+
if (counter.count === 0) {
|
|
17402
|
+
return { settings, rewritten: 0 };
|
|
17403
|
+
}
|
|
17404
|
+
return { settings: { ...settings, hooks }, rewritten: counter.count };
|
|
17405
|
+
}
|
|
17406
|
+
|
|
17352
17407
|
// src/core/hooks-settings.ts
|
|
17353
17408
|
init_fs();
|
|
17354
17409
|
import { dirname as dirname3 } from "node:path";
|
|
@@ -18036,6 +18091,32 @@ async function installHooksSettings(targetDir, _options, result) {
|
|
|
18036
18091
|
warn("install.hooks_settings_failed", { error: message });
|
|
18037
18092
|
}
|
|
18038
18093
|
}
|
|
18094
|
+
var BOM = "\uFEFF";
|
|
18095
|
+
async function migrateRestoredHookCommands(targetDir, result) {
|
|
18096
|
+
const layout = getProviderLayout();
|
|
18097
|
+
const settingsPath = join8(targetDir, layout.rootDir, "settings.local.json");
|
|
18098
|
+
if (!await fileExists(settingsPath)) {
|
|
18099
|
+
return;
|
|
18100
|
+
}
|
|
18101
|
+
try {
|
|
18102
|
+
const raw = await readTextFile(settingsPath);
|
|
18103
|
+
const hasBom = raw.startsWith(BOM);
|
|
18104
|
+
const trailingNewline = raw.endsWith(`
|
|
18105
|
+
`) ? `
|
|
18106
|
+
` : "";
|
|
18107
|
+
const current = JSON.parse(hasBom ? raw.slice(BOM.length) : raw);
|
|
18108
|
+
const { settings, rewritten } = migrateHookCommands(current);
|
|
18109
|
+
if (rewritten > 0) {
|
|
18110
|
+
const body = `${JSON.stringify(settings, null, 2)}${trailingNewline}`;
|
|
18111
|
+
await writeTextFile(settingsPath, hasBom ? `${BOM}${body}` : body);
|
|
18112
|
+
debug("install.hook_commands_migrated", { rewritten: String(rewritten) });
|
|
18113
|
+
}
|
|
18114
|
+
} catch (err) {
|
|
18115
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
18116
|
+
result.warnings.push(`Failed to migrate hook commands in settings.local.json: ${message}`);
|
|
18117
|
+
warn("install.hook_commands_migration_failed", { error: message });
|
|
18118
|
+
}
|
|
18119
|
+
}
|
|
18039
18120
|
async function installEntryDocWithTracking(targetDir, options, result) {
|
|
18040
18121
|
const language = options.language ?? DEFAULT_LANGUAGE2;
|
|
18041
18122
|
const overwrite = !!(options.force || options.backup);
|
|
@@ -18111,6 +18192,7 @@ async function install(options) {
|
|
|
18111
18192
|
result.warnings.push(`Failed to restore ${failure.path}: ${failure.reason}`);
|
|
18112
18193
|
}
|
|
18113
18194
|
}
|
|
18195
|
+
await migrateRestoredHookCommands(options.targetDir, result);
|
|
18114
18196
|
await cleanupPreservation(preservation.tempDir);
|
|
18115
18197
|
}
|
|
18116
18198
|
await updateInstallConfig(options.targetDir, options, result.installedComponents);
|
|
@@ -20948,6 +21030,37 @@ async function backfillStatusLineRefreshInterval(targetDir, options) {
|
|
|
20948
21030
|
});
|
|
20949
21031
|
}
|
|
20950
21032
|
}
|
|
21033
|
+
async function migrateHookCommandsInSettingsLocal(targetDir, options) {
|
|
21034
|
+
if (options.dryRun) {
|
|
21035
|
+
return;
|
|
21036
|
+
}
|
|
21037
|
+
const layout = getProviderLayout();
|
|
21038
|
+
const settingsPath = join18(targetDir, layout.rootDir, "settings.local.json");
|
|
21039
|
+
if (!await fileExists(settingsPath)) {
|
|
21040
|
+
return;
|
|
21041
|
+
}
|
|
21042
|
+
try {
|
|
21043
|
+
const raw = await readTextFile(settingsPath);
|
|
21044
|
+
const bom = raw.startsWith("\uFEFF") ? "\uFEFF" : "";
|
|
21045
|
+
const parsed = JSON.parse(bom ? raw.slice(bom.length) : raw);
|
|
21046
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
21047
|
+
return;
|
|
21048
|
+
}
|
|
21049
|
+
const { settings, rewritten } = migrateHookCommands(parsed);
|
|
21050
|
+
if (rewritten === 0) {
|
|
21051
|
+
return;
|
|
21052
|
+
}
|
|
21053
|
+
const trailingNewline = raw.endsWith(`
|
|
21054
|
+
`) ? `
|
|
21055
|
+
` : "";
|
|
21056
|
+
await writeTextFile(settingsPath, `${bom}${JSON.stringify(settings, null, 2)}${trailingNewline}`);
|
|
21057
|
+
info("update.hook_commands_migrated", { count: String(rewritten) });
|
|
21058
|
+
} catch {
|
|
21059
|
+
warn("update.hook_commands_migration_failed", {
|
|
21060
|
+
path: settingsPath
|
|
21061
|
+
});
|
|
21062
|
+
}
|
|
21063
|
+
}
|
|
20951
21064
|
async function runFullUpdatePostProcessing(options, result, config) {
|
|
20952
21065
|
const isFullUpdate = !options.components || options.components.length === 0;
|
|
20953
21066
|
if (isFullUpdate) {
|
|
@@ -20960,6 +21073,9 @@ async function runFullUpdatePostProcessing(options, result, config) {
|
|
|
20960
21073
|
await backfillStatusLineRefreshInterval(options.targetDir, options);
|
|
20961
21074
|
}
|
|
20962
21075
|
}
|
|
21076
|
+
if (isFullUpdate || options.components?.includes("hooks")) {
|
|
21077
|
+
await migrateHookCommandsInSettingsLocal(options.targetDir, options);
|
|
21078
|
+
}
|
|
20963
21079
|
if (!options.dryRun) {
|
|
20964
21080
|
config.version = result.newVersion;
|
|
20965
21081
|
config.lastUpdated = new Date().toISOString();
|
package/dist/index.js
CHANGED
|
@@ -330,6 +330,8 @@ var MESSAGES = {
|
|
|
330
330
|
"install.entry_md_skipped": "{{entry}} skipped ({{reason}})",
|
|
331
331
|
"install.lockfile_generated": "Lockfile generated ({{files}} files tracked)",
|
|
332
332
|
"install.lockfile_failed": "Failed to generate lockfile: {{error}}",
|
|
333
|
+
"install.hook_commands_migrated": "Migrated {{rewritten}} hook command(s) in settings.local.json",
|
|
334
|
+
"install.hook_commands_migration_failed": "Failed to migrate hook commands in settings.local.json: {{error}}",
|
|
333
335
|
"lockfile.not_found": "Lockfile not found: {{path}}",
|
|
334
336
|
"lockfile.invalid_version": "Invalid lockfile version: {{path}}",
|
|
335
337
|
"lockfile.invalid_structure": "Invalid lockfile structure: {{path}}",
|
|
@@ -351,6 +353,8 @@ var MESSAGES = {
|
|
|
351
353
|
"update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
|
|
352
354
|
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}} updated: {{hint}}",
|
|
353
355
|
"update.namespace_synced": "Namespace synced: {{file}} ({{component}})",
|
|
356
|
+
"update.hook_commands_migrated": "Migrated {{count}} hook command(s) to CLAUDE_PROJECT_DIR-anchored paths",
|
|
357
|
+
"update.hook_commands_migration_failed": "Failed to migrate hook commands in {{path}}",
|
|
354
358
|
"config.load_failed": "Failed to load config: {{error}}",
|
|
355
359
|
"config.not_found": "Config not found at {{path}}, using defaults",
|
|
356
360
|
"config.saved": "Config saved to {{path}}",
|
|
@@ -376,6 +380,8 @@ var MESSAGES = {
|
|
|
376
380
|
"install.entry_md_skipped": "{{entry}} 건너뜀 ({{reason}})",
|
|
377
381
|
"install.lockfile_generated": "잠금 파일 생성 완료 ({{files}}개 파일 추적)",
|
|
378
382
|
"install.lockfile_failed": "잠금 파일 생성 실패: {{error}}",
|
|
383
|
+
"install.hook_commands_migrated": "settings.local.json의 훅 명령 {{rewritten}}개를 마이그레이션했습니다",
|
|
384
|
+
"install.hook_commands_migration_failed": "settings.local.json의 훅 명령 마이그레이션에 실패했습니다: {{error}}",
|
|
379
385
|
"lockfile.not_found": "잠금 파일 없음: {{path}}",
|
|
380
386
|
"lockfile.invalid_version": "잠금 파일 버전 유효하지 않음: {{path}}",
|
|
381
387
|
"lockfile.invalid_structure": "잠금 파일 구조 유효하지 않음: {{path}}",
|
|
@@ -397,6 +403,8 @@ var MESSAGES = {
|
|
|
397
403
|
"update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
|
|
398
404
|
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}}) 업데이트됨: {{hint}}",
|
|
399
405
|
"update.namespace_synced": "네임스페이스 동기화: {{file}} ({{component}})",
|
|
406
|
+
"update.hook_commands_migrated": "CLAUDE_PROJECT_DIR 기준 경로로 훅 명령 {{count}}개를 마이그레이션했습니다",
|
|
407
|
+
"update.hook_commands_migration_failed": "{{path}}의 훅 명령 마이그레이션에 실패했습니다",
|
|
400
408
|
"config.load_failed": "설정 로드 실패: {{error}}",
|
|
401
409
|
"config.not_found": "{{path}}에 설정 없음, 기본값 사용",
|
|
402
410
|
"config.saved": "설정 저장: {{path}}",
|
|
@@ -1094,6 +1102,52 @@ async function cleanupPreservation(tempDir) {
|
|
|
1094
1102
|
}
|
|
1095
1103
|
}
|
|
1096
1104
|
|
|
1105
|
+
// src/core/hook-command-migration.ts
|
|
1106
|
+
var OLD_HOOK_COMMAND = /^(bash )?(?:\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)$/;
|
|
1107
|
+
function rewriteRelativeHookCommand(command) {
|
|
1108
|
+
const match = OLD_HOOK_COMMAND.exec(command);
|
|
1109
|
+
if (match === null) {
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
const interpreter = match[1] ?? "";
|
|
1113
|
+
return `${interpreter}"\${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/${match[2]}"`;
|
|
1114
|
+
}
|
|
1115
|
+
function isRecord(value) {
|
|
1116
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1117
|
+
}
|
|
1118
|
+
function rewriteNode(node, counter) {
|
|
1119
|
+
if (Array.isArray(node)) {
|
|
1120
|
+
return node.map((item) => rewriteNode(item, counter));
|
|
1121
|
+
}
|
|
1122
|
+
if (!isRecord(node)) {
|
|
1123
|
+
return node;
|
|
1124
|
+
}
|
|
1125
|
+
const out = {};
|
|
1126
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1127
|
+
if (key === "command" && typeof value === "string") {
|
|
1128
|
+
const rewritten = rewriteRelativeHookCommand(value);
|
|
1129
|
+
if (rewritten !== null) {
|
|
1130
|
+
counter.count++;
|
|
1131
|
+
out[key] = rewritten;
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
out[key] = rewriteNode(value, counter);
|
|
1136
|
+
}
|
|
1137
|
+
return out;
|
|
1138
|
+
}
|
|
1139
|
+
function migrateHookCommands(settings) {
|
|
1140
|
+
if (!isRecord(settings.hooks)) {
|
|
1141
|
+
return { settings, rewritten: 0 };
|
|
1142
|
+
}
|
|
1143
|
+
const counter = { count: 0 };
|
|
1144
|
+
const hooks = rewriteNode(settings.hooks, counter);
|
|
1145
|
+
if (counter.count === 0) {
|
|
1146
|
+
return { settings, rewritten: 0 };
|
|
1147
|
+
}
|
|
1148
|
+
return { settings: { ...settings, hooks }, rewritten: counter.count };
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1097
1151
|
// src/core/hooks-settings.ts
|
|
1098
1152
|
init_fs();
|
|
1099
1153
|
import { dirname as dirname2 } from "node:path";
|
|
@@ -2026,6 +2080,32 @@ async function installHooksSettings(targetDir, _options, result) {
|
|
|
2026
2080
|
warn("install.hooks_settings_failed", { error: message });
|
|
2027
2081
|
}
|
|
2028
2082
|
}
|
|
2083
|
+
var BOM = "\uFEFF";
|
|
2084
|
+
async function migrateRestoredHookCommands(targetDir, result) {
|
|
2085
|
+
const layout = getProviderLayout();
|
|
2086
|
+
const settingsPath = join5(targetDir, layout.rootDir, "settings.local.json");
|
|
2087
|
+
if (!await fileExists(settingsPath)) {
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
try {
|
|
2091
|
+
const raw = await readTextFile(settingsPath);
|
|
2092
|
+
const hasBom = raw.startsWith(BOM);
|
|
2093
|
+
const trailingNewline = raw.endsWith(`
|
|
2094
|
+
`) ? `
|
|
2095
|
+
` : "";
|
|
2096
|
+
const current = JSON.parse(hasBom ? raw.slice(BOM.length) : raw);
|
|
2097
|
+
const { settings, rewritten } = migrateHookCommands(current);
|
|
2098
|
+
if (rewritten > 0) {
|
|
2099
|
+
const body = `${JSON.stringify(settings, null, 2)}${trailingNewline}`;
|
|
2100
|
+
await writeTextFile(settingsPath, hasBom ? `${BOM}${body}` : body);
|
|
2101
|
+
debug("install.hook_commands_migrated", { rewritten: String(rewritten) });
|
|
2102
|
+
}
|
|
2103
|
+
} catch (err) {
|
|
2104
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2105
|
+
result.warnings.push(`Failed to migrate hook commands in settings.local.json: ${message}`);
|
|
2106
|
+
warn("install.hook_commands_migration_failed", { error: message });
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2029
2109
|
async function installEntryDocWithTracking(targetDir, options, result) {
|
|
2030
2110
|
const language = options.language ?? DEFAULT_LANGUAGE;
|
|
2031
2111
|
const overwrite = !!(options.force || options.backup);
|
|
@@ -2101,6 +2181,7 @@ async function install(options) {
|
|
|
2101
2181
|
result.warnings.push(`Failed to restore ${failure.path}: ${failure.reason}`);
|
|
2102
2182
|
}
|
|
2103
2183
|
}
|
|
2184
|
+
await migrateRestoredHookCommands(options.targetDir, result);
|
|
2104
2185
|
await cleanupPreservation(preservation.tempDir);
|
|
2105
2186
|
}
|
|
2106
2187
|
await updateInstallConfig(options.targetDir, options, result.installedComponents);
|
|
@@ -2326,7 +2407,7 @@ var package_default = {
|
|
|
2326
2407
|
workspaces: [
|
|
2327
2408
|
"packages/*"
|
|
2328
2409
|
],
|
|
2329
|
-
version: "1.1.
|
|
2410
|
+
version: "1.1.93",
|
|
2330
2411
|
description: "Batteries-included agent harness for Claude Code",
|
|
2331
2412
|
type: "module",
|
|
2332
2413
|
bin: {
|
|
@@ -2350,7 +2431,8 @@ var package_default = {
|
|
|
2350
2431
|
},
|
|
2351
2432
|
scripts: {
|
|
2352
2433
|
dev: "bun run src/cli/index.ts",
|
|
2353
|
-
build: "bun build src/cli/index.ts --outdir dist/cli --target node && bun build src/index.ts --outdir dist --target node && bun run scripts/sync-source-lockfile.ts",
|
|
2434
|
+
build: "bun build src/cli/index.ts --outdir dist/cli --target node && bun build src/index.ts --outdir dist --target node && bun run scripts/sync-source-lockfile.ts && bun run scripts/sync-hooks-settings.ts",
|
|
2435
|
+
"sync:hooks": "bun run scripts/sync-hooks-settings.ts",
|
|
2354
2436
|
test: "bun test tests/ packages/eval-core/",
|
|
2355
2437
|
"test:unit": "bun test tests/unit",
|
|
2356
2438
|
"test:integration": "bun test tests/integration",
|
|
@@ -5157,6 +5239,37 @@ async function backfillStatusLineRefreshInterval(targetDir, options) {
|
|
|
5157
5239
|
});
|
|
5158
5240
|
}
|
|
5159
5241
|
}
|
|
5242
|
+
async function migrateHookCommandsInSettingsLocal(targetDir, options) {
|
|
5243
|
+
if (options.dryRun) {
|
|
5244
|
+
return;
|
|
5245
|
+
}
|
|
5246
|
+
const layout = getProviderLayout();
|
|
5247
|
+
const settingsPath = join7(targetDir, layout.rootDir, "settings.local.json");
|
|
5248
|
+
if (!await fileExists(settingsPath)) {
|
|
5249
|
+
return;
|
|
5250
|
+
}
|
|
5251
|
+
try {
|
|
5252
|
+
const raw = await readTextFile(settingsPath);
|
|
5253
|
+
const bom = raw.startsWith("\uFEFF") ? "\uFEFF" : "";
|
|
5254
|
+
const parsed = JSON.parse(bom ? raw.slice(bom.length) : raw);
|
|
5255
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
5256
|
+
return;
|
|
5257
|
+
}
|
|
5258
|
+
const { settings, rewritten } = migrateHookCommands(parsed);
|
|
5259
|
+
if (rewritten === 0) {
|
|
5260
|
+
return;
|
|
5261
|
+
}
|
|
5262
|
+
const trailingNewline = raw.endsWith(`
|
|
5263
|
+
`) ? `
|
|
5264
|
+
` : "";
|
|
5265
|
+
await writeTextFile(settingsPath, `${bom}${JSON.stringify(settings, null, 2)}${trailingNewline}`);
|
|
5266
|
+
info("update.hook_commands_migrated", { count: String(rewritten) });
|
|
5267
|
+
} catch {
|
|
5268
|
+
warn("update.hook_commands_migration_failed", {
|
|
5269
|
+
path: settingsPath
|
|
5270
|
+
});
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5160
5273
|
async function runFullUpdatePostProcessing(options, result, config) {
|
|
5161
5274
|
const isFullUpdate = !options.components || options.components.length === 0;
|
|
5162
5275
|
if (isFullUpdate) {
|
|
@@ -5169,6 +5282,9 @@ async function runFullUpdatePostProcessing(options, result, config) {
|
|
|
5169
5282
|
await backfillStatusLineRefreshInterval(options.targetDir, options);
|
|
5170
5283
|
}
|
|
5171
5284
|
}
|
|
5285
|
+
if (isFullUpdate || options.components?.includes("hooks")) {
|
|
5286
|
+
await migrateHookCommandsInSettingsLocal(options.targetDir, options);
|
|
5287
|
+
}
|
|
5172
5288
|
if (!options.dryRun) {
|
|
5173
5289
|
config.version = result.newVersion;
|
|
5174
5290
|
config.lastUpdated = new Date().toISOString();
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"workspaces": [
|
|
4
4
|
"packages/*"
|
|
5
5
|
],
|
|
6
|
-
"version": "1.1.
|
|
6
|
+
"version": "1.1.93",
|
|
7
7
|
"description": "Batteries-included agent harness for Claude Code",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"bin": {
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"dev": "bun run src/cli/index.ts",
|
|
30
|
-
"build": "bun build src/cli/index.ts --outdir dist/cli --target node && bun build src/index.ts --outdir dist --target node && bun run scripts/sync-source-lockfile.ts",
|
|
30
|
+
"build": "bun build src/cli/index.ts --outdir dist/cli --target node && bun build src/index.ts --outdir dist --target node && bun run scripts/sync-source-lockfile.ts && bun run scripts/sync-hooks-settings.ts",
|
|
31
|
+
"sync:hooks": "bun run scripts/sync-hooks-settings.ts",
|
|
31
32
|
"test": "bun test tests/ packages/eval-core/",
|
|
32
33
|
"test:unit": "bun test tests/unit",
|
|
33
34
|
"test:integration": "bun test tests/integration",
|