oh-my-customcode 1.1.105 → 1.1.107
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 +8 -9
- package/dist/cli/index.js +323 -273
- package/dist/index.js +223 -140
- package/package.json +4 -3
- package/templates/.claude/hooks/hooks.json +0 -10
- package/templates/.claude/hooks/scripts/claude-md-reinject.sh +7 -1
- package/templates/.claude/hooks/scripts/cost-cap-advisor.sh +107 -52
- package/templates/.claude/hooks/scripts/session-env-check.sh +0 -8
- package/templates/.claude/hooks/scripts/session-reflection.sh +60 -15
- package/templates/.claude/hooks/scripts/stuck-detector.sh +110 -14
- package/templates/.claude/rules/MAY-optimization.md +1 -1
- package/templates/.claude/rules/MUST-agent-design.md +1 -1
- package/templates/.claude/rules/SHOULD-memory-integration.md +1 -1
- package/templates/.claude/rules/SHOULD-verification-ladder.md +9 -0
- package/templates/.claude/settings.json +0 -10
- package/templates/.claude/skills/claude-native/SKILL.md +36 -2
- package/templates/.claude/skills/intent-detection/patterns/agent-triggers.yaml +0 -8
- package/templates/.claude/skills/omcustom-feedback/SKILL.md +51 -22
- package/templates/.claude/skills/pipeline/workflows/auto-dev.yaml +251 -29
- package/templates/.claude/skills/post-release-followup/SKILL.md +108 -14
- package/templates/.claude/skills/scout/SKILL.md +41 -6
- package/templates/.github/workflows/wiki-sync.yml +26 -93
- package/templates/CLAUDE.md +1 -1
- package/templates/CLAUDE.md.en +1 -1
- package/templates/CLAUDE.md.ko +1 -1
- package/templates/README.md +4 -4
- package/templates/guides/claude-code/15-version-compatibility.md +6 -0
- package/templates/guides/external-tools/ecc-absorption-decisions.md +2 -0
- package/templates/guides/multi-provider-exec/README.md +7 -18
- package/templates/manifest.json +3 -3
- package/templates/workflows/auto-dev.yaml +251 -29
- package/templates/.claude/hooks/scripts/rtk-intercept.sh +0 -77
- package/templates/.claude/skills/rtk-exec/SKILL.md +0 -199
- package/templates/.claude/skills/rtk-exec/scripts/rtk-wrapper.cjs +0 -377
package/dist/index.js
CHANGED
|
@@ -416,7 +416,7 @@ var MESSAGES = {
|
|
|
416
416
|
"update.file_applied": "Applied update to {{path}}",
|
|
417
417
|
"update.lockfile_regenerated": "Lockfile regenerated ({{files}} files tracked)",
|
|
418
418
|
"update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
|
|
419
|
-
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}}
|
|
419
|
+
"update.protected_file_updated": "⟳ Protected file {{file}} in {{component}} selected for update: The protected-file policy allows this template update.",
|
|
420
420
|
"update.namespace_synced": "Namespace synced: {{file}} ({{component}})",
|
|
421
421
|
"update.hook_commands_migrated": "Migrated {{count}} hook/statusLine command(s) to CLAUDE_PROJECT_DIR-anchored paths",
|
|
422
422
|
"update.hook_commands_migration_failed": "Failed to migrate hook commands in {{path}}",
|
|
@@ -430,18 +430,10 @@ var MESSAGES = {
|
|
|
430
430
|
"codex.installing_npm": "Installing Codex CLI via npm...",
|
|
431
431
|
"codex.install_failed": "Failed to install Codex CLI: {{error}}",
|
|
432
432
|
"codex.unsupported_os": "Codex CLI auto-install is not supported on {{os}}",
|
|
433
|
-
"rtk.already_installed": "RTK is already installed",
|
|
434
|
-
"rtk.installing_brew": "Installing RTK via Homebrew...",
|
|
435
|
-
"rtk.installing_curl": "Installing RTK via install script...",
|
|
436
|
-
"rtk.install_failed": "Failed to install RTK: {{error}}",
|
|
437
|
-
"rtk.unsupported_os": "RTK auto-install is not supported on {{os}}",
|
|
438
433
|
"mcp.ontology_rag_configured": "ontology-rag MCP server configured successfully",
|
|
439
434
|
"install.codex_installing": "Installing Codex CLI...",
|
|
440
435
|
"install.codex_success": "Codex CLI installed successfully",
|
|
441
436
|
"install.codex_already": "Codex CLI is already installed",
|
|
442
|
-
"install.rtk_installing": "Installing RTK...",
|
|
443
|
-
"install.rtk_success": "RTK installed successfully",
|
|
444
|
-
"install.rtk_already": "RTK is already installed",
|
|
445
437
|
"install.preserved": "Preserved {{files}} file(s) and {{dirs}} directory(ies) from the existing installation",
|
|
446
438
|
"install.restored": "Restored {{files}} file(s) and {{dirs}} directory(ies) after installation",
|
|
447
439
|
"install.backup_failed": "Failed to back up {{path}}: {{error}}",
|
|
@@ -453,7 +445,6 @@ var MESSAGES = {
|
|
|
453
445
|
"preserve.restore_failed": "Failed to restore file {{file}}: {{error}}",
|
|
454
446
|
"preserve_files.invalid_path": "Ignored invalid preserve_files entry from {{source}}: {{path}} ({{reason}})",
|
|
455
447
|
"update.codex_missing": "Codex CLI is not installed",
|
|
456
|
-
"update.rtk_missing": "RTK is not installed",
|
|
457
448
|
"update.self_update_skipped": "Skipped update: source project cannot update itself",
|
|
458
449
|
"update.deprecated_file_invalid_path": "Skipped deprecated file with invalid path: {{path}} ({{reason}})",
|
|
459
450
|
"update.deprecated_file_removed": "Removed deprecated file: {{path}} ({{reason}})",
|
|
@@ -461,8 +452,8 @@ var MESSAGES = {
|
|
|
461
452
|
"update.entry_doc_force_updated": "Entry document force-updated: {{path}}",
|
|
462
453
|
"update.entry_merge_warning": "Entry document merge warning: {{warning}}",
|
|
463
454
|
"update.entry_template_not_found": "Entry document template not found: {{template}}",
|
|
464
|
-
"update.protected_file_force_overwrite": "Protected file {{file}} in {{component}}
|
|
465
|
-
"update.protected_file_skipped": "Protected file {{file}} in {{component}} skipped:
|
|
455
|
+
"update.protected_file_force_overwrite": "Protected file {{file}} in {{component}} selected for overwrite: The --force-overwrite-all option bypasses the protected-file preservation policy.",
|
|
456
|
+
"update.protected_file_skipped": "Protected file {{file}} in {{component}} skipped: The protected-file preservation policy skips this update. Use --force-overwrite-all to override.",
|
|
466
457
|
"update.settings_local_backfill_failed": "Failed to backfill statusLine refreshInterval in {{path}}",
|
|
467
458
|
"general.done": "Done!",
|
|
468
459
|
"general.failed": "Failed",
|
|
@@ -538,7 +529,7 @@ var MESSAGES = {
|
|
|
538
529
|
"update.file_applied": "{{path}} 업데이트 적용",
|
|
539
530
|
"update.lockfile_regenerated": "잠금 파일 재생성 완료 ({{files}}개 파일 추적)",
|
|
540
531
|
"update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
|
|
541
|
-
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}})
|
|
532
|
+
"update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}}) 업데이트 대상: 보호 파일 정책에 따라 템플릿 업데이트가 허용됩니다.",
|
|
542
533
|
"update.namespace_synced": "네임스페이스 동기화: {{file}} ({{component}})",
|
|
543
534
|
"update.hook_commands_migrated": "CLAUDE_PROJECT_DIR 기준 경로로 훅/상태줄 명령 {{count}}개를 마이그레이션했습니다",
|
|
544
535
|
"update.hook_commands_migration_failed": "{{path}}의 훅 명령 마이그레이션에 실패했습니다",
|
|
@@ -552,18 +543,10 @@ var MESSAGES = {
|
|
|
552
543
|
"codex.installing_npm": "npm으로 Codex CLI를 설치합니다...",
|
|
553
544
|
"codex.install_failed": "Codex CLI 설치에 실패했습니다: {{error}}",
|
|
554
545
|
"codex.unsupported_os": "{{os}}에서는 Codex CLI 자동 설치를 지원하지 않습니다",
|
|
555
|
-
"rtk.already_installed": "RTK가 이미 설치되어 있습니다",
|
|
556
|
-
"rtk.installing_brew": "Homebrew로 RTK를 설치합니다...",
|
|
557
|
-
"rtk.installing_curl": "설치 스크립트로 RTK를 설치합니다...",
|
|
558
|
-
"rtk.install_failed": "RTK 설치에 실패했습니다: {{error}}",
|
|
559
|
-
"rtk.unsupported_os": "{{os}}에서는 RTK 자동 설치를 지원하지 않습니다",
|
|
560
546
|
"mcp.ontology_rag_configured": "ontology-rag MCP 서버 구성을 완료했습니다",
|
|
561
547
|
"install.codex_installing": "Codex CLI를 설치합니다...",
|
|
562
548
|
"install.codex_success": "Codex CLI를 설치했습니다",
|
|
563
549
|
"install.codex_already": "Codex CLI가 이미 설치되어 있습니다",
|
|
564
|
-
"install.rtk_installing": "RTK를 설치합니다...",
|
|
565
|
-
"install.rtk_success": "RTK를 설치했습니다",
|
|
566
|
-
"install.rtk_already": "RTK가 이미 설치되어 있습니다",
|
|
567
550
|
"install.preserved": "기존 설치에서 파일 {{files}}개와 디렉토리 {{dirs}}개를 보존했습니다",
|
|
568
551
|
"install.restored": "설치 후 파일 {{files}}개와 디렉토리 {{dirs}}개를 복원했습니다",
|
|
569
552
|
"install.backup_failed": "{{path}} 백업에 실패했습니다: {{error}}",
|
|
@@ -575,7 +558,6 @@ var MESSAGES = {
|
|
|
575
558
|
"preserve.restore_failed": "파일 {{file}} 복원에 실패했습니다: {{error}}",
|
|
576
559
|
"preserve_files.invalid_path": "{{source}}의 유효하지 않은 preserve_files 항목을 무시했습니다: {{path}} ({{reason}})",
|
|
577
560
|
"update.codex_missing": "Codex CLI가 설치되어 있지 않습니다",
|
|
578
|
-
"update.rtk_missing": "RTK가 설치되어 있지 않습니다",
|
|
579
561
|
"update.self_update_skipped": "업데이트를 건너뜁니다: 소스 프로젝트는 스스로 업데이트할 수 없습니다",
|
|
580
562
|
"update.deprecated_file_invalid_path": "경로가 유효하지 않아 지원 중단 파일을 건너뜁니다: {{path}} ({{reason}})",
|
|
581
563
|
"update.deprecated_file_removed": "지원 중단 파일을 삭제했습니다: {{path}} ({{reason}})",
|
|
@@ -583,8 +565,8 @@ var MESSAGES = {
|
|
|
583
565
|
"update.entry_doc_force_updated": "진입 문서를 강제로 업데이트했습니다: {{path}}",
|
|
584
566
|
"update.entry_merge_warning": "진입 문서 병합 경고: {{warning}}",
|
|
585
567
|
"update.entry_template_not_found": "진입 문서 템플릿을 찾을 수 없습니다: {{template}}",
|
|
586
|
-
"update.protected_file_force_overwrite": "{{component}}의 보호 파일 {{file}}
|
|
587
|
-
"update.protected_file_skipped": "{{component}}의 보호 파일 {{file}}을(를) 건너뜁니다:
|
|
568
|
+
"update.protected_file_force_overwrite": "{{component}}의 보호 파일 {{file}} 덮어쓰기 대상: --force-overwrite-all 옵션에 따라 보호 파일 보존 정책을 적용하지 않습니다.",
|
|
569
|
+
"update.protected_file_skipped": "{{component}}의 보호 파일 {{file}}을(를) 건너뜁니다: 보호 파일 보존 정책에 따라 업데이트를 건너뜁니다. 덮어쓰려면 --force-overwrite-all 옵션을 사용하십시오.",
|
|
588
570
|
"update.settings_local_backfill_failed": "{{path}}의 statusLine refreshInterval 보충에 실패했습니다",
|
|
589
571
|
"general.done": "완료!",
|
|
590
572
|
"general.failed": "실패",
|
|
@@ -1173,6 +1155,7 @@ var LEGACY_OMCUSTOM_DESCRIPTIONS = [
|
|
|
1173
1155
|
];
|
|
1174
1156
|
var STANDALONE_SCRIPT_COMMAND = /^(?:bash )?(?:"(?:\$\{CLAUDE_PROJECT_DIR:-\.\}\/|\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)"|(?:\.\/)?\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh))$/;
|
|
1175
1157
|
var SCRIPT_REFERENCE = /\.claude\/hooks\/((?:scripts\/)?[A-Za-z0-9._-]+\.sh)/g;
|
|
1158
|
+
var RETIRED_RTK_DESCRIPTION = "RTK auto-intercept — transparently rewrites CLI commands through RTK proxy when available (R013 advisory)";
|
|
1176
1159
|
function matcherKey(group) {
|
|
1177
1160
|
return typeof group.matcher === "string" ? group.matcher : "";
|
|
1178
1161
|
}
|
|
@@ -1234,15 +1217,52 @@ function isOwnedCommand(hook, owned) {
|
|
|
1234
1217
|
const script = match?.[1] ?? match?.[2];
|
|
1235
1218
|
return script !== undefined && owned.scripts.has(script);
|
|
1236
1219
|
}
|
|
1237
|
-
function
|
|
1220
|
+
function isRetiredRtkCommand(hook, event, matcher) {
|
|
1221
|
+
if (event !== "PreToolUse" || matcher !== "Bash" || !isRecord(hook) || hook.type !== "command" || typeof hook.command !== "string") {
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
const match = STANDALONE_SCRIPT_COMMAND.exec(hook.command);
|
|
1225
|
+
return (match?.[1] ?? match?.[2]) === "scripts/rtk-intercept.sh";
|
|
1226
|
+
}
|
|
1227
|
+
function retireRtkGroup(group, event) {
|
|
1228
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
1229
|
+
return [group];
|
|
1230
|
+
}
|
|
1231
|
+
const remaining = group.hooks.filter((hook) => !isRetiredRtkCommand(hook, event, matcherKey(group)));
|
|
1232
|
+
if (remaining.length === group.hooks.length) {
|
|
1233
|
+
return [group];
|
|
1234
|
+
}
|
|
1235
|
+
return remaining.length === 0 ? [] : [{ ...group, hooks: remaining }];
|
|
1236
|
+
}
|
|
1237
|
+
function retireRtkHookBlocks(existing) {
|
|
1238
|
+
if (!isRecord(existing)) {
|
|
1239
|
+
return existing;
|
|
1240
|
+
}
|
|
1241
|
+
const updates = {};
|
|
1242
|
+
for (const [event, value] of Object.entries(existing)) {
|
|
1243
|
+
if (!Array.isArray(value)) {
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
const residual = value.flatMap((group) => retireRtkGroup(group, event));
|
|
1247
|
+
if (residual.length !== value.length || residual.some((group, index) => group !== value[index])) {
|
|
1248
|
+
updates[event] = residual;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return Object.keys(updates).length === 0 ? existing : { ...existing, ...updates };
|
|
1252
|
+
}
|
|
1253
|
+
function userResidual(group, event, owned, ownership) {
|
|
1238
1254
|
if (!isRecord(group) || !Array.isArray(group.hooks)) {
|
|
1239
1255
|
return [group];
|
|
1240
1256
|
}
|
|
1257
|
+
const matcher = matcherKey(group);
|
|
1258
|
+
if (group.description === RETIRED_RTK_DESCRIPTION) {
|
|
1259
|
+
return retireRtkGroup(group, event);
|
|
1260
|
+
}
|
|
1241
1261
|
if (typeof group.description === "string" && ownership.descriptions.has(group.description)) {
|
|
1242
1262
|
return [];
|
|
1243
1263
|
}
|
|
1244
|
-
const ownedForMatcher = owned?.get(
|
|
1245
|
-
const remaining = group.hooks.filter((hook) => !isOwnedCommand(hook, ownedForMatcher));
|
|
1264
|
+
const ownedForMatcher = owned?.get(matcher);
|
|
1265
|
+
const remaining = group.hooks.filter((hook) => !isRetiredRtkCommand(hook, event, matcher) && !isOwnedCommand(hook, ownedForMatcher));
|
|
1246
1266
|
if (remaining.length === group.hooks.length) {
|
|
1247
1267
|
return [group];
|
|
1248
1268
|
}
|
|
@@ -1265,7 +1285,7 @@ function mergeHookBlocks(existing, generated) {
|
|
|
1265
1285
|
continue;
|
|
1266
1286
|
}
|
|
1267
1287
|
const owned = ownership.events.get(event);
|
|
1268
|
-
const residual = value.flatMap((group) => userResidual(group, owned, ownership));
|
|
1288
|
+
const residual = value.flatMap((group) => userResidual(group, event, owned, ownership));
|
|
1269
1289
|
if (residual.length === 0) {
|
|
1270
1290
|
continue;
|
|
1271
1291
|
}
|
|
@@ -2221,8 +2241,23 @@ function sortFilesByPath(files) {
|
|
|
2221
2241
|
const sorted = Object.keys(files).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
2222
2242
|
return Object.fromEntries(sorted.map((key) => [key, files[key]]));
|
|
2223
2243
|
}
|
|
2244
|
+
var RETIRED_RTK_PATHS = [
|
|
2245
|
+
".claude/skills/rtk-exec/SKILL.md",
|
|
2246
|
+
".claude/skills/rtk-exec/scripts/rtk-wrapper.cjs",
|
|
2247
|
+
".claude/hooks/scripts/rtk-intercept.sh"
|
|
2248
|
+
];
|
|
2249
|
+
function getRetiredPaths(options) {
|
|
2250
|
+
const retiredPaths = new Set([...RETIRED_RTK_PATHS, ...options.retiredPaths ?? []]);
|
|
2251
|
+
for (const path of retiredPaths) {
|
|
2252
|
+
if (typeof path !== "string" || /[\\:*?[\]{}\0]/.test(path) || path.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
2253
|
+
throw new Error("retiredPaths must contain exact canonical relative file paths");
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
return retiredPaths;
|
|
2257
|
+
}
|
|
2224
2258
|
async function generateLockfile(targetDir, generatorVersion, templateVersion, options = {}) {
|
|
2225
2259
|
const files = {};
|
|
2260
|
+
const retiredPaths = getRetiredPaths(options);
|
|
2226
2261
|
const gitVisible = options.excludeGitIgnored ? await listGitVisiblePaths(targetDir) : null;
|
|
2227
2262
|
const componentRoots = COMPONENT_PATHS.map(([prefix]) => join4(targetDir, prefix));
|
|
2228
2263
|
for (const componentRoot of componentRoots) {
|
|
@@ -2231,7 +2266,7 @@ async function generateLockfile(targetDir, generatorVersion, templateVersion, op
|
|
|
2231
2266
|
debug("lockfile.component_dir_missing", { path: componentRoot });
|
|
2232
2267
|
continue;
|
|
2233
2268
|
}
|
|
2234
|
-
const allFiles = await collectFiles(componentRoot, targetDir, false);
|
|
2269
|
+
const allFiles = (await collectFiles(componentRoot, targetDir, false)).filter((absolutePath) => !retiredPaths.has(relative2(targetDir, absolutePath).replace(/\\/g, "/")));
|
|
2235
2270
|
for (const absolutePath of allFiles) {
|
|
2236
2271
|
const relativePath = relative2(targetDir, absolutePath).replace(/\\/g, "/");
|
|
2237
2272
|
if (gitVisible !== null && !isGitVisible(gitVisible, relativePath)) {
|
|
@@ -2314,65 +2349,6 @@ async function generateAndWriteLockfileForDir(targetDir, options = {}) {
|
|
|
2314
2349
|
}
|
|
2315
2350
|
}
|
|
2316
2351
|
|
|
2317
|
-
// src/core/rtk-installer.ts
|
|
2318
|
-
import { execSync as execSync2 } from "node:child_process";
|
|
2319
|
-
import { platform as platform2 } from "node:os";
|
|
2320
|
-
var defaultDeps2 = {
|
|
2321
|
-
exec: execSync2,
|
|
2322
|
-
getPlatform: platform2
|
|
2323
|
-
};
|
|
2324
|
-
function isRtkInstalled(deps = defaultDeps2) {
|
|
2325
|
-
try {
|
|
2326
|
-
deps.exec("which rtk", { stdio: "pipe", timeout: 3000 });
|
|
2327
|
-
return true;
|
|
2328
|
-
} catch {
|
|
2329
|
-
return false;
|
|
2330
|
-
}
|
|
2331
|
-
}
|
|
2332
|
-
function installRtk(deps = defaultDeps2) {
|
|
2333
|
-
if (process.env.CI || false || false) {
|
|
2334
|
-
return false;
|
|
2335
|
-
}
|
|
2336
|
-
if (isRtkInstalled(deps)) {
|
|
2337
|
-
info("rtk.already_installed");
|
|
2338
|
-
return true;
|
|
2339
|
-
}
|
|
2340
|
-
const os = deps.getPlatform();
|
|
2341
|
-
try {
|
|
2342
|
-
if (os === "darwin") {
|
|
2343
|
-
try {
|
|
2344
|
-
info("rtk.installing_brew");
|
|
2345
|
-
deps.exec("brew install rtk-ai/tap/rtk", {
|
|
2346
|
-
stdio: "inherit",
|
|
2347
|
-
timeout: 120000
|
|
2348
|
-
});
|
|
2349
|
-
return true;
|
|
2350
|
-
} catch {
|
|
2351
|
-
info("rtk.installing_curl");
|
|
2352
|
-
deps.exec("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
|
|
2353
|
-
stdio: "inherit",
|
|
2354
|
-
timeout: 120000
|
|
2355
|
-
});
|
|
2356
|
-
return isRtkInstalled(deps);
|
|
2357
|
-
}
|
|
2358
|
-
} else if (os === "linux") {
|
|
2359
|
-
info("rtk.installing_curl");
|
|
2360
|
-
deps.exec("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
|
|
2361
|
-
stdio: "inherit",
|
|
2362
|
-
timeout: 120000
|
|
2363
|
-
});
|
|
2364
|
-
return isRtkInstalled(deps);
|
|
2365
|
-
} else {
|
|
2366
|
-
warn("rtk.unsupported_os", { os });
|
|
2367
|
-
return false;
|
|
2368
|
-
}
|
|
2369
|
-
} catch (err) {
|
|
2370
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2371
|
-
warn("rtk.install_failed", { error: message });
|
|
2372
|
-
return false;
|
|
2373
|
-
}
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
2352
|
// src/core/scope-filter.ts
|
|
2377
2353
|
function getSkillScope(content) {
|
|
2378
2354
|
const cleaned = content.replace(/^\uFEFF/, "");
|
|
@@ -2661,19 +2637,6 @@ async function updateInstallConfig(targetDir, options, installedComponents) {
|
|
|
2661
2637
|
config.installedComponents = installedComponents;
|
|
2662
2638
|
await saveConfig(targetDir, config);
|
|
2663
2639
|
}
|
|
2664
|
-
function installRtkIfNeeded(result) {
|
|
2665
|
-
if (!isRtkInstalled()) {
|
|
2666
|
-
info("install.rtk_installing");
|
|
2667
|
-
const rtkInstalled = installRtk();
|
|
2668
|
-
if (rtkInstalled) {
|
|
2669
|
-
info("install.rtk_success");
|
|
2670
|
-
} else {
|
|
2671
|
-
result.warnings.push("RTK installation failed — install manually: brew install rtk-ai/tap/rtk");
|
|
2672
|
-
}
|
|
2673
|
-
} else {
|
|
2674
|
-
info("install.rtk_already");
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
2640
|
function installCodexIfNeeded(result) {
|
|
2678
2641
|
if (!isCodexInstalled()) {
|
|
2679
2642
|
info("install.codex_installing");
|
|
@@ -2728,7 +2691,6 @@ async function install(options) {
|
|
|
2728
2691
|
} else {
|
|
2729
2692
|
info("install.lockfile_generated", { files: String(lockfileResult.fileCount) });
|
|
2730
2693
|
}
|
|
2731
|
-
installRtkIfNeeded(result);
|
|
2732
2694
|
installCodexIfNeeded(result);
|
|
2733
2695
|
result.success = true;
|
|
2734
2696
|
success("install.success");
|
|
@@ -2936,14 +2898,15 @@ async function detectProvider(_options = {}) {
|
|
|
2936
2898
|
};
|
|
2937
2899
|
}
|
|
2938
2900
|
// src/core/updater.ts
|
|
2939
|
-
import {
|
|
2901
|
+
import { lstat, realpath as realpath2, unlink } from "node:fs/promises";
|
|
2902
|
+
import { isAbsolute as isAbsolute3, join as join8, relative as relative3, resolve as resolve3, sep as sep4 } from "node:path";
|
|
2940
2903
|
// package.json
|
|
2941
2904
|
var package_default = {
|
|
2942
2905
|
name: "oh-my-customcode",
|
|
2943
2906
|
workspaces: [
|
|
2944
2907
|
"packages/*"
|
|
2945
2908
|
],
|
|
2946
|
-
version: "1.1.
|
|
2909
|
+
version: "1.1.107",
|
|
2947
2910
|
description: "Batteries-included agent harness for Claude Code",
|
|
2948
2911
|
type: "module",
|
|
2949
2912
|
bin: {
|
|
@@ -2977,7 +2940,7 @@ var package_default = {
|
|
|
2977
2940
|
lint: "biome check src/ tests/ scripts/",
|
|
2978
2941
|
"lint:fix": "biome check --write src/ tests/ scripts/",
|
|
2979
2942
|
format: "biome format --write src/ tests/ scripts/",
|
|
2980
|
-
typecheck: "tsc --noEmit",
|
|
2943
|
+
typecheck: "tsc --noEmit && tsc --noEmit -p tsconfig.wiki-links.json",
|
|
2981
2944
|
"docs:dev": "vitepress dev docs",
|
|
2982
2945
|
"docs:build": "vitepress build docs",
|
|
2983
2946
|
prepare: "sh scripts/setup-hooks.sh || true",
|
|
@@ -2989,7 +2952,7 @@ var package_default = {
|
|
|
2989
2952
|
"@inquirer/prompts": "^8.3.2",
|
|
2990
2953
|
commander: "^15.0.0",
|
|
2991
2954
|
i18next: "^26.0.2",
|
|
2992
|
-
yaml: "
|
|
2955
|
+
yaml: "2.8.3"
|
|
2993
2956
|
},
|
|
2994
2957
|
devDependencies: {
|
|
2995
2958
|
"@anthropic-ai/sdk": "^0.127.0",
|
|
@@ -2998,6 +2961,7 @@ var package_default = {
|
|
|
2998
2961
|
"@types/js-yaml": "^4.0.9",
|
|
2999
2962
|
"@types/nodemailer": "^8.0.0",
|
|
3000
2963
|
"js-yaml": "^5.0.0",
|
|
2964
|
+
"mdast-util-from-markdown": "2.1.0",
|
|
3001
2965
|
nodemailer: "^10.0.0",
|
|
3002
2966
|
typescript: "^7.0.2",
|
|
3003
2967
|
vitepress: "^1.6.4"
|
|
@@ -5588,6 +5552,86 @@ ${MANAGED_END}`;
|
|
|
5588
5552
|
|
|
5589
5553
|
// src/core/updater.ts
|
|
5590
5554
|
var CUSTOMIZATION_MANIFEST_FILE = ".omcustom-customizations.json";
|
|
5555
|
+
var RETIRED_RTK_FILES = [
|
|
5556
|
+
{ path: ".claude/skills/rtk-exec/SKILL.md", component: "skills" },
|
|
5557
|
+
{ path: ".claude/skills/rtk-exec/scripts/rtk-wrapper.cjs", component: "skills" },
|
|
5558
|
+
{ path: ".claude/hooks/scripts/rtk-intercept.sh", component: "hooks" }
|
|
5559
|
+
];
|
|
5560
|
+
function getRtkRetirement(result) {
|
|
5561
|
+
result.rtkRetirement ??= {
|
|
5562
|
+
removed: [],
|
|
5563
|
+
wouldRemove: [],
|
|
5564
|
+
preserved: [],
|
|
5565
|
+
settingsChanged: [],
|
|
5566
|
+
wouldChangeSettings: [],
|
|
5567
|
+
settingsConflicts: []
|
|
5568
|
+
};
|
|
5569
|
+
return result.rtkRetirement;
|
|
5570
|
+
}
|
|
5571
|
+
async function validateRtkTarget(targetDir, path) {
|
|
5572
|
+
const validation = validatePreserveFilePath(path, targetDir);
|
|
5573
|
+
if (!validation.valid)
|
|
5574
|
+
throw new Error("path escapes project");
|
|
5575
|
+
const fullPath = resolve3(targetDir, path);
|
|
5576
|
+
const fileStat = await lstat(fullPath);
|
|
5577
|
+
if (!fileStat.isFile() || fileStat.isSymbolicLink())
|
|
5578
|
+
throw new Error("not a regular owned file");
|
|
5579
|
+
const root = await realpath2(targetDir);
|
|
5580
|
+
const resolvedFile = await realpath2(fullPath);
|
|
5581
|
+
const within = relative3(root, resolvedFile);
|
|
5582
|
+
if (within === "" || within === ".." || within.startsWith(`..${sep4}`) || isAbsolute3(within)) {
|
|
5583
|
+
throw new Error("resolved path escapes project");
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
function isRtkPathPreserved(path, config, customizations) {
|
|
5587
|
+
const paths = [
|
|
5588
|
+
...config.preserveFiles ?? [],
|
|
5589
|
+
...customizations?.preserveFiles ?? [],
|
|
5590
|
+
...customizations?.modifiedFiles ?? [],
|
|
5591
|
+
...(config.customComponents ?? []).map((component) => component.path)
|
|
5592
|
+
];
|
|
5593
|
+
return paths.some((preserved) => path === preserved || path.startsWith(`${preserved}/`));
|
|
5594
|
+
}
|
|
5595
|
+
async function retireRtkFile(path, options, lockfile, result, preserved) {
|
|
5596
|
+
const retirement = getRtkRetirement(result);
|
|
5597
|
+
try {
|
|
5598
|
+
await validateRtkTarget(options.targetDir, path);
|
|
5599
|
+
if (preserved)
|
|
5600
|
+
throw new Error("explicitly preserved");
|
|
5601
|
+
if (retirement.settingsConflicts.length > 0)
|
|
5602
|
+
throw new Error("settings retirement conflict");
|
|
5603
|
+
const entry = lockfile?.files[path];
|
|
5604
|
+
if (!entry || typeof entry.templateHash !== "string" || !/^[a-f0-9]{64}$/.test(entry.templateHash)) {
|
|
5605
|
+
throw new Error("missing or invalid old ownership");
|
|
5606
|
+
}
|
|
5607
|
+
const fullPath = join8(options.targetDir, path);
|
|
5608
|
+
if (await computeFileHash(fullPath) !== entry.templateHash)
|
|
5609
|
+
throw new Error("user-modified file");
|
|
5610
|
+
if (options.dryRun) {
|
|
5611
|
+
retirement.wouldRemove.push(path);
|
|
5612
|
+
return;
|
|
5613
|
+
}
|
|
5614
|
+
await validateRtkTarget(options.targetDir, path);
|
|
5615
|
+
await unlink(fullPath);
|
|
5616
|
+
retirement.removed.push(path);
|
|
5617
|
+
result.removedDeprecatedFiles.push(path);
|
|
5618
|
+
} catch (err) {
|
|
5619
|
+
if (err.code === "ENOENT")
|
|
5620
|
+
return;
|
|
5621
|
+
const reason = err instanceof Error ? err.message : "unreadable ownership or file";
|
|
5622
|
+
retirement.preserved.push({ path, reason });
|
|
5623
|
+
result.preservedFiles.push(path);
|
|
5624
|
+
result.warnings.push(`RTK retirement preserved ${path}: ${reason}`);
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
async function retireRtkFiles(options, lockfile, result, config, customizations) {
|
|
5628
|
+
const fullUpdate = !options.components || options.components.length === 0;
|
|
5629
|
+
const files = RETIRED_RTK_FILES.filter((file) => fullUpdate || options.components?.includes(file.component));
|
|
5630
|
+
for (const file of files) {
|
|
5631
|
+
await retireRtkFile(file.path, options, lockfile, result, isRtkPathPreserved(file.path, config, customizations));
|
|
5632
|
+
}
|
|
5633
|
+
return RETIRED_RTK_FILES.map((file) => file.path);
|
|
5634
|
+
}
|
|
5591
5635
|
function createUpdateResult() {
|
|
5592
5636
|
return {
|
|
5593
5637
|
success: false,
|
|
@@ -5775,32 +5819,52 @@ async function backfillStatusLineRefreshInterval(targetDir, options) {
|
|
|
5775
5819
|
});
|
|
5776
5820
|
}
|
|
5777
5821
|
}
|
|
5778
|
-
async function migrateHookCommandsInSettingsLocal(targetDir, options) {
|
|
5779
|
-
|
|
5780
|
-
|
|
5822
|
+
async function migrateHookCommandsInSettingsLocal(targetDir, options, result) {
|
|
5823
|
+
for (const name of ["settings.json", "settings.local.json"]) {
|
|
5824
|
+
await migrateRtkSettingsFile(targetDir, options, result, name);
|
|
5781
5825
|
}
|
|
5826
|
+
}
|
|
5827
|
+
async function migrateRtkSettingsFile(targetDir, options, result, name) {
|
|
5782
5828
|
const layout = getProviderLayout();
|
|
5783
|
-
const
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
}
|
|
5829
|
+
const path = `${layout.rootDir}/${name}`;
|
|
5830
|
+
const settingsPath = join8(targetDir, path);
|
|
5831
|
+
const retirement = getRtkRetirement(result);
|
|
5787
5832
|
try {
|
|
5833
|
+
await validateRtkTarget(targetDir, path);
|
|
5788
5834
|
const raw = await readTextFile(settingsPath);
|
|
5789
5835
|
const bom = raw.startsWith("\uFEFF") ? "\uFEFF" : "";
|
|
5790
5836
|
const parsed = JSON.parse(bom ? raw.slice(bom.length) : raw);
|
|
5791
5837
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
5838
|
+
throw new Error("unknown settings shape");
|
|
5839
|
+
}
|
|
5840
|
+
const current = parsed;
|
|
5841
|
+
const hooks = retireRtkHookBlocks(current.hooks);
|
|
5842
|
+
const retired = hooks !== current.hooks;
|
|
5843
|
+
const input = retired ? { ...current, hooks } : current;
|
|
5844
|
+
const { settings, rewritten } = name === "settings.local.json" ? migrateHookCommands(input) : { settings: input, rewritten: 0 };
|
|
5845
|
+
if (JSON.stringify(settings.hooks)?.includes("rtk-intercept.sh")) {
|
|
5846
|
+
retirement.settingsConflicts.push(path);
|
|
5847
|
+
result.warnings.push(`RTK retirement retained user or unknown wiring in ${path}`);
|
|
5848
|
+
}
|
|
5849
|
+
if (!retired && rewritten === 0) {
|
|
5792
5850
|
return;
|
|
5793
5851
|
}
|
|
5794
|
-
|
|
5795
|
-
|
|
5852
|
+
if (options.dryRun) {
|
|
5853
|
+
retirement.wouldChangeSettings.push(path);
|
|
5796
5854
|
return;
|
|
5797
5855
|
}
|
|
5798
5856
|
const trailingNewline = raw.endsWith(`
|
|
5799
5857
|
`) ? `
|
|
5800
5858
|
` : "";
|
|
5859
|
+
await validateRtkTarget(targetDir, path);
|
|
5801
5860
|
await writeTextFile(settingsPath, `${bom}${JSON.stringify(settings, null, 2)}${trailingNewline}`);
|
|
5861
|
+
retirement.settingsChanged.push(path);
|
|
5802
5862
|
info("update.hook_commands_migrated", { count: String(rewritten) });
|
|
5803
|
-
} catch {
|
|
5863
|
+
} catch (err) {
|
|
5864
|
+
if (err.code === "ENOENT")
|
|
5865
|
+
return;
|
|
5866
|
+
retirement.settingsConflicts.push(path);
|
|
5867
|
+
result.warnings.push(`RTK retirement could not safely migrate ${path}`);
|
|
5804
5868
|
warn("update.hook_commands_migration_failed", {
|
|
5805
5869
|
path: settingsPath
|
|
5806
5870
|
});
|
|
@@ -5820,7 +5884,7 @@ async function runFullUpdatePostProcessing(options, result, config, customizatio
|
|
|
5820
5884
|
}
|
|
5821
5885
|
}
|
|
5822
5886
|
if (isFullUpdate || options.components?.includes("hooks")) {
|
|
5823
|
-
await migrateHookCommandsInSettingsLocal(options.targetDir, options);
|
|
5887
|
+
await migrateHookCommandsInSettingsLocal(options.targetDir, options, result);
|
|
5824
5888
|
}
|
|
5825
5889
|
if (!options.dryRun) {
|
|
5826
5890
|
config.version = result.newVersion;
|
|
@@ -5837,6 +5901,29 @@ async function runFullUpdatePostProcessing(options, result, config, customizatio
|
|
|
5837
5901
|
});
|
|
5838
5902
|
}
|
|
5839
5903
|
}
|
|
5904
|
+
async function getUpdateVersionDecision(installedVersion, cliVersion) {
|
|
5905
|
+
if (!(installedVersion !== "0.0.0" && compareSemver(installedVersion, cliVersion) > 0)) {
|
|
5906
|
+
return "standard";
|
|
5907
|
+
}
|
|
5908
|
+
if (installedVersion !== "2.0.0") {
|
|
5909
|
+
return "blocked";
|
|
5910
|
+
}
|
|
5911
|
+
const match = /^1\.1\.(0|[1-9]\d*)$/.exec(cliVersion);
|
|
5912
|
+
if (!match) {
|
|
5913
|
+
return "blocked";
|
|
5914
|
+
}
|
|
5915
|
+
const patch = Number(match[1]);
|
|
5916
|
+
if (!Number.isSafeInteger(patch) || patch < 107) {
|
|
5917
|
+
return "blocked";
|
|
5918
|
+
}
|
|
5919
|
+
const manifestPath = resolveTemplatePath(getProviderLayout().manifestFile);
|
|
5920
|
+
if (!await fileExists(manifestPath)) {
|
|
5921
|
+
return "blocked";
|
|
5922
|
+
}
|
|
5923
|
+
const manifest = await readJsonFile(manifestPath);
|
|
5924
|
+
const correctionAllowed = typeof manifest === "object" && manifest !== null && !Array.isArray(manifest) && "version" in manifest && manifest.version === cliVersion;
|
|
5925
|
+
return correctionAllowed ? "correction" : "blocked";
|
|
5926
|
+
}
|
|
5840
5927
|
function compareSemver(a, b) {
|
|
5841
5928
|
const pa = a.split(".").map(Number);
|
|
5842
5929
|
const pb = b.split(".").map(Number);
|
|
@@ -5847,24 +5934,16 @@ function compareSemver(a, b) {
|
|
|
5847
5934
|
}
|
|
5848
5935
|
return 0;
|
|
5849
5936
|
}
|
|
5850
|
-
function checkAndInstallRtkAfterUpdate() {
|
|
5851
|
-
if (!isRtkInstalled()) {
|
|
5852
|
-
warn("update.rtk_missing");
|
|
5853
|
-
console.log(i18n.t("cli.update.rtkMissing"));
|
|
5854
|
-
const rtkInstalled = installRtk();
|
|
5855
|
-
if (rtkInstalled) {
|
|
5856
|
-
console.log(i18n.t("cli.update.rtkInstalled"));
|
|
5857
|
-
}
|
|
5858
|
-
}
|
|
5859
|
-
}
|
|
5860
5937
|
async function updateProjectRegistry(targetDir, newVersion) {
|
|
5861
5938
|
try {
|
|
5862
5939
|
await Promise.resolve().then(() => init_registry());
|
|
5863
5940
|
await registerProject(targetDir, newVersion);
|
|
5864
5941
|
} catch {}
|
|
5865
5942
|
}
|
|
5866
|
-
async function regenerateLockfile(
|
|
5867
|
-
|
|
5943
|
+
async function regenerateLockfile(options, result, retiredPaths) {
|
|
5944
|
+
if (options.dryRun)
|
|
5945
|
+
return;
|
|
5946
|
+
const lockfileResult = await generateAndWriteLockfileForDir(options.targetDir, { retiredPaths });
|
|
5868
5947
|
if (lockfileResult.warning) {
|
|
5869
5948
|
result.warnings.push(lockfileResult.warning);
|
|
5870
5949
|
warn("update.lockfile_failed", { error: lockfileResult.warning });
|
|
@@ -5905,7 +5984,8 @@ async function update(options) {
|
|
|
5905
5984
|
const config = await loadConfig(options.targetDir);
|
|
5906
5985
|
result.previousVersion = config.version;
|
|
5907
5986
|
const cliVersion = package_default.version;
|
|
5908
|
-
|
|
5987
|
+
const versionDecision = await getUpdateVersionDecision(result.previousVersion, cliVersion);
|
|
5988
|
+
if (versionDecision === "blocked") {
|
|
5909
5989
|
result.success = false;
|
|
5910
5990
|
result.error = `Downgrade prevented: project has v${result.previousVersion} but CLI is v${cliVersion}. Update the CLI first: npm install -g oh-my-customcode@latest`;
|
|
5911
5991
|
return result;
|
|
@@ -5914,6 +5994,11 @@ async function update(options) {
|
|
|
5914
5994
|
return result;
|
|
5915
5995
|
}
|
|
5916
5996
|
const updateCheck = await checkForUpdates(options.targetDir);
|
|
5997
|
+
if (versionDecision === "correction" && updateCheck.latestVersion !== cliVersion) {
|
|
5998
|
+
result.success = false;
|
|
5999
|
+
result.error = `Version correction prevented: CLI is v${cliVersion} but templates are v${updateCheck.latestVersion}. Reinstall the CLI: npm install -g oh-my-customcode@latest`;
|
|
6000
|
+
return result;
|
|
6001
|
+
}
|
|
5917
6002
|
result.newVersion = updateCheck.latestVersion;
|
|
5918
6003
|
if (!updateCheck.hasUpdates && !options.force) {
|
|
5919
6004
|
info("update.no_updates");
|
|
@@ -5921,7 +6006,7 @@ async function update(options) {
|
|
|
5921
6006
|
result.skippedComponents = options.components || getAllUpdateComponents();
|
|
5922
6007
|
return result;
|
|
5923
6008
|
}
|
|
5924
|
-
await handleBackupIfRequested(options.targetDir, !!options.backup, result);
|
|
6009
|
+
await handleBackupIfRequested(options.targetDir, !!options.backup && !options.dryRun, result);
|
|
5925
6010
|
const manifestCustomizations = await resolveManifestCustomizations(options, options.targetDir);
|
|
5926
6011
|
const configPreserveFiles = resolveConfigPreserveFiles(options, config);
|
|
5927
6012
|
const customizations = resolveCustomizations(manifestCustomizations, configPreserveFiles, options.targetDir);
|
|
@@ -5929,9 +6014,10 @@ async function update(options) {
|
|
|
5929
6014
|
const components = options.components || getAllUpdateComponents();
|
|
5930
6015
|
await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config, lockfile);
|
|
5931
6016
|
await runFullUpdatePostProcessing(options, result, config, customizations);
|
|
5932
|
-
await
|
|
5933
|
-
|
|
5934
|
-
|
|
6017
|
+
const retiredPaths = await retireRtkFiles(options, lockfile, result, config, customizations);
|
|
6018
|
+
await regenerateLockfile(options, result, retiredPaths);
|
|
6019
|
+
if (!options.dryRun)
|
|
6020
|
+
checkAndInstallCodexAfterUpdate();
|
|
5935
6021
|
if (result.success && !options.dryRun) {
|
|
5936
6022
|
await updateProjectRegistry(options.targetDir, result.newVersion);
|
|
5937
6023
|
}
|
|
@@ -6107,22 +6193,19 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
6107
6193
|
if (options.forceOverwriteAll) {
|
|
6108
6194
|
warn("update.protected_file_force_overwrite", {
|
|
6109
6195
|
file: protectedPath,
|
|
6110
|
-
component
|
|
6111
|
-
hint: "File contains AI behavioral constraints. Overwriting because --force-overwrite-all was set."
|
|
6196
|
+
component
|
|
6112
6197
|
});
|
|
6113
6198
|
} else {
|
|
6114
6199
|
warn("update.protected_file_skipped", {
|
|
6115
6200
|
file: protectedPath,
|
|
6116
|
-
component
|
|
6117
|
-
hint: "File was modified by user and preserved. Use --force-overwrite-all to override."
|
|
6201
|
+
component
|
|
6118
6202
|
});
|
|
6119
6203
|
}
|
|
6120
6204
|
}
|
|
6121
6205
|
for (const updatedPath of protectedUpdatedPaths) {
|
|
6122
6206
|
info("update.protected_file_updated", {
|
|
6123
6207
|
file: updatedPath,
|
|
6124
|
-
component
|
|
6125
|
-
hint: "Protected file updated (unmodified by user, matches lockfile hash)."
|
|
6208
|
+
component
|
|
6126
6209
|
});
|
|
6127
6210
|
}
|
|
6128
6211
|
skipPaths.push(...protectedSkipPaths);
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"workspaces": [
|
|
4
4
|
"packages/*"
|
|
5
5
|
],
|
|
6
|
-
"version": "1.1.
|
|
6
|
+
"version": "1.1.107",
|
|
7
7
|
"description": "Batteries-included agent harness for Claude Code",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"lint": "biome check src/ tests/ scripts/",
|
|
38
38
|
"lint:fix": "biome check --write src/ tests/ scripts/",
|
|
39
39
|
"format": "biome format --write src/ tests/ scripts/",
|
|
40
|
-
"typecheck": "tsc --noEmit",
|
|
40
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.wiki-links.json",
|
|
41
41
|
"docs:dev": "vitepress dev docs",
|
|
42
42
|
"docs:build": "vitepress build docs",
|
|
43
43
|
"prepare": "sh scripts/setup-hooks.sh || true",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@inquirer/prompts": "^8.3.2",
|
|
50
50
|
"commander": "^15.0.0",
|
|
51
51
|
"i18next": "^26.0.2",
|
|
52
|
-
"yaml": "
|
|
52
|
+
"yaml": "2.8.3"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@anthropic-ai/sdk": "^0.127.0",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"@types/js-yaml": "^4.0.9",
|
|
59
59
|
"@types/nodemailer": "^8.0.0",
|
|
60
60
|
"js-yaml": "^5.0.0",
|
|
61
|
+
"mdast-util-from-markdown": "2.1.0",
|
|
61
62
|
"nodemailer": "^10.0.0",
|
|
62
63
|
"typescript": "^7.0.2",
|
|
63
64
|
"vitepress": "^1.6.4"
|
|
@@ -82,16 +82,6 @@
|
|
|
82
82
|
],
|
|
83
83
|
"description": "Schema-based tool input validation — Phase 1 advisory only"
|
|
84
84
|
},
|
|
85
|
-
{
|
|
86
|
-
"matcher": "tool == \"Bash\"",
|
|
87
|
-
"hooks": [
|
|
88
|
-
{
|
|
89
|
-
"type": "command",
|
|
90
|
-
"command": "bash \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/scripts/rtk-intercept.sh\""
|
|
91
|
-
}
|
|
92
|
-
],
|
|
93
|
-
"description": "RTK auto-intercept — transparently rewrites CLI commands through RTK proxy when available (R013 advisory)"
|
|
94
|
-
},
|
|
95
85
|
{
|
|
96
86
|
"matcher": "tool == \"Bash\" && tool_input.command matches \"git (reset|clean|checkout|restore|switch|rebase|merge)\"",
|
|
97
87
|
"hooks": [
|