@kaydash9999/banker-plugins 0.20.2 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +203 -1
  4. package/PRIVACY.md +34 -4
  5. package/README.md +46 -15
  6. package/bin/banker.js +228 -51
  7. package/bin/lib/codex-hooks.mjs +249 -0
  8. package/bin/lib/telemetry-config.mjs +33 -16
  9. package/codex/manifest.json +14 -11
  10. package/codex/skills/graceful-pause/SKILL.md +30 -0
  11. package/codex/skills/progress/SKILL.md +42 -0
  12. package/codex/skills/progress/scripts/progress-plan.mjs +722 -0
  13. package/codex/skills/progress/scripts/progress.mjs +295 -0
  14. package/codex/transform-matrix.md +19 -14
  15. package/commands/setup.md +23 -0
  16. package/hooks/obsidize-hook.mjs +116 -4
  17. package/hooks/progress-plan.mjs +1 -1
  18. package/hooks/progress.mjs +143 -38
  19. package/hooks/register.mjs +13 -3
  20. package/hooks/telemetry-count-codex.mjs +222 -0
  21. package/hooks/update-checkin.mjs +15 -7
  22. package/hooks/update-fetch.mjs +15 -9
  23. package/hooks/update-notify.mjs +30 -17
  24. package/package.json +1 -1
  25. package/skills/3d-intro-build/SKILL.md +11 -5
  26. package/skills/3d-intro-build/references/azure-adapter.mjs +374 -81
  27. package/skills/3d-intro-build/references/curate.mjs +48 -14
  28. package/skills/3d-intro-build/references/preview-lib.mjs +51 -15
  29. package/skills/3d-intro-build/references/prompts.md +4 -4
  30. package/skills/3d-intro-build/references/serve.mjs +6 -1
  31. package/skills/3d-intro-build/references/verify-intro.mjs +16 -9
  32. package/skills/3d-intro-build/references/video-pool.mjs +250 -135
  33. package/skills/3d-intro-setup/SKILL.md +41 -10
  34. package/skills/3d-intro-setup/references/azure-adapter.mjs +374 -81
  35. package/skills/3d-intro-setup/references/video-pool.mjs +250 -135
  36. package/skills/anti-drm/SKILL.md +6 -3
  37. package/skills/anti-drm/scripts/anti-drm.mjs +49 -12
  38. package/skills/anti-drm/scripts/instruction-toggle.mjs +105 -20
  39. package/skills/append_wiki/SKILL.md +34 -8
  40. package/skills/audit-mock/SKILL.md +1 -1
  41. package/skills/audit-security/SKILL.md +64 -2
  42. package/skills/compact-wiki/SKILL.md +37 -6
  43. package/skills/deep-research/SKILL.md +11 -0
  44. package/skills/docs-setup/SKILL.md +37 -7
  45. package/skills/lfm/SKILL.md +148 -0
  46. package/skills/lineage/SKILL.md +25 -15
  47. package/skills/lineage/lineage.py +564 -301
  48. package/skills/motion-graphic-make/SKILL.md +1 -1
  49. package/skills/motion-graphic-make/references/curate.mjs +48 -14
  50. package/skills/motion-graphic-make/references/preview-lib.mjs +51 -15
  51. package/skills/motion-graphic-make/references/serve.mjs +6 -1
  52. package/skills/motion-graphic-setup/SKILL.md +58 -13
  53. package/skills/obsidizer/SKILL.md +30 -11
  54. package/skills/obsidizer/obsidize.mjs +50 -12
  55. package/skills/omc-patch/SKILL.md +86 -23
  56. package/skills/omc-patch/scripts/omc-patch.mjs +460 -116
  57. package/skills/payload-mon/SKILL.md +6 -2
  58. package/skills/payload-mon/scripts/payload-mon.mjs +88 -12
  59. package/skills/payload-mon/scripts/payload-size.mjs +4 -1
  60. package/skills/pdf-vision-extract/SKILL.md +11 -6
  61. package/skills/ralph-qa/SKILL.md +125 -151
  62. package/skills/ralph-qa/references/gemini-seat.mjs +67 -19
  63. package/skills/ralph-qa/references/payload-scan.mjs +22 -7
  64. package/skills/ralph-qa/references/seat-payload.mjs +646 -0
  65. package/skills/ralph-qa/references/seat-run.mjs +956 -0
  66. package/skills/ralph-qa/references/seat-spawn.mjs +205 -0
  67. package/skills/ralph-qa/references/verifier-probe.mjs +57 -16
  68. package/skills/rfp-author/SKILL.md +5 -3
  69. package/skills/rfp-author/references/vendor-rfp-structure.md +1 -1
  70. package/skills/setup-bypass-permissions/SKILL.md +27 -9
  71. package/skills/setup-bypass-permissions/scripts/codex-bypass.mjs +135 -0
  72. package/skills/setup-bypass-permissions/scripts/codex-config.mjs +210 -0
  73. package/skills/setup-harness-factory/SKILL.md +17 -1
  74. package/skills/setup-insane-search/SKILL.md +16 -0
  75. package/skills/setup-java/SKILL.md +20 -1
  76. package/skills/setup-lsp/SKILL.md +56 -13
  77. package/skills/setup-mcp/SKILL.md +73 -4
  78. package/skills/setup-node/SKILL.md +31 -6
  79. package/skills/setup-omc/SKILL.md +24 -0
  80. package/skills/setup-omc-hud/SKILL.md +42 -9
  81. package/skills/setup-omc-hud/assets/apply-hud.mjs +242 -27
  82. package/skills/setup-omc-hud/assets/codex-config.mjs +210 -0
  83. package/skills/setup-omc-hud/assets/codex-status-line.mjs +90 -0
  84. package/skills/setup-omc-hud/assets/omc-hud-custom.mjs +81 -14
  85. package/skills/setup-omc-hud/scripts/claude-update-last.mjs +5 -3
  86. package/skills/setup-playwright/SKILL.md +45 -2
  87. package/skills/setup-pwsh/SKILL.md +19 -0
  88. package/skills/setup-python/SKILL.md +45 -7
  89. package/skills/setup-sandbox/SKILL.md +30 -5
  90. package/skills/setup-stitch/SKILL.md +22 -0
  91. package/skills/setup-tmux/SKILL.md +24 -4
  92. package/skills/smart-compact/SKILL.md +58 -12
  93. package/skills/smart-compact/scripts/codex-gate.mjs +384 -0
  94. package/skills/tone-compact/SKILL.md +2 -1
  95. package/skills/tone-compact/scripts/instruction-toggle.mjs +105 -20
  96. package/skills/ultra-chain/SKILL.md +12 -7
  97. package/skills/ultra-chain/references/preview-lib.mjs +51 -15
  98. package/skills/ultra-chain/references/serve.mjs +6 -1
  99. package/skills/ultra-interview/SKILL.md +14 -2
  100. package/skills/update-banker/SKILL.md +19 -3
  101. package/skills/vertical-pptx/SKILL.md +6 -3
  102. package/skills/vertical-pptx-setup/SKILL.md +76 -20
@@ -15,7 +15,12 @@
15
15
  * the torn read outright; the CAS only narrows the lost-update window to that
16
16
  * one syscall — a residual race remains, and is accepted (see writeIfUnchanged).
17
17
  *
