therookie 0.4.5 → 0.4.13

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.
@@ -70,11 +70,18 @@ orphan_mark_flushed() {
70
70
  "$(printf '{"status":"flushed","source":"%s","message_count":%s}' "$source" "$mc")"
71
71
  }
72
72
 
73
+ # reason 은 감사 기록용 — 생략 시 기존 동작(bootstrap_old) 유지.
74
+ # spec 2026-07-20-codex-exec-prompt-pollution §4.2.1: codex_exec skip 은 reason 을 따로 남긴다
75
+ # (bootstrap_old 를 그대로 쓰면 거짓 감사 기록이 된다).
73
76
  orphan_mark_baseline() {
74
- local sid="$1"
77
+ local sid="$1" reason="${2:-bootstrap_old}"
75
78
  [ -n "$sid" ] || return 1
79
+ # reason 은 marker JSON 에 그대로 박히므로 안전 문자만 남긴다 — 따옴표·백슬래시가 섞이면
80
+ # marker 가 깨져 파싱하는 쪽(scanner·telemetry)이 오작동한다 (codex 코드검토 R1 P2).
81
+ reason="$(printf '%s' "$reason" | tr -cd 'A-Za-z0-9_.:-')"
82
+ [ -n "$reason" ] || reason="unknown"
76
83
  _orphan_write_marker "$(_orphan_baseline_dir)" "$sid" \
77
- '{"status":"baseline_skipped","reason":"bootstrap_old"}'
84
+ "$(printf '{"status":"baseline_skipped","reason":"%s"}' "$reason")"
78
85
  }
79
86
 
80
87
  # P0.4 — 짧고 내용 없는(low_value) 세션 marker. record_pending 대신 호출 → 재적재 차단.
@@ -142,6 +142,31 @@ transcript_messages_json_codex() {
142
142
  " "$path" 2>/dev/null || echo '[]'
143
143
  }
144
144
 
145
+ # Codex rollout 이 `codex exec`(비대화형 one-shot)로 생성됐는지. exit 0 = 그렇다.
146
+ # spec 2026-07-20-codex-exec-prompt-pollution §4.2 — codex exec 는 차차가 도구로 부른 호출이라
147
+ # 그 안의 role='user' 블록은 에이전트가 쓴 프롬프트다(두목 발화 아님). 사람의 Codex 사용
148
+ # (Codex Desktop·codex-tui)은 여기 걸리지 않는다.
149
+ # ⚠️ fail-open 범위: session_meta 부재·파싱 실패뿐 아니라 **node 실행 실패도** 1(= exec 아님)로
150
+ # 떨어진다(codex 코드검토 R1 P2). 실무상 노출은 거의 없다 — node 가 없거나 깨졌으면 앞선
151
+ # transcript_codex_cwd 가 먼저 빈 값을 반환해 codex_skip_unknown_cwd 로 걸러지고,
152
+ # 그마저 통과해도 transcript_messages_json_codex 가 빈 배열을 내 skip 된다.
153
+ # 과캡처(사람 대화 유실 없음) 쪽으로 여는 것이 기존 sweep 기조와도 일치한다.
154
+ transcript_codex_is_exec() {
155
+ local path="$1"
156
+ [ -f "$path" ] || return 1
157
+ node -e "
158
+ const fs=require('node:fs');
159
+ try{
160
+ const fd=fs.openSync(process.argv[1],'r'); const buf=Buffer.alloc(65536);
161
+ const n=fs.readSync(fd,buf,0,65536,0); fs.closeSync(fd);
162
+ const first=buf.toString('utf8',0,n).split('\n').find(l=>l.trim());
163
+ const j=JSON.parse(first);
164
+ const p=(j.type==='session_meta'&&j.payload)?j.payload:{};
165
+ process.exit((p.originator==='codex_exec'||p.source==='exec')?0:1);
166
+ }catch{ process.exit(1); }
167
+ " "$path" 2>/dev/null
168
+ }
169
+
145
170
  # Codex rollout 첫 줄 session_meta 의 cwd (프로젝트 매칭용). 첫 64KB 만 읽음.
146
171
  transcript_codex_cwd() {
147
172
  local path="$1"
@@ -69,6 +69,23 @@ if [ "$SOURCE" = "clear" ] || [ "$SOURCE" = "compact" ]; then
69
69
  if command -v skill_gate_record_epoch >/dev/null 2>&1; then
70
70
  skill_gate_record_epoch "$SESSION_ID" "$TRANSCRIPT_PATH" || true
71
71
  fi
72
+ # spec 2026-07-25 §3.2 — recall episode 파서용 append-only boundary log.
73
+ # skill-gate 의 .epoch(단일 offset·덮어쓰기, skill-로드 감지 전용)와 별개다. episode 파서는
74
+ # multiple compact 이력 + transcript_path 검증이 필요하므로 전용 append-only 파일을 쓴다.
75
+ if [ -n "$SESSION_ID" ] && [ -n "$TRANSCRIPT_PATH" ]; then
76
+ ROOKIE_BND_SID="$SESSION_ID" ROOKIE_BND_TP="$TRANSCRIPT_PATH" ROOKIE_BND_SRC="$SOURCE" node -e '
77
+ try {
78
+ const fs=require("fs"), path=require("path"), crypto=require("crypto"), os=require("os");
79
+ const sid=process.env.ROOKIE_BND_SID, tp=process.env.ROOKIE_BND_TP, src=process.env.ROOKIE_BND_SRC;
80
+ let size=0; try{ size=fs.statSync(tp).size; }catch{}
81
+ const dir=path.join(os.homedir(),".rookie","recall-episode");
82
+ fs.mkdirSync(dir,{recursive:true,mode:0o700});
83
+ const key=crypto.createHash("sha256").update(sid).digest("hex");
84
+ const line=JSON.stringify({transcript_path:tp,offset_bytes:size,source:src,recorded_at:new Date().toISOString()})+"\n";
85
+ fs.appendFileSync(path.join(dir,key+".boundaries"),line,{mode:0o600});
86
+ } catch {}
87
+ ' 2>/dev/null || true
88
+ fi
72
89
  fi
73
90
 
74
91
  # Codex hook 은 실행 cwd 가 프로젝트와 다를 수 있어 stdin cwd 우선(없으면 $PWD — Claude 무변화).
@@ -5,7 +5,8 @@
5
5
  "matcher": ".*",
6
6
  "__rookie_managed": true,
7
7
  "hooks": [
8
- { "type": "command", "command": "__FLUSH_SESSION_CMD__" }
8
+ { "type": "command", "command": "__FLUSH_SESSION_CMD__" },
9
+ { "type": "command", "command": "__RECALL_EPISODE_METRIC_CMD__" }
9
10
  ]
10
11
  }
11
12
  ],
@@ -32,6 +33,20 @@
32
33
  "hooks": [
33
34
  { "type": "command", "command": "__GUARD_DESTRUCTIVE_DB_CMD__" }
34
35
  ]
36
+ },
37
+ {
38
+ "matcher": "Edit|Write|NotebookEdit",
39
+ "__rookie_managed": true,
40
+ "hooks": [
41
+ { "type": "command", "command": "__WORK_RECALL_GATE_CMD__" }
42
+ ]
43
+ },
44
+ {
45
+ "matcher": "AskUserQuestion",
46
+ "__rookie_managed": true,
47
+ "hooks": [
48
+ { "type": "command", "command": "__WORK_RECALL_GATE_CMD__" }
49
+ ]
35
50
  }
36
51
  ],
37
52
  "PostToolUse": [
@@ -41,6 +56,13 @@
41
56
  "hooks": [
42
57
  { "type": "command", "command": "__POSTTOOL_RECALL_NUDGE_CMD__" }
43
58
  ]
59
+ },
60
+ {
61
+ "matcher": "Bash|.*rookie_recall",
62
+ "__rookie_managed": true,
63
+ "hooks": [
64
+ { "type": "command", "command": "__WORK_RECALL_GATE_CMD__" }
65
+ ]
44
66
  }
45
67
  ],
46
68
  "UserPromptSubmit": [
@@ -42,6 +42,9 @@ cat | ROOKIE_GATE_DIR="$GATE_DIR" node -e '
42
42
  const SKILL_MSG="⚠️ 이 세션에서 아직 rookie Skill 을 로드하지 않았습니다. 다른 작업·응답 전에 먼저 rookie Skill 을 호출하세요 — 회상(recall)·미link 등록 제안·기억 저장 절차가 그 안에 있습니다. (이미 호출했다면 이 안내는 사라집니다.)";
43
43
  const RECALL_MSG="🧠 이 프롬프트에 과거 맥락·방법 회상 신호가 있습니다. grep·파일 열람·자기판단·두목 되묻기 이전에 GET /api/rookie/recall 을 먼저 호출하세요 (brand.ts 회상-순서 룰).";
44
44
  const CKPT_MSG="📌 이 세션이 길어지고 있습니다. 직전 checkpoint 이후 마일스톤(커밋·배포·설계 수렴·단계 전환)이 지났다면 checkpoint 진입카드를 저장하세요 — 지나지 않았다면 무시해도 됩니다.";
45
+ // 미로드 + 회상신호 전용(spec 2026-07-22 D1). 두 메시지를 잇기만 하면 "먼저 Skill" vs "먼저 recall" 이
46
+ // 충돌하고, recall 절차 자체가 SKILL.md 안에 있어 미로드 세션은 그 절차를 아직 못 읽었다 — 순서를 못박는다.
47
+ const COMBO_MSG="🧠 이 프롬프트에 과거 맥락·방법 회상 신호가 있는데, 이 세션은 아직 rookie Skill 을 로드하지 않았습니다. 순서대로 하세요 — ① rookie Skill 을 먼저 호출하고 ② 그 안의 회상 절차로 이번 프롬프트에 대해 GET /api/rookie/recall 을 최소 1회 호출한 뒤 ③ 답하세요. grep·파일 열람·git log 추론·두목 되묻기는 그 다음입니다.";
45
48
  const injectMsg=(msg)=>{ try{ process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:msg}})); }catch{} };
