therookie 0.4.38 → 0.4.39
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 +1 -1
- package/src/cli.js +4 -2
- package/src/commands/codex-setup.js +10 -0
- package/src/commands/doctor.js +3 -1
- package/src/commands/update.js +6 -8
- package/src/lib/claude-md-block.js +145 -59
- package/templates/codex-md/rookie-block.md +7 -0
- package/version.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "therookie",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.39",
|
|
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": [
|
package/src/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ const HELP = `rookie — the Rookie Skill/Hook 설치 관리자
|
|
|
20
20
|
사용:
|
|
21
21
|
rookie init [--local] [--force] [--yes] [--deep-scan] [--claude-md | --no-claude-md]
|
|
22
22
|
rookie login [--local] [--claude-md | --no-claude-md]
|
|
23
|
-
rookie update [--force] [--claude-md | --no-claude-md]
|
|
23
|
+
rookie update [--force] [--claude-md | --no-claude-md] [--codex-md | --no-codex-md]
|
|
24
24
|
rookie link [--local]
|
|
25
25
|
rookie save [--file <body.json>] [--json] # 또는 stdin. --json = 서버 raw 응답 출력
|
|
26
26
|
rookie status
|
|
@@ -28,7 +28,7 @@ const HELP = `rookie — the Rookie Skill/Hook 설치 관리자
|
|
|
28
28
|
rookie unlink [--local | --global]
|
|
29
29
|
rookie token list | revoke <id> | rotate
|
|
30
30
|
rookie sweep-recovery [--force]
|
|
31
|
-
rookie codex-setup [--yes]
|
|
31
|
+
rookie codex-setup [--yes] [--codex-md | --no-codex-md]
|
|
32
32
|
rookie done-scan [--force]
|
|
33
33
|
rookie context-diet [--json] [--force]
|
|
34
34
|
rookie orphan-extract [--batch=5]
|
|
@@ -40,6 +40,8 @@ const HELP = `rookie — the Rookie Skill/Hook 설치 관리자
|
|
|
40
40
|
--deep-scan 홈 디렉토리에서 로컬 Skill 전수 스캔
|
|
41
41
|
--claude-md 글로벌 ~/.claude/CLAUDE.md 에 더루키 관리 블록을 넣는다(심링크·이미 같은 규칙이 있어도, update 에서도)
|
|
42
42
|
--no-claude-md 글로벌 CLAUDE.md 를 건드리지 않는다 (환경 변수 ROOKIE_NO_CLAUDE_MD=1 과 같다)
|
|
43
|
+
--codex-md Codex 전역 지시 파일(AGENTS.override.md 가 있으면 그것, 없으면 AGENTS.md)에 더루키 관리 블록을 넣는다
|
|
44
|
+
--no-codex-md Codex 전역 지시 파일을 건드리지 않는다 (환경 변수 ROOKIE_NO_CODEX_MD=1 과 같다)
|
|
43
45
|
|
|
44
46
|
명령:
|
|
45
47
|
init 첫 설치. 비밀번호 입력 받아 PAT 발급 + Skill/Hook 배포.
|
|
@@ -4,6 +4,7 @@ import { existsSync } from 'node:fs'
|
|
|
4
4
|
import { confirm } from '../prompt.js'
|
|
5
5
|
import { loadConfig } from '../config.js'
|
|
6
6
|
import { applyCodexSetup } from '../lib/deploy-artifacts.js'
|
|
7
|
+
import { runCodexMdStep } from '../lib/claude-md-block.js'
|
|
7
8
|
|
|
8
9
|
// Codex config.toml 에 더루키 SessionStart hook 을 동의 기반으로 등록한다(managed 블록만).
|
|
9
10
|
// 명시적 Codex-only 재설정 진입점 — 내부 write 는 applyCodexSetup(3층 adapter) 공유(spec §4 item 6).
|
|
@@ -33,4 +34,13 @@ export async function runCodexSetup(flags) {
|
|
|
33
34
|
process.exit(1)
|
|
34
35
|
}
|
|
35
36
|
console.log('✓ 등록 완료. 새 Codex 세션부터 자동 적용됩니다.')
|
|
37
|
+
|
|
38
|
+
// spec 2026-09-29 codex-agents-md — Codex 전역 지시 파일에 "더루키 먼저" 관리 블록(동의 기반).
|
|
39
|
+
const { lines } = await runCodexMdStep({
|
|
40
|
+
mode: 'connect',
|
|
41
|
+
flags,
|
|
42
|
+
interactive: !!process.stdin.isTTY && !flags.yes,
|
|
43
|
+
confirmFn: confirm,
|
|
44
|
+
})
|
|
45
|
+
for (const line of lines) console.log(line)
|
|
36
46
|
}
|
package/src/commands/doctor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
2
|
-
import { claudeMdStatusLine } from '../lib/claude-md-block.js'
|
|
2
|
+
import { claudeMdStatusLine, codexMdStatusLine } from '../lib/claude-md-block.js'
|
|
3
3
|
import { homedir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { execSync } from 'node:child_process'
|
|
@@ -45,6 +45,8 @@ export async function runDoctor(flags) {
|
|
|
45
45
|
console.log(hasRookieHook(lSettings) ? '✓ 로컬 hook 등록됨' : ' 로컬 hook 없음')
|
|
46
46
|
const claudeMdLine = claudeMdStatusLine()
|
|
47
47
|
if (claudeMdLine) console.log(claudeMdLine)
|
|
48
|
+
const codexMdLine = codexMdStatusLine()
|
|
49
|
+
if (codexMdLine) console.log(codexMdLine)
|
|
48
50
|
|
|
49
51
|
if (g && l && g.version !== l.version) {
|
|
50
52
|
console.log(`⚠ 버전 불일치: 글로벌 v${g.version} vs 로컬 v${l.version}`)
|
package/src/commands/update.js
CHANGED
|
@@ -13,7 +13,7 @@ import { semverGt, fetchReleaseManifest, selfUpdateAndRerun, isNpmManagedInstall
|
|
|
13
13
|
import { installSkill } from '../lib/skill-installer.js'
|
|
14
14
|
import { claudeHookCommand, escapeJsonStringValue } from '../lib/hook-command.js'
|
|
15
15
|
import { mergeHooks } from '../lib/merge-hooks.js'
|
|
16
|
-
import { runClaudeMdStep } from '../lib/claude-md-block.js'
|
|
16
|
+
import { runClaudeMdStep, runCodexMdStep } from '../lib/claude-md-block.js'
|
|
17
17
|
import { confirm } from '../prompt.js'
|
|
18
18
|
|
|
19
19
|
const __filename = fileURLToPath(import.meta.url)
|
|
@@ -58,13 +58,11 @@ export async function runUpdate(flags) {
|
|
|
58
58
|
// manifest "최신" 조기 종료·서버 실패보다 앞에 둔다. 표식이 있으면 갱신, 없으면 대화형 확인 또는
|
|
59
59
|
// --claude-md 일 때만 붙인다 — update 는 모델이 비대화로 돌리는 경로가 있다.
|
|
60
60
|
{
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
flags,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
})
|
|
67
|
-
for (const line of lines) console.log(line)
|
|
61
|
+
const interactive = !!process.stdin.isTTY && !flags.yes
|
|
62
|
+
for (const step of [runClaudeMdStep, runCodexMdStep]) {
|
|
63
|
+
const { lines } = await step({ mode: 'update', flags, interactive, confirmFn: confirm })
|
|
64
|
+
for (const line of lines) console.log(line)
|
|
65
|
+
}
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
let serverManifest
|
|
@@ -21,6 +21,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
21
21
|
|
|
22
22
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
23
23
|
export const DEFAULT_TEMPLATE_PATH = join(__dirname, '..', '..', 'templates', 'claude-md', 'rookie-block.md')
|
|
24
|
+
export const CODEX_TEMPLATE_PATH = join(__dirname, '..', '..', 'templates', 'codex-md', 'rookie-block.md')
|
|
24
25
|
|
|
25
26
|
const BEGIN_RE = /<!-- rookie:begin v(\d+) -->/g
|
|
26
27
|
const END_MARK = '<!-- rookie:end -->'
|
|
@@ -58,28 +59,61 @@ export function upsertRookieBlock(content, blockText) {
|
|
|
58
59
|
return content + sep + blockText + '\n'
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
//
|
|
62
|
+
// 대상 서술자 — 어느 폴더의 어느 파일을 관리하는가(spec 2026-09-29 codex-agents-md §3).
|
|
63
|
+
// candidates: 앞에서부터 먼저 존재하는 파일이 Codex/Claude 가 실제로 읽는 파일이다.
|
|
64
|
+
export function claudeTarget({ home = homedir() } = {}) {
|
|
65
|
+
return {
|
|
66
|
+
dir: join(home, '.claude'),
|
|
67
|
+
candidates: ['CLAUDE.md'],
|
|
68
|
+
createName: 'CLAUDE.md',
|
|
69
|
+
needles: [SAME_INTENT_NEEDLE],
|
|
70
|
+
noDirAction: 'no_claude_dir',
|
|
71
|
+
templatePath: DEFAULT_TEMPLATE_PATH,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Codex 는 $CODEX_HOME(없으면 ~/.codex)의 AGENTS.override.md 가 있으면 그것만 읽고 AGENTS.md 는 무시한다
|
|
76
|
+
// (2026-09-29 codex 0.153.4 바이너리 strings + codex exec 실측).
|
|
77
|
+
export function codexTarget({ home = homedir(), env = process.env } = {}) {
|
|
78
|
+
return {
|
|
79
|
+
dir: env.CODEX_HOME ? env.CODEX_HOME : join(home, '.codex'),
|
|
80
|
+
candidates: ['AGENTS.override.md', 'AGENTS.md'],
|
|
81
|
+
createName: 'AGENTS.md',
|
|
82
|
+
needles: [SAME_INTENT_NEEDLE, 'rookie_recall'],
|
|
83
|
+
noDirAction: 'no_codex_dir',
|
|
84
|
+
templatePath: CODEX_TEMPLATE_PATH,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function pickTargetFile(dir, candidates) {
|
|
89
|
+
for (const name of candidates) {
|
|
90
|
+
const p = join(dir, name)
|
|
91
|
+
try { return { path: p, lst: lstatSync(p) } } catch { /* 다음 후보 */ }
|
|
92
|
+
}
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 무엇을 할지 정한다(쓰지 않는다). force = --claude-md / --codex-md.
|
|
62
97
|
// action: create | append | replace | noop | noop_edited | skip_same_intent |
|
|
63
|
-
// skip_symlink_unmanaged | skip_broken_symlink | skip_newer | malformed | no_claude_dir
|
|
64
|
-
export function
|
|
65
|
-
const tpl = template ?? readTemplateBlock()
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const isSymlink = lst.isSymbolicLink()
|
|
98
|
+
// skip_symlink_unmanaged | skip_broken_symlink | skip_newer | malformed | no_claude_dir | no_codex_dir
|
|
99
|
+
export function planManagedMd({ target, force = false, template } = {}) {
|
|
100
|
+
const tpl = template ?? readTemplateBlock(target.templatePath)
|
|
101
|
+
const createPath = join(target.dir, target.createName)
|
|
102
|
+
const base = { path: createPath, target: createPath, isSymlink: false, template: tpl, needles: target.needles }
|
|
103
|
+
if (!existsSync(target.dir)) return { ...base, action: target.noDirAction }
|
|
104
|
+
|
|
105
|
+
const found = pickTargetFile(target.dir, target.candidates)
|
|
106
|
+
if (!found) return { ...base, action: 'create' }
|
|
107
|
+
|
|
108
|
+
const path = found.path
|
|
109
|
+
let real = path
|
|
110
|
+
const isSymlink = found.lst.isSymbolicLink()
|
|
77
111
|
if (isSymlink) {
|
|
78
|
-
try {
|
|
112
|
+
try { real = realpathSync(path) } catch { return { ...base, path, target: path, isSymlink, action: 'skip_broken_symlink' } }
|
|
79
113
|
}
|
|
80
|
-
const content = readFileSync(
|
|
114
|
+
const content = readFileSync(real, 'utf8')
|
|
81
115
|
const p = parseRookieBlock(content)
|
|
82
|
-
const ctx = { ...base, target, isSymlink, content, existing: p }
|
|
116
|
+
const ctx = { ...base, path, target: real, isSymlink, content, existing: p }
|
|
83
117
|
|
|
84
118
|
if (p.state === 'malformed') return { ...ctx, action: 'malformed' }
|
|
85
119
|
if (p.state === 'ok') {
|
|
@@ -90,10 +124,18 @@ export function planClaudeMd({ home = homedir(), force = false, template } = {})
|
|
|
90
124
|
}
|
|
91
125
|
// 표식 없음
|
|
92
126
|
if (isSymlink && !force) return { ...ctx, action: 'skip_symlink_unmanaged' }
|
|
93
|
-
if (content.includes(
|
|
127
|
+
if (target.needles.some((n) => content.includes(n)) && !force) return { ...ctx, action: 'skip_same_intent' }
|
|
94
128
|
return { ...ctx, action: 'append' }
|
|
95
129
|
}
|
|
96
130
|
|
|
131
|
+
export function planClaudeMd({ home = homedir(), force = false, template } = {}) {
|
|
132
|
+
return planManagedMd({ target: claudeTarget({ home }), force, template })
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function planCodexMd({ home = homedir(), env = process.env, force = false, template } = {}) {
|
|
136
|
+
return planManagedMd({ target: codexTarget({ home, env }), force, template })
|
|
137
|
+
}
|
|
138
|
+
|
|
97
139
|
export const WRITING_ACTIONS = new Set(['create', 'append', 'replace'])
|
|
98
140
|
|
|
99
141
|
// plan 대로 쓴다. 대상 파일 폴더에 임시 파일을 만들고 rename — 심링크 경로 자체는 건드리지 않는다.
|
|
@@ -121,9 +163,29 @@ export function applyClaudeMd(plan) {
|
|
|
121
163
|
return { written: true, action: plan.action, target, backup: mode !== null ? `${target}.rookie-bak` : null }
|
|
122
164
|
}
|
|
123
165
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
166
|
+
// 대상별 문구·플래그. CLAUDE.md 문구는 0.4.38 과 글자 그대로다(테스트가 고정한다).
|
|
167
|
+
export const KINDS = {
|
|
168
|
+
claude: {
|
|
169
|
+
label: () => '글로벌 CLAUDE.md',
|
|
170
|
+
forceFlag: 'claude-md',
|
|
171
|
+
optOutFlag: 'no-claude-md',
|
|
172
|
+
optOutEnv: 'ROOKIE_NO_CLAUDE_MD',
|
|
173
|
+
plan: ({ home, force, template }) => planClaudeMd({ home, force, template }),
|
|
174
|
+
},
|
|
175
|
+
codex: {
|
|
176
|
+
// Codex 는 override 가 있으면 그 파일을 읽으므로 실제 파일 이름을 그대로 보인다.
|
|
177
|
+
label: (plan) => `Codex ${plan ? basename(plan.path) : 'AGENTS.md'}`,
|
|
178
|
+
forceFlag: 'codex-md',
|
|
179
|
+
optOutFlag: 'no-codex-md',
|
|
180
|
+
optOutEnv: 'ROOKIE_NO_CODEX_MD',
|
|
181
|
+
plan: ({ home, env, force, template }) => planCodexMd({ home, env, force, template }),
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 명령(init·login·update·codex-setup)이 공유하는 한 단계. 출력 줄 목록을 돌려준다(테스트 가능하게).
|
|
186
|
+
// mode 'connect'(login·init·codex-setup): 기본 적용. 'update': 표식이 있을 때만 갱신, 없으면 대화형 확인 또는 강제 플래그.
|
|
187
|
+
export async function runManagedMdStep({
|
|
188
|
+
kind = 'claude',
|
|
127
189
|
mode,
|
|
128
190
|
flags = {},
|
|
129
191
|
env = process.env,
|
|
@@ -132,40 +194,44 @@ export async function runClaudeMdStep({
|
|
|
132
194
|
home = homedir(),
|
|
133
195
|
template,
|
|
134
196
|
} = {}) {
|
|
197
|
+
const K = KINDS[kind]
|
|
135
198
|
const lines = []
|
|
136
|
-
if (flags[
|
|
199
|
+
if (flags[K.optOutFlag] || env[K.optOutEnv] === '1') return { lines, result: { written: false, action: 'opted_out' } }
|
|
137
200
|
|
|
138
|
-
const force = !!flags[
|
|
201
|
+
const force = !!flags[K.forceFlag]
|
|
202
|
+
const hintCmd = `rookie update --${K.forceFlag}`
|
|
139
203
|
let plan
|
|
140
204
|
try {
|
|
141
|
-
plan =
|
|
205
|
+
plan = K.plan({ home, env, force, template })
|
|
142
206
|
} catch (e) {
|
|
143
|
-
lines.push(`⚠
|
|
207
|
+
lines.push(`⚠ ${K.label()} 점검 실패: ${e.message}`)
|
|
144
208
|
return { lines, result: { written: false, action: 'error' } }
|
|
145
209
|
}
|
|
210
|
+
const label = K.label(plan)
|
|
146
211
|
const where = plan.isSymlink ? `${plan.path} → ${plan.target}` : plan.path
|
|
147
212
|
|
|
148
213
|
switch (plan.action) {
|
|
149
214
|
case 'no_claude_dir':
|
|
215
|
+
case 'no_codex_dir':
|
|
150
216
|
case 'noop':
|
|
151
217
|
return { lines, result: { written: false, action: plan.action } }
|
|
152
218
|
case 'noop_edited':
|
|
153
|
-
lines.push(`
|
|
219
|
+
lines.push(` ${label} 의 더루키 블록이 수정돼 있어 그대로 둡니다 — 템플릿으로 되돌리려면 ${hintCmd}`)
|
|
154
220
|
return { lines, result: { written: false, action: plan.action } }
|
|
155
221
|
case 'skip_same_intent':
|
|
156
|
-
lines.push(`
|
|
222
|
+
lines.push(` ${label} 에 더루키 회상 규칙이 이미 있어 블록을 넣지 않았습니다 — 관리 블록으로 두려면 --${K.forceFlag}`)
|
|
157
223
|
return { lines, result: { written: false, action: plan.action } }
|
|
158
224
|
case 'skip_symlink_unmanaged':
|
|
159
|
-
lines.push(`
|
|
225
|
+
lines.push(` ${label} 가 심링크라 블록을 넣지 않았습니다(${where}) — 넣으려면 --${K.forceFlag}`)
|
|
160
226
|
return { lines, result: { written: false, action: plan.action } }
|
|
161
227
|
case 'skip_broken_symlink':
|
|
162
|
-
lines.push(`⚠
|
|
228
|
+
lines.push(`⚠ ${label} 심링크의 대상이 없습니다(${plan.path}) — 건드리지 않았습니다`)
|
|
163
229
|
return { lines, result: { written: false, action: plan.action } }
|
|
164
230
|
case 'skip_newer':
|
|
165
|
-
lines.push(`
|
|
231
|
+
lines.push(` ${label} 의 더루키 블록(v${plan.existing.version})이 이 CLI(v${plan.template.version})보다 새 버전이라 그대로 둡니다`)
|
|
166
232
|
return { lines, result: { written: false, action: plan.action } }
|
|
167
233
|
case 'malformed':
|
|
168
|
-
lines.push(`⚠
|
|
234
|
+
lines.push(`⚠ ${label} 의 더루키 블록 표식이 손상돼 건드리지 않았습니다 — 수동 점검: ${plan.target}`)
|
|
169
235
|
return { lines, result: { written: false, action: plan.action } }
|
|
170
236
|
default:
|
|
171
237
|
break
|
|
@@ -175,18 +241,18 @@ export async function runClaudeMdStep({
|
|
|
175
241
|
const isNewBlock = plan.action === 'create' || plan.action === 'append'
|
|
176
242
|
if (mode === 'update' && isNewBlock && !force) {
|
|
177
243
|
if (!interactive) {
|
|
178
|
-
lines.push(`
|
|
244
|
+
lines.push(` ${label} 에 더루키 블록이 없습니다 — 넣으려면 ${hintCmd} (rookie doctor 로 확인)`)
|
|
179
245
|
return { lines, result: { written: false, action: 'needs_consent' } }
|
|
180
246
|
}
|
|
181
|
-
console.log(describePlan(plan))
|
|
182
|
-
if (!(await confirmFn(
|
|
183
|
-
lines.push(
|
|
247
|
+
console.log(describePlan(plan, label))
|
|
248
|
+
if (!(await confirmFn(`${label} 에 이 블록을 넣을까요?`, { defaultYes: false }))) {
|
|
249
|
+
lines.push(` ${label} 블록 추가를 건너뛰었습니다`)
|
|
184
250
|
return { lines, result: { written: false, action: 'declined' } }
|
|
185
251
|
}
|
|
186
252
|
} else if (mode === 'connect' && isNewBlock && interactive) {
|
|
187
|
-
console.log(describePlan(plan))
|
|
188
|
-
if (!(await confirmFn(
|
|
189
|
-
lines.push(
|
|
253
|
+
console.log(describePlan(plan, label))
|
|
254
|
+
if (!(await confirmFn(`${label} 에 이 블록을 넣을까요?`, { defaultYes: true }))) {
|
|
255
|
+
lines.push(` ${label} 블록 추가를 건너뛰었습니다 (나중에: ${hintCmd})`)
|
|
190
256
|
return { lines, result: { written: false, action: 'declined' } }
|
|
191
257
|
}
|
|
192
258
|
}
|
|
@@ -195,49 +261,69 @@ export async function runClaudeMdStep({
|
|
|
195
261
|
try {
|
|
196
262
|
result = applyClaudeMd(plan)
|
|
197
263
|
} catch (e) {
|
|
198
|
-
lines.push(`⚠
|
|
264
|
+
lines.push(`⚠ ${label} 쓰기 실패(원본 보존): ${e.message}`)
|
|
199
265
|
return { lines, result: { written: false, action: 'error' } }
|
|
200
266
|
}
|
|
201
267
|
if (result.written) {
|
|
202
268
|
const verb = plan.action === 'replace' ? `갱신(v${plan.existing.version} → v${plan.template.version})` : `추가(v${plan.template.version})`
|
|
203
|
-
lines.push(`✓
|
|
269
|
+
lines.push(`✓ ${label} 더루키 블록 ${verb}: ${where}`)
|
|
204
270
|
if (result.backup) lines.push(` · 이전 본문 backup → ${result.backup}`)
|
|
205
|
-
if (mode === 'connect' && !interactive && isNewBlock) lines.push(
|
|
271
|
+
if (mode === 'connect' && !interactive && isNewBlock) lines.push(` (넣지 않으려면 표식 사이를 지우고 ${K.optOutEnv}=1 로 두세요)`)
|
|
206
272
|
}
|
|
207
273
|
return { lines, result }
|
|
208
274
|
}
|
|
209
275
|
|
|
276
|
+
export async function runClaudeMdStep(opts = {}) {
|
|
277
|
+
return runManagedMdStep({ ...opts, kind: 'claude' })
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function runCodexMdStep(opts = {}) {
|
|
281
|
+
return runManagedMdStep({ ...opts, kind: 'codex' })
|
|
282
|
+
}
|
|
283
|
+
|
|
210
284
|
// 쓰기 전에 보여 줄 설명.
|
|
211
|
-
export function describePlan(plan) {
|
|
285
|
+
export function describePlan(plan, label = '글로벌 CLAUDE.md') {
|
|
212
286
|
const where = plan.isSymlink ? `${plan.path} → ${plan.target}` : plan.path
|
|
213
287
|
const what = plan.action === 'create' ? '새 파일로 만들고' : plan.action === 'append' ? '파일 끝에' : '기존 블록을 바꿔'
|
|
214
288
|
return [
|
|
215
|
-
`\n
|
|
289
|
+
`\n${label} 에 더루키 관리 블록을 둡니다 — ${where}`,
|
|
216
290
|
` ${what} 아래 블록을 넣습니다(표식 밖의 글은 건드리지 않습니다):`,
|
|
217
291
|
...plan.template.text.split('\n').map((l) => ` | ${l}`),
|
|
218
292
|
].join('\n')
|
|
219
293
|
}
|
|
220
294
|
|
|
221
295
|
// doctor 용 한 줄(읽기만).
|
|
222
|
-
export function
|
|
296
|
+
export function managedMdStatusLine({ kind = 'claude', home = homedir(), env = process.env, template } = {}) {
|
|
297
|
+
const K = KINDS[kind]
|
|
298
|
+
const hintCmd = `rookie update --${K.forceFlag}`
|
|
223
299
|
let plan
|
|
224
|
-
try { plan =
|
|
300
|
+
try { plan = K.plan({ home, env, force: false, template }) } catch (e) { return `⚠ ${K.label()} 점검 실패: ${e.message}` }
|
|
301
|
+
const label = K.label(plan)
|
|
225
302
|
const v = plan.template.version
|
|
226
303
|
switch (plan.action) {
|
|
227
|
-
case 'no_claude_dir':
|
|
228
|
-
case '
|
|
229
|
-
case '
|
|
230
|
-
case '
|
|
231
|
-
case '
|
|
232
|
-
case '
|
|
304
|
+
case 'no_claude_dir':
|
|
305
|
+
case 'no_codex_dir': return null
|
|
306
|
+
case 'noop': return `✓ ${label} 더루키 블록 v${v}`
|
|
307
|
+
case 'replace': return `⚠ ${label} 더루키 블록 v${plan.existing.version} → v${v} — rookie update`
|
|
308
|
+
case 'noop_edited': return `⚠ ${label} 더루키 블록이 수정됨 — 되돌리려면 ${hintCmd}`
|
|
309
|
+
case 'skip_newer': return `✓ ${label} 더루키 블록 v${plan.existing.version} (이 CLI 보다 새 버전)`
|
|
310
|
+
case 'skip_same_intent': return `✓ ${label} 에 더루키 회상 규칙 있음(관리 블록 아님)`
|
|
233
311
|
case 'skip_symlink_unmanaged':
|
|
234
|
-
return plan.content.includes(
|
|
235
|
-
?
|
|
236
|
-
:
|
|
237
|
-
case 'skip_broken_symlink': return `⚠
|
|
238
|
-
case 'malformed': return `✗
|
|
239
|
-
case 'create': return
|
|
240
|
-
case 'append': return
|
|
312
|
+
return plan.needles.some((n) => plan.content.includes(n))
|
|
313
|
+
? `✓ ${label}(심링크)에 더루키 회상 규칙 있음(관리 블록 아님)`
|
|
314
|
+
: `⚠ ${label}(심링크)에 더루키 블록 없음 — ${hintCmd}`
|
|
315
|
+
case 'skip_broken_symlink': return `⚠ ${label} 심링크 대상 없음 — ${plan.path}`
|
|
316
|
+
case 'malformed': return `✗ ${label} 더루키 블록 손상(begin/end 불일치) — 수동 점검: ${plan.target}`
|
|
317
|
+
case 'create': return `⚠ ${label} 없음 — ${hintCmd}`
|
|
318
|
+
case 'append': return `⚠ ${label} 에 더루키 블록 없음 — ${hintCmd}`
|
|
241
319
|
default: return null
|
|
242
320
|
}
|
|
243
321
|
}
|
|
322
|
+
|
|
323
|
+
export function claudeMdStatusLine({ home = homedir(), template } = {}) {
|
|
324
|
+
return managedMdStatusLine({ kind: 'claude', home, template })
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function codexMdStatusLine({ home = homedir(), env = process.env, template } = {}) {
|
|
328
|
+
return managedMdStatusLine({ kind: 'codex', home, env, template })
|
|
329
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<!-- rookie:begin v1 -->
|
|
2
|
+
## 더루키(the Rookie) 기억 — 로컬보다 먼저
|
|
3
|
+
- 전에 한 일·결정·맥락이 필요하면(어제·전에·이어서 같은 말이 없어도) 로컬 메모·Codex 자체 기억을 뒤지기 **전에** 더루키 회상을 먼저 부른다 — rookie MCP 도구 `rookie_recall` 이 있으면 그것을, 없으면 셸에서 `GET /api/rookie/recall`(rookie CLI 설정의 endpoint·토큰). 로컬 기억은 보조다.
|
|
4
|
+
- 새로 기억할 것(운영 사실·결정·규칙·취향)은 **더루키에 먼저** 저장한다 — `rookie_save` 도구 또는 셸의 `rookie save`. 로컬 메모는 필요할 때만 보조로 남긴다.
|
|
5
|
+
- 예외: 작업 폴더에 `.rookie-ignore` 가 있거나, `ROOKIE_DISABLED=1` 이거나, `~/.rookie/config.json` 이 없으면 위 두 줄을 따르지 않는다.
|
|
6
|
+
- 세부 절차는 세션 시작 주입(더루키 hook)이 정본이다 — 여기에 옮겨 적지 않는다.
|
|
7
|
+
<!-- rookie:end -->
|
package/version.json
CHANGED