@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.
- package/CHANGELOG.md +65 -0
- package/commands/harness-solo.md +20 -20
- package/commands/harness-team.md +1 -1
- package/gotchas/coo-developer.md +13 -0
- package/gotchas/documentationer.md +13 -0
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +416 -0
- package/scripts/harness-archive.sh +123 -0
- package/scripts/harness-dashboard-up.sh +72 -0
- package/scripts/harness-dashboard.sh +509 -0
- package/scripts/harness-goal-init.sh +72 -0
- package/scripts/harness-goal-show.sh +37 -0
- package/scripts/harness-gotcha-memory.sh +348 -0
- package/scripts/harness-gotcha-register.sh +393 -0
- package/scripts/harness-meeting-doc.sh +153 -0
- package/scripts/harness-monitor.sh +398 -0
- package/scripts/harness-next.sh +577 -0
- package/scripts/harness-progress-set.sh +41 -0
- package/scripts/harness-prompt-history.sh +164 -0
- package/scripts/harness-queue-manager.sh +694 -0
- package/scripts/harness-session-start.sh +140 -0
- package/scripts/harness-statusline.sh +139 -0
- package/scripts/harness-task-session.sh +48 -0
- package/scripts/harness-tmux.sh +372 -0
- package/scripts/harness-user-prompt-submit.sh +123 -0
- package/scripts/init-agents-md.sh +337 -0
- package/scripts/init-ref-docs.sh +262 -0
- package/scripts/lib/harness-audit.sh +113 -0
- package/scripts/lib/harness-guardrail.sh +148 -0
- package/scripts/lib/harness-keywait.sh +57 -0
- package/scripts/lib/harness-render-progress.sh +346 -0
- package/scripts/scan-project.sh +367 -0
- package/skills/brainstorming/SKILL.md +4 -4
- package/skills/conductor/SKILL.md +19 -16
- package/skills/coo-developer/SKILL.md +42 -0
- package/skills/cqo/SKILL.md +7 -0
- package/skills/cto/SKILL.md +7 -0
- package/skills/dispatcher/SKILL.md +17 -28
- package/skills/dispatcher/persona-ceo.md +2 -1
- package/skills/documentationer/SKILL.md +46 -0
- package/skills/evaluator-code-quality/SKILL.md +1 -1
- package/skills/evaluator-functional/SKILL.md +1 -1
- package/skills/evaluator-visual/SKILL.md +1 -1
- package/skills/generator-backend/SKILL.md +1 -1
- package/skills/generator-frontend/SKILL.md +1 -1
- package/skills/meeting-manager/SKILL.md +34 -1
- package/skills/planner/SKILL.md +23 -9
- package/skills/planner/persona-coo-hr.md +17 -0
- 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
|
package/commands/harness-solo.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
docmeta:
|
|
3
3
|
id: harness-solo
|
|
4
|
-
title: /harness-solo —
|
|
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 —
|
|
27
|
+
# /harness-solo — 비상용 Single-Agent Fallback (v6.0+)
|
|
28
28
|
|
|
29
|
-
> v6.0
|
|
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
|
-
|
|
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"** →
|
|
79
|
-
- **next_agent = "generator-backend"** →
|
|
80
|
-
- **next_agent = "generator-frontend"** →
|
|
81
|
-
- **next_agent = "evaluator-functional"** →
|
|
82
|
-
- **next_agent = "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
|
-
각 에이전트는 완료 즉시
|
|
86
|
+
각 에이전트는 완료 즉시 **내부 handoff(`harness-next.sh` 또는 SessionStart hook)** 로 다음 에이전트에 넘겨집니다. Owner가 `/harness-next` 를 직접 다룬다는 전제는 6.0 회사 모델과 맞지 않습니다.
|
|
87
87
|
|
|
88
88
|
**예외 — 사용자 승인 게이트가 있는 단계:**
|
|
89
|
-
- **Brainstorming**: brainstorm-spec.md 완성 후 사용자 승인 대기 → 승인 시
|
|
90
|
-
- **Planner**: plan.md / api-contract.json 완성 후 사용자 승인 대기 → 승인 시
|
|
89
|
+
- **Brainstorming**: brainstorm-spec.md 완성 후 사용자 승인 대기 → 승인 시 내부 handoff
|
|
90
|
+
- **Planner**: plan.md / api-contract.json 완성 후 사용자 승인 대기 → 승인 시 내부 handoff
|
|
91
91
|
|
|
92
|
-
흐름이
|
|
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
|
-
##
|
|
98
|
+
## 사용 권장 상황
|
|
99
99
|
|
|
100
100
|
- 프롬프트로 각 에이전트 순차 실행
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
101
|
+
- 내부 handoff 스크립트 장애 시 응급 복구
|
|
102
|
+
- 아주 짧은 수동 hotfix / 상태 수선
|
|
103
|
+
- 정상 회사 루프로 복귀: `/harness-team`
|
package/commands/harness-team.md
CHANGED
|
@@ -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.
|
|
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"
|