@kaydash9999/banker-plugins 0.18.0 โ†’ 0.19.1

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 (35) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +103 -0
  4. package/README.md +18 -7
  5. package/bin/lib/telemetry-config.mjs +25 -0
  6. package/codex/manifest.json +1 -0
  7. package/codex/transform-matrix.md +1 -0
  8. package/hooks/graceful-pause.mjs +4 -2
  9. package/hooks/obsidize-hook.mjs +61 -32
  10. package/hooks/progress-plan.mjs +14 -8
  11. package/hooks/progress.mjs +187 -48
  12. package/hooks/register.mjs +15 -7
  13. package/hooks/telemetry-count-skill.mjs +4 -6
  14. package/hooks/telemetry-count.mjs +4 -6
  15. package/hooks/update-checkin.mjs +46 -43
  16. package/hooks/update-fetch.mjs +8 -7
  17. package/package.json +1 -1
  18. package/skills/anti-drm/SKILL.md +46 -15
  19. package/skills/anti-drm/scripts/anti-drm.mjs +213 -61
  20. package/skills/anti-drm/scripts/instruction-toggle.mjs +280 -44
  21. package/skills/lineage/SKILL.md +4 -1
  22. package/skills/lineage/lineage.py +196 -73
  23. package/skills/omc-patch/SKILL.md +37 -11
  24. package/skills/omc-patch/scripts/omc-patch.mjs +635 -103
  25. package/skills/theme-selection/SKILL.md +165 -0
  26. package/skills/theme-selection/assets/LICENSE +21 -0
  27. package/skills/theme-selection/assets/NOTICE.md +30 -0
  28. package/skills/theme-selection/assets/palettes.json +81 -0
  29. package/skills/theme-selection/references/contrast.md +138 -0
  30. package/skills/theme-selection/references/integrate.md +161 -0
  31. package/skills/theme-selection/scripts/themes.mjs +1067 -0
  32. package/skills/tone-compact/SKILL.md +14 -5
  33. package/skills/tone-compact/scripts/instruction-toggle.mjs +280 -44
  34. package/skills/tone-compact/scripts/tone-compact.mjs +4 -4
  35. package/skills/update-banker/SKILL.md +1 -1
@@ -4,7 +4,7 @@
4
4
  Deterministic core. One-line summary per assistant turn (cached, redacted).
5
5
  Renders assistant markdown, filters harness noise, folds long messages, and
6
6
  self-verifies the output markup. See SKILL.md for the full design and the
7
- `work/lineage-change-request.html` spec for the classification rules.
7
+ `docs/lineage-change-request.html` spec for the classification rules.
8
8
 
9
9
  Runtime-agnostic: works from any transcript via --session / --from-transcript.
10
10
  Auto-discovery assumes Claude Code's ~/.claude/projects/<encoded-cwd>/ layout.
@@ -18,8 +18,10 @@ import math
18
18
  import os
19
19
  import pathlib
20
20
  import re
21
+ import stat
21
22
  import sys
22
23
  import tempfile
24
+ import unicodedata
23
25
  from html.parser import HTMLParser
24
26
 
25
27
  if sys.version_info < (3, 7):
@@ -424,6 +426,96 @@ KNOWN_CONTENT_ITEMS = {"text", "tool_use", "tool_result", "thinking"}
424
426
  _CTRL_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f]") # strip control chars (keeps \t\n)
425
427
 
426
428
 
429
+ def _json_object(raw, line_no):
430
+ """The JSON object a transcript line holds, else None after a WARN: a line that is not JSON, is nested
431
+ deeper than the parser reads, holds a number longer than it reads (ValueError since 3.11), or holds
432
+ something other than an object is skipped, not fatal."""
433
+ try:
434
+ obj = json.loads(raw)
435
+ except (ValueError, RecursionError): # JSONDecodeError is a ValueError
436
+ print(f"[WARN] line {line_no}: invalid JSON skipped", file=sys.stderr)
437
+ return None
438
+ if not isinstance(obj, dict):
439
+ print(f"[WARN] line {line_no}: not a JSON object skipped", file=sys.stderr)
440
+ return None
441
+ return obj
442
+
443
+
444
+ def _is_turn_record(obj, seen_unknown_type, unsafe_schema, line_no):
445
+ """Whether a record is a user or assistant turn. A side chain or a team record is not, nor is another type:
446
+ one nobody knows is named once, and a schema marker (v...) stops the run unless --unsafe-schema. A record
447
+ whose type, or a turn whose message, has a shape a transcript does not give is named and skipped."""
448
+ if obj.get("isSidechain") or obj.get("teamName") or obj.get("agentName"):
449
+ return False
450
+ t = obj.get("type")
451
+ if (t and not isinstance(t, str)) or (t in ("user", "assistant") and _odd_message(obj.get("message"))):
452
+ print(f"[WARN] line {line_no}: record of an unknown shape skipped", file=sys.stderr)
453
+ return False
454
+ if t in ("user", "assistant"):
455
+ return True
456
+ if t and t not in KNOWN_TYPES and t not in seen_unknown_type:
457
+ seen_unknown_type.add(t)
458
+ print(f"[WARN] unknown record type {t!r} skipped", file=sys.stderr) # repr: no raw control character
459
+ if not unsafe_schema and t.startswith("v"):
460
+ print(f"[ERROR] schema marker {t!r} unrecognized โ€” "
461
+ "rerun with --unsafe-schema to proceed",
462
+ file=sys.stderr)
463
+ raise SystemExit(2)
464
+ return False
465
+
466
+
467
+ def _stamp(ts):
468
+ """A record's timestamp when it is text, else None: a number or another shape reads as a missing one."""
469
+ return ts if isinstance(ts, str) else None
470
+
471
+
472
+ def _odd_message(msg):
473
+ """Whether a turn's message has a shape a transcript does not give (a missing or empty one reads as empty)."""
474
+ return bool(msg) and not isinstance(msg, dict)
475
+
476
+
477
+ def _odd_item(it, text, name):
478
+ """Whether a content item's type, text or tool name has a shape a transcript does not give. A missing or empty
479
+ text reads as empty and a missing or null tool name as '?'."""
480
+ return ((it is not None and not isinstance(it, str)) or (it == "text" and not isinstance(text, str))
481
+ or (it == "tool_use" and not isinstance(name, str)))
482
+
483
+
484
+ def _take_item(item, line_no, text_parts, tools):
485
+ """Add one item of a message's content list to the text parts or the tool counts. An item whose type, text or
486
+ tool name has a shape a transcript does not give is named and skipped."""
487
+ if not isinstance(item, dict):
488
+ return
489
+ it, text, name = item.get("type"), item.get("text") or "", item.get("name")
490
+ name = "?" if name is None else name
491
+ if _odd_item(it, text, name):
492
+ print(f"[WARN] line {line_no}: content item of an unknown shape skipped", file=sys.stderr)
493
+ elif it == "text":
494
+ text_parts.append(text)
495
+ elif it == "tool_use":
496
+ tools[name] = tools.get(name, 0) + 1
497
+ elif it not in KNOWN_CONTENT_ITEMS:
498
+ print(f"[WARN] line {line_no}: unknown content type "
499
+ f"{it!r} skipped", file=sys.stderr)
500
+
501
+
502
+ def _content_parts(content, line_no):
503
+ """(text parts, tool counts) of a message's content, or None for a shape nobody knows (named in a WARN
504
+ unless the content is missing)."""
505
+ text_parts, tools = [], {}
506
+ if isinstance(content, str):
507
+ text_parts.append(content)
508
+ elif isinstance(content, list):
509
+ for item in content:
510
+ _take_item(item, line_no, text_parts, tools)
511
+ else:
512
+ if content is not None:
513
+ print(f"[WARN] line {line_no}: unknown content shape "
514
+ f"({type(content).__name__})", file=sys.stderr)
515
+ return None
516
+ return text_parts, tools
517
+
518
+
427
519
  def parse_turns(stream, unsafe_schema: bool = False, session_id=None,
428
520
  session_name=None):
429
521
  """Yield dicts: {role, text, ts, uuid, tools, line_no, parts, session,
@@ -438,59 +530,18 @@ def parse_turns(stream, unsafe_schema: bool = False, session_id=None,
438
530
  raw = raw.strip()
439
531
  if not raw:
440
532
  continue
441
- try:
442
- obj = json.loads(raw)
443
- except json.JSONDecodeError:
444
- print(f"[WARN] line {line_no}: invalid JSON skipped", file=sys.stderr)
445
- continue
446
- if obj.get("isSidechain"):
447
- continue
448
- if obj.get("teamName") or obj.get("agentName"):
533
+ obj = _json_object(raw, line_no)
534
+ if obj is None or not _is_turn_record(obj, seen_unknown_type, unsafe_schema, line_no):
449
535
  continue
450
- t = obj.get("type")
451
- if t not in ("user", "assistant"):
452
- if t and t not in KNOWN_TYPES and t not in seen_unknown_type:
453
- seen_unknown_type.add(t)
454
- print(f"[WARN] unknown record type '{t}' skipped", file=sys.stderr)
455
- if not unsafe_schema and t and t.startswith("v"):
456
- print(f"[ERROR] schema marker '{t}' unrecognized โ€” "
457
- "rerun with --unsafe-schema to proceed",
458
- file=sys.stderr)
459
- raise SystemExit(2)
460
- continue
461
-
462
536
  msg = obj.get("message") or {}
463
- role = msg.get("role") or t
464
- content = msg.get("content")
465
- ts = obj.get("timestamp")
537
+ role = msg.get("role") or obj.get("type")
538
+ ts = _stamp(obj.get("timestamp"))
466
539
  uuid_ = obj.get("uuid") or hashlib.sha256(
467
540
  f"{line_no}:{ts}".encode()).hexdigest()[:16]
468
-
469
- text_parts = []
470
- tools = {}
471
- if isinstance(content, str):
472
- text_parts.append(content)
473
- elif isinstance(content, list):
474
- for item in content:
475
- if not isinstance(item, dict):
476
- continue
477
- it = item.get("type")
478
- if it == "text":
479
- text_parts.append(item.get("text") or "")
480
- elif it == "tool_use":
481
- tools[item.get("name", "?")] = tools.get(
482
- item.get("name", "?"), 0) + 1
483
- elif it in ("tool_result", "thinking"):
484
- continue
485
- elif it not in KNOWN_CONTENT_ITEMS:
486
- print(f"[WARN] line {line_no}: unknown content type "
487
- f"'{it}' skipped", file=sys.stderr)
488
- else:
489
- if content is not None:
490
- print(f"[WARN] line {line_no}: unknown content shape "
491
- f"({type(content).__name__})", file=sys.stderr)
541
+ got = _content_parts(msg.get("content"), line_no)
542
+ if got is None:
492
543
  continue
493
-
544
+ text_parts, tools = got
494
545
  text = _CTRL_RE.sub("", "\n".join(p for p in text_parts if p).strip())
495
546
  if not text and not tools:
496
547
  continue
@@ -692,18 +743,16 @@ def _stdin_id_key():
692
743
  to a model that never sees the key, so an id gives no way to test a guessed password. With no
693
744
  cache folder to keep it in, the key lasts one run."""
