@walwal-harness/cli 6.1.4 → 6.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -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/company-pipeline-manifest.json +71 -0
- package/assets/templates/config.json +29 -27
- package/assets/templates/memory.md +16 -2
- package/assets/templates/progress.json.template +6 -5
- package/bin/init.js +180 -90
- package/conventions/shared.md +34 -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 +157 -56
- package/scripts/harness-archive.sh +7 -5
- package/scripts/harness-dashboard-up.sh +11 -3
- package/scripts/harness-hourly-review.sh +582 -0
- package/scripts/harness-meeting-doc.sh +55 -16
- package/scripts/harness-next.sh +1 -1
- package/scripts/harness-parity-audit.sh +101 -0
- package/scripts/harness-queue-manager.sh +13 -6
- package/scripts/harness-service-ops-monitor.sh +273 -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 +180 -0
- package/scripts/harness-wake.sh +350 -0
- package/scripts/harness-worker-dispatch.sh +188 -0
- package/scripts/lib/harness-agent-resolver.sh +127 -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
|
@@ -32,7 +32,7 @@ sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
|
|
|
32
32
|
current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
33
33
|
next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
34
34
|
agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
|
|
35
|
-
mode=$(jq -r '.mode // "
|
|
35
|
+
mode=$(jq -r '.mode // "company"' "$PROGRESS" 2>/dev/null)
|
|
36
36
|
conductor_state=$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null)
|
|
37
37
|
task_stop_active=$(jq -r '.task_stop.active // false' "$PROGRESS" 2>/dev/null)
|
|
38
38
|
task_stop_reason=$(jq -r '.task_stop.reason // "null"' "$PROGRESS" 2>/dev/null)
|
|
@@ -53,32 +53,65 @@ now_epoch() {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
# ─────────────────────────────────────────
|
|
56
|
-
#
|
|
57
|
-
# feature-queue.json 에 활성 작업(in_progress)이 있는데 mode 가 solo/paused 면
|
|
58
|
-
# 누군가 progress.json 을 통째로 덮어써서 team_state 를 날린 것. mode=team 으로
|
|
59
|
-
# 자동 복원하고 경고 로그 남김.
|
|
56
|
+
# Normalize legacy mode drift — v6.3+ always runs company mode.
|
|
60
57
|
# ─────────────────────────────────────────
|
|
61
58
|
FEATURE_QUEUE_HEAL="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
62
|
-
if [
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
if [ "$mode" != "company" ]; then
|
|
60
|
+
bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" \
|
|
61
|
+
'.mode = "company" |
|
|
62
|
+
.mode_decision.owner = "conductor" |
|
|
63
|
+
.mode_decision.policy = "always_company_parallel" |
|
|
64
|
+
.mode_decision.user_override = null |
|
|
65
|
+
.mode_decision.decided_at = (now | todate) |
|
|
66
|
+
.mode_decision.rationale = "legacy mode normalized to always-on company mode"' \
|
|
67
|
+
2>/dev/null || true
|
|
68
|
+
mode="company"
|
|
69
|
+
[ -f "$PROJECT_ROOT/.harness/progress.log" ] && echo "$(date +'%Y-%m-%d %H:%M') | system | heal | mode | normalized legacy mode to company" >> "$PROJECT_ROOT/.harness/progress.log"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# ─────────────────────────────────────────
|
|
73
|
+
# Truthful Logging Heal — strip future-dated progress.log entries
|
|
74
|
+
# ─────────────────────────────────────────
|
|
75
|
+
PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
|
|
76
|
+
if [ -f "$PROGRESS_LOG" ]; then
|
|
77
|
+
NOW_KST=$(date "+%Y-%m-%d %H:%M")
|
|
78
|
+
NOW_UTC_MIN=$(date -u "+%Y-%m-%d %H:%M")
|
|
79
|
+
QUARANTINE_FILE="$PROGRESS_LOG.future-quarantine.$(date +%s)"
|
|
80
|
+
KEEP_FILE="$PROGRESS_LOG.tmp.$$"
|
|
81
|
+
|
|
82
|
+
# 미래 시각으로 시작하는 라인 격리. KST(YYYY-MM-DD HH:MM) 와 ISO(YYYY-MM-DDTHH:MM:SSZ) 둘 다 처리.
|
|
83
|
+
awk -v now_kst="$NOW_KST" -v now_utc="$NOW_UTC_MIN" -v qfile="$QUARANTINE_FILE" '
|
|
84
|
+
function iso_to_minute(s) {
|
|
85
|
+
gsub("\\[", "", s); gsub("T", " ", s); gsub("Z", "", s)
|
|
86
|
+
return substr(s, 1, 16)
|
|
87
|
+
}
|
|
88
|
+
{
|
|
89
|
+
future=0
|
|
90
|
+
if (match($0, /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}/)) {
|
|
91
|
+
ts = substr($0, RSTART, RLENGTH)
|
|
92
|
+
if (ts > now_kst) future=1
|
|
93
|
+
} else if (match($0, /^\[?[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}/)) {
|
|
94
|
+
ts_min = iso_to_minute(substr($0, RSTART, RLENGTH+5))
|
|
95
|
+
if (ts_min > now_utc) future=1
|
|
96
|
+
}
|
|
97
|
+
if (future) print >> qfile
|
|
98
|
+
else print
|
|
99
|
+
}
|
|
100
|
+
' "$PROGRESS_LOG" > "$KEEP_FILE"
|
|
101
|
+
|
|
102
|
+
if [ -f "$QUARANTINE_FILE" ] && [ -s "$QUARANTINE_FILE" ]; then
|
|
103
|
+
QCOUNT=$(wc -l < "$QUARANTINE_FILE" | tr -d ' ')
|
|
104
|
+
mv "$KEEP_FILE" "$PROGRESS_LOG"
|
|
105
|
+
echo "$(date +'%Y-%m-%d %H:%M') | system | heal | log | quarantined ${QCOUNT} future-dated lines → $(basename "$QUARANTINE_FILE")" >> "$PROGRESS_LOG"
|
|
106
|
+
else
|
|
107
|
+
rm -f "$KEEP_FILE" "$QUARANTINE_FILE" 2>/dev/null
|
|
75
108
|
fi
|
|
76
109
|
fi
|
|
77
110
|
|
|
78
111
|
# ─────────────────────────────────────────
|
|
79
|
-
#
|
|
112
|
+
# Company Mode — autonomous parallel company loop
|
|
80
113
|
# ─────────────────────────────────────────
|
|
81
|
-
if [ "$mode" = "
|
|
114
|
+
if [ "$mode" = "company" ]; then
|
|
82
115
|
FEATURE_QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
83
116
|
passed=0; total=0; in_prog=0; failed=0
|
|
84
117
|
if [ -f "$FEATURE_QUEUE" ]; then
|
|
@@ -88,18 +121,9 @@ if [ "$mode" = "team" ]; then
|
|
|
88
121
|
failed=$(jq '.queue.failed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
89
122
|
fi
|
|
90
123
|
|
|
91
|
-
echo "# Harness
|
|
124
|
+
echo "# Harness Company Mode active"
|
|
92
125
|
echo "# Queue: ${passed}/${total} passed, ${in_prog} in progress, ${failed} failed"
|
|
93
|
-
echo "#
|
|
94
|
-
echo "# Stop: /harness-stop | Emergency fallback: /harness-solo"
|
|
95
|
-
exit 0
|
|
96
|
-
fi
|
|
97
|
-
|
|
98
|
-
# ─────────────────────────────────────────
|
|
99
|
-
# Paused Mode — 중단 상태 안내
|
|
100
|
-
# ─────────────────────────────────────────
|
|
101
|
-
if [ "$mode" = "paused" ]; then
|
|
102
|
-
echo "# Harness paused — resume with /harness-team"
|
|
126
|
+
echo "# Company loop runs autonomously with parallel workers. Owner input is an interrupt only; do not wait for Owner after the initial GOAL."
|
|
103
127
|
exit 0
|
|
104
128
|
fi
|
|
105
129
|
|
|
@@ -144,9 +168,14 @@ fi
|
|
|
144
168
|
# init 상태: 첫 안내
|
|
145
169
|
# ─────────────────────────────────────────
|
|
146
170
|
if [ "$sprint_status" = "init" ]; then
|
|
171
|
+
if [ "$next_agent" != "null" ] && [ "$next_agent" != "none" ] && [ "$next_agent" != "dispatcher" ]; then
|
|
172
|
+
echo "# Harness Company Mode active"
|
|
173
|
+
echo "# Sprint is init, but next_agent=${next_agent}; continue the autonomous loop instead of waiting for Owner."
|
|
174
|
+
else
|
|
147
175
|
echo "# Harness ready — say \"하네스 엔지니어링 시작\" or /harness-dispatcher"
|
|
148
176
|
echo "# 기본 경로는 회사 루프입니다: dispatch -> CEO meeting -> COO/CTO 분배 -> gen/eval -> CQO -> service-ops -> batch meeting."
|
|
149
177
|
exit 0
|
|
178
|
+
fi
|
|
150
179
|
fi
|
|
151
180
|
|
|
152
181
|
# ─────────────────────────────────────────
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# harness-statusline.sh — Claude Code statusline hook (v5 unified)
|
|
3
3
|
# 터미널 하단에 항상 고정되는 1줄 compact 상태 표시.
|
|
4
|
-
#
|
|
5
|
-
# Team: [S1] FULL | team | T1:gen T2:eval T3:idle | 2/5 feat | ctx 45% | $1.23
|
|
4
|
+
# Company: [C1] FULL | company | T1:gen T2:eval T3:idle | 2/5 work | ctx 45% | $1.23
|
|
6
5
|
|
|
7
6
|
# Read Claude Code session data from stdin
|
|
8
7
|
input=$(cat)
|
|
@@ -32,14 +31,13 @@ if [ ! -f "$PROGRESS" ]; then
|
|
|
32
31
|
fi
|
|
33
32
|
|
|
34
33
|
# Read harness state
|
|
35
|
-
|
|
36
|
-
sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null)
|
|
34
|
+
cycle_num=$(jq -r '.sprint.number // .cycle.number // 0' "$PROGRESS" 2>/dev/null)
|
|
37
35
|
pipeline=$(jq -r '.pipeline // "?"' "$PROGRESS" 2>/dev/null)
|
|
38
36
|
current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
39
37
|
agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
|
|
40
38
|
next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
41
39
|
retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS" 2>/dev/null)
|
|
42
|
-
harness_mode
|
|
40
|
+
harness_mode="company"
|
|
43
41
|
|
|
44
42
|
# Pipeline short name
|
|
45
43
|
case "$pipeline" in
|
|
@@ -111,8 +109,8 @@ fi
|
|
|
111
109
|
# Build compact status line based on mode
|
|
112
110
|
FEATURE_QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
|
|
113
111
|
|
|
114
|
-
if [
|
|
115
|
-
#
|
|
112
|
+
if [ -f "$FEATURE_QUEUE" ]; then
|
|
113
|
+
# Company mode: show worker status instead of single agent
|
|
116
114
|
team_info=""
|
|
117
115
|
for i in 1 2 3; do
|
|
118
116
|
t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$FEATURE_QUEUE" 2>/dev/null)
|
|
@@ -127,13 +125,11 @@ if [ "$harness_mode" = "team" ] && [ -f "$FEATURE_QUEUE" ]; then
|
|
|
127
125
|
t_passed=$(jq '.queue.passed | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
128
126
|
t_total=$(jq '[.queue.ready, (.queue.blocked | keys), (.queue.in_progress | keys), .queue.passed, .queue.failed] | flatten | length' "$FEATURE_QUEUE" 2>/dev/null || echo 0)
|
|
129
127
|
|
|
130
|
-
echo "[
|
|
128
|
+
echo "[C${cycle_num}] ${pl} | company | ${team_info}| ${t_passed}/${t_total} work | ctx ${context_pct}% | \$${cost}"
|
|
131
129
|
else
|
|
132
|
-
# Solo/paused mode: show single agent status
|
|
133
|
-
mode_tag="${harness_mode}"
|
|
134
130
|
if [ -n "$model_short" ]; then
|
|
135
|
-
echo "[
|
|
131
|
+
echo "[C${cycle_num}] ${pl} | company | ${status_icon}${agent_short}${retry_str} | ${model_short} | ${completed_features}/${total_features} work | ctx ${context_pct}% | \$${cost}"
|
|
136
132
|
else
|
|
137
|
-
echo "[
|
|
133
|
+
echo "[C${cycle_num}] ${pl} | company | ${status_icon}${agent_short}${retry_str} | ${completed_features}/${total_features} work | ctx ${context_pct}% | \$${cost}"
|
|
138
134
|
fi
|
|
139
135
|
fi
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-stop.sh — Stop hook (v6.2)
|
|
3
|
+
#
|
|
4
|
+
# Claude 가 한 turn 을 끝내려 할 때 발화.
|
|
5
|
+
# conductor.state == "running" 이고 다음 작업이 남아 있으면
|
|
6
|
+
# stdout 으로 {"decision":"block", "reason":"..."} JSON 을 출력해
|
|
7
|
+
# Claude 가 멈추지 않고 다음 tick 으로 자동 연쇄하게 한다.
|
|
8
|
+
#
|
|
9
|
+
# 멈춰야 할 경우 (회사 루프 정지·task_stop 등) 는 그냥 exit 0
|
|
10
|
+
# → Claude 가 정상적으로 turn 을 종료.
|
|
11
|
+
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
|
|
14
|
+
INPUT=$(cat 2>/dev/null || echo "{}")
|
|
15
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)
|
|
16
|
+
[ -z "$CWD" ] && CWD="$PWD"
|
|
17
|
+
|
|
18
|
+
# 하네스 초기화 안 된 곳: no-op
|
|
19
|
+
[ -f "$CWD/.harness/progress.json" ] || exit 0
|
|
20
|
+
[ -f "$CWD/.harness/config.json" ] || exit 0
|
|
21
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
22
|
+
|
|
23
|
+
PROGRESS="$CWD/.harness/progress.json"
|
|
24
|
+
CONFIG="$CWD/.harness/config.json"
|
|
25
|
+
|
|
26
|
+
# Opt-out
|
|
27
|
+
AUTO_CHAIN=$(jq -r '.behavior.auto_chain_on_stop // true' "$CONFIG" 2>/dev/null || echo "true")
|
|
28
|
+
if [ "$AUTO_CHAIN" != "true" ]; then exit 0; fi
|
|
29
|
+
|
|
30
|
+
# 무한루프 방지: 한 sprint 안에서 stop_chain_count 가 상한을 넘으면 중단
|
|
31
|
+
STOP_CHAIN_MAX=$(jq -r '.behavior.auto_chain_max_per_sprint // 200' "$CONFIG" 2>/dev/null || echo 200)
|
|
32
|
+
STOP_CHAIN_COUNT=$(jq -r '.conductor.stop_chain_count // 0' "$PROGRESS" 2>/dev/null || echo 0)
|
|
33
|
+
if [ "${STOP_CHAIN_COUNT:-0}" -ge "${STOP_CHAIN_MAX:-200}" ]; then exit 0; fi
|
|
34
|
+
|
|
35
|
+
# 회사 루프 상태 확인
|
|
36
|
+
CONDUCTOR_STATE=$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null || echo "idle")
|
|
37
|
+
NEXT_AGENT=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null || echo "none")
|
|
38
|
+
AGENT_STATUS=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null || echo "pending")
|
|
39
|
+
TASK_STOP_ACTIVE=$(jq -r '.task_stop.active // false' "$PROGRESS" 2>/dev/null || echo "false")
|
|
40
|
+
ESCALATION=$(jq -r '.conductor.escalation // "null"' "$PROGRESS" 2>/dev/null || echo "null")
|
|
41
|
+
SPRINT_STATUS=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null || echo "init")
|
|
42
|
+
|
|
43
|
+
# stop hook 이 자기 자신에 의해 재진입되지 않도록 hook event 필터링
|
|
44
|
+
HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null || true)
|
|
45
|
+
if [ "$HOOK_EVENT" = "Stop" ]; then
|
|
46
|
+
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false' 2>/dev/null || echo "false")
|
|
47
|
+
if [ "$STOP_HOOK_ACTIVE" = "true" ]; then exit 0; fi
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 자동 연쇄 조건:
|
|
51
|
+
# conductor 가 running
|
|
52
|
+
# AND task_stop 비활성
|
|
53
|
+
# AND task_stop 비활성
|
|
54
|
+
# AND (next_agent 가 명시되어 있거나, agent_status=completed 라서 다음 tick 결정 필요)
|
|
55
|
+
should_chain="false"
|
|
56
|
+
if [ "$CONDUCTOR_STATE" = "running" ] \
|
|
57
|
+
&& [ "$TASK_STOP_ACTIVE" != "true" ]; then
|
|
58
|
+
if [ "$NEXT_AGENT" != "none" ] && [ "$NEXT_AGENT" != "null" ]; then
|
|
59
|
+
should_chain="true"
|
|
60
|
+
elif [ "$AGENT_STATUS" = "completed" ]; then
|
|
61
|
+
should_chain="true"
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
if [ "$should_chain" != "true" ]; then exit 0; fi
|
|
66
|
+
|
|
67
|
+
# stop_chain_count 증가 (느슨한 카운터 — race 허용)
|
|
68
|
+
NEW_COUNT=$((STOP_CHAIN_COUNT + 1))
|
|
69
|
+
TMP=$(mktemp)
|
|
70
|
+
jq --argjson n "$NEW_COUNT" '.conductor.stop_chain_count = $n |
|
|
71
|
+
.conductor.last_stop_chain_at = (now | todate)' "$PROGRESS" > "$TMP" 2>/dev/null \
|
|
72
|
+
&& mv "$TMP" "$PROGRESS" || rm -f "$TMP"
|
|
73
|
+
|
|
74
|
+
# Claude 에게 다음 행동 지시
|
|
75
|
+
if [ "$ESCALATION" != "null" ]; then
|
|
76
|
+
REASON="자율 회사 루프 진행 중. escalation=${ESCALATION} 는 정지 사유가 아니라 meeting-manager/service-ops 공유 대상이다. next_agent=${NEXT_AGENT}, agent_status=${AGENT_STATUS}, conductor=${CONDUCTOR_STATE}. SKILL 에 따라 conductor-tick 을 굴려 meeting-manager 회의로 라우팅하거나 다음 부서를 spawn 하라. progress.log 에는 절대 미래 시각을 쓰지 말 것. 진짜로 끝났을 때만 turn 을 종료하라."
|
|
77
|
+
else
|
|
78
|
+
REASON="자율 회사 루프 진행 중. next_agent=${NEXT_AGENT}, agent_status=${AGENT_STATUS}, conductor=${CONDUCTOR_STATE}. SKILL 에 따라 다음 부서를 spawn 하거나 conductor-tick 을 한 번 더 굴려라. progress.log 에는 절대 미래 시각을 쓰지 말 것. 진짜로 끝났을 때만 turn 을 종료하라."
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
jq -nc \
|
|
82
|
+
--arg reason "$REASON" \
|
|
83
|
+
'{decision: "block", reason: $reason}'
|
|
84
|
+
|
|
85
|
+
exit 0
|
|
@@ -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,180 @@
|
|
|
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
|
+
-e "s|{{HOME}}|$HOME|g" \
|
|
127
|
+
"$template" > "$PLIST_PATH"
|
|
128
|
+
|
|
129
|
+
# 기존 jobs 언로드 (idempotent)
|
|
130
|
+
launchctl unload "$PLIST_PATH" 2>/dev/null || true
|
|
131
|
+
|
|
132
|
+
# 로드
|
|
133
|
+
if launchctl load "$PLIST_PATH" 2>/dev/null; then
|
|
134
|
+
echo "[wake] launchd loaded: $PLIST_PATH"
|
|
135
|
+
else
|
|
136
|
+
echo "[wake] WARN: launchctl load 실패. 다음 명령으로 직접 시도해 보세요:" >&2
|
|
137
|
+
echo " launchctl load $PLIST_PATH" >&2
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
cmd_status
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
cmd_uninstall() {
|
|
144
|
+
if [ -f "$PLIST_PATH" ]; then
|
|
145
|
+
launchctl unload "$PLIST_PATH" 2>/dev/null || true
|
|
146
|
+
rm -f "$PLIST_PATH"
|
|
147
|
+
echo "[wake] uninstalled: $PLIST_PATH"
|
|
148
|
+
else
|
|
149
|
+
echo "[wake] not installed"
|
|
150
|
+
fi
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
cmd_run_now() {
|
|
154
|
+
bash "$WAKE_SH"
|
|
155
|
+
echo "[wake] run-now done. log:"
|
|
156
|
+
tail -20 "$LOG_DIR/wake.log" 2>/dev/null || true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
case "${1:-}" in
|
|
160
|
+
install) shift; cmd_install "$@" ;;
|
|
161
|
+
add) shift; cmd_add "${1:-$PWD}" ;;
|
|
162
|
+
remove) shift; cmd_remove "${1:-$PWD}" ;;
|
|
163
|
+
list) cmd_list ;;
|
|
164
|
+
status) cmd_status ;;
|
|
165
|
+
uninstall) cmd_uninstall ;;
|
|
166
|
+
run-now) cmd_run_now ;;
|
|
167
|
+
*)
|
|
168
|
+
cat <<USAGE
|
|
169
|
+
harness-wake-install.sh — launchd hourly wake 관리
|
|
170
|
+
|
|
171
|
+
install [project-root...] plist 생성 + 로드 (+ 프로젝트 등록)
|
|
172
|
+
add <project-root> 프로젝트 등록만
|
|
173
|
+
remove <project-root> 프로젝트 등록 해제
|
|
174
|
+
list 등록된 프로젝트 출력
|
|
175
|
+
status launchd / plist / 등록 상태
|
|
176
|
+
run-now 지금 즉시 wake 실행 (테스트)
|
|
177
|
+
uninstall plist 언로드 + 제거
|
|
178
|
+
USAGE
|
|
179
|
+
;;
|
|
180
|
+
esac
|