@walwal-harness/cli 3.2.1 → 3.3.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.
@@ -12,7 +12,7 @@
12
12
  ├── config.json # 하네스 설정
13
13
  ├── progress.json # 기계 판독 상태 (세션 오케스트레이션)
14
14
  ├── progress.log # 사람 판독 히스토리 (append-only)
15
- ├── next-prompt.txt # 다음 세션용 프롬프트 (claude CLI 파이프용)
15
+ ├── handoff.json # 세션 전환 문서 (prompt, model, artifacts, regression 등)
16
16
  ├── actions/ # 현재 활성 문서
17
17
  │ ├── pipeline.json # Dispatcher 결정 (어떤 파이프라인인지)
18
18
  │ ├── plan.md # 제품 사양
@@ -143,7 +143,7 @@ pending → draft → reviewed → approved
143
143
  |------|------|
144
144
  | `.harness/progress.json` | 기계 판독 상태 (현재 에이전트, 파이프라인, 실패 정보) |
145
145
  | `.harness/progress.log` | 사람 판독 히스토리 (append-only) |
146
- | `.harness/next-prompt.txt` | 다음 세션용 프롬프트 (claude CLI 파이프용) |
146
+ | `.harness/handoff.json` | 세션 전환 문서 (prompt, model, thinking_mode, artifacts, regression) |
147
147
 
148
148
  ### 실행 방법
149
149
 
@@ -164,7 +164,7 @@ Feature-level 프로그래스를 출력하고 다음 에이전트를 안내합
164
164
  /harness-generator-backend
165
165
 
166
166
  # 방법 B: claude CLI 자동 실행
