@walwal-harness/cli 6.1.4 → 6.1.6

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +43 -74
  3. package/assets/launchd/com.walwal.harness-wake.plist.template +32 -0
  4. package/assets/templates/CONVENTIONS.md +12 -0
  5. package/assets/templates/HARNESS.md +8 -8
  6. package/assets/templates/company-pipeline-manifest.json +71 -0
  7. package/assets/templates/config.json +29 -27
  8. package/assets/templates/memory.md +16 -2
  9. package/assets/templates/progress.json.template +6 -5
  10. package/bin/init.js +180 -90
  11. package/conventions/shared.md +34 -0
  12. package/gotchas/conductor.md +16 -0
  13. package/gotchas/dispatcher.md +16 -0
  14. package/gotchas/meeting-manager.md +8 -0
  15. package/gotchas/service-ops.md +8 -0
  16. package/package.json +4 -4
  17. package/scripts/conductor-tick.sh +157 -56
  18. package/scripts/harness-archive.sh +7 -5
  19. package/scripts/harness-dashboard-up.sh +11 -3
  20. package/scripts/harness-hourly-review.sh +582 -0
  21. package/scripts/harness-meeting-doc.sh +55 -16
  22. package/scripts/harness-next.sh +1 -1
  23. package/scripts/harness-parity-audit.sh +101 -0
  24. package/scripts/harness-queue-manager.sh +13 -6
  25. package/scripts/harness-service-ops-monitor.sh +273 -0
  26. package/scripts/harness-session-start.sh +60 -31
  27. package/scripts/harness-statusline.sh +8 -12
  28. package/scripts/harness-stop.sh +85 -0
  29. package/scripts/harness-user-prompt-submit.sh +11 -29
  30. package/scripts/harness-wake-install.sh +180 -0
  31. package/scripts/harness-wake.sh +350 -0
  32. package/scripts/harness-worker-dispatch.sh +188 -0
  33. package/scripts/lib/harness-agent-resolver.sh +127 -0
  34. package/scripts/lib/harness-progress-migrate.sh +6 -1
  35. package/scripts/lib/harness-render-progress.sh +3 -3
  36. package/skills/brainstorming/SKILL.md +2 -2
  37. package/skills/conductor/SKILL.md +67 -55
  38. package/skills/cqo/SKILL.md +1 -0
  39. package/skills/cto/SKILL.md +1 -0
  40. package/skills/dispatcher/SKILL.md +33 -14
  41. package/skills/evaluator-code-quality/SKILL.md +4 -4
  42. package/skills/evaluator-functional/SKILL.md +6 -6
  43. package/skills/evaluator-visual/SKILL.md +4 -4
  44. package/skills/generator-backend/SKILL.md +4 -4
  45. package/skills/generator-frontend/SKILL.md +5 -5
  46. package/skills/meeting-manager/SKILL.md +82 -12
  47. package/skills/planner/SKILL.md +3 -3
  48. package/skills/service-ops/SKILL.md +25 -0
  49. package/commands/harness-solo.md +0 -103
  50. package/commands/harness-stop.md +0 -53
  51. package/commands/harness-team.md +0 -530
  52. package/scripts/harness-dashboard.sh +0 -509
  53. package/scripts/harness-goal-init.sh +0 -72
  54. package/scripts/harness-goal-show.sh +0 -37
  55. package/scripts/harness-gotcha-memory.sh +0 -348
  56. package/scripts/harness-monitor.sh +0 -398
  57. package/scripts/harness-prompt-history.sh +0 -164
  58. package/scripts/harness-tmux.sh +0 -372
@@ -32,7 +32,7 @@ sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
32
32
  current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
33
33
  next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
34
34
  agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
35
- mode=$(jq -r '.mode // "team"' "$PROGRESS" 2>/dev/null)
35
+ mode=$(jq -r '.mode // "company"' "$PROGRESS" 2>/dev/null)
36
36
  conductor_state=$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null)
37
37
  task_stop_active=$(jq -r '.task_stop.active // false' "$PROGRESS" 2>/dev/null)
38
38
  task_stop_reason=$(jq -r '.task_stop.reason // "null"' "$PROGRESS" 2>/dev/null)
@@ -53,32 +53,65 @@ now_epoch() {
53
53
  }
