priorcase 0.1.0
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/LICENSE +62 -0
- package/README.md +929 -0
- package/THIRD-PARTY-NOTICES.md +33 -0
- package/bin/prior.js +66 -0
- package/package.json +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
priorcase — End User License Agreement
|
|
2
|
+
Copyright (c) 2026 priorcase. All rights reserved.
|
|
3
|
+
|
|
4
|
+
This is proprietary software. It is licensed, not sold.
|
|
5
|
+
|
|
6
|
+
1. GRANT OF LICENSE
|
|
7
|
+
|
|
8
|
+
Subject to the terms below, you are granted a non-exclusive,
|
|
9
|
+
non-transferable, revocable license to install and use the priorcase
|
|
10
|
+
binary ("the Software") on computers you own or control, for your own
|
|
11
|
+
personal or internal business purposes.
|
|
12
|
+
|
|
13
|
+
2. WHAT YOU MAY DO
|
|
14
|
+
|
|
15
|
+
a. Install and run the Software on any number of machines you own or
|
|
16
|
+
control.
|
|
17
|
+
b. Use the Software to create, read, and modify your own decision
|
|
18
|
+
records. **The files the Software writes are yours.** They are plain
|
|
19
|
+
Markdown on your disk, they contain no license restriction, and this
|
|
20
|
+
agreement places no claim on them. You may keep, publish, migrate,
|
|
21
|
+
or delete them freely, including after this license ends.
|
|
22
|
+
|
|
23
|
+
3. WHAT YOU MAY NOT DO
|
|
24
|
+
|
|
25
|
+
a. Reverse engineer, decompile, or disassemble the Software, except to
|
|
26
|
+
the extent that applicable law expressly permits despite this
|
|
27
|
+
limitation.
|
|
28
|
+
b. Redistribute, sublicense, rent, lease, or sell the Software.
|
|
29
|
+
c. Remove or alter any copyright, trademark, or other proprietary
|
|
30
|
+
notices.
|
|
31
|
+
d. Use the Software to build a competing product.
|
|
32
|
+
|
|
33
|
+
4. NO NETWORK, NO TELEMETRY
|
|
34
|
+
|
|
35
|
+
The Software does not transmit your decision records, prompts, or
|
|
36
|
+
conversations anywhere. It runs entirely on your machine and reads no
|
|
37
|
+
API keys. Any future networked feature will be separate, opt-in, and
|
|
38
|
+
covered by its own agreement.
|
|
39
|
+
|
|
40
|
+
5. NO WARRANTY
|
|
41
|
+
|
|
42
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
43
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
45
|
+
|
|
46
|
+
6. LIMITATION OF LIABILITY
|
|
47
|
+
|
|
48
|
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES,
|
|
49
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
50
|
+
ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
51
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
52
|
+
|
|
53
|
+
7. THIRD-PARTY COMPONENTS
|
|
54
|
+
|
|
55
|
+
The Software includes open-source components under their own licenses.
|
|
56
|
+
See THIRD-PARTY-NOTICES.md.
|
|
57
|
+
|
|
58
|
+
8. TERMINATION
|
|
59
|
+
|
|
60
|
+
This license ends if you breach it. On termination you must stop using
|
|
61
|
+
the Software and delete your copies. **Your decision records are not
|
|
62
|
+
affected** — see section 2(b).
|
package/README.md
ADDED
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
# priorcase
|
|
2
|
+
|
|
3
|
+
에이전트가 내린 결정을 사람이 시키지 않아도 남기고, 다음 판단 시점에 알아서 꺼내 주는 층이다.
|
|
4
|
+
마크다운 + frontmatter 로 볼트에 쌓이는 결정 노트를 만들고(`capture`), 색인을 갱신하고
|
|
5
|
+
(`index`), 관련 있는 과거 결정을 찾아 오고(`recall`), 결과가 나오면 회고를 붙인다(`review`).
|
|
6
|
+
런타임 의존이 없는 Go 단일 정적 바이너리 `prior` 하나로 전부 한다.
|
|
7
|
+
|
|
8
|
+
## 설치
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install -g priorcase
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`prior` 가 PATH 에 들어가고, 훅·CLI·MCP 가 전부 그 하나로 돈다.
|
|
15
|
+
|
|
16
|
+
**MCP 만 쓸 거면 설치도 필요 없다.**
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{ "mcpServers": { "priorcase": { "command": "npx", "args": ["-y", "priorcase", "mcp"] } } }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**팀이면 레포에 묶어라.** 그래야 팀원 전원이 같은 판을 쓴다 — 공유 볼트에 서로 다른
|
|
23
|
+
판이 쓰면 갱신이 막힌다(아래 [스키마 판](#스키마-판) 참고).
|
|
24
|
+
|
|
25
|
+
```jsonc
|
|
26
|
+
// package.json
|
|
27
|
+
"devDependencies": { "priorcase": "1.2.3" }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
darwin/linux × arm64/x64. npm 이 자기 플랫폼 바이너리만 받는다 —
|
|
31
|
+
실행되는 것은 정적 Go 바이너리이고 **실행 시점 런타임 의존은 0 이다.**
|
|
32
|
+
(Node 는 설치 경로일 뿐 실행에 관여하지 않는다.)
|
|
33
|
+
|
|
34
|
+
> **⚠️ 아직 게시 전이다.** 첫 릴리스 태그를 밀어야 위 명령이 동작한다.
|
|
35
|
+
|
|
36
|
+
### 배선
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
prior doctor # 지금 상태를 진단한다
|
|
40
|
+
prior init # 훅 배선 계획을 보여 준다 (파일을 안 고친다)
|
|
41
|
+
prior init --apply # 실제로 배선한다
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**`prior` 가 PATH 에 있는지 확인하라.** 훅은 절대 경로로 배선되므로, PATH 에 없으면
|
|
45
|
+
**시스템은 멀쩡히 도는데 사람만 명령을 못 친다.** `prior doctor` 가 이걸 검사한다.
|
|
46
|
+
|
|
47
|
+
> **`npx` 로는 훅이 안 된다.** 훅은 `settings.json` 에 절대 경로가 박히고
|
|
48
|
+
> `user-prompt-submit` 은 매 프롬프트마다 도는데, npx 해석 지연이 거기 얹히면
|
|
49
|
+
> 대화가 느려진다. 훅을 쓰려면 `npm install -g` 로 깔아라.
|
|
50
|
+
|
|
51
|
+
### macOS 서명
|
|
52
|
+
|
|
53
|
+
**서명·공증하지 않는다. 그래도 막히지 않는다.**
|
|
54
|
+
|
|
55
|
+
macOS 의 `com.apple.quarantine` 딱지는 **다운로드한 프로그램이** 붙인다.
|
|
56
|
+
`npm`·`curl` 은 안 붙이고 브라우저는 붙인다. npm 으로 깔면 딱지가 없으므로
|
|
57
|
+
Gatekeeper 를 만나지 않는다.
|
|
58
|
+
|
|
59
|
+
브라우저로 tar.gz 를 직접 받았다면 그때만 한 줄이 필요하다:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
xattr -dr com.apple.quarantine ./prior
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Apple Developer 계정은 이 셋 중 하나가 될 때 든다 — 브라우저 다운로드를 주 경로로
|
|
66
|
+
삼을 때, `.app`·`.pkg` 를 낼 때, B2B 고객이 보안 심사에서 서명을 요구할 때.
|
|
67
|
+
|
|
68
|
+
### 라이선스
|
|
69
|
+
|
|
70
|
+
독점 소프트웨어다 ([LICENSE](LICENSE)). **다만 priorcase 가 쓰는 파일은 당신 것이다** —
|
|
71
|
+
평문 마크다운이고, 라이선스가 끝나도 그대로 남는다. 포함된 오픈소스 구성요소는
|
|
72
|
+
[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) 를 보라.
|
|
73
|
+
|
|
74
|
+
### 스키마 판
|
|
75
|
+
|
|
76
|
+
결정 노트에는 `schema` 판이 붙는다 (판 1 은 생략한다).
|
|
77
|
+
|
|
78
|
+
**팀이 볼트를 공유하면 한 명이 먼저 올린 상태가 정상이다.** 그때 옛 `prior` 는
|
|
79
|
+
그 사람의 노트를 **읽고 회수하지만 고치지는 않는다** — 모르는 규칙으로 쓰인 것을
|
|
80
|
+
우리 규칙으로 되쓰면 조용히 망가뜨리기 때문이다. `prior doctor` 가 그걸 경고로 알린다.
|
|
81
|
+
|
|
82
|
+
## 현재 상태
|
|
83
|
+
|
|
84
|
+
**v1 구현 완료.** 서브커맨드 열 개가 동작한다 — `capture` `recall` `review` `index`
|
|
85
|
+
`rollup` `doctor` `mcp` `watch` `hook` `init`.
|
|
86
|
+
|
|
87
|
+
다른 호스트 파서와 임베딩 검색은 v2 다.
|
|
88
|
+
|
|
89
|
+
## 설정
|
|
90
|
+
|
|
91
|
+
설정 파일 경로는 이 순서로 정해진다.
|
|
92
|
+
|
|
93
|
+
1. `--config <경로>` 플래그
|
|
94
|
+
2. `PRIORCASE_CONFIG` 환경변수
|
|
95
|
+
3. `$XDG_CONFIG_HOME/priorcase/config.toml` (보통 `~/.config/priorcase/config.toml`)
|
|
96
|
+
|
|
97
|
+
플래그를 붙일 수 없는 자리(훅·데몬 어댑터)가 있어서 환경변수 통로가 따로 있다.
|
|
98
|
+
셋 다 없으면 3번 경로를 열려다 실패한다 (실제 실행 결과 — 기본 경로를 명확히 보이려고
|
|
99
|
+
`XDG_CONFIG_HOME` 을 빈 디렉토리로 지정했다):
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
$ XDG_CONFIG_HOME=/tmp/nonexistent-xdg prior index
|
|
103
|
+
prior: 설정 파일을 열 수 없다 (/tmp/nonexistent-xdg/priorcase/config.toml): open /tmp/nonexistent-xdg/priorcase/config.toml: no such file or directory
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`PRIORCASE_VAULT` 는 별개다 — 설정 파일의 `vault` 값만 덮어쓴다 (테스트 볼트 격리용).
|
|
107
|
+
|
|
108
|
+
### `default_domain` 을 비우지 마라
|
|
109
|
+
|
|
110
|
+
작업 디렉토리가 어느 `[[domain]]` 의 `paths` 에도 안 걸리면 여기 적힌 도메인으로
|
|
111
|
+
기록된다. **비우면 그런 자리에서는 아무것도 기록되지 않는데, 겉으로는 조용하다** —
|
|
112
|
+
훅은 돌고 안전망은 표시까지 하다가 마지막에 막힌다. `prior init` 이 만드는 설정에는
|
|
113
|
+
`common` 이 들어 있다.
|
|
114
|
+
|
|
115
|
+
### `lang` 은 볼트에 남는 문자열만 정한다
|
|
116
|
+
|
|
117
|
+
색인 머리말, 회수 주입 라벨 같은 것이다. **결정 노트의 본문 언어는 여기가 정하지
|
|
118
|
+
않는다** — 판별기가 대화의 언어를 따라가므로, 한 볼트에 영어 대화와 한국어 대화가
|
|
119
|
+
섞여도 각 노트가 제 언어를 갖는다. CLI 진단 출력은 이 설정의 범위 밖이다.
|
|
120
|
+
|
|
121
|
+
```toml
|
|
122
|
+
vault = "~/Documents/Obsidian Vault"
|
|
123
|
+
lang = "ko" # 볼트에 남는 문자열의 언어 (ko | en)
|
|
124
|
+
|
|
125
|
+
# exclude·default_domain 은 top-level 키이므로 [[domain]] 보다 앞에 둔다.
|
|
126
|
+
# TOML 의 테이블 스코프 규칙상 테이블 헤더([[domain]]) 뒤에 오는 bare key 는
|
|
127
|
+
# 그 테이블(마지막 domain)의 필드로 읽힌다 — 여기 두면 top-level 이 아니라
|
|
128
|
+
# 도메인 항목의 필드가 되어 버린다.
|
|
129
|
+
exclude = ["/home/t/project/scratch"]
|
|
130
|
+
|
|
131
|
+
# 어느 [[domain]] 의 paths 에도 안 걸릴 때 쓸 도메인.
|
|
132
|
+
# 비우면 그런 자리에서는 아무것도 기록되지 않는다.
|
|
133
|
+
default_domain = "common"
|
|
134
|
+
|
|
135
|
+
# [naming] 은 필수다. rollup 만 선택이다 (없으면 prior rollup 이 무엇을 적으라고 알려 준다).
|
|
136
|
+
[naming]
|
|
137
|
+
decision_file = "{domain}-결정-{slug}-{date}.md"
|
|
138
|
+
decisions_dir = "{project}/decisions"
|
|
139
|
+
worklog = "99-{project}-작업-로그.md"
|
|
140
|
+
index = "_meta/00-결정-색인.md"
|
|
141
|
+
rollup = "98-{project}-작업-로그-요약.md"
|
|
142
|
+
|
|
143
|
+
# 데몬(prior watch)과 훅이 쓴다.
|
|
144
|
+
[capture]
|
|
145
|
+
signals = ["결정", "선택"] # 판별기가 있으면 쓰이지 않는다
|
|
146
|
+
min_turns = 6
|
|
147
|
+
quiesce_seconds = 3
|
|
148
|
+
judge_path = "" # 비면 자동 탐색. 못 찾으면 자동 기록이 꺼진다
|
|
149
|
+
judge_model = "claude-haiku-4-5"
|
|
150
|
+
|
|
151
|
+
# 무소속 결정이 쌓이는 곳. default_domain 이 이걸 가리킨다.
|
|
152
|
+
[[domain]]
|
|
153
|
+
prefix = "common"
|
|
154
|
+
folder = "common"
|
|
155
|
+
|
|
156
|
+
[[domain]]
|
|
157
|
+
prefix = "work"
|
|
158
|
+
folder = "work"
|
|
159
|
+
paths = ["/home/t/project/work"]
|
|
160
|
+
|
|
161
|
+
[[domain]]
|
|
162
|
+
prefix = "shop"
|
|
163
|
+
folder = "Shop"
|
|
164
|
+
paths = ["/home/t/Documents/shop-automation"]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`paths` 는 "지금 이 디렉토리가 어느 도메인인가"를 판정하는 데 쓴다 — `prior recall` 이
|
|
168
|
+
cwd 도메인 결정에 가산점을 준다. `exclude` 는 그 판정에서 빼는 경로다.
|
|
169
|
+
`worklog` 는 아직 어느 명령도 쓰지 않는다 (작업 로그 기록은 미구현).
|
|
170
|
+
|
|
171
|
+
### `[naming]` 은 필수다
|
|
172
|
+
|
|
173
|
+
`[naming]` 절이 통째로 빠지거나 네 키 중 하나라도 비면 그 자리에서 죽는다.
|
|
174
|
+
예전에는 통과시켰는데, 그러면 `decisions_dir` 이 빈 문자열이라 결정 폴더가 볼트 루트가
|
|
175
|
+
되고 색인이 볼트 디렉터리를 덮어쓰려 든다 — 설정 오류가 엉뚱한 층에서 엉뚱한 메시지로
|
|
176
|
+
터진다. 설정의 함정은 설정을 읽는 자리에서 잡는다. `[naming]` 을 뺀 `no-naming.toml`:
|
|
177
|
+
|
|
178
|
+
```toml
|
|
179
|
+
vault = "./vault"
|
|
180
|
+
|
|
181
|
+
[[domain]]
|
|
182
|
+
prefix = "priorcase-demo"
|
|
183
|
+
folder = "priorcase-demo"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
$ prior --config no-naming.toml index
|
|
188
|
+
prior: [naming] 의 decision_file 항목이 비어 있다 — 설정에 [naming] 절이 통째로 빠졌는지 확인하라
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`decision_file` 에는 추가 제약이 있다. `{domain}` · `{slug}` · `{date}` 자리표시자가 다
|
|
192
|
+
있어야 하고, `{domain}` 이 `{slug}` 보다 앞이어야 하며 둘 사이에 **결정 표식**이 있어야
|
|
193
|
+
하고, `-{date}.md` 로 끝나야 한다. `decisions_dir` 에는 `{project}` 가 있어야 한다.
|
|
194
|
+
|
|
195
|
+
### `decision_file` 이 국제화 지점이다
|
|
196
|
+
|
|
197
|
+
`{domain}` 과 `{slug}` 사이의 문자열이 **결정 표식**이다. 기본 한국어 템플릿에서는
|
|
198
|
+
`-결정-` 이고, 이 값이 파일명 필터·접두어 추출·스키마 검증의 유일한 정본이다. 코드
|
|
199
|
+
어디에도 `-결정-` 리터럴이 없으므로 템플릿을 바꾸면 전부 따라 바뀐다.
|
|
200
|
+
|
|
201
|
+
```toml
|
|
202
|
+
vault = "./vault-en"
|
|
203
|
+
|
|
204
|
+
[naming]
|
|
205
|
+
decision_file = "{domain}-decision-{slug}-{date}.md"
|
|
206
|
+
decisions_dir = "{project}/decisions"
|
|
207
|
+
worklog = "99-{project}-worklog.md"
|
|
208
|
+
index = "decisions/INDEX.md"
|
|
209
|
+
|
|
210
|
+
[[domain]]
|
|
211
|
+
prefix = "demo"
|
|
212
|
+
folder = "demo"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
이 설정으로 실제로 돌린 결과다.
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
$ prior --config en-config.toml capture \
|
|
219
|
+
--domain demo --slug storage-format-markdown \
|
|
220
|
+
--summary "Store decision notes as markdown with YAML frontmatter" \
|
|
221
|
+
--tag storage --date 2026-08-07
|
|
222
|
+
기록됨: demo/decisions/demo-decision-storage-format-markdown-2026-08-07.md
|
|
223
|
+
|
|
224
|
+
$ prior --config en-config.toml index
|
|
225
|
+
색인 1행 생성
|
|
226
|
+
|
|
227
|
+
$ prior --config en-config.toml recall --format inject storage format markdown
|
|
228
|
+
[과거 결정 참조]
|
|
229
|
+
- 2026-08-07 Store decision notes as markdown with YAML frontmatter (active/pending) → demo/decisions/demo-decision-storage-format-markdown-2026-08-07.md
|
|
230
|
+
|
|
231
|
+
$ prior --config en-config.toml review demo-decision-storage-format-markdown-2026-08-07 --outcome good
|
|
232
|
+
갱신됨: demo-decision-storage-format-markdown-2026-08-07
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 오타 키는 조용히 넘어가지 않는다
|
|
236
|
+
|
|
237
|
+
`prior` 는 go-toml/v2 의 `DisallowUnknownFields()` 로 strict 하게 읽는다. `exclude` 를
|
|
238
|
+
`[[domain]]` 뒤에 둔 `bad-config.toml`:
|
|
239
|
+
|
|
240
|
+
```toml
|
|
241
|
+
vault = "./vault"
|
|
242
|
+
|
|
243
|
+
[naming]
|
|
244
|
+
decision_file = "{domain}-결정-{slug}-{date}.md"
|
|
245
|
+
decisions_dir = "{project}/decisions"
|
|
246
|
+
worklog = "99-{project}-작업-로그.md"
|
|
247
|
+
index = "decisions/INDEX.md"
|
|
248
|
+
|
|
249
|
+
[[domain]]
|
|
250
|
+
prefix = "priorcase-demo"
|
|
251
|
+
folder = "priorcase-demo"
|
|
252
|
+
|
|
253
|
+
exclude = ["/home/t/project/scratch"]
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
그 자리에서 바로 에러가 난다 (실제 실행 결과):
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
$ prior --config bad-config.toml index
|
|
260
|
+
prior: 설정에 알 수 없는 키가 있다 (bad-config.toml):
|
|
261
|
+
10| prefix = "priorcase-demo"
|
|
262
|
+
11| folder = "priorcase-demo"
|
|
263
|
+
12|
|
|
264
|
+
13| exclude = ["/home/t/project/scratch"]
|
|
265
|
+
| ~~~~~~~ unknown field
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## 사용 예
|
|
269
|
+
|
|
270
|
+
아래는 전부 실제로 `prior` 를 빌드해 빈 임시 디렉토리에서 돌려서 나온 출력이다.
|
|
271
|
+
순서대로 따라 하면 그대로 재현된다 — `--date` 를 명시했으므로 오늘 날짜와 무관하다.
|
|
272
|
+
|
|
273
|
+
먼저 작업 디렉토리에 `demo-config.toml` 을 만든다.
|
|
274
|
+
|
|
275
|
+
```toml
|
|
276
|
+
vault = "./vault"
|
|
277
|
+
|
|
278
|
+
[naming]
|
|
279
|
+
decision_file = "{domain}-결정-{slug}-{date}.md"
|
|
280
|
+
decisions_dir = "{project}/decisions"
|
|
281
|
+
worklog = "99-{project}-작업-로그.md"
|
|
282
|
+
index = "decisions/INDEX.md"
|
|
283
|
+
|
|
284
|
+
[[domain]]
|
|
285
|
+
prefix = "priorcase-demo"
|
|
286
|
+
folder = "priorcase-demo"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### `prior capture` — 결정을 기록한다
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
$ prior --config demo-config.toml capture \
|
|
293
|
+
--domain priorcase-demo \
|
|
294
|
+
--slug 저장포맷-마크다운 \
|
|
295
|
+
--summary "결정 노트는 프론트매터 있는 마크다운으로 저장한다" \
|
|
296
|
+
--tag 저장 --tag 포맷 \
|
|
297
|
+
--date 2026-08-07 \
|
|
298
|
+
--body - <<'EOF'
|
|
299
|
+
## 결정
|
|
300
|
+
결정 노트는 YAML 프론트매터 + 마크다운 본문으로 저장한다.
|
|
301
|
+
|
|
302
|
+
## 근거
|
|
303
|
+
사람이 grep/에디터로 바로 읽고 고칠 수 있어야 한다. DB는 그 자체로 회수 채널이 하나 더 필요해진다.
|
|
304
|
+
EOF
|
|
305
|
+
기록됨: priorcase-demo/decisions/priorcase-demo-결정-저장포맷-마크다운-2026-08-07.md
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
한 번 더 기록해 둔다 — 아래 `index`·`recall` 예시가 이 두 번째 결정을 근거로 한다.
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
$ prior --config demo-config.toml capture \
|
|
312
|
+
--domain priorcase-demo \
|
|
313
|
+
--slug 회수-키워드매칭 \
|
|
314
|
+
--summary "회수는 임베딩 대신 파일명 접두어 + 키워드 매칭으로 시작한다" \
|
|
315
|
+
--tag 회수 --tag 검색 \
|
|
316
|
+
--date 2026-08-07 \
|
|
317
|
+
--body - <<'EOF'
|
|
318
|
+
## 결정
|
|
319
|
+
회수는 임베딩 유사도 대신 파일명 접두어(domain) + 키워드 매칭으로 시작한다.
|
|
320
|
+
|
|
321
|
+
## 근거
|
|
322
|
+
임베딩은 인덱싱 파이프라인과 벡터 스토어가 필요해 CLI 단일 바이너리 원칙과 맞지 않는다.
|
|
323
|
+
키워드 매칭은 의존 없이 바로 동작하고, 결정 노트는 파일명과 태그가 이미 신호가 풍부하다.
|
|
324
|
+
EOF
|
|
325
|
+
기록됨: priorcase-demo/decisions/priorcase-demo-결정-회수-키워드매칭-2026-08-07.md
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### `prior index` — 색인을 재생성한다
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
$ prior --config demo-config.toml index
|
|
332
|
+
색인 2행 생성
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
`decisions/INDEX.md` (설정의 `naming.index`) 에 날짜 · domain · summary · status ·
|
|
336
|
+
outcome · 링크 표가 생긴다.
|
|
337
|
+
|
|
338
|
+
#### 읽지 못한 노트는 조용히 사라지지 않는다
|
|
339
|
+
|
|
340
|
+
frontmatter 가 없거나 스키마가 옛 것이라 파싱에 실패한 노트는 색인에서 빠진다.
|
|
341
|
+
한 건 때문에 색인 전체가 죽지 않게 하려는 것이지만, **빠졌다는 사실은 반드시
|
|
342
|
+
알린다** — 요약 줄에 건수가 박히고, 어느 파일이 왜인지는 stderr 로 나온다.
|
|
343
|
+
|
|
344
|
+
위 데모 볼트에 깨진 노트 두 건을 넣어 보자 — 하나는 다른 도구가 남긴 구 스키마,
|
|
345
|
+
하나는 프론트매터가 아예 없는 것이다.
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
$ prior --config demo-config.toml index
|
|
349
|
+
색인 2행 생성 (2건 건너뜀 — 색인이 불완전하다)
|
|
350
|
+
경고: 결정 노트 2건을 읽지 못해 건너뛰었다 — 색인·회수에서 빠진다:
|
|
351
|
+
- priorcase-demo/decisions/priorcase-demo-결정-구스키마-2026-08-05.md
|
|
352
|
+
frontmatter 파싱 실패: yaml: unmarshal errors:
|
|
353
|
+
line 1: field title not found in type store.Meta
|
|
354
|
+
line 2: field project not found in type store.Meta
|
|
355
|
+
line 3: field created not found in type store.Meta
|
|
356
|
+
- priorcase-demo/decisions/priorcase-demo-결정-머리말없음-2026-08-06.md
|
|
357
|
+
frontmatter 가 없다 (--- 로 시작하지 않는다)
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
종료 코드는 그래도 0 이다. 원인은 `prior` 가 고칠 수 있는 것이 아니라 볼트 데이터를
|
|
361
|
+
사람이 정본 10키로 옮겨야 하는 것이고, 훅·크론에서 도는 `prior index` 가 그때까지
|
|
362
|
+
매번 실패하면 무시하는 법만 학습시키기 때문이다. `prior capture` · `prior review` 도
|
|
363
|
+
내부적으로 색인을 다시 쓰므로 같은 경고를 낸다.
|
|
364
|
+
|
|
365
|
+
### `prior recall` — 관련 과거 결정을 찾는다
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
$ prior --config demo-config.toml recall --format inject 회수 키워드
|
|
369
|
+
[과거 결정 참조]
|
|
370
|
+
- 2026-08-07 회수는 임베딩 대신 파일명 접두어 + 키워드 매칭으로 시작한다 (active/pending) → priorcase-demo/decisions/priorcase-demo-결정-회수-키워드매칭-2026-08-07.md
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`--format inject` 는 훅·MCP 어댑터가 그대로 컨텍스트에 주입할 수 있는 형태다.
|
|
374
|
+
`--format human`(기본)은 점수와 stem 을 보여준다.
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
$ prior --config demo-config.toml recall 회수 키워드
|
|
378
|
+
8 priorcase-demo-결정-회수-키워드매칭-2026-08-07
|
|
379
|
+
회수는 임베딩 대신 파일명 접두어 + 키워드 매칭으로 시작한다
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`status: regretted` 이거나 `outcome: bad` 인 결정이 결과에 끼면 `--format inject` 출력
|
|
383
|
+
끝에 회고를 먼저 읽으라는 경고 줄이 붙는다.
|
|
384
|
+
|
|
385
|
+
회수 대상에서 읽기 실패로 빠진 노트가 있으면 `prior index` 와 같은 경고를 낸다.
|
|
386
|
+
포맷과 무관하게 **항상 stderr** 다 — `--format inject` 의 stdout 은 훅이 그대로
|
|
387
|
+
컨텍스트에 넣는 순수 데이터라 한 줄도 섞이면 안 된다.
|
|
388
|
+
|
|
389
|
+
### 유사 slug 는 거부된다
|
|
390
|
+
|
|
391
|
+
같은 결정이 두 노트로 갈라지면 회수가 둘 다 물어오고 어느 쪽이 정본인지 알 수 없게 된다.
|
|
392
|
+
하이픈 · 공백 · 밑줄 · 대소문자만 다른 slug 는 같은 결정으로 보고 막는다 (실제 실행 결과):
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
$ prior --config demo-config.toml capture \
|
|
396
|
+
--domain priorcase-demo --slug 회수_키워드매칭 \
|
|
397
|
+
--summary "같은 결정을 다시 쓰려 한다" --date 2026-08-07
|
|
398
|
+
prior: 유사한 결정이 이미 있다: "priorcase-demo-결정-회수-키워드매칭-2026-08-07" (하이픈·공백·밑줄·대소문자만 다르다). 뒤집는 결정이면 --supersedes 를 쓰고, 정말 다른 결정이면 slug 를 구별되게 바꿔라
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### `--supersedes` 는 양방향으로 엮는다
|
|
402
|
+
|
|
403
|
+
`prior capture --supersedes` 와 `prior review --supersedes` 가 같은 로직을 탄다. 새 노트의
|
|
404
|
+
`supersedes` 에 위키링크가 들어갈 뿐 아니라, **뒤집힌 옛 노트도 함께 갱신된다** —
|
|
405
|
+
`status` 가 `superseded` 가 되고 `related` 에 새 노트가 추가된다. 옛 노트가 `active` 로
|
|
406
|
+
남아 있으면 회수 감점이 안 걸려 이미 뒤집힌 결정이 계속 만점으로 올라온다.
|
|
407
|
+
|
|
408
|
+
```
|
|
409
|
+
$ prior --config demo-config.toml capture \
|
|
410
|
+
--domain priorcase-demo \
|
|
411
|
+
--slug 회수-임베딩전환 \
|
|
412
|
+
--summary "회수를 임베딩 유사도로 바꾼다" \
|
|
413
|
+
--supersedes priorcase-demo-결정-회수-키워드매칭-2026-08-07 \
|
|
414
|
+
--date 2026-08-08
|
|
415
|
+
기록됨: priorcase-demo/decisions/priorcase-demo-결정-회수-임베딩전환-2026-08-08.md
|
|
416
|
+
|
|
417
|
+
관련 과거 결정:
|
|
418
|
+
- 2026-08-07 회수는 임베딩 대신 파일명 접두어 + 키워드 매칭으로 시작한다
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
뒤집힌 옛 노트가 실제로 이렇게 바뀐다.
|
|
422
|
+
|
|
423
|
+
```
|
|
424
|
+
$ head -9 vault/priorcase-demo/decisions/priorcase-demo-결정-회수-키워드매칭-2026-08-07.md
|
|
425
|
+
---
|
|
426
|
+
type: decision
|
|
427
|
+
date: 2026-08-07
|
|
428
|
+
domain: [priorcase-demo]
|
|
429
|
+
summary: "회수는 임베딩 대신 파일명 접두어 + 키워드 매칭으로 시작한다"
|
|
430
|
+
status: superseded
|
|
431
|
+
outcome: pending
|
|
432
|
+
supersedes: ""
|
|
433
|
+
related: ["[[priorcase-demo-결정-회수-임베딩전환-2026-08-08]]"]
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
두 노트를 모두 검증한 뒤에야 쓰기가 시작된다. 새 노트가 스키마 검증에서 걸리면 옛 노트도
|
|
437
|
+
건드리지 않는다 — 반쪽짜리 연결이 디스크에 남지 않는다.
|
|
438
|
+
|
|
439
|
+
### `prior review` — 결과가 나온 결정에 outcome·회고를 붙인다
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
$ prior --config demo-config.toml review priorcase-demo-결정-저장포맷-마크다운-2026-08-07 \
|
|
443
|
+
--outcome good \
|
|
444
|
+
--retro "그대로 잘 갔다. grep 으로 바로 찾아진다."
|
|
445
|
+
갱신됨: priorcase-demo-결정-저장포맷-마크다운-2026-08-07
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### `PRIORCASE_CONFIG` — 플래그를 못 쓰는 자리용
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
$ PRIORCASE_CONFIG=$PWD/demo-config.toml prior index
|
|
452
|
+
색인 3행 생성
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
플래그가 환경변수를 이긴다.
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
$ PRIORCASE_CONFIG=/없는/경로.toml prior --config demo-config.toml index
|
|
459
|
+
색인 3행 생성
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## MCP 서버로 쓰기
|
|
463
|
+
|
|
464
|
+
`prior mcp` 는 stdio MCP 서버를 띄운다. 사람이 직접 실행할 일은 없다 — 호스트가 이
|
|
465
|
+
프로세스를 띄우고 stdin/stdout 으로 JSON-RPC 를 주고받는다. **그래서 이 명령이 도는
|
|
466
|
+
동안 stdout 은 프로토콜 전용이다.** 진단 출력은 전부 stderr 로 나간다.
|
|
467
|
+
|
|
468
|
+
호스트 설정에 이렇게 등록한다 (Claude Desktop·Claude Code·그 밖의 MCP 호스트 공통 형태):
|
|
469
|
+
|
|
470
|
+
```json
|
|
471
|
+
{
|
|
472
|
+
"mcpServers": {
|
|
473
|
+
"priorcase": {
|
|
474
|
+
"command": "prior",
|
|
475
|
+
"args": ["--config", "/home/t/.config/priorcase/config.toml", "mcp"]
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
`--config` 를 생략하면 `PRIORCASE_CONFIG` → `$XDG_CONFIG_HOME/priorcase/config.toml`
|
|
482
|
+
순으로 찾는다. 호스트가 환경변수를 물려주지 않는 경우가 많으므로 **경로를 명시하는
|
|
483
|
+
쪽을 권한다.**
|
|
484
|
+
|
|
485
|
+
### 도구 4종
|
|
486
|
+
|
|
487
|
+
| 도구 | 필수 인자 | 하는 일 |
|
|
488
|
+
|---|---|---|
|
|
489
|
+
| `priorcase_recall` | `query` | 관련 과거 결정을 찾는다 |
|
|
490
|
+
| `priorcase_capture` | `domain` `slug` `summary` | 결정을 기록한다 |
|
|
491
|
+
| `priorcase_review` | `stem` | outcome·상태·회고를 갱신하거나 결정을 뒤집는다 |
|
|
492
|
+
| `priorcase_pending` | — | 데몬이 표시한 미확인 구간을 보고 해소한다 |
|
|
493
|
+
|
|
494
|
+
### 편승 — 응답에 과거 결정이 딸려 온다
|
|
495
|
+
|
|
496
|
+
도구 결과는 그 자체로 컨텍스트 주입이다. 그래서 무엇을 부르든 관련 과거 결정을 얹는다.
|
|
497
|
+
특히 `capture` 시점은 곧 결정 시점이라, 기록할 때 과거 결정이 따라 나오는 것이 가장
|
|
498
|
+
정확한 타이밍이다.
|
|
499
|
+
|
|
500
|
+
```
|
|
501
|
+
기록됨: alpha/decisions/alpha-결정-캐시계층-2026-08-07.md
|
|
502
|
+
|
|
503
|
+
[과거 결정 참조]
|
|
504
|
+
- 2026-08-01 저장 엔진을 임베디드 DB 로 고른다 (active/pending) → alpha/decisions/alpha-결정-저장엔진-2026-08-01.md
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### 세션 진입 — 요약 덤프가 아니라 행동 계약
|
|
508
|
+
|
|
509
|
+
MCP 에는 서버가 대화 중간에 텍스트를 밀어넣는 채널이 없다. 유일한 자리가 `initialize`
|
|
510
|
+
응답의 `instructions` 인데 **세션당 한 번**이다. 거기에 최근 결정을 쏟아부어도 주제가
|
|
511
|
+
바뀌는 순간 낡는다. 그래서 요약이 아니라 "언제 무엇을 부르라"를 심는다.
|
|
512
|
+
|
|
513
|
+
```
|
|
514
|
+
priorcase — 이 워크스페이스의 과거 결정을 기록하고 회수한다.
|
|
515
|
+
|
|
516
|
+
**새 작업이나 주제로 넘어갈 때마다 먼저 `priorcase_recall(주제)` 를 부른다.**
|
|
517
|
+
지금 볼트에 결정 4건이 쌓여 있다. 부르지 않으면 이미 뒤집힌 결정을 다시 제안하게 된다.
|
|
518
|
+
|
|
519
|
+
**되돌리기 어려운 선택을 했으면 그 자리에서 `priorcase_capture` 를 부른다.**
|
|
520
|
+
아키텍처·스키마·외부 서비스·가격처럼 나중에 "왜 이렇게 했지"를 묻게 될 선택이 대상이다.
|
|
521
|
+
...
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
호스트가 `instructions` 를 어떻게 쓰는지는 구현 재량이다. Claude Code 는 시스템
|
|
525
|
+
프롬프트에 넣는 것이 확인됐으나 전수 확인은 하지 않았다 — **무시하는 호스트가 있을 수
|
|
526
|
+
있다.** 그때는 편승만 남는다.
|
|
527
|
+
|
|
528
|
+
### 읽지 못한 노트는 응답 본문으로 알린다
|
|
529
|
+
|
|
530
|
+
CLI 는 같은 정보를 stderr 로 낸다. MCP 에서 그렇게 하면 호스트 로그로 흘러가고 에이전트
|
|
531
|
+
컨텍스트에는 안 들어간다 — 회수에서 노트가 빠졌다는 사실을 정작 회수하는 쪽이 모르게 된다.
|
|
532
|
+
그래서 **응답 본문에** 싣는다.
|
|
533
|
+
|
|
534
|
+
```
|
|
535
|
+
⚠️ 결정 노트 1건을 읽지 못해 색인·회수에서 빠졌다:
|
|
536
|
+
- alpha/decisions/alpha-결정-깨짐-2026-01-01.md
|
|
537
|
+
frontmatter 파싱 실패: yaml: unmarshal errors:
|
|
538
|
+
line 1: field title not found in type store.Meta
|
|
539
|
+
정본 10키로 옮겨야 회수 대상으로 돌아온다.
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
## `prior watch` — 놓친 기록을 줍는 데몬
|
|
543
|
+
|
|
544
|
+
에이전트가 `priorcase_capture` 를 부르지 않고 지나간 구간을 표시한다. **LLM 을 부르지
|
|
545
|
+
않는다** — "이 구간에 결정이 있었을 수 있다" 는 표시만 남기고, 판별은 다음 세션의
|
|
546
|
+
에이전트가 한다. 그 모델이 이미 전체 맥락을 갖고 있고, API 키 등록은 오픈소스의
|
|
547
|
+
진입 장벽이다.
|
|
548
|
+
|
|
549
|
+
```
|
|
550
|
+
$ prior watch
|
|
551
|
+
prior watch: transcript 1173개 — 현재 지점부터 감시 1173개 · 밀린 구간 확인 0개
|
|
552
|
+
prior watch: 감시 시작 (/Users/t/.claude/projects)
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**transcript 는 읽기만 한다.** 쓰는 것은 상태 파일뿐이고, 그것도 볼트 밖에 둔다.
|
|
556
|
+
|
|
557
|
+
| 파일 | 자리 |
|
|
558
|
+
|---|---|
|
|
559
|
+
| 체크포인트 · pending | `$XDG_STATE_HOME/priorcase/state.json` |
|
|
560
|
+
| 단일 인스턴스 락 | `$XDG_STATE_HOME/priorcase/watch.lock` |
|
|
561
|
+
|
|
562
|
+
### 기동 시 무엇을 하나
|
|
563
|
+
|
|
564
|
+
- **처음 보는 파일은 현재 끝으로 시딩한다.** 데몬이 켜지기 전의 대화는 안전망 대상이
|
|
565
|
+
아니다. 실측 1173개를 전부 훑으면 표시가 쏟아지고, 안전망이 소음이 되면 에이전트가
|
|
566
|
+
무시하는 법을 배운다. 켜기 전 기록까지 훑으려면 `--backfill`.
|
|
567
|
+
- **이미 아는 파일은 훑는다.** 데몬이 꺼져 있는 동안 자란 구간이 있을 수 있고, 그 파일은
|
|
568
|
+
다시 바뀌지 않으므로 이때 안 보면 **영원히** 검토되지 않는다.
|
|
569
|
+
|
|
570
|
+
### 언제 표시하나
|
|
571
|
+
|
|
572
|
+
```
|
|
573
|
+
체크포인트 이후 구간
|
|
574
|
+
→ 쓰기가 quiesce_seconds 동안 멎을 때까지 대기
|
|
575
|
+
→ 턴 수 임계 (도구 호출·도구 결과는 세지 않는다)
|
|
576
|
+
→ 키워드 시그널 ([capture] signals · 판별기가 있으면 건너뛴다)
|
|
577
|
+
→ **면제 크레딧이 남아 있지 않을 것**
|
|
578
|
+
→ pending 기록
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
마지막 조건이 중요하다. 실측으로 발화 6개를 넘는 세션 585개 중 **578개(99%)** 가
|
|
582
|
+
기본 시그널에 걸린다 — `변경`·`선택`·`대신` 은 흔한 낱말이다. 이 조건이 없으면
|
|
583
|
+
에이전트가 제 할 일을 다 한 세션까지 전부 표시된다.
|
|
584
|
+
|
|
585
|
+
**면제는 소모성이다. 마지막 확인 이후 새 노트가 생겼을 때만 면제한다.**
|
|
586
|
+
|
|
587
|
+
```
|
|
588
|
+
세션 축: 그 세션 id 를 단 노트 수 ← 날짜에 무관, 단조
|
|
589
|
+
날짜 축: 날짜별로, 그날 그 도메인 노트 수 ← 날짜마다 따로 센다
|
|
590
|
+
|
|
591
|
+
면제한다 ⟺ 어느 한 축이라도 지난번에 소모한 수보다 늘었다
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
노트가 새로 생겼다는 것은 그 사이 에이전트가 기록했다는 직접 증거다. 안 늘었으면
|
|
595
|
+
이 구간은 아직 아무도 안 봤다. 걸린 노트들은 거기서 소모된다.
|
|
596
|
+
|
|
597
|
+
**두 축을 통짜 개수 하나로 합치면 안 된다.** 날짜 축은 *그 구간이 걸친 날짜*로
|
|
598
|
+
걸러지므로 구간마다 창이 움직인다. 고점을 넓은 창으로 찍고 비교를 좁은 창으로 하면,
|
|
599
|
+
바쁜 날을 한 번 지나간 세션은 **그 뒤로 영영 면제되지 않는다** — 자정 넘김,
|
|
600
|
+
`claude --continue`, 데몬 정지 뒤 backfill, 볼트 아카이브가 전부 그 상태를 만든다.
|
|
601
|
+
실제로 그렇게 만들었다가 리뷰에서 잡혔다.
|
|
602
|
+
|
|
603
|
+
**안전망은 자기 출력으로 자기를 억제하지 않는다.** 판별기가 자동으로 만든 노트는
|
|
604
|
+
만든 그 자리에서 크레딧을 소모시킨다. 안 그러면 다음 스캔이 그것을 "새로 생겼다" 로
|
|
605
|
+
세어 아직 아무도 안 본 구간을 면제한다.
|
|
606
|
+
|
|
607
|
+
> **처음에는 있다/없다로 판정했고, 그것이 안전망을 죽였다.** 세션 축은 첫 노트가
|
|
608
|
+
> 생긴 순간부터 그 세션을 영구 면제했고, 날짜 축은 그날 그 도메인 전체를 면제했다 —
|
|
609
|
+
> **기록을 잘 하는 프로젝트일수록 안전망이 죽는** 구조였다. 컷오버 1일차에 한
|
|
610
|
+
> 세션의 노트 11건이 하루 종일 안전망을 껐고, `pending: null` 은 "깨끗하다" 가
|
|
611
|
+
> 아니라 "애초에 안 봤다" 였는데 `prior doctor` 는 그것을 `이상 없다` 로 보고했다.
|
|
612
|
+
>
|
|
613
|
+
> **남은 거칢**: 날짜 축은 같은 날 *다른 세션*이 남긴 노트도 센다. 나란히 띄운 두
|
|
614
|
+
> 창 중 부지런한 쪽이 조용한 쪽의 구간을 한 번 가려 줄 수 있다. 손해가 구간 하나로
|
|
615
|
+
> 묶이고, 앞서 표시한 구간이 그 때문에 사라지지는 않는다.
|
|
616
|
+
|
|
617
|
+
### 안전망이 한 일은 어디에 남나
|
|
618
|
+
|
|
619
|
+
`prior doctor` 가 완전 실패와 정상을 구분하려면 흔적이 있어야 한다. 없으면 둘 다 초록불이다.
|
|
620
|
+
|
|
621
|
+
| 어디 | 무엇 |
|
|
622
|
+
|---|---|
|
|
623
|
+
| `state.json` 의 `checkpoints[].at` | 마지막으로 훑은 시각. **성공한 스캔만** 남긴다 (실패가 "방금 훑음" 으로 보이면 증거가 거짓말을 한다) |
|
|
624
|
+
| `state.json` 의 `checkpoints[].session_credited` · `day_credited` · `suppressed` | 축별로 소모한 크레딧 · 누적 면제 횟수 |
|
|
625
|
+
| `promotions.jsonl` | 승격 **세 갈래 전부** — 기록함 · 기록 안 함(이유) · 실패(에러) |
|
|
626
|
+
|
|
627
|
+
상태 디렉토리(`$XDG_STATE_HOME/priorcase`)에는 이 둘 말고 잠금 파일 두 개가 더 있다 —
|
|
628
|
+
`watch.lock`(누가 훑기의 주인인가)과 `state.lock`(상태 파일을 고치는 동안). 잠금 파일은
|
|
629
|
+
백업할 필요가 없다.
|
|
630
|
+
|
|
631
|
+
`promotions.jsonl` 은 덧붙이기 전용이고 아무것도 이걸 정본으로 읽지 않는다. 언제든
|
|
632
|
+
지워도 된다. `state.json` 에 안 넣은 이유는 둘이다 — 상태 파일은 매 스캔마다 통째로
|
|
633
|
+
다시 쓰이고, 깨져서 지우면 이력까지 사라지는데 그때가 이력이 필요한 순간이다.
|
|
634
|
+
|
|
635
|
+
`prior doctor` 의 안전망 줄이 이렇게 나온다:
|
|
636
|
+
|
|
637
|
+
```
|
|
638
|
+
✓ 안전망 훅이 턴 경계마다 훑는다 (데몬 없음) · 최근 7일 기록 3건
|
|
639
|
+
· 마지막 훑기 30분 전 · 최근 7일 자동 기록 3건/판정 12건
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
`자동 기록 0건/판정 12건` 은 "판별기가 안 돈다" 가 아니라 **"12번 봤는데 기록할 게
|
|
643
|
+
없었다"** 이고, 그 둘은 전혀 다른 진단이다.
|
|
644
|
+
|
|
645
|
+
### 체크포인트는 언제 전진하나
|
|
646
|
+
|
|
647
|
+
전진 규칙이 이 데몬의 핵심이다. 옛 셸 구현에서 데이터가 사라지던 자리다.
|
|
648
|
+
|
|
649
|
+
| 상황 | 전진 | 왜 |
|
|
650
|
+
|---|---|---|
|
|
651
|
+
| 깨진 줄이 있다 | ❌ | 그 구간이 영원히 검토되지 않는다 |
|
|
652
|
+
| 턴 수가 임계 미만이다 | ❌ | 전진하면 임계가 영원히 안 찬다 |
|
|
653
|
+
| 임계를 넘겼다 | ✅ | 다 봤다 |
|
|
654
|
+
|
|
655
|
+
**쓰이는 중인 마지막 줄은 아예 읽지 않는다.** 개행으로 끝난 줄까지만 소비하므로,
|
|
656
|
+
반쯤 쓰인 줄은 다음 스캔이 처음부터 다시 읽는다.
|
|
657
|
+
|
|
658
|
+
### 표시된 구간을 확인하는 법
|
|
659
|
+
|
|
660
|
+
MCP 로 붙으면 세션 진입 `instructions` 에 실리고, `priorcase_pending` 으로 목록과
|
|
661
|
+
해소를 다룬다.
|
|
662
|
+
|
|
663
|
+
```
|
|
664
|
+
⚠️ **데몬이 표시한 미확인 구간이 2건 있다.** 이전 세션에서 결정을 내리고도
|
|
665
|
+
기록하지 않고 지나간 자리다. 확인해서 실제 결정이면 `priorcase_capture` 로 남기고,
|
|
666
|
+
아니면 `priorcase_pending` 으로 지워라 — 쌓아 두면 다음 세션에도 그대로 뜬다.
|
|
667
|
+
- 2026-08-06 alpha · 발화 12 · 시그널 결정
|
|
668
|
+
- 2026-08-07 beta · 발화 7 · 시그널 채택
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### 설정
|
|
672
|
+
|
|
673
|
+
```toml
|
|
674
|
+
[capture]
|
|
675
|
+
signals = ["결정", "선택", "하기로", "채택", "대신", "전략", "포기", "변경"]
|
|
676
|
+
min_turns = 6
|
|
677
|
+
quiesce_seconds = 3
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
`signals` 가 비면 **어떤 구간도 표시되지 않는다.** 그 상태로도 데몬은 정상 기동한
|
|
681
|
+
것처럼 보이므로, 그때는 기동 시 경고를 낸다.
|
|
682
|
+
|
|
683
|
+
## Claude Code 훅
|
|
684
|
+
|
|
685
|
+
`prior init` 이 배선한다. **기본은 계획만 보여 준다** — 이 설정 파일은 priorcase 만의 것이
|
|
686
|
+
아니라 다른 도구들과 공유하는 자리라, 실수로 한 번 돌려서 남의 훅이 사라지면 안 된다.
|
|
687
|
+
|
|
688
|
+
```
|
|
689
|
+
$ prior init
|
|
690
|
+
설정 파일: /home/t/.claude/settings.json
|
|
691
|
+
백업: /home/t/.claude/settings.json.priorcase-backup-20260807-235038
|
|
692
|
+
|
|
693
|
+
걷어낼 훅:
|
|
694
|
+
- SessionStart: /home/t/.claude/hooks/second-brain/session-start.sh
|
|
695
|
+
...
|
|
696
|
+
|
|
697
|
+
심을 훅:
|
|
698
|
+
+ SessionStart: PRIORCASE_HOOK=1 "/usr/local/bin/prior" hook session-start
|
|
699
|
+
...
|
|
700
|
+
|
|
701
|
+
손대지 않는 훅: 11개
|
|
702
|
+
|
|
703
|
+
계획만 보여 줬다. 실제로 바꾸려면 --apply 를 붙인다.
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
`--apply` 가 수정 전에 백업을 남기고, `prior init --revert` 가 **바이트 그대로** 되돌린다.
|
|
707
|
+
|
|
708
|
+
| 지키는 것 | 어떻게 |
|
|
709
|
+
|---|---|
|
|
710
|
+
| 남의 훅을 안 지운다 | `PRIORCASE_HOOK=1` 마커와 `--remove-matching` 에 걸리는 것만 지운다 |
|
|
711
|
+
| 모르는 설정 키를 안 잃는다 | 설정 전체를 map 으로 읽고 `hooks` 만 손댄다 |
|
|
712
|
+
| 깨진 설정을 안 덮어쓴다 | JSON 이 아니면 손대기 전에 멈춘다 |
|
|
713
|
+
| 두 번 돌려도 안전 | 마커로 자기 것을 먼저 걷어내고 다시 심는다 |
|
|
714
|
+
| 바로 쓸 수 있는 설정 | 볼트 디렉토리를 만들고, `common` 도메인과 `default_domain` 을 넣는다 |
|
|
715
|
+
| 로케일을 본다 | `LANG` 이 `ko` 로 시작하면 한국어 설정, 아니면 영어 설정 |
|
|
716
|
+
| 기존 설정 파일을 안 건드린다 | `config.toml` 은 **없을 때만** 만든다 |
|
|
717
|
+
|
|
718
|
+
### 각 훅이 하는 일
|
|
719
|
+
|
|
720
|
+
| 이벤트 | 하는 일 |
|
|
721
|
+
|---|---|
|
|
722
|
+
| `user-prompt-submit` | **관련 과거 결정을 강제 주입한다.** 이 어댑터의 존재 이유다 |
|
|
723
|
+
| `session-start` | 도메인 · 최근 결정 · 미확인 구간 · 기록 계약 |
|
|
724
|
+
| `stop` · `pre-compact` · `session-end` | 데몬이 안 돌면 대신 훑는다 |
|
|
725
|
+
| `pre-compact` · `session-end` | **판별기에 넘겨 자동 기록한다 — 데몬이 돌든 말든** |
|
|
726
|
+
|
|
727
|
+
### 데몬 없이도 안전망이 돈다
|
|
728
|
+
|
|
729
|
+
`prior watch` 는 상태 디렉토리에 락을 잡고 산다. 훅이 그 락을 시도해서 **얻으면 데몬이 없는
|
|
730
|
+
것**이므로 자기가 훑고 놓는다. 못 얻으면 데몬이 주인이라 건너뛴다.
|
|
731
|
+
|
|
732
|
+
소유자가 언제나 하나뿐이라 중복 처리가 구조적으로 불가능하고, **데몬 등록에 실패한
|
|
733
|
+
사용자도 턴 경계마다 안전망을 얻는다.** 그래서 `prior init` 은 launchd·systemd 에 서비스를
|
|
734
|
+
등록하지 않는다 — 되돌리기 어려운 일을 필수도 아닌 것에 하지 않는다.
|
|
735
|
+
|
|
736
|
+
> **승격은 이 소유권과 무관하다.** 이 락은 *훑기*의 주인만 정한다. 데몬은 판별기를
|
|
737
|
+
> 부르지 않고 세션이 끝난 것도 모르므로, 훅은 락을 못 얻어도 `session-end`·`pre-compact`
|
|
738
|
+
> 에서 자동 기록을 한다. 그러지 않으면 **`prior watch` 를 켜는 것이 자동 기록을 끄는
|
|
739
|
+
> 행위**가 된다 — 실제로 그렇게 만들었다가 리뷰에서 잡혔다.
|
|
740
|
+
>
|
|
741
|
+
> 그래서 데몬이 도는 동안에도 훅이 상태 파일을 고친다. 상태 파일은 **디스크가 정본**이고
|
|
742
|
+
> (쓰기는 `state.lock` 안에서 다시 읽고 고쳐 쓴다), 같은 구간을 둘이 집지 않도록
|
|
743
|
+
> pending 마다 **선점 표시**(`claimed_at`, 5분 뒤 자동 해제)를 찍는다.
|
|
744
|
+
|
|
745
|
+
### 세 가지 규율
|
|
746
|
+
|
|
747
|
+
1. **무슨 일이 있어도 종료 코드 0.** 훅이 실패해서 대화가 막히면, 사용자는 priorcase 을
|
|
748
|
+
고치는 게 아니라 지운다.
|
|
749
|
+
2. **stdout 은 에이전트 컨텍스트다.** `user-prompt-submit`·`session-start` 의 stdout 은
|
|
750
|
+
그대로 주입되므로 경고·에러가 한 줄도 섞이지 않는다. 설정 파일이 없을 때조차 그렇다.
|
|
751
|
+
3. **실패를 조용히 넘기지 않는다.** stdout 이 비어도 stderr 에는 반드시 남는다.
|
|
752
|
+
|
|
753
|
+
## `prior doctor` — 조용한 무동작을 보는 자리
|
|
754
|
+
|
|
755
|
+
이 시스템의 부품은 **전부 실패해도 대화를 막지 않도록** 만들어졌다. 훅은 무슨 일이
|
|
756
|
+
있어도 종료 코드 0이고, 회수는 못 찾으면 아무것도 안 내고, 데몬은 백그라운드다.
|
|
757
|
+
그 설계의 대가로 **고장이 정상과 구별되지 않는다.** `prior doctor` 가 그걸 구별한다.
|
|
758
|
+
|
|
759
|
+
```
|
|
760
|
+
$ prior doctor
|
|
761
|
+
✓ 설정 /home/t/.config/priorcase/config.toml
|
|
762
|
+
✓ 볼트 /home/t/vault
|
|
763
|
+
✓ 도메인 폴더 8개 중 4개는 아직 없다 [...] — 첫 결정을 쓸 때 만들어진다
|
|
764
|
+
✓ 미선언 도메인 없다
|
|
765
|
+
✓ 결정 노트 58건 전부 읽힌다
|
|
766
|
+
✓ 색인 58건과 일치한다
|
|
767
|
+
✓ 훅 배선 5개 전부 (남의 훅 11개는 손대지 않음)
|
|
768
|
+
✓ 훅 바이너리 /home/t/go/bin/prior
|
|
769
|
+
✓ 안전망 훅이 턴 경계마다 훑는다 (데몬 없음) · 미확인 구간 없음
|
|
770
|
+
|
|
771
|
+
이상 없다.
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
종료 코드로 옮긴다 — **경고 1 · 오류 2.** 자동화가 기계적으로 읽을 수 있다.
|
|
775
|
+
|
|
776
|
+
특히 보는 것 셋:
|
|
777
|
+
|
|
778
|
+
- **미선언 도메인** — 볼트에 결정 폴더가 있는데 설정에 없으면 그 프로젝트의 결정이
|
|
779
|
+
색인·회수에서 **통째로** 빠진다. 그런데 색인은 정상 생성되고 회수도 에러를 안 낸다.
|
|
780
|
+
- **훅 바이너리** — 훅에는 `prior` 의 절대 경로가 박힌다. 그 파일이 사라지면 훅은
|
|
781
|
+
종료 코드 0으로 아무 일도 안 하면서 정상으로 보인다.
|
|
782
|
+
- **미확인 구간 누적** — 7일 넘게 방치된 것이 있으면 따로 센다. 쌓인다는 것은
|
|
783
|
+
`prior capture` 가 안 불리고 있다는 뜻이다.
|
|
784
|
+
- **PATH** — `prior` 를 그냥 칠 수 있는가. 이게 안 되면 진단이 내는 모든 `→` 를
|
|
785
|
+
실행할 수 없어서 진단 자체가 무용지물이 된다.
|
|
786
|
+
|
|
787
|
+
경고마다 `→` 로 고치는 법을 준다. 진단만 하고 무엇을 하라는 말이 없으면
|
|
788
|
+
사용자는 그 경고를 무시하는 법을 배운다.
|
|
789
|
+
|
|
790
|
+
## `prior rollup` — 작업 로그 주간 요약
|
|
791
|
+
|
|
792
|
+
작업 로그(`99-*`)를 주 단위로 묶어 요약 파일(`98-*`)에 붙인다. **원본은 손대지 않는다.**
|
|
793
|
+
|
|
794
|
+
**요약문은 priorcase 가 만들지 않는다.** 어느 주가 남았는지 찾고, 그 주의 로그를 뽑고,
|
|
795
|
+
중복 없이 붙이는 일만 한다. 무엇을 요약이라 부를지는 전체 맥락을 가진 에이전트가 정한다 —
|
|
796
|
+
`prior capture` 와 같은 구조다. (셸 시절에는 여기서 LLM 을 불렀는데, 데몬에서 걷어낸 것과
|
|
797
|
+
같은 의존이라 같은 선택을 했다.)
|
|
798
|
+
|
|
799
|
+
```
|
|
800
|
+
$ prior rollup
|
|
801
|
+
mesh
|
|
802
|
+
2026-W29 → 요약 필요 (30632B)
|
|
803
|
+
synth
|
|
804
|
+
2026-W31 → 요약 필요 (46212B)
|
|
805
|
+
2026-W32 진행 중인 주 — 끝나면 요약한다
|
|
806
|
+
|
|
807
|
+
요약이 필요한 주 2개. 한 주씩:
|
|
808
|
+
1. prior rollup <프로젝트> <주> 로그를 읽는다
|
|
809
|
+
2. 읽고 요약문을 쓴다 ← 여기는 에이전트가 한다
|
|
810
|
+
3. prior rollup <프로젝트> <주> --body - 붙인다
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
건너뛴 주도 **이유와 함께** 보여 준다 — 목록에서 조용히 빠지면 왜 요약이 안 되는지
|
|
814
|
+
알 수 없다. 같은 주를 두 번 붙이지 않는다(덮어쓰면 앞의 요약이 사라진다).
|
|
815
|
+
|
|
816
|
+
`[naming]` 에 `rollup` 키가 필요하다. 없으면 무엇을 적을지 알려 주고 멈춘다.
|
|
817
|
+
|
|
818
|
+
```toml
|
|
819
|
+
[naming]
|
|
820
|
+
rollup = "98-{project}-작업-로그-요약.md"
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
## 개발
|
|
824
|
+
|
|
825
|
+
make build # go build -trimpath -ldflags="-s -w" -o prior ./cmd/prior
|
|
826
|
+
make test # go test ./...
|
|
827
|
+
make lint # go vet ./...
|
|
828
|
+
|
|
829
|
+
`Makefile` 은 `GOTOOLCHAIN=auto` 를 저장소에 고정해 둔다. 개발 머신이 Homebrew Go
|
|
830
|
+
1.23.3 + `GOTOOLCHAIN=local` 이면 최신 `x/text` 가 요구하는 Go 버전 때문에 맨몸
|
|
831
|
+
`go build`/`go mod tidy` 가 실패한다. `GOTOOLCHAIN=auto` 는 go.mod 가 요구하는
|
|
832
|
+
툴체인을 필요할 때 자동으로 받아 오게 해서 이 문제를 없앤다.
|
|
833
|
+
|
|
834
|
+
CI 는 `gofmt -l` · `go vet` · `go test -race` 를 돌린다.
|
|
835
|
+
|
|
836
|
+
### 실볼트 대조 테스트
|
|
837
|
+
|
|
838
|
+
실볼트 사본은 저장소에 넣지 않는다 (결정 노트에 개인 내용이 들어 있다). 대신
|
|
839
|
+
`PRIORCASE_TEST_VAULT` 가 설정됐을 때만 도는 로컬 전용 테스트가 있다. CI 에서는 건너뛴다.
|
|
840
|
+
|
|
841
|
+
PRIORCASE_TEST_VAULT="$HOME/Documents/Obsidian Vault" go test ./... -run RealVault -v
|
|
842
|
+
|
|
843
|
+
실볼트를 **읽기만** 한다 — 모든 결정 노트가 파싱되는지, 스키마를 통과하는지,
|
|
844
|
+
그리고 **색인 행 + 건너뛴 노트 == 디스크의 결정 노트** 가 성립하는지 본다.
|
|
845
|
+
이 등식이 지켜지면 노트는 색인에 들어갔거나 빠졌다고 보고됐거나 둘 중 하나이고,
|
|
846
|
+
조용히 사라진 것은 하나도 없다. 전후 스냅샷을 대조해 쓰지 않았음도 확인한다.
|
|
847
|
+
|
|
848
|
+
## 보장 수준
|
|
849
|
+
|
|
850
|
+
| | Claude Code | MCP 전용 호스트 |
|
|
851
|
+
|---|---|---|
|
|
852
|
+
| 결정 순간 기록 | 에이전트 `prior capture` | 동일 |
|
|
853
|
+
| 놓친 기록 줍기 | 데몬 | 동일 |
|
|
854
|
+
| 세션 진입 컨텍스트 | 훅 (보장) | `initialize.instructions` (사실상 동등) |
|
|
855
|
+
| 주제 전환 시 회수 | 훅 (강제) | 계약 + 편승 (유도) |
|
|
856
|
+
|
|
857
|
+
MCP 에는 서버가 대화 중간에 텍스트를 밀어넣는 채널이 없다. 마지막 줄이 유일한 차이고,
|
|
858
|
+
**그 차이는 v1 에서 닫히지 않는다** — 프로토콜의 한계이지 구현의 게으름이 아니다.
|
|
859
|
+
Claude Code 에서는 `prior hook user-prompt-submit` 이 매 프롬프트마다 관련 결정을 밀어넣고,
|
|
860
|
+
그 밖의 호스트에서는 `initialize.instructions` 의 행동 계약과 도구 응답 편승으로 유도한다.
|
|
861
|
+
|
|
862
|
+
네 칸 모두 이제 실제로 동작한다.
|
|
863
|
+
|
|
864
|
+
### 기록은 3층이다
|
|
865
|
+
|
|
866
|
+
**협조 없이도 기록된다.** 에이전트가 `prior capture` 를 부르지 않아도 세션 끝에
|
|
867
|
+
판별기가 대신 남긴다.
|
|
868
|
+
|
|
869
|
+
| 층 | 무엇 | 협조 | 비용 |
|
|
870
|
+
|---|---|---|---|
|
|
871
|
+
| ① | 에이전트가 결정 시점에 `prior capture` | 필요 | 0 |
|
|
872
|
+
| ② | 매 프롬프트에 **발췌를 들이민다** | 유도가 강해짐 | 0 |
|
|
873
|
+
| ③ | 세션 끝에 판별기가 대신 기록 | **불필요** | 토큰 |
|
|
874
|
+
|
|
875
|
+
②가 발췌를 같이 싣는 것이 핵심이다. "미확인 1건" 만 알리면 확인하려고 대화를 다시
|
|
876
|
+
읽어야 하는데, 그 비용이 크면 그냥 넘어간다. 눈앞에 있으면 부르는 것이 읽는 것보다 싸다.
|
|
877
|
+
|
|
878
|
+
③의 판별기는 **호스트 CLI 만** 쓴다 (`~/.local/bin/claude` → PATH 의 `claude`).
|
|
879
|
+
API 키를 직접 읽지 않는다 — 그건 진짜 장벽이고, 사용자가 모르는 사이에 과금되는
|
|
880
|
+
경로를 만들지 않기 위해서다. **CLI 가 없으면 자동 승격이 꺼지고 ①②만 남는다.**
|
|
881
|
+
|
|
882
|
+
```toml
|
|
883
|
+
[capture]
|
|
884
|
+
judge_path = "" # 비면 자동 탐색. 못 찾으면 자동 승격 꺼짐
|
|
885
|
+
judge_model = "claude-haiku-4-5"
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
**판별기가 있으면 `[capture] signals` 는 쓰이지 않는다.** 시그널은 "이 구간에 결정이
|
|
889
|
+
있을까" 를 낱말로 어림하는 것인데, 실측으로 발화 6개를 넘는 세션의 **98.8%** 를
|
|
890
|
+
통과시켜 거의 거르지 못한다. 그러면서 설정에 적힌 낱말이라 **대화 언어와 어긋나면
|
|
891
|
+
시스템이 조용히 죽는다** — 한국어 시그널로 영어 대화를 훑으면 아무것도 안 걸리는데
|
|
892
|
+
로그에는 정상으로 보인다. 판별기가 있으면 그 앞을 막지 않는다.
|
|
893
|
+
|
|
894
|
+
판별기가 **없는** 설치에서는 `signals` 가 유일한 필터이므로 대화 언어와 맞아야 한다.
|
|
895
|
+
`prior doctor` 가 어느 쪽인지 알려 준다.
|
|
896
|
+
|
|
897
|
+
`prior doctor` 가 어느 상태인지 알려 준다.
|
|
898
|
+
|
|
899
|
+
> **`summary` 와 `tags` 는 검색어다.** 회수는 파일명·`summary`·`tags` 만 본다 —
|
|
900
|
+
> 본문에만 있는 낱말로는 찾을 수 없다. 실측: 같은 노트가 태그를 주제 분류로 썼을 때
|
|
901
|
+
> 관련 질문 3개 중 0개, 회수 어휘로 바꾸니 3개 다 걸렸다. 판별기 지시문이 이걸 알려 준다.
|
|
902
|
+
|
|
903
|
+
> **판별기는 보수적이다.** 자동 노트는 손으로 쓴 것과 구분되지 않으므로, 애매한 것을
|
|
904
|
+
> 기록하면 볼트가 조용히 오염된다. 지시문의 절반이 "애매하면 기록하지 마라" 다.
|
|
905
|
+
> 실측: 진행 보고 8턴 → 0건, 진짜 결정 8턴 → 1건.
|
|
906
|
+
|
|
907
|
+
`prior pending` 으로 표시된 구간을 직접 보고 지울 수 있다.
|
|
908
|
+
|
|
909
|
+
### 안전망이 실제로 볼 수 있는 것
|
|
910
|
+
|
|
911
|
+
데몬은 transcript 를 읽어 결정 시그널을 찾는다. **그 기반은 생각보다 얇다.**
|
|
912
|
+
이 저장소를 만들면서 실 transcript 1173개(476MB)를 재어 본 결과다.
|
|
913
|
+
|
|
914
|
+
| | 실측 |
|
|
915
|
+
|---|---|
|
|
916
|
+
| 전체 transcript | 476 MB |
|
|
917
|
+
| 그중 **눈에 보이는 발화** | 15 MB — **3.3%** |
|
|
918
|
+
| 나머지 | 도구 호출·도구 결과·빈 thinking 서명 |
|
|
919
|
+
|
|
920
|
+
**에이전트의 사고(thinking)는 아예 볼 수 없다.** Claude Code 는 thinking 블록에
|
|
921
|
+
암호화된 서명만 저장하고 본문은 빈 문자열로 둔다 — 블록 13451개가 **전부** 그랬다.
|
|
922
|
+
|
|
923
|
+
그래서 한계가 분명하다. **사고 안에서만 내려지고 밖으로 한 줄도 안 나온 결정은
|
|
924
|
+
데몬이 볼 수 없다.** 데몬은 주 경로가 아니라 안전망이고, 주 경로는 결정을 내린
|
|
925
|
+
에이전트가 그 자리에서 `priorcase_capture` 를 부르는 것이다.
|
|
926
|
+
|
|
927
|
+
## 라이선스
|
|
928
|
+
|
|
929
|
+
MIT. `LICENSE` 참고.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 제3자 라이선스 고지 / Third-Party Notices
|
|
2
|
+
|
|
3
|
+
priorcase 바이너리에는 아래 오픈소스 구성요소가 포함된다. 각 구성요소는 자체
|
|
4
|
+
라이선스를 따르며, 그 라이선스 전문은 각 프로젝트 저장소에 있다.
|
|
5
|
+
|
|
6
|
+
priorcase binaries include the open-source components listed below. Each is
|
|
7
|
+
governed by its own license; full texts are available in the respective
|
|
8
|
+
project repositories.
|
|
9
|
+
|
|
10
|
+
| 구성요소 / Component | 버전 / Version | 라이선스 / License |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| `github.com/fsnotify/fsnotify` | v1.10.1 | BSD-3-Clause |
|
|
13
|
+
| `github.com/gofrs/flock` | v0.12.1 | BSD-3-Clause |
|
|
14
|
+
| `github.com/google/jsonschema-go` | v0.4.3 | MIT |
|
|
15
|
+
| `github.com/modelcontextprotocol/go-sdk` | v1.7.0 | MIT |
|
|
16
|
+
| `github.com/pelletier/go-toml/v2` | v2.4.3 | MIT |
|
|
17
|
+
| `github.com/segmentio/asm` | v1.1.3 | MIT |
|
|
18
|
+
| `github.com/segmentio/encoding` | v0.5.4 | MIT |
|
|
19
|
+
| `github.com/spf13/cobra` | v1.10.2 | Apache-2.0 |
|
|
20
|
+
| `github.com/spf13/pflag` | v1.0.9 | BSD-3-Clause |
|
|
21
|
+
| `github.com/yosida95/uritemplate/v3` | v3.0.2 | BSD-3-Clause |
|
|
22
|
+
| `go.yaml.in/yaml/v3` | v3.0.5 | MIT |
|
|
23
|
+
| `golang.org/x/oauth2` | v0.35.0 | BSD-3-Clause |
|
|
24
|
+
| `golang.org/x/sync` | v0.20.0 | BSD-3-Clause |
|
|
25
|
+
| `golang.org/x/text` | v0.28.0 | BSD-3-Clause |
|
|
26
|
+
| `golang.org/x/time` | v0.15.0 | BSD-3-Clause |
|
|
27
|
+
| `golang.org/x/sys` | v0.41.0 | BSD-3-Clause |
|
|
28
|
+
|
|
29
|
+
Go 표준 라이브러리는 BSD-3-Clause 를 따른다 (https://go.dev/LICENSE).
|
|
30
|
+
The Go standard library is BSD-3-Clause licensed.
|
|
31
|
+
|
|
32
|
+
이 목록은 `go list -deps ./cmd/prior` 로 **실제 바이너리에 링크되는 것만** 추렸다.
|
|
33
|
+
테스트 전용 의존성(testify, go-cmp 등)은 배포물에 들어가지 않아 제외했다.
|
package/bin/prior.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// priorcase 런처.
|
|
3
|
+
//
|
|
4
|
+
// **이 파일은 아무 일도 하지 않는다** — 자기 플랫폼의 네이티브 바이너리를 찾아
|
|
5
|
+
// 그대로 넘긴다. esbuild 가 쓰는 방식이고, 이유가 셋이다.
|
|
6
|
+
//
|
|
7
|
+
// 1. 설치 시 다운로드가 없다. npm 이 optionalDependencies 로 자기 플랫폼 것만
|
|
8
|
+
// 받는다 — postinstall 스크립트가 네트워크를 타면 사내망·오프라인에서 죽는다.
|
|
9
|
+
// 2. Node 는 **배달부일 뿐이다.** 실행되는 것은 정적 Go 바이너리이고,
|
|
10
|
+
// 런타임 의존은 여전히 0 이다 (D1).
|
|
11
|
+
// 3. `npx -y priorcase mcp` 가 그대로 된다 — MCP 생태계의 규범이다.
|
|
12
|
+
//
|
|
13
|
+
// exec 로 프로세스를 갈아탄다. 감싸면 신호(Ctrl-C)와 종료 코드가 한 겹 더 거쳐야
|
|
14
|
+
// 하는데, priorcase 는 훅으로 불려서 **종료 코드가 규약**이다.
|
|
15
|
+
|
|
16
|
+
const { spawnSync } = require("child_process");
|
|
17
|
+
const { existsSync } = require("fs");
|
|
18
|
+
const path = require("path");
|
|
19
|
+
|
|
20
|
+
const PKG = { darwin: "darwin", linux: "linux" };
|
|
21
|
+
const ARCH = { arm64: "arm64", x64: "x64" };
|
|
22
|
+
|
|
23
|
+
function binaryPath() {
|
|
24
|
+
const os = PKG[process.platform];
|
|
25
|
+
const arch = ARCH[process.arch];
|
|
26
|
+
if (!os || !arch) return null;
|
|
27
|
+
// **스코프를 안 쓴다.** 스코프를 쓰려면 npm 조직이 있어야 하는데, 조직 이름은
|
|
28
|
+
// 미리 확인할 API 가 없어 제출해 봐야만 쓸 수 있는지 안다 (옛 이름 casebook 이
|
|
29
|
+
// 실제로 그렇게 막혔다). 사용자가 치는 명령(npx -y priorcase mcp)에는 스코프가
|
|
30
|
+
// 나오지 않으므로 얻는 것도 없다.
|
|
31
|
+
const pkg = `priorcase-${os}-${arch}`;
|
|
32
|
+
try {
|
|
33
|
+
// require.resolve 가 node_modules 해석을 대신한다 — 경로를 직접 짜면
|
|
34
|
+
// pnpm·yarn PnP 같은 배치에서 깨진다.
|
|
35
|
+
const entry = require.resolve(`${pkg}/package.json`);
|
|
36
|
+
const p = path.join(path.dirname(entry), "bin", "prior");
|
|
37
|
+
return existsSync(p) ? p : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const bin = binaryPath();
|
|
44
|
+
if (!bin) {
|
|
45
|
+
const target = `${process.platform}-${process.arch}`;
|
|
46
|
+
process.stderr.write(
|
|
47
|
+
`priorcase: no binary for ${target}.\n` +
|
|
48
|
+
` Supported: darwin-arm64, darwin-x64, linux-arm64, linux-x64.\n` +
|
|
49
|
+
` If your platform is listed, the optional dependency did not install —\n` +
|
|
50
|
+
` try: npm install --include=optional priorcase\n`
|
|
51
|
+
);
|
|
52
|
+
// 훅은 언제나 exit 0 이어야 하지만, 여기는 **설치가 안 된 상태**다.
|
|
53
|
+
// 그건 조용히 넘어갈 일이 아니다 — prior doctor 가 볼 수 있도록 실패로 낸다.
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const r = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
58
|
+
if (r.error) {
|
|
59
|
+
process.stderr.write(`priorcase: ${r.error.message}\n`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
// 신호로 죽었으면 그 신호를 그대로 흉내 낸다 — 상위 셸이 Ctrl-C 를 알아야 한다.
|
|
63
|
+
if (r.signal) {
|
|
64
|
+
process.kill(process.pid, r.signal);
|
|
65
|
+
}
|
|
66
|
+
process.exit(r.status === null ? 1 : r.status);
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "priorcase",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Record your agent's decisions and surface them at the next judgment point",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"claude",
|
|
8
|
+
"agent",
|
|
9
|
+
"memory",
|
|
10
|
+
"decisions",
|
|
11
|
+
"obsidian"
|
|
12
|
+
],
|
|
13
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
14
|
+
"bin": {
|
|
15
|
+
"prior": "bin/prior.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin/",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"THIRD-PARTY-NOTICES.md",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"optionalDependencies": {
|
|
27
|
+
"priorcase-darwin-arm64": "0.1.0",
|
|
28
|
+
"priorcase-darwin-x64": "0.1.0",
|
|
29
|
+
"priorcase-linux-arm64": "0.1.0",
|
|
30
|
+
"priorcase-linux-x64": "0.1.0"
|
|
31
|
+
}
|
|
32
|
+
}
|