@walwal-harness/cli 6.0.5 → 6.1.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/commands/harness-solo.md +20 -20
  3. package/commands/harness-team.md +1 -1
  4. package/gotchas/coo-developer.md +13 -0
  5. package/gotchas/documentationer.md +13 -0
  6. package/package.json +1 -1
  7. package/scripts/conductor-tick.sh +416 -0
  8. package/scripts/harness-archive.sh +123 -0
  9. package/scripts/harness-dashboard-up.sh +72 -0
  10. package/scripts/harness-dashboard.sh +509 -0
  11. package/scripts/harness-goal-init.sh +72 -0
  12. package/scripts/harness-goal-show.sh +37 -0
  13. package/scripts/harness-gotcha-memory.sh +348 -0
  14. package/scripts/harness-gotcha-register.sh +393 -0
  15. package/scripts/harness-meeting-doc.sh +153 -0
  16. package/scripts/harness-monitor.sh +398 -0
  17. package/scripts/harness-next.sh +577 -0
  18. package/scripts/harness-progress-set.sh +41 -0
  19. package/scripts/harness-prompt-history.sh +164 -0
  20. package/scripts/harness-queue-manager.sh +694 -0
  21. package/scripts/harness-session-start.sh +140 -0
  22. package/scripts/harness-statusline.sh +139 -0
  23. package/scripts/harness-task-session.sh +48 -0
  24. package/scripts/harness-tmux.sh +372 -0
  25. package/scripts/harness-user-prompt-submit.sh +123 -0
  26. package/scripts/init-agents-md.sh +337 -0
  27. package/scripts/init-ref-docs.sh +262 -0
  28. package/scripts/lib/harness-audit.sh +113 -0
  29. package/scripts/lib/harness-guardrail.sh +148 -0
  30. package/scripts/lib/harness-keywait.sh +57 -0
  31. package/scripts/lib/harness-render-progress.sh +346 -0
  32. package/scripts/scan-project.sh +367 -0
  33. package/skills/brainstorming/SKILL.md +4 -4
  34. package/skills/conductor/SKILL.md +19 -16
  35. package/skills/coo-developer/SKILL.md +42 -0
  36. package/skills/cqo/SKILL.md +7 -0
  37. package/skills/cto/SKILL.md +7 -0
  38. package/skills/dispatcher/SKILL.md +17 -28
  39. package/skills/dispatcher/persona-ceo.md +2 -1
  40. package/skills/documentationer/SKILL.md +46 -0
  41. package/skills/evaluator-code-quality/SKILL.md +1 -1
  42. package/skills/evaluator-functional/SKILL.md +1 -1
  43. package/skills/evaluator-visual/SKILL.md +1 -1
  44. package/skills/generator-backend/SKILL.md +1 -1
  45. package/skills/generator-frontend/SKILL.md +1 -1
  46. package/skills/meeting-manager/SKILL.md +34 -1
  47. package/skills/planner/SKILL.md +23 -9
  48. package/skills/planner/persona-coo-hr.md +17 -0
  49. package/skills/service-ops/SKILL.md +19 -4
package/CHANGELOG.md CHANGED
@@ -29,6 +29,71 @@ docmeta:
29
29
 
30
30
  # Changelog
31
31
 