18
- * Usage: node obsidize.mjs <path> [--profile=auto|omc|generic] [--dry-run] [--json]
18
+ * Usage: node obsidize.mjs <path> [--profile=auto|omc|generic] [--dry-run] [--json] [--page]
19
+ * --page: <path> must be one plain page (a regular file with one name, judged without following a link), and the
20
+ * vault model reads only plain pages. The Codex hook passes it, since Codex runs that hook outside its sandbox.
21
+ * With --page, <path> is a bare page name, the process runs in the vault folder, and --vault=<dev>:<ino> must name
22
+ * that folder: every read and write then goes by a name relative to a cwd fixed on the checked folder, so a folder
23
+ * swapped for a link after the check cannot lead outside it.
19
24
  */
20
25
  import crypto from 'node:crypto';
21
26
  import fs from 'node:fs';
@@ -298,15 +303,32 @@ export function detectProfile(target) {
298
303
  const read = (p) => fs.readFileSync(p, 'utf8');
299
304
 
300
305
  /** Sorted explicitly — readdir order is not deterministic across filesystems. */
301
- function listMarkdown(dir) {
306
+ // Why a --page run may not go on: the page must be a bare name and the cwd the vault folder the caller checked.
307
+ export function pageRunRefusal(name, vault) {
308
+ if (!name || /[\\/]/.test(name) || name === '.' || name === '..') return `--page takes a bare page name: ${name}`;
309
+ const here = fs.statSync('.', { bigint: true });
310
+ return vault === `${here.dev}:${here.ino}` ? '' : '--page runs in the vault folder named by --vault=<dev>:<ino>';
311
+ }
312
+
313
+ // A regular file with one name: not a link, a hard link or a pipe.
314
+ function plain(file) {
315
+ try {
316
+ const st = fs.lstatSync(file);
317
+ return st.isFile() && st.nlink === 1;
318
+ } catch {
319
+ return false;
320
+ }
321
+ }
322
+
323
+ function listMarkdown(dir, plainOnly = false) {
302
324
  return fs.readdirSync(dir)
303
- .filter((f) => f.endsWith('.md') && !f.endsWith('.excalidraw.md'))
325
+ .filter((f) => f.endsWith('.md') && !f.endsWith('.excalidraw.md') && (!plainOnly || plain(path.join(dir, f))))
304
326
  .sort(byCodeUnit);
305
327
  }
306
328
 
307
329
  /** Slug index + H1 index for the whole vault: alias uniqueness and footer resolvability need both. */
308
- export function buildVaultModel(dir) {
309
- const files = listMarkdown(dir);
330
+ export function buildVaultModel(dir, plainOnly = false) {
331
+ const files = listMarkdown(dir, plainOnly);
310
332
  const basenames = new Set(files);
311
333
  const pages = new Map();
312
334
  const h1Counts = new Map();
@@ -403,6 +425,13 @@ function discardTemp(tmp) {
403
425
  try { fs.unlinkSync(tmp); } catch { /* renamed away, or never created */ }
404
426
  }
405
427
 
428
+ // fsync where the file system does it. One that does not (EINVAL, ENOTSUP) still gets the whole page by the rename;
429
+ // only its safety across a crash is less. The same codes as the 3d-intro and motion-graphic key files' syncIfAble.
430
+ const NO_FSYNC = new Set(['EINVAL', 'ENOTSUP']);
431
+ function syncIfAble(fd) {
432
+ try { fs.fsyncSync(fd); } catch (e) { if (!NO_FSYNC.has(e.code)) throw e; }
433
+ }
434
+
406
435
  /**
407
436
  * Write `text` into a fresh temp file beside `file` and fsync it; the caller renames it
408
437
  * into place. Never a partial in-place write (PM-7).
@@ -420,7 +449,7 @@ function writeTemp(file, text) {
420
449
  const fd = fs.openSync(tmp, 'wx', 0o600);
421
450
  try {
422
451
  fs.writeFileSync(fd, text, 'utf8');
423
- fs.fsyncSync(fd);
452
+ syncIfAble(fd);
424
453
  } catch (err) {
425
454
  fs.closeSync(fd);
426
455
  discardTemp(tmp);
@@ -466,12 +495,14 @@ function writeIfUnchanged(file, baseline, text) {
466
495
  }
467
496
  }
468
497
 
469
- export function run(target, { profile = 'auto', dryRun = false, now = null } = {}) {
470
- if (!fs.existsSync(target)) throw new Error(`path not found: ${target}`);
471
- const isFile = fs.statSync(target).isFile();
498
+ export function run(target, { profile = 'auto', dryRun = false, now = null, page = false } = {}) {
499
+ if (!fs.existsSync(target) && !page) throw new Error(`path not found: ${target}`);
500
+ // --page judges with one lstat, so a page swapped for a link after the caller looked is refused, not followed.
501
+ if (page && !plain(target)) throw new Error(`not a plain page: ${target}`);
502
+ const isFile = page || fs.statSync(target).isFile();
472
503
  const dir = isFile ? path.dirname(target) : target;
473
504
  const resolved = profile === 'auto' ? detectProfile(target) : profile;
474
- const model = buildVaultModel(dir);
505
+ const model = buildVaultModel(dir, page);
475
506
  const aliases = computeAliases(model, resolved);
476
507
  const stamp = now || new Date().toISOString();
477
508
 
@@ -521,7 +552,7 @@ function main(argv) {
521
552
  return hit ? hit.slice(name.length + 3) : def;
522
553
  };
523
554
  if (args.length !== 1) {
524
- console.error('usage: obsidize.mjs <path> [--profile=auto|omc|generic] [--dry-run] [--json]');
555
+ console.error('usage: obsidize.mjs <path> [--profile=auto|omc|generic] [--dry-run] [--json] [--page]');
525
556
  return 2;
526
557
  }
527
558
  const profile = opt('profile', 'auto');
@@ -529,7 +560,14 @@ function main(argv) {
529
560
  console.error(`unknown profile: ${profile}`);
530
561
  return 2;
531
562
  }
532
- const report = run(path.resolve(args[0]), { profile, dryRun: flag('dry-run') });
563
+ if (flag('page')) {
564
+ const why = pageRunRefusal(args[0], opt('vault', ''));
565
+ if (why) {
566
+ console.error(why);
567
+ return 2;
568
+ }
569
+ }
570
+ const report = run(flag('page') ? args[0] : path.resolve(args[0]), { profile, dryRun: flag('dry-run'), page: flag('page') });
533
571
  if (flag('json')) {
534
572
  console.log(JSON.stringify(report));
535
573
  } else {
@@ -11,6 +11,8 @@ OMC(oh-my-claudecode)가 새 버전으로 자동 업데이트되면 로컬 훅
11
11
 
12
12
  패치 내용은 두 가지다.
13
13
  - 훅 스크립트 최상위의 `await import(...)` 에 10초 시간 제한을 건다. 로드가 멈춰도 훅이 끝까지 가고 프로세스가 남지 않는다.
14
+ - 시간 제한에 걸리면 훅의 stderr 에 `[OMC local patch] import 가 10초 안에 끝나지 않아 빈 모듈로 진행합니다` 한 줄을 남긴다. 종료 코드는 그대로다. stderr 를 읽는 쪽이 닫혀 그 줄을 쓰지 못해도 같다.
15
+ - 이 줄이 없는 옛 헬퍼로 패치한 훅도 `적용됨` 으로 보이고 되돌리기도 그대로 된다. 새 헬퍼는 되돌린 뒤 다시 적용하거나 새 OMC 판에 적용할 때 들어간다.
14
16
  - `hooks.json` 의 `PostToolUse` 훅을 제거한다.
15
17
 
16
18
  추가로 OMC 마켓플레이스 저장소의 `origin` 을 `omc-pinned://<원래 URL>` 로 바꿔 자동 업데이트를 막는다.
@@ -39,18 +41,45 @@ OMC(oh-my-claudecode)가 새 버전으로 자동 업데이트되면 로컬 훅
39
41
  - 진단도 늘 `--no-update` 와 함께 한다. 그것 없이 진단하면 도구가 `인자 없이 다시 실행하십시오.` 라고 안내하는데, 인자 없는 apply 를 뜻하므로 따르지 않는다.
40
42
  - 인자 없는 `--revert` 는 설치된 모든 버전을 되돌리고 고정까지 푼다. 헬퍼 자리 결함에는 도구가 안내하는 대로 활성 폴더만 `--revert --root` 로 되돌린다(3단계 1).
41
43
  - `--root` 는 폴더의 이름표가 OMC 가 아니면 `OMC 플러그인 폴더가 아님` 으로 멈춘다. `.claude-plugin/plugin.json` 의 name 이 있으면 그것으로, 없으면 `package.json` 의 name 으로 정한다. 다른 폴더를 잘못 준 실수를 막는 확인이고, 이름표가 없는 폴더는 통과한다.
42
- - 쓸 파일이 링크로 플러그인 폴더 밖을 가리키거나 백업 자리(`.omcbak`)가 링크면 도구는 쓰지 않고 실패로 센다. 문구는 적용 때 `쓰지 않음`, 되돌리기 때 `되돌리지 않음`, hooks.json 은 `hooks.json 을 건드리지 않음` 이다.
44
+ - 쓸 파일이 링크로 플러그인 폴더 밖을 가리키거나 백업 자리(`.omcbak`)가 링크, 또는 일반 파일이 아닌 것(FIFO, 폴더)이면 도구는 쓰지 않고 실패로 센다. 문구는 적용 때 `쓰지 않음`, 되돌리기 때 `되돌리지 않음`, hooks.json 은 `hooks.json 을 건드리지 않음` 이다. 인자 없는 apply 의 판 바꾸기도 `installed_plugins.json.omcbak` 자리가 그렇다면 판을 바꾸지 않고 `installed_plugins.json 을 바꾸지 않음` 으로 멈춘다(종료 1).
45
+ - 같은 판 폴더에 새 원본이 깔렸는데 옛 `.omcbak` 이 남은 경우
46
+ - 되돌리기는 지금 파일에 패치가 없고 백업과 글이 다르면 덮지 않는다. `되돌리지 않음: <이름> (지금 파일에 패치가 없고 백업과 글이 다름. 백업이 옛 원본일 수 있음)` 으로 알리고 실패로 센다. hooks.json 은 `hooks.json 을 되돌리지 않음 (...)` 이다.
47
+ - 그 훅은 이미 원본이다. 남은 백업은 옛 원본이다. 지울지 사용자에게 묻고, 고른 경우에만 그 백업을 지운다.
48
+ - 적용은 백업이 지금 원본과 다르면 백업을 지금 원본으로 바꾸고 `ok 옛 백업을 지금 원본으로 바꿈: <이름>` 으로 알린다.
43
49
  - 패치 규칙은 OMC 훅 코드의 모양에 맞춰져 있다. 처음 보는 OMC 버전이면 반드시 `check` 를 먼저 실행한다.
44
50
  - `실패 N건` 이 1건 이상이면 멈추고 출력을 그대로 보고한다. 예외는 2단계에서 남기기로 한 `PINNED` 줄 하나뿐이다(3단계 2). 문법 오류가 난 파일은 도구가 원본으로 자동 롤백한다. 고친 hooks.json 이 다시 읽어 JSON 이 아니어도 원본으로 되돌리고(`hooks.json 손상으로 롤백`) 실패로 센다.
45
51
  - 판정은 출력 문구로 한다. 종료 코드 0 만으로 완료라고 보고하지 않는다.
46
52
  - 캐시에는 정션이나 심볼릭 링크로 이어진 버전 폴더가 있을 수 있다(예: 옛 버전 폴더가 새 버전을 가리킴). 이때 한 번의 변경이 두 버전 폴더에 함께 보인다. 정상이다.
47
53
  - 도구는 `os.homedir()/.claude` 를 쓴다. `CLAUDE_CONFIG_DIR` 로 설정 폴더를 옮겼다면 이 도구는 그 폴더를 보지 않는다.
48
- - 출력의 origin 에 `https://사용자:토큰@...` 처럼 자격증명이 들어 있으면, 보고할 때 `//***@` 로 가린다. 출력을 그대로 보고하라는 단계에서도 이 가림은 지킨다. 옛 판의 도구가 만든 `PINNED` 에는 그 URL 이 남아 있을 수 있다. `--check` 는 그 칸을 `[!] 고정 기록(PINNED)에 옛 URL 칸이 남아 있음 (적용하면 지움)` 으로 알리고 모두 적용된 상태로 보지 않는다. 적용(`--check` 가 아닌 실행)은 그 칸을 지우고 권한을 0600 으로 맞춘다(POSIX. Windows 는 chmod 가 쓰기 비트만 다뤄 폴더의 상속 권한을 따른다). 이 도구가 다 지울 수 없으면(칸이 든 기록이 링크, JSON 이 아님, 폴더에 쓰지 못함, 읽지 못함, 같은 파일의 다른 이름이 있음) `[!] 고정 기록(PINNED)의 옛 URL 칸을 ...` 줄로 알리고 적용은 실패로 센다(종료 1). 다른 이름이 있으면 적용은 이 이름에서만 지우고 다른 이름에는 남긴다. 링크는 따라가 쓰지 않으며, 링크 너머에 그 칸이 없으면 알리지 않는다. 되돌리기는 `PINNED` 를 지운다. 링크 너머에 그 칸이 있거나 링크 너머를 읽지 못하거나, 지우지 못하거나, 같은 파일의 다른 이름이 있을 때만 실패로 세고, 링크가 아닌 기록은 JSON 이 아니거나 읽지 못해도 그냥 지운다. `PINNED` 는 기록일 뿐이라 지워도 고정과 되돌리기에 영향이 없다. 알린 줄은 아래 `PINNED` 줄 다루기대로 묻는다. 기록 파일의 내용은 읽어 보이지 않는다(인증 정보가 들 수 있음).
54
+ - Codex 에서 실행하면 `workspace-write` 샌드박스가 `~/.claude` 쓰기를 막는다.
55
+ - 진단(`--check --no-update`)과 `probe-pm.mjs` 는 승인 없이 된다(probe 는 임시 폴더에만 쓴다). 진단의 `쓸 수 없음 (쓸 권한이 없음)` 은 샌드박스 때문이며, 패치가 필요한지의 판정은 그대로 맞다.
56
+ - 적용과 되돌리기는 그 명령을 샌드박스 밖 실행으로 승인을 요청해 돌린다. 승인 없이 돌리면 `실패 N건` 으로 끝나고 아무것도 바뀌지 않는다.
57
+ - 승인한 실행은 Codex 0.144.5 에서 적용, 고정, `PROBE GREEN` 까지 확인했다.
58
+ - 승인을 요청할 수 없거나(승인 정책 `never`) 거부되면, 같은 명령을 Codex 입력창에서 `!` 를 붙여 사용자가 직접 실행하게 안내한다(예: `!node "<경로>" --no-update`). 사용자가 친 `!` 명령은 샌드박스 밖에서 돈다(Codex 0.144.5, `-a never` 에서 실측). `codex exec` 처럼 사람이 없는 실행이면 그 명령을 보고하고 멈춘다.
59
+ - `--add-dir ~/.claude` 처럼 설정 폴더를 통째로 여는 방법은 쓰지 않는다. 그 세션이 훅과 설정을 고칠 수 있게 되어 샌드박스가 무력해진다.
60
+ - 명령의 자리 표시(`<...>`)에 도구 출력이나 `find` 가 낸 값(경로, 판, 이름)을 넣을 때는 bash, PowerShell 모두 작은따옴표로 감싼다. 값에 작은따옴표(PowerShell 은 `‘` `’` `‚` `‛` 도 작은따옴표로 읽는다)나 줄바꿈이 있으면 명령을 만들지 않고 그 값을 사용자에게 보인다.
61
+ - 출력의 origin 에 `https://사용자:토큰@...` 처럼 자격증명이 들어 있으면, 보고할 때 `//` 뒤부터 마지막 `@` 까지를 `***` 로 가린다(도구와 같은 범위). 출력을 그대로 보고하라는 단계에서도 이 가림은 지킨다.
62
+ - 옛 판의 도구가 만든 `PINNED` 에는 그 URL 이 남아 있을 수 있다.
63
+ - `--check` 는 그 칸을 `[!] 고정 기록(PINNED)에 옛 URL 칸이 남아 있음 (적용하면 지움)` 으로 알리고 모두 적용된 상태로 보지 않는다.
64
+ - 적용(`--check` 가 아닌 실행)은 그 칸을 지우고 권한을 0600 으로 맞춘다(POSIX. Windows 는 chmod 가 쓰기 비트만 다뤄 폴더의 상속 권한을 따른다).
65
+ - 이 도구가 다 지울 수 없으면(칸이 든 기록이 링크, JSON 이 아님, 폴더에 쓰지 못함, 읽지 못함, 같은 파일의 다른 이름이 있음) `[!] 고정 기록(PINNED)의 옛 URL 칸을 ...` 줄로 알리고 적용은 실패로 센다(종료 1).
66
+ - 다른 이름이 있으면 적용은 이 이름에서만 지우고 다른 이름에는 남긴다. 링크는 따라가 쓰지 않으며, 링크 너머에 그 칸이 없으면 알리지 않는다.
67
+ - 되돌리기는 `PINNED` 를 지운다. 링크 너머에 그 칸이 있거나 링크 너머를 읽지 못하거나, 지우지 못하거나, 같은 파일의 다른 이름이 있을 때만 실패로 센다.
68
+ - 링크가 아닌 기록은 JSON 이 아니거나 읽지 못해도 그냥 지운다. `PINNED` 는 기록일 뿐이라 지워도 고정과 되돌리기에 영향이 없다.
69
+ - 알린 줄은 아래 `PINNED` 줄 다루기대로 묻는다. 기록 파일의 내용은 읽어 보이지 않는다(인증 정보가 들 수 있음).
49
70
  - `PINNED` 줄 다루기. `[!] 고정 기록(PINNED)의 옛 URL 칸을 ...` 줄이 나오면 그 줄의 경로를 사용자에게 보이고 그 기록을 지울지 남길지 묻는다. `--check` 에서 나온 줄은 2단계 승인 질문에서 함께 묻는다. 적용이나 되돌리기에서 처음 나온 줄은 멈추고 보고한 뒤 묻는다. 이때도 기록의 내용은 읽지 않고 경로만 보인다.
50
71
  - 폴더 권한이 원인(`쓰지 못함`)이면 권한을 고칠지도 묻는다.
51
- - 링크면 URL 칸은 링크가 가리키는 파일에 있다. 그 파일의 경로도 보인다. 경로는 `node -e "console.log(require('fs').realpathSync(process.argv[1]))" "<링크 경로>"` 로 구한다(bash, PowerShell 같음). 그 파일이 `PINNED` 기록이 맞는지는 사용자가 직접 열어 확인한 뒤 답하게 한다(링크는 글자만 보고 막으므로 무관한 파일일 수 있다).
52
- - 같은 파일의 다른 이름이 있다는 줄이면 그 아래 `다른 이름은 ... 로 찾습니다.` 줄의 번호로 다른 이름을 찾아 경로를 보인다. 찾은 이름 중 `PINNED` 가 아닌 것이 다른 이름이다. POSIX 는 `find "<맨 위 폴더>" -xdev -inum <번호> 2>/dev/null` 이고, 맨 위 폴더는 `df -P "<PINNED 가 든 폴더>"` 둘째 줄의 마지막 칸이다. Windows 는 `fsutil file queryFileNameById <드라이브>\ <번호>` 이다(관리자 셸에서만 확인함). 찾지 못하면(권한으로 못 본 폴더 등) 번호를 그대로 사용자에게 알린다.
53
- - 사용자가 지우기를 고르면 그것이 승인이다. 보인 경로만 지운다. 링크면 링크와 그것이 가리키는 파일을, 다른 이름이 있으면 `PINNED` 와 찾은 다른 이름을 모두 지운다. 하나라도 남기면 그 안에 URL 이 남는다. 링크가 가리키는 파일이 `PINNED` 기록이 아니라고 사용자가 답하면 그 파일은 지우지 않고 링크만 지울지 묻는다.
72
+ - 링크면 URL 칸은 링크가 가리키는 파일에 있다. 그 파일의 경로도 보인다. 경로는 `node -e "console.log(require('fs').realpathSync(process.argv[1]))" '<링크 경로>'` 로 구한다(bash, PowerShell 같음). 그 파일이 `PINNED` 기록이 맞는지는 사용자가 직접 열어 확인한 뒤 답하게 한다(링크는 글자만 보고 막으므로 무관한 파일일 수 있다).
73
+ - 같은 파일의 다른 이름이 있다는 줄이면 그 아래 `다른 이름은 ... 로 찾습니다.` 줄의 번호로 다른 이름을 찾아 경로를 보인다. 찾은 이름 중 `PINNED` 가 아닌 것이 다른 이름이다.
74
+ - POSIX 는 먼저 홈 폴더에서 찾는다: `find "$HOME"/ -xdev -inum <번호> 2>/dev/null`(끝의 `/` 는 홈 폴더가 링크여도 그 안을 찾게 한다).
75
+ - 못 찾으면 번호를 사용자에게 알리고, `PINNED` 가 든 파일 시스템 전체에서 찾을지 묻는다. 오래 걸릴 수 있다.
76
+ - 그 파일 시스템의 맨 위 폴더는 Linux 는 `stat -c %m '<PINNED 가 든 폴더>'` 이다. macOS 는 `df -P '<PINNED 가 든 폴더>' | awk 'NR==2{for(i=6;i<=NF;i++) printf "%s%s",$i,(i<NF?" ":"\n")}'` 이다(공백 든 마운트 지점도 읽음). 그 폴더에서 같은 `find` 를 돌린다.
77
+ - 찾은 경로가 같은 파일인지 확인한다. 적용 전이면 `[ '<찾은 경로>' -ef '<PINNED>' ]` 가 참이어야 한다. 적용 뒤에는 `PINNED` 가 새 파일이다. 그래서 찾은 경로의 장치 번호가 `PINNED` 가 든 폴더의 것과 같아야 한다(Linux `stat -c %d`, macOS `stat -f %d`).
78
+ - Windows 는 적용 전이면 `fsutil hardlink list '<PINNED 경로>'` 가 모든 이름을 보인다. 볼륨 루트 기준 경로라 앞에 드라이브를 붙인다. 적용 뒤에는 `fsutil file queryFileNameById <드라이브>\ <번호>` 이다. 둘 다 관리자 셸에서만 확인했다.
79
+ - 찾은 경로마다 번호를 붙여 보이고, 지울 것을 사용자가 고르게 한다.
80
+ - 찾은 경로가 git 작업 트리 안이면(`git -C '<그 폴더>' rev-parse --is-inside-work-tree` 가 `true`) 지워도 git 이력에는 남는다고 알린다.
81
+ - 찾지 못하면(권한으로 못 본 폴더 등) 번호를 그대로 사용자에게 알린다.
82
+ - 사용자가 지우기를 고르면 그것이 승인이다. 고른 경로만 지운다. 링크면 링크와 그것이 가리키는 파일을, 다른 이름이 있으면 `PINNED` 와 고른 다른 이름을 지운다. 하나라도 남기면 그 안에 URL 이 남는다. 링크가 가리키는 파일이 `PINNED` 기록이 아니라고 사용자가 답하면 그 파일은 지우지 않고 링크만 지울지 묻는다.
54
83
  - 남기기로 하면 그 줄은 예상된 실패다. 적용에서는 3단계 2 의 예외, 검증에서는 4단계 1 의 예외를 따른다. 다른 이름이 있는 기록을 남기고 적용하면 적용은 `PINNED` 의 칸만 지운다. 그 뒤 `--check` 는 다른 이름을 보지 못하니 5단계 보고에 그 경로를 적는다.
55
84
 
56
85
  ## 절차
@@ -63,9 +92,13 @@ bash:
63
92
  R=$(node -e 'const j=require(require("path").join(require("os").homedir(),".claude/plugins/installed_plugins.json"));console.log(j.plugins["oh-my-claudecode@omc"][0].installPath)')
64
93
  ```
65
94
 
66
- PowerShell:
95
+ PowerShell(Windows PowerShell 5.1 과 PowerShell 7 에서 같다):
96
+ - `installed_plugins.json` 은 `-Encoding UTF8` 로 읽는다. 5.1 은 BOM 없는 파일을 시스템 코드 페이지(예: 949)로 읽어, 경로에 한글이 있으면 `$r` 이 빈 값이 된다.
97
+ - 도구 출력을 변수나 파이프로 받아 판정할 때는 먼저 `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8` 을 실행한다. 5.1 은 node 의 UTF-8 출력을 콘솔 코드 페이지로 읽어 한글 문구가 깨진다. 출력을 그대로 흘려 보는 것은 괜찮다.
98
+ - Git Bash 가 있으면 bash 블록을 먼저 쓴다.
99
+
67
100
  ```powershell
68
- $r = (Get-Content "$env:USERPROFILE\.claude\plugins\installed_plugins.json" -Raw | ConvertFrom-Json).plugins.'oh-my-claudecode@omc'[0].installPath
101
+ $r = (Get-Content "$env:USERPROFILE\.claude\plugins\installed_plugins.json" -Raw -Encoding UTF8 | ConvertFrom-Json).plugins.'oh-my-claudecode@omc'[0].installPath
69
102
  ```
70
103
 
71
104
  ### 1. 진단
@@ -75,20 +108,24 @@ node "<이 스킬 디렉터리 절대경로>/scripts/omc-patch.mjs" --check --no
75
108
  ```
76
109
 
77
110
  - `활성 버전: X (installed_plugins.json)` 줄로 실제 활성 버전을 확인한다.
78
- - 그 줄이 `(추정: ...)` 로 끝나면 도구가 `installed_plugins.json` 에서 활성 버전을 찾지 못한 것이다. 적용하지 않고 출력을 그대로 보고하고 끝낸다. 추정한 폴더를 패치하면 실제 활성 버전은 그대로 남는데 `check` 는 적용된 것으로 보일 수 있다.
111
+ - 그 줄이 `(추정: ...)` 로 끝나면 도구가 `installed_plugins.json` 에서 활성 버전을 찾지 못한 것이다. 결과 줄은 `활성 버전을 확인하지 못했습니다.` 이고, 도구의 적용도 쓰기 전에 멈춘다. 적용하지 않고 출력을 그대로 보고하고 끝낸다.
79
112
  - `패치 필요 N건` 은 설치된 모든 버전 폴더를 합친 수다. 판정은 `결과:` 줄로 한다. 그 아래 `참고:` 줄은 아래 항목대로 다룬다.
80
113
 
81
114
  | `결과:` 줄 | 다음 |
82
115
  |---|---|
83
- | `모두 적용된 상태입니다.` | 4단계 검증으로 건너뜀 |
84
- | `헬퍼 자리 결함 N건이 있습니다.` | 2단계 확인 뒤 3단계에서 활성 폴더부터 되돌림 |
85
- | `적용되지 않은 항목이 있습니다.` | 2단계 확인 뒤 3단계 적용 |
116
+ | `활성 버전을 확인하지 못했습니다.` | 적용하지 않고 위의 `활성 버전:` 줄을 그대로 보고(`installed_plugins.json` 에서 활성 버전을 찾지 못함). 사용자에게 Claude Code 의 `/plugin` 에서 OMC 플러그인(`oh-my-claudecode@omc`)이 설치되어 켜져 있는지 먼저 확인하라고 알림 |
86
117
  | `hooks.json 을 확인하지 못했습니다.` | 적용하지 않고 위의 `[!]` 줄을 그대로 보고 |
118
+ | `훅 스크립트를 확인하지 못했습니다.` | 적용하지 않고 위의 `[!] 읽지 못함` 줄을 그대로 보고(적용도 같은 훅을 읽지 못해 실패한다) |
87
119
  | `마켓플레이스 origin 을 확인하지 못했습니다.` | 적용하지 않고 위의 `[!]` 줄을 그대로 보고(git 이 없거나 마켓플레이스 저장소를 읽지 못함) |
120
+ | `헬퍼 자리 결함 N건이 있습니다.` | 2단계 확인 뒤 3단계에서 활성 폴더부터 되돌림 |
88
121
  | `고정 기록(PINNED)의 옛 URL 칸이 이 도구로 지울 수 없는 곳에 있습니다.` | 적용할 항목은 없다. 2단계에서 그 기록을 지울지 남길지만 묻는다(`규칙` 절의 `PINNED` 줄 다루기). 어느 쪽이든 그 뒤 4단계 검증으로 건너뜀 |
122
+ | `고정 기록(PINNED)을 확인하지 못했습니다.` | 기록을 읽지 못했거나 JSON 이 아니어서 옛 URL 칸이 남았는지 모른다. 바로 위 행과 같이 다룬다 |
123
+ | `적용되지 않은 항목이 있습니다.` | 2단계 확인 뒤 3단계 적용 |
124
+ | `모두 적용된 상태입니다.` | 4단계 검증으로 건너뜀 |
89
125
 
126
+ - 표는 도구가 고르는 순서다. 위에서부터 처음 맞는 줄 하나가 나온다.
90
127
  - `참고: 비활성 버전 ...` 은 지금 쓰지 않는 버전이라 판정에 들어가지 않는다.
91
- - `결과:` 줄 아래의 `참고: 고정 기록(PINNED)의 옛 URL 칸이 이 도구로 지울 수 없는 곳에 있습니다.` 는 적용할 항목도 있다는 뜻이다. 적용해도 그곳의 칸은 남고 적용은 `실패 1건` 으로 끝난다. 2단계에서 함께 묻는다. 위 표의 같은 문구 결과 줄은 적용할 항목 없이 이것만 남았을 때 나온다.
128
+ - `결과:` 줄 아래의 `참고: 고정 기록(PINNED)의 옛 URL 칸이 이 도구로 지울 수 없는 곳에 있습니다.` 는 적용할 항목도 있다는 뜻이다. 적용해도 그곳의 칸은 남고 적용은 `실패 1건` 으로 끝난다. 2단계에서 함께 묻는다. 위 표의 같은 문구 결과 줄은 적용할 항목 없이 이것만 남았을 때 나온다. `참고: 고정 기록(PINNED)을 확인하지 못했습니다.` 도 같다. 다만 칸이 있는지 모르는 것이라, 칸이 남아 있으면 적용도 지우지 못한다는 뜻이다.
92
129
  - 인자가 `check` 이면 여기서 결과만 보고하고 끝낸다.
93
130
 
94
131
  ### 2. 확인 (적용 전 필수)
@@ -113,13 +150,18 @@ node "<이 스킬 디렉터리 절대경로>/scripts/omc-patch.mjs" --check --no
113
150
 
114
151
  PowerShell:
115
152
  ```powershell
116
- $r = (Get-Content "$env:USERPROFILE\.claude\plugins\installed_plugins.json" -Raw | ConvertFrom-Json).plugins.'oh-my-claudecode@omc'[0].installPath
153
+ $r = (Get-Content "$env:USERPROFILE\.claude\plugins\installed_plugins.json" -Raw -Encoding UTF8 | ConvertFrom-Json).plugins.'oh-my-claudecode@omc'[0].installPath
117
154
  node "<이 스킬 디렉터리 절대경로>\scripts\omc-patch.mjs" --revert --root $r
118
155
  ```
119
156
 
120
157
  - `되돌림 <훅 이름>` 줄과 `되돌리기 완료.` 를 확인한다. 끝의 `재시작해야 반영됩니다` 는 이 중간 단계에서는 무시한다. 2 에서 바로 다시 패치한다.
121
158
  - `되돌리기를 끝내지 못했습니다.` 가 나오면 멈추고 위의 `[!]` 줄을 그대로 보고한다. 종료 코드는 1 이다. 끝 줄의 `실패 N건` 은 쓰기를 거부한 파일, `되돌리지 못한 것 N건` 은 백업이 없거나 수동 백업 후보를 쓸 수 없어 남은 파일이다. 끝 줄은 링크로 같은 폴더를 가리키는 판이 여럿이어도 파일마다 한 번 센다.
122
- - `[!]` 줄에 `패치는 있는데 백업이 없어 되돌리지 못했습니다 — <이름>` 이 있으면 그 파일에 `.omcbak` 백업이 없는 것이다. 이 도구로는 되돌릴 원본이 없으니 멈추고 보고한다. 원본은 같은 버전의 OMC 저장소에서 다시 받아야 한다. 파이프 대신 파일을 거친다(Windows PowerShell 5.1 의 파이프는 바이너리를 텍스트로 바꿔 파일을 깨뜨린다): `git -C "<마켓플레이스 폴더>" archive -o "<임시 tar>" v<버전> scripts/<이름>.mjs` 뒤 `tar -xf "<임시 tar>" -C "<활성 폴더>"`. 이 명령은 사용자 승인 뒤에만 실행한다.
159
+ - `[!]` 줄에 `패치는 있는데 백업이 없어 되돌리지 못했습니다 — <이름>` 이 있으면 그 파일에 `.omcbak` 백업이 없는 것이다. 이 도구로는 되돌릴 원본이 없으니 멈추고 보고한다.
160
+ - 원본은 같은 버전의 OMC 저장소에서 다시 받아야 한다. 이 명령은 사용자 승인 뒤에만 실행한다.
161
+ - 파이프 대신 새 임시 폴더의 파일을 거친다. Windows PowerShell 5.1 의 파이프는 바이너리를 텍스트로 바꿔 파일을 깨뜨린다.
162
+ - bash: `T=$(mktemp -d) && git -C '<마켓플레이스 폴더>' archive -o "$T/src.tar" 'v<버전>' 'scripts/<이름>.mjs' && tar -xf "$T/src.tar" -C '<활성 폴더>'; [ -n "$T" ] && rm -rf "$T"` 를 한 줄로 실행한다.
163
+ - PowerShell: `$T = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName()); if (New-Item -ItemType Directory $T -ErrorAction SilentlyContinue) { git -C '<마켓플레이스 폴더>' archive -o "$T\src.tar" 'v<버전>' 'scripts/<이름>.mjs'; if ($LASTEXITCODE -eq 0) { tar -xf "$T\src.tar" -C '<활성 폴더>' }; Remove-Item -Recurse -Force $T }` 를 한 줄로 실행한다.
164
+ - 둘 다 임시 폴더를 만들지 못하면 git 도 tar 도 돌리지 않는다. 다 쓴 임시 폴더는 지운다.
123
165
 
124
166
  2. 적용한다.
125
167
 
@@ -134,7 +176,7 @@ node "<이 스킬 디렉터리 절대경로>/scripts/omc-patch.mjs" --check --no
134
176
 
135
177
  ### 4. 검증 (다섯 가지 모두)
136
178
 
137
- 1. 다시 진단한다. `결과: 모두 적용된 상태입니다.` 가 나와야 하고, 활성 버전에 `헬퍼 자리 결함` 줄이 없어야 한다. 사용자가 `PINNED` 를 남기기로 했으면 `결과: 고정 기록(PINNED)의 옛 URL 칸이 이 도구로 지울 수 없는 곳에 있습니다.` 와 그 `[!]` 줄이 나오는 것은 정상이다. 나머지 검증을 계속한다.
179
+ 1. 다시 진단한다. `결과: 모두 적용된 상태입니다.` 가 나와야 하고, 활성 버전에 `헬퍼 자리 결함` 줄이 없어야 한다. 사용자가 `PINNED` 를 남기기로 했으면 `결과: 고정 기록(PINNED)의 옛 URL 칸이 이 도구로 지울 수 없는 곳에 있습니다.` 나 `결과: 고정 기록(PINNED)을 확인하지 못했습니다.` 와 그 `[!]` 줄이 나오는 것은 정상이다. 나머지 검증을 계속한다.
138
180
 
139
181
  ```bash
140
182
  node "<이 스킬 디렉터리 절대경로>/scripts/omc-patch.mjs" --check --no-update
@@ -151,7 +193,8 @@ node "<이 스킬 디렉터리 절대경로>/scripts/omc-patch.mjs" --check --no
151
193
 
152
194
  PowerShell:
153
195
  ```powershell
154
- $r = (Get-Content "$env:USERPROFILE\.claude\plugins\installed_plugins.json" -Raw | ConvertFrom-Json).plugins.'oh-my-claudecode@omc'[0].installPath
196
+ $r = (Get-Content "$env:USERPROFILE\.claude\plugins\installed_plugins.json" -Raw -Encoding UTF8 | ConvertFrom-Json).plugins.'oh-my-claudecode@omc'[0].installPath
197
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
155
198
  $f = @(Get-ChildItem "$r\scripts\*.omcbak" | % { $_.FullName -replace '\.omcbak$','' })
156
199
  $f | % { node --check $_; "{0} syntax={1}" -f (Split-Path $_ -Leaf), $LASTEXITCODE }
157
200
  (node "<이 스킬 디렉터리 절대경로>\scripts\omc-patch.mjs" --scan @f | ConvertFrom-Json) | % { "{0} depth={1}" -f (Split-Path $_.file -Leaf), (@($_.helperDecls | % depth) -join ',') }
@@ -168,7 +211,7 @@ node "<이 스킬 디렉터리 절대경로>/scripts/omc-patch.mjs" --check --no
168
211
 
169
212
  PowerShell:
170
213
  ```powershell
171
- $r = (Get-Content "$env:USERPROFILE\.claude\plugins\installed_plugins.json" -Raw | ConvertFrom-Json).plugins.'oh-my-claudecode@omc'[0].installPath
214
+ $r = (Get-Content "$env:USERPROFILE\.claude\plugins\installed_plugins.json" -Raw -Encoding UTF8 | ConvertFrom-Json).plugins.'oh-my-claudecode@omc'[0].installPath
172
215
  $env:CLAUDE_PLUGIN_ROOT = $r; $env:OMC_NOTIFY = '0'
173
216
  '{"session_id":"smoke-test","cwd":"C:\\nonexistent","hook_event_name":"Stop"}' | node "$r\scripts\run.cjs" "$r\scripts\persistent-mode.mjs"; "exit=$LASTEXITCODE"
174
217
  Remove-Item Env:CLAUDE_PLUGIN_ROOT, Env:OMC_NOTIFY
@@ -185,7 +228,7 @@ node "<이 스킬 디렉터리 절대경로>/scripts/omc-patch.mjs" --check --no
185
228
 
186
229
  PowerShell:
187
230
  ```powershell
188
- $r = (Get-Content "$env:USERPROFILE\.claude\plugins\installed_plugins.json" -Raw | ConvertFrom-Json).plugins.'oh-my-claudecode@omc'[0].installPath
231
+ $r = (Get-Content "$env:USERPROFILE\.claude\plugins\installed_plugins.json" -Raw -Encoding UTF8 | ConvertFrom-Json).plugins.'oh-my-claudecode@omc'[0].installPath
189
232
  node "<이 스킬 디렉터리 절대경로>\scripts\probe-pm.mjs" --plugin $r
190
233
  ```
191
234
 
@@ -238,15 +281,35 @@ node "<이 스킬 디렉터리 절대경로>/scripts/omc-patch.mjs" --revert
238
281
  - 헬퍼 자리 결함이 남은 채 `revert` 없이 적용을 다시 돌려도 바뀌는 것이 없다. 이미 감싼 파일은 건너뛰기 때문이다.
239
282
  - `--root` 와 `--revert --root` 는 그 폴더만 다룬다. 고정 해제까지 하려면 인자 없는 `--revert` 를 쓴다.
240
283
  - 고정을 풀지 않고 OMC 를 올리려면 먼저 `revert` 한다. 그렇지 않으면 마켓플레이스 fetch 가 계속 실패한다.
241
- - 고정 기록(`PINNED`)은 `~/.claude/omc-local-patches/PINNED` 에 생긴다. 고정한 시각, 판, 기기 이름만 담고 URL 은 담지 않는다. 옛 판의 기록에 URL 칸이 있으면 `--check` 가 알리고 다음 적용 때 지운다. 지우지 못하면 `[!]` 로 알리고 실패로 센다(링크는 따라가 쓰지 않음). 하드 링크로 이어진 다른 이름에는 그 칸이 남는다. 도구는 그 이름을 찾을 번호를 알린다. POSIX 에서는 이 계정만 읽을 수 있다(0600). Windows 에서는 폴더의 상속 권한을 따른다. 기록일 뿐이라 지워도 고정은 풀리지 않고, 이미 고정된 상태에서는 다시 만들어지지 않는다. `revert` 는 origin 값에서만 원래 URL 을 되찾는다. 다른 기기의 기록이면 알리기만 한다.
284
+ - 고정 기록(`PINNED`)은 `~/.claude/omc-local-patches/PINNED` 에 생긴다.
285
+ - 고정한 시각, 패치한 판, 기기 이름만 담고 URL 은 담지 않는다.
286
+ - 옛 판의 기록에 URL 칸이 있으면 `--check` 가 알리고 다음 적용 때 지운다. 지우지 못하면 `[!]` 로 알리고 실패로 센다(링크는 따라가 쓰지 않음).
287
+ - 하드 링크로 이어진 다른 이름에는 그 칸이 남는다. 기록을 읽지 못했거나 JSON 이 아니면 남았을 수 있다고 알린다. 도구는 그 이름을 찾을 번호를 알린다.
288
+ - POSIX 에서는 이 계정만 읽을 수 있다(0600). 기록 폴더는 처음 만들 때 이 계정만 쓰게(0700) 만든다. Windows 에서는 폴더의 상속 권한을 따른다.
289
+ - 기록일 뿐이라 지워도 고정은 풀리지 않고, 이미 고정된 상태에서는 다시 만들어지지 않는다.
290
+ - `revert` 는 origin 값에서만 원래 URL 을 되찾는다. 다른 기기의 기록이면 알리기만 한다.
242
291
  - `--check` 와 `--revert` 를 함께 주면 도구는 아무것도 하지 않고 종료 코드 1 로 멈춘다. 진단만 하려면 `--check` 만 준다.
243
292
  - 도구는 git 을 부를 때 저장소를 고르는 변수(`GIT_DIR`, `GIT_WORK_TREE` 등 `git rev-parse --local-env-vars` 목록)를 지운다. git 훅 안에서 불러도 마켓플레이스 저장소만 다룬다.
244
293
  - 마켓플레이스의 `.git` 이 깨졌으면(중단된 clone 등) git 이 위쪽 저장소로 올라가지 못하게 막는다. 그때 `[4/4]` 는 `origin remote 없음` 으로 끝나고 고정하지 않는다.
245
294
  - `--scan` 은 읽기만 한다. 플러그인 캐시도 `installed_plugins.json` 도 읽지 않고 준 파일만 본다.
246
295
  - 도구는 모르는 인자를 받지 않는다. `--check`, `--revert`, `--no-update`, `--root <폴더>`, `--scan <파일>...`, `--help` 밖의 인자나 `--root=<폴더>` 꼴을 주면 아무것도 하지 않고 사용법과 함께 종료 1 로 멈춘다.
247
- - 출력의 origin URL 에 사용자 이름과 비밀번호가 있으면 `//***@` 로 가린다. 도구는 origin 을 설정 값 그대로 읽어 고정하고 되돌린다(전역 `insteadOf` 를 펼치지 않음).
296
+ - 도구는 출력의 URL 을 `//` 뒤부터 마지막 `@` 까지 `***` 로 가린다. 비밀번호에 `/` `?` `#` 가 들어도 새지 않고, 경로에 `@` 가 든 URL 은 호스트까지 가려진다. 도구는 origin 을 설정 값 그대로 읽어 고정하고 되돌린다(전역 `insteadOf` 를 펼치지 않음).
297
+ - 인자 없는 apply 의 태그 받기는 시간 제한(120초)이 다 되거나 Ctrl+C 를 받으면 git 이 띄운 보조 프로세스까지 함께 끝낸다.
298
+ - 인자 없는 apply 가 태그를 받지 못하면 그 까닭을 한 줄로 알린다. 그 줄과 `--check` 의 origin 줄, `고정 실패`, `고정 해제 실패`, `origin 을 읽지 못함` 줄의 주소는 기본 상류(`https://github.com/Yeachan-Heo/oh-my-claudecode.git`)면 가린 꼴 그대로(쿼리는 이름 없는 토큰까지 통째로 `?***`), 그 밖이면 scheme 과 host 까지만 싣는다. 미러의 경로, 조각, `;` 매개변수에는 토큰이 들 수 있기 때문이다. 기본 상류인지는 호스트 앞의 사용자 정보만 빼고 본다. 경로의 `@` 뒤에 기본 상류를 적은 다른 호스트의 주소와 scheme 이 없는 주소(git 은 `user:<토큰>@github.com/...` 을 host `user` 의 scp 꼴로 읽는다)는 기본 상류가 아니다. git 의 줄에 든 주소는 git 이 찍은 그대로 읽어 꼴을 정하고, 줄의 나머지는 사용자 정보와 쿼리 값을 가린다. 다음 중 하나면 git 의 문구는 싣지 않고 종료 코드만 알린다. 그런 주소에서는 curl 이나 git 의 문구에 사용자 이름과 비밀번호 조각이 따옴표 밖에도 실릴 수 있기 때문이다.
299
+ - origin 에 사용자 정보가 있음
300
+ - git 의 오류 줄에 사용자 정보가 든 주소가 남음(전역 `insteadOf` 가 더한 것. 바른 꼴이면 git 이 줄에서 떼므로 그 줄은 그대로 싣는다)
301
+ - 프록시 주소(`http_proxy` 같은 환경 변수, `http.proxy` 설정)의 사용자 정보에 날 `/` `?` `#` `\` 가 있음
248
302
  - origin 을 바꾸지 못하면(`.git` 쓰기 권한 등) `[!] 고정 실패` 나 `[!] 고정 해제 실패` 와 종료 1 로 끝난다. 그 줄을 그대로 보고한다. 고정 해제에 실패하면 도구가 원래 URL 을 호스트까지만 보인다. 원문은 origin 의 `omc-pinned://` 뒤에 그대로 남는다.
249
- - `--check` 는 쓸 수 없는 파일을 `패치 필요, 쓸 수 없음` 으로 미리 알린다. 폴더 밖 링크 때문에 되돌리지 못한 파일은 손 복구 방법을 함께 낸다. 링크 대상 파일에 이 도구 계열의 패치가 보이지 않으면 덮어쓰라고 하지 않고, 링크가 가리키는 곳이 맞는지 먼저 확인하라고 한다. 사용자 승인 없이 그 방법을 실행하지 않는다.
250
- - 파일은 새 임시 파일을 디스크까지 내린 뒤 rename 으로 바꾼다. 하드 링크로 이어진 폴더 밖 파일은 바뀌지 않는다. 쓰지 못한 파일은 `쓰지 못함` 으로 이름을 대고 실패로 센다.
251
- - 읽을 수 없는 훅(권한, 폴더, FIFO)은 `읽지 못함: <이름> (<코드>)`, 쓸 권한이 없거나 일반 파일이 아닌 훅은 `쓰지 않음: <이름> (쓸 권한이 없음 또는 일반 파일이 아님)` 으로 알리고 실패로 센다. `--check` 는 읽지 못한 훅이 있으면 모두 적용된 상태로 보지 않는다. 설정 파일도 일반 파일만 읽는다. `installed_plugins.json` 이 FIFO 처럼 일반 파일이 아니면 활성 버전이 `(추정: ...)` 으로 나오고, `settings.json` 이면 `DISABLE_AUTOUPDATER` 줄을 생략한다. 도구가 처리하지 못한 오류는 경로를 정화한 한 줄과 종료 1 로 끝난다.
303
+ - `--check` 는 쓸 수 없는 파일을 `패치 필요, 쓸 수 없음` 으로 미리 알린다. 폴더 밖 링크 때문에 되돌리지 못한 파일은 손 복구 방법을 함께 낸다. 링크 대상 파일이 지금 이 도구가 만드는 패치본과 같지 않거나(옛 헬퍼로 패치한 파일도) 읽어 확인할 수 없으면 덮어쓰라고 하지 않고, 링크가 가리키는 곳이 맞는지 먼저 확인하라고 한다. 사용자 승인 없이 그 방법을 실행하지 않는다.
304
+ - 파일은 새 임시 파일을 디스크까지 내린 뒤 rename 으로 바꾼다. 디스크로 내리기를 받지 않는 파일 시스템(fsync 가 `EINVAL`, `ENOTSUP`)에서는 내리지 않고 바꾼다. 하드 링크로 이어진 폴더 밖 파일은 바뀌지 않는다. 쓰지 못한 파일은 `쓰지 못함` 으로 이름을 대고 실패로 센다.
305
+ - 읽을 수 없는 훅과 쓸 수 없는 훅
306
+ - 읽을 수 없는 훅(권한, 폴더, FIFO)은 `읽지 못함: <이름> (<코드>)` 으로 알리고 실패로 센다.
307
+ - 이미 있는 백업(`.omcbak`)을 읽지 못하면 적용은 그 훅을 패치하지 않고 `읽지 못함: <이름> 의 백업 (<코드>)` 으로 알리고 실패로 센다. hooks.json 은 `hooks.json 의 백업을 읽지 못함 (<코드>)` 이다.
308
+ - 쓸 권한이 없거나 일반 파일이 아닌 훅은 `쓰지 않음: <이름> (쓸 권한이 없음 또는 일반 파일이 아님)` 으로 알리고 실패로 센다.
309
+ - `--check` 는 활성 버전의 훅을 읽지 못하면 `결과: 훅 스크립트를 확인하지 못했습니다.` 와 종료 1 로 끝난다. 다시 실행하라고 하지 않는다.
310
+ - 비활성 버전의 훅이나 `hooks.json` 을 읽지 못하면 결과 줄은 바꾸지 않고 `참고:` 줄로 알린다. 종료 코드는 그래도 1 이다.
311
+ - 설정 파일도 일반 파일만 읽는다.
312
+ - `installed_plugins.json` 이 FIFO 처럼 일반 파일이 아니면 활성 버전이 `(추정: ...)` 으로 나온다.
313
+ - `settings.json` 이 그렇다면 `DISABLE_AUTOUPDATER` 줄을 생략한다.
314
+ - 도구가 처리하지 못한 오류는 경로를 정화한 한 줄과 종료 1 로 끝난다.
252
315
  - 도구의 주석과 출력 문구는 한글이다.