@walwal-harness/cli 6.1.5 → 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.
@@ -34,6 +34,7 @@ fi
34
34
 
35
35
  ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
36
36
  id="M-$(date -u +%Y%m%dT%H%M%SZ)-hourly"
37
+ stamp="$(date -u +%Y%m%dT%H%M%SZ)"
37
38
  meeting_dir="$PROJECT_ROOT/.harness/actions/meetings/$id"
38
39
  meeting_rel=".harness/actions/meetings/$id/meeting-$id.md"
39
40
  meeting_path="$PROJECT_ROOT/$meeting_rel"
@@ -52,6 +53,7 @@ current_agent="$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null || ech
52
53
  next_agent="$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null || echo none)"
53
54
  agent_status="$(jq -r '.agent_status // "unknown"' "$PROGRESS" 2>/dev/null || echo unknown)"
54
55
  conductor_state="$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null || echo idle)"
56
+ conductor_action="$(jq -r '.conductor.current_action // "none"' "$PROGRESS" 2>/dev/null || echo none)"
55
57
  tick_count="$(jq -r '.conductor.tick_count // 0' "$PROGRESS" 2>/dev/null || echo 0)"
56
58
  health_count="$(jq -r '(.service_ops.health // []) | length' "$PROGRESS" 2>/dev/null || echo 0)"
57
59
  alert_count="$(jq -r '[.service_ops.health[]? | select(.status == "down" or .status == "degraded")] | length' "$PROGRESS" 2>/dev/null || echo 0)"
@@ -66,11 +68,63 @@ last_meeting="$(jq -r '.meetings.last_review_path // .meetings.current_record_pa
66
68
  cqo_audit="$(jq -r '.cqo.audit_path // empty' "$PROGRESS" 2>/dev/null || true)"
67
69
  cto_review="$(jq -r '.cto.review_path // empty' "$PROGRESS" 2>/dev/null || true)"
68
70
  open_incidents="$(jq -r '(.service_ops.incident.open // []) | length' "$PROGRESS" 2>/dev/null || echo 0)"
71
+ incident_repeat_count="$(jq -r '.service_ops.incident.repeat_count // 0' "$PROGRESS" 2>/dev/null || echo 0)"
72
+ incident_recovery_required="$(jq -r '.service_ops.incident.recovery_required // false' "$PROGRESS" 2>/dev/null || echo false)"
73
+ prev_required_json="$(jq -c '.meetings.decision.required_execution // .meetings.required_execution // null' "$PROGRESS" 2>/dev/null || echo null)"
74
+ prev_required_action="$(jq -r '(.meetings.decision.required_execution // .meetings.required_execution // {}).action // "none"' "$PROGRESS" 2>/dev/null || echo none)"
75
+ prev_required_owner="$(jq -r '(.meetings.decision.required_execution // .meetings.required_execution // {}).owner // "none"' "$PROGRESS" 2>/dev/null || echo none)"
76
+ prev_required_deliverable="$(jq -r '(.meetings.decision.required_execution // .meetings.required_execution // {}).deliverable_path // ""' "$PROGRESS" 2>/dev/null || true)"
77
+ prev_execution_result="none"
78
+ prev_execution_detail="no prior required_execution"
79
+ if [ "$prev_required_json" != "null" ]; then
80
+ if [ -n "$prev_required_deliverable" ] && [ -f "$PROJECT_ROOT/$prev_required_deliverable" ]; then
81
+ prev_execution_result="executed_unverified"
82
+ prev_execution_detail="deliverable exists: $prev_required_deliverable"
83
+ if [ "$prev_required_action" = "runtime-recovery-runbook" ] && [ "${open_incidents:-0}" -eq 0 ] && [ "$alert_count" -eq 0 ]; then
84
+ prev_execution_result="executed_and_verified"
85
+ prev_execution_detail="deliverable exists and Service-Ops has no open incident"
86
+ fi
87
+ elif [ "$prev_required_action" = "runtime-recovery-runbook" ] && [ "$(jq -r '.cto.step_b_status // .cto.step_a_status // ""' "$PROGRESS" 2>/dev/null || true)" != "" ]; then
88
+ prev_execution_result="attempted_blocked"
89
+ prev_execution_detail="CTO recovery state advanced but required deliverable missing: ${prev_required_deliverable:-unset}"
90
+ else
91
+ prev_execution_result="paperwork_only_failure"
92
+ prev_execution_detail="required deliverable missing: ${prev_required_deliverable:-unset}"
93
+ fi
94
+ fi
69
95
  owner_escalation="false"