694
745
  path = CACHE_BASE / "stdin-id.key"
695
- try:
696
- key = path.read_bytes()
697
- if len(key) == 32:
698
- return key
699
- except OSError:
700
- pass
746
+ key = _read_small(path, 64)
747
+ if key is not None and len(key) == 32:
748
+ return key
701
749
  key = os.urandom(32)
702
750
  tmp = path.with_name("%s.%d" % (path.name, os.getpid()))
703
751
  try:
704
752
  CACHE_BASE.mkdir(parents=True, exist_ok=True)
705
753
  os.chmod(str(CACHE_BASE), 0o700)
706
- fd = os.open(str(tmp), os.O_WRONLY | os.O_CREAT | os.O_TRUNC | getattr(os, "O_BINARY", 0), 0o600)
754
+ # O_EXCL: a name that is already there (a link planted at this guessable name among them) is refused
755
+ fd = os.open(str(tmp), os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0), 0o600)
707
756
  with os.fdopen(fd, "wb") as f:
708
757
  f.write(key)
709
758
  os.replace(str(tmp), str(path)) # whole or not at all
@@ -734,14 +783,72 @@ def _page_clean_turn(t, extra, mode):
734
783
  return dict(t, text=clean(t["text"]), parts=[clean(p) for p in t.get("parts") or []])
735
784
 
736
785
 
786
+ # Letters that print as nothing: the Hangul fillers, the braille blank, and the combining marks Unicode ignores by
787
+ # default (the grapheme joiner, the Khmer inherent vowels, the Mongolian free variation selectors). With the control,
788
+ # format, private-use, unassigned and separator categories, a cache made only of them holds no summary a person can
789
+ # read (3.0.5).
790
+ _BLANK_LETTERS = frozenset(map(chr, (0x115F, 0x1160, 0x3164, 0xFFA0, 0x2800,
791
+ 0x034F, 0x17B4, 0x17B5, 0x180B, 0x180C, 0x180D, 0x180F)))
792
+ # Cn follows the Unicode table of the Python that runs: a character newer than that table reads as unassigned, so
793
+ # a cache made only of such characters is summarized again every run (the page is the same).
794
+ _BLANK_CATEGORIES = frozenset(("Cc", "Cf", "Co", "Cn", "Zs", "Zl", "Zp"))
795
+
796
+
797
+ def _blank(ch):
798
+ """Whether ch prints as nothing: whitespace, a blank-looking letter, or a variation selector.
799
+ White space is in the blank categories too (Zs, Zl, Zp, Cc); str.isspace() only answers it sooner."""
800
+ if ch.isspace() or ch in _BLANK_LETTERS or unicodedata.category(ch) in _BLANK_CATEGORIES:
801
+ return True
802
+ o = ord(ch)
803
+ return 0xFE00 <= o <= 0xFE0F or 0xE0100 <= o <= 0xE01EF
804
+
805
+
806
+ def _has_visible(text):
807
+ """Whether text holds a letter a person can see."""
808
+ return not all(_blank(ch) for ch in text)
809
+
810
+
811
+ CACHE_READ_MAX = 64 * 1024 # bytes: a cached summary or decision is some hundred (120 characters), so this is generous
812
+
813
+
814
+ def _read_small(path, limit=CACHE_READ_MAX):
815
+ """The bytes of the regular file at path when it holds at most `limit`, else None (missing, a link, a pipe, a
816
+ folder, a larger file). Opened without blocking and without following a link, so a pipe or a link left at a
817
+ cache place neither stops a run nor sends it to another file. Windows has neither flag: there the lstat
818
+ turns a link away, but a link swapped in between the lstat and the open is followed."""
819
+ flags = (os.O_RDONLY | getattr(os, "O_NONBLOCK", 0) | getattr(os, "O_NOFOLLOW", 0)
820
+ | getattr(os, "O_BINARY", 0))
821
+ try:
822
+ if not stat.S_ISREG(os.lstat(str(path)).st_mode): # a link (Windows has no O_NOFOLLOW), pipe, folder
823
+ return None
824
+ fd = os.open(str(path), flags)
825
+ with os.fdopen(fd, "rb") as f:
826
+ if not stat.S_ISREG(os.fstat(f.fileno()).st_mode):
827
+ return None
828
+ data = f.read(limit + 1)
829
+ except OSError:
830
+ return None
831
+ return data if len(data) <= limit else None
832
+
833
+
834
+ def _read_text(path):
835
+ """The UTF-8 text _read_small gives, else None (bytes that are not UTF-8: a DRM encrypted them)."""
836
+ data = _read_small(path)
837
+ try:
838
+ return None if data is None else data.decode("utf-8")
839
+ except UnicodeDecodeError:
840
+ return None
841
+
842
+
737
843
  def _read_cached(p):
738
844
  """The summary cached at p, else at the same name with .txt, which 3.0.2 and earlier wrote.
739
- None when neither can be read: missing, or bytes that are not UTF-8 (a DRM encrypted them)."""
845
+ None when neither holds one: missing, bytes that are not UTF-8 (a DRM encrypted them), or
846
+ nothing a person can see (a write that stopped early, or only blank-looking letters); the
847
+ turn is then summarized again (3.0.4, 3.0.5)."""
740
848
  for cached in (p, p.with_name(p.stem + ".txt")):
741
- try:
742
- return cached.read_text(encoding="utf-8").strip()
743
- except (OSError, UnicodeDecodeError):
744
- continue
849
+ text = (_read_text(cached) or "").strip()
850
+ if _has_visible(text):
851
+ return text
745
852
  return None
746
853
 
747
854
 
@@ -766,9 +873,7 @@ def read_or_summarize(turn: dict, session_id: str, rebuild: bool = False,
766
873
  redacted = summarize_turn(_page_clean_turn(turn, redact_extra, redact_mode))
767
874
  if p is not None:
768
875
  try:
769
- fd = os.open(str(p), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
770
- with os.fdopen(fd, "w", encoding="utf-8") as f:
771
- f.write(redacted)
876
+ _write_private(p, redacted)
772
877
  except OSError:
773
878
  pass
774
879
  return redacted, False
@@ -1371,7 +1476,7 @@ def _write_private(path, text):
1371
1476
  path.parent.mkdir(parents=True, exist_ok=True)
1372
1477
  fd, tmp = tempfile.mkstemp(dir=str(path.parent), prefix=f".{path.name}.", suffix=".tmp")
1373
1478
  try:
1374
- with os.fdopen(fd, "w", encoding="utf-8") as f:
1479
+ with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f:
1375
1480
  f.write(text)
1376
1481
  os.replace(tmp, str(path))
1377
1482
  except BaseException:
@@ -1382,6 +1487,13 @@ def _write_private(path, text):
1382
1487
  raise
1383
1488
 
1384
1489
 
1490
+ def _write_text_lf(path, text):
1491
+ """Write `text` to `path` as UTF-8 with LF line ends on every OS. Text mode on Windows
1492
+ would write CRLF, so the same transcript would give a different page there."""
1493
+ with open(str(path), "w", encoding="utf-8", newline="\n") as f:
1494
+ f.write(text)
1495
+
1496
+
1385
1497
  def _safe_name(s):
1386
1498
  """A file-name-safe form of an id read from a transcript or a pack."""
1387
1499
  s = re.sub(r"[^A-Za-z0-9_.-]", "_", str(s or "default"))[:80]
@@ -1417,8 +1529,8 @@ def read_llm_cache(turn_id, session, digest):
1417
1529
  "every turn is reviewed")
1418
1530
  return None
1419
1531
  try:
1420
- got = json.loads(path.read_text(encoding="utf-8"))
1421
- except (OSError, ValueError, UnicodeDecodeError):
1532
+ got = json.loads(_read_text(path) or "")
1533
+ except (ValueError, RecursionError):
1422
1534
  return None
1423
1535
  if not isinstance(got, dict):
1424
1536
  return None
@@ -1700,7 +1812,7 @@ def _is_pack_turn(x):
1700
1812
  def _load_pack(path):
1701
1813
  try:
1702
1814
  pack = json.loads(path.read_text(encoding="utf-8"))
1703
- except (OSError, ValueError, UnicodeDecodeError) as e:
1815
+ except (OSError, ValueError, UnicodeDecodeError, RecursionError) as e:
1704
1816
  return _bad(f"cannot read review pack {path}: {e}")
1705
1817
  if not isinstance(pack, dict) or pack.get("schema") != REVIEW_SCHEMA:
1706
1818
  return _bad(f"{path} is not a {REVIEW_SCHEMA} pack (made by another lineage version? "
@@ -1819,7 +1931,8 @@ def _clean_summary(s, extra, mode):
1819
1931
  next reviewer and the gate's critic, so no part of a key may stay in it."""
1820
1932
  pre, n = review_redact(" ".join(s.split()), extra)
1821
1933
  red, found = redact(pre, extra=extra, mode=mode)
1822
- return (_cut(red, SUMMARY_MAX) or None), n + sum(found.values())
1934
+ cut = _cut(red, SUMMARY_MAX)
1935
+ return (cut if _has_visible(cut) else None), n + sum(found.values())
1823
1936
 
1824
1937
 
1825
1938
  def _warn_coverage(pack, decided):
@@ -2304,6 +2417,16 @@ def default_output(args, jsonl_path, session_id):
2304
2417
  return f"work/lineage-{slug}.html"
2305
2418
 
2306
2419
 
