@walwal-harness/cli 6.1.3 → 6.1.5

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 (74) hide show
  1. package/CHANGELOG.md +38 -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 +216 -403
  6. package/assets/templates/config.json +27 -29
  7. package/assets/templates/memory.md +16 -2
  8. package/assets/templates/progress.json.template +6 -5
  9. package/bin/init.js +179 -90
  10. package/conventions/README.md +92 -0
  11. package/conventions/conductor.md +24 -0
  12. package/conventions/coo-developer.md +24 -0
  13. package/conventions/cqo.md +24 -0
  14. package/conventions/cto.md +24 -0
  15. package/conventions/dispatcher.md +24 -0
  16. package/conventions/documentationer.md +24 -0
  17. package/conventions/evaluator-architecture.md +24 -0
  18. package/conventions/evaluator-code-quality.md +24 -0
  19. package/conventions/evaluator-functional.md +24 -0
  20. package/conventions/evaluator-security.md +24 -0
  21. package/conventions/evaluator-visual.md +24 -0
  22. package/conventions/generator-backend.md +24 -0
  23. package/conventions/generator-designer.md +24 -0
  24. package/conventions/generator-devops.md +24 -0
  25. package/conventions/generator-frontend.md +24 -0
  26. package/conventions/meeting-manager.md +24 -0
  27. package/conventions/planner.md +24 -0
  28. package/conventions/service-ops.md +24 -0
  29. package/conventions/shared.md +40 -0
  30. package/gotchas/conductor.md +16 -0
  31. package/gotchas/dispatcher.md +16 -0
  32. package/gotchas/meeting-manager.md +8 -0
  33. package/gotchas/service-ops.md +8 -0
  34. package/package.json +5 -4
  35. package/scripts/conductor-tick.sh +61 -53
  36. package/scripts/harness-archive.sh +7 -5
  37. package/scripts/harness-dashboard-up.sh +11 -3
  38. package/scripts/harness-hourly-review.sh +303 -0
  39. package/scripts/harness-meeting-doc.sh +55 -16
  40. package/scripts/harness-next.sh +1 -1
  41. package/scripts/harness-queue-manager.sh +12 -5
  42. package/scripts/harness-service-ops-monitor.sh +228 -0
  43. package/scripts/harness-session-start.sh +60 -31
  44. package/scripts/harness-statusline.sh +8 -12
  45. package/scripts/harness-stop.sh +85 -0
  46. package/scripts/harness-user-prompt-submit.sh +11 -29
  47. package/scripts/harness-wake-install.sh +179 -0
  48. package/scripts/harness-wake.sh +258 -0
  49. package/scripts/harness-worker-dispatch.sh +157 -0
  50. package/scripts/lib/harness-progress-migrate.sh +6 -1
  51. package/scripts/lib/harness-render-progress.sh +3 -3
  52. package/skills/brainstorming/SKILL.md +2 -2
  53. package/skills/conductor/SKILL.md +67 -55
  54. package/skills/cqo/SKILL.md +1 -0
  55. package/skills/cto/SKILL.md +1 -0
  56. package/skills/dispatcher/SKILL.md +33 -14
  57. package/skills/evaluator-code-quality/SKILL.md +4 -4
  58. package/skills/evaluator-functional/SKILL.md +6 -6
  59. package/skills/evaluator-visual/SKILL.md +4 -4
  60. package/skills/generator-backend/SKILL.md +4 -4
  61. package/skills/generator-frontend/SKILL.md +5 -5
  62. package/skills/meeting-manager/SKILL.md +82 -12
  63. package/skills/planner/SKILL.md +3 -3
  64. package/skills/service-ops/SKILL.md +25 -0
  65. package/commands/harness-solo.md +0 -103
  66. package/commands/harness-stop.md +0 -53
  67. package/commands/harness-team.md +0 -530
  68. package/scripts/harness-dashboard.sh +0 -509
  69. package/scripts/harness-goal-init.sh +0 -72
  70. package/scripts/harness-goal-show.sh +0 -37
  71. package/scripts/harness-gotcha-memory.sh +0 -348
  72. package/scripts/harness-monitor.sh +0 -398
  73. package/scripts/harness-prompt-history.sh +0 -164
  74. package/scripts/harness-tmux.sh +0 -372