54
54
 
55
55
  # ─────────────────────────────────────────
56
- # Auto-heal mode drift — Team 상태 유실 복구
57
- # feature-queue.json 에 활성 작업(in_progress)이 있는데 mode 가 solo/paused 면
58
- # 누군가 progress.json 을 통째로 덮어써서 team_state 를 날린 것. mode=team 으로
59
- # 자동 복원하고 경고 로그 남김.
56
+ # Normalize legacy mode drift — v6.3+ always runs company mode.
60
57
  # ─────────────────────────────────────────
61
58
  FEATURE_QUEUE_HEAL="$PROJECT_ROOT/.harness/actions/feature-queue.json"
62
- if [ -f "$FEATURE_QUEUE_HEAL" ] && [ "$mode" != "team" ]; then
63
- active_count=$(jq -r '(.queue.in_progress | length) // 0' "$FEATURE_QUEUE_HEAL" 2>/dev/null || echo 0)
64
- if [ "${active_count:-0}" -gt 0 ]; then
65
- heal_teams=$active_count
66
- [ "$heal_teams" -gt 3 ] && heal_teams=3
67
- bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" \
68
- ".mode = \"team\" | .team_state.active_teams = ${heal_teams} | .team_state.paused_at = null" \
69
- 2>/dev/null
70
- mode="team"
71
- echo "# Harness: mode auto-healed to 'team' (feature-queue has ${active_count} active, but mode was drifted)" >&2
72
- if [ -f "$PROJECT_ROOT/.harness/progress.log" ]; then
73
- echo "$(date +'%Y-%m-%d %H:%M') | system | heal | mode | reset solo→team (queue had ${active_count} in_progress)" >> "$PROJECT_ROOT/.harness/progress.log"
74
- fi
59
+ if [ "$mode" != "company" ]; then
60
+ bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" \
61
+ '.mode = "company" |
62
+ .mode_decision.owner = "conductor" |
63
+ .mode_decision.policy = "always_company_parallel" |
64
+ .mode_decision.user_override = null |
65
+ .mode_decision.decided_at = (now | todate) |
66
+ .mode_decision.rationale = "legacy mode normalized to always-on company mode"' \
67
+ 2>/dev/null || true
68
+ mode="company"
69
+ [ -f "$PROJECT_ROOT/.harness/progress.log" ] && echo "$(date +'%Y-%m-%d %H:%M') | system | heal | mode | normalized legacy mode to company" >> "$PROJECT_ROOT/.harness/progress.log"
70
+ fi
71
+
72
+ # ─────────────────────────────────────────
73
+ # Truthful Logging Heal — strip future-dated progress.log entries
74
+ # ─────────────────────────────────────────
75
+ PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
76
+ if [ -f "$PROGRESS_LOG" ]; then
77
+ NOW_KST=$(date "+%Y-%m-%d %H:%M")
78
+ NOW_UTC_MIN=$(date -u "+%Y-%m-%d %H:%M")
79
+ QUARANTINE_FILE="$PROGRESS_LOG.future-quarantine.$(date +%s)"
80
+ KEEP_FILE="$PROGRESS_LOG.tmp.$$"
81
+
82
+ # 미래 시각으로 시작하는 라인 격리. KST(YYYY-MM-DD HH:MM) 와 ISO(YYYY-MM-DDTHH:MM:SSZ) 둘 다 처리.
83
+ awk -v now_kst="$NOW_KST" -v now_utc="$NOW_UTC_MIN" -v qfile="$QUARANTINE_FILE" '
84
+ function iso_to_minute(s) {
85
+ gsub("\\[", "", s); gsub("T", " ", s); gsub("Z", "", s)
86
+ return substr(s, 1, 16)
87
+ }
88
+ {
89
+ future=0
90
+ if (match($0, /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}/)) {
91
+ ts = substr($0, RSTART, RLENGTH)
92
+ if (ts > now_kst) future=1
93
+ } else if (match($0, /^\[?[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}/)) {
94
+ ts_min = iso_to_minute(substr($0, RSTART, RLENGTH+5))
95
+ if (ts_min > now_utc) future=1
96
+ }
97
+ if (future) print >> qfile
98
+ else print
99
+ }
100
+ ' "$PROGRESS_LOG" > "$KEEP_FILE"
101
+
102
+ if [ -f "$QUARANTINE_FILE" ] && [ -s "$QUARANTINE_FILE" ]; then
103
+ QCOUNT=$(wc -l < "$QUARANTINE_FILE" | tr -d ' ')
104
+ mv "$KEEP_FILE" "$PROGRESS_LOG"
105
+ echo "$(date +'%Y-%m-%d %H:%M') | system | heal | log | quarantined ${QCOUNT} future-dated lines → $(basename "$QUARANTINE_FILE")" >> "$PROGRESS_LOG"
106
+ else
107
+ rm -f "$KEEP_FILE" "$QUARANTINE_FILE" 2>/dev/null
75
108
  fi