32
+ ## 6.1.1 — COO hypothesis routing activation (2026-05-08)
33
+
34
+ ### Why
35
+ v6.1.0 에서 COO 직속 `coo-developer` / `documentationer` 역할과 hypothesis cell 문서는 추가됐지만, 실제 Conductor 라우팅은 여전히 `planner -> cto` 직행이었다. 그래서 "COO가 가설을 세우면 빠르게 리서치·실험·판정한다" 는 운용 모델이 문서상으로만 존재했다.
36
+
37
+ ### Changes
38
+ - **runtime routing for hypothesis mode**
39
+ - `planner.requested_mode = "hypothesis"` 이면 `documentationer` 로 우선 라우팅
40
+ - 이후 `documentationer -> coo-developer -> documentationer -> planner` 순서로 가설 검증 루프 진행
41
+ - 단계 추적용 상태를 `planner.last_brief` 에 `hypothesis:research`, `hypothesis:experiment`, `hypothesis:report`, `hypothesis:done` 으로 기록
42
+ - **agent registry update**
43
+ - `.harness/config.json` 의 `agents` 카탈로그에 `coo-developer`, `documentationer` 등록
44
+ - handoff/statusline/session-start 가 두 agent 를 정상 인식하도록 연결
45
+
46
+ ### Validation
47
+ - `bash -n scripts/conductor-tick.sh`
48
+ - `jq empty .harness/config.json`
49
+ - `/private/tmp` 샌드박스에서 hypothesis flow 시뮬레이션:
50
+ - `planner(completed, requested_mode=hypothesis) -> documentationer`
51
+ - `documentationer -> coo-developer`
52
+ - `coo-developer -> documentationer`
53
+ - `documentationer -> planner(requested_mode=hypothesis-verdict)`
54
+
55
+ ## 6.1.0 — document-driven company loop + task-session isolation (2026-05-08)
56
+
57
+ ### Why
58
+ v6.0.x 는 회사형 조직 비유와 Cxx 라우팅을 문서로는 설명했지만, 실제 런타임은 여전히 얕았다. 특히 다음 두 결함이 컸다.
59
+
60
+ - 회의 결과가 `누가 다음 owner 인가` 를 구조적으로 기록하지 않음
61
+ - `goal_adherence` 하락 시 원인 분류 없이 Planner로 보내는 경향이 있음
62
+
63
+ 또한 agent 간 자기강화 편향을 줄이기 위한 문서 중심 task session 분리도 충분히 강제되지 않았다.
64
+
65
+ ### Changes
66
+ - **document-driven meeting decision**
67
+ - `meeting-manager` 가 `notice.md`, `prep-*.md`, `meeting-<id>.md` skeleton 을 자동 생성
68
+ - 회의 기록에 `decision.owner`, `action_type`, `rationale`, `evidence`, `drift_classification` JSON 블록 추가
69
+ - `conductor-tick` 은 더 이상 `meeting_reason` 문자열만 보지 않고 회의 decision 문서를 우선 읽어 다음 owner 를 결정
70
+ - **goal drift classification**
71
+ - `implementation_drift | planning_drift | ops_drift | goal_drift` 분류 추가
72
+ - 분류별 기본 라우팅:
73
+ - `implementation_drift -> cto`
74
+ - `planning_drift -> planner`
75
+ - `ops_drift -> service-ops`
76
+ - `goal_drift -> dispatcher`
77
+ - **task-session isolation**
78
+ - `harness-next` 가 다음 agent 마다 `.harness/actions/task-sessions/<agent>/<id>.md` 생성
79
+ - `handoff.json` 에 `task_session_path` 추가
80
+ - `UserPromptSubmit` 가 running agent 와 다른 `/harness-*` 호출을 경고가 아니라 **hard block**
81
+ - **direct invocation policy**
82
+ - 모든 harness skill 의 `disable-model-invocation` 을 `false` 로 통일
83
+ - orchestration/control-plane 과 worker/evaluator 모두 직접 호출 가능 상태로 정리
84
+ - **archive reset hardening**
85
+ - archive 시 `workflow`, `meetings.decision`, `service_ops.drift_classification`, `task_sessions.current` 초기화
86
+
87
+ ### Validation
88
+ - `bash -n` 으로 주요 스크립트 문법 검증
89
+ - `jq empty` 로 `config.json`, `progress.json` 검증
90
+ - `/private/tmp` 샌드박스에서:
91
+ - `dispatcher -> meeting-manager` 시 회의 문서 자동 생성 확인
92
+ - 회의 decision 편집 후 `dispatcher / planner / cto` 로 문서 기반 재라우팅 확인
93
+ - `goal_adherence` 하락 시 drift classification 반영 확인
94
+ - context isolation hard block 확인
95
+ - `task_session_path` 생성 확인
96
+
32
97
  ## 6.0.5 — migrate 가 gotcha entry / bundle version 까지 sync (2026-05-07)
33
98
 
34
99
  ### Why
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  docmeta:
3
3
  id: harness-solo
4
- title: /harness-solo — Solo Mode 시작/전환
4
+ title: /harness-solo — 비상용 Single-Agent Fallback
5
5
  type: input
6
6
  createdAt: 2026-04-20T00:00:00Z
7
7
  updatedAt: 2026-05-07T00:00:00Z
@@ -24,14 +24,14 @@ docmeta:
24
24
  tags: [harness, solo-mode, command, override, v6]
25
25
  ---
26
26
 
27
- # /harness-solo — Solo Mode 사용자 Override (v6.0+)
27
+ # /harness-solo — 비상용 Single-Agent Fallback (v6.0+)
28
28
 
29
- > v6.0 부터 모드 결정은 **Conductor 가 자동**으로 합니다 (config.json `mode_selection.rules`). 본 명령은 Conductor 의 자동 결정을 사용자가 강제로 덮어쓰는 **override** 입니다.
29
+ > v6.0 의 정상 경로는 회사형 자동 루프입니다. 본 명령은 그 루프를 일시적으로 벗어나는 **비상용 override** 입니다.
30
30
  >
31
31
  > **효과**: `progress.json.mode = "solo"` 강제 + `mode_decision.user_override = "solo"` 기록. 현재 sprint 끝까지 유지. 다음 sprint 진입 시 Conductor 가 재자동결정. **Auto 복귀**: 사용자 발화 "auto 로 돌려" 또는 "Conductor 결정으로".
32
32
 
33
- 프롬프트 기반으로 하네스 파이프라인을 순차 진행합니다.
34
- Team 모드에서 전환 시, 진행 중이던 피처의 상태를 보존하고 Solo로 이어갑니다.
33
+ 디버깅, 스크립트 장애, 수동 hotfix 같은 예외 상황에서만 사용합니다.
34
+ 회사 루프에서 전환 시, 진행 중이던 피처의 상태를 보존하고 단일 에이전트 fallback 으로 이어갑니다.
35
35
 
