priorcase 0.2.0 → 0.3.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.
Files changed (2) hide show
  1. package/README.md +137 -21
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -99,8 +99,9 @@ Apple Developer 계정은 이 셋 중 하나가 될 때 든다 — 브라우저
99
99
  `XDG_CONFIG_HOME` 을 빈 디렉토리로 지정했다):
100
100
 
101
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
102
+ $ XDG_CONFIG_HOME=/tmp/nonexistent-xdg prior doctor
103
+ 설정 설정 파일을 열 수 없다 (/tmp/nonexistent-xdg/priorcase/config.toml): open /tmp/nonexistent-xdg/priorcase/config.toml: no such file or directory
104
+ → prior init --apply 가 기본 설정을 만든다
104
105
  ```
105
106
 
106
107
  `PRIORCASE_VAULT` 는 별개다 — 설정 파일의 `vault` 값만 덮어쓴다 (테스트 볼트 격리용).
@@ -344,13 +345,6 @@ EOF
344
345
  기록됨: priorcase-demo/decisions/priorcase-demo-결정-회수-키워드매칭-2026-08-07.md
345
346
  ```
346
347
 
347
- ### `prior index` — 색인을 재생성한다
348
-
349
- ```
350
- $ prior --config demo-config.toml index
351
- 색인 2행 생성
352
- ```
353
-
354
348
  `decisions/INDEX.md` (설정의 `naming.index`) 에 날짜 · domain · summary · status ·
355
349
  outcome · 링크 표가 생긴다.
356
350
 
