therookie 0.4.20 → 0.4.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/commands/doctor.js
CHANGED
|
@@ -124,6 +124,7 @@ export async function runDoctor(flags) {
|
|
|
124
124
|
await printDirectiveMetric(cfg)
|
|
125
125
|
await printHotWriteMetric(cfg)
|
|
126
126
|
await printAttributionMetric(cfg)
|
|
127
|
+
await printLayerInflowMetric(cfg)
|
|
127
128
|
await printPromotionMetric(cfg)
|
|
128
129
|
await printViolationTop(cfg)
|
|
129
130
|
await printEmbeddingGuard(cfg)
|
|
@@ -612,6 +613,53 @@ async function printEmbeddingGuard(cfg) {
|
|
|
612
613
|
}
|
|
613
614
|
}
|
|
614
615
|
|
|
616
|
+
/**
|
|
617
|
+
* spec 2026-09-01-durable-fact-inflow-restoration §4-1 — 판정식 복구.
|
|
618
|
+
*
|
|
619
|
+
* 예전 식은 `examined > 0 ? skipped / examined : 0` 이라, 볼 것이 아예 없으면
|
|
620
|
+
* 비율이 0 이 되어 **완전 고갈이 초록(✓)으로 표시**됐다. 나빠질수록 경고하다가
|
|
621
|
+
* 죽으면 정상으로 돌아오는 계기판이었고, durable fact 유입이 끊긴 두 달 반 동안
|
|
622
|
+
* 아무 신호도 없던 이유가 이것이다.
|
|
623
|
+
*
|
|
624
|
+
* 그래서 "볼 것이 없다"(starved)를 "다 걸러냈다"(degraded)와 별도 상태로 뗀다 —
|
|
625
|
+
* 둘은 원인이 다르다. 전자는 입력 쪽, 후자는 임계 쪽 문제다.
|
|
626
|
+
*/
|
|
627
|
+
export function classifyPromotionHealth({ calls, examined, skipped }) {
|
|
628
|
+
if (calls === 0) return { level: 'idle', prefix: '⚙️ ', note: '미가동' }
|
|
629
|
+
if (examined === 0)
|
|
630
|
+
return { level: 'starved', prefix: '⚠️ ', note: '검토할 cluster 0 — 입력 고갈' }
|
|
631
|
+
if (skipped / examined >= 0.8)
|
|
632
|
+
return { level: 'degraded', prefix: '⚠️ ', note: null }
|
|
633
|
+
return { level: 'ok', prefix: '✓ ', note: null }
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// spec 2026-09-01-durable-fact-inflow-restoration §6-4 — 기억층별 유입.
|
|
637
|
+
// 이 라인이 없어서 durable fact 유입이 끊긴 것을 두 달 반 뒤에야 알았다.
|
|
638
|
+
// 임계 판정은 서버가 한다(starved) — 여기서 다시 계산하면 두 기준이 갈린다.
|
|
639
|
+
async function printLayerInflowMetric(cfg) {
|
|
640
|
+
if (!cfg?.endpoint || !cfg?.token) return
|
|
641
|
+
try {
|
|
642
|
+
const s = await apiGet(cfg.endpoint, cfg.token, '/api/rookie/metric/layer-inflow/summary')
|
|
643
|
+
if (!s || !Array.isArray(s.layers)) return
|
|
644
|
+
if (s.fetchFailed) {
|
|
645
|
+
console.log('⚠️ 기억층 유입: 조회 실패 — 지표를 신뢰하지 말 것')
|
|
646
|
+
return
|
|
647
|
+
}
|
|
648
|
+
const parts = s.layers.map((l) => {
|
|
649
|
+
const age = l.daysSinceLast === null ? '유입 없음' : `${l.daysSinceLast}일 전`
|
|
650
|
+
return `${l.layer} ${l.count}${l.starved ? `(⚠️ ${age})` : ''}`
|
|
651
|
+
})
|
|
652
|
+
console.log(
|
|
653
|
+
`${s.anyStarved ? '⚠️ ' : '✓ '}기억층 유입 /${s.windowDays ?? 14}일: ${parts.join(' · ')}`
|
|
654
|
+
)
|
|
655
|
+
if (s.anyStarved) {
|
|
656
|
+
console.log(' → 유입이 끊긴 층이 있습니다. 저장 경로가 막혔는지 확인하세요.')
|
|
657
|
+
}
|
|
658
|
+
} catch {
|
|
659
|
+
// silent — endpoint 미응답 또는 미land 시 본 라인만 누락.
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
615
663
|
async function printPromotionMetric(cfg) {
|
|
616
664
|
if (!cfg?.endpoint || !cfg?.token) return
|
|
617
665
|
try {
|
|
@@ -625,17 +673,16 @@ async function printPromotionMetric(cfg) {
|
|
|
625
673
|
const promoted = summary?.totalPromoted ?? 0
|
|
626
674
|
const skipped = summary?.totalSkipped ?? 0
|
|
627
675
|
const window = summary?.windowDays ?? 7
|
|
628
|
-
|
|
629
|
-
|
|
676
|
+
const health = classifyPromotionHealth({ calls, examined, skipped })
|
|
677
|
+
if (health.level === 'idle') {
|
|
678
|
+
console.log(`${health.prefix}promotion: 0건/${window}일 (${health.note})`)
|
|
630
679
|
return
|
|
631
680
|
}
|
|
632
|
-
const skippedRatio = examined > 0 ? skipped / examined : 0
|
|
633
681
|
const promotionPct =
|
|
634
682
|
examined > 0 ? Math.round((promoted / examined) * 100) : 0
|
|
635
|
-
|
|
636
|
-
if (skippedRatio >= 0.8) prefix = '⚠️ '
|
|
683
|
+
const tail = health.note ? ` — ${health.note}` : ''
|
|
637
684
|
console.log(
|
|
638
|
-
`${prefix}promotion: ${calls}건/${window}일, examined ${examined} / promoted ${promoted} / skipped ${skipped} (승격률 ${promotionPct}%)`
|
|
685
|
+
`${health.prefix}promotion: ${calls}건/${window}일, examined ${examined} / promoted ${promoted} / skipped ${skipped} (승격률 ${promotionPct}%)${tail}`
|
|
639
686
|
)
|
|
640
687
|
} catch {
|
|
641
688
|
// silent — endpoint 미응답 또는 미land 시 본 라인만 누락.
|
package/templates/skill/SKILL.md
CHANGED
|
@@ -127,7 +127,9 @@ curl -s -H "Authorization: Bearer $TOKEN" \
|
|
|
127
127
|
|
|
128
128
|
## 저장 (hot write) — 요지
|
|
129
129
|
|
|
130
|
-
카테고리:
|
|
130
|
+
카테고리: **운영 사실(서버/포트/유저명/경로/명시 결정·측정 결과, 자동) → `facts`(durable 영구층)** / 다음 세션 진입점 → `working_cards` / 능력 증거(파일·결과물 산출 시, 자동) → `evidence` / 자기 규칙(사용자 명시 시그널 "원칙·습관·항상·절대" 만) → `self_rules` / 개인 취향(명시 시그널 "좋아해·선호·취향" 만) → `personal_facts` / 잡담·일회성·민감 정보 → 저장 안 함.
|
|
131
|
+
|
|
132
|
+
⚠️ **운영 사실을 `working_cards` 로 보내지 않는다.** 진입카드는 은퇴·supersede 로 사라지도록 설계된 층이라, 거기 넣은 영구 지식은 다음 카드가 닫으면 같이 사라진다(2026-06~08 실사고 — 영구층 유입 0). 판별: "다음 checkpoint 가 이 카드를 닫아도 여전히 참인가" → 그렇다면 `facts`.
|
|
131
133
|
|
|
132
134
|
저장 전 **recall dedup** — 같은 취지 durable fact 존재 시 새 저장 금지. **재확인 근거가 있으면** `verifications` confirmed 1건(fact 당 하루 1회, 서버 동일 게이트 — working card·근거 없는 회상 제외)으로 반복 신호를 남긴다. 성공 시 `[기억] <요약> 저장` 1줄, 실패 시 retry 1회 + 알림.
|
|
133
135
|
|
|
@@ -7,7 +7,7 @@ SKILL.md 코어에서 해당 신호가 보일 때만 읽는 절차 문서다.
|
|
|
7
7
|
비정상 종료 세션은 Stop hook 이 못 돌아 fact·진입카드가 없다 — chunk 복구는 `orphan_recover`, **fact 추출은 차차 책임**(서버 추출 off). 컨텍스트에 ``🛟 …`` 블록과 ``- <sid> :: <transcript_path>`` 목록이 보이면 **다른 응답 전에** 처리한다:
|
|
8
8
|
|
|
9
9
|
1. `rookie orphan-extract --batch=5` — 대기 세션을 오래된 순으로 읽어 rule-base 초안(요약·파일 경로·git sha, redact 통과)을 뽑는다. transcript 원문 통째 Read 금지(부족 시 보조 — Claude `~/.claude/projects/`, Codex `~/.codex/sessions/`).
|
|
10
|
-
2. 초안에서 **저장 가치가 있는 것만** — 운영
|
|
10
|
+
2. 초안에서 **저장 가치가 있는 것만** — durable 운영 사실→`facts`, 다음 진입점→working_card, 파일·결과물→evidence, 명시적 자기규칙→self_rule, 취향→personal_fact. 잡담·민감정보 금지(redactor 원칙). 0건 세션도 정상.
|
|
11
11
|
3. **recall 로 dedup 후** 누락분만 `POST /api/rookie/batch-mutate`(`project_id` 포함).
|
|
12
12
|
4. 처리한 세션마다(0건이어도) 초안 하단의 완료 marker 명령을 **그대로** 실행해 큐에서 비운다 — 안 하면 매 세션 재노출:
|
|
13
13
|
```bash
|
|
@@ -23,7 +23,9 @@ rookie save --file <body.json> # 또는: echo '<body>' | rookie save
|
|
|
23
23
|
"self_rules": [ { "category": "identity|voice|work_style|strength|preference|rule", "rule": "규칙 본문", "confidence": "high|medium|low", "peers_reviewed": "기존 규칙 목록을 보고 새 규칙이라 판단했을 때만 true (아래 검토 게이트)", "reinforces": "같은 취지 기존 규칙 UUID — 새로 저장하지 않고 강화만", "supersedes": "기존 rule UUID?", "deprecation_reason": "user_override|conflict|noise|manual?", "ownership_scope": "personal|project?", "project_id": "scope=project 일 때 필수 uuid" } ],
|
|
24
24
|
"evidence": [ { "capability": "능력 진술문", "approach": "접근법?", "status": "completed|in-progress?", "context": "맥락?", "artifact_paths": ["파일 절대경로 (project 폴백 매칭에도 쓰임)"], "project_id": "? (uuid=명시 귀속 / null=전역 / 생략=세션 귀속)" } ],
|
|
25
25
|
"supersede_links": [ { "parent_id": "폐기할 구 카드 UUID", "child_id": "대체하는 새 카드 UUID" } ],
|
|
26
|
-
"verifications": [ { "fact_id": "검증 대상 fact UUID", "method": "(위와 동일 enum)", "outcome": "confirmed|refuted|ambiguous", "evidence": "검증 명령 결과 한 줄" } ]
|
|
26
|
+
"verifications": [ { "fact_id": "검증 대상 fact UUID", "method": "(위와 동일 enum)", "outcome": "confirmed|refuted|ambiguous", "evidence": "검증 명령 결과 한 줄" } ],
|
|
27
|
+
"facts": { "items": [ { "body": "durable 운영 사실 본문", "tags": ["custom"], "reasoning": "왜 그런지?", "observed_at": "ISO8601?", "project_id": "? (uuid=명시 귀속 / null=명시 전역 / 생략=세션 귀속)", "linked_ids": ["이 사실이 대체하는 기존 durable fact UUID (같은 범위여야 함)"] } ], "repo_url": "(권장)", "repo_path": "(권장)", "conversation_id": "uuid?" },
|
|
28
|
+
"personal_facts": [ { "body": "두목 개인 취향·배경", "tags": ["preference"], "observed_at": "ISO8601?" } ]
|
|
27
29
|
}
|
|
28
30
|
```
|
|
29
31
|
|
|
@@ -55,7 +57,12 @@ top-level `all_committed` 가 **`true` 일 때만** `[기억] … 저장` 보고
|
|
|
55
57
|
- **직전 카드 supersede (필수 습관)**: 같은 작업 흐름(프로젝트·이어지는 제목/파일/브랜치)의 새 "다음:" 카드는 recall dedup 으로 확인한 직전 카드 `id` 를 `supersedes` 에 넣어 닫는다. 애매하면 생략. 프로젝트 불일치 parent 는 서버가 자동 skip.
|
|
56
58
|
- **cross-project·전역 예외**: 명백히 다른 프로젝트/전역 fact 는 **생략이 아니라 명시적으로 `project_id: null`**(전역) 또는 그 프로젝트 uuid — 생략은 세션 project 자동 귀속(오귀속). null 도 scope=all 회상에 잡힌다.
|
|
57
59
|
- **task 연결**: 확실히 매칭된 task 면 `task_id` 지정 — done 시 카드 자동 은퇴. project_id 는 생략해 task 에서 derive(불일치 시 400). **애매하면** task_id 없이 `__task_link_candidate:<task_id>` 태그만.
|
|
58
|
-
- **durable 사실 분리 저장 (필수)**: 진입 카드·checkpoint 본문에 durable 운영 사실(블로커 발생/해소·환경 변화·외부 상태 전환·측정 결과)을 끼워 넣지 않는다 —
|
|
60
|
+
- **durable 사실 분리 저장 (필수)**: 진입 카드·checkpoint 본문에 durable 운영 사실(블로커 발생/해소·환경 변화·외부 상태 전환·측정 결과)을 끼워 넣지 않는다 — **`facts` 키로 분리 저장**하고 진입 카드엔 요지만. (2026-09-01 이전엔 '별도 working_cards item' 이라고 안내했는데, 그것도 결국 진입카드라 은퇴 대상이었다 — 그래서 문을 따로 냈다.) 판별: "다음 checkpoint 가 이 카드를 닫아도 여전히 참이어야 하는 내용인가" → 그렇다면 분리. (진입 카드는 ephemeral pointer 라 체인 supersede 로 닫힌다 — 본문에만 있던 durable 사실이 회상 불가로 소실된 실사고 있음.)
|
|
61
|
+
- **facts (durable 영구층)**: 다음 checkpoint 가 닫아도 여전히 참인 운영 사실 — 블로커 발생·해소, 환경·경로·포트, 외부 상태 전환, 측정 결과, 함정. **`is_working=false` 로 저장돼 은퇴·TTL 대상이 아니다.**
|
|
62
|
+
- **working_cards 와 고르는 법**: "다음에 뭘 할지"면 working_cards, "무엇이 참인지"면 facts. 진입 카드 문장(`다음:` 으로 시작 등)을 facts 로 보내면 `looks_like_working_card` 로 거부된다 — 문을 잘못 고른 것이다.
|
|
63
|
+
- 거부 사유: `residual_marker`(본문에 redactor 출력 마커) / `project_not_accessible` / `supersede_scope_mismatch`(남의·다른 프로젝트·진입카드를 닫으려 함) / `too_many_items`(8 초과). 응답 `summary.facts` 의 `skipped_duplicate` 는 같은 범위에 같은 사실이 이미 있었다는 뜻이라 정상이다.
|
|
64
|
+
- ⚠️ 2026-06-16~09-01 이 문이 없어서 영구층 유입이 0이었다. 운영 사실을 진입 카드에 끼워 넣지 말고 여기로 보낸다.
|
|
65
|
+
- **personal_facts**: 두목 개인 취향·배경 (명시 시그널 "좋아해·선호·취향" 만).
|
|
59
66
|
- **self_rules**: 사용자 명시 자기 규칙 지시 시만.
|
|
60
67
|
- **범위를 반드시 판단한다 (필수)**: 규칙 본문이 **특정 프로젝트·앱·환경에서만 뜻이 통하면** `ownership_scope:"project"` + `project_id`(세션 시작 context 응답의 `current_project.id`)를 단다. 판별 한 문장 — **"다른 프로젝트 세션에서도 이 규칙이 필요한가."** 필요하면 전역(생략=personal), 아니면 project.
|
|
61
68
|
- project 예: 특정 앱의 빌드·배포 절차, 그 제품의 콘텐츠 선정 기준, 그 저장소의 파일 구조 규약, 특정 기기·계정에서만 쓰는 명령
|
package/version.json
CHANGED