167
- claude --prompt "$(cat .harness/next-prompt.txt)"
167
+ claude --model $(jq -r .model .harness/handoff.json) --prompt "$(jq -r .prompt .harness/handoff.json)"
168
168
  ```
169
169
 
170
170
  #### 4. SessionStart 훅 (자동)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": {
3
3
  "name": "6-Agent Production Harness",
4
- "version": "3.2.0",
4
+ "version": "3.3.0",
5
5
  "description": "Dispatcher + NestJS MSA + React/Next.js + Playwright 기반 실무 하네스",
6
6
  "source": "https://www.anthropic.com/engineering/harness-design-long-running-apps"
7
7
  },
@@ -10,7 +10,9 @@
10
10
  "role": "사용자 요청 분석 → 파이프라인 결정 (FE-ONLY / BE-ONLY / FULLSTACK)",
11
11
  "skill": "harness-dispatcher",
12
12
  "outputs": ["actions/pipeline.json"],
13
- "always_first": true
13
+ "always_first": true,
14
+ "model": "opus",
15
+ "thinking_mode": null
14
16
  },
15
17
  "brainstorming": {
16
18
  "role": "사용자의 러프한 요구사항을 대화형으로 구체화하여 Planner가 바로 쓸 수 있는 brainstorm-spec.md로 변환.",
@@ -33,6 +35,9 @@
33
35
  },
34
36
  "invoked_by": "dispatcher (user opt-in after suggestion)",
35
37
  "next_on_complete": "planner",
38
+ "model": "opus",
39
+ "thinking_mode": "plan",
40
+ "thinking_mode_description": "Claude Code plan 모드로 실행. 구조화된 계획 수립 후 실행.",
36
41
  "attribution": "Derived from obra/superpowers skills/brainstorming (MIT License)"
37
42
  },
38
43
  "planner": {
@@ -43,21 +48,30 @@
43
48
  "actions/plan.md",
44
49
  "actions/feature-list.json",
45
50
  "actions/api-contract.json"
46
- ]
51
+ ],
52
+ "model": "opus",
53
+ "thinking_mode": "ultraplan",
54
+ "thinking_mode_description": "최고 수준의 계획 모드. 아키텍처 결정, API 설계, 서비스 분할에 깊은 사고 필요."
47
55
  },
48
56
  "generator-backend": {
49
57
  "role": "NestJS MSA 서비스 구현, API 엔드포인트, DB 스키마",
50
58
  "skill": "harness-generator-backend",
51
59
  "inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
52
60
  "outputs": ["code:backend", "actions/sprint-contract.md"],
53
- "order": 1
61
+ "order": 1,
62
+ "model": "sonnet",
63
+ "thinking_mode": null,
64
+ "model_rationale": "코드 생성은 Sonnet이 비용 대비 효율적. 계획은 Planner가 이미 완료."
54
65
  },
55
66
  "generator-frontend": {
56
67
  "role": "React/Next.js UI 컴포넌트, 상태관리, API 연동",
57
68
  "skill": "harness-generator-frontend",
58
69
  "inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
59
70
  "outputs": ["code:frontend", "actions/sprint-contract.md"],
60
- "order": 2
71
+ "order": 2,
72
+ "model": "sonnet",
73
+ "thinking_mode": null,
74
+ "model_rationale": "코드 생성은 Sonnet이 비용 대비 효율적. 계획은 Planner가 이미 완료."
61
75
  },
62
76
  "evaluator-functional": {
63
77
  "role": "Playwright로 E2E 기능 검증, API 응답/DB 상태 확인",
@@ -65,6 +79,9 @@
65
79
  "tools": ["playwright:browser_*"],
66
80
  "inputs": ["actions/sprint-contract.md", "actions/feature-list.json", "actions/api-contract.json"],
67
81
  "outputs": ["actions/evaluation-functional.md"],
82
+ "model": "opus",
83
+ "thinking_mode": "ultrathink",
84
+ "thinking_mode_description": "최고 수준의 사고 모드. 적대적 검증에 깊은 추론이 필요. 각 항목을 비판적으로 분석.",
68
85
  "evaluation_template": "assets/templates/evaluation-functional.md.template",
69
86
  "adversarial_rules": {
70
87
  "comment": "Evaluator 적대적 행동 규칙 — 통과 편향 제거를 위한 강제 규칙",
@@ -92,6 +109,9 @@
92
109
  "tools": ["playwright:browser_take_screenshot", "playwright:browser_resize", "playwright:browser_snapshot"],
93
110
  "inputs": ["actions/sprint-contract.md", "actions/evaluation-functional.md", "actions/feature-list.json"],
94
111
  "outputs": ["actions/evaluation-visual.md"],
112
+ "model": "opus",
113
+ "thinking_mode": "ultrathink",
114
+ "thinking_mode_description": "최고 수준의 사고 모드. 시각적 결함, 접근성, AI슬롭을 비판적으로 탐지.",
95
115
  "evaluation_template": "assets/templates/evaluation-visual.md.template",
96
116
  "adversarial_rules": {
97
117
  "comment": "Visual Evaluator 적대적 행동 규칙",
@@ -119,7 +139,9 @@
119
139
  "inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
120
140
  "outputs": ["code:flutter", "actions/sprint-contract.md"],
121
141
  "order": 2,
122
- "fe_stack": "flutter"
142
+ "fe_stack": "flutter",
143
+ "model": "sonnet",
144
+ "thinking_mode": null
123
145
  },
124
146
  "evaluator-functional-flutter": {
125
147
  "role": "Flutter 앱 검증 — flutter analyze, flutter test, build_runner 일관성, 안티패턴 정적 검증",
@@ -127,7 +149,9 @@
127
149
  "tools": ["bash:flutter", "bash:dart"],
128
150
  "inputs": ["actions/sprint-contract.md"],
129
151
  "outputs": ["actions/evaluation-functional.md"],
130
- "fe_stack": "flutter"
152
+ "fe_stack": "flutter",
153
+ "model": "opus",
154
+ "thinking_mode": "ultrathink"
131
155
  }
132
156
  },
133
157
  "flow": {
@@ -288,7 +312,6 @@
288
312
  "isolation": true,
289
313
  "state_file": ".harness/progress.json",
290
314
  "state_log": ".harness/progress.log",
291
- "next_prompt_file": ".harness/next-prompt.txt",
292
315
  "handoff_file": ".harness/handoff.json",
293
316
  "context_guard": {
294
317
  "comment": "컨텍스트 분리 하드 가드레일. UserPromptSubmit 훅이 위반을 감지하면 경고를 주입한다.",
package/bin/init.js CHANGED
@@ -155,10 +155,10 @@ function scaffoldHarness() {
155
155
  fs.writeFileSync(progressLog, `# Harness Progress Log\n# ${date} — Initialized\n`);
156
156
  }
157
157
 
158
- // Create next-prompt.txt placeholder
159
- const nextPrompt = path.join(HARNESS_DIR, 'next-prompt.txt');
160
- if (!fileExists(nextPrompt) || isForce) {
161
- fs.writeFileSync(nextPrompt, '');
158
+ // Create handoff.json placeholder
159
+ const handoff = path.join(HARNESS_DIR, 'handoff.json');
160
+ if (!fileExists(handoff) || isForce) {
161
+ fs.writeFileSync(handoff, '{}');
162
162
  }
163
163
 
164
164
  log('.harness/ scaffolding complete');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "3.2.1",
3
+ "version": "3.3.1",
4
4
  "description": "Production harness for AI agent engineering — Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer (requirements refinement). Supports React and Flutter FE stacks.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
@@ -36,6 +36,6 @@
36
36
  "gotchas/"
37
37
  ],