46
49
  try{
47
50
  const fs=require("fs"), path=require("path"), crypto=require("crypto");
@@ -51,15 +54,20 @@ cat | ROOKIE_GATE_DIR="$GATE_DIR" node -e '
51
54
  const tp=(p.transcript_path||"").toString();
52
55
  const prompt=(p.prompt||"").toString();
53
56
  // recall 신호(과거 방법 재현) — 과거형 집중. 현재형(구현해)·미래순서(배포 전에/이전에)·조건형(했으면) 제외.
54
- const PAST_TIME=/어제|그때|저번|지난번|예전/;
57
+ // spec 2026-07-22 D2 — 수량+단위+"전" 추가. 뒤에 조사·경계만 허용해 합성어(전용·전송) 배제,
58
+ // 수량 없는 "배포 전에"(미래 순서)는 여전히 미탐.
59
+ const PAST_TIME=/어제|그때|저번|지난번|예전|아까|(방금|(\d+|한|두|두어|세|네|몇)\s*(분|시간|일|주|주일|달|개월|년))\s*전(?=[\s,.!?)\]]|$|에|부터|까지|의|엔|은|는|이|가|도|만|과|와|랑)|(전|이전)에\s*(만든|만들었|했던|하던|쓰던|썼던|정한|결정한|작업한|구현한|짠)/;
55
60
  const PAST_HOW=/어떻게[\s\S]{0,15}?(했|였|만들었|제작했|구현했|처리했|뽑았|짰|썼|캡[처쳐]했)(?!으면|으려|으실|으세|었으면)|어떻게\s*(하더라|했더라|됐)/;
56
- const REPRO=/재현|기억\s*나|했었(?!으면)/;
61
+ const REPRO=/재현|기억\s*나(?!면|다면|시|실|셔|세)|했었(?!으면)|했잖|하지\s*않았(?:나|어|었)/;
62
+ // spec 2026-07-22 D2 — 두목이 회상 실패를 지적하는 발화 자체가 최우선 트리거(최후 안전망).
63
+ // 후행 배제(면|다면|시|실|셔|세)로 조건형·경어체("기억 안 나면 물어봐", "기억 못 하시면") 제외.
64
+ const AMNESIA=/기억(을|은|이)?\s*(못|안)\s*(하|해|되|나|난)(?!면|다면|시|실|셔|세)|까먹(?!지)|처음\s*(인\s*것|인|것)\s*처럼|또\s*(잊(?!지)|까먹(?!지))/;
57
65
  // spec 2026-07-10 §3.B1 — SKILL.md 회상 표와 정합 확장(작업 연속·결정 회상·환경 질의).
58
66
  const CONTINUE=/이어서|이어가|계속\s*(하|해|진행)|마저|어디까지|재개|하던\s*(거|것|작업)/;
59
67
  const DECIDED=/뭐였|뭐더라|왜\s*였|왜\s*그랬|어떻게\s*됐|결정했|정했었/;
60
68
  // ENVQ 는 명사 단독 매칭 금지(코드 얘기 중 "포트" 빈발) — 의문 신호 동반 필수.
61
69
  const ENVQ=/(포트|유저명|계정|경로|도메인|호스트|엔드포인트|서버\s*주소)[^\n]{0,12}(뭐|몇|어디|알아|기억|였)/;
62
- const recallSignal = PAST_TIME.test(prompt)||PAST_HOW.test(prompt)||REPRO.test(prompt)||CONTINUE.test(prompt)||DECIDED.test(prompt)||ENVQ.test(prompt);
70
+ const recallSignal = PAST_TIME.test(prompt)||PAST_HOW.test(prompt)||REPRO.test(prompt)||CONTINUE.test(prompt)||DECIDED.test(prompt)||ENVQ.test(prompt)||AMNESIA.test(prompt);
63
71
  const key= sid ? crypto.createHash("sha256").update(sid).digest("hex") : "";
64
72
  let skillLoaded=false;
65
73
  if(key && fs.existsSync(path.join(gateDir,key+".loaded"))){ skillLoaded=true; } // marker = 로드됨 캐시(재scan 회피), early-return 아님
@@ -96,7 +104,10 @@ cat | ROOKIE_GATE_DIR="$GATE_DIR" node -e '
96
104
  fs.writeFileSync(pf,String(n),{mode:0o600});
97
105
  }catch{ ckptDue=false; } // 카운터 실패 → 조용히 skip (fail-soft)
98
106
  }
99
- if(!skillLoaded) return injectMsg(SKILL_MSG); // 미로드 → skill 리마인더 (recall 리마인더 안 함, 한 turn 한 메시지)
107
+ // spec 2026-07-22 D1 — 미로드 세션은 SKILL.md 회상 규범이 컨텍스트에 없다. 여기서 recall 지시까지
108
+ // 꺼버리면 회상이 가장 필요한 상태가 무방비가 된다(2026-07-22 catsper 사고). 한 turn 한 메시지는 유지.
109
+ if(!skillLoaded && recallSignal) return injectMsg(COMBO_MSG);
110
+ if(!skillLoaded) return injectMsg(SKILL_MSG); // 미로드 + 무신호 → skill 리마인더
100
111
  if(recallSignal) return injectMsg(RECALL_MSG); // 로드됨 + 과거-방법 신호 → recall 리마인더
101
112
  if(ckptDue) return injectMsg(CKPT_MSG); // 로드됨 + 무신호 + 임계 도달 → checkpoint 리마인더
102
113
  return; // 로드됨 + 신호 없음 → 조용
@@ -9,6 +9,12 @@ installed_by: __INSTALLED_BY__
9
9
 
10
10
  # Rookie 연결 스킬
11
11
 
12
+ > **references/ 분리 규칙**: 저빈도 절차는 이 스킬 폴더의 `references/` 에 있다. 아래 신호가 보이면 **반드시 해당 파일을 Read 하고 절차 전부를 그대로** 수행한다 — 요지 기억만으로 축약 실행 금지.
13
+ > - ``🛟 복구 세션`` 블록 / ``🍞 컨텍스트 다이어트 권고`` 블록 → `references/recovery.md`
14
+ > - 스펙 동기화 directive(`[루키 지시]` — link 후속·소개글·scan-specs) → `references/link.md`
15
+ > - `rookie save` 본문 작성 전(세션 첫 저장 전 1회 — 스키마·all_committed 게이트·supersede 처리) → `references/save.md`
16
+ > - task status 갱신·세션 마무리 reconcile·`## 🎯 완료 후보 감지` 블록·계층 도출 → `references/tasks.md`
17
+
12
18
  ## ⚡ 로드 직후 필수 (다른 응답 전에)
13
19
 
14
20
  진입카드·미link 상태는 이 skill 이 로드한다 — **다른 말 전에** 아래 "세션 시작" 절차를 실행한다:
@@ -16,7 +22,7 @@ installed_by: __INSTALLED_BY__
16
22
  1. `GET /api/rookie/context` 를 curl 로 호출해 `system_prompt`·`current_project`·`entry_cards` 전문을 받는다.
17
23
  2. **진입 카드 확인:**
18
24
  - **1순위**: `entry_cards[]` 를 직접 읽는다. 비어있지 않으면 **그 작업을 이어가려는 사용자 의도를 우선 가정**해 먼저 언급한다. 안전 신호 — `grade` `warn`/`old` → 이미 완료됐을 수 있으니 현재 상태부터 점검 / `is_meta_card` → 메타 카드 의심·단일 fact 분리 권고·`body` 절단본(전량 단정 금지) / `is_vague` → 목표 수준 카드 — recall 로 방법 보강 후 착수 / `has_contradiction` → 단정 전 점검 / `reasoning` → 근거로 고려.
19
- - **activity-stale**: `activity_stale=true`(또는 `newer_activity_count>0`) → 카드 이후 같은 프로젝트에서 세션이 더 진행됐다. `effective_grade`(age·activity 중 높은 등급)를 권위 기준으로, recall + 현재 상태(git log·해당 파일) 점검 후 착수. "이게 마지막 작업" 단정 금지.
25
+ - **activity-stale**: `activity_stale=true`(또는 `newer_activity_count>0`) → 카드 이후 같은 프로젝트에서 세션이 더 진행됐다. `effective_grade`(age·activity 중 높은 등급)를 권위 기준으로, recall + 현재 상태(git log·해당 파일) 점검 후 착수. "이게 마지막 작업" 단정 금지. **단 이 신호는 "카드 이후 활동이 있었다"는 뜻이지 "카드 내용이 틀렸다"는 뜻이 아니다** — 카드를 대체할 내용은 `ephemeral_recovery` 또는 카드 `recorded_at` 보다 최신인 회상 결과로만 확인한다(아래 "진입 카드 = 최신성 기준선").
20
26
  - **`ephemeral_recovery`(임시 복구, 저장 아님)**: activity-stale 카드에 붙는 read-time 합성물. `id` 없음 — `supersedes`·`task_id` 연결 금지. stale 카드와 **함께** 표시("직전 카드(오래됨) + 🛟 임시 복구: <headline>"). `synthesis_tier=bare`=세부 기록 없음으로만, `evidence`="바뀐 것으로 보고됨"(확인 아님). 진짜 다음 카드를 저장하면 다음 조회부터 사라진다.