96
+ strategy_total_signals=0
97
+ strategy_previous_signals="$(jq -r '.goals.strategy_cadence.total_signals // 0' "$PROGRESS" 2>/dev/null || echo 0)"
98
+ strategy_delta=0
99
+ strategy_status="not_applicable"
100
+ strategy_target_per_hour=0
101
+ strategy_target_per_day=0
102
+ strategy_last_signal="none"
103
+ strategy_required=false
104
+
105
+ if grep -Eiq 'strategy|algo|backtest|신규.?전략|시간당.?1|일.?6|daily.?6|hourly.?1' "$log_path" "$PROJECT_ROOT/.harness/actions/goals.md" 2>/dev/null; then
106
+ strategy_required=true
107
+ strategy_target_per_hour=1
108
+ strategy_target_per_day=6
109
+ strategy_total_signals="$(grep -Eic '(^|[|[:space:]])(new-strategy|strategy-generated|algos?_candidate|backtest-jobs?|backtest_enqueue|신규.?전략|전략.?생성)([|[:space:]]|$)' "$log_path" 2>/dev/null || echo 0)"
110
+ strategy_last_signal="$(grep -Ei '(^|[|[:space:]])(new-strategy|strategy-generated|algos?_candidate|backtest-jobs?|backtest_enqueue|신규.?전략|전략.?생성)([|[:space:]]|$)' "$log_path" 2>/dev/null | tail -1 || true)"
111
+ [ -n "$strategy_last_signal" ] || strategy_last_signal="none"
112
+ if [ "$strategy_total_signals" -lt "$strategy_previous_signals" ]; then
113
+ strategy_previous_signals="$strategy_total_signals"
114
+ fi
115
+ strategy_delta=$((strategy_total_signals - strategy_previous_signals))
116
+ if [ "$strategy_delta" -ge "$strategy_target_per_hour" ]; then
117
+ strategy_status="on_track"
118
+ else
119
+ strategy_status="behind_goal"
120
+ fi
121
+ fi
70
122
 
71
123
  verdict="working"
72
124
  if [ "$alert_count" -gt 0 ]; then
73
125
  verdict="incident"
126
+ elif [ "$strategy_required" = "true" ] && [ "$strategy_status" = "behind_goal" ]; then
127
+ verdict="goal_drift"
74
128
  elif [ "$conductor_state" = "escalated" ]; then
75
129
  verdict="owner_needed"
76
130
  elif [ "$conductor_state" = "waiting_owner" ]; then
@@ -83,6 +137,25 @@ meeting_type="standup"
83
137
  decision_owner="planner"
84
138
  decision_action="execution-plan"
85
139
  decision_rationale="정기 회의에서 active GOAL 대비 현재 작업과 운영 신호를 재정렬하고 다음 work package를 명확히 한다."
140
+ progress_meaningful_json='[]'
141
+ required_execution_json='null'
142
+ handoff_requires_execution=false
143
+ handoff_feature="$(printf "%s" "$conductor_action" | awk -F: '{print $NF}')"
144
+ case "$handoff_feature" in
145
+ ""|none|null|pass-through|monitor|completed|running|ready|execution-plan|continue-current-handoff|goal-alignment)
146
+ handoff_feature="$stamp"
147
+ ;;
148
+ esac
149
+ safe_handoff_feature="$(printf "%s" "$handoff_feature" | tr -cs 'A-Za-z0-9._-' '-' | sed 's/^-//; s/-$//')"
150
+ [ -n "$safe_handoff_feature" ] || safe_handoff_feature="$stamp"
151
+
152
+ case "$next_agent" in
153
+ planner|cto|cqo|generator-*|evaluator-*|documentationer|coo-developer)
154
+ if [ "$next_agent" != "$current_agent" ] || [[ "$conductor_action" == *handoff* ]] || [[ "$conductor_action" == advance:* ]] || [[ "$conductor_action" == spawn:* ]] || [[ "$conductor_action" == dispatch:* ]]; then
155
+ handoff_requires_execution=true
156
+ fi
157
+ ;;
158
+ esac
86
159
  case "$next_agent" in
87
160
  planner|cto|cqo|service-ops|dispatcher)
88
161
  decision_owner="$next_agent"
@@ -93,13 +166,56 @@ esac
93
166
  if [ "$verdict" = "incident" ]; then
94
167
  meeting_type="incident-war-room"
95
168
  decision_owner="cto"
