{ "schemaVersion": "2.0", "kind": "architecture-tree", "title": "sidecar — 아키텍처 SSOT", "summary": "AI 코딩 사이드카 엔진. 핵심: 규칙은 코드가 아니라 데이터 — 엔진(언제·어떻게 검사)은 공유, 규칙(무엇을 검사)만 per-repo 주입.", "viewer": "ARCHITECTURE.html", "serve": "python3 serve.py (로컬 서버 + 브라우저 자동 오픈; file:// fetch 차단 우회)", "convergence": { "note": "재발방지 학습 SSOT (구 인라인 마커 이관 · root-cause). state=3단계 수렴 pos-conv🟢(포지티브)|in-prog🔄(진행·검토)|neg-conv🔴(네거티브·falsified) · 레거시 ossified/in_flight/failed accepted · value=핵심 · threshold=재발조건/해결 · source=원파일. 재발 시 update-in-place.", "records": [ { "id": "EXEC_SPAWN_ERROR_UNHANDLED", "state": "pos-conv", "value": "execArgs must handle the child's `error` event (spawn ENOENT / EACCES) — degrade to a non-zero ExecResult, never let an unhandled `error` crash the process", "threshold": "a minimal-PATH hook env (SessionStart) couldn't spawn `bash` → unhandled 'error' event killed the whole SessionStart hook (node:events crash); every execShell caller was exposed", "source": "lib/exec.ts" }, { "id": "CLAUDEMD_NOT_L0", "state": "pos-conv", "value": "CLAUDE.md must never be classified L0 — it DECLARES the L0 list (fromMarkdown) and is re-injected each turn, it is not protected engine core", "threshold": "the L0 path-parser regex must NOT include a CLAUDE*.md alternation, or the guide captures itself", "source": "lib/lockdown.ts" }, { "id": "ARCH_SNAPSHOT_NOT_HISTORY", "state": "pos-conv", "value": "ARCHITECTURE.json/.md is a CURRENT-STATE snapshot tree, not a change log — 'update' means replace the affected node in-place and delete the old wording; NEVER append history/version/dated/previous/deprecated nodes (history → CHANGELOG + git). The inject note carries this every turn so the model stops accreting history into the tree", "threshold": "the word '갱신형'(updatable) was read as 'add an update entry', so sessions kept leaving version/dated/'이전엔…' nodes in the tree; hardened the every-turn inject note + commons c4 to say snapshot/replace-in-place explicitly", "source": "modules/architecture.ts" }, { "id": "CI_TRACK_NATIVE", "state": "pos-conv", "value": "PR CI status is tracked by `sidecar ci-track` (gh pr checks --json → aggregate + verdict + optional --watch/--merge-on-green), not by hand-rolled `gh pr checks|grep` + /tmp monitor sleep loops", "threshold": "merge-on-green campaigns repeatedly re-implemented CI polling inline (e.g. /tmp/pr3688_mon.sh) — no sidecar command tracked remote CI; this centralizes it (the poll runs inside the CLI, not a bash sleep loop)", "source": "modules/ci-track.ts" }, { "id": "NO_RAW_CLOUD_CLI", "state": "pos-conv", "value": "raw runpodctl/vastai/`cloud rent`/provider-API calls are blocked in code (pre bash), not just by an enforcement regex rule", "threshold": "a session ran `runpodctl pod create`/`cloud rent` directly because c11 was only a hint+warn; code guard runs before config rules with no override", "source": "modules/cloud-guard.ts" }, { "id": "NO_HANDROLLED_SHARD_FANOUT", "state": "in-prog", "value": "a hand-rolled launcher loop (split -n l/N + a for/while loop that setsid/nohup-backgrounds repeated `hexa run`/training launches) is the exact bypass that defeated NO_RAW_CLOUD_CLI — copy-to is whitelisted so the .sh sails through, then the fanout runs remotely unseen. Detected as a WARN that redirects to `hexa cloud fire-shards`", "threshold": "a session wrote /tmp/h1305_launch.sh (12-shard staggered `hexa run` decode loop) + `hexa cloud copy-to` + remote run, bypassing structured dispatch/register/cost-accounting; the guard only saw the whitelisted copy-to", "source": "modules/cloud-guard.ts" }, { "id": "QUOTE_AWARE_SEGMENT", "state": "pos-conv", "value": "command-head detection segments on UNQUOTED shell operators only — a `|`/`;`/`&` inside quotes is data, not a separator", "threshold": "`grep -E \"vast|runpod\"` false-blocked once `vast`/`runpod` became unconditional CLI heads: stripQuotes ran before the `|`-split, so the quoted alternation was torn into a bare `vast` segment", "source": "modules/cloud-guard.ts" }, { "id": "NO_VAST_VERB_WHITELIST", "state": "pos-conv", "value": "`vast` is blocked unconditionally in command position, same as `vastai`/`runpodctl` — NO verb whitelist", "threshold": "a verb whitelist (VAST_VERBS) only blocked ~10 verbs; `vast set api-key`/`vast scp`/`vast execute`/literally `vast cli` all leaked because their verb wasn't listed — the guard 're-unlocked' every time vast.ai added a subcommand", "source": "modules/cloud-guard.ts" }, { "id": "BOTH_RUNPOD_CLIS", "state": "pos-conv", "value": "both RunPod CLI heads (`runpodctl` Go + `runpod` Python) are blocked, plus the serverless host `api.runpod.ai` alongside the control-plane `api.runpod.io`/`rest.runpod.io`", "threshold": "only `runpodctl` was listed, so `runpod config`/`runpod project deploy`/`runpod pod create` and serverless `curl api.runpod.ai/v2//run` all leaked — same whitelist-too-narrow bypass as VAST_VERBS", "source": "modules/cloud-guard.ts" }, { "id": "SEGMENT_LOCAL_SANCTION", "state": "pos-conv", "value": "the hexa-builtin sanction in detectRawCloudCli is per-SEGMENT, not whole-command — one `hexa cloud …` token must not exempt a raw-provider tail", "threshold": "a top-level `if (isHexaBuiltin(cmd)) return null` exempted the ENTIRE string, so `hexa cloud pods; vastai create instance` leaked (the sanctioned head whitelisted the chained raw CLI) — same whitelist-too-broad failure mode as VAST_VERBS/BOTH_RUNPOD_CLIS; fix judges each segment on its own head", "source": "modules/cloud-guard.ts" }, { "id": "RENT_HUMAN_CONSENT_GATE", "state": "pos-conv", "value": "a GPU-pod rent (`hexa cloud rent|up`) is a real-money action gated on a fresh HUMAN consent token (`!sidecar sign rent`) minted only via the TUI bang — the agent's own `sidecar sign` mint and any write into ~/.sidecar/signs/ are code-blocked (SIGN-SELF-MINT/SIGN-FORGE); one-shot, consumed on admission, TTL/one-shot are code constants (no config opt-out). Gate rests on 'the agent cannot emit a `!` bang', not on the text parser being airtight", "threshold": "renting a pod costs money but nothing forced a human in the loop — the agent could `hexa cloud rent` unilaterally; ports the retired hexa `_sign_guard.hexa` consent gate (old `! sidecar sign commons` pattern). Indirect rents (bash -c/eval/ssh/pool wrapper) are blocked 'run it directly'; structural closure (hexa itself requiring the token) is a documented phase-2. FALSE-POSITIVE fix: detectSelfMint read the token after `sidecar sign` as a mint key even when it was a shell redirect (`sidecar sign 2>/dev/null` → rest[1]=`2>` since segments() splits on unquoted `&`), false-blocking a bare list+redirect — a mint key must now match /^[A-Za-z][\\w-]*/ so operators/empty fall through to ALLOW", "source": "modules/sign.ts" }, { "id": "COMPANION_SURFACE_NOT_INJECTED", "state": "pos-conv", "value": "a sibling CLI (hexa) and its subcommands (cloud/atlas/verify/drill) existed and worked, but the sidecar injected only its OWN command catalog (toolkit) — never the neighbour CLI's surface — so the agent re-probed 'is hexa installed? does cloud exist?' every session", "threshold": "toolkit injected sidecar commands only; no carrier surfaced an adjacent project-CLI's verb list, so each session started blind to hexa", "source": "modules/companions.ts" }, { "id": "CODE_GUARD_DESTRUCTIVE_BYPASS", "state": "pos-conv", "value": "--no-verify (gate bypass), git reset --hard/clean -fd (working-tree destroy), rm -rf / ~ $HOME (catastrophic), curl|wget|sh (remote code exec) are blocked in CODE before config rules — not regex-only — so a profile edit can't disable them; inline `# ...-ok` markers still allow an explicit per-command override", "threshold": "these were block-policy but enforcement.json-only; a regex/profile edit (or the stdin-input bug that disabled the whole pre layer) would have left the irreversible/gate-bypass commands unguarded", "source": "modules/danger-guard.ts" }, { "id": "STALE_BRANCH_TRAP", "state": "pos-conv", "value": "stale-branch 함정 4중 방어. (1) git-context 경고(HEAD vs origin/ behind/detached) — SessionStart + per-turn-when-stale 둘 다(behind>0 일 때만 매턴 재방출, clean+on-default 이면 0바이트 silent). (1b) READ-STALE 갭 봉합: (2)(3) 가드는 Write/Edit 만 deny 하므로 stale 코드를 '읽어서' 'X 가 코드에 있나?'에 답하는 경로(anima ρ-AXON 재프레임 오판)는 블록 불가 — per-turn 경고가 그 read-판정 실수를 능동 상기(경고=인지의 지속화). (2) 경고 무시 재발(anima #3736) → GIT-EDIT-OFF-MAIN 가드: main 워크트리가 non-default 브랜치에 주차된 채 Write/Edit 하면 deny. (3) ON-main 변종 재발(hexa-codex 증류섹션: 원격이 앞서갔는데 fetch 가 없어 로컬 ref 는 '최신'으로 보임 → 낡은 base 에 SSOT 재작성, pr-cycle 충돌로만 발각) → GIT-EDIT-STALE-MAIN 가드: TTL fetch(git.staleFetchTtlSec) 후 origin/ 가 대상 파일을 건드리며 앞서 있으면 deny, 무관 behind 는 warn. 경고=인지, 블록=예방(READ 는 블록 불가 → 지속-경고가 최선). probeGitContext 는 cwd 파라미터화.", "threshold": "재발조건: main 워크트리에서 stale base 로 편집 — 브랜치가 non-default 이거나(2), default 인데 원격이 앞서감(3·fetch 부재로 로컬 ref 가 가림). 해결: 경고+가드 셋 다 유지(git.guardOffMainEdit·git.guardStaleMainEdit 기본 ON) — 복귀는 ff-sync(merge --ff-only origin/) 또는 최신 tip 에서 격리 워크트리. 격리 워크트리 면제(탈출구). warn-only 회귀·옵트인 마커 키잉 금지(config-ts-1).", "source": "modules/git-context.ts" }, { "id": "FORCE_LEASE_DUAL_SSOT", "state": "pos-conv", "value": "`--force-with-lease` is ALLOWED by both the code guard (git-guard.ts) and config enforcement.json (H-FORCE-PUSH exceptions) — only blind --force/-f/+refspec is blocked, with a `# force-ok` escape", "threshold": "QA found git-guard blocked --force-with-lease while config exempted it → lease hard-blocked with no override, breaking the rebase→lease-push workflow; aligned code guard to config intent", "source": "modules/git-guard.ts" }, { "id": "ING_STALENESS_NUDGE", "state": "in-prog", "value": "code edited ≫ ing untouched is WARNED at Stop (c6) — code edits bump a counter, ing add/next/done resets it, Stop warns once past editThreshold then resets; warn-only (forcing an ing update has no ground truth, so a hard block would be a false-positive factory)", "threshold": "every-turn `ing inject` only SHOWED the board; sessions edited lots of code while the board went stale because nothing nudged an update", "source": "modules/ing-staleness.ts" }, { "id": "ING_BOARD_DEDICATED_REF", "state": "pos-conv", "value": "board lives on a dedicated `ing` ref via plumbing, never the working tree", "threshold": "tracked worktree file: branch-switch/reset clobbers session edits (happened); untracked: unshared", "source": "modules/ing.ts" }, { "id": "ING_NO_DIRECT_MAIN_PUSH", "state": "neg-conv", "value": "never store/push the board on main", "threshold": "protectedBranches repo blocks direct main push so the board is unusable; a dedicated ref is protected-main-safe", "source": "modules/ing.ts" }, { "id": "INIT-INJECT-DUP", "state": "pos-conv", "value": "init --hooks scaffolded the full host-wide inject set (commons/recommend/prefs/easy/load/architecture) into per-repo .claude/settings.json, duplicating the global install (~/.claude/settings.json) and/or the enabled plugin — same context injected 2-3x/turn, burying short user prompts", "threshold": "resolved: per-repo .claude/settings.json is BANNED — sidecar is global-only. init no longer scaffolds repo hooks (the --hooks flag is gone); hooks live solely in the global ~/.claude/settings.json via `sidecar install`. inject = host-wide policy, the global layer owns it", "source": "modules/init.ts" }, { "id": "HELP_NO_RAW_BACKTICK", "state": "pos-conv", "value": "a bare backtick in the cli HELP template literal terminates it → help/build break; lint HELP-BACKTICK(block) scans the HELP block and flags any unescaped backtick (escaped \\\\\\` allowed)", "threshold": "twice in one session a help-line edit added `code`-style backticks (fleet-full, folders), each silently breaking `sidecar help` until caught by hand — ossify as a commit gate", "source": "modules/lint.ts" }, { "id": "MAC_OOM_FANOUT_JETSAM", "state": "in-prog", "value": "a 16GB Mac OOM-dies (jetsam kills apps + kernel panic) because parallel fan-out (cycle-all/all-bg-go) accumulates 6+ detached `claude` processes (~400-490MB each) across sessions until macOS jetsams — mem-guard adds a free-RAM preflight before background-spawn + an opt-in launchd notify watchdog", "threshold": "JetsamEvent reports 6/13-6/18 + panic 6/17 showed six `2.1.179` (claude) procs as top memory holders at OOM time; preflight warns/blocks spawn when available RAM < warnPct", "source": "modules/mem-guard.ts" }, { "id": "NAMING_VERSION_SUFFIX", "state": "pos-conv", "value": "new files/dirs named foo_v2/_final/_copy/_old (via Write/Edit OR a mv/cp/touch/mkdir bash command) are BLOCKED — history belongs in git, not the filename; the `@canonical-ok` (write) / `# canonical-ok` (bash) marker is the only override", "threshold": "warn-only proved too weak — the stale-sibling pileup kept recurring, so the user escalated it to a hard block + added bash-command coverage (a CLI `mv a a_v2.ts` previously slipped past the Write-only guard)", "source": "modules/naming-guard.ts" }, { "id": "PRETOOLUSE_INPUT_FROM_STDIN", "state": "pos-conv", "value": "PreToolUse tool input must be read from STDIN (CC pipes the hook payload there), not only from $CLAUDE_TOOL_INPUT env — that env var is unset by current Claude Code, so reading env-only made every code guard (raw `vastai`/`runpodctl` block, force-push) a silent pass", "threshold": "a session ran raw `vastai`/`runpodctl` despite the ossified NO_RAW_CLOUD_CLI code guard because parseToolInput read an env var CC never set; verified by piping the real {tool_input:{command}} payload on stdin", "source": "modules/pre.ts" }, { "id": "PRETOOLUSE_DENY_SCHEMA", "state": "pos-conv", "value": "pre-hook block must emit hookSpecificOutput.permissionDecision=deny (current PreToolUse schema), not the legacy {decision:block} which current Claude Code ignores — else the guard prints but never blocks", "threshold": "a session ran raw `vastai destroy` despite c11 because emitBlock emitted only {decision:block}+exit0, which CC silently dropped; verified the new schema blocks via the actual `pre bash` hook output", "source": "modules/pre.ts" }, { "id": "ARXIV_RATELIMIT_NOT_NORESULTS", "state": "pos-conv", "value": "arXiv throttles a burst with EITHER a 'Rate exceeded.' text body OR an HTTP 503 page — both have 0 exactly like a genuine empty result. Detect throttle by status>=500 OR /rate exceeded/ OR a non-Atom body (no with totalResults 0. Auto-retry with backoff (notice each round so the agent recognizes a RATE problem), then loudFail with a rate-limit error; NEVER report 'no results' for a throttle", "threshold": "checked only the 'rate exceeded' string, so an HTTP-503 throttle slipped through and the command said 'no results' for a query with thousands of papers", "source": "modules/research.ts" }, { "id": "CODE_GUARD_SECRET_LITERAL", "state": "pos-conv", "value": "hardcoded API keys / private keys / provider tokens are blocked in CODE at write time (before config rules) — a committed secret is an irreversible git-history leak; regex-only enforcement could be disabled by a profile edit", "threshold": "H-SECRET-LITERAL was block-policy but enforcement.json-only; the stdin-input bug (and any profile edit) would have left credential writes unguarded", "source": "modules/secret-guard.ts" }, { "id": "SHADOW_PREMARKER_STALE", "state": "pos-conv", "value": "pre-marker shadows in ~/.claude/commands/ lack SHADOW_MARKER, so refresh skips them as 'hand-authored' and they go stale — 18/22 lost the source description's `Triggers —` line, so bare-text recognition of /fleet /sbs /easy … silently broke even though source commands/*.md were correct", "threshold": "resolved: `sidecar shadow --force` overwrites marker-less collisions from source (source = SSOT); run it once per host to heal pre-marker shadows. Re-breaks only if a NEW marker-less shadow layer is introduced without a heal path", "source": "modules/shadow.ts" }, { "id": "SHADOW_GEN_NATIVE", "state": "pos-conv", "value": "bare /cmd shadows are generated by `sidecar shadow` from sidecar's own commands/ — not by the retired sidecar package, and never baked to a $CLAUDE_PLUGIN_ROOT/sidecar-cache path", "threshold": "a session's /arxiv etc. died because sidecar-generated shadows pointed at a removed ~/.claude/plugins/cache/sidecar path; sidecar now owns shadow generation, marker-tracked + regenerable", "source": "modules/shadow.ts" }, { "id": "SHADOW_MARKER_AFTER_FRONTMATTER", "state": "pos-conv", "value": "the SHADOW_MARKER must be inserted AFTER the YAML frontmatter block, never prepended — Claude Code only parses `description:` when `---` is on line 1; a marker on line 1 pushed frontmatter down so the picker rendered the marker comment as the command's description", "threshold": "if the marker is ever prepended again, every shadow command shows '' instead of its description in the slash picker", "source": "modules/shadow.ts" }, { "id": "SHIP_PROPAGATE_ALL_SURFACES", "state": "pos-conv", "value": "merge+self-update+shadow must run as one unit", "threshold": "adding a slash command then running only pr-cycle+self-update left the shadow mirror stale → /fleet-abstract invisible; ship bundles all three", "source": "modules/ship.ts" }, { "id": "TOOLKIT_FROM_HELP_SSOT", "state": "pos-conv", "value": "the agent-facing command catalog (TOOLKIT.jsonl) is GENERATED from the HELP text in cli/index.ts, never hand-maintained in parallel; `toolkit check` snapshot-diffs committed vs generated so the two can't drift", "threshold": "commands existed + worked but had no proactive catalog/triggers, so the agent didn't know to use them (arxiv/secret blind spot); a hand-kept second catalog would just drift from help", "source": "modules/toolkit.ts" }, { "id": "HELP_CLOSE_DELIM_NEWLINE", "state": "pos-conv", "value": "the closing delimiter search must be the line-start `\\\\n\\\\`;`, NOT a bare `\\\\`;` — the HELP body contains escaped backticks (e.g. `worktree gc\\\\`;`) and a bare-`\\\\`;` search truncated there, silently dropping every command after worktree (atlas/convergence/ing/sync/upstream/verdict)", "threshold": "bare backtick-semicolon search matched an escaped inline backtick mid-body and cut the catalog short", "source": "modules/toolkit.ts" }, { "id": "WORKTREE_GONE_ONLY_PILEUP", "state": "pos-conv", "value": "gc reap widened to [gone] OR HEAD-age>maxAgeDays, yet pileup RECURRED (anima 41 wt / hexa-lang 16 + 8 full clones): gc only scans isAgent paths (.claude/worktrees/*) so sibling-dir (../repo-*), /tmp, and .worktrees/* worktrees are structurally exempt; merged-but-DIRTY worktrees (uncommitted state/ outputs · preserve-state violation) are skipped forever; the wt gauge + gc are cwd-repo-local so cross-repo pileups stay invisible (wt 0 🟢 in sidecar while anima holds 41); full clones named wt-* evade worktree tooling entirely", "threshold": "recurs while gc skips non-agent paths + dirty worktrees and the wt gauge is repo-local; resolve = extend gc scope to all linked worktrees (salvage dirty outputs to state/ first), lab-wide wt scan, purge clone impostors", "source": "modules/worktree.ts" }, { "id": "ARCH_INJECT_IGNORED", "state": "in-prog", "value": "갱신 지시가 트리 앞 괄호에 묻혀 모델이 시켜야만 갱신 — 턴마감 게이트를 트리 뒤(recency)로 이동 + 갱신 시 보고 의무화", "threshold": "ARCHITECTURE inject 가 갱신을 능동적으로 안 하고 사용자 지시에만 반응", "source": "modules/architecture.ts" }, { "id": "INJECT_DOC_SKELETON_ONDEMAND", "state": "pos-conv", "value": "정적 참조 doc(설계 트리)은 매 UserPromptSubmit 풀-주입 금지 — Claude Code canonical 대용량-doc 패턴은 스켈레톤(요약+목차)+온디맨드 Read. 풀덤프는 10KB additionalContext 한도 초과→하네스가 파일-폴백(2KB 프리뷰만)+매 턴 토큰 재지불. 정적 doc=SessionStart/Compact 1회(캐시 prefix 재사용), per-turn 행동규칙(commons·recommend·prefs·load·ing)만 매턴 유지. easy 는 둘을 가진 2-tier: lean 행동지침(styles/easy..md 의 `` 영역)만 UserPromptSubmit 매턴 + gold예시·4종ASCII템플릿·체크리스트 풀 레퍼런스는 SessionStart/Compact 1회 + `easy show` 온디맨드(event 분기 emit · 마커 없으면 풀-폴백 · 런타임 절단 아님). 턴마감 게이트는 Stop 훅으로 결정적 강제, 매턴 리마인더로 흩지 말 것", "threshold": "inject 소스 편집 시 — 정적 doc 을 UserPromptSubmit 에 다시 걸거나 풀-주입으로 되돌리면 재발", "source": "modules/architecture.ts" }, { "id": "convergence-triggers-json-1", "state": "pos-conv", "value": "agent-facing nudge text lives in the DATA file (config/convergence-triggers.json hint field), which OVERRIDES the code fallback in architecture.ts convergenceStopCheck. Editing only the code fallback leaves the live nudge stale (the user spotted the old --id wording still showing).", "threshold": "Updating a nudge/guard message: change the config JSON hint (the authoritative live string the Stop hook reads), not just the code fallback — grep the repo for the old phrasing to catch every copy.", "source": "config/convergence-triggers.json" }, { "id": "config-ts-1", "state": "pos-conv", "value": "sidecar 거버넌스(ship·ing Stop게이트·commit lint)의 발화 게이트는 어떤 opt-in 마커(CLAUDE.md·harness.config.json)로도 키잉하지 않는다 — 마커 키잉은 그 마커 없는 repo(특히 신규)에서 게이트가 silent-dead 되는 비대칭을 낳는다. 관리-프로젝트 개념(isManaged) 자체를 폐기하고, 유일한 바닥은 'git repo 인가'(inGitRepo = REPO_ROOT 에 .git) 하나 — ship/ing/commit 은 git repo 밖에서 의미가 없으므로 이건 opt-in 이 아니라 물리 조건. 모든 git repo(신규 포함) 무조건 발화, non-repo 디렉터리만 조용.", "threshold": "재발조건: 거버넌스가 '새 repo 에서 조용'하거나, '특정 repo만 관리대상'으로 취급하고 싶은 유혹(마커/이름패턴/경로 예외 도입) → 그건 방금 없앤 managed-마커의 부활이자 경로/이름 하드코딩 부채. 해결: 게이트는 inGitRepo 바닥 하나로 유지, 예외 필요시 sidecar-native 옵트아웃(# no-verify-ok / .harness)만 쓰고 이름패턴 하드코딩 금지.", "source": "lib/config.ts" }, { "id": "pre-ts-1", "state": "pos-conv", "value": "preWrite section-aware write-guards (commonsWriteViolation·dodontLengthWriteViolation) run on full-content Write ONLY, never on Edit's new_string fragment — a fragment truncates its boundary ## section body and false-positives it as COMMONS-NO-DODONT (adding ## Project/## Tree to a do/dont CLAUDE.md while anchoring on the next rule's ## header hard-DENIED a legit edit). Edit fragments fall through to commit-time lint backstop (full-file + claudeMdExempt).", "threshold": "재발조건: pre.ts 가 input.content/input.new_string 을 한 content 로 합쳐 section-검증 가드에 넘기면 Edit fragment 가 잘린 섹션으로 오차단. 해결: isEditFragment 로 두 가드만 skip(#267); desc/doc/naming(단일줄·파일명) 가드는 계속 실행.", "source": "modules/pre.ts" }, { "id": "architecture-json-1", "state": "pos-conv", "value": "ARCHITECTURE.json detail 셀이 300자 ARCH-BIG-CELL cap 을 다시 넘기는 회귀 — 특히 게이트/enforce 노드(isManaged·ing/architecture stop-check)에 메커니즘·근거 산문을 욱여넣을 때 한 셀이 슬그머니 cap 초과 → 다음 사람의 lint/ship 게이트를 막는다.", "threshold": "재발조건: detail 셀에 precedents/메커니즘 산문을 누적. 해결: 셀 편집 직후 'sidecar lint' 로 ARCH-BIG-CELL 확인 → kernel(요지)만 남기고 precedents/이력은 CHANGELOG+git 으로, 길면 자식 노드로 분해(commons single-doc c4).", "source": "ARCHITECTURE.json" }, { "id": "pr-cycle-ts-1", "state": "pos-conv", "value": "worktree 에서 pr-cycle/ship/reap 시 'merge failed' 오해 — GitHub squash-merge 성공 후 gh --delete-branch 의 로컬 checkout/branch-delete 가 워크트리 점유로 실패하는 것. [FIXED] pr-cycle merge loop + step4.5 main-wt ff + reap mergeStale(mergedDespiteLocalError) 모두 gh pr view state=MERGED verify 후 성공처리.", "threshold": "재발 4회(#278·#279·수동 4회·reap 초판 anima #2816/#2818 오보고). 재발조건: gh CLI 가 --delete-branch 후처리 방식/오류 문구 변경, 또는 gh pr merge 를 부르는 NEW 호출부에 MERGED-verify 누락 — gh pr merge 신규 호출부는 반드시 mergedDespiteLocalError 류 verify 동반.", "source": "modules/pr-cycle.ts" }, { "id": "commons-md-1", "state": "pos-conv", "value": "harness 매턴 inject 누적 = context-rot: 입력 토큰↑ → transformer 어텐션 분산 → 에이전트 열화(Chroma 18-model 실측 200K창이 50K에서 정확도 손실 · 구조적 속성, 학습으로 해결 안 됨). sidecar 는 매턴 ~7.5K tok(commons 14.6K+recommend 5.8K+CLAUDE+ing+easy) 재주입 → INJECT-OVERSIZED 게이트(lint.injectCaps)가 inject 소스별 byte 천장 강제. 잘라내기(emit-truncate) 금지 — 소스를 직접 트림해 통과.", "threshold": "injectCaps 키 부재/빈값=dead gate(무제한 비대). per-source cap 은 한 파일만 막음 → 여러 소스 합 비대(death-by-a-thousand-cuts)는 injectBudgetBytes 총합천장으로 차단. 언어변종은 turn당 1개만 주입 → dir키(styles/)뿐 아니라 file-key 변형셋(foo.md+foo..md)도 base별 MAX 1회만 총합계상(injectCapViolations groupMax · injects 리포트도 동일). 재발조건: 새 inject cap/budget 누락 · 변형을 이중계상 · 합이 천장 근접 → 소스 트림 먼저. ⚠️ cap 게이트는 그 파일이 STAGED 될 때만 발화 → 초과 상태가 조용히 굳고, 다음 편집자가 자기 diff 와 무관한 남의 초과분을 떠안는다(commons 11486B vs cap 10300 = 1186B 초과 방치). 초과가 드러나면 그 자리에서 상한 아래로 되돌리고(문구압축만으론 -2% 뿐 — 근거 괄호를 버리는 실질 감축 필요), 확보한 여유는 injectBudgetBytes 를 실측치+α 로 내려 래칫할 것. 상한 상향은 최후수단.", "source": "config/commons.md" }, { "id": "audit-ts-1", "state": "pos-conv", "value": "self-scorecard ratio 축은 평생 누적 로그를 읽으면 영구 0 으로 saturate = artifact. quality_gate 는 per-violation errors ÷ per-run lints(차원불일치), cost 는 pre_block 가드발동(=하네스 정상작동)을 ×50 페널티(부호역전). 수정: 최근 WINDOW_DAYS(14d) 창 + 부호교정(가드 catch/block 은 일하는 것이지 결함 아님) → 0→참값(quality_gate 9·cost 10). 로그는 삭제 안 함(증거보존), 읽기만 window.", "threshold": "0/10 축 발견 시: 로그 prune 으로 점수 올리기 금지(tune-to-green·은폐). 공식이 누적로그·역부호 artifact 인지부터 검증 후 metric 자체를 root-cause 교정. literal 만점 강요 금지 — 실제 dev 이력(위반 catch)은 정직히 반영", "source": "modules/audit.ts" }, { "id": "inject-ts-1", "state": "pos-conv", "value": "매턴 inject 가 2 CC surface(settings.json=sidecar install + plugin hooks.json=/plugin)에 동시 배선돼 둘 다 활성이면 같은 블록을 매턴 2× emit = per-turn 토큰 2배 = context-rot(AI 멍청). emitInject 가 프롬프트키 dedup(atomic mkdir락 · 30s TTL)로 매턴 1회만 emit. 잘라내기 아님(전체 1회 emit).", "threshold": "새 UserPromptSubmit inject 는 raw process.stdout.write 대신 emitInject 사용 필수(안 그러면 이중발화 재발). vanish 방지=TTL(빈 프롬프트키로 크로스턴 collide 시 재emit). 재발신호: 컨텍스트에 같은 inject 2번 보이면 dedup 미적용 의심", "source": "lib/inject.ts" }, { "id": "setup-ts-1", "state": "pos-conv", "value": "거버넌스 규칙(pr-cycle/ship 진입 등)을 per-turn inject 텍스트로만 실으면 advisory 라 실제 강제가 안 된다 — inject 는 '상기'일 뿐, 진짜 강제는 Stop[] 에 decision:block stop-check 를 배선해야 성립. 또 라이브 Stop 훅의 SSOT 는 글로벌 ~/.claude/settings.json(setup.ts hookSpec)이고 plugin hooks.json 는 /plugin 설치용 별개 표면 — 새 Stop 게이트는 둘 다 배선 + install-hooks 재실행해야 라이브 반영된다.", "threshold": "재발조건: 새 거버넌스 규칙을 commons/CLAUDE.md inject 로만 추가하고 Stop 게이트 미배선, 또는 setup.ts/hooks.json 한쪽만 배선. 실사례: `ing stop-check` 는 hooks.json 에만 있고 setup.ts Stop[] 에 없어 라이브 ~/.claude/settings.json 에서 평생 한 번도 안 떴다 — inject 문구는 \"차단\"을 약속했는데 이빨 0(=자율 준수). 해결: modules/.ts stop-check → setup.ts Stop[] + hooks.json 양쪽 → install-hooks --global → `grep ~/.claude/settings.json` 으로 라이브 존재를 눈으로 확인 → 스모크.", "source": "modules/setup.ts" }, { "id": "git-checkout-guard-ts-1", "state": "pos-conv", "value": "브랜치 보호 가드는 (1) HEAD 이동(checkout/switch) 과 (2) 체크아웃 없는 SHARED main ref 조작(git branch -f/-M/-m/-D/-d/-C/-c main) 둘 다 막아야 #3559 클래스(병렬세션 clobber) 완결. ref-mutation 은 공유 ref store 라 링크드 워크트리에서도 위험→무조건 차단(isMainWorktree 게이트 금지, 그건 checkout 전용). BUT args 스캔은 반드시 단일 git-branch 문장으로 스코프 — 안 하면 compound(feature 삭제 && fetch origin main)의 뒤 bare main 을 오인해 feature 삭제를 오차단(false-positive). 정상 ff(fetch origin main:main·merge --ff-only)는 mutating flag 무사용→통과=자폭 안 함.", "threshold": "재발조건: (a) 새 조작형태(update-ref refs/heads/main 등) 미커버 (b) main-ref-move 를 isMainWorktree 로 좁혀 링크드 우회 허용 (c) args 를 커맨드 전체로 스캔(문장경계 미적용)해 compound false-positive. 해결: 벡터별 detector + args 를 다음 git/셸연산자(|;&<>)에서 컷 + pre-bash stdin 스모크(block AND allow 양쪽, compound 포함)로 검증.", "source": "modules/git-checkout-guard.ts" }, { "id": "architecture-ts-1", "state": "pos-conv", "value": "ARCHITECTURE 트리 노드 대상 rule 게이트(id 강제·hygiene)는 노드 판정을 name 키 유무로만 하면 안 된다 — role/detail(역할/상세)만 있고 name 없는 노드도 뷰어엔 렌더되는 실노드인데, name-키잉 판정은 그걸 노드로 안 쳐서 id 강제를 통째 우회시킨다. rendered 판정 = name|role|detail(+legacy 이름/역할/상세) 로 넓혀야 모든 실노드가 id 강제된다.", "threshold": "재발조건: 트리 노드 검사를 obj['name'] 유무로 키잉 → name 없는 role/detail 노드가 silent 통과(id 강제 우회). 해결: isRenderedNode=name|role|detail 로 판정하고 구조래퍼(children-only)·columns(key/label)·convergence 는 계속 제외. 픽스처(name-noid + role-noname) 둘 다 block 되는지로 검증.", "source": "modules/architecture.ts" }, { "id": "pre-ts-2", "state": "pos-conv", "value": "parseToolInput/preWrite must gather input.edits[].new_string (MultiEdit shape has NO top-level content/new_string) into the scanned content — else every content-scanning write guard (INJECT-NON-ENGLISH english-only, secret-literal, portable-path, naming) silently passes MultiEdit. MultiEdit is an isEditFragment (partial fragments) so section-aware + byte/size caps still skip it.", "threshold": "piped {tool_input:{file_path:'/x/CLAUDE.md',edits:[{new_string:'한글'}]}} to sidecar pre write and confirmed it emitted permissionDecision:deny (was exit-0 silent pass before the fix); english-only + backtick-exempt MultiEdit cases still allow (state/pre-korean-multiedit-smoke/run.sh ALL PASS).", "source": "modules/pre.ts" }, { "id": "shadow-ts-1", "state": "pos-conv", "value": "sidecar 명령 이름이 Claude Code 기본 슬래시(/model·/config·/review·/compact·/init·/pr 등)와 겹치면, 셰도우로 미러된 bare /cmd 가 기본 명령에 가려져 picker 에서 안 보이거나 모호해진다(=우리 명령이 '반영 안 됨'처럼 보임). 실제 예: 모델 레지스트리 /model → CC 기본 /model 충돌 → /models rename.", "threshold": "재발조건: 새 명령을 CC 기본 슬래시와 동명으로 add. 해결: 네이밍 시 CC 기본 슬래시 목록과 대조 → 충돌 시 복수형/접두어로 회피(model→models). 이미 배포됐으면 modules/·cli 등록·commands/.md·상호참조·ARCHITECTURE 노드·TOOLKIT·CHANGELOG 일괄 rename 후 ship(shadow 가 orphan prune).", "source": "modules/shadow.ts" }, { "id": "index-ts-1", "state": "pos-conv", "value": "cli/index.ts 의 HELP 는 backtick 템플릿 리터럴 — help/description 절에 raw 백틱을 넣으면 리터럴이 조기 종료돼 esbuild 파싱이 통째 붕괴(Expected semicolon) → sidecar 명령 전부 죽음. 백틱 대신 작은따옴표를 쓰거나 이스케이프. 치명: self-update 는 그 깨진 CLI 로 돌아 스스로 못 고침(닭-달걀) → 설치 클론을 git 으로 origin/main 에 직접 하드동기화해야 복구.", "threshold": "재발조건: cli/index.ts HELP 절에 backtick 포함 텍스트 추가(modules/CLAUDE.md gotcha 무시). 해결: 작은따옴표/이스케이프 + ship 직전 'sidecar help >/dev/null' 스모크로 파싱 검증.", "source": "cli/index.ts" }, { "id": "fable-md-1", "state": "pos-conv", "value": "CC 슬래시 '!' 원라이너의 $ARGUMENTS 는 셸 실행 전 텍스트 치환 — 어떤 인용 전략(비인용→괄호/글롭 붕괴 · 쌍따옴표 감싸기/stdin 파이프→프롬프트 속 쌍따옴표에서 붕괴)도 임의 자유텍스트를 셸라인에 안전하게 못 넣는다. 자유텍스트 인자를 받는 커맨드는 '!' 원라이너 금지 → 런북형(에이전트가 Write 로 파일 저장 → --file 플래그)으로 배선.", "threshold": "재발조건: 자유텍스트 인자 커맨드(commands/*.md)를 '!' + $ARGUMENTS 인라인으로 배선. 해결: 런북형 Write→--file + 특수문자(따옴표·괄호·글롭·백틱) QA 케이스.", "source": "commands/lab.md" }, { "id": "fable-ts-1", "state": "pos-conv", "value": "헤드리스 claude -p 자식은 전역 ~/.claude/settings.json 의 거버넌스 Stop-훅(architecture/convergence stop-check)을 상속한다 — cwd 가 미커밋 변경 있는 git repo면 자식이 답을 낸 뒤 그 Stop-훅이 블록→계속 루프를 걸어 프로세스가 안 죽고 --timeout 에 exit 124 로 킬되며, 최종턴 .result 도 발산 대신 거버넌스 메타로 덮인다. 해결=자식을 --setting-sources project,local 로 띄워 전역 user 소스(거버넌스 훅)를 걷어냄(keychain 인증은 설정소스가 아니라 유지 · --bare 는 인증까지 날려 Not-logged-in 이라 불가).", "threshold": "재발조건: headless claude -p 를 전역 훅 상속(기본 setting-sources) + dirty git repo cwd 에서 실행. 해결: lab fable 기본 --sources project,local, 전체상속 필요시 --sources user,project,local + 사전 커밋.", "source": "modules/lab.ts" }, { "id": "inject-ts-2", "state": "pos-conv", "value": "inject 의 additionalContext 는 반드시 emitInject() 경유 stdout 의 {hookSpecificOutput:{hookEventName,additionalContext}} JSON 으로 내야 함 — Claude Code 가 읽는 유일 채널. process.stderr.write raw 텍스트는 additionalContext 로 안 읽혀 payload 통째로 조용히 버려짐(에이전트 도달 X). toolkit(명령카탈로그)·companions(이웃CLI)가 stderr 로 내고 있어 두 카탈로그가 여태 에이전트에 도달 못 함 → 'sidecar 명령 아예 인지 못함'(fable). 훅 이벤트명은 STDIN 페이로드에서, STDIN 없으면 조용.", "threshold": "재발조건: 새 inject 를 emitInject 대신 stdout/stderr.write 직접 방출(특히 stderr). 해결: emitInject(name,ev,text)+ev는 readStdin JSON. 검증=printf '{\"hook_event_name\":\"SessionStart\"}'|sidecar inject → stdout JSON 확인.", "source": "lib/inject.ts" }, { "id": "pr-cycle-hook-ts-1", "state": "pos-conv", "value": "punt 방지의 정답은 감지·경고·Stop게이트가 아니라 명령 자체의 코드수준 자동완결 — pre bash 가 `gh pr create` 를 재작성(updatedInput + permissionDecision:allow 필수, allow 없으면 CC 가 rewrite 무시)해 squash-merge·워크트리 정리까지 원자 실행(archive_sidecar pr-cycle-hook 정답지 1:1 포팅). goal-guard 식 '되묻기 감지' 접근은 오답으로 폐기(사용자 지시).", "threshold": "재발조건: 에이전트가 머지 여부를 되묻거나 PR 만 만들고 멈춤 · rewrite 가 allow 없이 emit 되어 무시됨. 해결: pre bash rewriter 유지(대량삭제 deny 게이트 포함) — 감지형 게이트로 되돌리지 말 것.", "source": "modules/pr-cycle-hook.ts" }, { "id": "goal-guard-ts-1", "state": "pos-conv", "value": "goal-guard Stop-hook 캐치(잔여 remnant + infra-punt)는 흔한 키워드('잔여'·'infra'/'인프라')를 쓰므로 정밀도가 반복 이슈다(#376 잔존→#383 잔여예외→infra 3rd catch). 공통 오탐패턴: ① 정당한 session-terminal인데 잡힘 ② 답변이 키워드 자체를 논의/인용(따옴표·백틱·kebab-slug)하는데 잡힘 ③ infra처럼 흔한 단어는 단독 언급도 잡힘. 해결원칙=좁은 AND 매칭: 잔여=whole-message BLOCKER_RE+ING_NEXT AND 예외 · infra=punt동사(whole)+키워드+벽신호(same-line) 3중 AND + fix부정형(line) · 공통 따옴표 skip + hasSessionTerminalBlocker 예외 재사용. no-escape-hatch: CONCRETE 신호로 좁혀 남용차단, defer경로 독립.", "threshold": "재발신호=정당한 종료(블로커·infra 외부의존)가 오차단 · 가드/커밋요약 답변이 오탐 · 흔한단어 단독언급 오발화. 4번째 재발(2026-07-13, #405): 교훈이 goal-guard 안에만 갇혀 architecture 의 Stop 스캐너 3종(convergence·gate·result)이 각자 patterns.find(includes) 를 손으로 굴리다 백틱 인용된 예시 토큰(설명문의 게이트 이름)에 자기 자신이 발화. 해결=매칭을 좁게(AND·same-line·CONCRETE 명사) 유지 + 실측 오탐만 보고 확장(넓히면 escape-hatch) · **어느 모듈이든 새 Stop 신호 스캐너를 추가할 때 patterns.find(hay.includes) 를 손으로 쓰지 말고 공용 매처(architecture findSignal → goal-guard isQuotedMention)를 반드시 경유**할 것 (quote-skip + session-terminal 예외 + 좁은 AND 3장치 재사용).", "source": "modules/goal-guard.ts" }, { "id": "fable-ts-2", "state": "pos-conv", "value": "job-id 등 인자를 join(baseDir, id) 로 리졸브할 때 id 가 빈 문자열이면 join(base,'')===base 가 되어 existsSync(base) 를 오탐 통과 → 존재하지 않는 대상을 '유령 job'(exitcode·pid 없는 것)으로 오인해 'process gone — likely crashed' 같은 엉뚱한 진단을 낸다. 해결=경로 리졸버 입구에서 무효(빈) id 를 먼저 가드(if(!id) return null) 해 깔끔한 no-such/usage 에러로 떨군다.", "threshold": "재발조건: join(baseDir, userArg) 결과를 existsSync 로만 검사하며 userArg 가 빈 문자열/undefined 일 수 있는 옵셔널-id 서브커맨드(lab result/wait/tail 등). 해결: 리졸버 최상단 빈-id 가드 + 옵셔널 id 서브커맨드는 usage 에러 경로 확보.", "source": "modules/lab.ts" }, { "id": "pool-ts-1", "state": "pos-conv", "value": "sidecar pool on : 로컬 ssh 가 죽어도(--timeout SIGKILL·ctrl-C·링크 끊김) 원격 커맨드 트리는 안 죽는다 — PTY 없는 sshd 는 파이프만 닫고 SIGHUP 을 보내지 않기 때문(실측 확인: HUP 트랩은 아예 발화하지 않음). 그래서 원격 자손이 init 으로 reparent(PPID=1)돼 RSS 를 문 채 잔류한다. 루프/재시도형 커맨드면 자기증식 fork storm 이 되고(자식만 reap 하면 생산기가 재생성 — 반드시 PPID=1 생산기 트리를 죽여야 함), 대용량 hexa 잡이면 cgroup throttle(D-state, wchan=mem_cgroup_handle_over_high)에 갇혀 호스트를 '다운'으로 오진시킨다(2026-07: 15GB iv_s 고아가 summer 를 25일간 죽은 호스트로 만듦). ✅ FIXED — fence() 가 커맨드를 백그라운드로 띄우고 자기 PPID 를 폴링하다 1 이 되면(=sshd 사망=고아화 신호) kill -- -$$ 로 프로세스그룹 전체를 수거한다. 그룹 멤버십은 reparent 후에도 유지되므로 이미 고아가 된 손자까지 닿는다. A/B 실측: 생 ssh=고아 2 생존 · fenced pool on=0. EXIT 는 의도적으로 트랩하지 않음(정상 종료 시 nohup 데몬은 살려둬야 함).", "threshold": "재발신호=호스트 부하 급등 + 동일 프로세스 떼가 D-state(wchan=mem_cgroup_handle_over_high)로 누적 · 풀 호스트가 🔴 인데 ssh 인증은 통과. 재도입 금지=pool on 의 원격 exec 를 fence() 없이 생 cmd 로 되돌리는 것, HUP 트랩만으로 충분하다고 믿는 것(PTY 없이는 HUP 이 안 옴). 이미 생긴 고아=자식 말고 PPID=1 생산기 트리를 kill.", "source": "modules/pool.ts" }, { "id": "ship-ts-1", "state": "pos-conv", "value": "governance Stop-게이트의 '마커 존재=통과' 탈출구는 검증이 아니라 키워드 매칭이라 임의 텍스트(🚢 SHIP: 보류(귀찮음))로 뚫린다. 더 깊은 결함: marker 체크를 dirty-tree 트리거보다 FIRST로 두면 marker가 global bypass가 되어 'done' 텍스트 주장이 dirty tree 위에서도 통과(텍스트가 트리로 증명가능한 상태를 위조). 해결=① 결정적 상태검증(clean tree=유일한 done)을 FIRST로 재배치, marker는 last-resort deferral 전용 ② 탈출구를 CONCRETE 클래스로 검증(foreign WIP·session-terminal 블로커·user 지시·다단계+ing next) ③ goal-guard의 BLOCKER_RE/ING_NEXT_RE/isQuotedMention 재사용해 클래스목록 single-SSOT. goal-guard-ts-1의 좁은-AND 원칙을 ship 게이트로 확장.", "threshold": "재발조건: Stop-게이트 탈출 마커를 정규식 존재검사만으로 통과 · marker 체크를 상태검증(git diff)보다 먼저 배치 · 성공을 텍스트로 신뢰. 해결: 상태검증 FIRST(텍스트-위조불가 경로) + 탈출구 CONCRETE 사유 클래스 AND 매칭 + 클래스목록 gate간 single-SSOT + 따옴표 meta-skip 재사용.", "source": "modules/ship.ts" }, { "id": "architecture-ts-2", "state": "pos-conv", "value": "ARCHITECTURE.json 라이터가 전체 트리를 고정 indent(=2)로 재직렬화하면, indent=1로 authored된 downstream repo(예: hexa-lang) 파일을 만질 때마다 파일 전체가 재직렬화돼 13k줄 churn diff → 스택 PR 재게이트(architecture-json-1 4× 재발). 해결=쓰기 전 기존 파일의 indent 폭을 감지(detectJsonIndent: /^\\{[^\\n]*\\n( +)\"/)해 보존, 신규/모호 파일만 canonical 2. 부분삽입(splice) 라이터(models/dataset)는 국소 블록이라 mass-churn 아님(별개 경미사안).", "threshold": "재발조건: ARCHITECTURE.json(또는 임의 JSON SSOT)을 JSON.stringify(root,null,고정폭)로 전체 재직렬화하면서 대상 파일의 기존 indent를 무시. 해결: 전체-재직렬화 라이터는 반드시 기존 indent 감지·보존(detectJsonIndent), 고정폭 강제 금지.", "source": "modules/architecture.ts" }, { "id": "owner-trust-1", "state": "pos-conv", "value": "AI assistants over-refused/over-reacted to owner-supplied credentials (keys·passwords·tokens) on the owner's OWN single-user machine — refusing, 'secret exposed!' alarms, and demanding key rotation merely because the owner viewed/pasted their own secret in a 1:1 channel. Root cause: no rule separated 'owner hands own secret to own assistant in a 1:1 channel' (legit) from 'secret reached a shared/public surface' (the real risk). Added commons owner-trust.", "threshold": "Recurs when an agent treats owner-in-1:1 credential handling as a leak. Fix = commons owner-trust: accept+use owner creds on their own box, prefer the secret vault by reference, advise rotation ONLY on a genuine shared/public/committed leak; never over-refuse/over-alarm.", "source": "owner-trust" }, { "id": "pr-cycle-ts-2", "state": "pos-conv", "value": "머지 게이트를 브랜치 보호에 위임한 것이 근인. 보호는 REQUIRED 부분집합만 강제하고, pr-cycle은 첫 시도부터 --admin이라 그마저 우회 → required 밖 체크가 전부 RED여도 머지된다. 2026-07-13 실측: hexa-lang #4930이 15개 RED(selfhost byte-eq·faithful-nobaseline 3타깃·own-link determinism/parity·cfallback-zero)인 채 reap에 수확 머지 — 유일한 required인 selfhost-gates-summary만 초록이었기 때문. codegen flip이 main 빌드를 깨뜨려(aprime_cc FATAL) 되돌려야 했다(#4932). 사람이 아니라 툴이 저지른 머지. FIX = 게이트를 툴 안으로 가져옴: pr-cycle이 매 머지 시도 전 전체 statusCheckRollup을 읽어 FAILURE면 실패 게이트 이름과 함께 거부, 미결이면 대기·재시도, 전체 그린일 때만 gh pr merge 도달. reap의 mergeStale도 plain 머지 전 checksGreen 요구(기존엔 --admin 폴백 앞에만 있었음). pr-cycle-ts-1(머지 성공을 실패로 오독=false-negative)과 반대 방향의 결함=false-positive.", "threshold": "재발조건: required 체크 집합이 실제 게이트 집합보다 좁은 repo에서 자동 머지(대부분의 repo가 그렇다). 진단: 머지된 PR의 statusCheckRollup에 FAILURE가 남아있으면 확진. ⚠️함정: 진행중 체크의 conclusion은 null이 아니라 빈 문자열 — jq의 // \"PENDING\" 폴백이 안 먹으므로 미결을 실패로 오분류하면 CI 도는 모든 PR을 가짜 RED로 거부하게 된다(미결=pending으로 취급할 것). gh pr merge 신규 호출부는 반드시 full-rollup 게이트 동반.", "source": "modules/pr-cycle.ts" }, { "id": "architecture-ts-3", "state": "pos-conv", "value": "search 의 walk 과 lint 의 ARCH-ID-MISSING 은 'rendered node' 판정을 공유해야 한다 — rendered = name/role/detail 중 아무거나. search 만 nodeName 유무로 게이트해서 c4 스켈레톤의 name-less {id, role} 트리는 전 노드가 스킵됐다(방문하되 한 건도 검사 안 함 ⇒ 파일에 그대로 있는 단어가 0 hit). lint 는 이미 같은 이유로 name 의존을 벗어났는데 search 만 안 따라옴", "threshold": "노드 판정 로직 수정 시: 한쪽(lint/search/inject/viewer)만 고치면 두 절반이 rendered-node 정의를 다르게 본다 — 판정을 공유하고 name 없는 트리로 회귀 테스트. 부수효과: ARCH-ID-FORMAT 의 명분(searchability)은 점 표기도 부분문자열 검색이 정상이라 kebab-case 를 정당화하지 못함", "source": "modules/architecture.ts" }, { "id": "config-ts-2", "state": "pos-conv", "value": "lint 규칙의 임계/형식은 config 로 노출한다 — archCellCap 은 처음부터 config-driven 인데 형제인 ARCH-ID-FORMAT 정규식만 하드코딩이라, 부모를 id 에 담는 의도적 관례(mech-3.falsifier · L4.arms)를 쓰는 저장소는 트리 의미를 뭉개거나(L1.one-bit-seam→l1-one-bit-seam 은 L1=Law 1 정보 상실) 게이트를 영구 우회하거나 둘 중 하나였다", "threshold": "새 lint 규칙에 임계/패턴을 넣을 때: 하드코딩하면 정당한 반례 저장소에 탈출구가 없어 게이트가 영구 우회된다(anima-v4: 103노드 중 96 위반 → 첫 커밋부터 게이트 우회 = 자기 엄격함으로 무력화). 기본값은 유지하되 config 키로 노출하고, 규칙의 명분이 실제로 그 형식을 요구하는지 확인하라 — ARCH-ID-FORMAT 의 'searchability' 명분은 점 표기도 부분문자열 검색되므로 kebab 을 정당화하지 못했다", "source": "lib/config.ts" }, { "id": "toolkit-inject-no-cut", "state": "pos-conv", "value": "an inject EMITTER that byte-slices content at render time (toolkit makeInject: use.slice(0,140), triggers.slice(0,6)) is a silent truncation that VIOLATES inject-lint and drops content the agent never sees — it hid 'lab full=BOTH parallel' from the SessionStart catalog, so the agent could not learn lab full runs both models in parallel. Fix pattern: the inject carries a COMPLETE-but-lean unit (HELP line 1, emitted verbatim, capped at author time by toolkitUseViolations ≤200B), overflow lives in a JSONL-only detail field; NEVER re-add a .slice in an emitter — trim the SOURCE.", "threshold": "재발조건: inject 에미터(renderInject 등)에 .slice(0,N)/tail-cut 재도입. 해결: use는 verbatim emit + author-time toolkitUseViolations 게이트, detail은 JSONL 전용; source(HELP line1)를 trim, 커팅 금지", "source": "modules/toolkit.ts" }, { "id": "turn-close-ts-1", "state": "pos-conv", "value": "거버넌스 leg 를 '저장소 존재'로 키잉하면 저장소 없는 repo 에서 규율이 silent-dead 된다 — config-ts-1(opt-in 마커 키잉)의 같은 결함이 turn-close activeLegs 전체(arch:!!designSsot · conv:!!json · ing:!!ing-ref)에서 재발했다. 부재 = 게이트 OFF + inject 무음 + 요구를 하는 주체 0. 저장소 생성이 마찰 0 이어도(ING 은 `ing add` 가 즉석 ref 생성) 자립하지 않는다: commons `ing-board` 는 보드-상태를 모르는 일반 문구라 '이 repo 는 보드가 없고 🔄 leg 이 꺼져 있다'를 아무도 말하지 않았다. 필요한 것은 저장소별 '부재 상태 + 무장 동작' 신호(부재일 때만 발화하는 씨앗) + 마찰 0 의 생성 경로 둘 다. 옵트아웃(archSeed·ingSeed:false)만 허용하고 옵트인 마커는 금지.", "threshold": "재발조건: 새 거버넌스 leg/inject 를 '그 저장소가 있으면 켠다'로 키잉 → 저장소 없는 repo(=그 규율이 가장 필요한 신규 repo)에서 요구가 통째로 사라짐. '생성이 쉬우니 자립한다'는 반례(ING)로 반박됨 — 쉬운 생성경로는 필요조건일 뿐. 해결: 저장소마다 부재-내성 씨앗(부재일 때만 emit·존재하면 0B)을 매턴 inject 에 함께 걸고, 예외는 옵트아웃 config 로만. 엔진이 대신 저장소를 만들어 leg 를 무장시키지 말 것(placeholder 가 게이트를 속임) — authoring 은 에이전트, 스텁 잔존은 lint 로 차단. 무조건 명령 금지: 가짜 항목으로 씨앗을 끄면 게이트가 허구에 무장된다.", "source": "modules/turn-close.ts" } ] }, "columns": [ { "key": "name", "label": "name", "tree": true }, { "key": "role", "label": "role" }, { "key": "id", "label": "id" }, { "key": "detail", "label": "detail" } ], "tree": { "name": "sidecar", "role": "프로젝트-무관 AI 코딩 사이드카 — 작업 흐름에 끼어드는 게이트 집합", "id": "sidecar", "detail": "엔진(.ts)은 모든 repo 공유, 규칙(JSON)만 주입. 1–4층은 5층(규칙 데이터)을 하드코딩하지 않는다.", "children": [ { "name": "surfaces — 5가지 통합 지점", "role": "에이전트가 무언가 하기 직전/직후 = 사이드카가 끼어들 자리", "id": "group-surfaces", "detail": "어떤 에이전트 런타임이든 이 5 hook으로 환원된다.", "children": [ { "name": "UserPromptSubmit", "role": "사용자가 말함 → prompt", "id": "surface-userpromptsubmit", "detail": "키워드→플레이북 매핑, 위험어→힌트. 결과: stderr(컨텍스트 주입)" }, { "name": "PreToolUse(Bash)", "role": "도구 호출 직전 → pre bash", "id": "surface-pretooluse-bash", "detail": "코드레벨 가드(아래 children) 를 먼저 평가 → 그 뒤 config enforcement 정규식 매칭(backup). 코드 가드는 config 보다 먼저 실행되고 끌 수 없다(profile 편집 무력화 방지) · 인라인 `# ...-ok` 마커만 예외. block=stdout JSON / warn=stderr", "children": [ { "name": "force-push 가드", "role": "히스토리 재작성 push 차단", "id": "guard-force-push", "detail": "blind --force/-f/+refspec 차단 · --force-with-lease 허용 · `# force-ok` 이스케이프 — config H-FORCE-PUSH 와 동일 정책" }, { "name": "branch-switch 가드", "role": "메인 워크트리 브랜치 전환 차단", "id": "guard-branch-switch", "detail": "modules/git-checkout-guard.ts — MAIN 워크트리에서 HEAD 이동하는 `git checkout `/`-b`/`switch` 차단(병렬 세션 untracked 작업 손실 #3559 방지). 링크드 워크트리·파일복원(`checkout -- path`)은 면제 · 모호한 `checkout ` 는 rev-parse 확인 · `-C ` 우회도 차단 · config `git.guardBranchSwitch`(기본 ON)." }, { "name": "main-ref-move 가드", "role": "체크아웃 없이 main ref 갈아치우기 차단", "id": "guard-main-ref-move", "detail": "git-checkout-guard.ts detectMainRefMove — 체크아웃 없이 SHARED main/master ref 를 `git branch -f`(재지정)·`-M/-m`(개명)·`-D/-d`(삭제)·`-C/-c`(복사)로 갈아치우는 형태 차단(branch-switch 자매·#3559 동종 · 공유 ref 라 모든 워크트리에서 위험→isMainWorktree 비게이트). feature 브랜치 삭제/개명·`fetch main:main` ff 는 통과 · config `git.guardBranchSwitch` 공유." }, { "name": "off-main-edit 가드", "role": "메인 워크트리가 non-default 브랜치일 때 Write/Edit 차단 (main 고정)", "id": "guard-off-main-edit", "detail": "modules/pre.ts preWrite (GIT-EDIT-OFF-MAIN) — main 워크트리가 non-default 브랜치에 주차된 채 Write/Edit 하는 걸 차단(stale-branch 함정 #3736 · branch-switch 가드의 편집측 대칭). Write·Edit 양쪽 발화 · non-default HEAD 만 probeGitContext(gcwd) 확인 후 차단 · 격리 워크트리 면제 · config `git.guardOffMainEdit`(기본 ON)." }, { "name": "stale-main-edit 가드", "role": "main 이 origin 보다 뒤처진 채 upstream 변경 파일 Write/Edit 차단", "id": "guard-stale-main-edit", "detail": "modules/pre.ts preWrite (GIT-EDIT-STALE-MAIN) — off-main-edit 의 on-main 대칭: main 이 origin/ 보다 뒤처졌고 upstream 커밋이 대상 파일을 건드리면 deny(낡은 base 편집=중복작업+충돌). TTL fetch 후 판정(git.guardStaleMainEdit, staleFetchTtlSec) · 무관 behind=warn · 격리 워크트리 면제 · 스모크 state/stale-main-edit-smoke." }, { "name": "pr-cycle 자동사이클 rewriter", "role": "gh pr create → 머지+정리까지 코드수준 자동 완결 (재작성)", "id": "guard-pr-cycle-rewrite", "detail": "modules/pr-cycle-hook.ts (pre bash · archive 포팅) — `gh pr create` 를 updatedInput+allow 로 재작성: `&& gh pr merge --squash --admin --delete-branch` + 링크드 워크트리 정리 + stacked --base→default 강제 · 대량삭제(D>50, D≥10×A)=deny · --draft/명시 merge/MARK 면제 · 옵트아웃 없음 · 스모크 state/pr-cycle-hook-smoke." }, { "name": "cloud-raw 가드", "role": "raw 클라우드 CLI 직접 호출 차단", "id": "guard-cloud-raw", "detail": "c11 — 구조화 dispatch 를 우회하는 raw runpodctl/vastai/ssh/scp/curl 직접 호출을 가드(`hexa cloud`/pod 흐름으로 유도)" }, { "name": "danger 가드", "role": "파괴적 명령 차단", "id": "guard-danger", "detail": "no-verify·reset-hard·curl|sh 는 상시 always-on · rm-rf-root(루트 자체 /, /*, ~, $HOME, *)는 config `dangerGuard.rmRfRoot` 토글 · 기본 false=OFF(opt-out)" }, { "name": "hand-rolled-fanout 가드", "role": "손수 fanout 루프 경고", "id": "guard-hand-rolled-fanout", "detail": "warn — copy-to 안의 손수 런처 fanout 은 구조화 dispatch·비용계상을 우회 → `fire-shards` 로 유도" }, { "name": "mem-guard", "role": "OOM 예방", "id": "guard-mem", "detail": "background-spawn(`&`/nohup/disown) 직전 free-RAM 체크 → warnPct 이하 warn·blockPct 이하 block · OOM jetsam 예방" }, { "name": "naming-guard", "role": "비-canonical 작명 차단(생성) + 터치 시 넛지", "id": "guard-naming", "detail": "버전/복사 접미사 파일·폴더명(`_v2`·`_copy`·`foo 2`…). 새 생성 BLOCK → canonical update-in-place 강제(Write/Edit + Bash mv/cp/touch/mkdir). 기존 비표준 파일 터치는 warn-only 넛지(생성만 막던 갭 — 편집 허용해 고치게). c25 · test/spec 제외. 면제는 자식 노드 참조.", "children": [ { "name": "면제 2단", "role": "파일 단위 · 디렉터리 단위 면제", "id": "detail-naming-exempt", "detail": "① 파일 단위 = 내용의 `@canonical-ok` / bash 의 `# canonical-ok`. ② 디렉터리 단위 = `.canonical-ok` 마커 — 그 디렉터리(또는 상위)에 있으면 서브트리 전체 면제, Write/Edit·Bash 양쪽(한쪽만이면 `mkdir zone/x` 막혀 반쪽), `.git` 에서 탐색 중단(경계 밖 마커가 남의 레포 면제 못 함). 용도=오너 선언 실험구역(예 `anima-v2/`) — 파일마다 마커는 세금이라 구역을 한 번 선언." } ] }, { "name": "portable-path 가드", "role": "SHIPPED 스크립트의 하드코딩 홈경로 넛지 (이식성)", "id": "guard-portable-path", "detail": "modules/portable-path-guard.ts (PORTABLE-PATH · preWrite advisory) — SHIPPED 런타임 스크립트(hooks/·commands/·skills/·bin/·pi/ 의 .sh/.py/.ts 등)가 절대 홈경로(`/Users/…`·`/home/…`) 하드코딩 시 warn(타 host/user 에서 깨짐 → `$HOME`·`${CLAUDE_PLUGIN_ROOT}` 런타임 해석). Write·Edit · config `portablePathGuard`(기본 ON) · archive 포팅." }, { "name": "single-doc(scatter) 가드", "role": "흩어진 문서 차단", "id": "guard-single-doc-scatter", "detail": "scatter 파일명(`*-report/summary/notes/plan/guide.md`·`UPPERCASE.md`·날짜접두 `.md`) 생성 BLOCK(scope 무관·`DOC-SCATTER`) → ARCHITECTURE(SSOT)+CHANGELOG(append) 통합 · quickref 규율(`DOC-NO-QUICKREF`)은 scopeDirs 한정 warn · `docs.allow` 리스트·`docs.enforce:off` 로 면제 (commons single-doc)" }, { "name": "commit-lint 게이트", "role": "agent git commit 에 lint 강제 (글로벌)", "id": "guard-commit-lint", "detail": "`COMMIT-LINT` — agent `git commit` 감지 시 모든 git repo(inGitRepo · managed-마커 폐기 · config 없으면 defaults)에서 `collectViolations`→`lintBlockers` 실행, block 위반이면 차단. per-repo git 훅과 동일 lint 를 글로벌 settings.json 경로로 훅-미설치 repo 까지 강제. `-a/-am`=tracked-modified 스코프 확장 · `--no-verify`/`# no-verify-ok` escape 공유" } ] }, { "name": "PreToolUse(Write)", "role": "쓰기 직전 → pre write", "id": "surface-pretooluse-write", "detail": "경로·내용·우회패턴 매칭 + write-time hard-deny(상세 4g/4h): SKILL-DESC-CAP/CMD-DESC-LONG · COMMONS-PROSE/NO-DODONT · DODONT-LONG/SECRET-LITERAL/HANDOFF-SCATTER · INJECT-NON-ENGLISH+CLAUDE-MD/COMMONS-OVERSIZED+HISTORY(CLAUDE.md·commons.md 저장시점 한글+byte cap 8000+이력 · commit 백스톱). stdout JSON(deny)/stderr(warn)" }, { "name": "PreToolUse(MCP)", "role": "MCP 툴 호출 직전 → pre tool", "id": "pre-mcp", "detail": "modules/annotation-guard.ts — matcher `mcp__.*`. MCP 툴은 그간 무가드(Bash/Write만). 훅에 annotation 부재 → config 레지스트리(`config/tool-annotations.json`·repo override)로 tool명→hint(readOnly/destructive/openWorld/sensitive) 분류 후 Rule-of-Two: mutation=warn · destructive+openWorld=block. toggle `annotationGuard`." }, { "name": "PostToolUse", "role": "도구 호출 직후 → post bash / edit", "id": "surface-posttooluse", "detail": "exit/파일경로로 실패 라우팅, L0 편집 경고, c6 ing-staleness(코드 편집 카운트 bump). errors 큐 / stderr" }, { "name": "Stop", "role": "에이전트 응답 종료 → 마지막 assistant 텍스트 기반 marker 게이트들 + staleness warn", "id": "surface-stop", "detail": "transcript 의 마지막 assistant 텍스트를 읽어 후처리하는 Stop 훅 (아래). decision:block marker 게이트(recommend·convergence·ing·architecture·ship) + warn-only(staleness·context-rot·prefs-drift·wt-stranded). 공통 리더 lastAssistantText(modules/recommend.ts · export).", "children": [ { "name": "recommend stop-check", "role": "박스로 종료 시 auto-proceed 강제 (정밀 decision:block)", "id": "guard-recommend-stop-check", "detail": "non-present default mode 인데 답변이 **진짜 박스/auto-pick 줄로 끝나면**(endsOnBox=마지막 ~2 비어있지않은 줄 검사) `decision:block` 으로 모델 재호출=강제. 정밀: tail=2 라 박스 뒤 작업/요약 따라오면 무발화 → 정상 땐 'Stop hook error' 안 뜨고 진짜 박스-멈춤만 강제(에러≠노이즈). 루프가드=stop_hook_active. (modules/recommend.ts)" }, { "name": "ing staleness-check", "role": "코드 다수 편집인데 ing 보드 무변동 경고 (c6)", "id": "guard-ing-staleness-check", "detail": "warn-only(stderr) — 코드 ≥editThreshold(기본 5) 편집했는데 ing 보드 무변동이면 1회 경고 후 카운터 리셋. 강제 불가(ground truth 없음). (modules/ing.ts)" }, { "name": "turn-close check (턴 마감 트리오)", "role": "매 응답에 🔄 ING · 🏛️ ARCHITECTURE · 🧬 CONVERGENCE 세 줄 강제 (단일 decision:block)", "id": "guard-turn-close", "detail": "세 줄을 함께 요구하는 단일 Stop 게이트. 각 줄 = `갱신`/`기록` 주장 또는 명시적 무변동(`변동 없음`/`해당 없음`). 게이트1·block1·reason1 에 누락/위조 leg 를 전부 나열 → 1회 re-turn 으로 복구. 배선 TWO-SURFACE(hooks.json + setup.ts Stop[] · setup-ts-1).", "children": [ { "name": "스캐너 4종 대체 (왜)", "role": "키워드 신호 → 결정적 트리오", "id": "detail-turn-close-why", "detail": "ing/architecture/convergence/gate stop-check 을 대체. 키워드 신호는 응답이 쓰인 뒤 예측불가하게 난입했고(가로채기), `stop_hook_active`(anti-wedge) 때문에 첫 게이트만 차단하고 나머지는 스킵 = 트리오의 2/3 이 구조적으로 강제 불가였다. 구 verb 는 silent no-op 툼스톤(stale settings.json 대비)." }, { "name": "주장 검증 (위조 차단)", "role": "마커만 쓰는 자기보고 위조를 diff 로 대조", "id": "detail-turn-close-forgery", "detail": "ING=`ing` ref sha 전진(베이스라인은 inject 가 UserPromptSubmit 에 스냅샷·transcript 키) · ARCHITECTURE=파일 diff(working·staged·직전커밋 3다리 — 직전커밋 다리는 worktree/ship 흐름에서 mid-turn 머지를 통과시킴) · CONVERGENCE=`records[]` 에 그 id 존재 + 같은 diff. 무변동 확인(`변동 없음`/`해당 없음`)은 검증하지 않음 = false-positive 회피 비대칭 (commons verify-done)." }, { "name": "활성 leg (inert-by-default)", "role": "지킬 게 없으면 완전 무음", "id": "detail-turn-close-legs", "detail": "leg 활성 조건 — 🔄=`ing` ref 또는 레거시 ING.jsonl 존재 · 🏛️=ARCHITECTURE.json/.md 존재 · 🧬=ARCHITECTURE.json(레코드 store 는 JSON 트리에만). 3개 다 꺼지면 check 는 exit 0 무음 → 설계트리·보드 없는 평범한 repo 는 게이트 자체가 없다. block reason 도 활성 leg 만 나열." }, { "name": "🌱 arch 부트스트랩 씨앗", "role": "저장소 부재를 견디는 유일한 요구 (새 repo 자립)", "id": "detail-turn-close-arch-seed", "detail": "모든 leg 이 '저장소 존재'로만 켜져 새 repo 에선 요구 자체가 없다(inject 무음·leg off·아무도 안 시킴). 부재 저장소 1개당 씨앗 1줄 — 파일검사(`designSsot`) + ref 검사(`ingBoardActive`).", "children": [ { "name": "발화 조건", "role": "저장소 부재일 때만 · 매턴", "id": "detail-arch-seed-when", "detail": "`turn-close inject` 가 부재 저장소마다 씨앗 1줄(각 ≈300-500B)을 UserPromptSubmit 마다 발화 — 생기면 죽은 가지(0B/턴). 옵트아웃 `archSeed:false`·`ingSeed:false`, 옵트인 마커는 금지(config-ts-1)." }, { "name": "씨앗 3종", "role": "arch 부재 · md-only(JSON 승격) · ing 보드 부재", "id": "detail-seed-kinds", "detail": "①`!legs.arch`=트리 없음 → 트리 작성 요구 ②`legs.arch && !legs.conv`=`ARCHITECTURE.md` 만 → 🧬 store 가 JSON 에만 있으므로 `ARCHITECTURE.json` 승격 요구(①②는 한 knob `archSeed`) ③`!legs.ing`=`refs/heads/ing` 없음 → 다단계 작업일 때 `ing add` 로 개설 요구(`ingSeed`) ④`configFileState()!=ok` → config 씨앗(아래)." }, { "name": "config 씨앗 = 뿌리 · 나머지 억제", "role": "harness.config.json 부재/깨짐 (knob 없음)", "id": "detail-config-seed", "detail": "config 없으면 기본값만 남아 `verify.checks`=[] → `sidecar ci` 가 공허한 green(검사 0인데 통과 보고) · changelog/protected-branch 게이트 · L0 전부 OFF. 발화 중엔 ①~③ 을 억제한다 — 그 씨앗들의 옵트아웃(`archSeed`/`ingSeed`)이 바로 이 파일 안에 살고, 열람만 하는 남의 clone 에서 3줄이 영구 반복되는 걸 1줄로 묶기 때문." }, { "name": "config 씨앗의 옵트아웃 역설", "role": "파일 생성 자체가 opt-out", "id": "detail-config-seed-optout", "detail": "knob 을 두면 그 knob 이 '없다고 말하는 그 파일' 안에 살게 된다 → 별도 knob 없음. 최소 `{}` 라도 쓰면 씨앗은 죽는다(남의 clone 은 untracked `{}` 로 침묵). host-wide 침묵장치는 정작 새 repo 에서 silent-dead(config-ts-1 한 층 위) 라 금지." }, { "name": "깨진 config 변종", "role": "존재하지만 파싱 실패", "id": "detail-config-seed-broken", "detail": "`config()` 는 malformed JSON 을 조용히 삼키고 defaults 로 폴백 → 파일이 있으니 겉보기엔 설정된 repo 인데 값은 전부 기본값. 부재보다 고약해서 별도 문구로 파싱 실패를 지목한다(`configFileState()` = ok|missing|invalid)." }, { "name": "ING 씨앗은 조건부 명령", "role": "빈 보드로 줄을 사지 않게", "id": "detail-ing-seed-conditional", "detail": "`ing add` 는 즉석 저장소 생성이라 문턱이 없지만, 무조건 '보드를 만들라'면 일회성 repo 가 줄을 끄려고 가짜 항목을 넣고 🔄 게이트가 허구에 무장된다(ARCH-PLACEHOLDER 와 같은 실패, 잡을 lint 도 없음). 그래서 '이번 일이 다단계면' 조건부 + 문구에 빈/가짜 항목 금지 명시." }, { "name": "엔진은 스캐폴드 금지", "role": "에이전트가 진짜 트리를 authoring", "id": "detail-arch-seed-author", "detail": "훅이 파일을 만들면 존재만으로 🏛️/🧬 leg 가 무장 → placeholder 가 게이트를 속인다(트리 없는 것보다 나쁨). 씨앗은 실제 코드를 읽고 쓰라고 요구하고, 남은 스텁은 `ARCH-PLACEHOLDER`(block)가 커밋게이트에서 잡는다." }, { "name": "예외: config 는 기계 생성", "role": "sidecar init --config-only", "id": "detail-config-scaffold-ok", "detail": "config 는 repo 에 대한 '주장'이 아니라 스택 감지 데이터라 스캐폴드가 정답 — 단 `--config-only`(config·.harness·gitignore·wrapper·git hooks) 로만. bare `init` 은 placeholder ARCHITECTURE.json 까지 써서 방금 그 함정에 빠진다." } ] }, { "name": "turn-close inject (매턴)", "role": "트리오 지시 + ing 베이스라인 스냅샷 + 부트스트랩 씨앗", "id": "detail-turn-close-inject", "detail": "UserPromptSubmit 마다 트리오 계약 재주입(활성 leg 만 · ~630B · dynamic = 고정 injectBudget 밖) + `ing` ref sha 를 `.harness/logs/turn-close-base.json` 에 스냅샷(Stop 시 ING 위조 대조용 · LOG_DIR mkdir 보장 · 씨앗만 뜨는 턴은 스냅샷 안 건드림). 트리오·씨앗은 `\\n` 으로 이어 ONE additionalContext.", "children": [ { "name": "스냅샷 부재 = 검증 SKIP", "role": "오차단보다 미검출을 택함", "id": "detail-turn-close-base-missing", "detail": "베이스라인이 없으면(Pi · 신규 세션 · 쓰기 실패) ING 위조 검증은 건너뛴다 — 절대 오차단하지 않는다. Pi 는 inject 만 탄다(블로킹 stop 훅 없음 → advisory)." } ] }, { "name": "🔬 GATE 마커 폐기", "role": "게이트 verdict = ARCHITECTURE 노드 편집 → 🏛️ 줄이 흡수", "id": "detail-turn-close-gate-absorbed", "detail": "gate verdict 는 곧 ARCHITECTURE.json `type:\"gate\"` 노드의 update-in-place 편집이라, 이미 매턴 필수인 🏛️ 줄이 보고+위조검증한다(single-doc). 4번째 마커는 같은 파일을 이중보고 → 삭제. gate-id 는 `🏛️ ARCHITECTURE 갱신:` 자유텍스트에 적는다." } ] }, { "name": "ship stop-check", "role": "impl/fix 후 verified-merge 진입 강제 (하이브리드 decision:block)", "id": "guard-ship-stop-check", "detail": "pr-cycle/ship ENTRY 게이트 — 미커밋 CODE diff 가 남았는데 응답에 `🚢 SHIP` 마커가 없으면 차단. marker-only 아님(하이브리드): clean-tree 성공경로는 실제 머지로만 도달(텍스트로 위조 불가), `🚢 SHIP: 보류(<사유>)`=명시적 WIP 유예. 게이트=`isManaged()` · anti-wedge 1/chain. cycle-docs-pr 을 inject 텍스트→결정적 강제로 승격. (modules/ship.ts)" }, { "name": "injects context-check", "role": "컨텍스트 fill rot 알람 (warn-only · 절대 block 아님)", "id": "guard-injects-context-check", "detail": "긴 세션이 context 창을 채우면 attention 저하(context-rot·lost-in-middle, 연구 ~150-400K대)로 에이전트가 둔해진다 → transcript 의 마지막 compact_boundary 이후 바이트로 현재 window(≈bytes/4 tok) 추정, WARN(200k)/LOUD(350k) 넘으면 /compact·새세션 권고. inject footprint(고정)와 별개=대화 누적분 감시. anti-wedge stop_hook_active. (modules/injects.ts)" }, { "name": "prefs stop-check", "role": "prefs 언어축 drift 알람 (warn-only · 절대 block 아님)", "id": "guard-prefs-stop-check", "detail": "턴마감 prefs 언어축 drift WARN(차단 아님) — DOCS: diff 의 doc(.md·README·CHANGELOG) 한글 산문 추가(백틱 면제) · RESPONSE: 응답 산문이 prefs.response 와 거의 다르면(Hangul<10% 휴리스틱). CODE축은 codeLangViolation(post.ts)이 per-edit 담당→제외. anti-wedge stop_hook_active. (modules/prefs.ts)" } ] }, { "name": "PreCompact·PostCompact", "role": "자동 컴팩션 전/후 → 세션-스코프 inject 재주입(컴팩션 생존)", "id": "surface-precompact-postcompact", "detail": "매-턴 inject(commons·recommend·prefs·easy·load·ing)=UserPromptSubmit · 세션-스코프(architecture·git-context·toolkit·companions·worktree+easy풀)=SessionStart→컴팩션증발→Pre/PostCompact 재주입. `lib/inject.ts` emitInject=이중배선(settings+plugin) 2×를 프롬프트/세션키 dedup→매턴·SessionStart 1회(inject-ts-1). `injects`=footprint" }, { "name": "체크포인트", "role": "commit/세션 → lint / ci / ing", "id": "surface-checkpoint", "detail": "검증·신선도·인계. exit+stderr. `ship`=sidecar-repo 전용 · inject-bloat 가드: injectCaps+injectBudget+INJECT-NON-ENGLISH(재주입 doc 한글차단)+CLAUDE-MD-OVERSIZED(CLAUDE.md byte cap 8000) — ALWAYS-ON·모든repo·config불필요·diff-aware = context-rot 재발방지 · lint↔ship 공유" } ] }, { "name": "layers — 층 구조", "role": "1=바닥(실행 입구) → 5=꼭대기(규칙 데이터)", "id": "group-layers", "detail": "재사용성의 비밀: 1–4층이 5층을 하드코딩하지 않는다.", "children": [ { "name": "L1 실행 입구", "role": "bin/sidecar — 런타임 자동탐색", "id": "layer-l1", "detail": "" }, { "name": "L2 디스패처", "role": "cli/index.ts — 명령 라우팅 진입점", "id": "layer-l2", "detail": "" }, { "name": "L3 공용 부품", "role": "lib/ — 경로찾기·로깅·실행·직렬화", "id": "layer-l3", "detail": "", "children": [ { "name": "paths.ts", "role": "repo-root 상향 탐색", "id": "lib-paths-ts", "detail": "config.json→.git 순, SIDECAR_REPO_ROOT override" }, { "name": "config.ts", "role": "harness.config.json 읽어 기본값과 머지", "id": "lib-config-ts", "detail": "모든 모듈의 단일 설정원" }, { "name": "log.ts", "role": "구조화 로그", "id": "lib-log-ts", "detail": "info/ok/warn/loudFail" }, { "name": "json.ts", "role": "JSON 직렬화 유틸", "id": "lib-json-ts", "detail": "" }, { "name": "exec.ts", "role": "셸/프로세스 실행", "id": "lib-exec-ts", "detail": "execShell=bash -lc, execArgs=직접 spawn" }, { "name": "lockdown.ts", "role": "L0 잠금 파일 검사 (opt-in)", "id": "lib-lockdown-ts", "detail": "" } ] }, { "name": "L4 기능 모듈", "role": "modules/*.ts — 언제·어떻게 검사할까", "id": "layer-l4", "detail": "① 완전 범용 등급만 포함", "children": [ { "name": "pre", "role": "정규식 규칙 엔진", "id": "module-pre", "detail": "규칙은 전부 외부 JSON" }, { "name": "post", "role": "exit/파일경로만 봄", "id": "module-post", "detail": "도메인 지식 0" }, { "name": "prompt", "role": "키워드→힌트 매핑", "id": "module-prompt", "detail": "키워드는 외부 JSON" }, { "name": "lint", "role": "staged-L0·신선도·수렴 + doc-gate (commit 전 게이트)", "id": "module-lint", "detail": "staged 변경에 대한 commit-time 게이트 모음. CHANGELOG·ARCHITECTURE(.json|.md) 미동반=block · ARCH-NOOP=ARCHITECTURE.json whitespace-only 재스테이징 차단(게이트 통과용 위장터치 봉쇄) · README 비강제·advisory · pr-cycle은 ing 현행화도 gate. 개별 체크는 children.", "children": [ { "name": "SHADOW-DESC (4f)", "role": "명령 desc 인지 (warn)", "id": "check-shadow-desc-4f", "detail": "commands/*.md description 이 ≤1400자 skill-listing cap + `Triggers —` 절 보유인지 — 누락 시 bare /cmd 자연어 인지가 죽으므로 환기(sidecar-repo 한정·commands/ 존재 시). sidecar s18 포팅" }, { "name": "CMD-DESC-LONG (4f-min)", "role": "명령 desc 미니멀 cap (block)", "id": "check-cmd-desc-long-4f-min", "detail": "commands/*.md description codepoint ≤ `lint.cmdDescCap`(기본 320)인지 — 1400 천장(SHADOW-DESC)과 별개의 빡빡한 '미니멀' 선. 초과면 차단: 하는 일+`Triggers —` 만 두고 플래그표/서브버브 카탈로그는 body/`--help`/`argument-hint` 로. write-time 도 같은 cap deny(`shadow.ts:descWriteViolation`→pre.ts) · 0=off" }, { "name": "INJECT-OVERSIZED (4j)", "role": "inject별 개별 바이트 cap (block)", "id": "check-inject-oversized-4j", "detail": "각 inject 소스가 **자기 cap** 이하인지 — `lint.injectCaps` 맵(path→budget · `/`끝=그 dir `*.md` 각각). recommend/easy 는 lang 변형(recommend.md+recommend..md · prefs response 로 택1 주입): 변형셋은 base별 MAX 1회만 총합계상. 산문 비대=매 턴 context 세금이라 소스 lean(emit 절단=손실 금지). commons/CLAUDE 는 각자 format lint. 0=off" }, { "name": "COMMONS-PROSE/NO-DODONT/DODONT-INCOMPLETE (4g)", "role": "commons·CLAUDE do/dont 양식 (block)", "id": "check-commons-prose-no-dodont-dodont-incomplete-4g", "detail": "**commons.md(또는 .harness override) + 루트 CLAUDE.md** 각 `## slug` 섹션이 `- do:` AND `- dont:` 줄만(둘 다 필수·산문 금지)인지 — 첫 `## ` 앞 preamble 면제 · 서브폴더 CLAUDE.md(folder-docs)는 자유양식 제외. 거버넌스/프로젝트규칙 SSOT 산문 재비대 + 한쪽-누락 차단(`commons.ts:lintCommonsFormat`+lintCommonsText)" }, { "name": "DODONT-LONG (4h)", "role": "do/dont 길이 cap (block)", "id": "check-dodont-long-4h", "detail": "staged commons.md + 루트 CLAUDE.md 의 do/dont 엔트리(연속줄 합산→cap 우회 차단) codepoint 가 `lint.dodontCap`(200) 초과 신규/증가면 차단. HEAD diff-aware grandfather · 서브폴더 제외 · write-time 선차단(`commonsWriteViolation`/`dodontLength`)은 Write-only(Edit fragment skip → commit-time 4g/4h 백스톱, 경계섹션 false-pos 회피)" }, { "name": "FOLDER-GUIDE-MISSING (4i)", "role": "폴더 가이드 (block)", "id": "check-folder-guide-missing-4i", "detail": "staged 폴더(folderGuides)에 CLAUDE.md 없으면 차단(존재만·folder-docs)" }, { "name": "HELP-BACKTICK (4j)", "role": "HELP 백틱 위생 (block)", "id": "check-help-backtick-4j", "detail": "cli/index.ts HELP 리터럴 내 이스케이프 안 된 백틱 차단(help/build 깨짐 재발 박제·텍스트스캔)" } ] }, { "name": "naming", "role": "repo 전수 비-canonical 이름 audit (write-guard 의 backlog 짝)", "id": "module-naming", "detail": "`audit [path] [--ing] [--gate]` — git ls-files 트리에서 버전/복사/중복 접미사 이름을 전수 스캔(`offendingToken` 재사용 = naming-guard 와 동일 판정). write-time naming-guard 는 새 파일만 막아 기존 backlog 를 못 보던 갭을 메움.", "children": [ { "name": "출력 모드", "id": "detail-output-mode", "detail": "기본 read-only 보고(exit 0) · `--gate`=위반 시 exit 1(commit/CI 게이트) · `--ing`=요약 1줄을 **그 repo 자기 ING** 보드에 add(보드=내 repo 전용 · cross-repo 전달 없음). cross-repo 감사는 각 repo 안에서 돌려 그 repo ING 에 착지." }, { "name": "생태계-native 면제 (오탐 방지)", "id": "detail-native-exempt", "detail": "native-canonical-first: Android res 한정자 `-v`(`values-v26`·`mipmap-anydpi-v26`)·bare `_version` 모듈명(`version\\d+` 만 위반)은 guard/audit 공통 면제 · audit 은 `archive/`·`.verdicts/` 트리 무시(frozen 이력·전이 로그)." } ] }, { "name": "install", "role": "공용(전역) 부트스트랩 — clone+래퍼+전역훅 (init=per-repo 와 별개)", "id": "module-install", "detail": "SSOT=scripts/install.sh(curl 가능); `sidecar install`은 이를 위임. clone dancinlab/sidecar→~/.sidecar/cli(있으면 ff) · 래퍼(심볼릭 아님)→~/.local/bin/sidecar · install-hooks --global. 멱등 · 플래그 --no-hooks/--ref/--dir/--bin/--dry-run · 갱신은 self-update" }, { "name": "shadow", "role": "sidecar commands/ → ~/.claude/commands/ 미러 (bare /cmd 생성)", "id": "module-shadow", "detail": "plan|remove|--force. 각 commands/*.md 를 ~/.claude/commands/ 에 얇은 /cmd 위임자로 복제 + SHADOW_MARKER 주석으로 추적 — 손수작성 동명파일은 skip. regen 은 소스에 없는 marker-shadow(폐기된 명령)를 orphan-prune(손수작성은 보존) — 명령 폐기 시 bare /cmd 자동 청소. 사이드카 무의존·재생성 가능(플러그인 미설치 폴백).", "children": [ { "name": "--force heal", "role": "마커-없는 stale 치유", "id": "detail-force-heal", "detail": "`--force` 는 마커-없는 충돌도 source(SSOT)로 덮어써 마커 도입 *전* stale shadow 를 heal (@convergence SHADOW_PREMARKER_STALE: 마커없는 옛 shadow 가 손수작성 false-positive 로 skip 돼 source 의 `Triggers —` 줄을 잃으면 맨-텍스트 명령 인지가 죽는다)." }, { "name": "마커 위치", "role": "frontmatter 뒤 삽입", "id": "detail-marker-location", "detail": "마커는 frontmatter 닫는 `---` **뒤**에 삽입(`withMarker`) — Claude Code 는 `---` 가 1행일 때만 `description:` 을 읽으므로 앞에 붙이면 picker 가 마커를 desc 로 표시(@convergence SHADOW_MARKER_AFTER_FRONTMATTER). desc 품질은 lint 4f(SHADOW-DESC) 게이트." }, { "name": "hexa 위임자", "role": "이웃 CLI 위임", "id": "detail-hexa", "detail": "일부 commands/*.md 는 `hexa` 위임자(/hexa·/cloud·/dojo·/deck·/verify·/atlas — hexa 부재 시 graceful echo; sidecar 자체 dojo 는 /hdojo). ⚠️ frontmatter argument-hint 중첩 큰따옴표 금지(YAML 깨짐) — 안쪽은 작은따옴표." } ] }, { "name": "pr-cycle", "role": "push→PR→검증머지→로컬 base sync", "id": "module-pr-cycle", "detail": "feature 브랜치에서 doc-gate→push→gh pr create→self-merge(squash/merge/rebase fallback·CI pending 재시도)→머지 커밋이 origin/base 에 올랐는지 검증→로컬 base ff-sync→merged worktree sweep→reap pass(modules/reap.ts 공용 엔진). main/master 에선 거부.", "children": [ { "name": "reap", "role": "방치 PR 배출구 (단독 sidecar reap · cron 가능)", "id": "module-reap", "detail": "modules/reap.ts. 내 열린 PR oldest-first: MERGEABLE→no-admin 스쿼시머지(정책 거부+체크 전부 그린이면 --admin 폴백 — 리뷰정책만 우회·CI 검증 유지) · CONFLICTING→refresh-merge · 코드충돌+closeAfterDays 무활동→브랜치 보존 close · draft/외부저자 제외 · run당 refresh 캡 · --dry-run=분류만.", "children": [ { "name": "refresh-merge", "role": "충돌 PR 자동 해소 (문서파일만)", "id": "module-reap-refresh-merge", "detail": "임시 워크트리에서 base→브랜치 merge(스쿼시라 force-push 불요): CHANGELOG/ING+unionPaths=라인합집합(delete/modify 는 base측 채택) · ARCHITECTURE=base측 · 비-union 해소는 pre-refresh sha PR 코멘트로 보존 · artifactPaths=브랜치측 · 코드 충돌 1개면 통째 abort. GitHub 는 merge=union 서버측 무시 → 로컬 refresh 가 유일 자동 경로." } ] }, { "name": "로컬 ff-sync", "role": "로컬 base 뒤처짐 방지", "id": "detail-ff-sync", "detail": "`git fetch origin :` · checkout 전환 없이 로컬 main 뒤처짐 방지 · non-ff 거부." }, { "name": "stale-PR reaper", "role": "내 열린 PR 전수 정리", "id": "detail-stale-pr-reaper", "detail": "내 다른 열린 PR 전수: MERGEABLE=자동 squash머지 · CONFLICTING/blocked=rebase/`gh pr close` 안내와 함께 큰소리 보고 · `--no-reap` 차단. 중단/실패한 머지가 영구 방치(충돌로 썩음)되던 누수 차단." }, { "name": "worktree 누적 한계", "role": "[gone] 못 거두는 워크트리", "id": "detail-worktree", "detail": "sweep=[gone] 머지브랜치만→isolation:worktree(fleet·sub-agent) 누적; `worktree gc` age백스톱(HEAD>maxAgeDays→refs/reaped 보존 후 reap). `worktree inject`(SessionStart/Compact)+`stop-check`(Stop·미push worktree·keyed dedup·never block)=stranded 워크트리+no-wt 브랜치+refs/reaped surface(항목별 ING 링크·clean=0B·prompt-scan)." } ] }, { "name": "ship", "role": "구현 후 모든 설치 surface 에 한 번에 전파 (orchestrator)", "id": "module-ship", "detail": "pr-cycle(검증 머지) → self-update(전역 CLI git-pull) → shadow(commands/ 재미러) 를 한 명령으로 올바른 순서 실행, 단계 실패 시 즉시 중단. shadow 빠뜨리면 새 슬래시가 picker 에 안 떠 '반영 안됨' 재발 → 셋을 묶어 락스텝. 플러그인(/plugin) 설치본은 별도 surface · `--no-doc` pr-cycle 로 forward" }, { "name": "verify", "role": "검증 명령 병렬 실행", "id": "module-verify", "detail": "명령은 config 선언" }, { "name": "ci", "role": "검증 명령 병렬 실행 + CI scaffold", "id": "module-ci", "detail": "`ci [all|fast|list]` = config `verify.checks` 병렬(실패 1개라도 exit 1). `ci scaffold` = `.github/workflows/ci.yml` 방출. `ci.fallback` = cost-free: `pick-runner` 가 self-hosted pool probe → 무료 self-hosted·없으면 github-hosted fallback. `ci.cachePaths` → `actions/cache@v4` warm" }, { "name": "paper", "role": "demiurge 하우스 논문 스캐폴드·표지·빌드·배포(Zenodo/arXiv)", "id": "module-paper", "detail": "new(스캐폴드) → cover(imagine 표지) → build(xelatex+bibtex×3) → publish/update/unpublish/status(배포). NeuroLM 정답지 미러 + 하드 content-floor 게이트.", "children": [ { "name": "build content-floor 게이트", "id": "detail-build-content-floor", "detail": "build=xelatex+bibtex×3 · pages/refs/result-figs 보고 · 하드 2게이트 ≥10p(g51) + ≥9 result figs(cover 제외 = NeuroLM바) · 둘다 `--min-*` 0 으로 해제 · 미달 exit3 · 깨진/빈 PDF 실패처리." }, { "name": "NeuroLM 정답지 미러", "id": "detail-neurolm", "detail": "외부 품질 정답지 NeuroLM(templates/paper/_reference_samples/2409.00101_neurolm.pdf · ICLR 2025 · 읽기전용) 미러 — 섹션 Intro·Background·Method·Experiments·Ablation·Discussion + 결과그림(TikZ 아키텍처 + pgfplots 막대 · 캡션=결과연결) · g5 tier-badge · natbib. 마케팅 표지는 허용(의도된 divergence). 손조립 규율을 도구로 박제." }, { "name": "배포 수명주기 (Zenodo/arXiv)", "id": "detail-zenodo-arxiv", "detail": "publish/update/unpublish/status `--to zenodo|arxiv|both` · 키는 secret · 원장 PAPERS//publish.json. Zenodo=완전 REST(업로드→메타→발행→DOI · update=new-version · unpublish=DRAFT 삭제). arXiv=제출 API 없음(SWORD 폐지)→tar.gz 패키징 + 제출 가이드(가짜 자동제출 안 함)" } ] }, { "name": "imagine", "role": "AI 이미지+영상 생성 (fal/openai · 키 secret 경유)", "id": "module-imagine", "detail": "출력 확장자로 분기: 이미지(.png/.jpg)=fal `openai/gpt-image-2`(또는 -b openai); 영상(.mp4/.mov)=fal Seedance 2.0(`-i` 없으면 text-to-video·있으면 image-to-video). 동일 queue+poll · 프롬프트는 FILE · 키는 secret get·-K curl 로 argv 누출 방지 · -m override" }, { "name": "email (alias mail)", "role": "Postmark 트랜잭션 메일 발송 (토큰 secret 경유)", "id": "module-email-alias-mail", "detail": "`POST /email`(Postmark REST). 서버 토큰=`secret get postmark.server_token`·`-K` curl 로 argv 누출 0. send: --to/--from/--subject + 본문(--text/--html=FILE·-m 인라인) + cc/bcc/reply-to/tag/stream + --attach + --dry. history=outbound API·list=토큰 점검. 성공 조용·실패 큰소리 + email.jsonl 원장" }, { "name": "lab", "role": "프론티어 모델에 지시 1건 per-call 위임하는 허브 (fable·sol·full · 기본 full · 세션 백엔드 불변)", "id": "module-lab", "detail": "`lab [fable|sol|full]` 위임 허브. 백엔드=선택 위치인자·생략=full(둘 병렬)·근접오타 거부. 백엔드=데이터(descriptor). 공용 1벌: 프롬프트 argv/--file/stdin→자식 stdin(누출0), bg 잡(--bg→ID·result/tail/wait/list), --dry·--cwd·--timeout(기본무제한). durable-log: foreground 전체 출력을 ~/.sidecar/lab/-.md에 tee(잘려도 무손실). 주의=lab.md.", "children": [ { "name": "fable 백엔드", "role": "Claude Fable 5 위임 (claude -p)", "id": "detail-lab-fable", "detail": "`claude -p --model claude-fable-5`. 기본 --sources project,local(전역훅 미상속·스톨차단)·기본 investigate(Write/Edit/NotebookEdit 거부·bash/read 허용; --write=bypassPermissions). opus fallback 무조건 금지(--settings availableModels·opus제외). -c/-r 다중턴. --json=claude json blob(.result)." }, { "name": "sol 백엔드", "role": "OpenAI Codex 5.6 위임 (codex exec)", "id": "detail-lab-sol", "detail": "`codex exec -m gpt-5.6-sol --skip-git-repo-check`·프롬프트=stdin(`-`). 기본 investigate=`-s read-only`·--write=`-s workspace-write`(resume 경로엔 -s 없어 `-c sandbox_mode` 오버라이드). -c/-r=`exec resume --last|`. --json=codex `-o`로 최종 메시지만 클린 캡처(JSONL 파싱 안 함)." }, { "name": "full", "role": "두 백엔드 병렬 자문", "id": "detail-lab-full", "detail": "같은 프롬프트를 fable+sol 동시 실행(Promise.all·버퍼 캡처)→`── fable ──`/`── sol ──` 라벨 섹션 출력. exit=fable≠0?fable:sol. --bg=백엔드별 잡 2개. -c/-r/-- 거부(백엔드별 세션·플래그 모호)·--sources는 fable leg 만." } ] }, { "name": "lab-mode", "role": "리포별 분업 토글 (labMode: off|fable|sol|full · 기본 off)", "id": "module-lab-mode", "detail": "리포별 토글. ON이면 매턴 inject 가 분업 지시: **설계·분석·난제=lab 위임**, **구현·빌드·git·ship=로컬**. 스위치=**그 repo 의 harness.config.json `labMode` 문자열 하나**(off|fable|sol|full — 백엔드명이 곧 on 스위치라 모순상태 불가 · bare on=full) · **호스트 전역 스코프 없음**(한 repo 가 다른 repo 를 못 켠다) · **미지정·미인식=off**(구 플래그도 불참조 · on/off 가 청소) · 3표면 배선." }, { "name": "goal-guard", "role": "작업-punt 차단 Stop 게이트 (다음-세션 미루기 + 잔여 방치)", "id": "module-goal-guard", "detail": "Stop-hook punt-차단 가드 · 3중 캐치 block: ① 꼬리 '다음 세션'·핑계(rate-limit·규모) ② 본문 '잔여'/'남은'/'잔존' 부정형 없이 ③ infra/인프라 벽(키워드+벽신호 same-line)+우회/격리/미룸→upstream-fix. 공통 예외: 따옴표 감싼 키워드=메타 skip · CONCRETE 블로커+`ing next`=session-terminal 첫정지 통과. decision:block · stop_hook_active 단발. CC 전용 · hooks.json+setup.ts Stop[]." }, { "name": "hypotheses", "role": "가설 폴더 단일 정규명(HYPOTHESES/) 강제·감사·마이그레이션", "id": "module-hypotheses", "detail": "config `hypotheses.dir`(기본 HYPOTHESES/)로 가설(pre-register→falsify→run→verdict) 작업을 한 폴더 고정. hypothesis-guard(pre write/bash)가 신규 stray 폴더(per-repo aliases 예 UNIVERSE·내장 hypothes*/가설* 패턴) 차단(신규=block·기존=warn·name-pattern 상시). check=감사(--gate) · migrate=git mv→canonical(병합) · scaffold · show." }, { "name": "fleet (+ lab/abstract/full)", "role": "영구 멀티레인 오케스트레이터 런북 (fire-on-arrival · 4모드)", "id": "module-fleet-lab-abstract-full", "detail": "MODES 맵(fleet·lab·abstract·full)이 엔진 1개를 공유 — roster 파일·런북·어휘만 다르다. 4모드 공통 go/stop/status, `/fleet …`는 sidecar 가 `$ARGUMENTS` forward.", "children": [ { "name": "fleet (기본)", "role": "빌드/멀티레인", "id": "mode-fleet", "detail": "roster `.harness/fleet/active` + templates/fleet.md. N개 독립 레인을 background Agent로, 각 레인은 착륙 즉시 다음 라운드를 스스로 발사(barrier 없음)." }, { "name": "fleet lab", "role": "research 특화", "id": "mode-fleet-lab", "detail": "roster `lab` + fleet-lab.md. 레인=막힌 프론티어(벽). 싼 research gate→정당화 시만 비싼 implement/measure→측정(c2)→SSOT(blocking-frontiers)→재-research/벽(🧱 reopenable)." }, { "name": "fleet abstract", "role": "추상화 특화", "id": "mode-fleet-abstract", "detail": "roster `abstract` + fleet-abstract.md. 레인=누적 법칙군/천장. census LAWS→한 겹 peel해 공유 trade-off(메타법칙) 도출→escape 원리 발명→반증가능 예측으로 캐스팅해 compute/research 핸드오프. 메타법칙(🌌)은 새 lens로 reopen, 산출 전부 미검증(d6 좌표≠발견), lazy-ceiling 금지(c14 d)." }, { "name": "fleet full", "role": "풀스택", "id": "mode-fleet-full", "detail": "roster `full` + fleet-full.md. 프론티어가 3 페이즈 순환 research→implement→(벽)→abstract→falsify — implement 스킵 금지(약한 레버도 싼 probe 로 벽 측정 후 abstract). 기본 순차(afg)·`parallel` 일 때만 Workflow fan-out. 게이트는 비용 — 싼/로컬 자동·비싼(pool/GPU)만 4축+go. depletion=양축 dry" } ] }, { "name": "errors", "role": "(kind,code)→severity 분류", "id": "module-errors", "detail": "맵 로드 = bundled config/ defaults 위에 per-repo `.harness/` override 병합(repo 가 code별 우선) → 새 bundled 규칙이 stale repo 맵에서 fallback-block 으로 둔갑 방지" }, { "name": "ledger", "role": "에이전트 작업 등록 (중복 방지)", "id": "module-ledger", "detail": "도메인 무관" }, { "name": "ing", "role": "진행보드 (전용 `ing` git ref · 내 repo 전용)", "id": "module-ing", "detail": "work/next · 보드는 내 현재 repo 전용 — cross-repo 전달(`--to`)은 폐기(직접수정 원칙 · commons upstream-fix). upstream 결함은 그 repo 에서 직접 고쳐 머지하고, 끊기는 작업만 `↩resume` 으로 내 보드에 남긴다. add/next 는 현재 branch(≠main)를 스탬프→stranded 브랜치/워크트리↔태스크 링크(findIngForBranch·worktree inject·end).", "children": [ { "name": "저장 = ing ref (worktree 아님)", "id": "detail-ing-ref-worktree", "detail": "done=scrub. 보드는 refs/heads/ing 에 plumbing(hash-object→mktree→commit-tree→update-ref) 저장 + best-effort push origin ing → branch-switch/reset 무관·공유·protected-main-safe. 보드 cwd=`git rev-parse --show-toplevel`(REPO_ROOT fallback)로 해석 — config-less worktree 가 sibling repo ref 에 쓰던 누수 차단" }, { "name": "verb 의미 · stdin", "id": "detail-verb-stdin", "detail": "add=work · next=다음 · done은 id 정확매칭 우선, text fallback은 1건일 때만(대량삭제 방지). `↩`-prefix resume 항목은 show/inject 맨 앞 표면화(c17 — upstream-fix 중단작업 복원). add/next 자유텍스트에 셸 특수문자(괄호·따옴표·$·→)면 `--stdin`(또는 단독 `-`)로 stdin 읽기 — 슬래시 `$ARGUMENTS` 무인용 깨짐 회피." }, { "name": "적체 방지 (나이 게이트)", "id": "detail-pileup-gate", "detail": "inject/show 가 각 work 항목에 나이 `⏳Nd` 표시 + `ing.staleDays`(기본 5) 초과 묵음 또는 `ing.maxActive`(기본 12) 초과 시 매 턴 `🧹 ING 적체 — … 끝난 항목 지금 done 로 scrub` 강제 지시. 자동삭제 아님 — 완료분이 ACTIVE 로 썩는 것 차단(나이는 done-vs-active proxy)." } ] }, { "name": "frontier", "role": "최전선 목표 (전용 `frontier` git ref · 단일슬롯)", "id": "module-frontier", "detail": "단 하나의 north-star 목표(최전선)를 추적 — ing(다건 보드)과 달리 SINGLE-SLOT. 저장은 ing ref 패턴 미러(refs/heads/frontier·FRONTIER.jsonl 1줄·plumbing+push). clear/swap 은 옛 최전선을 은퇴(→CHANGELOG); ref 는 ACTIVE 만. SessionStart+per-turn inject 로 표면화(미지정=0B 침묵). advisory only — Stop 강제게이트 없음.", "children": [ { "name": "verb 의미 (지정·진행·교체·해제)", "id": "detail-frontier-verbs", "detail": "set(지정)=지정, 기존 있으면 clobber 거부→swap 유도(유실 방지). go(진행)=push-now 지시 출력+선택인자를 진행노트 append. swap(교체)=기존 은퇴(id 증가)+신규 지정(없으면 set). clear(해제)=해제. 한글별칭+designate/advance/replace/release 허용. 셸특수문자 텍스트는 `--stdin`." } ] }, { "name": "bitter-gate", "role": "JSONL 빈도 분석", "id": "module-bitter-gate", "detail": "dormant 규칙 폐기 검토" }, { "name": "audit", "role": "JSONL 카운트 기반 6축 스코어카드", "id": "module-audit", "detail": "" }, { "name": "gc", "role": "마크다운 가이드 링크 drift 탐지", "id": "module-gc", "detail": "" }, { "name": "changelog", "role": "이력 SSOT = CHANGELOG.jsonl (append + auto-prune)", "id": "module-changelog", "detail": "이력을 `CHANGELOG.jsonl`(newest-first · {ts,title,body})로. `add`(body=stdin)+keep-N 자동 트림·`list`·`render`·`prune --keep N|--older-than D`·`autoprune`(SessionStart 배선·cap 초과시만)·`migrate`(md→jsonl). keep-N=lint.changelog.keep(기본 30)·옛 엔트리는 git 보존. CHANGELOG-MISSING 게이트는 .jsonl staged 요구" }, { "name": "architecture", "role": "설계 SSOT 스켈레톤 주입 (SessionStart + Compact · detail 은 온디맨드)", "id": "module-architecture", "detail": "스켈레톤(title+summary+2단계 목차)만 additionalContext 주입 — SessionStart + PreCompact/PostCompact(NOT per-turn · 정적 doc 의 매턴 풀-주입은 대용량-doc 안티패턴). 세부(`detail`셀·전체 트리·convergence)는 파일에 두고 `show`/`search`/Read 온디맨드. 턴-마감 게이트는 Stop 훅 `architecture stop-check` 강제(학습=convergence INJECT_DOC_SKELETON_ONDEMAND)", "children": [ { "name": "lint (트리 위생 · c4 강제)", "role": "fine-decomposition 게이트", "id": "detail-lint-c4", "detail": "`architecture lint` = 셀 cap(ARCH-BIG-CELL 300), piled>6(ARCH-PILED), history키(ARCH-HISTORY), rendered노드(name|role|detail) id 부재/중복/형식(ARCH-ID-MISSING/DUPE/FORMAT), init 스텁 잔존(ARCH-PLACEHOLDER)=block. rendered 0 비-canonical=ARCH-SCHEMA-UNRECOGNIZED(warn). 최상위 `convergence` 제외" }, { "name": "search (id 검색)", "role": "노드를 id/텍스트로 찾기", "id": "detail-search", "detail": "`architecture search ` = id/name/role/detail 대소문자무시 substring 매칭 → 매칭 노드의 id + breadcrumb 경로 출력. 각 트리 노드는 고유 kebab-case id 보유(레거시 `slug`/한글키 read-fallback · 카테고리는 id prefix `guard-`/`module-`/`config-`… 로 흡수), columns 의 `id` 컬럼으로 표시·HTML 뷰어 검색에도 노출" }, { "name": "convergence 재발학습 store (구 convergence 모듈 흡수)", "role": "재발방지 학습 SSOT — 인라인 @convergence 마커 폐기", "id": "detail-convergence-store-convergence", "detail": "재발방지 학습 SSOT=`convergence.records[]`(id·state·value·threshold·source). `add`=`--source` 필수·id 자동(파일명 slug)·동일 source 레코드 있으면 BLOCK→`for ` 검토 후 `edit`/`--new`(중복누적 금지). 터치 시 `convergenceForFile` 가 그 학습을 PreToolUse `additionalContext` inject(`convergenceOnTouch` ON)" }, { "id": "arch-doc-shape", "name": "doc-shape", "detail": "Two doc shapes are canonical and every reader goes through rootNode(): the WRAPPER ({title,summary,tree}) and the doc that IS the root node ({name,children}). Reading `.tree` alone gave root-node repos an empty skeleton and crashed the search walk.", "children": [ { "id": "arch-doc-shape-aliases", "name": "field-aliases", "detail": "Body text is read as detail ?? 상세 ?? value — root-node trees keep theirs in `value`. Every read is narrowed to string, so a non-text key can never reach the lowercasing search." } ] } ] }, { "name": "git-context", "role": "stale-branch 함정 가드 (SessionStart inject)", "id": "module-git-context", "detail": "HEAD vs origin/(main|master) 를 LOCAL ref 로 비교(fetch 없음) → BEHIND 이거나 detached-stale 이면 ⚠️ 경고 주입: '이 위치 파일은 최신이 아닐 수 있다 · git log origin/ -- 로 이미 머지됐는지 확인 후 코드 믿어라'. clean 한 default 브랜치면 무음(노이즈 0). 재발방지: 옛 feature 브랜치에서 옛 코드를 보고 이미 머지된 작업을 중복 구현한 사고(#3736 dup #3734) 차단" }, { "name": "claudemd", "role": "프로젝트 규칙 매턴 재주입", "id": "module-claudemd", "detail": "repo-root CLAUDE.md 를 매 UserPromptSubmit 마다 additionalContext 로 재주입(SessionStart 1회로는 긴 대화에서 묻힘) → 프로젝트 규칙을 commons급 salience 로 상주(MUST-FOLLOW 헤더 prepend). `` 있으면 그 섹션만·없으면 전체·80KB 절단·부재 무음. 디렉토리·모듈 트리는 ARCHITECTURE.json SSOT 라 CLAUDE.md 에 중복 안 함" }, { "name": "load", "role": "매턴 macOS 자원 압박 readout 주입 (CPU+RAM+swap, ⚠️ on danger)", "id": "module-load", "detail": "show|inject. 매 UserPromptSubmit 에 한 줄 readout(CPU·RAM·swap·wt·PR·🕐 현재날짜시간)을 additionalContext 로 주입 — 부하+현재시각 상주로 무거운 작업 전 자각·시간감각 유지. PR=열린 PR 수(gh · common-dir 5분 TTL 캐시+4s 타임아웃 · 실패시 최근값 · repo/gh 없으면 생략 — 매턴 훅 instant 원칙). 비-macOS 무음.", "children": [ { "name": "3축 신호등", "role": "CPU·RAM·swap·wt readout", "id": "detail-3", "detail": "CPU(load1÷cores·🟢<0.7 🟡<1.0 🔴≥1.0), RAM((active+wired+compressor)%+pressure), swap(🟢<2G 🟡<6G 🔴≥6G), wt(추가 worktree·🟢0-2 🟡3-9 🔴≥10 · stranded>0면 `N(M⚠)`🔴 무조건, TTL캐시). 어느 축이든 🔴/pressure≥warn 이면 선두 `⚠️` 마커 + 가드 노트(대량빌드·병렬agent·GPU 자제)" }, { "name": "동기·비용안전", "role": "왜 MEMORY 를 보나", "id": "detail-sync-cost-safety", "detail": "부하로 죽는 맥은 CPU 가 아니라 MEMORY(compressor+swap 폭증)로 죽으므로 RAM압박·swap 을 같이 본다. 비용안전: sysctl+vm_stat 만(즉답) — 전체 스캔으로 수초 걸리는 `memory_pressure` CLI 는 매턴 훅에 부적합이라 의도적 회피." } ] }, { "name": "toolkit", "role": "명령 카탈로그 — agent 가 전 명령을 인지·사용하도록 SessionStart 주입 (sidecar TOOLKIT 패리티)", "id": "module-toolkit", "detail": "list|inject|json|write|check. 단일 출처=cli/index.ts `HELP` 텍스트 파싱 + keyword 트리거 매핑. inject=SessionStart 에 전 명령을 compact 카탈로그(`id — use ⟨triggers⟩`)로 주입(키워드 사각지대 보완). write=TOOLKIT.jsonl 산출. check=dispatch case 커버리지 + 커밋본 drift(`lint` TOOLKIT-DRIFT warn)" }, { "name": "companions", "role": "이웃 CLI 명령 surface 주입 — agent 가 sibling 툴체인(hexa 등)을 더듬지 않고 인지하게 SessionStart 주입", "id": "module-companions", "detail": "toolkit 의 자매 — toolkit 이 sidecar 자신 명령을 주입하면 companions 는 이웃 CLI(hexa 등) 카탈로그를 주입. DOMAIN-AGNOSTIC: 어떤 CLI 는 데이터(harness.config.json `companions` + host `~/.sidecar/companions.json` union)라 엔진에 hexa 안 박음. 각 companion 카탈로그(default `--help`) 실행·절단·주입·부재/실패 skip" }, { "name": "sync", "role": "공유파일 sync 셸 스크립트 래퍼", "id": "module-sync", "detail": "" }, { "name": "pool", "role": "호스트 로스터 + 원격 실행 + 자원 프로브", "id": "module-pool", "detail": "호스트 로스터(~/.sidecar/pool.json · 글로벌) + ssh 원격 실행 + 자원 프로브. ssh=execArgs 직접 spawn($ 전개 방지).", "children": [ { "name": "guard", "id": "module-pool-guard", "role": "제한 호스트 차단", "detail": "shared:false 호스트는 allow 밖에서 on/status 차단 (allow 마커 cwd 일치 = 해제)." }, { "name": "on", "id": "module-pool-on", "role": "원격 실행 · 기본 orphan-fenced(ssh 사망→원격 그룹 수거) · --bg = setsid 분리(새 세션)로 ssh 끊김 생존 — 장시간 train/sweep/eval용, 즉시 반환+로그경로 폴링", "detail": "on [--timeout |0] — 기본 120s, 0=무제한 (고정 120s SIGKILL 절단 제거 · heavy-on-pool). 무출력 잡은 nohup detached + scp 로 우회." }, { "name": "orphan-fence", "id": "module-pool-fence", "role": "로컬 ssh 사망 시 원격 프로세스그룹 수거 (pool-ts-1)", "detail": "PTY 없는 sshd 는 SIGHUP 을 안 보내므로 HUP 트랩은 무용 — 신뢰할 신호는 고아화 자체다. fence() 가 커맨드를 백그라운드로 띄우고 자기 PPID 를 폴링, 1 이 되면(=sshd 사망) kill -- -$$ 로 그룹 전체를 수거한다. 그룹 멤버십은 reparent 후에도 유지되어 이미 고아가 된 손자·재생성 래퍼까지 닿는다. EXIT 는 미트랩 (정상 종료 시 nohup 데몬 보존)." }, { "name": "probes", "id": "module-pool-probes", "role": "스펙·부하 프로브", "detail": "specs=코어/메모리/GPU 캐시 · list·status=라이브 부하 SSH 병렬 ⚡뱃지. 프로브 타임아웃 시 connect-only 로 폴백해 '고부하'와 '도달 불가'를 구분 (SSH_ARGS ServerAlive* 로 idle 링크 유지)." }, { "name": "harden", "id": "module-pool-harden", "role": "OOM 메모리 펜스", "detail": "cgroup user-slice cap + system 예약 + earlyoom prefer/avoid — 무거운 잡이 박스를 OOM-리부트 시키지 못하게. 멱등 · passwordless sudo 필요." } ] } ] }, { "name": "L5 규칙 데이터", "role": "per-repo — 무엇을 막을까", "id": "layer-l5", "detail": "이 층만 프로젝트별로 달라짐", "children": [ { "name": "enforcement.json", "role": "pre 정규식 규칙", "id": "data-enforcement-json", "detail": "strict 기본(16룰 block-everything) — init 이 항상 이 세트를 깐다(과거 hardcore profile 흡수, 별도 변종 없음). H-RAW-GPU-RENT: raw vastai/runpodctl GPU 렌트(create/rent/launch) 차단 → hexa cloud rent 단일진입 강제(미귀속 고아포드 방지) = NO_RAW_CLOUD_CLI 코드가드(c11)의 정규식-프로파일 2중방어, exceptions 빈배열(전면차단)." }, { "name": "keywords.json", "role": "prompt 트리거 키워드", "id": "data-keywords-json", "detail": "" }, { "name": "severity-map.json", "role": "errors 분류 맵", "id": "data-severity-map-json", "detail": "fallback=block · 낮은 unknown_threshold (strict 기본). bundled config/ 가 base, repo `.harness/` 가 code별 override (errors 가 병합 로드)" }, { "name": "commons.md", "role": "거버넌스 SSOT (프로젝트-무관 do/dont 룰셋)", "id": "data-commons-md", "detail": "bundled `config/commons.md` = base, repo `.harness/commons.md` = **slug별 override** — `commons.ts:body()` 병합 로드 (severity-map 과 같은 규약: repo 가 그 키만 이김). 통째 교체 아님: override slug 만 repo 승 · base 에 없으면 append · 읽기 실패 → base 폴백. `{scratchDir}` 는 병합 후 치환(dont 줄 리터럴 경로 금지 — 자기모순)." }, { "name": "harness.config.json", "role": "repo 루트 설정", "id": "data-harness-config-json", "detail": "project·lockdown·verify.checks 등" } ] } ] }, { "name": "plugin — 배포 (자기완결 · 공용셋)", "role": "sidecar 를 Claude Code 플러그인으로 배포 — 훅 + 슬래시 명령 + CLI 본체가 ONE 유닛. 프로젝트 무관·중앙 1곳", "id": "group-plugin", "detail": "sidecar 를 에이전트 하네스에 배포 — 거버넌스 엔진(modules/*.ts)은 한 벌, 배선만 에이전트별. Claude Code = hooks/hooks.json+run.sh 플러그인 · Pi = pi/sidecar.ts 확장. 프로젝트 무관·중앙 1곳", "children": [ { "name": "hooks/hooks.json + hooks/run.sh", "role": "거버넌스 훅 배선 (guards + injects + SessionStart) → run.sh 디스패처", "id": "config-hooks-hooks-json-hooks-run-sh", "detail": "run.sh = 단일 해석점: ${CLAUDE_PLUGIN_ROOT}/bin/sidecar(번들) 우선 → 전역 `sidecar` 폴백 → 둘 다 없으면 exit 0(무음). CC가 stdin 으로 주는 페이로드를 exec 로 상속(pre 가 stdin 에서 tool input 읽음)" }, { "name": "commands/*.md", "role": "슬래시-명령 위임자 셋 (bare /cmd · shadow 가 미러하는 SOURCE)", "id": "config-commands-md", "detail": "프런트매터(description+Triggers+argument-hint+allowed-tools) + `!sidecar $ARGUMENTS` 위임자. 플러그인 명령으로 로드 안 됨(plugin.json `commands: []` → bare /cmd 와 `/sidecar:cmd` picker 중복 방지). `shadow` 가 ~/.claude/commands/ 에 bare /cmd 미러. 공용셋·프로젝트 무관 · hook-내부 제외" }, { "name": "pi/sidecar.ts (+ modules/pi.ts)", "role": "Pi 코딩에이전트 브리지 확장 — 같은 sidecar CLI 를 Pi 라이프사이클에 배선", "id": "config-pi-sidecar-ts", "detail": "Pi 이벤트→sidecar verb 번역(CC hooks 와 같은 CLI): before_agent_start=매턴 inject+prompt-scan · session_start=gc · tool_call=pre 가드(deny→block) · tool_result=post(advisory). Stop 하드게이트는 Pi 에 blocking stop 훅 없어 CC 전용. 설치 `sidecar pi install`. 상세 pi/CLAUDE.md" } ] }, { "name": "module-grading — 모듈 분류", "role": "'규칙을 JSON으로 빼면 다른 repo에서 그대로 도나?'", "id": "group-module-grading", "detail": "YES면 ①, NO면 ③", "children": [ { "name": "① 완전 범용", "role": "이 repo에 포함", "id": "grade-fully-generic", "detail": "pre/post/prompt/lint/verify/errors/ledger/bitter-gate/audit/gc/ing/convergence/sync/pool/architecture/claudemd" }, { "name": "② 부분 범용", "role": "패턴만 문서화, 코드 미포함", "id": "grade-partly-generic", "detail": "구현이 특정 디렉토리 규약에 묶임 (extending.md)" }, { "name": "③ 도메인 전용", "role": "포함 금지", "id": "grade-domain-specific", "detail": "deploy/db/ssh/health/site/정산 등 특정 인프라·비즈니스에 묶임" } ] }, { "name": "config-driven — config 주도 설계", "role": "lib/config.ts가 harness.config.json을 읽어 머지", "id": "group-config-driven", "detail": "설정 0으로도 합리적 기본 동작 + 점진적 커스터마이즈", "children": [ { "name": "project", "role": "로그/리포트 라벨", "id": "config-project", "detail": "" }, { "name": "lockdown.files", "role": "L0 잠금 파일", "id": "config-lockdown-files", "detail": "opt-in, 기본 0개" }, { "name": "enforcementFile / keywordsFile / severityMapFile", "role": "규칙 파일 위치", "id": "config-enforcementfile-keywordsfile-severitymapfile", "detail": "없으면 번들 기본 fallback" }, { "name": "verify.checks[]", "role": "검증 명령 선언", "id": "config-verify-checks", "detail": "{id,cmd,timeoutMs,slow}" }, { "name": "lint.freshnessFiles[] · convergence · guides[] · ledger.staleSec", "role": "기타 모듈별 대상 선언", "id": "config-lint-freshnessfiles-convergence-guides-ledger-stalesec", "detail": "" }, { "name": "dojo {defaultLang,stack,delegate}", "role": "dojo 스캐폴더 기본 스택 (선택)", "id": "config-dojo-defaultlang-stack-delegate", "detail": "엔진 무하드코딩 — 선호 학습/커널 스택을 per-repo config 가 운반. delegate 설정+hexa 있으면 `hexa dojo ` 위임. 부재 시 제네릭 py stub" }, { "name": "docs.scratchDir = state", "role": "산출물 단일 루트 (commons preserve-state 규칙문에 `{scratchDir}` 로 치환주입)", "id": "config-docs-scratchdir-state", "detail": "모든 작업 산출물(실험·벤치·verdict·스크래치)을 한 루트로 통일(c5) — 기본 repo-root `state/`, 평면 `/`. 엔진 무하드코딩: commons `preserve-state` + enforcement 규칙 데이터가 `{scratchDir}` 플레이스홀더 → `commons body()` / `pre.ts loadConfig()` 가 치환 (산문=원값 · 정규식=escape 후 · 예외항목=`{scratchDir}/` 후행슬래시 필수 — bare 값은 부분일치로 가드를 뚫음)." }, { "name": "docs.claudeMdSections[]", "role": "repo-root CLAUDE.md 필수 섹션 게이트", "id": "config-docs-claudemdsections", "detail": "repo-root CLAUDE.md 필수 헤딩 (기본 `[\"## Project\",\"## Tree\"]` · commit block · `docs.ts`). 각 헤딩 존재+비어있지않은 본문 필수 · tree-계열은 ├─/└─+설명 warn. Project→`NO-DESC`/Tree→`NO-TREE`/기타→`SECTION-MISSING`. `[]`=끔. 깊은 구조 SSOT=ARCHITECTURE.json · do/dont 포맷검사는 이 섹션만 prose 면제(`commons.ts`)" }, { "name": "labMode: \"off\"|\"fable\"|\"sol\"|\"full\"", "role": "리포별 프론티어 위임 토글 (lab-mode)", "id": "config-labmode", "detail": "그 repo 의 `harness.config.json` 이 유일한 스위치 — **호스트 전역 스코프 없음**(ambient 플래그는 무관한 repo 까지 매턴 지시문을 물렸다 · 한 repo 가 다른 repo 를 못 켠다). 값=문자열 1개 `off|fable|sol|full` · **미지정·미인식=off**=무주입 · `lab-mode on/off` 가 형제키 보존 write-back." }, { "name": "archSeed: true (기본 ON)", "role": "설계 SSOT 부트스트랩 씨앗 옵트아웃", "id": "config-archseed", "detail": "기본 ON — 요구는 트리가 **없는** repo 에 닿아야 의미가 있으므로 옵트인 마커는 금지(config-ts-1). `false` 면 `turn-close inject` 의 🌱 씨앗(트리 부재 · md-only JSON 승격)만 끄고 트리오 leg 는 무관. 설계트리가 정말 필요없는 스크래치/비코드 repo 용 · 옵트아웃은 추적되는 diff." }, { "name": "ingSeed: true (기본 ON)", "role": "ING 보드 부트스트랩 씨앗 옵트아웃", "id": "config-ingseed", "detail": "기본 ON · archSeed 와 별도 knob(수명이 다름 — arch 는 1회 작성으로 끝, ing 은 보드를 열 때까지 상시 알림). `false` 면 `refs/heads/ing` 부재 repo 에서 🌱 ING 씨앗 1줄만 끈다. 일회성/스크래치 repo 용." } ] }, { "name": "debt — 들어낸 비범용 부채 (교훈)", "role": "원본 도구 일반화 시 제거한 안티패턴", "id": "group-debt", "detail": "이 도구에서 피할 것", "children": [ { "name": "경로 하드코딩", "role": "절대경로 고정", "id": "debt-path-hardcode", "detail": "→ repo-root 상향 탐색 + env override" }, { "name": "L0 목록 하드코딩", "role": "fallback 배열에 실제 파일 박힘", "id": "debt-l0-list-hardcode", "detail": "→ config.lockdown (opt-in)" }, { "name": "도메인 모듈 번들", "role": "배포/머니 모듈이 코어에 섞임", "id": "debt-domain-module-bundle", "detail": "→ ①/③ 등급 분리, ③ 제외" }, { "name": "SSOT 단일 repo 고정", "role": "두 repo가 한 repo의 JSON에 수렴", "id": "debt-ssot-single-repo", "detail": "→ per-repo 로그, 공유는 sync로 명시" }, { "name": "검증 명령 하드코딩", "role": "pnpm test 등 박제", "id": "debt-verify-cmd-hardcode", "detail": "→ verify.checks[]로 선언" } ] }, { "name": "setup — 새 repo에 세우는 절차", "role": "참조: install.md(통합), extending.md(확장)", "id": "group-setup", "detail": "", "children": [ { "name": "1. 엔진 추가", "role": "git submodule add …/sidecar .harness-engine", "id": "step-1", "detail": "" }, { "name": "2. config 작성", "role": "cp harness.config.example.json harness.config.json", "id": "step-2-config", "detail": "→ 수정" }, { "name": "3. (선택) 규칙 추가", "role": "cp config/enforcement.json .harness/", "id": "step-3", "detail": "→ repo 규칙 append" }, { "name": "4. hook 배선 (전역 1벌)", "role": "sidecar install — 전역 ~/.claude/settings.json 에만 배선", "id": "step-4-hook-1", "detail": "GLOBAL-ONLY: per-repo .claude/settings.json 금지(전역과 중복돼 컨텍스트 2~3중 주입되던 버그). init 은 repo settings 를 안 쓰고, install-hooks --repo 는 거부. 훅은 호스트당 한 번 sidecar install 로 전역 배선 → 모든 repo 발사. (@convergence INIT-INJECT-DUP)" }, { "name": "5. 검증", "role": "sidecar audit / sidecar ci list", "id": "step-5", "detail": "" }, { "name": "6. 운영", "role": "commit 전 verify, 세션 끝 ing, 규칙 추가 전 bitter-gate", "id": "step-6", "detail": "" } ] }, { "name": "viewer — 아키텍처 뷰어", "role": "이 트리(ARCHITECTURE.json)를 사람이 보는 도구", "id": "group-viewer-group", "detail": "JSON이 SSOT, HTML/서버는 표시 수단", "children": [ { "name": "ARCHITECTURE.html", "role": "의존성 0 자립형 뷰어", "id": "viewer-architecture-html", "detail": "json fetch→컬럼 그리드 트리(접기·검색·다크모드·드롭 fallback)" }, { "name": "serve.py", "role": "python3 serve.py [port] [--no-open]", "id": "viewer-serve-py", "detail": "로컬 정적 서버 + 브라우저 자동 오픈 — file:// fetch 차단 우회" } ] } ] }, "results": { "note": "벤치·실험 결과 누적 SSOT (preserve-state · honesty). 방향 3단계(convergence 공유) pos-conv🟢|in-prog🔄|neg-conv🔴 + 결과전용 선행상태 untested⚪(등록됐으나 아직 안 잼 · 4번째 방향 아님). 세부 '왜'(막힘·불확정·무효)는 metric 자유서술. kind=bench|experiment · value=핵심결과 · metric=측정값/근거 · subject=대상. convergence 와 달리 file-touch 표면화·Stop게이트 없음 — 순수 누적(falsified/음성 보존). 같은 (kind,subject) 재측정은 update-in-place.", "records": [] } }