leerness 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,22 +1,23 @@
1
1
  # Leerness
2
2
 
3
- **Leerness는 AI 에이전트가 프로젝트의 맥락, 작업 규율, 언어 정책, 스킬 라이브러리, 진행 작업 상태를 일관되게 읽고 기록하도록 돕는 AX 최적화 개발 하네스입니다.**
3
+ **Leerness는 AI 에이전트가 프로젝트의 목표, 계획, 진행률, 작업 규율, 스킬 라이브러리, 세션 인수인계를 일관되게 관리하도록 돕는 AX 최적화 개발 하네스입니다.**
4
4
 
5
- Leerness는 프로젝트 안에 `.harness/` 지식 저장소를 만들고, AI가 작업 유형별로 어떤 파일을 읽어야 하는지와 작업 후 어떤 파일을 갱신해야 하는지 명확히 지시합니다. 세션이 끝날 때는 완료/진행중/미완료/예정/보류/대기 작업과 검증 결과를 남기도록 강제합니다.
5
+ Leerness는 `.harness/` 지식 저장소를 설치하고, AI가 작업 전에 어떤 파일을 읽어야 하는지와 작업 후 어떤 파일을 갱신해야 하는지 명확히 지시합니다. 프로젝트 계획은 `plan.md`, 실제 작업 상태는 `progress-tracker.md`, 실행 기준은 `guideline.md`로 분리해서 장기 맥락과 진행률을 안정적으로 유지합니다.
6
6
 
7
7
  ## 주요 기능
8
8
 
9
9
  | 기능 | 설명 |
10
10
  |---|---|
11
- | 언어 정책 | 설치 언어를 자동 감지하거나 `ko/en`으로 선택해 하네스와 스킬 문서의 작성 언어를 통일합니다. |
12
- | Context Routing | feature, ui, release, migration, debug 등 작업 유형별 참조 파일과 갱신 파일을 안내합니다. |
11
+ | 계획 수립/수정 | `plan.md`로 프로젝트 목표, 범위, milestone, 제외/드랍 항목, 변경 이력을 관리합니다. |
12
+ | 진행률 추적 | `progress-tracker.md`로 사용자 요청별 작업 상태와 다음 액션을 추적합니다. |
13
+ | Guideline 정책 | `guideline.md`가 `plan.md`와 `progress-tracker.md`를 참조해 계획 이행 기준을 제공합니다. |
14
+ | 신규 프로젝트 부트스트랩 | 계획이 없는 프로젝트에서는 먼저 계획 초안을 만들고 이후 구현을 진행하도록 유도합니다. |
15
+ | Context Routing | planning, feature, ui, release, migration, debug 등 작업 유형별 참조/갱신 파일을 안내합니다. |
13
16
  | Writeback Policy | 목적, 현재 상태, 아키텍처, 결정 로그, 릴리즈 체크리스트 등 정보를 어디에 기록할지 정의합니다. |
14
- | 진행 작업 추적 | 사용자 요청별 작업을 requested/planned/in-progress/waiting/on-hold/blocked/incomplete/done/dropped 상태로 추적합니다. |
15
- | 작업 드랍 | 사용자가 더 이상 원하지 않는 작업은 삭제하지 않고 `dropped`로 표시해 이력을 보존합니다. |
17
+ | 언어 정책 | 설치 언어를 자동 감지하거나 `ko/en`으로 선택해 하네스와 스킬 문서의 작성 언어를 통일합니다. |
16
18
  | 세션 종료 인수인계 | 매 세션 종료 시 완료/진행중/미완료/예정/보류/대기/드랍 작업과 추천 방향을 표기합니다. |
17
19
  | 스킬 라이브러리 | 검증된 작업 패턴을 스킬팩으로 설치하고, 한글명/가능 작업/최종 업데이트일/AI 검증 상태를 표시합니다. |
18
- | AI 검증 게이트 | 검증된 스킬만 npm/git 업로드가 가능하도록 검증 메타데이터와 토큰 게이트를 사용합니다. |
19
- | 디버그 | `leerness debug`로 AGENTS 방향지시, 언어 정책, 라우팅, 세션 종료 정책, 진행 작업 추적이 정상인지 점검합니다. |
20
+ | 디버그 | `leerness debug`로 AGENTS 방향지시, 계획, 진행률, 라우팅, 세션 종료 정책이 정상인지 점검합니다. |
20
21
 
21
22
  ## 설치
22
23
 
@@ -24,14 +25,7 @@ Leerness는 프로젝트 안에 `.harness/` 지식 저장소를 만들고, AI가
24
25
  npx leerness init
25
26
  ```
26
27
 
27
- 언어를 명시해서 설치:
28
-
29
- ```bash
30
- npx leerness init --language ko
31
- npx leerness init --language en
32
- ```
33
-
34
- 추천 스킬 포함:
28
+ 언어와 추천 스킬을 지정해서 설치:
35
29
 
36
30
  ```bash
37
31
  npx leerness init --language ko --skills recommended
@@ -44,6 +38,20 @@ npx leerness migrate --dry-run
44
38
  npx leerness migrate --language ko
45
39
  ```
46
40
 
41
+ ## 계획 관리
42
+
43
+ ```bash
44
+ leerness plan show
45
+ leerness plan init --goal "AI 상세페이지 생성 서비스"
46
+ leerness plan add "커머스 API 연동" --status planned
47
+ leerness plan update M-0002 --status in-progress --progress 40
48
+ leerness plan drop "관리자 대시보드" --reason "이번 버전 범위에서 제외"
49
+ leerness plan progress
50
+ leerness plan sync
51
+ ```
52
+
53
+ `plan.md`는 전체 계획과 범위를 관리합니다. `progress-tracker.md`는 구체적인 작업 상태를 관리합니다. `guideline.md`는 계획을 어떤 기준으로 수행할지 정의합니다.
54
+
47
55
  ## 주요 명령어
48
56
 
49
57
  ```bash
@@ -52,14 +60,13 @@ leerness migrate [path] [--dry-run] [--language auto|ko|en]
52
60
  leerness status [path]
53
61
  leerness verify [path]
54
62
  leerness debug [path]
55
- leerness route <task-type>
63
+ leerness route <planning|feature|ui|debugging|refactor|release|migration|new-install|skill-library|documentation|debug|session-close>
56
64
  ```
57
65
 
58
66
  작업 추적:
59
67
 
60
68
  ```bash
61
69
  leerness task list
62
- leerness task list --status planned,waiting,on-hold
63
70
  leerness task add "쿠팡 API 연동 검증" --status planned
64
71
  leerness task update T-0002 --status in-progress --next "인증 응답 확인"
65
72
  leerness task drop T-0002 --reason "사용자가 범위에서 제외"
@@ -80,108 +87,57 @@ leerness skill info ai-verified-skill-publisher
80
87
  leerness skill add ai-verified-skill-publisher
81
88
  ```
82
89
 
83
- 스킬 라이브러리:
84
-
85
- ```bash
86
- leerness library validate <path> --strict-ai
87
- leerness library verify <path> --ai --reviewer leerness-ai
88
- leerness library build <path>
89
- leerness library publish <built-library> --target npm|git --execute
90
- ```
91
-
92
90
  ## 생성되는 핵심 파일
93
91
 
94
92
  ```text
95
93
  .harness/