21
27
  - **폴백**(구버전 서버): `system_prompt` 의 `## 🔖 진행 중 작업 카드` 마커를 grep(유동 위치 — head/tail 훑기 금지). 텍스트 경로엔 `🔀 세션 N건`·`🛟 임시 복구` 라인으로 동기.
22
28
  - **무-주제 "이어서/계속하자"** 는 `entry_cards`(최신순) 우선. relevance recall 은 특정 주제를 명시했을 때만.
@@ -44,43 +50,11 @@ installed_by: __INSTALLED_BY__
44
50
  - 없으면 **매 세션 1회** "지금 등록할까요? ('그만 물어봐' 가능)" 제안 — git remote 있는 repo 면 특히.
45
51
  - **승인 시** 루키가 `rookie link` 직접 실행 + 1줄 보고. **거부 시** link-dismissed.json 에 cwd 추가. dismiss 후에도 "등록해줘" 하면 즉시 실행.
46
52
  - 자동 등록 금지(승인 전제). recall 0건(신규면 정상)과 미link 는 다른 신호 — **후자만** 알린다.
47
-
48
- 6.5. **link 후속 — 스펙 연결·소개글**: `system_prompt` 에 스펙 동기화 directive(`[루키 지시]`)가 있을 때만 능동 제안한다(판정은 서버 책임). **미연결** directive → "스펙을 연결할까요?" → 승인 시 (A)+(C) / **반쪽** directive → "동기화할까요?" → 승인 시 (A) 의 task 동기화만(PUT 생략).
49
- - **(A) 스펙 연결·동기화**: 로컬 스펙 문서를 찾아 `spec_doc_paths` 설정 + 문서별 task 동기화(문서 1개=task 1개). **task 는 전부 todo 시작(스펙 ≠ 구현)** — sync 에 `status` 금지(서버가 todo 고정), 재동기화해도 기존 status 보존. 진척은 `PATCH /api/rookie/tasks`.
50
- - 미연결일 때만: `PUT /api/rookie/projects/spec-source {"project_id":"<id>","spec_doc_paths":["docs/specs/**"]}`
51
- - 공통: `POST /api/rookie/tasks/sync {"project_id":"<id>","items":[{"title":"<스펙 제목>","spec_ref":"<파일경로>","spec_html":"<렌더 HTML>"}]}`
52
- - 스펙 문서가 없으면 "같이 만들까요?" → brainstorming 으로 작성 후 연결.
53
- - **(C) 소개글**: 비어 있으면 README·CLAUDE.md 파악 후 1~2문장 초안을 `PATCH /api/rookie/projects {"id","description"}` — 기존 소개글 덮어쓰기 금지. 완료 시 1줄 보고.
54
- - **거부** 시 `POST /api/rookie/projects/spec-dismiss {"project_id":"<id>","dismissed":true}` (되돌리기는 `false`). 명시 요청은 dismiss 와 무관하게 수행. 미link(§6) → 미스펙(directive) → 정상 3단계 중 해당 단계만 1회 알린다(승인 전제).
55
-
56
- 6.6. **spec 후속 자동 스캔**: link + `spec_doc_paths` 설정 시 세션 시작 1회 `rookie scan-specs`(하루 1회 throttle). 결과는 조용히 둔다(proposal 은 `/projects`). 명시 요청 시 `--force`. 미link·미설정·실패 조용히 skip.
57
-
58
- 7. **임베딩 헬스 경고**: `embedding_health.degraded:true` 면 세션당 1회 — ``⚠️ 회상 임베딩 가동률 {ratio×100}% (최근 {window_days}일 {call_count}건) — 키/quota 점검 필요.`` `false` 면 침묵.
59
- 8. **완료 후보 surface**: `## 🎯 완료 후보 감지` 블록의 `🎯 <task_id(전체 UUID)> "<title>" ← <sha> <subject>` 줄이 보이면 **다른 응답 전에** 후보별 done 승인 게이트로 여쭙는다(UUID 는 PATCH 용, 두목껜 title 만. `<sha>` 는 그대로): `"'<title>' 을 완료로 올릴까요? — 근거: 커밋 <sha> <subject>"` → OK 시에만 `PATCH {id, status:'done', detail:'<기존>\n\n✅ 완료 <date> — 커밋 <sha>'}` (**자동 done 금지**) / "아직/보류" 면 `PATCH {id, detail:'<기존>\n__done_defer:<sha>'}` 로 재알림 차단 — 이후 **다른 새 커밋** 매칭 시만 재surface. 블록 없으면 침묵. 근거는 주입된 git 출력만(self-loop 금지).
60
- 9. 실패(HTTP·네트워크)는 조용히 폴백 — 현재 프로젝트 `CLAUDE.md` 만 따릅니다.
61
-
62
- ## 복구 세션 tail 추출
63
-
64
- 비정상 종료 세션은 Stop hook 이 못 돌아 fact·진입카드가 없다 — chunk 복구는 `orphan_recover`, **fact 추출은 차차 책임**(서버 추출 off). 컨텍스트에 ``🛟 …`` 블록과 ``- <sid> :: <transcript_path>`` 목록이 보이면 **다른 응답 전에** 처리한다:
65
-
66
- 1. `rookie orphan-extract --batch=5` — 대기 세션을 오래된 순으로 읽어 rule-base 초안(요약·파일 경로·git sha, redact 통과)을 뽑는다. transcript 원문 통째 Read 금지(부족 시 보조 — Claude `~/.claude/projects/`, Codex `~/.codex/sessions/`).
67
- 2. 초안에서 **저장 가치가 있는 것만** — 운영 사실/결정→working_card, 파일·결과물→evidence, 명시적 자기규칙→self_rule, 취향→personal_fact. 잡담·민감정보 금지(redactor 원칙). 0건 세션도 정상.
68
- 3. **recall 로 dedup 후** 누락분만 `POST /api/rookie/batch-mutate`(`project_id` 포함).
69
- 4. 처리한 세션마다(0건이어도) 초안 하단의 완료 marker 명령을 **그대로** 실행해 큐에서 비운다 — 안 하면 매 세션 재노출:
70
- ```bash
71
- source "$HOME/.rookie/bin/lib/orphan-recovery.sh" 2>/dev/null && orphan_mark_client_extracted '<sid>'
72
- ```
73
- (로컬 설치면 `<project>/.rookie/bin/…`) Codex 세션은 `ROOKIE_ORPHAN_DIR="…/codex"` prefix. transcript 소실 세션도 marker 로 비운다.
74
- 5. 한 줄 보고(`[복구] 세션 N건 tail 추출·저장`). raw transcript·UUID 나열 금지. 남은 대기는 다음 세션에서.
75
-
76
- ## 컨텍스트 다이어트 (주1회, 승인 게이트)
77
-
78
- SessionStart hook 이 주 1회 임계 초과 시 ``## 🍞 컨텍스트 다이어트 권고`` 블록을 주입한다. 보이면 **다른 응답 전에 1회** 제안, 없으면 침묵. **전 항목 승인 게이트 — 자동 실행 없음.** 승인 시:
79
- - **CLAUDE.md**: 아카이브성 내용 `docs/` 이관 + 참조 1줄 치환. 의미 단위 압축(단순 truncate 금지). diff 제시 → 승인 후 커밋.
80
- - **스킬 정리**: 0회 후보 제시 → 고른 것만. settings 비활성화(가역) 우선 / 폴백 `~/.claude/skills/<name>` → `skills.disabled/` 이동(삭제 아님, symlink·플러그인은 안내만). keep 은 `~/.rookie/context-diet-keep.json`.
81
- - **루키 자기 다이어트**: rookie Skill 문서 중복 정리 — diff 제시 → 승인 후 반영(템플릿 SSOT 커밋 + 재배포).
82
-
83
- **거부/보류 snooze**: `~/.rookie/context-diet-snooze.json` 에 `{"<key>":"<ISO until>"}` 30일(전체 거부 90일. key = claude_md_project·claude_md_total·skills_agents_desc·rookie_self·unused_skills). 실패 항목만 1줄 보고. 현황 `rookie doctor`.
53
+ 7. **link 후속·spec 스캔**: `system_prompt` 에 스펙 동기화 directive(`[루키 지시]`)가 보이면 `references/link.md` 를 Read 후 절차대로(승인 게이트).
54
+ 8. **임베딩 헬스 경고**: `embedding_health.degraded:true` 면 세션당 1회 — ``⚠️ 회상 임베딩 가동률 {ratio×100}% (최근 {window_days}일 {call_count}건) — 키/quota 점검 필요.`` `false` 면 침묵.
55
+ 9. **완료 후보 surface**: `## 🎯 완료 후보 감지` 블록이 보이면 `references/tasks.md` 를 Read 후 **다른 응답 전에** 후보별 done 승인 게이트로 여쭙는다(**자동 done 금지**). 블록 없으면 침묵.
56
+ 10. **복구 세션·다이어트**: ``🛟 …`` 블록(+`- <sid> :: <transcript_path>` 목록) 또는 ``## 🍞 컨텍스트 다이어트 권고`` 블록이 보이면 `references/recovery.md` 를 Read 후 **다른 응답 전에** 그 절차대로 처리한다(다이어트는 전 항목 승인 게이트).
57
+ 11. **세션 시작 컨텍스트 조회** 실패(HTTP·네트워크)는 조용히 폴백 — 현재 프로젝트 `CLAUDE.md` 만 따릅니다. ⚠️ 이 폴백은 **§세션 시작 컨텍스트 조회 한정**이다 — 회상(recall) 실패에는 적용되지 않는다(아래 "회상 중단·실패 시" 참조).
84
58
 