@@ -36,7 +36,7 @@ default_decision_json() {
36
36
  implementation_drift) owner="cto"; action_type="implement" ;;
37
37
  planning_drift) owner="planner"; action_type="replan" ;;
38
38
  ops_drift) owner="service-ops"; action_type="monitor" ;;
39
- goal_drift) owner="dispatcher"; action_type="escalate-owner" ;;
39
+ goal_drift) owner="planner"; action_type="goal-realignment" ;;
40
40
  esac
41
41
  if [ "$requested_reason" = "goal-intake" ]; then
42
42
  owner="planner"
@@ -158,33 +158,43 @@ EOF
158
158
 
159
159
  cat > "$meeting_dir/prep-dispatcher.md" <<'EOF'
160
160
  # Prep — Dispatcher/CEO
161
- - Goal 자체가 흔들렸는가?
162
- - Owner escalation 이 필요한가?
163
- - 사업 우선순위 또는 KPI 정의를 바꿔야 하는가?
161
+ - Role: 회사 내부 CEO. Owner와의 유일한 외부 창구이며, Owner 입력은 최초 GOAL 이후 interrupt/additional request로만 취급한다.
162
+ - Must answer: Goal 자체가 흔들렸는가, 아니면 내부 실행/품질/운영 문제인가?
163
+ - Must decide: Owner escalation 이 정말 필요한가, 아니면 내부 임원진이 처리할 수 있는가?
164
+ - Must not: Owner를 기다린다는 결론으로 회의를 끝내지 마라.
165
+ - Evidence to cite: active GOAL, KPI, previous decision, escalation trigger.
164
166
  EOF
165
167
  cat > "$meeting_dir/prep-planner.md" <<'EOF'
166
168
  # Prep — Planner/COO
167
- - 기획/가설/웹리서치/레퍼런스 보강이 필요한가?
168
- - planning_drift 또는 goal_drift 근거는 무엇인가?
169
- - 수정해야 할 plan/feature/api 는 무엇인가?
169
+ - Role: COO. GOAL을 실행 가능한 work package, 가설, queue로 바꾸고 조직이 옆길로 새지 않게 한다.
170
+ - Must answer: planning_drift 또는 goal_drift 인가, 아니면 구현/품질/운영 문제인가?
171
+ - Must decide: 다음 operating cycle에서 어떤 work package를 queue/backlog/track으로 만들 것인가?
172
+ - Must not: "다음 sprint에서", "Owner가 정하면" 같은 대기형 결론을 쓰지 마라.
173
+ - Evidence to cite: feature-list, sprint-contract, queue, hypothesis/report artifacts.
170
174
  EOF
171
175
  cat > "$meeting_dir/prep-cto.md" <<'EOF'
172
176
  # Prep — CTO
173
- - 구현/아키텍처/기술선택이 원인인가?
174
- - implementation_drift 근거는 무엇인가?
175
- - 어떤 generator/evaluator를 다시 태워야 하는가?
177
+ - Role: CTO. 구현, 아키텍처, 기술선택, runtime recovery의 책임자다.
178
+ - Must answer: implementation_drift 인가, 런타임 장애인가, 설계 변경이 필요한가?
179
+ - Must decide: 어떤 generator/devops/hotfix worker를 태우고 어떤 deliverable을 요구할 것인가?
180
+ - Must not: 운영 장애를 단순 Service-Ops 알림으로만 남기지 마라. 복구 owner를 지정하라.
181
+ - Evidence to cite: code path, build/test output, runtime health, cto-review.
176
182
  EOF
177
183
  cat > "$meeting_dir/prep-cqo.md" <<'EOF'
178
184
  # Prep — CQO
179
- - 품질/회귀/검증 부족이 원인인가?
180
- - 어떤 evidence가 이 결론을 지지하는가?
181
- - evidence 없는 추정은 금지한다.
185
+ - Role: CQO. 품질, 회귀, 검증 기준의 최종 감시자다.
186
+ - Must answer: evidence가 충분한가, PASS/FAIL 판정이 방어 가능한가?
187
+ - Must decide: 어떤 evaluator/check가 필요하고, 어떤 주장은 검증 전까지 보류해야 하는가?
188
+ - Must not: evidence 없는 낙관론을 회의 결론으로 통과시키지 마라.
189
+ - Evidence to cite: evaluator reports, regression results, cqo-audit, reproducible checks.
182
190
  EOF
