leerness 1.9.202 → 1.9.204
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 +109 -0
- package/README.md +3 -3
- package/bin/harness.js +209 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,114 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.204 — 2026-05-22
|
|
4
|
+
|
|
5
|
+
**⏰ timezone 보강 + 🔄 auto-loop 활성 라벨 (사용자 명시 2종).**
|
|
6
|
+
|
|
7
|
+
### 사용자 명시
|
|
8
|
+
> 1. *"시간관련 기능 등은 해당 국가의 시간대를 고려해서 작업이 될 수 있는지"*
|
|
9
|
+
> 2. *"자동으로 깨어나서 작업하는 모드도 활성룰에 표시되어야 할 것 같은데, 활성 룰 에 표시되는 기능 점검"*
|
|
10
|
+
|
|
11
|
+
### 1. timezone 시스템 (Intl.DateTimeFormat 기반, 의존성 0)
|
|
12
|
+
- `_getLocalTz()` — `process.env.LEERNESS_TZ` → 시스템 timezone → `Asia/Seoul` fallback
|
|
13
|
+
- `_formatLocal(iso, opts)` — ISO UTC → 사용자 local time (예: `2026-05-22 10:13 KST`)
|
|
14
|
+
- 단축 라벨: KST / JST / UTC / 자동 추출
|
|
15
|
+
- **저장은 UTC ISO 유지** (이식성/일관성), **display 만 local time 변환**
|
|
16
|
+
|
|
17
|
+
### 2. resume CLI local time 표시
|
|
18
|
+
```
|
|
19
|
+
$ leerness resume
|
|
20
|
+
📅 plan 저장: 2026-05-22 09:48 KST (29분 전)
|
|
21
|
+
⏰ 예상 fire: 2026-05-22 10:13 KST (정시)
|
|
22
|
+
```
|
|
23
|
+
이전: `2026-05-22T00:48:23.298Z` (한국 사용자 +9 머릿속 변환 필요)
|
|
24
|
+
이후: `2026-05-22 09:48 KST` (즉시 인지 가능)
|
|
25
|
+
|
|
26
|
+
### 3. 자동 모드 활성 라벨 (헤드라인 12번째)
|
|
27
|
+
```
|
|
28
|
+
📊 헤드라인 (1.9.81/93/113/152/162/192/197/204): ... · 🔄 auto-loop 25min · ...
|
|
29
|
+
```
|
|
30
|
+
- `_getAutoLoopRule(root)` — `R-XXXX [every-round]` 활성 룰 자동 감지
|
|
31
|
+
- 룰 텍스트에서 `25분` 또는 `1500초` 패턴 추출 → 분 단위 표시
|
|
32
|
+
|
|
33
|
+
### 4. 누적 회귀 (1.9.198~203) — 모두 유지
|
|
34
|
+
|
|
35
|
+
### 5. stress-v149 — 16/16 PASS
|
|
36
|
+
- timezone (3) + resume display (1) + headline label (2) + 실 검증 (1) + 성능 (2) + 누적 (7)
|
|
37
|
+
- 성능: --version cold start avg **471 ms** · MCP 54 도구 **438 ms**
|
|
38
|
+
|
|
39
|
+
### 6. 자동 release 흐름
|
|
40
|
+
- main 자동 push **66 라운드 연속** (1.9.140~204)
|
|
41
|
+
- npm publish 자동 (1.9.178~)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 1.9.203 — 2026-05-22
|
|
46
|
+
|
|
47
|
+
**📋 자동 라운드 plan 자동 정리 + `leerness resume` 신규 CLI (사용자 명시).**
|
|
48
|
+
|
|
49
|
+
### 사용자 명시
|
|
50
|
+
> *"자동모드는 백그라운드에 다음 작업이 시작가능한 예상 시간을 설정해서 백그라운드에서 알람 트리거 같은걸 구현하고 일어났을때 해야하는 일을 정리해서 또 진행하고 반복으로 하는건 어떨까"*
|
|
51
|
+
|
|
52
|
+
### 1. `.harness/auto-resume-plan.json` 자동 저장
|
|
53
|
+
- 라운드 마무리 (release sync-main 시 npm publish 완료 후) **자동 저장**
|
|
54
|
+
- plan 구조:
|
|
55
|
+
- `savedAt`, `nextRoundVersion`, `expectedFireAt`, `intervalMin`
|
|
56
|
+
- `focus` (자유 텍스트)
|
|
57
|
+
- `contextSnapshot` (currentVersion, activeTaskId, activeTaskRequest, memorySurface, r0001Rule)
|
|
58
|
+
- `nextActions` (1.9.201 next-action-queue snapshot)
|
|
59
|
+
- 함수: `_loadAutoResumePlan` / `_writeAutoResumePlan` / `_buildAutoResumePlan` / `_autoResumePlanPath`
|
|
60
|
+
|
|
61
|
+
### 2. `leerness resume` 신규 CLI
|
|
62
|
+
일어났을 때 즉시 무엇부터 할지 한눈에:
|
|
63
|
+
```
|
|
64
|
+
$ leerness resume
|
|
65
|
+
# 🔄 leerness resume (1.9.203 자동 라운드 plan 적용)
|
|
66
|
+
📅 plan 저장: 2026-05-22T00:10:10.797Z (15분 전)
|
|
67
|
+
⏰ 예상 fire: 2026-05-22T00:35:10.797Z (정시)
|
|
68
|
+
🎯 focus: 다음 라운드: handoff → next-action take → 사용자 명시 또는 5축 매트릭스 보강
|
|
69
|
+
|
|
70
|
+
## 다음 라운드: next after 1.9.203
|
|
71
|
+
현재 버전: 1.9.203
|
|
72
|
+
활성 task: T-9999 — ...
|
|
73
|
+
memory: T2/D15/R1/L3
|
|
74
|
+
룰: 25분 간격 (사용자 명시, R-0001)
|
|
75
|
+
|
|
76
|
+
## 사전 정리된 next-actions (3건)
|
|
77
|
+
🛡 lessons.md "X" 관련 ...
|
|
78
|
+
`leerness lessons --auto --path .`
|
|
79
|
+
...
|
|
80
|
+
→ 즉시 task 추가: leerness next-action take
|
|
81
|
+
|
|
82
|
+
✓ resume 준비 완료 — 권장: leerness handoff . 또는 leerness next-action take
|
|
83
|
+
```
|
|
84
|
+
- `--json` 옵션 (자동화/MCP)
|
|
85
|
+
|
|
86
|
+
### 3. handoff 자동 plan 알림
|
|
87
|
+
handoff 진입 시 plan 존재 시 자동 노출:
|
|
88
|
+
```
|
|
89
|
+
## 📋 auto-resume-plan 로드 (1.9.203) — 15분 전 저장 (정시)
|
|
90
|
+
🎯 focus: 다음 라운드: ...
|
|
91
|
+
📦 다음 버전: next after 1.9.203
|
|
92
|
+
📥 사전 정리된 actions: 3건 → leerness next-action take
|
|
93
|
+
→ 상세: leerness resume
|
|
94
|
+
```
|
|
95
|
+
끄기: `LEERNESS_NO_RESUME_PLAN=1`
|
|
96
|
+
|
|
97
|
+
### 4. 자율 모드 cycle 완성
|
|
98
|
+
- 라운드 마무리 → plan 자동 저장 → ScheduleWakeup 25분 → wakeup → handoff plan 자동 로드 → resume / next-action take → 즉시 진입
|
|
99
|
+
|
|
100
|
+
### 5. 누적 회귀 (1.9.197~202) — 모두 유지
|
|
101
|
+
|
|
102
|
+
### 6. stress-v148 — 17/17 PASS
|
|
103
|
+
- auto-resume-plan 5 + resume CLI 3 + handoff 통합 + 성능 2 + 누적 회귀 7
|
|
104
|
+
- 성능: --version cold start avg **464 ms** · MCP 54 도구 **449 ms**
|
|
105
|
+
|
|
106
|
+
### 7. 자동 release 흐름
|
|
107
|
+
- main 자동 push **65 라운드 연속** (1.9.140~203)
|
|
108
|
+
- npm publish 자동 (1.9.178~)
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
3
112
|
## 1.9.202 — 2026-05-22
|
|
4
113
|
|
|
5
114
|
**🌐 C축 (공식 표준 스킬 자동 활용) 9.0/10 → 9.5/10 보강 — matched skill 설치 상태 표시 + leerness skill install-top.**
|
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) []() []() []() []() []() []() [-brightgreen)]() []() []() []() []() []()
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
15
|
+
║ v1.9.204 AI Agent Reliability Harness + Sandbox ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · sandbox · drift ║
|
|
17
|
-
║
|
|
17
|
+
║ ⏰ timezone 보강 (KST) · 🔄 auto-loop 25min 헤드라인 라벨 ║
|
|
18
18
|
╚══════════════════════════════════════════════════════════════╝
|
|
19
19
|
```
|
|
20
20
|
|
package/bin/harness.js
CHANGED
|
@@ -7,7 +7,7 @@ const cp = require('child_process');
|
|
|
7
7
|
const os = require('os'); // 1.9.178: _publishToNpm 에서 os.tmpdir() 사용 (전역 import)
|
|
8
8
|
const readline = require('readline');
|
|
9
9
|
|
|
10
|
-
const VERSION = '1.9.
|
|
10
|
+
const VERSION = '1.9.204';
|
|
11
11
|
|
|
12
12
|
// 1.9.184: DEP0190 (child_process shell: true) deprecation warning 억제 (사용자 명시).
|
|
13
13
|
// leerness 는 cross-platform PATH resolution 을 위해 shell: true 를 의도적으로 사용 (claude.cmd / ollama.cmd 등 Windows .cmd 처리).
|
|
@@ -130,6 +130,52 @@ function append(p, s) { mkdirp(path.dirname(p)); fs.appendFileSync(p, s, 'utf8')
|
|
|
130
130
|
function rel(root, p) { return path.relative(root, p).replace(/\\/g, '/') || '.'; }
|
|
131
131
|
function today() { return new Date().toISOString().slice(0, 10); }
|
|
132
132
|
function now() { return new Date().toISOString(); }
|
|
133
|
+
// 1.9.204: timezone 보강 (사용자 명시)
|
|
134
|
+
// ISO UTC timestamp 저장은 유지 (이식성/일관성) → display 시 사용자 local time 변환
|
|
135
|
+
// 환경변수: LEERNESS_TZ (default = 시스템 timezone, fallback: 'Asia/Seoul')
|
|
136
|
+
function _getLocalTz() {
|
|
137
|
+
if (process.env.LEERNESS_TZ) return process.env.LEERNESS_TZ;
|
|
138
|
+
try {
|
|
139
|
+
const sys = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
140
|
+
if (sys && sys !== 'UTC') return sys;
|
|
141
|
+
} catch {}
|
|
142
|
+
return 'Asia/Seoul';
|
|
143
|
+
}
|
|
144
|
+
// ISO timestamp → local time 표시 (예: "2026-05-22 10:13 KST")
|
|
145
|
+
function _formatLocal(iso, opts) {
|
|
146
|
+
if (!iso) return '?';
|
|
147
|
+
opts = opts || {};
|
|
148
|
+
const tz = opts.tz || _getLocalTz();
|
|
149
|
+
try {
|
|
150
|
+
const d = typeof iso === 'string' ? new Date(iso) : iso;
|
|
151
|
+
if (isNaN(d.getTime())) return String(iso);
|
|
152
|
+
const fmt = new Intl.DateTimeFormat('en-CA', {
|
|
153
|
+
timeZone: tz,
|
|
154
|
+
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
155
|
+
hour: '2-digit', minute: '2-digit',
|
|
156
|
+
hour12: false
|
|
157
|
+
});
|
|
158
|
+
const parts = fmt.formatToParts(d);
|
|
159
|
+
const get = (t) => (parts.find(p => p.type === t) || {}).value || '';
|
|
160
|
+
const date = `${get('year')}-${get('month')}-${get('day')}`;
|
|
161
|
+
const time = `${get('hour')}:${get('minute')}`;
|
|
162
|
+
// tz 약어 (KST/JST/UTC 등) — 간단 추출
|
|
163
|
+
const tzShort = tz === 'Asia/Seoul' ? 'KST' : tz === 'Asia/Tokyo' ? 'JST' : tz === 'UTC' ? 'UTC' : tz.split('/').pop().slice(0, 3);
|
|
164
|
+
return opts.dateOnly ? date : `${date} ${time} ${tzShort}`;
|
|
165
|
+
} catch { return String(iso); }
|
|
166
|
+
}
|
|
167
|
+
// 자동 모드 활성 여부 (R-XXXX every-round 룰 존재 시 true)
|
|
168
|
+
function _isAutoLoopActive(root) {
|
|
169
|
+
try {
|
|
170
|
+
const rules = readRules(root);
|
|
171
|
+
return rules.some(r => r.status === 'active' && /every-round|every-session/i.test(r.trigger || ''));
|
|
172
|
+
} catch { return false; }
|
|
173
|
+
}
|
|
174
|
+
function _getAutoLoopRule(root) {
|
|
175
|
+
try {
|
|
176
|
+
return readRules(root).find(r => r.status === 'active' && /every-round/i.test(r.trigger || '')) || null;
|
|
177
|
+
} catch { return null; }
|
|
178
|
+
}
|
|
133
179
|
function arg(name, def = null) { const i = process.argv.indexOf(name); return i >= 0 ? (process.argv[i + 1] || true) : def; }
|
|
134
180
|
function has(name) { return process.argv.includes(name); }
|
|
135
181
|
function nonFlagArgs() {
|
|
@@ -1733,6 +1779,115 @@ async function skillAutoCacheCmd(root, sub) {
|
|
|
1733
1779
|
if (c.expired) log(` → 갱신: leerness skill auto-cache refresh`);
|
|
1734
1780
|
}
|
|
1735
1781
|
|
|
1782
|
+
// 1.9.203: 자동 라운드 plan 정리 — 사용자 명시
|
|
1783
|
+
// "백그라운드에 다음 작업이 시작가능한 예상 시간 + 일어났을때 해야하는 일을 정리"
|
|
1784
|
+
// 라운드 마무리 시 .harness/auto-resume-plan.json 자동 저장 → 다음 wakeup 시 즉시 실행 가능
|
|
1785
|
+
// plan 구조: { savedAt, nextRoundVersion, expectedFireAt, focus, contextSnapshot, nextActions }
|
|
1786
|
+
function _autoResumePlanPath(root) { return path.join(root, '.harness', 'auto-resume-plan.json'); }
|
|
1787
|
+
function _loadAutoResumePlan(root) {
|
|
1788
|
+
try {
|
|
1789
|
+
const fp = _autoResumePlanPath(root);
|
|
1790
|
+
if (!exists(fp)) return null;
|
|
1791
|
+
const j = JSON.parse(read(fp));
|
|
1792
|
+
if (!j.savedAt) return null;
|
|
1793
|
+
const ageMs = Date.now() - new Date(j.savedAt).getTime();
|
|
1794
|
+
const expiredMs = j.expectedFireAt ? Date.now() - new Date(j.expectedFireAt).getTime() : 0;
|
|
1795
|
+
return { ...j, ageMs, ageMin: Math.floor(ageMs / 60000), elapsedFromExpected: Math.floor(expiredMs / 60000) };
|
|
1796
|
+
} catch { return null; }
|
|
1797
|
+
}
|
|
1798
|
+
function _writeAutoResumePlan(root, plan) {
|
|
1799
|
+
try {
|
|
1800
|
+
mkdirp(path.join(root, '.harness'));
|
|
1801
|
+
const payload = { savedAt: new Date().toISOString(), ...plan };
|
|
1802
|
+
writeUtf8(_autoResumePlanPath(root), JSON.stringify(payload, null, 2));
|
|
1803
|
+
return true;
|
|
1804
|
+
} catch { return false; }
|
|
1805
|
+
}
|
|
1806
|
+
// 현재 워크스페이스 상태에서 다음 라운드 plan 자동 생성 (현재 task / matrix / handoff gap / next-action queue 결합)
|
|
1807
|
+
function _buildAutoResumePlan(root, opts) {
|
|
1808
|
+
opts = opts || {};
|
|
1809
|
+
const intervalMin = opts.intervalMin || 25; // R-0001 영구 룰
|
|
1810
|
+
const now = Date.now();
|
|
1811
|
+
const expectedFireAt = new Date(now + intervalMin * 60 * 1000).toISOString();
|
|
1812
|
+
// 현재 in-progress task
|
|
1813
|
+
const rows = readProgressRows(root);
|
|
1814
|
+
const active = rows.find(r => r.status === 'in-progress' || r.status === '[진행]');
|
|
1815
|
+
// 현재 VERSION
|
|
1816
|
+
const currentVersion = VERSION;
|
|
1817
|
+
// next-action queue snapshot
|
|
1818
|
+
const queueState = _loadNextActionQueue(root);
|
|
1819
|
+
const queueTop = queueState.queue.length > 0 ? queueState.queue[queueState.queue.length - 1] : null;
|
|
1820
|
+
// memory surface counts
|
|
1821
|
+
const memorySurface = {
|
|
1822
|
+
tasksInProgress: rows.filter(r => r.status === 'in-progress').length,
|
|
1823
|
+
decisions: exists(decisionsPath(root)) ? (read(decisionsPath(root)).match(/^### \d{4}-\d{2}-\d{2}/gm) || []).length : 0,
|
|
1824
|
+
rulesActive: readRules(root).filter(r => r.status === 'active').length,
|
|
1825
|
+
lessons: exists(lessonsPath(root)) ? (read(lessonsPath(root)).match(/^### \d{4}-\d{2}-\d{2}/gm) || []).length : 0
|
|
1826
|
+
};
|
|
1827
|
+
return {
|
|
1828
|
+
nextRoundVersion: opts.nextRoundVersion || `next after ${currentVersion}`,
|
|
1829
|
+
expectedFireAt,
|
|
1830
|
+
intervalMin,
|
|
1831
|
+
focus: opts.focus || (active ? `${active.id} — ${(active.request || '').slice(0, 80)}` : '사용자 명시 대기 또는 5축 매트릭스 보강'),
|
|
1832
|
+
contextSnapshot: {
|
|
1833
|
+
currentVersion,
|
|
1834
|
+
activeTaskId: active ? active.id : null,
|
|
1835
|
+
activeTaskRequest: active ? (active.request || '').slice(0, 200) : null,
|
|
1836
|
+
memorySurface,
|
|
1837
|
+
r0001Rule: '25분 간격 (사용자 명시, R-0001)'
|
|
1838
|
+
},
|
|
1839
|
+
nextActions: (queueState.queue || []).slice(-3).map(a => ({ icon: a.icon, title: a.title, command: a.command || null })),
|
|
1840
|
+
note: opts.note || '다음 wakeup 시 leerness resume 또는 leerness handoff 실행 → plan 자동 적용'
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// 1.9.203: leerness resume 신규 CLI — auto-resume-plan 읽고 즉시 진행할 일 안내
|
|
1845
|
+
function resumeCmd(root) {
|
|
1846
|
+
root = absRoot(root);
|
|
1847
|
+
const plan = _loadAutoResumePlan(root);
|
|
1848
|
+
if (!plan) {
|
|
1849
|
+
log(`# leerness resume (1.9.203)`);
|
|
1850
|
+
log(` (auto-resume-plan 없음 — 첫 라운드)`);
|
|
1851
|
+
log(` → leerness handoff . 로 시작`);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (has('--json')) { log(JSON.stringify(plan, null, 2)); return; }
|
|
1855
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
1856
|
+
const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
|
|
1857
|
+
const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
|
|
1858
|
+
const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
|
|
1859
|
+
const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
1860
|
+
log(cyan(`# 🔄 leerness resume (1.9.203 자동 라운드 plan 적용)`));
|
|
1861
|
+
log('');
|
|
1862
|
+
log(` 📅 plan 저장: ${_formatLocal(plan.savedAt)} (${plan.ageMin}분 전)`);
|
|
1863
|
+
log(` ⏰ 예상 fire: ${_formatLocal(plan.expectedFireAt)}` + (plan.elapsedFromExpected > 0 ? yel(` (${plan.elapsedFromExpected}분 지연)`) : ' (정시)'));
|
|
1864
|
+
log(` 🎯 focus: ${plan.focus}`);
|
|
1865
|
+
log('');
|
|
1866
|
+
log(grn(`## 다음 라운드: ${plan.nextRoundVersion}`));
|
|
1867
|
+
if (plan.contextSnapshot) {
|
|
1868
|
+
const c = plan.contextSnapshot;
|
|
1869
|
+
log(` 현재 버전: ${c.currentVersion}`);
|
|
1870
|
+
if (c.activeTaskId) log(` 활성 task: ${c.activeTaskId} — ${(c.activeTaskRequest || '').slice(0, 70)}`);
|
|
1871
|
+
const m = c.memorySurface || {};
|
|
1872
|
+
log(` memory: T${m.tasksInProgress || 0}/D${m.decisions || 0}/R${m.rulesActive || 0}/L${m.lessons || 0}`);
|
|
1873
|
+
log(` 룰: ${c.r0001Rule || '-'}`);
|
|
1874
|
+
}
|
|
1875
|
+
if (plan.nextActions && plan.nextActions.length > 0) {
|
|
1876
|
+
log('');
|
|
1877
|
+
log(grn(`## 사전 정리된 next-actions (${plan.nextActions.length}건)`));
|
|
1878
|
+
for (const a of plan.nextActions) {
|
|
1879
|
+
log(` ${a.icon || '•'} ${a.title}`);
|
|
1880
|
+
if (a.command) log(dim(` \`${a.command}\``));
|
|
1881
|
+
}
|
|
1882
|
+
log('');
|
|
1883
|
+
log(dim(` → 즉시 task 추가: leerness next-action take`));
|
|
1884
|
+
}
|
|
1885
|
+
log('');
|
|
1886
|
+
log(dim(plan.note || ''));
|
|
1887
|
+
log('');
|
|
1888
|
+
log(grn(`✓ resume 준비 완료 — 권장: leerness handoff . 또는 leerness next-action take`));
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1736
1891
|
// 1.9.199: wakeup miss 자동 회복 강화 — last-handoff timestamp 기록 + 25min 기대 interval 정밀 측정
|
|
1737
1892
|
// 사용자 명시 (1.9.198 후속): "22:01에 라운드에 자동진입되지않았어"
|
|
1738
1893
|
// 1.9.196 task-log mtime 기반 detector 는 task-log 가 자주 안 업데이트되면 false positive.
|
|
@@ -3535,6 +3690,9 @@ function handoff(root) {
|
|
|
3535
3690
|
let _priorHandoffGap = { hasLast: false };
|
|
3536
3691
|
try { _priorHandoffGap = _getLastHandoffGap(root); } catch {}
|
|
3537
3692
|
try { _recordLastHandoff(root); } catch {}
|
|
3693
|
+
// 1.9.203: auto-resume-plan 자동 로드 (사용자 명시 — 자동 모드 알람 트리거)
|
|
3694
|
+
let _autoResumePlan = null;
|
|
3695
|
+
try { _autoResumePlan = _loadAutoResumePlan(root); } catch {}
|
|
3538
3696
|
// 1.9.96: --json 옵션 (구조화 출력, MCP 통합 / 외부 AI 친화)
|
|
3539
3697
|
if (has('--json')) {
|
|
3540
3698
|
const result = {
|
|
@@ -3738,6 +3896,23 @@ function handoff(root) {
|
|
|
3738
3896
|
parts.push(`🌐 official ${m.total}/${m.cacheTotal} (${ageStr}${status})`);
|
|
3739
3897
|
}
|
|
3740
3898
|
} catch {}
|
|
3899
|
+
// 12) 1.9.204: 자동 모드 활성 표시 (사용자 명시) — R-0001 every-round 룰 활성 시 헤드라인 노출
|
|
3900
|
+
try {
|
|
3901
|
+
const autoRule = _getAutoLoopRule(root);
|
|
3902
|
+
if (autoRule) {
|
|
3903
|
+
// 룰 텍스트에서 "25분" 우선, 없으면 "1500초" 패턴 추출 (분 우선순위)
|
|
3904
|
+
const txt = autoRule.rule || '';
|
|
3905
|
+
const minMatch = txt.match(/(\d+)\s*분/);
|
|
3906
|
+
const secMatch = txt.match(/(\d+)\s*초/);
|
|
3907
|
+
let label = '🔄 auto-loop';
|
|
3908
|
+
if (minMatch) {
|
|
3909
|
+
label = `🔄 auto-loop ${minMatch[1]}min`;
|
|
3910
|
+
} else if (secMatch) {
|
|
3911
|
+
label = `🔄 auto-loop ${Math.round(Number(secMatch[1]) / 60)}min`;
|
|
3912
|
+
}
|
|
3913
|
+
parts.push(label);
|
|
3914
|
+
}
|
|
3915
|
+
} catch {}
|
|
3741
3916
|
// 11) 1.9.197: A축 (범용 AI 하네스) 9.5→10 보강 — provider probe 60분 캐시 자동 노출
|
|
3742
3917
|
// 사용자 명시: "범용 AI 하네스 ... 최고의 도구" — 매 handoff 시 11 backend ready 상태 즉시 노출
|
|
3743
3918
|
try {
|
|
@@ -3750,7 +3925,7 @@ function handoff(root) {
|
|
|
3750
3925
|
if (parts.length) {
|
|
3751
3926
|
const isTty = process.stdout && process.stdout.isTTY;
|
|
3752
3927
|
const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
|
|
3753
|
-
log(cy(`📊 헤드라인 (1.9.81/93/113/152/162/192/197): ${parts.join(' · ')}`));
|
|
3928
|
+
log(cy(`📊 헤드라인 (1.9.81/93/113/152/162/192/197/204): ${parts.join(' · ')}`));
|
|
3754
3929
|
}
|
|
3755
3930
|
} catch {}
|
|
3756
3931
|
}
|
|
@@ -4049,6 +4224,26 @@ function handoff(root) {
|
|
|
4049
4224
|
}
|
|
4050
4225
|
} catch {}
|
|
4051
4226
|
}
|
|
4227
|
+
// 1.9.203: auto-resume-plan 알림 — 라운드 마무리 시 저장된 plan 자동 노출
|
|
4228
|
+
// 사용자 명시: "백그라운드에 다음 작업이 시작가능한 예상 시간 + 일어났을때 해야하는 일을 정리"
|
|
4229
|
+
if (_autoResumePlan && !has('--quiet') && process.env.LEERNESS_NO_RESUME_PLAN !== '1') {
|
|
4230
|
+
try {
|
|
4231
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
4232
|
+
const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
|
|
4233
|
+
const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
4234
|
+
const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
|
|
4235
|
+
const elapsed = _autoResumePlan.elapsedFromExpected || 0;
|
|
4236
|
+
const status = elapsed > 5 ? yel(`(${elapsed}분 지연)`) : (elapsed > -5 ? '(정시)' : dim(`(${Math.abs(elapsed)}분 일찍)`));
|
|
4237
|
+
log(cy(`## 📋 auto-resume-plan 로드 (1.9.203) — ${_autoResumePlan.ageMin}분 전 저장 ${status}`));
|
|
4238
|
+
log(dim(` 🎯 focus: ${_autoResumePlan.focus || '(none)'}`));
|
|
4239
|
+
log(dim(` 📦 다음 버전: ${_autoResumePlan.nextRoundVersion || '(none)'}`));
|
|
4240
|
+
if (_autoResumePlan.nextActions && _autoResumePlan.nextActions.length > 0) {
|
|
4241
|
+
log(dim(` 📥 사전 정리된 actions: ${_autoResumePlan.nextActions.length}건 → leerness next-action take`));
|
|
4242
|
+
}
|
|
4243
|
+
log(dim(` → 상세: leerness resume`));
|
|
4244
|
+
log('');
|
|
4245
|
+
} catch {}
|
|
4246
|
+
}
|
|
4052
4247
|
// 1.9.196/199: ScheduleWakeup miss detector — 자율 모드 사용자 명시 "못일어나는 경우 종종 있음"
|
|
4053
4248
|
// 1.9.196: task-log mtime 기반 (60min+) — false positive 가능
|
|
4054
4249
|
// 1.9.199: last-handoff.json 정밀 측정 — R-0001 룰 (25min) 대비 gap 측정
|
|
@@ -9130,6 +9325,16 @@ function _publishToNpm(root, opts = {}) {
|
|
|
9130
9325
|
if (pubR.status === 0) {
|
|
9131
9326
|
ok(`npm publish 완료: ${pkgName}@${pkgVersion}`);
|
|
9132
9327
|
try { _recordRun(root, { kind: 'npm_publish', package: pkgName, version: pkgVersion, dryRun: !!opts.dryRun, ok: true }); } catch {}
|
|
9328
|
+
// 1.9.203: 라운드 마무리 시 다음 라운드 plan 자동 저장
|
|
9329
|
+
try {
|
|
9330
|
+
const plan = _buildAutoResumePlan(root, {
|
|
9331
|
+
nextRoundVersion: `next after ${pkgVersion}`,
|
|
9332
|
+
focus: '다음 라운드: handoff → next-action take → 사용자 명시 또는 5축 매트릭스 보강',
|
|
9333
|
+
intervalMin: 25,
|
|
9334
|
+
note: `다음 wakeup 시: 1) leerness resume . 으로 plan 확인 2) leerness handoff . 실행 3) leerness next-action take 로 즉시 task 진입`
|
|
9335
|
+
});
|
|
9336
|
+
_writeAutoResumePlan(root, plan);
|
|
9337
|
+
} catch {}
|
|
9133
9338
|
} else {
|
|
9134
9339
|
const errOut = (pubR.stderr || pubR.stdout || '').slice(-400);
|
|
9135
9340
|
if (/EPUBLISHCONFLICT|already exists|cannot publish over/i.test(errOut)) {
|
|
@@ -14965,6 +15170,8 @@ async function main() {
|
|
|
14965
15170
|
if (cmd === 'roadmap') return roadmapCmd(args[1] || process.cwd());
|
|
14966
15171
|
// 1.9.201: next-action queue CLI (E축 9.5→10)
|
|
14967
15172
|
if (cmd === 'next-action') return nextActionCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
15173
|
+
// 1.9.203: leerness resume — auto-resume-plan 읽고 다음 라운드 즉시 안내 (사용자 명시)
|
|
15174
|
+
if (cmd === 'resume') return resumeCmd(arg('--path', process.cwd()));
|
|
14968
15175
|
if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
|
|
14969
15176
|
if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
|
|
14970
15177
|
if (cmd === 'rule' && args[1] === 'remove') return ruleRemove(arg('--path', process.cwd()), args[2]);
|