85
59
  ## 회상 (recall)
86
60
 
@@ -99,54 +73,62 @@ curl -s -H "Authorization: Bearer $TOKEN" \
99
73
 
100
74
  - **출처 프로젝트 구분 (필수)**: `project_name` 이 현재 세션 프로젝트와 **다르면** 출처를 밝히고, **현 세션의 "다음 할 일" 후보로 제안하지 않는다**(두목이 명시 언급 시 예외). `null` 은 전역/개인 — "(전역)" 표기.
101
75
  - **같은 주제 "다음:" 카드가 여러 장이면** `memory_at`(ISO 시각) 최신 카드가 현재 상태 기준. 구 카드는 이력으로만.
76
+ - **`newer_related` 신호 (필수)**: 결과 항목에 `newer_related`(더 최신인 의미 유사 LIVE 카드)가 붙어 있으면 그 카드를 **그대로 인용 금지** — `newer_related.summary_head` 를 확인하고, 필요하면 그 주제로 재-recall 해 **최신 카드 기준으로** 답한다. 신·구가 상충하면 최신이 이긴다.
77
+ - **결정·확정류 카드 최신성 교차 확인**: "확정·결정·정책·방향" 문구가 박힌 카드를 근거로 쓰기 전, 같은 주제의 더 최신 카드가 없는지 확인한다(`newer_related` + 서로 다른 recall 응답에서 얻은 카드끼리도 `memory_at` 비교). 옛 "확정"이 이후 결정으로 뒤집혔을 수 있다.
78
+ - **진입 카드 = 최신성 기준선 (필수)**: 회상 결과를 근거로 **진입 카드**를 "낡았다·이미 끝났다·뒤집혔다"고 판정하려면 **두 축 모두**에서 그 결과가 카드보다 최신이어야 하고, 두 시각을 함께 제시해야 한다 — ① 의미 시각: 결과 `observed_at` > 카드 `memory_at` ② 저장 시각: 결과 `memory_at` > 카드 `recorded_at`.
79
+ - **한 축이라도 뒤지거나 `null` 이면 단독 근거로 불충분** — 지연 저장된 옛 기억이 최신으로 보일 수 있다. 실제 상태(git log·해당 파일·API)로 확인한 뒤에만 뒤집는다.
80
+ - **`age_days` 는 축간 판정에 쓰지 않는다** — 카드는 floor 정수, 회상은 0.1 소수, 기준 시각도 별개 요청이다. self_rule 은 시각이 없으면 `age_days=0`(신선해 보임)이라 특히 위험하다.
81
+ - **`kind` 가 `fact` 가 아닌 결과(chunk·evidence·personal·self_rule)는 카드를 뒤집는 근거가 아니다** — 진입 카드와 같은 층위(working card)가 아니다. 배경으로만 쓴다.
82
+ - 카드보다 **오래된** 결과는 카드를 무효화하지 못한다 — 이력·배경으로만 쓴다. 애매하면 **카드를 다음 작업 의도의 기준선으로 신뢰**한다(판정 부담은 카드를 뒤집으려는 쪽에 있다). 단 이는 "카드가 아직 안 끝났다"는 보증이 아니다 — **완료 여부는 `activity_stale`·`ephemeral_recovery`·현재 repo 상태로 별도 점검**한다.
83
+ - **동일 카드 이중 계산 금지**: 결과 `id` 가 진입 카드 `id` 와 같으면 같은 카드다. 별개 근거로 세지 않는다. id 는 대조용 — **UUID 를 화면에 출력하지 않는다**.
102
84
  - **0건 + `meta.fail_closed:true` 자가교정**(조용히 1회 재시도): `missing_project_identifier` → 식별자 추가 또는 `scope=all` / `project_not_linked` → `scope=all`.
103
- - **자기 출력 분석 금지** — 트리거는 사용자 입력에서만(self-loop 위험). **매 turn 호출 금지** — 트리거 등장 시만.
85
+ - **회상 중단·실패 시 — 코드 탐색으로 대체하지 않는다 (필수)**: recall 호출이 인터럽트·에러로 끝났으면 **1회 재시도**한다. 그래도 못 했으면 git log·grep·파일 열람 결과만으로 "언제 누가 무엇을 했다"를 **단정하지 않는다** — "회상을 못 했다"를 밝히고 확인된 범위만 말한다. 세션 시작 §11 의 "조용히 폴백"은 컨텍스트 조회 한정이며 여기에 적용되지 않는다.
86
+ - **두목이 회상 실패를 지적하면 그 자체가 최우선 트리거 (필수)**: "기억 못하네"·"또 처음인 것처럼"·"전에 했잖아"·"또 까먹었어" 류는 **두목이 과거에 그 일이 있었음을 직접 확인해 준 신호**다. 해명·되묻기·코드 탐색보다 recall 이 먼저다. 한 쿼리 0건으로 끝내지 말고 키워드를 바꿔 여러 각도로 회상한다.
87
+ - **자기 출력 분석 금지** — 키워드 트리거는 사용자 입력에서만 탐지(self-loop 위험). **매 turn 호출 금지** — 트리거 등장 시만. 예외: 아래 "결정 제안 전 회상 게이트"는 이미 낸 출력의 재분석이 아니라 **앞으로 낼 제안의 사전 점검**이라 self-loop 이 아니다.
104
88
 
105
- ## 저장 (hot write, batch-mutate 직접 호출)
89
+ ### 결정 제안 전 회상 게이트 (필수)
106
90
 
107
- 카테고리: 운영 사실(서버/포트/유저명/경로/명시 결정, 자동) → working_cards / 능력 증거(파일·결과물 산출 시, 자동) → evidence / 자기 규칙(사용자 명시 시그널 "원칙·습관·항상·절대" 만) → self_rules / 개인 취향(명시 시그널 "좋아해·선호·취향" 만) → personal_fact 별도 경로 / 잡담·일회성·민감 정보 → 저장 안 함.
91
+ 방향·규약·정책·설계·미감 판단을 **새로 정하는 출력을 만들기 직전** — 두목께 여쭙는 형태("A안/B안 중 정할까요", "방향을 먼저 정하고 갈까요")든, 단독 선언 형태("A 방식으로 진행하겠습니다", "이 규약으로 추천드립니다")든 형태 무관:
108
92
 
109
- 저장 전 **recall dedup** — 같은 취지 durable fact 존재 시 새 저장 금지. **재확인 근거가 있으면** `verifications` confirmed 1건(fact 당 하루 1회, 서버 동일 게이트 — working card·근거 없는 회상 제외)으로 반복 신호를 남긴다. 성공 시 `[기억] <요약> 저장` 1줄, 실패 시 retry 1회 + 알림.
93
+ 1. 그 주제로 recall 1회(`scope=all`) — 같은 주제·같은 판단 대상을 이 세션에서 이미 recall 했으면 생략 가능.
94
+ 2. **기존 확정 결정이 나오면 재론 제안 금지** — 그 결정을 먼저 언급하고, 결정의 틀 안에서 실행안만 제안한다. 결정을 뒤집을 근거가 새로 생겼다면 "기존 결정 X가 있는데, Y 때문에 재검토가 필요해 보입니다"로 **기존 결정의 존재를 밝힌 채** 여쭙는다.
95
+ 3. 결정이 없으면(0건) 그대로 제안 진행 — 0건은 정상.
110
96
 
111
- **`rookie:mutate` fence 를 화면에 출력하지 않는다** — 저장은 Bash 직접 호출, 화면엔 `[기억] …` 한 줄만(config 는 `~/.rookie/config.json`, 토큰 출력 금지).
97
+ **제외 (게이트 비발동)**: 되돌리기 쉬운 구현 세부 — 변수·함수명, 커밋 메시지 문구, 로컬 리팩터 방식, 일회성 명령 옵션 등. 기준: 다음 세션에도 구속력이 남는 판단인가 — 남으면 게이트, 안 남으면 제외.
112
98
 