96
- LANGUAGE # ko 또는 en
97
- language-policy.md # 문서/스킬 작성 언어 규칙
98
- project-brief.md # 프로젝트 목적/사용자/성공 기준
99
- current-state.md # 현재 상태/다음 작업/블로커
100
- architecture.md # 구조/모듈/데이터 흐름
101
- context-map.md # 기능별 참조 파일 지도
102
- decisions.md # 결정 로그
103
- guardrails.md # 금지사항/보안/민감영역 규칙
104
- design-system.md # UI/UX/컴포넌트 일관성
105
- feature-contracts.md # 기능 입력/출력/상태/오류 계약
106
- testing-strategy.md # 검증 전략
107
- release-checklist.md # 배포/npm/git/환경변수/롤백 조건
108
- progress-tracker.md # 사용자 요청별 진행/미완료/보류/대기 추적
109
- session-handoff.md # 다음 세션 인수인계
110
- session-close-policy.md # 세션 종료 보고 규칙
111
- anti-lazy-work-policy.md # 게으른 작업 방지 규칙
112
- debug-guide.md # 하네스 작동 점검 기준
113
- context-routing.md # 작업 유형별 읽기/갱신 라우팅
114
- writeback-policy.md # 어떤 정보를 어디에 기록할지
115
- task-type-map.md # 사용자 요청 작업 유형 매핑
116
- AX_MIGRATION_GUIDE.md # AI용 구버전 마이그레이션 가이드
117
- AX_NEW_PROJECT_GUIDE.md # AI용 신규 설치/프로젝트 반영 가이드
118
- AX_SKILL_LIBRARY_GUIDE.md # AI용 스킬 라이브러리 가이드
119
- ```
120
-
121
- ## 언어 정책
122
-
123
- 설치 시 `--language auto`가 기본입니다. Leerness는 기존 README, AGENTS, 하네스 문서를 보고 한국어/영어를 추정합니다. 대화형 터미널에서는 사용자가 직접 선택할 수 있습니다.
124
-
125
- 언어 정책은 아래 파일에 저장됩니다.
126
-
127
- ```text
128
- .harness/LANGUAGE
129
- .harness/language-policy.md
130
- ```
131
-
132
- AI는 하네스 문서, 스킬 문서, 세션 인수인계, 진행 작업 목록을 이 언어로 작성해야 합니다. 단, 코드 식별자, 파일명, 명령어, 환경변수명, API 필드명은 원문을 유지합니다.
133
-
134
- ## 진행 작업과 드랍 처리
135
-
136
- `progress-tracker.md`는 사용자 요청을 세션 간 추적합니다.
137
-
138
- 상태값:
139
-
140
- ```text
141
- requested
142
- planned
143
- in-progress
144
- waiting
145
- on-hold
146
- blocked
147
- incomplete
148
- done
149
- dropped
150
- ```
151
-
152
- 사용자가 어떤 작업을 중단하거나 범위에서 제외하면 삭제하지 않고 `dropped`로 표시합니다.
153
-
154
- ```bash
155
- leerness task drop T-0004 --reason "사용자가 이번 범위에서 제외"
156
- ```
157
-
158
- 세션 종료 시 unresolved 상태인 작업은 자동으로 표기 대상입니다.
159
-
160
- ```text
161
- requested, planned, in-progress, waiting, on-hold, blocked, incomplete
94
+ plan.md # 프로젝트 목표, 범위, milestone, 제외/드랍 항목
95
+ progress-tracker.md # 사용자 요청별 작업 상태와 진행률
96
+ guideline.md # plan/progress 기반 실행 기준
97
+ LANGUAGE # ko 또는 en
98
+ language-policy.md # 문서/스킬 작성 언어 규칙
99
+ project-brief.md # 프로젝트 목적/사용자/성공 기준
100
+ current-state.md # 현재 상태/다음 작업/블로커
101
+ architecture.md # 구조/모듈/데이터 흐름
102
+ context-map.md # 기능별 참조 파일 지도
103
+ decisions.md # 결정 로그
104
+ guardrails.md # 금지사항/보안/민감영역 규칙
105
+ design-system.md # UI/UX/컴포넌트 일관성
106
+ feature-contracts.md # 기능 입력/출력/상태/오류 계약
107
+ testing-strategy.md # 검증 전략
108
+ release-checklist.md # 배포/npm/git/환경변수/롤백 조건
109
+ session-handoff.md # 다음 세션 인수인계
110
+ session-close-policy.md # 세션 종료 보고 규칙
111
+ anti-lazy-work-policy.md # 게으른 작업 방지 규칙
112
+ context-routing.md # 작업 유형별 읽기/갱신 라우팅
113
+ writeback-policy.md # 어떤 정보를 어디에 기록할지
114
+ task-type-map.md # 사용자 요청 작업 유형 매핑
115
+ AX_PLAN_GUIDE.md # AI용 계획 수립/수정/동기화 가이드
116
+ AX_MIGRATION_GUIDE.md # AI용 구버전 마이그레이션 가이드
117
+ AX_NEW_PROJECT_GUIDE.md # AI용 신규 설치/프로젝트 반영 가이드
118
+ AX_SKILL_LIBRARY_GUIDE.md # AI용 스킬 라이브러리 가이드
162
119
  ```
163
120
 
164
121
  ## 디버그
165
122
 
166
- 하네스가 제대로 작동하는지 확인합니다.
167
-
168
123
  ```bash
169
124
  leerness debug
170
125
  ```
171
126
 
172
127
  점검 항목:
173
128
 
174
- - AGENTS.md language-policy, context-routing, writeback-policy, progress-tracker, anti-lazy policy를 참조하는지
175
- - `.harness/language-policy.md`가 존재하고 언어가 manifest에 기록됐는지
176
- - context-routing/writeback/task-type-map이 있는지
177
- - progress-tracker 작업 상태 표를 갖고 있는지
178
- - session-close-policy가 미완료/예정/보류/대기 작업 목록화를 요구하는지
129
+ - `AGENTS.md`가 plan, guideline, language-policy, context-routing, writeback-policy, progress-tracker를 참조하는지
130
+ - `plan.md`에 milestone과 out-of-scope 영역이 있는지
131
+ - `guideline.md`가 `plan.md`와 `progress-tracker.md`를 참조하는지
132
+ - `progress-tracker.md`가 작업 상태 표를 갖고 있는지
133
+ - 세션 종료 정책이 미완료/예정/보류/대기 작업 목록화를 요구하는지
179
134
  - 하네스 파일에 토큰/비밀번호/private key 의심 패턴이 없는지
180
135
 
181
136
  ## 세션 종료 보고 기준
182
137
 
183
138
  AI는 의미 있는 세션이 끝날 때 아래를 정리해야 합니다.
184
139
 
140
+ - 계획 진행 요약
185
141
  - 이번 세션에서 완료한 작업
186
142
  - 사용자가 요청한 작업 중 진행 중인 작업
187
143
  - 미완료/미시작 작업
package/bin/harness.js CHANGED
@@ -6,7 +6,7 @@ const path = require('path');
6
6
  const readline = require('readline');
7
7
  const childProcess = require('child_process');
8
8
 
9
- const VERSION = '1.4.0';
9
+ const VERSION = '1.5.0';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const MIGRATED = '<!-- leerness:migrated-legacy -->';
12
12
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
@@ -27,7 +27,7 @@ function write(p,s){ fs.mkdirSync(path.dirname(p),{recursive:true}); fs.writeFil
27
27
  function rel(root,p){ return path.relative(root,p).replace(/\\/g,'/') || '.'; }
28
28
  function parseJsonSafe(s,fallback){ try { return JSON.parse(s); } catch { return fallback; } }
29
29
  function isTextFile(p){ return /\.(md|mdc|txt|json|js|ts|tsx|jsx|yml|yaml|env|gitignore)$/i.test(p) || !path.extname(p); }
30
- function banner(){ log(''); log(c.bold+c.magenta+'Leerness v'+VERSION+c.reset); log(c.dim+'language policy · context routing · task tracking · debug'+c.reset); log(''); }
30
+ function banner(){ log(''); log(c.bold+c.magenta+'Leerness v'+VERSION+c.reset); log(c.dim+'plan orchestration · progress tracking · context routing · debug'+c.reset); log(''); }
31
31
  function installGuide(){
32
32
  log(c.bold+'설치/마이그레이션 안내'+c.reset);
33
33
  log(' - 기존 파일은 먼저 .harness/archive/ 에 백업합니다.');
@@ -61,6 +61,11 @@ function detectLanguage(root){
61
61
  return 'en';
62
62
  }
63
63
  function readConfiguredLanguage(root){
64
+
65
+ const pf=path.join(root,'.harness/plan.md');
66
+ if(exists(pf)){ const b=read(pf); if(b.includes('## Milestones')) ok('plan.md milestones 확인'); else { warnings++; warn('plan.md에 Milestones 섹션이 없습니다.'); } }
67
+ const gf=path.join(root,'.harness/guideline.md');
68
+ if(exists(gf)){ const b=read(gf); if(b.includes('plan.md')&&b.includes('progress-tracker.md')) ok('guideline.md가 plan/progress를 참조합니다.'); else { failures++; fail('guideline.md가 plan.md/progress-tracker.md를 참조하지 않습니다.'); } }
64
69
  const mf=path.join(root,'.harness/manifest.json');
65
70
  if(exists(mf)){ const j=parseJsonSafe(read(mf),{}); if(j.language) return normalizeLanguage(j.language); }
66
71
  const lp=path.join(root,'.harness/LANGUAGE');
@@ -148,15 +153,18 @@ Agent = Model + Leerness Harness.
148
153
  Before editing, route the task. Read .harness/language-policy.md, .harness/context-routing.md, and use \`leerness route <task-type>\` when the task type is unclear.
149
154
 
150
155
  ## Universal Read Order
151
- 1. .harness/project-brief.md
152
- 2. .harness/current-state.md
153
- 3. .harness/language-policy.md
154
- 4. .harness/context-routing.md
155
- 5. .harness/writeback-policy.md
156
- 6. .harness/task-type-map.md
157
- 7. .harness/context-map.md
158
- 8. .harness/guardrails.md
159
- 9. .harness/skills-lock.json
156
+ 1. .harness/plan.md
157
+ 2. .harness/progress-tracker.md
158
+ 3. .harness/project-brief.md
159
+ 4. .harness/current-state.md
160
+ 5. .harness/language-policy.md
161
+ 6. .harness/context-routing.md
162
+ 7. .harness/writeback-policy.md
163
+ 8. .harness/task-type-map.md
164
+ 9. .harness/context-map.md
165
+ 10. .harness/guideline.md
166
+ 11. .harness/guardrails.md
167
+ 12. .harness/skills-lock.json
160
168
 
161
169
  ## Language Rule
162
170
  - Before writing or updating any harness/skill/session document, read .harness/language-policy.md.
@@ -176,6 +184,8 @@ Before editing, route the task. Read .harness/language-policy.md, .harness/conte
176
184
 
177
185
  ## Writeback Rules
178
186
  - Always update current-state.md, task-log.md, and session-handoff.md after meaningful work.
187
+ - Update plan.md and progress-tracker.md when user scope, milestones, task status, exclusions, or planned work changes.
188
+ - Update guideline.md when execution standards, quality gates, or plan-following rules change.
179
189
  - Update decisions.md when a structural, technology, API, schema, deployment, or irreversible decision is made.
180
190
  - Update feature-contracts.md when input/output/state/error behavior changes.
181
191
  - Update design-system.md when UI rules, components, layout, spacing, or states change.
@@ -188,6 +198,19 @@ Before editing, route the task. Read .harness/language-policy.md, .harness/conte
188
198
  - Preserve .env.example and .gitignore; append missing Leerness entries only.
189
199
  - Keep secrets, tokens, cookies, credentials, and customer private data out of harness files.
190
200
 
201
+
202
+ ## Plan Rule
203
+ - Use .harness/plan.md as the source of truth for the user's goal, project scope, milestones, included work, excluded work, and plan changes.
204
+ - Before starting feature/release/refactor/migration work, check whether the request maps to an existing plan item.
205
+ - If the user adds scope, update plan.md and progress-tracker.md.
206
+ - If the user excludes scope, mark it under Dropped / Out of Scope instead of deleting history.
207
+ - If the project looks new or lacks a usable plan, do not start broad implementation blindly. First create or ask for enough information to create .harness/plan.md.
208
+
209
+ ## Guideline Rule
210
+ - Use .harness/guideline.md for execution standards.
211
+ - guideline.md must reference plan.md and progress-tracker.md, but should not become the primary progress database.
212
+ - Progress is tracked in plan.md milestones and progress-tracker.md task rows.
213
+
191
214
  ## Progress Tracker Rule
192
215
  - Track user-requested work in .harness/progress-tracker.md.
193
216
  - Use statuses: requested, planned, in-progress, waiting, on-hold, blocked, incomplete, done, dropped.
@@ -241,8 +264,79 @@ At the end of each session, list:
241
264
  '.harness/manifest.json': '{{MANIFEST}}\n',
242
265
  '.harness/skills-lock.json': '{{SKILLS_LOCK}}\n',
243
266
  '.harness/skill-config.schema.json': `${MARK}\n{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "title": "Leerness Skill Config",\n "type": "object",\n "additionalProperties": true\n}\n`,
267
+ '.harness/plan.md': `${MARK}
268
+ ---
269
+ leernessRole: plan
270
+ readWhen: [planning, every-feature, scope-change, new-project, resume-work, session-close]
271
+ updateWhen: [new-user-request, scope-added, scope-dropped, milestone-change, progress-change, plan-reprioritized]
272
+ doNotStore: [secrets, tokens, credentials, raw-private-data]
273
+ ---
274
+
275
+ # Project Plan
276
+
277
+ Updated: {{DATE}}
278
+
279
+ ## Project Goal
280
+ - Define the user's requested purpose here.
281
+
282
+ ## Scope
283
+ - Included work should be listed here.
284
+
285
+ ## Out of Scope / Dropped
286
+ | Item | Reason | Dropped At |
287
+ |---|---|---|
288
+
289
+ ## Milestones
290
+ | ID | Milestone | Status | Progress | Related Tasks | Notes |
291
+ |---|---|---|---:|---|---|
292
+ | M-0001 | Establish Leerness plan | planned | 0% | T-0001 | Replace with project-specific milestones. |
293
+
294
+ ## Plan Change Log
295
+ | Date | Change | Reason | Requested By |
296
+ |---|---|---|---|
297
+ | {{DATE}} | Plan file created | Leerness v{{VERSION}} installation or migration | Leerness |
298
+
299
+ ## Rules
300
+ - Add new user-requested scope before implementation when it changes the overall plan.
301
+ - Do not silently implement dropped or out-of-scope work.
302
+ - Link plan milestones to progress-tracker task IDs when possible.
303
+ - Keep this file high-level; detailed task status belongs in progress-tracker.md.
304
+ {{LEGACY_PLAN}}
305
+ `,
306
+ '.harness/guideline.md': `${MARK}
307
+ ---
308
+ leernessRole: guideline
309
+ readWhen: [every-task, planning, implementation, review, release]
310
+ updateWhen: [standard-change, quality-gate-change, plan-following-rule-change, repeated-process-failure]
311
+ doNotStore: [secrets, tokens, credentials, raw-private-data]
312
+ ---
313
+
314
+ # Project Guideline
315
+
316
+ ## Source of Direction
317
+ - Primary plan: .harness/plan.md
318
+ - Task status and progress: .harness/progress-tracker.md
319
+ - Current handoff state: .harness/current-state.md and .harness/session-handoff.md
320
+
321
+ ## How to Use the Plan
322
+ 1. Check whether the user request exists in plan.md.
323
+ 2. If it is new scope, add it to plan.md and progress-tracker.md before broad implementation.
324
+ 3. If it is excluded or dropped, do not implement it unless the user reopens it.
325
+ 4. Update progress after implementation or verification changes.
326
+
327
+ ## Progress Policy
328
+ - guideline.md describes how progress should be handled.
329
+ - plan.md and progress-tracker.md store actual progress values.
330
+ - Do not duplicate detailed task tables here.
331
+
332
+ ## Quality Gates
333
+ - Follow context-routing.md before editing.
334
+ - Follow writeback-policy.md after editing.
335
+ - Verify with project-specific commands before marking work done.
336
+ - Never mark work done if requested scope remains incomplete.
337
+ `,
244
338
  '.harness/project-brief.md': `${MARK}\n---\nleernessRole: project-brief\nreadWhen: [every-task, planning, product-direction, onboarding]\nupdateWhen: [purpose-change, user-change, success-criteria-change, product-direction-change]\ndoNotStore: [secrets, tokens, credentials, raw-customer-data]\n---\n\n# Project Brief: {{PROJECT}}\n\n## Purpose\n\n## Success Criteria\n\n## Users\n\n## Product Direction\n{{LEGACY_BRIEF}}\n`,