38
38
  "dependencies": {
39
- "@walwal-harness/cli": "^3.2.1"
39
+ "@walwal-harness/cli": "^3.3.0"
40
40
  }
41
41
  }
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  # harness-next.sh — 세션 오케스트레이터
3
3
  # 현재 progress.json 상태를 읽고 다음 에이전트를 결정한다.
4
- # Feature-level 프로그래스를 출력하고 next-prompt.txt를 생성한다.
4
+ # Feature-level 프로그래스를 출력하고 handoff.json을 생성한다.
5
5
  set -e
6
6
 
7
7
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
@@ -20,7 +20,7 @@ PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || {
20
20
  PROGRESS="$PROJECT_ROOT/.harness/progress.json"
21
21
  CONFIG="$PROJECT_ROOT/.harness/config.json"
22
22
  PIPELINE_JSON="$PROJECT_ROOT/.harness/actions/pipeline.json"
23
- NEXT_PROMPT="$PROJECT_ROOT/.harness/next-prompt.txt"
23
+ HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
24
24
 
25
25
  check_jq || exit 1
26
26
 
@@ -296,7 +296,7 @@ render_agent_bar "$PROJECT_ROOT"
296
296
  echo ""
297
297
 
298
298
  # ─────────────────────────────────────────
299
- # Generate next-prompt.txt
299
+ # Generate handoff.json (unified session transition document)
300
300
  # ─────────────────────────────────────────
301
301
  if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_status" != "blocked" ]; then
302
302
  # ── Escalation check: 3회 실패 시 Planner에게 scope 축소 요청 ──
@@ -304,14 +304,17 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
304
304
  if [ "$retry_count" -ge "$escalate_after" ] && [ "$sprint_status" = "failed" ] && [ "$next_agent" != "planner" ]; then
305
305
  echo " ⚠ Escalation: ${retry_count}회 실패 — Planner에게 scope 축소/접근 변경 요청"
306
306
  next_agent="planner"
307
- # progress.json에 에스컬레이션 기록
308
307
  jq --arg msg "Escalated after ${retry_count} failures. Planner must review scope or approach." \
309
308
  '.next_agent = "planner" |
310
309
  .failure.message = $msg |
311
310
  .failure.retry_target = "planner"' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
312
311
  fi
313
312
 
314
- # Build prompt
313
+ # ── Read model & thinking mode for next agent ──
314
+ agent_model=$(jq -r ".agents[\"${next_agent}\"].model // \"opus\"" "$CONFIG" 2>/dev/null || echo "opus")
315
+ agent_thinking=$(jq -r ".agents[\"${next_agent}\"].thinking_mode // \"null\"" "$CONFIG" 2>/dev/null || echo "null")
316
+
317
+ # ── Build prompt text (embedded in handoff.json) ──
315
318
  prompt="/harness-${next_agent} 를 실행하세요."
316
319
 
317
320
  if [ "$sprint_num" -gt 0 ]; then
@@ -322,22 +325,33 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
322
325
  prompt+="을 진행합니다."
323
326
  fi
324
327
 
325
- prompt+=$'\n'".harness/progress.json을 읽고 현재 상태를 확인하세요."
328
+ prompt+=$'\n'".harness/handoff.json을 읽고 컨텍스트를 확인하세요."
329
+
330
+ # Inject thinking mode instruction
331
+ if [ "$agent_thinking" != "null" ]; then
332
+ case "$agent_thinking" in
333
+ ultraplan)
334
+ prompt+=$'\n\n'"[Thinking Mode: ultraplan] /${agent_thinking} 모드를 사용하세요. 깊은 사고로 아키텍처와 설계를 수행합니다."
335
+ ;;
336
+ ultrathink)
337
+ prompt+=$'\n\n'"[Thinking Mode: ultrathink] /${agent_thinking} 모드를 사용하세요. 최대 추론 깊이로 비판적 검증을 수행합니다."
338
+ ;;
339
+ plan)
340
+ prompt+=$'\n\n'"[Thinking Mode: plan] /${agent_thinking} 모드를 사용하세요. 구조화된 계획을 수립한 후 실행합니다."
341
+ ;;
342
+ esac
343
+ fi
326
344
 
327
- # Add failure context if retrying (include previous failure summary)
345
+ # Add failure context if retrying
328
346
  failure_msg=$(jq -r '.failure.message // empty' "$PROGRESS")
