@walwal-harness/cli 1.3.0 → 2.0.1

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
@@ -135,7 +135,7 @@ your-project/
135
135
  ├── CLAUDE.md → AGENTS.md # 심볼릭 링크
136
136
  ├── .harness/
137
137
  │ ├── config.json # 하네스 설정
138
- │ ├── progress.txt # 세션 간 상태 전달
138
+ │ ├── progress.json # 세션 간 상태 (기계 판독)
139
139
  │ ├── gotchas/ # 에이전트별 실수 기록
140
140
  │ │ ├── planner.md
141
141
  │ │ ├── generator-backend.md
@@ -38,7 +38,7 @@
38
38
  | AGENTS.md | 전체 | Planner만 |
39
39
  | .harness/actions/api-contract.json | 전체 | Planner만 |
40
40
  | .harness/actions/feature-list.json | 전체 | passes: Generator, 나머지: Planner |
41
- | .harness/progress.txt | 전체 | 전체 (세션 종료 시 업데이트 의무) |
41
+ | .harness/progress.json | 전체 | 전체 (Session Boundary Protocol에 따라 업데이트) |
42
42
  | `[BE]` 소유 경로 | 전체 | Generator-Backend만 |
43
43
  | `[FE]` 소유 경로 | 전체 | Generator-Frontend만 |
44
44
  | .harness/archive/ | 전체 | 쓰기 금지 (불변) |
@@ -10,14 +10,9 @@
10
10
  .harness/
11
11
  ├── HARNESS.md # 이 파일
12
12
  ├── config.json # 하네스 설정
13
- ├── progress.txt # 세션 간 상태 전달 로그
14
- ├── prompts/ # 에이전트 프롬프트 (6개)
15
- │ ├── dispatcher.md # Dispatcher — 파이프라인 선택
16
- │ ├── planner.md # Planner — 제품 사양 + MSA 설계
17
- │ ├── generator-backend.md # Generator-BE — NestJS MSA 구현
18
- │ ├── generator-frontend.md # Generator-FE — React/Next.js 구현
19
- │ ├── evaluator-functional.md # Evaluator-기능 — Playwright E2E
20
- │ └── evaluator-visual.md # Evaluator-비주얼 — 디자인/접근성
13
+ ├── progress.json # 기계 판독 상태 (세션 오케스트레이션)
14
+ ├── progress.log # 사람 판독 히스토리 (append-only)
15
+ ├── next-prompt.txt # 다음 세션용 프롬프트 (claude CLI 파이프용)
21
16
  ├── actions/ # 현재 활성 문서
22
17
  │ ├── pipeline.json # Dispatcher 결정 (어떤 파이프라인인지)
23
18
  │ ├── plan.md # 제품 사양
@@ -79,55 +74,60 @@ Eval-Visual FAIL → Gen-FE 재작업 (max 10회)
79
74
  10회 초과 → 사용자 개입 요청
80
75
  ```
81
76
 
82
- ## 수동 프롬프트 실행 방법
77
+ ## 세션 오케스트레이션
83
78
 
84
- ### Step 0: Dispatcher (항상 최초 실행)
85
- ```
86
- ".harness/prompts/dispatcher.md를 읽고 Dispatcher로 동작하세요.
87
- [사용자의 자유 형식 요청]"
88
- ```
79
+ ### 핵심: 한 세션에 1 에이전트 단계
89
80
 
90
- ### Step 1: Planner
91
- ```
92
- ".harness/prompts/planner.md를 읽고 Planner 에이전트로 동작하세요.
93
- pipeline.json을 참조하여 [light/full] 모드로 실행합니다."
94
- ```
81
+ 각 에이전트는 독립 Claude Code 세션에서 실행됩니다. 컨텍스트 소진을 방지하고 품질을 유지합니다.
95
82
 
96
- ### Step 2: Generator-Backend (FULLSTACK, BE-ONLY)
97
- ```
98
- ".harness/prompts/generator-backend.md를 읽고 Generator-Backend으로 동작하세요.
99
- Sprint [N]을 시작합니다."
100
- ```
83
+ ### 상태 관리
101
84
 
102
- ### Step 3: Generator-Frontend (FULLSTACK, FE-ONLY)
103
- ```
104
- ".harness/prompts/generator-frontend.md를 읽고 Generator-Frontend으로 동작하세요.
105
- Sprint [N]의 프론트엔드를 구현합니다."
106
- ```
85
+ | 파일 | 역할 |
86
+ |------|------|
87
+ | `.harness/progress.json` | 기계 판독 상태 (현재 에이전트, 파이프라인, 실패 정보) |
88
+ | `.harness/progress.log` | 사람 판독 히스토리 (append-only) |
89
+ | `.harness/next-prompt.txt` | 다음 세션용 프롬프트 (claude CLI 파이프용) |
107
90
 
108
- ### Step 4: Evaluator-Functional
109
- ```
110
- ".harness/prompts/evaluator-functional.md를 읽고 Evaluator-Functional로 동작하세요.
111
- Sprint [N]을 평가합니다. Playwright MCP를 사용하세요."
112
- ```
91
+ ### 실행 방법
113
92
 
114
- ### Step 5: Evaluator-Visual
93
+ #### 1. 첫 세션: Dispatcher
115
94
  ```