245
- '.harness/current-state.md': `${MARK}\n---\nleernessRole: current-state\nreadWhen: [every-task, resume-work, planning, debugging, release]\nupdateWhen: [after-meaningful-work, blocker-change, next-step-change, status-change]\ndoNotStore: [secrets, tokens, credentials]\n---\n\n# Current State\n\nUpdated: {{DATE}}\n\n## Now\n- Leerness v{{VERSION}} installed or migrated.\n\n## Next\n- Fill project-brief, context-map, design-system, and feature-contracts.\n\n## Blockers\n- None recorded.\n{{LEGACY_STATE}}\n`,
339
+ '.harness/current-state.md': `${MARK}\n---\nleernessRole: current-state\nreadWhen: [every-task, resume-work, planning, debugging, release]\nupdateWhen: [after-meaningful-work, blocker-change, next-step-change, status-change]\ndoNotStore: [secrets, tokens, credentials]\n---\n\n# Current State\n\nUpdated: {{DATE}}\n\n## Now\n- Leerness v{{VERSION}} installed or migrated.\n\n## Next\n- Fill plan.md, project-brief, context-map, design-system, and feature-contracts.\n\n## Blockers\n- None recorded.\n{{LEGACY_STATE}}\n`,
246
340
  '.harness/architecture.md': `${MARK}\n---\nleernessRole: architecture\nreadWhen: [feature, refactor, integration, api, database, deployment]\nupdateWhen: [module-change, data-flow-change, integration-change, boundary-change]\ndoNotStore: [secrets, credentials]\n---\n\n# Architecture\n\n## Overview\n\n## Main Modules\n\n## Data Flow\n\n## External Services\n\n## Boundaries\n{{LEGACY_ARCH}}\n`,
247
341
  '.harness/context-map.md': `${MARK}\n---\nleernessRole: context-map\nreadWhen: [every-task, file-discovery, impact-analysis]\nupdateWhen: [new-important-file, moved-module, new-route, new-service, ownership-change]\ndoNotStore: [secrets, tokens]\n---\n\n# Context Map\n\n| Area | Files | Notes |\n|---|---|---|\n| UI | src/components/**, app/** | Check design-system.md first. |\n| API | src/api/**, server/**, functions/** | Preserve response contracts. |\n| Data | db/**, firestore/**, prisma/** | Confirm migrations. |\n| Tests | test/**, tests/**, __tests__/** | Add or update checks. |\n`,
248
342
  '.harness/decisions.md': `${MARK}\n---\nleernessRole: decisions\nreadWhen: [architecture, refactor, release, dependency-change, irreversible-change]\nupdateWhen: [important-decision, tradeoff, architecture-change, dependency-change, rollback-relevant-change]\ndoNotStore: [secrets, credentials]\n---\n\n# Decision Log\n\n## Template\n\n### YYYY-MM-DD — Title\n- Decision:\n- Reason:\n- Alternatives:\n- Impact:\n{{LEGACY_DECISIONS}}\n`,
@@ -308,7 +402,7 @@ doNotStore: [secrets, tokens, credentials, raw-private-data]
308
402
 
309
403
  # Progress Tracker
310
404
 
311
- Use this file to track user-requested work across sessions. Keep entries concrete and checkable. At session close, unresolved statuses must be listed.
405
+ Use this file to track user-requested work across sessions. Keep entries concrete and checkable. Link tasks to plan.md milestones when possible. At session close, unresolved statuses must be listed.
312
406
 
313
407
  | ID | User Request | Status | Owner | Last Update | Evidence / Notes | Next Action |
314
408
  |---|---|---|---|---|---|---|