36
36
  ## 실행 절차
37
37
 
@@ -49,12 +49,12 @@ echo "현재 모드: $MODE"
49
49
  bash scripts/harness-queue-manager.sh recover .
50
50
 
51
51
  # mode를 solo로 설정
52
- jq '.mode = "solo" | .team_state.active_teams = 0 | .team_state.paused_at = (now | todate)' .harness/progress.json > /tmp/progress_tmp.json && mv /tmp/progress_tmp.json .harness/progress.json
52
+ jq '.mode = "solo" | .mode_decision.user_override = "solo" | .team_state.active_teams = 0 | .team_state.paused_at = (now | todate)' .harness/progress.json > /tmp/progress_tmp.json && mv /tmp/progress_tmp.json .harness/progress.json
53
53
  ```
54
54
 
55
55
  **paused → solo 전환 시:**
56
56
  ```bash
57
- jq '.mode = "solo"' .harness/progress.json > /tmp/progress_tmp.json && mv /tmp/progress_tmp.json .harness/progress.json
57
+ jq '.mode = "solo" | .mode_decision.user_override = "solo"' .harness/progress.json > /tmp/progress_tmp.json && mv /tmp/progress_tmp.json .harness/progress.json
58
58
  ```
59
59
 
60
60
  ### Step 2: 현재 진행 상태 표시
@@ -75,29 +75,29 @@ fi
75
75
  progress.json의 `next_agent` 필드를 확인하여 다음 단계를 안내합니다:
76
76
 
77
77
  - **next_agent = "dispatcher"** → "프롬프트로 요구사항을 입력하면 자동으로 dispatcher가 분석합니다."
78
- - **next_agent = "planner"** → "/harness-planner 를 호출하거나, 계획을 요청하세요."
79
- - **next_agent = "generator-backend"** → "/harness-generator-backend 를 호출하세요."
80
- - **next_agent = "generator-frontend"** → "/harness-generator-frontend 를 호출하세요."
81
- - **next_agent = "evaluator-functional"** → "/harness-evaluator-functional 를 호출하세요."
82
- - **next_agent = "evaluator-visual"** → "/harness-evaluator-visual 를 호출하세요."
78
+ - **next_agent = "planner"** → Planner 단계가 다음입니다. SessionStart / 내부 handoff가 연결돼 있으면 자동 진입, 아니면 해당 스킬을 수동 호출합니다.
79
+ - **next_agent = "generator-backend"** → Backend generator 단계가 다음입니다.
80
+ - **next_agent = "generator-frontend"** → Frontend generator 단계가 다음입니다.
81
+ - **next_agent = "evaluator-functional"** → Functional evaluator 단계가 다음입니다.
82
+ - **next_agent = "evaluator-visual"** → Visual evaluator 단계가 다음입니다.
83
83
 
84
84
  ### Step 4: Solo 모드 진행
85
85
 
86
- 각 에이전트는 완료 즉시 **`/harness-next` 슬래시 명령을 자동 호출** 하여 다음 에이전트로 핸드오프합니다 (사용자가 매번 bash 명령을 칠 필요 없음).
86
+ 각 에이전트는 완료 즉시 **내부 handoff(`harness-next.sh` 또는 SessionStart hook)** 로 다음 에이전트에 넘겨집니다. Owner가 `/harness-next` 를 직접 다룬다는 전제는 6.0 회사 모델과 맞지 않습니다.
87
87
 
88
88
  **예외 — 사용자 승인 게이트가 있는 단계:**
89
- - **Brainstorming**: brainstorm-spec.md 완성 후 사용자 승인 대기 → 승인 시 자동 `/harness-next`
90
- - **Planner**: plan.md / api-contract.json 완성 후 사용자 승인 대기 → 승인 시 자동 `/harness-next`
89
+ - **Brainstorming**: brainstorm-spec.md 완성 후 사용자 승인 대기 → 승인 시 내부 handoff
90
+ - **Planner**: plan.md / api-contract.json 완성 후 사용자 승인 대기 → 승인 시 내부 handoff
91
91
 
92
- 흐름이 멈췄거나 수동으로 다음 단계를 트리거하고 싶으면 사용자가 직접 `/harness-next` 입력 가능.
92
+ 흐름이 멈췄다면 원인은 보통 내부 handoff 스크립트/훅 미설치 또는 `next_agent` 불일치입니다. 이 경우 상태 파일과 hook 설치 상태를 먼저 점검합니다.
93
93
 
94
94
  feature-queue.json이 존재하는 경우:
95
95
  - 피처 완료(evaluator PASS) 시 `bash scripts/harness-queue-manager.sh pass {FEATURE_ID} .`를 호출하여 공유 상태 업데이트
96
96
  - Team 모드로 전환해도 이미 완료된 피처는 skip됨
97
97
 
98
- ## Solo 모드에서 가능한 작업
98
+ ## 사용 권장 상황
99
99
 
100
100
  - 프롬프트로 각 에이전트 순차 실행
101
- - 코드 직접 수정 및 디버깅
102
- - 피처 단위 작업 선택
103
- - Team 모드로 언제든 전환: `/harness-team`
101
+ - 내부 handoff 스크립트 장애 시 응급 복구
102
+ - 아주 짧은 수동 hotfix / 상태 수선
103
+ - 정상 회사 루프로 복귀: `/harness-team`
@@ -54,7 +54,7 @@ Planner가 완료한 feature-list.json의 피처들을 최대 3개 팀이 병렬
54
54
 
55
55
  ```bash
