@walwal-harness/cli 5.9.6 → 6.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.
@@ -0,0 +1,85 @@
1
+ ---
2
+ docmeta:
3
+ id: gotchas-generator-backend-laravel
4
+ title: Generator-Backend Gotchas — Laravel/Livewire/Filament
5
+ type: output
6
+ createdAt: 2026-05-07T00:00:00Z
7
+ updatedAt: 2026-05-07T00:00:00Z
8
+ source:
9
+ producer: agent
10
+ skillId: harness-planner
11
+ inputs:
12
+ - documentId: agency-agents-engineering
13
+ uri: https://github.com/msitarzewski/agency-agents
14
+ relation: output-from
15
+ sections:
16
+ - sourceRange: { startLine: 1, endLine: 1 }
17
+ targetRange: { startLine: 1, endLine: 999 }
18
+ tags: [gotcha, generator-backend, laravel, livewire, filament, phase-b]
19
+ ---
20
+
21
+ <!-- Source: https://github.com/msitarzewski/agency-agents (MIT) -->
22
+
23
+ # Generator-Backend Gotchas — Laravel/Livewire/Filament
24
+
25
+ > 스택별 가드. 공통 가드는 `gotchas/generator-backend.md` 참조.
26
+ > 모든 항목은 `unverified` 상태로 시작, Planner 리뷰 후 `verified` 승격.
27
+
28
+ ## verified
29
+
30
+ (없음 — 첫 리비전)
31
+
32
+ ## unverified
33
+
34
+ ### G-LV-001 — Livewire 컴포넌트가 Eloquent 모델을 prop으로 받을 때 직렬화 충돌
35
+ - **언제**: 부모 컴포넌트 → 자식 컴포넌트로 모델을 직접 props 전달
36
+ - **증상**: 새 요청마다 모델 hydrate 실패, lazy load 시 fresh 로드되어 데이터 불일치
37
+ - **회피**: ID만 전달 + 자식이 자기 책임으로 fetch, 또는 `serializeable=true` 명시 + 변경 위험 인지
38
+
39
+ ### G-LV-002 — `wire:model.live` 무차별 사용으로 N+1 트래픽 폭발
40
+ - **증상**: 입력 1자마다 서버 왕복, 운영에서 큐 폭주
41
+ - **회피**: 폼 입력 default `wire:model.live.debounce.500ms` 또는 `wire:model.lazy`
42
+
43
+ ### G-FL-001 — Filament Resource `eagerLoad()` 누락
44
+ - **증상**: 인덱스 페이지 1회 로드에 컬럼 수 × 행 수 만큼 쿼리 (N+1)
45
+ - **회피**: Resource에 `protected $eagerLoadRelations = ['user', 'category']` 명시. Eval-Architecture가 검출.
46
+
47
+ ### G-FL-002 — Filament Tenancy panel에서 Global scope 누락
48
+ - **증상**: 한 테넌트 사용자가 다른 테넌트 데이터 노출
49
+ - **회피**: `getEloquentQuery()` 오버라이드 시 `parent::getEloquentQuery()` 후 scope 추가. Eval-Security 적발.
50
+
51
+ ### G-EL-001 — `whereHas` 다단 사용으로 인덱스 미사용 쿼리
52
+ - **증상**: relation 깊이 ≥ 2 + 결과 set 크면 secs 단위
53
+ - **회피**: subquery 또는 `whereIn(select 1 column)` 패턴, EXPLAIN 첨부
54
+
55
+ ### G-EL-002 — Mass assignment 누락 → 권한 우회
56
+ - **증상**: 사용자가 `is_admin` 같은 컬럼을 요청 본문에 끼워 권한 상승
57
+ - **회피**: 모든 모델 `$fillable` 또는 `$guarded = []` 의도 명시. Eval-Security CRITICAL.
58
+
59
+ ### G-MIG-001 — 인덱스 없는 외래키 컬럼
60
+ - **증상**: parent delete 시 child scan 풀스캔
61
+ - **회피**: 마이그레이션에서 `foreignId(...)->index()` 명시
62
+
63
+ ### G-JOB-001 — Job 안에서 retry 비대응 외부 호출
64
+ - **증상**: Stripe·외부 API 호출이 retry 시 중복 결제·중복 메시지
65
+ - **회피**: idempotency_key 또는 `ShouldBeUnique`. Eval-Security 검토.
66
+
67
+ ### G-CACHE-001 — `Cache::tags()` on file driver
68
+ - **증상**: 운영에서 silent fail (tag 무시)
69
+ - **회피**: prod cache driver는 redis 강제. config/cache.php boot 시 검증.
70
+
71
+ ### G-AUTH-001 — Sanctum 토큰 회전 정책 부재
72
+ - **증상**: 토큰 영구 유효 → 누출 시 영향 무한대
73
+ - **회피**: TTL + 사용 흐름별 ability 분리, 회전 명령 정의 (DevOps와 연계)
74
+
75
+ ### G-OBS-001 — observer chain → 부작용 폭발
76
+ - **증상**: User saving → Profile observer → Account observer → 무한 루프 또는 dead-lock
77
+ - **회피**: observer 안에서 다른 모델 save 금지 룰. 명시적 Action 호출로 대체.
78
+
79
+ ### G-FORM-001 — `$request->validate()` 인라인 사용
80
+ - **증상**: 검증 룰 재사용·테스트·문서화 모두 어려움
81
+ - **회피**: FormRequest 클래스 강제. Eval-CodeQuality lint 룰.
82
+
83
+ ### G-PERF-001 — Telescope prod 활성
84
+ - **증상**: 모든 쿼리·요청 적재 → DB 비대 + 보안 노출
85
+ - **회피**: env 분리, prod에서 `TelescopeServiceProvider` 등록 차단. Eval-Security 적발.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "5.9.6",
4
- "description": "Production harness for AI agent engineering — Solo/Team mode, Planner, Generator(BE/FE), Evaluator chain (Code-Quality → Functional → Visual), optional Brainstormer. Supports React, Next.js, and Flutter FE stacks.",
3
+ "version": "6.0.1",
4
+ "description": "Production harness for AI agent engineering — NEXUS-adapted company metaphor (Dispatcher/CEO + Conductor + Meeting-Manager + COO/Planner + CTO + CQO + Service-Ops). Solo/Team mode, Brainstormer, Planner, Generator(BE/FE/Designer/DevOps), Evaluator chain (Code-Quality → Functional → Visual + Architecture/Security). Supports React, Next.js, and Flutter FE stacks.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
7
7
  },
