leerness 1.36.16 → 1.36.17
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 +12 -0
- package/README.md +4 -4
- package/bin/leerness.js +51 -17
- package/lib/analyzers.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.17 — 2026-07-14 — 렌즈-외 표면 버그헌트: 데이터손실 2 + gate FN + 아카이브 주입 + --json 에러경로 (codex, 재현확정)
|
|
4
|
+
|
|
5
|
+
게시본 1.36.16 대상, database 렌즈를 제외한 CLI 표면을 codex로 광범위 적대 헌트 → 8건 중 6건 수정(고영향 재현확정), 2건 백로그(UR-0052).
|
|
6
|
+
|
|
7
|
+
- **P1 데이터손실 — `memory restore` (decisions/lessons)**: 복원이 MD 프로젝션에만 append 하고 canonical JSON을 안 고쳐, `list`엔 안 보이고 다음 add/save가 JSON→MD 재생성으로 복원분을 영구 삭제(재현확정). → 복원 후 MD를 canonical 파서로 재파싱해 `_saveDecisions`/`_saveLessons`로 JSON 재빌드.
|
|
8
|
+
- **P2 데이터손실 — `plan remove`**: `### ` 만 블록 경계로 써서 마지막 마일스톤 뒤 사용자 h1/h2 섹션(`## Risks` 등)이 함께 삭제(재현확정). → 모든 헤딩(`#{1,3} `)을 경계로 분할, `### M-` 블록만 삭제.
|
|
9
|
+
- **P2 gate FN — `verify-claim`**: FILE_RE/`_evidenceQuality`가 basename 첫 글자를 `[A-Za-z]`로만 잡아 `123.js` 같은 숫자시작 실파일 미추출 → 없는 파일 인용한 done 주장이 vacuous-pass(재현확정, 정상 done FP 회귀 없음 확인). → 첫 글자에 숫자 허용(prev-separator 가드 유지).
|
|
10
|
+
- **P2 아카이브 주입**: lesson/decision drop이 사용자 텍스트를 archive MD에 raw 삽입 → 개행으로 `## 제거` 델리미터 위조(가짜 archive 엔트리)(재현확정). → 아카이브 필드에 `_lineSafe`.
|
|
11
|
+
- **P2 --json 에러경로**: task update/drop·lesson/decision/plan/rule not-found 6경로가 `--json`에도 평문 출력 → JSON 소비자 파싱 실패. → `failJson(has('--json'), code, msg)`.
|
|
12
|
+
- **백로그(UR-0052)**: P1-2(10k+ ID 고정폭 `\d{4}` 리더 ~20곳, 날짜 혼동 위험으로 신중한 일괄 필요), P2-6(contract verify 주석 마스킹), P3-8(handoff `--compact` 단일 워크스페이스 분기).
|
|
13
|
+
- **검증**: selftest 287/287(신규 6수정 소스가드), 데이터손실 3건 재현→수정 후 재검, 게이트 e2e 386/386.
|
|
14
|
+
|
|
3
15
|
## 1.36.16 — 2026-07-13 — 게시본 1.36.15 클린룸 codex 9건 채택: database 렌즈 정직/정밀 + selftest·병합 하드닝
|
|
4
16
|
|
|
5
17
|
게시본 1.36.13~1.36.15 누적 신규 표면(문항 8→12, cap 16)을 codex 로 적대 리뷰 → 9건(P2/P3) 전부 재현/채택. clean 3축(i18n 패리티·앵커 유일성·크래시 없음).
|
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ MIT
|
|
|
125
125
|
<!-- leerness:project-readme:start -->
|
|
126
126
|
## Leerness Project Harness
|
|
127
127
|
|
|
128
|
-
이 프로젝트는 Leerness v1.36.
|
|
128
|
+
이 프로젝트는 Leerness v1.36.17 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
129
129
|
|
|
130
130
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
131
131
|
|
|
@@ -179,7 +179,7 @@ leerness memory restore decision <date|title>
|
|
|
179
179
|
|
|
180
180
|
### MCP server (외부 AI 통합)
|
|
181
181
|
|
|
182
|
-
Leerness v1.36.
|
|
182
|
+
Leerness v1.36.17는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
|
|
183
183
|
|
|
184
184
|
```jsonc
|
|
185
185
|
// 카테고리별
|
|
@@ -200,7 +200,7 @@ Leerness v1.36.16는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
200
200
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
201
201
|
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) 다음 라운드 예약.
|
|
202
202
|
|
|
203
|
-
현재 누적: **70 라운드 (1.9.40 → 1.36.
|
|
203
|
+
현재 누적: **70 라운드 (1.9.40 → 1.36.17)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
204
204
|
|
|
205
205
|
### 성능 가이드 (1.9.140 측정)
|
|
206
206
|
|
|
@@ -238,6 +238,6 @@ leerness release pack --close --auto-main-push
|
|
|
238
238
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
239
239
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
240
240
|
|
|
241
|
-
Last synced by Leerness v1.36.
|
|
241
|
+
Last synced by Leerness v1.36.17: 2026-07-14
|
|
242
242
|
<!-- leerness:project-readme:end -->
|
|
243
243
|
|
package/bin/leerness.js
CHANGED
|
@@ -33,7 +33,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
|
|
|
33
33
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _TOOL_CATALOG, _LSP_LANG_PATTERNS, OPTIMISM_PATTERNS, BUILT_IN_PERSONAS, STRINGS, BUILTIN_CATALOG, ROADMAP_STATUS_LABEL, ROADMAP_STATUS_COLOR, SECRET_PATTERNS, MERGE_OVERWRITE_FILES, MINIMAL_SKIP_KEYS, REQUIRED_WORKSPACE_FILES, KEYWORD_STOPWORDS, SKILL_CATALOG_PRESETS } = require('../lib/catalogs'); // 1.9.344/368/369 (UR-0025): catalog 분리 · 1.11.4 (UR-0007): _TOOL_CATALOG
|
|
34
34
|
const { findCorruptedStateJson: _findCorruptedStateJson } = require('../lib/state-integrity'); // 1.36.1 (클린룸 리뷰 FN): .harness/*.json 상태 무결성 (audit/health/check 공유)
|
|
35
35
|
|
|
36
|
-
const VERSION = '1.36.
|
|
36
|
+
const VERSION = '1.36.17';
|
|
37
37
|
|
|
38
38
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
39
39
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -3928,6 +3928,26 @@ function _selfTestCases() {
|
|
|
3928
3928
|
&& eq(F(['README.md']), ['docs']);
|
|
3929
3929
|
return catOk && mapOk;
|
|
3930
3930
|
} },
|
|
3931
|
+
{ name: '비-렌즈 버그헌트 수정 회귀 (게시본 1.36.16 codex): restore canonical 재빌드 + plan 헤딩경계 + FILE_RE 숫자시작 + archive/json 구조화 (소스가드)', run: () => {
|
|
3932
|
+
const src = read(__filename);
|
|
3933
|
+
// P1-1: memory restore 가 decisions/lessons canonical JSON 을 재빌드(MD-only append 아님)
|
|
3934
|
+
const restoreOk = src.includes("if (surface === 'decisions') _saveDecisions(root, _decisionsFromMd(read(activePath)))")
|
|
3935
|
+
&& src.includes("else if (surface === 'lessons') _saveLessons(root, _parseLessonEntries(read(activePath)))");
|
|
3936
|
+
// P2-4: plan remove 가 모든 헤딩(#{1,3} )을 경계로 split (사용자 h2 섹션 흡수 방지)
|
|
3937
|
+
const planOk = src.includes('const blocks = text.split(/\\n(?=#{1,3} )/);');
|
|
3938
|
+
// P2-5: FILE_RE + _evidenceQuality basename 첫 글자에 숫자 허용 (저-백슬래시 판별 조각)
|
|
3939
|
+
const fileReOk = src.includes(')?[A-Za-z0-9][');
|
|
3940
|
+
const analyzersOk = read(path.join(__dirname, '..', 'lib', 'analyzers.js')).includes('[A-Za-z0-9][\\w./');
|
|
3941
|
+
// P2-3: archive 쓰기에 _lineSafe (개행 델리미터 위조 차단)
|
|
3942
|
+
const archiveOk = src.includes('`\\n## 제거 ${today()} (target: "${_lineSafe(target)}")\\n${archiveBlocks}\\n`');
|
|
3943
|
+
// P2-7: drop/remove not-found 가 failJson (--json 에러 구조화)
|
|
3944
|
+
const jsonOk = src.includes("failJson(has('--json'), 'lesson_not_found'")
|
|
3945
|
+
&& src.includes("failJson(has('--json'), 'decision_not_found'")
|
|
3946
|
+
&& src.includes("failJson(has('--json'), 'milestone_not_found'")
|
|
3947
|
+
&& src.includes("failJson(has('--json'), 'task_not_found'")
|
|
3948
|
+
&& src.includes("failJson(has('--json'), 'rule_not_found'");
|
|
3949
|
+
return restoreOk && planOk && fileReOk && analyzersOk && archiveOk && jsonOk;
|
|
3950
|
+
} },
|
|
3931
3951
|
{ name: 'DB 렌즈 recall (dogfood FN, 1.36.4): 내용기반 감지 — 평범한 이름의 DB 모듈 잡고 산문 FP 0 (행위)', run: () => {
|
|
3932
3952
|
const T = _isDbContentText, W = _withDbDomain;
|
|
3933
3953
|
const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
@@ -7717,11 +7737,13 @@ function planRemoveCmd(root, target) {
|
|
|
7717
7737
|
if (!exists(pp)) return fail('plan.md 없음');
|
|
7718
7738
|
const text = read(pp);
|
|
7719
7739
|
// milestone 블록은 "### M-XXXX. 제목" 으로 시작; "## " (Out of Scope 등) 헤더 또는 EOF 이전까지
|
|
7720
|
-
|
|
7740
|
+
// codex 버그헌트 P2: `### ` 만 경계로 쓰면 마지막 마일스톤 뒤의 사용자 h1/h2 섹션(예: `## Risks`)이
|
|
7741
|
+
// 그 마일스톤 블록에 흡수돼 함께 삭제된다. 모든 헤딩(#{1,3} )을 경계로 잘라 각 섹션을 독립 블록화.
|
|
7742
|
+
const blocks = text.split(/\n(?=#{1,3} )/);
|
|
7721
7743
|
let removed = 0;
|
|
7722
7744
|
const kept = [];
|
|
7723
7745
|
for (const b of blocks) {
|
|
7724
|
-
if (!b.startsWith('### ')) { kept.push(b); continue; }
|
|
7746
|
+
if (!b.startsWith('### ')) { kept.push(b); continue; } // 마일스톤(### M-) 블록만 삭제 후보
|
|
7725
7747
|
const headerMatch = b.match(/^### (.+)$/m);
|
|
7726
7748
|
if (!headerMatch) { kept.push(b); continue; }
|
|
7727
7749
|
const titleLine = headerMatch[1].trim();
|
|
@@ -7744,7 +7766,7 @@ function planRemoveCmd(root, target) {
|
|
|
7744
7766
|
}
|
|
7745
7767
|
kept.push(b);
|
|
7746
7768
|
}
|
|
7747
|
-
if (removed === 0) return
|
|
7769
|
+
if (removed === 0) return failJson(has('--json'), 'milestone_not_found', `매칭 milestone 없음: "${target}"`); // codex P2: --json 에러 구조화
|
|
7748
7770
|
writeUtf8(pp, kept.join('\n'));
|
|
7749
7771
|
ok(`milestone removed: ${removed}건 (보존: .harness/plan.archive.md)`);
|
|
7750
7772
|
_autoRoadmap(absRoot(root), 'data-change');
|
|
@@ -7918,7 +7940,7 @@ function taskUpdate(root, id) {
|
|
|
7918
7940
|
}
|
|
7919
7941
|
if (!_validateChoice(arg('--status', null), TASK_STATUSES, 'task status')) { process.exitCode = 1; return; } // 1.9.310 (UR-0046)
|
|
7920
7942
|
const rows = readProgressRows(root);
|
|
7921
|
-
if (!rows.find(r => r.id === id)) {
|
|
7943
|
+
if (!rows.find(r => r.id === id)) { failJson(has('--json'), 'task_not_found', `task ${id} not found in progress-tracker.md`); return; } // codex P2: --json 에러 구조화
|
|
7922
7944
|
const patch = { id };
|
|
7923
7945
|
if (arg('--status') !== null) patch.status = _normTaskStatus(arg('--status')); // 1.11.3 (UR-0178): completed/verified → done 정규화
|
|
7924
7946
|
if (arg('--evidence') !== null) patch.evidence = arg('--evidence');
|
|
@@ -7936,7 +7958,7 @@ function taskDrop(root, id) {
|
|
|
7936
7958
|
if (!id) return fail('id required');
|
|
7937
7959
|
const rows = readProgressRows(root);
|
|
7938
7960
|
// 1.9.396 (6번째 외부평가/codex P1-B): 없는 task drop 시 가짜 row(request undefined) 생성 = 데이터 손상 → task update 와 동일하게 존재 확인 후 fail(no-op).
|
|
7939
|
-
if (!rows.find(r => r.id === id)) {
|
|
7961
|
+
if (!rows.find(r => r.id === id)) { failJson(has('--json'), 'task_not_found', `task ${id} not found in progress-tracker.md`); return; } // codex P2: --json 에러 구조화
|
|
7940
7962
|
upsertProgress(root, { id, status: 'dropped', evidence: arg('--reason','사용자 요청으로 제외'), nextAction: '없음' });
|
|
7941
7963
|
ok(`task dropped: ${id}`);
|
|
7942
7964
|
_autoRoadmap(absRoot(root), 'data-change');
|
|
@@ -8134,6 +8156,12 @@ function memoryRestoreCmd(root, surface, target) {
|
|
|
8134
8156
|
for (const blk of restoredBlocks) {
|
|
8135
8157
|
append(activePath, '\n' + blk + '\n');
|
|
8136
8158
|
}
|
|
8159
|
+
// codex 버그헌트 P1: decisions/lessons 는 canonical JSON 이 진실소스라(list/save 가 JSON 기준),
|
|
8160
|
+
// MD 프로젝션에만 append 하면 복원분이 list 에 안 보이고 다음 add/save 가 JSON→MD 재생성으로 영구 삭제.
|
|
8161
|
+
// 방금 복원된 MD 를 canonical 파서로 재파싱해 JSON 을 재빌드 → 복원분이 후속 write 에도 생존.
|
|
8162
|
+
if (surface === 'decisions') _saveDecisions(root, _decisionsFromMd(read(activePath)));
|
|
8163
|
+
else if (surface === 'lessons') _saveLessons(root, _parseLessonEntries(read(activePath)));
|
|
8164
|
+
// (plan 은 plan.md 가 진실소스라 append 로 충분)
|
|
8137
8165
|
// archive 재작성 — 모두 제거되면 파일 비움 (헤더만 남김 또는 삭제)
|
|
8138
8166
|
if (kept.length === 0) {
|
|
8139
8167
|
// archive 헤더만 남겨도 의미 있음 — 향후 다시 사용 가능
|
|
@@ -8198,19 +8226,21 @@ function lessonDropCmd(root, target) {
|
|
|
8198
8226
|
const kept = [];
|
|
8199
8227
|
const removed = [];
|
|
8200
8228
|
const all = _loadLessons(root);
|
|
8201
|
-
if (!all.length) return
|
|
8229
|
+
if (!all.length) return failJson(has('--json'), 'no_lessons', 'lessons 없음'); // codex P2: --json 에러 구조화
|
|
8202
8230
|
for (const l of all) {
|
|
8203
8231
|
if (l.date === target || (l.text || '').includes(target)) removed.push(l);
|
|
8204
8232
|
else kept.push(l);
|
|
8205
8233
|
}
|
|
8206
|
-
if (!removed.length) return
|
|
8234
|
+
if (!removed.length) return failJson(has('--json'), 'lesson_not_found', `매칭 lesson 없음: "${target}"`);
|
|
8207
8235
|
// archive 보존 — lessons.archive.md 에 projection MD 블록 형태로 추가
|
|
8208
8236
|
const archivePath = path.join(root, '.harness/lessons.archive.md');
|
|
8209
8237
|
const archiveHeader = exists(archivePath) ? '' : '# Lessons archive\n\n';
|
|
8238
|
+
// codex 버그헌트 P2: 사용자 텍스트(l.text/l.tag/target)를 archive MD 에 raw 삽입하면 개행으로
|
|
8239
|
+
// `## 제거 …` 델리미터를 위조해 가짜 archive 엔트리를 만들 수 있다(archive list/restore 오염). _lineSafe 로 개행 무력화.
|
|
8210
8240
|
const archiveBlocks = removed.map(l =>
|
|
8211
|
-
`\n### ${l.date}\n- Lesson: ${l.text}\n${l.tag ? `- Tag: ${l.tag}\n` : ''}`
|
|
8241
|
+
`\n### ${_lineSafe(l.date)}\n- Lesson: ${_lineSafe(l.text)}\n${l.tag ? `- Tag: ${_lineSafe(l.tag)}\n` : ''}`
|
|
8212
8242
|
).join('');
|
|
8213
|
-
append(archivePath, archiveHeader + `\n## 제거 ${today()} (target: "${target}")\n${archiveBlocks}\n`);
|
|
8243
|
+
append(archivePath, archiveHeader + `\n## 제거 ${today()} (target: "${_lineSafe(target)}")\n${archiveBlocks}\n`);
|
|
8214
8244
|
_saveLessons(root, kept);
|
|
8215
8245
|
ok(`lesson dropped: ${removed.length}건 (보존: .harness/lessons.archive.md)`);
|
|
8216
8246
|
_autoRoadmap(absRoot(root), 'data-change');
|
|
@@ -8287,22 +8317,23 @@ function decisionDropCmd(root, target) {
|
|
|
8287
8317
|
if (!target) return fail('decision drop <date|title-substring> 필요. 예: leerness decision drop "2026-05-20" 또는 leerness decision drop "PostgreSQL"');
|
|
8288
8318
|
// 1.9.339 (UR-0053): canonical JSON 기준 drop (date 또는 title substring 매칭) — JSON+MD projection 동시 갱신.
|
|
8289
8319
|
const all = _loadDecisions(root);
|
|
8290
|
-
if (!all.length) return
|
|
8320
|
+
if (!all.length) return failJson(has('--json'), 'no_decisions', 'decisions 없음'); // codex P2: --json 에러 구조화
|
|
8291
8321
|
const kept = [];
|
|
8292
8322
|
const removed = [];
|
|
8293
8323
|
for (const d of all) {
|
|
8294
8324
|
if (d.date === target || (d.title || '').includes(target)) removed.push(d);
|
|
8295
8325
|
else kept.push(d);
|
|
8296
8326
|
}
|
|
8297
|
-
if (!removed.length) return
|
|
8327
|
+
if (!removed.length) return failJson(has('--json'), 'decision_not_found', `매칭 decision 없음: "${target}"`);
|
|
8298
8328
|
// archive 보존 (projection MD 블록 형태)
|
|
8299
8329
|
const archivePath = path.join(root, '.harness/decisions.archive.md');
|
|
8300
8330
|
const archiveHeader = exists(archivePath) ? '' : '# Decisions archive\n\n';
|
|
8331
|
+
// codex 버그헌트 P2: 사용자 필드 raw 삽입 → 개행으로 `## 제거` 델리미터 위조 가능. _lineSafe 로 무력화.
|
|
8301
8332
|
const archiveBlocks = removed.map(d => {
|
|
8302
|
-
const head = d.date ? `${d.date} — ${d.title}` : d.title;
|
|
8303
|
-
return `\n### ${head}\n- Decision: ${d.decision || ''}\n- Reason: ${d.reason || ''}\n- Alternatives: ${d.alternatives || ''}\n- Impact: ${d.impact || ''}\n`;
|
|
8333
|
+
const head = d.date ? `${_lineSafe(d.date)} — ${_lineSafe(d.title)}` : _lineSafe(d.title);
|
|
8334
|
+
return `\n### ${head}\n- Decision: ${_lineSafe(d.decision || '')}\n- Reason: ${_lineSafe(d.reason || '')}\n- Alternatives: ${_lineSafe(d.alternatives || '')}\n- Impact: ${_lineSafe(d.impact || '')}\n`;
|
|
8304
8335
|
}).join('');
|
|
8305
|
-
append(archivePath, archiveHeader + `\n## 제거 ${today()} (target: "${target}")\n${archiveBlocks}\n`);
|
|
8336
|
+
append(archivePath, archiveHeader + `\n## 제거 ${today()} (target: "${_lineSafe(target)}")\n${archiveBlocks}\n`);
|
|
8306
8337
|
_saveDecisions(root, kept);
|
|
8307
8338
|
ok(`decision dropped: ${removed.length}건 (보존: .harness/decisions.archive.md)`);
|
|
8308
8339
|
_autoRoadmap(absRoot(root), 'data-change');
|
|
@@ -10971,7 +11002,10 @@ function verifyClaimCmd(root, taskId, opts = {}) {
|
|
|
10971
11002
|
// 1.36.10 (선재 백로그): Windows 백슬래시 경로 지원 — 'src\\inventory.mjs' 가 'inventory.mjs' 로,
|
|
10972
11003
|
// 'db\\migrations\\001_create.sql' 이 'create.sql' 로 잘려 추출돼 존재 검사가 false-fail 하던 결함.
|
|
10973
11004
|
// 구분자에 \\ 허용 + 추출 후 / 로 정규화(하류의 git diff 비교·렌즈 매핑은 / 기준).
|
|
10974
|
-
|
|
11005
|
+
// codex 버그헌트 P2: basename 첫 글자를 [A-Za-z] 로만 잡아 '123.js'·'2fa.ts' 같은 숫자시작 실파일이
|
|
11006
|
+
// 추출 안 돼 존재검사가 vacuous-pass → 없는 파일 인용한 done 주장이 통과(gate FN). 첫 글자에 숫자 허용.
|
|
11007
|
+
// (prev-separator 가드가 UNC/드라이브/URL 꼬리 오탐은 계속 차단하므로 FP 억제 유지)
|
|
11008
|
+
const FILE_RE = new RegExp(`(?:[A-Za-z][A-Za-z0-9_-]*[\\/\\\\])?[A-Za-z0-9][\\w./\\\\-]*\\.(?:${FILE_EXTS})\\b`, 'g');
|
|
10975
11009
|
// 1.36.11 (클린룸 P1-B): 매치 직전 문자가 경로 구분자면 더 긴 경로의 "꼬리"만 잡은 것(UNC \\\\server\\..., 드라이브 X:\\...,
|
|
10976
11010
|
// 숫자 시작 선두 세그먼트 등) — 잘린 꼬리를 루트-상대 경로로 검증하면 무관한 로컬 파일이 통과(우회성). 그런 매치는 버린다.
|
|
10977
11011
|
const filePatterns = [];
|
|
@@ -14547,7 +14581,7 @@ function ruleRemove(root, id) {
|
|
|
14547
14581
|
if (!id) return fail('id required');
|
|
14548
14582
|
const rules = readRules(root);
|
|
14549
14583
|
const i = rules.findIndex(r => r.id === id);
|
|
14550
|
-
if (i < 0) return
|
|
14584
|
+
if (i < 0) return failJson(has('--json'), 'rule_not_found', `rule not found: ${id}`); // codex P2: --json 에러 구조화
|
|
14551
14585
|
const removed = rules.splice(i, 1)[0];
|
|
14552
14586
|
writeRules(root, rules);
|
|
14553
14587
|
const archive = exists(rulesArchivePath(root)) ? read(rulesArchivePath(root)) : '# Rules archive\n\n| ID | Trigger | Rule | Added | Status | Removed |\n|---|---|---|---|---|---|\n';
|
package/lib/analyzers.js
CHANGED
|
@@ -36,7 +36,8 @@ function _shellGuardAnalyze(cmd, ctx) {
|
|
|
36
36
|
}
|
|
37
37
|
function _evidenceQuality(evidence) {
|
|
38
38
|
const e = String(evidence || '');
|
|
39
|
-
|
|
39
|
+
// codex 버그헌트 P2: basename 첫 글자에 숫자 허용 ('123.js' 등 숫자시작 실파일) — bin FILE_RE 와 정합.
|
|
40
|
+
const hasFile = /(?:[A-Za-z][\w-]*[\/\\])?[A-Za-z0-9][\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|prisma|sh)\b/i.test(e);
|
|
40
41
|
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
42
|
const hasLog = /Exit\s*[:=]|exit\s*code|Command\s*[:=]|npm\s+(?:test|run)|pytest|cargo\s+test|go\s+test/i.test(e);
|
|
42
43
|
const missing = [];
|