leerness 1.9.275 → 1.9.277
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 +46 -0
- package/README.md +25 -9
- package/bin/harness.js +148 -22
- package/package.json +1 -1
- package/scripts/e2e.js +36 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.277 — 2026-06-03 — 신규 provider 4종(opencode/qwen/aider/goose) + GPT-5.5 범용 하네스 방향 백로그 등록
|
|
4
|
+
|
|
5
|
+
**🤝 CLI 에이전트 4종을 정식 provider 로 추가 (설치 선택지 + 모델 catalog + dispatch/roles 라우팅). 빌트인 6 → 10종.**
|
|
6
|
+
|
|
7
|
+
### 신규 provider (사용자 명시)
|
|
8
|
+
1. **EXTERNAL_AGENTS +4** — `opencode`(오픈소스 터미널 에이전트) · `qwen`(Qwen Code) · `aider`(git-aware 페어프로그래밍) · `goose`(Block 범용 에이전트, MCP 확장). 각 `bin`/`envFlag`/`installCmd` 포함.
|
|
9
|
+
2. **자동 전파** — provider list(10)/cycle/setup-agents/`agents dispatch`/`roles`/`slash-commands` 가 4종 자동 흡수.
|
|
10
|
+
3. **install 흐름 10선택지** — 대화형 `_selectMany` + 비대화형 `1~10` 맵 확장.
|
|
11
|
+
4. **모델 catalog** — opencode(default/anthropic/openai) · qwen(qwen3-coder-plus/max/plus/turbo) · aider(sonnet/gpt-5/deepseek/o1-mini) · goose(default/claude/gpt).
|
|
12
|
+
5. **`_dispatchCommand`** — `opencode run` · `qwen -p` · `aider --message [--yes]` · `goose run -t` (모델 플래그 주입, best-effort).
|
|
13
|
+
6. **슬래시 레지스트리** — 4종 세션 슬래시/명령 큐레이션(opencode/qwen/aider/goose).
|
|
14
|
+
7. **config/nonsecret 키** — `LEERNESS_ENABLE_OPENCODE/QWEN/AIDER/GOOSE`.
|
|
15
|
+
|
|
16
|
+
### GPT-5.5 "범용 하네스 성장 방향" 판단 → 작업 스케줄 등록
|
|
17
|
+
GPT-5.5: leerness 는 실행 에이전트(Goose 등)와 경쟁하지 말고, 그들이 공통 의존하는 **운영 레이어(Memory+Policy+Handoff+Verification+Audit)**가 되면 범용 하네스로 성장 가능. **판단: 방향 타당 — leerness 는 이미 MCP 75도구/handoff/capabilities 로 상당 부분 정합**. 적용 항목을 백로그 등록:
|
|
18
|
+
- **UR-0030** 운영 레이어 정체성 포지셔닝 (실행기 경쟁 지양)
|
|
19
|
+
- **UR-0031** MCP 시맨틱 verb 정비 (get_context/start_task/record_decision/verify_done/make_handoff …)
|
|
20
|
+
- **UR-0032** `.leerness/` JSON 상태 스키마 (runs/tasks/decisions — 에이전트 간 인수인계 표준)
|
|
21
|
+
- **UR-0033** `leerness adapter <tool>` 도구별 선택 생성 (--minimal 확장)
|
|
22
|
+
- **UR-0034** 권한 등급 enforced (read-only…publish, 기본 보수적) — capabilities 확장
|
|
23
|
+
- **UR-0035** AGENTS.md(정적) vs leerness(동적) 역할 분리 포지셔닝
|
|
24
|
+
|
|
25
|
+
### 검증
|
|
26
|
+
- **selftest 27/27 PASS** · **E2E 224/224 PASS** (회귀 0) — agents list/quota 10 CLI · provider list 10.
|
|
27
|
+
|
|
28
|
+
## 1.9.276 — 2026-06-03 — 설치 부담 완화: init --dry-run / --minimal / --no-env (GPT-5.5 2차 리뷰)
|
|
29
|
+
|
|
30
|
+
**📦 GPT-5.5 2차 외부 평가(7/10)의 핵심 지적 "init이 너무 침투적 + 미리보기 부재"를 직접 해결.**
|
|
31
|
+
|
|
32
|
+
### 배경
|
|
33
|
+
GPT-5.5 가 1.9.275 를 직접 실행: 기능은 실용적이나 `init` 한 번에 50+ 파일 + `.claude`/`.cursor`/`.github`/`.env`/`README` 까지 건드려 "가볍게 설치"가 아님(#1). `init --dry-run` 부재(#2). `.env` 자동 생성 호불호(#6).
|
|
34
|
+
|
|
35
|
+
### 구현
|
|
36
|
+
1. **`init --dry-run`** — 생성/수정될 파일 목록 + 요약(생성/머지/보존 카운트) 미리보기 후 **조기 종료, 실제 변경 0**. dry 시 `_warnIfStale` 캐시 기록도 차단(부작용 0 보장). 기존 init 은 `dry:false` 하드코딩이라 미지원이던 것을 수정.
|
|
37
|
+
2. **`init --minimal`** — 코어 워크플로(handoff/verify/audit/session close)가 요구하지 않는 ~25개(에디터 통합 `.cursor`/`.github`, AX 가이드, 템플릿, 스킬, 특화 체크리스트 등) + `roadmap.html` + `.env` + `.claude` SessionStart hook 을 생략. **verify 필수 파일은 전부 보존** → minimal 설치도 verify 통과 (49→31 .harness 파일).
|
|
38
|
+
3. **`init --no-env`** — `.env`/`.env.example` 자동 생성만 생략 (토큰 자동 파일 호불호 대응).
|
|
39
|
+
4. **`MINIMAL_SKIP_KEYS` denylist** — verify required 목록(plan/progress/guideline/protected-files/design-system/anti-lazy/session-handoff/current-state/AGENTS)을 침범하지 않도록 설계.
|
|
40
|
+
5. **README/문서** — 한/영 보안 섹션에 설치 부담 완화 옵션 안내.
|
|
41
|
+
6. **selftest 26→27 + e2e 223→224** — minimal 필터(핵심 보존+비핵심 제외+verify 필수 보존) + dry-run(0파일)/minimal(verify 통과)/no-env(.env 제외) 통합 검증.
|
|
42
|
+
|
|
43
|
+
### 백로그 (GPT 2차 장기 항목)
|
|
44
|
+
- 위험 명령 별도 패키지 분리(release/pc/web), e2e 5분 내 완료 안정화, bin/harness.js 추가 모듈화(UR-0025 계속), README 첫 화면 단순화.
|
|
45
|
+
|
|
46
|
+
### 검증
|
|
47
|
+
- **selftest 27/27 PASS** · **E2E 224/224 PASS** (회귀 0). dry-run 실측 0 파일 생성 · minimal verify 통과 확인.
|
|
48
|
+
|
|
3
49
|
## 1.9.275 — 2026-06-03 — UR-0026: 릴리스 채널 (npm dist-tag 안정/실험)
|
|
4
50
|
|
|
5
51
|
**🚦 잦은 1.9.x 릴리스에 안정(latest)/실험(next) 채널 + 버전 고정 안내 — 사용자가 안정성을 제어 (GPT-5.5 리뷰).**
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
> **A CLI harness that stops AI coding agents from faking completion, duplicating work, forgetting context, and colliding.**
|
|
5
5
|
|
|
6
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []()
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
## 한눈에 보기
|
|
30
30
|
|
|
31
|
-
`leerness`는 **Claude Code · Cursor · Copilot · Codex · Antigravity · Grok** 같은 AI 코딩 에이전트와 함께 일할 때 생기는 구조적 문제를 자동으로 막아주는 CLI 도구입니다.
|
|
31
|
+
`leerness`는 **Claude Code · Cursor · Copilot · Codex · Antigravity · Grok · opencode · Qwen · Aider · Goose** 같은 AI 코딩 에이전트와 함께 일할 때 생기는 구조적 문제를 자동으로 막아주는 CLI 도구입니다.
|
|
32
32
|
|
|
33
33
|
AI는 코드를 빠르게 쓰지만, 다음 **5가지 함정**에 반복해서 빠집니다 — leerness는 이를 모두 자동으로 감지·차단·회복합니다.
|
|
34
34
|
|
|
@@ -139,7 +139,7 @@ leerness session close [path] # 세션 마감 + 다음 세션 인계
|
|
|
139
139
|
leerness drift check [path] # leerness 미사용 drift 점수
|
|
140
140
|
|
|
141
141
|
# 멀티 AI 에이전트 · 역할
|
|
142
|
-
leerness setup-agents # 활성화 (claude/codex/agy/grok/copilot/ollama)
|
|
142
|
+
leerness setup-agents # 활성화 (claude/codex/agy/grok/opencode/qwen/aider/goose/copilot/ollama)
|
|
143
143
|
leerness agents list | quota # 상태 / 한도
|
|
144
144
|
leerness agents dispatch "<task>" --role coder # 역할 기반 모델 라우팅
|
|
145
145
|
leerness agents bench "<task>" # 여러 CLI 동시 호출 + 비교
|
|
@@ -188,6 +188,14 @@ leerness capabilities # 권한 표면 + opt-out + 주의 명령 (--json
|
|
|
188
188
|
- 회사/운영 코드에서는 먼저 한 프로젝트에서 `init` 후 `git diff` 로 검토하고 커밋하세요.
|
|
189
189
|
- 자세히: [SECURITY.md](SECURITY.md)
|
|
190
190
|
|
|
191
|
+
**설치 부담 완화 옵션 (1.9.276):**
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
leerness init . --dry-run # 생성/수정될 파일 미리보기 (실제 변경 0)
|
|
195
|
+
leerness init . --minimal # 핵심 파일만 (에디터 통합/가이드/roadmap/.env 생략)
|
|
196
|
+
leerness init . --no-env # .env/.env.example 자동 생성만 생략
|
|
197
|
+
```
|
|
198
|
+
|
|
191
199
|
### 릴리스 채널 (안정 vs 실험)
|
|
192
200
|
|
|
193
201
|
leerness는 활발히 개발됩니다(잦은 1.9.x). 채널을 골라 안정성을 제어하세요:
|
|
@@ -207,7 +215,7 @@ leerness release channel # 현재 채널/정책 확인 (--json)
|
|
|
207
215
|
|
|
208
216
|
## At a glance
|
|
209
217
|
|
|
210
|
-
`leerness` is a CLI harness that automatically prevents the structural problems that arise when working with AI coding agents like **Claude Code · Cursor · Copilot · Codex · Antigravity · Grok**.
|
|
218
|
+
`leerness` is a CLI harness that automatically prevents the structural problems that arise when working with AI coding agents like **Claude Code · Cursor · Copilot · Codex · Antigravity · Grok · opencode · Qwen · Aider · Goose**.
|
|
211
219
|
|
|
212
220
|
AI writes code fast, but repeatedly falls into **5 traps** — leerness detects, blocks, and recovers from all of them.
|
|
213
221
|
|
|
@@ -318,7 +326,7 @@ leerness session close [path] # close session + hand off
|
|
|
318
326
|
leerness drift check [path] # leerness-disuse drift score
|
|
319
327
|
|
|
320
328
|
# Multi AI agents · roles
|
|
321
|
-
leerness setup-agents # enable (claude/codex/agy/grok/copilot/ollama)
|
|
329
|
+
leerness setup-agents # enable (claude/codex/agy/grok/opencode/qwen/aider/goose/copilot/ollama)
|
|
322
330
|
leerness agents list | quota # status / limits
|
|
323
331
|
leerness agents dispatch "<task>" --role coder # role-based model routing
|
|
324
332
|
leerness agents bench "<task>" # call several CLIs at once + compare
|
|
@@ -367,6 +375,14 @@ leerness capabilities # capability surface + opt-out + cautious command
|
|
|
367
375
|
- For company/production code, trial `init` in one project, review with `git diff`, then commit.
|
|
368
376
|
- Details: [SECURITY.md](SECURITY.md)
|
|
369
377
|
|
|
378
|
+
**Lighter-install options (1.9.276):**
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
leerness init . --dry-run # preview files to be created/modified (zero changes)
|
|
382
|
+
leerness init . --minimal # core files only (skips editor integrations/guides/roadmap/.env)
|
|
383
|
+
leerness init . --no-env # skip only .env/.env.example auto-creation
|
|
384
|
+
```
|
|
385
|
+
|
|
370
386
|
### Release channels (stable vs experimental)
|
|
371
387
|
|
|
372
388
|
leerness is actively developed (frequent 1.9.x). Pick a channel to control stability:
|
|
@@ -424,7 +440,7 @@ MIT — © leerness contributors
|
|
|
424
440
|
<!-- leerness:project-readme:start -->
|
|
425
441
|
## Leerness Project Harness
|
|
426
442
|
|
|
427
|
-
이 프로젝트는 Leerness v1.9.
|
|
443
|
+
이 프로젝트는 Leerness v1.9.277 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
428
444
|
|
|
429
445
|
### Core Commands
|
|
430
446
|
|
|
@@ -466,7 +482,7 @@ leerness memory restore decision <date|title>
|
|
|
466
482
|
|
|
467
483
|
### MCP server (외부 AI 통합)
|
|
468
484
|
|
|
469
|
-
Leerness v1.9.
|
|
485
|
+
Leerness v1.9.277는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **42개 도구**를 노출:
|
|
470
486
|
|
|
471
487
|
```jsonc
|
|
472
488
|
// 카테고리별
|
|
@@ -487,7 +503,7 @@ Leerness v1.9.275는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
487
503
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
488
504
|
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) 다음 라운드 예약.
|
|
489
505
|
|
|
490
|
-
현재 누적: **70 라운드 (1.9.40 → 1.9.
|
|
506
|
+
현재 누적: **70 라운드 (1.9.40 → 1.9.277)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
491
507
|
|
|
492
508
|
### 성능 가이드 (1.9.140 측정)
|
|
493
509
|
|
|
@@ -525,6 +541,6 @@ leerness release pack --close --auto-main-push
|
|
|
525
541
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
526
542
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
527
543
|
|
|
528
|
-
Last synced by Leerness v1.9.
|
|
544
|
+
Last synced by Leerness v1.9.277: 2026-06-03
|
|
529
545
|
<!-- leerness:project-readme:end -->
|
|
530
546
|
|
package/bin/harness.js
CHANGED
|
@@ -9,7 +9,7 @@ const readline = require('readline');
|
|
|
9
9
|
// 1.9.274 (UR-0025 1단계): 순수 유틸 함수 모듈 분리 (require-based, 비파괴). selftest 7종이 동작 검증.
|
|
10
10
|
const { _isSecretKey, compareVer, parseHarnessVersion, _classifyCJK, _riskLabel, _detectSystemLang, _parseSlashFromHelp } = require('../lib/pure-utils');
|
|
11
11
|
|
|
12
|
-
const VERSION = '1.9.
|
|
12
|
+
const VERSION = '1.9.277';
|
|
13
13
|
|
|
14
14
|
// 1.9.184: DEP0190 (child_process shell: true) deprecation warning 억제 (사용자 명시).
|
|
15
15
|
// leerness 는 cross-platform PATH resolution 을 위해 shell: true 를 의도적으로 사용 (claude.cmd / ollama.cmd 등 Windows .cmd 처리).
|
|
@@ -391,10 +391,23 @@ function skillLock(skills) {
|
|
|
391
391
|
return JSON.stringify(data, null, 2) + '\n';
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
-
|
|
394
|
+
// 1.9.276 (GPT-5.5 2차 리뷰): init --minimal 시 제외하는 키 — 에디터 통합/가이드/템플릿/스킬/특화 체크리스트 등
|
|
395
|
+
// "코어 워크플로(handoff/verify/audit/session close)"가 요구하지 않는 파일만 제외 (verify 필수 파일은 유지).
|
|
396
|
+
const MINIMAL_SKIP_KEYS = new Set([
|
|
397
|
+
'.cursor/rules/leerness.mdc', '.github/copilot-instructions.md',
|
|
398
|
+
'.harness/project-brief.md', '.harness/task-type-map.md', '.harness/architecture.md', '.harness/context-map.md',
|
|
399
|
+
'.harness/guardrails.md', '.harness/feature-contracts.md', '.harness/feature-graph.md',
|
|
400
|
+
'.harness/testing-strategy.md', '.harness/review-checklist.md', '.harness/release-checklist.md',
|
|
401
|
+
'.harness/session-close-policy.md', '.harness/language-policy.md', '.harness/test-evidence-policy.md',
|
|
402
|
+
'.harness/AX_PLAN_GUIDE.md', '.harness/AX_MIGRATION_GUIDE.md', '.harness/AX_NEW_PROJECT_GUIDE.md', '.harness/AX_SKILL_LIBRARY_GUIDE.md',
|
|
403
|
+
'.harness/skill-index.md',
|
|
404
|
+
'.harness/templates/end-of-session-report.md', '.harness/templates/decision.md', '.harness/templates/task-row.md',
|
|
405
|
+
'.claude/skills/leerness.md'
|
|
406
|
+
]);
|
|
407
|
+
function coreFiles(root, lang = 'ko', selectedSkills = [], opts = {}) {
|
|
395
408
|
const project = detectProjectName(root);
|
|
396
409
|
const skillRows = Object.entries(skillCatalog).map(([k, v]) => `| ${k} | ${v.displayNameKo} | ${v.capabilities.join(', ')} | ${v.lastUpdated} | ${v.verification} |`).join('\n');
|
|
397
|
-
|
|
410
|
+
const _files = {
|
|
398
411
|
'AGENTS.md': `${MARK}\n# Leerness Agent Instructions\n\n## ⭐ 매 세션 첫 행동 (1.9.39+)\n**반드시 \`.harness/session-workflow.md\`를 먼저 읽고 6단계 워크플로를 따른다**: 요청분석→계획→분배→sub-agent작업→종합검증→마감. 라운드 길이/복잡도 무관, drift 방지를 위해 모든 작업에 동일 흐름 유지.\n\n## Mandatory read order (session start)\n1. **.harness/session-workflow.md** (1.9.39+ 6단계 워크플로 — 최우선)\n2. .harness/context-routing.md\n3. .harness/session-handoff.md\n4. .harness/current-state.md\n5. .harness/plan.md\n6. .harness/progress-tracker.md\n7. .harness/guideline.md\n8. .harness/protected-files.md\n9. .harness/writeback-policy.md\n10. .harness/anti-lazy-work-policy.md\n11. **.harness/rules.md** (사용자 정의 영구 룰 — 매 세션 반드시 따름)\n\n## Required behavior\n- 작업 시작 시 \`leerness handoff .\`를 실행해 컨텍스트를 적재합니다 (handoff가 active rules를 자동 출력).\n- 작업 분류는 \`leerness route <task-type>\`로 확인합니다 (planning, feature, bugfix, refactor, research, consistency, release, migration, session-start, session-close, harness-maintenance).\n- 보호 파일/관리 섹션을 삭제하지 않습니다. 머지·아카이브·deprecated 표시를 사용합니다.\n- 의미 있는 변경 후 progress-tracker, current-state, task-log, session-handoff를 갱신합니다.\n- 완료 선언 전 \`leerness check .\` 또는 \`leerness lazy detect .\`로 자기검증합니다.\n- 변경 전 secret/encoding 가드: \`leerness scan secrets .\`, \`leerness encoding check .\`.\n- 같은 기능 중복 생성 전 design-system.md, consistency-policy.md, reuse-map.md를 확인합니다.\n- 매 세션 종료 시 \`leerness session close .\`로 9개 카테고리(완료/진행중/미완료/예정/대기/보류/차단/드랍/검증) + **활성 룰 검증 결과**를 보고합니다.\n- 업데이트는 \`leerness update --check\` (감지) → \`leerness update --yes\` (자동 마이그레이션).\n\n## 자연어 회고/통찰/브레인스토밍 (1.9.13)\n사용자가 자연어로 회고/통찰/브레인스토밍을 요청하면 즉시 leerness 명령으로 호출합니다.\n\n| 사용자 발화 (자연어) | 즉시 실행할 명령 |\n|---|---|\n| "회고해줘 / 돌아보자 / 정리해줘" | \`leerness retro\` |\n| "최근 N일 회고" | \`leerness retro --days N\` |\n| "통계 / 누적 지표 / insights" | \`leerness insights\` |\n| "X에 대해 브레인스토밍 / X 관련 자료 / X 시작 전 검토" | \`leerness brainstorm "X"\` |\n\nsession close가 매번 자동으로 한 줄 요약을 출력하고, 5세션마다 자동 깊은 회고를 실행합니다. 사용자가 명시 요청 시 즉시 호출.\n\n## 자연어 룰 처리 (1.9.8)\n사용자가 자연어로 영구 룰을 요청하면 즉시 leerness rule 명령으로 등록합니다.\n\n| 사용자 발화 (자연어) | 즉시 실행할 명령 |\n|---|---|\n| "매 업데이트마다 버전 bump해줘" | \`leerness rule add "버전을 patch로 bump" --trigger every-update\` |\n| "매 커밋마다 패치노트 추가해줘" | \`leerness rule add "패치노트 추가" --trigger every-commit\` |\n| "세션 종료마다 배포해줘" | \`leerness rule add "배포 (release publish)" --trigger session-close\` |\n| "X 룰 중지/그만/끄기" | \`leerness rule pause <ID>\` (해당 룰 ID는 list로 확인) |\n| "X 룰 제거/삭제" | \`leerness rule remove <ID>\` |\n| "모든 룰 중지" | \`leerness rule stop\` |\n| "룰 다시 켜줘" | \`leerness rule resume-all\` 또는 \`leerness rule resume <ID>\` |\n\n룰을 등록한 후 사용자에게 등록 결과(ID + trigger + 설명)를 보고하고, 그 이후 매 세션마다 자동 적용합니다. 사용자가 "중지" 또는 "제거"를 명시적으로 말하기 전까지는 룰을 비활성화하지 않습니다.\n\n## 룰 자동 적용 (1.9.8)\nleerness가 자동 검증 가능한 trigger:\n- **every-update / version bump 키워드 룰**: package.json의 version이 갱신됐는지 검사 (handoff/session close가 baseline 캐시와 비교).\n- **CHANGELOG / 패치노트 키워드 룰**: CHANGELOG.md의 mtime이 갱신됐는지 검사.\n- **test / 테스트 / verify 키워드 룰**: review-evidence.md에 오늘 verify-code 흔적이 있는지 검사.\n- **배포 / publish / push 키워드 룰**: 자동 검증 불가 → 사용자에게 release publish 명령을 안내.\n\n자동 검증 가능한 룰의 실행은 \`leerness release bump\`, \`leerness release note "..."\`, \`leerness release publish\`를 사용해 자동화합니다.\n`,
|
|
399
412
|
'CLAUDE.md': `${MARK}\n# Claude Code Instructions\n\nFollow AGENTS.md. Always run \`leerness handoff .\` at the start and \`leerness session close .\` before ending a session.\n\n**⭐ 매 세션 첫 행동 (1.9.39+)**: \`.harness/session-workflow.md\`의 6단계 워크플로(요청분석→계획→분배→sub-agent→종합검증→마감)를 따라야 함. drift critical 시 \`leerness drift check --auto-fix\`로 자동 회복.\n\nProtected files must not be deleted. Read .harness/anti-lazy-work-policy.md before claiming completion.\n\n## 자연어 영구 룰 (1.9.8)\n사용자가 "매 X마다 Y를 해줘" 같은 자연어 룰을 말하면 즉시 \`leerness rule add "Y" --trigger every-X\`로 등록하세요. 등록된 룰은 매 세션 \`handoff\`가 자동 출력하고, \`session close\`가 자동 검증해 보고합니다. 사용자가 "중지" / "그만" / "끄기"를 명시할 때만 \`rule pause/remove\`를 호출합니다.\n\n자세한 매핑은 AGENTS.md의 "자연어 룰 처리" 표를 참고하세요.\n`,
|
|
400
413
|
'.cursor/rules/leerness.mdc': `${MARK}\n---\nalwaysApply: true\n---\nFollow AGENTS.md and .harness/context-routing.md.\nRun: \`leerness handoff .\` at session start.\nRun: \`leerness session close .\` at session end.\nPreserve Leerness protected files.\n`,
|
|
@@ -611,6 +624,9 @@ leerness memory restore <surface> <target> # archive → active 복귀 (DELETE
|
|
|
611
624
|
'.claude/commands/update.md': `# /update\n\nleerness 자동 업데이트를 실행합니다 (감지 → 마이그레이션 → 검증).\n\n\`\`\`\n!leerness update --yes\n\`\`\`\n`,
|
|
612
625
|
'.claude/skills/leerness.md': `---\nname: leerness\ndescription: Leerness harness commands - handoff, audit, scan secrets, encoding check, lazy detect, session close, update. Use when the user asks to load project context, verify work quality, scan secrets, check encoding, or end a session.\n---\n\n# leerness skill\n\n## When to use\n- 사용자가 프로젝트 컨텍스트를 로드해달라고 할 때\n- 완료 선언 전 자기 검증을 요청할 때\n- 세션을 종료하거나 인수인계를 요청할 때\n- 시크릿/한글 인코딩 점검을 요청할 때\n- 새 leerness 버전 적용을 요청할 때\n\n## Commands\n\n\`\`\`bash\nleerness handoff . # 컨텍스트 로드\nleerness check . # pre-action 체크\nleerness audit . # 일관성/계획 정렬 감사\nleerness scan secrets . # 시크릿 패턴 스캔\nleerness encoding check . # UTF-8/BOM/CRLF\nleerness lazy detect . # 게으름 평가\nleerness memory search "key" # 결정/이력 검색\nleerness session close . # 종료 보고 + handoff 자동 생성\nleerness update --yes # 자동 업데이트\n\`\`\`\n`,
|
|
613
626
|
};
|
|
627
|
+
// 1.9.276: minimal 모드 — 코어가 요구하지 않는 파일 제외 (verify 필수 파일은 유지).
|
|
628
|
+
if (opts.minimal) { for (const k of MINIMAL_SKIP_KEYS) delete _files[k]; }
|
|
629
|
+
return _files;
|
|
614
630
|
}
|
|
615
631
|
|
|
616
632
|
function copyRecursiveSafe(src, dst) {
|
|
@@ -850,19 +866,24 @@ async function resolveInstallOptions(root, opts = {}) {
|
|
|
850
866
|
{ label: 'Codex (OpenAI codex CLI)', description: 'OpenAI 코드 모델', id: 'codex' },
|
|
851
867
|
{ label: 'Antigravity (agy CLI)', description: 'Google Antigravity 멀티모달 에이전트 (1.9.248~)', id: 'agy' },
|
|
852
868
|
{ label: 'Grok (xAI grok-cli)', description: 'xAI Grok CLI — grok-beta 등 (1.9.268~)', id: 'grok' },
|
|
869
|
+
{ label: 'opencode', description: '오픈소스 터미널 AI 코딩 에이전트 (1.9.277~)', id: 'opencode' },
|
|
870
|
+
{ label: 'Qwen Code (qwen)', description: 'Alibaba Qwen Code CLI (1.9.277~)', id: 'qwen' },
|
|
871
|
+
{ label: 'Aider', description: 'git-aware 페어 프로그래밍 CLI (1.9.277~)', id: 'aider' },
|
|
872
|
+
{ label: 'Goose (Block)', description: '오픈소스 범용 로컬 AI 에이전트 — MCP 확장 (1.9.277~)', id: 'goose' },
|
|
853
873
|
{ label: 'Copilot (gh extension)', description: 'GitHub Copilot CLI', id: 'copilot' },
|
|
854
874
|
{ label: 'Ollama (로컬 LLM — llama3/qwen 등)', description: 'http://localhost:11434 — 무료/오프라인', id: 'ollama' }
|
|
855
875
|
], { defaults: [] });
|
|
856
876
|
agentsOptIn = picked.length ? picked.map(p => p.id) : 'none';
|
|
857
877
|
} else {
|
|
858
878
|
log('\nCLI 에이전트 활성화 (복수 선택 — 콤마로 구분, opt-in):');
|
|
859
|
-
log(' 1) claude 2) codex 3) agy
|
|
879
|
+
log(' 1) claude 2) codex 3) agy 4) grok 5) opencode 6) qwen 7) aider 8) goose 9) copilot 10) ollama (예: 1,8 또는 all 또는 none)');
|
|
860
880
|
const a = (await ask('선택 [none]: ')).trim().toLowerCase();
|
|
861
|
-
|
|
881
|
+
const ALL_IDS = ['claude', 'codex', 'agy', 'grok', 'opencode', 'qwen', 'aider', 'goose', 'copilot', 'ollama'];
|
|
882
|
+
if (a === 'all') agentsOptIn = ALL_IDS.slice();
|
|
862
883
|
else if (!a || a === 'none' || a === '0') agentsOptIn = 'none';
|
|
863
884
|
else {
|
|
864
|
-
const map = { '1': 'claude', '2': 'codex', '3': 'agy', '4': 'grok', '5': '
|
|
865
|
-
const picks = a.split(/[,\s]+/).map(t => map[t] || (
|
|
885
|
+
const map = { '1': 'claude', '2': 'codex', '3': 'agy', '4': 'grok', '5': 'opencode', '6': 'qwen', '7': 'aider', '8': 'goose', '9': 'copilot', '10': 'ollama' };
|
|
886
|
+
const picks = a.split(/[,\s]+/).map(t => map[t] || (ALL_IDS.includes(t) ? t : null)).filter(Boolean);
|
|
866
887
|
agentsOptIn = picks.length ? picks : 'none';
|
|
867
888
|
}
|
|
868
889
|
}
|
|
@@ -917,7 +938,8 @@ async function install(root, opts = {}) {
|
|
|
917
938
|
// 1.9.32: init 시 ASCII 배너 + 빠른 시작 가이드 (migrate는 quiet)
|
|
918
939
|
if (!opts.migration && !has('--no-banner')) _banner({ quickStart: !opts.dry });
|
|
919
940
|
// 1.9.33: npx 캐시로 옛 버전이 실행될 때 경고 (migrate/--no-stale-check 시 스킵)
|
|
920
|
-
|
|
941
|
+
// 1.9.276: dry-run 시 스킵 — 캐시 파일(.harness/cache) 생성 방지 (dry = 부작용 0 보장)
|
|
942
|
+
if (!opts.migration && !has('--no-stale-check') && !opts.nonInteractive && !opts.dry) {
|
|
921
943
|
try { await _warnIfStale(root); } catch {}
|
|
922
944
|
}
|
|
923
945
|
const resolved = await resolveInstallOptions(root, opts);
|
|
@@ -936,7 +958,7 @@ async function install(root, opts = {}) {
|
|
|
936
958
|
if (resolved.permissionMode) log(`Agent 권한 모드: ${resolved.permissionMode} (1.9.174 — REPL에서 \`:permissions extended|full\` 로 즉시 변경 가능)`);
|
|
937
959
|
// 1.9.10: 스킬 카탈로그 출처 안내
|
|
938
960
|
// 1.9.184 (사용자 명시): leerness-skillpack 미사용 정책 — 안내 메시지 제거. builtin catalog 만 사용.
|
|
939
|
-
const files = coreFiles(root, lang, skills);
|
|
961
|
+
const files = coreFiles(root, lang, skills, { minimal: !!opts.minimal });
|
|
940
962
|
const backup = createBackup(root, opts.force ? 'force' : (opts.migration ? 'migration' : 'init'), files, opts.dry);
|
|
941
963
|
if (opts.dry) {
|
|
942
964
|
log(`Backup target: ${backup.archiveDir}`);
|
|
@@ -986,6 +1008,19 @@ async function install(root, opts = {}) {
|
|
|
986
1008
|
process.stdout.write('\r\x1b[K'); // clear progress line
|
|
987
1009
|
log(` \x1b[32m✓\x1b[0m leerness 파일 설치 완료 (${totalFiles}개)`);
|
|
988
1010
|
}
|
|
1011
|
+
// 1.9.276 (GPT-5.5 2차 리뷰): dry-run 요약 + 조기 종료 (파일 미생성). 실제 적용 안내.
|
|
1012
|
+
if (opts.dry) {
|
|
1013
|
+
const created = actions.filter(a => a.action === 'create').length;
|
|
1014
|
+
const merged = actions.filter(a => a.action === 'merge/update').length;
|
|
1015
|
+
const preserved = actions.filter(a => a.action === 'preserve').length;
|
|
1016
|
+
log('');
|
|
1017
|
+
log(`# [dry-run] 요약${opts.minimal ? ' (--minimal)' : ''} — 실제 파일은 생성/수정되지 않았습니다`);
|
|
1018
|
+
log(` 생성 ${created} · 머지/갱신 ${merged} · 보존 ${preserved} · 총 ${totalFiles}`);
|
|
1019
|
+
if (!opts.noEnv && !opts.minimal) log(` + .env/.env.example, .gitignore, roadmap.html, .claude hook (실제 실행 시)`);
|
|
1020
|
+
log(` → 실제 적용: leerness init ${rel(process.cwd(), root) || '.'}${opts.minimal ? ' --minimal' : ''}${opts.noEnv ? ' --no-env' : ''} --yes`);
|
|
1021
|
+
_cleanupSigint();
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
989
1024
|
if (!opts.dry) {
|
|
990
1025
|
mergeLinesFile(path.join(root, '.gitignore'), [
|
|
991
1026
|
// 1.9.153: .env 직접 생성 + 사용자 글로벌 룰 SECRET_PATTERNS 6종 일괄 ignore (audit 통합)
|
|
@@ -1021,11 +1056,16 @@ async function install(root, opts = {}) {
|
|
|
1021
1056
|
'LEERNESS_NPM_TOKEN=',
|
|
1022
1057
|
'LEERNESS_GITHUB_TOKEN=',
|
|
1023
1058
|
];
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1059
|
+
// 1.9.276 (GPT-5.5 2차 리뷰): --no-env 또는 --minimal 시 .env/.env.example 자동 생성 건너뜀 (호불호 + 침투성 완화).
|
|
1060
|
+
if (opts.noEnv || opts.minimal) {
|
|
1061
|
+
log(` ⏭ .env/.env.example 생성 건너뜀 (${opts.minimal ? '--minimal' : '--no-env'}) — 토큰 필요 시 .env.example 참고해 수동 생성`);
|
|
1062
|
+
} else {
|
|
1063
|
+
mergeLinesFile(path.join(root, '.env.example'), envLines);
|
|
1064
|
+
try {
|
|
1065
|
+
mergeEnvFile(path.join(root, '.env'), envLines);
|
|
1066
|
+
} catch (e) {
|
|
1067
|
+
warn(`.env 생성/마이그레이션 실패 (계속 진행): ${e.message}`);
|
|
1068
|
+
}
|
|
1029
1069
|
}
|
|
1030
1070
|
// 1.9.187: 비시크릿 LEERNESS_* 설정 → .harness/leerness-config.json (AI 가시성)
|
|
1031
1071
|
try {
|
|
@@ -1036,6 +1076,10 @@ async function install(root, opts = {}) {
|
|
|
1036
1076
|
LEERNESS_ENABLE_CODEX: enable('codex') ? '1' : '0',
|
|
1037
1077
|
LEERNESS_ENABLE_AGY: enable('agy') ? '1' : '0',
|
|
1038
1078
|
LEERNESS_ENABLE_GROK: enable('grok') ? '1' : '0',
|
|
1079
|
+
LEERNESS_ENABLE_OPENCODE: enable('opencode') ? '1' : '0',
|
|
1080
|
+
LEERNESS_ENABLE_QWEN: enable('qwen') ? '1' : '0',
|
|
1081
|
+
LEERNESS_ENABLE_AIDER: enable('aider') ? '1' : '0',
|
|
1082
|
+
LEERNESS_ENABLE_GOOSE: enable('goose') ? '1' : '0',
|
|
1039
1083
|
LEERNESS_ENABLE_COPILOT: enable('copilot') ? '1' : '0',
|
|
1040
1084
|
LEERNESS_ENABLE_OLLAMA: enable('ollama') ? '1' : '0',
|
|
1041
1085
|
LEERNESS_SKILL_DISCOVER_URL: '',
|
|
@@ -1116,11 +1160,14 @@ async function install(root, opts = {}) {
|
|
|
1116
1160
|
}
|
|
1117
1161
|
}
|
|
1118
1162
|
} catch {}
|
|
1119
|
-
|
|
1163
|
+
// 1.9.276: --minimal 시 .claude SessionStart hook 도 건너뜀 (침투성 완화)
|
|
1164
|
+
if (!has('--no-auto-update') && !opts.minimal) {
|
|
1120
1165
|
try { autoUpdateInstall(root); } catch (e) { warn('auto-update hook install skipped: ' + (e && e.message)); }
|
|
1166
|
+
} else if (opts.minimal) {
|
|
1167
|
+
log(` ⏭ .claude SessionStart hook 건너뜀 (--minimal) — 필요 시 leerness auto-update install .`);
|
|
1121
1168
|
}
|
|
1122
|
-
// 1.9.12: install 직후 첫 roadmap.html 자동 생성
|
|
1123
|
-
if (!has('--no-auto-roadmap')) {
|
|
1169
|
+
// 1.9.12: install 직후 첫 roadmap.html 자동 생성 (1.9.276: --minimal 시 건너뜀 — 침투성/용량 완화)
|
|
1170
|
+
if (!has('--no-auto-roadmap') && !opts.minimal) {
|
|
1124
1171
|
try { _autoRoadmap(root, 'install'); } catch (e) { warn('auto-roadmap 실패: ' + (e && e.message)); }
|
|
1125
1172
|
}
|
|
1126
1173
|
// 1.9.148: 1.9.32 중복 prompt 제거 (사용자 명시 — CLI 에이전트 prompt 중복).
|
|
@@ -2906,13 +2953,14 @@ function _selfTestCases() {
|
|
|
2906
2953
|
{ name: '_agentSlashHint: grok 슬래시 요약 + copilot 하위명령 라벨', run: () => { const g = _agentSlashHint('.', 'grok'); const c = _agentSlashHint('.', 'copilot'); return !!g && g.count > 0 && /Grok/.test(g.summary) && !!c && c.invoke === 'subcommand' && /하위명령/.test(c.summary); } },
|
|
2907
2954
|
{ name: '_parseSlashFromHelp: 슬래시 검출 + 플래그 제외 (1.9.267)', run: () => { const r = _parseSlashFromHelp(' /help show help\n /model switch model\n --version print version\n', 'slash'); return r.length === 2 && r[0].cmd === '/help' && /show help/.test(r[0].desc) && !r.some(c => /version/.test(c.cmd)); } },
|
|
2908
2955
|
{ name: '_parseSlashFromHelp: subcommand 모드 들여쓰기 파싱 (1.9.267)', run: () => { const r = _parseSlashFromHelp('Commands:\n suggest 제안\n explain 설명\n', 'subcommand'); return r.length >= 2 && r.some(c => c.cmd === 'suggest') && r.some(c => c.cmd === 'explain'); } },
|
|
2909
|
-
{ name: 'EXTERNAL_AGENTS: grok
|
|
2956
|
+
{ name: 'EXTERNAL_AGENTS: 10종 (grok + opencode/qwen/aider/goose 포함) (1.9.277)', run: () => { const ids = EXTERNAL_AGENTS.map(a => a.id); return EXTERNAL_AGENTS.length === 10 && ['grok','opencode','qwen','aider','goose'].every(id => ids.includes(id)) && EXTERNAL_AGENTS.find(a => a.id === 'goose').envFlag === 'LEERNESS_ENABLE_GOOSE'; } },
|
|
2910
2957
|
{ name: '_detectSystemLang: POSIX LANG ko_KR/en_US 판별 (1.9.269)', run: () => _detectSystemLang({ LANG: 'ko_KR.UTF-8' }) === 'ko' && _detectSystemLang({ LANG: 'en_US.UTF-8' }) === 'en' },
|
|
2911
2958
|
{ name: '_detectSystemLang: LC_ALL 우선 + LANGUAGE 폴백 (1.9.269)', run: () => _detectSystemLang({ LC_ALL: 'ko_KR.UTF-8', LANG: 'en_US.UTF-8' }) === 'ko' && _detectSystemLang({ LANGUAGE: 'en_GB' }) === 'en' },
|
|
2912
2959
|
{ name: 'ROLE_CATALOG + _normalizeRole: 7종 + 한국어 별칭 (1.9.270)', run: () => { const keys = Object.keys(ROLE_CATALOG); return keys.length === 7 && keys.includes('coder') && keys.includes('reviewer') && _normalizeRole('코딩') === 'coder' && _normalizeRole('검수자') === 'reviewer' && _normalizeRole('지휘관') === 'commander'; } },
|
|
2913
2960
|
{ name: '_pickModel: top/code/fast 등급 선택 (1.9.270)', run: () => { return _pickModel('codex', 'code') === 'gpt-5-codex' && _pickModel('claude', 'top') === 'claude-opus-4-7' && /haiku/.test(_pickModel('claude', 'fast')); } },
|
|
2914
2961
|
{ name: 'CAPABILITY_SURFACE: 6 영역 + risk/optOut + 주의명령 (1.9.272)', run: () => { const ks = Object.keys(CAPABILITY_SURFACE); return ks.length === 6 && ks.includes('automationBridges') && Object.values(CAPABILITY_SURFACE).every(v => ['low','medium','high'].includes(v.risk) && v.optOut && v.desc) && POWERFUL_COMMANDS.length >= 5; } },
|
|
2915
2962
|
{ name: '_resolveNpmTag: latest 기본 + next 허용 + 잘못된 형식 폴백 (1.9.275)', run: () => _resolveNpmTag(null, {}) === 'latest' && _resolveNpmTag('next', {}) === 'next' && _resolveNpmTag(null, { LEERNESS_NPM_TAG: 'next' }) === 'next' && _resolveNpmTag('bad tag!', {}) === 'latest' && _resolveNpmTag('Beta', {}) === 'beta' },
|
|
2963
|
+
{ name: 'coreFiles --minimal: 핵심 유지 + 비핵심 제외 + verify 필수 보존 (1.9.276)', run: () => { const full = coreFiles('.', 'ko', []); const min = coreFiles('.', 'ko', [], { minimal: true }); const keep = ['.harness/plan.md','.harness/progress-tracker.md','.harness/session-handoff.md','AGENTS.md','CLAUDE.md','.harness/consistency-policy.md','.harness/reuse-map.md','.harness/encoding-policy.md','.harness/secret-policy.md']; const drop = ['.cursor/rules/leerness.mdc','.harness/skill-index.md','.harness/architecture.md']; const verifyReq = ['.harness/design-system.md','.harness/protected-files.md','.harness/current-state.md']; if (!verifyReq.every(k => min[k])) return false; return Object.keys(min).length < Object.keys(full).length && keep.every(k => min[k]) && drop.every(k => !min[k]); } },
|
|
2916
2964
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
2917
2965
|
];
|
|
2918
2966
|
}
|
|
@@ -9425,6 +9473,10 @@ const _LEERNESS_NONSECRET_KEYS = new Set([
|
|
|
9425
9473
|
'LEERNESS_ENABLE_CODEX',
|
|
9426
9474
|
'LEERNESS_ENABLE_AGY',
|
|
9427
9475
|
'LEERNESS_ENABLE_GROK',
|
|
9476
|
+
'LEERNESS_ENABLE_OPENCODE',
|
|
9477
|
+
'LEERNESS_ENABLE_QWEN',
|
|
9478
|
+
'LEERNESS_ENABLE_AIDER',
|
|
9479
|
+
'LEERNESS_ENABLE_GOOSE',
|
|
9428
9480
|
'LEERNESS_ENABLE_COPILOT',
|
|
9429
9481
|
'LEERNESS_ENABLE_OLLAMA',
|
|
9430
9482
|
'LEERNESS_SKILL_DISCOVER_URL', // 공개 URL
|
|
@@ -10083,6 +10135,15 @@ const EXTERNAL_AGENTS = [
|
|
|
10083
10135
|
// → provider cycle / setup-agents / dispatch / slash-commands --refresh probe 가 grok 도 자동 처리.
|
|
10084
10136
|
{ id: 'grok', bin: 'grok', envFlag: 'LEERNESS_ENABLE_GROK', versionArgs: ['--version'], desc: 'xAI Grok CLI (커뮤니티 grok-cli, /model grok-beta 등)',
|
|
10085
10137
|
installCmd: 'npm i -g @vibe-kit/grok-cli', installHint: 'https://github.com/superagent-ai/grok-cli (xAI API 키 필요 — /login 또는 GROK_API_KEY)' },
|
|
10138
|
+
// 1.9.277 (사용자 명시): 신규 CLI 에이전트 4종 — opencode/qwen/aider/goose. 활성 시 sub-agent dispatch/roles 라우팅 대상.
|
|
10139
|
+
{ id: 'opencode', bin: 'opencode', envFlag: 'LEERNESS_ENABLE_OPENCODE', versionArgs: ['--version'], desc: 'opencode — 오픈소스 터미널 AI 코딩 에이전트 (provider-agnostic)',
|
|
10140
|
+
installCmd: 'npm i -g opencode-ai', installHint: 'https://opencode.ai (또는 curl -fsSL https://opencode.ai/install | bash)' },
|
|
10141
|
+
{ id: 'qwen', bin: 'qwen', envFlag: 'LEERNESS_ENABLE_QWEN', versionArgs: ['--version'], desc: 'Qwen Code CLI (Alibaba qwen3-coder 등)',
|
|
10142
|
+
installCmd: 'npm i -g @qwen-code/qwen-code', installHint: 'https://github.com/QwenLM/qwen-code (DASHSCOPE/OpenAI 호환 키)' },
|
|
10143
|
+
{ id: 'aider', bin: 'aider', envFlag: 'LEERNESS_ENABLE_AIDER', versionArgs: ['--version'], desc: 'Aider — git-aware 페어 프로그래밍 CLI (--model 임의 지정)',
|
|
10144
|
+
installCmd: 'python -m pip install aider-install && aider-install', installHint: 'https://aider.chat (pip install aider-chat · API 키 env)' },
|
|
10145
|
+
{ id: 'goose', bin: 'goose', envFlag: 'LEERNESS_ENABLE_GOOSE', versionArgs: ['--version'], desc: 'Goose — Block 오픈소스 범용 로컬 AI 에이전트 (MCP 확장)',
|
|
10146
|
+
installCmd: 'curl -fsSL https://github.com/block/goose/releases/latest/download/download_cli.sh | bash', installHint: 'https://block.github.io/goose (goose configure 로 provider/model 설정)' },
|
|
10086
10147
|
{ id: 'copilot', bin: 'gh', envFlag: 'LEERNESS_ENABLE_COPILOT', versionArgs: ['copilot', '--version'], desc: 'GitHub Copilot CLI (gh copilot)',
|
|
10087
10148
|
installCmd: 'gh extension install github/gh-copilot', installHint: 'https://github.com/github/gh-copilot (gh CLI 선행 설치 필요)' },
|
|
10088
10149
|
// 1.9.146: Ollama 추가 (사용자 명시 요청 #3) — 로컬 LLM, HTTP API 11434
|
|
@@ -10161,6 +10222,40 @@ const AGENT_SLASH_COMMANDS = {
|
|
|
10161
10222
|
{ cmd: 'explain', desc: '명령 설명 (gh copilot explain "...")' },
|
|
10162
10223
|
{ cmd: 'config', desc: 'gh copilot 설정' }
|
|
10163
10224
|
]
|
|
10225
|
+
},
|
|
10226
|
+
// 1.9.277: 신규 4종 슬래시/세션 명령 (best-effort 큐레이션 — 배포판마다 차이 가능, 사용자 override 권장)
|
|
10227
|
+
opencode: {
|
|
10228
|
+
label: 'opencode', asOf: '1.9.277', invoke: 'slash',
|
|
10229
|
+
note: 'TUI 세션 슬래시 — 배포판마다 차이 가능.',
|
|
10230
|
+
commands: [
|
|
10231
|
+
{ cmd: '/help', desc: '명령 목록' }, { cmd: '/new', desc: '새 세션' }, { cmd: '/models', desc: '모델 선택' },
|
|
10232
|
+
{ cmd: '/init', desc: '프로젝트 컨텍스트' }, { cmd: '/undo', desc: '되돌리기' }, { cmd: '/share', desc: '세션 공유' }, { cmd: '/exit', desc: '종료' }
|
|
10233
|
+
]
|
|
10234
|
+
},
|
|
10235
|
+
qwen: {
|
|
10236
|
+
label: 'Qwen Code CLI', asOf: '1.9.277', invoke: 'slash',
|
|
10237
|
+
note: 'Gemini CLI 계열 슬래시(qwen-code) 기준 큐레이션.',
|
|
10238
|
+
commands: [
|
|
10239
|
+
{ cmd: '/help', desc: '명령 목록' }, { cmd: '/clear', desc: '컨텍스트 초기화' }, { cmd: '/memory', desc: '메모리 관리' },
|
|
10240
|
+
{ cmd: '/tools', desc: '도구 목록' }, { cmd: '/model', desc: '모델 전환' }, { cmd: '/quit', desc: '종료' }
|
|
10241
|
+
]
|
|
10242
|
+
},
|
|
10243
|
+
aider: {
|
|
10244
|
+
label: 'Aider', asOf: '1.9.277', invoke: 'slash',
|
|
10245
|
+
note: 'aider 세션 내 슬래시 명령 (in-chat commands).',
|
|
10246
|
+
commands: [
|
|
10247
|
+
{ cmd: '/add', desc: '파일 컨텍스트 추가' }, { cmd: '/drop', desc: '파일 제거' }, { cmd: '/ask', desc: '코드 수정 없이 질문' },
|
|
10248
|
+
{ cmd: '/run', desc: '셸 명령 실행' }, { cmd: '/test', desc: '테스트 실행' }, { cmd: '/diff', desc: '변경 diff' },
|
|
10249
|
+
{ cmd: '/commit', desc: '커밋' }, { cmd: '/undo', desc: '마지막 커밋 취소' }, { cmd: '/help', desc: '도움말' }
|
|
10250
|
+
]
|
|
10251
|
+
},
|
|
10252
|
+
goose: {
|
|
10253
|
+
label: 'Goose (Block)', asOf: '1.9.277', invoke: 'slash',
|
|
10254
|
+
note: 'goose 세션 내 명령 (배포판마다 차이 가능).',
|
|
10255
|
+
commands: [
|
|
10256
|
+
{ cmd: '/?', desc: '도움말' }, { cmd: '/mode', desc: '모드 전환(auto/approve/chat)' }, { cmd: '/extension', desc: 'MCP 확장 관리' },
|
|
10257
|
+
{ cmd: '/builtin', desc: '빌트인 확장' }, { cmd: '/exit', desc: '종료' }
|
|
10258
|
+
]
|
|
10164
10259
|
}
|
|
10165
10260
|
};
|
|
10166
10261
|
function _agentSlashFile(root) { return path.join(absRoot(root), '.harness', 'agent-slash-commands.json'); }
|
|
@@ -10372,7 +10467,7 @@ function slashCommandsCmd(root, agentArg, opts = {}) {
|
|
|
10372
10467
|
}
|
|
10373
10468
|
|
|
10374
10469
|
// 1.9.157: Provider Registry — 사용자 정의 provider 동적 추가 (.harness/providers.json)
|
|
10375
|
-
// 빌트인
|
|
10470
|
+
// 빌트인 10종 (EXTERNAL_AGENTS: claude/codex/agy/grok/opencode/qwen/aider/goose/copilot/ollama) + 사용자 정의를 merge. OpenRouter / Bedrock 등 새 CLI 즉시 흡수 가능.
|
|
10376
10471
|
// 파일 형식: { "schemaVersion": 1, "providers": [{ id, bin, envFlag, versionArgs, desc, installHint }] }
|
|
10377
10472
|
function _providersFile(root) { return path.join(absRoot(root), '.harness', 'providers.json'); }
|
|
10378
10473
|
function _readUserProviders(root) {
|
|
@@ -11252,6 +11347,11 @@ function _dispatchCommand(agentId, task, writeMode, model) {
|
|
|
11252
11347
|
if (agentId === 'codex') return `codex ${writeMode ? 'exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox' : 'exec --skip-git-repo-check'}${m ? ` -m ${m}` : ''} "${q}"`;
|
|
11253
11348
|
if (agentId === 'agy') return `agy ${writeMode ? '-p --yolo' : '-p'}${m ? ` --model ${m}` : ''} "${q}"`;
|
|
11254
11349
|
if (agentId === 'grok') return `grok${writeMode ? ' --yolo' : ''}${m ? ` --model ${m}` : ''} "${q}"`;
|
|
11350
|
+
// 1.9.277: 신규 provider 4종 — best-effort 비대화형 호출 (실제 실행은 사용자/메인)
|
|
11351
|
+
if (agentId === 'opencode') return `opencode run${m ? ` --model ${m}` : ''} "${q}"`;
|
|
11352
|
+
if (agentId === 'qwen') return `qwen -p "${q}"${m ? ` # --model ${m} (env/설정)` : ''}`;
|
|
11353
|
+
if (agentId === 'aider') return `aider --message "${q}"${writeMode ? ' --yes' : ' --no-auto-commits'}${m ? ` --model ${m}` : ''}`;
|
|
11354
|
+
if (agentId === 'goose') return `goose run -t "${q}"${m && m !== 'default' ? ` # provider/model: goose configure` : ''}`;
|
|
11255
11355
|
if (agentId === 'copilot') return `gh copilot suggest "${q}"`;
|
|
11256
11356
|
if (agentId === 'ollama') return `# ollama — leerness agent "${q}" --provider ollama${m ? ` --model ${m}` : ''} 로 직접 호출 (REPL: leerness agent)`;
|
|
11257
11357
|
return `# ${agentId}: 명령 빌더 미정의`;
|
|
@@ -17639,6 +17739,29 @@ const _PROVIDER_MODEL_CATALOG = {
|
|
|
17639
17739
|
{ id: 'grok-2', note: 'xAI Grok 2' },
|
|
17640
17740
|
{ id: 'grok-2-mini', note: '빠른 응답' }
|
|
17641
17741
|
],
|
|
17742
|
+
// 1.9.277: 신규 provider 4종 모델 (provider-agnostic 인 도구는 default + 설정 안내)
|
|
17743
|
+
opencode: [
|
|
17744
|
+
{ id: 'default', note: 'opencode 설정 모델 (opencode auth/models 로 provider별 지정)' },
|
|
17745
|
+
{ id: 'anthropic/claude-sonnet', note: 'Anthropic 경유' },
|
|
17746
|
+
{ id: 'openai/gpt-5', note: 'OpenAI 경유' }
|
|
17747
|
+
],
|
|
17748
|
+
qwen: [
|
|
17749
|
+
{ id: 'qwen3-coder-plus', note: '코드 특화 최신' },
|
|
17750
|
+
{ id: 'qwen-max', note: '최고 성능' },
|
|
17751
|
+
{ id: 'qwen-plus', note: '균형' },
|
|
17752
|
+
{ id: 'qwen-turbo', note: '빠름' }
|
|
17753
|
+
],
|
|
17754
|
+
aider: [
|
|
17755
|
+
{ id: 'sonnet', note: 'Anthropic Sonnet (aider --model)' },
|
|
17756
|
+
{ id: 'gpt-5', note: 'OpenAI' },
|
|
17757
|
+
{ id: 'deepseek', note: 'DeepSeek (가성비)' },
|
|
17758
|
+
{ id: 'o1-mini', note: '빠른 reasoning' }
|
|
17759
|
+
],
|
|
17760
|
+
goose: [
|
|
17761
|
+
{ id: 'default', note: 'goose configure 로 설정한 provider/model' },
|
|
17762
|
+
{ id: 'claude-sonnet', note: 'Anthropic 경유' },
|
|
17763
|
+
{ id: 'gpt-5', note: 'OpenAI 경유' }
|
|
17764
|
+
],
|
|
17642
17765
|
copilot: [
|
|
17643
17766
|
{ id: 'default', note: 'gh copilot 기본 (모델 선택 불가)' }
|
|
17644
17767
|
],
|
|
@@ -17651,7 +17774,7 @@ const _PROVIDER_MODEL_CATALOG = {
|
|
|
17651
17774
|
};
|
|
17652
17775
|
|
|
17653
17776
|
// 1.9.170: provider cycle 순서 (Tab) — 빌트인 6종(1.9.268 grok 추가). user provider는 동적으로 뒤에 추가.
|
|
17654
|
-
const _PROVIDER_CYCLE_ORDER = ['ollama', 'claude', 'codex', 'agy', 'grok', 'copilot'];
|
|
17777
|
+
const _PROVIDER_CYCLE_ORDER = ['ollama', 'claude', 'codex', 'agy', 'grok', 'opencode', 'qwen', 'aider', 'goose', 'copilot'];
|
|
17655
17778
|
|
|
17656
17779
|
// 1.9.148: planner/reviewer/actor 역할 시스템 프롬프트 (Gemini 권고 — 자기-승인 편향 방지)
|
|
17657
17780
|
const _AGENT_ROLE_PROMPTS = {
|
|
@@ -20649,7 +20772,8 @@ async function main() {
|
|
|
20649
20772
|
}
|
|
20650
20773
|
} catch {}
|
|
20651
20774
|
}
|
|
20652
|
-
|
|
20775
|
+
// 1.9.276 (GPT-5.5 2차 리뷰): init --dry-run(미리보기) / --minimal(핵심 파일만) / --no-env(.env 생략)
|
|
20776
|
+
if (cmd === 'init') return await install(args[1] || process.cwd(), { force:has('--force'), dry:has('--dry-run'), migration:false, minimal:has('--minimal'), noEnv:has('--no-env') });
|
|
20653
20777
|
// 1.9.64: install <skill-id-or-url> 별칭 (= skill install). 자주 쓰는 명령 단축형.
|
|
20654
20778
|
// 단, init이 leerness install . 같은 형태로도 동작하던 옛 호환은 유지 — args[1]이 디렉토리면 init으로 라우팅.
|
|
20655
20779
|
if (cmd === 'install') {
|
|
@@ -21000,5 +21124,7 @@ module.exports = {
|
|
|
21000
21124
|
// 1.9.272: 권한/보안 표면 공개 (GPT-5.5 리뷰) — 단위 테스트
|
|
21001
21125
|
CAPABILITY_SURFACE, POWERFUL_COMMANDS, capabilitiesCmd,
|
|
21002
21126
|
// 1.9.275: 릴리스 채널 (npm dist-tag, GPT-5.5 리뷰) — 단위 테스트
|
|
21003
|
-
_resolveNpmTag, releaseChannelCmd
|
|
21127
|
+
_resolveNpmTag, releaseChannelCmd,
|
|
21128
|
+
// 1.9.276: init --minimal 파일 필터 (GPT-5.5 2차 리뷰) — 단위 테스트
|
|
21129
|
+
MINIMAL_SKIP_KEYS, coreFiles
|
|
21004
21130
|
};
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -807,14 +807,16 @@ total++;
|
|
|
807
807
|
&& /\| grok \|/.test(r1.stdout)
|
|
808
808
|
&& /\| copilot \|/.test(r1.stdout);
|
|
809
809
|
// env 모두 0 → 비활성
|
|
810
|
-
// 1.9.146: Ollama
|
|
811
|
-
const
|
|
810
|
+
// 1.9.146: Ollama → 5 · 1.9.268: grok → 6 · 1.9.277: opencode/qwen/aider/goose → 10 CLI
|
|
811
|
+
const offAll = { LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_GROK: '0', LEERNESS_ENABLE_OPENCODE: '0', LEERNESS_ENABLE_QWEN: '0', LEERNESS_ENABLE_AIDER: '0', LEERNESS_ENABLE_GOOSE: '0', LEERNESS_ENABLE_COPILOT: '0', LEERNESS_ENABLE_OLLAMA: '0' };
|
|
812
|
+
const env2 = { ...process.env, ...offAll };
|
|
812
813
|
const r2 = cp.spawnSync(process.execPath, [CLI, 'agents', 'list', '--json'], { encoding: 'utf8', timeout: 15000, env: env2 });
|
|
813
814
|
let parsed = null;
|
|
814
815
|
try { parsed = JSON.parse(r2.stdout); } catch {}
|
|
815
|
-
const okJson = parsed && Array.isArray(parsed.agents) && parsed.agents.length ===
|
|
816
|
-
const
|
|
817
|
-
|
|
816
|
+
const okJson = parsed && Array.isArray(parsed.agents) && parsed.agents.length === 10 && parsed.agents.every(a => a.status !== 'ready');
|
|
817
|
+
const okNew = /\| opencode \|/.test(r1.stdout) && /\| qwen \|/.test(r1.stdout) && /\| aider \|/.test(r1.stdout) && /\| goose \|/.test(r1.stdout);
|
|
818
|
+
const ok = okList && okJson && okNew;
|
|
819
|
+
console.log(ok ? '✓ B(1.9.30+1.9.277) agents list: 10 CLI (claude/codex/agy/grok/opencode/qwen/aider/goose/copilot/ollama)' : `✗ agents list 실패 (list=${okList} json=${okJson} new=${okNew})`);
|
|
818
820
|
if (!ok) { failed++; console.log(r1.stdout.slice(0, 500)); }
|
|
819
821
|
}
|
|
820
822
|
|
|
@@ -853,11 +855,11 @@ total++;
|
|
|
853
855
|
const r2 = cp.spawnSync(process.execPath, [CLI, 'agents', 'quota', '--json'], { encoding: 'utf8', timeout: 15000, env });
|
|
854
856
|
let parsed = null;
|
|
855
857
|
try { parsed = JSON.parse(r2.stdout); } catch {}
|
|
856
|
-
// 1.9.
|
|
857
|
-
const okJson = parsed && Array.isArray(parsed.quota) && parsed.quota.length ===
|
|
858
|
+
// 1.9.277: opencode/qwen/aider/goose → 10 CLI
|
|
859
|
+
const okJson = parsed && Array.isArray(parsed.quota) && parsed.quota.length === 10
|
|
858
860
|
&& parsed.quota.every(q => typeof q.id === 'string' && typeof q.status === 'string' && (q.hint === null || typeof q.hint === 'string'));
|
|
859
861
|
const ok = okText && okJson;
|
|
860
|
-
console.log(ok ? '✓ B(1.9.31+1.9.
|
|
862
|
+
console.log(ok ? '✓ B(1.9.31+1.9.277) agents quota: 10 CLI 사용량/안내' : `✗ quota 실패 (text=${okText} json=${okJson})`);
|
|
861
863
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
862
864
|
}
|
|
863
865
|
|
|
@@ -2815,5 +2817,31 @@ total++;
|
|
|
2815
2817
|
if (!ok) failed++;
|
|
2816
2818
|
}
|
|
2817
2819
|
|
|
2820
|
+
// 1.9.276 회귀 (GPT-5.5 2차 리뷰): init --dry-run / --minimal / --no-env
|
|
2821
|
+
total++;
|
|
2822
|
+
{
|
|
2823
|
+
// --dry-run: 파일 0개 생성 + 요약 출력
|
|
2824
|
+
const dDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-dry-'));
|
|
2825
|
+
const rDry = cp.spawnSync(process.execPath, [CLI, 'init', dDir, '--dry-run', '--yes'], { encoding: 'utf8', timeout: 30000 });
|
|
2826
|
+
const dryNoFiles = fs.readdirSync(dDir).length === 0;
|
|
2827
|
+
const drySummary = /\[dry-run\] 요약/.test(rDry.stdout) && !fs.existsSync(path.join(dDir, '.harness'));
|
|
2828
|
+
// --minimal --no-env: 핵심 유지 + 비핵심/.env/.cursor/roadmap 제외
|
|
2829
|
+
const mDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-min-'));
|
|
2830
|
+
cp.spawnSync(process.execPath, [CLI, 'init', mDir, '--minimal', '--no-env', '--yes'], { encoding: 'utf8', timeout: 30000 });
|
|
2831
|
+
const fDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-full-'));
|
|
2832
|
+
cp.spawnSync(process.execPath, [CLI, 'init', fDir, '--yes'], { encoding: 'utf8', timeout: 30000 });
|
|
2833
|
+
const minCount = fs.existsSync(path.join(mDir, '.harness')) ? fs.readdirSync(path.join(mDir, '.harness')).length : 0;
|
|
2834
|
+
const fullCount = fs.existsSync(path.join(fDir, '.harness')) ? fs.readdirSync(path.join(fDir, '.harness')).length : 0;
|
|
2835
|
+
const minimalOk = minCount > 0 && minCount < fullCount
|
|
2836
|
+
&& fs.existsSync(path.join(mDir, '.harness', 'plan.md')) && fs.existsSync(path.join(mDir, 'AGENTS.md'))
|
|
2837
|
+
&& !fs.existsSync(path.join(mDir, '.env')) && !fs.existsSync(path.join(mDir, '.cursor')) && !fs.existsSync(path.join(mDir, 'roadmap.html'));
|
|
2838
|
+
// minimal 이어도 verify 통과(필수 파일 보존)
|
|
2839
|
+
const rVerify = cp.spawnSync(process.execPath, [CLI, 'verify', mDir], { encoding: 'utf8', timeout: 20000 });
|
|
2840
|
+
const verifyOk = rVerify.status === 0;
|
|
2841
|
+
const ok = dryNoFiles && drySummary && minimalOk && verifyOk;
|
|
2842
|
+
console.log(ok ? '✓ B(1.9.276) init --dry-run(0파일)/--minimal(축소+verify통과)/--no-env(.env 제외)' : `✗ init 옵션 실패 (dry=${dryNoFiles} drySum=${drySummary} min=${minimalOk}(${minCount}<${fullCount}) verify=${verifyOk})`);
|
|
2843
|
+
if (!ok) { failed++; console.log((rVerify.stdout || '').slice(0, 300)); }
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2818
2846
|
console.log(`\nE2E result: ${total - failed}/${total} passed`);
|
|
2819
2847
|
if (failed > 0) process.exit(1);
|