96
- decision_action="runtime-recovery"
97
- decision_rationale="Service-Ops가 down/degraded 신호를 발견했으므로 CTO가 복구 경로를 정하고 CQO가 복구 evidence를 검증한다."
169
+ if [ "$incident_recovery_required" = "true" ] || [ "${incident_repeat_count:-0}" -ge 2 ]; then
170
+ decision_action="runtime-recovery-runbook"
171
+ decision_rationale="동일 운영 장애가 ${incident_repeat_count}회 연속 감지됐다. 반복 회의가 아니라 CTO가 복구 runbook을 실행하고 CQO가 evidence를 검증한다."
172
+ progress_meaningful_json='["runtime-recovery-runbook required"]'
173
+ required_execution_json="$(jq -n --arg id "RE-$stamp-runtime-recovery" --arg source "$meeting_rel" '{
174
+ id:$id,
175
+ owner:"cto",
176
+ action:"runtime-recovery-runbook",
177
+ deliverable_path:".harness/actions/cto-runbook-runtime-recovery-current.md",
178
+ verifier:"cqo+service-ops",
179
+ due:"next_tick",
180
+ source_path:$source,
181
+ success_condition:"Service-Ops verifies every configured production service as ok; partial recovery remains incident."
182
+ }')"
183
+ else
184
+ decision_action="runtime-recovery"
185
+ decision_rationale="Service-Ops가 down/degraded 신호를 발견했으므로 CTO가 복구 경로를 정하고 CQO가 복구 evidence를 검증한다."
186
+ progress_meaningful_json='["runtime-recovery decision"]'
187
+ required_execution_json="$(jq -n --arg id "RE-$stamp-runtime-recovery" --arg source "$meeting_rel" '{
188
+ id:$id,
189
+ owner:"cto",
190
+ action:"runtime-recovery",
191
+ deliverable_path:".harness/actions/cto-action-runtime-recovery-current.md",
192
+ verifier:"service-ops",
193
+ due:"next_tick",
194
+ source_path:$source,
195
+ success_condition:"Service-Ops health improves or records a concrete blocker."
196
+ }')"
197
+ fi
98
198
  elif [ "$verdict" = "idle" ] || [ "$verdict" = "autonomous_review" ]; then
99
199
  meeting_type="followup-review"
100
200
  decision_owner="planner"
101
201
  decision_action="goal-alignment"
102
202
  decision_rationale="Owner 대기 없이 CEO/COO/CTO/CQO/Service-Ops가 GOAL 대비 다음 work package를 재정렬한다."
203
+ elif [ "$verdict" = "goal_drift" ]; then
204
+ meeting_type="spec-review"
205
+ decision_owner="planner"
206
+ decision_action="strategy-cadence-recovery"
207
+ decision_rationale="전략 생성 cadence가 목표보다 뒤처졌다. COO가 다음 시간의 전략 후보 생성/백테스트 work package를 즉시 재배정한다."
208
+ progress_meaningful_json='["strategy cadence recovery decision"]'
209
+ required_execution_json="$(jq -n --arg id "RE-$stamp-strategy-cadence" --arg source "$meeting_rel" --arg deliverable ".harness/actions/strategy-work-package-${stamp}.md" '{
210
+ id:$id,
211
+ owner:"planner",
212
+ action:"create-strategy-work-package",
213
+ deliverable_path:$deliverable,
214
+ verifier:"cqo",
215
+ due:"next_tick",
216
+ source_path:$source,
217
+ success_condition:"A strategy work package exists with candidate idea, data source, backtest command, and CQO acceptance criteria."
218
+ }')"
103
219
  elif [ "$verdict" = "owner_needed" ]; then
104
220
  meeting_type="all-hands"
105
221
  decision_owner="dispatcher"
@@ -108,6 +224,82 @@ elif [ "$verdict" = "owner_needed" ]; then
108
224
  owner_escalation="true"
109
225
  fi
110
226
 