183
191
  cat > "$meeting_dir/prep-service-ops.md" <<'EOF'
184
192
  # Prep — Service-Ops
185
- - 어떤 KPI/로그/incident가 Goal에서 벗어났는가?
186
- - ops_drift 여부를 먼저 판단하라.
187
- - 운영 측 evidence를 문서 경로와 함께 적어라.
193
+ - Role: Service-Ops. 운영 신호, KPI, incident, monitor cadence의 책임자다.
194
+ - Must answer: 어떤 KPI/로그/health/incident가 Goal에서 벗어났는가?
195
+ - Must decide: ops_drift 인가, incident-war-room 이 필요한가, monitor cadence를 바꿔야 하는가?
196
+ - Must not: 서버 down을 혼자 경고만 하고 끝내지 마라. 회의에 올려 CTO/CQO action으로 연결하라.
197
+ - Evidence to cite: ops-report path, health table, incident timeline, last_check.
188
198
  EOF
189
199
 
190
200
  decision_json="$(default_decision_json "$drift")"
@@ -217,6 +227,35 @@ $(jq '.prior_tracks' <<<"$resolved_fork_context")
217
227
  - goal_adherence: $goal_adherence
218
228
  $fork_section
219
229
 
230
+ ## Required Role Positions
231
+
232
+ Meeting-Manager must fill this section before reporting the meeting as complete.
233
+
234
+ ### Dispatcher/CEO
235
+ - Position:
236
+ - Evidence:
237
+ - Escalation needed: yes/no
238
+
239
+ ### Planner/COO
240
+ - Position:
241
+ - Evidence:
242
+ - Proposed work package:
243
+
244
+ ### CTO
245
+ - Position:
246
+ - Evidence:
247
+ - Engineering action:
248
+
249
+ ### CQO
250
+ - Position:
251
+ - Evidence:
252
+ - Quality gate:
253
+
254
+ ### Service-Ops
255
+ - Position:
256
+ - Evidence:
257
+ - Operational action:
258
+
220
259
  ## Decision JSON