76
109
  fi
77
110
 
78
111
  # ─────────────────────────────────────────
79
- # Team Mode — 팀이 자율 실행 중이면 오케스트레이터 안내
112
+ # Company Mode — autonomous parallel company loop
80
113
  # ─────────────────────────────────────────
81
- if [ "$mode" = "team" ]; then
114
+ if [ "$mode" = "company" ]; then
82
115
  FEATURE_QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
83
116
  passed=0; total=0; in_prog=0; failed=0
84
117
  if [ -f "$FEATURE_QUEUE" ]; then
@@ -88,18 +121,9 @@ if [ "$mode" = "team" ]; then
88
121
  failed=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
89
122
  fi
90
123
 
91
- echo "# Harness Team Mode active"
124
+ echo "# Harness Company Mode active"
92
125
  echo "# Queue: ${passed}/${total} passed, ${in_prog} in progress, ${failed} failed"
93
- echo "# Worker pool runs autonomously (Gen-Eval loop, max 5 retries). Control-plane Cxx agents are outside this limit."
94
- echo "# Stop: /harness-stop | Emergency fallback: /harness-solo"
95
- exit 0
96
- fi
97
-
98
- # ─────────────────────────────────────────
99
- # Paused Mode — 중단 상태 안내
100
- # ─────────────────────────────────────────
101
- if [ "$mode" = "paused" ]; then
102
- echo "# Harness paused — resume with /harness-team"
126
+ echo "# Company loop runs autonomously with parallel workers. Owner input is an interrupt only; do not wait for Owner after the initial GOAL."
103
127
  exit 0
104
128
  fi
105
129
 
@@ -144,9 +168,14 @@ fi
144
168
  # init 상태: 첫 안내
145
169
  # ─────────────────────────────────────────
146
170
  if [ "$sprint_status" = "init" ]; then
171
+ if [ "$next_agent" != "null" ] && [ "$next_agent" != "none" ] && [ "$next_agent" != "dispatcher" ]; then
172
+ echo "# Harness Company Mode active"
173
+ echo "# Sprint is init, but next_agent=${next_agent}; continue the autonomous loop instead of waiting for Owner."
174
+ else
147
175
  echo "# Harness ready — say \"하네스 엔지니어링 시작\" or /harness-dispatcher"
148
176
  echo "# 기본 경로는 회사 루프입니다: dispatch -> CEO meeting -> COO/CTO 분배 -> gen/eval -> CQO -> service-ops -> batch meeting."
149
177
  exit 0
178
+ fi
150
179
  fi
151
180
 
152
181
  # ─────────────────────────────────────────
@@ -1,8 +1,7 @@
1
1
  #!/bin/bash
2
2
  # harness-statusline.sh — Claude Code statusline hook (v5 unified)
3
3
  # 터미널 하단에 항상 고정되는 1줄 compact 상태 표시.
4
- # Solo: [S1] FULL | solo | >backend | sonnet | 2/5 feat | ctx 45% | $1.23
5
- # Team: [S1] FULL | team | T1:gen T2:eval T3:idle | 2/5 feat | ctx 45% | $1.23
4
+ # Company: [C1] FULL | company | T1:gen T2:eval T3:idle | 2/5 work | ctx 45% | $1.23
6
5
 
7
6
  # Read Claude Code session data from stdin
8
7
  input=$(cat)
@@ -32,14 +31,13 @@ if [ ! -f "$PROGRESS" ]; then
32
31
  fi
33
32
 
