leerness 1.9.303 → 1.9.305
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 +44 -0
- package/README.md +6 -6
- package/bin/harness.js +47 -66
- package/lib/analyzers.js +90 -0
- package/lib/mcp-tools.js +2 -1
- package/package.json +1 -1
- package/scripts/e2e.js +46 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.305 — 2026-06-04 — honesty-check: AI 인식론적 정직성 점검 (사용자 명시)
|
|
4
|
+
|
|
5
|
+
**🧠 사용자 요청 — AI 가 (1) 모르는 걸 아는 척, (2) 정보를 안 모으고, (3) 검증 없이 섣부르게 판단하는지를 점검하는 인식론적 정직성 가드 신설.**
|
|
6
|
+
|
|
7
|
+
### 배경 (사용자 명시)
|
|
8
|
+
"AI 가 모르는 정보를 아는 척하지 않는지 / 모르는 정보를 알기 위해 정확한 정보를 수집하는지 / 수집 후 검증 없이 섣부른 판단을 하지 않는지" 점검 요청. 기존 leerness 는 부분 커버만: `optimism-check`(행동 주장 vs 코드 흔적, 11종), `api-skill`(외부 문서 수집, opt-in), `verify-claim`(검증). **3차원을 통합 점검하는 정직성 가드는 없었음.**
|
|
9
|
+
|
|
10
|
+
### 구현
|
|
11
|
+
1. **`_epistemicHonestyCheck(text)`** (lib/analyzers.js, 순수) — 3차원 휴리스틱:
|
|
12
|
+
- **pretend-knowledge**(high) — 단정 표현(반드시/항상/100%/always…)인데 근거·출처(파일/문서/테스트/로그) 없음
|
|
13
|
+
- **premature-judgment**(high) — 추정 표현(아마/추정/probably/should…) + 완료·성공 결론인데 검증 근거 없음
|
|
14
|
+
- **no-info-gathering**(med) — 외부 API/버전/스펙 언급인데 수집 흔적(공식문서/api-skill/조회) 없음 (파일경로 `api.js` 오탐 제외)
|
|
15
|
+
2. **`leerness honesty-check <T-ID> | --text "<주장>"`** — 양호 시 exit 0, high 발견 시 exit 1(에이전트 self-gate). `--json`.
|
|
16
|
+
3. **MCP `leerness_honesty_check` (82번째 도구, read-only)** — 에이전트가 단언 전 자기 주장 self-check.
|
|
17
|
+
4. **`verify-claim --strict-claims` 통합** — high-severity 정직성 발견도 strict 실패에 반영(낙관 + 정직성 통합 게이트).
|
|
18
|
+
5. selftest 52→53 · e2e 249→250.
|
|
19
|
+
|
|
20
|
+
### 검증
|
|
21
|
+
- **selftest 53/53 PASS** · **E2E 250/250 PASS** (회귀 0).
|
|
22
|
+
- 실측: "이 기능은 항상 동작"→pretend-knowledge(exit1), "아마 될 것 같다. 완료"→premature-judgment, "이 API rate limit 초당 5회"→no-info-gathering, "src/api.js 수정 12/12 통과(Exit:0)"→양호(exit0). 오탐 0(6/6 케이스).
|
|
23
|
+
- 휴리스틱 advisory — 근거(파일·문서·테스트·로그)나 수집 흔적(공식문서·api-skill·조회) 명시 시 해소.
|
|
24
|
+
|
|
25
|
+
## 1.9.304 — 2026-06-04 — UR-0025 (6단계): 순수 분석/검증 함수 → lib/analyzers.js 모듈 분리
|
|
26
|
+
|
|
27
|
+
**🧩 모놀리스 분리 계속 — 순수 분석/검증 함수 4종을 비파괴 분리. lib 6모듈 체제(630줄 외부화).**
|
|
28
|
+
|
|
29
|
+
### 배경
|
|
30
|
+
외부 리뷰 핵심(UR-0038~0043) 완료 후, 두 리뷰 공통 지적인 단일 대형 파일을 계속 점진 분리. 데이터 카탈로그(1~5단계) 다음으로 **순수 함수**(부작용 0, 입력→출력) 클러스터를 분리.
|
|
31
|
+
|
|
32
|
+
### 구현 (UR-0025 6단계)
|
|
33
|
+
1. **`lib/analyzers.js` 신설** — 순수 분석/검증 함수 4종(62줄):
|
|
34
|
+
- `_evidenceQuality`(evidence 완전성: 파일/테스트/로그 근거)
|
|
35
|
+
- `_parseEvidenceStats`(review-evidence pass/fail 집계)
|
|
36
|
+
- `_shellGuardAnalyze`(셸 호환성 6규칙 정적 분석)
|
|
37
|
+
- `_claimFileInGit`(verify-claim git 교차검증 매칭)
|
|
38
|
+
2. **비파괴 require-based 분리** — verify-claim/shell-guard/review-evidence 소비처는 동일 바인딩. 모두 순수(내부 의존 0) 검증.
|
|
39
|
+
3. **harness.js 21686→21630줄**. lib 6모듈: pure-utils+agent-registry+role-catalog+catalogs+mcp-tools+analyzers = 630줄 외부화.
|
|
40
|
+
4. selftest 51→52 (4함수 동일참조 단일출처 + 인라인 제거) · e2e 248→249 (모듈 standalone + shell-guard 동작 회귀).
|
|
41
|
+
|
|
42
|
+
### 검증
|
|
43
|
+
- **selftest 52/52 PASS** · **E2E 249/249 PASS** (회귀 0).
|
|
44
|
+
- 기존 selftest(_evidenceQuality/_parseEvidenceStats/_shellGuardAnalyze ×2/_claimFileInGit)가 추출 정합성 즉시 검증.
|
|
45
|
+
- `shell-guard "a && b" --json` 정상 동작(모듈 분리 후 회귀 없음).
|
|
46
|
+
|
|
3
47
|
## 1.9.303 — 2026-06-04 — UR-0043: 상태 파일 lost-update 락 (외부 AI 리뷰 — 멀티에이전트 안전 완성)
|
|
4
48
|
|
|
5
49
|
**🔐 원자적 쓰기(UR-0038)가 막지 못한 동시 read-modify-write lost-update를 advisory 락으로 차단. Codex/Opus가 지적한 "상태 쓰기에 락 0건" 해소 — 멀티에이전트 동시 쓰기 안전성 완성.**
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
> **A CLI harness that stops AI coding agents from faking completion, duplicating work, forgetting context, and colliding.**
|
|
5
5
|
|
|
6
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []()
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -471,7 +471,7 @@ MIT — © leerness contributors
|
|
|
471
471
|
<!-- leerness:project-readme:start -->
|
|
472
472
|
## Leerness Project Harness
|
|
473
473
|
|
|
474
|
-
이 프로젝트는 Leerness v1.9.
|
|
474
|
+
이 프로젝트는 Leerness v1.9.305 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
475
475
|
|
|
476
476
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
477
477
|
|
|
@@ -525,7 +525,7 @@ leerness memory restore decision <date|title>
|
|
|
525
525
|
|
|
526
526
|
### MCP server (외부 AI 통합)
|
|
527
527
|
|
|
528
|
-
Leerness v1.9.
|
|
528
|
+
Leerness v1.9.305는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **82개 도구**를 노출:
|
|
529
529
|
|
|
530
530
|
```jsonc
|
|
531
531
|
// 카테고리별
|
|
@@ -538,7 +538,7 @@ Leerness v1.9.303는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
538
538
|
// • Workflow: session_close / agents_list / task_export / env_check / usage_stats / reuse_map / whats_new
|
|
539
539
|
|
|
540
540
|
// MCP server 실행: leerness mcp serve
|
|
541
|
-
// tools/list 응답:
|
|
541
|
+
// tools/list 응답: 82 도구
|
|
542
542
|
```
|
|
543
543
|
|
|
544
544
|
### Autonomous mode (자율 모드)
|
|
@@ -546,7 +546,7 @@ Leerness v1.9.303는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
546
546
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
547
547
|
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) 다음 라운드 예약.
|
|
548
548
|
|
|
549
|
-
현재 누적: **70 라운드 (1.9.40 → 1.9.
|
|
549
|
+
현재 누적: **70 라운드 (1.9.40 → 1.9.305)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
550
550
|
|
|
551
551
|
### 성능 가이드 (1.9.140 측정)
|
|
552
552
|
|
|
@@ -584,6 +584,6 @@ leerness release pack --close --auto-main-push
|
|
|
584
584
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
585
585
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
586
586
|
|
|
587
|
-
Last synced by Leerness v1.9.
|
|
587
|
+
Last synced by Leerness v1.9.305: 2026-06-04
|
|
588
588
|
<!-- leerness:project-readme:end -->
|
|
589
589
|
|
package/bin/harness.js
CHANGED
|
@@ -9,10 +9,12 @@ const readline = require('readline');
|
|
|
9
9
|
// 1.9.274 (UR-0025 1단계): 순수 유틸 함수 모듈 분리 (require-based, 비파괴). selftest 7종이 동작 검증.
|
|
10
10
|
const { _isSecretKey, compareVer, parseHarnessVersion, _classifyCJK, _riskLabel, _detectSystemLang, _parseSlashFromHelp,
|
|
11
11
|
PERMISSION_TIERS, _tierRank, _requiredTier, _policyAllows, _resolveNpmTag, _mcpJsonContent, _newRunRecord } = require('../lib/pure-utils');
|
|
12
|
+
// 1.9.304 (UR-0025): 순수 분석/검증 함수 모듈 분리.
|
|
13
|
+
const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInGit, _epistemicHonestyCheck } = require('../lib/analyzers');
|
|
12
14
|
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
13
15
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST } = require('../lib/catalogs');
|
|
14
16
|
|
|
15
|
-
const VERSION = '1.9.
|
|
17
|
+
const VERSION = '1.9.305';
|
|
16
18
|
|
|
17
19
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
18
20
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -3068,6 +3070,8 @@ function _selfTestCases() {
|
|
|
3068
3070
|
{ name: 'MCP requiredTier 메타데이터 + 정책 minTier 게이트 (UR-0041 외부리뷰 1.9.301)', run: () => { const T = require('../lib/mcp-tools'); const allValid = T.length >= 81 && T.every(t => PERMISSION_TIERS.includes(t.requiredTier)); const get = n => (T.find(t => t.name === n) || {}).requiredTier; const classOk = get('leerness_state_record') === 'safe-write' && get('leerness_provider_add') === 'safe-write' && get('leerness_web') === 'network' && get('leerness_handoff') === 'read-only' && get('leerness_audit') === 'read-only'; const src = read(__filename); const gateOk = /_tierRank\(minTier\) > _tierRank\(required\)/.test(src) && /_policyEnforce\(targetPath, cliArgs\.join\(' '\), _toolDef/.test(src); return allValid && classOk && gateOk; } },
|
|
3069
3071
|
{ name: 'verify-claim git diff 시맨틱 교차검증: _gitChangedFiles/_claimFileInGit + strict FAIL 통합 (UR-0042 외부리뷰 1.9.302)', run: () => { const fnOk = typeof _gitChangedFiles === 'function' && typeof _claimFileInGit === 'function'; const matchOk = _claimFileInGit('src/api.js', new Set(['src/api.js'])) === true && _claimFileInGit('./src/api.js', new Set(['src/api.js'])) === true && _claimFileInGit('other.js', new Set(['src/api.js'])) === false && _claimFileInGit('x', null) === null; const src = read(__filename); const wired = /git diff 교차검증/.test(src) && /\|\| !gitClaimOk/.test(src) && /_gitChangedFiles\(root\)/.test(src); return fnOk && matchOk && wired; } },
|
|
3070
3072
|
{ 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; } },
|
|
3073
|
+
{ 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)); } },
|
|
3074
|
+
{ 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; } },
|
|
3071
3075
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3072
3076
|
];
|
|
3073
3077
|
}
|
|
@@ -3111,38 +3115,7 @@ function selfTestCmd(opts = {}) {
|
|
|
3111
3115
|
// leerness 는 셸을 직접 가로챌 수 없으므로(0-dep Node CLI) → 실행 전 정적 분석 + 실패 기록/회수로 대응.
|
|
3112
3116
|
// ctx: { shell: 'powershell'|'cmd'|'bash'|'sh', psVersion: '5'|'7'|null }
|
|
3113
3117
|
// 반환: { shell, psVersion, issues: [{ rule, severity, detail, suggestion }] } — 순수 함수 (단위 테스트 가능)
|
|
3114
|
-
|
|
3115
|
-
const c = String(cmd || '');
|
|
3116
|
-
const shell = (ctx && ctx.shell) || 'unknown';
|
|
3117
|
-
const psVer = ctx && ctx.psVersion != null ? parseInt(ctx.psVersion, 10) : null;
|
|
3118
|
-
const issues = [];
|
|
3119
|
-
const isWinPowerShell = shell === 'powershell' && psVer != null && psVer < 6; // 5.1 = Windows PowerShell
|
|
3120
|
-
// 규칙 1: PowerShell 5.1 에서 && / || 체이닝 미지원 (pwsh 7+ 부터 지원)
|
|
3121
|
-
if (isWinPowerShell && /\s&&\s|\s\|\|\s/.test(c)) {
|
|
3122
|
-
issues.push({ rule: 'ps5-chain', severity: 'error', detail: 'Windows PowerShell 5.1 은 && / || 연산자를 지원하지 않습니다 (PowerShell 7+ 부터 지원).', suggestion: 'A; if ($?) { B } (조건부) 또는 A; B (무조건) 로 분리. 또는 pwsh 7 설치.' });
|
|
3123
|
-
}
|
|
3124
|
-
// 규칙 2: PowerShell 에서 2>/dev/null → 2>$null
|
|
3125
|
-
if (shell === 'powershell' && /2>\s*\/dev\/null/.test(c)) {
|
|
3126
|
-
issues.push({ rule: 'ps-devnull', severity: 'error', detail: 'PowerShell 은 /dev/null 경로가 없습니다.', suggestion: '2>$null 사용 (PowerShell 리다이렉트).' });
|
|
3127
|
-
}
|
|
3128
|
-
// 규칙 3: PowerShell 에서 inline env (VAR=val cmd) 미지원
|
|
3129
|
-
if (shell === 'powershell' && /^[A-Z_][A-Z0-9_]*=[^\s]+\s+\S/.test(c.trim())) {
|
|
3130
|
-
issues.push({ rule: 'ps-inline-env', severity: 'error', detail: 'PowerShell 은 VAR=val cmd 형식의 inline 환경변수를 지원하지 않습니다.', suggestion: "$env:VAR='val'; cmd 로 분리." });
|
|
3131
|
-
}
|
|
3132
|
-
// 규칙 4: PowerShell 에서 Unix 전용 명령 (rm -rf / ls -la 등) — 별칭은 되나 플래그 오류 가능
|
|
3133
|
-
if (shell === 'powershell' && /\brm\s+-rf\b/.test(c)) {
|
|
3134
|
-
issues.push({ rule: 'ps-rm-rf', severity: 'warn', detail: 'PowerShell 에서 rm -rf 는 -rf 플래그 파싱 오류 가능 (rm 은 Remove-Item 별칭).', suggestion: 'Remove-Item -Recurse -Force <path> 사용.' });
|
|
3135
|
-
}
|
|
3136
|
-
// 규칙 5: CMD 에서 ; 는 명령 구분자가 아님 (한 줄로 실행됨)
|
|
3137
|
-
if (shell === 'cmd' && /;/.test(c) && !/&&|\|\|/.test(c)) {
|
|
3138
|
-
issues.push({ rule: 'cmd-semicolon', severity: 'warn', detail: 'CMD 는 ; 를 명령 구분자로 처리하지 않습니다 (인자로 전달됨).', suggestion: 'A && B (조건부) 또는 A & B (무조건) 사용.' });
|
|
3139
|
-
}
|
|
3140
|
-
// 규칙 6: PowerShell 에서 && 가 있으나 버전 미상 — 정보성
|
|
3141
|
-
if (shell === 'powershell' && psVer == null && /\s&&\s/.test(c)) {
|
|
3142
|
-
issues.push({ rule: 'ps-version-unknown', severity: 'info', detail: 'PowerShell 버전 미상 — 5.1 이면 && 미지원, 7+ 이면 지원.', suggestion: '$PSVersionTable.PSVersion 확인. 안전하게 A; if ($?) { B } 권장.' });
|
|
3143
|
-
}
|
|
3144
|
-
return { shell, psVersion: psVer, issues };
|
|
3145
|
-
}
|
|
3118
|
+
// _shellGuardAnalyze → lib/analyzers.js (1.9.304 UR-0025)
|
|
3146
3119
|
function _shellFailuresPath(root) { return path.join(absRoot(root), '.harness', 'shell-failures.json'); }
|
|
3147
3120
|
function _loadShellFailures(root) {
|
|
3148
3121
|
try { const f = _shellFailuresPath(root); if (!exists(f)) return { failures: [] }; const j = JSON.parse(read(f)); return j && Array.isArray(j.failures) ? j : { failures: [] }; } catch { return { failures: [] }; }
|
|
@@ -9435,17 +9408,7 @@ function reuseMapCmd(root) {
|
|
|
9435
9408
|
// "src/foo.js + 5개 테스트 (54/54 통과)" 같은 주장을 파싱해 실제 파일/카운트 확인
|
|
9436
9409
|
// 1.9.287 (Codex 리뷰 수렴): evidence 완전성 평가 — "테스트 통과만으로 done" 차단.
|
|
9437
9410
|
// done 주장은 (a) 수정 파일 경로 (b) 테스트명/개수 가 evidence 에 있어야 신뢰 가능. 순수 함수(selftest).
|
|
9438
|
-
|
|
9439
|
-
const e = String(evidence || '');
|
|
9440
|
-
const hasFile = /(?:[A-Za-z][\w-]*[\/\\])?[A-Za-z][\w./\\-]*\.(?:js|ts|tsx|jsx|mjs|cjs|py|go|rs|rb|kt|cs|gd|java|php|swift|c|cpp|h|html|css|scss|vue|svelte|json|yaml|yml|toml|md|sql|sh)\b/i.test(e);
|
|
9441
|
-
const hasTest = /(\d+)\s*(?:\/\s*\d+\s*)?(?:통과|passed|passing|개\s*테스트)|\btests?\b\s*[:=]?\s*\d|Tests?:\s*\d|\b\d+\s*tests?\b/i.test(e);
|
|
9442
|
-
const hasLog = /Exit\s*[:=]|exit\s*code|Command\s*[:=]|npm\s+(?:test|run)|pytest|cargo\s+test|go\s+test/i.test(e);
|
|
9443
|
-
const missing = [];
|
|
9444
|
-
if (!hasFile) missing.push('수정 파일 경로');
|
|
9445
|
-
if (!hasTest) missing.push('테스트명/개수');
|
|
9446
|
-
if (!hasLog) missing.push('실행 로그(Command/Exit)');
|
|
9447
|
-
return { hasFile, hasTest, hasLog, ok: hasFile && hasTest, missing };
|
|
9448
|
-
}
|
|
9411
|
+
// _evidenceQuality → lib/analyzers.js (1.9.304 UR-0025)
|
|
9449
9412
|
// 1.9.302 (UR-0042, 외부리뷰 Opus G-1): git 변경 파일 집합 — verify-claim 시맨틱 교차검증용.
|
|
9450
9413
|
// working tree(staged/unstaged/untracked) + 직전 커밋(HEAD~1..HEAD) 변경을 합쳐, "주장한 파일이 실제로 변경됐는가" 판정.
|
|
9451
9414
|
// git repo 아니거나 git 미설치면 null(검증 불가 → 페널티 X). 경로는 root-relative forward-slash.
|
|
@@ -9467,12 +9430,7 @@ function _gitChangedFiles(root) {
|
|
|
9467
9430
|
} catch { return null; }
|
|
9468
9431
|
}
|
|
9469
9432
|
// 주장 파일이 git 변경 집합에 있는지(상대경로 prefix 차이 허용).
|
|
9470
|
-
|
|
9471
|
-
if (!gitSet) return null;
|
|
9472
|
-
const c = String(claimed).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
9473
|
-
for (const g of gitSet) { if (g === c || g.endsWith('/' + c) || c.endsWith('/' + g)) return true; }
|
|
9474
|
-
return false;
|
|
9475
|
-
}
|
|
9433
|
+
// _claimFileInGit → lib/analyzers.js (1.9.304 UR-0025)
|
|
9476
9434
|
function verifyClaimCmd(root, taskId) {
|
|
9477
9435
|
root = absRoot(root);
|
|
9478
9436
|
if (!taskId) return fail('verify-claim <T-ID> 필요. 예: leerness verify-claim T-0008');
|
|
@@ -9648,10 +9606,13 @@ function verifyClaimCmd(root, taskId) {
|
|
|
9648
9606
|
// 1.9.26: --strict-claims — 낙관적 표시 자동 감지 (evidence vs 코드 호출 흔적)
|
|
9649
9607
|
let optimismSuspects = [];
|
|
9650
9608
|
let strictOk = true;
|
|
9609
|
+
let honestyFindings = [];
|
|
9651
9610
|
if (has('--strict-claims')) {
|
|
9652
9611
|
const codeText = _scanCodeForPatterns(root);
|
|
9653
9612
|
optimismSuspects = _detectOptimism(evidence, codeText);
|
|
9654
|
-
|
|
9613
|
+
// 1.9.305 (사용자 명시): 인식론적 정직성 high 발견(근거없는 단정/미검증 판단)도 strict 실패에 반영.
|
|
9614
|
+
honestyFindings = _epistemicHonestyCheck(evidence).findings.filter(f => f.severity === 'high');
|
|
9615
|
+
strictOk = optimismSuspects.length === 0 && honestyFindings.length === 0;
|
|
9655
9616
|
}
|
|
9656
9617
|
|
|
9657
9618
|
log('');
|
|
@@ -9665,10 +9626,11 @@ function verifyClaimCmd(root, taskId) {
|
|
|
9665
9626
|
if (declaredPass) log(` - 주장과 실행 결과 일치: ${declaredPassMatchesActual ? '✓ pass' : '⚠ 다름'}`);
|
|
9666
9627
|
}
|
|
9667
9628
|
if (has('--strict-claims')) {
|
|
9668
|
-
if (strictOk) log(` - 낙관적 표시 (--strict-claims): ✓ pass (의심 없음)`);
|
|
9629
|
+
if (strictOk) log(` - 낙관적 표시 + 정직성 (--strict-claims): ✓ pass (의심 없음)`);
|
|
9669
9630
|
else {
|
|
9670
|
-
log(` - 낙관적 표시 (--strict-claims): ⚠ FAIL (${optimismSuspects.length}
|
|
9631
|
+
log(` - 낙관적 표시 + 정직성 (--strict-claims): ⚠ FAIL (낙관 ${optimismSuspects.length} · 정직성 ${honestyFindings.length})`);
|
|
9671
9632
|
for (const s of optimismSuspects) log(` · [${s.kind}] ${s.label}: evidence에 주장 있는데 코드에 호출 흔적 없음`);
|
|
9633
|
+
for (const f of honestyFindings) log(` · [정직성:${f.dim}] ${f.label}: ${f.detail}`);
|
|
9672
9634
|
}
|
|
9673
9635
|
}
|
|
9674
9636
|
// 1.9.302 (UR-0042): git diff 시맨틱 교차검증 — 주장한 파일이 실제 git 변경(working tree+직전커밋)에 있는가.
|
|
@@ -10293,6 +10255,33 @@ function _computeConfidence(evidence, codeText) {
|
|
|
10293
10255
|
return Math.round(confidence * 100) / 100;
|
|
10294
10256
|
}
|
|
10295
10257
|
|
|
10258
|
+
// 1.9.305 (사용자 명시): AI 인식론적 정직성 점검 — 모르는 걸 아는 척 / 정보 미수집 / 미검증 섣부른 판단.
|
|
10259
|
+
// leerness honesty-check <T-ID> 또는 --text "<주장>". 휴리스틱 advisory, high-severity 시 exit 1(에이전트 self-gate).
|
|
10260
|
+
function honestyCheckCmd(root, arg1) {
|
|
10261
|
+
root = absRoot(root || process.cwd());
|
|
10262
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
10263
|
+
const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s, gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s, rd = s => isTty ? `\x1b[31m${s}\x1b[0m` : s, dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
10264
|
+
const textArg = arg('--text', null);
|
|
10265
|
+
let subject = '', sourceLabel = '';
|
|
10266
|
+
if (textArg) { subject = String(textArg); sourceLabel = '--text'; }
|
|
10267
|
+
else if (arg1 && !arg1.startsWith('-')) {
|
|
10268
|
+
const row = readProgressRows(root).find(r => r.id === arg1);
|
|
10269
|
+
if (!row) { fail(`progress-tracker.md에 ${arg1} 없음.`); process.exitCode = 1; return; }
|
|
10270
|
+
subject = row.evidence || ''; sourceLabel = `${arg1} evidence`;
|
|
10271
|
+
} else { fail('사용법: leerness honesty-check <T-ID> 또는 leerness honesty-check --text "<주장>"'); process.exitCode = 1; return; }
|
|
10272
|
+
const r = _epistemicHonestyCheck(subject);
|
|
10273
|
+
const high = r.findings.filter(f => f.severity === 'high').length;
|
|
10274
|
+
if (has('--json')) { log(JSON.stringify({ source: sourceLabel, ...r, highCount: high }, null, 2)); if (high > 0) process.exitCode = 1; return; }
|
|
10275
|
+
log(cy(`# leerness honesty-check (1.9.305) — AI 인식론적 정직성 점검`));
|
|
10276
|
+
log(dm(` 대상: ${sourceLabel} · "${subject.slice(0, 80)}${subject.length > 80 ? '…' : ''}"`));
|
|
10277
|
+
log('');
|
|
10278
|
+
if (r.ok) { log(gr(' ✓ 정직성 신호 양호 — 근거 없는 단정 / 미검증 섣부른 판단 / 외부정보 미수집 흔적 없음')); return; }
|
|
10279
|
+
log(rd(` ⚠ ${r.findings.length}건 발견:`));
|
|
10280
|
+
for (const f of r.findings) { log(` ${f.severity === 'high' ? '🔴' : '🟡'} [${f.dim}] ${f.label}`); log(dm(` ${f.detail}`)); }
|
|
10281
|
+
log('');
|
|
10282
|
+
log(dm(' ℹ 휴리스틱 advisory — 근거(파일·문서·테스트·로그) 또는 수집 흔적(공식문서·api-skill·조회) 명시 시 해소.'));
|
|
10283
|
+
if (high > 0) process.exitCode = 1;
|
|
10284
|
+
}
|
|
10296
10285
|
function optimismCheckCmd(root, taskId) {
|
|
10297
10286
|
root = absRoot(root || process.cwd());
|
|
10298
10287
|
if (!taskId) return fail('optimism-check <T-ID> 필요. 예: leerness optimism-check T-0001');
|
|
@@ -16932,6 +16921,9 @@ function mcpServeCmd(root) {
|
|
|
16932
16921
|
case 'leerness_about':
|
|
16933
16922
|
cliArgs = ['about', '--json'];
|
|
16934
16923
|
break;
|
|
16924
|
+
case 'leerness_honesty_check':
|
|
16925
|
+
cliArgs = ['honesty-check', ...(args.text ? ['--text', String(args.text)] : [String(args.taskId || '')]), '--path', targetPath, '--json'];
|
|
16926
|
+
break;
|
|
16935
16927
|
default:
|
|
16936
16928
|
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
|
|
16937
16929
|
}
|
|
@@ -18497,19 +18489,7 @@ function reuseCheckCmd(root, feature, opts = {}) {
|
|
|
18497
18489
|
// "설치한 스킬이 코딩 성능/정확도에 긍정 영향?" — 무거운 벤치 harness 가 아니라, 기존 데이터
|
|
18498
18490
|
// (skill 사용 빈도 + review-evidence 검증 통과율)를 상관(인과 아님)으로 advisory 제시. offline.
|
|
18499
18491
|
// review-evidence.md 파싱 (순수) — 검증 항목 수 + pass/fail 추정 (Exit 0 / 'PASS' vs 'fail'/Exit !=0).
|
|
18500
|
-
|
|
18501
|
-
const t = String(text || '');
|
|
18502
|
-
const blocks = t.split(/\n(?=## )/).filter(b => /Command:|Exit:|verify|test/i.test(b));
|
|
18503
|
-
let pass = 0, fail = 0;
|
|
18504
|
-
for (const b of blocks) {
|
|
18505
|
-
const exitM = b.match(/Exit:\s*(-?\d+)/i);
|
|
18506
|
-
if (exitM) { (parseInt(exitM[1], 10) === 0 ? pass++ : fail++); continue; }
|
|
18507
|
-
if (/\bPASS\b|통과|성공|✓/i.test(b)) pass++;
|
|
18508
|
-
else if (/\bFAIL\b|실패|오류|error|✗/i.test(b)) fail++;
|
|
18509
|
-
}
|
|
18510
|
-
const entries = blocks.length;
|
|
18511
|
-
return { entries, pass, fail, rate: (pass + fail) ? Math.round(pass / (pass + fail) * 100) : null };
|
|
18512
|
-
}
|
|
18492
|
+
// _parseEvidenceStats → lib/analyzers.js (1.9.304 UR-0025)
|
|
18513
18493
|
function skillImpactCmd(root) {
|
|
18514
18494
|
root = absRoot(root || process.cwd());
|
|
18515
18495
|
const json = has('--json');
|
|
@@ -21334,6 +21314,7 @@ async function main() {
|
|
|
21334
21314
|
if (cmd === 'deps') return depsImpactCmd(arg('--path', process.cwd()), args[1]);
|
|
21335
21315
|
if (cmd === 'register-pending') return registerPendingCmd(arg('--path', process.cwd()), args.slice(1).filter(x => !x.startsWith('-')));
|
|
21336
21316
|
if (cmd === 'optimism-check') return optimismCheckCmd(arg('--path', process.cwd()), args[1]);
|
|
21317
|
+
if (cmd === 'honesty-check') return honestyCheckCmd(arg('--path', process.cwd()), args[1]);
|
|
21337
21318
|
if (cmd === 'persona') return personaCmd(arg('--path', process.cwd()), args[1], args[2]);
|
|
21338
21319
|
if (cmd === 'review') return reviewCmd(arg('--path', process.cwd()), args[1]);
|
|
21339
21320
|
if (cmd === 'agents') return agentsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
package/lib/analyzers.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// lib/analyzers.js — 순수 분석/검증 함수 (부작용 0, 입력→출력).
|
|
2
|
+
// 1.9.304 (UR-0025): bin/harness.js 에서 비파괴 분리. selftest(evidenceQuality/parseEvidenceStats/shellGuardAnalyze/claimFileInGit)가 동작 검증.
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
function _shellGuardAnalyze(cmd, ctx) {
|
|
6
|
+
const c = String(cmd || '');
|
|
7
|
+
const shell = (ctx && ctx.shell) || 'unknown';
|
|
8
|
+
const psVer = ctx && ctx.psVersion != null ? parseInt(ctx.psVersion, 10) : null;
|
|
9
|
+
const issues = [];
|
|
10
|
+
const isWinPowerShell = shell === 'powershell' && psVer != null && psVer < 6; // 5.1 = Windows PowerShell
|
|
11
|
+
// 규칙 1: PowerShell 5.1 에서 && / || 체이닝 미지원 (pwsh 7+ 부터 지원)
|
|
12
|
+
if (isWinPowerShell && /\s&&\s|\s\|\|\s/.test(c)) {
|
|
13
|
+
issues.push({ rule: 'ps5-chain', severity: 'error', detail: 'Windows PowerShell 5.1 은 && / || 연산자를 지원하지 않습니다 (PowerShell 7+ 부터 지원).', suggestion: 'A; if ($?) { B } (조건부) 또는 A; B (무조건) 로 분리. 또는 pwsh 7 설치.' });
|
|
14
|
+
}
|
|
15
|
+
// 규칙 2: PowerShell 에서 2>/dev/null → 2>$null
|
|
16
|
+
if (shell === 'powershell' && /2>\s*\/dev\/null/.test(c)) {
|
|
17
|
+
issues.push({ rule: 'ps-devnull', severity: 'error', detail: 'PowerShell 은 /dev/null 경로가 없습니다.', suggestion: '2>$null 사용 (PowerShell 리다이렉트).' });
|
|
18
|
+
}
|
|
19
|
+
// 규칙 3: PowerShell 에서 inline env (VAR=val cmd) 미지원
|
|
20
|
+
if (shell === 'powershell' && /^[A-Z_][A-Z0-9_]*=[^\s]+\s+\S/.test(c.trim())) {
|
|
21
|
+
issues.push({ rule: 'ps-inline-env', severity: 'error', detail: 'PowerShell 은 VAR=val cmd 형식의 inline 환경변수를 지원하지 않습니다.', suggestion: "$env:VAR='val'; cmd 로 분리." });
|
|
22
|
+
}
|
|
23
|
+
// 규칙 4: PowerShell 에서 Unix 전용 명령 (rm -rf / ls -la 등) — 별칭은 되나 플래그 오류 가능
|
|
24
|
+
if (shell === 'powershell' && /\brm\s+-rf\b/.test(c)) {
|
|
25
|
+
issues.push({ rule: 'ps-rm-rf', severity: 'warn', detail: 'PowerShell 에서 rm -rf 는 -rf 플래그 파싱 오류 가능 (rm 은 Remove-Item 별칭).', suggestion: 'Remove-Item -Recurse -Force <path> 사용.' });
|
|
26
|
+
}
|
|
27
|
+
// 규칙 5: CMD 에서 ; 는 명령 구분자가 아님 (한 줄로 실행됨)
|
|
28
|
+
if (shell === 'cmd' && /;/.test(c) && !/&&|\|\|/.test(c)) {
|
|
29
|
+
issues.push({ rule: 'cmd-semicolon', severity: 'warn', detail: 'CMD 는 ; 를 명령 구분자로 처리하지 않습니다 (인자로 전달됨).', suggestion: 'A && B (조건부) 또는 A & B (무조건) 사용.' });
|
|
30
|
+
}
|
|
31
|
+
// 규칙 6: PowerShell 에서 && 가 있으나 버전 미상 — 정보성
|
|
32
|
+
if (shell === 'powershell' && psVer == null && /\s&&\s/.test(c)) {
|
|
33
|
+
issues.push({ rule: 'ps-version-unknown', severity: 'info', detail: 'PowerShell 버전 미상 — 5.1 이면 && 미지원, 7+ 이면 지원.', suggestion: '$PSVersionTable.PSVersion 확인. 안전하게 A; if ($?) { B } 권장.' });
|
|
34
|
+
}
|
|
35
|
+
return { shell, psVersion: psVer, issues };
|
|
36
|
+
}
|
|
37
|
+
function _evidenceQuality(evidence) {
|
|
38
|
+
const e = String(evidence || '');
|
|
39
|
+
const hasFile = /(?:[A-Za-z][\w-]*[\/\\])?[A-Za-z][\w./\\-]*\.(?:js|ts|tsx|jsx|mjs|cjs|py|go|rs|rb|kt|cs|gd|java|php|swift|c|cpp|h|html|css|scss|vue|svelte|json|yaml|yml|toml|md|sql|sh)\b/i.test(e);
|
|
40
|
+
const hasTest = /(\d+)\s*(?:\/\s*\d+\s*)?(?:통과|passed|passing|개\s*테스트)|\btests?\b\s*[:=]?\s*\d|Tests?:\s*\d|\b\d+\s*tests?\b/i.test(e);
|
|
41
|
+
const hasLog = /Exit\s*[:=]|exit\s*code|Command\s*[:=]|npm\s+(?:test|run)|pytest|cargo\s+test|go\s+test/i.test(e);
|
|
42
|
+
const missing = [];
|
|
43
|
+
if (!hasFile) missing.push('수정 파일 경로');
|
|
44
|
+
if (!hasTest) missing.push('테스트명/개수');
|
|
45
|
+
if (!hasLog) missing.push('실행 로그(Command/Exit)');
|
|
46
|
+
return { hasFile, hasTest, hasLog, ok: hasFile && hasTest, missing };
|
|
47
|
+
}
|
|
48
|
+
function _claimFileInGit(claimed, gitSet) {
|
|
49
|
+
if (!gitSet) return null;
|
|
50
|
+
const c = String(claimed).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
51
|
+
for (const g of gitSet) { if (g === c || g.endsWith('/' + c) || c.endsWith('/' + g)) return true; }
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
function _parseEvidenceStats(text) {
|
|
55
|
+
const t = String(text || '');
|
|
56
|
+
const blocks = t.split(/\n(?=## )/).filter(b => /Command:|Exit:|verify|test/i.test(b));
|
|
57
|
+
let pass = 0, fail = 0;
|
|
58
|
+
for (const b of blocks) {
|
|
59
|
+
const exitM = b.match(/Exit:\s*(-?\d+)/i);
|
|
60
|
+
if (exitM) { (parseInt(exitM[1], 10) === 0 ? pass++ : fail++); continue; }
|
|
61
|
+
if (/\bPASS\b|통과|성공|✓/i.test(b)) pass++;
|
|
62
|
+
else if (/\bFAIL\b|실패|오류|error|✗/i.test(b)) fail++;
|
|
63
|
+
}
|
|
64
|
+
const entries = blocks.length;
|
|
65
|
+
return { entries, pass, fail, rate: (pass + fail) ? Math.round(pass / (pass + fail) * 100) : null };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 1.9.305 (사용자 명시): AI 인식론적 정직성 점검 — 모르는 걸 아는 척 / 정보 미수집 / 미검증 섣부른 판단 휴리스틱 탐지.
|
|
69
|
+
// 순수 함수(텍스트→findings). 휴리스틱 advisory — 단정/추정/외부참조 표현 vs 근거·수집 흔적 대조. opt-in 점검용.
|
|
70
|
+
function _epistemicHonestyCheck(text) {
|
|
71
|
+
const t = String(text || '');
|
|
72
|
+
const findings = [];
|
|
73
|
+
// 공통: 근거/출처 흔적 (파일경로·URL·테스트결과·Exit·문서·api-skill·인용·조회 흔적)
|
|
74
|
+
const hasSource = /(?:[\w./-]+\.(?:js|ts|tsx|jsx|py|go|rs|rb|md|json|ya?ml|toml|sql|sh)\b)|https?:\/\/|\bExit\s*[:=]|\d+\s*\/\s*\d+\s*(?:통과|passed)|\b(?:passed|passing)\b|근거[::]|출처[::]|api-skill|공식\s*문서|문서\s*(?:확인|참조|에\s*따르면)|읽었|조회(?:함|했|함\b)|확인(?:함|했|됨)|grep|로그[::]/i.test(t);
|
|
75
|
+
// 차원1: 모르는 걸 아는 척 — 단정 표현인데 근거 없음
|
|
76
|
+
const definitive = /(반드시|항상|언제나|무조건|확실(?:히|함|하게)|당연히|틀림없|100\s*%|always|never|guaranteed|definitely|obviously|certainly)/i.test(t);
|
|
77
|
+
if (definitive && !hasSource) findings.push({ dim: 'pretend-knowledge', severity: 'high', label: '근거 없는 단정', detail: '단정적 표현이 있으나 근거/출처(파일·문서·테스트·로그)가 없음 — 모르는 정보를 아는 척할 위험.' });
|
|
78
|
+
// 차원2: 미검증 섣부른 판단 — 추정 표현 + 완료/성공 결론인데 근거 없음
|
|
79
|
+
const assumption = /(아마|추정|것\s*같|듯\s*(?:하|싶)|probably|likely|maybe|perhaps|i\s*(?:think|assume|guess|believe|suppose)|should\s*(?:work|be|pass|fix)|생각(?:됩니다|된다|함|돼)|일\s*것|예상(?:됩니다|된다|됨)|짐작)/i.test(t);
|
|
80
|
+
const conclusion = /(완료|done|성공|통과|해결(?:됨|했|함|되었)|fixed|resolved|works?\b|작동(?:함|한다|됨)|구현(?:됨|했|완료))/i.test(t);
|
|
81
|
+
if (assumption && conclusion && !hasSource) findings.push({ dim: 'premature-judgment', severity: 'high', label: '검증 없는 섣부른 판단', detail: '가정·추정 표현과 완료·성공 결론이 함께 있으나 검증 근거가 없음 — 검증 없이 섣부르게 판단할 위험.' });
|
|
82
|
+
// 차원3: 정보 미수집 — 외부 API/라이브러리/버전/스펙 언급인데 수집·근거 흔적 없음
|
|
83
|
+
// \bAPI\b(?!\.[a-z]) 로 파일경로(api.js/api.ts) 오탐 제외. 강한 근거(hasSource)나 수집 흔적(gathered) 있으면 통과.
|
|
84
|
+
const externalRef = /(\bAPI\b(?!\.[a-z])|\bSDK\b|라이브러리|\blibrary\b|\bpackage\b|엔드포인트|\bendpoint\b|버전\s*\d|v\d+\.\d+|\bspec\b|rate\s*limit|레이트\s*리밋|문서에\s*따르면)/i.test(t);
|
|
85
|
+
const gathered = /(https?:\/\/|api-skill|공식\s*문서|\bdocs?\b|문서\s*(?:확인|참조|읽)|읽었|조회(?:함|했)|확인(?:함|했|됨)|fetch|검색(?:함|했)|레퍼런스|reference)/i.test(t);
|
|
86
|
+
if (externalRef && !gathered && !hasSource) findings.push({ dim: 'no-info-gathering', severity: 'medium', label: '외부 정보 미수집', detail: '외부 API/라이브러리/버전/스펙 언급이 있으나 정보 수집(공식문서·api-skill·조회) 흔적이 없음 — 정확한 정보를 먼저 수집 권장.' });
|
|
87
|
+
return { ok: findings.length === 0, findings, dimensions: ['pretend-knowledge', 'premature-judgment', 'no-info-gathering'] };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInGit, _epistemicHonestyCheck };
|
package/lib/mcp-tools.js
CHANGED
|
@@ -85,5 +85,6 @@ module.exports = [
|
|
|
85
85
|
{ name: 'leerness_state_verify', requiredTier: 'safe-write', description: '1.9.279 (UR-0031) — request_verification / verify_done. 현재 run 의 검증 결과 기록. 인자: { path?, result (required: "pass"|"fail"), note? }. 응답: { verified, result, run }.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, result: { type: 'string', enum: ['pass', 'fail'] }, note: { type: 'string' } }, required: ['result'] } },
|
|
86
86
|
{ name: 'leerness_state_handoff', requiredTier: 'safe-write', description: '1.9.279 (UR-0031) — create_handoff / make_handoff. 현재 run 을 마감하고 다음 에이전트용 .leerness/handoff/latest.{md,json} 작성 (Claude→Goose→Codex 인수인계 표준). 인자: { path?, summary (required) }. 응답: { handoff, run }.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, summary: { type: 'string' } }, required: ['summary'] } },
|
|
87
87
|
{ name: 'leerness_get_project_context', requiredTier: 'read-only', description: '1.9.292 (UR-0031) — get_project_context. 외부 에이전트가 작업 시작 전 단 1콜로 "지금 무엇을 알아야 하는가"를 파악하는 집약 컨텍스트. 여러 소스(현재 작업/미답 사용자 요청/최근 결정/활성 룰/next-actions/memory surface/프로젝트 의도)를 한 번에 구조화 회수. read-only. 인자: { path? }. 응답: { version, project, currentTask, openRequests, recentDecisions, activeRules, nextActions, memory }.', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
88
|
-
{ name: 'leerness_about', requiredTier: 'read-only', description: '1.9.296 (UR-0030) — leerness 정체성/포지셔닝 회수. leerness 가 무엇인가: "AI 에이전트 운영 레이어"(실행기 아님) — 기억·정책·인수인계·검증·감사 5계층. read-only. 어떤 에이전트든 "이 도구가 무엇이고 무엇을 보완하는가"를 1콜로 파악. 응답: { identity, isNot, tagline, layers[], complements, entryPoints, surface }.', inputSchema: { type: 'object', properties: {} } }
|
|
88
|
+
{ name: 'leerness_about', requiredTier: 'read-only', description: '1.9.296 (UR-0030) — leerness 정체성/포지셔닝 회수. leerness 가 무엇인가: "AI 에이전트 운영 레이어"(실행기 아님) — 기억·정책·인수인계·검증·감사 5계층. read-only. 어떤 에이전트든 "이 도구가 무엇이고 무엇을 보완하는가"를 1콜로 파악. 응답: { identity, isNot, tagline, layers[], complements, entryPoints, surface }.', inputSchema: { type: 'object', properties: {} } },
|
|
89
|
+
{ name: 'leerness_honesty_check', requiredTier: 'read-only', description: '1.9.305 (사용자 명시) — AI 인식론적 정직성 점검. 주장/evidence 텍스트가 (1) 모르는 걸 아는 척(근거 없는 단정), (2) 검증 없는 섣부른 판단(추정+완료 결론), (3) 외부 정보 미수집(API/버전/스펙 언급+수집흔적 없음) 중 무엇에 해당하는지 휴리스틱 탐지. read-only. 에이전트가 자기 주장을 단언 전 self-check. 인자: { path?, taskId?, text? }. 응답: { ok, findings[], dimensions[], highCount }.', inputSchema: { type: 'object', properties: { path: { type: 'string' }, taskId: { type: 'string' }, text: { type: 'string' } } } }
|
|
89
90
|
];
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -654,7 +654,7 @@ total++;
|
|
|
654
654
|
fs.appendFileSync(path.join(tmpS, '.harness/progress-tracker.md'),
|
|
655
655
|
'| T-0050 | done | DB 마이그레이션 | 사용자 데이터 DB에 저장, 1000건 insert 성공 | (완료) | 2026-05-15 |\n');
|
|
656
656
|
const r = cp.spawnSync(process.execPath, [CLI, 'verify-claim', 'T-0050', '--path', tmpS, '--strict-claims'], { encoding: 'utf8', timeout: 10000 });
|
|
657
|
-
const ok = r.status !== 0 && /낙관적
|
|
657
|
+
const ok = r.status !== 0 && /낙관적 표시.*\(--strict-claims\): ⚠ FAIL/.test(r.stdout) && /DB 호출/.test(r.stdout);
|
|
658
658
|
console.log(ok ? '✓ B(1.9.26) verify-claim --strict-claims: DB 거짓 evidence 통합 감지' : '✗ --strict-claims 실패');
|
|
659
659
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
660
660
|
}
|
|
@@ -3432,5 +3432,50 @@ total++;
|
|
|
3432
3432
|
if (!ok) failed++;
|
|
3433
3433
|
}
|
|
3434
3434
|
|
|
3435
|
+
// 1.9.304 회귀 (UR-0025): lib/analyzers.js 모듈 분리 — 단일출처 + 부작용 0 + 소비 명령 회귀
|
|
3436
|
+
total++;
|
|
3437
|
+
{
|
|
3438
|
+
let ok = false;
|
|
3439
|
+
try {
|
|
3440
|
+
const a = require(path.resolve(__dirname, '..', 'lib', 'analyzers.js'));
|
|
3441
|
+
const dataOk = typeof a._evidenceQuality === 'function' && typeof a._parseEvidenceStats === 'function' &&
|
|
3442
|
+
typeof a._shellGuardAnalyze === 'function' && typeof a._claimFileInGit === 'function' &&
|
|
3443
|
+
a._evidenceQuality('src/api.js 수정, 12/12 통과 (Exit: 0)').ok === true &&
|
|
3444
|
+
a._shellGuardAnalyze('a && b', { shell: 'powershell', psVersion: '5' }).issues.some(i => i.rule === 'ps5-chain') &&
|
|
3445
|
+
a._claimFileInGit('src/api.js', new Set(['src/api.js'])) === true;
|
|
3446
|
+
const harnessSrc = fs.readFileSync(path.resolve(__dirname, '..', 'bin', 'harness.js'), 'utf8');
|
|
3447
|
+
const movedOut = !/function _evidenceQuality\(evidence\) \{/.test(harnessSrc) && !/function _shellGuardAnalyze\(cmd, ctx\) \{/.test(harnessSrc) && /require\('\.\.\/lib\/analyzers'\)/.test(harnessSrc);
|
|
3448
|
+
// 소비 명령 회귀: shell-guard (_shellGuardAnalyze 사용)
|
|
3449
|
+
const sg = cp.spawnSync(process.execPath, [CLI, 'shell-guard', 'a && b', '--json'], { cwd: tmp, encoding: 'utf8', timeout: 20000 });
|
|
3450
|
+
const cmdOk = sg.status === 0 && /"shell"/.test(sg.stdout || '');
|
|
3451
|
+
ok = dataOk && movedOut && cmdOk;
|
|
3452
|
+
} catch {}
|
|
3453
|
+
console.log(ok ? '✓ B(1.9.304) lib/analyzers 모듈 분리: 단일출처 + 인라인 제거 + shell-guard 동작 (UR-0025)' : '✗ analyzers 모듈 분리 실패');
|
|
3454
|
+
if (!ok) failed++;
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
// 1.9.305 회귀 (사용자 명시): honesty-check — AI 인식론적 정직성 3차원 + exit code + MCP 노출
|
|
3458
|
+
total++;
|
|
3459
|
+
{
|
|
3460
|
+
let ok = false;
|
|
3461
|
+
try {
|
|
3462
|
+
// 양호(근거 있음) → exit 0 + ✓
|
|
3463
|
+
const good = cp.spawnSync(process.execPath, [CLI, 'honesty-check', '--text', 'src/api.js 수정, 12/12 통과 (Exit: 0)'], { cwd: tmp, encoding: 'utf8', timeout: 15000 });
|
|
3464
|
+
const goodOk = good.status === 0 && /정직성 신호 양호/.test(good.stdout || '');
|
|
3465
|
+
// 근거 없는 단정 → exit 1 + pretend-knowledge
|
|
3466
|
+
const bad1 = cp.spawnSync(process.execPath, [CLI, 'honesty-check', '--text', '이 기능은 항상 정상 동작합니다', '--json'], { cwd: tmp, encoding: 'utf8', timeout: 15000 });
|
|
3467
|
+
const b1 = JSON.parse(bad1.stdout); const bad1Ok = bad1.status === 1 && b1.findings.some(f => f.dim === 'pretend-knowledge');
|
|
3468
|
+
// 미검증 섣부른 판단 → premature-judgment
|
|
3469
|
+
const bad2 = cp.spawnSync(process.execPath, [CLI, 'honesty-check', '--text', '아마 될 것 같습니다. 구현 완료했습니다', '--json'], { cwd: tmp, encoding: 'utf8', timeout: 15000 });
|
|
3470
|
+
const bad2Ok = JSON.parse(bad2.stdout).findings.some(f => f.dim === 'premature-judgment');
|
|
3471
|
+
// MCP tools/list 에 노출
|
|
3472
|
+
const ml = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], { cwd: tmp, encoding: 'utf8', timeout: 15000, input: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }) + '\n' });
|
|
3473
|
+
const mcpOk = JSON.parse(ml.stdout.split('\n').filter(Boolean)[0]).result.tools.some(t => t.name === 'leerness_honesty_check');
|
|
3474
|
+
ok = goodOk && bad1Ok && bad2Ok && mcpOk;
|
|
3475
|
+
} catch {}
|
|
3476
|
+
console.log(ok ? '✓ B(1.9.305) honesty-check: 3차원 탐지 + exit code + MCP 노출 (모르는걸 아는척/미검증판단/정보미수집)' : '✗ honesty-check 실패');
|
|
3477
|
+
if (!ok) failed++;
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3435
3480
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
3436
3481
|
if (failed > 0) process.exit(1);
|