therookie 0.4.24 → 0.4.25

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.24",
3
+ "version": "0.4.25",
4
4
  "description": "the Rookie — the AI new hire that remembers what you teach it. A CLI that connects a personal AI memory to AI tools like Claude and Codex.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -5,11 +5,20 @@ import { toPosixPath } from './hook-command.js'
5
5
  export const ROOKIE_BEGIN = '# >>> rookie codex hook (managed) — 수동 편집 금지 >>>'
6
6
  export const ROOKIE_END = '# <<< rookie codex hook (managed) <<<'
7
7
 
8
- function block(hookScriptPath, platform = process.platform) {
9
- // Codex SessionStart command handler. additionalContext 출력 형식은 Claude 와 동일.
8
+ function block(paths, platform = process.platform) {
9
+ // Codex SessionStart / UserPromptSubmit command handler. additionalContext 출력 형식은 Claude 와 동일.
10
10
  // Windows 는 backslash 경로를 forward-slash 로 변환(Git Bash 가 실행) — macOS/Linux 무변경.
11
- const path = toPosixPath(hookScriptPath, platform)
12
- return [
11
+ //
12
+ // spec 2026-09-02 (2단계 로드 강제): UserPromptSubmit 이 없으면 SessionStart 가 만든
13
+ // 인격 대기열을 아무도 소비하지 않는다 — 인격은 안 들어가고 파일만 쌓인다(2026-09-02 실사고).
14
+ // 둘은 같이 등록해야 짝이 맞는다. paths 는 문자열(SessionStart 만) 또는 {sessionStart, promptSubmit}.
15
+ const sessionStart = toPosixPath(
16
+ typeof paths === 'string' ? paths : paths.sessionStart,
17
+ platform
18
+ )
19
+ const promptSubmitRaw = typeof paths === 'string' ? null : paths.promptSubmit
20
+ const promptSubmit = promptSubmitRaw ? toPosixPath(promptSubmitRaw, platform) : null
21
+ const lines = [
13
22
  ROOKIE_BEGIN,
14
23
  '[[hooks.SessionStart]]',
15
24
  'matcher = "startup|resume"',
@@ -18,15 +27,28 @@ function block(hookScriptPath, platform = process.platform) {
18
27
  'type = "command"',
19
28
  // B-3: codex_cli 표면을 인자로 전달 → session-start.sh 가 x-rookie-surface 로 비콘.
20
29
  // (Codex Desktop 도 같은 config.toml 을 읽어 codex_cli 로 집계 — desktop 세분화는 후속.)
21
- `command = 'bash "${path}" codex_cli'`,
30
+ `command = 'bash "${sessionStart}" codex_cli'`,
22
31
  'timeout = 15',
23
- ROOKIE_END,
24
- ].join('\n')
32
+ ]
33
+ if (promptSubmit) {
34
+ lines.push(
35
+ '',
36
+ '[[hooks.UserPromptSubmit]]',
37
+ '',
38
+ '[[hooks.UserPromptSubmit.hooks]]',
39
+ 'type = "command"',
40
+ `command = 'bash "${promptSubmit}"'`,
41
+ // 파일만 읽고 끝나는 경로라 짧게 — Claude 쪽도 3초다.
42
+ 'timeout = 5'
43
+ )
44
+ }
45
+ lines.push(ROOKIE_END)
46
+ return lines.join('\n')
25
47
  }
26
48
 
27
49
  // 기존 managed 블록이 있으면 교체, 없으면 끝에 append. 마커 밖은 보존.
28
- export function upsertCodexHookBlock(existingToml, hookScriptPath, platform = process.platform) {
29
- const fresh = block(hookScriptPath, platform)
50
+ export function upsertCodexHookBlock(existingToml, hookPaths, platform = process.platform) {
51
+ const fresh = block(hookPaths, platform)
30
52
  const begin = existingToml.indexOf(ROOKIE_BEGIN)
31
53
  if (begin === -1) {
32
54
  const base = existingToml.replace(/\s*$/, '')
@@ -170,7 +170,11 @@ export function applyCodexSetup({ home = homedir() } = {}) {
170
170
  const existing = existsSync(configPath) ? readFileSync(configPath, 'utf8') : ''
171
171
  let next
172
172
  try {
173
- next = upsertCodexHookBlock(existing, hookScript)
173
+ next = upsertCodexHookBlock(existing, {
174
+ sessionStart: hookScript,
175
+ // 인격 대기열 소비자 — 짝이 없으면 파일만 쌓인다(spec 2026-09-02).
176
+ promptSubmit: join(homedir(), '.rookie', 'bin', 'user-prompt-skill-gate.sh'),
177
+ })
174
178
  } catch (e) {
175
179
  // marker 손상(begin 만 있고 end 없음) → 파일을 건드리지 않고 경고 신호만 반환(사용자 config 보호).
176
180
  if (e?.code === 'MALFORMED_CODEX_MARKER') return { configPath, hookScript, error: 'malformed_marker' }
@@ -61,3 +61,34 @@ pending_inject_write() {
61
61
  ' 2>/dev/null || true
62
62
  return 0
63
63
  }
64
+
65
+ # pending_inject_sweep — 소비되지 않고 남은 대기열 청소.
66
+ # 짝이 되는 UserPromptSubmit 훅이 없는 환경(구 Codex 등록본 등)에서는 대기열이
67
+ # 만들어지기만 하고 아무도 읽지 않는다. 그대로 두면 파일이 쌓이고, 그 자체가
68
+ # 조용한 실패가 된다(2026-09-02 실사고 — Codex 는 SessionStart 만 등록돼 있었다).
69
+ # 24시간 넘게 남은 것만 지운다 — 진행 중인 세션의 대기열은 건드리지 않는다.
70
+ pending_inject_sweep() {
71
+ local dir
72
+ dir="$(pending_inject_dir)"
73
+ [ -d "$dir" ] || return 0
74
+ ROOKIE_PI_DIR="$dir" ROOKIE_DROP_LOG="${ROOKIE_DROP_LOG:-$HOME/.rookie/dropped-mutations.log}" node -e '
75
+ try {
76
+ const fs=require("fs"), path=require("path");
77
+ const dir=process.env.ROOKIE_PI_DIR, MAX=24*3600*1000;
78
+ let swept=0;
79
+ for(const f of fs.readdirSync(dir)){
80
+ if(!f.endsWith(".json") && !f.endsWith(".lock")) continue;
81
+ const p=path.join(dir,f);
82
+ try{
83
+ const st=fs.lstatSync(p);
84
+ if(!st.isFile()){ fs.unlinkSync(p); continue; }
85
+ if(Date.now()-st.mtimeMs>MAX){ fs.unlinkSync(p); swept+=1; }
86
+ }catch{}
87
+ }
88
+ if(swept>0){
89
+ try{ fs.appendFileSync(process.env.ROOKIE_DROP_LOG, new Date().toISOString()+" pending_inject swept_unconsumed="+swept+"\n"); }catch{}
90
+ }
91
+ } catch {}
92
+ ' 2>/dev/null || true
93
+ return 0
94
+ }
@@ -261,6 +261,8 @@ if [ "$STATUS_CODE" = "200" ]; then
261
261
  # spec 2026-09-02 (2단계 로드 강제) — 예산에서 생략된 섹션 본문을 대기열에 둔다.
262
262
  # UserPromptSubmit 이 첫 발화에 직접 주입한다. resume 은 컨텍스트가 살아 있어 제외.
263
263
  if command -v pending_inject_write >/dev/null 2>&1; then
264
+ # 소비자 없는 환경에서 쌓인 옛 대기열부터 치운다(24시간 초과분만).
265
+ command -v pending_inject_sweep >/dev/null 2>&1 && pending_inject_sweep || true
264
266
  pending_inject_write "$SESSION_ID" "$RESP_FILE" "$SOURCE" || true
265
267
  fi
266
268
 
package/version.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "cli_version": "0.4.24",
2
+ "cli_version": "0.4.25",
3
3
  "skill_version": "1.39.0"
4
4
  }