leerness 1.9.442 → 1.9.444
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 +25 -0
- package/README.md +4 -4
- package/bin/leerness.js +81 -7
- package/lib/pure-utils.js +19 -1
- package/package.json +1 -1
- package/scripts/e2e.js +43 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.444 — 2026-06-08 — CI/PR 턴키: leerness ci init (GPT-5.5 전략리뷰 §6.7, UR-0152)
|
|
4
|
+
|
|
5
|
+
**⚙️ leerness 를 팀/CI 품질 게이트로 확장 — PR 마다 `leerness gate` 를 실행하는 GitHub Actions 워크플로 1-커맨드 생성.**
|
|
6
|
+
|
|
7
|
+
### 변경
|
|
8
|
+
- 신규 `leerness ci init [path] [--force] [--json]`: `.github/workflows/leerness-gate.yml` 생성 — `on: pull_request` 에서 `npx -y leerness gate .` 실행. gate = verify + audit + scan secrets + encoding + lazy. **exit 1 시 PR 체크 실패** → 증거 없는 완료·커밋 시크릿·인코딩 깨짐·규칙 위반을 CI 에서 차단.
|
|
9
|
+
- 워크플로 상단에 **exit-code 정책 주석** 명시(통과=0 / 실패=1). 멱등(이미 존재 시 경고, `--force` 로 덮어쓰기). `--json` 구조화 출력.
|
|
10
|
+
|
|
11
|
+
### 검증 (회귀 0)
|
|
12
|
+
- **selftest 188→189** (워크플로 콘텐츠 + 와이어), **E2E 신규 B(1.9.444)**: `ci init` → 파일 생성 + 핵심 라인(name/pull_request/gate) 포함 + 재실행 멱등.
|
|
13
|
+
- 행위 재현: 임시 워크스페이스에 워크플로 생성·내용 검증·멱등 확인.
|
|
14
|
+
|
|
15
|
+
## 1.9.443 — 2026-06-08 — evidence-first 완료 게이트 completion_claim_allowed (GPT-5.5 전략리뷰 §6.3/6.4, UR-0153)
|
|
16
|
+
|
|
17
|
+
**🔒 leerness 최대 차별점 강화: 증거로부터 "완료 주장 가능 여부"를 파생·노출.**
|
|
18
|
+
|
|
19
|
+
### 변경
|
|
20
|
+
- 순수 `_completionClaimAllowed(record)` (pure-utils): run-record 증거 기반 완료 게이트 — **변경 파일 존재 + 검증 실행(tests/commands) + 미해결 errors 0 + verification_result === 'pass'** 일 때만 `allowed:true`. 미검증/실패/증거부족은 사유(reasons)와 함께 불허.
|
|
21
|
+
- `state show`(text + `--json`), `state verify --json`, `state handoff`(latest.json + .md + --json) 에 `completion_claim_allowed` 노출 → 다음 에이전트/PR 리뷰어가 증거 게이트를 직접 읽음.
|
|
22
|
+
- GPT-5.5 전략리뷰가 evidence-first 를 핵심 차별점으로 명시 — run-record 14필드 스키마(기존)에 완료 가능성 판정을 더해 "완료 주장 ↔ 증거" 연결을 표면화.
|
|
23
|
+
|
|
24
|
+
### 검증 (회귀 0)
|
|
25
|
+
- **selftest 187→188** (순수 6케이스 + state/handoff 와이어), **E2E 신규 B(1.9.443)**: 증거 없음 → no(사유), 증거+verify pass → allowed.
|
|
26
|
+
- 행위 재현: state start→show(no) → record+verify pass → show(--json allowed=true, reasons=[]).
|
|
27
|
+
|
|
3
28
|
## 1.9.442 — 2026-06-08 — task 계열 positional path 지원 (12th 외부평가 Sonnet P1, UR-0141)
|
|
4
29
|
|
|
5
30
|
**🐛 P1 데이터 오염 수정: `task add "제목" ./경로` / `task list /경로` 가 무시되고 cwd 에 저장되던 문제.**
|
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.9.
|
|
171
|
+
이 프로젝트는 Leerness v1.9.444 하네스를 사용합니다. 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.9.
|
|
225
|
+
Leerness v1.9.444는 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.442는 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.9.
|
|
246
|
+
현재 누적: **70 라운드 (1.9.40 → 1.9.444)** · 매 라운드 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.9.
|
|
284
|
+
Last synced by Leerness v1.9.444: 2026-06-08
|
|
285
285
|
<!-- leerness:project-readme:end -->
|
|
286
286
|
|
package/bin/leerness.js
CHANGED
|
@@ -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 } = require('../lib/pure-utils'); // 1.9.318~
|
|
28
|
+
_parseChangelogBetween, _cellSafe, _cellUnescape, _lineSafe, _parseLimit, _parseAddTitle, _parseImplExports, _taskPositionalPath, _completionClaimAllowed } = require('../lib/pure-utils'); // 1.9.318~443 (UR-0025/0053/0075/0086/0087/0104/0122/0141/0153): 순수 유틸 모듈 분리
|
|
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.9.
|
|
34
|
+
const VERSION = '1.9.444';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -3326,6 +3326,35 @@ function _selfTestCases() {
|
|
|
3326
3326
|
const wired = read(__filename).includes("arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd()");
|
|
3327
3327
|
return pure && wired;
|
|
3328
3328
|
} },
|
|
3329
|
+
{ name: 'GPT-5.5 전략리뷰 §6.3 (UR-0153): evidence-first 완료 게이트 _completionClaimAllowed + state/handoff 노출 (1.9.443)', run: () => {
|
|
3330
|
+
const m = require('../lib/pure-utils');
|
|
3331
|
+
if (typeof m._completionClaimAllowed !== 'function' || m._completionClaimAllowed !== _completionClaimAllowed) return false;
|
|
3332
|
+
const f = m._completionClaimAllowed;
|
|
3333
|
+
const ok = f({ files_changed: ['a.js'], tests_run: ['npm test'], errors: [], verification_result: 'pass' });
|
|
3334
|
+
const noFiles = f({ files_changed: [], tests_run: ['t'], verification_result: 'pass' });
|
|
3335
|
+
const noVerifyRun = f({ files_changed: ['a'], tests_run: [], commands_run: [], verification_result: 'pass' });
|
|
3336
|
+
const failed = f({ files_changed: ['a'], tests_run: ['t'], verification_result: 'fail' });
|
|
3337
|
+
const notVerified = f({ files_changed: ['a'], commands_run: ['c'], verification_result: null });
|
|
3338
|
+
const withErr = f({ files_changed: ['a'], tests_run: ['t'], errors: ['boom'], verification_result: 'pass' });
|
|
3339
|
+
const pure = ok.allowed === true && ok.reasons.length === 0
|
|
3340
|
+
&& noFiles.allowed === false && noFiles.reasons.includes('no_files_changed')
|
|
3341
|
+
&& noVerifyRun.allowed === false && noVerifyRun.reasons.includes('no_verification_run')
|
|
3342
|
+
&& failed.allowed === false && failed.reasons.includes('verification_failed')
|
|
3343
|
+
&& notVerified.allowed === false && notVerified.reasons.includes('not_verified')
|
|
3344
|
+
&& withErr.allowed === false && withErr.reasons.includes('unresolved_errors');
|
|
3345
|
+
const src = read(__filename);
|
|
3346
|
+
const wired = src.includes('completion_claim_allowed: rec ? _completionClaimAllowed(rec) : null') // state show json
|
|
3347
|
+
&& src.includes('completion_claim_allowed: _ccaH'); // handoff json + latest.json
|
|
3348
|
+
return pure && wired;
|
|
3349
|
+
} },
|
|
3350
|
+
{ name: 'GPT-5.5 전략리뷰 §6.7 (UR-0152): ci init — PR gate 워크플로 생성 + exit-code 정책 (1.9.444)', run: () => {
|
|
3351
|
+
if (typeof ciInitCmd !== 'function') return false;
|
|
3352
|
+
const wf = LEERNESS_GATE_WORKFLOW;
|
|
3353
|
+
const contentOk = /name:\s*leerness-gate/.test(wf) && /on:\s*\n\s*pull_request:/.test(wf) && /leerness gate \./.test(wf) && /exit code 정책/.test(wf) && /actions\/checkout@v4/.test(wf);
|
|
3354
|
+
const src = read(__filename);
|
|
3355
|
+
const wired = src.includes("cmd === 'ci' && (args[1] === 'init'") && src.includes('ciInitCmd(absRoot(_resolveRoot(args[2]))');
|
|
3356
|
+
return contentOk && wired;
|
|
3357
|
+
} },
|
|
3329
3358
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3330
3359
|
];
|
|
3331
3360
|
}
|
|
@@ -3904,6 +3933,7 @@ function commandsCmd(root) {
|
|
|
3904
3933
|
{ cmd: 'capabilities [--json]', desc: '권한·보안 표면 공개 (무엇을 하는지 + opt-out + 주의 명령) — 1.9.272' },
|
|
3905
3934
|
{ cmd: 'state show|start|record|verify|handoff', desc: '.leerness/ JSON 상태 substrate (에이전트 간 인수인계 표준) — 1.9.278' },
|
|
3906
3935
|
{ cmd: 'adapter <tool>|list [--dry-run]', desc: '도구별 지침/.mcp.json 선택 생성 (claude/cursor/codex/goose/...) — 1.9.280' },
|
|
3936
|
+
{ cmd: 'ci init [path] [--force]', desc: 'PR 마다 leerness gate 실행하는 GitHub Actions 워크플로 생성 (.github/workflows/leerness-gate.yml) — 1.9.444' },
|
|
3907
3937
|
{ cmd: 'policy show|set|check', desc: '권한 등급 (read-only…publish) — opt-in enforced (위험 명령 차단) — 1.9.281' },
|
|
3908
3938
|
{ cmd: 'reuse-check "<기능>"', desc: '외부 OSS 빌드 vs 재사용 결정 게이트 (오프라인 카테고리+체크리스트) — 1.9.285' },
|
|
3909
3939
|
{ cmd: 'skill impact', desc: '스킬 설치 영향 경량 상관추적 (사용 빈도 ↔ 검증 통과율, advisory) — 1.9.286' },
|
|
@@ -15983,6 +16013,44 @@ function runsShowCmd(root, id) {
|
|
|
15983
16013
|
if (!exists(fp)) return fail(`run 없음: ${id}`);
|
|
15984
16014
|
log(read(fp));
|
|
15985
16015
|
}
|
|
16016
|
+
|
|
16017
|
+
// 1.9.444 (GPT-5.5 전략리뷰 §6.7, UR-0152): CI/PR 턴키 — PR 마다 leerness gate 를 실행하는 GitHub Actions 워크플로 생성.
|
|
16018
|
+
// gate = verify + audit + scan secrets + encoding + lazy. exit 1 시 PR 체크 실패 → 증거 없는 완료/시크릿/규칙 위반을 CI 에서 차단.
|
|
16019
|
+
const LEERNESS_GATE_WORKFLOW = [
|
|
16020
|
+
'# leerness gate — AI 코딩 작업 증거/규칙/검증 게이트 (PR CI). leerness ci init 로 생성.',
|
|
16021
|
+
'# exit code 정책: 통과=0, 실패(테스트 실패 / 커밋 시크릿 / 인코딩 깨짐 / 게으름·증거 누락 / 필수 규칙 파일 없음)=1 → PR 체크 실패.',
|
|
16022
|
+
'name: leerness-gate',
|
|
16023
|
+
'on:',
|
|
16024
|
+
' pull_request:',
|
|
16025
|
+
' workflow_dispatch:',
|
|
16026
|
+
'jobs:',
|
|
16027
|
+
' gate:',
|
|
16028
|
+
' runs-on: ubuntu-latest',
|
|
16029
|
+
' steps:',
|
|
16030
|
+
' - uses: actions/checkout@v4',
|
|
16031
|
+
' - uses: actions/setup-node@v4',
|
|
16032
|
+
' with:',
|
|
16033
|
+
" node-version: '20'",
|
|
16034
|
+
' - name: leerness gate',
|
|
16035
|
+
' run: npx -y leerness gate .',
|
|
16036
|
+
'',
|
|
16037
|
+
].join('\n');
|
|
16038
|
+
function ciInitCmd(root, opts = {}) {
|
|
16039
|
+
root = absRoot(root || process.cwd());
|
|
16040
|
+
const relPath = '.github/workflows/leerness-gate.yml';
|
|
16041
|
+
const fp = path.join(root, '.github', 'workflows', 'leerness-gate.yml');
|
|
16042
|
+
const json = !!opts.json;
|
|
16043
|
+
if (exists(fp) && !opts.force) {
|
|
16044
|
+
if (json) { log(JSON.stringify({ ok: true, created: false, path: relPath, reason: 'exists' })); return; }
|
|
16045
|
+
warn(`이미 존재: ${relPath} (덮어쓰려면 --force)`);
|
|
16046
|
+
return;
|
|
16047
|
+
}
|
|
16048
|
+
mkdirp(path.join(root, '.github', 'workflows'));
|
|
16049
|
+
writeUtf8(fp, LEERNESS_GATE_WORKFLOW);
|
|
16050
|
+
if (json) { log(JSON.stringify({ ok: true, created: true, path: relPath })); return; }
|
|
16051
|
+
ok(`생성: ${relPath} — PR 마다 leerness gate 실행 (exit 1 시 PR 체크 실패)`);
|
|
16052
|
+
log(' gate = verify + audit + scan secrets + encoding + lazy. 증거 없는 완료·시크릿·규칙 위반을 CI 에서 차단.');
|
|
16053
|
+
}
|
|
15986
16054
|
// 1.9.294 (UR-0025 3단계): 역할/모델 카탈로그(_PROVIDER_MODEL_CATALOG + _AGENT_ROLE_PROMPTS + ROLE_CATALOG + _ROLE_ALIASES) 데이터 모듈 분리 (비파괴, require-based).
|
|
15987
16055
|
const { _PROVIDER_MODEL_CATALOG, _AGENT_ROLE_PROMPTS, ROLE_CATALOG, _ROLE_ALIASES } = require('../lib/role-catalog');
|
|
15988
16056
|
function _normalizeRole(name) {
|
|
@@ -16541,7 +16609,7 @@ function stateCmd(root, sub, ...args) {
|
|
|
16541
16609
|
if (note) rec.decisions = [...(rec.decisions || []), `verify(${result}): ${note}`];
|
|
16542
16610
|
});
|
|
16543
16611
|
if (!rec) return fail(`run 없음: ${curId}`);
|
|
16544
|
-
if (json) { log(JSON.stringify({ verified: curId, result, run: rec }, null, 2)); return; }
|
|
16612
|
+
if (json) { log(JSON.stringify({ verified: curId, result, run: rec, completion_claim_allowed: _completionClaimAllowed(rec) }, null, 2)); return; }
|
|
16545
16613
|
(result === 'pass' ? ok : warn)(`검증 결과: ${curId} → ${result}`);
|
|
16546
16614
|
return;
|
|
16547
16615
|
}
|
|
@@ -16556,19 +16624,22 @@ function stateCmd(root, sub, ...args) {
|
|
|
16556
16624
|
if (!rec) return fail(`run 없음: ${curId}`);
|
|
16557
16625
|
// 다음 에이전트가 읽을 latest handoff (json + md)
|
|
16558
16626
|
const hdir = path.join(_leernessStateDir(root), 'handoff'); mkdirp(hdir);
|
|
16559
|
-
|
|
16627
|
+
// 1.9.443 (UR-0153): handoff evidence 에 completion_claim_allowed 포함 — 다음 에이전트/PR 이 증거 게이트를 직접 읽음.
|
|
16628
|
+
const _ccaH = _completionClaimAllowed(rec);
|
|
16629
|
+
writeUtf8(path.join(hdir, 'latest.json'), JSON.stringify({ ...rec, completion_claim_allowed: _ccaH }, null, 2) + '\n');
|
|
16560
16630
|
writeUtf8(path.join(hdir, 'latest.md'),
|
|
16561
|
-
`# Handoff — ${rec.run_id}\n\n- task: ${rec.task_id || '-'}\n- agent: ${rec.agent_name || '-'} · model: ${rec.model_name || '-'}\n- goal: ${rec.goal || '-'}\n- files_changed: ${rec.files_changed.join(', ') || '-'}\n- tests_run: ${rec.tests_run.join(', ') || '-'}\n- verification: ${rec.verification_result || '-'}\n\n## Summary\n${rec.handoff_summary}\n`);
|
|
16631
|
+
`# Handoff — ${rec.run_id}\n\n- task: ${rec.task_id || '-'}\n- agent: ${rec.agent_name || '-'} · model: ${rec.model_name || '-'}\n- goal: ${rec.goal || '-'}\n- files_changed: ${rec.files_changed.join(', ') || '-'}\n- tests_run: ${rec.tests_run.join(', ') || '-'}\n- verification: ${rec.verification_result || '-'}\n- completion_claim_allowed: ${_ccaH.allowed ? 'yes' : 'no (' + _ccaH.reasons.join(', ') + ')'}\n\n## Summary\n${rec.handoff_summary}\n`);
|
|
16562
16632
|
state.currentRunId = null; // 다음 에이전트가 새 run 시작
|
|
16563
16633
|
_saveLeernessState(root, state);
|
|
16564
|
-
if (json) { log(JSON.stringify({ handoff: rec.run_id, run: rec }, null, 2)); return; }
|
|
16634
|
+
if (json) { log(JSON.stringify({ handoff: rec.run_id, run: rec, completion_claim_allowed: _ccaH }, null, 2)); return; }
|
|
16565
16635
|
ok(`handoff 작성: ${rec.run_id} → .leerness/handoff/latest.{md,json}`);
|
|
16566
16636
|
return;
|
|
16567
16637
|
}
|
|
16568
16638
|
|
|
16569
16639
|
// default: show
|
|
16570
16640
|
const rec = curId ? _loadRun(root, curId) : null;
|
|
16571
|
-
|
|
16641
|
+
// 1.9.443 (UR-0153): evidence-first 완료 게이트 파생 노출.
|
|
16642
|
+
if (json) { log(JSON.stringify({ state, currentRun: rec, completion_claim_allowed: rec ? _completionClaimAllowed(rec) : null }, null, 2)); return; }
|
|
16572
16643
|
log(`# leerness state (1.9.278, UR-0032) — .leerness/ 구조화 상태`);
|
|
16573
16644
|
log(`project: ${state.project} · runs 누적: ${state.runCounter || 0} · 현재 run: ${curId || '(없음)'}`);
|
|
16574
16645
|
if (rec) {
|
|
@@ -16580,6 +16651,8 @@ function stateCmd(root, sub, ...args) {
|
|
|
16580
16651
|
log(` commands_run: ${rec.commands_run.join(', ') || '-'}`);
|
|
16581
16652
|
log(` tests_run: ${rec.tests_run.join(', ') || '-'} · verification: ${rec.verification_result || '-'}`);
|
|
16582
16653
|
if (rec.decisions.length) log(` decisions: ${rec.decisions.length}`);
|
|
16654
|
+
const _cca = _completionClaimAllowed(rec); // 1.9.443 (UR-0153): 증거 기반 완료 주장 가능 여부
|
|
16655
|
+
log(` completion_claim_allowed: ${_cca.allowed ? 'yes ✓' : 'no ✗ (' + _cca.reasons.join(', ') + ')'}`);
|
|
16583
16656
|
} else {
|
|
16584
16657
|
log(` → 시작: leerness state start "<goal>" [--agent claude --model claude-opus-4-7 --task T-0001]`);
|
|
16585
16658
|
}
|
|
@@ -18853,6 +18926,7 @@ async function main() {
|
|
|
18853
18926
|
if (cmd === 'setup-agents' || cmd === 'setup' && args[1] === 'agents') return await setupAgentsCmd(args[1] && args[1] !== 'agents' ? args[1] : (arg('--path', args[2] || process.cwd())));
|
|
18854
18927
|
if (cmd === 'session' && args[1] === 'close') return sessionClose(_resolveRoot(args[2]), { json: has('--json') });
|
|
18855
18928
|
// 1.9.151: viewwork 명령 제거 (사용자 명시 — leerness 와 무관). session close 의 viewworkEmit 콜도 함께 제거.
|
|
18929
|
+
if (cmd === 'ci' && (args[1] === 'init' || args[1] == null)) return ciInitCmd(absRoot(_resolveRoot(args[2])), { force: has('--force'), json: has('--json') }); // 1.9.444 (UR-0152): CI gate 워크플로 생성
|
|
18856
18930
|
if (cmd === 'route') return route(args[1] || 'planning');
|
|
18857
18931
|
if (cmd === 'self' && args[1] === 'check') return await selfCheck(absRoot(arg('--path', args[2] || process.cwd())));
|
|
18858
18932
|
if (cmd === 'self' && args[1] === 'migrate') return log('Run: npx --yes leerness@latest migrate . --dry-run, then migrate without --dry-run after review.');
|
package/lib/pure-utils.js
CHANGED
|
@@ -162,6 +162,22 @@ function _newRunRecord(opts = {}) {
|
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
// 1.9.443 (GPT-5.5 전략리뷰 §6.3/6.4, UR-0153): evidence-first 완료 게이트 — run-record 증거로 "완료 주장 가능" 여부 파생.
|
|
166
|
+
// 허용 조건: 변경 파일 존재 + 검증 실행(tests/commands) + 미해결 errors 0 + verification_result === 'pass'.
|
|
167
|
+
// verification 미실행/실패는 불허(증거 없는 완료 차단). reasons 로 불허 사유 명시. 순수 함수(저장 X, 읽을 때 계산).
|
|
168
|
+
function _completionClaimAllowed(rec) {
|
|
169
|
+
const r = rec || {};
|
|
170
|
+
const A = (x) => (Array.isArray(x) ? x : []);
|
|
171
|
+
const reasons = [];
|
|
172
|
+
if (A(r.files_changed).length === 0) reasons.push('no_files_changed');
|
|
173
|
+
if (A(r.tests_run).length === 0 && A(r.commands_run).length === 0) reasons.push('no_verification_run');
|
|
174
|
+
if (A(r.errors).length > 0) reasons.push('unresolved_errors');
|
|
175
|
+
const vr = String(r.verification_result || '').toLowerCase();
|
|
176
|
+
if (vr === 'fail') reasons.push('verification_failed');
|
|
177
|
+
else if (vr !== 'pass') reasons.push('not_verified');
|
|
178
|
+
return { allowed: reasons.length === 0, reasons };
|
|
179
|
+
}
|
|
180
|
+
|
|
165
181
|
// 1.9.318 (UR-0025): 순수 HTML 파싱 유틸 (api-skill 문서 수집용) — fs/네트워크 의존 0, URL/regex 만 사용.
|
|
166
182
|
function _htmlToText(html) {
|
|
167
183
|
if (!html) return '';
|
|
@@ -1007,7 +1023,9 @@ module.exports = {
|
|
|
1007
1023
|
// 1.9.416 (9th 외부평가, UR-0122): add 류 제목 파싱(flag/경로 break) 단일 출처
|
|
1008
1024
|
_parseAddTitle,
|
|
1009
1025
|
// 1.9.442 (12th 외부평가, UR-0141): task 계열 positional path 안전 추출
|
|
1010
|
-
_taskPositionalPath
|
|
1026
|
+
_taskPositionalPath,
|
|
1027
|
+
// 1.9.443 (GPT-5.5 전략리뷰 §6.3, UR-0153): evidence-first 완료 게이트
|
|
1028
|
+
_completionClaimAllowed
|
|
1011
1029
|
};
|
|
1012
1030
|
|
|
1013
1031
|
// 1.9.355 (UR-0075 Phase A): AI 에이전트용 크로스버전 마이그레이션 안전 워크플로 가이드 (순수 텍스트). 임시설치 + --path + 백업 + diff 검증.
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -6131,5 +6131,48 @@ total++;
|
|
|
6131
6131
|
if (!ok) failed++;
|
|
6132
6132
|
}
|
|
6133
6133
|
|
|
6134
|
+
// 1.9.443 (GPT-5.5 전략리뷰 §6.3, UR-0153): evidence-first 완료 게이트 — state 워크플로에서 completion_claim_allowed 파생.
|
|
6135
|
+
total++;
|
|
6136
|
+
{
|
|
6137
|
+
let ok = false;
|
|
6138
|
+
try {
|
|
6139
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-cca-'));
|
|
6140
|
+
cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
|
|
6141
|
+
cp.spawnSync(process.execPath, [CLI, 'state', 'start', '목표', '--path', d], { encoding: 'utf8', timeout: 15000 });
|
|
6142
|
+
const before = cp.spawnSync(process.execPath, [CLI, 'state', 'show', '--json', '--path', d], { encoding: 'utf8', timeout: 15000 });
|
|
6143
|
+
cp.spawnSync(process.execPath, [CLI, 'state', 'record', '--files-changed', 'a.js', '--tests', 'npm test', '--path', d], { encoding: 'utf8', timeout: 15000 });
|
|
6144
|
+
cp.spawnSync(process.execPath, [CLI, 'state', 'verify', 'pass', '--path', d], { encoding: 'utf8', timeout: 15000 });
|
|
6145
|
+
const after = cp.spawnSync(process.execPath, [CLI, 'state', 'show', '--json', '--path', d], { encoding: 'utf8', timeout: 15000 });
|
|
6146
|
+
const bj = JSON.parse(before.stdout);
|
|
6147
|
+
const aj = JSON.parse(after.stdout);
|
|
6148
|
+
fs.rmSync(d, { recursive: true, force: true });
|
|
6149
|
+
ok = bj.completion_claim_allowed && bj.completion_claim_allowed.allowed === false
|
|
6150
|
+
&& aj.completion_claim_allowed && aj.completion_claim_allowed.allowed === true;
|
|
6151
|
+
} catch {}
|
|
6152
|
+
console.log(ok ? '✓ B(1.9.443) UR-0153: evidence-first completion_claim_allowed (증거없음=no, 증거+pass=yes)' : '✗ completion_claim_allowed 실패');
|
|
6153
|
+
if (!ok) failed++;
|
|
6154
|
+
}
|
|
6155
|
+
|
|
6156
|
+
// 1.9.444 (GPT-5.5 전략리뷰 §6.7, UR-0152): ci init — PR gate 워크플로 생성(멱등).
|
|
6157
|
+
total++;
|
|
6158
|
+
{
|
|
6159
|
+
let ok = false;
|
|
6160
|
+
try {
|
|
6161
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ci-'));
|
|
6162
|
+
const r1 = cp.spawnSync(process.execPath, [CLI, 'ci', 'init', d], { encoding: 'utf8', timeout: 15000 });
|
|
6163
|
+
const wfPath = path.join(d, '.github', 'workflows', 'leerness-gate.yml');
|
|
6164
|
+
const created = fs.existsSync(wfPath);
|
|
6165
|
+
const content = created ? fs.readFileSync(wfPath, 'utf8') : '';
|
|
6166
|
+
const contentOk = /name:\s*leerness-gate/.test(content) && /pull_request:/.test(content) && /leerness gate \./.test(content);
|
|
6167
|
+
// 멱등: 재실행 시 경고(덮어쓰기 X, exit 0)
|
|
6168
|
+
const r2 = cp.spawnSync(process.execPath, [CLI, 'ci', 'init', d], { encoding: 'utf8', timeout: 15000 });
|
|
6169
|
+
const idempotent = r2.status === 0 && /이미 존재|exists/.test((r2.stdout || '') + (r2.stderr || ''));
|
|
6170
|
+
fs.rmSync(d, { recursive: true, force: true });
|
|
6171
|
+
ok = r1.status === 0 && created && contentOk && idempotent;
|
|
6172
|
+
} catch {}
|
|
6173
|
+
console.log(ok ? '✓ B(1.9.444) UR-0152: ci init — PR gate 워크플로 생성 + 멱등' : '✗ ci init 실패');
|
|
6174
|
+
if (!ok) failed++;
|
|
6175
|
+
}
|
|
6176
|
+
|
|
6134
6177
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
6135
6178
|
if (failed > 0) process.exit(1);
|