227
+ if [ "$verdict" = "working" ] && [ "$handoff_requires_execution" = "true" ]; then
228
+ decision_owner="$next_agent"
229
+ case "$next_agent" in
230
+ planner)
231
+ decision_action="execution-plan"
232
+ deliverable_path=".harness/actions/planner-execution-plan-${safe_handoff_feature}.md"
233
+ verifier="cto+cqo"
234
+ success_condition="Planner creates a concrete execution plan that reconciles the current handoff with GOAL, queue priority, non-goals, owner, and verifier."
235
+ ;;
236
+ cto)
237
+ decision_action="technical-handoff"
238
+ deliverable_path=".harness/actions/cto-action-${safe_handoff_feature}.md"
239
+ verifier="cqo"
240
+ success_condition="CTO records implementation scope, affected files, validation commands, blocker status, and the next execution owner."
241
+ ;;
242
+ cqo)
243
+ decision_action="quality-verification"
244
+ deliverable_path=".harness/actions/cqo-audit-${safe_handoff_feature}.md"
245
+ verifier="meeting-manager"
246
+ success_condition="CQO records PASS/FAIL with evidence, regressions, and the next routing decision."
247
+ ;;
248
+ generator-*)
249
+ decision_action="implement-${safe_handoff_feature}"
250
+ deliverable_path=".harness/actions/gen-report-${safe_handoff_feature}.md"
251
+ verifier="cqo+evaluator-code-quality"
252
+ success_condition="Generator produces the named implementation report, changed-file list, validation output, and handoff to CQO/evaluator."
253
+ ;;
254
+ evaluator-*)
255
+ decision_action="verify-${safe_handoff_feature}"
256
+ deliverable_path=".harness/actions/evaluation-${safe_handoff_feature}.md"
257
+ verifier="cqo"
258
+ success_condition="Evaluator writes an evidence-backed PASS/FAIL report and does not rubber-stamp missing evidence."
259
+ ;;
260
+ documentationer)
261
+ decision_action="document-${safe_handoff_feature}"
262
+ deliverable_path=".harness/actions/documentation-${safe_handoff_feature}.md"
263
+ verifier="planner"
264
+ success_condition="Documentationer writes the requested brief/report with source paths and decision impact."
265
+ ;;
266
+ coo-developer)
267
+ decision_action="spike-${safe_handoff_feature}"
268
+ deliverable_path=".harness/actions/hypothesis/${safe_handoff_feature}/spike/report.md"
269
+ verifier="planner+cqo"
270
+ success_condition="COO developer records spike result, repro/evidence, and verdict input for Planner."
271
+ ;;
272
+ *)
273
+ decision_action="execute-${safe_handoff_feature}"
274
+ deliverable_path=".harness/actions/execution-${safe_handoff_feature}.md"
275
+ verifier="meeting-manager"
276
+ success_condition="The owner writes a concrete deliverable and next-step evidence before the following hourly review."
277
+ ;;
278
+ esac
279
+ decision_rationale="working 상태라도 현재 handoff는 산출물 없는 '계속 진행'으로 통과할 수 없다. 다음 tick까지 owner가 deliverable을 남기고 verifier가 검증한다."
280
+ progress_meaningful_json="$(jq -n --arg item "execution contract required: $next_agent/$decision_action" '[$item]')"
281
+ required_execution_json="$(jq -n \
282
+ --arg id "RE-$stamp-handoff" \
283
+ --arg owner "$decision_owner" \
284
+ --arg action "$decision_action" \
285
+ --arg deliverable "$deliverable_path" \
286
+ --arg verifier "$verifier" \
287
+ --arg source "$meeting_rel" \
288
+ --arg success "$success_condition" \
289
+ --arg conductor_action "$conductor_action" \
290
+ '{
291
+ id:$id,
292
+ owner:$owner,
293
+ action:$action,
294
+ deliverable_path:$deliverable,
295
+ verifier:$verifier,
296
+ due:"next_tick",
297
+ source_path:$source,
298
+ source_conductor_action:$conductor_action,
299
+ success_condition:$success
300
+ }')"
301
+ fi
302
+
111
303
  decision_json="$(jq -n \
112
304
  --arg owner "$decision_owner" \
113
305
  --arg action_type "$decision_action" \
@@ -117,6 +309,8 @@ decision_json="$(jq -n \
117
309
  --arg ops_report "$ops_report" \
118
310
  --arg cqo_audit "$cqo_audit" \
119
311
  --arg cto_review "$cto_review" \
312
+ --argjson progress_meaningful "$progress_meaningful_json" \
313
+ --argjson required_execution "$required_execution_json" \
120
314
  --argjson owner_escalation "$owner_escalation" '
121
315
  {
122
316
  owner: $owner,
@@ -132,6 +326,11 @@ decision_json="$(jq -n \
132
326
  drift_classification: $drift,
133
327
  source_path: $source,
134
328
  owner_escalation: $owner_escalation,
329
+ progress_classification: {
330
+ meaningful_actions: $progress_meaningful,
331
+ paperwork_only: ["hourly-review", "service-ops-monitor"]
332
+ },
333
+ required_execution: $required_execution,
135
334
  tracks: [],
136
335
  rendezvous: null
137
336
  }')"
@@ -165,6 +364,9 @@ meeting_json="$(jq -n \
165
364
  echo "- drift_classification: $drift"
166
365
  echo "- previous_decision: $last_decision"
167
366
  echo "- previous_meeting: $last_meeting"
367
+ echo "- previous_required_execution: $prev_required_owner/$prev_required_action"
368
+ echo "- previous_execution_result: $prev_execution_result"
369
+ echo "- previous_execution_detail: $prev_execution_detail"
168
370
  if [ -n "$ops_report" ]; then
169
371
  echo "- service_ops_report: $ops_report"
170
372
  fi
@@ -175,6 +377,21 @@ meeting_json="$(jq -n \
175
377
  echo "- cqo_audit: $cqo_audit"
176
378
  fi
177
379
  echo ""