329
347
  if [ -n "$failure_msg" ] && [ "$failure_msg" != "null" ]; then
330
348
  prompt+=$'\n\n'"이전 실패 사유: ${failure_msg}"
331
349
  prompt+=$'\n'"같은 접근을 반복하지 말고, 실패 원인을 분석한 후 다른 전략으로 시도하세요."
332
350
  fi
333
351
 
334
- echo "$prompt" > "$NEXT_PROMPT"
335
-
336
- # ── Generate structured handoff.json ──
337
- HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
352
+ # ── Collect artifacts ──
338
353
  FEATURE_LIST="$PROJECT_ROOT/.harness/actions/feature-list.json"
339
354
 
340
- # Collect available artifacts
341
355
  local -a artifacts_ready=()
342
356
  for f in plan.md feature-list.json api-contract.json sprint-contract.md evaluation-functional.md evaluation-visual.md; do
343
357
  if [ -f "$PROJECT_ROOT/.harness/actions/$f" ]; then
@@ -353,7 +367,7 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
353
367
  focus_features=$(jq '[.features[]? | select(.passes == null or (.passes | length) == 0 or ((.passes // []) | map(select(. == "evaluator-functional")) | length == 0)) | .id] | .[0:5]' "$FEATURE_LIST" 2>/dev/null || echo "[]")
354
368
  fi
355
369
 
356
- # ── Regression data: collect previous sprint's passed AC from archive ──
370
+ # ── Regression data ──
357
371
  local regression_source="null"
358
372
  local prev_sprint=$((sprint_num - 1))
359
373
  local prev_archive="$PROJECT_ROOT/.harness/archive/sprint-$(printf '%03d' $prev_sprint)"
@@ -367,7 +381,7 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
367
381
  fi
368
382
  fi
369
383
 
370
- # ── Eval-specific scoring config ──
384
+ # ── Eval-specific config ──
371
385
  local eval_config="null"
372
386
  local cross_validation_data="null"
373
387
  case "$next_agent" in
@@ -384,22 +398,24 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
384
398
  ;;
385
399
  esac
386
400
 
387
- # ── Cross-Validation: evaluator-visual이면 functional 결과 파싱 ──
401
+ # ── Cross-Validation ──
388
402
  if [ "$next_agent" = "evaluator-visual" ]; then
389
403
  local func_eval="$PROJECT_ROOT/.harness/actions/evaluation-functional.md"
390
404
  if [ -f "$func_eval" ]; then
391
- # evaluation-functional.md 내 JSON 코드블록에서 Cross-Validation Data 추출
392
405
  cross_validation_data=$(sed -n '/```json/,/```/p' "$func_eval" | tail -n +2 | head -n -1 | jq 'select(.evaluator == "functional")' 2>/dev/null || echo "null")
393
406
  fi
394
407
  fi
395
408
 
396
- # Build handoff.json
409
+ # ── Build handoff.json (single source of truth for session transition) ──
397
410
  jq -n \
398
411
  --arg from "${current_agent:-dispatcher}" \
399
412
  --arg to "$next_agent" \
413
+ --arg prompt "$prompt" \
400
414
  --argjson sprint "$sprint_num" \
401
415
  --argjson retry "$retry_count" \
402
416
  --arg status "$sprint_status" \
417
+ --arg agent_model "$agent_model" \
418
+ --arg agent_thinking "$agent_thinking" \
403
419
  --arg failure_msg "${failure_msg:-}" \
404
420
  --argjson artifacts "$artifacts_json" \
405
421
  --argjson focus "$focus_features" \
@@ -410,9 +426,12 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
410
426
  '{
411
427
  from: $from,
412
428
  to: $to,
429
+ prompt: $prompt,
413
430
  sprint: $sprint,
414
431
  retry_count: $retry,
415
432
  sprint_status: $status,
433
+ model: $agent_model,
434
+ thinking_mode: (if $agent_thinking == "null" then null else $agent_thinking end),
416
435
  failure_context: (if $failure_msg != "" then $failure_msg else null end),
417
436
  artifacts_ready: $artifacts,
418
437
  focus_features: $focus,
@@ -424,12 +443,20 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
424
443
  }' > "$HANDOFF"
425
444
 
426
445
  elif [ "$next_agent" = "archive" ]; then