@@ -336,13 +430,48 @@ Every session-close report must list all tasks whose Status is planned, waiting,
336
430
  `,
337
431
  '.harness/anti-lazy-work-policy.md': `${MARK}\n---\nleernessRole: anti-lazy-work-policy\nreadWhen: [every-task, end-of-session, verification, planning]\nupdateWhen: [quality-failure, repeated-shortcut, missed-verification, reporting-rule-change]\ndoNotStore: [secrets, tokens, credentials]\n---\n\n# Anti-Lazy Work Policy\n\nThe AI must not appear productive while leaving important work vague or incomplete.\n\n## Required Behavior\n\n- State exactly what was done and what was not done.\n- Prefer concrete file paths, commands, checks, and outputs.\n- Do not skip obvious verification when tools are available.\n- If a check cannot be run, say so and provide the exact command to run.\n- Do not collapse multiple unfinished user requests into a generic sentence.\n- Do not overwrite project memory to avoid doing the harder merge.\n- Do not call a task complete only because files were generated. Confirm behavior or clearly label it unverified.\n\n## Laziness Warning Signs\n\n- done without changed files or verification.\n- should work without a check.\n- No mention of incomplete user-requested items.\n- No next exact action.\n- Memory files not updated after meaningful work.\n\n## Minimum Final Answer Standard\n\nA final answer after meaningful work must include:\n\n- Completed\n- In progress\n- Incomplete\n- Verification\n- Updated memory\n- Risks\n- Recommended next directions\n`,
338
432
  '.harness/templates/end-of-session-report.md': `${MARK}\n# End-of-Session Report\n\n## Completed This Session\n-\n\n## In Progress From User Requests\n-\n\n## Incomplete / Not Started From User Requests\n-\n\n## Planned Tasks\n-\n\n## Waiting Tasks\n-\n\n## On-Hold Tasks\n-\n\n## Blocked Tasks\n-\n\n## Dropped By User\n-\n\n## Verification\n-\n\n## Files Changed\n-\n\n## Memory Files Updated\n-\n\n## Risks / Assumptions / Blockers\n-\n\n## Recommended Next Directions\n-\n\n## Next Exact Step\n-\n`,
339
- '.harness/debug-guide.md': `${MARK}\n# Leerness Debug Guide\n\nUse this when checking whether the harness is actually guiding the AI.\n\n## Debug Checklist\n\n- AGENTS.md references language-policy, context-routing, writeback-policy, progress-tracker, and anti-lazy policy.\n- language-policy.md exists and defines one primary language.\n- context-routing.md maps task types to read/update files.\n- writeback-policy.md explains where each kind of information goes.\n- progress-tracker.md contains a task table and unresolved status values.\n- session-close-policy.md forces active unresolved work to be listed.\n- anti-lazy-work-policy.md prevents unverified completion claims.\n- skills-lock.json records installed skills.\n\nRun: leerness debug [path]\n`,
433
+ '.harness/debug-guide.md': `${MARK}\n# Leerness Debug Guide\n\nUse this when checking whether the harness is actually guiding the AI.\n\n## Debug Checklist\n\n- AGENTS.md references plan.md, guideline.md, language-policy, context-routing, writeback-policy, progress-tracker, and anti-lazy policy.\n- language-policy.md exists and defines one primary language.\n- context-routing.md maps task types to read/update files.\n- writeback-policy.md explains where each kind of information goes.\n- plan.md exists and contains milestones/out-of-scope areas.
434
+ - guideline.md references plan.md and progress-tracker.md.
435
+ - progress-tracker.md contains a task table and unresolved status values.\n- session-close-policy.md forces active unresolved work to be listed.\n- anti-lazy-work-policy.md prevents unverified completion claims.\n- skills-lock.json records installed skills.\n\nRun: leerness debug [path]\n`,
340
436
  '.harness/skill-index.md': `${MARK}\n# Skill Index\n\n| Task | Skill |\n|---|---|\n| Codebase analysis | skills/codebase-analysis.md |\n| Feature implementation | skills/feature-implementation.md |\n| Debugging | skills/debugging.md |\n| UI consistency | skills/ui-consistency.md |\n| Release | skills/release-check.md |\n`,
341
437
  '.harness/context-routing.md': `${MARK}\n# Context Routing\n\nUse this file to decide what to read before work and what to update afterward.\n\n## feature\nRead: project-brief, current-state, architecture, context-map, feature-contracts, skills/feature-implementation.\nUpdate: current-state, task-log, session-handoff, feature-contracts, context-map when paths change.\n\n## ui\nRead: design-system, feature-contracts, context-map, skills/ui-consistency.\nUpdate: design-system, feature-contracts, current-state, task-log, session-handoff.\n\n## debugging\nRead: current-state, task-log, feature-contracts, testing-strategy, skills/debugging.\nUpdate: task-log, current-state, session-handoff, testing-strategy when regression coverage changes.\n\n## release\nRead: release-checklist, testing-strategy, current-state, decisions, secret-policy.\nUpdate: release-checklist, task-log, current-state, session-handoff.\n\n## migration\nRead: AX_MIGRATION_GUIDE, writeback-policy, task-type-map.\nUpdate: only missing files by default; preserve project memory unless --force.\n\n## session-close\nRead: session-close-policy, progress-tracker, current-state, task-log, session-handoff, anti-lazy-work-policy.\nUpdate: session-handoff, progress-tracker, current-state, task-log, and any relevant memory files changed by the session.\n`,
342
438
  '.harness/writeback-policy.md': `${MARK}\n# Writeback Policy\n\n## current-state.md\nCurrent progress, blockers, next work.\n\n## task-log.md\nWhat changed, when, and verification result.\n\n## session-handoff.md\nEnough context for the next AI session to continue.\n\n## decisions.md\nImportant choices and tradeoffs.\n\n## release-checklist.md\nDeploy commands, env requirements, rollback, failures.\n\n## design-system.md\nUI rules and reusable patterns.\n\n## feature-contracts.md\nInput/output/state/error contracts.\n\n## project-brief.md\nProduct purpose and success criteria only.\n\n## progress-tracker.md\nUser-requested work items, status, evidence, and next actions across sessions.\n\n## session-close-policy.md\nFinal response and handoff rules. Update only when the reporting standard changes.\n\n## anti-lazy-work-policy.md\nQuality guardrails that prevent vague or incomplete closure. Update when repeated failure patterns appear.\n`,
343
- '.harness/task-type-map.md': `${MARK}\n# Task Type Map\n\n| User request | Task type | First files |\n|---|---|---|\n| 기능 | feature | feature-contracts, architecture |\n| 디자인/UI | ui | design-system |\n| 오류 수정 | debugging | task-log, debugging skill |\n| 구조 개선 | refactor | architecture, decisions |\n| 배포 | release | release-checklist |\n| 하네스 전환 | migration | AX_MIGRATION_GUIDE |\n| 신규 적용 | new-install | AX_NEW_PROJECT_GUIDE |\n| 스킬 저장/배포 | skill-library | AX_SKILL_LIBRARY_GUIDE |\n`,
439
+ '.harness/task-type-map.md': `${MARK}\n# Task Type Map\n\n| User request | Task type | First files |\n|---|---|---|\n| 계획 수립/수정 | planning | plan, progress-tracker, guideline |
440
+ | 새 기능 | feature | feature-contracts, architecture |\n| 디자인/UI | ui | design-system |\n| 오류 수정 | debugging | task-log, debugging skill |\n| 구조 개선 | refactor | architecture, decisions |\n| 배포 | release | release-checklist |\n| 하네스 전환 | migration | AX_MIGRATION_GUIDE |\n| 신규 적용 | new-install | AX_NEW_PROJECT_GUIDE |\n| 스킬 저장/배포 | skill-library | AX_SKILL_LIBRARY_GUIDE |\n`,
344
441
  '.harness/AX_MIGRATION_GUIDE.md': `${MARK}\n# AX Migration Guide\n\n## Goal\nMigrate old harness files without losing project memory.\n\n## Procedure\n1. Run: leerness migrate --dry-run\n2. Confirm archive target.\n3. Run: leerness migrate\n4. Check .env.example and .gitignore were merged, not replaced.\n5. Check project memory files were preserved.\n6. Fill only missing context using archived legacy files.\n7. Run: leerness status && leerness verify.\n\n## Critical Rule\nDo not overwrite existing project-brief, current-state, architecture, decisions, release-checklist, feature-contracts, or design-system unless the user explicitly asks for --force.\n`,
345
- '.harness/AX_NEW_PROJECT_GUIDE.md': `${MARK}\n# AX New Project Guide\n\n## Goal\nAfter initial installation, populate Leerness memory from the actual project.\n\n## Read actual project files\n- package/config files\n- app/routes/pages\n- API/server/functions\n- DB/schema/rules\n- deploy/CI files\n- tests\n\n## Fill memory files\n- project-brief.md: purpose and success criteria\n- architecture.md: modules and data flow\n- context-map.md: important files and routes\n- design-system.md: existing UI patterns\n- feature-contracts.md: major features and states\n- release-checklist.md: real deploy commands and env requirements\n`,
442
+ '.harness/AX_PLAN_GUIDE.md': `${MARK}
443
+ # AX Plan Guide
444
+
445
+ Use this guide when creating, updating, dropping, or syncing the project plan.
446
+
447
+ ## Purpose
448
+ plan.md keeps the user's intended outcome, scope, milestones, exclusions, and plan changes visible across sessions. progress-tracker.md keeps concrete task states. guideline.md defines how the plan should be followed.
449
+
450
+ ## When a user asks for new work
451
+ 1. Check .harness/plan.md.
452
+ 2. Decide whether the request is an existing milestone, a subtask, new scope, or out of scope.
453
+ 3. If new scope, add a milestone or task reference.
454
+ 4. Add or update the matching row in progress-tracker.md.
455
+ 5. Route implementation using context-routing.md.
456
+
457
+ ## When a user drops work
458
+ 1. Do not delete history.
459
+ 2. Mark the task dropped in progress-tracker.md.
460
+ 3. Add it to plan.md Out of Scope / Dropped with the reason.
461
+ 4. Mention the drop in session-handoff.md.
462
+
463
+ ## New project detection
464
+ If the project lacks a meaningful plan, project brief, source structure, or clear success criteria, first create a plan draft from the user request. If the user request is not enough, ask for the missing goal/scope before broad implementation.
465
+
466
+ ## Sync rule
467
+ Run or follow: leerness plan sync
468
+ - plan.md: milestones and scope
469
+ - progress-tracker.md: concrete task statuses
470
+ - guideline.md: execution standards and progress policy
471
+ - current-state.md: immediate next work
472
+ `,
473
+ '.harness/AX_NEW_PROJECT_GUIDE.md': `${MARK}\n# AX New Project Guide\n\n## Goal\nAfter initial installation, populate Leerness memory from the actual project.\n\n## Read actual project files\n- package/config files\n- app/routes/pages\n- API/server/functions\n- DB/schema/rules\n- deploy/CI files\n- tests\n\n## Fill memory files\n- plan.md: user goal, scope, milestones, dropped/out-of-scope work
474
+ - project-brief.md: purpose and success criteria\n- architecture.md: modules and data flow\n- context-map.md: important files and routes\n- design-system.md: existing UI patterns\n- feature-contracts.md: major features and states\n- release-checklist.md: real deploy commands and env requirements\n`,
346
475
  '.harness/AX_SKILL_LIBRARY_GUIDE.md': `${MARK}\n# AX Skill Library Guide\n\n## AI-verified skill lifecycle\n1. Learn from a validated implementation.\n2. Remove secrets and keep env variable names only.\n3. Add displayNameKo, capabilities, lastUpdated, verification metadata.\n4. Run validate.\n5. Run verify --ai.\n6. Build.\n7. Publish dry-run.\n8. Publish with --execute only after token gate passes.\n`,