2420
+ # The controls that embed, override or isolate text direction (U+202A..U+202E, U+2066..U+2069). In a page they
2421
+ # reorder what a reader sees and can reach past the element that holds them, so the page shows each as a visible
2422
+ # [U+XXXX] instead. Plain letters of right-to-left scripts, and the direction marks, stay.
2423
+ _BIDI_CONTROLS = re.compile("[" + "".join(map(chr, (*range(0x202A, 0x202F), *range(0x2066, 0x206A)))) + "]")
2424
+
2425
+
2426
+ def _show_bidi_controls(text):
2427
+ return _BIDI_CONTROLS.sub(lambda m: "[U+%04X]" % ord(m.group()), text)
2428
+
2429
+
2307
2430
  def render_and_write(turns, session_id, args, all_sessions=False, markdown=True,
2308
2431
  open_details=False, title="Session Lineage", summaries=None,
2309
2432
  prior_redactions=None):
@@ -2333,8 +2456,8 @@ def render_and_write(turns, session_id, args, all_sessions=False, markdown=True,
2333
2456
  html_doc = HTML_TEMPLATE
2334
2457
  html_doc = html_doc.replace("{{TITLE}}", html.escape(title), 1)
2335
2458
  html_doc = html_doc.replace("{{HEADER_TITLE}}", html.escape(title), 1)
2336
- html_doc = html_doc.replace("{{DATE_RANGE}}", html.escape(date_range), 1)
2337
- html_doc = html_doc.replace("{{TURNS}}", "\n".join(rows), 1)
2459
+ html_doc = html_doc.replace("{{DATE_RANGE}}", html.escape(_show_bidi_controls(date_range)), 1)
2460
+ html_doc = html_doc.replace("{{TURNS}}", _show_bidi_controls("\n".join(rows)), 1)
2338
2461
 
2339
2462
  errs = self_verify(html_doc)
2340
2463
  if errs:
@@ -2342,7 +2465,7 @@ def render_and_write(turns, session_id, args, all_sessions=False, markdown=True,
2342
2465
 
2343
2466
  out = _with_timestamp_suffix(args.output)
2344
2467
  out.parent.mkdir(parents=True, exist_ok=True)
2345
- out.write_text(html_doc, encoding="utf-8")
2468
+ _write_text_lf(out, html_doc)
2346
2469
 
2347
2470
  _run_reviewer_gate(turns, out, session_id, args, summaries=summaries)
2348
2471
 
@@ -2586,7 +2709,7 @@ def _check_coverage(verdict, samples):
2586
2709
  """Exit 2 unless the verdict answers each of the samples in file `samples`."""
2587
2710
  try:
2588
2711
  gaps = _verdict_gaps(verdict, json.loads(samples.read_text(encoding="utf-8")))
2589
- except (OSError, ValueError, UnicodeDecodeError, AttributeError) as e:
2712
+ except (OSError, ValueError, UnicodeDecodeError, AttributeError, RecursionError) as e:
2590
2713
  gaps = f"cannot read the samples: {e}"
2591
2714
  if gaps:
2592
2715
  print(f"[lineage] ERROR: the verdict does not answer {samples} ({gaps}); have the "
@@ -41,11 +41,17 @@ OMC(oh-my-claudecode)๊ฐ€ ์ƒˆ ๋ฒ„์ „์œผ๋กœ ์ž๋™ ์—…๋ฐ์ดํŠธ๋˜๋ฉด ๋กœ์ปฌ ํ›…
41
41
  - `--root` ๋Š” ํด๋”์˜ ์ด๋ฆ„ํ‘œ๊ฐ€ OMC ๊ฐ€ ์•„๋‹ˆ๋ฉด `OMC ํ”Œ๋Ÿฌ๊ทธ์ธ ํด๋”๊ฐ€ ์•„๋‹˜` ์œผ๋กœ ๋ฉˆ์ถ˜๋‹ค. `.claude-plugin/plugin.json` ์˜ name ์ด ์žˆ์œผ๋ฉด ๊ทธ๊ฒƒ์œผ๋กœ, ์—†์œผ๋ฉด `package.json` ์˜ name ์œผ๋กœ ์ •ํ•œ๋‹ค. ๋‹ค๋ฅธ ํด๋”๋ฅผ ์ž˜๋ชป ์ค€ ์‹ค์ˆ˜๋ฅผ ๋ง‰๋Š” ํ™•์ธ์ด๊ณ , ์ด๋ฆ„ํ‘œ๊ฐ€ ์—†๋Š” ํด๋”๋Š” ํ†ต๊ณผํ•œ๋‹ค.
42
42
  - ์“ธ ํŒŒ์ผ์ด ๋งํฌ๋กœ ํ”Œ๋Ÿฌ๊ทธ์ธ ํด๋” ๋ฐ–์„ ๊ฐ€๋ฆฌํ‚ค๊ฑฐ๋‚˜ ๋ฐฑ์—… ์ž๋ฆฌ(`.omcbak`)๊ฐ€ ๋งํฌ๋ฉด ๋„๊ตฌ๋Š” ์“ฐ์ง€ ์•Š๊ณ  ์‹คํŒจ๋กœ ์„ผ๋‹ค. ๋ฌธ๊ตฌ๋Š” ์ ์šฉ ๋•Œ `์“ฐ์ง€ ์•Š์Œ`, ๋˜๋Œ๋ฆฌ๊ธฐ ๋•Œ `๋˜๋Œ๋ฆฌ์ง€ ์•Š์Œ`, hooks.json ์€ `hooks.json ์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Œ` ์ด๋‹ค.
43
43
  - ํŒจ์น˜ ๊ทœ์น™์€ OMC ํ›… ์ฝ”๋“œ์˜ ๋ชจ์–‘์— ๋งž์ถฐ์ ธ ์žˆ๋‹ค. ์ฒ˜์Œ ๋ณด๋Š” OMC ๋ฒ„์ „์ด๋ฉด ๋ฐ˜๋“œ์‹œ `check` ๋ฅผ ๋จผ์ € ์‹คํ–‰ํ•œ๋‹ค.
44
- - `์‹คํŒจ N๊ฑด` ์ด 1๊ฑด ์ด์ƒ์ด๋ฉด ๋ฉˆ์ถ”๊ณ  ์ถœ๋ ฅ์„ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ ํ•œ๋‹ค. ๋ฌธ๋ฒ• ์˜ค๋ฅ˜๊ฐ€ ๋‚œ ํŒŒ์ผ์€ ๋„๊ตฌ๊ฐ€ ์›๋ณธ์œผ๋กœ ์ž๋™ ๋กค๋ฐฑํ•œ๋‹ค.
44
+ - `์‹คํŒจ N๊ฑด` ์ด 1๊ฑด ์ด์ƒ์ด๋ฉด ๋ฉˆ์ถ”๊ณ  ์ถœ๋ ฅ์„ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ ํ•œ๋‹ค. ์˜ˆ์™ธ๋Š” 2๋‹จ๊ณ„์—์„œ ๋‚จ๊ธฐ๊ธฐ๋กœ ํ•œ `PINNED` ์ค„ ํ•˜๋‚˜๋ฟ์ด๋‹ค(3๋‹จ๊ณ„ 2). ๋ฌธ๋ฒ• ์˜ค๋ฅ˜๊ฐ€ ๋‚œ ํŒŒ์ผ์€ ๋„๊ตฌ๊ฐ€ ์›๋ณธ์œผ๋กœ ์ž๋™ ๋กค๋ฐฑํ•œ๋‹ค. ๊ณ ์นœ hooks.json ์ด ๋‹ค์‹œ ์ฝ์–ด JSON ์ด ์•„๋‹ˆ์–ด๋„ ์›๋ณธ์œผ๋กœ ๋˜๋Œ๋ฆฌ๊ณ (`hooks.json ์†์ƒ์œผ๋กœ ๋กค๋ฐฑ`) ์‹คํŒจ๋กœ ์„ผ๋‹ค.
45
45
  - ํŒ์ •์€ ์ถœ๋ ฅ ๋ฌธ๊ตฌ๋กœ ํ•œ๋‹ค. ์ข…๋ฃŒ ์ฝ”๋“œ 0 ๋งŒ์œผ๋กœ ์™„๋ฃŒ๋ผ๊ณ  ๋ณด๊ณ ํ•˜์ง€ ์•Š๋Š”๋‹ค.
46
46
  - ์บ์‹œ์—๋Š” ์ •์…˜์ด๋‚˜ ์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋กœ ์ด์–ด์ง„ ๋ฒ„์ „ ํด๋”๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค(์˜ˆ: ์˜› ๋ฒ„์ „ ํด๋”๊ฐ€ ์ƒˆ ๋ฒ„์ „์„ ๊ฐ€๋ฆฌํ‚ด). ์ด๋•Œ ํ•œ ๋ฒˆ์˜ ๋ณ€๊ฒฝ์ด ๋‘ ๋ฒ„์ „ ํด๋”์— ํ•จ๊ป˜ ๋ณด์ธ๋‹ค. ์ •์ƒ์ด๋‹ค.
47
47
  - ๋„๊ตฌ๋Š” `os.homedir()/.claude` ๋ฅผ ์“ด๋‹ค. `CLAUDE_CONFIG_DIR` ๋กœ ์„ค์ • ํด๋”๋ฅผ ์˜ฎ๊ฒผ๋‹ค๋ฉด ์ด ๋„๊ตฌ๋Š” ๊ทธ ํด๋”๋ฅผ ๋ณด์ง€ ์•Š๋Š”๋‹ค.
48
- - ์ถœ๋ ฅ์˜ origin ์— `https://์‚ฌ์šฉ์ž:ํ† ํฐ@...` ์ฒ˜๋Ÿผ ์ž๊ฒฉ์ฆ๋ช…์ด ๋“ค์–ด ์žˆ์œผ๋ฉด, ๋ณด๊ณ ํ•  ๋•Œ `//***@` ๋กœ ๊ฐ€๋ฆฐ๋‹ค. ์ถœ๋ ฅ์„ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ ํ•˜๋ผ๋Š” ๋‹จ๊ณ„์—์„œ๋„ ์ด ๊ฐ€๋ฆผ์€ ์ง€ํ‚จ๋‹ค. ๊ทธ URL ์€ `PINNED` ์—๋„ ํ‰๋ฌธ์œผ๋กœ ๋‚จ์œผ๋‹ˆ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆฐ๋‹ค.
48
+ - ์ถœ๋ ฅ์˜ origin ์— `https://์‚ฌ์šฉ์ž:ํ† ํฐ@...` ์ฒ˜๋Ÿผ ์ž๊ฒฉ์ฆ๋ช…์ด ๋“ค์–ด ์žˆ์œผ๋ฉด, ๋ณด๊ณ ํ•  ๋•Œ `//***@` ๋กœ ๊ฐ€๋ฆฐ๋‹ค. ์ถœ๋ ฅ์„ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ ํ•˜๋ผ๋Š” ๋‹จ๊ณ„์—์„œ๋„ ์ด ๊ฐ€๋ฆผ์€ ์ง€ํ‚จ๋‹ค. ์˜› ํŒ์˜ ๋„๊ตฌ๊ฐ€ ๋งŒ๋“  `PINNED` ์—๋Š” ๊ทธ URL ์ด ๋‚จ์•„ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. `--check` ๋Š” ๊ทธ ์นธ์„ `[!] ๊ณ ์ • ๊ธฐ๋ก(PINNED)์— ์˜› URL ์นธ์ด ๋‚จ์•„ ์žˆ์Œ (์ ์šฉํ•˜๋ฉด ์ง€์›€)` ์œผ๋กœ ์•Œ๋ฆฌ๊ณ  ๋ชจ๋‘ ์ ์šฉ๋œ ์ƒํƒœ๋กœ ๋ณด์ง€ ์•Š๋Š”๋‹ค. ์ ์šฉ(`--check` ๊ฐ€ ์•„๋‹Œ ์‹คํ–‰)์€ ๊ทธ ์นธ์„ ์ง€์šฐ๊ณ  ๊ถŒํ•œ์„ 0600 ์œผ๋กœ ๋งž์ถ˜๋‹ค(POSIX. Windows ๋Š” chmod ๊ฐ€ ์“ฐ๊ธฐ ๋น„ํŠธ๋งŒ ๋‹ค๋ค„ ํด๋”์˜ ์ƒ์† ๊ถŒํ•œ์„ ๋”ฐ๋ฅธ๋‹ค). ์ด ๋„๊ตฌ๊ฐ€ ๋‹ค ์ง€์šธ ์ˆ˜ ์—†์œผ๋ฉด(์นธ์ด ๋“  ๊ธฐ๋ก์ด ๋งํฌ, JSON ์ด ์•„๋‹˜, ํด๋”์— ์“ฐ์ง€ ๋ชปํ•จ, ์ฝ์ง€ ๋ชปํ•จ, ๊ฐ™์€ ํŒŒ์ผ์˜ ๋‹ค๋ฅธ ์ด๋ฆ„์ด ์žˆ์Œ) `[!] ๊ณ ์ • ๊ธฐ๋ก(PINNED)์˜ ์˜› URL ์นธ์„ ...` ์ค„๋กœ ์•Œ๋ฆฌ๊ณ  ์ ์šฉ์€ ์‹คํŒจ๋กœ ์„ผ๋‹ค(์ข…๋ฃŒ 1). ๋‹ค๋ฅธ ์ด๋ฆ„์ด ์žˆ์œผ๋ฉด ์ ์šฉ์€ ์ด ์ด๋ฆ„์—์„œ๋งŒ ์ง€์šฐ๊ณ  ๋‹ค๋ฅธ ์ด๋ฆ„์—๋Š” ๋‚จ๊ธด๋‹ค. ๋งํฌ๋Š” ๋”ฐ๋ผ๊ฐ€ ์“ฐ์ง€ ์•Š์œผ๋ฉฐ, ๋งํฌ ๋„ˆ๋จธ์— ๊ทธ ์นธ์ด ์—†์œผ๋ฉด ์•Œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ๋˜๋Œ๋ฆฌ๊ธฐ๋Š” `PINNED` ๋ฅผ ์ง€์šด๋‹ค. ๋งํฌ ๋„ˆ๋จธ์— ๊ทธ ์นธ์ด ์žˆ๊ฑฐ๋‚˜ ๋งํฌ ๋„ˆ๋จธ๋ฅผ ์ฝ์ง€ ๋ชปํ•˜๊ฑฐ๋‚˜, ์ง€์šฐ์ง€ ๋ชปํ•˜๊ฑฐ๋‚˜, ๊ฐ™์€ ํŒŒ์ผ์˜ ๋‹ค๋ฅธ ์ด๋ฆ„์ด ์žˆ์„ ๋•Œ๋งŒ ์‹คํŒจ๋กœ ์„ธ๊ณ , ๋งํฌ๊ฐ€ ์•„๋‹Œ ๊ธฐ๋ก์€ JSON ์ด ์•„๋‹ˆ๊ฑฐ๋‚˜ ์ฝ์ง€ ๋ชปํ•ด๋„ ๊ทธ๋ƒฅ ์ง€์šด๋‹ค. `PINNED` ๋Š” ๊ธฐ๋ก์ผ ๋ฟ์ด๋ผ ์ง€์›Œ๋„ ๊ณ ์ •๊ณผ ๋˜๋Œ๋ฆฌ๊ธฐ์— ์˜ํ–ฅ์ด ์—†๋‹ค. ์•Œ๋ฆฐ ์ค„์€ ์•„๋ž˜ `PINNED` ์ค„ ๋‹ค๋ฃจ๊ธฐ๋Œ€๋กœ ๋ฌป๋Š”๋‹ค. ๊ธฐ๋ก ํŒŒ์ผ์˜ ๋‚ด์šฉ์€ ์ฝ์–ด ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค(์ธ์ฆ ์ •๋ณด๊ฐ€ ๋“ค ์ˆ˜ ์žˆ์Œ).
49
+ - `PINNED` ์ค„ ๋‹ค๋ฃจ๊ธฐ. `[!] ๊ณ ์ • ๊ธฐ๋ก(PINNED)์˜ ์˜› URL ์นธ์„ ...` ์ค„์ด ๋‚˜์˜ค๋ฉด ๊ทธ ์ค„์˜ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์ด๊ณ  ๊ทธ ๊ธฐ๋ก์„ ์ง€์šธ์ง€ ๋‚จ๊ธธ์ง€ ๋ฌป๋Š”๋‹ค. `--check` ์—์„œ ๋‚˜์˜จ ์ค„์€ 2๋‹จ๊ณ„ ์Šน์ธ ์งˆ๋ฌธ์—์„œ ํ•จ๊ป˜ ๋ฌป๋Š”๋‹ค. ์ ์šฉ์ด๋‚˜ ๋˜๋Œ๋ฆฌ๊ธฐ์—์„œ ์ฒ˜์Œ ๋‚˜์˜จ ์ค„์€ ๋ฉˆ์ถ”๊ณ  ๋ณด๊ณ ํ•œ ๋’ค ๋ฌป๋Š”๋‹ค. ์ด๋•Œ๋„ ๊ธฐ๋ก์˜ ๋‚ด์šฉ์€ ์ฝ์ง€ ์•Š๊ณ  ๊ฒฝ๋กœ๋งŒ ๋ณด์ธ๋‹ค.
50
+ - ํด๋” ๊ถŒํ•œ์ด ์›์ธ(`์“ฐ์ง€ ๋ชปํ•จ`)์ด๋ฉด ๊ถŒํ•œ์„ ๊ณ ์น ์ง€๋„ ๋ฌป๋Š”๋‹ค.
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` ๊ธฐ๋ก์ด ์•„๋‹ˆ๋ผ๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ตํ•˜๋ฉด ๊ทธ ํŒŒ์ผ์€ ์ง€์šฐ์ง€ ์•Š๊ณ  ๋งํฌ๋งŒ ์ง€์šธ์ง€ ๋ฌป๋Š”๋‹ค.
54
+ - ๋‚จ๊ธฐ๊ธฐ๋กœ ํ•˜๋ฉด ๊ทธ ์ค„์€ ์˜ˆ์ƒ๋œ ์‹คํŒจ๋‹ค. ์ ์šฉ์—์„œ๋Š” 3๋‹จ๊ณ„ 2 ์˜ ์˜ˆ์™ธ, ๊ฒ€์ฆ์—์„œ๋Š” 4๋‹จ๊ณ„ 1 ์˜ ์˜ˆ์™ธ๋ฅผ ๋”ฐ๋ฅธ๋‹ค. ๋‹ค๋ฅธ ์ด๋ฆ„์ด ์žˆ๋Š” ๊ธฐ๋ก์„ ๋‚จ๊ธฐ๊ณ  ์ ์šฉํ•˜๋ฉด ์ ์šฉ์€ `PINNED` ์˜ ์นธ๋งŒ ์ง€์šด๋‹ค. ๊ทธ ๋’ค `--check` ๋Š” ๋‹ค๋ฅธ ์ด๋ฆ„์„ ๋ณด์ง€ ๋ชปํ•˜๋‹ˆ 5๋‹จ๊ณ„ ๋ณด๊ณ ์— ๊ทธ ๊ฒฝ๋กœ๋ฅผ ์ ๋Š”๋‹ค.
49
55
 
50
56
  ## ์ ˆ์ฐจ
51
57
 
@@ -70,23 +76,30 @@ node "<์ด ์Šคํ‚ฌ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ ˆ๋Œ€๊ฒฝ๋กœ>/scripts/omc-patch.mjs" --check --no
70
76
 
71
77
  - `ํ™œ์„ฑ ๋ฒ„์ „: X (installed_plugins.json)` ์ค„๋กœ ์‹ค์ œ ํ™œ์„ฑ ๋ฒ„์ „์„ ํ™•์ธํ•œ๋‹ค.
72
78
  - ๊ทธ ์ค„์ด `(์ถ”์ •: ...)` ๋กœ ๋๋‚˜๋ฉด ๋„๊ตฌ๊ฐ€ `installed_plugins.json` ์—์„œ ํ™œ์„ฑ ๋ฒ„์ „์„ ์ฐพ์ง€ ๋ชปํ•œ ๊ฒƒ์ด๋‹ค. ์ ์šฉํ•˜์ง€ ์•Š๊ณ  ์ถœ๋ ฅ์„ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ ํ•˜๊ณ  ๋๋‚ธ๋‹ค. ์ถ”์ •ํ•œ ํด๋”๋ฅผ ํŒจ์น˜ํ•˜๋ฉด ์‹ค์ œ ํ™œ์„ฑ ๋ฒ„์ „์€ ๊ทธ๋Œ€๋กœ ๋‚จ๋Š”๋ฐ `check` ๋Š” ์ ์šฉ๋œ ๊ฒƒ์œผ๋กœ ๋ณด์ผ ์ˆ˜ ์žˆ๋‹ค.
73
- - `ํŒจ์น˜ ํ•„์š” N๊ฑด` ์€ ์„ค์น˜๋œ ๋ชจ๋“  ๋ฒ„์ „ ํด๋”๋ฅผ ํ•ฉ์นœ ์ˆ˜๋‹ค. ํŒ์ •์€ ๋งจ ์•„๋ž˜ `๊ฒฐ๊ณผ:` ์ค„๋กœ ํ•œ๋‹ค.
79
+ - `ํŒจ์น˜ ํ•„์š” N๊ฑด` ์€ ์„ค์น˜๋œ ๋ชจ๋“  ๋ฒ„์ „ ํด๋”๋ฅผ ํ•ฉ์นœ ์ˆ˜๋‹ค. ํŒ์ •์€ `๊ฒฐ๊ณผ:` ์ค„๋กœ ํ•œ๋‹ค. ๊ทธ ์•„๋ž˜ `์ฐธ๊ณ :` ์ค„์€ ์•„๋ž˜ ํ•ญ๋ชฉ๋Œ€๋กœ ๋‹ค๋ฃฌ๋‹ค.
74
80
 
75
81
  | `๊ฒฐ๊ณผ:` ์ค„ | ๋‹ค์Œ |
76
82
  |---|---|
77
83
  | `๋ชจ๋‘ ์ ์šฉ๋œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.` | 4๋‹จ๊ณ„ ๊ฒ€์ฆ์œผ๋กœ ๊ฑด๋„ˆ๋œ€ |
78
84
  | `ํ—ฌํผ ์ž๋ฆฌ ๊ฒฐํ•จ N๊ฑด์ด ์žˆ์Šต๋‹ˆ๋‹ค.` | 2๋‹จ๊ณ„ ํ™•์ธ ๋’ค 3๋‹จ๊ณ„์—์„œ ํ™œ์„ฑ ํด๋”๋ถ€ํ„ฐ ๋˜๋Œ๋ฆผ |
79
85
  | `์ ์šฉ๋˜์ง€ ์•Š์€ ํ•ญ๋ชฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค.` | 2๋‹จ๊ณ„ ํ™•์ธ ๋’ค 3๋‹จ๊ณ„ ์ ์šฉ |
86
+ | `hooks.json ์„ ํ™•์ธํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.` | ์ ์šฉํ•˜์ง€ ์•Š๊ณ  ์œ„์˜ `[!]` ์ค„์„ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ  |
87
+ | `๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค origin ์„ ํ™•์ธํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.` | ์ ์šฉํ•˜์ง€ ์•Š๊ณ  ์œ„์˜ `[!]` ์ค„์„ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ (git ์ด ์—†๊ฑฐ๋‚˜ ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค ์ €์žฅ์†Œ๋ฅผ ์ฝ์ง€ ๋ชปํ•จ) |
88
+ | `๊ณ ์ • ๊ธฐ๋ก(PINNED)์˜ ์˜› URL ์นธ์ด ์ด ๋„๊ตฌ๋กœ ์ง€์šธ ์ˆ˜ ์—†๋Š” ๊ณณ์— ์žˆ์Šต๋‹ˆ๋‹ค.` | ์ ์šฉํ•  ํ•ญ๋ชฉ์€ ์—†๋‹ค. 2๋‹จ๊ณ„์—์„œ ๊ทธ ๊ธฐ๋ก์„ ์ง€์šธ์ง€ ๋‚จ๊ธธ์ง€๋งŒ ๋ฌป๋Š”๋‹ค(`๊ทœ์น™` ์ ˆ์˜ `PINNED` ์ค„ ๋‹ค๋ฃจ๊ธฐ). ์–ด๋А ์ชฝ์ด๋“  ๊ทธ ๋’ค 4๋‹จ๊ณ„ ๊ฒ€์ฆ์œผ๋กœ ๊ฑด๋„ˆ๋œ€ |
80
89
 
81
90
  - `์ฐธ๊ณ : ๋น„ํ™œ์„ฑ ๋ฒ„์ „ ...` ์€ ์ง€๊ธˆ ์“ฐ์ง€ ์•Š๋Š” ๋ฒ„์ „์ด๋ผ ํŒ์ •์— ๋“ค์–ด๊ฐ€์ง€ ์•Š๋Š”๋‹ค.
91
+ - `๊ฒฐ๊ณผ:` ์ค„ ์•„๋ž˜์˜ `์ฐธ๊ณ : ๊ณ ์ • ๊ธฐ๋ก(PINNED)์˜ ์˜› URL ์นธ์ด ์ด ๋„๊ตฌ๋กœ ์ง€์šธ ์ˆ˜ ์—†๋Š” ๊ณณ์— ์žˆ์Šต๋‹ˆ๋‹ค.` ๋Š” ์ ์šฉํ•  ํ•ญ๋ชฉ๋„ ์žˆ๋‹ค๋Š” ๋œป์ด๋‹ค. ์ ์šฉํ•ด๋„ ๊ทธ๊ณณ์˜ ์นธ์€ ๋‚จ๊ณ  ์ ์šฉ์€ `์‹คํŒจ 1๊ฑด` ์œผ๋กœ ๋๋‚œ๋‹ค. 2๋‹จ๊ณ„์—์„œ ํ•จ๊ป˜ ๋ฌป๋Š”๋‹ค. ์œ„ ํ‘œ์˜ ๊ฐ™์€ ๋ฌธ๊ตฌ ๊ฒฐ๊ณผ ์ค„์€ ์ ์šฉํ•  ํ•ญ๋ชฉ ์—†์ด ์ด๊ฒƒ๋งŒ ๋‚จ์•˜์„ ๋•Œ ๋‚˜์˜จ๋‹ค.
82
92
  - ์ธ์ž๊ฐ€ `check` ์ด๋ฉด ์—ฌ๊ธฐ์„œ ๊ฒฐ๊ณผ๋งŒ ๋ณด๊ณ ํ•˜๊ณ  ๋๋‚ธ๋‹ค.
83
93
 
84
94
  ### 2. ํ™•์ธ (์ ์šฉ ์ „ ํ•„์ˆ˜)
85
95
 
86
- `--check` ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ ์ฃผ๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์šฉํ•ด๋„ ๋˜๋Š”์ง€ ๋ฌป๋Š”๋‹ค. ๋ฌป๊ธฐ ์ „์— ์•„๋ž˜๋ฅผ ์•Œ๋ฆฐ๋‹ค.
96
+ `--check` ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ ์ฃผ๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์šฉํ•ด๋„ ๋˜๋Š”์ง€ ๋ฌป๋Š”๋‹ค. ๋ฌป๊ธฐ ์ „์— ์•„๋ž˜๋ฅผ ์•Œ๋ฆฐ๋‹ค. 1๋‹จ๊ณ„ ๊ฒฐ๊ณผ๊ฐ€ ์œ„ ํ‘œ์˜ `PINNED` ์ค„์ด๋ฉด ์ ์šฉํ•  ํ•ญ๋ชฉ์ด ์—†๋‹ค. ์ ์šฉ์€ ๋ฌป์ง€ ์•Š๊ณ  ์•„๋ž˜ `PINNED` ํ•ญ๋ชฉ๋งŒ ํ•œ๋‹ค.
87
97
  - ํ™œ์„ฑ OMC ๋ฒ„์ „์˜ `scripts/*.mjs` ์™€ `hooks/hooks.json` ์„ ๊ณ ์นœ๋‹ค. ์›๋ณธ์€ `.omcbak` ๋กœ ๋‚จ๋Š”๋‹ค.
88
98
  - ํ—ฌํผ ์ž๋ฆฌ ๊ฒฐํ•จ์ด ์žˆ์œผ๋ฉด ํ™œ์„ฑ ํด๋”๋ฅผ `.omcbak` ์›๋ณธ์œผ๋กœ ๋จผ์ € ๋˜๋Œ๋ฆฐ ๋’ค ๋‹ค์‹œ ํŒจ์น˜ํ•œ๋‹ค.
89
- - OMC ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค `origin` ์„ `omc-pinned://` ๋กœ ๋ฐ”๊พผ๋‹ค. OMC ๊ฐ€ ๋” ์ด์ƒ ์ž๋™ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š๋Š”๋‹ค. ๋˜๋Œ๋ฆฌ๊ธฐ๋Š” `revert`.
99
+ - OMC ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค `origin` ์„ `omc-pinned://` ๋กœ ๋ฐ”๊พผ๋‹ค(`์ด๋ฏธ ๊ณ ์ •๋จ` ์ด๋ฉด ๊ทธ๋Œ€๋กœ ๋‘”๋‹ค). OMC ๊ฐ€ ๋” ์ด์ƒ ์ž๋™ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š๋Š”๋‹ค. ๋˜๋Œ๋ฆฌ๊ธฐ๋Š” `revert`.
100
+ - 1๋‹จ๊ณ„์— `[!] ๊ณ ์ • ๊ธฐ๋ก(PINNED)์˜ ์˜› URL ์นธ์„ ...` ์ค„์ด ์žˆ์—ˆ์œผ๋ฉด ๊ทธ ๊ธฐ๋ก์„ ์–ด๋–ป๊ฒŒ ํ• ์ง€ ํ•จ๊ป˜ ๋ฌป๋Š”๋‹ค(`๊ทœ์น™` ์ ˆ์˜ `PINNED` ์ค„ ๋‹ค๋ฃจ๊ธฐ).
101
+ - ์ ์šฉํ•  ํ•ญ๋ชฉ๋„ ์žˆ์œผ๋ฉด ๋‹ต์€ ์…‹์ด๋‹ค. ์ง€์šฐ๊ณ  ์ ์šฉ(์Šน์ธํ•œ ํŒŒ์ผ์„ ์ง€์šด ๋’ค 3๋‹จ๊ณ„๋กœ, ์ ์šฉ์€ ์‹คํŒจ 0๊ฑด), ๋‚จ๊ธฐ๊ณ  ์ ์šฉ(3๋‹จ๊ณ„๋กœ, ๊ทธ ์ค„ ํ•˜๋‚˜๊ฐ€ ์‹คํŒจ๋กœ ๋‚จ์•„ `์‹คํŒจ 1๊ฑด`), ์ ์šฉํ•˜์ง€ ์•Š์Œ(์—ฌ๊ธฐ์„œ ๋๋ƒ„).
102
+ - 1๋‹จ๊ณ„ ๊ฒฐ๊ณผ๊ฐ€ ์œ„ ํ‘œ์˜ `PINNED` ์ค„์ด๋ฉด ์ ์šฉํ•  ํ•ญ๋ชฉ์ด ์—†๋‹ค. ์ ์šฉ์€ ๋ฌป์ง€ ์•Š๊ณ  ์ง€์šธ์ง€ ๋‚จ๊ธธ์ง€๋งŒ ๋ฌป๋Š”๋‹ค. ์–ด๋А ์ชฝ์ด๋“  ๊ทธ ๋’ค 4๋‹จ๊ณ„๋กœ ๊ฑด๋„ˆ๋›ด๋‹ค.
90
103
  - ์Šน์ธ์ด ์—†์œผ๋ฉด ์ ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
91
104
 
92
105
  ### 3. ์ ์šฉ
@@ -115,12 +128,13 @@ node "<์ด ์Šคํ‚ฌ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ ˆ๋Œ€๊ฒฝ๋กœ>/scripts/omc-patch.mjs" --check --no
115
128
  ```
116
129
 
117
130
  - `ํŒจ์น˜ N๊ฑด / ์‹คํŒจ 0๊ฑด`, `[3/4]` ์˜ PostToolUse ์ œ๊ฑฐ, `[4/4]` ์˜ ๊ณ ์ • ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•œ๋‹ค.
118
- - ์‹คํŒจ๊ฐ€ 1๊ฑด ์ด์ƒ์ด๋ฉด ๋ฉˆ์ถ”๊ณ  ์ถœ๋ ฅ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ ํ•œ๋‹ค.
131
+ - ๋ ์ค„ `์™„๋ฃŒ. ์‹คํŒจ N๊ฑด.` ์˜ N ์ด 1 ์ด์ƒ์ด๋ฉด ๋ฉˆ์ถ”๊ณ  ์ถœ๋ ฅ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ ํ•œ๋‹ค. ์˜ˆ์™ธ๋Š” ํ•˜๋‚˜๋‹ค. `์™„๋ฃŒ. ์‹คํŒจ 1๊ฑด.` ์ด๊ณ  ๊ทธ 1๊ฑด์ด 2๋‹จ๊ณ„์—์„œ ๋‚จ๊ธฐ๊ธฐ๋กœ ํ•œ `PINNED` ์˜ `[!]` ์ค„์ด๋ฉด ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  4๋‹จ๊ณ„๋กœ ๊ฐ„๋‹ค. ๋์˜ `[!] PostToolUse ํ›… ์ œ๊ฑฐ๋Š” ...` ์ค„์€ ์žฌ์‹œ์ž‘ ์•ˆ๋‚ด๋ผ ์‹คํŒจ๊ฐ€ ์•„๋‹ˆ๋‹ค.
132
+ - `์™„๋ฃŒ. ์‹คํŒจ 1๊ฑด.` ์ด๊ณ  ๊ทธ 1๊ฑด์ด 1๋‹จ๊ณ„์— ์—†๋˜ `[!] ๊ณ ์ • ๊ธฐ๋ก(PINNED)` ์ค„์ด๋ฉด ๋ฉˆ์ถ”๊ณ  ๋ณด๊ณ ํ•œ ๋’ค `PINNED` ์ค„ ๋‹ค๋ฃจ๊ธฐ๋Œ€๋กœ ๋ฌป๋Š”๋‹ค. ํ›… ํŒจ์น˜๋Š” ์ด๋ฏธ ์ ์šฉ๋ผ ์žˆ๋‹ค. ์ง€์šฐ๊ธฐ๋กœ ํ•˜๋ฉด ์ง€์šด ๋’ค, ๋‚จ๊ธฐ๊ธฐ๋กœ ํ•˜๋ฉด ๊ทธ๋Œ€๋กœ 4๋‹จ๊ณ„๋กœ ๊ฐ„๋‹ค.
119
133
  - `[!] ํ—ฌํผ ์ž๋ฆฌ ๊ฒฐํ•จ N๊ฑด์€ ๊ณ ์น˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.` ๊ฐ€ ๋‚˜์˜ค๋ฉด 1 ์„ ๊ฑด๋„ˆ๋›ฐ์—ˆ๊ฑฐ๋‚˜ 1 ์ด ๊ทธ ํŒŒ์ผ์„ ๋˜๋Œ๋ฆฌ์ง€ ๋ชปํ•œ ๊ฒƒ์ด๋‹ค. 1 ๊ณผ 2 ๋ฅผ ํ•œ ๋ฒˆ๋งŒ ๋‹ค์‹œ ํ•œ๋‹ค. ๋‹ค์‹œ ํ•ด๋„ ๊ฐ™์œผ๋ฉด ๋ฉˆ์ถ”๊ณ  ๋ณด๊ณ ํ•œ๋‹ค.
120
134
 
121
135
  ### 4. ๊ฒ€์ฆ (๋‹ค์„ฏ ๊ฐ€์ง€ ๋ชจ๋‘)
122
136
 
123
- 1. ๋‹ค์‹œ ์ง„๋‹จํ•œ๋‹ค. `๊ฒฐ๊ณผ: ๋ชจ๋‘ ์ ์šฉ๋œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.` ๊ฐ€ ๋‚˜์™€์•ผ ํ•˜๊ณ , ํ™œ์„ฑ ๋ฒ„์ „์— `ํ—ฌํผ ์ž๋ฆฌ ๊ฒฐํ•จ` ์ค„์ด ์—†์–ด์•ผ ํ•œ๋‹ค.
137
+ 1. ๋‹ค์‹œ ์ง„๋‹จํ•œ๋‹ค. `๊ฒฐ๊ณผ: ๋ชจ๋‘ ์ ์šฉ๋œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.` ๊ฐ€ ๋‚˜์™€์•ผ ํ•˜๊ณ , ํ™œ์„ฑ ๋ฒ„์ „์— `ํ—ฌํผ ์ž๋ฆฌ ๊ฒฐํ•จ` ์ค„์ด ์—†์–ด์•ผ ํ•œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ `PINNED` ๋ฅผ ๋‚จ๊ธฐ๊ธฐ๋กœ ํ–ˆ์œผ๋ฉด `๊ฒฐ๊ณผ: ๊ณ ์ • ๊ธฐ๋ก(PINNED)์˜ ์˜› URL ์นธ์ด ์ด ๋„๊ตฌ๋กœ ์ง€์šธ ์ˆ˜ ์—†๋Š” ๊ณณ์— ์žˆ์Šต๋‹ˆ๋‹ค.` ์™€ ๊ทธ `[!]` ์ค„์ด ๋‚˜์˜ค๋Š” ๊ฒƒ์€ ์ •์ƒ์ด๋‹ค. ๋‚˜๋จธ์ง€ ๊ฒ€์ฆ์„ ๊ณ„์†ํ•œ๋‹ค.
124
138
 
125
139
  ```bash
126
140
  node "<์ด ์Šคํ‚ฌ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ ˆ๋Œ€๊ฒฝ๋กœ>/scripts/omc-patch.mjs" --check --no-update
@@ -184,17 +198,22 @@ node "<์ด ์Šคํ‚ฌ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ ˆ๋Œ€๊ฒฝ๋กœ>/scripts/omc-patch.mjs" --check --no
184
198
  | ๊ทธ ๋ฐ–์˜ `probe-pm:` ์ค„(์ข…๋ฃŒ ์ฝ”๋“œ 2) | ๋ช…๋ น ์˜ค๋ฅ˜๋‹ค. ๊ฒฝ๋กœ๋ฅผ ๋‹ค์‹œ ๊ตฌํ•ด ํ•œ ๋ฒˆ๋งŒ ๋‹ค์‹œ ์‹คํ–‰ํ•œ๋‹ค. ๊ฐ™์œผ๋ฉด ์ถœ๋ ฅ์„ ๋ณด๊ณ ํ•˜๊ณ  ๋ฉˆ์ถ˜๋‹ค |
185
199
  | ํŒ์ • ์ค„ ๋’ค์˜ `probe-pm: ์ž„์‹œ ํด๋”๋ฅผ ์ง€์šฐ์ง€ ๋ชปํ•จ` | ํŒ์ •์€ ๊ทธ๋Œ€๋กœ ์“ด๋‹ค. ๋‚จ์€ ์ž„์‹œ ํด๋” ๊ฒฝ๋กœ๋ฅผ ๋ณด๊ณ ์— ์ ์–ด ์‚ฌ์šฉ์ž๊ฐ€ ์ง€์šฐ๊ฒŒ ํ•œ๋‹ค |
186
200
 
187
- 5. ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค ๊ณ ์ •์„ ํ™•์ธํ•œ๋‹ค. `omc-pinned://` ๋กœ ์‹œ์ž‘ํ•˜๋ฉด ๊ณ ์ •๋œ ๊ฒƒ์ด๋‹ค.
201
+ 5. ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค ๊ณ ์ •์„ ํ™•์ธํ•œ๋‹ค. ๋ช…๋ น์€ origin ์ด `omc-pinned://` ๋กœ ์‹œ์ž‘ํ•˜๋Š”์ง€๋งŒ ๋ณด๊ณ  `pinned` ๋‚˜ `not-pinned` ํ•œ ๋‹จ์–ด๋ฅผ ๋‚ธ๋‹ค. origin ์˜ ์ž๊ฒฉ ์ฆ๋ช…๊ณผ ์ฟผ๋ฆฌ ๊ฐ’์€ ์ถœ๋ ฅ์— ๋‚˜์˜ค์ง€ ์•Š๋Š”๋‹ค. `pinned` ๋ฉด ๊ณ ์ •๋œ ๊ฒƒ์ด๋‹ค.
188
202
 
189
203
  ```bash
190
- git -C "$HOME/.claude/plugins/marketplaces/omc" remote get-url origin
204
+ case "$(git -C "$HOME/.claude/plugins/marketplaces/omc" config --get remote.origin.url)" in omc-pinned://*) echo pinned;; *) echo not-pinned;; esac
191
205
  ```
192
206
 
193
- PowerShell ์—์„œ๋Š” `$HOME` ๋Œ€์‹  `$env:USERPROFILE` ์„ ์“ด๋‹ค.
207
+ PowerShell:
208
+
209
+ ```powershell
210
+ if ((git -C "$env:USERPROFILE/.claude/plugins/marketplaces/omc" config --get remote.origin.url) -like 'omc-pinned://*') { 'pinned' } else { 'not-pinned' }
211
+ ```
194
212
 
195
213
  ### 5. ๋ณด๊ณ 
196
214
 
197
215
  - ํ™œ์„ฑ ๋ฒ„์ „, ํŒจ์น˜ ๊ฑด์ˆ˜, ํ—ฌํผ ์ž๋ฆฌ ๊ฒฐํ•จ ์ฒ˜๋ฆฌ ์—ฌ๋ถ€, PostToolUse ์ œ๊ฑฐ ๋ชฉ๋ก, ๊ณ ์ • ์ƒํƒœ, project-memory ํ™•์ธ ๊ฒฐ๊ณผ
216
+ - `PINNED` ์ค„์ด ์žˆ์—ˆ์œผ๋ฉด ์–ด๋–ป๊ฒŒ ํ–ˆ๋Š”์ง€(์ง€์šด ๊ฒฝ๋กœ, ๋˜๋Š” ๋‚จ๊ธด ๊ธฐ๋ก๊ณผ ๊ทธ ๊ฒฝ๋กœ. ๋‹ค๋ฅธ ์ด๋ฆ„์ด ์žˆ๋˜ ๊ธฐ๋ก์€ ๋‹ค๋ฅธ ์ด๋ฆ„์˜ ๊ฒฝ๋กœ)
198
217
  - ํ›… ์Šคํฌ๋ฆฝํŠธ ํŒจ์น˜๋Š” ๋ฐ”๋กœ ์ ์šฉ๋œ๋‹ค. ํ›…์ด ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค ํŒŒ์ผ์„ ์ƒˆ๋กœ ์ฝ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
199
218
  - PostToolUse ์ œ๊ฑฐ๋Š” `hooks.json` ๋ณ€๊ฒฝ์ด๋ผ ๊ฐ ์„ธ์…˜์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•ด์•ผ ์ ์šฉ๋œ๋‹ค.
200
219
  - Windows ์—์„œ ์ด๋ฏธ ์Œ“์ธ ์ปค๋„ ํ’€์€ ์žฌ๋ถ€ํŒ…์œผ๋กœ๋งŒ ํšŒ์ˆ˜๋œ๋‹ค. ์žฌ๋ถ€ํŒ…์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ํ•œ๋‹ค.
@@ -209,6 +228,7 @@ node "<์ด ์Šคํ‚ฌ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ ˆ๋Œ€๊ฒฝ๋กœ>/scripts/omc-patch.mjs" --revert
209
228
  - `.omcbak` ๋ฐฑ์—…์œผ๋กœ ์„ค์น˜๋œ ๋ชจ๋“  ๋ฒ„์ „์˜ ํ›… ์Šคํฌ๋ฆฝํŠธ์™€ `hooks.json` ์„ ๋˜๋Œ๋ฆฌ๊ณ  ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค `origin` ์„ ์›๋ž˜ URL ๋กœ ๋ณต๊ตฌํ•œ๋‹ค.
210
229
  - ๋˜๋Œ๋ฆฐ ๋’ค Claude Code ๋ฅผ ๋‹ค์‹œ ์‹œ์ž‘ํ•ด์•ผ ๋ฐ˜์˜๋œ๋‹ค.
211
230
  - `๋˜๋Œ๋ฆฌ๊ธฐ๋ฅผ ๋๋‚ด์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.` ๊ฐ€ ๋‚˜์˜ค๋ฉด ์œ„์˜ `[!]` ์ค„์„ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ ํ•œ๋‹ค. ์ข…๋ฃŒ ์ฝ”๋“œ๋Š” 1 ์ด๋‹ค. ๋ฐฑ์—…์ด ์—†์–ด ๋‚จ์€ ํŒŒ์ผ๊ณผ hooks.json ๋„ ์ด ๋ ์ค„๋กœ ๋๋‚œ๋‹ค.
231
+ - ๊ณ ์ • ๊ธฐ๋ก(`PINNED`)์„ ์ง€์šฐ์ง€ ๋ชปํ•˜๋ฉด(์ฝ๊ธฐ ์ „์šฉ ํด๋”, ํด๋”์ธ `PINNED`) `[!] ๊ณ ์ • ๊ธฐ๋ก(PINNED)์„ ์ง€์šฐ์ง€ ๋ชปํ•จ` ์œผ๋กœ ์•Œ๋ฆฌ๊ณ  ์‹คํŒจ๋กœ ์„ผ๋‹ค. ๋งํฌ์ธ `PINNED` ๋„ˆ๋จธ์— ์˜› URL ์นธ์ด ์žˆ์œผ๋ฉด ๋งํฌ๋ฅผ ๋‘๊ณ  `[!]` ๋กœ ์•Œ๋ฆฐ๋‹ค. ์˜› URL ์นธ์ด ๋“  ๊ธฐ๋ก์— ๊ฐ™์€ ํŒŒ์ผ์˜ ๋‹ค๋ฅธ ์ด๋ฆ„์ด ์žˆ์œผ๋ฉด ์ด ์ด๋ฆ„๋งŒ ์ง€์šฐ๊ณ  `[!]` ๋กœ ์•Œ๋ฆฐ๋‹ค. ์…‹ ๋‹ค origin ์€ ๊ทธ๋Œ€๋กœ ๋˜๋Œ๋ฆฐ๋‹ค. ์œ„ `๊ทœ์น™` ์ ˆ์˜ `PINNED` ์ค„ ๋‹ค๋ฃจ๊ธฐ๋Œ€๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฌป๋Š”๋‹ค.
212
232
  - `์ด ๋„๊ตฌ๊ฐ€ ๋งŒ๋“ค์ง€ ์•Š์€ ์ˆ˜์ •์ด ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค` ๊ฐ€ ๋‚˜์˜ค๋ฉด ๋ฐฑ์—…์ด ์—†๋Š” ์† ์ˆ˜์ •์ด๋‹ค. ์ถœ๋ ฅ์— ์ ํžŒ ๋ณต๊ตฌ ๋ช…๋ น์„ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ ํ•˜๊ณ , ์‚ฌ์šฉ์ž ์Šน์ธ ์—†์ด ์‹คํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.
213
233
 
214
234
  ## ํ•จ์ •
@@ -218,9 +238,15 @@ node "<์ด ์Šคํ‚ฌ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ ˆ๋Œ€๊ฒฝ๋กœ>/scripts/omc-patch.mjs" --revert
218
238
  - ํ—ฌํผ ์ž๋ฆฌ ๊ฒฐํ•จ์ด ๋‚จ์€ ์ฑ„ `revert` ์—†์ด ์ ์šฉ์„ ๋‹ค์‹œ ๋Œ๋ ค๋„ ๋ฐ”๋€Œ๋Š” ๊ฒƒ์ด ์—†๋‹ค. ์ด๋ฏธ ๊ฐ์‹ผ ํŒŒ์ผ์€ ๊ฑด๋„ˆ๋›ฐ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
219
239
  - `--root` ์™€ `--revert --root` ๋Š” ๊ทธ ํด๋”๋งŒ ๋‹ค๋ฃฌ๋‹ค. ๊ณ ์ • ํ•ด์ œ๊นŒ์ง€ ํ•˜๋ ค๋ฉด ์ธ์ž ์—†๋Š” `--revert` ๋ฅผ ์“ด๋‹ค.
220
240
  - ๊ณ ์ •์„ ํ’€์ง€ ์•Š๊ณ  OMC ๋ฅผ ์˜ฌ๋ฆฌ๋ ค๋ฉด ๋จผ์ € `revert` ํ•œ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค fetch ๊ฐ€ ๊ณ„์† ์‹คํŒจํ•œ๋‹ค.
221
- - ๊ณ ์ • ๊ธฐ๋ก(`PINNED`)์€ `~/.claude/omc-local-patches/PINNED` ์— ์ƒ๊ธด๋‹ค. ๊ธฐ๋ก์ผ ๋ฟ์ด๋ผ ์ง€์›Œ๋„ ๊ณ ์ •์€ ํ’€๋ฆฌ์ง€ ์•Š๊ณ , ์ด๋ฏธ ๊ณ ์ •๋œ ์ƒํƒœ์—์„œ๋Š” ๋‹ค์‹œ ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š๋Š”๋‹ค. `revert` ๋Š” origin ๊ฐ’์—์„œ๋งŒ ์›๋ž˜ URL ์„ ๋˜์ฐพ๊ณ , `PINNED` ์˜ ๊ฐ’์œผ๋กœ origin ์„ ์“ฐ์ง€ ์•Š๋Š”๋‹ค. ๋‹ค๋ฅธ ๊ธฐ๊ธฐ์˜ ๊ธฐ๋ก์ด๋ฉด ์•Œ๋ฆฌ๊ธฐ๋งŒ ํ•œ๋‹ค.
241
+ - ๊ณ ์ • ๊ธฐ๋ก(`PINNED`)์€ `~/.claude/omc-local-patches/PINNED` ์— ์ƒ๊ธด๋‹ค. ๊ณ ์ •ํ•œ ์‹œ๊ฐ, ํŒ, ๊ธฐ๊ธฐ ์ด๋ฆ„๋งŒ ๋‹ด๊ณ  URL ์€ ๋‹ด์ง€ ์•Š๋Š”๋‹ค. ์˜› ํŒ์˜ ๊ธฐ๋ก์— URL ์นธ์ด ์žˆ์œผ๋ฉด `--check` ๊ฐ€ ์•Œ๋ฆฌ๊ณ  ๋‹ค์Œ ์ ์šฉ ๋•Œ ์ง€์šด๋‹ค. ์ง€์šฐ์ง€ ๋ชปํ•˜๋ฉด `[!]` ๋กœ ์•Œ๋ฆฌ๊ณ  ์‹คํŒจ๋กœ ์„ผ๋‹ค(๋งํฌ๋Š” ๋”ฐ๋ผ๊ฐ€ ์“ฐ์ง€ ์•Š์Œ). ํ•˜๋“œ ๋งํฌ๋กœ ์ด์–ด์ง„ ๋‹ค๋ฅธ ์ด๋ฆ„์—๋Š” ๊ทธ ์นธ์ด ๋‚จ๋Š”๋‹ค. ๋„๊ตฌ๋Š” ๊ทธ ์ด๋ฆ„์„ ์ฐพ์„ ๋ฒˆํ˜ธ๋ฅผ ์•Œ๋ฆฐ๋‹ค. POSIX ์—์„œ๋Š” ์ด ๊ณ„์ •๋งŒ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค(0600). Windows ์—์„œ๋Š” ํด๋”์˜ ์ƒ์† ๊ถŒํ•œ์„ ๋”ฐ๋ฅธ๋‹ค. ๊ธฐ๋ก์ผ ๋ฟ์ด๋ผ ์ง€์›Œ๋„ ๊ณ ์ •์€ ํ’€๋ฆฌ์ง€ ์•Š๊ณ , ์ด๋ฏธ ๊ณ ์ •๋œ ์ƒํƒœ์—์„œ๋Š” ๋‹ค์‹œ ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š๋Š”๋‹ค. `revert` ๋Š” origin ๊ฐ’์—์„œ๋งŒ ์›๋ž˜ URL ์„ ๋˜์ฐพ๋Š”๋‹ค. ๋‹ค๋ฅธ ๊ธฐ๊ธฐ์˜ ๊ธฐ๋ก์ด๋ฉด ์•Œ๋ฆฌ๊ธฐ๋งŒ ํ•œ๋‹ค.
222
242
  - `--check` ์™€ `--revert` ๋ฅผ ํ•จ๊ป˜ ์ฃผ๋ฉด ๋„๊ตฌ๋Š” ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๊ณ  ์ข…๋ฃŒ ์ฝ”๋“œ 1 ๋กœ ๋ฉˆ์ถ˜๋‹ค. ์ง„๋‹จ๋งŒ ํ•˜๋ ค๋ฉด `--check` ๋งŒ ์ค€๋‹ค.
223
243
  - ๋„๊ตฌ๋Š” git ์„ ๋ถ€๋ฅผ ๋•Œ ์ €์žฅ์†Œ๋ฅผ ๊ณ ๋ฅด๋Š” ๋ณ€์ˆ˜(`GIT_DIR`, `GIT_WORK_TREE` ๋“ฑ `git rev-parse --local-env-vars` ๋ชฉ๋ก)๋ฅผ ์ง€์šด๋‹ค. git ํ›… ์•ˆ์—์„œ ๋ถˆ๋Ÿฌ๋„ ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค ์ €์žฅ์†Œ๋งŒ ๋‹ค๋ฃฌ๋‹ค.
224
244
  - ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค์˜ `.git` ์ด ๊นจ์กŒ์œผ๋ฉด(์ค‘๋‹จ๋œ clone ๋“ฑ) git ์ด ์œ„์ชฝ ์ €์žฅ์†Œ๋กœ ์˜ฌ๋ผ๊ฐ€์ง€ ๋ชปํ•˜๊ฒŒ ๋ง‰๋Š”๋‹ค. ๊ทธ๋•Œ `[4/4]` ๋Š” `origin remote ์—†์Œ` ์œผ๋กœ ๋๋‚˜๊ณ  ๊ณ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.
225
245
  - `--scan` ์€ ์ฝ๊ธฐ๋งŒ ํ•œ๋‹ค. ํ”Œ๋Ÿฌ๊ทธ์ธ ์บ์‹œ๋„ `installed_plugins.json` ๋„ ์ฝ์ง€ ์•Š๊ณ  ์ค€ ํŒŒ์ผ๋งŒ ๋ณธ๋‹ค.
246
+ - ๋„๊ตฌ๋Š” ๋ชจ๋ฅด๋Š” ์ธ์ž๋ฅผ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค. `--check`, `--revert`, `--no-update`, `--root <ํด๋”>`, `--scan <ํŒŒ์ผ>...`, `--help` ๋ฐ–์˜ ์ธ์ž๋‚˜ `--root=<ํด๋”>` ๊ผด์„ ์ฃผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๊ณ  ์‚ฌ์šฉ๋ฒ•๊ณผ ํ•จ๊ป˜ ์ข…๋ฃŒ 1 ๋กœ ๋ฉˆ์ถ˜๋‹ค.
247
+ - ์ถœ๋ ฅ์˜ origin URL ์— ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์žˆ์œผ๋ฉด `//***@` ๋กœ ๊ฐ€๋ฆฐ๋‹ค. ๋„๊ตฌ๋Š” origin ์„ ์„ค์ • ๊ฐ’ ๊ทธ๋Œ€๋กœ ์ฝ์–ด ๊ณ ์ •ํ•˜๊ณ  ๋˜๋Œ๋ฆฐ๋‹ค(์ „์—ญ `insteadOf` ๋ฅผ ํŽผ์น˜์ง€ ์•Š์Œ).
248
+ - origin ์„ ๋ฐ”๊พธ์ง€ ๋ชปํ•˜๋ฉด(`.git` ์“ฐ๊ธฐ ๊ถŒํ•œ ๋“ฑ) `[!] ๊ณ ์ • ์‹คํŒจ` ๋‚˜ `[!] ๊ณ ์ • ํ•ด์ œ ์‹คํŒจ` ์™€ ์ข…๋ฃŒ 1 ๋กœ ๋๋‚œ๋‹ค. ๊ทธ ์ค„์„ ๊ทธ๋Œ€๋กœ ๋ณด๊ณ ํ•œ๋‹ค. ๊ณ ์ • ํ•ด์ œ์— ์‹คํŒจํ•˜๋ฉด ๋„๊ตฌ๊ฐ€ ์›๋ž˜ URL ์„ ํ˜ธ์ŠคํŠธ๊นŒ์ง€๋งŒ ๋ณด์ธ๋‹ค. ์›๋ฌธ์€ origin ์˜ `omc-pinned://` ๋’ค์— ๊ทธ๋Œ€๋กœ ๋‚จ๋Š”๋‹ค.
249
+ - `--check` ๋Š” ์“ธ ์ˆ˜ ์—†๋Š” ํŒŒ์ผ์„ `ํŒจ์น˜ ํ•„์š”, ์“ธ ์ˆ˜ ์—†์Œ` ์œผ๋กœ ๋ฏธ๋ฆฌ ์•Œ๋ฆฐ๋‹ค. ํด๋” ๋ฐ– ๋งํฌ ๋•Œ๋ฌธ์— ๋˜๋Œ๋ฆฌ์ง€ ๋ชปํ•œ ํŒŒ์ผ์€ ์† ๋ณต๊ตฌ ๋ฐฉ๋ฒ•์„ ํ•จ๊ป˜ ๋‚ธ๋‹ค. ๋งํฌ ๋Œ€์ƒ ํŒŒ์ผ์— ์ด ๋„๊ตฌ ๊ณ„์—ด์˜ ํŒจ์น˜๊ฐ€ ๋ณด์ด์ง€ ์•Š์œผ๋ฉด ๋ฎ์–ด์“ฐ๋ผ๊ณ  ํ•˜์ง€ ์•Š๊ณ , ๋งํฌ๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ณณ์ด ๋งž๋Š”์ง€ ๋จผ์ € ํ™•์ธํ•˜๋ผ๊ณ  ํ•œ๋‹ค. ์‚ฌ์šฉ์ž ์Šน์ธ ์—†์ด ๊ทธ ๋ฐฉ๋ฒ•์„ ์‹คํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.
250
+ - ํŒŒ์ผ์€ ์ƒˆ ์ž„์‹œ ํŒŒ์ผ์„ ๋””์Šคํฌ๊นŒ์ง€ ๋‚ด๋ฆฐ ๋’ค rename ์œผ๋กœ ๋ฐ”๊พผ๋‹ค. ํ•˜๋“œ ๋งํฌ๋กœ ์ด์–ด์ง„ ํด๋” ๋ฐ– ํŒŒ์ผ์€ ๋ฐ”๋€Œ์ง€ ์•Š๋Š”๋‹ค. ์“ฐ์ง€ ๋ชปํ•œ ํŒŒ์ผ์€ `์“ฐ์ง€ ๋ชปํ•จ` ์œผ๋กœ ์ด๋ฆ„์„ ๋Œ€๊ณ  ์‹คํŒจ๋กœ ์„ผ๋‹ค.
251
+ - ์ฝ์„ ์ˆ˜ ์—†๋Š” ํ›…(๊ถŒํ•œ, ํด๋”, FIFO)์€ `์ฝ์ง€ ๋ชปํ•จ: <์ด๋ฆ„> (<์ฝ”๋“œ>)`, ์“ธ ๊ถŒํ•œ์ด ์—†๊ฑฐ๋‚˜ ์ผ๋ฐ˜ ํŒŒ์ผ์ด ์•„๋‹Œ ํ›…์€ `์“ฐ์ง€ ์•Š์Œ: <์ด๋ฆ„> (์“ธ ๊ถŒํ•œ์ด ์—†์Œ ๋˜๋Š” ์ผ๋ฐ˜ ํŒŒ์ผ์ด ์•„๋‹˜)` ์œผ๋กœ ์•Œ๋ฆฌ๊ณ  ์‹คํŒจ๋กœ ์„ผ๋‹ค. `--check` ๋Š” ์ฝ์ง€ ๋ชปํ•œ ํ›…์ด ์žˆ์œผ๋ฉด ๋ชจ๋‘ ์ ์šฉ๋œ ์ƒํƒœ๋กœ ๋ณด์ง€ ์•Š๋Š”๋‹ค. ์„ค์ • ํŒŒ์ผ๋„ ์ผ๋ฐ˜ ํŒŒ์ผ๋งŒ ์ฝ๋Š”๋‹ค. `installed_plugins.json` ์ด FIFO ์ฒ˜๋Ÿผ ์ผ๋ฐ˜ ํŒŒ์ผ์ด ์•„๋‹ˆ๋ฉด ํ™œ์„ฑ ๋ฒ„์ „์ด `(์ถ”์ •: ...)` ์œผ๋กœ ๋‚˜์˜ค๊ณ , `settings.json` ์ด๋ฉด `DISABLE_AUTOUPDATER` ์ค„์„ ์ƒ๋žตํ•œ๋‹ค. ๋„๊ตฌ๊ฐ€ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ•œ ์˜ค๋ฅ˜๋Š” ๊ฒฝ๋กœ๋ฅผ ์ •ํ™”ํ•œ ํ•œ ์ค„๊ณผ ์ข…๋ฃŒ 1 ๋กœ ๋๋‚œ๋‹ค.
226
252
  - ๋„๊ตฌ์˜ ์ฃผ์„๊ณผ ์ถœ๋ ฅ ๋ฌธ๊ตฌ๋Š” ํ•œ๊ธ€์ด๋‹ค.