113
- ```bash
114
- rookie save --file <body.json> # 또는: echo '<body>' | rookie save
115
- ```
116
- ⚠️ **`curl` 로 직접 batch-mutate 금지** — `rookie save` 가 cwd·git remote·hostname 을 자동 주입해 전역 누수를 막는다. 본문엔 **기억할 내용만**.
117
-
118
- ⚠️ **전역 fact 는 `project_id: null` 을 명시한다.** 식별자도 명시 의사도 없으면 서버가 `missing_workspace_context` 로 **거부**. 미등록 폴더 저장은 **그 폴더에 격리**, `rookie link` 시 자동 승격.
119
-
120
- `<body>` 스키마 (필요한 키만):
121
- ```json
122
- {
123
- "working_cards": { "items": [ { "summary": "진입 카드 본문", "verification": { "method": "ssh|curl|git|grep|lsof|read|reasoning|assertion", "evidence": "직접 명령 결과 한 줄" }, "supersedes": ["부모 fact UUID"], "tags": ["custom"], "ttl_at": "ISO8601?", "artifact_paths": ["task 매칭용? ≤20 (저장 안 함)"], "project_id": "?", "task_id": "?" } ], "repo_url": "(권장) git remote — 없으면 project 귀속 안 됨", "repo_path": "(권장) repo_url 없을 때 hostname 매칭", "project_id": "(레거시) item 이 우선, 보통 생략", "conversation_id": "uuid?" },
124
- "self_rules": [ { "category": "identity|voice|work_style|strength|preference|rule", "rule": "규칙 본문", "confidence": "high|medium|low", "supersedes": "기존 rule UUID?", "deprecation_reason": "user_override|conflict|noise|manual?" } ],
125
- "evidence": [ { "capability": "능력 진술문", "approach": "접근법?", "status": "completed|in-progress?", "context": "맥락?", "artifact_paths": ["파일 절대경로 (project 폴백 매칭에도 쓰임)"], "project_id": "? (uuid=명시 귀속 / null=전역 / 생략=세션 귀속)" } ],
126
- "verifications": [ { "fact_id": "검증 대상 fact UUID", "method": "(위와 동일 enum)", "outcome": "confirmed|refuted|ambiguous", "evidence": "검증 명령 결과 한 줄" } ]
127
- }
128
- ```
99
+ 디버깅·탐사 중 발견한 문제를 "새 문제"로 프레이밍했더라도, 해결 방향을 정하는 출력을 내는 순간 이 게이트가 걸린다 — 이미 정해진 것을 다시 정하(자고 하)는 사고의 1차 방지선.
100
+
101
+ ### 작업 착수 전 회상 게이트 (필수)
102
+
103
+ 위 게이트의 **형제**다 — 저쪽은 *정하기 전*, 이쪽은 *손대기 전*. 두목의 신고·요청을 받아 **조사·수정·구현에 착수하기 전**, 프롬프트에 과거 단서가 **없어도** 그 대상(기능·화면·파일·증상)으로 recall 1회.
104
+
105
+ 1. **버그·이상 신고** — "X가 안 된다/비어 있다/이상하다" → 착수 전 "이 증상·이 파일을 전에 만졌는가".
106
+ 2. **기능 요청** — "X 만들어줘" → "전에 만들었거나, 시도했다가 접었는가".
107
+ 3. **두목께 되묻기 직전** — 되물을 내용의 답이 이미 기억에 있는지 먼저 본다. **되묻기는 회상 다음이다.**
108
+
109
+ 같은 대상으로 이 세션에서 이미 recall 했으면 생략. **0건은 정상** — 그대로 착수한다.
110
+
111
+ **회상 결과는 소비까지가 회상이다 (필수)**: recall 로 이 주제와 관련 있는 최신 절차·맥락을 확보했다면 **그대로 실행한다** — "혹시 몰라서" 두목께 되묻거나 재조사하는 것은 백지 상태 기본자세의 재발이다. 되묻기는 회상 결과가 이 주제와 무관하거나, 0건이거나, 실행에 명백히 빠진 정보가 있을 때만 한다. (2026-07-24: 실기기 배포 절차를 recall 로 확보하고도 AskUserQuestion 으로 되물어 "처음인 것처럼" 지적받은 사고 — AskUserQuestion 은 훅이 1차 deny 로 멈춰 세운다. deny reason 을 받으면 회상 결과부터 재확인한다.)
112
+
113
+ **제외**: 두목이 방금 준 정보만으로 끝나는 일(오타 수정, 방금 붙여넣은 에러 읽기), 순수 신규 파일 생성, 이 세션에서 이미 다룬 대상.
129
114
 
130
- ### 응답 확인 — `all_committed` 게이트 + 귀속 확인
131
- **귀속 확인**(all_committed 와 무관): `repo_url`/`repo_path` 를 보냈는데 `working_cards.project_attributed=false` 또는 `evidence.unattributed>0` 이면(전역 의도 `project_id:null` 명시 제외) `⚠️ [기억] 프로젝트 미귀속 저장 — rookie link 상태 확인 필요` 1줄 경고.
115
+ > 왜 필요한가: 회상 트리거는 **두목 말투**(어제·전에·이어서)를 본다. 그런데 "세운 슬롯이 비어있어" 같은 **평범한 신고**에는 과거 단서가 없다 — 실제로 그 발화 때문에 몇 시간 전 자기가 고친 버그를 처음 보는 문제로 취급하고 조사에 착수한 사고가 있었다(2026-07-22). 어휘가 아니라 **내가 무엇을 하려는가**가 방아쇠다.
132
116
 
133
- top-level `all_committed` 가 **`true` 일 때만** `[기억] … 저장` 보고. `false` 면:
134
- - **`rejected[]`**(정책 거부 — 재시도 무의미): `meta_card_summary`·`residual_marker`(민감정보)·`self_rule_rejected` — 단일 fact 분리 또는 원인 제거 후 재저장, 민감정보 마커면 포기.
135
- - **`failed[]`** (일시 실패): `insert_failed`·`unexpected_error` 등 — retry 1회, 그래도 실패면 보고.
136
- - 미저장이 남으면 `⚠️ [기억] N건 미저장 — <사유>` 한 줄(raw JSON·UUID 금지). `all_committed:true` 전엔 "저장 완료" 라 하지 않는다.
117
+ ### 현재 상태 주장 검증 게이트 (필수)
137
118
 
138
- > **레거시 폴백**: 직접 curl 불가 환경만 응답 끝 ` ```rookie:mutate ``` ` fence(Stop hook dispatch). 일반 세션 fence 금지.
119
+ 블로커 유무·제원(길이·포맷·버전)·토큰/연결 상태·배포 상태 등 **"지금 그러하다" 류 주장을 하기 전**:
120
+ 1. recall 로 더루키 기억을 먼저 확인하고,
121
+ 2. 30초 내 실측 가능하면(ffprobe·health-check·curl·git log) **실측이 정본**이다.
122
+ - **로컬 문서(md·plan·worklog)만으로 현재 상태를 단정하지 않는다** — 문서는 기록 시점 스냅샷이고 유지가 안 돼 실제와 다른 경우가 많다. 문서 내용을 답에 쓸 때는 "문서상 X (실측 미확인)" 처럼 출처·검증 여부를 구분해 말하고, 확인된 사실처럼 서술하지 않는다. 낡은 문서를 발견하면 정정을 제안한다(자동 수정 금지).
123
+ - **파일 mtime·미커밋 여부로 "미결/진행중" 단정 금지** — 저장소 파일 상태로 작업 상태를 추론하기 전에 recall 먼저.
139
124
 
140
- ### 트리거 조건
141
- - **working_cards**: 다음 세션 진입 카드 — 세션 마무리 + 아래 checkpoint, 매 턴·매 단계 금지. **body 에 `repo_url`/`repo_path` 필수** — 서버가 세션 project 로 자동 귀속, 미link 면 NULL(정상).
142
- - **checkpoint(세션 중간 저장)**: 마일스톤(커밋/push·배포·설계 수렴·큰 단계 전환) 완결 시 `rookie save --json` 으로 카드 1건 — `tags:["checkpoint"]`, 직전 checkpoint 를 `supersedes` 로 닫기(응답 `summary.working_cards.ids[0]` 기억, `superseded_count` 확인). 마무리 저장도 마지막 checkpoint supersede. id 분실 시 recall dedup, 애매하면 supersedes 생략.
143
- - **직전 카드 supersede (필수 습관)**: 같은 작업 흐름(프로젝트·이어지는 제목/파일/브랜치)의 새 "다음:" 카드는 recall dedup 으로 확인한 직전 카드 `id` 를 `supersedes` 에 넣어 닫는다. 애매하면 생략. 프로젝트 불일치 parent 는 서버가 자동 skip.
144
- - **cross-project·전역 예외**: 명백히 다른 프로젝트/전역 fact 는 **생략이 아니라 명시적으로 `project_id: null`**(전역) 또는 그 프로젝트 uuid — 생략은 세션 project 자동 귀속(오귀속). null 도 scope=all 회상에 잡힌다.
145
- - **task 연결**: 확실히 매칭된 task 면 `task_id` 지정 — done 시 카드 자동 은퇴. project_id 는 생략해 task 에서 derive(불일치 시 400). **애매하면** task_id 없이 `__task_link_candidate:<task_id>` 태그만.
146
- - **self_rules**: 사용자 명시 자기 규칙 지시 시만. / **evidence**: 파일·결과물 산출 시. / **verifications**: 루키가 직접 명령으로 fact 검증 시.
125
+ ## 저장 (hot write) — 요지
147
126
 
