leerness 1.36.165 → 1.36.167
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 +8 -0
- package/README.md +15 -5
- package/bin/leerness.js +343 -74
- package/lib/claims-baseline.js +272 -0
- package/lib/mcp-tools.js +1 -1
- package/package.json +2 -2
- package/scripts/claims-baseline-concurrency-probe.js +130 -0
- package/scripts/claims-baseline-probe.js +263 -0
- package/scripts/command-flags-probe.js +344 -0
- package/scripts/e2e-concurrency.js +2 -2
- package/scripts/e2e.js +6 -6
package/bin/leerness.js
CHANGED
|
@@ -36,6 +36,7 @@ const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE
|
|
|
36
36
|
const { tokenizeForRank: _tokenizeForRank, expandQuery: _expandQuery, scoreHits: _scoreHits, suggestTerms: _suggestTerms } = require('../lib/search-core'); // 1.36.23: memory search 랭킹 코어(순수·0-deps)
|
|
37
37
|
const { findCorruptedStateJson: _findCorruptedStateJson } = require('../lib/state-integrity'); // 1.36.1 (클린룸 리뷰 FN): .leerness/*.json 상태 무결성 (audit/health/check 공유)
|
|
38
38
|
const _cursorHook = require('../lib/cursor-session-hook');
|
|
39
|
+
const _claimsBaseline = require('../lib/claims-baseline');
|
|
39
40
|
const {
|
|
40
41
|
CANONICAL_WORKSPACE_DIR,
|
|
41
42
|
LEGACY_WORKSPACE_DIR,
|
|
@@ -47,7 +48,7 @@ const {
|
|
|
47
48
|
migrateLegacyWorkspace,
|
|
48
49
|
} = require('../lib/workspace-dir');
|
|
49
50
|
|
|
50
|
-
const VERSION = '1.36.
|
|
51
|
+
const VERSION = '1.36.167';
|
|
51
52
|
|
|
52
53
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
53
54
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -711,17 +712,57 @@ function _optSub(a) {
|
|
|
711
712
|
if (/^([A-Za-z]:[\\/]|\/|\.\.?[\\/])/.test(v)) return undefined; // 선행 구분자 path-like → 하위명령 아님
|
|
712
713
|
return v;
|
|
713
714
|
}
|
|
714
|
-
|
|
715
|
+
// 같은 이름이 route 에 따라 boolean/value 두 계약을 갖는 역사적 표면.
|
|
716
|
+
// update --check (boolean)
|
|
717
|
+
// referee add --check "command" (value)
|
|
718
|
+
// preview serve --keep (boolean)
|
|
719
|
+
// init/release cleanup --keep N (value)
|
|
720
|
+
// 전역 registry 하나로 arity 를 추측하면 정상 명령을 거부하거나 positional 값을 삼키므로,
|
|
721
|
+
// 명령/하위명령이 확인된 뒤 두 번째 pass 에서만 좁게 override 한다.
|
|
722
|
+
function _commandBooleanValueFlags(cmd, sub) {
|
|
723
|
+
const out = new Set();
|
|
724
|
+
if (cmd === 'update') out.add('--check');
|
|
725
|
+
if (cmd === 'preview' && sub === 'serve') out.add('--keep');
|
|
726
|
+
return out;
|
|
727
|
+
}
|
|
728
|
+
function _nonFlagArgsPass(booleanValueFlags = new Set()) {
|
|
715
729
|
const out = [];
|
|
716
730
|
const withValue = _VALUE_FLAGS;
|
|
717
731
|
const a = process.argv.slice(2);
|
|
718
732
|
for (let i = 0; i < a.length; i++) {
|
|
719
733
|
const x = a[i];
|
|
720
|
-
if (x.startsWith('-')) {
|
|
734
|
+
if (x.startsWith('-')) {
|
|
735
|
+
const name = x.split('=')[0];
|
|
736
|
+
if (withValue.has(name) && !booleanValueFlags.has(name) && !x.includes('=') && a[i+1] && !a[i+1].startsWith('-')) i++;
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
721
739
|
out.push(x);
|
|
722
740
|
}
|
|
723
741
|
return out;
|
|
724
742
|
}
|
|
743
|
+
function _commandSubHint(cmd) {
|
|
744
|
+
const raw = process.argv.slice(2);
|
|
745
|
+
const at = raw.indexOf(cmd);
|
|
746
|
+
if (at < 0) return undefined;
|
|
747
|
+
for (let i = at + 1; i < raw.length; i++) {
|
|
748
|
+
const token = raw[i];
|
|
749
|
+
if (token.startsWith('-')) {
|
|
750
|
+
const name = token.split('=')[0];
|
|
751
|
+
// preview 의 --keep 은 serve 를 알아야 boolean 임을 알 수 있고, serve 는 --keep 뒤에도 올 수 있다.
|
|
752
|
+
// 이 1개만 subcommand 탐색에서 provisional boolean 으로 보고 실제 arity 는 route 확정 후 검증한다.
|
|
753
|
+
const ambiguousBeforeSubcommand = cmd === 'preview' && name === '--keep';
|
|
754
|
+
if (!ambiguousBeforeSubcommand && _VALUE_FLAGS.has(name) && !token.includes('=') && raw[i + 1] && !raw[i + 1].startsWith('-')) i++;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
return token;
|
|
758
|
+
}
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
function nonFlagArgs() {
|
|
762
|
+
const initial = _nonFlagArgsPass();
|
|
763
|
+
const overrides = _commandBooleanValueFlags(initial[0], _commandSubHint(initial[0]) || initial[1]);
|
|
764
|
+
return overrides.size ? _nonFlagArgsPass(overrides) : initial;
|
|
765
|
+
}
|
|
725
766
|
function argAll(name) {
|
|
726
767
|
const out = []; const a = process.argv;
|
|
727
768
|
for (let i = 0; i < a.length; i++) if (a[i] === name && a[i+1] && !a[i+1].startsWith('-')) out.push(a[++i]);
|
|
@@ -1278,7 +1319,7 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
|
|
|
1278
1319
|
'.leerness/test-evidence-policy.md': fm('test-evidence-policy', ['검증 결과 기록 시'], ['검증 형식 변경'], `# Test Evidence Policy\n\n매 검증은 \`.leerness/review-evidence.md\`에 누적 기록합니다.\n\n## Format\n\`\`\`\n## YYYY-MM-DD HH:MM\nTask: T-XXXX\nCommand: <명령>\nExit: <코드>\nNote: <주요 결과 요약>\nArtifacts: <스크린샷/로그 경로>\n\`\`\`\n`),
|
|
1279
1320
|
'.leerness/review-evidence.md': fm('review-evidence', ['진행 보고','릴리즈 검토'], ['검증 결과 기록'], `# Review Evidence\n\nVerification command/result history. Append-only.\n`),
|
|
1280
1321
|
'.leerness/AX_PLAN_GUIDE.md': fm('ax-plan-guide', ['계획 수립/변경','신규 프로젝트'], ['계획 가이드 변경'], `# AX Plan Guide\n\n1. 사용자 요청이 기존 plan.md 범위 내인지 확인합니다.\n2. 새 범위라면 plan.md(milestone)와 progress-tracker.md(T-id) 양쪽에 추가합니다.\n3. 사용자가 범위를 드랍하면 삭제 대신 dropped 표기를 추가합니다.\n4. 신규 프로젝트는 코딩 전에 plan.md/project-brief.md를 채웁니다.\n`),
|
|
1281
|
-
'.leerness/AX_MIGRATION_GUIDE.md': fm('ax-migration-guide', ['마이그레이션 전'], ['마이그레이션 정책 변경'], `# AX Migration Guide\n\n- Back up before changes (\`.leerness/archive/\`).\n- 기존 프로젝트 메모리 보존 (preserve-by-default).\n- .env.example/.gitignore는 라인 단위 머지.\n- 보호 파일을 삭제하지 않습니다.\n- 마이그레이션 보고서는 \`.leerness/migration-report.md\`.\n- 자동: \`leerness update --yes\`가 위 절차를 백업·머지·검증까지 한번에 수행합니다.\n`),
|
|
1322
|
+
'.leerness/AX_MIGRATION_GUIDE.md': fm('ax-migration-guide', ['마이그레이션 전'], ['마이그레이션 정책 변경'], `# AX Migration Guide\n\n- Back up before changes (\`.leerness/archive/\`).\n- 기존 프로젝트 메모리 보존 (preserve-by-default).\n- .env.example/.gitignore는 라인 단위 머지.\n- 보호 파일을 삭제하지 않습니다.\n- 마이그레이션 보고서는 \`.leerness/migration-report.md\`.\n- 자동: \`leerness update --yes\`가 위 절차를 백업·머지·검증까지 한번에 수행합니다.\n- 레거시 완료 증거는 소급 수정하지 않습니다. 명시적 경계 이전의 현재 실패만 \`leerness verify-claim baseline create --before <T-ID> --yes\`로 격리합니다.\n- claims baseline은 전체 tracker 행+실패 사유 지문이 같을 때만 적용됩니다. 변경·신규 실패·손상 baseline은 fail-closed하며 \`verify-claim --all --raw\`로 원판정을 감사합니다.\n- 생성된 claims baseline은 CLI로 덮어쓰지 않습니다. 새 정책이 필요하면 기존 tracked 파일의 제거/교체를 별도 코드리뷰로 승인합니다.\n`),
|
|
1282
1323
|
'.leerness/AX_NEW_PROJECT_GUIDE.md': fm('ax-new-project-guide', ['신규 프로젝트 감지'], ['신규 설치 정책 변경'], `# AX New Project Guide\n\nBefore coding, ask or infer the project goal, users, scope, out-of-scope, stack, deployment target, and milestones. Then fill plan.md and project-brief.md.\n`),
|
|
1283
1324
|
'.leerness/AX_SKILL_LIBRARY_GUIDE.md': fm('ax-skill-library-guide', ['스킬 학습/검증/업로드'], ['스킬 정책 변경'], `# AX Skill Library Guide\n\nValidated skills require metadata, sensitive data scan, AI verification, dry-run publish, and explicit execute approval.\n`),
|
|
1284
1325
|
'.leerness/skill-index.md': fm('skill-index', ['작업별 스킬 선택'], ['스킬 추가/삭제'], `# Skill Index\n\n| ID | Korean Name | Capabilities | Last Updated | Verification |\n|---|---|---|---|---|\n${skillRows}\n`),
|
|
@@ -1302,7 +1343,7 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
|
|
|
1302
1343
|
_files['.leerness/test-evidence-policy.md'] = fm('test-evidence-policy', ['recording verification results'], ['verification format changes'], `# Test Evidence Policy\n\nEvery verification is appended to \`.leerness/review-evidence.md\`.\n\n## Format\n\`\`\`\n## YYYY-MM-DD HH:MM\nTask: T-XXXX\nCommand: <command>\nExit: <code>\nNote: <key result summary>\nArtifacts: <screenshot/log paths>\n\`\`\`\n`, 'en');
|
|
1303
1344
|
_files['.leerness/review-evidence.md'] = fm('review-evidence', ['progress reports', 'release review'], ['verification results recorded'], `# Review Evidence\n\nVerification command/result history. Append-only.\n`, 'en');
|
|
1304
1345
|
_files['.leerness/AX_PLAN_GUIDE.md'] = fm('ax-plan-guide', ['planning/plan changes', 'new project'], ['plan guide changes'], `# AX Plan Guide\n\n1. Check whether the user's request is within the current plan.md scope.\n2. If it is new scope, add it to both plan.md (milestone) and progress-tracker.md (T-id).\n3. When the user drops scope, mark it dropped instead of deleting.\n4. For new projects, fill plan.md/project-brief.md before coding.\n`, 'en');
|
|
1305
|
-
_files['.leerness/AX_MIGRATION_GUIDE.md'] = fm('ax-migration-guide', ['before migration'], ['migration policy changes'], `# AX Migration Guide\n\n- Back up before changes (\`.leerness/archive/\`).\n- Preserve existing project memory (preserve-by-default).\n- Merge .env.example/.gitignore line by line.\n- Never delete protected files.\n- Migration report: \`.leerness/migration-report.md\`.\n- Automatic: \`leerness update --yes\` performs backup, merge, and verification in one step.\n`, 'en');
|
|
1346
|
+
_files['.leerness/AX_MIGRATION_GUIDE.md'] = fm('ax-migration-guide', ['before migration'], ['migration policy changes'], `# AX Migration Guide\n\n- Back up before changes (\`.leerness/archive/\`).\n- Preserve existing project memory (preserve-by-default).\n- Merge .env.example/.gitignore line by line.\n- Never delete protected files.\n- Migration report: \`.leerness/migration-report.md\`.\n- Automatic: \`leerness update --yes\` performs backup, merge, and verification in one step.\n- Never rewrite legacy completion evidence. Isolate only current failures before an explicit boundary with \`leerness verify-claim baseline create --before <T-ID> --yes\`.\n- A claims baseline applies only while the full tracker row and failure reasons match. Changed/new failures or a corrupt baseline fail closed; audit raw verdicts with \`verify-claim --all --raw\`.\n- The CLI never overwrites a created claims baseline. Approve removal or replacement of the tracked policy file as a separate code-reviewed change.\n`, 'en');
|
|
1306
1347
|
_files['.leerness/AX_NEW_PROJECT_GUIDE.md'] = fm('ax-new-project-guide', ['new project detected'], ['fresh-install policy changes'], `# AX New Project Guide\n\nBefore coding, ask or infer the project goal, users, scope, out-of-scope, stack, deployment target, and milestones. Then fill plan.md and project-brief.md.\n`, 'en');
|
|
1307
1348
|
_files['.leerness/AX_SKILL_LIBRARY_GUIDE.md'] = fm('ax-skill-library-guide', ['skill learning/verification/upload'], ['skill policy changes'], `# AX Skill Library Guide\n\nValidated skills require metadata, sensitive data scan, AI verification, dry-run publish, and explicit execute approval.\n`, 'en');
|
|
1308
1349
|
_files['.leerness/skill-index.md'] = fm('skill-index', ['choosing skills per task'], ['skills added/removed'], `# Skill Index\n\n| ID | Korean Name | Capabilities | Last Updated | Verification |\n|---|---|---|---|---|\n${skillRows}\n`, 'en');
|
|
@@ -4823,7 +4864,7 @@ function _selfTestCases() {
|
|
|
4823
4864
|
{ name: 'CV-2/UR-0077: fetchNpmLatest 신형 Node win EINVAL 회피 (npm JS 진입점 + try/catch)', run: () => { if (typeof fetchNpmLatest !== 'function') return false; const src = read(__filename); const i = src.indexOf('function fetchNpmLatest'); if (i < 0) return false; const body = src.slice(i, i + 1600); return body.includes("spawnNpmSync(['view', pkg, 'version']") && /try \{/.test(body) && !/execFile\(|cmd\.exe \/d/.test(body); } },
|
|
4824
4865
|
{ name: 'CV-1/UR-0076: arg() --path=값 파싱 + _resolveRoot(--path>positional>cwd) 행위', run: () => { if (typeof _resolveRoot !== 'function') return false; const save = process.argv; try { process.argv = ['node', 'h', 'context', '--path=/tmp/eqform']; const eq = arg('--path', null) === '/tmp/eqform'; process.argv = ['node', 'h', 'context', 'X', '--path', '/tmp/flag']; const flagWins = _resolveRoot('X') === '/tmp/flag'; process.argv = ['node', 'h', 'context', '/tmp/pos']; const posWins = _resolveRoot('/tmp/pos') === '/tmp/pos'; process.argv = ['node', 'h', 'context']; const cwdFb = _resolveRoot(undefined) === process.cwd(); return eq && flagWins && posWins && cwdFb; } finally { process.argv = save; } } },
|
|
4825
4866
|
{ name: 'CV-4/UR-0079: _pruneArchives archive retention (최신 keep 유지, 오래된 prune) 행위', run: () => { if (typeof _pruneArchives !== 'function') return false; const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_prune_')); try { const adir = path.join(tmp, '.leerness', 'archive'); fs.mkdirSync(adir, { recursive: true }); for (let i = 0; i < 5; i++) fs.mkdirSync(path.join(adir, 'leerness-1.9.' + i + '-stamp')); const pruned = _pruneArchives(tmp, 2); const left = fs.readdirSync(adir).filter(n => /^leerness-/.test(n)).length; return pruned === 3 && left === 2; } finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} } } },
|
|
4826
|
-
{ name: 'CV-7/UR-0082: commands 카탈로그 + help 에 누락 명령군 등재 (표면 drift 가드)', run: () => { const src = read(__filename); const ci = src.indexOf('function commandsCmd'); const hi = src.indexOf('function help('); if (ci < 0 || hi < 0) return false; const cbody = src.slice(ci, ci + 8000); const hbody = src.slice(hi, hi + 7000); const must = ['install-safety', 'feature add', 'creds list', 'incident list', 'webhook serve', 'deploy auto', 'runs list', 'permissions list', 'whats-new', 'migrate audit']; return must.every(c => cbody.includes(c)) && hbody.includes('install-safety') && hbody.includes('feature add'); } },
|
|
4867
|
+
{ name: 'CV-7/UR-0082: commands 카탈로그 + help 에 누락 명령군 등재 (표면 drift 가드)', run: () => { const src = read(__filename); const ci = src.indexOf('function commandsCmd'); const hi = src.indexOf('function help('); if (ci < 0 || hi < 0) return false; const cbody = src.slice(ci, ci + 8000); const hbody = src.slice(hi, hi + 7000); const must = ['verify-code', 'contract verify', 'install-safety', 'feature add', 'creds list', 'incident list', 'webhook serve', 'deploy auto', 'runs list', 'permissions list', 'whats-new', 'migrate audit']; return must.every(c => cbody.includes(c)) && hbody.includes('install-safety') && hbody.includes('feature add'); } },
|
|
4827
4868
|
{ name: 'UR-0083(4th외부평가 9.3): auto-update hook 비침투 (update --quiet 모드 + hook --check --quiet + 업그레이드)', run: () => { const src = read(__filename); const quietMode = /const quiet = !!opts\.quiet \|\| has\('--quiet'\)/.test(src); const hookQuiet = src.includes("command: 'leerness update --check --quiet'"); const upgrade = /includes\('leerness update --check'\) && !h\.command\.includes\('--quiet'\)/.test(src); return quietMode && hookQuiet && upgrade; } },
|
|
4828
4869
|
{ name: 'CV-6/UR-0081: 시크릿 스캐너 FP/FN — _isPlaceholderSecret + _looksSecretLike 행위', run: () => { if (typeof _isPlaceholderSecret !== 'function' || typeof _looksSecretLike !== 'function') return false; const fp = _isPlaceholderSecret('change-me') && _isPlaceholderSecret('your-api-key-here') && _isPlaceholderSecret('<token>') && _isPlaceholderSecret('') && !_isPlaceholderSecret('hunter2realpass'); const fn = _looksSecretLike('secret123') && _looksSecretLike('a'.repeat(24)) && !_looksSecretLike('processEnv') && !_looksSecretLike('reqBodyPassword'); return fp && fn; } },
|
|
4829
4870
|
{ name: 'UR-0025: _mergeLines/_mergeEnvLines 순수 코어 모듈 분리 + 행위 (1.9.367)', run: () => { if (typeof _mergeLines !== 'function' || typeof _mergeEnvLines !== 'function') return false; const m = require('../lib/pure-utils'); const moved = m._mergeLines === _mergeLines && m._mergeEnvLines === _mergeEnvLines; const ml = _mergeLines('a\n', ['a', 'b']) === 'a\nb\n'; const meKeep = _mergeEnvLines('FOO=keep\n', ['FOO=new']) === 'FOO=keep\n'; const meAdd = _mergeEnvLines('FOO=keep\n', ['BAR=add']).includes('BAR=add'); return moved && ml && meKeep && meAdd; } },
|
|
@@ -5512,20 +5553,40 @@ function _selfTestCases() {
|
|
|
5512
5553
|
} },
|
|
5513
5554
|
{ name: '1.33.3 (verify-claim --all → gate+MCP): _verifyClaimsAll 코어(exit 없음) + gate --claims opt-in 6번째(기본 5 유지) + MCP leerness_verify_claim_all def↔case', run: () => {
|
|
5514
5555
|
const src = read(__filename);
|
|
5515
|
-
const coreDef = src.match(/function _verifyClaimsAll\(root\) \{[\s\S]*?\n\}/); // 함수 본문만 캡처(첫 줄머리 } 까지)
|
|
5516
|
-
const core = !!coreDef && coreDef[0].includes('
|
|
5556
|
+
const coreDef = src.match(/function _verifyClaimsAll\(root, options = \{\}\) \{[\s\S]*?\n\}/); // 함수 본문만 캡처(첫 줄머리 } 까지)
|
|
5557
|
+
const core = !!coreDef && coreDef[0].includes('const raw = { ok: failed.length === 0, total: doneRows.length, failed: failed.length, results };')
|
|
5558
|
+
&& coreDef[0].includes('_claimsBaseline.applyBaseline(rows, raw') && !/process\.exit\(/.test(coreDef[0]); // 코어는 절대 process.exit( 안 함(게이트 step 집계 보호)
|
|
5517
5559
|
// 1.36.82: 헤더 문자열을 **정확 리터럴**로 잡으면 그 줄을 만질 때마다 자기 줄만 매칭해 조용히 통과한다
|
|
5518
5560
|
// (이번 라운드에만 두 번 그렇게 됐다). 표현이 아니라 **불변식**을 본다:
|
|
5519
5561
|
// 헤더가 withClaims 로 5/6 을 계산하고, --claims 일 때만 verify-claims 단계가 붙는다.
|
|
5520
5562
|
const gateOptIn = src.includes("const withClaims = has('--claims');")
|
|
5521
5563
|
&& /# leerness gate \(\$\{[^}]*withClaims \? 6 : 5[^}]*\} checks\)/.test(src)
|
|
5522
|
-
&&
|
|
5523
|
-
const reuse = src.includes(
|
|
5564
|
+
&& /if \(withClaims\) step\('verify-claims',[\s\S]{0,100}?const r = _verifyClaimsAll\(root\)/.test(src);
|
|
5565
|
+
const reuse = src.includes("const res = _verifyClaimsAll(root, { raw: has('--raw') });"); // CLI 도 코어 공유(분기 없음)
|
|
5524
5566
|
const tools = require('../lib/mcp-tools');
|
|
5525
5567
|
const def = tools.find(t => t.name === 'leerness_verify_claim_all');
|
|
5526
5568
|
const mcpOk = !!def && def.requiredTier === 'read-only' && src.includes("case 'leerness_verify_claim_all':") && /case 'leerness_verify_claim_all':[\s\S]{0,160}'verify-claim', '--all'[\s\S]{0,80}'--json'/.test(src);
|
|
5527
5569
|
return core && gateOptIn && reuse && mcpOk && tools.length >= 84;
|
|
5528
5570
|
} },
|
|
5571
|
+
{ name: '1.36.167 (T-0145): claims baseline은 경계 이전 exact 행+사유만 승인하고 새/변경 실패·손상은 fail-closed (행위)', run: () => {
|
|
5572
|
+
const b = require('../lib/claims-baseline');
|
|
5573
|
+
const row = (id, status, evidence, nextAction = 'next') => ({ id, status, request: `request ${id}`, evidence, nextAction, updated: '2026-08-26' });
|
|
5574
|
+
const rows = [row('T-0001', 'done', 'legacy prose'), row('T-0002', 'in-progress', 'boundary'), row('T-0003', 'done', 'new prose')];
|
|
5575
|
+
const raw = { ok: false, total: 2, failed: 2, results: [
|
|
5576
|
+
{ id: 'T-0001', request: 'request T-0001', status: 'done', ok: false, reasons: ['unverifiable-claim'] },
|
|
5577
|
+
{ id: 'T-0003', request: 'request T-0003', status: 'done', ok: false, reasons: ['unverifiable-claim'] },
|
|
5578
|
+
] };
|
|
5579
|
+
const built = b.buildBaseline(rows, raw, { beforeTaskId: 'T-0002', version: VERSION, createdAt: '2026-08-26T00:00:00.000Z' });
|
|
5580
|
+
const valid = b.validateBaseline(built.doc).ok && built.doc.entries.length === 1;
|
|
5581
|
+
const applied = b.applyBaseline(rows, raw, { state: 'valid', file: 'fixture', doc: built.doc });
|
|
5582
|
+
const changedRows = [{ ...rows[0], nextAction: 'changed' }, rows[1], rows[2]];
|
|
5583
|
+
const changed = b.applyBaseline(changedRows, raw, { state: 'valid', file: 'fixture', doc: built.doc });
|
|
5584
|
+
const corrupt = b.validateBaseline({ ...built.doc, integrity: 'sha256:' + '0'.repeat(64) });
|
|
5585
|
+
return valid && applied.ok === false && applied.baselined === 1 && applied.failed === 1
|
|
5586
|
+
&& applied.baseline.newFailures.includes('T-0003')
|
|
5587
|
+
&& changed.baselined === 0 && changed.failed === 2 && changed.baseline.mismatched.includes('T-0001')
|
|
5588
|
+
&& corrupt.ok === false && corrupt.problems.includes('integrity-mismatch');
|
|
5589
|
+
} },
|
|
5529
5590
|
{ name: '14th 버그헌트 P2 (UR-0178/0179/0180): completed→done 정규화 + rule archive _cellSafe + nextRuleId 아카이브 스캔 (1.11.3)', run: () => {
|
|
5530
5591
|
if (_normTaskStatus('completed') !== 'done' || _normTaskStatus('verified') !== 'done' || _normTaskStatus('in-progress') !== 'in-progress') return false;
|
|
5531
5592
|
const src = read(__filename);
|
|
@@ -5755,7 +5816,7 @@ function _selfTestCases() {
|
|
|
5755
5816
|
} },
|
|
5756
5817
|
{ name: '범용성 P2 (UR-0048): task update unknown flag 거부 + did-you-mean(인수인계 유실 차단) (1.17.5)', run: () => {
|
|
5757
5818
|
const src = read(__filename);
|
|
5758
|
-
const wired = src.includes('function _rejectUnknownFlags(allowed, usageHint)') && src.includes("'unknown_flag'") && src.includes("_rejectUnknownFlags(['--status', '--evidence', '--next', '--note']");
|
|
5819
|
+
const wired = src.includes('function _rejectUnknownFlags(allowed, usageHint, options = {})') && src.includes("'unknown_flag'") && src.includes("_rejectUnknownFlags(['--status', '--evidence', '--next', '--note']");
|
|
5759
5820
|
// did-you-mean prefix 로직: --next-action 은 --next 를 prefix 로 가짐
|
|
5760
5821
|
const dymOk = '--next-action'.startsWith('--next');
|
|
5761
5822
|
return wired && dymOk;
|
|
@@ -10481,21 +10542,24 @@ function _p0103FlagsOk() {
|
|
|
10481
10542
|
const eqOk = pv2.includes('equals form title') && !pv2.includes('ZZEQ');
|
|
10482
10543
|
const rtEq = R(['agents', 'route', '--tier=normal', 'fix the parser']);
|
|
10483
10544
|
const eqRoute = !/비어 있|empty_task/.test((rtEq.stdout || '') + (rtEq.stderr || ''));
|
|
10484
|
-
// ④ 동적 레지스트리 플래그(brief set 은 arg('--'+f.flag) 로 만든다)에 오탐
|
|
10545
|
+
// ④ 동적 레지스트리 플래그(brief set 은 arg('--'+f.flag) 로 만든다)에 오탐 실패가 없어야 한다 — 판별 대조군 포함
|
|
10485
10546
|
const W2 = /알 수 없는 플래그|Unknown flag/;
|
|
10486
|
-
const
|
|
10487
|
-
|
|
10488
|
-
|
|
10547
|
+
const dynGood = R(['brief', 'set', '--intro', 'hello']);
|
|
10548
|
+
const dynBad = R(['brief', 'set', '--introo', 'x']);
|
|
10549
|
+
const dynOk = !W2.test((dynGood.stdout || '') + (dynGood.stderr || ''))
|
|
10550
|
+
&& dynBad.status !== 0 && W2.test((dynBad.stdout || '') + (dynBad.stderr || ''));
|
|
10551
|
+
// ⑤ 오타 fail-closed — 판별 대조군: 실재 플래그는 조용하고, 없는 플래그만 구조화 실패한다.
|
|
10489
10552
|
const good = R(['pulse', '--json']);
|
|
10490
10553
|
const typo = R(['pulse', '--json', '--nosuchflagzz']);
|
|
10491
10554
|
const quietOnReal = !/알 수 없는 플래그|Unknown flag/.test(good.stderr || '');
|
|
10492
10555
|
// 프로젝트가 OS locale 에 따라 en 으로 초기화돼도 같은 계약을 검증한다.
|
|
10493
10556
|
// 경고 정규식은 바로 위의 ko/en 공용 단일출처를 재사용해 CI locale 에 종속되지 않게 한다.
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10557
|
+
let typoJson = null; try { typoJson = JSON.parse((typo.stdout || '').trim()); } catch { typoJson = null; }
|
|
10558
|
+
const rejectsTypo = typo.status !== 0 && typoJson?.code === 'unknown_flag'
|
|
10559
|
+
&& String(typoJson?.error || '').includes('--nosuchflagzz');
|
|
10560
|
+
// JSON 오류는 stdout 단일 객체, stderr 무오염이다.
|
|
10561
|
+
const stderrClean = !(typo.stderr || '').trim();
|
|
10562
|
+
return titleClean && pathClean && eqOk && eqRoute && dynOk && quietOnReal && rejectsTypo && stderrClean;
|
|
10499
10563
|
} catch { return false; } finally { try { fs.rmSync(arena, { recursive: true, force: true }); } catch { /* 정리 실패는 판정과 무관 */ } }
|
|
10500
10564
|
}
|
|
10501
10565
|
// 1.36.102 (명령 표면 감사 P1 2건): 회귀 가드.
|
|
@@ -10924,7 +10988,9 @@ function commandsCmd(root) {
|
|
|
10924
10988
|
{ cmd: 'scan secrets [path]', desc: '시크릿 탐지', descEn: 'secret detection' },
|
|
10925
10989
|
{ cmd: 'encoding check [path]', desc: '인코딩 검증', descEn: 'encoding verification' },
|
|
10926
10990
|
{ cmd: 'lazy detect [path] [--json]', desc: '게으른 작업 감지 (1.9.101)', descEn: 'detect lazy/incomplete work (1.9.101)' },
|
|
10927
|
-
{ cmd: 'verify-claim <T-ID|--all> [--run-tests] [--
|
|
10991
|
+
{ cmd: 'verify-claim <T-ID|--all> [--run-tests] [--strict-claims] [--raw] · baseline create --before <T-ID> --yes|show', cmdEn: 'verify-claim <T-ID|--all> [--run-tests] [--strict-claims] [--raw] · baseline create --before <T-ID> --yes|show', desc: '주장 검증 — --all은 명시적 legacy baseline 적용, --raw는 원판정 감사. baseline create는 경계 이전 exact 행+사유 실패만 격리하며 evidence를 수정하지 않음 (1.36.167)', descEn: 'claim verification — --all applies an explicit legacy baseline; --raw audits raw verdicts. baseline create isolates only exact row+reason failures before a boundary without editing evidence (1.36.167)' },
|
|
10992
|
+
{ cmd: 'verify-code [path] [--build] [--bench] [--strict]', desc: '프로젝트 테스트·린트·타입검사 자동 감지 실행 + 검증 증거 기록', descEn: 'auto-detect and run project tests/lint/typecheck, then record verification evidence' },
|
|
10993
|
+
{ cmd: 'contract verify <spec.md> <impl.js> [--allow-empty] [--json]', desc: '명세의 함수·필드 선언과 구현 export 정적 대조', descEn: 'statically compare functions and fields declared in a spec with implementation exports' },
|
|
10928
10994
|
{ cmd: `lens [${_lensDomainList()}] [--json]`, desc: '분야별 자기질문 품질 렌즈 + 분야간 인과관계 (1.18.3 · database/contract/recovery/observability 는 심화, axes 는 8축 경량 점검)', descEn: 'per-domain self-question quality lenses + cross-domain causality (1.18.3 · database/contract/recovery/observability are deep dives, axes is a light 8-axis check)' },
|
|
10929
10995
|
// 1.36.101 (명령 표면 감사): 1.36.98 에서 디스패처에만 붙이고 commands/--help 에 등재하지 않아 '있는데 안 보이는' 명령이었다.
|
|
10930
10996
|
{ cmd: 'library [show|page] [path] [--json] [--ai]', desc: '재사용 인벤토리 — 컴포넌트·디자인 토큰을 저장소에서 추출. page: 오프라인 단일 HTML · --ai: 에이전트용 압축 JSON (1.36.98 P-0013)', descEn: 'reuse inventory — extracts components and design tokens from the repo. page: offline single HTML · --ai: compact JSON for agents (1.36.98 P-0013)' },
|
|
@@ -14473,16 +14539,130 @@ function taskAdd(root, text) {
|
|
|
14473
14539
|
// 1.17.5 (UR-0048, 5축 실증 P2): 모르는 옵션 조용히 무시 차단 — `task update --next-action "x"` 처럼 오타/미존재 플래그가
|
|
14474
14540
|
// "✓ task updated" 와 함께 값을 버려, 쓴 에이전트는 기록됐다고 믿고 다음 에이전트는 placeholder 를 받는(인수인계 유실) 최악의 실패 양식.
|
|
14475
14541
|
// prefix 기반 did-you-mean(--next-action → --next) + exit 1. 전역 플래그(--path/--json/--force/--lenient)는 항상 허용.
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14542
|
+
// T-0144: 명령별 검증은 options.globals 로 그 명령이 실제 지원하는 공통 플래그만 지정한다.
|
|
14543
|
+
// 기본값은 기존 task update 계약을 보존하고, exact 명령만 좁혀 과거의 묵시적 무시를 차단한다.
|
|
14544
|
+
function _rejectUnknownFlags(allowed, usageHint, options = {}) {
|
|
14545
|
+
const globals = Array.isArray(options.globals) ? options.globals : ['--path', '--json', '--force', '--lenient'];
|
|
14546
|
+
const all = new Set([...allowed, ...globals]);
|
|
14547
|
+
const seen = _argvLongFlagNames();
|
|
14479
14548
|
const unknown = [...new Set(seen.filter(f => !all.has(f)))];
|
|
14480
14549
|
if (!unknown.length) return true;
|
|
14481
14550
|
const dym = (u) => { const c = [...all].find(k => u.startsWith(k) || k.startsWith(u)); return c ? ` — 혹시 ${c}?` : ''; };
|
|
14482
|
-
|
|
14551
|
+
const detail = unknown.map(u => u + dym(u)).join(', ');
|
|
14552
|
+
const supported = [...all].join(' ');
|
|
14553
|
+
const en = _uiLang(process.cwd()) === 'en';
|
|
14554
|
+
failJson(has('--json'), 'unknown_flag', en
|
|
14555
|
+
? `Unsupported option for this command: ${detail} (supported: ${supported}${usageHint ? ' · ' + usageHint : ''}) — rejected instead of silently discarding its value`
|
|
14556
|
+
: `이 명령에서 지원하지 않는 옵션: ${detail} (지원: ${supported}${usageHint ? ' · ' + usageHint : ''}) — 값이 조용히 버려지는 것을 방지하기 위해 거부`);
|
|
14483
14557
|
return false;
|
|
14484
14558
|
}
|
|
14485
14559
|
|
|
14560
|
+
function _argvLongFlagNames() {
|
|
14561
|
+
return [...new Set(process.argv.slice(2)
|
|
14562
|
+
.filter(a => typeof a === 'string' && a.startsWith('--') && a !== '--')
|
|
14563
|
+
.map(a => a.split('=')[0]))];
|
|
14564
|
+
}
|
|
14565
|
+
|
|
14566
|
+
// T-0144: 모든 명령에서 이름 자체가 없는 플래그는 migration/usage/write 이전에 fail-closed.
|
|
14567
|
+
function _rejectUnregisteredFlags() {
|
|
14568
|
+
const unknown = _argvLongFlagNames().filter(name => !_VALUE_FLAGS.has(name) && !_BOOL_FLAGS.has(name));
|
|
14569
|
+
if (!unknown.length) return true;
|
|
14570
|
+
const en = _uiLang(process.cwd()) === 'en';
|
|
14571
|
+
const human = en
|
|
14572
|
+
? `Unknown flag: ${unknown.join(' ')} — no such flag in leerness. Its value could otherwise be absorbed into text or a path. Check: leerness --help`
|
|
14573
|
+
: `알 수 없는 플래그: ${unknown.join(' ')} — leerness 에 없는 이름입니다. 값이 본문이나 경로 인자에 섞이지 않도록 거부했습니다. 확인: leerness --help`;
|
|
14574
|
+
if (has('--json')) {
|
|
14575
|
+
// JSON 본문 자체는 stdout 의 단일 문서다. 예전 경고 문구를 그대로 넣으면 소비자가 이를
|
|
14576
|
+
// "JSON 앞에 섞인 warning" 으로 오인하므로 code를 단일출처로 두고 메시지는 구조화 용어를 쓴다.
|
|
14577
|
+
failJson(true, 'unknown_flag', en
|
|
14578
|
+
? `Unregistered option: ${unknown.join(' ')} — rejected before its value could alter text or path arguments`
|
|
14579
|
+
: `등록되지 않은 옵션: ${unknown.join(' ')} — 값이 본문이나 경로 인자에 섞이기 전에 거부했습니다`);
|
|
14580
|
+
} else {
|
|
14581
|
+
// 사람용 계약은 기존 stderr 가시성을 보존하되, T-0144부터 exit 1로 fail-closed 한다.
|
|
14582
|
+
console.error('⚠ ' + human);
|
|
14583
|
+
process.exitCode = 1;
|
|
14584
|
+
}
|
|
14585
|
+
return false;
|
|
14586
|
+
}
|
|
14587
|
+
|
|
14588
|
+
// 알려진 플래그도 형태가 틀리면 소비자가 기본값으로 조용히 폴백할 수 있다.
|
|
14589
|
+
// `--strict=true`는 has('--strict')가 false이고, `--language --json`은 language 값이 비어도 둘 다 성공했다.
|
|
14590
|
+
function _rejectMalformedFlagForms(cmd, args) {
|
|
14591
|
+
const raw = process.argv.slice(2);
|
|
14592
|
+
const errors = [];
|
|
14593
|
+
const valueChoices = { '--language': new Set(['auto', 'ko', 'en']) };
|
|
14594
|
+
const commandBooleans = _commandBooleanValueFlags(cmd, args && args[1]);
|
|
14595
|
+
for (let i = 0; i < raw.length; i++) {
|
|
14596
|
+
const token = raw[i];
|
|
14597
|
+
if (typeof token !== 'string' || !token.startsWith('--') || token === '--') continue;
|
|
14598
|
+
const eqAt = token.indexOf('=');
|
|
14599
|
+
const name = eqAt >= 0 ? token.slice(0, eqAt) : token;
|
|
14600
|
+
const booleanHere = _BOOL_FLAGS.has(name) || commandBooleans.has(name);
|
|
14601
|
+
if (booleanHere && eqAt >= 0) {
|
|
14602
|
+
errors.push({ kind: 'boolean_value', name, token });
|
|
14603
|
+
continue;
|
|
14604
|
+
}
|
|
14605
|
+
if (_VALUE_FLAGS.has(name) && !commandBooleans.has(name)) {
|
|
14606
|
+
const missingEqualsValue = eqAt >= 0 && token.slice(eqAt + 1) === '';
|
|
14607
|
+
const missingNextValue = eqAt < 0 && (raw[i + 1] == null || String(raw[i + 1]).startsWith('--'));
|
|
14608
|
+
if (missingEqualsValue || missingNextValue) {
|
|
14609
|
+
// gate 는 누락 referee 를 자체 check 배열(referee_missing_value)로 진단한다. 그 구조를
|
|
14610
|
+
// migration 전에 보존하는 early read-only 경로가 main 에 있으므로 여기서 평탄화하지 않는다.
|
|
14611
|
+
if (cmd === 'gate' && name === '--require-referee') continue;
|
|
14612
|
+
errors.push({ kind: 'missing_value', name, token });
|
|
14613
|
+
}
|
|
14614
|
+
else if (valueChoices[name]) {
|
|
14615
|
+
const value = eqAt >= 0 ? token.slice(eqAt + 1) : String(raw[i + 1]);
|
|
14616
|
+
if (!valueChoices[name].has(value.toLowerCase())) errors.push({ kind: 'invalid_value', name, token, value, choices: [...valueChoices[name]] });
|
|
14617
|
+
}
|
|
14618
|
+
}
|
|
14619
|
+
}
|
|
14620
|
+
if (!errors.length) return true;
|
|
14621
|
+
const onlyMissing = errors.every(e => e.kind === 'missing_value');
|
|
14622
|
+
const onlyInvalidValue = errors.every(e => e.kind === 'invalid_value');
|
|
14623
|
+
const en = _uiLang(process.cwd()) === 'en';
|
|
14624
|
+
const detail = errors.map(e => {
|
|
14625
|
+
if (e.kind === 'missing_value') {
|
|
14626
|
+
if (e.name === '--path') return en
|
|
14627
|
+
? '--path was given without a value (see command usage)'
|
|
14628
|
+
: '--path 에 값이 없습니다 (명령 사용법 확인)';
|
|
14629
|
+
return `${e.name} (${en ? 'value required; see command usage' : '값 필요; 명령 사용법 확인'})`;
|
|
14630
|
+
}
|
|
14631
|
+
if (e.kind === 'invalid_value') return `${e.name}=${e.value} (${en ? 'choose' : '허용값'}: ${e.choices.join('|')})`;
|
|
14632
|
+
return `${e.token} (${en ? `boolean flag; use ${e.name}` : `부울 플래그 — ${e.name} 형태로 사용`})`;
|
|
14633
|
+
}).join(', ');
|
|
14634
|
+
const code = onlyMissing ? 'missing_flag_value' : (onlyInvalidValue ? 'invalid_flag_value' : 'invalid_flag_syntax');
|
|
14635
|
+
failJson(has('--json'), code, en
|
|
14636
|
+
? `Invalid flag syntax: ${detail}`
|
|
14637
|
+
: `잘못된 플래그 형식: ${detail}`);
|
|
14638
|
+
return false;
|
|
14639
|
+
}
|
|
14640
|
+
|
|
14641
|
+
// 전역 registry는 "값을 갖는가"만 답한다. 아래 audit 완료 route는 현재 명령이 그 플래그를
|
|
14642
|
+
// 실제 소비하는지도 검사한다. 확인하지 않은 97개 표면을 추측 allowlist로 막지 않고 검증된 route부터 확장한다.
|
|
14643
|
+
// --path 는 기존 공용 호출 래퍼가 모든 명령에 붙이는 호환 플래그다. 명령이 별도 positional 파일을
|
|
14644
|
+
// 쓰더라도 이를 거부하면 정상 호출을 false-BLOCK 하므로 공통으로 보존한다.
|
|
14645
|
+
const _COMMON_COMMAND_FLAGS = ['--language', '--no-stale-check', '--path'];
|
|
14646
|
+
const _STRICT_COMMAND_FLAGS = {
|
|
14647
|
+
commands: { allowed: [], globals: [..._COMMON_COMMAND_FLAGS, '--json'], usage: 'commands [--json] [--language en|ko] [--path <path>]' },
|
|
14648
|
+
lens: { allowed: [], globals: [..._COMMON_COMMAND_FLAGS, '--json'], usage: 'lens [domain] [--json]' },
|
|
14649
|
+
about: { allowed: [], globals: [..._COMMON_COMMAND_FLAGS, '--json'], usage: 'about [--json]' },
|
|
14650
|
+
identity: { allowed: [], globals: [..._COMMON_COMMAND_FLAGS, '--json'], usage: 'identity [--json]' },
|
|
14651
|
+
gate: { allowed: ['--claims', '--require-referee'], globals: [..._COMMON_COMMAND_FLAGS, '--json'], usage: 'gate [path] [--claims] [--require-referee <id>] [--json]' },
|
|
14652
|
+
'verify-claim': { allowed: ['--all', '--run-tests', '--test-cmd', '--strict-claims', '--require-evidence', '--lenient', '--referee', '--raw'], globals: [..._COMMON_COMMAND_FLAGS, '--json'], usage: 'verify-claim <T-ID|--all> [--run-tests] [--strict-claims] [--raw] [--json]' },
|
|
14653
|
+
'verify-claim baseline create': { allowed: ['--before', '--yes'], globals: [..._COMMON_COMMAND_FLAGS, '--json'], usage: 'verify-claim baseline create --before <T-ID> --yes [--json]' },
|
|
14654
|
+
'verify-claim baseline show': { allowed: [], globals: [..._COMMON_COMMAND_FLAGS, '--json'], usage: 'verify-claim baseline show [--json]' },
|
|
14655
|
+
'verify-code': { allowed: ['--build', '--bench', '--strict'], globals: [..._COMMON_COMMAND_FLAGS], usage: 'verify-code [path] [--build] [--bench] [--strict]' },
|
|
14656
|
+
'contract verify': { allowed: ['--allow-empty'], globals: [..._COMMON_COMMAND_FLAGS, '--json'], usage: 'contract verify <spec.md> <impl.js> [--allow-empty] [--json]' },
|
|
14657
|
+
};
|
|
14658
|
+
function _validateCommandFlags(cmd, args) {
|
|
14659
|
+
let route = cmd === 'contract' && args[1] === 'verify' ? 'contract verify' : cmd;
|
|
14660
|
+
if (cmd === 'verify-claim' && args[1] === 'baseline') route = `verify-claim baseline ${args[2] || ''}`.trim();
|
|
14661
|
+
const cfg = _STRICT_COMMAND_FLAGS[route];
|
|
14662
|
+
if (!cfg) return true;
|
|
14663
|
+
return _rejectUnknownFlags(cfg.allowed, cfg.usage, { globals: cfg.globals });
|
|
14664
|
+
}
|
|
14665
|
+
|
|
14486
14666
|
function taskUpdate(root, id) {
|
|
14487
14667
|
if (!_requireInit(root, 'task update')) return; // 1.9.311 (UR-0047): init 가드
|
|
14488
14668
|
if (!_rejectUnknownFlags(['--status', '--evidence', '--next', '--note'], 'task update T-0001 --status done --evidence "..." --next "..."')) { process.exitCode = 1; return; } // 1.17.5 (UR-0048)
|
|
@@ -18694,7 +18874,10 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
18694
18874
|
// 1.34.1 (16th리뷰 정직화): 기본 게이트 5체크는 워크스페이스-상태 휴리스틱(handoff/test-run/evidence 부재 등)으로 거짓완료를 잡고, 콘텐츠-레벨 주장(파일 존재·테스트 카운트·스텁·optimism)은 검사하지 않음. verify-claim --all 은 그 콘텐츠 차원을 추가 — lazy detect 가 깨끗한(handoff·테스트기록 완비) 성숙 프로젝트에선 5체크가 통과해도 이 검사만 콘텐츠 거짓을 잡음(실증: gate 기본 exit 0 vs --claims exit 1).
|
|
18695
18875
|
// 1.33.3: 일괄 검증 코어 — 렌더/exit 없이 결과만 반환. verifyClaimAllCmd(CLI 렌더+exit) 와 gate --claims(opt-in 체크) 가 공유.
|
|
18696
18876
|
// gate 의 step() 은 process.exit 가 아니라 process.exitCode 로 실패를 감지하므로, 코어는 절대 process.exit 하지 않음(게이트 프로세스 조기종료 방지).
|
|
18697
|
-
|
|
18877
|
+
// 1.36.167 (T-0145): 오래된 프로젝트의 검증기 도입 이전 완료 행을 소급 수정하지 않으면서도 새 실패를
|
|
18878
|
+
// 계속 차단한다. 기준선은 명시적 tracker 경계 이전의 **현재 실패만** 담고, 전체 행+사유 지문이 같을 때만
|
|
18879
|
+
// 정책상 승인한다. 행/사유 변경·새 실패·손상 파일은 fail-closed. --raw 는 원판정을 계속 감사한다.
|
|
18880
|
+
function _verifyClaimsAll(root, options = {}) {
|
|
18698
18881
|
root = absRoot(root);
|
|
18699
18882
|
const rows = readProgressRows(root);
|
|
18700
18883
|
const doneRows = rows.filter(r => /done|완료|completed/i.test(String(r.status || '')));
|
|
@@ -18703,30 +18886,111 @@ function _verifyClaimsAll(root) {
|
|
|
18703
18886
|
const runMemo = {};
|
|
18704
18887
|
const results = doneRows.map(r => verifyClaimCmd(root, r.id, { collect: true, runMemo }));
|
|
18705
18888
|
const failed = results.filter(x => x && !x.ok);
|
|
18706
|
-
|
|
18889
|
+
const raw = { ok: failed.length === 0, total: doneRows.length, failed: failed.length, results };
|
|
18890
|
+
return _claimsBaseline.applyBaseline(rows, raw, options.raw ? null : _claimsBaseline.loadBaseline(root), { raw: !!options.raw });
|
|
18707
18891
|
}
|
|
18708
18892
|
function verifyClaimAllCmd(root) {
|
|
18709
18893
|
root = absRoot(root);
|
|
18710
18894
|
const _j = has('--json');
|
|
18711
18895
|
const _L = _uiLang(root); const t = (ko, en) => (_L === 'en' ? en : ko);
|
|
18712
|
-
const res = _verifyClaimsAll(root);
|
|
18896
|
+
const res = _verifyClaimsAll(root, { raw: has('--raw') });
|
|
18713
18897
|
const { results, total } = res;
|
|
18714
18898
|
if (_j) {
|
|
18715
|
-
log(JSON.stringify({ ok: res.ok, root: path.basename(root), total, failed: res.failed, results }, null, 2));
|
|
18716
|
-
if (res.
|
|
18899
|
+
log(JSON.stringify({ ok: res.ok, root: path.basename(root), total, failed: res.failed, rawFailed: res.rawFailed, baselined: res.baselined, baseline: res.baseline, errors: res.errors, results }, null, 2));
|
|
18900
|
+
if (!res.ok) process.exitCode = 1;
|
|
18717
18901
|
return;
|
|
18718
18902
|
}
|
|
18719
|
-
log(`# verify-claim --all (${path.basename(root)})`);
|
|
18903
|
+
log(`# verify-claim --all${has('--raw') ? ' --raw' : ''} (${path.basename(root)})`);
|
|
18904
|
+
if (res.errors && res.errors.length) {
|
|
18905
|
+
fail(t(`claims baseline 사용 불가: ${res.errors.join(', ')} (${(res.baseline && res.baseline.problems || []).join(', ') || 'context invalid'})`, `claims baseline unusable: ${res.errors.join(', ')} (${(res.baseline && res.baseline.problems || []).join(', ') || 'context invalid'})`));
|
|
18906
|
+
return process.exit(1);
|
|
18907
|
+
}
|
|
18720
18908
|
if (!total) { log(t(' 완료(done) 주장이 없어 검증할 항목이 없습니다.', ' No completed (done) claims to verify.')); return; }
|
|
18721
|
-
log(t(` 완료 주장 ${total}건
|
|
18909
|
+
log(t(` 완료 주장 ${total}건 — 원판정 실패 ${res.rawFailed} · 레거시 부채 승인 ${res.baselined} · 현재 차단 ${res.failed}`, ` ${total} completed claim(s) — raw failures ${res.rawFailed} · accepted legacy debt ${res.baselined} · blocking now ${res.failed}`));
|
|
18722
18910
|
log('');
|
|
18723
18911
|
for (const r of results) {
|
|
18724
|
-
if (r.
|
|
18912
|
+
if (r && r.baselineAccepted) log(` ≈ ${r.id} ${String(r.request || '').slice(0, 48)} ${t('← 레거시 부채', '← legacy debt')}: ${(r.baselineReasons || []).join(', ')}`);
|
|
18913
|
+
else if (r.ok) log(` ✓ ${r.id} ${String(r.request || '').slice(0, 60)}`);
|
|
18725
18914
|
else log(` ✗ ${r.id} ${String(r.request || '').slice(0, 50)} ${t('← 불일치', '← mismatch')}: ${r.reasons.join(', ')}`);
|
|
18726
18915
|
}
|
|
18727
18916
|
log('');
|
|
18728
18917
|
if (res.failed) { log(t(` ⚠ ${res.failed}건의 완료 주장이 증거와 불일치 — 재검토 권장 (개별 상세: leerness verify-claim <T-ID>)`, ` ⚠ ${res.failed} completed claim(s) do not match evidence — review (per-task detail: leerness verify-claim <T-ID>)`)); return process.exit(1); }
|
|
18729
|
-
log(t(` ✓
|
|
18918
|
+
if (res.baselined) log(t(` ✓ 신규·변경된 완료 주장에 차단 실패 없음 (레거시 부채 ${res.baselined}건은 baseline에 격리; --raw로 원판정 감사)`, ` ✓ no blocking failure in new or changed claims (${res.baselined} legacy debt item(s) isolated by baseline; audit raw verdicts with --raw)`));
|
|
18919
|
+
else log(t(` ✓ 모든 완료 주장이 증거와 일치`, ` ✓ all completed claims match evidence`));
|
|
18920
|
+
}
|
|
18921
|
+
|
|
18922
|
+
function claimBaselineCmd(root, sub) {
|
|
18923
|
+
root = absRoot(root);
|
|
18924
|
+
const json = has('--json');
|
|
18925
|
+
const _L = _uiLang(root); const t = (ko, en) => (_L === 'en' ? en : ko);
|
|
18926
|
+
const loaded = _claimsBaseline.loadBaseline(root);
|
|
18927
|
+
if (sub === 'show') {
|
|
18928
|
+
if (loaded.state === 'invalid') {
|
|
18929
|
+
failJson(json, 'baseline_invalid', t(`claims-baseline.json 손상/형상 무효: ${(loaded.problems || []).join(', ')}`, `claims-baseline.json is corrupt or invalid: ${(loaded.problems || []).join(', ')}`));
|
|
18930
|
+
return;
|
|
18931
|
+
}
|
|
18932
|
+
if (json) {
|
|
18933
|
+
log(JSON.stringify({ ok: true, state: loaded.state, path: loaded.file, baseline: loaded.doc }, null, 2));
|
|
18934
|
+
return;
|
|
18935
|
+
}
|
|
18936
|
+
if (loaded.state === 'absent') { log(t('claims baseline 없음', 'no claims baseline')); return; }
|
|
18937
|
+
log(`# claims baseline (${path.basename(root)})`);
|
|
18938
|
+
log(t(` 경계: ${loaded.doc.beforeTaskId} 이전 · 부채 ${loaded.doc.entries.length}건 · 생성 ${loaded.doc.createdAt}`, ` boundary: before ${loaded.doc.beforeTaskId} · debt ${loaded.doc.entries.length} · created ${loaded.doc.createdAt}`));
|
|
18939
|
+
return;
|
|
18940
|
+
}
|
|
18941
|
+
if (sub !== 'create') {
|
|
18942
|
+
failJson(json, 'unknown_subcommand', t('사용법: leerness verify-claim baseline create --before <T-ID> --yes | baseline show', 'usage: leerness verify-claim baseline create --before <T-ID> --yes | baseline show'));
|
|
18943
|
+
return;
|
|
18944
|
+
}
|
|
18945
|
+
if (!has('--yes')) {
|
|
18946
|
+
failJson(json, 'confirmation_required', t('레거시 실패를 정책 부채로 승인하려면 --yes가 필요합니다 (evidence 원문은 수정하지 않음)', '--yes is required to acknowledge legacy failures as policy debt (historical evidence is not modified)'));
|
|
18947
|
+
return;
|
|
18948
|
+
}
|
|
18949
|
+
const beforeTaskId = arg('--before', null);
|
|
18950
|
+
if (!beforeTaskId || beforeTaskId === true) {
|
|
18951
|
+
failJson(json, 'missing_boundary', t('--before <T-ID>가 필요합니다. 이 경계와 이후의 실패는 절대 baseline에 포함되지 않습니다.', '--before <T-ID> is required. Failures at or after that boundary are never baselined.'));
|
|
18952
|
+
return;
|
|
18953
|
+
}
|
|
18954
|
+
if (loaded.state === 'invalid') {
|
|
18955
|
+
failJson(json, 'baseline_invalid', t(`기존 claims-baseline.json이 손상되어 덮어쓰지 않습니다: ${(loaded.problems || []).join(', ')}`, `existing claims-baseline.json is invalid and will not be overwritten: ${(loaded.problems || []).join(', ')}`));
|
|
18956
|
+
return;
|
|
18957
|
+
}
|
|
18958
|
+
if (loaded.state === 'valid') {
|
|
18959
|
+
failJson(json, 'baseline_exists', t('기존 claims-baseline.json은 불변 정책으로 취급되어 다시 계산하거나 덮어쓰지 않습니다. 새 정책이 필요하면 tracked 파일 변경을 별도 코드리뷰로 승인하세요.', 'existing claims-baseline.json is treated as immutable policy and will not be recalculated or overwritten. Approve any tracked policy-file change in a separate code review.'));
|
|
18960
|
+
return;
|
|
18961
|
+
}
|
|
18962
|
+
const rows = readProgressRows(root);
|
|
18963
|
+
const raw = _verifyClaimsAll(root, { raw: true });
|
|
18964
|
+
let built;
|
|
18965
|
+
try { built = _claimsBaseline.buildBaseline(rows, raw, { beforeTaskId, version: VERSION, createdAt: now() }); }
|
|
18966
|
+
catch (error) {
|
|
18967
|
+
failJson(json, error && error.code === 'E_CLAIMS_BASELINE_BOUNDARY' ? 'boundary_not_found' : 'baseline_create_failed', error.message);
|
|
18968
|
+
return;
|
|
18969
|
+
}
|
|
18970
|
+
if (!built.doc.entries.length) {
|
|
18971
|
+
failJson(json, 'nothing_to_baseline', t(`${beforeTaskId} 이전에 실패한 완료 주장이 없습니다. 빈 기준선은 만들지 않습니다.`, `there are no failed completed claims before ${beforeTaskId}; an empty baseline was not created.`));
|
|
18972
|
+
return;
|
|
18973
|
+
}
|
|
18974
|
+
let file;
|
|
18975
|
+
try { file = _claimsBaseline.saveBaseline(root, built.doc, _withLock); }
|
|
18976
|
+
catch (error) {
|
|
18977
|
+
failJson(json, error && error.code === 'E_CLAIMS_BASELINE_EXISTS' ? 'baseline_exists' : 'baseline_write_failed', error.message);
|
|
18978
|
+
return;
|
|
18979
|
+
}
|
|
18980
|
+
const out = {
|
|
18981
|
+
ok: true,
|
|
18982
|
+
path: file,
|
|
18983
|
+
beforeTaskId,
|
|
18984
|
+
evaluatedBefore: built.stats.evaluatedBefore,
|
|
18985
|
+
baselined: built.stats.baselined,
|
|
18986
|
+
blockingAtOrAfterBoundary: built.stats.failuresAtOrAfterBoundary,
|
|
18987
|
+
historicalEvidenceModified: false,
|
|
18988
|
+
integrity: built.doc.integrity,
|
|
18989
|
+
replaced: false,
|
|
18990
|
+
};
|
|
18991
|
+
if (json) { log(JSON.stringify(out, null, 2)); return; }
|
|
18992
|
+
ok(t(`claims baseline 생성: ${built.stats.baselined}건 격리 · ${beforeTaskId}와 이후는 제외`, `claims baseline created: ${built.stats.baselined} isolated · ${beforeTaskId} and later excluded`));
|
|
18993
|
+
log(t(` evidence 원문 수정 0 · 행/사유 변경 시 면제 무효 · 새 실패 차단 ${out.blockingAtOrAfterBoundary}건`, ` historical evidence edits 0 · any row/reason change invalidates its exemption · ${out.blockingAtOrAfterBoundary} new failure(s) still block`));
|
|
18730
18994
|
}
|
|
18731
18995
|
|
|
18732
18996
|
// 1.9.22: orchestrate — Ollama 로컬 LLM으로 best-of-N 멀티 에이전트 시뮬
|
|
@@ -20885,7 +21149,21 @@ function gate(root) {
|
|
|
20885
21149
|
step('encoding check', () => encodingCheck(root));
|
|
20886
21150
|
step('lazy detect', () => lazyDetect(root));
|
|
20887
21151
|
// 1.33.3: opt-in 정밀 per-claim 검증. 코어(_verifyClaimsAll)는 exit 안 하고 결과만 반환 → step 의 exitCode 감지로 실패 집계. 비-json 모드는 불일치 task 를 fail() 로 표면화.
|
|
20888
|
-
|
|
21152
|
+
// 1.36.167: 기준선이 있으면 정확히 일치하는 과거 실패만 별도 부채로 승인한다. JSON은 원판정/승인/현재차단을
|
|
21153
|
+
// 모두 노출해 "실패가 사라졌다"고 오해하지 않게 하고, 손상 baseline은 별도 오류로 fail-closed한다.
|
|
21154
|
+
if (withClaims) step('verify-claims', () => {
|
|
21155
|
+
const r = _verifyClaimsAll(root);
|
|
21156
|
+
_stepDetail = { total: r.total, failed: r.failed, rawFailed: r.rawFailed, baselined: r.baselined, baselineState: r.baseline && r.baseline.state, baseline: r.baseline, ...(r.errors && r.errors.length ? { errors: r.errors } : {}) };
|
|
21157
|
+
if (!r.ok) {
|
|
21158
|
+
process.exitCode = 1;
|
|
21159
|
+
if (!jsonMode) {
|
|
21160
|
+
if (r.errors && r.errors.length) fail(`claims baseline 사용 불가: ${r.errors.join(', ')} (${(r.baseline && r.baseline.problems || []).join(', ')})`);
|
|
21161
|
+
r.results.filter(x => x && !x.ok).forEach(x => fail(`${x.id} 불일치: ${x.reasons.join(', ')}`));
|
|
21162
|
+
}
|
|
21163
|
+
} else if (!jsonMode && r.baselined) {
|
|
21164
|
+
log(` ⓘ 레거시 부채 ${r.baselined}건은 지문 일치 baseline으로 격리됨 (원판정 실패 ${r.rawFailed}건 · --raw 감사 가능)`);
|
|
21165
|
+
}
|
|
21166
|
+
});
|
|
20889
21167
|
// 1.36.82 (공허한 가드 스윕 High #2, 실측 확인): `referee verify` 성공 시 툴이 사용자에게
|
|
20890
21168
|
// "이제 gate --require-referee <id> 가 이 검증기를 신뢰합니다" 라고 **안내하는데 gate 는 이 플래그를 읽지도 않았다**.
|
|
20891
21169
|
// 사용자는 안내받은 명령을 CI 에 넣고 검증기가 머지를 막는다고 믿지만 아무 일도 일어나지 않았고,
|
|
@@ -24809,7 +25087,7 @@ function _mcpToCliArgs(name, args, targetPath) {
|
|
|
24809
25087
|
case 'leerness_drift_check': cliArgs = ['drift', 'check', targetPath, '--json']; break;
|
|
24810
25088
|
case 'leerness_audit': cliArgs = ['audit', targetPath, '--json', ...(args.fix ? ['--fix'] : []), ...(args.strict ? ['--strict'] : [])]; break;
|
|
24811
25089
|
case 'leerness_verify_claim': cliArgs = ['verify-claim', args.taskId, '--path', targetPath, ...(args.refereeId ? ['--referee', String(args.refereeId)] : []), ...(args.runTests ? ['--run-tests'] : []), ...(args.strictClaims ? ['--strict-claims'] : []), ...(args.lenient ? ['--lenient'] : [])]; break;
|
|
24812
|
-
case 'leerness_verify_claim_all': cliArgs = ['verify-claim', '--all', '--path', targetPath, '--json', ...(args.runTests ? ['--run-tests'] : []), ...(args.strictClaims ? ['--strict-claims'] : []), ...(args.lenient ? ['--lenient'] : [])]; break; // 1.33.3:
|
|
25090
|
+
case 'leerness_verify_claim_all': cliArgs = ['verify-claim', '--all', '--path', targetPath, '--json', ...(args.runTests ? ['--run-tests'] : []), ...(args.strictClaims ? ['--strict-claims'] : []), ...(args.lenient ? ['--lenient'] : []), ...(args.raw ? ['--raw'] : [])]; break; // 1.33.3/1.36.167: 일괄검증 + 원판정 감사
|
|
24813
25091
|
case 'leerness_contract_verify': cliArgs = ['contract', 'verify', args.spec, args.impl]; break;
|
|
24814
25092
|
case 'leerness_agents_list': cliArgs = ['agents', 'list', '--json']; break;
|
|
24815
25093
|
case 'leerness_reuse_map': cliArgs = ['reuse-map', targetPath, ...(args.allApps ? ['--all-apps'] : []), ...(args.strictElements ? ['--strict-elements'] : []), '--json']; break;
|
|
@@ -29571,8 +29849,10 @@ STATUS & DIAGNOSTICS
|
|
|
29571
29849
|
whats-new [path] Recent version changes
|
|
29572
29850
|
|
|
29573
29851
|
VERIFICATION (evidence-gated "done")
|
|
29574
|
-
verify-claim <T-ID> [--run-tests] [--
|
|
29575
|
-
Check evidence vs reality
|
|
29852
|
+
verify-claim <T-ID|--all> [--run-tests] [--raw] [--json]
|
|
29853
|
+
Check evidence vs reality; --raw ignores a legacy baseline
|
|
29854
|
+
verify-claim baseline create --before <T-ID> --yes | baseline show
|
|
29855
|
+
Isolate fingerprint-bound legacy debt without editing evidence
|
|
29576
29856
|
contract verify <spec.md> <impl.js> [--json] [--allow-empty] Spec <-> implementation match (empty spec rejected unless --allow-empty)
|
|
29577
29857
|
verify-code [path] [--build] [--bench] Run tests/lint/typecheck, record evidence
|
|
29578
29858
|
gate [path] One-call CI gate: verify + audit + scan + encoding + lazy
|
|
@@ -29672,35 +29952,13 @@ async function main() {
|
|
|
29672
29952
|
return log(VERSION);
|
|
29673
29953
|
}
|
|
29674
29954
|
if (has('--help') || has('-h')) return help();
|
|
29675
|
-
|
|
29676
|
-
if (!
|
|
29677
|
-
|
|
29678
|
-
|
|
29679
|
-
|
|
29680
|
-
//
|
|
29681
|
-
|
|
29682
|
-
// 그 값이 본문에 붙는다(실측: `decision add "use redis" --resaon faster` → 저장된 제목 "use redis faster").
|
|
29683
|
-
// 파싱 기본값을 뒤집으면 미분류 부울 리터럴 쪽에서 positional 을 삼키는 새 회귀가 나므로, 동작 대신
|
|
29684
|
-
// 가시성을 준다 — stderr 로만 알리고 stdout/--json/exit code 는 건드리지 않는다(기계 소비자 무영향).
|
|
29685
|
-
{
|
|
29686
|
-
const _unknownFlags = [];
|
|
29687
|
-
for (const _tok of process.argv.slice(2)) {
|
|
29688
|
-
if (typeof _tok !== 'string' || !_tok.startsWith('--') || _tok === '--') continue;
|
|
29689
|
-
const _name = _tok.split('=')[0];
|
|
29690
|
-
if (_VALUE_FLAGS.has(_name) || _BOOL_FLAGS.has(_name)) continue;
|
|
29691
|
-
if (!_unknownFlags.includes(_name)) _unknownFlags.push(_name);
|
|
29692
|
-
}
|
|
29693
|
-
if (_unknownFlags.length) {
|
|
29694
|
-
const _L = _uiLang(process.cwd());
|
|
29695
|
-
console.error(_L === 'en'
|
|
29696
|
-
? `⚠ Unknown flag: ${_unknownFlags.join(' ')} — no such flag in leerness. Its value can be absorbed into the text or path argument. Check: leerness --help`
|
|
29697
|
-
: `⚠ 알 수 없는 플래그: ${_unknownFlags.join(' ')} — leerness 에 없는 이름입니다. 그 값이 본문이나 경로 인자에 섞일 수 있습니다. 확인: leerness --help`);
|
|
29698
|
-
}
|
|
29699
|
-
}
|
|
29700
|
-
// 1.36.103 (codex 검수 #9): --keep 은 소비처가 둘인데 한쪽(release cleanup)은 거부하고 다른 쪽(아카이브 retention)은
|
|
29701
|
-
// 조용히 기본값 10 이 됐다 — `init . --keep abc --json` 이 exit 0 + {ok:true} 로 끝났다.
|
|
29702
|
-
// "준 값을 조용히 바꾸지 않는다" 를 소비처마다 반복하지 않고 진입점 한 곳에서 강제한다.
|
|
29703
|
-
{
|
|
29955
|
+
// 입력 오류는 자동 legacy migration·usage 기록보다 먼저 거부해야 실패 호출이 상태를 바꾸지 않는다.
|
|
29956
|
+
if (!_rejectUnregisteredFlags()) return;
|
|
29957
|
+
if (!_rejectMalformedFlagForms(cmd, args)) return;
|
|
29958
|
+
if (!_validateCommandFlags(cmd, args)) return;
|
|
29959
|
+
// 값이 존재하지만 의미가 잘못된 --keep 도 legacy migration/write 전에 거부한다.
|
|
29960
|
+
// preview serve 의 --keep 은 route별 boolean 계약이므로 숫자 검증 대상이 아니다.
|
|
29961
|
+
if (!_commandBooleanValueFlags(cmd, args[1]).has('--keep')) {
|
|
29704
29962
|
const _k = arg('--keep', null);
|
|
29705
29963
|
if (_k !== null) {
|
|
29706
29964
|
const _kn = Number(_k);
|
|
@@ -29713,6 +29971,14 @@ async function main() {
|
|
|
29713
29971
|
}
|
|
29714
29972
|
}
|
|
29715
29973
|
}
|
|
29974
|
+
if (cmd !== 'migrate-workspace-dir' && !_guardLinkedWorkspaceTargets(args)) return;
|
|
29975
|
+
// gate 의 누락 referee 는 기존의 진단 가능한 checks[] 계약을 유지하되, 잘못된 호출이
|
|
29976
|
+
// legacy workspace migration을 일으키기 전에 읽기전용으로 종료한다.
|
|
29977
|
+
if (cmd === 'gate' && _argAllStrict('--require-referee').missing) return gate(arg('--path', args[1] || process.cwd()));
|
|
29978
|
+
if (!_autoMigrateLegacyWorkspace(args, cmd)) return;
|
|
29979
|
+
// 1.36.144 (T-0117, 전수 실측): `--dry-run` 을 받았으면 쓰기를 막는다 — 구현한 명령은 애초에 안 쓰므로
|
|
29980
|
+
// 영향이 없고, 무시하던 명령은 첫 쓰기에서 멈춰 **아무것도 바꾸지 않은 채** 사유를 말한다.
|
|
29981
|
+
if (has('--dry-run')) { setDryRunGuard(true); try { require('../lib/git').setGitDryRun(true); } catch {} }
|
|
29716
29982
|
// 1.36.101 (명령 표면 감사): '없는 경로에 초록 보고' 클래스를 디스패처 한 곳에서 막는다.
|
|
29717
29983
|
// 실측 — 32개 조사 대상 중 16개가 존재하지 않는 디렉토리에 exit 0 과 정상 보고를 냈다
|
|
29718
29984
|
// (pulse/round-history/milestones/session-resume 는 "✓ 비정상 종료 신호 없음 · R0 · 정상 운영 중" 까지 찍었다 — 오타가 안심 보고가 된다).
|
|
@@ -30185,7 +30451,13 @@ async function main() {
|
|
|
30185
30451
|
// 측정이 관측 대상을 바꾸면 안 된다.
|
|
30186
30452
|
if (cmd === 'handoff') { const _hp = arg('--path', args[1] || process.cwd()); const _hr = handoffCmd(_hp); if (has('--writeback') && process.env.LEERNESS_HOOK !== '1') { try { _tech.refreshTechProfile(_hp); } catch {} _maybeAutoGraph(_hp); } return _hr; } // T-0136: 기본은 추적 파일 쓰기 0. 명시적 --writeback 만 기존 프로필/그래프 투영을 갱신한다.
|
|
30187
30453
|
if (cmd === 'reuse-map') return reuseMapCmd(arg('--path', args[1] || process.cwd()));
|
|
30188
|
-
if (cmd === 'verify-claim') {
|
|
30454
|
+
if (cmd === 'verify-claim') {
|
|
30455
|
+
const _p = arg('--path', process.cwd());
|
|
30456
|
+
if (args[1] === 'baseline') return claimBaselineCmd(_p, args[2]);
|
|
30457
|
+
if (args[1] === '--all' || has('--all')) return verifyClaimAllCmd(_p);
|
|
30458
|
+
if (has('--raw')) return failJson(has('--json'), 'raw_requires_all', '--raw 는 verify-claim --all 에서만 사용합니다. 개별 task 판정은 항상 원판정입니다.');
|
|
30459
|
+
return verifyClaimCmd(_p, args[1]);
|
|
30460
|
+
} // 1.33.2/1.36.167: --all 일괄검증 + 명시적 legacy baseline
|
|
30189
30461
|
if (cmd === 'orchestrate') return await orchestrateCmd(arg('--path', process.cwd()), args.slice(1).filter(x => !x.startsWith('-')));
|
|
30190
30462
|
if (cmd === 'llm-bench' && args[1] === 'record') return llmBenchRecordCmd(arg('--path', process.cwd()));
|
|
30191
30463
|
if (cmd === 'deps') return depsImpactCmd(arg('--path', process.cwd()), args[1]);
|
|
@@ -30448,12 +30720,9 @@ async function main() {
|
|
|
30448
30720
|
// 1.36.51 (사용자 요청 UR-0061): 모호성 질문 + 미리보기 승인 워크플로
|
|
30449
30721
|
if (cmd === 'clarify') return _clar.clarifyCmd(arg('--path', process.cwd()), args.slice(1).filter(x => !x.startsWith('-')).join(' '), { has });
|
|
30450
30722
|
if (cmd === 'preview') {
|
|
30451
|
-
//
|
|
30452
|
-
|
|
30453
|
-
|
|
30454
|
-
if (_pvRaw[i].startsWith('--')) { if (!_pvRaw[i].includes('=') && _VALUE_FLAGS.has(_pvRaw[i]) && _pvRaw[i + 1]) i++; continue; } // 1.36.75 (검수 #2): --mockup 값이 제목에 흡수되던 것. 1.36.103: 인라인 목록 → _VALUE_FLAGS (--select 등이 제목에 붙던 것) + 등호형은 다음 토큰을 먹지 않음(codex 검수 #5 재현)
|
|
30455
|
-
_pvToks.push(_pvRaw[i]);
|
|
30456
|
-
}
|
|
30723
|
+
// 전역 2-pass 파서가 command별 arity까지 반영한 positional 단일출처다.
|
|
30724
|
+
// preview 전용 raw 재파싱은 --keep 을 다시 value로 오인해 뒤의 P-ID를 삼켰다(T-0144 교차검토).
|
|
30725
|
+
const _pvToks = args.slice(1);
|
|
30457
30726
|
// 1.36.80 (UR-0067): 라이브 미리보기 — serve(로컬 서버) / mode(self|project) 는 별도 모듈로 위임
|
|
30458
30727
|
const _pvRoot = arg('--path', process.cwd());
|
|
30459
30728
|
if (_pvToks[0] === 'serve') return _pvsrv.previewServeCmd(_pvRoot, _pvToks[1], { has, arg, previews: _clar._loadPreviews(_pvRoot) });
|