380
+ echo "## Progress Classification"
381
+ echo ""
382
+ echo "- previous_required_execution_result: $prev_execution_result"
383
+ echo "- previous_required_execution_detail: $prev_execution_detail"
384
+ if [ "$verdict" = "incident" ] && { [ "$incident_recovery_required" = "true" ] || [ "${incident_repeat_count:-0}" -ge 2 ]; }; then
385
+ echo "- meaningful_actions: runtime-recovery-runbook required"
386
+ echo "- paperwork_only: hourly meeting minutes, Service-Ops monitor report"
387
+ elif [ "$verdict" = "goal_drift" ]; then
388
+ echo "- meaningful_actions: strategy cadence recovery decision"
389
+ echo "- paperwork_only: hourly meeting minutes, Service-Ops monitor report"
390
+ else
391
+ echo "- meaningful_actions: none detected in this deterministic batch until conductor/agent tick records execution"
392
+ echo "- paperwork_only: hourly meeting minutes, Service-Ops monitor report"
393
+ fi
394
+ echo ""
178
395
  echo "## Role Briefs"
179
396
  echo ""
180
397
  echo "### Dispatcher/CEO"
@@ -214,6 +431,12 @@ meeting_json="$(jq -n \
214
431
  echo "- warnings: $warn_count"
215
432
  echo "- alerts: $alert_count"
216
433
  echo "- open_incidents: $open_incidents"
434
+ echo "- incident_repeat_count: $incident_repeat_count"
435
+ echo "- incident_recovery_required: $incident_recovery_required"
436
+ if [ "$strategy_required" = "true" ]; then
437
+ echo "- strategy_cadence: $strategy_status (delta=$strategy_delta, total_signals=$strategy_total_signals, target_per_hour=$strategy_target_per_hour, target_per_day=$strategy_target_per_day)"
438
+ echo "- last_strategy_signal: $strategy_last_signal"
439
+ fi
217
440
  echo "- 판단: 운영 신호를 회의에 제출하고, 장애 시 incident-war-room을 요구한다."
218
441
  echo ""
219
442
  if [ "$health_count" -gt 0 ]; then
@@ -234,11 +457,22 @@ meeting_json="$(jq -n \
234
457
  case "$verdict" in
235
458
  incident)
236
459
  echo "- Service-Ops: down/degraded 서비스가 있으므로 feature work보다 장애 triage가 우선이다."
237
- echo "- CTO: 복구 절차, 재시작, 설정 누락, 포트 충돌 중 원인을 좁히고 runtime-recovery work item을 받는다."
460
+ if [ "$incident_recovery_required" = "true" ] || [ "${incident_repeat_count:-0}" -ge 2 ]; then
461
+ echo "- CTO: 같은 장애가 반복됐으므로 추가 회의가 아니라 runtime-recovery-runbook 실행으로 전환한다."
462
+ else
463
+ echo "- CTO: 복구 절차, 재시작, 설정 누락, 포트 충돌 중 원인을 좁히고 runtime-recovery work item을 받는다."
464
+ fi
238
465
  echo "- CQO: 복구 완료 주장은 health check와 재현 가능한 evidence 없이는 PASS로 보지 않는다."
239
466
  echo "- COO: 장애 복구가 GOAL 진행을 막는 범위를 queue에 반영한다."
240
467
  echo "- CEO: Owner에게 묻지 않고 내부 복구 루프를 진행한다. 단 GOAL 변경이 필요하면 별도 escalation한다."
241
468
  ;;
469
+ goal_drift)
470
+ echo "- CEO: Owner가 없어도 최초 GOAL의 KPI를 기준으로 회사 루프를 평가한다."
471
+ echo "- COO: 전략 생성/백테스트 cadence가 목표보다 낮으므로 다음 work package를 전략 후보 생성으로 고정한다."
472
+ echo "- CTO: 필요한 데이터/실행 경로가 막혔는지 점검한다."
473
+ echo "- CQO: 후보 전략은 backtest evidence 없이는 유의미 진척으로 인정하지 않는다."
474
+ echo "- Service-Ops: 운영 장애가 cadence 저하 원인인지 계속 감시한다."
475
+ ;;
242
476
  owner_needed)
243
477
  echo "- CEO: conductor escalation의 원인이 내부 해결 가능한지 우선 판정한다."
244
478
  echo "- COO/CTO/CQO/Service-Ops: 각자 해결 가능한 내부 action을 제시하고, 불가능할 때만 Owner escalation을 유지한다."
@@ -272,6 +506,11 @@ meeting_json="$(jq -n \
272
506
  echo "- Service-Ops: 복구 후 monitor를 다시 돌려 last_check와 health evidence를 갱신한다."
273
507
  echo "- CQO: 복구 evidence를 검증하고 regression risk를 기록한다."
274
508
  ;;
509
+ goal_drift)
510
+ echo "- Planner/COO: 다음 autonomous tick에서 전략 후보 생성/백테스트 work package를 발급한다."
511
+ echo "- CTO: 전략 실행 경로와 데이터 의존성 차단 여부를 점검한다."
512
+ echo "- CQO: backtest 결과가 없는 후보를 유의미 진척으로 집계하지 않는다."
513
+ ;;
275
514
  idle|autonomous_review)