56
56
  # progress.json에 mode=team 설정
57
- jq '.mode = "team" | .team_state.active_teams = 3 | .team_state.paused_at = null' .harness/progress.json > /tmp/progress_tmp.json && mv /tmp/progress_tmp.json .harness/progress.json
57
+ jq '.mode = "team" | .mode_decision.user_override = "team" | .team_state.active_teams = 3 | .team_state.paused_at = null' .harness/progress.json > /tmp/progress_tmp.json && mv /tmp/progress_tmp.json .harness/progress.json
58
58
 
59
59
  # Queue 초기화 또는 복구
60
60
  if [ ! -f .harness/actions/feature-queue.json ]; then
@@ -0,0 +1,13 @@
1
+ # Gotchas — COO Developer
2
+
3
+ ### [G-001] 운영코드와 실험코드 혼동 <!-- rule_id: coo-developer-experiment-vs-production -->
4
+ - **Status**: unverified
5
+ - **Date**: 2026-05-08
6
+ - **Source**: planner:manual
7
+ - **Trigger**: COO 직속 가설검증 셀 신설
8
+ - **Wrong**: spike 코드를 운영 경로의 정본처럼 다룸
9
+ - **Right**: 실험 코드는 실험으로 남기고, 정규화가 필요하면 Planner가 다시 Sprint artifact로 승격
10
+ - **Why**: 이 셀의 목적은 빠른 사실 확인이지 운영 품질 보장이 아니다
11
+ - **Scope**: `coo-developer`의 모든 실험 작업
12
+ - **Occurrences**: 1
13
+ - **Last-Seen**: 2026-05-08
@@ -0,0 +1,13 @@
1
+ # Gotchas — Documentationer
2
+
3
+ ### [G-001] 보고서가 근거를 앞서감 <!-- rule_id: documentationer-claim-without-evidence -->
4
+ - **Status**: unverified
5
+ - **Date**: 2026-05-08
6
+ - **Source**: planner:manual
7
+ - **Trigger**: COO 직속 가설검증 셀 신설
8
+ - **Wrong**: 리서치나 실험 근거보다 해석을 먼저 확정함
9
+ - **Right**: evidence를 명시하고, 판정은 유효/무효/보류 중 하나로 제한
10
+ - **Why**: Documentationer의 산출물은 COO 의사결정의 입력이며, 환상적 확신을 만들면 안 된다
11
+ - **Scope**: `documentationer`의 모든 보고서와 브리프
12
+ - **Occurrences**: 1
13
+ - **Last-Seen**: 2026-05-08
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "6.0.5",
3
+ "version": "6.1.1",
4
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"
@@ -0,0 +1,416 @@
1
+ #!/bin/bash
2
+ # conductor-tick.sh — document-driven company-loop router
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ source "$SCRIPT_DIR/lib/harness-render-progress.sh"
7
+
8
+ PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || exit 0
9
+ PROGRESS="$PROJECT_ROOT/.harness/progress.json"
10
+ CONFIG="$PROJECT_ROOT/.harness/config.json"
11
+ FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
12
+
13
+ [ -f "$PROGRESS" ] || exit 0
14
+ [ -f "$CONFIG" ] || exit 0
15
+ command -v jq >/dev/null 2>&1 || exit 0
16
+
17
+ candidate="${HARNESS_CONDUCTOR_CANDIDATE:-}"
18
+ if [ -z "$candidate" ]; then
19
+ candidate=$(jq -r '.next_agent // "null"' "$PROGRESS" 2>/dev/null || echo "null")
20
+ fi
21
+
22
+ current_agent=$(jq -r '.current_agent // "null"' "$PROGRESS")
23
+ agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS")
24
+ pipeline=$(jq -r '.pipeline // "null"' "$PROGRESS")
25
+ mode=$(jq -r '.mode // "auto"' "$PROGRESS")
26
+ user_override=$(jq -r '.mode_decision.user_override // "null"' "$PROGRESS")
27
+ requested_mode=$(jq -r '.service_ops.requested_mode // "null"' "$PROGRESS")
28
+ planner_requested_mode=$(jq -r '.planner.requested_mode // "null"' "$PROGRESS")
29
+ planner_last_brief=$(jq -r '.planner.last_brief // "null"' "$PROGRESS")
30
+ goal_adherence=$(jq -r '.goals.current_adherence // "null"' "$PROGRESS")
31
+ meetings_active_count=$(jq -r '(.meetings.active | length) // 0' "$PROGRESS")
32
+ cqo_verdict=$(jq -r '.cqo.sprint_verdict // "pending"' "$PROGRESS")
33
+ ops_report=$(jq -r '.service_ops.auto_retro.last_report // "null"' "$PROGRESS")
34
+ cto_hotfixes=$(jq -r '.cto.open_hotfixes // 0' "$PROGRESS")
35
+ ops_recommendations=$(jq -r '.service_ops.auto_retro.open_recommendations // 0' "$PROGRESS")
36
+ ops_alerts=$(jq -r '.service_ops.monitor.alerts_this_sprint // 0' "$PROGRESS")
37
+ open_incidents=$(jq -r '(.service_ops.incident.open | length) // 0' "$PROGRESS")
38
+ workflow_stage=$(jq -r '.workflow.stage // "goal-intake"' "$PROGRESS")
39
+ meeting_type=$(jq -r '.meetings.requested_type // "null"' "$PROGRESS")
40
+ meeting_reason=$(jq -r '.meetings.requested_reason // "null"' "$PROGRESS")
41
+ cqo_regressions=$(jq -r '.cqo.open_regressions // 0' "$PROGRESS")
42
+ cqo_axes_below=$(jq -r '(.cqo.axes_below_threshold | length) // 0' "$PROGRESS")
43
+ plan_status=$(jq -r '.artifacts["plan.md"].status // "pending"' "$PROGRESS")
44
+ feature_status=$(jq -r '.artifacts["feature-list.json"].status // "pending"' "$PROGRESS")
45
+ api_status=$(jq -r '.artifacts["api-contract.json"].status // "pending"' "$PROGRESS")
46
+ service_ops_drift=$(jq -r '.service_ops.drift_classification // "null"' "$PROGRESS")
47
+
48
+ set_progress() {
49
+ bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" "$1" >/dev/null
50
+ }
51
+
52
+ escape_json_string() {
53
+ jq -Rn --arg v "$1" '$v'
54
+ }
55
+
56
+ maybe_select_mode() {
57
+ local effective="$mode"
58
+ if [ "$user_override" != "null" ] && [ -n "$user_override" ]; then
59
+ effective="$user_override"
60
+ set_progress ".mode = \"$effective\" |
61
+ .mode_decision.owner = \"conductor\" |
62
+ .mode_decision.user_override = \"$effective\" |
63
+ .mode_decision.decided_at = (now | todate) |
64
+ .mode_decision.rationale = \"user override: $effective\""
65
+ echo "$effective"
66
+ return
67
+ fi
68
+ if [ "$mode" != "auto" ] || [ ! -f "$FEATURES" ]; then
69
+ echo "$mode"
70
+ return
71
+ fi
72
+
73
+ local feature_count ready_at_start critical_depth
74
+ feature_count=$(jq '.features | length // 0' "$FEATURES" 2>/dev/null || echo 0)
75
+ ready_at_start=$(jq '[.features[]? | select(((.depends_on // []) | length) == 0)] | length' "$FEATURES" 2>/dev/null || echo 0)
76
+ critical_depth=$(jq '
77
+ def feature($id): .features[]? | select(.id == $id);
78
+ def depth($id):
79
+ (feature($id) | .depends_on // []) as $deps
80
+ | if ($deps | length) == 0 then 1 else ([ $deps[] | depth(.) ] | max + 1) end;
81
+ ([.features[]?.id | depth(.)] | max) // 0
82
+ ' "$FEATURES" 2>/dev/null || echo 0)
83
+
84
+ local team_ready team_features team_depth selected rationale
85
+ team_ready=$(jq -r '.mode_selection.rules.force_team_when.ready_at_start_gte // 3' "$CONFIG")
86
+ team_features=$(jq -r '.mode_selection.rules.force_team_when.feature_count_gte // 6' "$CONFIG")
87
+ team_depth=$(jq -r '.mode_selection.rules.force_team_when.critical_path_depth_lte // 2' "$CONFIG")
88
+
89
+ if [ "$ready_at_start" -ge "$team_ready" ] && [ "$feature_count" -ge "$team_features" ] && [ "$critical_depth" -le "$team_depth" ]; then
90
+ selected="team"
91
+ else
92
+ selected=$(jq -r '.mode_selection.rules.tie_breaker // "team"' "$CONFIG")
93
+ fi
94
+ rationale="company-default: ready=${ready_at_start}, features=${feature_count}, depth=${critical_depth} -> ${selected}"
95
+ set_progress ".mode = \"$selected\" |
96
+ .mode_decision.owner = \"conductor\" |
97
+ .mode_decision.decided_at = (now | todate) |
98
+ .mode_decision.rationale = \"$rationale\""
99
+ echo "$selected"
100
+ }
101
+
102
+ infer_drift_classification() {
103
+ if [ "$service_ops_drift" != "null" ] && [ -n "$service_ops_drift" ]; then
104
+ echo "$service_ops_drift"
105
+ return
106
+ fi
107
+ if [ "$cqo_verdict" = "FAIL" ] || [ "${cqo_regressions:-0}" -gt 0 ] || [ "${cqo_axes_below:-0}" -gt 0 ]; then
108
+ echo "implementation_drift"
109
+ return
110
+ fi
111
+ if [ "$requested_mode" = "incident" ] || [ "${open_incidents:-0}" -gt 0 ] || [ "${ops_alerts:-0}" -gt 0 ]; then
112
+ echo "ops_drift"
113
+ return
114
+ fi
115
+ if [ "$goal_adherence" != "null" ] && awk "BEGIN {exit !($goal_adherence < 0.7)}"; then
116
+ if [ "$cqo_verdict" = "PASS" ] && [ "${open_incidents:-0}" -eq 0 ] && [ "${ops_alerts:-0}" -eq 0 ]; then
117
+ echo "planning_drift"
118
+ elif [ "$(jq -r '.goals.active_id // "null"' "$PROGRESS")" = "null" ]; then
119
+ echo "goal_drift"
120
+ else
121
+ echo "goal_drift"
122
+ fi
123
+ return
124
+ fi
125
+ echo "unknown"
126
+ }
127
+
128
+ meeting_stage_for_owner() {
129
+ case "$1" in
130
+ planner) echo "coo-planning" ;;
131
+ cto) echo "cto-review" ;;
132
+ cqo) echo "quality-review" ;;
133
+ service-ops) echo "ops-review" ;;
134
+ dispatcher) echo "goal-intake" ;;
135
+ *) echo "goal-intake" ;;
136
+ esac
137
+ }
138
+
139
+ load_meeting_decision() {
140
+ local decision
141
+ decision=$(bash "$SCRIPT_DIR/harness-meeting-doc.sh" "$PROJECT_ROOT" read-decision 2>/dev/null || true)
142
+ if [ -n "$decision" ]; then
143
+ echo "$decision"
144
+ else
145
+ jq -c '.meetings.decision // {}' "$PROGRESS"
146
+ fi
147
+ }
148
+
149
+ effective_mode="$(maybe_select_mode)"
150
+ drift_classification="$(infer_drift_classification)"
151
+
152
+ next="$candidate"
153
+ action="pass-through"
154
+ new_conductor_state="running"
155
+ new_workflow_stage="$workflow_stage"
156
+ meeting_filter=""
157
+ planner_filter=""
158
+ ops_filter=""
159
+ meeting_prepare=false
160
+
161
+ plan_artifacts_ready=false
162
+ if [ "$plan_status" != "pending" ] || [ "$feature_status" != "pending" ] || [ "$api_status" != "pending" ]; then
163
+ plan_artifacts_ready=true
164
+ fi
165
+
166
+ first_generator="generator-backend"
167
+ if [ "$pipeline" = "FE-ONLY" ]; then
168
+ first_generator="generator-frontend"
169
+ fi
170
+
171
+ if [ "$current_agent" = "dispatcher" ] && [ "$agent_status" = "completed" ]; then
172
+ next="meeting-manager"
173
+ action="convene:all-hands:goal-intake"
174
+ new_conductor_state="waiting_meeting"
175
+ new_workflow_stage="ceo-meeting"
176
+ meeting_prepare=true
177
+ meeting_filter="
178
+ .meetings.active = [\"meeting-manager\"] |
179
+ .meetings.requested_type = \"all-hands\" |
180
+ .meetings.requested_reason = \"goal-intake\" |
181
+ .meetings.decision = {
182
+ \"owner\": \"planner\",
183
+ \"action_type\": \"goal-alignment\",
184
+ \"rationale\": \"CEO intake always starts with COO alignment.\",
185
+ \"evidence\": [],
186
+ \"drift_classification\": null,
187
+ \"source_path\": null
188
+ }"
189
+
190
+ elif [ "$meetings_active_count" -gt 0 ] && [ "$current_agent" != "meeting-manager" ]; then
191
+ next="meeting-manager"
192
+ action="spawn:meeting-manager:${meeting_type}"
193
+ new_conductor_state="waiting_meeting"
194
+
195
+ elif [ "$requested_mode" != "null" ] && [ -n "$requested_mode" ] && [ "$current_agent" != "service-ops" ]; then
196
+ next="service-ops"
197
+ action="spawn:service-ops:${requested_mode}"
198
+
199
+ elif [ "$goal_adherence" != "null" ] && awk "BEGIN {exit !($goal_adherence < 0.7)}"; then
200
+ next="meeting-manager"
201
+ action="convene:spec-review:${drift_classification}"
202
+ new_conductor_state="waiting_meeting"
203
+ new_workflow_stage="goal-drift-review"
204
+ meeting_prepare=true
205
+ meeting_filter="
206
+ .meetings.active = [\"meeting-manager\"] |
207
+ .meetings.requested_type = \"spec-review\" |
208
+ .meetings.requested_reason = \"goal-drift\" |
209
+ .service_ops.drift_classification = \"$drift_classification\" |
210
+ .meetings.decision = {
211
+ \"owner\": null,
212
+ \"action_type\": null,
213
+ \"rationale\": null,
214
+ \"evidence\": [],
215
+ \"drift_classification\": \"$drift_classification\",
216
+ \"source_path\": null
217
+ }"
218
+
219
+ elif [ "$current_agent" = "meeting-manager" ] && [ "$agent_status" = "completed" ]; then
220
+ meeting_decision="$(load_meeting_decision)"
221
+ owner=$(jq -r '.owner // "null"' <<<"$meeting_decision")
222
+ action_type=$(jq -r '.action_type // "triage"' <<<"$meeting_decision")
223
+ rationale=$(jq -r '.rationale // ""' <<<"$meeting_decision")
224
+ evidence=$(jq -c '.evidence // []' <<<"$meeting_decision")
225
+ decision_drift=$(jq -r '.drift_classification // "unknown"' <<<"$meeting_decision")
226
+ source_path=$(jq -r '.source_path // "null"' <<<"$meeting_decision")
227
+
228
+ if [ "$owner" = "null" ] || [ -z "$owner" ]; then
229
+ case "$decision_drift" in
230
+ implementation_drift) owner="cto" ;;
231
+ planning_drift) owner="planner" ;;
232
+ ops_drift) owner="service-ops" ;;
233
+ goal_drift) owner="dispatcher" ;;
234
+ *)
235
+ if [ "$plan_artifacts_ready" = false ]; then owner="planner"; else owner="cto"; fi
236
+ ;;
237
+ esac
238
+ fi
239
+
240
+ next="$owner"
241
+ action="dispatch:${owner}:${action_type}"
242
+ new_workflow_stage="$(meeting_stage_for_owner "$owner")"
243
+ if [ "$owner" = "planner" ]; then
244
+ planner_filter=".planner.requested_mode = \"$action_type\""
245
+ fi
246
+ meeting_filter="
247
+ .meetings.active = [] |
248
+ .meetings.last_type = .meetings.requested_type |
249
+ .meetings.last_reason = .meetings.requested_reason |
250
+ .meetings.last_decision = \"$owner:$action_type\" |
251
+ .meetings.requested_type = null |
252
+ .meetings.requested_reason = null |
253
+ .meetings.decision = {
254
+ \"owner\": \"$owner\",
255
+ \"action_type\": \"$action_type\",
256
+ \"rationale\": $(escape_json_string "$rationale"),
257
+ \"evidence\": $evidence,
258
+ \"drift_classification\": $(escape_json_string "$decision_drift"),
259
+ \"source_path\": $(escape_json_string "$source_path")
260
+ }"
261
+
262
+ elif [ "$current_agent" = "planner" ] && [ "$agent_status" = "completed" ]; then
263
+ if [ "$planner_requested_mode" = "hypothesis" ]; then
264
+ next="documentationer"
265
+ action="dispatch:hypothesis:documentationer"
266
+ new_workflow_stage="coo-hypothesis-research"
267
+ planner_filter='
268
+ .planner.last_brief = "hypothesis:research" |
269
+ .planner.requested_mode = null'
270
+ else
271
+ next="cto"
272
+ action="handoff:cto:plan-ready"
273
+ new_workflow_stage="cto-review"
274
+ planner_filter='
275
+ .planner.last_brief = (.planner.requested_mode // "goal-alignment") |
276
+ .planner.requested_mode = null'
277
+ fi
278
+
279
+ elif [ "$current_agent" = "documentationer" ] && [ "$agent_status" = "completed" ]; then
280
+ if [ "$planner_last_brief" = "hypothesis:research" ]; then
281
+ next="coo-developer"
282
+ action="dispatch:hypothesis:experiment"
283
+ new_workflow_stage="coo-hypothesis-experiment"
284
+ planner_filter='.planner.last_brief = "hypothesis:experiment"'
285
+ elif [ "$planner_last_brief" = "hypothesis:report" ]; then
286
+ next="planner"
287
+ action="dispatch:planner:hypothesis-verdict"
288
+ new_workflow_stage="coo-hypothesis-verdict"
289
+ planner_filter='
290
+ .planner.last_brief = "hypothesis:done" |
291
+ .planner.requested_mode = "hypothesis-verdict"'
292
+ fi
293
+
294
+ elif [ "$current_agent" = "coo-developer" ] && [ "$agent_status" = "completed" ]; then
295
+ if [ "$planner_last_brief" = "hypothesis:experiment" ]; then
296
+ next="documentationer"
297
+ action="dispatch:hypothesis:report"
298
+ new_workflow_stage="coo-hypothesis-report"
299
+ planner_filter='.planner.last_brief = "hypothesis:report"'
300
+ fi
301
+
302
+ elif [ "$current_agent" = "cto" ]; then
303
+ if [ "${cto_hotfixes:-0}" -gt 0 ] || [ "${ops_recommendations:-0}" -gt 0 ]; then
304
+ next="planner"
305
+ action="dispatch:planner:execution-plan"
306
+ new_workflow_stage="coo-replan"
307
+ planner_filter='.planner.requested_mode = "execution-plan"'
308
+ elif [ "$candidate" = "archive" ] && [ "$workflow_stage" = "ops-review" ]; then
309
+ next="archive"
310
+ action="archive"
311
+ new_conductor_state="completed"
312
+ new_workflow_stage="ops-monitoring"
313
+ else
314
+ next="$first_generator"
315
+ action="dispatch:workers:${first_generator}"
316
+ new_workflow_stage="implementation"
317
+ fi
318
+
319
+ elif [ "$candidate" = "archive" ]; then
320
+ if [[ "$current_agent" == evaluator-* ]] && [ "$cqo_verdict" = "pending" ]; then
321
+ next="cqo"
322
+ action="spawn:cqo"
323
+ new_workflow_stage="quality-review"
324
+ elif [ "$current_agent" = "cqo" ]; then
325
+ if [ "$cqo_verdict" = "FAIL" ] || [ "${cqo_regressions:-0}" -gt 0 ] || [ "${cqo_axes_below:-0}" -gt 0 ]; then
326
+ next="meeting-manager"
327
+ action="convene:spec-review:implementation_drift"
328
+ new_conductor_state="waiting_meeting"
329
+ new_workflow_stage="quality-review"
330
+ meeting_prepare=true
331
+ meeting_filter='
332
+ .meetings.active = ["meeting-manager"] |
333
+ .meetings.requested_type = "spec-review" |
334
+ .meetings.requested_reason = "quality-fail" |
335
+ .service_ops.drift_classification = "implementation_drift" |
336
+ .meetings.decision = {
337
+ "owner": null,
338
+ "action_type": null,
339
+ "rationale": null,
340
+ "evidence": [],
341
+ "drift_classification": "implementation_drift",
342
+ "source_path": null
343
+ }'
344
+ else
345
+ next="service-ops"
346
+ action="spawn:service-ops:auto-retro"
347
+ new_workflow_stage="ops-review"
348
+ ops_filter='.service_ops.requested_mode = "auto-retro"'
349
+ fi
350
+ elif [ "$cqo_verdict" != "pending" ] && [ "$ops_report" = "null" ]; then
351
+ next="service-ops"
352
+ action="spawn:service-ops:auto-retro"
353
+ new_workflow_stage="ops-review"
354
+ ops_filter='.service_ops.requested_mode = "auto-retro"'
355
+ elif [ "$current_agent" = "service-ops" ]; then
356
+ local_drift="$drift_classification"
357
+ if [ "$requested_mode" = "auto-retro" ] || [ "${open_incidents:-0}" -gt 0 ] || [ "${ops_alerts:-0}" -gt 0 ] || { [ "$goal_adherence" != "null" ] && awk "BEGIN {exit !($goal_adherence < 0.7)}"; }; then
358
+ next="meeting-manager"
359
+ action="convene:sprint-review:${local_drift}"
360
+ new_conductor_state="waiting_meeting"
361
+ new_workflow_stage="ops-review"
362
+ meeting_prepare=true
363
+ meeting_filter="
364
+ .meetings.active = [\"meeting-manager\"] |
365
+ .meetings.requested_type = \"sprint-review\" |
366
+ .meetings.requested_reason = \"ops-batch\" |
367
+ .service_ops.drift_classification = \"$local_drift\" |
368
+ .meetings.decision = {
369
+ \"owner\": null,
370
+ \"action_type\": null,
371
+ \"rationale\": null,
372
+ \"evidence\": [],
373
+ \"drift_classification\": \"$local_drift\",
374
+ \"source_path\": null
375
+ }"
376
+ else
377
+ next="archive"
378
+ action="archive"
379
+ new_conductor_state="completed"
380
+ new_workflow_stage="ops-monitoring"
381
+ ops_filter='.service_ops.requested_mode = null'
382
+ fi
383
+ fi
384
+ fi
385
+
386
+ filter=".conductor.state = \"$new_conductor_state\" |
387
+ .conductor.current_action = \"$action\" |
388
+ .conductor.last_tick = (now | todate) |
389
+ .conductor.tick_count = ((.conductor.tick_count // 0) + 1) |
390
+ .next_agent = \"$next\" |
391
+ .workflow.stage = \"$new_workflow_stage\" |
392
+ .workflow.last_transition = (now | todate) |
393
+ .workflow.last_reason = \"$action\""
394
+
395
+ if [ -n "$meeting_filter" ]; then
396
+ filter="$filter | $meeting_filter"
397
+ fi
398
+ if [ -n "$planner_filter" ]; then
399
+ filter="$filter | $planner_filter"
400
+ fi
401
+ if [ -n "$ops_filter" ]; then
402
+ filter="$filter | $ops_filter"
403
+ elif [ "$current_agent" = "service-ops" ] && [ "$next" != "service-ops" ]; then
404
+ filter="$filter | .service_ops.requested_mode = null"
405
+ fi
406
+ if [ "$next" = "meeting-manager" ] && [ "$workflow_stage" = "ops-review" ]; then
407
+ filter="$filter | .workflow.loop_count = ((.workflow.loop_count // 0) + 1)"
408
+ fi
409
+
410
+ set_progress "$filter"
411
+
412
+ if [ "$meeting_prepare" = true ] && [ "$next" = "meeting-manager" ]; then
413
+ bash "$SCRIPT_DIR/harness-meeting-doc.sh" "$PROJECT_ROOT" prepare >/dev/null 2>&1 || true
414
+ fi
415
+
416
+ echo "$next"