leerness 1.9.444 → 1.10.0
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 +85 -0
- package/README.md +4 -4
- package/bin/leerness.js +98 -14
- package/lib/pure-utils.js +19 -2
- package/package.json +1 -1
- package/scripts/e2e.js +28 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,90 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.10.0 — 2026-06-08 — 🛡️ [안정화/Stable] 1.10 안정 minor (배포 정책 첫 minor 릴리스)
|
|
4
|
+
|
|
5
|
+
**🛡️ 안정화(Stable) minor — patch 누적(1.9.446~449)을 검증·통합해 npm 에 안정 버전으로 공개.** R-0011 배포 정책(patch 는 누적, minor 만 npm 공개)의 첫 minor 릴리스.
|
|
6
|
+
|
|
7
|
+
### 이번 minor 에 담긴 개선 (1.9.446~449, 그동안 GitHub 에만 누적)
|
|
8
|
+
- **npm 배포 minor-gate** (UR-0160): patch 는 npm 미배포, minor/major 변동 시에만 안정 배포. `release sync-main . --publish-npm` 강제 옵션.
|
|
9
|
+
- **plan progress 읽기전용 요약** (UR-0145): 완료율% + 상태별 + `--json` + 변경의도 인자 경고(silent ignore 제거).
|
|
10
|
+
- **export/prompt = adapter 별칭** (UR-0154, GPT-5.5 §6.6): `export --target claude|cursor|codex|agents-md`.
|
|
11
|
+
- **plan drop 역할 분리** (UR-0143): scope 드랍을 plan.md(Out of Scope)에만 기록, progress-tracker phantom task 제거.
|
|
12
|
+
|
|
13
|
+
### 직전 안정(1.9.x)부터의 핵심 (참고)
|
|
14
|
+
- evidence-first 완료 게이트 `completion_claim_allowed` (UR-0153, GPT-5.5 §6.3), CI/PR 턴키 `ci init` (UR-0152, §6.7), task/add-family positional path (UR-0141/0151), 시크릿 스캐너 prefix placeholder 가드(12th 리뷰 Opus P2), README ASCII 배너.
|
|
15
|
+
|
|
16
|
+
### 검증 (회귀 0)
|
|
17
|
+
- **selftest 194 PASS** · **E2E 365/365 PASS** · npm gate=minor_bump(배포) 확인.
|
|
18
|
+
|
|
19
|
+
### 안정화 표시 (R-0006)
|
|
20
|
+
CHANGELOG [안정화/Stable] · git tag annotation (Stable) · GitHub release (Stable) · npm dist-tag `stable` 시도.
|
|
21
|
+
|
|
22
|
+
## 1.9.449 — 2026-06-08 — plan drop: progress-tracker T- 행 미생성 (12th 외부평가 Sonnet P3, UR-0143)
|
|
23
|
+
|
|
24
|
+
**🐛 plan↔progress 역할 분리: scope 드랍이 phantom task 를 만들던 문제.**
|
|
25
|
+
|
|
26
|
+
### 변경
|
|
27
|
+
- `plan drop` 이 progress-tracker 에 `dropped` 상태의 `T-` task 행을 만들던 동작 제거 — 이제 plan.md `## Out of Scope / Dropped`(D-) 에만 기록. scope 드랍의 단일 출처 = plan.md.
|
|
28
|
+
- 기존엔 scope 드랍 1건이 plan.md D- + progress-tracker T- 양쪽에 생겨 plan↔progress 역할 혼선 + `task list` 노이즈(실제 작업 아닌 phantom T-) 발생.
|
|
29
|
+
|
|
30
|
+
### 검증 (회귀 0)
|
|
31
|
+
- **selftest 193→194** (planDrop 에 dropped-task upsert 없음 + ok 메시지가 plan.md 가리킴), 행위 재현: plan drop → D-0001 기록 + progress T- 행 미증가.
|
|
32
|
+
- patch(1.9.449, 같은 minor) — R-0011 정책상 npm 미배포, GitHub 만 갱신.
|
|
33
|
+
|
|
34
|
+
## 1.9.448 — 2026-06-08 — export/prompt = adapter 별칭 (GPT-5.5 전략리뷰 §6.6/7.3, UR-0154)
|
|
35
|
+
|
|
36
|
+
**🔌 GPT-5.5 권고 표면명 정합: `leerness export/prompt --target <agent>`.**
|
|
37
|
+
|
|
38
|
+
### 변경
|
|
39
|
+
- 신규 `export --target <agent>` / `prompt --target <agent>` — 기존 `adapter <tool>` 의 별칭(도구별 지침/계약 파일 생성: claude/cursor/codex/copilot/goose). `--target` 플래그 또는 positional 모두 지원, 미지정 시 list.
|
|
40
|
+
- `agents-md`/`agents`/`agent` → `codex`(AGENTS.md 생성) 별칭 매핑 — GPT-5.5 가 권고한 `export --target agents-md` 가 그대로 동작.
|
|
41
|
+
- GPT-5.5 전략리뷰가 권고한 명령 표면명(`export`/`prompt --target`)을 기존 adapter 위에 제공 → 외부 사용자 발견성↑.
|
|
42
|
+
|
|
43
|
+
### 검증 (회귀 0)
|
|
44
|
+
- **selftest 192→193** (와이어), 행위 재현: `export --target claude`→CLAUDE.md, `export --target agents-md`→AGENTS.md.
|
|
45
|
+
- patch(1.9.448, 같은 minor) — R-0011 정책상 npm 미배포, GitHub 만 갱신.
|
|
46
|
+
|
|
47
|
+
### 보류 메모
|
|
48
|
+
- UR-0146(init --json 순수화): init=install 큰 핸들러의 다중 log(~15) quiet-mode 게이팅 필요 — P3 대비 침투적이라 quiet-mode 리팩터 라운드로 보류. audit --fix --json 의 fixApplied(=fix모드)/fixed(=카운트)는 의미상 일관(by-design).
|
|
49
|
+
|
|
50
|
+
## 1.9.447 — 2026-06-08 — plan progress 읽기전용 요약 명확화 (12th 외부평가 Codex P3, UR-0145)
|
|
51
|
+
|
|
52
|
+
**🐛 명령명-동작 불일치 해소: `plan progress` 가 인자를 silent ignore 하던 문제.**
|
|
53
|
+
|
|
54
|
+
### 변경
|
|
55
|
+
- `plan progress` 를 읽기 전용 진행 요약으로 명확화 — **완료율%**(done/total) + 상태별 카운트 + 마일스톤 수 출력. `--json` 구조화 지원.
|
|
56
|
+
- `plan progress M-0002 --status … --progress …` 처럼 변경 의도 인자가 오면 **읽기 전용임을 경고**(기존: silent ignore) — 상태/진행 변경은 `task update`/`plan add|drop` 안내.
|
|
57
|
+
- 기존엔 task status 카운트만 출력하며 명령명이 milestone 변경처럼 오인됨.
|
|
58
|
+
|
|
59
|
+
### 검증 (회귀 0)
|
|
60
|
+
- **selftest 191→192** (richer 출력 + --json + 변경의도 경고 와이어), 행위 재현: 완료율 1/3(33%)·--json(total/done/percent)·M-id+--status 시 경고.
|
|
61
|
+
- patch(1.9.447, 같은 minor) — R-0011 정책상 npm 미배포, GitHub 만 갱신.
|
|
62
|
+
|
|
63
|
+
## 1.9.446 — 2026-06-08 — npm 배포 minor-gate (R-0011/UR-0160)
|
|
64
|
+
|
|
65
|
+
**📦 배포 정책 도구화: npm 배포는 minor(x.x) 변동 시에만 — patch(x.x.x) 는 자동 스킵.**
|
|
66
|
+
|
|
67
|
+
### 변경
|
|
68
|
+
- 순수 `_shouldPublishNpm(current, published, force)` + `_minorKey` (pure-utils): npm latest 와 major.minor 비교 → minor/major ↑ 면 배포, 같은 minor 내 patch 면 스킵. 최초/force 는 배포.
|
|
69
|
+
- `_publishToNpm` 에 minor-gate 추가(1.5단계): `npm view <pkg> version` 으로 latest 확인 후, 같은 minor patch 면 npm publish 스킵(GitHub 커밋·CHANGELOG 는 유지). `release sync-main . --publish-npm` 으로 강제 배포.
|
|
70
|
+
- 사용자 정책(R-0011): 자잘한 patch 는 npm 미배포로 누적, 안정화되면 minor 올려 안정 버전만 npm 노출 → npm latest 안정성 향상.
|
|
71
|
+
|
|
72
|
+
### 검증 (회귀 0)
|
|
73
|
+
- **selftest 190→191** (순수 7케이스 + 와이어): 1.9.446 vs 1.9.445=스킵(same_minor), 1.10.0=배포(minor_bump), 2.0.0=배포, force/최초=배포.
|
|
74
|
+
- 본 패치(1.9.446)부터 정책 적용 — npm 미배포(같은 minor), GitHub 만 갱신.
|
|
75
|
+
|
|
76
|
+
## 1.9.445 — 2026-06-08 — add-family positional path 일관화 (UR-0151, UR-0141 후속)
|
|
77
|
+
|
|
78
|
+
**🐛 데이터 오염 일관화: `decision/lesson/rule add` 도 positional path 인식 (task 와 동일).**
|
|
79
|
+
|
|
80
|
+
### 변경
|
|
81
|
+
- `decision add`, `lesson save`, `rule add` 가 positional path 를 인식 — `--path` > path-like positional > cwd. 1.9.442(task)에서 도입한 순수 `_taskPositionalPath` 재사용 → 제목/내용은 경로로 오인 안 함(선행 구분자 only), 값-플래그값(`--reason`/`--trigger`/`--tag` 등) 제외.
|
|
82
|
+
- `_TASK_VALUE_FLAGS` 에 add-family 값-플래그(`--trigger`/`--tag`) 추가.
|
|
83
|
+
- 기존엔 블록 root 가 `arg('--path', cwd)` 만 사용 → 비-루트 cwd 에서 add 시 cwd 오염(멀티프로젝트). 이제 task 와 일관.
|
|
84
|
+
|
|
85
|
+
### 검증 (회귀 0)
|
|
86
|
+
- **selftest 189→190** (와이어 3종 + --trigger 값 제외), **E2E 신규 B(1.9.445)**: 다른 cwd 에서 decision/lesson/rule add `<ws>` → ws 저장 + cwd 오염 0.
|
|
87
|
+
|
|
3
88
|
## 1.9.444 — 2026-06-08 — CI/PR 턴키: leerness ci init (GPT-5.5 전략리뷰 §6.7, UR-0152)
|
|
4
89
|
|
|
5
90
|
**⚙️ leerness 를 팀/CI 품질 게이트로 확장 — PR 마다 `leerness gate` 를 실행하는 GitHub Actions 워크플로 1-커맨드 생성.**
|
package/README.md
CHANGED
|
@@ -168,7 +168,7 @@ MIT
|
|
|
168
168
|
<!-- leerness:project-readme:start -->
|
|
169
169
|
## Leerness Project Harness
|
|
170
170
|
|
|
171
|
-
이 프로젝트는 Leerness v1.
|
|
171
|
+
이 프로젝트는 Leerness v1.10.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
172
172
|
|
|
173
173
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
174
174
|
|
|
@@ -222,7 +222,7 @@ leerness memory restore decision <date|title>
|
|
|
222
222
|
|
|
223
223
|
### MCP server (외부 AI 통합)
|
|
224
224
|
|
|
225
|
-
Leerness v1.
|
|
225
|
+
Leerness v1.10.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
|
|
226
226
|
|
|
227
227
|
```jsonc
|
|
228
228
|
// 카테고리별
|
|
@@ -243,7 +243,7 @@ Leerness v1.9.444는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
243
243
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
244
244
|
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
|
|
245
245
|
|
|
246
|
-
현재 누적: **70 라운드 (1.9.40 → 1.
|
|
246
|
+
현재 누적: **70 라운드 (1.9.40 → 1.10.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
247
247
|
|
|
248
248
|
### 성능 가이드 (1.9.140 측정)
|
|
249
249
|
|
|
@@ -281,6 +281,6 @@ leerness release pack --close --auto-main-push
|
|
|
281
281
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
282
282
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
283
283
|
|
|
284
|
-
Last synced by Leerness v1.
|
|
284
|
+
Last synced by Leerness v1.10.0: 2026-06-08
|
|
285
285
|
<!-- leerness:project-readme:end -->
|
|
286
286
|
|
package/bin/leerness.js
CHANGED
|
@@ -25,13 +25,13 @@ const { _isSecretKey, _isPlaceholderSecret, _looksSecretLike, _mergeLines, _merg
|
|
|
25
25
|
_withBuiltinSource, _esc, _roadmapTokenStyles, _parseSkillMd,
|
|
26
26
|
_migrationGuideText, _parseContractSpec, _gitignoreMatch,
|
|
27
27
|
_featureGraphTemplate, _parseFeatureGraph, _nextFeatureId, _featureBlock, _featureImpactBfs,
|
|
28
|
-
_parseChangelogBetween, _cellSafe, _cellUnescape, _lineSafe, _parseLimit, _parseAddTitle, _parseImplExports, _taskPositionalPath, _completionClaimAllowed } = require('../lib/pure-utils'); // 1.9.318~
|
|
28
|
+
_parseChangelogBetween, _cellSafe, _cellUnescape, _lineSafe, _parseLimit, _parseAddTitle, _parseImplExports, _taskPositionalPath, _completionClaimAllowed, _minorKey, _shouldPublishNpm } = require('../lib/pure-utils'); // 1.9.318~446 (UR-0025/.../0153/0160): 순수 유틸 모듈 분리
|
|
29
29
|
// 1.9.304 (UR-0025): 순수 분석/검증 함수 모듈 분리.
|
|
30
30
|
const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInGit, _epistemicHonestyCheck } = require('../lib/analyzers');
|
|
31
31
|
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
32
32
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _LSP_LANG_PATTERNS, OPTIMISM_PATTERNS, BUILT_IN_PERSONAS, STRINGS, BUILTIN_CATALOG, ROADMAP_STATUS_LABEL, ROADMAP_STATUS_COLOR, SECRET_PATTERNS, MERGE_OVERWRITE_FILES, MINIMAL_SKIP_KEYS, REQUIRED_WORKSPACE_FILES, KEYWORD_STOPWORDS, SKILL_CATALOG_PRESETS } = require('../lib/catalogs'); // 1.9.344/368/369 (UR-0025): catalog 분리 (MERGE_OVERWRITE_FILES/MINIMAL_SKIP_KEYS 포함)
|
|
33
33
|
|
|
34
|
-
const VERSION = '1.
|
|
34
|
+
const VERSION = '1.10.0';
|
|
35
35
|
|
|
36
36
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
37
37
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -3096,7 +3096,7 @@ function _selfTestCases() {
|
|
|
3096
3096
|
} },
|
|
3097
3097
|
{ name: '10th 외부평가 Sonnet P2: rule add flag/경로 break(_parseAddTitle) — trigger 값/경로 흡수 차단 (1.9.426)', run: () => {
|
|
3098
3098
|
const src = read(__filename);
|
|
3099
|
-
const wired = src.includes("ruleAdd(arg('--path', process.cwd()
|
|
3099
|
+
const wired = src.includes("ruleAdd(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd(), _parseAddTitle(args, 2))");
|
|
3100
3100
|
const m = require('../lib/pure-utils');
|
|
3101
3101
|
const u = m._parseAddTitle(['rule', 'add', '세션', '점검', '--trigger', 'every-session', '/p'], 2) === '세션 점검';
|
|
3102
3102
|
return wired && u;
|
|
@@ -3355,6 +3355,56 @@ function _selfTestCases() {
|
|
|
3355
3355
|
const wired = src.includes("cmd === 'ci' && (args[1] === 'init'") && src.includes('ciInitCmd(absRoot(_resolveRoot(args[2]))');
|
|
3356
3356
|
return contentOk && wired;
|
|
3357
3357
|
} },
|
|
3358
|
+
{ name: 'UR-0151: decision/lesson/rule add positional path 지원(_taskPositionalPath 재사용, cwd 오염 차단) (1.9.445)', run: () => {
|
|
3359
|
+
const src = read(__filename);
|
|
3360
|
+
const rule = src.includes("ruleAdd(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd(), _parseAddTitle(args, 2))");
|
|
3361
|
+
const lesson = src.includes("if (cmd === 'lesson') {\n const root = absRoot(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd());");
|
|
3362
|
+
const decision = src.includes("if (cmd === 'decision') {\n const root = absRoot(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd());");
|
|
3363
|
+
// rule add 의 --trigger 값은 경로 아님(path-like 아님) + 값-플래그 제외
|
|
3364
|
+
const m = require('../lib/pure-utils');
|
|
3365
|
+
const trig = m._taskPositionalPath(['rule', 'add', '룰', '--trigger', 'every-update', '/p'], 2) === '/p'
|
|
3366
|
+
&& m._taskPositionalPath(['rule', 'add', '룰', '--trigger', 'every-update'], 2) === null;
|
|
3367
|
+
return rule && lesson && decision && trig;
|
|
3368
|
+
} },
|
|
3369
|
+
{ name: 'R-0011/UR-0160: npm 배포 minor-gate _shouldPublishNpm + _publishToNpm 와이어 (1.9.446)', run: () => {
|
|
3370
|
+
const m = require('../lib/pure-utils');
|
|
3371
|
+
if (typeof m._shouldPublishNpm !== 'function' || m._shouldPublishNpm !== _shouldPublishNpm) return false;
|
|
3372
|
+
const f = m._shouldPublishNpm;
|
|
3373
|
+
const pure = f('1.9.446', '1.9.445', false).publish === false // 같은 minor patch → 미배포
|
|
3374
|
+
&& f('1.9.446', '1.9.445', false).reason === 'same_minor'
|
|
3375
|
+
&& f('1.10.0', '1.9.445', false).publish === true // minor ↑ → 배포
|
|
3376
|
+
&& f('1.10.0', '1.9.445', false).reason === 'minor_bump'
|
|
3377
|
+
&& f('2.0.0', '1.9.445', false).publish === true // major ↑ → 배포
|
|
3378
|
+
&& f('1.9.446', '1.9.445', true).publish === true // force → 배포
|
|
3379
|
+
&& f('1.9.0', null, false).publish === true // 최초 → 배포
|
|
3380
|
+
&& m._minorKey('1.9.445') === '1.9' && m._minorKey('1.10.0') === '1.10';
|
|
3381
|
+
const src = read(__filename);
|
|
3382
|
+
const wired = src.includes('const gate = _shouldPublishNpm(pkgVersion, publishedLatest, false);')
|
|
3383
|
+
&& src.includes("forcePublish: has('--publish-npm')");
|
|
3384
|
+
return pure && wired;
|
|
3385
|
+
} },
|
|
3386
|
+
{ name: '12th 외부평가 Codex P3 (UR-0145): plan progress 읽기전용 요약(완료율%/--json) + 변경의도 인자 경고 (1.9.447)', run: () => {
|
|
3387
|
+
const src = read(__filename);
|
|
3388
|
+
const richer = src.includes('const percent = total ? Math.round((done / total) * 100) : 0;') && src.includes("log(`# plan progress (읽기 전용 요약)`);");
|
|
3389
|
+
const jsonOut = src.includes("log(JSON.stringify({ ok: true, total, done, percent, milestones, counts }, null, 2)); return;");
|
|
3390
|
+
const intentWarn = src.includes('if (opts.updateIntent) warn(') && src.includes("updateIntent: args.slice(2).some(a => /^M-\\d/i.test(a)) || has('--status') || arg('--progress', null) != null");
|
|
3391
|
+
return richer && jsonOut && intentWarn;
|
|
3392
|
+
} },
|
|
3393
|
+
{ name: 'GPT-5.5 전략리뷰 §6.6 (UR-0154): export/prompt = adapter 별칭 (--target + agents-md→codex) (1.9.448)', run: () => {
|
|
3394
|
+
const src = read(__filename);
|
|
3395
|
+
const wired = src.includes("if (cmd === 'export' || cmd === 'prompt') {")
|
|
3396
|
+
&& src.includes("arg('--target', null)")
|
|
3397
|
+
&& src.includes("'agents-md': 'codex'")
|
|
3398
|
+
&& src.includes('return adapterCmd(arg(\'--path\', process.cwd()), _xTool);');
|
|
3399
|
+
return wired;
|
|
3400
|
+
} },
|
|
3401
|
+
{ name: '12th 외부평가 Sonnet P3 (UR-0143): plan drop 은 plan.md(D-)만 기록, progress-tracker T- 행 미생성 (1.9.449)', run: () => {
|
|
3402
|
+
const src = read(__filename);
|
|
3403
|
+
// planDrop 에서 upsertProgress(dropped task) 제거 + ok 메시지가 plan.md 가리킴
|
|
3404
|
+
const noTaskRow = !/function planDrop[\s\S]*?upsertProgress\(root, \{ id: tid, status: 'dropped'/.test(src);
|
|
3405
|
+
const dropsToPlan = src.includes("ok(`plan dropped: ${id} → .harness/plan.md (Out of Scope / Dropped)`);");
|
|
3406
|
+
return noTaskRow && dropsToPlan;
|
|
3407
|
+
} },
|
|
3358
3408
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3359
3409
|
];
|
|
3360
3410
|
}
|
|
@@ -3934,6 +3984,7 @@ function commandsCmd(root) {
|
|
|
3934
3984
|
{ cmd: 'state show|start|record|verify|handoff', desc: '.leerness/ JSON 상태 substrate (에이전트 간 인수인계 표준) — 1.9.278' },
|
|
3935
3985
|
{ cmd: 'adapter <tool>|list [--dry-run]', desc: '도구별 지침/.mcp.json 선택 생성 (claude/cursor/codex/goose/...) — 1.9.280' },
|
|
3936
3986
|
{ cmd: 'ci init [path] [--force]', desc: 'PR 마다 leerness gate 실행하는 GitHub Actions 워크플로 생성 (.github/workflows/leerness-gate.yml) — 1.9.444' },
|
|
3987
|
+
{ cmd: 'export|prompt --target <agent>', desc: 'adapter 별칭 — 도구별 지침/계약 파일 생성 (claude/cursor/codex/agents-md/...) — 1.9.448' },
|
|
3937
3988
|
{ cmd: 'policy show|set|check', desc: '권한 등급 (read-only…publish) — opt-in enforced (위험 명령 차단) — 1.9.281' },
|
|
3938
3989
|
{ cmd: 'reuse-check "<기능>"', desc: '외부 OSS 빌드 vs 재사용 결정 게이트 (오프라인 카테고리+체크리스트) — 1.9.285' },
|
|
3939
3990
|
{ cmd: 'skill impact', desc: '스킬 설치 영향 경량 상관추적 (사용 빈도 ↔ 검증 통과율, advisory) — 1.9.286' },
|
|
@@ -6310,15 +6361,26 @@ function planDrop(root, text) {
|
|
|
6310
6361
|
} else {
|
|
6311
6362
|
append(planFile, `\n${droppedHeader}\n| ID | Item | Reason | Date |\n|---|---|---|---|\n| ${id} | ${text} | ${reason} | ${today()} |\n`);
|
|
6312
6363
|
}
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
ok(`plan dropped: ${id} →
|
|
6364
|
+
// 1.9.449 (12th 외부평가 Sonnet P3, UR-0143): progress-tracker 에 dropped task(T-) 행 생성 제거 — plan drop 은 plan.md "Out of Scope / Dropped"(D-) 에만 기록.
|
|
6365
|
+
// 기존엔 scope 드랍이 phantom T- task 로도 생겨 plan↔progress 역할 혼선 + task list 노이즈. 드랍 기록의 단일 출처 = plan.md.
|
|
6366
|
+
ok(`plan dropped: ${id} → .harness/plan.md (Out of Scope / Dropped)`);
|
|
6316
6367
|
}
|
|
6317
|
-
function planProgress(root) {
|
|
6368
|
+
function planProgress(root, opts = {}) {
|
|
6369
|
+
// 1.9.447 (12th 외부평가 Codex P3, UR-0145): 읽기 전용 진행 요약 — 완료율%+상태별+마일스톤 수. --json 지원.
|
|
6370
|
+
// 기존엔 task status 카운트만 출력하며 명령명이 milestone 변경처럼 오인됨 + M-id/--status/--progress 를 silent ignore → 변경의도 인자 시 경고.
|
|
6318
6371
|
const rows = readProgressRows(root);
|
|
6372
|
+
const tasks = rows.filter(r => /^T-/.test(r.id));
|
|
6319
6373
|
const counts = {}; for (const s of STATUSES) counts[s] = 0;
|
|
6320
|
-
for (const r of
|
|
6321
|
-
|
|
6374
|
+
for (const r of tasks) if (counts[r.status] != null) counts[r.status]++;
|
|
6375
|
+
const total = tasks.length;
|
|
6376
|
+
const done = counts['done'] || 0;
|
|
6377
|
+
const percent = total ? Math.round((done / total) * 100) : 0;
|
|
6378
|
+
const milestones = rows.filter(r => /^M-/.test(r.id)).length;
|
|
6379
|
+
if (opts.json) { log(JSON.stringify({ ok: true, total, done, percent, milestones, counts }, null, 2)); return; }
|
|
6380
|
+
log(`# plan progress (읽기 전용 요약)`);
|
|
6381
|
+
log(` 완료율: ${done}/${total} (${percent}%)${milestones ? ` · 마일스톤 ${milestones}개` : ''}`);
|
|
6382
|
+
for (const s of STATUSES) if (counts[s]) log(` ${s}: ${counts[s]}`);
|
|
6383
|
+
if (opts.updateIntent) warn('plan progress 는 읽기 전용 요약입니다 — 상태/진행 변경은 task update <T-ID> --status … 또는 plan add|drop 을 사용하세요.');
|
|
6322
6384
|
}
|
|
6323
6385
|
// 1.9.126: plan remove — milestone 블록을 plan.md에서 제거 + archive 보존
|
|
6324
6386
|
// Memory Surface DELETE 5종 완전 완성 (task drop / decision drop / lesson drop / rule remove / plan remove)
|
|
@@ -13017,7 +13079,7 @@ function releaseSyncMainCmd(root) {
|
|
|
13017
13079
|
// opt-out: --no-npm 또는 LEERNESS_NO_NPM_PUBLISH=1
|
|
13018
13080
|
// 토큰 미설정 시 친절한 안내 후 skip (실패 X).
|
|
13019
13081
|
if (!has('--no-npm') && process.env.LEERNESS_NO_NPM_PUBLISH !== '1') {
|
|
13020
|
-
try { _publishToNpm(root, { dryRun: has('--dry-run-npm') }); } catch (e) { warn('npm publish 시도 실패 (계속): ' + e.message); }
|
|
13082
|
+
try { _publishToNpm(root, { dryRun: has('--dry-run-npm'), forcePublish: has('--publish-npm') }); } catch (e) { warn('npm publish 시도 실패 (계속): ' + e.message); } // 1.9.446 (UR-0160): --publish-npm 으로 minor-gate 강제 우회
|
|
13021
13083
|
}
|
|
13022
13084
|
}
|
|
13023
13085
|
|
|
@@ -13186,6 +13248,21 @@ function _publishToNpm(root, opts = {}) {
|
|
|
13186
13248
|
}
|
|
13187
13249
|
} catch {} // 네트워크 실패 시 그냥 publish 시도
|
|
13188
13250
|
|
|
13251
|
+
// 1.5) R-0011/UR-0160: npm 배포는 minor(x.x) 변동 시에만 — 같은 minor 내 patch 는 스킵(GitHub/CHANGELOG 는 유지). --publish-npm 으로 강제.
|
|
13252
|
+
if (!opts.forcePublish) {
|
|
13253
|
+
let publishedLatest = null;
|
|
13254
|
+
try {
|
|
13255
|
+
const latestR = cp.spawnSync('npm', ['view', pkgName, 'version'], { cwd: root, encoding: 'utf8', shell: true, timeout: 15000 });
|
|
13256
|
+
if (latestR.status === 0) publishedLatest = (latestR.stdout || '').trim();
|
|
13257
|
+
} catch {}
|
|
13258
|
+
const gate = _shouldPublishNpm(pkgVersion, publishedLatest, false);
|
|
13259
|
+
if (!gate.publish) {
|
|
13260
|
+
log(` ⏸ npm 배포 스킵 (R-0011): patch(${pkgVersion}) 는 npm 미배포 — 직전 npm minor(${_minorKey(publishedLatest) || '?'}) 와 동일. minor 올릴 때만 안정 버전으로 배포. (강제: release sync-main . --publish-npm)`);
|
|
13261
|
+
return;
|
|
13262
|
+
}
|
|
13263
|
+
if (gate.reason === 'minor_bump') log(` ▶ minor 변동(${_minorKey(publishedLatest)} → ${_minorKey(pkgVersion)}) — npm 안정 배포 진행`);
|
|
13264
|
+
}
|
|
13265
|
+
|
|
13189
13266
|
// 2) 임시 .npmrc 생성 (토큰 노출 방지)
|
|
13190
13267
|
let tmpDir;
|
|
13191
13268
|
try {
|
|
@@ -19028,6 +19105,13 @@ async function main() {
|
|
|
19028
19105
|
const _aRoot = (args[2] && !args[2].startsWith('-')) ? args[2] : arg('--path', process.cwd());
|
|
19029
19106
|
return adapterCmd(_aRoot, _aTool);
|
|
19030
19107
|
}
|
|
19108
|
+
// 1.9.448 (GPT-5.5 전략리뷰 §6.6/7.3, UR-0154): export/prompt = adapter 별칭 — GPT-5.5 권고 표면명 정합(도구별 지침/계약 파일 생성).
|
|
19109
|
+
if (cmd === 'export' || cmd === 'prompt') {
|
|
19110
|
+
const _ADAPTER_ALIAS = { 'agents-md': 'codex', 'agents': 'codex', 'agent': 'codex', 'agents.md': 'codex' };
|
|
19111
|
+
let _xTool = arg('--target', null) || (args[1] && !args[1].startsWith('-') ? args[1] : 'list');
|
|
19112
|
+
_xTool = _ADAPTER_ALIAS[String(_xTool).toLowerCase()] || _xTool;
|
|
19113
|
+
return adapterCmd(arg('--path', process.cwd()), _xTool);
|
|
19114
|
+
}
|
|
19031
19115
|
// 1.9.245: API skill cache — 공식 문서·관련링크 자동 정리 (사용자 명시 UR-0015)
|
|
19032
19116
|
if (cmd === 'api-skill') return apiSkillCmd(arg('--path', process.cwd()), args[1] || 'help');
|
|
19033
19117
|
// 1.9.208: leerness constraints <list|check|add> — 플랫폼/API 제약 사전 체크 (사용자 명시)
|
|
@@ -19044,7 +19128,7 @@ async function main() {
|
|
|
19044
19128
|
if (cmd === 'idempotency') return idempotencyCmd(arg('--path', process.cwd()), args[1]);
|
|
19045
19129
|
// 1.9.213: leerness intent <classify|expand|domains> — intent inference + scope expansion (사용자 명시)
|
|
19046
19130
|
if (cmd === 'intent') return intentCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
19047
|
-
if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()
|
|
19131
|
+
if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd(), _parseAddTitle(args, 2)); // 1.9.426: flag/경로 break(_parseAddTitle) · 1.9.445 (UR-0151): positional path 지원(제목과 분리)
|
|
19048
19132
|
if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
|
|
19049
19133
|
if (cmd === 'rule' && args[1] === 'remove') return ruleRemove(arg('--path', process.cwd()), args[2]);
|
|
19050
19134
|
if (cmd === 'rule' && args[1] === 'pause') return rulePause(arg('--path', process.cwd()), args[2]);
|
|
@@ -19084,7 +19168,7 @@ async function main() {
|
|
|
19084
19168
|
if (sub==='add') return planAdd(root, args.slice(2).join(' ') || '새 계획');
|
|
19085
19169
|
if (sub==='drop') return planDrop(root, args.slice(2).join(' ') || '드랍 항목');
|
|
19086
19170
|
if (sub==='remove') return planRemoveCmd(root, args[2]);
|
|
19087
|
-
if (sub==='progress') return planProgress(root);
|
|
19171
|
+
if (sub==='progress') return planProgress(root, { json: has('--json'), updateIntent: args.slice(2).some(a => /^M-\d/i.test(a)) || has('--status') || arg('--progress', null) != null }); // 1.9.447 (UR-0145): --json + 변경의도 인자 경고
|
|
19088
19172
|
if (sub==='sync') return planSync(root);
|
|
19089
19173
|
// 1.9.119: plan list — 모든 milestone JSON/verbose
|
|
19090
19174
|
if (sub==='list') return planListCmd(absRoot(_resolveRoot(args[2])), { json: has('--json') }); // 1.9.412 (UR-0100): positional path 지원
|
|
@@ -19125,7 +19209,7 @@ async function main() {
|
|
|
19125
19209
|
// 1.9.112: lesson save — lessons.md에 새 lesson 추가
|
|
19126
19210
|
// 1.9.117: lesson list — lessons.md 조회 + --tag 필터 + --json
|
|
19127
19211
|
if (cmd === 'lesson') {
|
|
19128
|
-
const root = absRoot(arg('--path', process.cwd())
|
|
19212
|
+
const root = absRoot(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd()); const sub = args[1] || ''; // 1.9.445 (UR-0151): positional path 지원
|
|
19129
19213
|
if (sub === 'save') {
|
|
19130
19214
|
const textParts = [];
|
|
19131
19215
|
for (let i = 2; i < args.length; i++) {
|
|
@@ -19152,7 +19236,7 @@ async function main() {
|
|
|
19152
19236
|
// 1.9.108: decision add — decisions.md에 새 설계 결정 추가
|
|
19153
19237
|
// 1.9.118: decision list — decisions.md 전체 조회 + --json
|
|
19154
19238
|
if (cmd === 'decision') {
|
|
19155
|
-
const root = absRoot(arg('--path', process.cwd())
|
|
19239
|
+
const root = absRoot(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd()); const sub = args[1] || ''; // 1.9.445 (UR-0151): positional path 지원
|
|
19156
19240
|
if (sub === 'add') {
|
|
19157
19241
|
// args[2..] 가 title (단, --flag 또는 경로형 positional 이 시작되기 전까지)
|
|
19158
19242
|
// 1.9.351 (UR-0064) → 1.9.416 (UR-0122): 공유 헬퍼 _parseAddTitle 로 단일화(flag/경로 break) + 빈 입력 거부
|
package/lib/pure-utils.js
CHANGED
|
@@ -1025,7 +1025,9 @@ module.exports = {
|
|
|
1025
1025
|
// 1.9.442 (12th 외부평가, UR-0141): task 계열 positional path 안전 추출
|
|
1026
1026
|
_taskPositionalPath,
|
|
1027
1027
|
// 1.9.443 (GPT-5.5 전략리뷰 §6.3, UR-0153): evidence-first 완료 게이트
|
|
1028
|
-
_completionClaimAllowed
|
|
1028
|
+
_completionClaimAllowed,
|
|
1029
|
+
// 1.9.446 (R-0011/UR-0160): npm 배포 minor-gate
|
|
1030
|
+
_minorKey, _shouldPublishNpm
|
|
1029
1031
|
};
|
|
1030
1032
|
|
|
1031
1033
|
// 1.9.355 (UR-0075 Phase A): AI 에이전트용 크로스버전 마이그레이션 안전 워크플로 가이드 (순수 텍스트). 임시설치 + --path + 백업 + diff 검증.
|
|
@@ -1288,6 +1290,21 @@ function _lineSafe(s) { return String(s == null ? '' : s).replace(/\r\n|\r|\n/g,
|
|
|
1288
1290
|
// 1.9.407 (8번째 버그헌트, UR-0111): --limit 안전 파싱 — NaN(예: '--limit abc')/음수/0 은 slice(0,NaN)=[] 로 모든 결과를 조용히 숨김 → 기본값으로 폴백.
|
|
1289
1291
|
function _parseLimit(raw, def) { const n = parseInt(raw, 10); return (Number.isFinite(n) && n > 0) ? n : def; }
|
|
1290
1292
|
|
|
1293
|
+
// 1.9.446 (R-0011/UR-0160): npm 배포 minor-gate. current(현재 버전) vs published(npm latest) 의 major.minor 비교.
|
|
1294
|
+
// minor 가 올라갔으면(또는 최초/major↑) publish, 같은 minor 내 patch 면 skip. force 면 무조건 publish.
|
|
1295
|
+
function _minorKey(v) { const m = String(v || '').match(/^(\d+)\.(\d+)/); return m ? `${m[1]}.${m[2]}` : null; }
|
|
1296
|
+
function _shouldPublishNpm(current, published, force) {
|
|
1297
|
+
if (force) return { publish: true, reason: 'forced' };
|
|
1298
|
+
const cm = String(current || '').match(/^(\d+)\.(\d+)/);
|
|
1299
|
+
if (!cm) return { publish: false, reason: 'invalid_current' };
|
|
1300
|
+
const pm = String(published || '').match(/^(\d+)\.(\d+)/);
|
|
1301
|
+
if (!pm) return { publish: true, reason: 'no_published' }; // 최초 배포
|
|
1302
|
+
const c = [Number(cm[1]), Number(cm[2])], p = [Number(pm[1]), Number(pm[2])];
|
|
1303
|
+
if (c[0] > p[0] || (c[0] === p[0] && c[1] > p[1])) return { publish: true, reason: 'minor_bump' }; // major/minor ↑
|
|
1304
|
+
if (c[0] === p[0] && c[1] === p[1]) return { publish: false, reason: 'same_minor' }; // patch — 미배포
|
|
1305
|
+
return { publish: false, reason: 'not_ahead' }; // 동일/하위
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1291
1308
|
// 1.9.416 (9th 외부평가 Sonnet/Codex, UR-0122): add 류(task/requests/decision) 제목 파싱 단일 출처.
|
|
1292
1309
|
// positional 을 join 하되 첫 --flag 또는 경로형 토큰(/x, C:\x, ./x, ../x)에서 멈춤 →
|
|
1293
1310
|
// `task add "제목" /some/path` 가 경로를 제목에 흡수하던 오염(decision add 는 이미 차단)을 일관 적용.
|
|
@@ -1305,7 +1322,7 @@ function _parseAddTitle(args, startIdx = 0) {
|
|
|
1305
1322
|
// 1.9.442 (12th 외부평가 Sonnet UR-0141): task 계열 positional path 안전 추출.
|
|
1306
1323
|
// _parseAddTitle 과 동일한 path-like 판정(선행 구분자 / ./ ../ C:\)으로 제목/ID/맨이름은 경로로 오인 안 함(src/auth 같은 내부 슬래시 제목 보호).
|
|
1307
1324
|
// 값-취하는 플래그(--evidence /abs/log 등)의 값은 root 후보에서 제외(직전 토큰이 값-플래그면 skip) → 오탐 차단. 첫 path-like positional 만 반환, 없으면 null.
|
|
1308
|
-
const _TASK_VALUE_FLAGS = new Set(['--status', '--evidence', '--priority', '--note', '--reason', '--title', '--desc', '--summary', '--id', '--limit', '--from', '--to']);
|
|
1325
|
+
const _TASK_VALUE_FLAGS = new Set(['--status', '--evidence', '--priority', '--note', '--reason', '--title', '--desc', '--summary', '--id', '--limit', '--from', '--to', '--trigger', '--tag']); // 1.9.445 (UR-0151): rule/lesson add 값-플래그(--trigger/--tag) 포함
|
|
1309
1326
|
function _taskPositionalPath(args, startIdx = 2) {
|
|
1310
1327
|
const a = args || [];
|
|
1311
1328
|
for (let i = startIdx; i < a.length; i++) {
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -1457,7 +1457,7 @@ total++;
|
|
|
1457
1457
|
const r = cp.spawnSync(process.execPath, [CLI, 'migrate', tmpC, '--yes', '--no-banner', '--no-stale-check'], { encoding: 'utf8', timeout: 60000 });
|
|
1458
1458
|
const ok = r.status === 0
|
|
1459
1459
|
&& /AI must re-read/.test(r.stdout)
|
|
1460
|
-
&& /1\.9\.36 →
|
|
1460
|
+
&& /1\.9\.36 → \d+\.\d+\.\d+/.test(r.stdout)
|
|
1461
1461
|
&& /신규 명령/.test(r.stdout);
|
|
1462
1462
|
console.log(ok ? '✓ B(1.9.41) migrate stdout: AI must re-read 차분 자동 출력' : `✗ migrate 차분 출력 실패`);
|
|
1463
1463
|
if (!ok) { failed++; console.log(r.stdout.slice(-800)); }
|
|
@@ -1902,7 +1902,7 @@ total++;
|
|
|
1902
1902
|
&& /███████╗/.test(r.stdout)
|
|
1903
1903
|
&& /verify · remember/.test(r.stdout)
|
|
1904
1904
|
&& /AI 에이전트 검수.기억.드리프트 방지 하네스/.test(r.stdout)
|
|
1905
|
-
&& /
|
|
1905
|
+
&& /v\d+\.\d+\.\d+/.test(r.stdout);
|
|
1906
1906
|
console.log(ok ? '✓ B(1.9.34) 배너 색상 + ASCII + 한국어' : `✗ 배너 색상 실패`);
|
|
1907
1907
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1908
1908
|
}
|
|
@@ -6174,5 +6174,31 @@ total++;
|
|
|
6174
6174
|
if (!ok) failed++;
|
|
6175
6175
|
}
|
|
6176
6176
|
|
|
6177
|
+
// 1.9.445 (UR-0151): add-family(decision/lesson/rule) positional path — 다른 cwd 에서 실행해도 positional 경로에 저장(cwd 오염 차단).
|
|
6178
|
+
total++;
|
|
6179
|
+
{
|
|
6180
|
+
let ok = false;
|
|
6181
|
+
try {
|
|
6182
|
+
const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-addpos-'));
|
|
6183
|
+
const cwd3 = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-cwd3-'));
|
|
6184
|
+
cp.spawnSync(process.execPath, [CLI, 'init', ws, '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000 });
|
|
6185
|
+
cp.spawnSync(process.execPath, [CLI, 'decision', 'add', '결정E2E', ws, '--reason', 'r'], { encoding: 'utf8', timeout: 15000, cwd: cwd3 });
|
|
6186
|
+
cp.spawnSync(process.execPath, [CLI, 'lesson', 'save', '교훈E2E', ws], { encoding: 'utf8', timeout: 15000, cwd: cwd3 });
|
|
6187
|
+
cp.spawnSync(process.execPath, [CLI, 'rule', 'add', '룰E2E', '--trigger', 'every-update', ws], { encoding: 'utf8', timeout: 15000, cwd: cwd3 });
|
|
6188
|
+
const hdir = path.join(ws, '.harness');
|
|
6189
|
+
let savedAll = false;
|
|
6190
|
+
try {
|
|
6191
|
+
const blob = fs.readdirSync(hdir).map(f => { try { return fs.readFileSync(path.join(hdir, f), 'utf8'); } catch { return ''; } }).join('\n');
|
|
6192
|
+
savedAll = blob.includes('결정E2E') && blob.includes('교훈E2E') && blob.includes('룰E2E');
|
|
6193
|
+
} catch {}
|
|
6194
|
+
const cwdClean = !fs.existsSync(path.join(cwd3, '.harness'));
|
|
6195
|
+
fs.rmSync(ws, { recursive: true, force: true });
|
|
6196
|
+
fs.rmSync(cwd3, { recursive: true, force: true });
|
|
6197
|
+
ok = savedAll && cwdClean;
|
|
6198
|
+
} catch {}
|
|
6199
|
+
console.log(ok ? '✓ B(1.9.445) UR-0151: decision/lesson/rule add positional path 저장 + cwd 오염 차단' : '✗ add-family positional path 실패');
|
|
6200
|
+
if (!ok) failed++;
|
|
6201
|
+
}
|
|
6202
|
+
|
|
6177
6203
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
6178
6204
|
if (failed > 0) process.exit(1);
|