@@ -24,6 +24,13 @@
24
24
  "planner",
25
25
  "generator",
26
26
  "evaluator",
27
+ "nexus",
28
+ "ceo",
29
+ "cto",
30
+ "cqo",
31
+ "conductor",
32
+ "meeting-manager",
33
+ "service-ops",
27
34
  "nestjs",
28
35
  "react",
29
36
  "flutter",
@@ -45,6 +52,7 @@
45
52
  "scripts/",
46
53
  "commands/",
47
54
  "assets/",
48
- "gotchas/"
55
+ "gotchas/",
56
+ "CHANGELOG.md"
49
57
  ]
50
58
  }
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ # walwal-harness — Brick Office dashboard launcher
3
+ #
4
+ # 처음 쓰는 사용자도 한 줄로 대시보드를 띄워볼 수 있게 만든 helper.
5
+ # walwal-harness CLI 패키지에는 R3F + Next.js 무게 때문에 dashboard 가 미포함이고,
6
+ # 본 스크립트가 git sparse-checkout 으로 apps/harness-dashboard/ 만 가져와서
7
+ # 사용자 프로젝트 외부 (~/.walwal-harness/dashboard) 에 격리 설치 후 dev 실행한다.
8
+ #
9
+ # Usage:
10
+ # bash scripts/harness-dashboard-up.sh # install + dev (port 3001)
11
+ # bash scripts/harness-dashboard-up.sh --port 3050 # override port
12
+ # HARNESS_ROOT=/path/to/project bash ... # explicit harness root
13
+ set -euo pipefail
14
+
15
+ REPO_URL="${WALWAL_HARNESS_REPO:-https://github.com/30eggis/walwal-harness.git}"
16
+ DASHBOARD_PATH="apps/harness-dashboard"
17
+ LOCAL_DIR="${HOME}/.walwal-harness/dashboard"
18
+ PORT="${PORT:-3001}"
19
+
20
+ while [[ $# -gt 0 ]]; do
21
+ case "$1" in
22
+ --port) PORT="$2"; shift 2 ;;
23
+ --reinstall) rm -rf "$LOCAL_DIR"; shift ;;
24
+ --help|-h)
25
+ sed -n '2,16p' "$0"
26
+ exit 0 ;;
27
+ *) echo "[brick-office] unknown arg: $1" >&2; exit 2 ;;
28
+ esac
29
+ done
30
+
31
+ if [[ -z "${HARNESS_ROOT:-}" ]]; then
32
+ HARNESS_ROOT="$(pwd)"
33
+ fi
34
+ if [[ ! -d "${HARNESS_ROOT}/.harness" ]]; then
35
+ echo "[brick-office] FAIL: ${HARNESS_ROOT} 에 .harness/ 가 없습니다."
36
+ echo " 먼저 'npx walwal-harness' 로 초기화하세요."
37
+ exit 3
38
+ fi
39
+
40
+ echo "[brick-office] HARNESS_ROOT = ${HARNESS_ROOT}"
41
+ echo "[brick-office] dashboard 격리 경로 = ${LOCAL_DIR}"
42
+
43
+ if [[ ! -d "${LOCAL_DIR}/${DASHBOARD_PATH}" ]]; then
44
+ echo "[brick-office] 첫 실행 — git sparse-checkout 으로 dashboard 만 가져옵니다 (~5MB)."
45
+ mkdir -p "${LOCAL_DIR}"
46
+ cd "${LOCAL_DIR}"
47
+ git init -q
48
+ git remote add origin "${REPO_URL}" 2>/dev/null || true
49
+ git config core.sparseCheckout true
50
+ echo "${DASHBOARD_PATH}/" > .git/info/sparse-checkout
51
+ git fetch --depth=1 origin main -q
52
+ git checkout main -q
53
+ echo "[brick-office] 다운로드 완료."
54
+ fi
55
+
56
+ cd "${LOCAL_DIR}/${DASHBOARD_PATH}"
57
+
58
+ if [[ ! -d node_modules ]]; then
59
+ echo "[brick-office] npm install (한 번만 실행, ~30s)..."
60
+ npm install --no-audit --no-fund --loglevel=error
61
+ fi
62
+
63
+ echo ""
64
+ echo "╔══════════════════════════════════════════════════════════╗"
65
+ echo "║ Brick Office — http://localhost:${PORT}"
66
+ echo "║ ║"
67
+ echo "║ HARNESS_ROOT 이 가리키는 프로젝트의 .harness/ 상태를 ║"
68
+ echo "║ 실시간 (SSE) 시각화합니다. Ctrl+C 로 종료. ║"
69
+ echo "╚══════════════════════════════════════════════════════════╝"
70
+ echo ""
71
+
72
+ HARNESS_ROOT="${HARNESS_ROOT}" npm run dev:dashboard -- -p "${PORT}"
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ # harness-goal-init.sh — 새 GOAL 발급 (CEO=Dispatcher 전용)
3
+ # 사용법: bash scripts/harness-goal-init.sh "<title>"
4
+ #
5
+ # 동작:
6
+ # 1. .harness/actions/goals.md 가 없으면 template로 생성
7
+ # 2. 다음 GOAL ID(G-N) 발급 → progress.json.goals.list 에 등록
8
+ # 3. progress.json.goals.active_id 갱신
9
+ # 4. CTO 협의 미완료 상태(cto_feasibility=null, owner_confirmed=false)
10
+ #
11
+ # 후속:
12
+ # - CTO는 cto_feasibility 의견을 협의 후 같은 항목에 채워줌
13
+ # - Owner 최종 확인 시 dispatcher 가 owner_confirmed=true 로 갱신
14
+ set -euo pipefail
15
+
16
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
17
+ GOAL_FILE="$ROOT/.harness/actions/goals.md"
18
+ TEMPLATE="$ROOT/.harness/actions/goals.md.template"
19
+ PROGRESS="$ROOT/.harness/progress.json"
20
+
21
+ TITLE="${1:-}"
22
+ if [ -z "$TITLE" ]; then
23
+ echo "usage: $0 \"<title>\"" >&2
24
+ exit 1
25
+ fi
26
+
27
+ mkdir -p "$ROOT/.harness/actions"
28
+
29
+ # 1. 파일이 없으면 template 복사 후 ISO 시각 채움
30
+ if [ ! -f "$GOAL_FILE" ]; then
31
+ if [ ! -f "$TEMPLATE" ]; then
32
+ echo "template missing: $TEMPLATE" >&2; exit 1
33
+ fi
34
+ ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
35
+ sed "s|<ISO>|$ISO|g" "$TEMPLATE" > "$GOAL_FILE"
36
+ fi
37
+
38
+ # 2. 다음 GOAL ID 계산
39
+ LAST_N=$(jq -r '.goals.list | map(.id // "G-0") | map(sub("G-"; "")) | map(tonumber? // 0) | max // 0' "$PROGRESS" 2>/dev/null || echo 0)
40
+ NEXT_N=$((LAST_N + 1))
41
+ GOAL_ID="G-${NEXT_N}"
42
+ ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
43
+
44
+ # 3. progress.json 에 등록 (partial update)
45
+ jq --arg id "$GOAL_ID" --arg title "$TITLE" --arg iso "$ISO" '
46
+ .goals.list += [{
47
+ "id": $id,
48
+ "title": $title,
49
+ "status": "draft",
50
+ "owner_confirmed": false,
51
+ "cto_feasibility": null,
52
+ "created_at": $iso
53
+ }] |
54
+ .goals.active_id = $id |
55
+ .updated_at = $iso
56
+ ' "$PROGRESS" > "$PROGRESS.tmp" && mv "$PROGRESS.tmp" "$PROGRESS"
57
+
58
+ # 4. goals.md 에 GOAL 섹션 append (CEO가 이후 본문 채움)
59
+ cat >> "$GOAL_FILE" <<EOF
60
+
61
+ ## $GOAL_ID — $TITLE
62
+ status: draft
63
+ owner_confirmed: false
64
+ cto_feasibility: null
65
+ created_at: $ISO
66
+
67
+ (CEO가 success_metrics·deadline·kpis·runbook 채움)
68
+ EOF
69
+
70
+ echo "$GOAL_ID 발급 완료: $TITLE"
71
+ echo "→ $GOAL_FILE 에서 본문 작성"
72
+ echo "→ CTO 협의 후 owner 확정 시 owner_confirmed=true"
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+ # harness-goal-show.sh — 현재 active GOAL + 적합도 요약 출력
3
+ set -euo pipefail
4
+
5
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
6
+ PROGRESS="$ROOT/.harness/progress.json"
7
+ GOAL_FILE="$ROOT/.harness/actions/goals.md"
8
+
9
+ if [ ! -f "$PROGRESS" ]; then
10
+ echo "progress.json 없음. bash init.sh 먼저 실행." >&2; exit 1
11
+ fi
12
+
13
+ ACTIVE_ID=$(jq -r '.goals.active_id // ""' "$PROGRESS")
14
+ ADHERENCE=$(jq -r '.goals.current_adherence // "n/a"' "$PROGRESS")
15
+
16
+ if [ -z "$ACTIVE_ID" ]; then
17
+ echo "Active GOAL 없음. CEO(Dispatcher)가 첫 발화에서 발급."
18
+ echo " bash scripts/harness-goal-init.sh \"<title>\""
19
+ exit 0
20
+ fi
21
+
22
+ echo "Active GOAL: $ACTIVE_ID"
23
+ echo "Adherence: $ADHERENCE"
24
+ echo ""
25
+
26
+ # progress.json 의 GOAL 메타 출력
27
+ jq -r --arg id "$ACTIVE_ID" '
28
+ .goals.list[] | select(.id == $id) |
29
+ "title: \(.title)",
30
+ "status: \(.status)",
31
+ "owner_confirmed: \(.owner_confirmed)",
32
+ "cto_feasibility: \(.cto_feasibility // "pending")",
33
+ "created_at: \(.created_at // "?")"
34
+ ' "$PROGRESS"
35
+
36
+ echo ""
37
+ echo "본문: $GOAL_FILE 에서 ## $ACTIVE_ID 섹션 참조"
@@ -415,25 +415,24 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
415
415
  # ── Collect artifacts ──