347
476
  '.harness/skills/codebase-analysis.md': `${MARK}\n# Skill: Codebase Analysis\n\nRead context-map, architecture, current-state, and related source files before proposing changes.\n`,
348
477
  '.harness/skills/feature-implementation.md': `${MARK}\n# Skill: Feature Implementation\n\nDefine contract, inspect existing patterns, implement minimal change, verify, update memory.\n`,
@@ -356,8 +485,8 @@ Every session-close report must list all tasks whose Status is planned, waiting,
356
485
  '.harness/templates/decision.md': `${MARK}\n# Decision\n\n## Decision\n\n## Reason\n\n## Alternatives\n\n## Impact\n`
357
486
  };
358
487
 
359
- const memoryFiles = new Set(['.harness/project-brief.md','.harness/current-state.md','.harness/architecture.md','.harness/context-map.md','.harness/decisions.md','.harness/task-log.md','.harness/constraints.md','.harness/guardrails.md','.harness/design-system.md','.harness/feature-contracts.md','.harness/testing-strategy.md','.harness/review-checklist.md','.harness/release-checklist.md','.harness/session-handoff.md','.harness/progress-tracker.md','.harness/language-policy.md','.harness/debug-guide.md','.harness/skill-index.md','.harness/secret-policy.md']);
360
- const refreshableFiles = new Set(['AGENTS.md','CLAUDE.md','.cursor/rules/leerness.mdc','.github/copilot-instructions.md','.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md','.harness/AX_SKILL_LIBRARY_GUIDE.md','.harness/AX_MIGRATION_GUIDE.md','.harness/AX_NEW_PROJECT_GUIDE.md','.harness/session-close-policy.md','.harness/anti-lazy-work-policy.md','.harness/templates/end-of-session-report.md','.harness/debug-guide.md','.harness/language-policy.md','.harness/LANGUAGE','.harness/manifest.json','.harness/HARNESS_VERSION']);
488
+ const memoryFiles = new Set(['.harness/plan.md','.harness/guideline.md','.harness/project-brief.md','.harness/current-state.md','.harness/architecture.md','.harness/context-map.md','.harness/decisions.md','.harness/task-log.md','.harness/constraints.md','.harness/guardrails.md','.harness/design-system.md','.harness/feature-contracts.md','.harness/testing-strategy.md','.harness/review-checklist.md','.harness/release-checklist.md','.harness/session-handoff.md','.harness/progress-tracker.md','.harness/language-policy.md','.harness/debug-guide.md','.harness/skill-index.md','.harness/secret-policy.md']);
489
+ const refreshableFiles = new Set(['AGENTS.md','CLAUDE.md','.cursor/rules/leerness.mdc','.github/copilot-instructions.md','.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md','.harness/AX_PLAN_GUIDE.md','.harness/AX_SKILL_LIBRARY_GUIDE.md','.harness/AX_MIGRATION_GUIDE.md','.harness/AX_NEW_PROJECT_GUIDE.md','.harness/session-close-policy.md','.harness/anti-lazy-work-policy.md','.harness/templates/end-of-session-report.md','.harness/debug-guide.md','.harness/language-policy.md','.harness/LANGUAGE','.harness/manifest.json','.harness/HARNESS_VERSION']);
361
490
  function uniqueLinesAppend(current, addition){
362
491
  const lines=current.split(/\r?\n/); const seen=new Set(lines.map(x=>x.trim()).filter(Boolean));
363
492
  const add=addition.split(/\r?\n/).filter(line=>{ const t=line.trim(); if(!t||seen.has(t)) return false; seen.add(t); return true; });
@@ -384,9 +513,9 @@ function writeCoreSafely(root,file,body,opts={}){
384
513
  }
385
514
  if(dryRun) info('[dry-run] 보존: '+file+' (덮어쓰려면 --force)'); else ok('보존: '+file+' (덮어쓰려면 --force)'); return 'preserved';
386
515
  }
387
- function manifest(root,selectedSkills,language){ return JSON.stringify({name:projectName(root),harnessVersion:VERSION,language,languageName:languageName(language),installedAt:now(),managedFiles:Object.keys(coreFiles),selectedSkills,nonDestructiveMigration:true,taskStatuses:['requested','planned','in-progress','waiting','on-hold','blocked','incomplete','done','dropped']},null,2); }
516
+ function manifest(root,selectedSkills,language){ return JSON.stringify({name:projectName(root),harnessVersion:VERSION,language,languageName:languageName(language),installedAt:now(),managedFiles:Object.keys(coreFiles),selectedSkills,nonDestructiveMigration:true,taskStatuses:['requested','planned','in-progress','waiting','on-hold','blocked','incomplete','done','dropped'],planEnabled:true},null,2); }
388
517
  function skillsLock(root,selectedSkills){ const lock={harnessVersion:VERSION,installedAt:now(),installedSkills:{}}; for(const name of selectedSkills){ const meta=getSkillMeta(name); if(meta) lock.installedSkills[name]={version:meta.version,source:'bundled',title:meta.title,displayNameKo:meta.displayNameKo||meta.title,lastUpdated:meta.lastUpdated,verificationStatus:(meta.verification||{}).status||'unknown'}; } return JSON.stringify(lock,null,2); }
389
- function makeContext(root,legacyText,selectedSkills,language){ const lang=normalizeLanguage(language||readConfiguredLanguage(root)||detectLanguage(root)); return { PROJECT:projectName(root), DATE:today(), VERSION, LANGUAGE:lang, LANGUAGE_NAME:languageName(lang), LANGUAGE_POLICY:languagePolicyBody(lang), LEGACY_AGENT:legacyBlock('agent instructions',pick(legacyText,['AGENTS.md','AGENT.md','CLAUDE.md','.cursorrules','.cursor/rules/project-rules.mdc','.cursor/rules/leerness.mdc','.github/copilot-instructions.md'])), LEGACY_BRIEF:legacyBlock('project context',pick(legacyText,['PROJECT_CONTEXT.md','CONTEXT.md','docs/guideline.md','AI_HARNESS.md','HARNESS.md'])), LEGACY_STATE:legacyBlock('state',pick(legacyText,['CURRENT_STATE.md','TASK_LOG.md','docs/history.md'])), LEGACY_ARCH:legacyBlock('architecture',pick(legacyText,['ARCHITECTURE.md'])), LEGACY_DECISIONS:legacyBlock('decisions',pick(legacyText,['DECISIONS.md'])), MANIFEST:manifest(root,selectedSkills,lang), SKILLS_LOCK:skillsLock(root,selectedSkills) }; }
518
+ function makeContext(root,legacyText,selectedSkills,language){ const lang=normalizeLanguage(language||readConfiguredLanguage(root)||detectLanguage(root)); return { PROJECT:projectName(root), DATE:today(), VERSION, LANGUAGE:lang, LANGUAGE_NAME:languageName(lang), LANGUAGE_POLICY:languagePolicyBody(lang), LEGACY_AGENT:legacyBlock('agent instructions',pick(legacyText,['AGENTS.md','AGENT.md','CLAUDE.md','.cursorrules','.cursor/rules/project-rules.mdc','.cursor/rules/leerness.mdc','.github/copilot-instructions.md'])), LEGACY_PLAN:legacyBlock('plan context',pick(legacyText,['PLAN.md','plan.md','.harness/plan.md'])), LEGACY_BRIEF:legacyBlock('project context',pick(legacyText,['PROJECT_CONTEXT.md','CONTEXT.md','docs/guideline.md','AI_HARNESS.md','HARNESS.md'])), LEGACY_STATE:legacyBlock('state',pick(legacyText,['CURRENT_STATE.md','TASK_LOG.md','docs/history.md'])), LEGACY_ARCH:legacyBlock('architecture',pick(legacyText,['ARCHITECTURE.md'])), LEGACY_DECISIONS:legacyBlock('decisions',pick(legacyText,['DECISIONS.md'])), MANIFEST:manifest(root,selectedSkills,lang), SKILLS_LOCK:skillsLock(root,selectedSkills) }; }
390
519
 