221
260
  \`\`\`json
222
261
  $(jq -n \
@@ -196,7 +196,7 @@ run_pre_eval_gate() {
196
196
  run_conductor_tick() {
197
197
  local candidate="$1"
198
198
  local owner
199
- owner=$(jq -r '.mode_selection.owner // empty' "$CONFIG" 2>/dev/null || true)
199
+ owner=$(jq -r '.company_mode.owner // "conductor"' "$CONFIG" 2>/dev/null || true)
200
200
  if [ "$owner" != "conductor" ]; then return 0; fi
201
201
  if [ ! -x "$SCRIPT_DIR/conductor-tick.sh" ]; then return 0; fi
202
202
 
@@ -5,7 +5,8 @@
5
5
  # feature-queue.json을 생성/관리한다.
6
6
  #
7
7
  # Commands:
8
- # init feature-list.json → feature-queue.json 초기 생성
8
+ # init [sprint|all] [project-root]
9
+ # feature-list.json → feature-queue.json 초기 생성
9
10
  # dequeue <team> ready 큐에서 feature를 꺼내 team에 배정
10
11
  # pass <fid> feature를 passed로 이동, blocked→ready 전이
11
12
  # fail <fid> feature를 failed로 이동
@@ -71,8 +72,10 @@ fi
71
72
 
72
73
  # ══════════════════════════════════════════
73
74
  # init — Build queue from feature-list.json
74
- # Usage: init [sprint_number]
75
- # sprint_number: optional, filter features by sprint (default: all)
75
+ # Usage: init [sprint_number|all] [project-root]
76
+ # sprint_number: optional, filter features by sprint (default: all).
77
+ # If passing a project root, use `init all /path/to/project`; otherwise the
78
+ # first argument is interpreted as the sprint filter.
76
79
  # ══════════════════════════════════════════
77
80
  cmd_init() {
78
81
  local sprint_filter="${1:-all}"
@@ -85,11 +88,15 @@ cmd_init() {
85
88
  # Build dependency graph and topological sort
86
89
  # Output: feature-queue.json with ready (no deps) and blocked (has deps)
87
90
  jq --argjson concurrency "$CONCURRENCY" --arg sprint "$sprint_filter" '
88
- # Build passed set (features already passed by evaluator)
91
+ # Build passed set (features already passed by evaluator or imported audit).
92
+ # Legacy projects use string passes; newer NEXUS artifacts may use objects.
89
93
  def passed_set:
90
94
  [.features[] | select(
91
95
  ((.passes // []) | length > 0) and
92
- ((.passes // []) | any(. == "evaluator-functional"))
96
+ ((.passes // []) | any(
97
+ (. == "evaluator-functional") or
98
+ ((type == "object") and ((.status // "") == "PASS"))
99
+ ))
93
100
  ) | .id] ;
94
101
 
95
102
  # Filter features by sprint if specified
@@ -0,0 +1,228 @@
1
+ #!/bin/bash
2
+ # harness-service-ops-monitor.sh — deterministic Service-Ops production check
3
+ #
4
+ # Reads .harness/config.json runtime.production.services[] and writes:
5
+ # - progress.json.service_ops.health[]
6
+ # - progress.json.service_ops.monitor.last_check
7
+ # - .harness/actions/ops-report-hourly-<timestamp>.md
8
+ #
9
+ # This script is intentionally deterministic and does not require an LLM. Claude
10
+ # can add analysis later, but the Owner must always have a disk-backed record.
11
+
12
+ set -uo pipefail
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
15
+ source "$SCRIPT_DIR/lib/harness-render-progress.sh"
16
+
17
+ PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || exit 0
18
+ PROGRESS="$PROJECT_ROOT/.harness/progress.json"
19
+ CONFIG="$PROJECT_ROOT/.harness/config.json"
20
+ ACTIONS_DIR="$PROJECT_ROOT/.harness/actions"
21
+ OPS_DIR="$PROJECT_ROOT/.harness/ops"
22
+
23
+ [ -f "$PROGRESS" ] || exit 0
24
+ [ -f "$CONFIG" ] || exit 0
25
+ command -v jq >/dev/null 2>&1 || exit 0
26
+
27
+ mkdir -p "$ACTIONS_DIR" "$OPS_DIR"
28
+
29
+ live=$(jq -r '.runtime.production.live // false' "$CONFIG" 2>/dev/null || echo false)
30
+ service_count=$(jq -r '(.runtime.production.services // []) | length' "$CONFIG" 2>/dev/null || echo 0)
31
+ ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
32
+ stamp="$(date -u +%Y%m%dT%H%M%SZ)"
33
+ report_rel=".harness/actions/ops-report-hourly-${stamp}.md"
34
+ report_path="$PROJECT_ROOT/$report_rel"
35
+
36
+ json_escape() {
37
+ jq -Rn --arg v "$1" '$v'
38
+ }
39
+
40
+ tcp_check() {
41
+ local host="$1"
42
+ local port="$2"
43
+ if command -v nc >/dev/null 2>&1; then
44
+ nc -z -w 2 "$host" "$port" >/dev/null 2>&1
45
+ return $?
46
+ fi
47
+ bash -c ":</dev/tcp/$host/$port" >/dev/null 2>&1
48
+ }
49
+
50
+ http_status() {
51
+ local url="$1"
52
+ if command -v curl >/dev/null 2>&1; then
53
+ curl -s -o /dev/null -m 3 -w "%{http_code}" "$url" 2>/dev/null || echo "000"
54
+ else
55
+ echo "000"
56
+ fi
57
+ }
58
+
59
+ log_summary_json() {
60
+ local log_path="$1"
61
+ if [ -z "$log_path" ] || [ "$log_path" = "null" ]; then
62
+ jq -n '{configured:false, exists:false, recent_errors:0, last_line:null}'
63
+ return
64
+ fi
65
+ if [ ! -f "$log_path" ]; then
66
+ jq -n --arg path "$log_path" '{configured:true, exists:false, path:$path, recent_errors:0, last_line:null}'
67
+ return
68
+ fi
69
+ local recent errors last_line
70
+ recent="$(tail -200 "$log_path" 2>/dev/null || true)"
71
+ errors="$(printf '%s\n' "$recent" | grep -Eic 'error|exception|fatal|panic|traceback|fail' || true)"
72
+ last_line="$(tail -1 "$log_path" 2>/dev/null || true)"
73
+ jq -n --arg path "$log_path" --arg last "$last_line" --argjson errors "${errors:-0}" \
74
+ '{configured:true, exists:true, path:$path, recent_errors:$errors, last_line:$last}'
75
+ }
76
+
77
+ if [ "$live" != "true" ] || [ "${service_count:-0}" -eq 0 ]; then
78
+ jq --arg ts "$ts" --arg report "$report_rel" '
79
+ .service_ops.monitor.last_check = $ts |
80
+ .service_ops.monitor.stream_active = false |
81
+ .service_ops.monitor.last_report = $report |
82
+ .service_ops.health = []
83
+ ' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
84
+
85
+ {
86
+ echo "# Service-Ops Hourly Report"
87
+ echo ""
88
+ echo "- ts: $ts"
89
+ echo "- production.live: $live"
90
+ echo "- services: $service_count"
91
+ echo "- verdict: NO_PRODUCTION_SERVICES"
92
+ echo ""
93
+ echo "No production service endpoints are configured. Owner+CEO+CTO must define runtime.production.services[] before Service-Ops can monitor live servers."
94
+ } > "$report_path"
95
+ echo "$report_rel"
96
+ exit 0
97
+ fi
98
+
99
+ results_file="$(mktemp)"
100
+ : > "$results_file"
101
+
102
+ i=0
103
+ while [ "$i" -lt "$service_count" ]; do
104
+ svc="$(jq -c ".runtime.production.services[$i]" "$CONFIG")"
105
+ name="$(jq -r '.name // ("service-" + (input_line_number|tostring))' <<<"$svc")"
106
+ host="$(jq -r '.host // "127.0.0.1"' <<<"$svc")"
107
+ port="$(jq -r '.port // empty' <<<"$svc")"
108
+ health_path="$(jq -r '.health_path // empty' <<<"$svc")"
109
+ expected_status="$(jq -r '.expected_status // 200' <<<"$svc")"
110
+ log_path="$(jq -r '.log_path // empty' <<<"$svc")"
111
+
112
+ port_state="unknown"
113
+ if [ -n "$port" ] && tcp_check "$host" "$port"; then
114
+ port_state="listening"
115
+ else
116
+ port_state="down"
117
+ fi
118
+
119
+ health_status="null"
120
+ health_ok="null"
121
+ health_url=""
122
+ if [ -n "$health_path" ] && [ "$health_path" != "null" ] && [ -n "$port" ]; then
123
+ health_url="http://${host}:${port}${health_path}"
124
+ health_status="$(http_status "$health_url")"
125
+ if [ "$health_status" = "$expected_status" ]; then health_ok="true"; else health_ok="false"; fi
126
+ fi
127
+
128
+ log_json="$(log_summary_json "$log_path")"
129
+
130
+ status="ok"
131
+ if [ "$port_state" != "listening" ]; then
132
+ status="down"
133
+ elif [ "$health_ok" = "false" ]; then
134
+ status="degraded"
135
+ elif [ "$(jq -r '.configured and (.exists|not)' <<<"$log_json")" = "true" ]; then
136
+ status="log_missing"
137
+ elif [ "$(jq -r '.recent_errors // 0' <<<"$log_json")" -gt 0 ]; then
138
+ status="warn"
139
+ fi
140
+
141
+ jq -n \
142
+ --arg ts "$ts" \
143
+ --arg name "$name" \
144
+ --arg host "$host" \
145
+ --arg port "$port" \
146
+ --arg port_state "$port_state" \
147
+ --arg health_path "$health_path" \
148
+ --arg health_url "$health_url" \
149
+ --arg expected_status "$expected_status" \
150
+ --arg health_status "$health_status" \
151
+ --arg health_ok "$health_ok" \
152
+ --arg status "$status" \
153
+ --argjson log "$log_json" \
154
+ '{
155
+ ts:$ts,
156
+ name:$name,
157
+ host:$host,
158
+ port:($port|tonumber?),
159
+ port_state:$port_state,
160
+ health_path:(if $health_path == "" or $health_path == "null" then null else $health_path end),
161
+ health_url:(if $health_url == "" then null else $health_url end),
162
+ expected_status:($expected_status|tonumber?),
163
+ health_status:(if $health_status == "null" then null else ($health_status|tonumber?) end),
164
+ health_ok:(if $health_ok == "true" then true elif $health_ok == "false" then false else null end),
165
+ log:$log,
166
+ status:$status
167
+ }' >> "$results_file"
168
+ i=$((i + 1))
169
+ done
170
+
171
+ results_json="$(jq -s '.' "$results_file")"
172
+ rm -f "$results_file"
173
+
174
+ down_count="$(jq '[.[] | select(.status == "down" or .status == "degraded")] | length' <<<"$results_json")"
175
+ warn_count="$(jq '[.[] | select(.status == "warn" or .status == "log_missing")] | length' <<<"$results_json")"
176
+ ok_count="$(jq '[.[] | select(.status == "ok")] | length' <<<"$results_json")"
177
+ verdict="OK"
178
+ if [ "$down_count" -gt 0 ]; then verdict="INCIDENT"; elif [ "$warn_count" -gt 0 ]; then verdict="WARN"; fi
179
+
180
+ incidents_json="$(jq '
181
+ [ .[] | select(.status == "down" or .status == "degraded") |
182
+ {
183
+ id: ("OPS-" + (.name | ascii_upcase | gsub("[^A-Z0-9]+";"-"))),
184
+ dept: "Operations",
185
+ severity: (if .status == "down" then "critical" else "high" end),
186
+ message: (.name + " " + .status + " (" + .host + ":" + (.port|tostring) + ")"),
187
+ ts: .ts
188
+ }
189
+ ]' <<<"$results_json")"
190
+
191
+ jq --arg ts "$ts" --arg report "$report_rel" --argjson health "$results_json" --argjson incidents "$incidents_json" --argjson warn "$warn_count" --argjson alert "$down_count" '
192
+ .service_ops.monitor.stream_active = false |
193
+ .service_ops.monitor.last_check = $ts |
194
+ .service_ops.monitor.last_report = $report |
195
+ .service_ops.monitor.warns_this_sprint = ((.service_ops.monitor.warns_this_sprint // 0) + $warn) |
196
+ .service_ops.monitor.alerts_this_sprint = ((.service_ops.monitor.alerts_this_sprint // 0) + $alert) |
197
+ .service_ops.health = $health |
198
+ .service_ops.incident.open = $incidents
199
+ ' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
200
+
201
+ {
202
+ echo "# Service-Ops Hourly Report"
203
+ echo ""
204
+ echo "- ts: $ts"
205
+ echo "- production.live: true"
206
+ echo "- services: $service_count"
207
+ echo "- verdict: $verdict"
208
+ echo "- ok: $ok_count"
209
+ echo "- warnings: $warn_count"
210
+ echo "- alerts: $down_count"
211
+ echo ""
212
+ echo "| Service | Port | Health | Logs | Status |"
213
+ echo "|---|---:|---|---|---|"
214
+ jq -r '.[] |
215
+ "| \(.name) | \(.host):\(.port) \(.port_state) | " +
216
+ (if .health_path == null then "n/a" else ((.health_status // "000")|tostring) + " expected " + ((.expected_status // 200)|tostring) end) +
217
+ " | " +
218
+ (if (.log.configured|not) then "not configured" elif (.log.exists|not) then "missing" else ((.log.recent_errors|tostring) + " recent errors") end) +
219
+ " | \(.status) |"' <<<"$results_json"
220
+ echo ""
221
+ echo "## Raw"
222
+ echo ""
223
+ echo '```json'
224
+ jq '.' <<<"$results_json"
225
+ echo '```'
226
+ } > "$report_path"
227
+
228
+ echo "$report_rel"
@@ -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