148
- ### 파일 변경 알림
149
- 파일을 생성·수정했다면: ```json {"files_created":["절대경로"],"files_modified":["절대경로"]} ```
127
+ 카테고리: 운영 사실(서버/포트/유저명/경로/명시 결정, 자동) → working_cards / 능력 증거(파일·결과물 산출 시, 자동) → evidence / 자기 규칙(사용자 명시 시그널 "원칙·습관·항상·절대" 만) → self_rules / 개인 취향(명시 시그널 "좋아해·선호·취향" 만) → personal_fact 별도 경로 / 잡담·일회성·민감 정보 → 저장 안 함.
128
+
129
+ 저장 전 **recall dedup** — 같은 취지 durable fact 존재 시 새 저장 금지. **재확인 근거가 있으면** `verifications` confirmed 1건(fact 당 하루 1회, 서버 동일 게이트 — working card·근거 없는 회상 제외)으로 반복 신호를 남긴다. 성공 시 `[기억] <요약> 저장` 1줄, 실패 시 retry 1회 + 알림.
130
+
131
+ **본문 작성 전 `references/save.md` 를 Read 한다(세션 첫 저장 전 1회 필수)** — 스키마·`all_committed` 게이트·귀속 확인·supersede 후보 처리·checkpoint/durable 분리 규칙이 거기 있다. 호출은 `rookie save --file <body.json>` (curl 직접 batch-mutate 금지, `rookie:mutate` fence 화면 출력 금지, 토큰 출력 금지).
150
132
 
151
133
  ## 세션 마무리 자동 저장 (추출 책임이 루키에게)
152
134
 
@@ -154,31 +136,10 @@ top-level `all_committed` 가 **`true` 일 때만** `[기억] … 저장` 보고
154
136
 
155
137
  1. **다음 진입점 1건** — 미완결 작업·다음 액션·대기 결정을 추론해 working_cards 1건. 본문은 "다음: <바로 실행할 첫 행동>" 시작(목표형 "~이어가기" 금지, 방법 미정이면 "다음: 방법 후보 A/B 중 두목과 결정"). `[고정]`·`[실패]`·`[두목 제약]` 마커 권장 + 파일·명령·근거 1줄(속기·내부기호 금지, 평이한 한국어). 명확히 완결됐으면 생략(0건 정상).
156
138
  2. **미저장 운영 사실·능력 증거·자기 규칙** — recall dedup 후 누락분만.
157
- 3. **프로젝트 task status reconcile** (link 세션만) — `GET /api/rookie/tasks?project_id=<id>` 1회 후, **이번 세션 실제 산물과 사용자 지시**를 task 와 매칭(루키 자기 서술 근거 금지):
158
- - **(가) git 산물 수집 먼저** (git 이 진실):
159
- ```bash
160
- git log --oneline ${SESSION_START_SHA:-HEAD~20}..HEAD
161
- git diff --name-only ${SESSION_START_SHA:-HEAD~20}..HEAD
162
- git status --short # START_DIRTY 제외분만 산물
163
- ```
164
- 커밋·파일·PR 또는 DB 검증 fact 가 매칭 근거 — **양쪽 다 0 이면 어떤 status 도 바꾸지 않는다(특히 done)**. DB 검증만으로 완성되는 작업은 검증 fact 로 done 후보 인정(자동 done 금지 동일). `SESSION_START_SHA` 미캡처 시 폴백 `HEAD~20`(과탐해도 doing 까지만).
165
- - (나) 산물을 task 의 `spec_ref`/`title`/`detail` 과 의미 매칭 — 변경 파일이 `spec_ref` 영역과 겹치면 강한 매칭.
166
- - 작업했는데 todo → `PATCH {id, status:'doing'}` (자동). / 매칭 없음 → `POST /tasks {project_id, title, status:'doing', source:'agent'}`.
167
- - doing 중 근거 실재하는 완성건 → **일괄 1회 보고** `"다음 N개를 완료로 올릴까요? — A(근거…), B(근거…)"` → OK 시에만 done PATCH. "느낌상 완료" 제외.
139
+ 3. **프로젝트 task status reconcile** (link 세션만) — `references/tasks.md` 를 Read 후 그 절차대로(git 산물 수집 → 의미 매칭 → doing 자동 / done 승인 게이트).
168
140
 
169
141
  **오탐 방지**: 작업 중단의 "그만"과 세션 종료의 "그만"을 구분. 애매하면 저장(까먹는 비용 > 중복 비용). 매 turn 저장 금지 — 마무리 1회. **저장 전 recall dedup 필수.**
170
142
 
171
- ## 프로젝트 task 추적 (status 는 루키 전용)
172
-
173
- `/projects` 의 task status 는 **루키가 실제 업무·증거 기반으로만 갱신**한다(수동 토글 없음). 매 turn 금지 — **착수/완료 시점만**. 자기 출력 분석 금지(마무리 reconcile 은 위 §).
174
-
175
- - **진행중 (doing)** — 착수 시 `GET /api/rookie/tasks?project_id=$PID` 후 의미 매칭: 확실 → `PATCH {id, status:'doing'}` + 1줄 알림 / 애매 → "이 작업이 'X' task 맞나요?" 1회 확인 / 매칭 없음 → `POST /tasks {…, status:'doing', source:'agent'}` 신규.
176
- - **완료 (done)** — **자동 done 금지.** `"'X' 를 완료로 올리겠습니다 — 근거: <파일/PR/검증 한 줄>. 올릴까요?"` → OK 시 `PATCH {id, status:'done', detail:'<기존>\n\n✅ 완료 YYYY-MM-DD — <근거>'}`.
177
- - **보류 (blocked)** — `PATCH {id, status:'blocked', blocked_reason:'<사유>'}`. / **재개 (done→doing)** — 추가 작업 발생 시 루키 자율 `PATCH {id, status:'doing', detail:'<기존>\n\n🔄 재개 YYYY-MM-DD — <사유>'}` (확인 불요).
178
-
179
- ### 자동 계층 도출 (승인 게이트) — 평면 task 다수 시 상위 목표 제안(spec 2026-07-05)
143
+ ## 프로젝트 task 추적 — 요지
180
144
 