416
416
  FEATURE_LIST="$PROJECT_ROOT/.harness/actions/feature-list.json"
417
417
 
418
- local -a artifacts_ready=()
418
+ artifacts_ready=()
419
419
  for f in plan.md feature-list.json api-contract.json sprint-contract.md evaluation-functional.md evaluation-visual.md; do
420
420
  if [ -f "$PROJECT_ROOT/.harness/actions/$f" ]; then
421
421
  artifacts_ready+=("$f")
422
422
  fi
423
423
  done
424
- local artifacts_json
425
424
  artifacts_json=$(printf '%s\n' "${artifacts_ready[@]}" | jq -R . | jq -s .)
426
425
 
427
426
  # Collect focus features (incomplete ones)
428
- local focus_features="[]"
427
+ focus_features="[]"
429
428
  if [ -f "$FEATURE_LIST" ]; then
430
429
  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 "[]")
431
430
  fi
432
431
 
433
432
  # ── Regression data ──
434
- local regression_source="null"
435
- local prev_sprint=$((sprint_num - 1))
436
- local prev_archive="$PROJECT_ROOT/.harness/archive/sprint-$(printf '%03d' $prev_sprint)"
433
+ regression_source="null"
434
+ prev_sprint=$((sprint_num - 1))
435
+ prev_archive="$PROJECT_ROOT/.harness/archive/sprint-$(printf '%03d' $prev_sprint)"
437
436
  if [ "$prev_sprint" -ge 1 ] && [ -d "$prev_archive" ]; then