427
- cat > "$NEXT_PROMPT" <<'PROMPT'
428
- Sprint 문서를 아카이브 하세요.
429
- .harness/actions/의 스프린트 문서를 .harness/archive/sprint-NNN/으로 이동합니다.
430
- .harness/progress.json을 읽고 sprint 번호를 확인하세요.
431
- PROMPT
446
+ jq -n \
447
+ --arg from "${current_agent:-evaluator}" \
448
+ --argjson sprint "$sprint_num" \
449
+ --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
450
+ '{
451
+ from: $from,
452
+ to: "archive",
453
+ prompt: "Sprint 문서를 아카이브하세요.\n.harness/actions/의 스프린트 문서를 .harness/archive/sprint-NNN/으로 이동합니다.\n.harness/handoff.json을 읽고 sprint 번호를 확인하세요.",
454
+ sprint: $sprint,
455
+ model: "opus",
456
+ thinking_mode: null,
457
+ timestamp: $timestamp
458
+ }' > "$HANDOFF"
432
459
 
433
460
  else
434
- echo "" > "$NEXT_PROMPT"
461
+ echo '{}' > "$HANDOFF"
435
462
  fi
@@ -88,6 +88,28 @@ if [ "$retry_count" -gt 0 ]; then
88
88
  retry_str=" R${retry_count}"
89
89
  fi
90
90
 
91
+ # Model/mode for current or next agent
92
+ CONFIG="$PROJECT_ROOT/.harness/config.json"
93
+ active_agent="$current_agent"
94
+ if [ "$active_agent" = "none" ] || [ "$active_agent" = "null" ]; then
95
+ active_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
96
+ fi
97
+ model_short=""
98
+ if [ -f "$CONFIG" ] && [ "$active_agent" != "none" ] && [ "$active_agent" != "null" ]; then
99
+ am=$(jq -r ".agents[\"${active_agent}\"].model // empty" "$CONFIG" 2>/dev/null)
100
+ at=$(jq -r ".agents[\"${active_agent}\"].thinking_mode // empty" "$CONFIG" 2>/dev/null)
101
+ if [ -n "$am" ]; then
102
+ model_short="${am}"
103
+ if [ -n "$at" ] && [ "$at" != "null" ]; then
104
+ model_short+="/${at}"
105
+ fi
106
+ fi
107
+ fi
108
+
91
109
  # Build compact status line
92
- # Format: [S1] FULL | >backend | 2/5 feat | R0 | ctx 45% | $1.23
93
- echo "[S${sprint_num}] ${pl} | ${status_icon}${agent_short}${retry_str} | ${completed_features}/${total_features} feat | ctx ${context_pct}% | \$${cost}"
110
+ # Format: [S1] FULL | >backend | sonnet | 2/5 feat | ctx 45% | $1.23
111
+ if [ -n "$model_short" ]; then
112
+ echo "[S${sprint_num}] ${pl} | ${status_icon}${agent_short}${retry_str} | ${model_short} | ${completed_features}/${total_features} feat | ctx ${context_pct}% | \$${cost}"
113
+ else
114
+ echo "[S${sprint_num}] ${pl} | ${status_icon}${agent_short}${retry_str} | ${completed_features}/${total_features} feat | ctx ${context_pct}% | \$${cost}"
115
+ fi
@@ -268,9 +268,28 @@ render_progress() {
268
268
 
269
269
  # ── Next Action ──
270
270
  if [ "$next_agent" != "none" ] && [ "$next_agent" != "null" ] && [ "$agent_status" != "blocked" ]; then
271
+ # Read model & thinking mode from config
272
+ local next_model="opus"
273
+ local next_thinking="null"
274
+ if [ -f "$CONFIG" ]; then
275
+ next_model=$(jq -r ".agents[\"${next_agent}\"].model // \"opus\"" "$CONFIG" 2>/dev/null || echo "opus")
276
+ next_thinking=$(jq -r ".agents[\"${next_agent}\"].thinking_mode // \"null\"" "$CONFIG" 2>/dev/null || echo "null")
277
+ fi
278
+
279
+ local mode_str=""
280
+ if [ "$next_thinking" != "null" ]; then
281
+ mode_str=" [/${next_thinking}]"
282
+ fi
283
+
271
284
  echo ""
272
- echo " Next → /harness-${next_agent}"
273
- echo " Auto → claude --prompt \"\$(cat .harness/next-prompt.txt)\""
285
+ echo " Next → /harness-${next_agent} (model: ${next_model}${mode_str})"
286
+
287
+ # Build auto CLI command with model flag, reading prompt from handoff.json
288
+ local model_flag=""
289
+ if [ "$next_model" != "opus" ]; then
290
+ model_flag=" --model ${next_model}"
291
+ fi
292
+ echo " Auto → claude${model_flag} --prompt \"\$(jq -r .prompt .harness/handoff.json)\""
274
293
  fi
275
294
 
276
295
  echo ""