leerness 1.35.5 → 1.35.7
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 +30 -0
- package/README.md +4 -4
- package/bin/leerness.js +97 -33
- package/lib/agents.js +11 -5
- package/package.json +1 -1
- package/scripts/e2e.js +48 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.35.7 — 2026-07-03 — 19th GPT5.5pro 평가: verify-claim declared-pass 부풀림 게이팅 + spec 리포터 파싱 + json ok/reasons (UR-0013)
|
|
4
|
+
|
|
5
|
+
**웹 GPT 5.5 Pro 확장의 독립 평가보고서(1.35.6 게시본 대상) 검토 라운드**. 맹신 X 재현 결과 보고서의 헤드라인 버그가 **3경로 전부 실재** — 원인 분석의 소스 위치까지 정확했음. 검토 중 추가 발견 2건(자기모순 출력 + Node v26 파서 FN)도 함께 수정.
|
|
6
|
+
|
|
7
|
+
### 확정 버그 (게시본 1.35.6 재현)
|
|
8
|
+
- **declared-pass mismatch 미게이팅 (P1)**: evidence 주장 "3/3 passed" vs 실행 "2/2" 를 감지·표시("⚠ 불일치")하고도 human/`--json`/`--all` 3경로 모두 **exit 0** — 부풀린 완료 주장이 플래그십을 통과. 게다가 human 최종요약은 불일치 출력 직후 "✓ evidence 주장이 실제 파일·테스트·실행 결과와 일치" **자기모순 표기**.
|
|
9
|
+
- **Node v26 파서 FN (추가발견)**: 신형 Node 는 비-TTY 도 spec 리포터가 기본(`ℹ pass 2`) → 기존 5패턴 전부 미매치 → `parsed=null` 로 mismatch 검사 자체가 조용히 스킵.
|
|
10
|
+
|
|
11
|
+
### 수정
|
|
12
|
+
- **부풀림 게이팅 (`_declaredPassMismatch`)**: 실행 pass < 주장 pass 면 human `overallFail` + `--json` exit + collect(`--all`/`gate --claims`) `declared-pass-mismatch` reason 3경로 FAIL. **방향성 게이트** — 실행 ≥ 주장(테스트가 늘어난 정상 흐름)은 무벌점(testCountMatch 의 "실측≥주장" 규칙과 동일 철학, 1.18.1식 거짓차단 FP 회피). 완화: `--lenient`. 요약 라벨도 "✗ FAIL (주장이 실행 결과보다 부풀려짐)" 로 자기모순 제거.
|
|
13
|
+
- **`_parseTestStdout` 순수 추출 + spec 리포터 패턴 6**: 기존 5패턴(비율/jest/mocha/tap/pytest) 원형 유지 + node:test spec 리포터(`pass N`/`fail N` 라인-전체 앵커, denom = pass+fail) 신규 — 선두 비단어문자만 허용해 타 러너 프로즈 오탐 차단(테스트명 "pass 2 (1 ms)" 미매치 확인).
|
|
14
|
+
- **단건 `--json` top-level `ok`/`reasons` (평가 개선제안 ②)**: CI 가 verdict 필드를 재조합할 필요 없이 collect 경로와 동일 어휘. exit 는 reasons 기반 단일 판정으로 통합 — ok↔exit 불일치 원천 차단(기존 게이트 동일).
|
|
15
|
+
|
|
16
|
+
### 검증
|
|
17
|
+
- selftest **267** (신규: 파서 5+스펙 행위 + 게이팅 3경로 와이어 + json ok/reasons). full e2e **382** (신규: 부풀림 exit 1 human/json/--all + spec 파싱 + 정직주장/실행≥주장 무벌점 FP 가드).
|
|
18
|
+
|
|
19
|
+
## 1.35.6 — 2026-07-02 — 18th 위임실증: codex-leerness 준수 라이브 검증 + dispatch harness 브리프 + bench stdin hang 수정
|
|
20
|
+
|
|
21
|
+
**위임 실증 라운드 (사용자 명시: "codex가 leerness를 참조하고 작업을 진행하는지 확인 후 고도화")**. 라이브 재현: leerness init 된 임시 프로젝트에 위임 룰(codex 구현/Claude 검수) 등록 → **leerness 를 전혀 언급하지 않은** 순수 코딩 태스크를 dispatch 레시피 그대로 codex(0.141)에 위임.
|
|
22
|
+
|
|
23
|
+
### 실증 결과 (79 exec 명령 전수 분석)
|
|
24
|
+
- **codex 는 AGENTS.md 경유로 leerness 를 자발 준수함 (강한 준수 확인)**: session-workflow/필수 읽기 순서 11개 파일 → handoff → drift check → scan secrets → route → task add → reuse find/impact → 구현 → node --test 실측 → task update(--evidence) → **verify-claim --run-tests 자기검증(테스트 명령 미지정 경고를 보고 --test-cmd 붙여 재검증까지)** → lens code/test → lazy detect → session close. progress-tracker 에 T-0002 done + 실측 증거 기록 확인.
|
|
25
|
+
|
|
26
|
+
### 수정 (실증에서 발견)
|
|
27
|
+
- **dispatch harness 위임 브리프 자동 접두 (`_harnessBrief`, 안전망)**: codex 준수는 (a) cwd=프로젝트 루트, (b) AGENTS.md 지원 CLI, (c) 자발 준수 3조건 의존 → dispatch 태스크 앞에 4단계 계약(handoff 적재 → task add → evidence 기록+verify-claim → session close)을 자동 접두. 셸 임베드 안전(backtick/달러/쌍따옴표-free). `--raw` 로 원문 위임.
|
|
28
|
+
- **agents bench codex stdin hang (확인, 실버그)**: `codex exec` 는 stdin 이 열린 파이프면 `Reading additional input from stdin...` 에서 EOF 를 무한 대기(라이브 재현 — 40바이트 출력으로 8분+ hang). bench 의 `cp.spawn(..., { shell: true })` 는 기본 stdio pipe 로 stdin 을 닫지 않아 codex 가 항상 타임아웃으로 왜곡됨 → `stdio: ['ignore','pipe','pipe']`. dispatch 안내에도 비대화형 spawn 시 stdin 닫기 경고 추가.
|
|
29
|
+
|
|
30
|
+
### 검증
|
|
31
|
+
- selftest **266** (신규: `_harnessBrief` 내용/셸-안전 + dispatch 접두 와이어 + bench stdio 가드). full e2e **381/381** (flag-bleed 테스트는 의도 보존형으로 갱신 — 기본: 브리프 뒤 태스크 비오염, --raw: 1.9.435 원형). **npm 배포됨** (사용자 지시, automation token) — 게시본 재실증: selftest 266 + dispatch 브리프/--raw 행위 확인.
|
|
32
|
+
|
|
3
33
|
## 1.35.5 — 2026-06-27 — 17th 버그헌트: scan .json5/.jsonc FN + verify-claim git 매칭 정밀도
|
|
4
34
|
|
|
5
35
|
**비-graph 버그헌트(게시본 1.35.4 후, R-0011)**. Explore 에이전트가 낸 6개 후보를 **맹신 X 재현으로 검증** → 강한 후보 다수가 거짓 판명(멀티라인 시크릿·`test_`고엔트로피 FN은 실제 재현에서 이미 flagged; agent의 `api_secret`↔`secret` 매칭 가정도 word-boundary로 오류), **확정 2건만** 수정.
|
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ MIT
|
|
|
115
115
|
<!-- leerness:project-readme:start -->
|
|
116
116
|
## Leerness Project Harness
|
|
117
117
|
|
|
118
|
-
이 프로젝트는 Leerness v1.35.
|
|
118
|
+
이 프로젝트는 Leerness v1.35.7 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
119
119
|
|
|
120
120
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
121
121
|
|
|
@@ -169,7 +169,7 @@ leerness memory restore decision <date|title>
|
|
|
169
169
|
|
|
170
170
|
### MCP server (외부 AI 통합)
|
|
171
171
|
|
|
172
|
-
Leerness v1.35.
|
|
172
|
+
Leerness v1.35.7는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
|
|
173
173
|
|
|
174
174
|
```jsonc
|
|
175
175
|
// 카테고리별
|
|
@@ -190,7 +190,7 @@ Leerness v1.35.5는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
|
|
|
190
190
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
191
191
|
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) 다음 라운드 예약.
|
|
192
192
|
|
|
193
|
-
현재 누적: **70 라운드 (1.9.40 → 1.35.
|
|
193
|
+
현재 누적: **70 라운드 (1.9.40 → 1.35.7)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
194
194
|
|
|
195
195
|
### 성능 가이드 (1.9.140 측정)
|
|
196
196
|
|
|
@@ -228,6 +228,6 @@ leerness release pack --close --auto-main-push
|
|
|
228
228
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
229
229
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
230
230
|
|
|
231
|
-
Last synced by Leerness v1.35.
|
|
231
|
+
Last synced by Leerness v1.35.7: 2026-07-03
|
|
232
232
|
<!-- leerness:project-readme:end -->
|
|
233
233
|
|
package/bin/leerness.js
CHANGED
|
@@ -32,7 +32,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
|
|
|
32
32
|
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
33
33
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _TOOL_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 분리 · 1.11.4 (UR-0007): _TOOL_CATALOG
|
|
34
34
|
|
|
35
|
-
const VERSION = '1.35.
|
|
35
|
+
const VERSION = '1.35.7';
|
|
36
36
|
|
|
37
37
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
38
38
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -2874,11 +2874,42 @@ function _selfTestCases() {
|
|
|
2874
2874
|
{ name: '_withLock/_updateRun: lost-update 락(O_EXCL+재진입) + 적용 (UR-0043 외부리뷰 1.9.303)', run: () => { const src = read(__filename); const fnOk = typeof _withLock === 'function' && typeof _sleepSyncMs === 'function' && typeof _updateRun === 'function'; const reentrant = /if \(_heldLocks\.has\(lockPath\)\) return fn\(\)/.test(src); const excl = /fs\.openSync\(lockPath, 'wx'\)/.test(src); const applied = /const id = _withLock\(progressPath\(root\)/.test(src) && /_updateRun\(root, curId/.test(src); return fnOk && reentrant && excl && applied; } },
|
|
2875
2875
|
{ name: 'lib/analyzers: 분석/검증 함수 4종 모듈 단일출처 분리 (UR-0025 1.9.304)', run: () => { const m = require('../lib/analyzers'); return m._evidenceQuality === _evidenceQuality && m._shellGuardAnalyze === _shellGuardAnalyze && m._parseEvidenceStats === _parseEvidenceStats && m._claimFileInGit === _claimFileInGit && !/function _evidenceQuality\(evidence\) \{/.test(read(__filename)) && !/function _shellGuardAnalyze\(cmd, ctx\) \{/.test(read(__filename)); } },
|
|
2876
2876
|
{ name: '17th헌트: _claimFileInGit bare-basename 충돌 차단 + scan .json5/.jsonc 포함 (1.35.5)', run: () => { const a = require('../lib/analyzers'); const collisionFixed = a._claimFileInGit('src/test.js', new Set(['test.js'])) !== true; const forwardOk = a._claimFileInGit('test.js', new Set(['src/test.js'])) === true; const exactOk = a._claimFileInGit('src/a.js', new Set(['src/a.js'])) === true; const nestedReverseOk = a._claimFileInGit('x/src/a.js', new Set(['src/a.js'])) === true; const src = read(__filename); const json5Ext = src.includes("'.js" + "on5'") && src.includes("'.js" + "onc'"); return collisionFixed && forwardOk && exactOk && nestedReverseOk && json5Ext; } },
|
|
2877
|
+
{ name: '18th 위임실증: _harnessBrief 계약 브리프(handoff/task/verify-claim/session close, backtick·달러-free) + dispatch 접두 와이어 + bench stdin ignore (1.35.6)', run: () => {
|
|
2878
|
+
// (1) 브리프 내용: 위임 계약 4단계 포함 + 셸 임베드 안전(backtick/달러 금지 — command substitution 차단).
|
|
2879
|
+
const b = _harnessBrief();
|
|
2880
|
+
const briefOk = typeof b === 'string' && b.includes('leerness handoff .') && b.includes('task add') && b.includes('verify-claim') && b.includes('session close') && b.includes('--evidence') && !b.includes('`') && !b.includes('$') && !b.includes('"');
|
|
2881
|
+
// (2) dispatch 와이어: lib/agents.js 가 --raw 옵트아웃으로 브리프를 접두하고, DI 로 _harnessBrief 를 받음.
|
|
2882
|
+
const agentsSrc = read(path.join(__dirname, '..', 'lib', 'agents.js'));
|
|
2883
|
+
const wired = /has\('--raw'\)[\s\S]{0,120}_harnessBrief\(\) \+ task/.test(agentsSrc) && /_harnessBrief,/.test(read(__filename));
|
|
2884
|
+
// (3) bench stdin hang 수정: codex exec 가 열린 stdin 파이프에서 EOF 대기(실측) → spawn stdio ignore.
|
|
2885
|
+
const stdinFixed = agentsSrc.includes("{ shell: true, stdio: ['ignore', 'pipe', 'pipe'] }");
|
|
2886
|
+
return briefOk && wired && stdinFixed;
|
|
2887
|
+
} },
|
|
2888
|
+
{ name: '19th GPT5.5평가 (UR-0013): _parseTestStdout 러너 인식(jest/tap/spec/비율) + declared-pass 부풀림 3경로 게이팅 + json ok/reasons (1.35.7)', run: () => {
|
|
2889
|
+
// (1) 파서 행위: 기존 4형식 보존 + spec 리포터(패턴 6) 신규 — denom = pass + fail.
|
|
2890
|
+
const p = _parseTestStdout;
|
|
2891
|
+
const tap = p('# tests 2\n# pass 2\n# fail 0');
|
|
2892
|
+
const spec = p('ℹ tests 2\nℹ suites 0\nℹ pass 2\nℹ fail 0\n');
|
|
2893
|
+
const specFail = p('ℹ pass 3\nℹ fail 1\n');
|
|
2894
|
+
const jest = p('Tests: 5 passed, 6 total');
|
|
2895
|
+
const ratio = p('12/12 passed');
|
|
2896
|
+
const parseOk = tap && tap.num === 2 && tap.denom === 2 && spec && spec.num === 2 && spec.denom === 2
|
|
2897
|
+
&& specFail && specFail.num === 3 && specFail.denom === 4 && jest && jest.num === 5 && jest.denom === 6
|
|
2898
|
+
&& ratio && ratio.num === 12 && p('no test output here') === null && p(' ✓ pass 2 (1 ms) extra') === null;
|
|
2899
|
+
// (2) 게이팅 와이어: 3경로(human overallFail / json reasons / collect reasons) + 방향성(실행 pass < 주장 pass 만 FAIL).
|
|
2900
|
+
const src = read(__filename);
|
|
2901
|
+
const gateWired = /overallFail = [^\n]*_declaredPassMismatch/.test(src)
|
|
2902
|
+
&& (src.match(/reasons\.push\('declared-pass-mismatch'\)/g) || []).length === 2
|
|
2903
|
+
&& src.includes('runResult.parsed.num < declaredPass.num');
|
|
2904
|
+
// (3) 단건 --json top-level ok/reasons — exit 는 reasons 기반 단일 판정.
|
|
2905
|
+
const jsonOk = src.includes('out.ok = out.reasons.length === 0') && src.includes('if (!out.ok) return process.exit(1)');
|
|
2906
|
+
return parseOk && gateWired && jsonOk;
|
|
2907
|
+
} },
|
|
2877
2908
|
{ name: 'honesty-check: AI 인식론적 정직성 3차원 + MCP/CLI/strict 통합 (사용자명시 1.9.305)', run: () => { const h = _epistemicHonestyCheck; const d1 = h('이 기능은 항상 정상 동작합니다').findings.some(f => f.dim === 'pretend-knowledge'); const d2 = h('아마 될 것 같습니다. 구현 완료했습니다').findings.some(f => f.dim === 'premature-judgment'); const d3 = h('이 API 의 rate limit 은 초당 5회입니다').findings.some(f => f.dim === 'no-info-gathering'); const clean = h('src/api.js 수정, 12/12 통과 (Exit: 0)').ok === true; const src = read(__filename); const wired = require('../lib/mcp-tools').some(t => t.name === 'leerness_honesty_check') && /if \(cmd === 'honesty-check'\)/.test(src) && /honestyFindings = _epistemicHonestyCheck/.test(src); return d1 && d2 && d3 && clean && wired; } },
|
|
2878
2909
|
{ name: 'exit code 일관성: fail()→exitCode 1 행위 + unknown 명령 안내 (UR-0045 / CV-5 행위화 1.9.366)', run: () => { if (typeof fail !== 'function') return false; const saved = process.exitCode; const _w = process.stdout.write; let setOk = false; try { process.stdout.write = () => true; process.exitCode = 0; fail('selftest probe'); setOk = process.exitCode === 1; } finally { process.stdout.write = _w; process.exitCode = saved; } const src = read(__filename); const dispatchOk = /알 수 없는 명령: \$\{cmd\}/.test(src); return setOk && dispatchOk; } },
|
|
2879
2910
|
{ name: 'brief: 프로젝트 청사진 set/show/export + README 개요 섹션 (UR-0055 사용자명시 1.9.307)', run: () => { const src = read(__filename); const fnOk = typeof briefCmd === 'function' && typeof _loadBrief === 'function' && typeof _briefBlueprint === 'function' && _BRIEF_FIELDS.length === 10; const b = { project: 'X', intro: 'i', purpose: 'p', problem: '', features: ['f1', 'f2'], stack: ['s'], architecture: '', users: [], success: [], nonGoals: [], currentState: '' }; const bp = _briefBlueprint(b, VERSION); const bpOk = /Blueprint/.test(bp) && /소개 \(Intro\)/.test(bp) && /f1/.test(bp) && /신규 프로젝트 시작 가이드/.test(bp); const rb = _briefReadmeBlock(b); const rbOk = rb.includes(BRIEF_START) && rb.includes(BRIEF_END) && /프로젝트 개요/.test(rb) && /\*\*목적\*\*/.test(rb); return fnOk && bpOk && rbOk && /if \(cmd === 'brief'\)/.test(src); } },
|
|
2880
2911
|
{ name: 'brief 2단계: update --direction 이력 + MCP leerness_brief + context 통합 (UR-0055 1.9.308)', run: () => { const src = read(__filename); const b = { project: 'X', intro: '', purpose: '', problem: '', features: [], stack: [], architecture: '', users: [], success: [], nonGoals: [], currentState: '', directionHistory: ['2026-06-04: 확대'] }; const bpOk = /개발 방향 이력/.test(_briefBlueprint(b, VERSION)) && /최근 개발 방향 변경/.test(_briefReadmeBlock(b)); const histWired = /sub === 'update'/.test(src) && /brief\.directionHistory \|\| \[\]\), `\$\{today\(\)\}/.test(src); const mcpOk = require('../lib/mcp-tools').some(t => t.name === 'leerness_brief'); const ctxOk = /brief: \{ intro:/.test(src); return bpOk && histWired && mcpOk && ctxOk; } },
|
|
2881
|
-
{ name: 'verify-claim: done 주장 evidence 기본강제 + --lenient + MCP/json 도달 (UR-0048 설치리뷰 critical 1.9.309)', run: () => { const src = read(__filename); const def = /const mustHaveEvidence = !has\('--lenient'\) && \(isDoneClaim \|\| has\('--require-evidence'\)\)/.test(src); const threshold = /has\('--require-evidence'\) \? evq\.ok : \(evq\.hasFile \|\| evq\.hasTest \|\| evq\.hasLog\)/.test(src); const jsonWired = /evidenceComplete:/.test(src) &&
|
|
2912
|
+
{ name: 'verify-claim: done 주장 evidence 기본강제 + --lenient + MCP/json 도달 (UR-0048 설치리뷰 critical 1.9.309)', run: () => { const src = read(__filename); const def = /const mustHaveEvidence = !has\('--lenient'\) && \(isDoneClaim \|\| has\('--require-evidence'\)\)/.test(src); const threshold = /has\('--require-evidence'\) \? evq\.ok : \(evq\.hasFile \|\| evq\.hasTest \|\| evq\.hasLog\)/.test(src); const jsonWired = /evidenceComplete:/.test(src) && /if \(!evidenceQualityOk\) out\.reasons\.push\('evidence-incomplete'\)/.test(src) && /if \(!out\.ok\) return process\.exit\(1\)/.test(src); /* 1.35.7: reasons 기반 단일 exit 재배선(의도 동일 — evidence 게이트가 json exit 도달) */ const mcpLenient = !!require('../lib/mcp-tools').find(t => t.name === 'leerness_verify_claim').inputSchema.properties.lenient; return def && threshold && jsonWired && mcpLenient; } },
|
|
2882
2913
|
{ name: '입력 스키마 검증: task status/rule trigger 무효값 거부 + every-round 보존 (UR-0046 설치리뷰 1.9.310)', run: () => { const src = read(__filename); const sets = TASK_STATUSES.has('done') && TASK_STATUSES.has('in-progress') && !TASK_STATUSES.has('nonsense') && RULE_TRIGGERS.has('every-round') && RULE_TRIGGERS.has('every-update') && !RULE_TRIGGERS.has('not-a-trigger'); const helper = typeof _validateChoice === 'function' && _validateChoice('done', TASK_STATUSES, 'x') === true; const wired = /_validateChoice\(arg\('--status', null\), TASK_STATUSES/.test(src) && /_validateChoice\(trigger, RULE_TRIGGERS/.test(src); return sets && helper && wired; } },
|
|
2883
2914
|
{ name: 'init 가드: 미초기화 write 차단 + 다중마커 판별 + --force 우회 (UR-0047 설치리뷰 1.9.311)', run: () => { const src = read(__filename); const fnOk = typeof _isInitialized === 'function' && typeof _requireInit === 'function'; const _fix = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_initfix_')); let liveOk = false; try { fs.writeFileSync(path.join(_fix, 'AGENTS.md'), 'x'); liveOk = _isInitialized(_fix) === true; } finally { try { fs.rmSync(_fix, { recursive: true, force: true }); } catch {} } const emptyOk = _isInitialized(path.join(os.tmpdir(), '__leerness_noinit_marker__')) === false; const wired = ["task add", "task update", "plan add", "decision add", "rule add", "lesson save", "brief set"].every(l => src.includes(`_requireInit(root, '${l}')`)) && !src.includes("_requireInit(root, 'state " + "start')"); const force = /if \(_isInitialized\(root\) \|\| has\('--force'\)\) return true/.test(src); return fnOk && liveOk && emptyOk && wired && force; } },
|
|
2884
2915
|
{ name: 'secret 스캐너 현대 키: OpenAI proj/svcacct·Anthropic api03(_)·GitHub 변종·Stripe·npm 검출 + 오탐 가드 (UR-0050 설치리뷰 1.9.312)', run: () => { const hit = (s) => SECRET_PATTERNS.some(p => { p.re.lastIndex = 0; return p.re.test(s); }); const named = (s, nm) => SECRET_PATTERNS.some(p => { p.re.lastIndex = 0; return p.re.test(s) && p.name === nm; }); const A = 'A'.repeat(40); const projKey = 'sk-' + 'proj-' + A + '_' + A; const svcKey = 'sk-' + 'svcacct-' + A; const antKey = 'sk-' + 'ant-api03-' + A + '_' + A; const ghoKey = 'gho_' + 'a1B2'.repeat(9); const stripeKey = 'sk_' + 'live_' + A; const npmKey = 'npm_' + 'a1B2'.repeat(9); const asiaKey = 'ASIA' + 'ABCD1234EFGH5678'; const legacy = 'sk-' + A; const hits = hit(projKey) && hit(svcKey) && hit(antKey) && hit(ghoKey) && hit(stripeKey) && hit(npmKey) && hit(asiaKey) && hit(legacy); const names = named(projKey, 'OpenAI project/service key') && named(antKey, 'Anthropic API key') && named(stripeKey, 'Stripe secret key') && named(npmKey, 'npm token'); const clean = !hit('const userName = "john' + '_doe_2024";') && !hit('https://example.com/path/to/page'); return hits && names && clean; } },
|
|
@@ -10427,6 +10458,37 @@ function _vcImplIsEmpty(body) {
|
|
|
10427
10458
|
return residue === ''; // 의미 토큰이 하나도 안 남으면 스텁
|
|
10428
10459
|
}
|
|
10429
10460
|
|
|
10461
|
+
// 1.35.7 (UR-0013, GPT5.5pro 평가): --run-tests stdout 파서 순수 추출 — selftest 단위검증 가능 + spec 리포터 인식.
|
|
10462
|
+
// 패턴 1~5는 1.9.20/UR-0045 원형 그대로, 6(node:test spec 리포터 "pass N"/"fail N" 라인)만 신규.
|
|
10463
|
+
// 추가발견 배경: Node v26 은 비-TTY 에서도 spec 리포터가 기본 → TAP "# pass N" 이 없어 parsed=null,
|
|
10464
|
+
// declared-pass 불일치 검사가 조용히 스킵됐음. 라인-전체 앵커(선두 비단어문자만 허용)로 타 러너 프로즈 오탐 차단.
|
|
10465
|
+
function _parseTestStdout(out) {
|
|
10466
|
+
out = String(out || '');
|
|
10467
|
+
// 1) X/Y passing|passed|pass|통과
|
|
10468
|
+
const m = out.match(/(\d+)\s*\/\s*(\d+)\s*(?:passed|통과|pass|passing)/i);
|
|
10469
|
+
if (m) return { num: parseInt(m[1], 10), denom: parseInt(m[2], 10) };
|
|
10470
|
+
// 2) jest: "Tests: N passed, M total" — 통과 + 총
|
|
10471
|
+
const m2 = out.match(/Tests?:\s*(?:\d+\s*failed,\s*)?(\d+)\s*passed(?:,\s*(\d+)\s*total)?/i);
|
|
10472
|
+
if (m2) return { num: parseInt(m2[1], 10), denom: parseInt(m2[2] || m2[1], 10) };
|
|
10473
|
+
// 3) mocha: "N passing" — 단독 패턴이면 total = passing
|
|
10474
|
+
const m3 = out.match(/^\s*(\d+)\s+passing\b/im);
|
|
10475
|
+
if (m3) return { num: parseInt(m3[1], 10), denom: parseInt(m3[1], 10) };
|
|
10476
|
+
// 4) tap: "# pass N"
|
|
10477
|
+
const m4 = out.match(/#\s*pass\s+(\d+)/i);
|
|
10478
|
+
if (m4) return { num: parseInt(m4[1], 10), denom: parseInt(m4[1], 10) };
|
|
10479
|
+
// 5) pytest: "N passed in 0.12s" (UR-0045 — 파이썬 러너 출력 인식)
|
|
10480
|
+
const m5 = out.match(/(\d+)\s+passed\b/i);
|
|
10481
|
+
if (m5) return { num: parseInt(m5[1], 10), denom: parseInt(m5[1], 10) };
|
|
10482
|
+
// 6) node:test spec 리포터: "ℹ pass 2" / "ℹ fail 0" — denom = pass + fail (fail 라인 없으면 0)
|
|
10483
|
+
const mp6 = out.match(/^[^\w\r\n]*pass\s+(\d+)\s*$/im);
|
|
10484
|
+
if (mp6) {
|
|
10485
|
+
const mf6 = out.match(/^[^\w\r\n]*fail\s+(\d+)\s*$/im);
|
|
10486
|
+
const p6 = parseInt(mp6[1], 10);
|
|
10487
|
+
return { num: p6, denom: p6 + (mf6 ? parseInt(mf6[1], 10) : 0) };
|
|
10488
|
+
}
|
|
10489
|
+
return null;
|
|
10490
|
+
}
|
|
10491
|
+
|
|
10430
10492
|
function verifyClaimCmd(root, taskId, opts = {}) {
|
|
10431
10493
|
root = absRoot(root);
|
|
10432
10494
|
const _j = has('--json'); // 1.9.400 (7번째 버그헌트 P1-B, UR-0105): --json 에러도 구조화
|
|
@@ -10567,31 +10629,8 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
10567
10629
|
runResult = { skipped: true, reason: `테스트 명령 차단(${r.error}) — '${testCmd}' 실행 불가 (불일치 판정 아님). leerness permissions set extended 또는 allowList 에 추가 권장` };
|
|
10568
10630
|
} else {
|
|
10569
10631
|
const out = (r.stdout || '') + (r.stderr || '');
|
|
10570
|
-
// 1.9.20
|
|
10571
|
-
|
|
10572
|
-
// 1) X/Y passing|passed|pass|통과
|
|
10573
|
-
let m = out.match(/(\d+)\s*\/\s*(\d+)\s*(?:passed|통과|pass|passing)/i);
|
|
10574
|
-
if (m) parsed = { num: parseInt(m[1], 10), denom: parseInt(m[2], 10) };
|
|
10575
|
-
// 2) jest: "Tests: N passed, M total" — 통과 + 총
|
|
10576
|
-
if (!parsed) {
|
|
10577
|
-
const m2 = out.match(/Tests?:\s*(?:\d+\s*failed,\s*)?(\d+)\s*passed(?:,\s*(\d+)\s*total)?/i);
|
|
10578
|
-
if (m2) parsed = { num: parseInt(m2[1], 10), denom: parseInt(m2[2] || m2[1], 10) };
|
|
10579
|
-
}
|
|
10580
|
-
// 3) mocha: "N passing" — 단독 패턴이면 total = passing
|
|
10581
|
-
if (!parsed) {
|
|
10582
|
-
const m3 = out.match(/^\s*(\d+)\s+passing\b/im);
|
|
10583
|
-
if (m3) parsed = { num: parseInt(m3[1], 10), denom: parseInt(m3[1], 10) };
|
|
10584
|
-
}
|
|
10585
|
-
// 4) tap: "# pass N" 또는 "ok N"
|
|
10586
|
-
if (!parsed) {
|
|
10587
|
-
const m4 = out.match(/#\s*pass\s+(\d+)/i);
|
|
10588
|
-
if (m4) parsed = { num: parseInt(m4[1], 10), denom: parseInt(m4[1], 10) };
|
|
10589
|
-
}
|
|
10590
|
-
// 5) pytest: "N passed in 0.12s" (UR-0045 — 파이썬 러너 출력 인식)
|
|
10591
|
-
if (!parsed) {
|
|
10592
|
-
const m5 = out.match(/(\d+)\s+passed\b/i);
|
|
10593
|
-
if (m5) parsed = { num: parseInt(m5[1], 10), denom: parseInt(m5[1], 10) };
|
|
10594
|
-
}
|
|
10632
|
+
// 1.9.20 패턴 1~5 + 1.35.7 spec 리포터(패턴 6) — _parseTestStdout 순수 함수로 추출 (UR-0013, selftest 단위검증)
|
|
10633
|
+
const parsed = _parseTestStdout(out);
|
|
10595
10634
|
runResult = {
|
|
10596
10635
|
skipped: false,
|
|
10597
10636
|
cmd: testCmd,
|
|
@@ -10631,6 +10670,11 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
10631
10670
|
const gitStrongMismatch = gitApplicable && claimedInGit.length === 0; // 변경 있는데 주장 파일이 git 변경에 전무
|
|
10632
10671
|
// 1.11.2 (UR-0175): git strongMismatch 는 기본 게이트에서 제외(advisory) — 커밋 후 검증(정상 흐름)에서 커밋된 파일이 working-tree 변경에 없어 false-fail 하므로. --strict-claims 시에만 FAIL 기여. 기본 게이트는 신뢰도 높은 optimism(claimsConsistent)이 담당.
|
|
10633
10672
|
const gitClaimOk = !(has('--strict-claims') && gitStrongMismatch);
|
|
10673
|
+
// 1.35.7 (UR-0013, GPT5.5pro 평가 확정버그): declared-pass 부풀림 게이팅 — 주장 비율(예: 3/3)의 pass 가 실행 pass(예: 2)보다 크면 FAIL.
|
|
10674
|
+
// 이전: 3경로(human overallFail / --json exit / collect reasons) 모두 불일치를 감지·표시만 하고 exit 0 — 부풀린 주장이 플래그십을 통과 + 최종요약은 "일치 ✓" 자기모순.
|
|
10675
|
+
// 주장 개수("N개")는 이미 게이팅(testCountMatch)되는데 비율 주장("N/M")만 안 되던 내부 비일관성 해소.
|
|
10676
|
+
// 방향성 게이트(실행 pass ≥ 주장 pass 면 통과) — 테스트가 늘어난 정상 흐름을 벌하지 않음(testCountMatch 의 실측≥주장 규칙과 동일 철학). 완화: --lenient.
|
|
10677
|
+
const _declaredPassMismatch = !lenient && !!(runResult && !runResult.skipped && runResult.parsed && declaredPass && runResult.parsed.num < declaredPass.num);
|
|
10634
10678
|
|
|
10635
10679
|
// 1.33.2 (verify-claim --all): 집계 모드는 렌더/exit 없이 verdict 만 반환. 게이팅 부울은 아래 --json/human 경로와 동일 계산을 공유(분기 없음).
|
|
10636
10680
|
if (opts.collect) {
|
|
@@ -10645,6 +10689,7 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
10645
10689
|
if (claimsChecked && stubFiles.length > 0) reasons.push('stub-impl');
|
|
10646
10690
|
if (has('--strict-claims') && testLinkOk === false) reasons.push('test-impl-unlinked');
|
|
10647
10691
|
if (_runFail) reasons.push('tests-failed');
|
|
10692
|
+
if (_declaredPassMismatch) reasons.push('declared-pass-mismatch'); // 1.35.7 (UR-0013): --all/gate --claims 도 부풀림 게이팅
|
|
10648
10693
|
return { id: taskId, request: row.request, status: row.status, ok: reasons.length === 0, reasons };
|
|
10649
10694
|
}
|
|
10650
10695
|
|
|
@@ -10677,10 +10722,21 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
10677
10722
|
out.verdict.declaredPassMatches = (runResult.parsed.num === declaredPass.num && runResult.parsed.denom === declaredPass.denom);
|
|
10678
10723
|
}
|
|
10679
10724
|
}
|
|
10725
|
+
// 1.35.7 (UR-0013 GPT5.5평가 개선②): top-level ok/reasons — CI 가 verdict 필드를 재조합하지 않아도 되도록 collect 경로와 동일 어휘.
|
|
10726
|
+
// exit 는 reasons 기반 단일 판정(ok↔exit 불일치 원천 차단) — 기존 게이트(1.11.2/1.17.3/1.17.4) 동일 + declared-pass-mismatch 추가.
|
|
10727
|
+
out.reasons = [];
|
|
10728
|
+
if (!filesAllExist) out.reasons.push('files-missing');
|
|
10729
|
+
if (out.verdict.testCountMatch === false) out.reasons.push('test-count-short');
|
|
10730
|
+
if (!evidenceQualityOk) out.reasons.push('evidence-incomplete');
|
|
10731
|
+
if (claimsChecked && !strictOk) out.reasons.push('optimism/honesty');
|
|
10732
|
+
if (!gitClaimOk) out.reasons.push('git-mismatch');
|
|
10733
|
+
if (claimsChecked && stubFiles.length > 0) out.reasons.push('stub-impl');
|
|
10734
|
+
if (has('--strict-claims') && testLinkOk === false) out.reasons.push('test-impl-unlinked');
|
|
10735
|
+
if (runResult && !runResult.skipped && !runResult.allPassed) out.reasons.push('tests-failed');
|
|
10736
|
+
if (_declaredPassMismatch) out.reasons.push('declared-pass-mismatch');
|
|
10737
|
+
out.ok = out.reasons.length === 0;
|
|
10680
10738
|
log(JSON.stringify(out, null, 2));
|
|
10681
|
-
if (
|
|
10682
|
-
// 1.11.2 (UR-0175): --json 도 optimism+git 게이팅 — 머신 경로가 허위완료를 통과시키지 않도록(human 경로와 동일).
|
|
10683
|
-
if (!filesAllExist || out.verdict.testCountMatch === false || !evidenceQualityOk || (claimsChecked && !strictOk) || !gitClaimOk || (claimsChecked && stubFiles.length > 0) || (has('--strict-claims') && testLinkOk === false)) return process.exit(1); // 1.17.3 (UR-0046): 스텁(done 기본) + 테스트 미연결(strict). 1.17.4 (UR-0047): testCountMatch null(측정불가)은 미기여
|
|
10739
|
+
if (!out.ok) return process.exit(1);
|
|
10684
10740
|
return;
|
|
10685
10741
|
}
|
|
10686
10742
|
|
|
@@ -10735,7 +10791,7 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
10735
10791
|
if (runResult && !runResult.skipped) {
|
|
10736
10792
|
// 1.27.1 (13번째 외부리뷰 #3): exit 0 인데 테스트 비율을 못 파싱한 경우(예: 비-테스트 --test-cmd)를 '✓ all passed' 로 거짓표기하지 않음 — '실행됨, 테스트 수 미확인' 으로 정직 표기(판정/exit 불변 → 이색 테스트러너 FP 없음).
|
|
10737
10793
|
log(` - ${runResult.cmd || 'npm test'} ${t('실행', 'run')}: ${runTestsOk ? (runResult.parsed ? '✓ all passed' : t('✓ 실행됨 (exit 0) — 테스트 수 미확인', '✓ ran (exit 0) — test count unconfirmed')) : '✗ FAIL'}`);
|
|
10738
|
-
if (declaredPass) log(` - ${t('주장과 실행 결과 일치', 'claimed matches run')}: ${declaredPassMatchesActual ? '✓ pass' : t('⚠ 다름', '⚠ differs')}`);
|
|
10794
|
+
if (declaredPass) log(` - ${t('주장과 실행 결과 일치', 'claimed matches run')}: ${declaredPassMatchesActual ? '✓ pass' : (_declaredPassMismatch ? t('✗ FAIL (주장이 실행 결과보다 부풀려짐)', '✗ FAIL (claim inflated vs run)') : t('⚠ 다름 (실행 ≥ 주장 — pass)', '⚠ differs (run ≥ claimed — pass)'))}`); // 1.35.7 (UR-0013): 자기모순 표기 제거 — 부풀림은 FAIL 라벨
|
|
10739
10795
|
}
|
|
10740
10796
|
// 1.11.2 (UR-0175): optimism+정직성 — done 주장은 기본 게이팅(claimsChecked). 완화: --lenient.
|
|
10741
10797
|
if (claimsChecked) {
|
|
@@ -10773,7 +10829,7 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
10773
10829
|
} else if (testLinkOk === true && claimsChecked) {
|
|
10774
10830
|
log(` - ${t('테스트-구현 연결', 'test-impl link')}: ${t('✓ pass (테스트가 구현을 참조)', '✓ pass (test references the impl)')}`);
|
|
10775
10831
|
}
|
|
10776
|
-
const overallFail = !allFilesOk || !testOk || (runResult && !runResult.skipped && !runTestsOk) || (claimsChecked && !strictOk) || !evidenceQualityOk || !gitClaimOk || (claimsChecked && stubFiles.length > 0) || (has('--strict-claims') && testLinkOk === false);
|
|
10832
|
+
const overallFail = !allFilesOk || !testOk || (runResult && !runResult.skipped && !runTestsOk) || (claimsChecked && !strictOk) || !evidenceQualityOk || !gitClaimOk || (claimsChecked && stubFiles.length > 0) || (has('--strict-claims') && testLinkOk === false) || _declaredPassMismatch; // 1.35.7 (UR-0013): declared-pass 부풀림 게이팅
|
|
10777
10833
|
// 1.9.287: 정직한 한계 고지 — 테스트 통과 ≠ 의미적 구현 정확성
|
|
10778
10834
|
if (claimsChecked || mustHaveEvidence) {
|
|
10779
10835
|
log('');
|
|
@@ -12516,6 +12572,14 @@ async function setupAgentsCmd(root, opts = {}) {
|
|
|
12516
12572
|
|
|
12517
12573
|
// 1.9.152: 단일 agent dispatch 명령 빌더 — agents dispatch (단일) + agents multi (복수) 가 공유
|
|
12518
12574
|
// 1.9.270: model 인자 추가 — 역할(roles) 기반 dispatch 시 provider 별 모델 플래그 주입 (없으면 기존 동작 그대로).
|
|
12575
|
+
// 1.35.6 (18th 위임실증): 위임 프롬프트 harness 계약 브리프 — dispatch 태스크 앞에 접두.
|
|
12576
|
+
// 실증(2026-07-02, codex 0.141): AGENTS.md 로드 시 codex 는 handoff→task add→verify-claim→session close 를 자발 준수(79 exec 재현).
|
|
12577
|
+
// 단 cwd 가 프로젝트 루트가 아니거나 AGENTS.md 미지원 CLI 면 계약 미전달 → 프롬프트 접두가 안전망.
|
|
12578
|
+
// 셸 명령 문자열에 임베드되므로 backtick/달러 금지 (command substitution/변수 확장 방지) — 인용은 작은따옴표만.
|
|
12579
|
+
function _harnessBrief() {
|
|
12580
|
+
return "[leerness 위임 프로토콜] 시작: 'leerness handoff .' 로 컨텍스트/active rules 적재 후 'leerness task add' 로 이 작업 등록. 완료 전: evidence(수정 파일 경로 + 실제 테스트 결과)를 'leerness task update <T-ID> --status done --evidence ...' 로 기록하고 'leerness verify-claim <T-ID> --run-tests' 로 자기검증. 종료: 'leerness session close .' 실행. --- 작업: ";
|
|
12581
|
+
}
|
|
12582
|
+
|
|
12519
12583
|
function _dispatchCommand(agentId, task, writeMode, model) {
|
|
12520
12584
|
const q = String(task || '').replace(/"/g, '\\"');
|
|
12521
12585
|
const m = model ? String(model).replace(/"/g, '') : '';
|
|
@@ -12535,7 +12599,7 @@ function _dispatchCommand(agentId, task, writeMode, model) {
|
|
|
12535
12599
|
|
|
12536
12600
|
const _agents = require('../lib/agents');
|
|
12537
12601
|
// 1.9.424 (UR-0025/UR-0125 큰 핸들러 모듈화 9번째): agentsCmd → lib/agents.js (DI 위임, rest→array)
|
|
12538
|
-
function agentsCmd(root, sub, ...args) { return _agents.agentsCmd(root, sub, args, { VERSION, has, arg, _agentSlashHint, _allProviders, _checkAgent, _cliChat, _dispatchCommand, _loadEnvFile, _normalizeRole, _policyEnforce, _readUserProviders, _recommendAgent, _recordRun, _resolveRole, _shellQuoteArg, lessonsPath, taskLogPath }); }
|
|
12602
|
+
function agentsCmd(root, sub, ...args) { return _agents.agentsCmd(root, sub, args, { VERSION, has, arg, _agentSlashHint, _allProviders, _checkAgent, _cliChat, _dispatchCommand, _harnessBrief, _loadEnvFile, _normalizeRole, _policyEnforce, _readUserProviders, _recommendAgent, _recordRun, _resolveRole, _shellQuoteArg, lessonsPath, taskLogPath }); }
|
|
12539
12603
|
|
|
12540
12604
|
function personaCmd(root, sub, idOrName, ...rest) {
|
|
12541
12605
|
root = absRoot(root || process.cwd());
|
package/lib/agents.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// lib/agents.js — agents 오케스트레이션 핸들러 (UR-0025/UR-0125 큰 핸들러 모듈화 9번째, 1.9.424)
|
|
2
|
-
// bin/leerness.js 에서 agentsCmd(442줄) 분리. DI: harness 고유 의존(VERSION, has, arg, _agentSlashHint, _allProviders, _checkAgent, _cliChat, _dispatchCommand, _loadEnvFile, _normalizeRole, _policyEnforce, _readUserProviders, _recommendAgent, _recordRun, _resolveRole, _shellQuoteArg, lessonsPath, taskLogPath) 주입.
|
|
2
|
+
// bin/leerness.js 에서 agentsCmd(442줄) 분리. DI: harness 고유 의존(VERSION, has, arg, _agentSlashHint, _allProviders, _checkAgent, _cliChat, _dispatchCommand, _harnessBrief, _loadEnvFile, _normalizeRole, _policyEnforce, _readUserProviders, _recommendAgent, _recordRun, _resolveRole, _shellQuoteArg, lessonsPath, taskLogPath) 주입.
|
|
3
3
|
// io 프리미티브는 ./io, EXTERNAL_AGENTS 는 ./agent-registry, cp/path/fs 빌트인.
|
|
4
4
|
// 시그니처 (root, sub, ...args) → (root, sub, args[], deps): rest 를 배열 인자로 받아 재귀에 deps 전달. 동작 무변경.
|
|
5
5
|
'use strict';
|
|
@@ -10,7 +10,7 @@ const { log, ok, warn, fail, failJson, today, now, absRoot, exists, read, readBu
|
|
|
10
10
|
const { EXTERNAL_AGENTS, AGENT_SLASH_COMMANDS } = require('./agent-registry');
|
|
11
11
|
|
|
12
12
|
function agentsCmd(root, sub, args = [], deps = {}) {
|
|
13
|
-
const { VERSION, has, arg, _agentSlashHint, _allProviders, _checkAgent, _cliChat, _dispatchCommand, _loadEnvFile, _normalizeRole, _policyEnforce, _readUserProviders, _recommendAgent, _recordRun, _resolveRole, _shellQuoteArg, lessonsPath, taskLogPath } = deps;
|
|
13
|
+
const { VERSION, has, arg, _agentSlashHint, _allProviders, _checkAgent, _cliChat, _dispatchCommand, _harnessBrief, _loadEnvFile, _normalizeRole, _policyEnforce, _readUserProviders, _recommendAgent, _recordRun, _resolveRole, _shellQuoteArg, lessonsPath, taskLogPath } = deps;
|
|
14
14
|
root = absRoot(root || process.cwd());
|
|
15
15
|
// 1.9.435 (11th 외부평가 Codex P2, UR-0137): dispatch/multi task 파싱 — flag 값이 task 본문에 흡수되던 버그 수정.
|
|
16
16
|
// 상위(bin)에서 args 는 '--to' flag 만 제거되고 값(codex)은 positional 로 남아 기존 filter 가 task 에 흡수시켰음.
|
|
@@ -284,9 +284,13 @@ function agentsCmd(root, sub, args = [], deps = {}) {
|
|
|
284
284
|
if (roleModel) log(`# 🎭 모델: ${roleModel} (역할 기반 라우팅, 1.9.270)`);
|
|
285
285
|
log('');
|
|
286
286
|
// 1.9.270: _dispatchCommand 로 통일 (roleModel 주입) — 명령 빌더 단일화
|
|
287
|
-
|
|
287
|
+
// 1.35.6 (18th 위임실증): harness 계약 브리프 자동 접두 — codex 실측(0.141)에서 AGENTS.md 로드 시 준수는 확인됐으나,
|
|
288
|
+
// cwd 가 프로젝트 루트가 아니거나 AGENTS.md 미지원 CLI(aider/qwen 등)면 계약이 전달되지 않음 → 프롬프트 접두가 안전망. --raw 로 원문 위임.
|
|
289
|
+
const dispatchTask = (has('--raw') || typeof _harnessBrief !== 'function') ? task : (_harnessBrief() + task);
|
|
290
|
+
log(_dispatchCommand(target, dispatchTask, writeMode, roleModel));
|
|
291
|
+
if (!has('--raw') && typeof _harnessBrief === 'function') log(`# ℹ harness 위임 브리프 자동 접두 (1.35.6) — 원문만 위임하려면 --raw`);
|
|
288
292
|
if (target === 'claude' && writeMode) log(`# ⚠ --dangerously-skip-permissions: 도구 권한 자동 승인 (파일 수정 가능)`);
|
|
289
|
-
if (target === 'codex') { log(`# ℹ codex는 PowerShell 경유 — POSIX /tmp 경로는 C:\\tmp\\로 해석됨`); if (writeMode) log(`# ⚠ --dangerously-bypass-approvals-and-sandbox: sandbox 우회`); }
|
|
293
|
+
if (target === 'codex') { log(`# ℹ codex는 PowerShell 경유 — POSIX /tmp 경로는 C:\\tmp\\로 해석됨`); log(`# ⚠ 비대화형 spawn 시 stdin 을 닫고 실행 — 열린 파이프면 codex 가 'Reading additional input from stdin...' EOF 대기로 hang (1.35.6 실측)`); if (writeMode) log(`# ⚠ --dangerously-bypass-approvals-and-sandbox: sandbox 우회`); }
|
|
290
294
|
if (target === 'agy' && writeMode) log(`# ⚠ --yolo: 워크스페이스 파일 직접 수정 가능`);
|
|
291
295
|
if (target === 'grok' && writeMode) log(`# ⚠ grok --yolo: 자동 승인 (배포판에 따라 플래그 상이 가능)`);
|
|
292
296
|
// 1.9.266 (UR-0021 2단계): 대상 에이전트의 슬래시 명령 힌트 — sub-agent 작업 시 알맞은 슬래시 명령 참조
|
|
@@ -355,7 +359,9 @@ function agentsCmd(root, sub, args = [], deps = {}) {
|
|
|
355
359
|
cmdArgs = ['copilot', 'suggest', qTask];
|
|
356
360
|
cmd = 'gh';
|
|
357
361
|
}
|
|
358
|
-
|
|
362
|
+
// 1.35.6 (18th 위임실증): stdin 'ignore' — codex exec 는 stdin 이 열린 파이프면 'Reading additional input from stdin...' 에서
|
|
363
|
+
// EOF 를 무한 대기해 항상 타임아웃으로 왜곡됨(라이브 재현). 인자 모드 CLI 는 stdin 불필요 → 닫고 spawn.
|
|
364
|
+
const r = cp.spawn(cmd, cmdArgs, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
359
365
|
let stdout = '', stderr = '';
|
|
360
366
|
r.stdout.on('data', d => { stdout += d; });
|
|
361
367
|
r.stderr.on('data', d => { stderr += d; });
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -6493,13 +6493,19 @@ total++;
|
|
|
6493
6493
|
try {
|
|
6494
6494
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-disp-'));
|
|
6495
6495
|
cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
|
|
6496
|
-
const
|
|
6496
|
+
const env = { ...process.env, LEERNESS_ENABLE_CODEX: '1' };
|
|
6497
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', 'REVIEWTASK', '--to', 'codex', '--path', d], { encoding: 'utf8', timeout: 20000, env });
|
|
6497
6498
|
const out = r.stdout || '';
|
|
6498
|
-
//
|
|
6499
|
-
|
|
6499
|
+
// 1.35.6: 기본은 harness 브리프가 접두되므로 task 는 "... 작업: REVIEWTASK" 끝에 그대로(코덱스/경로 흡수 없음).
|
|
6500
|
+
const briefOk = /작업: REVIEWTASK"/.test(out) && !/REVIEWTASK codex"/.test(out) && !/REVIEWTASK.*tmp/.test(out);
|
|
6501
|
+
// --raw 는 1.9.435 원형 보존 — task 가 정확히 "REVIEWTASK" 로만 인용.
|
|
6502
|
+
const rRaw = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', 'REVIEWTASK', '--to', 'codex', '--raw', '--path', d], { encoding: 'utf8', timeout: 20000, env });
|
|
6503
|
+
const outRaw = rRaw.stdout || '';
|
|
6504
|
+
const rawOk = /"REVIEWTASK"/.test(outRaw) && !/"REVIEWTASK codex"/.test(outRaw) && !/REVIEWTASK.*tmp/.test(outRaw) && !/위임 프로토콜/.test(outRaw);
|
|
6505
|
+
ok = briefOk && rawOk;
|
|
6500
6506
|
fs.rmSync(d, { recursive: true, force: true });
|
|
6501
6507
|
} catch {}
|
|
6502
|
-
console.log(ok ? '✓ B(1.9.435) UR-0137: agents dispatch task 에 --to/경로 값 흡수 없음' : '✗ agents dispatch flag bleed');
|
|
6508
|
+
console.log(ok ? '✓ B(1.9.435/1.35.6) UR-0137: agents dispatch task 에 --to/경로 값 흡수 없음 (브리프 접두 + --raw 원형)' : '✗ agents dispatch flag bleed');
|
|
6503
6509
|
if (!ok) failed++;
|
|
6504
6510
|
}
|
|
6505
6511
|
|
|
@@ -6812,5 +6818,43 @@ total++;
|
|
|
6812
6818
|
if (!ok) failed++;
|
|
6813
6819
|
}
|
|
6814
6820
|
|
|
6821
|
+
// 1.35.7 (UR-0013, GPT5.5pro 평가): declared-pass 부풀림 게이팅 — 주장(3/3) vs 실행(2/2) → exit 1 (human/--json/--all 3경로) + spec 리포터 파싱(패턴 6) + 실행≥주장 무벌점.
|
|
6822
|
+
total++;
|
|
6823
|
+
{
|
|
6824
|
+
let ok = false;
|
|
6825
|
+
try {
|
|
6826
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-dpm-'));
|
|
6827
|
+
cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
|
|
6828
|
+
fs.mkdirSync(path.join(d, 'src'), { recursive: true });
|
|
6829
|
+
fs.writeFileSync(path.join(d, 'src', 'x.js'), 'module.exports = { f: () => 1 };\n');
|
|
6830
|
+
fs.writeFileSync(path.join(d, 'x.test.js'), 'test();\ntest();\n');
|
|
6831
|
+
cp.spawnSync(process.execPath, [CLI, 'task', 'add', 'dpm', '--path', d], { encoding: 'utf8', timeout: 15000 });
|
|
6832
|
+
cp.spawnSync(process.execPath, [CLI, 'task', 'update', 'T-0002', '--status', 'done', '--evidence', 'src/x.js implemented, x.test.js added; 3/3 passed', '--path', d], { encoding: 'utf8', timeout: 15000 });
|
|
6833
|
+
// (1) 부풀림(주장 3/3, 실행 2/2 — jest 형식 echo): human exit 1 + FAIL 라벨
|
|
6834
|
+
const r1 = cp.spawnSync(process.execPath, [CLI, 'verify-claim', 'T-0002', '--run-tests', '--test-cmd', 'echo Tests: 2 passed, 2 total', '--path', d], { encoding: 'utf8', timeout: 20000 });
|
|
6835
|
+
const inflatedFails = r1.status === 1 && /부풀려짐|inflated/.test(r1.stdout);
|
|
6836
|
+
// (2) --json: top-level ok=false + reasons 에 declared-pass-mismatch + verdict 필드 유지
|
|
6837
|
+
const r2 = cp.spawnSync(process.execPath, [CLI, 'verify-claim', 'T-0002', '--run-tests', '--test-cmd', 'echo Tests: 2 passed, 2 total', '--json', '--path', d], { encoding: 'utf8', timeout: 20000 });
|
|
6838
|
+
let jsonOk = false; try { const j = JSON.parse(r2.stdout); jsonOk = r2.status === 1 && j.ok === false && j.reasons.includes('declared-pass-mismatch') && j.verdict.declaredPassMatches === false; } catch {}
|
|
6839
|
+
// (3) --all collect: 동일 reason 으로 집계 실패
|
|
6840
|
+
const r3 = cp.spawnSync(process.execPath, [CLI, 'verify-claim', '--all', '--run-tests', '--test-cmd', 'echo Tests: 2 passed, 2 total', '--json', '--path', d], { encoding: 'utf8', timeout: 20000 });
|
|
6841
|
+
let allOk = false; try { const j3 = JSON.parse(r3.stdout); const e3 = (j3.results || []).find(x => x.id === 'T-0002'); allOk = r3.status === 1 && e3 && e3.ok === false && e3.reasons.includes('declared-pass-mismatch'); } catch {}
|
|
6842
|
+
// (4) spec 리포터 파싱(신규 패턴 6): 라인-전체 "pass 2" → parsed 2/2 → 부풀림 감지 exit 1
|
|
6843
|
+
const r4 = cp.spawnSync(process.execPath, [CLI, 'verify-claim', 'T-0002', '--run-tests', '--test-cmd', 'echo pass 2', '--path', d], { encoding: 'utf8', timeout: 20000 });
|
|
6844
|
+
const specParsed = r4.status === 1 && /2\/2/.test(r4.stdout);
|
|
6845
|
+
// (5) 정직 주장(2/2 = 실행) → exit 0 (FP 없음) / (6) 실행이 주장보다 많음(주장 2/2, 실행 3/3) → 무벌점 exit 0
|
|
6846
|
+
cp.spawnSync(process.execPath, [CLI, 'task', 'update', 'T-0002', '--status', 'done', '--evidence', 'src/x.js implemented, x.test.js added; 2/2 passed', '--path', d], { encoding: 'utf8', timeout: 15000 });
|
|
6847
|
+
const r5 = cp.spawnSync(process.execPath, [CLI, 'verify-claim', 'T-0002', '--run-tests', '--test-cmd', 'echo Tests: 2 passed, 2 total', '--path', d], { encoding: 'utf8', timeout: 20000 });
|
|
6848
|
+
const r6 = cp.spawnSync(process.execPath, [CLI, 'verify-claim', 'T-0002', '--run-tests', '--test-cmd', 'echo Tests: 3 passed, 3 total', '--path', d], { encoding: 'utf8', timeout: 20000 });
|
|
6849
|
+
const truthfulOk = r5.status === 0;
|
|
6850
|
+
const growthOk = r6.status === 0;
|
|
6851
|
+
fs.rmSync(d, { recursive: true, force: true });
|
|
6852
|
+
ok = inflatedFails && jsonOk && allOk && specParsed && truthfulOk && growthOk;
|
|
6853
|
+
if (!ok) console.log(` [dpm 디버그] inflated=${inflatedFails} json=${jsonOk} all=${allOk} spec=${specParsed} truthful=${truthfulOk} growth=${growthOk}`);
|
|
6854
|
+
} catch {}
|
|
6855
|
+
console.log(ok ? '✓ B(1.35.7) UR-0013: declared-pass 부풀림 게이팅(human/json/--all) + spec 리포터 파싱 + 실행≥주장 무벌점' : '✗ declared-pass mismatch 게이팅 실패');
|
|
6856
|
+
if (!ok) failed++;
|
|
6857
|
+
}
|
|
6858
|
+
|
|
6815
6859
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
6816
6860
|
if (failed > 0) process.exit(1);
|