@@ -377,9 +371,8 @@ $ prior --config demo-config.toml index
377
371
  ```
378
372
 
379
373
  종료 코드는 그래도 0 이다. 원인은 `prior` 가 고칠 수 있는 것이 아니라 볼트 데이터를
380
- 사람이 정본 10키로 옮겨야 하는 것이고, 훅·크론에서 도는 `prior index` 가 그때까지
381
- 매번 실패하면 무시하는 법만 학습시키기 때문이다. `prior capture` · `prior review` 도
382
- 내부적으로 색인을 다시 쓰므로 같은 경고를 낸다.
374
+ 사람이 정본 10키로 옮겨야 하는 것이고, 실행마다 실패하면 무시하는 법만
375
+ 학습시키기 때문이다. `prior capture` · `prior review` 도 같은 경고를 낸다.
383
376
 
384
377
  ### `prior recall` — 관련 과거 결정을 찾는다
385
378
 
@@ -401,10 +394,112 @@ $ prior --config demo-config.toml recall 회수 키워드
401
394
  `status: regretted` 이거나 `outcome: bad` 인 결정이 결과에 끼면 `--format inject` 출력
402
395
  끝에 회고를 먼저 읽으라는 경고 줄이 붙는다.
403
396
 
404
- 회수 대상에서 읽기 실패로 빠진 노트가 있으면 `prior index` 와 같은 경고를 낸다.
397
+ 회수 대상에서 읽기 실패로 빠진 노트가 있으면 `prior capture` 와 같은 경고를 낸다.
405
398
  포맷과 무관하게 **항상 stderr** 다 — `--format inject` 의 stdout 은 훅이 그대로
406
399
  컨텍스트에 넣는 순수 데이터라 한 줄도 섞이면 안 된다.
407
400
 
401
+ ### 회수 점수 — 긴 요약이 이기지 않는다
402
+
403
+ 회수는 `stem + summary + tags`(head)에 질의어가 걸리는지 보고, CJK 는 부분문자열로
404
+ 맞춘다. 그래서 **head 가 길면 그물이 커진다** — 관련성과 무관하게 우연한 히트가 늘고,
405
+ 주입은 상위 3줄뿐이라 그 편향이 곧 탈락이다.
406
+
407
+ 실측(2026-08-27, 실볼트 결정 420건 · Claude Code 트랜스크립트 894개):
408
+
409
+ | 요약 길이 | 평균 주입 횟수 |
410
+ |---|---|
411
+ | 0~66자 | 0.4회 |
412
+ | 66~101자 | 2.4회 |
413
+ | 101~155자 | 4.7회 |
414
+ | **155~1760자** | **18.7회** |
415
+
416
+ 47배다. 상위 5개 노트가 전체 주입의 40.3%를 먹었고 **420건 중 228건(54%)이 단 한 번도
417
+ 안 떴다.** 교차 프로젝트 주입이 57.3%였는데 어시스턴트가 그 노트를 이후에 언급한 것은
418
+ 4.4%뿐이었던 이유가 여기다.
419
+
420
+ 그래서 head 히트에 **BM25 의 길이 정규화**를 걸었다:
421
+
422
+ ```
423
+ norm = (k+1) / (1 + k*((1-b) + b*len/ref)) k=1.2, b=0.5, ref=200자
424
+ ```
425
+
426
+ - **1.0 을 넘지 않는다.** 짧은 head 에 가점을 주지 않으므로 `ref`(200자) 아래의 노트는
427
+ 점수가 한 점도 안 바뀐다 — 이 변경은 "긴 head 감점" 하나다.
428
+ - **바닥이 1 이다.** 감점의 목적은 순위를 낮추는 것이지 안 보이게 하는 것이 아니다.
429
+ head 히트가 반올림으로 0 이 되어 노트가 사라지지 않는다.
430
+ - **본문 히트는 정규화하지 않는다.** 가중치가 1 이라 순위를 뒤집는 힘이 없고, 본문
431
+ 길이까지 재면 "짧게 쓴 결정문이 유리하다" 는 잘못된 유인이 생긴다.
432
+
433
+ 계수는 스윕으로 골랐다. 정답을 아는 질의 720건(파일명에서 뽑은 것 414 + 요약의 **뒤쪽**
434
+ 1/3 에서 뽑은 것 306)으로 회귀를 재고, **정답 순위가 나빠지지 않는 가장 센 설정**을 썼다:
435
+
436
+ | 설정 | Q4/Q1 편향 | 주입된 서로 다른 문서 | slug MRR | 요약뒤쪽 MRR |
437
+ |---|---|---|---|---|
438
+ | 없음(옛 동작) | 10.0배 | 227 | 0.921 | 0.945 |
439
+ | **b=0.5 ref=200** | **5.0배** | **258** | **0.922** | **0.952** |
440
+ | b=0.75 ref=180 | 4.2배 | 266 | 0.908 | 0.936 |
441
+ | b=0.5 ref=120 | 3.8배 | 266 | 0.899 | 0.936 |
442
+
443
+ **요약을 N자로 잘라 head 를 만드는 안은 기각했다.** 절단선 뒤의 낱말이 head 에서 통째로
444
+ 사라지고, 요약은 본문에 복사돼 있지 않아 body 히트로도 안 걸린다. 위 표의 `요약뒤쪽`
445
+ 세트가 그것을 재는 자리이고, 정규화는 그 세트의 MRR 을 **올리면서** 같은 편향을 잡는다.
446
+
447
+ ### 규칙 (`type: rule`) — 도메인 없는 판단 기준
448
+
449
+ `_meta/rules/*.md` 에 `type: rule` 로 둔 노트는 **결정과 다른 계층**이다. 회수가 따로
450
+ 훑어 자기 자리에 넣고, 주입 블록 맨 위에 `[규칙]` 로 나온다.
451
+
452
+ ```
453
+ [과거 결정 참조]
454
+ - [규칙] 안 하면 확실히 실패하고 해도 손해가 0 인 비대칭이면, 검증을 앞세우지 않고 먼저 넣는다. → _meta/rules/규칙-한쪽-손해가-0이면-검증보다-먼저-넣는다.md
455
+ - 2026-08-27 GP-1561 은 DOM 검증 전에 코드를 먼저 넣는다 — … (active/good) → editup/decisions/…
456
+ ```
457
+
458
+ **왜 필요한가.** 결정 414건의 요약 중 규칙·기준 어휘를 담은 것이 99건(24%)이고 나머지
459
+ 76%는 사건 서술이다 — "GP-1561 실동작 검증 완료, 주소는 wcms" 는 그 프로젝트 밖에서
460
+ 쓸 것이 없다. 전이되는 것은 "downside 가 0 이면 검증보다 먼저 넣는다" 같은 규칙인데,
461
+ 그 규칙이 `editup-결정-gp1561-…` 이라는 **사건 이름 안에 갇혀** 있었다. 도메인 쌍
462
+ 어휘 Jaccard 평균이 0.046 이라 낱말도 안 겹친다.
463
+
464
+ 계약은 셋이다.
465
+
466
+ - **도메인이 없다.** 파일에 `domain` 이 적혀 있어도 회수가 지운다. 도메인을 가지면
467
+ `weightCwdDomain`(+2)이 자기 폴더에서만 붙어 다시 한 프로젝트의 것이 된다.
468
+ - **자리가 따로 있다** (`Options.RuleLimit`, 훅은 2). 결정과 섞어 자르면 규칙이 언제나
469
+ 진다 — 규칙 요약은 한 줄이고 결정 요약은 중앙 184자다. 반대로 규칙이 결정 슬롯을
470
+ 먹어서도 안 된다.
471
+ - **폴더가 없으면 아무것도 달라지지 않는다.** 점수 계산이 규칙 없던 때와 한 바이트도
472
+ 같다. 켜는 것은 폴더를 만드는 행위 하나이고, 발견 표면은 `prior doctor` 다.
473
+
474
+ `prior doctor` 가 검사하는 것: 건수 · 읽지 못한 규칙 · **출처 결정이 없는 규칙**
475
+ (`related` 가 비었다) · 요약이 200자를 넘는 규칙.
476
+
477
+ **쓰는 명령은 없다.** 규칙은 증류물이라 자동 생성이 아니라 큐레이션이고, `_meta` 는
478
+ 이미 사람이 손으로 관리하는 구역이다(네이밍 규약·회수 동의어). 결정문의 문장을 그대로
479
+ 옮기고 `related` 에 출처를 건다 — 원본이 없으면 그건 규칙이 아니라 의견이다.
480
+
481
+ ### 회수 동의어와 추상화 다리
482
+
483
+ 볼트의 `_meta/00-회수-동의어.md` 는 **질의 쪽**을 넓히는 표다(`- 회수, 불러오기, 검색`).
484
+ 한 묶음의 낱말은 서로를 대신하고, 정확히 맞으면 3점 · 형제 낱말이 맞으면 2점이라
485
+ 정확히 맞은 노트가 언제나 앞선다.
486
+
487
+ 그 표의 「추상화 다리」 절은 종류가 다르다 — 왼쪽이 **지금 겪는 구체 상황의 말**이고
488
+ 오른쪽이 **그 상황을 이미 겪고 남긴 패턴의 이름**이다. 고치려는 고장이 이것이다:
489
+
490
+ | 질의 | 결과 |
491
+ |---|---|
492
+ | `비대칭` | priorcase·editup·mesh 3개 프로젝트 정확히 |
493
+ | "넣을까 말까 고민인데 넣어봐야 손해는 없을것같아" (같은 상황) | **0건** |
494
+
495
+ **유추를 회수하려면 유추의 이름을 이미 알아야 한다. 그런데 그 이름이 바로 얻고 싶은
496
+ 답이다.** 다리가 그 순환을 끊는다.
497
+
498
+ 다리를 고를 때 재는 것은 낱말 하나의 빈도가 아니라 **한 묶음에서 둘이 같이 걸리는
499
+ 빈도**다. 대화체 질의는 히트가 둘 있어야 후보가 되므로(`minHeadHits`) 단독 발화는
500
+ 아무 일도 일으키지 않는다 — 실측으로 `넣어` 는 실제 프롬프트 500개 중 10건 걸리지만
501
+ 같은 묶음의 두 낱말이 같이 걸린 프롬프트는 0건이었다.
502
+
408
503
  ### 유사 slug 는 거부된다
409
504
 
410
505
  같은 결정이 두 노트로 갈라지면 회수가 둘 다 물어오고 어느 쪽이 정본인지 알 수 없게 된다.
@@ -467,15 +562,15 @@ $ prior --config demo-config.toml review priorcase-demo-결정-저장포맷-마
467
562
  ### `PRIORCASE_CONFIG` — 플래그를 못 쓰는 자리용
468
563
 
469
564
  ```
470
- $ PRIORCASE_CONFIG=$PWD/demo-config.toml prior index
471
- 색인 3행 생성
565
+ $ PRIORCASE_CONFIG=$PWD/demo-config.toml prior doctor | head -1
566
+ 설정 <cwd>/demo-config.toml
472
567
  ```
473
568
 
474
569
  플래그가 환경변수를 이긴다.
475
570
 
476
571
  ```
477
- $ PRIORCASE_CONFIG=/없는/경로.toml prior --config demo-config.toml index
478
- 색인 3행 생성
572
+ $ PRIORCASE_CONFIG=/없는/경로.toml prior --config demo-config.toml doctor | head -1
573
+ 설정 <cwd>/demo-config.toml
479
574
  ```
480
575
 
481
576
  ## MCP 서버로 쓰기
@@ -889,10 +984,31 @@ CI 는 `gofmt -l` · `go vet` · `go test -race` 를 돌린다.
889
984
 
890
985
  PRIORCASE_TEST_VAULT="$HOME/Documents/Obsidian Vault" go test ./... -run RealVault -v
891
986
 
892
- 실볼트를 **읽기만** 한다 모든 결정 노트가 파싱되는지, 스키마를 통과하는지,
893
- 그리고 **색인 + 건너뛴 노트 == 디스크의 결정 노트** 가 성립하는지 본다.
894
- 등식이 지켜지면 노트는 색인에 들어갔거나 빠졌다고 보고됐거나 중 하나이고,
895
- 조용히 사라진 것은 하나도 없다. 전후 스냅샷을 대조해 쓰지 않았음도 확인한다.
987
+ 실볼트를 **읽기만** 한다. 도메인은 설정이 아니라 **폴더 구조에서 유도한다**
988
+ (`<도메인>/decisions/` 있는 최상위 폴더) 사용자 설정에 의존하면 머신에
989
+ 선언되지 않은 도메인이 조용히 빠진 채로 측정된다. 보는 것은 셋이다.
990
+
991
+ 1. 모든 결정 노트가 읽히는지 (하나라도 못 읽으면 실패)
992
+ 2. **정답을 아는 질의 두 벌**의 순위 — 파일명 slug 에서 뽑은 것(길이 중립)과 요약의
993
+ 뒤쪽 1/3 에서 뽑은 것(긴 요약의 꼬리가 살아 있는지). MRR 이 0.80 아래로 떨어지면
994
+ 실패다 — 점수식 상수를 만지다 회수를 깨는 것을 막는 가드다.
995
+ 3. 프롬프트 세트를 같이 주면 **head 길이 4분위별 평균 주입 횟수**까지 잰다.
996
+
997
+ ```
998
+ PRIORCASE_TEST_VAULT=~/Documents/Obsidian\ Vault \
999
+ PRIORCASE_MEASURE_PROMPTS=/tmp/prompts.json \
1000
+ go test ./internal/core/search -run RealVault -v
1001
+ ```
1002
+
1003
+ 프롬프트 세트는 `[{"cwd": "...", "prompt": "..."}, ...]` JSON 이고, Claude Code
1004
+ 트랜스크립트(`~/.claude/projects/*/*.jsonl`)에서 `type == "user"` 이고
1005
+ `promptSource == "typed"` 인 줄의 `cwd` 와 `message.content` 로 만든다.
1006
+ **합성 질의로는 길이 편향이 재현되지 않는다** — 고장의 원인이 대화체 프롬프트의
1007
+ 우연한 부분문자열 히트이기 때문이다.
1008
+
1009
+ 점수식 상수(`refHeadRunes`·`normB`·`weightSynonym`·`penaltySuperseded`)는 전부 이
1010
+ 하네스로 골랐고, **볼트가 커지면 다시 재야 한다.** 절대값은 스냅샷에 묶여 있으므로
1011
+ 봐야 하는 것은 같은 스냅샷의 A/B 다.
896
1012
 
897
1013
  ## 보장 수준
898
1014
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "priorcase",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Record your agent's decisions and surface them at the next judgment point",
5
5
  "keywords": [
6
6
  "mcp",
@@ -24,9 +24,9 @@
24
24
  "node": ">=18"
25
25
  },
26
26
  "optionalDependencies": {
27
- "priorcase-darwin-arm64": "0.2.0",
28
- "priorcase-darwin-x64": "0.2.0",
29
- "priorcase-linux-arm64": "0.2.0",
30
- "priorcase-linux-x64": "0.2.0"
27
+ "priorcase-darwin-arm64": "0.3.0",
28
+ "priorcase-darwin-x64": "0.3.0",
29
+ "priorcase-linux-arm64": "0.3.0",
30
+ "priorcase-linux-x64": "0.3.0"
31
31
  }
32
32
  }