391
520
  function listSkillPacks(){ if(!exists(PACKS_DIR)) return []; return fs.readdirSync(PACKS_DIR).map(n=>getSkillMeta(n)).filter(Boolean).sort((a,b)=>a.name.localeCompare(b.name)); }
392
521
  function getSkillMeta(name){ const metaPath=path.join(PACKS_DIR,name,'skill.json'); if(!exists(metaPath)) return null; const meta=parseJsonSafe(read(metaPath),null); if(!meta||!meta.name) return null; return meta; }
@@ -448,6 +577,92 @@ function libraryCommand(args,flags){ const sub=args[1]||'help'; if(sub==='guide'
448
577
 
449
578
 
450
579
 
580
+ function planFile(root){ return path.join(root,'.harness/plan.md'); }
581
+ function ensurePlan(root){
582
+ const p=planFile(root);
583
+ if(!exists(p)){
584
+ const ctx=makeContext(root,{},[],readConfiguredLanguage(root)||detectLanguage(root));
585
+ writeCoreSafely(root,'.harness/plan.md',fill(coreFiles['.harness/plan.md'],ctx),{force:false,dryRun:false});
586
+ }
587
+ return p;
588
+ }
589
+ function readPlanText(root){ const p=ensurePlan(root); return read(p); }
590
+ function writePlanText(root,body){ write(planFile(root),body); }
591
+ function nextMilestoneId(body){ let max=0; for(const m of body.matchAll(/\bM-(\d+)\b/g)) max=Math.max(max,parseInt(m[1],10)); return 'M-'+String(max+1).padStart(4,'0'); }
592
+ function appendPlanChange(body,change,reason,by){
593
+ const row=`| ${today()} | ${String(change).replace(/\|/g,'/')} | ${String(reason||'-').replace(/\|/g,'/')} | ${String(by||'user').replace(/\|/g,'/')} |`;
594
+ if(body.includes('## Plan Change Log')) return body.replace(/(## Plan Change Log[\s\S]*?\n\|---\|---\|---\|---\|\n)/, '$1'+row+'\n');
595
+ return body.replace(/\s*$/,'\n\n## Plan Change Log\n| Date | Change | Reason | Requested By |\n|---|---|---|---|\n'+row+'\n');
596
+ }
597
+ function planAdd(root,text,flags={}){
598
+ root=path.resolve(root||process.cwd()); const file=ensurePlan(root); let body=read(file); const id=nextMilestoneId(body);
599
+ const status=String(flags.status||'planned'); const progress=String(flags.progress||'0').replace(/%$/,'')+'%';
600
+ const row=`| ${id} | ${String(text).replace(/\|/g,'/')} | ${status} | ${progress} | - | ${String(flags.note||'Added from user request').replace(/\|/g,'/')} |`;
601
+ if(body.includes('## Milestones')){
602
+ body=body.replace(/(## Milestones[\s\S]*?\n\|---\|---\|---\|---:?\|---\|---\|\n)/, '$1'+row+'\n');
603
+ } else {
604
+ body=body.replace(/\s*$/,'\n\n## Milestones\n| ID | Milestone | Status | Progress | Related Tasks | Notes |\n|---|---|---|---:|---|---|\n'+row+'\n');
605
+ }
606
+ body=appendPlanChange(body,'Added '+id+': '+text,flags.reason||'new user request',flags.by||'user');
607
+ writePlanText(root,body); ok('계획 추가: '+id);
608
+ const tasks=readProgressTasks(root); tasks.push({id:nextTaskId(tasks),request:text,status:status==='done'?'done':'planned',owner:'AI',lastUpdate:today(),evidence:'Linked to '+id,nextAction:String(flags.next||'Start or refine this plan item')}); writeProgressTasks(root,tasks);
609
+ }
610
+ function planDrop(root,item,flags={}){
611
+ root=path.resolve(root||process.cwd()); const file=ensurePlan(root); let body=read(file); const reason=String(flags.reason||'Dropped by user');
612
+ const row=`| ${String(item).replace(/\|/g,'/')} | ${reason.replace(/\|/g,'/')} | ${today()} |`;
613
+ if(body.includes('## Out of Scope / Dropped')) body=body.replace(/(## Out of Scope \/ Dropped[\s\S]*?\n\|---\|---\|---\|\n)/, '$1'+row+'\n');
614
+ else body=body.replace(/\s*$/,'\n\n## Out of Scope / Dropped\n| Item | Reason | Dropped At |\n|---|---|---|\n'+row+'\n');
615
+ body=appendPlanChange(body,'Dropped: '+item,reason,flags.by||'user'); writePlanText(root,body); ok('계획 드랍 기록: '+item);
616
+ const tasks=readProgressTasks(root); let matched=false; for(const t of tasks){ if(String(t.request).includes(item)||String(item).includes(t.request)){ t.status='dropped'; t.evidence=reason; t.nextAction='None'; t.lastUpdate=today(); matched=true; } }
617
+ if(!matched) tasks.push({id:nextTaskId(tasks),request:item,status:'dropped',owner:'AI',lastUpdate:today(),evidence:reason,nextAction:'None'}); writeProgressTasks(root,tasks);
618
+ }
619
+ function planUpdate(root,id,flags={}){
620
+ root=path.resolve(root||process.cwd()); const file=ensurePlan(root); let body=read(file); let changed=false;
621
+ body=body.split(/\r?\n/).map(line=>{
622
+ if(!line.startsWith('| '+id+' |')) return line;
623
+ const cols=splitTableLine(line); if(cols.length<6) return line;
624
+ if(flags.title||flags.name) cols[1]=String(flags.title||flags.name).replace(/\|/g,'/');
625
+ if(flags.status) cols[2]=String(flags.status);
626
+ if(flags.progress!==undefined) cols[3]=String(flags.progress).replace(/%$/,'')+'%';
627
+ if(flags.tasks) cols[4]=String(flags.tasks).replace(/\|/g,'/');
628
+ if(flags.note) cols[5]=String(flags.note).replace(/\|/g,'/');
629
+ changed=true; return '| '+cols.join(' | ')+' |';
630
+ }).join('\n');
631
+ if(!changed){ fail('계획 ID를 찾을 수 없습니다: '+id); process.exitCode=1; return; }
632
+ body=appendPlanChange(body,'Updated '+id,flags.reason||'plan update',flags.by||'user'); writePlanText(root,body); ok('계획 업데이트: '+id);
633
+ }
634
+ function planProgress(root){
635
+ root=path.resolve(root||process.cwd()); const body=readPlanText(root); const rows=[];
636
+ for(const line of body.split(/\r?\n/)){ if(/^\|\s*M-/.test(line)){ const c=splitTableLine(line); if(c.length>=6) rows.push({id:c[0],milestone:c[1],status:c[2],progress:c[3],tasks:c[4],notes:c[5]}); } }
637
+ banner(); log('Plan progress: '+root); if(!rows.length){ warn('계획 milestone이 없습니다. leerness plan add 를 사용하세요.'); return; }
638
+ rows.forEach(r=>log(` - ${r.id} [${r.status}] ${r.progress} ${r.milestone} :: tasks=${r.tasks}`));
639
+ }
640
+ function planSync(root){
641
+ root=path.resolve(root||process.cwd()); ensurePlan(root); const tasks=readProgressTasks(root);
642
+ const active=tasks.filter(t=>ACTIVE_TASK_STATUSES.has(t.status));
643
+ const done=tasks.filter(t=>t.status==='done').length; const total=tasks.filter(t=>t.status!=='dropped').length; const pct=total?Math.round(done/total*100):0;
644
+ const cs=path.join(root,'.harness/current-state.md');
645
+ if(exists(cs)){
646
+ let body=read(cs); const block=`\n## Plan Sync\n- Updated: ${today()}\n- Non-dropped task progress: ${done}/${total} (${pct}%)\n- Active unresolved tasks: ${active.length}\n- Source: plan.md + progress-tracker.md\n`;
647
+ if(body.includes('## Plan Sync')) body=body.replace(/\n## Plan Sync[\s\S]*?(?=\n## |$)/, block);
648
+ else body=body.replace(/\s*$/, block+'\n');
649
+ write(cs,body); ok('current-state.md plan sync 갱신');
650
+ }
651
+ ok('plan/progress sync 완료: '+done+'/'+total+' = '+pct+'%');
652
+ }
653
+ function planCommand(args,flags){
654
+ const sub=args[1]||'show'; const root=path.resolve(flags.path||((sub==='show'||sub==='progress'||sub==='sync'||sub==='init')?args[2]:process.cwd())||process.cwd());
655
+ if(sub==='show'){ log(readPlanText(root)); return; }
656
+ if(sub==='init'){ ensurePlan(root); if(flags.goal){ planAdd(root,String(flags.goal),{status:'planned',reason:'initial project goal',by:'user'}); } ok('plan.md 준비 완료'); return; }
657
+ if(sub==='add'){ const text=args[2]||flags.title||flags.name; if(!text){ fail('추가할 계획 내용을 입력하세요.'); process.exitCode=1; return; } return planAdd(root,text,flags); }
658
+ if(sub==='drop'||sub==='remove'){ const item=args[2]||flags.title||flags.name; if(!item){ fail('드랍할 계획 항목을 입력하세요.'); process.exitCode=1; return; } return planDrop(root,item,flags); }
659
+ if(sub==='update'){ const id=args[2]; if(!id){ fail('업데이트할 계획 ID를 입력하세요.'); process.exitCode=1; return; } return planUpdate(root,id,flags); }
660
+ if(sub==='progress') return planProgress(root);
661
+ if(sub==='sync') return planSync(root);
662
+ fail('알 수 없는 plan 명령: '+sub);
663
+ }
664
+
665
+
451
666
  const ACTIVE_TASK_STATUSES = new Set(['requested','planned','in-progress','waiting','on-hold','blocked','incomplete']);
452
667
  const ALL_TASK_STATUSES = new Set(['requested','planned','in-progress','waiting','on-hold','blocked','incomplete','done','dropped']);
453
668
  function progressFile(root){ return path.join(root,'.harness/progress-tracker.md'); }
@@ -513,15 +728,20 @@ function taskCommand(args,flags){
513
728
  }
514
729
  function debugHarness(root){
515
730
  root=path.resolve(root||process.cwd()); banner(); let failures=0,warnings=0;
516
- const required=['AGENTS.md','.harness/language-policy.md','.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md','.harness/session-close-policy.md','.harness/progress-tracker.md','.harness/anti-lazy-work-policy.md','.harness/debug-guide.md'];
731
+ const required=['AGENTS.md','.harness/plan.md','.harness/guideline.md','.harness/language-policy.md','.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md','.harness/session-close-policy.md','.harness/progress-tracker.md','.harness/anti-lazy-work-policy.md','.harness/debug-guide.md'];
517
732
  for(const f of required){ if(exists(path.join(root,f))) ok('존재: '+f); else { failures++; fail('누락: '+f); } }
518
733
  const ag=path.join(root,'AGENTS.md');
519
734
  if(exists(ag)){
520
735
  const body=read(ag);
521
- for(const term of ['language-policy.md','context-routing.md','writeback-policy.md','progress-tracker.md','anti-lazy-work-policy.md','End-of-Session Contract']){
736
+ for(const term of ['plan.md','guideline.md','language-policy.md','context-routing.md','writeback-policy.md','progress-tracker.md','anti-lazy-work-policy.md','End-of-Session Contract']){
522
737
  if(body.includes(term)) ok('AGENTS 방향지시 포함: '+term); else { failures++; fail('AGENTS 방향지시 누락: '+term); }
523
738
  }
524
739
  }
740
+
741
+ const pf=path.join(root,'.harness/plan.md');
742
+ if(exists(pf)){ const b=read(pf); if(b.includes('## Milestones')) ok('plan.md milestones 확인'); else { warnings++; warn('plan.md에 Milestones 섹션이 없습니다.'); } }
743
+ const gf=path.join(root,'.harness/guideline.md');
744
+ if(exists(gf)){ const b=read(gf); if(b.includes('plan.md')&&b.includes('progress-tracker.md')) ok('guideline.md가 plan/progress를 참조합니다.'); else { failures++; fail('guideline.md가 plan.md/progress-tracker.md를 참조하지 않습니다.'); } }
525
745
  const mf=path.join(root,'.harness/manifest.json');
526
746
  if(exists(mf)){ const m=parseJsonSafe(read(mf),{}); if(m.language) ok('언어 설정: '+m.language+' ('+(m.languageName||languageName(m.language))+')'); else { warnings++; warn('manifest language 누락'); } }
527
747
  const tasks=readProgressTasks(root);
@@ -542,10 +762,10 @@ function closeSession(root){
542
762
  log('Session close checklist');
543
763
  log('');
544
764
  log('Read before closing:');
545
- ['.harness/session-close-policy.md','.harness/progress-tracker.md','.harness/current-state.md','.harness/task-log.md','.harness/session-handoff.md','.harness/anti-lazy-work-policy.md'].forEach(x=>log(' - '+x));
765
+ ['.harness/session-close-policy.md','.harness/plan.md','.harness/guideline.md','.harness/progress-tracker.md','.harness/current-state.md','.harness/task-log.md','.harness/session-handoff.md','.harness/anti-lazy-work-policy.md'].forEach(x=>log(' - '+x));
546
766
  log('');
547
767
  log('Required final report sections:');
548
- ['Completed This Session','In Progress From User Requests','Incomplete / Not Started From User Requests','Planned Tasks','Waiting Tasks','On-Hold Tasks','Blocked Tasks','Dropped By User','Verification','Files Changed','Memory Files Updated','Risks / Assumptions / Blockers','Recommended Next Directions','Next Exact Step'].forEach(x=>log(' - '+x));
768
+ ['Plan Progress Summary','Completed This Session','In Progress From User Requests','Incomplete / Not Started From User Requests','Planned Tasks','Waiting Tasks','On-Hold Tasks','Blocked Tasks','Dropped By User','Verification','Files Changed','Memory Files Updated','Risks / Assumptions / Blockers','Recommended Next Directions','Next Exact Step'].forEach(x=>log(' - '+x));
549
769
  log('');
550
770
  if(exists(template)) log(read(template));
551
771
  else log('# End-of-Session Report\n\n## Completed This Session\n-\n\n## In Progress From User Requests\n-\n\n## Incomplete / Not Started From User Requests\n-\n\n## Planned Tasks\n-\n\n## Waiting Tasks\n-\n\n## On-Hold Tasks\n-\n\n## Blocked Tasks\n-\n\n## Dropped By User\n-\n\n## Verification\n-\n\n## Files Changed\n-\n\n## Memory Files Updated\n-\n\n## Risks / Assumptions / Blockers\n-\n\n## Recommended Next Directions\n-\n\n## Next Exact Step\n-\n');
@@ -563,9 +783,9 @@ function closeSession(root){
563
783
  }
564
784
  function sessionCommand(args){ const sub=args[1]||'close'; if(sub==='close'||sub==='handoff'||sub==='end') return closeSession(args[2]||process.cwd()); fail('알 수 없는 session 명령: '+sub); }
565
785
 
566
- const routeData={feature:{read:['project-brief.md','current-state.md','architecture.md','context-map.md','feature-contracts.md'],update:['current-state.md','task-log.md','session-handoff.md','feature-contracts.md']},ui:{read:['design-system.md','feature-contracts.md','context-map.md'],update:['design-system.md','feature-contracts.md','current-state.md','task-log.md']},debugging:{read:['current-state.md','task-log.md','feature-contracts.md','testing-strategy.md'],update:['task-log.md','current-state.md','session-handoff.md']},refactor:{read:['architecture.md','decisions.md','guardrails.md'],update:['architecture.md','decisions.md','task-log.md']},release:{read:['release-checklist.md','testing-strategy.md','current-state.md','decisions.md','secret-policy.md'],update:['release-checklist.md','task-log.md','current-state.md','session-handoff.md']},migration:{read:['AX_MIGRATION_GUIDE.md','context-routing.md','writeback-policy.md'],update:['Only missing files by default','Use --force only when requested']},'new-install':{read:['AX_NEW_PROJECT_GUIDE.md','actual project files'],update:['project-brief.md','architecture.md','context-map.md','design-system.md','feature-contracts.md','release-checklist.md']},'skill-library':{read:['AX_SKILL_LIBRARY_GUIDE.md','skill-index.md','secret-policy.md'],update:['skill-index.md','skills-lock.json','task-log.md']},documentation:{read:['writeback-policy.md','context-routing.md'],update:['specific memory file','task-log.md','session-handoff.md']},debug:{read:['debug-guide.md','AGENTS.md','language-policy.md','context-routing.md','writeback-policy.md','progress-tracker.md','session-close-policy.md'],update:['debug findings in task-log.md','progress-tracker.md when user-requested debug work changes']},'session-close':{read:['session-close-policy.md','progress-tracker.md','current-state.md','task-log.md','session-handoff.md','anti-lazy-work-policy.md'],update:['session-handoff.md','progress-tracker.md','current-state.md','task-log.md','relevant changed memory files']}};
786
+ const routeData={planning:{read:['plan.md','progress-tracker.md','project-brief.md','current-state.md','guideline.md'],update:['plan.md','progress-tracker.md','current-state.md','session-handoff.md']},feature:{read:['plan.md','progress-tracker.md','project-brief.md','current-state.md','architecture.md','context-map.md','feature-contracts.md'],update:['current-state.md','task-log.md','session-handoff.md','feature-contracts.md']},ui:{read:['design-system.md','feature-contracts.md','context-map.md'],update:['design-system.md','feature-contracts.md','current-state.md','task-log.md']},debugging:{read:['current-state.md','task-log.md','feature-contracts.md','testing-strategy.md'],update:['task-log.md','current-state.md','session-handoff.md']},refactor:{read:['architecture.md','decisions.md','guardrails.md'],update:['architecture.md','decisions.md','task-log.md']},release:{read:['plan.md','progress-tracker.md','release-checklist.md','testing-strategy.md','current-state.md','decisions.md','secret-policy.md'],update:['release-checklist.md','task-log.md','current-state.md','session-handoff.md']},migration:{read:['AX_MIGRATION_GUIDE.md','context-routing.md','writeback-policy.md'],update:['Only missing files by default','Use --force only when requested']},'new-install':{read:['AX_NEW_PROJECT_GUIDE.md','AX_PLAN_GUIDE.md','actual project files'],update:['plan.md','project-brief.md','architecture.md','context-map.md','design-system.md','feature-contracts.md','release-checklist.md']},'skill-library':{read:['AX_SKILL_LIBRARY_GUIDE.md','skill-index.md','secret-policy.md'],update:['skill-index.md','skills-lock.json','task-log.md']},documentation:{read:['writeback-policy.md','context-routing.md'],update:['specific memory file','task-log.md','session-handoff.md']},debug:{read:['debug-guide.md','AGENTS.md','plan.md','guideline.md','language-policy.md','context-routing.md','writeback-policy.md','progress-tracker.md','session-close-policy.md'],update:['debug findings in task-log.md','progress-tracker.md when user-requested debug work changes']},'session-close':{read:['session-close-policy.md','plan.md','guideline.md','progress-tracker.md','current-state.md','task-log.md','session-handoff.md','anti-lazy-work-policy.md'],update:['session-handoff.md','progress-tracker.md','current-state.md','task-log.md','relevant changed memory files']}};
567
787
  function routePath(x){ if(String(x).includes('/')||String(x).endsWith('.md')&&['AGENTS.md','CLAUDE.md','README.md'].includes(String(x))) return String(x); if(String(x).includes(' ')) return String(x); return '.harness/'+x; }
568
788
  function routeCommand(task){ banner(); if(!task||task==='list'){ log('사용 가능한 task type: '+Object.keys(routeData).join(', ')); return; } const r=routeData[task]; if(!r){ fail('알 수 없는 task type: '+task); process.exitCode=1; return; } log('Task route: '+task); log('\nRead before work:'); r.read.forEach(x=>log(' - '+routePath(x))); log('\nUpdate after work:'); r.update.forEach(x=>log(' - '+routePath(x))); }
569
- function help(){ log(['Leerness v'+VERSION,'','Usage:',' leerness init [path] [--yes] [--language auto|ko|en] [--skills recommended|all|office,commerce-api] [--force]',' leerness migrate [path] [--dry-run] [--language auto|ko|en] [--force]',' leerness status [path]',' leerness verify [path]',' leerness debug [path]',' leerness route <feature|ui|debugging|refactor|release|migration|new-install|skill-library|documentation|debug|session-close>','','Tasks:',' leerness task list [--status planned,waiting,on-hold]',' leerness task add "request" [--status planned]',' leerness task update T-0002 --status in-progress',' leerness task drop T-0002 --reason "user dropped"','','Session:',' leerness session close [path]','','Skills:',' leerness skill list',' leerness skill info <name>',' leerness skill add <name> [--path <project>]',' leerness skill remove <name> [--path <project>]',' leerness skill learn <name> --from <validated-skill-path>','','Skill library:',' leerness library guide [path]',' leerness library validate <path> [--strict-ai]',' leerness library verify <path> --ai --reviewer leerness-ai',' leerness library build <path> [--package leerness-skill-name]',' leerness library publish <built-library> --target npm|git [--execute]',''].join('\n')); }
570
- async function main(){ const parsed=parseArgs(process.argv.slice(2)); const args=parsed.positionals, flags=parsed.flags; if(flags.version||flags.v){ log(VERSION); return; } if(flags.help||flags.h){ help(); return; } const cmd=args[0]||'init'; if(cmd==='init') return init(args[1]||process.cwd(),flags); if(cmd==='migrate') return migrate(args[1]||process.cwd(),flags); if(cmd==='status') return status(args[1]||process.cwd()); if(cmd==='verify') return verify(args[1]||process.cwd()); if(cmd==='route') return routeCommand(args[1]||'list'); if(cmd==='debug') return debugHarness(args[1]||process.cwd()); if(cmd==='task') return taskCommand(args,flags); if(cmd==='session') return sessionCommand(args); if(cmd==='skill') return skillCommand(args,flags); if(cmd==='library') return libraryCommand(args,flags); help(); process.exitCode=1; }
789
+ function help(){ log(['Leerness v'+VERSION,'','Usage:',' leerness init [path] [--yes] [--language auto|ko|en] [--skills recommended|all|office,commerce-api] [--force]',' leerness migrate [path] [--dry-run] [--language auto|ko|en] [--force]',' leerness status [path]',' leerness verify [path]',' leerness debug [path]',' leerness route <planning|feature|ui|debugging|refactor|release|migration|new-install|skill-library|documentation|debug|session-close>','','Plan:',' leerness plan show [path]',' leerness plan init [path] --goal "project goal"',' leerness plan add "milestone or scope" [--status planned]',' leerness plan drop "item" --reason "user excluded"',' leerness plan update M-0002 --status in-progress --progress 40',' leerness plan progress [path]',' leerness plan sync [path]','','Tasks:',' leerness task list [--status planned,waiting,on-hold]',' leerness task add "request" [--status planned]',' leerness task update T-0002 --status in-progress',' leerness task drop T-0002 --reason "user dropped"','','Session:',' leerness session close [path]','','Skills:',' leerness skill list',' leerness skill info <name>',' leerness skill add <name> [--path <project>]',' leerness skill remove <name> [--path <project>]',' leerness skill learn <name> --from <validated-skill-path>','','Skill library:',' leerness library guide [path]',' leerness library validate <path> [--strict-ai]',' leerness library verify <path> --ai --reviewer leerness-ai',' leerness library build <path> [--package leerness-skill-name]',' leerness library publish <built-library> --target npm|git [--execute]',''].join('\n')); }
790
+ async function main(){ const parsed=parseArgs(process.argv.slice(2)); const args=parsed.positionals, flags=parsed.flags; if(flags.version||flags.v){ log(VERSION); return; } if(flags.help||flags.h){ help(); return; } const cmd=args[0]||'init'; if(cmd==='init') return init(args[1]||process.cwd(),flags); if(cmd==='migrate') return migrate(args[1]||process.cwd(),flags); if(cmd==='status') return status(args[1]||process.cwd()); if(cmd==='verify') return verify(args[1]||process.cwd()); if(cmd==='route') return routeCommand(args[1]||'list'); if(cmd==='debug') return debugHarness(args[1]||process.cwd()); if(cmd==='plan') return planCommand(args,flags); if(cmd==='task') return taskCommand(args,flags); if(cmd==='session') return sessionCommand(args); if(cmd==='skill') return skillCommand(args,flags); if(cmd==='library') return libraryCommand(args,flags); help(); process.exitCode=1; }
571
791
  main().catch(err=>{ fail(err.stack||err.message); process.exit(1); });
@@ -0,0 +1,17 @@
1
+ # AX Plan Guide
2
+
3
+ Leerness uses `.harness/plan.md` as the high-level project plan and `.harness/progress-tracker.md` as the concrete task status database.
4
+
5
+ ## When to read
6
+ - Before feature, release, refactor, migration, or broad implementation work
7
+ - When a user adds, excludes, reprioritizes, or changes scope
8
+ - At session close
9
+
10
+ ## When to update
11
+ - New user-requested scope appears
12
+ - A milestone changes status or progress
13
+ - A user drops or excludes work
14
+ - A task moves between planned, in-progress, waiting, on-hold, blocked, incomplete, done, or dropped
15
+
16
+ ## Rule
17
+ `guideline.md` defines how the plan is followed. It should reference `plan.md` and `progress-tracker.md`, but it should not be the primary place for detailed task progress.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.4.0",
4
- "description": "Leerness: 언어 정책, 진행 작업 드랍, 세션 종료 미완료 목록, 라우팅 디버그를 지원하는 AX 최적화 개발 하네스.",
3
+ "version": "1.5.0",
4
+ "description": "Leerness: plan.md 기반 계획 수립/수정, progress-tracker 진행률 추적, guideline 참조 정책, 세션 인수인계와 AX 라우팅을 지원하는 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
7
7
  "ai",
@@ -35,7 +35,15 @@
35
35
  "language-policy",
36
36
  "auto-language-detection",
37
37
  "task-drop",
38
- "harness-debug"
38
+ "harness-debug",
39
+ "plan-md",
40
+ "project-plan",
41
+ "plan-orchestration",
42
+ "plan-progress",
43
+ "guideline-policy",
44
+ "planning-router",
45
+ "project-roadmap",
46
+ "scope-drop"
39
47
  ],
40
48
  "bin": {
41
49
  "leerness": "./bin/harness.js"
@@ -49,7 +57,7 @@
49
57
  "LICENSE"
50
58
  ],
51
59
  "scripts": {
52
- "test": "node ./bin/harness.js --help && node ./bin/harness.js init --yes --language ko --skills office,commerce-api,ai-verified-skill-publisher ./tmp-harness-test && node ./bin/harness.js task add \"테스트 예정 작업\" --status planned --path ./tmp-harness-test && node ./bin/harness.js task drop T-0002 --reason \"테스트 드랍\" --path ./tmp-harness-test && node ./bin/harness.js route debug && node ./bin/harness.js session close ./tmp-harness-test && node ./bin/harness.js debug ./tmp-harness-test && node ./bin/harness.js verify ./tmp-harness-test",
60
+ "test": "node ./bin/harness.js --help && node ./bin/harness.js init --yes --language ko --skills office,commerce-api,ai-verified-skill-publisher ./tmp-harness-test && node ./bin/harness.js plan add \"테스트 계획 항목\" --status planned --path ./tmp-harness-test && node ./bin/harness.js plan progress ./tmp-harness-test && node ./bin/harness.js plan drop \"테스트 계획 항목\" --reason \"테스트 제외\" --path ./tmp-harness-test && node ./bin/harness.js plan sync ./tmp-harness-test && node ./bin/harness.js task add \"테스트 예정 작업\" --status planned --path ./tmp-harness-test && node ./bin/harness.js route planning && node ./bin/harness.js session close ./tmp-harness-test && node ./bin/harness.js debug ./tmp-harness-test && node ./bin/harness.js verify ./tmp-harness-test",
53
61
  "prepack": "node ./bin/harness.js --version"
54
62
  },
55
63
  "repository": {