34
33
  # Read harness state
35
- sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
36
- sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null)
34
+ cycle_num=$(jq -r '.sprint.number // .cycle.number // 0' "$PROGRESS" 2>/dev/null)
37
35
  pipeline=$(jq -r '.pipeline // "?"' "$PROGRESS" 2>/dev/null)
38
36
  current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
39
37
  agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
40
38
  next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
41
39
  retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS" 2>/dev/null)
42
- harness_mode=$(jq -r '.mode // "solo"' "$PROGRESS" 2>/dev/null)
40
+ harness_mode="company"
43
41
 
44
42
  # Pipeline short name
45
43
  case "$pipeline" in
@@ -111,8 +109,8 @@ fi
111
109
  # Build compact status line based on mode
112
110
  FEATURE_QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
113
111
 
114
- if [ "$harness_mode" = "team" ] && [ -f "$FEATURE_QUEUE" ]; then
115
- # Team mode: show team status instead of single agent
112
+ if [ -f "$FEATURE_QUEUE" ]; then
113
+ # Company mode: show worker status instead of single agent
116
114
  team_info=""
117
115
  for i in 1 2 3; do
118
116
  t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$FEATURE_QUEUE" 2>/dev/null)
@@ -127,13 +125,11 @@ if [ "$harness_mode" = "team" ] && [ -f "$FEATURE_QUEUE" ]; then
127
125
  t_passed=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
