therookie 0.4.36 → 0.4.37
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "therookie",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.37",
|
|
4
4
|
"description": "Persistent memory for Claude Code and Codex CLI — teach your AI once and it remembers across sessions, tools, and machines. The AI new hire that remembers what you teach it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -35,6 +35,9 @@ export function listPendingExtractions({ orphanRoot }) {
|
|
|
35
35
|
// spec 2026-09-08 §2.4 — 'no_writes'(저장 0건) 와 비정상 종료를 구분해 표시한다.
|
|
36
36
|
// 섞이면 진짜 복구 대기가 묻힌다.
|
|
37
37
|
reason: m.reason || 'crash',
|
|
38
|
+
// 'stale_writes' 는 마지막 저장 시각을 같이 싣는다 — 어디부터가 미저장인지가
|
|
39
|
+
// 초안을 읽는 쪽의 첫 질문이다.
|
|
40
|
+
since: m.since || null,
|
|
38
41
|
recoveredAt: m.recovered_at || null,
|
|
39
42
|
// codex marker 는 codex 서브루트에 산다 — marker 명령이 이 루트를 겨냥해야
|
|
40
43
|
// 큐에서 실제로 빠진다(코드검토 P2). orphan-recovery.sh 의 ROOKIE_ORPHAN_DIR 규약.
|
|
@@ -105,7 +108,12 @@ export function renderDraft(entry, draft) {
|
|
|
105
108
|
const lines = []
|
|
106
109
|
const clean = (s) => redactDraftText(s).redacted.replace(/\s+/g, ' ').trim()
|
|
107
110
|
// spec 2026-09-08 §2.4 — 사유를 앞에 붙인다. 저장 0건과 비정상 종료는 봐야 할 것이 다르다.
|
|
108
|
-
const why =
|
|
111
|
+
const why =
|
|
112
|
+
entry.reason === 'no_writes'
|
|
113
|
+
? '저장 0건'
|
|
114
|
+
: entry.reason === 'stale_writes'
|
|
115
|
+
? `마지막 저장 뒤 미저장 구간${entry.since ? ` (저장 ${entry.since} 이후)` : ''}`
|
|
116
|
+
: '비정상 종료'
|
|
109
117
|
lines.push(
|
|
110
118
|
`◦ [${entry.runtime}] 세션 ${hashId8(entry.sid)} · ${why} (recovered ${entry.recoveredAt ?? 'unknown'})`
|
|
111
119
|
)
|
|
@@ -35,14 +35,17 @@ export function resolveSessionId(explicit) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* 저장이 생겼으니 "저장 0건" 대기는 더 이상 참이 아니다 —
|
|
38
|
+
* 저장이 생겼으니 "저장 0건"·"마지막 저장 뒤 미저장 구간" 대기는 더 이상 참이 아니다 —
|
|
39
|
+
* 그 표시만 지운다(이어 연 세션이 그 구간을 저장한 경우).
|
|
39
40
|
* 비정상 종료 복구 대기(reason 없음)는 tail 이 아직 안 건져졌으므로 건드리지 않는다.
|
|
40
41
|
*/
|
|
42
|
+
const SAVE_CLEARABLE = new Set(['no_writes', 'stale_writes'])
|
|
43
|
+
|
|
41
44
|
export function clearNoWritesPending(sessionId) {
|
|
42
45
|
if (!sessionId) return false
|
|
43
46
|
try {
|
|
44
47
|
const f = join(orphanRoot(), 'pending-extraction', sessionId + '.json')
|
|
45
|
-
if (JSON.parse(readFileSync(f, 'utf8'))?.reason
|
|
48
|
+
if (!SAVE_CLEARABLE.has(JSON.parse(readFileSync(f, 'utf8'))?.reason)) return false
|
|
46
49
|
rmSync(f, { force: true })
|
|
47
50
|
return true
|
|
48
51
|
} catch {
|
|
@@ -305,9 +305,16 @@ if [ "$HTTP_CODE" = "200" ]; then
|
|
|
305
305
|
# 어디에도 남지 않는다. transcript 는 있으므로 다음 세션이 건지도록 미추출 큐에 올린다.
|
|
306
306
|
# 내용 없는 세션(MESSAGE_COUNT=0)은 제외 — 건질 게 없다.
|
|
307
307
|
if command -v orphan_session_had_write >/dev/null 2>&1 \
|
|
308
|
-
&& [ "${MESSAGE_COUNT:-0}" -gt 0 ] 2>/dev/null
|
|
309
|
-
|
|
310
|
-
|
|
308
|
+
&& [ "${MESSAGE_COUNT:-0}" -gt 0 ] 2>/dev/null; then
|
|
309
|
+
if ! orphan_session_had_write "$SESSION_ID"; then
|
|
310
|
+
orphan_mark_no_writes "$SESSION_ID" "$TRANSCRIPT_PATH" || true
|
|
311
|
+
elif command -v orphan_session_has_unsaved_tail >/dev/null 2>&1 \
|
|
312
|
+
&& orphan_session_has_unsaved_tail "$SESSION_ID" "$TRANSCRIPT_PATH"; then
|
|
313
|
+
# 저장은 있었지만 그 뒤로 대화가 임계 이상 더 쌓였다 — 마지막 저장 뒤 구간은
|
|
314
|
+
# 아무 데도 안 남는다(2026-09-22 실측). 같은 큐에 사유만 갈라 올린다.
|
|
315
|
+
orphan_mark_no_writes "$SESSION_ID" "$TRANSCRIPT_PATH" "stale_writes" \
|
|
316
|
+
"$(orphan_session_last_write_at "$SESSION_ID")" || true
|
|
317
|
+
fi
|
|
311
318
|
fi
|
|
312
319
|
else
|
|
313
320
|
# §17 H-2: 큐 적재 + 1회 chain drain (일시적 깜빡임 회복)
|
|
@@ -113,17 +113,96 @@ orphan_session_had_write() {
|
|
|
113
113
|
[ -f "$dir/$key" ]
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
# 저장 마커 경로 — had_write 와 같은 키를 쓴다(세션 구간 단위).
|
|
117
|
+
_orphan_session_write_marker_path() {
|
|
118
|
+
local sid="$1"
|
|
119
|
+
[ -n "$sid" ] || return 1
|
|
120
|
+
command -v shasum >/dev/null 2>&1 || return 1
|
|
121
|
+
local key
|
|
122
|
+
key="$(_orphan_session_write_key "$sid")"
|
|
123
|
+
[ -n "$key" ] || return 1
|
|
124
|
+
printf '%s/%s' "${ROOKIE_SESSION_WRITES_DIR:-$HOME/.rookie/session-writes}" "$key"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# 마지막 저장 시각(ISO UTC). 마커가 없거나 못 읽으면 빈 출력.
|
|
128
|
+
# date -r 은 BSD/GNU 해석이 갈려 쓰지 않는다 — node 로 mtime 을 읽는다.
|
|
129
|
+
orphan_session_last_write_at() {
|
|
130
|
+
local sid="$1" marker
|
|
131
|
+
marker="$(_orphan_session_write_marker_path "$sid")" || return 1
|
|
132
|
+
[ -f "$marker" ] || return 1
|
|
133
|
+
node -e "
|
|
134
|
+
const fs = require('node:fs');
|
|
135
|
+
try { console.log(new Date(fs.statSync(process.argv[1]).mtimeMs).toISOString()); }
|
|
136
|
+
catch { console.log(''); }
|
|
137
|
+
" "$marker" 2>/dev/null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# 마지막 저장 이후 쌓인 대화 수. 셀 수 없으면 0 — 무동작이 오탐보다 안전하다.
|
|
141
|
+
orphan_session_tail_count() {
|
|
142
|
+
local sid="$1" transcript="$2" marker
|
|
143
|
+
[ -n "$transcript" ] && [ -f "$transcript" ] || { printf '0'; return 0; }
|
|
144
|
+
marker="$(_orphan_session_write_marker_path "$sid")" || { printf '0'; return 0; }
|
|
145
|
+
[ -f "$marker" ] || { printf '0'; return 0; }
|
|
146
|
+
node -e "
|
|
147
|
+
const fs = require('node:fs');
|
|
148
|
+
const out = (n) => { console.log(String(n)); process.exit(0); };
|
|
149
|
+
let since;
|
|
150
|
+
try { since = fs.statSync(process.argv[2]).mtimeMs; } catch { out(0); }
|
|
151
|
+
let raw;
|
|
152
|
+
try { raw = fs.readFileSync(process.argv[1], 'utf8'); } catch { out(0); }
|
|
153
|
+
let n = 0;
|
|
154
|
+
for (const line of raw.split('\n')) {
|
|
155
|
+
const t = line.trim(); if (!t) continue;
|
|
156
|
+
let j; try { j = JSON.parse(t); } catch { continue; }
|
|
157
|
+
const m = j.message || j;
|
|
158
|
+
if (m.role !== 'user' && m.role !== 'assistant') continue;
|
|
159
|
+
const ts = Date.parse(j.timestamp || m.timestamp || '');
|
|
160
|
+
if (!Number.isFinite(ts) || ts <= since) continue;
|
|
161
|
+
let c = m.content;
|
|
162
|
+
if (typeof c !== 'string') {
|
|
163
|
+
c = Array.isArray(c)
|
|
164
|
+
? c.filter(b => b && b.type === 'text' && typeof b.text === 'string').map(b => b.text).join('\n').trim()
|
|
165
|
+
: '';
|
|
166
|
+
} else { c = c.trim(); }
|
|
167
|
+
if (!c) continue;
|
|
168
|
+
n++;
|
|
169
|
+
}
|
|
170
|
+
out(n);
|
|
171
|
+
" "$transcript" "$marker" 2>/dev/null || printf '0'
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# 마지막 저장 뒤로 임계 이상이 쌓였는가 — Stop hook 이 돌았다는 것과 그 구간이 기억에
|
|
175
|
+
# 남았다는 것은 다른 일이다(2026-09-22 실측: 세션 5개가 flushed source=stop 으로 닫히고
|
|
176
|
+
# 마지막 저장 뒤 30분 넘는 구간이 카드 없이 사라졌다).
|
|
177
|
+
#
|
|
178
|
+
# 기본 3 인 이유: 세는 것은 본문 있는 user·assistant 메시지라 도구 호출만 한 턴은 안 세인다.
|
|
179
|
+
# 같은 실측에서 두목 지시 한 건을 받아 처리하고 끝난 세션의 tail 이 3 이었고, 42·10 인 세션과
|
|
180
|
+
# 함께 잡혀야 했다. 저장으로 잘 닫은 세션은 뒤에 인사 한두 줄만 남아 3 에 못 미친다 —
|
|
181
|
+
# 임계를 낮게 잡되 0 으로 두지 않는 이유가 그것이다(큐가 넓어지면 다음 세션이 큐를 안 믿는다).
|
|
182
|
+
orphan_session_has_unsaved_tail() {
|
|
183
|
+
local sid="$1" transcript="$2" count min
|
|
184
|
+
min="${ROOKIE_UNSAVED_TAIL_MIN:-3}"
|
|
185
|
+
case "$min" in (*[!0-9]*|'') min=3 ;; esac
|
|
186
|
+
count="$(orphan_session_tail_count "$sid" "$transcript")"
|
|
187
|
+
case "$count" in (*[!0-9]*|'') return 1 ;; esac
|
|
188
|
+
[ "$count" -ge "$min" ]
|
|
189
|
+
}
|
|
190
|
+
|
|
116
191
|
# 저장 0건이면 pending-extraction 에 reason=no_writes 로 올린다.
|
|
117
192
|
# 비정상 종료(crash) 사유와 섞이지 않게 reason 을 남긴다 — 섞이면 진짜 복구 대기가 묻힌다.
|
|
118
193
|
orphan_mark_no_writes() {
|
|
119
|
-
local sid="$1" transcript="${2:-}"
|
|
194
|
+
local sid="$1" transcript="${2:-}" reason="${3:-no_writes}" since="${4:-}"
|
|
120
195
|
[ -n "$sid" ] || return 1
|
|
196
|
+
# reason·since 는 marker JSON 에 그대로 박힌다 — 따옴표·백슬래시가 섞이면 읽는 쪽이 깨진다.
|
|
197
|
+
reason="$(printf '%s' "$reason" | tr -cd 'A-Za-z0-9_.:-')"
|
|
198
|
+
[ -n "$reason" ] || reason="no_writes"
|
|
199
|
+
since="$(printf '%s' "$since" | tr -cd 'A-Za-z0-9_.:-')"
|
|
121
200
|
# 이미 추출을 마쳤거나 대기 중이면 덮지 않는다.
|
|
122
201
|
[ -f "$(_orphan_client_extracted_dir)/$sid.json" ] && return 0
|
|
123
202
|
[ -f "$(_orphan_pending_dir)/$sid.json" ] && return 0
|
|
124
203
|
_orphan_write_marker "$(_orphan_pending_dir)" "$sid" \
|
|
125
|
-
"$(printf '{"status":"pending_extraction","reason":"
|
|
126
|
-
"$transcript" "$(date -u +%Y-%m-%dT%H:%M:%SZ)")"
|
|
204
|
+
"$(printf '{"status":"pending_extraction","reason":"%s","since":"%s","transcript_path":"%s","recovered_at":"%s"}' \
|
|
205
|
+
"$reason" "$since" "$transcript" "$(date -u +%Y-%m-%dT%H:%M:%SZ)")"
|
|
127
206
|
}
|
|
128
207
|
|
|
129
208
|
# P0.4 — 짧고 내용 없는(low_value) 세션 marker. record_pending 대신 호출 → 재적재 차단.
|
package/version.json
CHANGED