leerness 1.9.445 → 1.11.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/CHANGELOG.md +160 -0
- package/README.md +4 -4
- package/bin/leerness.js +179 -20
- package/lib/io.js +8 -4
- package/lib/pure-utils.js +21 -2
- package/lib/session-close.js +2 -0
- package/package.json +1 -1
- package/scripts/e2e.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,165 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.11.0 — 2026-06-08 — 🛡️ [안정화/Stable] CLI 견고성 + --json 에이전트 계약 안정 minor
|
|
4
|
+
|
|
5
|
+
**🛡️ 안정화(Stable) minor — 13번째 멀티에이전트 버그헌트로 발견·수정한 patch(1.10.1~1.10.5)를 검증·통합해 npm 에 안정 공개.** R-0011 배포 정책의 2번째 minor 릴리스.
|
|
6
|
+
|
|
7
|
+
### 이번 minor 에 담긴 개선 (1.10.1~1.10.5, 그동안 GitHub 에만 누적)
|
|
8
|
+
- **시크릿 스캐너 정밀화** (UR-0144): AWS 공식 예제키 `AKIA…EXAMPLE`(접미사) 오탐 차단, 중간 example 실키는 보존(FN 정책 유지).
|
|
9
|
+
- **--json 에이전트 계약 하드닝** (UR-0146/0173/0174): `init --json` 순수 JSON(배너/진행바/대화 메뉴 억제, io quiet 모드) + `scan secrets`/`encoding check`/`lazy detect`/`deps` 잘못된 경로 구조화 에러(`failJson`, exit 1).
|
|
10
|
+
- **정직성·견고성** (UR-0167): `session close <파일>` 가 실패를 성공(exit 0)으로 오판하던 문제 → 구조화 에러 + exit 1.
|
|
11
|
+
- **CLI 인자 견고성** (UR-0169): `arg()` 가 값 없이 끝나면 `true` 누출 / 다음 `--flag` 흡수하던 문제 → def 반환(음수값 보존).
|
|
12
|
+
- **표·출력 안전화** (UR-0170/0168/0171): plan drop 표셀 `_cellSafe`, handoff --json 배너 억제, env encoding-check positional 디렉토리 존중.
|
|
13
|
+
|
|
14
|
+
### 검증 (회귀 0)
|
|
15
|
+
- **selftest 201 PASS** · **E2E 365/365 PASS** · npm gate=minor_bump(배포) 확인. 13번째 버그헌트 8건 중 7건 수정 + 2건 by-design(UR-0142 intent classify, audit --fix).
|
|
16
|
+
|
|
17
|
+
### 안정화 표시 (R-0006)
|
|
18
|
+
CHANGELOG [안정화/Stable] · git tag annotation (Stable) · GitHub release (Stable) · npm dist-tag `stable` 시도.
|
|
19
|
+
|
|
20
|
+
## 1.10.5 — 2026-06-08 — arg() 플래그 흡수 방지 + handoff --json 배너 억제 (13th 버그헌트 잔여, UR-0168/0169)
|
|
21
|
+
|
|
22
|
+
**🧰 CLI 인자 견고성 + --json 순수성 마무리.**
|
|
23
|
+
|
|
24
|
+
### 변경
|
|
25
|
+
- **arg() 값 플래그 흡수 방지** (UR-0169, P3): `arg(name)` 가 값 없이 끝나면 boolean `true` 를 누출(`--reason` 가 plan.md 에 'true' 기록)하거나, 다음 토큰이 `--flag` 면 그것을 값으로 흡수(`--target --path` → target='--path')하던 문제 → 다음 토큰이 없거나 `--` 장식 플래그면 `def` 반환. **음수 값(`--x -5` 등 단일 `-`)은 보존**.
|
|
26
|
+
- **handoff --json 미-init 배너 억제** (UR-0168, P2): `handoff --json` 이 미-init 디렉토리에서 사람용 "init 미실행" 경고를 JSON 앞에 출력(비-JSON)하던 문제 → `--json` 시 배너 억제(순수 JSON).
|
|
27
|
+
|
|
28
|
+
### 검증 (회귀 0)
|
|
29
|
+
- **selftest 199→201** (arg: 흡수방지+음수보존+정상값, handoff 배너 와이어), 행위 재현: `export --target --path` 미흡수, `--reason`(끝) true 누출 없음, `handoff --json` 미-init 순수 JSON.
|
|
30
|
+
- arg() 는 코어 함수 → **E2E 365 전수**가 회귀 안전망. patch(1.10.5, 같은 minor) — R-0011 정책상 npm 미배포. (13th 잔여: UR-0172 시크릿 prefix FP — FN 회귀 위험으로 신중 검토 예정)
|
|
31
|
+
|
|
32
|
+
## 1.10.4 — 2026-06-08 — 13번째 버그헌트 후속 P2/P3 클러스터 (UR-0167/0170/0171)
|
|
33
|
+
|
|
34
|
+
**🐛 정직성·견고성: 실패 오판 + 표 손상 + 무시되던 인자 수정.**
|
|
35
|
+
|
|
36
|
+
### 변경
|
|
37
|
+
- **session close 디렉토리 가드** (UR-0167, P2-정직성): `session close <파일경로>` 가 `mkdir <path>/.harness` 에서 ENOTDIR 크래시 + **exit 0(실패를 성공으로 오판)** 하던 문제 → 경로 없음/디렉토리 아님이면 `failJson(code:'path_not_found')` + exit 1.
|
|
38
|
+
- **plan drop 표셀 안전화** (UR-0170, P3): `plan drop` 의 text/`--reason` 에 든 파이프(`|`)·개행이 plan.md 마크다운 표 칼럼을 깨뜨리던 문제 → `_cellSafe`(task/rule UR-0104 와 동일, round-trip 가능) 적용.
|
|
39
|
+
- **env encoding-check positional 디렉토리** (UR-0171, P3): `env encoding-check <dir>` 가 positional 인자를 무시하고 cwd 를 스캔(→ 잘못된 디렉토리 false 'no risk') 하던 문제 → 형제 `env check/sync/detect` 와 동일하게 positional 존중.
|
|
40
|
+
|
|
41
|
+
### 검증 (회귀 0)
|
|
42
|
+
- **selftest 198→199**, 행위 재현: session close 파일경로 → exit 1 + JSON, plan drop 파이프 → `\|` escape(round-trip OK), env encoding-check `<dir>` → scanned 정상.
|
|
43
|
+
- patch(1.10.4, 같은 minor) — R-0011 정책상 npm 미배포, GitHub 만 갱신. (13th 잔여: UR-0168 handoff 배너 · UR-0169 arg 값흡수 · UR-0172 시크릿 prefix FP)
|
|
44
|
+
|
|
45
|
+
## 1.10.3 — 2026-06-08 — --json 순수성 하드닝 (13번째 멀티에이전트 버그헌트, UR-0173/0174)
|
|
46
|
+
|
|
47
|
+
**🤖 에이전트 계약 강화: `--json` 이 항상 기계 파싱 가능한 출력만 내도록.**
|
|
48
|
+
|
|
49
|
+
### 변경
|
|
50
|
+
- **init --json TTY 누출 차단** (UR-0173, 1.10.2 회귀): 인터랙티브 터미널에서 `init --json` 이 배너/진행바/언어선택 메뉴를 JSON 앞에 출력하던 문제 → 배너·진행바(`isTty`)에 `!opts.json` 게이트 + `nonInteractive` 적용. 비-TTY(파이프/CI)는 기존에도 정상.
|
|
51
|
+
- **walk 기반 스캐너 --json 경로 오류 구조화** (UR-0174): `scan secrets` / `encoding check` / `lazy detect` 가 없는/파일 경로에서 `walk()` throw → 사람용 `✗ ENOENT/ENOTDIR` 텍스트(비-JSON)를 뱉던 문제 → `status()` 패턴(path 가드 + `failJson(code:'path_not_found')`, exit 1) 통일.
|
|
52
|
+
- **deps --json 인자누락 구조화** (UR-0174): `deps --json` 의 capability 누락이 사람용 텍스트 → `failJson(code:'missing_arg')`.
|
|
53
|
+
|
|
54
|
+
### 13번째 버그헌트 — 후속 등록 (이번 미수정, 백로그)
|
|
55
|
+
- UR-0167 session close --json 파일경로 exit 0 오판 · UR-0168 handoff --json 미-init 배너 누출 · UR-0169 arg() 값없는 플래그 흡수 · UR-0170 plan drop 파이프 표 깨짐 · UR-0171 env encoding-check positional 무시 · UR-0172 시크릿 prefix+단어 placeholder FP(검토).
|
|
56
|
+
|
|
57
|
+
### 검증 (회귀 0)
|
|
58
|
+
- **selftest 196→198** (4종 --json 구조화 에러 spawn 검증 + init 게이트 와이어), 행위 재현: 4종 모두 `{ok:false,code}` exit 1, init --json TTY 순수 JSON.
|
|
59
|
+
- patch(1.10.3, 같은 minor) — R-0011 정책상 npm 미배포, GitHub 만 갱신.
|
|
60
|
+
|
|
61
|
+
## 1.10.2 — 2026-06-08 — init --json 순수 JSON 출력 (12th 외부평가 Codex P3, UR-0146)
|
|
62
|
+
|
|
63
|
+
**🤖 `init --json` 이 사람용 출력 대신 순수 JSON 요약 1개 — AI 에이전트가 JSON.parse 가능.**
|
|
64
|
+
|
|
65
|
+
### 변경
|
|
66
|
+
- `leerness init --json` 이 기존엔 `--json` 을 silent ignore 하고 배너+15줄 사람용 로그를 출력 → 이제 **순수 JSON 요약**: `{ ok, action:'init', version, path, harnessFiles, dryRun, minimal }`.
|
|
67
|
+
- io 에 **quiet 모드(`setQuiet`)** 추가 — `log/ok/warn`(사람용)만 묵음, `fail/failJson`(오류)은 항상 노출. init(install) 큰 핸들러 내부 다중 log 게이팅(침투적) 없이 비침투적 묵음.
|
|
68
|
+
- init 디스패치가 `--json` 시 `setQuiet(true)` → install → `finally setQuiet(false)` → JSON 1개 emit (배너/공지 누출 0).
|
|
69
|
+
|
|
70
|
+
### 검증 (회귀 0)
|
|
71
|
+
- **selftest 195→196** (setQuiet: log/ok 묵음 + fail 노출 + 와이어), 행위 재현: `init --json` → 파싱 가능 JSON, 배너 누출 없음.
|
|
72
|
+
- patch(1.10.2, 같은 minor) — R-0011 정책상 npm 미배포, GitHub 만 갱신.
|
|
73
|
+
|
|
74
|
+
## 1.10.1 — 2026-06-08 — 시크릿 스캐너 AWS …EXAMPLE 키 오탐 수정 (12th 외부평가 Opus P3, UR-0144)
|
|
75
|
+
|
|
76
|
+
**🔒 AWS 공식 예제키(AKIA…EXAMPLE) 오탐 차단 — 실키 FN 정책은 보존.**
|
|
77
|
+
|
|
78
|
+
### 변경
|
|
79
|
+
- `_isPlaceholderSecret`: 값이 **'example' 로 끝나면(접미사)** placeholder 로 판정 — AWS 문서 표준 예제키 `AKIAIOSFODNN7EXAMPLE` 등 오탐(FP) 차단.
|
|
80
|
+
- **중간에 'example' 이 있는 실키**(`sk-EXAMPLEab12…`, `sk-proj-realKEYexample…`)는 접미사가 아니라 미해당 → 실키-FN 정책(UR-0105) 보존. 실키가 'example' 로 끝날 확률 0.
|
|
81
|
+
- 직전 시도(1.9.440)에서 'example' 통째 매칭이 FN 정책과 충돌해 보류했던 것을 접미사 한정으로 정밀 해결.
|
|
82
|
+
|
|
83
|
+
### 함께 종결 (코드 변경 없음)
|
|
84
|
+
- **UR-0142** intent classify 항상 default → **by-design**: intent classify 는 precise/broad scope 분류이며 default 는 scope 신호 없는 입력의 정상 중립 결과(맹신 X 직접 재현: "정확히"→precise, "전체 다양한"→broad). 리뷰의 task-type 기대는 오해.
|
|
85
|
+
|
|
86
|
+
### 검증 (회귀 0)
|
|
87
|
+
- **selftest 194→195** (AWS EXAMPLE placeholder + 중간 example 실키 real + 진짜키 탐지), 기존 FN 정책 셀프테스트(UR-0106/0109) 유지.
|
|
88
|
+
- patch(1.10.1, 같은 minor) — R-0011 정책상 npm 미배포, GitHub 만 갱신.
|
|
89
|
+
|
|
90
|
+
## 1.10.0 — 2026-06-08 — 🛡️ [안정화/Stable] 1.10 안정 minor (배포 정책 첫 minor 릴리스)
|
|
91
|
+
|
|
92
|
+
**🛡️ 안정화(Stable) minor — patch 누적(1.9.446~449)을 검증·통합해 npm 에 안정 버전으로 공개.** R-0011 배포 정책(patch 는 누적, minor 만 npm 공개)의 첫 minor 릴리스.
|
|
93
|
+
|
|
94
|
+
### 이번 minor 에 담긴 개선 (1.9.446~449, 그동안 GitHub 에만 누적)
|
|
95
|
+
- **npm 배포 minor-gate** (UR-0160): patch 는 npm 미배포, minor/major 변동 시에만 안정 배포. `release sync-main . --publish-npm` 강제 옵션.
|
|
96
|
+
- **plan progress 읽기전용 요약** (UR-0145): 완료율% + 상태별 + `--json` + 변경의도 인자 경고(silent ignore 제거).
|
|
97
|
+
- **export/prompt = adapter 별칭** (UR-0154, GPT-5.5 §6.6): `export --target claude|cursor|codex|agents-md`.
|
|
98
|
+
- **plan drop 역할 분리** (UR-0143): scope 드랍을 plan.md(Out of Scope)에만 기록, progress-tracker phantom task 제거.
|
|
99
|
+
|
|
100
|
+
### 직전 안정(1.9.x)부터의 핵심 (참고)
|
|
101
|
+
- evidence-first 완료 게이트 `completion_claim_allowed` (UR-0153, GPT-5.5 §6.3), CI/PR 턴키 `ci init` (UR-0152, §6.7), task/add-family positional path (UR-0141/0151), 시크릿 스캐너 prefix placeholder 가드(12th 리뷰 Opus P2), README ASCII 배너.
|
|
102
|
+
|
|
103
|
+
### 검증 (회귀 0)
|
|
104
|
+
- **selftest 194 PASS** · **E2E 365/365 PASS** · npm gate=minor_bump(배포) 확인.
|
|
105
|
+
|
|
106
|
+
### 안정화 표시 (R-0006)
|
|
107
|
+
CHANGELOG [안정화/Stable] · git tag annotation (Stable) · GitHub release (Stable) · npm dist-tag `stable` 시도.
|
|
108
|
+
|
|
109
|
+
## 1.9.449 — 2026-06-08 — plan drop: progress-tracker T- 행 미생성 (12th 외부평가 Sonnet P3, UR-0143)
|
|
110
|
+
|
|
111
|
+
**🐛 plan↔progress 역할 분리: scope 드랍이 phantom task 를 만들던 문제.**
|
|
112
|
+
|
|
113
|
+
### 변경
|
|
114
|
+
- `plan drop` 이 progress-tracker 에 `dropped` 상태의 `T-` task 행을 만들던 동작 제거 — 이제 plan.md `## Out of Scope / Dropped`(D-) 에만 기록. scope 드랍의 단일 출처 = plan.md.
|
|
115
|
+
- 기존엔 scope 드랍 1건이 plan.md D- + progress-tracker T- 양쪽에 생겨 plan↔progress 역할 혼선 + `task list` 노이즈(실제 작업 아닌 phantom T-) 발생.
|
|
116
|
+
|
|
117
|
+
### 검증 (회귀 0)
|
|
118
|
+
- **selftest 193→194** (planDrop 에 dropped-task upsert 없음 + ok 메시지가 plan.md 가리킴), 행위 재현: plan drop → D-0001 기록 + progress T- 행 미증가.
|
|
119
|
+
- patch(1.9.449, 같은 minor) — R-0011 정책상 npm 미배포, GitHub 만 갱신.
|
|
120
|
+
|
|
121
|
+
## 1.9.448 — 2026-06-08 — export/prompt = adapter 별칭 (GPT-5.5 전략리뷰 §6.6/7.3, UR-0154)
|
|
122
|
+
|
|
123
|
+
**🔌 GPT-5.5 권고 표면명 정합: `leerness export/prompt --target <agent>`.**
|
|
124
|
+
|
|
125
|
+
### 변경
|
|
126
|
+
- 신규 `export --target <agent>` / `prompt --target <agent>` — 기존 `adapter <tool>` 의 별칭(도구별 지침/계약 파일 생성: claude/cursor/codex/copilot/goose). `--target` 플래그 또는 positional 모두 지원, 미지정 시 list.
|
|
127
|
+
- `agents-md`/`agents`/`agent` → `codex`(AGENTS.md 생성) 별칭 매핑 — GPT-5.5 가 권고한 `export --target agents-md` 가 그대로 동작.
|
|
128
|
+
- GPT-5.5 전략리뷰가 권고한 명령 표면명(`export`/`prompt --target`)을 기존 adapter 위에 제공 → 외부 사용자 발견성↑.
|
|
129
|
+
|
|
130
|
+
### 검증 (회귀 0)
|
|
131
|
+
- **selftest 192→193** (와이어), 행위 재현: `export --target claude`→CLAUDE.md, `export --target agents-md`→AGENTS.md.
|
|
132
|
+
- patch(1.9.448, 같은 minor) — R-0011 정책상 npm 미배포, GitHub 만 갱신.
|
|
133
|
+
|
|
134
|
+
### 보류 메모
|
|
135
|
+
- UR-0146(init --json 순수화): init=install 큰 핸들러의 다중 log(~15) quiet-mode 게이팅 필요 — P3 대비 침투적이라 quiet-mode 리팩터 라운드로 보류. audit --fix --json 의 fixApplied(=fix모드)/fixed(=카운트)는 의미상 일관(by-design).
|
|
136
|
+
|
|
137
|
+
## 1.9.447 — 2026-06-08 — plan progress 읽기전용 요약 명확화 (12th 외부평가 Codex P3, UR-0145)
|
|
138
|
+
|
|
139
|
+
**🐛 명령명-동작 불일치 해소: `plan progress` 가 인자를 silent ignore 하던 문제.**
|
|
140
|
+
|
|
141
|
+
### 변경
|
|
142
|
+
- `plan progress` 를 읽기 전용 진행 요약으로 명확화 — **완료율%**(done/total) + 상태별 카운트 + 마일스톤 수 출력. `--json` 구조화 지원.
|
|
143
|
+
- `plan progress M-0002 --status … --progress …` 처럼 변경 의도 인자가 오면 **읽기 전용임을 경고**(기존: silent ignore) — 상태/진행 변경은 `task update`/`plan add|drop` 안내.
|
|
144
|
+
- 기존엔 task status 카운트만 출력하며 명령명이 milestone 변경처럼 오인됨.
|
|
145
|
+
|
|
146
|
+
### 검증 (회귀 0)
|
|
147
|
+
- **selftest 191→192** (richer 출력 + --json + 변경의도 경고 와이어), 행위 재현: 완료율 1/3(33%)·--json(total/done/percent)·M-id+--status 시 경고.
|
|
148
|
+
- patch(1.9.447, 같은 minor) — R-0011 정책상 npm 미배포, GitHub 만 갱신.
|
|
149
|
+
|
|
150
|
+
## 1.9.446 — 2026-06-08 — npm 배포 minor-gate (R-0011/UR-0160)
|
|
151
|
+
|
|
152
|
+
**📦 배포 정책 도구화: npm 배포는 minor(x.x) 변동 시에만 — patch(x.x.x) 는 자동 스킵.**
|
|
153
|
+
|
|
154
|
+
### 변경
|
|
155
|
+
- 순수 `_shouldPublishNpm(current, published, force)` + `_minorKey` (pure-utils): npm latest 와 major.minor 비교 → minor/major ↑ 면 배포, 같은 minor 내 patch 면 스킵. 최초/force 는 배포.
|
|
156
|
+
- `_publishToNpm` 에 minor-gate 추가(1.5단계): `npm view <pkg> version` 으로 latest 확인 후, 같은 minor patch 면 npm publish 스킵(GitHub 커밋·CHANGELOG 는 유지). `release sync-main . --publish-npm` 으로 강제 배포.
|
|
157
|
+
- 사용자 정책(R-0011): 자잘한 patch 는 npm 미배포로 누적, 안정화되면 minor 올려 안정 버전만 npm 노출 → npm latest 안정성 향상.
|
|
158
|
+
|
|
159
|
+
### 검증 (회귀 0)
|
|
160
|
+
- **selftest 190→191** (순수 7케이스 + 와이어): 1.9.446 vs 1.9.445=스킵(same_minor), 1.10.0=배포(minor_bump), 2.0.0=배포, force/최초=배포.
|
|
161
|
+
- 본 패치(1.9.446)부터 정책 적용 — npm 미배포(같은 minor), GitHub 만 갱신.
|
|
162
|
+
|
|
3
163
|
## 1.9.445 — 2026-06-08 — add-family positional path 일관화 (UR-0151, UR-0141 후속)
|
|
4
164
|
|
|
5
165
|
**🐛 데이터 오염 일관화: `decision/lesson/rule add` 도 positional path 인식 (task 와 동일).**
|
package/README.md
CHANGED
|
@@ -168,7 +168,7 @@ MIT
|
|
|
168
168
|
<!-- leerness:project-readme:start -->
|
|
169
169
|
## Leerness Project Harness
|
|
170
170
|
|
|
171
|
-
이 프로젝트는 Leerness v1.
|
|
171
|
+
이 프로젝트는 Leerness v1.11.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
172
172
|
|
|
173
173
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
174
174
|
|
|
@@ -222,7 +222,7 @@ leerness memory restore decision <date|title>
|
|
|
222
222
|
|
|
223
223
|
### MCP server (외부 AI 통합)
|
|
224
224
|
|
|
225
|
-
Leerness v1.
|
|
225
|
+
Leerness v1.11.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
|
|
226
226
|
|
|
227
227
|
```jsonc
|
|
228
228
|
// 카테고리별
|
|
@@ -243,7 +243,7 @@ Leerness v1.9.445는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
243
243
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
244
244
|
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
|
|
245
245
|
|
|
246
|
-
현재 누적: **70 라운드 (1.9.40 → 1.
|
|
246
|
+
현재 누적: **70 라운드 (1.9.40 → 1.11.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
247
247
|
|
|
248
248
|
### 성능 가이드 (1.9.140 측정)
|
|
249
249
|
|
|
@@ -281,6 +281,6 @@ leerness release pack --close --auto-main-push
|
|
|
281
281
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
282
282
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
283
283
|
|
|
284
|
-
Last synced by Leerness v1.
|
|
284
|
+
Last synced by Leerness v1.11.0: 2026-06-08
|
|
285
285
|
<!-- leerness:project-readme:end -->
|
|
286
286
|
|
package/bin/leerness.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const cp = require('child_process');
|
|
7
|
-
const { log, ok, warn, fail, failJson, today, now, absRoot, exists, read, readBuf, mkdirp, writeUtf8, append, rel } = require('../lib/io'); // 1.9.382/383 (UR-0025): 출력/시간/파일 프리미티브 공유 모듈
|
|
7
|
+
const { log, ok, warn, fail, failJson, setQuiet, today, now, absRoot, exists, read, readBuf, mkdirp, writeUtf8, append, rel } = require('../lib/io'); // 1.9.382/383 (UR-0025): 출력/시간/파일 프리미티브 공유 모듈 · 1.10.2 (UR-0146): setQuiet
|
|
8
8
|
const os = require('os'); // 1.9.178: _publishToNpm 에서 os.tmpdir() 사용 (전역 import)
|
|
9
9
|
const readline = require('readline');
|
|
10
10
|
// 1.9.274 (UR-0025 1단계): 순수 유틸 함수 모듈 분리 (require-based, 비파괴). selftest 7종이 동작 검증.
|
|
@@ -25,13 +25,13 @@ const { _isSecretKey, _isPlaceholderSecret, _looksSecretLike, _mergeLines, _merg
|
|
|
25
25
|
_withBuiltinSource, _esc, _roadmapTokenStyles, _parseSkillMd,
|
|
26
26
|
_migrationGuideText, _parseContractSpec, _gitignoreMatch,
|
|
27
27
|
_featureGraphTemplate, _parseFeatureGraph, _nextFeatureId, _featureBlock, _featureImpactBfs,
|
|
28
|
-
_parseChangelogBetween, _cellSafe, _cellUnescape, _lineSafe, _parseLimit, _parseAddTitle, _parseImplExports, _taskPositionalPath, _completionClaimAllowed } = require('../lib/pure-utils'); // 1.9.318~
|
|
28
|
+
_parseChangelogBetween, _cellSafe, _cellUnescape, _lineSafe, _parseLimit, _parseAddTitle, _parseImplExports, _taskPositionalPath, _completionClaimAllowed, _minorKey, _shouldPublishNpm } = require('../lib/pure-utils'); // 1.9.318~446 (UR-0025/.../0153/0160): 순수 유틸 모듈 분리
|
|
29
29
|
// 1.9.304 (UR-0025): 순수 분석/검증 함수 모듈 분리.
|
|
30
30
|
const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInGit, _epistemicHonestyCheck } = require('../lib/analyzers');
|
|
31
31
|
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
32
32
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _LSP_LANG_PATTERNS, OPTIMISM_PATTERNS, BUILT_IN_PERSONAS, STRINGS, BUILTIN_CATALOG, ROADMAP_STATUS_LABEL, ROADMAP_STATUS_COLOR, SECRET_PATTERNS, MERGE_OVERWRITE_FILES, MINIMAL_SKIP_KEYS, REQUIRED_WORKSPACE_FILES, KEYWORD_STOPWORDS, SKILL_CATALOG_PRESETS } = require('../lib/catalogs'); // 1.9.344/368/369 (UR-0025): catalog 분리 (MERGE_OVERWRITE_FILES/MINIMAL_SKIP_KEYS 포함)
|
|
33
33
|
|
|
34
|
-
const VERSION = '1.
|
|
34
|
+
const VERSION = '1.11.0';
|
|
35
35
|
|
|
36
36
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
37
37
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -202,7 +202,11 @@ function _getAutoLoopRule(root) {
|
|
|
202
202
|
}
|
|
203
203
|
function arg(name, def = null) {
|
|
204
204
|
const i = process.argv.indexOf(name);
|
|
205
|
-
if (i >= 0)
|
|
205
|
+
if (i >= 0) {
|
|
206
|
+
// 1.10.5 (13th 버그헌트 P3, UR-0169): 값 없음(끝) 또는 다음 토큰이 --장식플래그면 def 반환 — 기존 `argv[i+1] || true` 는 다음 --flag 를 값으로 흡수하거나 boolean true 를 누출(예: --reason 가 'true' 기록)했음. 음수값(-5 등 단일 -)은 값으로 보존.
|
|
207
|
+
const next = process.argv[i + 1];
|
|
208
|
+
return (next != null && !next.startsWith('--')) ? next : def;
|
|
209
|
+
}
|
|
206
210
|
const eq = process.argv.find(a => a.startsWith(name + '=')); // --name=value 형태 (외부리뷰 CV-1/UR-0076)
|
|
207
211
|
return eq ? eq.slice(name.length + 1) : def;
|
|
208
212
|
}
|
|
@@ -930,7 +934,7 @@ async function install(root, opts = {}) {
|
|
|
930
934
|
}
|
|
931
935
|
} catch {}
|
|
932
936
|
// 1.9.32: init 시 ASCII 배너 + 빠른 시작 가이드 (migrate는 quiet)
|
|
933
|
-
if (!opts.migration && !has('--no-banner')) _banner({ quickStart: !opts.dry });
|
|
937
|
+
if (!opts.migration && !has('--no-banner') && !opts.json) _banner({ quickStart: !opts.dry }); // 1.10.3 (UR-0173): --json 시 배너 억제(순수 JSON)
|
|
934
938
|
// 1.9.33: npx 캐시로 옛 버전이 실행될 때 경고 (migrate/--no-stale-check 시 스킵)
|
|
935
939
|
// 1.9.276: dry-run 시 스킵 — 캐시 파일(.harness/cache) 생성 방지 (dry = 부작용 0 보장)
|
|
936
940
|
if (!opts.migration && !has('--no-stale-check') && !opts.nonInteractive && !opts.dry) {
|
|
@@ -973,7 +977,7 @@ async function install(root, opts = {}) {
|
|
|
973
977
|
// 1.9.184 (사용자 명시): 파일 설치 — 생성 목록 나열 X, 로딩바 + 단일 완료 메시지.
|
|
974
978
|
const actions = [];
|
|
975
979
|
const totalFiles = Object.keys(files).length;
|
|
976
|
-
const isTty = process.stdout.isTTY && !opts.dry;
|
|
980
|
+
const isTty = process.stdout.isTTY && !opts.dry && !opts.json; // 1.10.3 (UR-0173): --json 시 진행바/완료메시지 억제(순수 JSON)
|
|
977
981
|
const drawProgress = (done, file) => {
|
|
978
982
|
if (!isTty) return;
|
|
979
983
|
const pct = Math.round(done * 100 / totalFiles);
|
|
@@ -3366,6 +3370,105 @@ function _selfTestCases() {
|
|
|
3366
3370
|
&& m._taskPositionalPath(['rule', 'add', '룰', '--trigger', 'every-update'], 2) === null;
|
|
3367
3371
|
return rule && lesson && decision && trig;
|
|
3368
3372
|
} },
|
|
3373
|
+
{ name: 'R-0011/UR-0160: npm 배포 minor-gate _shouldPublishNpm + _publishToNpm 와이어 (1.9.446)', run: () => {
|
|
3374
|
+
const m = require('../lib/pure-utils');
|
|
3375
|
+
if (typeof m._shouldPublishNpm !== 'function' || m._shouldPublishNpm !== _shouldPublishNpm) return false;
|
|
3376
|
+
const f = m._shouldPublishNpm;
|
|
3377
|
+
const pure = f('1.9.446', '1.9.445', false).publish === false // 같은 minor patch → 미배포
|
|
3378
|
+
&& f('1.9.446', '1.9.445', false).reason === 'same_minor'
|
|
3379
|
+
&& f('1.10.0', '1.9.445', false).publish === true // minor ↑ → 배포
|
|
3380
|
+
&& f('1.10.0', '1.9.445', false).reason === 'minor_bump'
|
|
3381
|
+
&& f('2.0.0', '1.9.445', false).publish === true // major ↑ → 배포
|
|
3382
|
+
&& f('1.9.446', '1.9.445', true).publish === true // force → 배포
|
|
3383
|
+
&& f('1.9.0', null, false).publish === true // 최초 → 배포
|
|
3384
|
+
&& m._minorKey('1.9.445') === '1.9' && m._minorKey('1.10.0') === '1.10';
|
|
3385
|
+
const src = read(__filename);
|
|
3386
|
+
const wired = src.includes('const gate = _shouldPublishNpm(pkgVersion, publishedLatest, false);')
|
|
3387
|
+
&& src.includes("forcePublish: has('--publish-npm')");
|
|
3388
|
+
return pure && wired;
|
|
3389
|
+
} },
|
|
3390
|
+
{ name: '12th 외부평가 Codex P3 (UR-0145): plan progress 읽기전용 요약(완료율%/--json) + 변경의도 인자 경고 (1.9.447)', run: () => {
|
|
3391
|
+
const src = read(__filename);
|
|
3392
|
+
const richer = src.includes('const percent = total ? Math.round((done / total) * 100) : 0;') && src.includes("log(`# plan progress (읽기 전용 요약)`);");
|
|
3393
|
+
const jsonOut = src.includes("log(JSON.stringify({ ok: true, total, done, percent, milestones, counts }, null, 2)); return;");
|
|
3394
|
+
const intentWarn = src.includes('if (opts.updateIntent) warn(') && src.includes("updateIntent: args.slice(2).some(a => /^M-\\d/i.test(a)) || has('--status') || arg('--progress', null) != null");
|
|
3395
|
+
return richer && jsonOut && intentWarn;
|
|
3396
|
+
} },
|
|
3397
|
+
{ name: 'GPT-5.5 전략리뷰 §6.6 (UR-0154): export/prompt = adapter 별칭 (--target + agents-md→codex) (1.9.448)', run: () => {
|
|
3398
|
+
const src = read(__filename);
|
|
3399
|
+
const wired = src.includes("if (cmd === 'export' || cmd === 'prompt') {")
|
|
3400
|
+
&& src.includes("arg('--target', null)")
|
|
3401
|
+
&& src.includes("'agents-md': 'codex'")
|
|
3402
|
+
&& src.includes('return adapterCmd(arg(\'--path\', process.cwd()), _xTool);');
|
|
3403
|
+
return wired;
|
|
3404
|
+
} },
|
|
3405
|
+
{ name: '12th 외부평가 Sonnet P3 (UR-0143): plan drop 은 plan.md(D-)만 기록, progress-tracker T- 행 미생성 (1.9.449)', run: () => {
|
|
3406
|
+
const src = read(__filename);
|
|
3407
|
+
// planDrop 에서 upsertProgress(dropped task) 제거 + ok 메시지가 plan.md 가리킴
|
|
3408
|
+
const noTaskRow = !/function planDrop[\s\S]*?upsertProgress\(root, \{ id: tid, status: 'dropped'/.test(src);
|
|
3409
|
+
const dropsToPlan = src.includes("ok(`plan dropped: ${id} → .harness/plan.md (Out of Scope / Dropped)`);");
|
|
3410
|
+
return noTaskRow && dropsToPlan;
|
|
3411
|
+
} },
|
|
3412
|
+
{ name: '12th 외부평가 Opus P3 (UR-0144): AWS …EXAMPLE 접미사 placeholder + 중간 example 실키 FN 보존 (1.10.1)', run: () => {
|
|
3413
|
+
const m = require('../lib/pure-utils'); const f = m._isPlaceholderSecret;
|
|
3414
|
+
return f('AKIAIOSFODNN7EXAMPLE') === true
|
|
3415
|
+
&& f('sk-EXAMPLEab12cd34ef56gh78ij90kl') === false
|
|
3416
|
+
&& f('sk-proj-realKEYexample9988776655') === false
|
|
3417
|
+
&& f('AKIAJQXMP7RZ2KL9WXYZ') === false
|
|
3418
|
+
&& read(path.join(path.dirname(__filename), '..', 'lib', 'pure-utils.js')).includes('if (/example$/.test(v)) return true;');
|
|
3419
|
+
} },
|
|
3420
|
+
{ name: '12th 외부평가 Codex P3 (UR-0146): init --json 순수 JSON(quiet 모드) + 오류는 quiet 무시 (1.10.2)', run: () => {
|
|
3421
|
+
const io = require('../lib/io');
|
|
3422
|
+
if (typeof io.setQuiet !== 'function') return false;
|
|
3423
|
+
// quiet 면 log/ok/warn 묵음, fail/failJson 은 노출
|
|
3424
|
+
let out = ''; const _w = process.stdout.write; process.stdout.write = s => { out += s; return true; };
|
|
3425
|
+
let quietLogSuppressed, failShown;
|
|
3426
|
+
try { io.setQuiet(true); io.log('HUMAN'); io.ok('OK'); quietLogSuppressed = !/HUMAN|OK/.test(out); out = ''; io.fail('ERR'); failShown = /ERR/.test(out); }
|
|
3427
|
+
finally { io.setQuiet(false); process.stdout.write = _w; process.exitCode = 0; }
|
|
3428
|
+
const src = read(__filename);
|
|
3429
|
+
const wired = src.includes("setQuiet(true);") && src.includes("action: 'init', version: VERSION, path: _initRoot, harnessFiles");
|
|
3430
|
+
return quietLogSuppressed && failShown && wired;
|
|
3431
|
+
} },
|
|
3432
|
+
{ name: '13th 버그헌트 P2 (UR-0174): scan secrets/encoding check/lazy detect/deps --json 잘못된경로 구조화 에러 (1.10.3)', run: () => {
|
|
3433
|
+
const cp = require('child_process'); const osm = require('os');
|
|
3434
|
+
const bad = path.join(osm.tmpdir(), '__leerness_nope_' + process.pid);
|
|
3435
|
+
const isJson = (a) => { const r = cp.spawnSync(process.execPath, [__filename, ...a], { encoding: 'utf8' }); if (r.status !== 1) return false; try { const j = JSON.parse((r.stdout || '').trim()); return j.ok === false && !!j.code; } catch { return false; } };
|
|
3436
|
+
return isJson(['scan', 'secrets', '--path', bad, '--json'])
|
|
3437
|
+
&& isJson(['lazy', 'detect', '--path', bad, '--json'])
|
|
3438
|
+
&& isJson(['encoding', 'check', '--path', __filename, '--json']); // 파일 경로(디렉토리 아님)
|
|
3439
|
+
} },
|
|
3440
|
+
{ name: '13th 버그헌트 P2 (UR-0173): init --json 배너/진행바/대화 억제 와이어 (1.10.3)', run: () => {
|
|
3441
|
+
const src = read(__filename);
|
|
3442
|
+
return src.includes("!has('--no-banner') && !opts.json")
|
|
3443
|
+
&& src.includes('process.stdout.isTTY && !opts.dry && !opts.json')
|
|
3444
|
+
&& src.includes('{ ..._initOpts, json: true, nonInteractive: true }');
|
|
3445
|
+
} },
|
|
3446
|
+
{ name: '13th 버그헌트 P2/P3 (UR-0167/0170/0171): session close 디렉토리 가드 + plan drop _cellSafe + env encoding-check positional (1.10.4)', run: () => {
|
|
3447
|
+
const scSrc = read(path.join(path.dirname(__filename), '..', 'lib', 'session-close.js'));
|
|
3448
|
+
const sc = scSrc.includes("!fs.statSync(root).isDirectory()") && scSrc.includes("'path_not_found'");
|
|
3449
|
+
const src = read(__filename);
|
|
3450
|
+
const planSafe = src.includes('const safeText = _cellSafe(text); const safeReason = _cellSafe(reason);') && src.includes('| ${id} | ${safeText} | ${safeReason} |');
|
|
3451
|
+
const envPos = src.includes("(args[2] && !args[2].startsWith('-')) ? args[2] : arg('--path', process.cwd()), args[1] === 'summary' ? null : 'encoding-check')");
|
|
3452
|
+
return sc && planSafe && envPos;
|
|
3453
|
+
} },
|
|
3454
|
+
{ name: '13th 버그헌트 P3 (UR-0169): arg() 값없음/다음--플래그 흡수 방지 (음수값 보존) (1.10.5)', run: () => {
|
|
3455
|
+
const save = process.argv;
|
|
3456
|
+
try {
|
|
3457
|
+
process.argv = ['node', 'h', '--target', '--path', '/x'];
|
|
3458
|
+
if (arg('--target', null) !== null) return false; // 다음 --flag 흡수 X → def
|
|
3459
|
+
process.argv = ['node', 'h', '--reason'];
|
|
3460
|
+
if (arg('--reason', 'D') !== 'D') return false; // 끝(값없음) → def (true 누출 X)
|
|
3461
|
+
process.argv = ['node', 'h', '--x', '-5'];
|
|
3462
|
+
if (arg('--x', null) !== '-5') return false; // 음수값(단일 -)은 보존
|
|
3463
|
+
process.argv = ['node', 'h', '--target', 'claude'];
|
|
3464
|
+
if (arg('--target', null) !== 'claude') return false; // 정상 값
|
|
3465
|
+
return true;
|
|
3466
|
+
} finally { process.argv = save; }
|
|
3467
|
+
} },
|
|
3468
|
+
{ name: '13th 버그헌트 P2 (UR-0168): handoff 미-init 배너 --json 억제 와이어 (1.10.5)', run: () => {
|
|
3469
|
+
const src = read(__filename);
|
|
3470
|
+
return src.includes("!has('--no-init-check') && !has('--json')) {");
|
|
3471
|
+
} },
|
|
3369
3472
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3370
3473
|
];
|
|
3371
3474
|
}
|
|
@@ -3945,6 +4048,7 @@ function commandsCmd(root) {
|
|
|
3945
4048
|
{ cmd: 'state show|start|record|verify|handoff', desc: '.leerness/ JSON 상태 substrate (에이전트 간 인수인계 표준) — 1.9.278' },
|
|
3946
4049
|
{ cmd: 'adapter <tool>|list [--dry-run]', desc: '도구별 지침/.mcp.json 선택 생성 (claude/cursor/codex/goose/...) — 1.9.280' },
|
|
3947
4050
|
{ cmd: 'ci init [path] [--force]', desc: 'PR 마다 leerness gate 실행하는 GitHub Actions 워크플로 생성 (.github/workflows/leerness-gate.yml) — 1.9.444' },
|
|
4051
|
+
{ cmd: 'export|prompt --target <agent>', desc: 'adapter 별칭 — 도구별 지침/계약 파일 생성 (claude/cursor/codex/agents-md/...) — 1.9.448' },
|
|
3948
4052
|
{ cmd: 'policy show|set|check', desc: '권한 등급 (read-only…publish) — opt-in enforced (위험 명령 차단) — 1.9.281' },
|
|
3949
4053
|
{ cmd: 'reuse-check "<기능>"', desc: '외부 OSS 빌드 vs 재사용 결정 게이트 (오프라인 카테고리+체크리스트) — 1.9.285' },
|
|
3950
4054
|
{ cmd: 'skill impact', desc: '스킬 설치 영향 경량 상관추적 (사용 빈도 ↔ 검증 통과율, advisory) — 1.9.286' },
|
|
@@ -6312,24 +6416,37 @@ function planAdd(root, text) {
|
|
|
6312
6416
|
function planDrop(root, text) {
|
|
6313
6417
|
const id = nextId(root, 'D');
|
|
6314
6418
|
const reason = arg('--reason', '사용자 요청으로 제외');
|
|
6419
|
+
// 1.10.4 (13th 버그헌트 P3, UR-0170): text/reason 의 파이프(|)·개행이 plan.md 마크다운 표 칼럼을 깨뜨림 → _cellSafe 셀 안전화(task/rule UR-0104 와 동일).
|
|
6420
|
+
const safeText = _cellSafe(text); const safeReason = _cellSafe(reason);
|
|
6315
6421
|
const planFile = planPath(root); let p = exists(planFile) ? read(planFile) : '';
|
|
6316
6422
|
const droppedHeader = '## Out of Scope / Dropped';
|
|
6317
6423
|
if (p.includes(droppedHeader)) {
|
|
6318
6424
|
p = p.replace(droppedHeader + '\n| ID | Item | Reason | Date |\n|---|---|---|---|\n',
|
|
6319
|
-
droppedHeader + '\n| ID | Item | Reason | Date |\n|---|---|---|---|\n' + `| ${id} | ${
|
|
6425
|
+
droppedHeader + '\n| ID | Item | Reason | Date |\n|---|---|---|---|\n' + `| ${id} | ${safeText} | ${safeReason} | ${today()} |\n`);
|
|
6320
6426
|
writeUtf8(planFile, p);
|
|
6321
6427
|
} else {
|
|
6322
|
-
append(planFile, `\n${droppedHeader}\n| ID | Item | Reason | Date |\n|---|---|---|---|\n| ${id} | ${
|
|
6428
|
+
append(planFile, `\n${droppedHeader}\n| ID | Item | Reason | Date |\n|---|---|---|---|\n| ${id} | ${safeText} | ${safeReason} | ${today()} |\n`);
|
|
6323
6429
|
}
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
ok(`plan dropped: ${id} →
|
|
6430
|
+
// 1.9.449 (12th 외부평가 Sonnet P3, UR-0143): progress-tracker 에 dropped task(T-) 행 생성 제거 — plan drop 은 plan.md "Out of Scope / Dropped"(D-) 에만 기록.
|
|
6431
|
+
// 기존엔 scope 드랍이 phantom T- task 로도 생겨 plan↔progress 역할 혼선 + task list 노이즈. 드랍 기록의 단일 출처 = plan.md.
|
|
6432
|
+
ok(`plan dropped: ${id} → .harness/plan.md (Out of Scope / Dropped)`);
|
|
6327
6433
|
}
|
|
6328
|
-
function planProgress(root) {
|
|
6434
|
+
function planProgress(root, opts = {}) {
|
|
6435
|
+
// 1.9.447 (12th 외부평가 Codex P3, UR-0145): 읽기 전용 진행 요약 — 완료율%+상태별+마일스톤 수. --json 지원.
|
|
6436
|
+
// 기존엔 task status 카운트만 출력하며 명령명이 milestone 변경처럼 오인됨 + M-id/--status/--progress 를 silent ignore → 변경의도 인자 시 경고.
|
|
6329
6437
|
const rows = readProgressRows(root);
|
|
6438
|
+
const tasks = rows.filter(r => /^T-/.test(r.id));
|
|
6330
6439
|
const counts = {}; for (const s of STATUSES) counts[s] = 0;
|
|
6331
|
-
for (const r of
|
|
6332
|
-
|
|
6440
|
+
for (const r of tasks) if (counts[r.status] != null) counts[r.status]++;
|
|
6441
|
+
const total = tasks.length;
|
|
6442
|
+
const done = counts['done'] || 0;
|
|
6443
|
+
const percent = total ? Math.round((done / total) * 100) : 0;
|
|
6444
|
+
const milestones = rows.filter(r => /^M-/.test(r.id)).length;
|
|
6445
|
+
if (opts.json) { log(JSON.stringify({ ok: true, total, done, percent, milestones, counts }, null, 2)); return; }
|
|
6446
|
+
log(`# plan progress (읽기 전용 요약)`);
|
|
6447
|
+
log(` 완료율: ${done}/${total} (${percent}%)${milestones ? ` · 마일스톤 ${milestones}개` : ''}`);
|
|
6448
|
+
for (const s of STATUSES) if (counts[s]) log(` ${s}: ${counts[s]}`);
|
|
6449
|
+
if (opts.updateIntent) warn('plan progress 는 읽기 전용 요약입니다 — 상태/진행 변경은 task update <T-ID> --status … 또는 plan add|drop 을 사용하세요.');
|
|
6333
6450
|
}
|
|
6334
6451
|
// 1.9.126: plan remove — milestone 블록을 plan.md에서 제거 + archive 보존
|
|
6335
6452
|
// Memory Surface DELETE 5종 완전 완성 (task drop / decision drop / lesson drop / rule remove / plan remove)
|
|
@@ -7209,6 +7326,8 @@ function _collectSecretFindings(root) {
|
|
|
7209
7326
|
|
|
7210
7327
|
function scanSecrets(root, opts = {}) {
|
|
7211
7328
|
root = absRoot(root);
|
|
7329
|
+
// 1.10.3 (13th 버그헌트 P2, UR-0174): 없는 경로에서 --json 비-JSON(ENOENT) 차단 → 구조화 에러. 단 파일 경로는 지원(UR-0072: _collectSecretFindings 가 파일 root 단일 스캔) → 존재성만 검사(디렉토리 강제 X).
|
|
7330
|
+
if (!exists(root)) { failJson(has('--json') || opts.json, 'path_not_found', `경로 없음: ${root}`); return; }
|
|
7212
7331
|
const { committed, ignored } = _collectSecretFindings(root);
|
|
7213
7332
|
// 1.9.415 (9th 외부평가 Opus/Codex): --json 일관성 — 기존엔 --json 무시하고 사람용 텍스트만 출력하던 FN.
|
|
7214
7333
|
if (has('--json') || opts.json) {
|
|
@@ -7230,6 +7349,8 @@ function scanSecrets(root, opts = {}) {
|
|
|
7230
7349
|
|
|
7231
7350
|
function encodingCheck(root, opts = {}) {
|
|
7232
7351
|
root = absRoot(root);
|
|
7352
|
+
// 1.10.3 (13th 버그헌트 P2, UR-0174): 잘못된 경로(없음/파일)에서 walk() throw → --json 비-JSON 차단.
|
|
7353
|
+
if (!exists(root) || !fs.statSync(root).isDirectory()) { failJson(has('--json') || opts.json, 'path_not_found', `경로 없음 또는 디렉토리 아님: ${root}`); return; }
|
|
7233
7354
|
let warnings = 0; const findings = [];
|
|
7234
7355
|
for (const file of walk(root)) {
|
|
7235
7356
|
const ext = path.extname(file).toLowerCase();
|
|
@@ -7276,6 +7397,8 @@ function encodingCheck(root, opts = {}) {
|
|
|
7276
7397
|
function lazyDetect(root, opts = {}) {
|
|
7277
7398
|
root = absRoot(root);
|
|
7278
7399
|
const jsonMode = !!opts.json || has('--json');
|
|
7400
|
+
// 1.10.3 (13th 버그헌트 P2, UR-0174): 잘못된 경로(없음/파일)에서 walk() throw → --json 비-JSON 차단.
|
|
7401
|
+
if (!exists(root) || !fs.statSync(root).isDirectory()) { failJson(jsonMode, 'path_not_found', `경로 없음 또는 디렉토리 아님: ${root}`); return; }
|
|
7279
7402
|
let issues = 0;
|
|
7280
7403
|
const findings = []; // 1.9.101: { kind, severity, message, ...details }
|
|
7281
7404
|
const _warn = (msg, finding) => { if (finding) findings.push(finding); if (!jsonMode) warn(msg); };
|
|
@@ -9129,7 +9252,7 @@ function handoffCmd(root) {
|
|
|
9129
9252
|
// 1.9.35 개선 #1: .harness 부재 시 즉시 경고 (자동 init 권장)
|
|
9130
9253
|
// 사용자가 신규 디렉토리에서 handoff 호출 시 sub-agent 작업이 길을 잃지 않도록.
|
|
9131
9254
|
const absR = absRoot(root || process.cwd());
|
|
9132
|
-
if (!exists(path.join(absR, '.harness')) && !has('--no-init-check')) {
|
|
9255
|
+
if (!exists(path.join(absR, '.harness')) && !has('--no-init-check') && !has('--json')) { // 1.10.5 (13th 버그헌트 P2, UR-0168): --json 시 사람용 미-init 배너 억제(순수 JSON, 앞부분 비-JSON 누출 차단)
|
|
9133
9256
|
const isTty = process.stdout && process.stdout.isTTY;
|
|
9134
9257
|
const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
|
|
9135
9258
|
const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
@@ -9934,7 +10057,7 @@ async function orchestrateCmd(root, goalParts) {
|
|
|
9934
10057
|
// → 회귀 발생 시 어느 프로젝트인지 즉시 보고
|
|
9935
10058
|
function depsImpactCmd(root, targetCapability) {
|
|
9936
10059
|
root = absRoot(root || process.cwd());
|
|
9937
|
-
if (!targetCapability) {
|
|
10060
|
+
if (!targetCapability) { failJson(has('--json'), 'missing_arg', 'impact <capability> 필요. 예: leerness impact Character'); return; } // 1.10.3 (UR-0174): --json 구조화 에러
|
|
9938
10061
|
const paths = _collectWorkspacePaths(root);
|
|
9939
10062
|
if (!paths.length) {
|
|
9940
10063
|
// --all-apps 자동
|
|
@@ -13028,7 +13151,7 @@ function releaseSyncMainCmd(root) {
|
|
|
13028
13151
|
// opt-out: --no-npm 또는 LEERNESS_NO_NPM_PUBLISH=1
|
|
13029
13152
|
// 토큰 미설정 시 친절한 안내 후 skip (실패 X).
|
|
13030
13153
|
if (!has('--no-npm') && process.env.LEERNESS_NO_NPM_PUBLISH !== '1') {
|
|
13031
|
-
try { _publishToNpm(root, { dryRun: has('--dry-run-npm') }); } catch (e) { warn('npm publish 시도 실패 (계속): ' + e.message); }
|
|
13154
|
+
try { _publishToNpm(root, { dryRun: has('--dry-run-npm'), forcePublish: has('--publish-npm') }); } catch (e) { warn('npm publish 시도 실패 (계속): ' + e.message); } // 1.9.446 (UR-0160): --publish-npm 으로 minor-gate 강제 우회
|
|
13032
13155
|
}
|
|
13033
13156
|
}
|
|
13034
13157
|
|
|
@@ -13197,6 +13320,21 @@ function _publishToNpm(root, opts = {}) {
|
|
|
13197
13320
|
}
|
|
13198
13321
|
} catch {} // 네트워크 실패 시 그냥 publish 시도
|
|
13199
13322
|
|
|
13323
|
+
// 1.5) R-0011/UR-0160: npm 배포는 minor(x.x) 변동 시에만 — 같은 minor 내 patch 는 스킵(GitHub/CHANGELOG 는 유지). --publish-npm 으로 강제.
|
|
13324
|
+
if (!opts.forcePublish) {
|
|
13325
|
+
let publishedLatest = null;
|
|
13326
|
+
try {
|
|
13327
|
+
const latestR = cp.spawnSync('npm', ['view', pkgName, 'version'], { cwd: root, encoding: 'utf8', shell: true, timeout: 15000 });
|
|
13328
|
+
if (latestR.status === 0) publishedLatest = (latestR.stdout || '').trim();
|
|
13329
|
+
} catch {}
|
|
13330
|
+
const gate = _shouldPublishNpm(pkgVersion, publishedLatest, false);
|
|
13331
|
+
if (!gate.publish) {
|
|
13332
|
+
log(` ⏸ npm 배포 스킵 (R-0011): patch(${pkgVersion}) 는 npm 미배포 — 직전 npm minor(${_minorKey(publishedLatest) || '?'}) 와 동일. minor 올릴 때만 안정 버전으로 배포. (강제: release sync-main . --publish-npm)`);
|
|
13333
|
+
return;
|
|
13334
|
+
}
|
|
13335
|
+
if (gate.reason === 'minor_bump') log(` ▶ minor 변동(${_minorKey(publishedLatest)} → ${_minorKey(pkgVersion)}) — npm 안정 배포 진행`);
|
|
13336
|
+
}
|
|
13337
|
+
|
|
13200
13338
|
// 2) 임시 .npmrc 생성 (토큰 노출 방지)
|
|
13201
13339
|
let tmpDir;
|
|
13202
13340
|
try {
|
|
@@ -18839,7 +18977,20 @@ async function main() {
|
|
|
18839
18977
|
} catch {}
|
|
18840
18978
|
}
|
|
18841
18979
|
// 1.9.276 (GPT-5.5 2차 리뷰): init --dry-run(미리보기) / --minimal(핵심 파일만) / --no-env(.env 생략)
|
|
18842
|
-
if (cmd === 'init')
|
|
18980
|
+
if (cmd === 'init') {
|
|
18981
|
+
const _initRoot = absRoot(arg('--path', args[1] && !args[1].startsWith('-') ? args[1] : process.cwd()));
|
|
18982
|
+
const _initOpts = { force:has('--force'), dry:has('--dry-run'), migration:false, minimal:has('--minimal'), noEnv:has('--no-env') };
|
|
18983
|
+
if (has('--json')) {
|
|
18984
|
+
// 1.10.2 (UR-0146): init --json — 사람용 출력 묵음(setQuiet) + 순수 JSON 요약 1개. 기존엔 --json 을 silent ignore(배너만 출력).
|
|
18985
|
+
// 1.10.3 (UR-0173): json:true → 배너/진행바 억제(TTY 누출 차단), nonInteractive:true → 대화 메뉴 차단(머신 계약).
|
|
18986
|
+
setQuiet(true);
|
|
18987
|
+
try { await install(_initRoot, { ..._initOpts, json: true, nonInteractive: true }); } finally { setQuiet(false); }
|
|
18988
|
+
let harnessFiles = 0; try { harnessFiles = exists(path.join(_initRoot, '.harness')) ? fs.readdirSync(path.join(_initRoot, '.harness')).length : 0; } catch {}
|
|
18989
|
+
log(JSON.stringify({ ok: true, action: 'init', version: VERSION, path: _initRoot, harnessFiles, dryRun: !!_initOpts.dry, minimal: !!_initOpts.minimal }, null, 2));
|
|
18990
|
+
return;
|
|
18991
|
+
}
|
|
18992
|
+
return await install(_initRoot, _initOpts); // 1.9.355 (UR-0075): --path 지원
|
|
18993
|
+
}
|
|
18843
18994
|
// 1.9.64: install <skill-id-or-url> 별칭 (= skill install). 자주 쓰는 명령 단축형.
|
|
18844
18995
|
// 단, init이 leerness install . 같은 형태로도 동작하던 옛 호환은 유지 — args[1]이 디렉토리면 init으로 라우팅.
|
|
18845
18996
|
if (cmd === 'install') {
|
|
@@ -19002,7 +19153,8 @@ async function main() {
|
|
|
19002
19153
|
// 1.9.241: leerness env summary|encoding — 환경 종합 + 셸 스크립트 인코딩 위험 (사용자 명시 UR-0014)
|
|
19003
19154
|
// 기존 env check/sync/detect (1.9.71) 와 충돌하지 않음 — summary/encoding 신규 서브
|
|
19004
19155
|
if (cmd === 'env' && (args[1] === 'summary' || args[1] === 'encoding' || args[1] === 'encoding-check'))
|
|
19005
|
-
|
|
19156
|
+
// 1.10.4 (13th 버그헌트 P3, UR-0171): positional 디렉토리 인자 존중(형제 env check/sync/detect 와 통일) — 기존엔 무시하고 cwd 스캔 → 잘못된 디렉토리 false 'no risk'.
|
|
19157
|
+
return envCmd((args[2] && !args[2].startsWith('-')) ? args[2] : arg('--path', process.cwd()), args[1] === 'summary' ? null : 'encoding-check');
|
|
19006
19158
|
// 1.9.254: leerness path-setup [--apply] — CLI PATH 자동 등록 (사용자 명시 UR-0019)
|
|
19007
19159
|
// npm global bin 경로 감지 + leerness 가 PATH 에서 찾아지는지 확인 → 미등록 시 플랫폼별 등록
|
|
19008
19160
|
if (cmd === 'path-setup' || cmd === 'path') return pathSetupCmd(arg('--path', process.cwd()), { apply: has('--apply'), json: has('--json') });
|
|
@@ -19039,6 +19191,13 @@ async function main() {
|
|
|
19039
19191
|
const _aRoot = (args[2] && !args[2].startsWith('-')) ? args[2] : arg('--path', process.cwd());
|
|
19040
19192
|
return adapterCmd(_aRoot, _aTool);
|
|
19041
19193
|
}
|
|
19194
|
+
// 1.9.448 (GPT-5.5 전략리뷰 §6.6/7.3, UR-0154): export/prompt = adapter 별칭 — GPT-5.5 권고 표면명 정합(도구별 지침/계약 파일 생성).
|
|
19195
|
+
if (cmd === 'export' || cmd === 'prompt') {
|
|
19196
|
+
const _ADAPTER_ALIAS = { 'agents-md': 'codex', 'agents': 'codex', 'agent': 'codex', 'agents.md': 'codex' };
|
|
19197
|
+
let _xTool = arg('--target', null) || (args[1] && !args[1].startsWith('-') ? args[1] : 'list');
|
|
19198
|
+
_xTool = _ADAPTER_ALIAS[String(_xTool).toLowerCase()] || _xTool;
|
|
19199
|
+
return adapterCmd(arg('--path', process.cwd()), _xTool);
|
|
19200
|
+
}
|
|
19042
19201
|
// 1.9.245: API skill cache — 공식 문서·관련링크 자동 정리 (사용자 명시 UR-0015)
|
|
19043
19202
|
if (cmd === 'api-skill') return apiSkillCmd(arg('--path', process.cwd()), args[1] || 'help');
|
|
19044
19203
|
// 1.9.208: leerness constraints <list|check|add> — 플랫폼/API 제약 사전 체크 (사용자 명시)
|
|
@@ -19095,7 +19254,7 @@ async function main() {
|
|
|
19095
19254
|
if (sub==='add') return planAdd(root, args.slice(2).join(' ') || '새 계획');
|
|
19096
19255
|
if (sub==='drop') return planDrop(root, args.slice(2).join(' ') || '드랍 항목');
|
|
19097
19256
|
if (sub==='remove') return planRemoveCmd(root, args[2]);
|
|
19098
|
-
if (sub==='progress') return planProgress(root);
|
|
19257
|
+
if (sub==='progress') return planProgress(root, { json: has('--json'), updateIntent: args.slice(2).some(a => /^M-\d/i.test(a)) || has('--status') || arg('--progress', null) != null }); // 1.9.447 (UR-0145): --json + 변경의도 인자 경고
|
|
19099
19258
|
if (sub==='sync') return planSync(root);
|
|
19100
19259
|
// 1.9.119: plan list — 모든 milestone JSON/verbose
|
|
19101
19260
|
if (sub==='list') return planListCmd(absRoot(_resolveRoot(args[2])), { json: has('--json') }); // 1.9.412 (UR-0100): positional path 지원
|
package/lib/io.js
CHANGED
|
@@ -6,15 +6,19 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// 1.10.2 (UR-0146): quiet 모드 — 사람용 출력(log/ok/warn) 억제. init --json 등에서 큰 핸들러의 다수 log 를 비침투적으로 묵음 → 순수 JSON 1개만 출력.
|
|
10
|
+
// fail/failJson(오류)은 묵음 대상 아님(에러는 항상 노출). setQuiet 로 토글, 호출부 finally 에서 반드시 복구.
|
|
11
|
+
let _quiet = false;
|
|
12
|
+
function setQuiet(v) { _quiet = !!v; }
|
|
13
|
+
function log(s = '') { if (_quiet) return; console.log(s); }
|
|
10
14
|
function ok(s) { log('✓ ' + s); }
|
|
11
15
|
function warn(s) { log('⚠ ' + s); }
|
|
12
16
|
// fail() 은 오류 신호 → exit code 1 설정 (CI/MCP/에이전트가 실패를 성공으로 오판 방지, UR-0045).
|
|
13
|
-
function fail(s) { log('✗ ' + s); process.exitCode = 1; }
|
|
17
|
+
function fail(s) { console.log('✗ ' + s); process.exitCode = 1; } // quiet 무시(오류는 항상 노출)
|
|
14
18
|
// 1.9.398 (6번째 외부평가/codex P1-C, UR-0099): --json 모드 에러는 구조화 출력 — AI 에이전트가 에러 경로에서 JSON.parse 실패하지 않도록.
|
|
15
19
|
// jsonMode 면 {ok:false,error,code} + exit1, 아니면 사람용 fail(). 양쪽 exit code 1 일관.
|
|
16
20
|
function failJson(jsonMode, code, msg) {
|
|
17
|
-
if (jsonMode) { log(JSON.stringify({ ok: false, error: msg, code }, null, 2)); process.exitCode = 1; }
|
|
21
|
+
if (jsonMode) { console.log(JSON.stringify({ ok: false, error: msg, code }, null, 2)); process.exitCode = 1; } // quiet 무시(오류 JSON 항상 노출)
|
|
18
22
|
else fail(msg);
|
|
19
23
|
}
|
|
20
24
|
function today() { return new Date().toISOString().slice(0, 10); }
|
|
@@ -48,4 +52,4 @@ function writeUtf8(p, s) {
|
|
|
48
52
|
function append(p, s) { mkdirp(path.dirname(p)); fs.appendFileSync(p, s, 'utf8'); }
|
|
49
53
|
function rel(root, p) { return path.relative(root, p).replace(/\\/g, '/') || '.'; }
|
|
50
54
|
|
|
51
|
-
module.exports = { log, ok, warn, fail, failJson, today, now, absRoot, exists, read, readBuf, mkdirp, writeUtf8, append, rel };
|
|
55
|
+
module.exports = { log, ok, warn, fail, failJson, setQuiet, today, now, absRoot, exists, read, readBuf, mkdirp, writeUtf8, append, rel };
|
package/lib/pure-utils.js
CHANGED
|
@@ -652,7 +652,9 @@ function _isPlaceholderSecret(value) {
|
|
|
652
652
|
const hasRealPrefix = /^(?:sk-|sk-proj-|pk_|rk_|akia|ghp_|gho_|ghs_|ghr_|github_pat_|xox[baprs]-|aiza|ya29\.|glpat-|-----begin)/.test(v);
|
|
653
653
|
// 1.9.436 (11th 외부평가 Opus P3): prefix 가 있어도 본문이 동일문자 8+연속(AKIAXXXX…/…00000000…)이면 명백한 더미 → placeholder. 실키는 고엔트로피라 무영향.
|
|
654
654
|
if (/(.)\1{7,}/.test(alnum)) return true;
|
|
655
|
-
// (12th 외부평가 Opus P3
|
|
655
|
+
// 1.10.1 (12th 외부평가 Opus P3, UR-0144): 'example' 로 끝나면(접미사) placeholder — AWS 공식 예제키 AKIAIOSFODNN7EXAMPLE 등.
|
|
656
|
+
// 중간에 'example' 이 있는 실키(sk-EXAMPLEab12…, sk-proj-realKEYexample…)는 접미사 아니라 미해당 → 기존 FN 정책(UR-0105) 보존. 실키는 'example' 로 끝날 확률 0.
|
|
657
|
+
if (/example$/.test(v)) return true;
|
|
656
658
|
// 실키 prefix → 항상 실키(마커 무시). 그 외 마커 단어 있으면 placeholder(고엔트로피여도). prefix 없고 마커 없고 고엔트로피 → 실키.
|
|
657
659
|
if (hasRealPrefix) return false;
|
|
658
660
|
if (hasMarker) return true;
|
|
@@ -1025,7 +1027,9 @@ module.exports = {
|
|
|
1025
1027
|
// 1.9.442 (12th 외부평가, UR-0141): task 계열 positional path 안전 추출
|
|
1026
1028
|
_taskPositionalPath,
|
|
1027
1029
|
// 1.9.443 (GPT-5.5 전략리뷰 §6.3, UR-0153): evidence-first 완료 게이트
|
|
1028
|
-
_completionClaimAllowed
|
|
1030
|
+
_completionClaimAllowed,
|
|
1031
|
+
// 1.9.446 (R-0011/UR-0160): npm 배포 minor-gate
|
|
1032
|
+
_minorKey, _shouldPublishNpm
|
|
1029
1033
|
};
|
|
1030
1034
|
|
|
1031
1035
|
// 1.9.355 (UR-0075 Phase A): AI 에이전트용 크로스버전 마이그레이션 안전 워크플로 가이드 (순수 텍스트). 임시설치 + --path + 백업 + diff 검증.
|
|
@@ -1288,6 +1292,21 @@ function _lineSafe(s) { return String(s == null ? '' : s).replace(/\r\n|\r|\n/g,
|
|
|
1288
1292
|
// 1.9.407 (8번째 버그헌트, UR-0111): --limit 안전 파싱 — NaN(예: '--limit abc')/음수/0 은 slice(0,NaN)=[] 로 모든 결과를 조용히 숨김 → 기본값으로 폴백.
|
|
1289
1293
|
function _parseLimit(raw, def) { const n = parseInt(raw, 10); return (Number.isFinite(n) && n > 0) ? n : def; }
|
|
1290
1294
|
|
|
1295
|
+
// 1.9.446 (R-0011/UR-0160): npm 배포 minor-gate. current(현재 버전) vs published(npm latest) 의 major.minor 비교.
|
|
1296
|
+
// minor 가 올라갔으면(또는 최초/major↑) publish, 같은 minor 내 patch 면 skip. force 면 무조건 publish.
|
|
1297
|
+
function _minorKey(v) { const m = String(v || '').match(/^(\d+)\.(\d+)/); return m ? `${m[1]}.${m[2]}` : null; }
|
|
1298
|
+
function _shouldPublishNpm(current, published, force) {
|
|
1299
|
+
if (force) return { publish: true, reason: 'forced' };
|
|
1300
|
+
const cm = String(current || '').match(/^(\d+)\.(\d+)/);
|
|
1301
|
+
if (!cm) return { publish: false, reason: 'invalid_current' };
|
|
1302
|
+
const pm = String(published || '').match(/^(\d+)\.(\d+)/);
|
|
1303
|
+
if (!pm) return { publish: true, reason: 'no_published' }; // 최초 배포
|
|
1304
|
+
const c = [Number(cm[1]), Number(cm[2])], p = [Number(pm[1]), Number(pm[2])];
|
|
1305
|
+
if (c[0] > p[0] || (c[0] === p[0] && c[1] > p[1])) return { publish: true, reason: 'minor_bump' }; // major/minor ↑
|
|
1306
|
+
if (c[0] === p[0] && c[1] === p[1]) return { publish: false, reason: 'same_minor' }; // patch — 미배포
|
|
1307
|
+
return { publish: false, reason: 'not_ahead' }; // 동일/하위
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1291
1310
|
// 1.9.416 (9th 외부평가 Sonnet/Codex, UR-0122): add 류(task/requests/decision) 제목 파싱 단일 출처.
|
|
1292
1311
|
// positional 을 join 하되 첫 --flag 또는 경로형 토큰(/x, C:\x, ./x, ../x)에서 멈춤 →
|
|
1293
1312
|
// `task add "제목" /some/path` 가 경로를 제목에 흡수하던 오염(decision add 는 이미 차단)을 일관 적용.
|
package/lib/session-close.js
CHANGED
|
@@ -13,6 +13,8 @@ const { _sanitizeFences, _parseArchiveBlocks } = require('./pure-utils');
|
|
|
13
13
|
function sessionClose(root, opts = {}, deps = {}) {
|
|
14
14
|
const { VERSION, STATUSES, MARK, has, arg, harnessPath, readProgressRows, evidencePath, handoffPath, currentStatePath, taskLogPath, verifyRules, _autoRoadmap, _readUsageStats, readSessionCounter, writeSessionCounter, _retroAggregate, _retroOneLine, retroCmd, _loadDecisions, readRules, planPath, _loadLessons, _readFeatureGraph, _auditUserRequests, _detectDeliveredRequests, _computeRoundHistory, _computeMilestones, _computeRecentChanges, _collectPyFiles, _analyzePyFile, _collectRuntimeEnv, _scanShellScriptsEncoding, _listAPISkills, _matchAPISkills, _loadShellFailures, _shellEnvDrift, _runPreWakeAudit, _saveAndAppendPreWakeReport, _runIdempotencyAudit, _detectAbnormalShutdown, _updateUserRequest } = deps;
|
|
15
15
|
root = absRoot(root);
|
|
16
|
+
// 1.10.4 (13th 버그헌트 P2, UR-0167): 경로 없음/디렉토리 아님 → 구조화 에러 + exit 1. mkdir <path>/.harness ENOTDIR 크래시 & 실패를 성공(exit 0)으로 오판하던 문제 차단.
|
|
17
|
+
if (!exists(root) || !fs.statSync(root).isDirectory()) { failJson(!!opts.json || has('--json'), 'path_not_found', `경로 없음 또는 디렉토리 아님: ${root}`); return; }
|
|
16
18
|
// 1.9.103: --json 모드 — stdout 억제 후 구조화 출력
|
|
17
19
|
const jsonMode = !!opts.json || has('--json');
|
|
18
20
|
const _origWrite = process.stdout.write.bind(process.stdout);
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -1457,7 +1457,7 @@ total++;
|
|
|
1457
1457
|
const r = cp.spawnSync(process.execPath, [CLI, 'migrate', tmpC, '--yes', '--no-banner', '--no-stale-check'], { encoding: 'utf8', timeout: 60000 });
|
|
1458
1458
|
const ok = r.status === 0
|
|
1459
1459
|
&& /AI must re-read/.test(r.stdout)
|
|
1460
|
-
&& /1\.9\.36 →
|
|
1460
|
+
&& /1\.9\.36 → \d+\.\d+\.\d+/.test(r.stdout)
|
|
1461
1461
|
&& /신규 명령/.test(r.stdout);
|
|
1462
1462
|
console.log(ok ? '✓ B(1.9.41) migrate stdout: AI must re-read 차분 자동 출력' : `✗ migrate 차분 출력 실패`);
|
|
1463
1463
|
if (!ok) { failed++; console.log(r.stdout.slice(-800)); }
|
|
@@ -1902,7 +1902,7 @@ total++;
|
|
|
1902
1902
|
&& /███████╗/.test(r.stdout)
|
|
1903
1903
|
&& /verify · remember/.test(r.stdout)
|
|
1904
1904
|
&& /AI 에이전트 검수.기억.드리프트 방지 하네스/.test(r.stdout)
|
|
1905
|
-
&& /
|
|
1905
|
+
&& /v\d+\.\d+\.\d+/.test(r.stdout);
|
|
1906
1906
|
console.log(ok ? '✓ B(1.9.34) 배너 색상 + ASCII + 한국어' : `✗ 배너 색상 실패`);
|
|
1907
1907
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1908
1908
|
}
|