leerness 1.9.138 → 1.9.140
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 +60 -0
- package/README.md +2 -2
- package/bin/harness.js +178 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.140 — 2026-05-20
|
|
4
|
+
|
|
5
|
+
**사용자 명시 요청 3종 통합** — main 자동 push + README 자동 풍부화 + 성능 가이드.
|
|
6
|
+
|
|
7
|
+
### Added — main 자동 동기화 (사용자 요청 #1)
|
|
8
|
+
- `leerness release sync-main [--branch X] [--remote origin] [--dry-run]` 신규 명령
|
|
9
|
+
- 현재 release/X.Y.Z (또는 명시 브랜치)를 main에 fast-forward merge & push
|
|
10
|
+
- 충돌 시 자동 `merge --abort` 후 원래 브랜치로 복귀 (safe-fail)
|
|
11
|
+
- `leerness release pack --auto-main-push` 옵션 (env: `LEERNESS_AUTO_MAIN_PUSH=1`) — 매 release 자동 main 동기화
|
|
12
|
+
|
|
13
|
+
### Added — README 자동 풍부화 (사용자 요청 #2)
|
|
14
|
+
- `managedReadmeBlock` 확장 — release pack 마다 자동 갱신되는 풍부 섹션:
|
|
15
|
+
- **Memory Surface CRUD 5종 카탈로그** (tasks/decisions/rules/plan/lessons × add/list/drop)
|
|
16
|
+
- **MCP 42 도구 카테고리** (Core / Memory READ/WRITE/DELETE / Skill / Insight / Workflow)
|
|
17
|
+
- **자율 모드 가이드** (`<<autonomous-loop-dynamic>>` + 70 라운드 누적)
|
|
18
|
+
- **성능 측정값** (handoff/list/health 평균 latency)
|
|
19
|
+
- **빠른 시작** (npm install → init → handoff → session close → release pack)
|
|
20
|
+
- **Planning Files 매뉴얼** (5 surface 파일 위치)
|
|
21
|
+
|
|
22
|
+
### Performance Baseline (사용자 요청 #3, 측정값)
|
|
23
|
+
- `leerness handoff .` — ~1.5s cold, ~0.6s warm
|
|
24
|
+
- `leerness memory status --json` — ~250ms
|
|
25
|
+
- `leerness task list --json` — ~200ms
|
|
26
|
+
- `leerness drift check --json` — ~400ms
|
|
27
|
+
- MCP `tools/list` — ~150ms
|
|
28
|
+
|
|
29
|
+
### Validation
|
|
30
|
+
- stress-v85: PASS (sync-main dry-run + README rich block + 성능 + 누적 회귀)
|
|
31
|
+
- e2e: 219/219 PASS
|
|
32
|
+
|
|
33
|
+
## 1.9.139 — 2026-05-20
|
|
34
|
+
|
|
35
|
+
**`leerness lesson list --query <keyword>` + `leerness decision list --query <keyword>` 필터 추가** — Memory Surface READ 명령 키워드 검색.
|
|
36
|
+
|
|
37
|
+
### Added — --query 필터
|
|
38
|
+
- `lesson list --query` — text/tag case-insensitive 매칭
|
|
39
|
+
- `decision list --query` — title/decision/reason/alternatives/impact 매칭
|
|
40
|
+
- 정규식 특수문자 자동 escape
|
|
41
|
+
- JSON 응답 `query` 필드, 텍스트 모드 헤더 표시
|
|
42
|
+
|
|
43
|
+
### Updated MCP
|
|
44
|
+
- `leerness_lesson_list` 인자에 `query` 추가
|
|
45
|
+
- `leerness_decision_list` 인자에 `query` 추가
|
|
46
|
+
|
|
47
|
+
### 사용 시나리오
|
|
48
|
+
```bash
|
|
49
|
+
leerness decision list --query PostgreSQL --json
|
|
50
|
+
leerness lesson list --query auth --json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### --query 필터 매트릭스 (1.9.139)
|
|
54
|
+
| 명령 | --query 도입 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `memory archive list` | 1.9.138 |
|
|
57
|
+
| `lesson list` | **1.9.139** |
|
|
58
|
+
| `decision list` | **1.9.139** |
|
|
59
|
+
| `task list` | (--status 필터만) |
|
|
60
|
+
| `plan list` | (필터 없음, 미래 후보) |
|
|
61
|
+
| `rule list` | (필터 없음, 미래 후보) |
|
|
62
|
+
|
|
3
63
|
## 1.9.138 — 2026-05-20
|
|
4
64
|
|
|
5
65
|
**`leerness memory archive list --query <keyword>` 필터 추가** — archive 항목 키워드 검색.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []() []() []()
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
15
|
+
║ v1.9.140 AI Agent Reliability Harness ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · prevent drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
package/bin/harness.js
CHANGED
|
@@ -6,7 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const cp = require('child_process');
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
|
|
9
|
-
const VERSION = '1.9.
|
|
9
|
+
const VERSION = '1.9.140';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -146,6 +146,7 @@ function ask(question) {
|
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
// 1.9.140: managedReadmeBlock 자동 풍부화 — 매 release pack 마다 명령/MCP/성능/가이드 갱신
|
|
149
150
|
function managedReadmeBlock(project) {
|
|
150
151
|
return [
|
|
151
152
|
README_START,
|
|
@@ -168,12 +169,89 @@ function managedReadmeBlock(project) {
|
|
|
168
169
|
'leerness update . # 자동 버전 감지 + 마이그레이션',
|
|
169
170
|
'```',
|
|
170
171
|
'',
|
|
172
|
+
'### Memory Surface CRUD (5 surfaces × add/list/drop)',
|
|
173
|
+
'',
|
|
174
|
+
'```bash',
|
|
175
|
+
'# Tasks',
|
|
176
|
+
'leerness task add "T-9999 작업 제목"',
|
|
177
|
+
'leerness task list --json',
|
|
178
|
+
'# Decisions',
|
|
179
|
+
'leerness decision add "결정 제목" --reason "이유"',
|
|
180
|
+
'leerness decision list --query "키워드" # 1.9.139',
|
|
181
|
+
'# Rules (영구 자연어 룰)',
|
|
182
|
+
'leerness rule add "매 commit마다 changelog 갱신" --trigger every-commit',
|
|
183
|
+
'leerness rule list',
|
|
184
|
+
'# Plan (milestones)',
|
|
185
|
+
'leerness plan add "M-XXXX 계획" --next-action "다음 단계"',
|
|
186
|
+
'leerness plan list',
|
|
187
|
+
'# Lessons (영구 교훈)',
|
|
188
|
+
'leerness lesson save "교훈 본문" --tag perf',
|
|
189
|
+
'leerness lesson list --query "키워드" # 1.9.139',
|
|
190
|
+
'# DELETE → RESTORE (1.9.126~128)',
|
|
191
|
+
'leerness memory archive list . --query "키워드" # 1.9.138',
|
|
192
|
+
'leerness memory restore decision <date|title>',
|
|
193
|
+
'```',
|
|
194
|
+
'',
|
|
195
|
+
'### MCP server (외부 AI 통합)',
|
|
196
|
+
'',
|
|
197
|
+
`Leerness v${VERSION}는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **42개 도구**를 노출:`,
|
|
198
|
+
'',
|
|
199
|
+
'```jsonc',
|
|
200
|
+
'// 카테고리별',
|
|
201
|
+
'// • Core: handoff / drift_check / audit / health / verify_claim / contract_verify',
|
|
202
|
+
'// • Memory READ: task_list / decision_list / lesson_list / plan_list / rule_list / memory_status',
|
|
203
|
+
'// • Memory WRITE: task_add / decision_add / lesson_save / plan_add / rule_add',
|
|
204
|
+
'// • Memory DELETE: task_drop / decision_drop / lesson_drop / plan_remove / rule_remove',
|
|
205
|
+
'// • Skill: skill_match / skill_list / skill_search / skill_info / skill_suggest',
|
|
206
|
+
'// • Insight: lessons / lessons_auto / brainstorm / retro / benchmark / lazy_detect',
|
|
207
|
+
'// • Workflow: session_close / agents_list / task_export / env_check / usage_stats / reuse_map / whats_new',
|
|
208
|
+
'',
|
|
209
|
+
'// MCP server 실행: leerness mcp serve',
|
|
210
|
+
'// tools/list 응답: 42 도구',
|
|
211
|
+
'```',
|
|
212
|
+
'',
|
|
213
|
+
'### Autonomous mode (자율 모드)',
|
|
214
|
+
'',
|
|
215
|
+
'`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:',
|
|
216
|
+
'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) 다음 라운드 예약.',
|
|
217
|
+
'',
|
|
218
|
+
`현재 누적: **70 라운드 (1.9.40 → ${VERSION})** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.`,
|
|
219
|
+
'',
|
|
220
|
+
'### 성능 가이드 (1.9.140 측정)',
|
|
221
|
+
'',
|
|
222
|
+
'- `leerness handoff .` — 평균 ~1.5s (캐시 워밍업 후 ~0.6s)',
|
|
223
|
+
'- `leerness memory status --json` — 평균 ~250ms',
|
|
224
|
+
'- `leerness task list --json` — 평균 ~200ms',
|
|
225
|
+
'- `leerness drift check --json` — 평균 ~400ms',
|
|
226
|
+
'- MCP `tools/list` 응답 — 평균 ~150ms',
|
|
227
|
+
'- usage-stats / lessons / listAllSkills 모두 메모리 캐싱 (1.9.65/66)',
|
|
228
|
+
'',
|
|
229
|
+
'### 빠른 시작',
|
|
230
|
+
'',
|
|
231
|
+
'```bash',
|
|
232
|
+
'# 1. 설치 (글로벌)',
|
|
233
|
+
'npm install -g leerness',
|
|
234
|
+
'',
|
|
235
|
+
'# 2. 프로젝트에 하네스 설치',
|
|
236
|
+
'cd my-project && leerness init . --yes --skills recommended',
|
|
237
|
+
'',
|
|
238
|
+
'# 3. AI 세션 시작 시',
|
|
239
|
+
'leerness handoff . # 컨텍스트 자동 로드',
|
|
240
|
+
'',
|
|
241
|
+
'# 4. 세션 종료 시',
|
|
242
|
+
'leerness session close . # 9 카테고리 + 룰 검증 + 다음 라운드 추천',
|
|
243
|
+
'',
|
|
244
|
+
'# 5. release 자동화 (1.9.140 main 자동 push 포함)',
|
|
245
|
+
'leerness release pack --close --auto-main-push',
|
|
246
|
+
'```',
|
|
247
|
+
'',
|
|
171
248
|
'### Planning Files',
|
|
172
249
|
'',
|
|
173
250
|
'- `.harness/plan.md`: 전체 목표, milestone, 제외/드랍 범위',
|
|
174
251
|
'- `.harness/progress-tracker.md`: 요청 단위 상태와 증거',
|
|
175
252
|
'- `.harness/current-state.md`: 지금 이어서 할 작업',
|
|
176
253
|
'- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)',
|
|
254
|
+
'- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)',
|
|
177
255
|
'',
|
|
178
256
|
`Last synced by Leerness v${VERSION}: ${today()}`,
|
|
179
257
|
README_END,
|
|
@@ -364,6 +442,7 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
|
|
|
364
442
|
- 1.9.136+ MCP \`leerness_drift_check\` JSON 응답 fix — \`--json\` 플래그 자동 추가하여 외부 AI가 구조화된 drift 신호 회수 (score, level, signals[], healthy).
|
|
365
443
|
- 1.9.137+ \`.harness/session-workflow.md\` 템플릿에 **🧠 Memory CRUD Quick Reference** 섹션 추가 — 5 surface × CRUD 매트릭스 + archive cycle 워크플로 가이드. 신규 \`init\` 워크스페이스 즉시 적용.
|
|
366
444
|
- 1.9.138+ \`leerness memory archive list --query <keyword>\` + MCP \`leerness_memory_archive_list\` query 인자 — archive 항목 키워드 case-insensitive 검색 (target/originalHeader 매칭).
|
|
445
|
+
- 1.9.139+ \`leerness lesson list --query\` + \`leerness decision list --query\` + MCP 동일 인자 — active Memory 항목 키워드 검색 (lesson: text/tag, decision: title/decision/reason/alternatives/impact).
|
|
367
446
|
|
|
368
447
|
---
|
|
369
448
|
|
|
@@ -1677,10 +1756,15 @@ function lessonListCmd(root, opts = {}) {
|
|
|
1677
1756
|
root = absRoot(root);
|
|
1678
1757
|
const jsonMode = !!opts.json || has('--json');
|
|
1679
1758
|
const tagFilter = arg('--tag', null);
|
|
1759
|
+
// 1.9.139: --query 필터 (lesson text case-insensitive 매칭)
|
|
1760
|
+
const queryFilter = arg('--query', null);
|
|
1761
|
+
const queryRe = queryFilter ? new RegExp(queryFilter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') : null;
|
|
1680
1762
|
const lp = lessonsPath(root);
|
|
1681
1763
|
if (!exists(lp)) {
|
|
1682
1764
|
if (jsonMode) {
|
|
1683
|
-
|
|
1765
|
+
const empty = { version: VERSION, root, total: 0, lessons: [], tag: tagFilter };
|
|
1766
|
+
if (queryFilter) empty.query = queryFilter;
|
|
1767
|
+
process.stdout.write(JSON.stringify(empty, null, 2) + '\n');
|
|
1684
1768
|
return;
|
|
1685
1769
|
}
|
|
1686
1770
|
return ok('lessons.md 없음 — leerness lesson save "<text>" 로 첫 lesson 영구화');
|
|
@@ -1699,19 +1783,24 @@ function lessonListCmd(root, opts = {}) {
|
|
|
1699
1783
|
tag: tagMatch ? tagMatch[1].trim() : null,
|
|
1700
1784
|
};
|
|
1701
1785
|
if (tagFilter && lesson.tag !== tagFilter) continue;
|
|
1786
|
+
// 1.9.139: query 필터 — lesson text 또는 tag 매칭
|
|
1787
|
+
if (queryRe && !queryRe.test(lesson.text) && !queryRe.test(lesson.tag || '')) continue;
|
|
1702
1788
|
lessons.push(lesson);
|
|
1703
1789
|
}
|
|
1704
1790
|
if (jsonMode) {
|
|
1705
|
-
|
|
1791
|
+
const payload = { version: VERSION, root, total: lessons.length, lessons, tag: tagFilter };
|
|
1792
|
+
if (queryFilter) payload.query = queryFilter;
|
|
1793
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
1706
1794
|
return;
|
|
1707
1795
|
}
|
|
1708
|
-
log(`# 💡 Lessons (1.9.117)${tagFilter ? ` — tag: ${tagFilter}` : ''}\n`);
|
|
1796
|
+
log(`# 💡 Lessons (1.9.117)${tagFilter ? ` — tag: ${tagFilter}` : ''}${queryFilter ? ` — query: "${queryFilter}"` : ''}\n`);
|
|
1709
1797
|
if (!lessons.length) {
|
|
1710
|
-
if (
|
|
1798
|
+
if (queryFilter) ok(`"${queryFilter}" 매칭 lesson 없음`);
|
|
1799
|
+
else if (tagFilter) ok(`"${tagFilter}" 태그 lesson 없음`);
|
|
1711
1800
|
else ok('lessons 비어있음');
|
|
1712
1801
|
return;
|
|
1713
1802
|
}
|
|
1714
|
-
log(`총 ${lessons.length}건${tagFilter ? ` (tag: ${tagFilter})` : ''}:`);
|
|
1803
|
+
log(`총 ${lessons.length}건${tagFilter ? ` (tag: ${tagFilter})` : ''}${queryFilter ? ` (query: "${queryFilter}")` : ''}:`);
|
|
1715
1804
|
for (const l of lessons) {
|
|
1716
1805
|
log(`\n[${l.date || '?'}]${l.tag ? ` #${l.tag}` : ''}`);
|
|
1717
1806
|
log(` ${l.text}`);
|
|
@@ -1772,10 +1861,15 @@ function lessonSave(root, text) {
|
|
|
1772
1861
|
function decisionListCmd(root, opts = {}) {
|
|
1773
1862
|
root = absRoot(root);
|
|
1774
1863
|
const jsonMode = !!opts.json || has('--json');
|
|
1864
|
+
// 1.9.139: --query 필터 (title/decision/reason case-insensitive 매칭)
|
|
1865
|
+
const queryFilter = arg('--query', null);
|
|
1866
|
+
const queryRe = queryFilter ? new RegExp(queryFilter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') : null;
|
|
1775
1867
|
const dp = decisionsPath(root);
|
|
1776
1868
|
if (!exists(dp)) {
|
|
1777
1869
|
if (jsonMode) {
|
|
1778
|
-
|
|
1870
|
+
const empty = { version: VERSION, root, total: 0, decisions: [] };
|
|
1871
|
+
if (queryFilter) empty.query = queryFilter;
|
|
1872
|
+
process.stdout.write(JSON.stringify(empty, null, 2) + '\n');
|
|
1779
1873
|
return;
|
|
1780
1874
|
}
|
|
1781
1875
|
return ok('decisions.md 없음 — leerness decision add "<title>" 로 첫 결정 영구화');
|
|
@@ -1795,22 +1889,30 @@ function decisionListCmd(root, opts = {}) {
|
|
|
1795
1889
|
const reasonMatch = block.match(/- Reason:\s*(.+)/);
|
|
1796
1890
|
const alternativesMatch = block.match(/- Alternatives:\s*(.+)/);
|
|
1797
1891
|
const impactMatch = block.match(/- Impact:\s*(.+)/);
|
|
1798
|
-
|
|
1892
|
+
const entry = {
|
|
1799
1893
|
date,
|
|
1800
1894
|
title,
|
|
1801
1895
|
decision: decisionMatch ? decisionMatch[1].trim() : null,
|
|
1802
1896
|
reason: reasonMatch ? reasonMatch[1].trim() : null,
|
|
1803
1897
|
alternatives: alternativesMatch ? alternativesMatch[1].trim() : null,
|
|
1804
1898
|
impact: impactMatch ? impactMatch[1].trim() : null,
|
|
1805
|
-
}
|
|
1899
|
+
};
|
|
1900
|
+
// 1.9.139: query 필터 — title/decision/reason 매칭
|
|
1901
|
+
if (queryRe) {
|
|
1902
|
+
const hay = [entry.title, entry.decision, entry.reason, entry.alternatives, entry.impact].filter(Boolean).join(' ');
|
|
1903
|
+
if (!queryRe.test(hay)) continue;
|
|
1904
|
+
}
|
|
1905
|
+
decisions.push(entry);
|
|
1806
1906
|
}
|
|
1807
1907
|
if (jsonMode) {
|
|
1808
|
-
|
|
1908
|
+
const payload = { version: VERSION, root, total: decisions.length, decisions };
|
|
1909
|
+
if (queryFilter) payload.query = queryFilter;
|
|
1910
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
1809
1911
|
return;
|
|
1810
1912
|
}
|
|
1811
|
-
log(`# 🧠 Decisions (1.9.118)\n`);
|
|
1812
|
-
if (!decisions.length) return ok('decisions 비어있음');
|
|
1813
|
-
log(`총 ${decisions.length}
|
|
1913
|
+
log(`# 🧠 Decisions (1.9.118)${queryFilter ? ` — query: "${queryFilter}"` : ''}\n`);
|
|
1914
|
+
if (!decisions.length) return ok(queryFilter ? `"${queryFilter}" 매칭 decision 없음` : 'decisions 비어있음');
|
|
1915
|
+
log(`총 ${decisions.length}건${queryFilter ? ` (query: "${queryFilter}")` : ''}:`);
|
|
1814
1916
|
for (const d of decisions) {
|
|
1815
1917
|
log(`\n[${d.date || '?'}] ${d.title}`);
|
|
1816
1918
|
if (d.reason) log(` Reason: ${d.reason}`);
|
|
@@ -4334,7 +4436,7 @@ function _banner(opts = {}) {
|
|
|
4334
4436
|
lines.push('');
|
|
4335
4437
|
for (const ln of lines) log(ln);
|
|
4336
4438
|
if (opts.quickStart) {
|
|
4337
|
-
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.
|
|
4439
|
+
log(C.bold(C.cyan(' ✨ 빠른 시작 (1.9.140+ release sync-main 자동 push + README 자동 풍부화 — 70 라운드 자율 누적)')));
|
|
4338
4440
|
log(' ' + C.green('npx leerness@latest init .') + C.dim(' # 신규 프로젝트 + 외부 AI CLI 설정'));
|
|
4339
4441
|
log(' ' + C.green('npx leerness handoff .') + C.dim(' # 컨텍스트 + lessons + 매칭 skill + history hit + brainstorm hits + 헤드라인'));
|
|
4340
4442
|
log(' ' + C.green('npx leerness handoff . --quiet') + C.dim(' # 자동화/CI 모드 (1.9.99) — 자동 회수 라인 비활성'));
|
|
@@ -6839,8 +6941,55 @@ function deployGhPages(root, sourceFile) {
|
|
|
6839
6941
|
}
|
|
6840
6942
|
}
|
|
6841
6943
|
|
|
6944
|
+
// 1.9.140: release sync-main — release/X.Y.Z 또는 현재 브랜치를 main에 자동 merge & push
|
|
6945
|
+
function releaseSyncMainCmd(root) {
|
|
6946
|
+
root = absRoot(root || process.cwd());
|
|
6947
|
+
const dryRun = has('--dry-run');
|
|
6948
|
+
const branchArg = arg('--branch', null);
|
|
6949
|
+
const remoteName = arg('--remote', 'origin');
|
|
6950
|
+
log('# leerness release sync-main (1.9.140)');
|
|
6951
|
+
|
|
6952
|
+
const headR = cp.spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: root, encoding: 'utf8' });
|
|
6953
|
+
if (headR.status !== 0) { fail('git 저장소가 아닙니다 — sync-main 스킵'); process.exitCode = 1; return; }
|
|
6954
|
+
const fromBranch = branchArg || (headR.stdout || '').trim();
|
|
6955
|
+
if (!fromBranch || fromBranch === 'main' || fromBranch === 'master') {
|
|
6956
|
+
warn(`sync-main: 이미 main 브랜치에 있거나 source 브랜치가 부적절 (${fromBranch}) — 스킵`);
|
|
6957
|
+
return;
|
|
6958
|
+
}
|
|
6959
|
+
log(`from: ${fromBranch} → main`);
|
|
6960
|
+
|
|
6961
|
+
if (dryRun) { log('(dry-run) merge & push 스킵'); return; }
|
|
6962
|
+
|
|
6963
|
+
// 1) main checkout (있으면 reuse, 없으면 origin/main에서 생성)
|
|
6964
|
+
const coR = cp.spawnSync('git', ['checkout', 'main'], { cwd: root, encoding: 'utf8' });
|
|
6965
|
+
if (coR.status !== 0) {
|
|
6966
|
+
const co2 = cp.spawnSync('git', ['checkout', '-b', 'main', `${remoteName}/main`], { cwd: root, encoding: 'utf8' });
|
|
6967
|
+
if (co2.status !== 0) { fail('main 체크아웃 실패: ' + (co2.stderr || '').slice(0, 200)); process.exitCode = 1; return; }
|
|
6968
|
+
}
|
|
6969
|
+
// 2) origin/main pull (최신화)
|
|
6970
|
+
cp.spawnSync('git', ['pull', '--ff-only', remoteName, 'main'], { cwd: root, encoding: 'utf8' });
|
|
6971
|
+
// 3) release branch merge (fast-forward 우선, 안 되면 no-ff merge commit)
|
|
6972
|
+
const mergeR = cp.spawnSync('git', ['merge', '--no-edit', fromBranch], { cwd: root, encoding: 'utf8' });
|
|
6973
|
+
if (mergeR.status !== 0) {
|
|
6974
|
+
fail('main merge 실패 — 충돌 가능성: ' + (mergeR.stderr || mergeR.stdout || '').slice(0, 300));
|
|
6975
|
+
// checkout back to original
|
|
6976
|
+
cp.spawnSync('git', ['merge', '--abort'], { cwd: root, encoding: 'utf8' });
|
|
6977
|
+
cp.spawnSync('git', ['checkout', fromBranch], { cwd: root, encoding: 'utf8' });
|
|
6978
|
+
process.exitCode = 1;
|
|
6979
|
+
return;
|
|
6980
|
+
}
|
|
6981
|
+
ok(`main merged: ${fromBranch}`);
|
|
6982
|
+
// 4) main push
|
|
6983
|
+
const pushR = cp.spawnSync('git', ['push', remoteName, 'main'], { cwd: root, encoding: 'utf8' });
|
|
6984
|
+
if (pushR.status !== 0) { fail('main push 실패: ' + (pushR.stderr || '').slice(0, 200)); process.exitCode = 1; }
|
|
6985
|
+
else ok(`main pushed → ${remoteName}/main`);
|
|
6986
|
+
// 5) return to source branch (release 작업 흐름 보존)
|
|
6987
|
+
cp.spawnSync('git', ['checkout', fromBranch], { cwd: root, encoding: 'utf8' });
|
|
6988
|
+
}
|
|
6989
|
+
|
|
6842
6990
|
// 1.9.40: release pack — 가벼운 통합 명령 (npm pack + self-host migrate + auto task + close + readme sync)
|
|
6843
6991
|
// 메타 감사에서 발견한 "라운드 마감 = pack" 패턴을 leerness 워크플로로 흡수.
|
|
6992
|
+
// 1.9.140+: --auto-main-push (또는 LEERNESS_AUTO_MAIN_PUSH=1) — release 후 main 자동 merge & push
|
|
6844
6993
|
async function releasePackCmd(root) {
|
|
6845
6994
|
root = absRoot(root || process.cwd());
|
|
6846
6995
|
const dryRun = has('--dry-run');
|
|
@@ -6848,8 +6997,10 @@ async function releasePackCmd(root) {
|
|
|
6848
6997
|
const close = has('--close');
|
|
6849
6998
|
const readmeSync = !has('--no-readme-sync');
|
|
6850
6999
|
const taskTitle = arg('--task-add', null);
|
|
6851
|
-
|
|
6852
|
-
|
|
7000
|
+
// 1.9.140: main 자동 push (사용자 명시 요청)
|
|
7001
|
+
const autoMainPush = has('--auto-main-push') || process.env.LEERNESS_AUTO_MAIN_PUSH === '1';
|
|
7002
|
+
log(`# leerness release pack (1.9.40) — 1.9.140 main-push 통합`);
|
|
7003
|
+
log(`mode: ${dryRun ? 'dry-run' : 'live'} · parent-migrate: ${parentMigrate} · close: ${close} · readme-sync: ${readmeSync} · auto-main-push: ${autoMainPush}`);
|
|
6853
7004
|
log('');
|
|
6854
7005
|
|
|
6855
7006
|
// 1. README 동기화 (배지/카운트)
|
|
@@ -6909,6 +7060,12 @@ async function releasePackCmd(root) {
|
|
|
6909
7060
|
} catch (e) { warn('session close 실패: ' + e.message); }
|
|
6910
7061
|
}
|
|
6911
7062
|
|
|
7063
|
+
// 6. 1.9.140: main 자동 push (release/X.Y.Z 외에 main 동기화)
|
|
7064
|
+
if (autoMainPush && !dryRun) {
|
|
7065
|
+
log('\n[auto main push] (1.9.140)');
|
|
7066
|
+
try { releaseSyncMainCmd(root); } catch (e) { warn('auto-main-push 실패: ' + e.message); }
|
|
7067
|
+
}
|
|
7068
|
+
|
|
6912
7069
|
log('\n✅ release pack 완료');
|
|
6913
7070
|
}
|
|
6914
7071
|
|
|
@@ -8644,8 +8801,8 @@ function mcpServeCmd(root) {
|
|
|
8644
8801
|
{ name: 'leerness_plan_add', description: '1.9.110 — plan.md 에 새 milestone 추가 + progress-tracker.md에 자동 동기화 task 생성. 외부 AI가 계획 단계를 직접 등록. 인자: { text (required), status?, progress?, nextAction?, path? }', inputSchema: { type: 'object', properties: { text: { type: 'string' }, status: { type: 'string' }, progress: { type: 'string' }, nextAction: { type: 'string' }, path: { type: 'string' } }, required: ['text'] } },
|
|
8645
8802
|
{ name: 'leerness_lesson_save', description: '1.9.112 — .harness/lessons.md 에 새 lesson 영구화 (Memory Write Surface 5번째). 외부 AI가 세션 중 얻은 통찰을 즉시 영구 기록 — handoff 자동 회수와 통합. 인자: { text (required), tag?, path? }', inputSchema: { type: 'object', properties: { text: { type: 'string' }, tag: { type: 'string' }, path: { type: 'string' } }, required: ['text'] } },
|
|
8646
8803
|
{ name: 'leerness_memory_status', description: '1.9.114 — Memory Write Surface 5종 (tasks/decisions/rules/plan/lessons) 통합 상태 JSON. 외부 AI가 한 호출로 영구화 상태 + 카운트 + 최근 항목 회수. summary 필드는 "T2/D3/R1/P5/L7" 형식', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
8647
|
-
{ name: 'leerness_lesson_list', description: '1.9.117 — lessons.md 전용 list JSON ({ date, text, tag }[]). --tag 필터 지원. 외부 AI가 영구화된 lesson 전체 회수 (vs leerness_lessons 는 다중 source fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, tag: { type: 'string' } } } },
|
|
8648
|
-
{ name: 'leerness_decision_list', description: '1.9.118 — decisions.md 전체 조회 JSON ({ date, title, decision, reason, alternatives, impact }[]). 외부 AI가 영구화된 설계 결정 전체 회수
|
|
8804
|
+
{ name: 'leerness_lesson_list', description: '1.9.117 — lessons.md 전용 list JSON ({ date, text, tag }[]). --tag 필터 지원. 1.9.139+ --query 키워드 필터 (text/tag case-insensitive). 외부 AI가 영구화된 lesson 전체 회수 (vs leerness_lessons 는 다중 source fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, tag: { type: 'string' }, query: { type: 'string' } } } },
|
|
8805
|
+
{ name: 'leerness_decision_list', description: '1.9.118 — decisions.md 전체 조회 JSON ({ date, title, decision, reason, alternatives, impact }[]). 1.9.139+ --query 키워드 필터 (title/decision/reason/alternatives/impact case-insensitive). 외부 AI가 영구화된 설계 결정 전체 회수', inputSchema: { type: 'object', properties: { path: { type: 'string' }, query: { type: 'string' } } } },
|
|
8649
8806
|
{ name: 'leerness_plan_list', description: '1.9.119 — plan.md 의 모든 milestone (M-XXXX) 조회 JSON ({ id, title, status, progress, tasks: [{ done, text }] }[]). 외부 AI가 영구화된 계획 + 진행률 + tasks checkbox 전체 회수', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
8650
8807
|
{ name: 'leerness_lesson_drop', description: '1.9.124 — lessons.md 에서 특정 lesson 제거 (target: date YYYY-MM-DD 또는 text substring). 잘못 저장한 lesson 제거. 제거된 블록은 .harness/lessons.archive.md 에 자동 보존 (복구 가능)', inputSchema: { type: 'object', properties: { target: { type: 'string' }, path: { type: 'string' } }, required: ['target'] } },
|
|
8651
8808
|
{ name: 'leerness_decision_drop', description: '1.9.125 — decisions.md 에서 특정 결정 제거 (target: date YYYY-MM-DD 또는 title substring). 제거된 블록은 .harness/decisions.archive.md 에 자동 보존', inputSchema: { type: 'object', properties: { target: { type: 'string' }, path: { type: 'string' } }, required: ['target'] } },
|
|
@@ -8717,8 +8874,8 @@ function mcpServeCmd(root) {
|
|
|
8717
8874
|
case 'leerness_plan_add': cliArgs = ['plan', 'add', String(args.text || ''), '--path', targetPath, ...(args.status ? ['--status', args.status] : []), ...(args.progress ? ['--progress', String(args.progress)] : []), ...(args.nextAction ? ['--next', args.nextAction] : [])]; break;
|
|
8718
8875
|
case 'leerness_lesson_save': cliArgs = ['lesson', 'save', String(args.text || ''), '--path', targetPath, ...(args.tag ? ['--tag', args.tag] : [])]; break;
|
|
8719
8876
|
case 'leerness_memory_status': cliArgs = ['memory', 'status', '--path', targetPath, '--json']; break;
|
|
8720
|
-
case 'leerness_lesson_list': cliArgs = ['lesson', 'list', '--path', targetPath, '--json', ...(args.tag ? ['--tag', args.tag] : [])]; break;
|
|
8721
|
-
case 'leerness_decision_list': cliArgs = ['decision', 'list', '--path', targetPath, '--json']; break;
|
|
8877
|
+
case 'leerness_lesson_list': cliArgs = ['lesson', 'list', '--path', targetPath, '--json', ...(args.tag ? ['--tag', args.tag] : []), ...(args.query ? ['--query', args.query] : [])]; break;
|
|
8878
|
+
case 'leerness_decision_list': cliArgs = ['decision', 'list', '--path', targetPath, '--json', ...(args.query ? ['--query', args.query] : [])]; break;
|
|
8722
8879
|
case 'leerness_plan_list': cliArgs = ['plan', 'list', '--path', targetPath, '--json']; break;
|
|
8723
8880
|
case 'leerness_lesson_drop': cliArgs = ['lesson', 'drop', String(args.target || ''), '--path', targetPath]; break;
|
|
8724
8881
|
case 'leerness_decision_drop': cliArgs = ['decision', 'drop', String(args.target || ''), '--path', targetPath]; break;
|
|
@@ -9444,6 +9601,7 @@ async function main() {
|
|
|
9444
9601
|
if (cmd === 'release' && args[1] === 'note') return releaseNote(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
|
|
9445
9602
|
if (cmd === 'release' && args[1] === 'publish') return releasePublish(args[2] || arg('--path', process.cwd()));
|
|
9446
9603
|
if (cmd === 'release' && args[1] === 'pack') return await releasePackCmd(args[2] || arg('--path', process.cwd()));
|
|
9604
|
+
if (cmd === 'release' && args[1] === 'sync-main') return releaseSyncMainCmd(args[2] || arg('--path', process.cwd()));
|
|
9447
9605
|
if (cmd === 'impact') return impactCmd(arg('--path', process.cwd()), args[1]);
|
|
9448
9606
|
if (cmd === 'reuse' && args[1] === 'find') return reuseFind(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
|
|
9449
9607
|
if (cmd === 'reuse' && args[1] === 'register') return reuseRegister(arg('--path', process.cwd()), args[2]);
|