128
126
  t_total=$(jq '[.queue.ready, (.queue.blocked | keys), (.queue.in_progress | keys), .queue.passed, .queue.failed] | flatten | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
129
127
 
130
- echo "[S${sprint_num}] ${pl} | team | ${team_info}| ${t_passed}/${t_total} feat | ctx ${context_pct}% | \$${cost}"
128
+ echo "[C${cycle_num}] ${pl} | company | ${team_info}| ${t_passed}/${t_total} work | ctx ${context_pct}% | \$${cost}"
131
129
  else
132
- # Solo/paused mode: show single agent status
133
- mode_tag="${harness_mode}"
134
130
  if [ -n "$model_short" ]; then
135
- echo "[S${sprint_num}] ${pl} | ${mode_tag} | ${status_icon}${agent_short}${retry_str} | ${model_short} | ${completed_features}/${total_features} feat | ctx ${context_pct}% | \$${cost}"
131
+ echo "[C${cycle_num}] ${pl} | company | ${status_icon}${agent_short}${retry_str} | ${model_short} | ${completed_features}/${total_features} work | ctx ${context_pct}% | \$${cost}"
136
132
  else
137
- echo "[S${sprint_num}] ${pl} | ${mode_tag} | ${status_icon}${agent_short}${retry_str} | ${completed_features}/${total_features} feat | ctx ${context_pct}% | \$${cost}"
133
+ echo "[C${cycle_num}] ${pl} | company | ${status_icon}${agent_short}${retry_str} | ${completed_features}/${total_features} work | ctx ${context_pct}% | \$${cost}"
138
134
  fi
139
135
  fi
@@ -0,0 +1,85 @@
1
+ #!/bin/bash
2
+ # harness-stop.sh — Stop hook (v6.2)
3
+ #
4
+ # Claude 가 한 turn 을 끝내려 할 때 발화.
5
+ # conductor.state == "running" 이고 다음 작업이 남아 있으면
6
+ # stdout 으로 {"decision":"block", "reason":"..."} JSON 을 출력해
7
+ # Claude 가 멈추지 않고 다음 tick 으로 자동 연쇄하게 한다.
8
+ #
9
+ # 멈춰야 할 경우 (회사 루프 정지·task_stop 등) 는 그냥 exit 0
10
+ # → Claude 가 정상적으로 turn 을 종료.
11
+
12
+ set -uo pipefail
13
+
14
+ INPUT=$(cat 2>/dev/null || echo "{}")
15
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)
16
+ [ -z "$CWD" ] && CWD="$PWD"
17
+
18
+ # 하네스 초기화 안 된 곳: no-op
19
+ [ -f "$CWD/.harness/progress.json" ] || exit 0
20
+ [ -f "$CWD/.harness/config.json" ] || exit 0
21
+ command -v jq >/dev/null 2>&1 || exit 0
22
+
23
+ PROGRESS="$CWD/.harness/progress.json"
24
+ CONFIG="$CWD/.harness/config.json"
25
+
26
+ # Opt-out
27
+ AUTO_CHAIN=$(jq -r '.behavior.auto_chain_on_stop // true' "$CONFIG" 2>/dev/null || echo "true")
28
+ if [ "$AUTO_CHAIN" != "true" ]; then exit 0; fi
29
+
30
+ # 무한루프 방지: 한 sprint 안에서 stop_chain_count 가 상한을 넘으면 중단
31
+ STOP_CHAIN_MAX=$(jq -r '.behavior.auto_chain_max_per_sprint // 200' "$CONFIG" 2>/dev/null || echo 200)
32
+ STOP_CHAIN_COUNT=$(jq -r '.conductor.stop_chain_count // 0' "$PROGRESS" 2>/dev/null || echo 0)
33
+ if [ "${STOP_CHAIN_COUNT:-0}" -ge "${STOP_CHAIN_MAX:-200}" ]; then exit 0; fi
34
+
35
+ # 회사 루프 상태 확인
36
+ CONDUCTOR_STATE=$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null || echo "idle")
37
+ NEXT_AGENT=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null || echo "none")
38
+ AGENT_STATUS=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null || echo "pending")
39
+ TASK_STOP_ACTIVE=$(jq -r '.task_stop.active // false' "$PROGRESS" 2>/dev/null || echo "false")
40
+ ESCALATION=$(jq -r '.conductor.escalation // "null"' "$PROGRESS" 2>/dev/null || echo "null")
41
+ SPRINT_STATUS=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null || echo "init")
42
+
43
+ # stop hook 이 자기 자신에 의해 재진입되지 않도록 hook event 필터링
44
+ HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null || true)
45
+ if [ "$HOOK_EVENT" = "Stop" ]; then
46
+ STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false' 2>/dev/null || echo "false")
47
+ if [ "$STOP_HOOK_ACTIVE" = "true" ]; then exit 0; fi
48
+ fi
49
+
50
+ # 자동 연쇄 조건:
51
+ # conductor 가 running
52
+ # AND task_stop 비활성
53
+ # AND task_stop 비활성
54
+ # AND (next_agent 가 명시되어 있거나, agent_status=completed 라서 다음 tick 결정 필요)
55
+ should_chain="false"
56
+ if [ "$CONDUCTOR_STATE" = "running" ] \
57
+ && [ "$TASK_STOP_ACTIVE" != "true" ]; then
58
+ if [ "$NEXT_AGENT" != "none" ] && [ "$NEXT_AGENT" != "null" ]; then
59
+ should_chain="true"
60
+ elif [ "$AGENT_STATUS" = "completed" ]; then
61
+ should_chain="true"
62
+ fi
63
+ fi
64
+
65
+ if [ "$should_chain" != "true" ]; then exit 0; fi
66
+
67
+ # stop_chain_count 증가 (느슨한 카운터 — race 허용)
68
+ NEW_COUNT=$((STOP_CHAIN_COUNT + 1))
69
+ TMP=$(mktemp)
70
+ jq --argjson n "$NEW_COUNT" '.conductor.stop_chain_count = $n |
71
+ .conductor.last_stop_chain_at = (now | todate)' "$PROGRESS" > "$TMP" 2>/dev/null \
72
+ && mv "$TMP" "$PROGRESS" || rm -f "$TMP"
73
+
74
+ # Claude 에게 다음 행동 지시
75
+ if [ "$ESCALATION" != "null" ]; then
76
+ REASON="자율 회사 루프 진행 중. escalation=${ESCALATION} 는 정지 사유가 아니라 meeting-manager/service-ops 공유 대상이다. next_agent=${NEXT_AGENT}, agent_status=${AGENT_STATUS}, conductor=${CONDUCTOR_STATE}. SKILL 에 따라 conductor-tick 을 굴려 meeting-manager 회의로 라우팅하거나 다음 부서를 spawn 하라. progress.log 에는 절대 미래 시각을 쓰지 말 것. 진짜로 끝났을 때만 turn 을 종료하라."
77
+ else
78
+ REASON="자율 회사 루프 진행 중. next_agent=${NEXT_AGENT}, agent_status=${AGENT_STATUS}, conductor=${CONDUCTOR_STATE}. SKILL 에 따라 다음 부서를 spawn 하거나 conductor-tick 을 한 번 더 굴려라. progress.log 에는 절대 미래 시각을 쓰지 말 것. 진짜로 끝났을 때만 turn 을 종료하라."
79
+ fi
80
+
81
+ jq -nc \
82
+ --arg reason "$REASON" \
83
+ '{decision: "block", reason: $reason}'
84
+
85
+ exit 0
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
2
  # harness-user-prompt-submit.sh — UserPromptSubmit hook (v5 unified)
