leerness 1.9.149 → 1.9.151
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 +81 -0
- package/README.md +3 -3
- package/bin/harness.js +215 -76
- package/package.json +1 -1
- package/scripts/e2e.js +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.151 — 2026-05-20
|
|
4
|
+
|
|
5
|
+
**install 흐름 — CLI 에이전트 복수 선택 + REPL 자동 시작 prompt + viewwork 제거 + help 검증 (사용자 명시 3종).**
|
|
6
|
+
|
|
7
|
+
자율 모드 81 라운드.
|
|
8
|
+
|
|
9
|
+
### Added — install: CLI 에이전트 복수 선택 (사용자 명시)
|
|
10
|
+
- 기존 4지 단일 선택 (`none`/`claude`/`ollama`/`all`) → **5개 후보 다중 선택**
|
|
11
|
+
- Claude / Codex / Gemini / Copilot / Ollama
|
|
12
|
+
- Space 토글, `a` 전체, `n` 해제, Enter 확정
|
|
13
|
+
- non-TTY fallback: 콤마 구분 (예: `1,5` 또는 `claude,ollama` 또는 `all` 또는 `none`)
|
|
14
|
+
- `.env.example` LEERNESS_ENABLE_* 플래그가 선택된 에이전트별로 정확히 활성화 (이전엔 `all` 외에는 단일만)
|
|
15
|
+
- 배열/문자열/'all'/'none' 모두 처리하는 `enabledSet` Set 기반 헬퍼 (back-compat)
|
|
16
|
+
|
|
17
|
+
### Added — install 종료 후 REPL 자동 시작 prompt (사용자 명시)
|
|
18
|
+
- 모든 문항 (언어/에이전트/권한) 끝난 후 — **에이전트가 선택된 경우만** REPL 활성화 여부 묻기
|
|
19
|
+
- "예" 선택 시 설치 완료 직후 1.9.149 `_agentRepl` 자동 진입 (Ollama provider / actor 역할)
|
|
20
|
+
- 기본값 "아니오" (안전) — 사용자가 토큰/모델 설정 후 별도로 `leerness agent` 실행
|
|
21
|
+
|
|
22
|
+
### Removed — viewwork 관련 (사용자 명시 — leerness 와 무관)
|
|
23
|
+
- `viewworkEmit()` / `viewworkInstall()` 함수 정의 삭제
|
|
24
|
+
- 라우터 분기 `cmd === 'viewwork'` 2종 제거
|
|
25
|
+
- `session close` 의 자동 `viewworkEmit` 콜 제거
|
|
26
|
+
- help 텍스트에서 `leerness viewwork install/emit` 2줄 제거
|
|
27
|
+
- `scripts/e2e.js` 의 `viewwork install` + `viewwork emit` 테스트 라인 제거
|
|
28
|
+
- 기존 프로젝트의 `.viewwork/` 디렉토리는 leerness 가 삭제하지 않음 (사용자 책임)
|
|
29
|
+
|
|
30
|
+
### Verified — help 모든 명령어 통과
|
|
31
|
+
- sub-agent 병렬 검증: **39/39 명령어 모두 exit 0 또는 1 (정상)** — TypeError / ReferenceError / unknown command 0건
|
|
32
|
+
- 검증 명령어: init / migrate / update / status / verify / debug / audit / check / scan / encoding / lazy / memory / handoff / orchestrate / deps / persona / agents / setup-agents / reuse-map / session / route / self / readme / consistency / plan / task / skill / gate / retro / insights / brainstorm / roadmap / verify-code / lessons / rule / release / health / runs / permissions / env / creds / decision / lesson / plan list / agent
|
|
33
|
+
|
|
34
|
+
### Verified
|
|
35
|
+
- e2e 217/217 (viewwork 2건 삭제로 219→217)
|
|
36
|
+
- stress-v96: 26/26 (복수선택 4종 + REPL prompt 4종 + viewwork 제거 6종 + help 5종 + 누적 회귀 7종)
|
|
37
|
+
- VERSION = 1.9.151 / autonomous-rounds = 81
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 1.9.150 — 2026-05-20
|
|
42
|
+
|
|
43
|
+
**Sandboxing — `runCommandSafe()` wrapper + REPL slash-commands (3중 LLM 합의 #3 / Codex 권고).**
|
|
44
|
+
|
|
45
|
+
자율 모드 80 라운드 마일스톤. 1.9.149 REPL 위에 **샌드박스 보안 레이어** + **leerness 내부 명령 직접 호출**을 추가.
|
|
46
|
+
|
|
47
|
+
### Added — `runCommandSafe(cmd, args, opts)` sandbox wrapper
|
|
48
|
+
- **cwd jail** — `cwd` 가 root 밖 (path traversal) 이면 즉시 exit 126 + `blocked: 'cwd_jail'` 기록
|
|
49
|
+
- **shell:false 기본** — shell injection 표면 차단. `allowShell: true` 시만 shell:true (npm/pytest 호환)
|
|
50
|
+
- **env scrub** — 안전 화이트리스트만 통과 (`PATH`, `HOME`, `TMP`, `LEERNESS_*`, `NPM_CONFIG_*`, ...)
|
|
51
|
+
- 시크릿 환경변수 (DB_PASSWORD, API_KEY 등) 자식 프로세스에 누출 방지
|
|
52
|
+
- **timeout 한도** — 기본 5분, max 10분 (clamp)
|
|
53
|
+
- **permissions 검증** — 1.9.146 `permissions.shell.allowList` 자동 연동
|
|
54
|
+
- basic 모드 (`shell.exec=false`) 에선 핵심 도구 (git/npm/node/pnpm/yarn) 만 허용
|
|
55
|
+
- allowList 외 명령은 즉시 reject + `blocked: 'permissions'` 기록
|
|
56
|
+
- **자동 observability** — 호출마다 `_recordRun` 으로 cmd/args/durationMs/status/cwd 자동 기록
|
|
57
|
+
|
|
58
|
+
### Changed — 위험 호출 sandbox 치환
|
|
59
|
+
- `verify-code` (line ~7473): `cp.spawnSync(t.cmd, [], { shell: true })` → `runCommandSafe(t.cmd, ...)`
|
|
60
|
+
- `deploy auto` (line ~10580): `cp.spawnSync(meta.deployCommand, [], { shell: true })` → `runCommandSafe(...)`
|
|
61
|
+
- `agents bench` (line ~8866): `cp.spawnSync(cmd, cliArgs, { shell: true })` → `runCommandSafe(...)`
|
|
62
|
+
- 3 곳 모두 env scrub + cwd jail + observability 자동 적용
|
|
63
|
+
|
|
64
|
+
### Added — REPL slash-commands (1.9.149 위에)
|
|
65
|
+
- `:verify` — `leerness verify-code` 직접 호출 (sandboxed)
|
|
66
|
+
- `:audit` — `leerness audit` (보안 + drift + lazy)
|
|
67
|
+
- `:handoff` — `leerness handoff --quiet --no-drift-check`
|
|
68
|
+
- `:health` — `leerness health --json`
|
|
69
|
+
- 모두 `runCommandSafe` 경유 — 자식 leerness 호출도 sandbox 적용
|
|
70
|
+
- REPL 안에서 agent가 "현재 상태 점검해줘" 같은 메타 명령으로 leerness 기능을 즉시 호출 가능
|
|
71
|
+
|
|
72
|
+
### Security
|
|
73
|
+
- 시크릿 환경변수 누출 표면 대폭 축소 — `runCommandSafe` 호출 시 화이트리스트 외 env 미전달
|
|
74
|
+
- 사용자 글로벌 룰 준수: API 키/DB 비밀번호 절대 자식 프로세스에 자동 전파 금지
|
|
75
|
+
- `_reports/`, `.harness/agent-sessions/`, `.harness/runs/`, `.harness/credentials.local.json` 비공개 정책 유지
|
|
76
|
+
|
|
77
|
+
### Verified
|
|
78
|
+
- e2e 220/220 (slash-commands handleMeta 통합 + sandbox wrapper)
|
|
79
|
+
- stress-v95: 19/19 (sandbox 5종 + REPL slash 4종 + 누적 회귀 1.9.146~149)
|
|
80
|
+
- VERSION = 1.9.150 / autonomous-rounds = 80
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
3
84
|
## 1.9.149 — 2026-05-20
|
|
4
85
|
|
|
5
86
|
**REPL agent (Hermes/OpenClaw/OpenCode 스타일) + observability lite — 사용자 명시 요청 + 3중 LLM 합의 #2.**
|
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,8 +12,8 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
16
|
-
║ verify · remember · orchestrate · audit ·
|
|
15
|
+
║ v1.9.151 AI Agent Reliability Harness + Sandbox ║
|
|
16
|
+
║ verify · remember · orchestrate · audit · sandbox · drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
|
19
19
|
|
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.151';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -688,23 +688,30 @@ async function resolveInstallOptions(root, opts = {}) {
|
|
|
688
688
|
// 1.9.148: 스킬 prompt 제거 (사용자 명시 요청) — leerness가 자동으로 공식 표준 스킬 5종 설치.
|
|
689
689
|
// 필요할 때 사용자가 leerness skill install <id> 로 추가 가능.
|
|
690
690
|
if (!explicitSkills) skills = parseSkillsValue('recommended');
|
|
691
|
-
// 1.9.
|
|
692
|
-
//
|
|
693
|
-
let agentsOptIn = null;
|
|
691
|
+
// 1.9.151: CLI 에이전트 활성화 — 복수 선택 (사용자 명시 요청)
|
|
692
|
+
// _selectMany 로 Space 토글, a 전체, n 해제, Enter 확정. 선택된 에이전트들만 .env.example에 LEERNESS_ENABLE_* 활성화.
|
|
693
|
+
let agentsOptIn = null; // string[] (다중) 또는 'none' (선택 안함)
|
|
694
694
|
if (shouldAsk && !opts._skipAgentsPrompt) {
|
|
695
695
|
if (useInteractive) {
|
|
696
|
-
const
|
|
697
|
-
{ label: '
|
|
698
|
-
{ label: '
|
|
699
|
-
{ label: '
|
|
700
|
-
{ label: '
|
|
701
|
-
|
|
702
|
-
|
|
696
|
+
const picked = await _selectMany('CLI 에이전트 활성화 (복수 선택, Space 토글) — sub-agent 위임용', [
|
|
697
|
+
{ label: 'Claude (ANTHROPIC_API_KEY 또는 claude CLI)', description: '추론력 최고 — 코드 작성/리뷰 기본', id: 'claude' },
|
|
698
|
+
{ label: 'Codex (OpenAI codex CLI)', description: 'OpenAI 코드 모델', id: 'codex' },
|
|
699
|
+
{ label: 'Gemini (gemini CLI)', description: 'Google 멀티모달 모델', id: 'gemini' },
|
|
700
|
+
{ label: 'Copilot (gh extension)', description: 'GitHub Copilot CLI', id: 'copilot' },
|
|
701
|
+
{ label: 'Ollama (로컬 LLM — llama3/qwen 등)', description: 'http://localhost:11434 — 무료/오프라인', id: 'ollama' }
|
|
702
|
+
], { defaults: [] });
|
|
703
|
+
agentsOptIn = picked.length ? picked.map(p => p.id) : 'none';
|
|
703
704
|
} else {
|
|
704
|
-
log('\nCLI 에이전트 활성화 (
|
|
705
|
-
log('1)
|
|
706
|
-
const a = await ask('선택 [
|
|
707
|
-
|
|
705
|
+
log('\nCLI 에이전트 활성화 (복수 선택 — 콤마로 구분, opt-in):');
|
|
706
|
+
log(' 1) claude 2) codex 3) gemini 4) copilot 5) ollama (예: 1,5 또는 all 또는 none)');
|
|
707
|
+
const a = (await ask('선택 [none]: ')).trim().toLowerCase();
|
|
708
|
+
if (a === 'all') agentsOptIn = ['claude', 'codex', 'gemini', 'copilot', 'ollama'];
|
|
709
|
+
else if (!a || a === 'none' || a === '0') agentsOptIn = 'none';
|
|
710
|
+
else {
|
|
711
|
+
const map = { '1': 'claude', '2': 'codex', '3': 'gemini', '4': 'copilot', '5': 'ollama' };
|
|
712
|
+
const picks = a.split(/[,\s]+/).map(t => map[t] || (['claude','codex','gemini','copilot','ollama'].includes(t) ? t : null)).filter(Boolean);
|
|
713
|
+
agentsOptIn = picks.length ? picks : 'none';
|
|
714
|
+
}
|
|
708
715
|
}
|
|
709
716
|
}
|
|
710
717
|
// 1.9.146: 권한 모드 (사용자 명시 요청 #5 — agent IDE 모드 사전 prompt)
|
|
@@ -726,7 +733,24 @@ async function resolveInstallOptions(root, opts = {}) {
|
|
|
726
733
|
permissionMode = a === '2' ? 'extended' : a === '3' ? 'full' : 'basic';
|
|
727
734
|
}
|
|
728
735
|
}
|
|
729
|
-
|
|
736
|
+
// 1.9.151: 모든 문항 종료 후 — REPL 모드 즉시 활성화 여부 (사용자 명시 요청)
|
|
737
|
+
// 선택된 에이전트가 있을 때만 표시. 설치 완료 후 install() 가 처리.
|
|
738
|
+
let startRepl = false;
|
|
739
|
+
const hasAgents = Array.isArray(agentsOptIn) && agentsOptIn.length > 0;
|
|
740
|
+
if (shouldAsk && hasAgents && !opts._skipReplPrompt) {
|
|
741
|
+
if (useInteractive) {
|
|
742
|
+
const rOpt = await _selectOne('설치 완료 후 REPL agent 모드를 즉시 시작할까요?', [
|
|
743
|
+
{ label: '아니오 — 설치만 완료 (나중에 `leerness agent` 로 실행)', description: '권장 — 토큰/모델 설정 후 사용', id: 'no' },
|
|
744
|
+
{ label: '예 — 설치 직후 REPL 모드 진입 (Hermes/OpenClaw 스타일)', description: 'Ollama 우선 — 가능하면 자동 모델 선택', id: 'yes' }
|
|
745
|
+
], { defaultIndex: 0 });
|
|
746
|
+
startRepl = rOpt && rOpt.id === 'yes';
|
|
747
|
+
} else {
|
|
748
|
+
log('\n설치 완료 후 REPL agent 모드를 즉시 시작할까요? (y/N)');
|
|
749
|
+
const a = (await ask('선택 [N]: ')).trim().toLowerCase();
|
|
750
|
+
startRepl = a === 'y' || a === 'yes';
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return { lang, skills, agentsOptIn, permissionMode, startRepl };
|
|
730
754
|
}
|
|
731
755
|
|
|
732
756
|
async function install(root, opts = {}) {
|
|
@@ -753,7 +777,11 @@ async function install(root, opts = {}) {
|
|
|
753
777
|
log(`Target: ${root}`);
|
|
754
778
|
log(`Language: ${lang}`);
|
|
755
779
|
log(`Skills: ${skills.length ? skills.join(', ') : 'none (건너뜀)'}`);
|
|
756
|
-
if (resolved.agentsOptIn && resolved.agentsOptIn !== 'none')
|
|
780
|
+
if (resolved.agentsOptIn && resolved.agentsOptIn !== 'none') {
|
|
781
|
+
const list = Array.isArray(resolved.agentsOptIn) ? resolved.agentsOptIn.join(', ') : String(resolved.agentsOptIn);
|
|
782
|
+
log(`Agents 활성화: ${list}`);
|
|
783
|
+
}
|
|
784
|
+
if (resolved.startRepl) log(`REPL 자동 시작: 예 (설치 완료 후 \`leerness agent\` 진입)`);
|
|
757
785
|
if (resolved.permissionMode) log(`Agent 권한 모드: ${resolved.permissionMode}`);
|
|
758
786
|
// 1.9.10: 스킬 카탈로그 출처 안내
|
|
759
787
|
if (SKILLPACK_SOURCE === 'builtin') log(`Skill catalog source: builtin (leerness-skillpack 미설치 — \`npm i leerness-skillpack\`로 확장 가능)`);
|
|
@@ -799,9 +827,15 @@ async function install(root, opts = {}) {
|
|
|
799
827
|
// 1.9.149: agent REPL 세션 + observability runs 비공개 (대화 내용 보호)
|
|
800
828
|
'.harness/agent-sessions/','.harness/runs/'
|
|
801
829
|
]);
|
|
802
|
-
// 1.9.
|
|
830
|
+
// 1.9.151: agentsOptIn 복수 선택 지원 — 배열 또는 'none' 또는 'all' (back-compat) 모두 처리
|
|
803
831
|
const a = resolved.agentsOptIn || 'none';
|
|
804
|
-
const
|
|
832
|
+
const enabledSet = (() => {
|
|
833
|
+
if (Array.isArray(a)) return new Set(a);
|
|
834
|
+
if (a === 'all') return new Set(['claude', 'codex', 'gemini', 'copilot', 'ollama']);
|
|
835
|
+
if (a === 'none' || !a) return new Set();
|
|
836
|
+
return new Set([a]); // back-compat: 단일 문자열
|
|
837
|
+
})();
|
|
838
|
+
const enable = (cli) => enabledSet.has(cli);
|
|
805
839
|
mergeLinesFile(path.join(root, '.env.example'), [
|
|
806
840
|
'# Leerness uses environment variable names only. Do not store real secrets here.',
|
|
807
841
|
'LEERNESS_NPM_TOKEN=','LEERNESS_GITHUB_TOKEN=',
|
|
@@ -809,11 +843,11 @@ async function install(root, opts = {}) {
|
|
|
809
843
|
`LEERNESS_OLLAMA_BASE_URL=${enable('ollama') ? 'http://localhost:11434' : ''}`,
|
|
810
844
|
'# 선택. 기본 모델 (orchestrate --model 로 override 가능). 예: llama3 / qwen2.5-coder / gpt-oss',
|
|
811
845
|
'LEERNESS_OLLAMA_MODEL=',
|
|
812
|
-
'# 1.9.30+1.9.146 — 외부 AI CLI 활성화
|
|
846
|
+
'# 1.9.30+1.9.146+1.9.151 — 외부 AI CLI 활성화 플래그 (복수 선택). 1=활성, 0/미설정=비활성. 메인 에이전트가 sub-agent 분배 시 활성 CLI들에 작업 위임 가능.',
|
|
813
847
|
`LEERNESS_ENABLE_CLAUDE=${enable('claude') ? 1 : 0}`,
|
|
814
|
-
`LEERNESS_ENABLE_CODEX=${enable('
|
|
815
|
-
`LEERNESS_ENABLE_GEMINI=${enable('
|
|
816
|
-
`LEERNESS_ENABLE_COPILOT=${enable('
|
|
848
|
+
`LEERNESS_ENABLE_CODEX=${enable('codex') ? 1 : 0}`,
|
|
849
|
+
`LEERNESS_ENABLE_GEMINI=${enable('gemini') ? 1 : 0}`,
|
|
850
|
+
`LEERNESS_ENABLE_COPILOT=${enable('copilot') ? 1 : 0}`,
|
|
817
851
|
`LEERNESS_ENABLE_OLLAMA=${enable('ollama') ? 1 : 0}`,
|
|
818
852
|
'# 1.9.42 — agentskills.io 공개 표준 스킬 자동 탐색 (opt-in). URL 설정 시 `leerness skill discover` 사용 가능.',
|
|
819
853
|
'# 예: LEERNESS_SKILL_DISCOVER_URL=https://agentskills.io/llms.txt',
|
|
@@ -869,6 +903,15 @@ async function install(root, opts = {}) {
|
|
|
869
903
|
// 1.9.148: 1.9.32 중복 prompt 제거 (사용자 명시 — CLI 에이전트 prompt 중복).
|
|
870
904
|
// resolveInstallOptions (1.9.146) 가 이미 모든 prompt 모은 위치에 통합된 4지선다 prompt 있음.
|
|
871
905
|
// 별도 setupAgents 명령은 사용자가 명시적으로 `leerness setup-agents` 호출 시에만.
|
|
906
|
+
// 1.9.151: 설치 완료 직후 — startRepl 선택 시 REPL agent 모드 즉시 진입 (사용자 명시 요청)
|
|
907
|
+
if (resolved.startRepl && !opts.migration && process.stdin.isTTY && process.env.LEERNESS_NO_PROMPT !== '1') {
|
|
908
|
+
log('');
|
|
909
|
+
log('🚀 설치 완료 — REPL agent 모드를 시작합니다 (1.9.149 Hermes/OpenClaw 스타일)...');
|
|
910
|
+
log('');
|
|
911
|
+
try {
|
|
912
|
+
await _agentRepl(root, { provider: 'ollama', role: 'actor' });
|
|
913
|
+
} catch (e) { warn('REPL 진입 실패: ' + e.message); }
|
|
914
|
+
}
|
|
872
915
|
}
|
|
873
916
|
}
|
|
874
917
|
|
|
@@ -7470,7 +7513,8 @@ function verifyCodeCmd(root) {
|
|
|
7470
7513
|
for (const t of tasks) {
|
|
7471
7514
|
log(`\n## ${t.name}: ${t.cmd}`);
|
|
7472
7515
|
const start = Date.now();
|
|
7473
|
-
|
|
7516
|
+
// 1.9.150: runCommandSafe — cwd jail + env scrub + observability 자동 (shell:true 유지 — npm/pytest 호환)
|
|
7517
|
+
const r = runCommandSafe(t.cmd, [], { cwd: root, root, timeout: 5 * 60 * 1000, allowShell: true, kind: 'verify_code_task', label: `verify-${t.name}` });
|
|
7474
7518
|
const dur = Date.now() - start;
|
|
7475
7519
|
if (r.status === 0) ok(`${t.name} passed (${dur}ms)`);
|
|
7476
7520
|
else if (t.optional && r.status === 127) warn(`${t.name} 스킵 (${t.cmd} 없음)`);
|
|
@@ -8192,48 +8236,10 @@ function autoUpdateInstall(root) {
|
|
|
8192
8236
|
ok('/update slash command added');
|
|
8193
8237
|
}
|
|
8194
8238
|
|
|
8195
|
-
//
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
if (!exists(dir)) return;
|
|
8200
|
-
const file = path.join(dir, 'agent-events.jsonl');
|
|
8201
|
-
const line = JSON.stringify({
|
|
8202
|
-
at: Date.now(),
|
|
8203
|
-
agent: ev.agent || 'leerness',
|
|
8204
|
-
agentKind: ev.agentKind || 'system',
|
|
8205
|
-
action: ev.action || 'task',
|
|
8206
|
-
path: ev.path || '/.harness',
|
|
8207
|
-
tool: ev.tool || 'leerness-cli',
|
|
8208
|
-
toolKind: ev.toolKind || 'task',
|
|
8209
|
-
note: ev.note || ''
|
|
8210
|
-
}) + '\n';
|
|
8211
|
-
try { fs.appendFileSync(file, line, 'utf8'); } catch {}
|
|
8212
|
-
}
|
|
8213
|
-
|
|
8214
|
-
function viewworkInstall(root) {
|
|
8215
|
-
root = absRoot(root);
|
|
8216
|
-
const dir = path.join(root, '.viewwork');
|
|
8217
|
-
mkdirp(dir);
|
|
8218
|
-
if (!exists(path.join(dir, 'agent-events.jsonl'))) writeUtf8(path.join(dir, 'agent-events.jsonl'), '');
|
|
8219
|
-
if (!exists(path.join(dir, 'config.json'))) writeUtf8(path.join(dir, 'config.json'), JSON.stringify({ schemaVersion: 2 }, null, 2) + '\n');
|
|
8220
|
-
if (!exists(path.join(dir, 'version'))) writeUtf8(path.join(dir, 'version'), '2\n');
|
|
8221
|
-
const settingsDir = path.join(root, '.claude');
|
|
8222
|
-
mkdirp(settingsDir);
|
|
8223
|
-
const settingsFile = path.join(settingsDir, 'settings.local.json');
|
|
8224
|
-
let settings = {};
|
|
8225
|
-
if (exists(settingsFile)) { try { settings = JSON.parse(read(settingsFile)); } catch {} }
|
|
8226
|
-
settings.hooks = settings.hooks || {};
|
|
8227
|
-
if (!settings.hooks.Stop) settings.hooks.Stop = [];
|
|
8228
|
-
if (!settings.hooks.Stop.some(h => h.command && h.command.includes('leerness viewwork'))) {
|
|
8229
|
-
settings.hooks.Stop.push({ matcher: '*', command: 'leerness viewwork emit . --action task --note "claude session stop"' });
|
|
8230
|
-
}
|
|
8231
|
-
writeUtf8(settingsFile, JSON.stringify(settings, null, 2) + '\n');
|
|
8232
|
-
writeUtf8(path.join(root, '.claude/commands/viewwork-ping.md'),
|
|
8233
|
-
`# /viewwork-ping\n\nViewWork 이벤트를 수동으로 기록합니다.\n\n\`\`\`\n!leerness viewwork emit . --action note --note \"manual ping\"\n\`\`\`\n`);
|
|
8234
|
-
ok('viewwork hook installed');
|
|
8235
|
-
ok('claude .claude/settings.local.json updated (Stop hook adds a viewwork event)');
|
|
8236
|
-
}
|
|
8239
|
+
// 1.9.151: ViewWork hook 제거 (사용자 명시 — leerness와 무관한 외부 도구)
|
|
8240
|
+
// 이전 1.9.0~1.9.150 에서 .viewwork/ 디렉토리에 hook 으로 이벤트 기록했으나, ViewWork는 leerness 의존 산출물이
|
|
8241
|
+
// 아닌 별도 도구임. 사용자가 직접 ViewWork 를 사용하지 않으면 leerness 가 이를 강제할 이유가 없음.
|
|
8242
|
+
// 기존 프로젝트의 .viewwork/ 폴더는 그대로 유지 (leerness 가 삭제하지 않음 — 사용자 책임).
|
|
8237
8243
|
|
|
8238
8244
|
// 1.9.37: drift detection — 메타파일 staleness 측정으로 "leerness 점점 안 쓰는" 현상 감지
|
|
8239
8245
|
function driftCheckCmd(root, opts = {}) {
|
|
@@ -8862,7 +8868,8 @@ async function _benchmarkMeasure(root, task) {
|
|
|
8862
8868
|
else if (agent.id === 'gemini') { cmd = 'gemini'; cliArgs = ['-p', task]; }
|
|
8863
8869
|
else continue;
|
|
8864
8870
|
const t0 = Date.now();
|
|
8865
|
-
|
|
8871
|
+
// 1.9.150: runCommandSafe — agent CLI bench sandbox (env scrub + observability)
|
|
8872
|
+
const r = runCommandSafe(cmd, cliArgs, { cwd: process.cwd(), root, timeout: 60000, allowShell: true, kind: 'agent_bench', label: `bench-${agent.id}`, allowOutsideCwd: true });
|
|
8866
8873
|
const baseTime = Date.now() - t0;
|
|
8867
8874
|
// leerness 검수 layer time 추정 (verify-claim 형식)
|
|
8868
8875
|
const t1 = Date.now();
|
|
@@ -10010,6 +10017,111 @@ function _recordRun(root, entry) {
|
|
|
10010
10017
|
return id;
|
|
10011
10018
|
} catch { return null; }
|
|
10012
10019
|
}
|
|
10020
|
+
|
|
10021
|
+
// 1.9.150: Sandboxing — runCommandSafe wrapper (Codex 권고: 3중 LLM 합의 #3)
|
|
10022
|
+
// cwd jail (root 밖 거부) + shell:false 기본 + timeout + env scrub + permissions allowList 검증 + _recordRun 자동
|
|
10023
|
+
const _ENV_SAFE_KEYS = new Set([
|
|
10024
|
+
'PATH', 'HOME', 'USERPROFILE', 'TEMP', 'TMP', 'TMPDIR', 'NODE_PATH', 'NODE_ENV',
|
|
10025
|
+
'LANG', 'LC_ALL', 'LC_CTYPE', 'SHELL', 'COMSPEC', 'SYSTEMROOT', 'WINDIR', 'OS',
|
|
10026
|
+
'PROCESSOR_ARCHITECTURE', 'PROCESSOR_IDENTIFIER', 'NUMBER_OF_PROCESSORS',
|
|
10027
|
+
'PROGRAMFILES', 'PROGRAMFILES(X86)', 'APPDATA', 'LOCALAPPDATA',
|
|
10028
|
+
'GITHUB_TOKEN', 'NPM_TOKEN', 'CI', 'GH_TOKEN'
|
|
10029
|
+
]);
|
|
10030
|
+
function _scrubEnv(extraEnv) {
|
|
10031
|
+
const out = {};
|
|
10032
|
+
for (const k of Object.keys(process.env || {})) {
|
|
10033
|
+
if (_ENV_SAFE_KEYS.has(k) || k.startsWith('LEERNESS_') || k.startsWith('NPM_CONFIG_')) {
|
|
10034
|
+
out[k] = process.env[k];
|
|
10035
|
+
}
|
|
10036
|
+
}
|
|
10037
|
+
if (extraEnv && typeof extraEnv === 'object') {
|
|
10038
|
+
for (const k of Object.keys(extraEnv)) {
|
|
10039
|
+
// Allow caller overrides — explicit opt-in
|
|
10040
|
+
if (extraEnv[k] !== undefined) out[k] = String(extraEnv[k]);
|
|
10041
|
+
}
|
|
10042
|
+
}
|
|
10043
|
+
return out;
|
|
10044
|
+
}
|
|
10045
|
+
function _isCwdSafe(root, cwd) {
|
|
10046
|
+
try {
|
|
10047
|
+
if (!cwd) return true;
|
|
10048
|
+
const r = path.resolve(absRoot(root));
|
|
10049
|
+
const c = path.resolve(cwd);
|
|
10050
|
+
if (c === r) return true;
|
|
10051
|
+
const rel = path.relative(r, c);
|
|
10052
|
+
return !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
10053
|
+
} catch { return false; }
|
|
10054
|
+
}
|
|
10055
|
+
function runCommandSafe(cmd, args, opts) {
|
|
10056
|
+
// opts: { cwd, root, timeout, env, stdio, kind, label, allowShell, encoding, input, allowOutsideCwd }
|
|
10057
|
+
opts = opts || {};
|
|
10058
|
+
const root = opts.root || opts.cwd || process.cwd();
|
|
10059
|
+
const cwd = opts.cwd || root;
|
|
10060
|
+
const cmdStr = String(cmd || '').trim();
|
|
10061
|
+
const argList = Array.isArray(args) ? args.slice() : [];
|
|
10062
|
+
const t0 = Date.now();
|
|
10063
|
+
const label = opts.label || opts.kind || 'shell_exec';
|
|
10064
|
+
// 1) cwd jail
|
|
10065
|
+
if (!opts.allowOutsideCwd && !_isCwdSafe(root, cwd)) {
|
|
10066
|
+
const r = { status: 126, stdout: '', stderr: `runCommandSafe: cwd outside root rejected (${cwd})`, error: 'cwd_jail', blocked: true };
|
|
10067
|
+
try { _recordRun(root, { kind: label, cmd: cmdStr, args: argList, durationMs: Date.now() - t0, ok: false, blocked: 'cwd_jail' }); } catch {}
|
|
10068
|
+
return r;
|
|
10069
|
+
}
|
|
10070
|
+
// 2) permissions allowList (1.9.146)
|
|
10071
|
+
try {
|
|
10072
|
+
const perms = _readPermissions(root);
|
|
10073
|
+
const exec = perms.shell?.exec !== false; // basic 에선 false
|
|
10074
|
+
const allow = perms.shell?.allowList || [];
|
|
10075
|
+
if (!exec && !opts.allowOutsideCwd) {
|
|
10076
|
+
// basic 모드 — git/npm/node 같은 핵심 도구는 허용 (release/install 흐름 유지)
|
|
10077
|
+
const coreAllow = ['git', 'npm', 'npx', 'node', 'pnpm', 'yarn'];
|
|
10078
|
+
const first = cmdStr.split(/\s+/)[0];
|
|
10079
|
+
if (!coreAllow.includes(first) && !allow.includes('*') && !allow.includes(first)) {
|
|
10080
|
+
const r = { status: 126, stdout: '', stderr: `runCommandSafe: shell.exec=false (mode=${perms.mode}). allowList: ${allow.join(',') || '(없음)'} / core: ${coreAllow.join(',')}`, error: 'permissions', blocked: true };
|
|
10081
|
+
try { _recordRun(root, { kind: label, cmd: cmdStr, args: argList, durationMs: Date.now() - t0, ok: false, blocked: 'permissions', mode: perms.mode }); } catch {}
|
|
10082
|
+
return r;
|
|
10083
|
+
}
|
|
10084
|
+
}
|
|
10085
|
+
} catch {}
|
|
10086
|
+
// 3) spawn — shell:false 기본 (shell injection 차단). allowShell=true 시만 shell:true (deploy/build 호환)
|
|
10087
|
+
const useShell = !!opts.allowShell;
|
|
10088
|
+
const timeout = Math.min(opts.timeout || 5 * 60 * 1000, 10 * 60 * 1000);
|
|
10089
|
+
const spawnOpts = {
|
|
10090
|
+
cwd,
|
|
10091
|
+
encoding: opts.encoding || 'utf8',
|
|
10092
|
+
timeout,
|
|
10093
|
+
shell: useShell,
|
|
10094
|
+
env: _scrubEnv(opts.env),
|
|
10095
|
+
input: opts.input,
|
|
10096
|
+
stdio: opts.stdio || 'pipe'
|
|
10097
|
+
};
|
|
10098
|
+
let r;
|
|
10099
|
+
try {
|
|
10100
|
+
if (useShell) {
|
|
10101
|
+
// shell:true 모드 — 인자가 cmd 안에 포함된 단일 문자열인 경우 처리
|
|
10102
|
+
r = cp.spawnSync(cmdStr + (argList.length ? ' ' + argList.join(' ') : ''), [], spawnOpts);
|
|
10103
|
+
} else {
|
|
10104
|
+
// 단일 명령어로 들어온 경우 자동 분리
|
|
10105
|
+
let bin = cmdStr, finalArgs = argList;
|
|
10106
|
+
if (!argList.length && /\s/.test(cmdStr)) {
|
|
10107
|
+
const parts = cmdStr.split(/\s+/);
|
|
10108
|
+
bin = parts[0]; finalArgs = parts.slice(1);
|
|
10109
|
+
}
|
|
10110
|
+
r = cp.spawnSync(bin, finalArgs, spawnOpts);
|
|
10111
|
+
}
|
|
10112
|
+
} catch (e) {
|
|
10113
|
+
r = { status: 1, stdout: '', stderr: e.message, error: 'spawn_exception' };
|
|
10114
|
+
}
|
|
10115
|
+
const dt = Date.now() - t0;
|
|
10116
|
+
try {
|
|
10117
|
+
_recordRun(root, {
|
|
10118
|
+
kind: label, cmd: cmdStr, args: argList,
|
|
10119
|
+
durationMs: dt, status: r.status, ok: r.status === 0,
|
|
10120
|
+
shell: useShell, cwd: path.relative(absRoot(root), cwd) || '.'
|
|
10121
|
+
});
|
|
10122
|
+
} catch {}
|
|
10123
|
+
return r;
|
|
10124
|
+
}
|
|
10013
10125
|
function runsListCmd(root) {
|
|
10014
10126
|
root = absRoot(root || process.cwd());
|
|
10015
10127
|
const dir = _runsDir(root);
|
|
@@ -10077,8 +10189,8 @@ async function _agentRepl(root, opts) {
|
|
|
10077
10189
|
// 환영 메시지 + 모델 선택
|
|
10078
10190
|
log('');
|
|
10079
10191
|
log(C.bold(C.cy(' ╔════════════════════════════════════════════════════╗')));
|
|
10080
|
-
log(C.bold(C.cy(' ║ leerness agent — REPL mode (1.9.
|
|
10081
|
-
log(C.bold(C.cy(' ║ Hermes / OpenClaw / OpenCode 스타일
|
|
10192
|
+
log(C.bold(C.cy(' ║ leerness agent — REPL mode (1.9.150) ║')));
|
|
10193
|
+
log(C.bold(C.cy(' ║ Hermes / OpenClaw / OpenCode 스타일 + Sandbox ║')));
|
|
10082
10194
|
log(C.bold(C.cy(' ╚════════════════════════════════════════════════════╝')));
|
|
10083
10195
|
log('');
|
|
10084
10196
|
// Ollama 모델 자동 감지 — model이 명시되지 않았으면 사용자에게 선택지 제공
|
|
@@ -10100,6 +10212,7 @@ async function _agentRepl(root, opts) {
|
|
|
10100
10212
|
}
|
|
10101
10213
|
log('');
|
|
10102
10214
|
log(C.dim(' 메타 명령: :help | :model <m> | :role <r> | :provider <p> | :clear | :save | :history | :quit'));
|
|
10215
|
+
log(C.dim(' Slash 명령 (1.9.150): :verify | :audit | :handoff | :health'));
|
|
10103
10216
|
log(C.dim(` 현재 — provider=${state.provider} model=${state.model || '(없음)'} role=${state.role} permissions=${_readPermissions(root).mode}`));
|
|
10104
10217
|
log('');
|
|
10105
10218
|
const prompt = () => isTty ? C.cy(`agent[${state.role}]> `) : 'agent> ';
|
|
@@ -10125,6 +10238,11 @@ async function _agentRepl(root, opts) {
|
|
|
10125
10238
|
log(' :save — 세션 즉시 저장');
|
|
10126
10239
|
log(' :permissions — 현재 권한 모드 표시');
|
|
10127
10240
|
log(' :quit / :exit / :q — 종료 (자동 저장)');
|
|
10241
|
+
log(C.bold('\n Slash 명령 (1.9.150) — leerness 내부 명령 직접 호출:'));
|
|
10242
|
+
log(' :verify — leerness verify-code (테스트/타입/린트 자동 검수)');
|
|
10243
|
+
log(' :audit — leerness audit (보안 + drift + lazy)');
|
|
10244
|
+
log(' :handoff — leerness handoff --quiet (현재 컨텍스트 요약)');
|
|
10245
|
+
log(' :health — leerness health --json (종합 헬스 체크)');
|
|
10128
10246
|
return false;
|
|
10129
10247
|
}
|
|
10130
10248
|
if (op === 'model') { state.model = rest.join(' ') || state.model; log(C.green(` model = ${state.model}`)); return false; }
|
|
@@ -10149,6 +10267,26 @@ async function _agentRepl(root, opts) {
|
|
|
10149
10267
|
}
|
|
10150
10268
|
if (op === 'save') { saveSession(); log(C.dim(` → ${rel(root, sessionPath())}`)); return false; }
|
|
10151
10269
|
if (op === 'permissions') { permissionsListCmd(root); return false; }
|
|
10270
|
+
// 1.9.150: leerness 내부 명령 slash-commands — :verify / :audit / :handoff / :health
|
|
10271
|
+
if (op === 'verify' || op === 'audit' || op === 'handoff' || op === 'health') {
|
|
10272
|
+
const subArgs = {
|
|
10273
|
+
verify: ['verify-code', root],
|
|
10274
|
+
audit: ['audit', root],
|
|
10275
|
+
handoff: ['handoff', root, '--quiet', '--no-drift-check'],
|
|
10276
|
+
health: ['health', root, '--json']
|
|
10277
|
+
}[op];
|
|
10278
|
+
log(C.dim(` → leerness ${subArgs.join(' ')}`));
|
|
10279
|
+
const t0 = Date.now();
|
|
10280
|
+
const r = runCommandSafe(process.execPath, [__filename, ...subArgs], {
|
|
10281
|
+
cwd: root, root, timeout: 60000, kind: 'agent_repl_slash', label: `repl-${op}`,
|
|
10282
|
+
env: { LEERNESS_NO_BANNER: '1', LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1' }
|
|
10283
|
+
});
|
|
10284
|
+
const dt = Date.now() - t0;
|
|
10285
|
+
if (r.stdout) log(r.stdout.trim().split('\n').slice(0, 30).join('\n'));
|
|
10286
|
+
if (r.status === 0) log(C.green(` ✓ :${op} 완료 (${dt}ms)`));
|
|
10287
|
+
else log(C.yel(` ⚠ :${op} 실패 (exit ${r.status}, ${dt}ms)`));
|
|
10288
|
+
return false;
|
|
10289
|
+
}
|
|
10152
10290
|
log(C.yel(` 알 수 없는 명령: :${op} (:help 참고)`));
|
|
10153
10291
|
return false;
|
|
10154
10292
|
};
|
|
@@ -10208,16 +10346,17 @@ async function agentCmd(root, taskArg) {
|
|
|
10208
10346
|
return;
|
|
10209
10347
|
}
|
|
10210
10348
|
// non-TTY: 사용법만 출력
|
|
10211
|
-
log('# leerness agent (1.9.146/148/149) — Hermes/OpenClaw 스타일 CLI 에이전트');
|
|
10349
|
+
log('# leerness agent (1.9.146/148/149/150) — Hermes/OpenClaw 스타일 CLI 에이전트 + Sandbox');
|
|
10212
10350
|
log('');
|
|
10213
10351
|
log('사용법:');
|
|
10214
|
-
log(' leerness agent #
|
|
10352
|
+
log(' leerness agent # 1.9.149 REPL 모드 (모델 선택 + 채팅)');
|
|
10215
10353
|
log(' leerness agent "<task>" # 1회 위임 (actor 역할 기본)');
|
|
10216
10354
|
log(' leerness agent "<task>" --role planner # 계획만 (1.9.148)');
|
|
10217
10355
|
log(' leerness agent "<task>" --role reviewer # 비판적 검토 (1.9.148)');
|
|
10218
10356
|
log(' leerness agent --interactive --model qwen2.5-coder # 명시적 REPL + model 선택');
|
|
10219
10357
|
log('');
|
|
10220
10358
|
log('REPL 메타 명령: :help / :model / :role / :provider / :history / :save / :quit');
|
|
10359
|
+
log('REPL Slash 명령 (1.9.150): :verify / :audit / :handoff / :health (sandboxed runCommandSafe)');
|
|
10221
10360
|
log('');
|
|
10222
10361
|
log('현재 활성 provider: ' + (_activeCliAgents().join(', ') || '(없음) — .env에서 LEERNESS_ENABLE_* 활성화'));
|
|
10223
10362
|
log('권한 모드: ' + (_readPermissions(root).mode || 'basic'));
|
|
@@ -10575,7 +10714,8 @@ async function deployAutoCmd(root, service) {
|
|
|
10575
10714
|
log(`service: ${service} · command: ${meta.deployCommand}`);
|
|
10576
10715
|
if (has('--dry-run')) { log('(dry-run) 실제 실행 스킵'); return; }
|
|
10577
10716
|
const t0 = Date.now();
|
|
10578
|
-
|
|
10717
|
+
// 1.9.150: runCommandSafe — deploy 명령 sandbox (env scrub + permissions 검증 + observability)
|
|
10718
|
+
const r = runCommandSafe(meta.deployCommand, [], { cwd: root, root, timeout: 10 * 60 * 1000, allowShell: true, stdio: 'inherit', kind: 'deploy_auto', label: `deploy-${service}` });
|
|
10579
10719
|
const dt = Date.now() - t0;
|
|
10580
10720
|
if (r.status === 0) {
|
|
10581
10721
|
ok(`deploy 성공: ${service} (${dt}ms)`);
|
|
@@ -11011,7 +11151,7 @@ function reuseAutodetectCmd(root) {
|
|
|
11011
11151
|
}
|
|
11012
11152
|
|
|
11013
11153
|
function help() {
|
|
11014
|
-
log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 명세 ↔ 구현 일치 검사 (함수/필드)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js의 module.exports → reuse-map 후보 등록\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state 자동 갱신\n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim에 낙관적 표시 자동 검사 통합\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness
|
|
11154
|
+
log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 명세 ↔ 구현 일치 검사 (함수/필드)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js의 module.exports → reuse-map 후보 등록\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state 자동 갱신\n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim에 낙관적 표시 자동 검사 통합\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
|
|
11015
11155
|
leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
|
|
11016
11156
|
leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
|
|
11017
11157
|
leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
|
|
@@ -11113,9 +11253,8 @@ async function main() {
|
|
|
11113
11253
|
if (cmd === 'whats-new') return whatsNewCmd(args[1] || arg('--path', process.cwd()));
|
|
11114
11254
|
if (cmd === 'reuse' && args[1] === 'autodetect') return reuseAutodetectCmd(args[2] || arg('--path', process.cwd()));
|
|
11115
11255
|
if (cmd === 'setup-agents' || cmd === 'setup' && args[1] === 'agents') return await setupAgentsCmd(args[1] && args[1] !== 'agents' ? args[1] : (args[2] || process.cwd()));
|
|
11116
|
-
if (cmd === 'session' && args[1] === 'close')
|
|
11117
|
-
|
|
11118
|
-
if (cmd === 'viewwork' && args[1] === 'emit') return viewworkEmit(args[2] || process.cwd(), { action: arg('--action','task'), note: arg('--note',''), agent: arg('--agent','leerness'), tool: arg('--tool','leerness-cli') });
|
|
11256
|
+
if (cmd === 'session' && args[1] === 'close') return sessionClose(args[2] || process.cwd(), { json: has('--json') });
|
|
11257
|
+
// 1.9.151: viewwork 명령 제거 (사용자 명시 — leerness 와 무관). session close 의 viewworkEmit 콜도 함께 제거.
|
|
11119
11258
|
if (cmd === 'route') return route(args[1] || 'planning');
|
|
11120
11259
|
if (cmd === 'self' && args[1] === 'check') return await selfCheck(absRoot(args[2] || process.cwd()));
|
|
11121
11260
|
if (cmd === 'self' && args[1] === 'migrate') return log('Run: npx --yes leerness@latest migrate . --dry-run, then migrate without --dry-run after review.');
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -145,8 +145,7 @@ total++;
|
|
|
145
145
|
if (!ok) failed++;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
run('viewwork emit', ['viewwork', 'emit', tmp, '--action', 'note', '--note', 'e2e ping']);
|
|
148
|
+
// 1.9.151: viewwork 명령 제거 (사용자 명시 — leerness 와 무관)
|
|
150
149
|
run('route planning', ['route', 'planning']);
|
|
151
150
|
run('route bugfix', ['route', 'bugfix']);
|
|
152
151
|
run('skill list', ['skill', 'list']);
|