276
515
  echo "- Planner/COO: Owner 대기 없이 active GOAL 기준 다음 work package를 정의한다."
277
516
  echo "- Conductor: meeting decision을 읽고 다음 부서를 자동 배정한다."
@@ -280,9 +519,34 @@ meeting_json="$(jq -n \
280
519
  echo "- Conductor: 다음 autonomous tick에서 decision.owner/action_type 기준으로 handoff한다."
281
520
  ;;
282
521
  esac
522
+ echo ""
523
+ echo "## Required Execution"
524
+ echo ""
525
+ if [ "$required_execution_json" = "null" ]; then
526
+ echo "- none"
527
+ else
528
+ jq -r '"- owner: \(.owner)\n- action: \(.action)\n- deliverable_path: \(.deliverable_path)\n- verifier: \(.verifier)\n- due: \(.due)\n- success_condition: \(.success_condition)"' <<<"$required_execution_json"
529
+ fi
283
530
  } > "$meeting_path"
284
531
 
285
- jq --arg ts "$ts" --arg id "$id" --arg rel "$meeting_rel" --arg verdict "$verdict" --arg type "$meeting_type" --argjson decision "$decision_json" '
532
+ jq \
533
+ --arg ts "$ts" \
534
+ --arg id "$id" \
535
+ --arg rel "$meeting_rel" \
536
+ --arg verdict "$verdict" \
537
+ --arg type "$meeting_type" \
538
+ --arg strategy_status "$strategy_status" \
539
+ --arg strategy_last_signal "$strategy_last_signal" \
540
+ --arg prev_execution_result "$prev_execution_result" \
541
+ --arg prev_execution_detail "$prev_execution_detail" \
542
+ --argjson strategy_required "$strategy_required" \
543
+ --argjson strategy_target_per_hour "$strategy_target_per_hour" \
544
+ --argjson strategy_target_per_day "$strategy_target_per_day" \
545
+ --argjson strategy_total_signals "$strategy_total_signals" \
546
+ --argjson strategy_previous_signals "$strategy_previous_signals" \
547
+ --argjson strategy_delta "$strategy_delta" \
548
+ --argjson incident_recovery_required "$incident_recovery_required" \
549
+ --argjson decision "$decision_json" '
286
550
  .meetings.last_type = "hourly-review" |
287
551
  .meetings.last_reason = $verdict |
288
552
  .meetings.last_review_at = $ts |
@@ -291,7 +555,22 @@ jq --arg ts "$ts" --arg id "$id" --arg rel "$meeting_rel" --arg verdict "$verdic
291
555
  .meetings.current_record_path = $rel |
292
556
  .meetings.last_attendees = ["dispatcher","planner","cto","cqo","service-ops","meeting-manager"] |
293
557
  .meetings.decision = $decision |
