@walwal-harness/cli 6.1.4 → 6.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +43 -74
- package/assets/launchd/com.walwal.harness-wake.plist.template +32 -0
- package/assets/templates/CONVENTIONS.md +12 -0
- package/assets/templates/HARNESS.md +8 -8
- package/assets/templates/config.json +25 -27
- package/assets/templates/memory.md +16 -2
- package/assets/templates/progress.json.template +6 -5
- package/bin/init.js +179 -90
- package/conventions/shared.md +16 -0
- package/gotchas/conductor.md +16 -0
- package/gotchas/dispatcher.md +16 -0
- package/gotchas/meeting-manager.md +8 -0
- package/gotchas/service-ops.md +8 -0
- package/package.json +4 -4
- package/scripts/conductor-tick.sh +61 -53
- package/scripts/harness-archive.sh +7 -5
- package/scripts/harness-dashboard-up.sh +11 -3
- package/scripts/harness-hourly-review.sh +303 -0
- package/scripts/harness-meeting-doc.sh +55 -16
- package/scripts/harness-next.sh +1 -1
- package/scripts/harness-queue-manager.sh +12 -5
- package/scripts/harness-service-ops-monitor.sh +228 -0
- package/scripts/harness-session-start.sh +60 -31
- package/scripts/harness-statusline.sh +8 -12
- package/scripts/harness-stop.sh +85 -0
- package/scripts/harness-user-prompt-submit.sh +11 -29
- package/scripts/harness-wake-install.sh +179 -0
- package/scripts/harness-wake.sh +258 -0
- package/scripts/harness-worker-dispatch.sh +157 -0
- package/scripts/lib/harness-progress-migrate.sh +6 -1
- package/scripts/lib/harness-render-progress.sh +3 -3
- package/skills/brainstorming/SKILL.md +2 -2
- package/skills/conductor/SKILL.md +67 -55
- package/skills/cqo/SKILL.md +1 -0
- package/skills/cto/SKILL.md +1 -0
- package/skills/dispatcher/SKILL.md +33 -14
- package/skills/evaluator-code-quality/SKILL.md +4 -4
- package/skills/evaluator-functional/SKILL.md +6 -6
- package/skills/evaluator-visual/SKILL.md +4 -4
- package/skills/generator-backend/SKILL.md +4 -4
- package/skills/generator-frontend/SKILL.md +5 -5
- package/skills/meeting-manager/SKILL.md +82 -12
- package/skills/planner/SKILL.md +3 -3
- package/skills/service-ops/SKILL.md +25 -0
- package/commands/harness-solo.md +0 -103
- package/commands/harness-stop.md +0 -53
- package/commands/harness-team.md +0 -530
- package/scripts/harness-dashboard.sh +0 -509
- package/scripts/harness-goal-init.sh +0 -72
- package/scripts/harness-goal-show.sh +0 -37
- package/scripts/harness-gotcha-memory.sh +0 -348
- package/scripts/harness-monitor.sh +0 -398
- package/scripts/harness-prompt-history.sh +0 -164
- package/scripts/harness-tmux.sh +0 -372
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# harness-user-prompt-submit.sh — UserPromptSubmit hook (v5 unified)
|
|
3
|
-
# 핵심 상태 + 라우팅 지시만 주입. 회사
|
|
3
|
+
# 핵심 상태 + 라우팅 지시만 주입. 회사 루프는 항상 병렬/자율 진행.
|
|
4
4
|
set -e
|
|
5
5
|
|
|
6
6
|
INPUT=$(cat)
|
|
@@ -102,41 +102,23 @@ EOF
|
|
|
102
102
|
exit 0
|
|
103
103
|
fi
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if [ -f "$
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if [ "$MODE" = "team" ]; then
|
|
112
|
-
FEATURE_QUEUE="$CWD/.harness/actions/feature-queue.json"
|
|
113
|
-
T_PASSED=0; T_TOTAL=0; T_FAILED=0
|
|
114
|
-
if [ -f "$FEATURE_QUEUE" ]; then
|
|
115
|
-
T_PASSED=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
116
|
-
T_TOTAL=$(jq '[.queue.ready, (.queue.blocked | keys), (.queue.in_progress | keys), .queue.passed, .queue.failed] | flatten | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
117
|
-
T_FAILED=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
118
|
-
fi
|
|
119
|
-
|
|
120
|
-
cat <<EOF
|
|
121
|
-
[harness] team | ${T_PASSED}/${T_TOTAL} passed | ${T_FAILED} failed
|
|
122
|
-
|
|
123
|
-
## Team Mode Active
|
|
124
|
-
- worker pool(기본 3)이 자율적으로 Gen→Eval 루프 실행 중 (max 5 retries)
|
|
125
|
-
- CEO/Meeting/CTO/CQO/Service-Ops control-plane은 이 상한에 포함되지 않음
|
|
126
|
-
- 역할: 모니터링, 실패 대응, Goal 점검
|
|
127
|
-
- /harness-stop → 중단 | /harness-solo → 비상용 단일 에이전트 fallback
|
|
128
|
-
- skip: "harness skip" 시 일반 대화
|
|
129
|
-
EOF
|
|
130
|
-
exit 0
|
|
105
|
+
FEATURE_QUEUE="$CWD/.harness/actions/feature-queue.json"
|
|
106
|
+
T_PASSED=0; T_TOTAL=0; T_FAILED=0
|
|
107
|
+
if [ -f "$FEATURE_QUEUE" ]; then
|
|
108
|
+
T_PASSED=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
109
|
+
T_TOTAL=$(jq '[.queue.ready, (.queue.blocked | keys), (.queue.in_progress | keys), .queue.passed, .queue.failed] | flatten | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
110
|
+
T_FAILED=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
131
111
|
fi
|
|
132
112
|
|
|
133
|
-
# ── Compact context 주입 (solo/paused mode) ──
|
|
134
113
|
cat <<EOF
|
|
135
|
-
[harness] S${SPRINT_NUM} | ${PIPELINE} | agent=${CURRENT_AGENT} (${AGENT_STATUS}) | next=${NEXT_AGENT}
|
|
114
|
+
[harness] company | S${SPRINT_NUM} | ${PIPELINE} | agent=${CURRENT_AGENT} (${AGENT_STATUS}) | next=${NEXT_AGENT} | queue=${T_PASSED}/${T_TOTAL} passed | failed=${T_FAILED}
|
|
136
115
|
${CONTEXT_WARNING}
|
|
137
116
|
## Route
|
|
138
117
|
- pipeline=none/dispatcher 미실행 → harness-dispatcher 스킬 호출
|
|
139
118
|
- 기본 경로는 conductor 중심 회사 루프다: dispatch -> CEO meeting -> planner(COO) -> cto -> gen/eval -> cqo -> service-ops -> batch meeting
|
|
119
|
+
- worker pool(기본 3)이 자율적으로 Gen→Eval 루프 실행 중이며 control-plane은 이 상한에 포함되지 않는다
|
|
120
|
+
- Owner에게 "계속 진행", "진행할까요?", "다음 명령을 입력하세요"를 요구하지 않는다. GOAL이 있고 escalation이 아니면 즉시 next_agent/current_agent 업무를 수행한다
|
|
121
|
+
- Owner 입력은 목표 변경·사고·결과 확인용이지 회사 진행을 펌프하는 신호가 아니다
|
|
140
122
|
- conductor 는 Planner 직행 대신 meeting-manager / cto / cqo / service-ops 로 next_agent를 재작성할 수 있다
|
|
141
123
|
- 기능 요청 → pipeline flow | 실수 지적 → gotcha flow | 메타 질문 → 짧게 응답 (skip)
|
|
142
124
|
- 활성 pipeline → next_agent/current_agent 컨텍스트로 계속
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-wake-install.sh — launchd hourly wake 설치/관리
|
|
3
|
+
#
|
|
4
|
+
# 사용법:
|
|
5
|
+
# bash scripts/harness-wake-install.sh install [project-root...]
|
|
6
|
+
# bash scripts/harness-wake-install.sh add <project-root>
|
|
7
|
+
# bash scripts/harness-wake-install.sh remove <project-root>
|
|
8
|
+
# bash scripts/harness-wake-install.sh list
|
|
9
|
+
# bash scripts/harness-wake-install.sh status
|
|
10
|
+
# bash scripts/harness-wake-install.sh uninstall
|
|
11
|
+
#
|
|
12
|
+
# install: 등록된 프로젝트를 합치고 launchd job 을 (재)로드. 추가 인자가 있으면 그 경로들을 새로 추가.
|
|
13
|
+
# add/remove: ~/.walwal-harness/projects.list 만 수정.
|
|
14
|
+
# list: 현재 등록 목록 출력.
|
|
15
|
+
# status: launchd 상태 확인.
|
|
16
|
+
# uninstall: plist 언로드 + 제거.
|
|
17
|
+
|
|
18
|
+
set -uo pipefail
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
21
|
+
WAKE_SH="$SCRIPT_DIR/harness-wake.sh"
|
|
22
|
+
HARNESS_DIR="$HOME/.walwal-harness"
|
|
23
|
+
PROJECTS_LIST="$HARNESS_DIR/projects.list"
|
|
24
|
+
LOG_DIR="$HARNESS_DIR/logs"
|
|
25
|
+
LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
|
|
26
|
+
PLIST_NAME="com.walwal.harness-wake"
|
|
27
|
+
PLIST_PATH="$LAUNCH_AGENTS_DIR/$PLIST_NAME.plist"
|
|
28
|
+
|
|
29
|
+
# 템플릿 위치 후보 — walwal-harness 패키지 안일 수도, 프로젝트 안일 수도.
|
|
30
|
+
detect_template() {
|
|
31
|
+
local candidates=(
|
|
32
|
+
"$SCRIPT_DIR/../assets/launchd/com.walwal.harness-wake.plist.template"
|
|
33
|
+
"$HOME/project/walwal-harness/assets/launchd/com.walwal.harness-wake.plist.template"
|
|
34
|
+
)
|
|
35
|
+
for c in "${candidates[@]}"; do
|
|
36
|
+
[ -f "$c" ] && { echo "$c"; return 0; }
|
|
37
|
+
done
|
|
38
|
+
return 1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ensure_dirs() {
|
|
42
|
+
mkdir -p "$HARNESS_DIR" "$LOG_DIR" "$LAUNCH_AGENTS_DIR"
|
|
43
|
+
touch "$PROJECTS_LIST"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
abs_path() {
|
|
47
|
+
local p="$1"
|
|
48
|
+
if [ -d "$p" ]; then
|
|
49
|
+
(cd "$p" && pwd)
|
|
50
|
+
else
|
|
51
|
+
echo "$p"
|
|
52
|
+
fi
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
cmd_add() {
|
|
56
|
+
ensure_dirs
|
|
57
|
+
local project
|
|
58
|
+
project=$(abs_path "$1")
|
|
59
|
+
if grep -Fxq "$project" "$PROJECTS_LIST" 2>/dev/null; then
|
|
60
|
+
echo "[wake] already registered: $project"
|
|
61
|
+
return 0
|
|
62
|
+
fi
|
|
63
|
+
echo "$project" >> "$PROJECTS_LIST"
|
|
64
|
+
echo "[wake] added: $project"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
cmd_remove() {
|
|
68
|
+
ensure_dirs
|
|
69
|
+
local project
|
|
70
|
+
project=$(abs_path "$1")
|
|
71
|
+
if ! grep -Fxq "$project" "$PROJECTS_LIST" 2>/dev/null; then
|
|
72
|
+
echo "[wake] not registered: $project"
|
|
73
|
+
return 0
|
|
74
|
+
fi
|
|
75
|
+
local tmp
|
|
76
|
+
tmp=$(mktemp)
|
|
77
|
+
grep -Fxv "$project" "$PROJECTS_LIST" > "$tmp" || true
|
|
78
|
+
mv "$tmp" "$PROJECTS_LIST"
|
|
79
|
+
echo "[wake] removed: $project"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
cmd_list() {
|
|
83
|
+
ensure_dirs
|
|
84
|
+
if [ ! -s "$PROJECTS_LIST" ]; then
|
|
85
|
+
echo "(empty)"
|
|
86
|
+
return 0
|
|
87
|
+
fi
|
|
88
|
+
cat "$PROJECTS_LIST"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
cmd_status() {
|
|
92
|
+
if [ -f "$PLIST_PATH" ]; then
|
|
93
|
+
echo "[wake] plist: $PLIST_PATH (exists)"
|
|
94
|
+
else
|
|
95
|
+
echo "[wake] plist: $PLIST_PATH (NOT installed)"
|
|
96
|
+
fi
|
|
97
|
+
if launchctl list 2>/dev/null | grep -q "$PLIST_NAME"; then
|
|
98
|
+
echo "[wake] launchd: loaded"
|
|
99
|
+
launchctl list | awk -v n="$PLIST_NAME" '$3==n {print " pid="$1" exit="$2" label="$3}'
|
|
100
|
+
else
|
|
101
|
+
echo "[wake] launchd: NOT loaded"
|
|
102
|
+
fi
|
|
103
|
+
echo "[wake] projects:"
|
|
104
|
+
cmd_list | sed 's/^/ /'
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
cmd_install() {
|
|
108
|
+
ensure_dirs
|
|
109
|
+
|
|
110
|
+
# 추가 인자가 있으면 add
|
|
111
|
+
shift_count=0
|
|
112
|
+
for p in "$@"; do
|
|
113
|
+
cmd_add "$p"
|
|
114
|
+
done
|
|
115
|
+
|
|
116
|
+
local template
|
|
117
|
+
template=$(detect_template) || {
|
|
118
|
+
echo "[wake] ERROR: plist template not found" >&2
|
|
119
|
+
return 1
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# plist 생성
|
|
123
|
+
sed \
|
|
124
|
+
-e "s|{{WAKE_SCRIPT}}|$WAKE_SH|g" \
|
|
125
|
+
-e "s|{{LOG_DIR}}|$LOG_DIR|g" \
|
|
126
|
+
"$template" > "$PLIST_PATH"
|
|
127
|
+
|
|
128
|
+
# 기존 jobs 언로드 (idempotent)
|
|
129
|
+
launchctl unload "$PLIST_PATH" 2>/dev/null || true
|
|
130
|
+
|
|
131
|
+
# 로드
|
|
132
|
+
if launchctl load "$PLIST_PATH" 2>/dev/null; then
|
|
133
|
+
echo "[wake] launchd loaded: $PLIST_PATH"
|
|
134
|
+
else
|
|
135
|
+
echo "[wake] WARN: launchctl load 실패. 다음 명령으로 직접 시도해 보세요:" >&2
|
|
136
|
+
echo " launchctl load $PLIST_PATH" >&2
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
cmd_status
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
cmd_uninstall() {
|
|
143
|
+
if [ -f "$PLIST_PATH" ]; then
|
|
144
|
+
launchctl unload "$PLIST_PATH" 2>/dev/null || true
|
|
145
|
+
rm -f "$PLIST_PATH"
|
|
146
|
+
echo "[wake] uninstalled: $PLIST_PATH"
|
|
147
|
+
else
|
|
148
|
+
echo "[wake] not installed"
|
|
149
|
+
fi
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
cmd_run_now() {
|
|
153
|
+
bash "$WAKE_SH"
|
|
154
|
+
echo "[wake] run-now done. log:"
|
|
155
|
+
tail -20 "$LOG_DIR/wake.log" 2>/dev/null || true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case "${1:-}" in
|
|
159
|
+
install) shift; cmd_install "$@" ;;
|
|
160
|
+
add) shift; cmd_add "${1:-$PWD}" ;;
|
|
161
|
+
remove) shift; cmd_remove "${1:-$PWD}" ;;
|
|
162
|
+
list) cmd_list ;;
|
|
163
|
+
status) cmd_status ;;
|
|
164
|
+
uninstall) cmd_uninstall ;;
|
|
165
|
+
run-now) cmd_run_now ;;
|
|
166
|
+
*)
|
|
167
|
+
cat <<USAGE
|
|
168
|
+
harness-wake-install.sh — launchd hourly wake 관리
|
|
169
|
+
|
|
170
|
+
install [project-root...] plist 생성 + 로드 (+ 프로젝트 등록)
|
|
171
|
+
add <project-root> 프로젝트 등록만
|
|
172
|
+
remove <project-root> 프로젝트 등록 해제
|
|
173
|
+
list 등록된 프로젝트 출력
|
|
174
|
+
status launchd / plist / 등록 상태
|
|
175
|
+
run-now 지금 즉시 wake 실행 (테스트)
|
|
176
|
+
uninstall plist 언로드 + 제거
|
|
177
|
+
USAGE
|
|
178
|
+
;;
|
|
179
|
+
esac
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-wake.sh — hourly safety wake (v6.2)
|
|
3
|
+
#
|
|
4
|
+
# launchd 가 1시간 주기로 호출. 등록된 모든 walwal-harness 프로젝트에 대해:
|
|
5
|
+
# 1) idle 한 시간이 임계치 이상이면 (.harness/progress.json mtime 기준, review 실행 전 캡처)
|
|
6
|
+
# 2) deterministic hourly review 를 디스크에 남김
|
|
7
|
+
# 3) 기본값으로 기존 tmux 세션에 긴 prompt 를 주입하지 않고 headless 1-tick Claude 를 실행
|
|
8
|
+
#
|
|
9
|
+
# 등록 방식: ~/.walwal-harness/projects.list 에 한 줄에 하나씩 절대경로 기재.
|
|
10
|
+
# 또는 환경변수 WALWAL_HARNESS_PROJECTS 에 콜론(:)으로 구분된 절대경로 목록.
|
|
11
|
+
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
|
|
14
|
+
LOG_DIR="${HOME}/.walwal-harness/logs"
|
|
15
|
+
mkdir -p "$LOG_DIR"
|
|
16
|
+
LOG="$LOG_DIR/wake.log"
|
|
17
|
+
|
|
18
|
+
ts() { date "+%Y-%m-%d %H:%M:%S"; }
|
|
19
|
+
say() { echo "[$(ts)] $*" >> "$LOG"; }
|
|
20
|
+
|
|
21
|
+
stat_mtime() {
|
|
22
|
+
local path="$1"
|
|
23
|
+
stat -f "%m" "$path" 2>/dev/null || stat -c "%Y" "$path" 2>/dev/null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
in_force_wake_window() {
|
|
27
|
+
local hhmm start end
|
|
28
|
+
hhmm="$(date "+%H%M")"
|
|
29
|
+
start="${HARNESS_WAKE_FORCE_START_HHMM:-0555}"
|
|
30
|
+
end="${HARNESS_WAKE_FORCE_END_HHMM:-0615}"
|
|
31
|
+
|
|
32
|
+
# 기본값은 Anthropic 5-hour reset 이 자주 걸리는 06:00 KST 주변.
|
|
33
|
+
# StartInterval=3600 이 몇 분 밀려 실행돼도 복구되도록 20분 창을 둔다.
|
|
34
|
+
[ "$((10#$hhmm))" -ge "$((10#$start))" ] && [ "$((10#$hhmm))" -le "$((10#$end))" ]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
find_tmux_session() {
|
|
38
|
+
local project_root="$1"
|
|
39
|
+
local base sanitized upper var cand
|
|
40
|
+
|
|
41
|
+
base=$(basename "$project_root")
|
|
42
|
+
sanitized=$(echo "$base" | tr -- '-. ' '___')
|
|
43
|
+
for cand in "claude_${sanitized}_" "claude_${base}_"; do
|
|
44
|
+
if tmux has-session -t "$cand" 2>/dev/null; then
|
|
45
|
+
echo "$cand"
|
|
46
|
+
return 0
|
|
47
|
+
fi
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
upper=$(echo "$sanitized" | tr 'a-z' 'A-Z')
|
|
51
|
+
var="WALWAL_HARNESS_TMUX_${upper}"
|
|
52
|
+
if [ -n "${!var:-}" ] && tmux has-session -t "${!var}" 2>/dev/null; then
|
|
53
|
+
echo "${!var}"
|
|
54
|
+
return 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
return 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
start_headless_tick() {
|
|
61
|
+
local project_root="$1"
|
|
62
|
+
local idle="$2"
|
|
63
|
+
local force="$3"
|
|
64
|
+
local review_path="$4"
|
|
65
|
+
local mode stamp wake_dir ops_dir prompt_rel log_rel prompt_path log_path pid_file old_pid pid status tmux_session
|
|
66
|
+
|
|
67
|
+
mode="${HARNESS_WAKE_MODE:-headless}"
|
|
68
|
+
stamp="$(date -u "+%Y%m%dT%H%M%SZ")"
|
|
69
|
+
wake_dir="$project_root/.harness/actions/wake"
|
|
70
|
+
ops_dir="$project_root/.harness/ops/wake"
|
|
71
|
+
mkdir -p "$wake_dir" "$ops_dir"
|
|
72
|
+
|
|
73
|
+
prompt_rel=".harness/actions/wake/wake-${stamp}.prompt.md"
|
|
74
|
+
log_rel=".harness/ops/wake/wake-${stamp}.log"
|
|
75
|
+
prompt_path="$project_root/$prompt_rel"
|
|
76
|
+
log_path="$project_root/$log_rel"
|
|
77
|
+
pid_file="$ops_dir/wake.pid"
|
|
78
|
+
|
|
79
|
+
if [ -f "$pid_file" ]; then
|
|
80
|
+
old_pid="$(cat "$pid_file" 2>/dev/null || true)"
|
|
81
|
+
if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
|
|
82
|
+
say "skip-headless: $project_root (previous wake pid=$old_pid still running)"
|
|
83
|
+
return 0
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
cat > "$prompt_path" <<EOF
|
|
88
|
+
You are the walwal-harness autonomous hourly wake tick.
|
|
89
|
+
|
|
90
|
+
Project root: $project_root
|
|
91
|
+
Wake reason: idle=${idle}s force=${force}
|
|
92
|
+
Hourly review: ${review_path:-"(not available)"}
|
|
93
|
+
|
|
94
|
+
Policy:
|
|
95
|
+
- Owner input after the initial GOAL is an interrupt or additional request, never a reason to wait.
|
|
96
|
+
- Never set conductor.state to waiting_owner. Normalize any waiting_owner state to waiting_meeting and continue.
|
|
97
|
+
- meeting-manager is the default synchronization loop. Incidents, service warnings, goal drift, and escalations go to meeting-manager first.
|
|
98
|
+
- Keep this to one bounded autonomous tick. Do not run an endless loop.
|
|
99
|
+
- Do not write future timestamps to .harness/progress.log or artifacts.
|
|
100
|
+
- Keep terminal output concise; write evidence to project files.
|
|
101
|
+
|
|
102
|
+
Tasks:
|
|
103
|
+
1. Read CONVENTIONS.md if present, .harness/conventions/shared.md, relevant .harness/conventions/<role>.md, .harness/gotchas/<role>.md, .harness/memory.md, .harness/progress.json, and the hourly review above.
|
|
104
|
+
2. If service_ops.requested_mode is monitor, run or trigger one service-ops monitor pass and record last_check. stream_active may be true only during the pass and must be false when finished.
|
|
105
|
+
3. Treat the hourly review as Executive Meeting Minutes. Verify it contains CEO/COO/CTO/CQO/Service-Ops role positions, discussion, decision JSON, and action items.
|
|
106
|
+
4. Check conductor state against the current GOAL and route the next department without waiting for Owner.
|
|
107
|
+
5. If there is an active incident or operational warning, make sure meeting-manager has shared context and an action decision that names CTO/CQO/Service-Ops responsibilities.
|
|
108
|
+
6. Append a short factual summary to .harness/progress.log and any normal harness artifacts used by this project.
|
|
109
|
+
EOF
|
|
110
|
+
|
|
111
|
+
case "$mode" in
|
|
112
|
+
record)
|
|
113
|
+
status="recorded"
|
|
114
|
+
;;
|
|
115
|
+
headless|"")
|
|
116
|
+
if command -v claude >/dev/null 2>&1; then
|
|
117
|
+
(
|
|
118
|
+
cd "$project_root" || exit 1
|
|
119
|
+
claude -p "$(cat "$prompt_path")" > "$log_path" 2>&1
|
|
120
|
+
) &
|
|
121
|
+
pid=$!
|
|
122
|
+
echo "$pid" > "$pid_file"
|
|
123
|
+
status="spawned pid=$pid"
|
|
124
|
+
else
|
|
125
|
+
status="recorded claude-not-found"
|
|
126
|
+
fi
|
|
127
|
+
;;
|
|
128
|
+
*)
|
|
129
|
+
status="recorded unsupported-mode=$mode"
|
|
130
|
+
;;
|
|
131
|
+
esac
|
|
132
|
+
|
|
133
|
+
echo "$(date -u "+%Y-%m-%dT%H:%M:%SZ") | wake | headless | $status | idle=${idle}s | force=${force} | prompt=$prompt_rel | log=$log_rel" >> "$project_root/.harness/progress.log"
|
|
134
|
+
say "wake-headless: $project_root (idle ${idle}s, force=${force}, mode=${mode}) → $status $log_rel"
|
|
135
|
+
|
|
136
|
+
if tmux_session="$(find_tmux_session "$project_root" 2>/dev/null)"; then
|
|
137
|
+
tmux display-message -t "$tmux_session" "walwal wake headless tick: $status ($log_rel)" 2>/dev/null || true
|
|
138
|
+
fi
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
PROJECTS_LIST="${HOME}/.walwal-harness/projects.list"
|
|
142
|
+
PROJECTS=()
|
|
143
|
+
|
|
144
|
+
if [ -n "${WALWAL_HARNESS_PROJECTS:-}" ]; then
|
|
145
|
+
IFS=':' read -r -a PROJECTS <<< "$WALWAL_HARNESS_PROJECTS"
|
|
146
|
+
elif [ -f "$PROJECTS_LIST" ]; then
|
|
147
|
+
while IFS= read -r line; do
|
|
148
|
+
line="${line%%#*}"
|
|
149
|
+
line="$(echo "$line" | xargs)"
|
|
150
|
+
[ -z "$line" ] && continue
|
|
151
|
+
PROJECTS+=("$line")
|
|
152
|
+
done < "$PROJECTS_LIST"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
if [ "${#PROJECTS[@]}" -eq 0 ]; then
|
|
156
|
+
say "no registered projects (set ~/.walwal-harness/projects.list or WALWAL_HARNESS_PROJECTS)"
|
|
157
|
+
exit 0
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# 임계치: idle 60분 이상이면 깨운다 (Stop 훅이 정상 동작하면 거의 연속 활동이므로
|
|
161
|
+
# 1시간 이상 정지 = 어디선가 멈춘 상태로 판단)
|
|
162
|
+
IDLE_THRESHOLD_SECONDS="${HARNESS_WAKE_IDLE_SECONDS:-3300}" # 55분 (안전 마진)
|
|
163
|
+
NOW_EPOCH=$(date "+%s")
|
|
164
|
+
FORCE_WAKE=0
|
|
165
|
+
if in_force_wake_window; then
|
|
166
|
+
FORCE_WAKE=1
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
for PROJECT_ROOT in "${PROJECTS[@]}"; do
|
|
170
|
+
if [ ! -d "$PROJECT_ROOT" ]; then
|
|
171
|
+
say "skip: $PROJECT_ROOT (not a directory)"
|
|
172
|
+
continue
|
|
173
|
+
fi
|
|
174
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
175
|
+
if [ ! -f "$PROGRESS" ]; then
|
|
176
|
+
say "skip: $PROJECT_ROOT (no .harness/progress.json)"
|
|
177
|
+
continue
|
|
178
|
+
fi
|
|
179
|
+
PROJECT_FORCE_WAKE="$FORCE_WAKE"
|
|
180
|
+
|
|
181
|
+
if command -v jq >/dev/null 2>&1 && [ "$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null || echo idle)" = "waiting_owner" ]; then
|
|
182
|
+
if [ -x "$PROJECT_ROOT/scripts/harness-progress-set.sh" ]; then
|
|
183
|
+
bash "$PROJECT_ROOT/scripts/harness-progress-set.sh" "$PROJECT_ROOT" \
|
|
184
|
+
'.conductor.state = "waiting_meeting" |
|
|
185
|
+
.conductor.current_action = "autonomous-normalize-waiting-owner" |
|
|
186
|
+
.next_agent = "meeting-manager" |
|
|
187
|
+
.agent_status = "pending" |
|
|
188
|
+
.meetings.active = ((.meetings.active // []) + ["meeting-manager"] | unique) |
|
|
189
|
+
.meetings.requested_type = (.meetings.requested_type // "followup-review") |
|
|
190
|
+
.meetings.requested_reason = (.meetings.requested_reason // "autonomous-normalize-waiting-owner") |
|
|
191
|
+
.workflow.stage = "ops-monitoring" |
|
|
192
|
+
.workflow.last_transition = (now | todate) |
|
|
193
|
+
.workflow.last_reason = "autonomous-normalize-waiting-owner"' \
|
|
194
|
+
>/dev/null 2>&1 || true
|
|
195
|
+
say "normalize: $PROJECT_ROOT (conductor=waiting_owner → waiting_meeting)"
|
|
196
|
+
PROJECT_FORCE_WAKE=1
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# idle 시간은 hourly-review 실행 전에 캡처한다. review 는 progress.json 과
|
|
201
|
+
# progress.log 를 갱신하므로, 이후 mtime 을 보면 wake 가 자기 활동을
|
|
202
|
+
# "프로젝트 활동 중"으로 오판한다.
|
|
203
|
+
if MTIME=$(stat_mtime "$PROGRESS"); then :;
|
|
204
|
+
else
|
|
205
|
+
say "skip: $PROJECT_ROOT (cannot stat progress.json)"
|
|
206
|
+
continue
|
|
207
|
+
fi
|
|
208
|
+
IDLE=$((NOW_EPOCH - MTIME))
|
|
209
|
+
|
|
210
|
+
# Every hourly batch must leave disk-backed evidence, even if Claude/tmux is
|
|
211
|
+
# stopped or busy. This deterministic review runs after the idle snapshot.
|
|
212
|
+
if [ -x "$PROJECT_ROOT/scripts/harness-hourly-review.sh" ]; then
|
|
213
|
+
review_output=$(bash "$PROJECT_ROOT/scripts/harness-hourly-review.sh" "$PROJECT_ROOT" 2>/dev/null || true)
|
|
214
|
+
review_path=$(printf "%s\n" "$review_output" | grep -Eo '(/[^[:space:]]+|[.][^[:space:]]+)\.md' | tail -1)
|
|
215
|
+
if [ -z "$review_path" ]; then
|
|
216
|
+
review_path=$(printf "%s\n" "$review_output" | tail -1)
|
|
217
|
+
fi
|
|
218
|
+
if [ -n "$review_path" ]; then
|
|
219
|
+
say "review: $PROJECT_ROOT → $review_path"
|
|
220
|
+
else
|
|
221
|
+
say "review-fail: $PROJECT_ROOT"
|
|
222
|
+
fi
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
if [ "$IDLE" -lt "$IDLE_THRESHOLD_SECONDS" ] && [ "$PROJECT_FORCE_WAKE" -ne 1 ]; then
|
|
226
|
+
say "skip: $PROJECT_ROOT (idle ${IDLE}s < threshold ${IDLE_THRESHOLD_SECONDS}s — 활동 중)"
|
|
227
|
+
continue
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# 회사 루프가 멈춘 상태인지 확인
|
|
231
|
+
if command -v jq >/dev/null 2>&1; then
|
|
232
|
+
CSTATE=$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null || echo "idle")
|
|
233
|
+
SSTATUS=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null || echo "init")
|
|
234
|
+
if [ "$SSTATUS" = "completed" ] || [ "$SSTATUS" = "aborted" ]; then
|
|
235
|
+
say "continue: $PROJECT_ROOT (sprint=$SSTATUS — autonomous review still required)"
|
|
236
|
+
fi
|
|
237
|
+
if [ "$CSTATE" = "paused" ] || [ "$CSTATE" = "completed" ] || [ "$CSTATE" = "escalated" ]; then
|
|
238
|
+
say "continue: $PROJECT_ROOT (conductor=$CSTATE — route through meeting-manager/service-ops)"
|
|
239
|
+
fi
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# 발화 직전 service-ops monitor 트리거 partial update.
|
|
243
|
+
# requested_mode 만 set — conductor-tick 이 이걸 보고 service-ops 를 spawn 한다.
|
|
244
|
+
# stream_active 는 service-ops 본인이 spawn 시 §3.1.1 에 따라 직접 set (정직성 룰).
|
|
245
|
+
# auto-retro / incident / null / monitor 가 아닌 모드가 진행 중이면 덮어쓰지 않음.
|
|
246
|
+
if [ -x "$PROJECT_ROOT/scripts/harness-progress-set.sh" ]; then
|
|
247
|
+
bash "$PROJECT_ROOT/scripts/harness-progress-set.sh" "$PROJECT_ROOT" \
|
|
248
|
+
'if (.service_ops.requested_mode // "") == "" or (.service_ops.requested_mode // "") == "monitor" or (.service_ops.requested_mode // "") == "auto-retro"
|
|
249
|
+
then .service_ops.requested_mode = "monitor" |
|
|
250
|
+
.service_ops.monitor.wake_trigger_at = (now | todate)
|
|
251
|
+
else . end' \
|
|
252
|
+
>/dev/null 2>&1 || true
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
start_headless_tick "$PROJECT_ROOT" "$IDLE" "$PROJECT_FORCE_WAKE" "$review_path"
|
|
256
|
+
done
|
|
257
|
+
|
|
258
|
+
exit 0
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-worker-dispatch.sh — connect feature queue to actual worker launches
|
|
3
|
+
#
|
|
4
|
+
# Deterministic responsibilities:
|
|
5
|
+
# 1. auto-dispatch ready features to idle worker slots
|
|
6
|
+
# 2. create per-worker prompt + log paths
|
|
7
|
+
# 3. optionally launch `claude -p` workers in background
|
|
8
|
+
# 4. write progress.json.company_state so dashboards/CLI show real activity
|
|
9
|
+
|
|
10
|
+
set -uo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
13
|
+
source "$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
14
|
+
|
|
15
|
+
PROJECT_ROOT="$(resolve_harness_root "${1:-.}")" || exit 0
|
|
16
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
17
|
+
CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
18
|
+
FEATURES="$PROJECT_ROOT/.harness/actions/feature-list.json"
|
|
19
|
+
QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
20
|
+
WORKER_DIR="$PROJECT_ROOT/.harness/actions/workers"
|
|
21
|
+
LOG_DIR="$PROJECT_ROOT/.harness/ops/workers"
|
|
22
|
+
|
|
23
|
+
[ -f "$PROGRESS" ] || exit 0
|
|
24
|
+
[ -f "$CONFIG" ] || exit 0
|
|
25
|
+
[ -f "$FEATURES" ] || exit 0
|
|
26
|
+
[ -x "$SCRIPT_DIR/harness-queue-manager.sh" ] || exit 0
|
|
27
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
28
|
+
|
|
29
|
+
mkdir -p "$WORKER_DIR" "$LOG_DIR"
|
|
30
|
+
|
|
31
|
+
spawn_mode="$(jq -r '.company_mode.worker_spawn // "claude"' "$CONFIG" 2>/dev/null || echo claude)"
|
|
32
|
+
pipeline="$(jq -r '.pipeline // "FULLSTACK"' "$PROGRESS" 2>/dev/null || echo FULLSTACK)"
|
|
33
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
34
|
+
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
35
|
+
|
|
36
|
+
agent_for_feature() {
|
|
37
|
+
local fid="$1"
|
|
38
|
+
local layer service title
|
|
39
|
+
layer="$(jq -r --arg id "$fid" '.features[]? | select(.id == $id) | (.layer // .type // "")' "$FEATURES" 2>/dev/null | head -1)"
|
|
40
|
+
service="$(jq -r --arg id "$fid" '.features[]? | select(.id == $id) | (.service // "")' "$FEATURES" 2>/dev/null | head -1)"
|
|
41
|
+
title="$(jq -r --arg id "$fid" '.features[]? | select(.id == $id) | (.title // .name // .description // "")' "$FEATURES" 2>/dev/null | head -1)"
|
|
42
|
+
case "$(printf '%s %s %s' "$layer" "$service" "$title" | tr '[:upper:]' '[:lower:]')" in
|
|
43
|
+
*frontend*|*ui*|*web*|*react*|*next*) echo "generator-frontend" ;;
|
|
44
|
+
*design*|*token*|*component-spec*) echo "generator-designer" ;;
|
|
45
|
+
*devops*|*infra*|*deploy*|*ci*) echo "generator-devops" ;;
|
|
46
|
+
*)
|
|
47
|
+
if [ "$pipeline" = "FE-ONLY" ]; then echo "generator-frontend"; else echo "generator-backend"; fi
|
|
48
|
+
;;
|
|
49
|
+
esac
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
build_prompt() {
|
|
53
|
+
local team="$1" fid="$2" agent="$3" prompt_path="$4" log_path="$5"
|
|
54
|
+
cat > "$prompt_path" <<EOF
|
|
55
|
+
You are running as walwal-harness company worker.
|
|
56
|
+
|
|
57
|
+
Agent: harness-${agent}
|
|
58
|
+
Worker slot: ${team}
|
|
59
|
+
Feature ID: ${fid}
|
|
60
|
+
Project root: ${PROJECT_ROOT}
|
|
61
|
+
|
|
62
|
+
Follow these rules exactly:
|
|
63
|
+
1. Read AGENTS.md, CONVENTIONS.md if present, .harness/conventions/shared.md, .harness/conventions/${agent}.md if present, .harness/gotchas/${agent}.md if present, .harness/memory.md, .harness/progress.json, .harness/actions/feature-list.json, .harness/actions/api-contract.json, and the relevant harness skill for ${agent}.
|
|
64
|
+
2. Work only on FEATURE_ID=${fid}. Do not broaden scope.
|
|
65
|
+
3. Respect IA ownership and do not edit files outside your department.
|
|
66
|
+
4. Write a report to .harness/actions/gen-report-${fid}.md if you are a generator, or .harness/actions/evaluation-${fid}.md if you are an evaluator.
|
|
67
|
+
5. On success, update feature-list passes and run: bash scripts/harness-queue-manager.sh pass ${fid}
|
|
68
|
+
6. On failure, write the reason and run: bash scripts/harness-queue-manager.sh fail ${fid} "<short reason>"
|
|
69
|
+
7. Use partial progress updates only via scripts/harness-progress-set.sh.
|
|
70
|
+
8. Append real timestamped progress to .harness/progress.log. Never write future timestamps.
|
|
71
|
+
|
|
72
|
+
This worker log is: ${log_path#$PROJECT_ROOT/}
|
|
73
|
+
EOF
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if [ ! -f "$QUEUE" ]; then
|
|
77
|
+
bash "$SCRIPT_DIR/harness-queue-manager.sh" init all "$PROJECT_ROOT" >/dev/null 2>&1 || exit 0
|
|
78
|
+
else
|
|
79
|
+
bash "$SCRIPT_DIR/harness-queue-manager.sh" recover "$PROJECT_ROOT" >/dev/null 2>&1 || true
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
pairs="$(bash "$SCRIPT_DIR/harness-queue-manager.sh" auto-dispatch "$PROJECT_ROOT" 2>/dev/null || echo "[]")"
|
|
83
|
+
if ! jq -e 'type == "array"' >/dev/null 2>&1 <<<"$pairs"; then
|
|
84
|
+
pairs="[]"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
count="$(jq 'length' <<<"$pairs")"
|
|
88
|
+
if [ "$count" -eq 0 ]; then
|
|
89
|
+
active_workers="$(jq '[.teams | to_entries[] | select(.value.status == "busy") | {team:(.key|tonumber), feature:.value.feature, pid:.value.pid, phase:(.value.phase // "gen")}]' "$QUEUE" 2>/dev/null || echo "[]")"
|
|
90
|
+
jq --arg ts "$ts" --argjson workers "$active_workers" '
|
|
91
|
+
.company_state.active_workers = ($workers | length) |
|
|
92
|
+
.company_state.workers = $workers |
|
|
93
|
+
.company_state.last_dispatch_at = (.company_state.last_dispatch_at // null)
|
|
94
|
+
' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
95
|
+
echo "[]"
|
|
96
|
+
exit 0
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
dispatches_file="$(mktemp)"
|
|
100
|
+
: > "$dispatches_file"
|
|
101
|
+
|
|
102
|
+
i=0
|
|
103
|
+
while [ "$i" -lt "$count" ]; do
|
|
104
|
+
team="$(jq -r ".[$i].team" <<<"$pairs")"
|
|
105
|
+
fid="$(jq -r ".[$i].feature" <<<"$pairs")"
|
|
106
|
+
agent="$(agent_for_feature "$fid")"
|
|
107
|
+
prompt_rel=".harness/actions/workers/T${team}-${fid}-${stamp}.prompt.md"
|
|
108
|
+
log_rel=".harness/ops/workers/T${team}-${fid}-${stamp}.log"
|
|
109
|
+
prompt_path="$PROJECT_ROOT/$prompt_rel"
|
|
110
|
+
log_path="$PROJECT_ROOT/$log_rel"
|
|
111
|
+
|
|
112
|
+
build_prompt "$team" "$fid" "$agent" "$prompt_path" "$log_path"
|
|
113
|
+
|
|
114
|
+
pid="null"
|
|
115
|
+
status="recorded"
|
|
116
|
+
if [ "$spawn_mode" = "claude" ] && command -v claude >/dev/null 2>&1; then
|
|
117
|
+
(
|
|
118
|
+
cd "$PROJECT_ROOT" || exit 1
|
|
119
|
+
claude -p "$(cat "$prompt_path")" > "$log_path" 2>&1
|
|
120
|
+
) &
|
|
121
|
+
pid="$!"
|
|
122
|
+
status="spawned"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
tmpq="${QUEUE}.tmp.$$.$i"
|
|
126
|
+
jq --arg tid "$team" --arg agent "$agent" --arg prompt "$prompt_rel" --arg log "$log_rel" --arg status "$status" --argjson pid "$pid" --arg ts "$ts" '
|
|
127
|
+
.teams[$tid].pid = $pid |
|
|
128
|
+
.teams[$tid].agent = $agent |
|
|
129
|
+
.teams[$tid].prompt = $prompt |
|
|
130
|
+
.teams[$tid].log = $log |
|
|
131
|
+
.teams[$tid].spawn_status = $status |
|
|
132
|
+
.teams[$tid].started_at = $ts |
|
|
133
|
+
.queue.in_progress[.teams[$tid].feature].agent = $agent |
|
|
134
|
+
.queue.in_progress[.teams[$tid].feature].prompt = $prompt |
|
|
135
|
+
.queue.in_progress[.teams[$tid].feature].log = $log |
|
|
136
|
+
.queue.in_progress[.teams[$tid].feature].pid = $pid
|
|
137
|
+
' "$QUEUE" > "$tmpq" && mv "$tmpq" "$QUEUE"
|
|
138
|
+
|
|
139
|
+
jq -n --argjson team "$team" --arg feature "$fid" --arg agent "$agent" --arg prompt "$prompt_rel" --arg log "$log_rel" --arg status "$status" --argjson pid "$pid" \
|
|
140
|
+
'{team:$team, feature:$feature, agent:$agent, prompt:$prompt, log:$log, status:$status, pid:$pid}' >> "$dispatches_file"
|
|
141
|
+
echo "$ts | conductor | worker-dispatch | $status | T${team}/${fid} | $agent pid=$pid" >> "$PROJECT_ROOT/.harness/progress.log"
|
|
142
|
+
i=$((i + 1))
|
|
143
|
+
done
|
|
144
|
+
|
|
145
|
+
dispatches="$(jq -s '.' "$dispatches_file")"
|
|
146
|
+
rm -f "$dispatches_file"
|
|
147
|
+
active_workers="$(jq '[.teams | to_entries[] | select(.value.status == "busy") | {team:(.key|tonumber), feature:.value.feature, agent:.value.agent, pid:.value.pid, phase:(.value.phase // "gen"), prompt:.value.prompt, log:.value.log, spawn_status:.value.spawn_status}]' "$QUEUE")"
|
|
148
|
+
|
|
149
|
+
jq --arg ts "$ts" --argjson dispatches "$dispatches" --argjson workers "$active_workers" '
|
|
150
|
+
.company_state.active_workers = ($workers | length) |
|
|
151
|
+
.company_state.workers = $workers |
|
|
152
|
+
.company_state.last_dispatch_at = $ts |
|
|
153
|
+
.company_state.last_dispatch = $dispatches |
|
|
154
|
+
.conductor.worker_dispatches = ((.conductor.worker_dispatches // []) + $dispatches)
|
|
155
|
+
' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
|
|
156
|
+
|
|
157
|
+
echo "$dispatches"
|
|
@@ -19,7 +19,7 @@ migrate_progress_schema() {
|
|
|
19
19
|
|
|
20
20
|
# v6.2 — Parallel tracks (fork-join) fields. tracks.length >= 2 → fork.
|
|
21
21
|
# Note: there is intentionally NO `meetings.decision.mode` field — single/parallel is derived
|
|
22
|
-
# from tracks.length. Runtime progress.mode is
|
|
22
|
+
# from tracks.length. Runtime progress.mode is normalized to always-on company mode.
|
|
23
23
|
local filter='
|
|
24
24
|
.conductor.tracks = (.conductor.tracks // [])
|
|
25
25
|
| .conductor.rendezvous = (.conductor.rendezvous // null)
|
|
@@ -35,6 +35,11 @@ migrate_progress_schema() {
|
|
|
35
35
|
| .meetings.decision.rendezvous = (.meetings.decision.rendezvous // null)
|
|
36
36
|
| del(.meetings.decision.mode)
|
|
37
37
|
| del(.meetings.requested_mode)
|
|
38
|
+
| .mode = "company"
|
|
39
|
+
| .mode_decision.owner = "conductor"
|
|
40
|
+
| .mode_decision.policy = "always_company_parallel"
|
|
41
|
+
| .mode_decision.user_override = null
|
|
42
|
+
| .mode_decision.rationale = (.mode_decision.rationale // "company mode normalized by migration")
|
|
38
43
|
'
|
|
39
44
|
|
|
40
45
|
local tmp
|