116
- ".harness/prompts/evaluator-visual.md를 읽고 Evaluator-Visual로 동작하세요.
117
- Sprint [N]의 비주얼을 평가합니다."
95
+ "하네스 엔지니어링 시작" 또는 /harness-dispatcher
118
96
  ```
119
97
 
120
- ### Archive (Eval-Visual PASS 후)
98
+ #### 2. 이후 세션: harness-next.sh로 안내받기
99
+ ```bash
100
+ bash scripts/harness-next.sh
121
101
  ```
122
- "Sprint [N] 문서를 아카이브 하세요.
123
- .harness/actions/의 스프린트 문서를 .harness/archive/sprint-NNN/으로 이동합니다."
102
+ Feature-level 프로그래스를 출력하고 다음 에이전트를 안내합니다.
103
+
104
+ #### 3. 다음 세션 시작
105
+ ```bash
106
+ # 방법 A: 새 세션에서 스킬 직접 호출
107
+ /harness-generator-backend
108
+
109
+ # 방법 B: claude CLI 자동 실행
110
+ claude --prompt "$(cat .harness/next-prompt.txt)"
124
111
  ```
125
112
 
113
+ #### 4. SessionStart 훅 (자동)
114
+ 세션 시작 시 `.claude/settings.json` 훅이 자동으로 현재 프로그래스를 출력합니다.
115
+
116
+ ### Session Boundary Protocol
117
+
118
+ 모든 에이전트 스킬에 내장된 프로토콜:
119
+
120
+ - **On Start**: `progress.json` 읽기 → `agent_status: "running"` 설정
121
+ - **On Complete**: `progress.json` 업데이트 → `next_agent` 계산 → **STOP**
122
+ - **On Fail** (Evaluator): `failure` 정보 기록 → `retry_target` 설정 → **STOP**
123
+
124
+ 에이전트는 절대 다음 에이전트를 직접 호출하지 않습니다.
125
+
126
126
  ## 핵심 원칙
127
127
 
128
128
  1. **Backend First** — API가 안정된 후 Frontend 연동 (없는 API 호출 방지)
129
129
  2. **api-contract.json이 진실의 원천** — FE↔Gateway↔Services 간 유일한 계약
130
- 3. **한 세션에 1 스프린트** — 컨텍스트 소진 방지
130
+ 3. **한 세션에 1 에이전트 단계** — 컨텍스트 소진 방지, Session Boundary Protocol 준수
131
131
  4. **feature-list.json의 passes만 수정** — 기능 정의는 Planner만 변경
132
132
  5. **테스트 삭제/약화 금지** — 테스트는 계약이다
133
133
  6. **Evaluator는 회의적** — 자기 설득 금지, 기준 미달 = FAIL
@@ -8,54 +8,46 @@
8
8
  "agents": {
9
9
  "dispatcher": {
10
10
  "role": "사용자 요청 분석 → 파이프라인 결정 (FE-ONLY / BE-ONLY / FULLSTACK)",
11
- "prompt_file": "prompts/dispatcher.md",
11
+ "skill": "harness-dispatcher",
12
12
  "outputs": ["actions/pipeline.json"],
13
- "triggers_next": "planner",
14
13
  "always_first": true
15
14
  },
16
15
  "planner": {
17
16
  "role": "제품 사양 + API 계약서 + 서비스 분할 설계",
18
- "prompt_file": "prompts/planner.md",
17
+ "skill": "harness-planner",
19
18
  "outputs": [
20
19
  "actions/plan.md",
21
20
  "actions/feature-list.json",
22
21
  "actions/api-contract.json"
23
- ],
24
- "triggers_next": ["generator-backend", "generator-frontend"]
22
+ ]
25
23
  },
26
24
  "generator-backend": {
27
25
  "role": "NestJS MSA 서비스 구현, API 엔드포인트, DB 스키마",
28
- "prompt_file": "prompts/generator-backend.md",
26
+ "skill": "harness-generator-backend",
29
27
  "inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
30
28
  "outputs": ["code:backend", "actions/sprint-contract.md"],
31
- "triggers_next": "generator-frontend",
32
29
  "order": 1
33
30
  },
34
31
  "generator-frontend": {
35
32
  "role": "React/Next.js UI 컴포넌트, 상태관리, API 연동",
36
- "prompt_file": "prompts/generator-frontend.md",
33
+ "skill": "harness-generator-frontend",
37
34
  "inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
38
35
  "outputs": ["code:frontend", "actions/sprint-contract.md"],
39
- "triggers_next": "evaluator-functional",
40
36
  "order": 2
41
37
  },
42
38
  "evaluator-functional": {
43
39
  "role": "Playwright로 E2E 기능 검증, API 응답/DB 상태 확인",
44
- "prompt_file": "prompts/evaluator-functional.md",
40
+ "skill": "harness-evaluator-functional",
45
41
  "tools": ["playwright:browser_*"],
46
42
  "inputs": ["actions/sprint-contract.md"],
47
- "outputs": ["actions/evaluation-functional.md"],
48
- "on_fail": "generator-backend OR generator-frontend (결함 위치에 따라)",
49
- "on_pass": "evaluator-visual"
43
+ "outputs": ["actions/evaluation-functional.md"]
50
44
  },
51
45
  "evaluator-visual": {
52
46
  "role": "디자인 일관성, 반응형, 접근성, AI슬롭 감지",
53
- "prompt_file": "prompts/evaluator-visual.md",
47
+ "skill": "harness-evaluator-visual",
54
48
  "tools": ["playwright:browser_take_screenshot", "playwright:browser_resize", "playwright:browser_snapshot"],
55
49
  "inputs": ["actions/sprint-contract.md", "actions/evaluation-functional.md"],
56
- "outputs": ["actions/evaluation-visual.md"],
57
- "on_fail": "generator-frontend",
58
- "on_pass": "archive_and_next_sprint"
50
+ "outputs": ["actions/evaluation-visual.md"]
59
51
  }
60
52
  },
61
53
  "flow": {
@@ -77,12 +69,18 @@
77
69
  "max_retries_per_sprint": 10,
78
70
  "archive_on_sprint_complete": true
79
71
  },
72
+ "session": {
73
+ "isolation": true,
74
+ "state_file": ".harness/progress.json",
75
+ "state_log": ".harness/progress.log",
76
+ "next_prompt_file": ".harness/next-prompt.txt"
77
+ },
80
78
  "paths": {
81
79
  "actions": ".harness/actions",
82
80
  "archive": ".harness/archive",
83
- "prompts": ".harness/prompts",
84
81
  "gotchas": ".harness/gotchas",
85
- "progress": ".harness/progress.txt"
82
+ "progress_state": ".harness/progress.json",
83
+ "progress_log": ".harness/progress.log"
86
84
  },
87
85
  "recommended_skills": {
88
86
  "comment": "Claude Code에 설치하면 하네스 품질이 향상되는 외부 스킬 목록",
@@ -0,0 +1,22 @@
1
+ {
2
+ "version": 1,
3
+ "project_name": "",
4
+ "pipeline": null,
5
+ "sprint": {
6
+ "number": 0,
7
+ "status": "init",
8
+ "retry_count": 0
9
+ },
10
+ "current_agent": null,
11
+ "agent_status": "pending",
12
+ "completed_agents": [],
13
+ "next_agent": "dispatcher",
14
+ "failure": {
15
+ "agent": null,
16
+ "location": null,
17
+ "message": null,
18
+ "retry_target": null
19
+ },
20
+ "updated_at": "{{DATE}}",
21
+ "history": []
22
+ }
package/bin/init.js CHANGED
@@ -112,7 +112,7 @@ function scaffoldHarness() {
112
112
 
113
113
  // Copy templates as initial files
114
114
  const templateMap = {
115
- 'progress.txt.template': path.join(HARNESS_DIR, 'progress.txt'),
115
+ 'progress.json.template': path.join(HARNESS_DIR, 'progress.json'),
116
116
  };
117
117
 
118
118
  const templatesDir = path.join(PKG_ROOT, 'assets', 'templates');
@@ -141,6 +141,19 @@ function scaffoldHarness() {
141
141
  copyFile(harnessMdSrc, harnessMdDest);
142
142
  }
143
143
 
144
+ // Create progress.log
145
+ const progressLog = path.join(HARNESS_DIR, 'progress.log');
146
+ if (!fileExists(progressLog) || isForce) {
147
+ const date = new Date().toISOString().split('T')[0];
148
+ fs.writeFileSync(progressLog, `# Harness Progress Log\n# ${date} — Initialized\n`);
149
+ }
150
+
151
+ // Create next-prompt.txt placeholder
152
+ const nextPrompt = path.join(HARNESS_DIR, 'next-prompt.txt');
153
+ if (!fileExists(nextPrompt) || isForce) {
154
+ fs.writeFileSync(nextPrompt, '');
155
+ }
156
+
144
157
  log('.harness/ scaffolding complete');