294
- if $verdict == "incident" or $verdict == "autonomous_review" or $verdict == "idle"
558
+ .meetings.previous_required_execution_result = $prev_execution_result |
559
+ .meetings.previous_required_execution_detail = $prev_execution_detail |
560
+ .meetings.required_execution = $decision.required_execution |
561
+ .goals.strategy_cadence = {
562
+ "required": $strategy_required,
563
+ "status": $strategy_status,
564
+ "target_per_hour": $strategy_target_per_hour,
565
+ "target_per_day": $strategy_target_per_day,
566
+ "total_signals": $strategy_total_signals,
567
+ "previous_total_signals": $strategy_previous_signals,
568
+ "delta_since_last_review": $strategy_delta,
569
+ "last_signal": $strategy_last_signal,
570
+ "last_check": $ts
571
+ } |
572
+ .service_ops.incident.recovery_required = $incident_recovery_required |
573
+ if $verdict == "incident" or $verdict == "autonomous_review" or $verdict == "idle" or $verdict == "goal_drift"
295
574
  then .meetings.active = ((.meetings.active // []) + ["meeting-manager","dispatcher","planner","cto","cqo","service-ops"] | unique) |
296
575
  .meetings.requested_type = (.meetings.requested_type // $type) |
297
576
  .meetings.requested_reason = (.meetings.requested_reason // $verdict)
@@ -0,0 +1,101 @@
1
+ #!/bin/bash
2
+ # harness-parity-audit.sh — compare installed project company-loop surface to walwal-harness
3
+
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ HARNESS_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
8
+ MANIFEST="$HARNESS_ROOT/assets/templates/company-pipeline-manifest.json"
9
+
10
+ usage() {
11
+ echo "usage: bash scripts/harness-parity-audit.sh <project-root> [<project-root> ...]" >&2
12
+ }
13
+
14
+ [ "$#" -gt 0 ] || { usage; exit 2; }
15
+ [ -f "$MANIFEST" ] || { echo "MISSING manifest: $MANIFEST" >&2; exit 2; }
16
+ command -v jq >/dev/null 2>&1 || { echo "MISSING jq" >&2; exit 2; }
17
+
18
+ sha() {
19
+ LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 shasum -a 256 "$1" | awk '{print $1}'
20
+ }
21
+
22
+ project_path_for() {
23
+ local rel="$1"
24
+ case "$rel" in
25
+ conventions/*) echo ".harness/$rel" ;;
26
+ gotchas/*) echo ".harness/$rel" ;;
27
+ skills/*) echo ".harness/$rel" ;;
28
+ *) echo "$rel" ;;
29
+ esac
30
+ }
31
+
32
+ config_core_filter() {
33
+ jq -S '
34
+ del(.project, .name, .description, .paths, .production, .telegram, .services, .owners, .runtime, .tech_stack, .integrations)
35
+ ' "$1"
36
+ }
37
+
38
+ status=0
39
+ for project in "$@"; do
40
+ project="$(cd "$project" && pwd)"
41
+ echo "== $project =="
42
+
43
+ if [ ! -d "$project/.harness" ]; then
44
+ echo "DRIFT missing .harness"
45
+ status=1
46
+ continue
47
+ fi
48
+
49
+ while IFS= read -r rel; do
50
+ src="$HARNESS_ROOT/$rel"
51
+ dst="$project/$(project_path_for "$rel")"
52
+ if [ ! -f "$src" ]; then
53
+ echo "UPSTREAM_REQUIRED missing-source $rel"
54
+ status=1
55
+ elif [ ! -f "$dst" ]; then
56
+ echo "DRIFT missing $rel -> $(project_path_for "$rel")"
57
+ status=1
58
+ elif [ "$(sha "$src")" != "$(sha "$dst")" ]; then
59
+ echo "DRIFT hash $rel -> $(project_path_for "$rel")"
60
+ status=1
61
+ else
62
+ echo "PASS $rel"
63
+ fi
64
+ done < <(jq -r '.identical_files[]' "$MANIFEST")
65
+
66
+ while IFS=$'\t' read -r src_rel dst_rel mode; do
67
+ src="$HARNESS_ROOT/$src_rel"
68
+ dst="$project/$dst_rel"
69
+ if [ ! -f "$dst" ]; then
70
+ echo "DRIFT missing $dst_rel"
71
+ status=1
72
+ elif [ "$mode" = "identical" ] && [ "$(sha "$src")" != "$(sha "$dst")" ]; then
73
+ echo "DRIFT hash $src_rel -> $dst_rel"
74
+ status=1
75
+ else
76
+ echo "PASS $src_rel -> $dst_rel"
77
+ fi
78
+ done < <(jq -r '.template_mappings[] | [.source, .target, .mode] | @tsv' "$MANIFEST")
79
+
80
+ if [ -f "$project/.harness/config.json" ]; then
81
+ tmp_src="$(mktemp)"
82
+ tmp_dst="$(mktemp)"
83
+ config_core_filter "$HARNESS_ROOT/assets/templates/config.json" > "$tmp_src"
84
+ config_core_filter "$project/.harness/config.json" > "$tmp_dst"
85
+ if jq -e -s '.[0] == .[1]' "$tmp_src" "$tmp_dst" >/dev/null; then
86
+ echo "PASS config-core"
87
+ else
88
+ echo "DRIFT config-core"
89
+ diff -u "$tmp_src" "$tmp_dst" | sed 's/^/ /'
90
+ status=1
91
+ fi
92
+ rm -f "$tmp_src" "$tmp_dst"
93
+ else
94
+ echo "DRIFT missing .harness/config.json"
95
+ status=1
96
+ fi
97
+
98
+ echo
99
+ done
100
+
101
+ exit "$status"
@@ -141,7 +141,7 @@ cmd_init() {
141
141
  }) | from_entries
142
142
  )
143
143
  }
144
- ' "$FEATURES" > "$QUEUE"
144
+ ' "$FEATURES" > "${QUEUE}.tmp.$$" && mv "${QUEUE}.tmp.$$" "$QUEUE"
145
145
 
146
146
  local ready_count blocked_count passed_count
147
147
  ready_count=$(jq '.queue.ready | length' "$QUEUE")
@@ -79,7 +79,15 @@ if [ "$live" != "true" ] || [ "${service_count:-0}" -eq 0 ]; then
79
79
  .service_ops.monitor.last_check = $ts |
80
80
  .service_ops.monitor.stream_active = false |
81
81
  .service_ops.monitor.last_report = $report |
82
- .service_ops.health = []
82
+ .service_ops.health = [] |
83
+ .service_ops.incident.open = [] |
84
+ .service_ops.incident.signature = "" |
85
+ .service_ops.incident.repeat_count = 0 |
86
+ .service_ops.incident.recovery_required = false |
87
+ .service_ops.incident.partial_recovery = false |
88
+ .service_ops.incident.close_candidate = false |
89
+ .service_ops.incident.closed = false |
90
+ .service_ops.incident.last_seen_at = $ts
83
91
  ' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
84
92
 
85
93
  {
@@ -187,15 +195,47 @@ incidents_json="$(jq '
187
195
  ts: .ts
188
196
  }
189
197
  ]' <<<"$results_json")"
198
+ incident_signature="$(jq -r '[.[].id] | sort | join(",")' <<<"$incidents_json")"
199
+ prev_signature="$(jq -r '(.service_ops.incident.signature // "") as $s | if $s != "" then $s else ([.service_ops.incident.open[]?.id] | sort | join(",")) end' "$PROGRESS" 2>/dev/null || echo "")"
200
+ prev_repeat="$(jq -r '.service_ops.incident.repeat_count // 0' "$PROGRESS" 2>/dev/null || echo 0)"
201
+ repeat_count=0
202
+ if [ -n "$incident_signature" ]; then
203
+ if [ -n "$prev_signature" ] && [ "${prev_repeat:-0}" -lt 1 ]; then
204
+ prev_repeat=1
205
+ fi
206
+ if [ "$incident_signature" = "$prev_signature" ]; then
207
+ repeat_count=$((prev_repeat + 1))
208
+ else
209
+ repeat_count=1
210
+ fi
211
+ fi
212
+ recovery_required=false
213
+ if [ "$repeat_count" -ge 2 ]; then
214
+ recovery_required=true
215
+ fi
216
+ partial_recovery=false
217
+ close_candidate=false
218
+ if [ "$ok_count" -gt 0 ] && [ "$down_count" -gt 0 ]; then
219
+ partial_recovery=true
220
+ elif [ "$down_count" -eq 0 ] && [ "$warn_count" -eq 0 ]; then
221
+ close_candidate=true
222
+ fi
190
223
 
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" '
224
+ jq --arg ts "$ts" --arg report "$report_rel" --arg signature "$incident_signature" --argjson repeat "$repeat_count" --argjson recovery "$recovery_required" --argjson partial "$partial_recovery" --argjson close_candidate "$close_candidate" --argjson health "$results_json" --argjson incidents "$incidents_json" --argjson warn "$warn_count" --argjson alert "$down_count" '
192
225
  .service_ops.monitor.stream_active = false |
193
226
  .service_ops.monitor.last_check = $ts |
194
227
  .service_ops.monitor.last_report = $report |
195
228
  .service_ops.monitor.warns_this_sprint = ((.service_ops.monitor.warns_this_sprint // 0) + $warn) |
196
229
  .service_ops.monitor.alerts_this_sprint = ((.service_ops.monitor.alerts_this_sprint // 0) + $alert) |
197
230
  .service_ops.health = $health |
198
- .service_ops.incident.open = $incidents
231
+ .service_ops.incident.open = $incidents |
232
+ .service_ops.incident.signature = $signature |
233
+ .service_ops.incident.repeat_count = $repeat |
234
+ .service_ops.incident.recovery_required = $recovery |
235
+ .service_ops.incident.partial_recovery = $partial |
236
+ .service_ops.incident.close_candidate = $close_candidate |
237
+ .service_ops.incident.closed = (if $close_candidate then true else false end) |
238
+ .service_ops.incident.last_seen_at = $ts
199
239
  ' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
200
240
 
201
241
  {
@@ -208,6 +248,11 @@ jq --arg ts "$ts" --arg report "$report_rel" --argjson health "$results_json" --
208
248
  echo "- ok: $ok_count"
209
249
  echo "- warnings: $warn_count"
210
250
  echo "- alerts: $down_count"
251
+ echo "- incident_signature: ${incident_signature:-none}"
252
+ echo "- repeat_count: $repeat_count"
253
+ echo "- recovery_required: $recovery_required"
254
+ echo "- partial_recovery: $partial_recovery"
255
+ echo "- close_candidate: $close_candidate"
211
256
  echo ""
212
257
  echo "| Service | Port | Health | Logs | Status |"
213
258
  echo "|---|---:|---|---|---|"
@@ -123,6 +123,7 @@ cmd_install() {
123
123
  sed \
124
124
  -e "s|{{WAKE_SCRIPT}}|$WAKE_SH|g" \
125
125
  -e "s|{{LOG_DIR}}|$LOG_DIR|g" \
126
+ -e "s|{{HOME}}|$HOME|g" \
126
127
  "$template" > "$PLIST_PATH"
127
128
 
128
129
  # 기존 jobs 언로드 (idempotent)