3
- # 핵심 상태 + 라우팅 지시만 주입. 회사 루프 기본, solo는 비상용 override.
3
+ # 핵심 상태 + 라우팅 지시만 주입. 회사 루프는 항상 병렬/자율 진행.
4
4
  set -e
5
5
 
6
6
  INPUT=$(cat)
@@ -102,41 +102,23 @@ EOF
102
102
  exit 0
103
103
  fi
104
104
 
105
- # ── Mode 기반 분기 ──
106
- MODE="team"
107
- if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
108
- MODE=$(jq -r '.mode // "team"' "$CWD/.harness/progress.json" 2>/dev/null || echo "team")
109
- fi
110
-
111
- if [ "$MODE" = "team" ]; then
112
- FEATURE_QUEUE="$CWD/.harness/actions/feature-queue.json"
113
- T_PASSED=0; T_TOTAL=0; T_FAILED=0
114
- if [ -f "$FEATURE_QUEUE" ]; then
115
- T_PASSED=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
116
- T_TOTAL=$(jq '[.queue.ready, (.queue.blocked | keys), (.queue.in_progress | keys), .queue.passed, .queue.failed] | flatten | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
117
- T_FAILED=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
118
- fi
119
-
120
- cat <<EOF
121
- [harness] team | ${T_PASSED}/${T_TOTAL} passed | ${T_FAILED} failed
122
-
123
- ## Team Mode Active
124
- - worker pool(기본 3)이 자율적으로 Gen→Eval 루프 실행 중 (max 5 retries)
125
- - CEO/Meeting/CTO/CQO/Service-Ops control-plane은 이 상한에 포함되지 않음
126
- - 역할: 모니터링, 실패 대응, Goal 점검
127
- - /harness-stop → 중단 | /harness-solo → 비상용 단일 에이전트 fallback
128
- - skip: "harness skip" 시 일반 대화
129
- EOF
130
- exit 0
105
+ FEATURE_QUEUE="$CWD/.harness/actions/feature-queue.json"
106
+ T_PASSED=0; T_TOTAL=0; T_FAILED=0
107
+ if [ -f "$FEATURE_QUEUE" ]; then
108
+ T_PASSED=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
109
+ T_TOTAL=$(jq '[.queue.ready, (.queue.blocked | keys), (.queue.in_progress | keys), .queue.passed, .queue.failed] | flatten | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
110
+ T_FAILED=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
131
111
  fi
132
112
 
133
- # ── Compact context 주입 (solo/paused mode) ──
134
113
  cat <<EOF
135
- [harness] S${SPRINT_NUM} | ${PIPELINE} | agent=${CURRENT_AGENT} (${AGENT_STATUS}) | next=${NEXT_AGENT}
114
+ [harness] company | S${SPRINT_NUM} | ${PIPELINE} | agent=${CURRENT_AGENT} (${AGENT_STATUS}) | next=${NEXT_AGENT} | queue=${T_PASSED}/${T_TOTAL} passed | failed=${T_FAILED}
136
115
  ${CONTEXT_WARNING}
137
116
  ## Route
138
117
  - pipeline=none/dispatcher 미실행 → harness-dispatcher 스킬 호출
139
118
  - 기본 경로는 conductor 중심 회사 루프다: dispatch -> CEO meeting -> planner(COO) -> cto -> gen/eval -> cqo -> service-ops -> batch meeting
119
+ - worker pool(기본 3)이 자율적으로 Gen→Eval 루프 실행 중이며 control-plane은 이 상한에 포함되지 않는다
120
+ - Owner에게 "계속 진행", "진행할까요?", "다음 명령을 입력하세요"를 요구하지 않는다. GOAL이 있고 escalation이 아니면 즉시 next_agent/current_agent 업무를 수행한다
121
+ - Owner 입력은 목표 변경·사고·결과 확인용이지 회사 진행을 펌프하는 신호가 아니다
140
122
  - conductor 는 Planner 직행 대신 meeting-manager / cto / cqo / service-ops 로 next_agent를 재작성할 수 있다
141
123
  - 기능 요청 → pipeline flow | 실수 지적 → gotcha flow | 메타 질문 → 짧게 응답 (skip)
142
124
  - 활성 pipeline → next_agent/current_agent 컨텍스트로 계속
@@ -0,0 +1,180 @@
1
+ #!/bin/bash
2
+ # harness-wake-install.sh — launchd hourly wake 설치/관리
3
+ #
4
+ # 사용법:
5
+ # bash scripts/harness-wake-install.sh install [project-root...]
6
+ # bash scripts/harness-wake-install.sh add <project-root>
7
+ # bash scripts/harness-wake-install.sh remove <project-root>
8
+ # bash scripts/harness-wake-install.sh list
9
+ # bash scripts/harness-wake-install.sh status
10
+ # bash scripts/harness-wake-install.sh uninstall
11
+ #
12
+ # install: 등록된 프로젝트를 합치고 launchd job 을 (재)로드. 추가 인자가 있으면 그 경로들을 새로 추가.
13
+ # add/remove: ~/.walwal-harness/projects.list 만 수정.
14
+ # list: 현재 등록 목록 출력.
15
+ # status: launchd 상태 확인.
16
+ # uninstall: plist 언로드 + 제거.
17
+
18
+ set -uo pipefail
19
+
20
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
21
+ WAKE_SH="$SCRIPT_DIR/harness-wake.sh"
22
+ HARNESS_DIR="$HOME/.walwal-harness"
23
+ PROJECTS_LIST="$HARNESS_DIR/projects.list"
24
+ LOG_DIR="$HARNESS_DIR/logs"
25
+ LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
26
+ PLIST_NAME="com.walwal.harness-wake"
27
+ PLIST_PATH="$LAUNCH_AGENTS_DIR/$PLIST_NAME.plist"
28
+
29
+ # 템플릿 위치 후보 — walwal-harness 패키지 안일 수도, 프로젝트 안일 수도.
30
+ detect_template() {
31
+ local candidates=(
32
+ "$SCRIPT_DIR/../assets/launchd/com.walwal.harness-wake.plist.template"
33
+ "$HOME/project/walwal-harness/assets/launchd/com.walwal.harness-wake.plist.template"
34
+ )
35
+ for c in "${candidates[@]}"; do
36
+ [ -f "$c" ] && { echo "$c"; return 0; }
37
+ done
38
+ return 1
39
+ }
40
+
41
+ ensure_dirs() {
42
+ mkdir -p "$HARNESS_DIR" "$LOG_DIR" "$LAUNCH_AGENTS_DIR"
43
+ touch "$PROJECTS_LIST"
44
+ }
45
+
46
+ abs_path() {
47
+ local p="$1"
48
+ if [ -d "$p" ]; then
49
+ (cd "$p" && pwd)
50
+ else
51
+ echo "$p"
52
+ fi
53
+ }
54
+
55
+ cmd_add() {
56
+ ensure_dirs
57
+ local project
58
+ project=$(abs_path "$1")
59
+ if grep -Fxq "$project" "$PROJECTS_LIST" 2>/dev/null; then
60
+ echo "[wake] already registered: $project"
61
+ return 0
62
+ fi
63
+ echo "$project" >> "$PROJECTS_LIST"
64
+ echo "[wake] added: $project"
65
+ }
66
+
67
+ cmd_remove() {
68
+ ensure_dirs
69
+ local project
70
+ project=$(abs_path "$1")
71
+ if ! grep -Fxq "$project" "$PROJECTS_LIST" 2>/dev/null; then
72
+ echo "[wake] not registered: $project"
73
+ return 0
74
+ fi
75
+ local tmp
76
+ tmp=$(mktemp)
77
+ grep -Fxv "$project" "$PROJECTS_LIST" > "$tmp" || true
78
+ mv "$tmp" "$PROJECTS_LIST"
79
+ echo "[wake] removed: $project"
80
+ }
81
+
82
+ cmd_list() {
83
+ ensure_dirs
84
+ if [ ! -s "$PROJECTS_LIST" ]; then
85
+ echo "(empty)"
86
+ return 0
87
+ fi
88
+ cat "$PROJECTS_LIST"
89
+ }
90
+
91
+ cmd_status() {
92
+ if [ -f "$PLIST_PATH" ]; then
93
+ echo "[wake] plist: $PLIST_PATH (exists)"
94
+ else
95
+ echo "[wake] plist: $PLIST_PATH (NOT installed)"
96
+ fi
97
+ if launchctl list 2>/dev/null | grep -q "$PLIST_NAME"; then
98
+ echo "[wake] launchd: loaded"
99
+ launchctl list | awk -v n="$PLIST_NAME" '$3==n {print " pid="$1" exit="$2" label="$3}'
100
+ else
101
+ echo "[wake] launchd: NOT loaded"
102
+ fi
103
+ echo "[wake] projects:"
104
+ cmd_list | sed 's/^/ /'
105
+ }
106
+
107
+ cmd_install() {
108
+ ensure_dirs
109
+
110
+ # 추가 인자가 있으면 add
111
+ shift_count=0
112
+ for p in "$@"; do
113
+ cmd_add "$p"
114
+ done
115
+
116
+ local template
117
+ template=$(detect_template) || {
118
+ echo "[wake] ERROR: plist template not found" >&2
119
+ return 1
120
+ }
121
+
122
+ # plist 생성
123
+ sed \
124
+ -e "s|{{WAKE_SCRIPT}}|$WAKE_SH|g" \
125
+ -e "s|{{LOG_DIR}}|$LOG_DIR|g" \
126
+ -e "s|{{HOME}}|$HOME|g" \
127
+ "$template" > "$PLIST_PATH"
128
+
129
+ # 기존 jobs 언로드 (idempotent)
130
+ launchctl unload "$PLIST_PATH" 2>/dev/null || true
131
+
132
+ # 로드
133
+ if launchctl load "$PLIST_PATH" 2>/dev/null; then
134
+ echo "[wake] launchd loaded: $PLIST_PATH"
135
+ else
136
+ echo "[wake] WARN: launchctl load 실패. 다음 명령으로 직접 시도해 보세요:" >&2
137
+ echo " launchctl load $PLIST_PATH" >&2
138
+ fi
139
+
140
+ cmd_status
141
+ }
142
+
143
+ cmd_uninstall() {
144
+ if [ -f "$PLIST_PATH" ]; then
145
+ launchctl unload "$PLIST_PATH" 2>/dev/null || true
146
+ rm -f "$PLIST_PATH"
147
+ echo "[wake] uninstalled: $PLIST_PATH"
148
+ else
149
+ echo "[wake] not installed"
150
+ fi
151
+ }
152
+
153
+ cmd_run_now() {
154
+ bash "$WAKE_SH"
155
+ echo "[wake] run-now done. log:"
156
+ tail -20 "$LOG_DIR/wake.log" 2>/dev/null || true
157
+ }
158
+
159
+ case "${1:-}" in
160
+ install) shift; cmd_install "$@" ;;
161
+ add) shift; cmd_add "${1:-$PWD}" ;;
162
+ remove) shift; cmd_remove "${1:-$PWD}" ;;
163
+ list) cmd_list ;;
164
+ status) cmd_status ;;
165
+ uninstall) cmd_uninstall ;;
166
+ run-now) cmd_run_now ;;
167
+ *)
168
+ cat <<USAGE
169
+ harness-wake-install.sh — launchd hourly wake 관리
170
+
171
+ install [project-root...] plist 생성 + 로드 (+ 프로젝트 등록)
172
+ add <project-root> 프로젝트 등록만
173
+ remove <project-root> 프로젝트 등록 해제
174
+ list 등록된 프로젝트 출력
175
+ status launchd / plist / 등록 상태
176
+ run-now 지금 즉시 wake 실행 (테스트)
177
+ uninstall plist 언로드 + 제거
178
+ USAGE
179
+ ;;
180
+ esac