145
158
  }
146
159
 
@@ -186,12 +199,29 @@ function installScripts() {
186
199
 
187
200
  if (fs.existsSync(scriptsSrc)) {
188
201
  ensureDir(scriptsDest);
189
- const files = fs.readdirSync(scriptsSrc);
190
- for (const file of files) {
191
- const dest = path.join(scriptsDest, file);
192
- if (!fileExists(dest) || isForce) {
193
- copyFile(path.join(scriptsSrc, file), dest);
194
- try { fs.chmodSync(dest, '755'); } catch (e) {}
202
+ const entries = fs.readdirSync(scriptsSrc, { withFileTypes: true });
203
+ for (const entry of entries) {
204
+ const srcPath = path.join(scriptsSrc, entry.name);
205
+ const destPath = path.join(scriptsDest, entry.name);
206
+ if (entry.isDirectory()) {
207
+ // Copy subdirectories (e.g., lib/)
208
+ if (!fileExists(destPath) || isForce) {
209
+ copyDir(srcPath, destPath);
210
+ // Make scripts in subdirs executable
211
+ try {
212
+ const subFiles = fs.readdirSync(destPath);
213
+ for (const f of subFiles) {
214
+ if (f.endsWith('.sh')) {
215
+ fs.chmodSync(path.join(destPath, f), '755');
216
+ }
217
+ }
218
+ } catch (e) {}
219
+ }
220
+ } else {
221
+ if (!fileExists(destPath) || isForce) {
222
+ copyFile(srcPath, destPath);
223
+ try { fs.chmodSync(destPath, '755'); } catch (e) {}
224
+ }
195
225
  }
196
226
  }
197
227
  }
@@ -199,6 +229,72 @@ function installScripts() {
199
229
  log('Scripts installation complete');
200
230
  }
201
231
 
232
+ // ─────────────────────────────────────────
233
+ // 3b. SessionStart hook
234
+ // ─────────────────────────────────────────
235
+ function installSessionHook() {
236
+ log('Installing SessionStart hook...');
237
+
238
+ const settingsDir = path.join(PROJECT_ROOT, '.claude');
239
+ const settingsFile = path.join(settingsDir, 'settings.json');
240
+
241
+ ensureDir(settingsDir);
242
+
243
+ let settings = {};
244
+ if (fileExists(settingsFile)) {
245
+ try {
246
+ settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
247
+ } catch (e) {
248
+ log('WARNING: Could not parse existing .claude/settings.json, creating new');
249
+ }
250
+ }
251
+
252
+ // Ensure hooks.SessionStart array exists
253
+ if (!settings.hooks) settings.hooks = {};
254
+ if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
255
+
256
+ // Migrate any legacy flat entries (created by older walwal-harness versions)
257
+ // into the correct matcher + hooks-array shape that Claude Code expects.
258
+ const hookCmd = 'bash scripts/harness-session-start.sh';
259
+ let migrated = false;
260
+ settings.hooks.SessionStart = settings.hooks.SessionStart
261
+ .map((entry) => {
262
+ if (entry && typeof entry === 'object' && Array.isArray(entry.hooks)) {
263
+ return entry; // already in correct shape
264
+ }
265
+ if (entry && typeof entry === 'object' && entry.type === 'command' && entry.command) {
266
+ migrated = true;
267
+ return { matcher: '', hooks: [{ type: entry.type, command: entry.command }] };
268
+ }
269
+ return entry;
270
+ })
271
+ .filter(Boolean);
272
+
273
+ // Check if our hook is already installed (inside any matcher group)
274
+ const alreadyInstalled = settings.hooks.SessionStart.some(
275
+ (entry) =>
276
+ entry &&
277
+ Array.isArray(entry.hooks) &&
278
+ entry.hooks.some(
279
+ (h) => h && h.command && h.command.includes('harness-session-start')
280
+ )
281
+ );
282
+
283
+ if (!alreadyInstalled) {
284
+ settings.hooks.SessionStart.push({
285
+ matcher: '',
286
+ hooks: [{ type: 'command', command: hookCmd }]
287
+ });
288
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
289
+ log('SessionStart hook installed in .claude/settings.json');
290
+ } else if (migrated) {
291
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
292
+ log('SessionStart hook migrated to matcher + hooks-array format');
293
+ } else {
294
+ log('SessionStart hook already installed');
295
+ }
296
+ }
297
+
202
298
  // ─────────────────────────────────────────
203
299
  // 4. AGENTS.md + CLAUDE.md
204
300
  // ─────────────────────────────────────────
@@ -390,6 +486,7 @@ function main() {
390
486
  scaffoldHarness();
391
487
  installSkills();
392
488
  installScripts();
489
+ installSessionHook();
393
490
  setupAgentsMd();
394
491
  checkPlaywrightMcp();
395
492
  checkRecommendedSkills();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "1.3.0",
3
+ "version": "2.0.1",
4
4
  "description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual) with Gotcha management",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
@@ -0,0 +1,140 @@
1
+ #!/bin/bash
2
+ # harness-next.sh — 세션 오케스트레이터
3
+ # 현재 progress.json 상태를 읽고 다음 에이전트를 결정한다.
4
+ # Feature-level 프로그래스를 출력하고 next-prompt.txt를 생성한다.
5
+ set -e
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ source "$SCRIPT_DIR/lib/harness-render-progress.sh"
9
+
10
+ # ─────────────────────────────────────────
11
+ # Resolve project root
12
+ # ─────────────────────────────────────────
13
+ PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || {
14
+ echo "[harness] ERROR: .harness/ directory not found" >&2
15
+ echo " Run 'npx walwal-harness' to initialize." >&2
16
+ exit 1
17
+ }
18
+
19
+ PROGRESS="$PROJECT_ROOT/.harness/progress.json"
20
+ CONFIG="$PROJECT_ROOT/.harness/config.json"
21
+ NEXT_PROMPT="$PROJECT_ROOT/.harness/next-prompt.txt"
22
+
23
+ check_jq || exit 1
24
+
25
+ if [ ! -f "$PROGRESS" ]; then
26
+ echo "[harness] ERROR: progress.json not found" >&2
27
+ echo " Run 'npx walwal-harness --force' to reinitialize." >&2
28
+ exit 1
29
+ fi
30
+
31
+ # ─────────────────────────────────────────
32
+ # Read current state
33
+ # ─────────────────────────────────────────
34
+ pipeline=$(jq -r '.pipeline // "null"' "$PROGRESS")
35
+ sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS")
36
+ sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS")
37
+ current_agent=$(jq -r '.current_agent // "null"' "$PROGRESS")
38
+ agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS")
39
+ next_agent=$(jq -r '.next_agent // "null"' "$PROGRESS")
40
+ retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS")
41
+ max_retries=$(jq -r '.flow.max_retries_per_sprint // 10' "$CONFIG" 2>/dev/null || echo 10)
42
+
43
+ # ─────────────────────────────────────────
44
+ # Determine next agent
45
+ # ─────────────────────────────────────────
46
+ compute_next_agent() {
47
+ local current="$1"
48
+ local status="$2"
49
+
50
+ # If blocked, no next
51
+ if [ "$status" = "blocked" ]; then
52
+ echo "null"
53
+ return
54
+ fi
55
+
56
+ # If failure with retry target, use that
57
+ local retry_target
58
+ retry_target=$(jq -r '.failure.retry_target // "null"' "$PROGRESS")
59
+ if [ "$retry_target" != "null" ] && [ "$status" = "failed" ]; then
60
+ echo "$retry_target"
61
+ return
62
+ fi
63
+
64
+ # If next_agent is already set in progress.json, use it
65
+ if [ "$next_agent" != "null" ]; then
66
+ echo "$next_agent"
67
+ return
68
+ fi
69
+
70
+ # Compute from pipeline sequence
71
+ if [ "$pipeline" = "null" ] || [ ! -f "$CONFIG" ]; then
72
+ echo "dispatcher"
73
+ return
74
+ fi
75
+
76
+ local -a agents
77
+ mapfile -t agents < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null | sed 's/:.*//')
78
+
79
+ local found=false
80
+ for agent in "${agents[@]}"; do
81
+ if [ "$found" = true ]; then
82
+ echo "$agent"
83
+ return
84
+ fi
85
+ if [ "$agent" = "$current" ]; then
86
+ found=true
87
+ fi
88
+ done
89
+
90
+ # Current is last agent → sprint complete
91
+ echo "archive"
92
+ }
93
+
94
+ # If current agent completed, compute next
95
+ if [ "$agent_status" = "completed" ] && [ "$next_agent" = "null" ]; then
96
+ next_agent=$(compute_next_agent "$current_agent" "$agent_status")
97
+ fi
98
+
99
+ # ─────────────────────────────────────────
100
+ # Render progress
101
+ # ─────────────────────────────────────────
102
+ render_progress "$PROJECT_ROOT"
103
+ render_agent_bar "$PROJECT_ROOT"
104
+ echo ""
105
+
106
+ # ─────────────────────────────────────────
107
+ # Generate next-prompt.txt
108
+ # ─────────────────────────────────────────
109
+ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_status" != "blocked" ]; then
110
+ # Build prompt
111
+ prompt="/harness-${next_agent} 를 실행하세요."
112
+
113
+ if [ "$sprint_num" -gt 0 ]; then
114
+ prompt+=$'\n'"Sprint ${sprint_num}"
115
+ if [ "$sprint_status" = "failed" ]; then
116
+ prompt+=" (retry ${retry_count}/${max_retries})"
117
+ fi
118
+ prompt+="을 진행합니다."
119
+ fi
120
+
121
+ prompt+=$'\n'".harness/progress.json을 읽고 현재 상태를 확인하세요."
122
+
123
+ # Add failure context if retrying
124
+ failure_msg=$(jq -r '.failure.message // empty' "$PROGRESS")
125
+ if [ -n "$failure_msg" ] && [ "$failure_msg" != "null" ]; then
126
+ prompt+=$'\n\n'"이전 실패 사유: ${failure_msg}"
127
+ fi
128
+
129
+ echo "$prompt" > "$NEXT_PROMPT"
130
+
131
+ elif [ "$next_agent" = "archive" ]; then
132
+ cat > "$NEXT_PROMPT" <<'PROMPT'
133
+ Sprint 문서를 아카이브 하세요.
134
+ .harness/actions/의 스프린트 문서를 .harness/archive/sprint-NNN/으로 이동합니다.
135
+ .harness/progress.json을 읽고 sprint 번호를 확인하세요.
136
+ PROMPT
137
+
138
+ else
139
+ echo "" > "$NEXT_PROMPT"
140
+ fi
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ # harness-session-start.sh — SessionStart 훅
3
+ # Claude Code 세션 시작 시 자동 실행되어 현재 하네스 상태를 출력한다.
4
+ # .claude/settings.json의 SessionStart 훅으로 등록된다.
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ LIB="$SCRIPT_DIR/lib/harness-render-progress.sh"
8
+
9
+ # lib이 없으면 silent exit (훅이므로 에러 출력하지 않음)
10
+ if [ ! -f "$LIB" ]; then exit 0; fi
11
+ source "$LIB"
12
+
13
+ # jq 없으면 silent exit
14
+ command -v jq &>/dev/null || exit 0
15
+
16
+ # .harness/ 찾기
17
+ PROJECT_ROOT="$(resolve_harness_root "." 2>/dev/null)" || exit 0
18
+
19
+ PROGRESS="$PROJECT_ROOT/.harness/progress.json"
20
+ [ -f "$PROGRESS" ] || exit 0
21
+
22
+ # init 상태면 간단 안내만
23
+ sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null)
24
+ if [ "$sprint_status" = "init" ]; then
25
+ echo "# Harness ready — say \"하네스 엔지니어링 시작\" or /harness-dispatcher"
26
+ exit 0
27
+ fi
28
+
29
+ # Feature-level 프로그래스 출력
30
+ render_progress "$PROJECT_ROOT"
31
+ render_agent_bar "$PROJECT_ROOT"
@@ -267,7 +267,7 @@ ${USER_RULES}
267
267
  | AGENTS.md | 전체 | Planner만 |
268
268
  | .harness/actions/api-contract.json | 전체 | Planner만 |
269
269
  | .harness/actions/feature-list.json | 전체 | passes: Generator, 나머지: Planner |
270
- | .harness/progress.txt | 전체 | 전체 (세션 종료 시 업데이트 의무) |
270
+ | .harness/progress.json | 전체 | 전체 (Session Boundary Protocol에 따라 업데이트) |
271
271
  | \`[BE]\` 소유 경로 | 전체 | Generator-Backend만 |
272
272
  | \`[FE]\` 소유 경로 | 전체 | Generator-Frontend만 |
273
273
  | .harness/archive/ | 전체 | 쓰기 금지 (불변) |
@@ -291,7 +291,7 @@ AGENTS.md 또는 api-contract.json 변경이 필요할 때:
291
291
  | 명령 | 설명 |
292
292
  |------|------|
293
293
  | \`bash scripts/scan-project.sh\` | 프로젝트 구조 재스캔 |
294
- | \`.harness/progress.txt\` | 현재 진행 상태 |
294
+ | \`.harness/progress.json\` | 현재 진행 상태 (기계 판독) |
295
295
  | \`.harness/actions/\` | 활성 스프린트 문서 |
296
296
  | \`.harness/HARNESS.md\` | 하네스 상세 가이드 |
297
297
  AGENTSEOF
@@ -0,0 +1,318 @@
1
+ #!/bin/bash
2
+ # harness-render-progress.sh — Feature-level 프로그래스 렌더링 (공통 함수)
3
+ # source로 포함하여 사용: source "$(dirname "$0")/lib/harness-render-progress.sh"
4
+
5
+ # ─────────────────────────────────────────
6
+ # Dependencies: jq
7
+ # ─────────────────────────────────────────
8
+ check_jq() {
9
+ if ! command -v jq &>/dev/null; then
10
+ echo "[harness] ERROR: jq is required. Install with: brew install jq" >&2
11
+ return 1
12
+ fi
13
+ }
14
+
15
+ # ─────────────────────────────────────────
16
+ # resolve_harness_root — .harness/ 디렉토리 찾기
17
+ # ─────────────────────────────────────────
18
+ resolve_harness_root() {
19
+ local dir="${1:-.}"
20
+ dir="$(cd "$dir" && pwd)"
21
+ while [ "$dir" != "/" ]; do
22
+ if [ -d "$dir/.harness" ]; then
23
+ echo "$dir"
24
+ return 0
25
+ fi
26
+ dir="$(dirname "$dir")"
27
+ done
28
+ return 1
29
+ }
30
+
31
+ # ─────────────────────────────────────────
32
+ # render_progress — Feature-level 프로그래스 출력
33
+ #
34
+ # Args:
35
+ # $1 — project root (optional, defaults to auto-detect)
36
+ #
37
+ # Reads:
38
+ # .harness/progress.json
39
+ # .harness/config.json
40
+ # .harness/actions/feature-list.json (if exists)
41
+ # ─────────────────────────────────────────
42
+ render_progress() {
43
+ check_jq || return 1
44
+
45
+ local PROJECT_ROOT
46
+ PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || {
47
+ echo "[harness] .harness/ directory not found" >&2
48
+ return 1
49
+ }
50
+
51
+ local PROGRESS="$PROJECT_ROOT/.harness/progress.json"
52
+ local CONFIG="$PROJECT_ROOT/.harness/config.json"
53
+ local FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
54
+
55
+ if [ ! -f "$PROGRESS" ]; then
56
+ return 1
57
+ fi
58
+
59
+ # Read progress.json
60
+ local pipeline sprint_num sprint_status retry_count
61
+ local current_agent agent_status next_agent
62
+ local failure_agent failure_location failure_message
63
+
64
+ pipeline=$(jq -r '.pipeline // "unknown"' "$PROGRESS")
65
+ sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS")
66
+ sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS")
67
+ retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS")
68
+ current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS")
69
+ agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS")
70
+ next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS")
71
+ failure_agent=$(jq -r '.failure.agent // empty' "$PROGRESS")
72
+ failure_location=$(jq -r '.failure.location // empty' "$PROGRESS")
73
+ failure_message=$(jq -r '.failure.message // empty' "$PROGRESS")
74
+
75
+ # Read max retries from config
76
+ local max_retries=10
77
+ if [ -f "$CONFIG" ]; then
78
+ max_retries=$(jq -r '.flow.max_retries_per_sprint // 10' "$CONFIG")
79
+ fi
80
+
81
+ # Pipeline agents for progress bar
82
+ local -a pipeline_agents
83
+ if [ -f "$CONFIG" ] && [ "$pipeline" != "null" ] && [ "$pipeline" != "unknown" ]; then
84
+ mapfile -t pipeline_agents < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null | sed 's/:.*//')
85
+ fi
86
+
87
+ # ── Header ──
88
+ local header="Sprint ${sprint_num} / ${pipeline}"
89
+ local pad_len=$(( 40 - ${#header} ))
90
+ local padding=""
91
+ for ((i=0; i<pad_len; i++)); do padding+="═"; done
92
+ echo ""
93
+ echo " ═══ ${header} ${padding}"
94
+ echo ""
95
+
96
+ # ── Feature Progress ──
97
+ if [ -f "$FEATURES" ]; then
98
+ local total_features completed_features=0 in_progress_features=0
99
+ total_features=$(jq '.features | length' "$FEATURES" 2>/dev/null || echo 0)
100
+
101
+ if [ "$total_features" -gt 0 ]; then
102
+ # Determine columns based on pipeline
103
+ local show_be=true show_fe=true
104
+ case "$pipeline" in
105
+ BE-ONLY) show_fe=false ;;
106
+ FE-ONLY) show_be=false ;;
107
+ esac
108
+
109
+ # Build feature lines
110
+ local i=0
111
+ while [ $i -lt "$total_features" ]; do
112
+ local fid fname passes be_pass fe_pass eval_func_pass eval_visual_pass
113
+ fid=$(jq -r ".features[$i].id // \"F-$((i+1))\"" "$FEATURES")
114
+ fname=$(jq -r ".features[$i].name // .features[$i].description // \"Feature $((i+1))\"" "$FEATURES")
115
+ passes=$(jq -r ".features[$i].passes // [] | .[]" "$FEATURES" 2>/dev/null)
116
+
117
+ # Truncate name to 20 chars
118
+ if [ ${#fname} -gt 20 ]; then
119
+ fname="${fname:0:18}.."
120
+ fi
121
+
122
+ # Check passes
123
+ be_pass="○"; fe_pass="○"; eval_func_pass="○"; eval_visual_pass="○"
124
+ local eval_done=false
125
+
126
+ while IFS= read -r p; do
127
+ case "$p" in
128
+ generator-backend) be_pass="✓" ;;
129
+ generator-frontend) fe_pass="✓" ;;
130
+ evaluator-functional) eval_func_pass="✓" ;;
131
+ evaluator-visual) eval_visual_pass="✓" ;;
132
+ esac
133
+ done <<< "$passes"
134
+
135
+ # Eval column: both must pass
136
+ local eval_pass="○"
137
+ if [ "$eval_func_pass" = "✓" ] && [ "$eval_visual_pass" = "✓" ]; then
138
+ eval_pass="✓"
139
+ elif [ "$eval_func_pass" = "✓" ] || [ "$eval_visual_pass" = "✓" ]; then
140
+ eval_pass="◐"
141
+ fi
142
+
143
+ # Feature status icon
144
+ local icon="○"
145
+ local is_complete=false
146
+ if [ "$show_be" = true ] && [ "$show_fe" = true ]; then
147
+ # FULLSTACK: all must pass
148
+ if [ "$be_pass" = "✓" ] && [ "$fe_pass" = "✓" ] && [ "$eval_pass" = "✓" ]; then
149
+ icon="✓"; is_complete=true
150
+ elif [ "$be_pass" = "✓" ] || [ "$fe_pass" = "✓" ]; then
151
+ icon="◐"
152
+ fi
153
+ elif [ "$show_be" = true ]; then
154
+ # BE-ONLY
155
+ if [ "$be_pass" = "✓" ] && [ "$eval_pass" = "✓" ]; then
156
+ icon="✓"; is_complete=true
157
+ elif [ "$be_pass" = "✓" ]; then
158
+ icon="◐"
159
+ fi
160
+ else
161
+ # FE-ONLY
162
+ if [ "$fe_pass" = "✓" ] && [ "$eval_pass" = "✓" ]; then
163
+ icon="✓"; is_complete=true
164
+ elif [ "$fe_pass" = "✓" ]; then
165
+ icon="◐"
166
+ fi
167
+ fi
168
+
169
+ if [ "$is_complete" = true ]; then
170
+ completed_features=$((completed_features + 1))
171
+ fi
172
+
173
+ # Build columns
174
+ local cols=""
175
+ if [ "$show_be" = true ]; then cols+="BE${be_pass} "; fi
176
+ if [ "$show_fe" = true ]; then cols+="FE${fe_pass} "; fi
177
+ cols+="Eval${eval_pass}"
178
+
179
+ printf " %s %-5s %-22s %s\n" "$icon" "$fid" "$fname" "$cols"
180
+
181
+ i=$((i + 1))
182
+ done
183
+
184
+ echo ""
185
+
186
+ # ── Progress Bar ──
187
+ local steps_per_feature=0
188
+ if [ "$show_be" = true ]; then steps_per_feature=$((steps_per_feature + 1)); fi
189
+ if [ "$show_fe" = true ]; then steps_per_feature=$((steps_per_feature + 1)); fi
190
+ steps_per_feature=$((steps_per_feature + 1)) # eval
191
+
192
+ local total_steps=$((total_features * steps_per_feature))
193
+ local done_steps=0
194
+
195
+ # Count individual passes across all features
196
+ i=0
197
+ while [ $i -lt "$total_features" ]; do
198
+ passes=$(jq -r ".features[$i].passes // [] | .[]" "$FEATURES" 2>/dev/null)
199
+ while IFS= read -r p; do
200
+ case "$p" in
201
+ generator-backend)
202
+ if [ "$show_be" = true ]; then done_steps=$((done_steps + 1)); fi
203
+ ;;
204
+ generator-frontend)
205
+ if [ "$show_fe" = true ]; then done_steps=$((done_steps + 1)); fi
206
+ ;;
207
+ evaluator-functional|evaluator-visual)
208
+ # Each eval counts as 0.5 of the eval step
209
+ # We'll handle this by checking both at the end
210
+ ;;
211
+ esac
212
+ done <<< "$passes"
213
+
214
+ # Check eval completion
215
+ local ef ev
216
+ ef=$(jq -r ".features[$i].passes // [] | map(select(. == \"evaluator-functional\")) | length" "$FEATURES" 2>/dev/null)
217
+ ev=$(jq -r ".features[$i].passes // [] | map(select(. == \"evaluator-visual\")) | length" "$FEATURES" 2>/dev/null)
218
+ if [ "${ef:-0}" -gt 0 ] && [ "${ev:-0}" -gt 0 ]; then
219
+ done_steps=$((done_steps + 1))
220
+ elif [ "${ef:-0}" -gt 0 ] || [ "${ev:-0}" -gt 0 ]; then
221
+ # partial eval — don't count
222
+ :
223
+ fi
224
+
225
+ i=$((i + 1))
226
+ done
227
+
228
+ local pct=0
229
+ if [ "$total_steps" -gt 0 ]; then
230
+ pct=$(( done_steps * 100 / total_steps ))
231
+ fi
232
+
233
+ local bar_width=16
234
+ local filled=$(( pct * bar_width / 100 ))
235
+ local empty=$(( bar_width - filled ))
236
+ local bar=""
237
+ for ((j=0; j<filled; j++)); do bar+="█"; done
238
+ for ((j=0; j<empty; j++)); do bar+="░"; done
239
+
240
+ echo " Features: ${completed_features}/${total_features} completed"
241
+ echo " Pipeline: ${bar} ${pct}%"
242
+ fi
243
+ else
244
+ echo " (feature-list.json not yet created — run Planner first)"
245
+ fi
246
+
247
+ echo ""
248
+
249
+ # ── Current Agent Status ──
250
+ if [ "$current_agent" != "none" ] && [ "$current_agent" != "null" ]; then
251
+ echo " Current: ${current_agent} [${agent_status}] (retry ${retry_count}/${max_retries})"
252
+ fi
253
+
254
+ # ── Failure Info ──
255
+ if [ -n "$failure_agent" ] && [ "$failure_agent" != "null" ]; then
256
+ echo ""
257
+ echo " ⚠ FAIL: ${failure_agent} → ${failure_location}"
258
+ if [ -n "$failure_message" ] && [ "$failure_message" != "null" ]; then
259
+ echo " ${failure_message}"
260
+ fi
261
+ fi
262
+
263
+ # ── Blocked ──
264
+ if [ "$agent_status" = "blocked" ]; then
265
+ echo ""
266
+ echo " ✖ BLOCKED: retry limit (${max_retries}) reached — user intervention required"
267
+ fi
268
+
269
+ # ── Next Action ──
270
+ if [ "$next_agent" != "none" ] && [ "$next_agent" != "null" ] && [ "$agent_status" != "blocked" ]; then
271
+ echo ""
272
+ echo " Next → /harness-${next_agent}"
273
+ echo " Auto → claude --prompt \"\$(cat .harness/next-prompt.txt)\""
274
+ fi
275
+
276
+ echo ""
277
+ }
278
+
279
+ # ─────────────────────────────────────────
280
+ # render_agent_bar — 에이전트 시퀀스 바 (compact)
281
+ # ─────────────────────────────────────────
282
+ render_agent_bar() {
283
+ local PROGRESS="${1:-.}/.harness/progress.json"
284
+ local CONFIG="${1:-.}/.harness/config.json"
285
+
286
+ if [ ! -f "$PROGRESS" ] || [ ! -f "$CONFIG" ]; then return 1; fi
287
+
288
+ local pipeline current_agent
289
+ pipeline=$(jq -r '.pipeline // "unknown"' "$PROGRESS")
290
+ current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS")
291
+
292
+ local completed_agents
293
+ completed_agents=$(jq -r '.completed_agents // [] | .[]' "$PROGRESS")
294
+
295
+ if [ "$pipeline" = "null" ] || [ "$pipeline" = "unknown" ]; then return 1; fi
296
+
297
+ local agents_line=" Agents: "
298
+ local first=true
299
+
300
+ while IFS= read -r agent; do
301
+ agent=$(echo "$agent" | sed 's/:.*//') # strip mode suffix like :light, :api-only
302
+ if [ "$first" = true ]; then
303
+ first=false
304
+ else
305
+ agents_line+=" → "
306
+ fi
307
+
308
+ if echo "$completed_agents" | grep -q "^${agent}$"; then
309
+ agents_line+="${agent}✓"
310
+ elif [ "$agent" = "$current_agent" ]; then
311
+ agents_line+="[${agent}]"
312
+ else
313
+ agents_line+="${agent}"
314
+ fi
315
+ done < <(jq -r ".flow.pipeline_selection.pipelines[\"${pipeline}\"][]" "$CONFIG" 2>/dev/null)
316
+
317
+ echo "$agents_line"
318
+ }
@@ -6,6 +6,23 @@ disable-model-invocation: false
6
6
 
7
7
  # Dispatcher — Pipeline Selector + Gotcha Manager
8
8
 
9
+ ## Session Boundary Protocol
10
+
11
+ ### On Start
12
+ 1. `.harness/progress.json` 읽기 — `next_agent`가 `"dispatcher"`인지 확인
13
+ 2. progress.json 업데이트: `current_agent` → `"dispatcher"`, `agent_status` → `"running"`, `updated_at` 갱신
14
+
15
+ ### On Complete
16
+ 1. progress.json 업데이트:
17
+ - `agent_status` → `"completed"`
18
+ - `completed_agents`에 `"dispatcher"` 추가
19
+ - `next_agent` → `"planner"`
20
+ - `pipeline` → 선택된 파이프라인 (FULLSTACK/FE-ONLY/BE-ONLY)
21
+ - `sprint.number` → `1`, `sprint.status` → `"in_progress"`
22
+ 2. `.harness/progress.log`에 요약 한 줄 추가
23
+ 3. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
24
+ 4. 출력: `"✓ Dispatcher 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
25
+
9
26
  ## 1. Request Classification (최우선)
10
27
 
11
28
  사용자 입력을 먼저 분류합니다:
@@ -50,4 +67,4 @@ AGENTS.md 비하네스 → 기존 백업 + 리빌드
50
67
 
51
68
  ## 5. Output
52
69
 
53
- `.harness/actions/pipeline.json` 생성 → 사용자 확인 → 다음 에이전트 실행
70
+ `.harness/actions/pipeline.json` 생성 → 사용자 확인 → Session Boundary Protocol On Complete 실행
@@ -6,6 +6,37 @@ disable-model-invocation: true
6
6
 
7
7
  # Evaluator-Functional — Playwright MCP
8
8
 
9
+ ## Session Boundary Protocol
10
+
11
+ ### On Start
12
+ 1. `.harness/progress.json` 읽기 — `next_agent`가 `"evaluator-functional"`인지 확인
13
+ 2. progress.json 업데이트: `current_agent` → `"evaluator-functional"`, `agent_status` → `"running"`, `updated_at` 갱신
14
+
15
+ ### On Complete (PASS)
16
+ 1. progress.json 업데이트:
17
+ - `agent_status` → `"completed"`
18
+ - `completed_agents`에 `"evaluator-functional"` 추가
19
+ - `next_agent` → 파이프라인에 따라 결정 (FULLSTACK/FE-ONLY: `"evaluator-visual"`, BE-ONLY: `"archive"`)
20
+ - `failure` 필드 초기화
21
+ 2. `feature-list.json`의 통과 feature `passes`에 `"evaluator-functional"` 추가
22
+ 3. `.harness/progress.log`에 PASS 요약 추가
23
+ 4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
24
+ 5. 출력: `"✓ Evaluator-Functional PASS. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
25
+
26
+ ### On Fail
27
+ 1. progress.json 업데이트:
28
+ - `agent_status` → `"failed"`
29
+ - `failure.agent` → `"evaluator-functional"`
30
+ - `failure.location` → `"backend"` 또는 `"frontend"` (결함 위치)
31
+ - `failure.message` → 실패 요약 (1줄)
32
+ - `failure.retry_target` → `"generator-backend"` 또는 `"generator-frontend"`
33
+ - `next_agent` → `failure.retry_target`과 동일
34
+ - `sprint.retry_count` 증가
35
+ 2. `sprint.retry_count >= 10`이면 `agent_status` → `"blocked"`, 사용자 개입 요청
36
+ 3. `.harness/progress.log`에 FAIL 요약 추가
37
+ 4. **STOP.**
38
+ 5. 출력: `"✖ Evaluator-Functional FAIL. bash scripts/harness-next.sh 실행하여 재작업 대상 확인."`
39
+
9
40
  ## Critical Mindset
10
41
 
11
42
  - **회의적 평가자**. Generator의 자체 평가를 신뢰하지 마세요.
@@ -20,7 +51,7 @@ disable-model-invocation: true
20
51
  3. `actions/sprint-contract.md` — BE + FE 성공 기준 전체
21
52
  4. `actions/feature-list.json` — 이번 스프린트 범위
22
53
  5. `actions/api-contract.json` — 기대 API 동작
23
- 6. `progress.txt`
54
+ 6. `.harness/progress.json`
24
55
 
25
56
  ## Evaluation Steps
26
57
 
@@ -54,5 +85,5 @@ Playwright 도구 → [도구 레퍼런스](references/playwright-tools.md)
54
85
 
55
86
  ## After Evaluation
56
87
 
57
- - **PASS** → Evaluator-Visual 핸드오프
58
- - **FAIL** → `failure_location` 기반 라우팅 (backend/frontend), max 10회
88
+ - **PASS** → Session Boundary Protocol On Complete (PASS) 실행
89
+ - **FAIL** → Session Boundary Protocol On Fail 실행
@@ -6,6 +6,37 @@ disable-model-invocation: true
6
6
 
7
7
  # Evaluator-Visual — Design & Accessibility
8
8
 
9
+ ## Session Boundary Protocol
10
+
11
+ ### On Start
12
+ 1. `.harness/progress.json` 읽기 — `next_agent`가 `"evaluator-visual"`인지 확인
13
+ 2. progress.json 업데이트: `current_agent` → `"evaluator-visual"`, `agent_status` → `"running"`, `updated_at` 갱신
14
+
15
+ ### On Complete (PASS)
16
+ 1. progress.json 업데이트:
17
+ - `agent_status` → `"completed"`
18
+ - `completed_agents`에 `"evaluator-visual"` 추가
19
+ - `next_agent` → `"archive"`
20
+ - `failure` 필드 초기화
21
+ 2. `feature-list.json`의 통과 feature `passes`에 `"evaluator-visual"` 추가
22
+ 3. `.harness/progress.log`에 PASS 요약 추가
23
+ 4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
24
+ 5. 출력: `"✓ Evaluator-Visual PASS. bash scripts/harness-next.sh 실행하여 아카이브 진행."`
25
+
26
+ ### On Fail
27
+ 1. progress.json 업데이트:
28
+ - `agent_status` → `"failed"`
29
+ - `failure.agent` → `"evaluator-visual"`
30
+ - `failure.location` → `"frontend"` (비주얼 = 항상 프론트)
31
+ - `failure.message` → 실패 요약 (1줄)
32
+ - `failure.retry_target` → `"generator-frontend"`
33
+ - `next_agent` → `"generator-frontend"`
34
+ - `sprint.retry_count` 증가
35
+ 2. `sprint.retry_count >= 10`이면 `agent_status` → `"blocked"`, 사용자 개입 요청
36
+ 3. `.harness/progress.log`에 FAIL 요약 추가
37
+ 4. **STOP.**
38
+ 5. 출력: `"✖ Evaluator-Visual FAIL. bash scripts/harness-next.sh 실행하여 재작업 대상 확인."`
39
+
9
40
  ## Startup
10
41
 
11
42
  1. `AGENTS.md` 읽기
@@ -37,5 +68,5 @@ disable-model-invocation: true
37
68
 
38
69
  ## After Evaluation
39
70
 
40
- - **PASS** → progress.txt 업데이트, Archive 실행 요청
41
- - **FAIL** → Generator-Frontend 재작업 (비주얼=항상 프론트), max 10회
71
+ - **PASS** → Session Boundary Protocol On Complete (PASS) 실행
72
+ - **FAIL** → Session Boundary Protocol On Fail 실행
@@ -6,11 +6,29 @@ disable-model-invocation: true
6
6
 
7
7
  # Generator-Backend — NestJS MSA
8
8
 
9
+ ## Session Boundary Protocol
10
+
11
+ ### On Start
12
+ 1. `.harness/progress.json` 읽기 — `next_agent`가 `"generator-backend"`인지 확인
13
+ 2. progress.json 업데이트: `current_agent` → `"generator-backend"`, `agent_status` → `"running"`, `updated_at` 갱신
14
+ 3. `failure` 필드 확인 — retry인 경우 `evaluation-functional.md`의 실패 사유 우선 읽기
15
+
16
+ ### On Complete
17
+ 1. progress.json 업데이트:
18
+ - `agent_status` → `"completed"`
19
+ - `completed_agents`에 `"generator-backend"` 추가
20
+ - `next_agent` → 파이프라인에 따라 결정 (FULLSTACK: `"generator-frontend"`, BE-ONLY: `"evaluator-functional"`)
21
+ - `failure` 필드 초기화 (retry 성공 시)
22
+ 2. `feature-list.json`의 해당 feature `passes`에 `"generator-backend"` 추가
23
+ 3. `.harness/progress.log`에 요약 추가
24
+ 4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
25
+ 5. 출력: `"✓ Generator-Backend 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
26
+
9
27
  ## Startup
10
28
 
11
29
  1. `AGENTS.md` 읽기 — IA-MAP, 권한 확인
12
30
  2. `.harness/gotchas/generator-backend.md` 읽기 — **과거 실수 반복 금지**
13
- 3. `pwd` + `.harness/progress.txt` + `git log --oneline -20`
31
+ 3. `pwd` + `.harness/progress.json` + `git log --oneline -20`
14
32
  4. `.harness/actions/api-contract.json` 읽기 — **이것이 스펙**
15
33
  5. `.harness/actions/feature-list.json` — `layer: "backend"` 필터
16
34
  6. 통합 러너: `npm run dev`
@@ -6,11 +6,29 @@ disable-model-invocation: true
6
6
 
7
7
  # Generator-Frontend — React/Next.js + Premium Design
8
8
 
9
+ ## Session Boundary Protocol
10
+
11
+ ### On Start
12
+ 1. `.harness/progress.json` 읽기 — `next_agent`가 `"generator-frontend"`인지 확인
13
+ 2. progress.json 업데이트: `current_agent` → `"generator-frontend"`, `agent_status` → `"running"`, `updated_at` 갱신
14
+ 3. `failure` 필드 확인 — retry인 경우 평가 문서의 실패 사유 우선 읽기
15
+
16
+ ### On Complete
17
+ 1. progress.json 업데이트:
18
+ - `agent_status` → `"completed"`
19
+ - `completed_agents`에 `"generator-frontend"` 추가
20
+ - `next_agent` → `"evaluator-functional"`
21
+ - `failure` 필드 초기화 (retry 성공 시)
22
+ 2. `feature-list.json`의 해당 feature `passes`에 `"generator-frontend"` 추가
23
+ 3. `.harness/progress.log`에 요약 추가
24
+ 4. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
25
+ 5. 출력: `"✓ Generator-Frontend 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
26
+
9
27
  ## Startup
10
28
 
11
29
  1. `AGENTS.md` 읽기 — IA-MAP, 권한 확인
12
30
  2. `.harness/gotchas/generator-frontend.md` 읽기 — **과거 실수 반복 금지**
13
- 3. `pwd` + `.harness/progress.txt` + `git log --oneline -20`
31
+ 3. `pwd` + `.harness/progress.json` + `git log --oneline -20`
14
32
  4. `.harness/actions/api-contract.json` 읽기 — **Gateway가 유일한 API 인터페이스**
15
33
  5. `.harness/actions/feature-list.json` — `layer: "frontend"` 필터
16
34
  6. Gateway 확인: `curl -s http://localhost:3000/health`
@@ -6,11 +6,26 @@ disable-model-invocation: true
6
6
 
7
7
  # Planner Agent
8
8
 
9
+ ## Session Boundary Protocol
10
+
11
+ ### On Start
12
+ 1. `.harness/progress.json` 읽기 — `next_agent`가 `"planner"`인지 확인
13
+ 2. progress.json 업데이트: `current_agent` → `"planner"`, `agent_status` → `"running"`, `updated_at` 갱신
14
+
15
+ ### On Complete
16
+ 1. progress.json 업데이트:
17
+ - `agent_status` → `"completed"`
18
+ - `completed_agents`에 `"planner"` 추가
19
+ - `next_agent` → 파이프라인에 따라 결정 (FULLSTACK/BE-ONLY: `"generator-backend"`, FE-ONLY: `"generator-frontend"`)
20
+ 2. `.harness/progress.log`에 요약 추가
21
+ 3. **STOP. 다음 에이전트를 직접 호출하지 않는다.**
22
+ 4. 출력: `"✓ Planner 완료. bash scripts/harness-next.sh 실행하여 다음 단계 확인."`
23
+
9
24
  ## Startup
10
25
 
11
26
  1. `AGENTS.md` 읽기
12
27
  2. `.harness/gotchas/planner.md` 읽기 — **과거 실수 반복 금지**
13
- 3. `.harness/progress.txt` 읽기
28
+ 3. `.harness/progress.json` 읽기
14
29
  4. `.harness/actions/pipeline.json` 읽기 — `planner_mode` 확인
15
30
 
16
31
  ## Outputs (4개)
@@ -43,6 +58,5 @@ disable-model-invocation: true
43
58
 
44
59
  ## After Completion
45
60
 
46
- 1. `progress.txt` 업데이트 (Phase: PLANNED)
47
- 2. 사용자에게 plan.md + api-contract.json 리뷰 요청
48
- 3. 승인 후 → 다음 에이전트 (pipeline.json 참조)
61
+ 1. 사용자에게 plan.md + api-contract.json 리뷰 요청
62
+ 2. 승인 후 → Session Boundary Protocol On Complete 실행
@@ -1,15 +0,0 @@
1
- # Harness Progress Log
2
- # 세션 간 상태 전달을 위한 진행 기록 파일
3
- # ─────────────────────────────────────────────────
4
-
5
- ## [{{DATE}}] Harness Initialized
6
- - walwal-harness 설치 완료
7
- - 6-Agent 아키텍처 (Dispatcher → Planner → Gen-BE → Gen-FE → Eval-Func → Eval-Visual)
8
- - Gotcha 관리 시스템 활성화
9
-
10
- ## Current State
11
- - Phase: INIT (Dispatcher 실행 대기)
12
- - Active Sprint: none
13
- - Features Completed: 0 / 0
14
- - Blocking Issues: none
15
- - Next Action: "하네스 엔지니어링 시작"으로 Dispatcher 실행