438
437
  if [ -f "$prev_archive/feature-list.json" ]; then
439
438
  regression_source=$(jq '{
@@ -445,8 +444,8 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
445
444
  fi
446
445
 
447
446
  # ── Eval-specific config ──
448
- local eval_config="null"
449
- local cross_validation_data="null"
447
+ eval_config="null"
448
+ cross_validation_data="null"
450
449
  case "$next_agent" in
451
450
  evaluator-*)
452
451
  eval_config=$(jq '{
@@ -462,15 +461,15 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
462
461
  esac
463
462
 
464
463
  # ── Cross-Validation (chain: code-quality → functional → visual) ──
465
- local cross_validation_from_code_quality="null"
464
+ cross_validation_from_code_quality="null"
466
465
  if [ "$next_agent" = "evaluator-functional" ] || [ "$next_agent" = "evaluator-visual" ]; then
467
- local cq_eval="$PROJECT_ROOT/.harness/actions/evaluation-code-quality.md"
466
+ cq_eval="$PROJECT_ROOT/.harness/actions/evaluation-code-quality.md"
468
467
  if [ -f "$cq_eval" ]; then
469
468
  cross_validation_from_code_quality=$(sed -n '/```json/,/```/p' "$cq_eval" | tail -n +2 | head -n -1 | jq 'select(.evaluator == "code-quality" or .cross_validation_from_code_quality)' 2>/dev/null || echo "null")
470
469
  fi
471
470
  fi
472
471
  if [ "$next_agent" = "evaluator-visual" ]; then
473
- local func_eval="$PROJECT_ROOT/.harness/actions/evaluation-functional.md"
472
+ func_eval="$PROJECT_ROOT/.harness/actions/evaluation-functional.md"
474
473
  if [ -f "$func_eval" ]; then
475
474
  cross_validation_data=$(sed -n '/```json/,/```/p' "$func_eval" | tail -n +2 | head -n -1 | jq 'select(.evaluator == "functional")' 2>/dev/null || echo "null")
476
475
  fi
@@ -0,0 +1,247 @@
1
+ ---
2
+ name: harness-conductor
3
+ description: "자율 실행 엔진. Dispatcher(CEO)가 하달한 GOAL을 받아 Planner→Gen→Eval→Service-Ops 루프를 사용자 개입 없이 끊김없이 진행한다. 3회 FAIL/GOAL 위반/인시던트 시 Dispatcher 통해 Owner에게 escalation. 트리거: '컨덕터 시작', 'conductor run', 'autopilot on'."
4
+ disable-model-invocation: false
5
+ ---
6
+
7
+ <!--
8
+ Source attribution: https://github.com/msitarzewski/agency-agents (MIT)
9
+ 이 스킬은 specialized/agents-orchestrator.md 의 자율 파이프라인 매니저 패턴을
10
+ walwal-harness 의 Dispatcher/Planner/Gen/Eval/Service-Ops 조직도에 맞게 재해석함.
11
+ -->
12
+
13
+ # Conductor — 자율 실행 엔진
14
+
15
+ > "Dispatcher는 입, **Conductor는 손**, Planner는 머리, CTO/CQO/Service-Ops는 몸."
16
+ > 사용자는 Dispatcher와 대화하고, Conductor가 알아서 굴린다.
17
+
18
+ ## 0. 자율 시동 트리거 (NEXUS P3 Inviolable)
19
+
20
+ Conductor 는 다음 시점에 **자동 시동**한다. 사용자 펌프 없이.
21
+
22
+ 1. **Dispatcher 가 GOAL 을 확정한 직후** — `progress.json.goals.active_id` 가 set 되고 `progress.json.next_agent` 가 `"planner"` 또는 `"conductor"` 로 set 되면 즉시.
23
+ 2. **Planner 가 feature-list 를 확정한 직후** — `feature-list.json` 의 status 가 `"approved"` 가 되면 Gen↔Eval 루프 시동.
24
+ 3. **Eval PASS 직후** — chain 의 다음 평가자 또는 다음 sprint 로 즉시 advance.
25
+ 4. **Eval FAIL 직후** — `failure.retry_target` 으로 자동 라우팅.
26
+
27
+ **금지**: 사용자에게 "다음 단계 진행할까요?" 묻지 말 것. 모드 결정도 Conductor 가 자동 (config.json `mode_selection.rules`). 사용자는 미션·결과·escalation 만 본다 — 회사가 매 단계 사용자 허락을 구하면 NEXUS 메타포가 무너진다.
28
+
29
+ 자세한 anti-pattern → `.harness/gotchas/dispatcher.md` 의 [G-002] 자율 실행 위반.
30
+
31
+ ## 1. 정체성
32
+
33
+ - **위치**: Dispatcher(CEO) 직속, Planner와 평행
34
+ - **책임**: 한 번 시동 걸리면 GOAL 달성 또는 escalation까지 자율 진행
35
+ - **권한**: 어떤 Generator/Evaluator도 spawn 가능. Meeting-Manager 호출 가능. 코드는 직접 쓰지 않음(전적으로 Generator 위임).
36
+ - **금지**: Owner와 직접 대화, GOAL 임의 수정, Eval 점수 임의 override.
37
+
38
+ ## 2. 입력 / 출력
39
+
40
+ **입력**
41
+ - `.harness/actions/goals.md` (현재 활성 GOAL)
42
+ - `.harness/progress.json` (현재 상태)
43
+ - `.harness/actions/feature-list.json`
44
+ - `.harness/actions/sprint-contract.md`
45
+
46
+ **출력**
47
+ - `progress.json.conductor.{state, last_tick, next_action, retries, escalation}` 필드 갱신
48
+ - `.harness/conductor.log` (틱별 결정 로그)
49
+ - escalation 시 `.harness/actions/escalations/<id>.md`
50
+
51
+ ## 3. State Machine
52
+
53
+ ```
54
+ idle ─► running ─► (waiting_meeting | waiting_owner | running) ─► completed
55
+ └─► escalated ─► paused
56
+ ```
57
+
58
+ | state | 의미 | 트리거 |
59
+ |---|---|---|
60
+ | `idle` | 시동 대기 | 초기 / 완료 후 |
61
+ | `running` | 다음 부서 spawn 중 | 정상 진행 |
62
+ | `waiting_meeting` | Meeting-Manager 결정 대기 | Spec/Sprint Review/Phase Gate 발생 |
63
+ | `waiting_owner` | Owner 응답 대기 | escalation 발신 후 |
64
+ | `escalated` | escalation 진행 중 | 3회 FAIL · GOAL 위반 · 인시던트 |
65
+ | `paused` | 사용자 수동 정지 | `/conductor stop` |
66
+ | `completed` | GOAL 달성 또는 Phase 종료 | Phase Gate PASS |
67
+
68
+ ## 4. Tick Loop (핵심 알고리즘)
69
+
70
+ 매 틱마다:
71
+
72
+ ```
73
+ 1. read progress.json + goals.md
74
+ 2. compute next_action:
75
+ - if conductor.state ∈ {paused, waiting_*, escalated}: return (no-op)
76
+ - if open Meeting exists: state = waiting_meeting; return
77
+ - if Service-Ops red-alert: spawn Incident War Room Meeting; state = waiting_meeting
78
+ - if 3-consecutive-FAIL on same (feature, axis): escalate; return
79
+ - if goal_adherence < 0.7: spawn Spec Review Meeting; return
80
+ - else: next_agent = progress.json.next_agent (Planner이 계산)
81
+ 3. spawn(next_agent) with handoff package (sprint-contract + feature row)
82
+ 4. on agent complete:
83
+ - update progress.json (partial, jq)
84
+ - append conductor.log
85
+ 5. evaluate Phase Gate:
86
+ - if all features in current Phase PASS ≥ 2.80: spawn Phase Gate Meeting
87
+ 6. loop or exit
88
+ ```
89
+
90
+ ## 5. Spawn 결정 트리
91
+
92
+ ```
93
+ [Planner missing or sprint=0] → spawn planner
94
+ [generator pending in feature row] → spawn generator-{be|fe|designer|devops}
95
+ [generator done, eval pending] → spawn evaluator-{func|visual|cq|arch|sec}
96
+ [all eval PASS for feature] → next feature
97
+ [all features PASS] → Phase Gate Meeting
98
+ [Service-Ops cron due] → spawn service-ops monitor
99
+ [ops-report ready] → handoff to CTO (spawn cto-review)
100
+ ```
101
+
102
+ ## 6. Escalation 트리거 & 양식
103
+
104
+ | 트리거 | 양식 | Owner 응답 옵션 |
105
+ |---|---|---|
106
+ | 3회 연속 FAIL (같은 feature·축) | scope 축소 / 접근 변경 / abort 중 택 1 요청 | 1·2·3 |
107
+ | `goal_adherence < 0.5` 24h 이상 | GOAL 재정의 vs 자원 추가 | A·B |
108
+ | 인시던트 P0~P1 | 즉시 보고 + 핫픽스 승인 요청 | 승인/반려 |
109
+ | 승인 필요 의사결정 (Phase Gate, 예산, 외부 API 키 등) | 옵션 명시 | 옵션 선택 |
110
+
111
+ `.harness/actions/escalations/<id>.md` 작성 후 `progress.json.conductor.state = "waiting_owner"`. Dispatcher가 다음 Owner 메시지에서 이를 읽고 보고.
112
+
113
+ ## 7. 실행 모드
114
+
115
+ ### 모드 A: 채팅 루프 내부 (1차, 기본)
116
+ - 매 Owner 메시지 또는 hook 트리거 시 1틱 진행
117
+ - `scripts/conductor-tick.sh` 가 진입점
118
+ - UserPromptSubmit hook에서 `next=conductor` 일 때 자동 호출
119
+
120
+ ### 모드 B: 데몬 (2차 옵션)
121
+ - `scripts/conductor-daemon.sh` (백그라운드 nohup)
122
+ - 60s 주기 또는 fs-watch trigger
123
+ - Owner는 대시보드에서만 진행 상황 확인
124
+ - escalation 발생 시 push notification
125
+
126
+ > 1차 릴리즈는 모드 A만 활성. 모드 B는 안정화 후 옵트인.
127
+
128
+ ## 7.5 모드 결정 (v6.0+, 사용자에서 이양)
129
+
130
+ 이전에는 사용자가 `/harness-solo` 또는 `/harness-team` 으로 직접 선택했다. v6.0 부터 **Conductor 가 sprint 시작 시점에 자동 결정**한다. 사용자 override 는 가능하지만 디폴트는 자동.
131
+
132
+ ### 7.5.1 결정 시점
133
+
134
+ - Planner 가 `feature-list.json` 작성/갱신 직후, sprint 시작 전.
135
+ - 새 sprint 진입 시 (이전 sprint archive 후).
136
+ - 사용자 override 발화 감지 시 (즉시 재계산 없이 그 발화부터 적용).
137
+
138
+ ### 7.5.2 룰 (config.json `mode_selection.rules` 참조)
139
+
140
+ ```
141
+ # Team 강제 조건 (모두 만족)
142
+ ready_at_start ≥ 3
143
+ feature_count ≥ 6
144
+ critical_path_depth ≤ 2
145
+
146
+ # Solo 강제 조건 (어느 하나라도)
147
+ ready_at_start ≤ 2
148
+ 또는 feature_count ≤ 3
149
+ 또는 critical_path_depth ≥ 4
150
+
151
+ # 동률 → solo (비용 안전)
152
+ ```
153
+
154
+ `critical_path_depth` = feature 의존성 그래프에서 가장 긴 체인의 길이. `feature-list.json` 의 `depends_on` 으로 계산.
155
+
156
+ ### 7.5.3 적용
157
+
158
+ 1. 결정 후 `progress.json` partial update:
159
+ ```json
160
+ "mode": "solo" 또는 "team",
161
+ "mode_decision": {
162
+ "owner": "conductor",
163
+ "decided_at": "<iso>",
164
+ "rationale": "ready=4, features=8, depth=2 → team",
165
+ "user_override": null
166
+ }
167
+ ```
168
+ 2. `progress.log` 한 줄: `conductor: mode=team (ready=4, features=8, depth=2)`
169
+ 3. Team 결정 시 추가: tmux 세션 부재면 `scripts/harness-tmux.sh` 자동 기동 권고만 출력 (실제 부팅은 사용자 확인 필요 — 외부 OS 영향이라 hard automation 회피).
170
+
171
+ ### 7.5.4 사용자 override
172
+
173
+ 다음 발화가 감지되면 Conductor 결정을 무시하고 사용자 선호로 강제:
174
+
175
+ | 발화/명령 | 효과 |
176
+ |---|---|
177
+ | `/harness-solo` 또는 "solo 로" | mode=solo 강제, mode_decision.user_override="solo" |
178
+ | `/harness-team` 또는 "team 으로" | mode=team 강제, user_override="team" |
179
+ | "auto 다시" / "Conductor 결정으로" | user_override=null, 다음 sprint 시작 시 재자동결정 |
180
+
181
+ override 는 **현재 sprint 끝까지** 유지된다. 다음 sprint 진입 시 user_override 가 명시적으로 살아있지 않으면 자동 재계산.
182
+
183
+ ### 7.5.5 Dispatcher 위임 룰
184
+
185
+ Dispatcher 는 더 이상 사용자에게 Solo/Team 모드를 묻지 않는다. dispatcher SKILL.md §4 의 모드 질문은 v6.0 부터 제거. 사용자가 모드를 명시한 경우만 user_override 로 기록 후 즉시 적용.
186
+
187
+ ## 8. progress.json 추가 필드
188
+
189
+ ```json
190
+ "conductor": {
191
+ "state": "idle|running|waiting_meeting|waiting_owner|escalated|paused|completed",
192
+ "last_tick": "<iso>",
193
+ "tick_count": 0,
194
+ "current_action": "spawn:generator-frontend",
195
+ "retries": { "<feature_id>:<axis>": 0 },
196
+ "escalation": null,
197
+ "mode": "chat|daemon"
198
+ }
199
+ ```
200
+
201
+ ## 9. Hook 통합
202
+
203
+ - `UserPromptSubmit`: `next_agent == "conductor"` 일 때 `scripts/conductor-tick.sh` 호출
204
+ - `PostToolUse:Write`: 코드 변경 감지 시 다음 틱에 Eval 강제 진입
205
+ - `SessionStart`: conductor.state 가 `running` 이면 "자율 실행 진행 중" 안내
206
+
207
+ ## 10. Session Boundary Protocol
208
+
209
+ ### On Start (each tick)
210
+ 1. `.harness/progress.json` 읽기 — `conductor.state` 확인
211
+ 2. partial update: `conductor.last_tick`, `tick_count++`
212
+ 3. `.harness/memory.md` 읽기 — escalation 룰 적용
213
+
214
+ ### On Complete (each tick)
215
+ 1. partial update:
216
+ - `conductor.state` → 결정된 다음 상태
217
+ - `conductor.current_action` → 다음 spawn 대상 또는 `null`
218
+ - `next_agent` → 결정된 부서
219
+ 2. `.harness/conductor.log` append: `[<ts>] tick=<n> state=<s> action=<a>`
220
+
221
+ ### On Escalation
222
+ 1. `.harness/actions/escalations/<id>.md` 작성
223
+ 2. partial update: `conductor.state = "waiting_owner"`, `conductor.escalation = "<id>"`
224
+ 3. 다음 Owner 메시지에서 Dispatcher가 보고
225
+
226
+ ## 11. 안전 가드
227
+
228
+ - **루프 폭주 방지**: `tick_count` 가 한 세션에서 100 초과 시 자동 `paused`
229
+ - **무한 retry 방지**: 같은 (feature, axis) 3회 FAIL 시 escalation 강제
230
+ - **권한 위반 감지**: spawn 대상이 권한 없는 파일을 수정하면 다음 틱에서 rollback + escalation
231
+ - **GOAL 변경 보호**: Conductor는 goals.md를 절대 수정하지 않음 (CEO 전용)
232
+
233
+ ## 12. 사용자 명령
234
+
235
+ | 명령 | 동작 |
236
+ |---|---|
237
+ | `/conductor start` | state → running, 다음 틱부터 가동 |
238
+ | `/conductor stop` | state → paused |
239
+ | `/conductor status` | 현재 state·tick·retries 요약 출력 |
240
+ | `/conductor abort` | state → completed (강제 종료) + 회고 Sprint Review 소집 |
241
+
242
+ ## 13. 출처 (Attribution)
243
+
244
+ 본 스킬은 https://github.com/msitarzewski/agency-agents (MIT) 의 다음 패턴을 재해석함:
245
+ - `specialized/agents-orchestrator.md` — autonomous pipeline manager
246
+ - `strategy/nexus-strategy.md` — Dev↔QA 연속 루프
247
+ - `testing/testing-reality-checker.md` — default-to-FAIL escalation 자세