181
- - **⭐ 3단 구조 원칙 (두목 2026-07-05, 항상 적용)**: 잎사귀 task 를 목표(1단) 직계에 붙이지 않는다 — 반드시 **목표 → 중간그룹(2단) → 잎(3단)**. 중간그룹이 없으면 먼저 만들거나 제안. done 잎도 예외 없다.
182
- - **트리거**: 두목 명시 요청 또는 미분류 최상위 task 다수(예 ≥8) 시 **1회 제안**. **절차**: tasks 조회 → `title`/`detail` 의미로 배정 → 요약 제시(**raw uuid 금지, title 로**) → **승인 게이트**: OK 한 그룹만 기록.
183
- - **기록**: 신규 목표/중간그룹 `POST /api/rookie/tasks {project_id, title, detail?, status:'todo', source:'agent', parent_task_id?}` — 목표는 parent 없이, 중간그룹은 `parent_task_id`=목표. 잎은 `PATCH {id, parent_task_id:<중간그룹>}`. DB trigger 가 최종 검증(owner/project·무사이클·≤32단), 실패 자식만 400 라벨 1줄 보고 후 skip.
184
- - **안전**: 기존 `parent_task_id` 있는 task 재배정 금지(명시 지시 시만). 목표 중복이면 기존에 붙인다. 신규 목표에 자식 0건이면 명시 보고(자동 삭제 금지). 부분 실패 무손실. **보고**: "목표 N개 · 자식 M건 연결(실패 K건: 사유)" 1줄.
145
+ task status 는 루키가 실제 업무·증거 기반으로만 갱신한다(수동 토글 없음) — 매 turn 금지, **착수/완료 시점만**. **자동 done 금지**(항상 근거 제시 + 승인 게이트). status 변경·계층 도출 전 `references/tasks.md` 를 Read 하고 그 절차(착수 doing 매칭·done 승인·blocked/재개·계층 3단 구조 원칙)를 따른다.
@@ -0,0 +1,18 @@
1
+ # link 후속 — 스펙 연결·소개글·자동 스캔
2
+
3
+ SKILL.md 코어 §세션 시작에서 스펙 동기화 directive(`[루키 지시]`)가 보일 때만 읽는 절차 문서다.
4
+
5
+ ## 스펙 연결·소개글 (승인 게이트)
6
+
7
+ `system_prompt` 에 스펙 동기화 directive 가 있을 때만 능동 제안한다(판정은 서버 책임). **미연결** directive → "스펙을 연결할까요?" → 승인 시 (A)+(C) / **반쪽** directive → "동기화할까요?" → 승인 시 (A) 의 task 동기화만(PUT 생략).
8
+
9
+ - **(A) 스펙 연결·동기화**: 로컬 스펙 문서를 찾아 `spec_doc_paths` 설정 + 문서별 task 동기화(문서 1개=task 1개). **task 는 전부 todo 시작(스펙 ≠ 구현)** — sync 에 `status` 금지(서버가 todo 고정), 재동기화해도 기존 status 보존. 진척은 `PATCH /api/rookie/tasks`.
10
+ - 미연결일 때만: `PUT /api/rookie/projects/spec-source {"project_id":"<id>","spec_doc_paths":["docs/specs/**"]}`
11
+ - 공통: `POST /api/rookie/tasks/sync {"project_id":"<id>","items":[{"title":"<스펙 제목>","spec_ref":"<파일경로>","spec_html":"<렌더 HTML>"}]}`
12
+ - 스펙 문서가 없으면 "같이 만들까요?" → brainstorming 으로 작성 후 연결.
13
+ - **(C) 소개글**: 비어 있으면 README·CLAUDE.md 파악 후 1~2문장 초안을 `PATCH /api/rookie/projects {"id","description"}` — 기존 소개글 덮어쓰기 금지. 완료 시 1줄 보고.
14
+ - **거부** 시 `POST /api/rookie/projects/spec-dismiss {"project_id":"<id>","dismissed":true}` (되돌리기는 `false`). 명시 요청은 dismiss 와 무관하게 수행. 미link(§6) → 미스펙(directive) → 정상 3단계 중 해당 단계만 1회 알린다(승인 전제).
15
+
16
+ ## spec 후속 자동 스캔
17
+
18
+ link + `spec_doc_paths` 설정 시 세션 시작 1회 `rookie scan-specs`(하루 1회 throttle). 결과는 조용히 둔다(proposal 은 `/projects`). 명시 요청 시 `--force`. 미link·미설정·실패 조용히 skip.
@@ -0,0 +1,26 @@
1
+ # 저빈도 신호 대응 — 복구 세션 tail 추출 · 컨텍스트 다이어트
2
+
3
+ SKILL.md 코어에서 해당 신호가 보일 때만 읽는 절차 문서다. 절차는 여기 적힌 대로 전부 수행한다.
4
+
5
+ ## 복구 세션 tail 추출
6
+
7
+ 비정상 종료 세션은 Stop hook 이 못 돌아 fact·진입카드가 없다 — chunk 복구는 `orphan_recover`, **fact 추출은 차차 책임**(서버 추출 off). 컨텍스트에 ``🛟 …`` 블록과 ``- <sid> :: <transcript_path>`` 목록이 보이면 **다른 응답 전에** 처리한다:
8
+
9
+ 1. `rookie orphan-extract --batch=5` — 대기 세션을 오래된 순으로 읽어 rule-base 초안(요약·파일 경로·git sha, redact 통과)을 뽑는다. transcript 원문 통째 Read 금지(부족 시 보조 — Claude `~/.claude/projects/`, Codex `~/.codex/sessions/`).
10
+ 2. 초안에서 **저장 가치가 있는 것만** — 운영 사실/결정→working_card, 파일·결과물→evidence, 명시적 자기규칙→self_rule, 취향→personal_fact. 잡담·민감정보 금지(redactor 원칙). 0건 세션도 정상.
11
+ 3. **recall 로 dedup 후** 누락분만 `POST /api/rookie/batch-mutate`(`project_id` 포함).
12
+ 4. 처리한 세션마다(0건이어도) 초안 하단의 완료 marker 명령을 **그대로** 실행해 큐에서 비운다 — 안 하면 매 세션 재노출:
13
+ ```bash
14
+ source "$HOME/.rookie/bin/lib/orphan-recovery.sh" 2>/dev/null && orphan_mark_client_extracted '<sid>'
15
+ ```
16
+ (로컬 설치면 `<project>/.rookie/bin/…`) Codex 세션은 `ROOKIE_ORPHAN_DIR="…/codex"` prefix. transcript 소실 세션도 marker 로 비운다.
17
+ 5. 한 줄 보고(`[복구] 세션 N건 tail 추출·저장`). raw transcript·UUID 나열 금지. 남은 대기는 다음 세션에서.
18
+
19
+ ## 컨텍스트 다이어트 (주1회, 승인 게이트)
20
+
21
+ SessionStart hook 이 주 1회 임계 초과 시 ``## 🍞 컨텍스트 다이어트 권고`` 블록을 주입한다. 보이면 **다른 응답 전에 1회** 제안, 없으면 침묵. **전 항목 승인 게이트 — 자동 실행 없음.** 승인 시:
22
+ - **CLAUDE.md**: 아카이브성 내용 `docs/` 이관 + 참조 1줄 치환. 의미 단위 압축(단순 truncate 금지). diff 제시 → 승인 후 커밋.
23
+ - **스킬 정리**: 0회 후보 제시 → 고른 것만. settings 비활성화(가역) 우선 / 폴백 `~/.claude/skills/<name>` → `skills.disabled/` 이동(삭제 아님, symlink·플러그인은 안내만). keep 은 `~/.rookie/context-diet-keep.json`.
24
+ - **루키 자기 다이어트**: rookie Skill 문서 중복 정리 — diff 제시 → 승인 후 반영(템플릿 SSOT 커밋 + 재배포).
25
+
26
+ **거부/보류 snooze**: `~/.rookie/context-diet-snooze.json` 에 `{"<key>":"<ISO until>"}` 30일(전체 거부 90일. key = claude_md_project·claude_md_total·skills_agents_desc·rookie_self·unused_skills). 실패 항목만 1줄 보고. 현황 `rookie doctor`.
@@ -0,0 +1,53 @@
1
+ # 저장 상세 — batch-mutate 스키마·응답 게이트·supersede·트리거
2
+
3
+ `rookie save` 본문을 작성하기 전(세션 첫 저장 전 1회) 읽는 절차 문서다. SKILL.md 코어의 저장 요지를 이 문서가 구체화한다 — 여기 게이트는 전부 필수.
4
+
5
+ ## 호출 방법
6
+
7
+ ```bash
8
+ rookie save --file <body.json> # 또는: echo '<body>' | rookie save
9
+ ```
10
+ ⚠️ **`curl` 로 직접 batch-mutate 금지** — `rookie save` 가 cwd·git remote·hostname 을 자동 주입해 전역 누수를 막는다. 본문엔 **기억할 내용만**.
11
+
12
+ ⚠️ **전역 fact 는 `project_id: null` 을 명시한다.** 식별자도 명시 의사도 없으면 서버가 `missing_workspace_context` 로 **거부**. 미등록 폴더 저장은 **그 폴더에 격리**, `rookie link` 시 자동 승격.
13
+
14
+ **`rookie:mutate` fence 를 화면에 출력하지 않는다** — 저장은 Bash 직접 호출, 화면엔 `[기억] …` 한 줄만(config 는 `~/.rookie/config.json`, 토큰 출력 금지).
15
+
16
+ > **레거시 폴백**: 직접 curl 불가 환경만 응답 끝 ` ```rookie:mutate ``` ` fence(Stop hook dispatch). 일반 세션 fence 금지.
17
+
18
+ ## `<body>` 스키마 (필요한 키만)
19
+
20
+ ```json
21
+ {
22
+ "working_cards": { "items": [ { "summary": "진입 카드 본문", "verification": { "method": "ssh|curl|git|grep|lsof|read|reasoning|assertion", "evidence": "직접 명령 결과 한 줄" }, "supersedes": ["부모 fact UUID"], "tags": ["custom"], "ttl_at": "ISO8601?", "artifact_paths": ["task 매칭용? ≤20 (저장 안 함)"], "project_id": "?", "task_id": "?" } ], "repo_url": "(권장) git remote — 없으면 project 귀속 안 됨", "repo_path": "(권장) repo_url 없을 때 hostname 매칭", "project_id": "(레거시) item 이 우선, 보통 생략", "conversation_id": "uuid?" },
23
+ "self_rules": [ { "category": "identity|voice|work_style|strength|preference|rule", "rule": "규칙 본문", "confidence": "high|medium|low", "supersedes": "기존 rule UUID?", "deprecation_reason": "user_override|conflict|noise|manual?" } ],
24
+ "evidence": [ { "capability": "능력 진술문", "approach": "접근법?", "status": "completed|in-progress?", "context": "맥락?", "artifact_paths": ["파일 절대경로 (project 폴백 매칭에도 쓰임)"], "project_id": "? (uuid=명시 귀속 / null=전역 / 생략=세션 귀속)" } ],
25
+ "supersede_links": [ { "parent_id": "폐기할 구 카드 UUID", "child_id": "대체하는 새 카드 UUID" } ],
26
+ "verifications": [ { "fact_id": "검증 대상 fact UUID", "method": "(위와 동일 enum)", "outcome": "confirmed|refuted|ambiguous", "evidence": "검증 명령 결과 한 줄" } ]
27
+ }
28
+ ```
29
+
30
+ ## 응답 확인 — `all_committed` 게이트 + 귀속 확인
31
+
32
+ **귀속 확인**(all_committed 와 무관): `repo_url`/`repo_path` 를 보냈는데 `working_cards.project_attributed=false` 또는 `evidence.unattributed>0` 이면(전역 의도 `project_id:null` 명시 제외) `⚠️ [기억] 프로젝트 미귀속 저장 — rookie link 상태 확인 필요` 1줄 경고.
33
+
34
+ top-level `all_committed` 가 **`true` 일 때만** `[기억] … 저장` 보고. `false` 면:
35
+ - **`rejected[]`**(정책 거부 — 재시도 무의미): `meta_card_summary`·`residual_marker`(민감정보)·`self_rule_rejected` — 단일 fact 분리 또는 원인 제거 후 재저장, 민감정보 마커면 포기. `supersede_link_skipped` 는 id·scope 를 고쳐야 하는 것 — 같은 payload 재전송 금지.
36
+ - **`failed[]`** (일시 실패): `insert_failed`·`unexpected_error` 등 — retry 1회, 그래도 실패면 보고.
37
+ - 미저장이 남으면 `⚠️ [기억] N건 미저장 — <사유>` 한 줄(raw JSON·UUID 금지). `all_committed:true` 전엔 "저장 완료" 라 하지 않는다.
38
+
39
+ **supersede 후보 처리 (같은 턴, 필수)**: 저장 응답 `summary.working_cards.supersede_candidates[]` 에 후보가 있으면 **그 자리에서** 판단한다 — 후보(`summary_head`·`memory_at`)가 새 카드가 대체하는 같은 작업 흐름의 구 카드면 즉시 후속 `rookie save` 로 닫는다: `{"supersede_links":[{"parent_id":"<후보 id>","child_id":"<새 카드 id>"}]}`. 병행 중인 다른 트랙 카드면 무시. **후보를 보고도 무근거로 방치 금지** — 애매하면 애매한 이유가 있어야 한다. (구 결정·상태 카드가 안 닫혀 다음 세션이 폐기된 결정을 회상하는 사고의 1차 방지선.)
40
+
41
+ ## 트리거 조건 상세
42
+
43
+ - **working_cards**: 다음 세션 진입 카드 — 세션 마무리 + 아래 checkpoint, 매 턴·매 단계 금지. **body 에 `repo_url`/`repo_path` 필수** — 서버가 세션 project 로 자동 귀속, 미link 면 NULL(정상).
44
+ - **checkpoint(세션 중간 저장)**: 마일스톤(커밋/push·배포·설계 수렴·큰 단계 전환) 완결 시 `rookie save --json` 으로 카드 1건 — `tags:["checkpoint"]`, 직전 checkpoint 를 `supersedes` 로 닫기(응답 `summary.working_cards.ids[0]` 기억, `superseded_count` 확인). 마무리 저장도 마지막 checkpoint supersede. id 분실 시 recall dedup, 애매하면 supersedes 생략.
45
+ - **직전 카드 supersede (필수 습관)**: 같은 작업 흐름(프로젝트·이어지는 제목/파일/브랜치)의 새 "다음:" 카드는 recall dedup 으로 확인한 직전 카드 `id` 를 `supersedes` 에 넣어 닫는다. 애매하면 생략. 프로젝트 불일치 parent 는 서버가 자동 skip.
46
+ - **cross-project·전역 예외**: 명백히 다른 프로젝트/전역 fact 는 **생략이 아니라 명시적으로 `project_id: null`**(전역) 또는 그 프로젝트 uuid — 생략은 세션 project 자동 귀속(오귀속). null 도 scope=all 회상에 잡힌다.
47
+ - **task 연결**: 확실히 매칭된 task 면 `task_id` 지정 — done 시 카드 자동 은퇴. project_id 는 생략해 task 에서 derive(불일치 시 400). **애매하면** task_id 없이 `__task_link_candidate:<task_id>` 태그만.
48
+ - **durable 사실 분리 저장 (필수)**: 진입 카드·checkpoint 본문에 durable 운영 사실(블로커 발생/해소·환경 변화·외부 상태 전환·측정 결과)을 끼워 넣지 않는다 — **별도 working_cards item 으로 분리 저장**하고 진입 카드엔 요지만. 판별: "다음 checkpoint 가 이 카드를 닫아도 여전히 참이어야 하는 내용인가" → 그렇다면 분리. (진입 카드는 ephemeral pointer 라 체인 supersede 로 닫힌다 — 본문에만 있던 durable 사실이 회상 불가로 소실된 실사고 있음.)
49
+ - **self_rules**: 사용자 명시 자기 규칙 지시 시만. / **evidence**: 파일·결과물 산출 시. / **verifications**: 루키가 직접 명령으로 fact 검증 시.
50
+
51
+ ## 파일 변경 알림
52
+
53
+ 파일을 생성·수정했다면: ```json {"files_created":["절대경로"],"files_modified":["절대경로"]} ```
@@ -0,0 +1,35 @@
1
+ # task 추적 상세 — 마무리 reconcile·status 갱신·자동 계층 도출
2
+
3
+ 세션 마무리(마무리 징후 감지) 또는 task status 를 바꾸려는 시점에 읽는 절차 문서다. status 는 루키 전용 — `/projects` 의 task status 는 **루키가 실제 업무·증거 기반으로만 갱신**한다(수동 토글 없음). 매 turn 금지 — **착수/완료 시점만**. 자기 출력 분석 금지.
4
+
5
+ ## 세션 마무리 task status reconcile (link 세션만)
6
+
7
+ `GET /api/rookie/tasks?project_id=<id>` 1회 후, **이번 세션 실제 산물과 사용자 지시**를 task 와 매칭(루키 자기 서술 근거 금지):
8
+
9
+ - **(가) git 산물 수집 먼저** (git 이 진실):
10
+ ```bash
11
+ git log --oneline ${SESSION_START_SHA:-HEAD~20}..HEAD
12
+ git diff --name-only ${SESSION_START_SHA:-HEAD~20}..HEAD
13
+ git status --short # START_DIRTY 제외분만 산물
14
+ ```
15
+ 커밋·파일·PR 또는 DB 검증 fact 가 매칭 근거 — **양쪽 다 0 이면 어떤 status 도 바꾸지 않는다(특히 done)**. DB 검증만으로 완성되는 작업은 검증 fact 로 done 후보 인정(자동 done 금지 동일). `SESSION_START_SHA` 미캡처 시 폴백 `HEAD~20`(과탐해도 doing 까지만).
16
+ - (나) 산물을 task 의 `spec_ref`/`title`/`detail` 과 의미 매칭 — 변경 파일이 `spec_ref` 영역과 겹치면 강한 매칭.
17
+ - 작업했는데 todo → `PATCH {id, status:'doing'}` (자동). / 매칭 없음 → `POST /tasks {project_id, title, status:'doing', source:'agent'}`.
18
+ - doing 중 근거 실재하는 완성건 → **일괄 1회 보고** `"다음 N개를 완료로 올릴까요? — A(근거…), B(근거…)"` → OK 시에만 done PATCH. "느낌상 완료" 제외.
19
+
20
+ ## status 갱신 (착수/완료/보류/재개)
21
+
22
+ - **진행중 (doing)** — 착수 시 `GET /api/rookie/tasks?project_id=$PID` 후 의미 매칭: 확실 → `PATCH {id, status:'doing'}` + 1줄 알림 / 애매 → "이 작업이 'X' task 맞나요?" 1회 확인 / 매칭 없음 → `POST /tasks {…, status:'doing', source:'agent'}` 신규.
23
+ - **완료 (done)** — **자동 done 금지.** `"'X' 를 완료로 올리겠습니다 — 근거: <파일/PR/검증 한 줄>. 올릴까요?"` → OK 시 `PATCH {id, status:'done', detail:'<기존>\n\n✅ 완료 YYYY-MM-DD — <근거>'}`.
24
+ - **보류 (blocked)** — `PATCH {id, status:'blocked', blocked_reason:'<사유>'}`. / **재개 (done→doing)** — 추가 작업 발생 시 루키 자율 `PATCH {id, status:'doing', detail:'<기존>\n\n🔄 재개 YYYY-MM-DD — <사유>'}` (확인 불요).
25
+
26
+ ## 완료 후보 surface (세션 시작 블록)
27
+
28
+ `## 🎯 완료 후보 감지` 블록의 `🎯 <task_id(전체 UUID)> "<title>" ← <sha> <subject>` 줄이 보이면 **다른 응답 전에** 후보별 done 승인 게이트로 여쭙는다(UUID 는 PATCH 용, 두목껜 title 만. `<sha>` 는 그대로): `"'<title>' 을 완료로 올릴까요? — 근거: 커밋 <sha> <subject>"` → OK 시에만 `PATCH {id, status:'done', detail:'<기존>\n\n✅ 완료 <date> — 커밋 <sha>'}` (**자동 done 금지**) / "아직/보류" 면 `PATCH {id, detail:'<기존>\n__done_defer:<sha>'}` 로 재알림 차단 — 이후 **다른 새 커밋** 매칭 시만 재surface. 블록 없으면 침묵. 근거는 주입된 git 출력만(self-loop 금지).
29
+
30
+ ## 자동 계층 도출 (승인 게이트) — 평면 task 다수 시 상위 목표 제안 (spec 2026-07-05)
31
+
32
+ - **⭐ 3단 구조 원칙 (두목 2026-07-05, 항상 적용)**: 잎사귀 task 를 목표(1단) 직계에 붙이지 않는다 — 반드시 **목표 → 중간그룹(2단) → 잎(3단)**. 중간그룹이 없으면 먼저 만들거나 제안. done 잎도 예외 없다.
33
+ - **트리거**: 두목 명시 요청 또는 미분류 최상위 task 다수(예 ≥8) 시 **1회 제안**. **절차**: tasks 조회 → `title`/`detail` 의미로 배정 → 요약 제시(**raw uuid 금지, title 로**) → **승인 게이트**: OK 한 그룹만 기록.
34
+ - **기록**: 신규 목표/중간그룹 `POST /api/rookie/tasks {project_id, title, detail?, status:'todo', source:'agent', parent_task_id?}` — 목표는 parent 없이, 중간그룹은 `parent_task_id`=목표. 잎은 `PATCH {id, parent_task_id:<중간그룹>}`. DB trigger 가 최종 검증(owner/project·무사이클·≤32단), 실패 자식만 400 라벨 1줄 보고 후 skip.
35
+ - **안전**: 기존 `parent_task_id` 있는 task 재배정 금지(명시 지시 시만). 목표 중복이면 기존에 붙인다. 신규 목표에 자식 0건이면 명시 보고(자동 삭제 금지). 부분 실패 무손실. **보고**: "목표 N개 · 자식 M건 연결(실패 K건: 사유)" 1줄.
package/version.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "cli_version": "0.4.5",
3
- "skill_version": "1.26.0"
2
+ "cli_version": "0.4.13",
3
+ "skill_version": "1.34.0"
4
4
  }