@walwal-harness/cli 6.0.4 → 6.1.0
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 +63 -0
- package/bin/init.js +121 -4
- package/commands/harness-solo.md +20 -20
- package/commands/harness-team.md +1 -1
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +382 -0
- package/scripts/harness-archive.sh +14 -0
- package/scripts/harness-meeting-doc.sh +153 -0
- package/scripts/harness-next.sh +31 -0
- package/scripts/harness-session-start.sh +16 -5
- package/scripts/harness-task-session.sh +48 -0
- package/scripts/harness-user-prompt-submit.sh +27 -9
- package/skills/brainstorming/SKILL.md +4 -4
- package/skills/conductor/SKILL.md +12 -16
- package/skills/cqo/SKILL.md +7 -0
- package/skills/cto/SKILL.md +7 -0
- package/skills/dispatcher/SKILL.md +17 -28
- 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 +33 -1
- package/skills/planner/SKILL.md +23 -9
- package/skills/service-ops/SKILL.md +19 -4
|
@@ -0,0 +1,382 @@
|
|
|
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
|
+
goal_adherence=$(jq -r '.goals.current_adherence // "null"' "$PROGRESS")
|
|
29
|
+
meetings_active_count=$(jq -r '(.meetings.active | length) // 0' "$PROGRESS")
|
|
30
|
+
cqo_verdict=$(jq -r '.cqo.sprint_verdict // "pending"' "$PROGRESS")
|
|
31
|
+
ops_report=$(jq -r '.service_ops.auto_retro.last_report // "null"' "$PROGRESS")
|
|
32
|
+
cto_hotfixes=$(jq -r '.cto.open_hotfixes // 0' "$PROGRESS")
|
|
33
|
+
ops_recommendations=$(jq -r '.service_ops.auto_retro.open_recommendations // 0' "$PROGRESS")
|
|
34
|
+
ops_alerts=$(jq -r '.service_ops.monitor.alerts_this_sprint // 0' "$PROGRESS")
|
|
35
|
+
open_incidents=$(jq -r '(.service_ops.incident.open | length) // 0' "$PROGRESS")
|
|
36
|
+
workflow_stage=$(jq -r '.workflow.stage // "goal-intake"' "$PROGRESS")
|
|
37
|
+
meeting_type=$(jq -r '.meetings.requested_type // "null"' "$PROGRESS")
|
|
38
|
+
meeting_reason=$(jq -r '.meetings.requested_reason // "null"' "$PROGRESS")
|
|
39
|
+
cqo_regressions=$(jq -r '.cqo.open_regressions // 0' "$PROGRESS")
|
|
40
|
+
cqo_axes_below=$(jq -r '(.cqo.axes_below_threshold | length) // 0' "$PROGRESS")
|
|
41
|
+
plan_status=$(jq -r '.artifacts["plan.md"].status // "pending"' "$PROGRESS")
|
|
42
|
+
feature_status=$(jq -r '.artifacts["feature-list.json"].status // "pending"' "$PROGRESS")
|
|
43
|
+
api_status=$(jq -r '.artifacts["api-contract.json"].status // "pending"' "$PROGRESS")
|
|
44
|
+
service_ops_drift=$(jq -r '.service_ops.drift_classification // "null"' "$PROGRESS")
|
|
45
|
+
|
|
46
|
+
set_progress() {
|
|
47
|
+
bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" "$1" >/dev/null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
escape_json_string() {
|
|
51
|
+
jq -Rn --arg v "$1" '$v'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
maybe_select_mode() {
|
|
55
|
+
local effective="$mode"
|
|
56
|
+
if [ "$user_override" != "null" ] && [ -n "$user_override" ]; then
|
|
57
|
+
effective="$user_override"
|
|
58
|
+
set_progress ".mode = \"$effective\" |
|
|
59
|
+
.mode_decision.owner = \"conductor\" |
|
|
60
|
+
.mode_decision.user_override = \"$effective\" |
|
|
61
|
+
.mode_decision.decided_at = (now | todate) |
|
|
62
|
+
.mode_decision.rationale = \"user override: $effective\""
|
|
63
|
+
echo "$effective"
|
|
64
|
+
return
|
|
65
|
+
fi
|
|
66
|
+
if [ "$mode" != "auto" ] || [ ! -f "$FEATURES" ]; then
|
|
67
|
+
echo "$mode"
|
|
68
|
+
return
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
local feature_count ready_at_start critical_depth
|
|
72
|
+
feature_count=$(jq '.features | length // 0' "$FEATURES" 2>/dev/null || echo 0)
|
|
73
|
+
ready_at_start=$(jq '[.features[]? | select(((.depends_on // []) | length) == 0)] | length' "$FEATURES" 2>/dev/null || echo 0)
|
|
74
|
+
critical_depth=$(jq '
|
|
75
|
+
def feature($id): .features[]? | select(.id == $id);
|
|
76
|
+
def depth($id):
|
|
77
|
+
(feature($id) | .depends_on // []) as $deps
|
|
78
|
+
| if ($deps | length) == 0 then 1 else ([ $deps[] | depth(.) ] | max + 1) end;
|
|
79
|
+
([.features[]?.id | depth(.)] | max) // 0
|
|
80
|
+
' "$FEATURES" 2>/dev/null || echo 0)
|
|
81
|
+
|
|
82
|
+
local team_ready team_features team_depth selected rationale
|
|
83
|
+
team_ready=$(jq -r '.mode_selection.rules.force_team_when.ready_at_start_gte // 3' "$CONFIG")
|
|
84
|
+
team_features=$(jq -r '.mode_selection.rules.force_team_when.feature_count_gte // 6' "$CONFIG")
|
|
85
|
+
team_depth=$(jq -r '.mode_selection.rules.force_team_when.critical_path_depth_lte // 2' "$CONFIG")
|
|
86
|
+
|
|
87
|
+
if [ "$ready_at_start" -ge "$team_ready" ] && [ "$feature_count" -ge "$team_features" ] && [ "$critical_depth" -le "$team_depth" ]; then
|
|
88
|
+
selected="team"
|
|
89
|
+
else
|
|
90
|
+
selected=$(jq -r '.mode_selection.rules.tie_breaker // "team"' "$CONFIG")
|
|
91
|
+
fi
|
|
92
|
+
rationale="company-default: ready=${ready_at_start}, features=${feature_count}, depth=${critical_depth} -> ${selected}"
|
|
93
|
+
set_progress ".mode = \"$selected\" |
|
|
94
|
+
.mode_decision.owner = \"conductor\" |
|
|
95
|
+
.mode_decision.decided_at = (now | todate) |
|
|
96
|
+
.mode_decision.rationale = \"$rationale\""
|
|
97
|
+
echo "$selected"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
infer_drift_classification() {
|
|
101
|
+
if [ "$service_ops_drift" != "null" ] && [ -n "$service_ops_drift" ]; then
|
|
102
|
+
echo "$service_ops_drift"
|
|
103
|
+
return
|
|
104
|
+
fi
|
|
105
|
+
if [ "$cqo_verdict" = "FAIL" ] || [ "${cqo_regressions:-0}" -gt 0 ] || [ "${cqo_axes_below:-0}" -gt 0 ]; then
|
|
106
|
+
echo "implementation_drift"
|
|
107
|
+
return
|
|
108
|
+
fi
|
|
109
|
+
if [ "$requested_mode" = "incident" ] || [ "${open_incidents:-0}" -gt 0 ] || [ "${ops_alerts:-0}" -gt 0 ]; then
|
|
110
|
+
echo "ops_drift"
|
|
111
|
+
return
|
|
112
|
+
fi
|
|
113
|
+
if [ "$goal_adherence" != "null" ] && awk "BEGIN {exit !($goal_adherence < 0.7)}"; then
|
|
114
|
+
if [ "$cqo_verdict" = "PASS" ] && [ "${open_incidents:-0}" -eq 0 ] && [ "${ops_alerts:-0}" -eq 0 ]; then
|
|
115
|
+
echo "planning_drift"
|
|
116
|
+
elif [ "$(jq -r '.goals.active_id // "null"' "$PROGRESS")" = "null" ]; then
|
|
117
|
+
echo "goal_drift"
|
|
118
|
+
else
|
|
119
|
+
echo "goal_drift"
|
|
120
|
+
fi
|
|
121
|
+
return
|
|
122
|
+
fi
|
|
123
|
+
echo "unknown"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
meeting_stage_for_owner() {
|
|
127
|
+
case "$1" in
|
|
128
|
+
planner) echo "coo-planning" ;;
|
|
129
|
+
cto) echo "cto-review" ;;
|
|
130
|
+
cqo) echo "quality-review" ;;
|
|
131
|
+
service-ops) echo "ops-review" ;;
|
|
132
|
+
dispatcher) echo "goal-intake" ;;
|
|
133
|
+
*) echo "goal-intake" ;;
|
|
134
|
+
esac
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
load_meeting_decision() {
|
|
138
|
+
local decision
|
|
139
|
+
decision=$(bash "$SCRIPT_DIR/harness-meeting-doc.sh" "$PROJECT_ROOT" read-decision 2>/dev/null || true)
|
|
140
|
+
if [ -n "$decision" ]; then
|
|
141
|
+
echo "$decision"
|
|
142
|
+
else
|
|
143
|
+
jq -c '.meetings.decision // {}' "$PROGRESS"
|
|
144
|
+
fi
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
effective_mode="$(maybe_select_mode)"
|
|
148
|
+
drift_classification="$(infer_drift_classification)"
|
|
149
|
+
|
|
150
|
+
next="$candidate"
|
|
151
|
+
action="pass-through"
|
|
152
|
+
new_conductor_state="running"
|
|
153
|
+
new_workflow_stage="$workflow_stage"
|
|
154
|
+
meeting_filter=""
|
|
155
|
+
planner_filter=""
|
|
156
|
+
ops_filter=""
|
|
157
|
+
meeting_prepare=false
|
|
158
|
+
|
|
159
|
+
plan_artifacts_ready=false
|
|
160
|
+
if [ "$plan_status" != "pending" ] || [ "$feature_status" != "pending" ] || [ "$api_status" != "pending" ]; then
|
|
161
|
+
plan_artifacts_ready=true
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
first_generator="generator-backend"
|
|
165
|
+
if [ "$pipeline" = "FE-ONLY" ]; then
|
|
166
|
+
first_generator="generator-frontend"
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
if [ "$current_agent" = "dispatcher" ] && [ "$agent_status" = "completed" ]; then
|
|
170
|
+
next="meeting-manager"
|
|
171
|
+
action="convene:all-hands:goal-intake"
|
|
172
|
+
new_conductor_state="waiting_meeting"
|
|
173
|
+
new_workflow_stage="ceo-meeting"
|
|
174
|
+
meeting_prepare=true
|
|
175
|
+
meeting_filter="
|
|
176
|
+
.meetings.active = [\"meeting-manager\"] |
|
|
177
|
+
.meetings.requested_type = \"all-hands\" |
|
|
178
|
+
.meetings.requested_reason = \"goal-intake\" |
|
|
179
|
+
.meetings.decision = {
|
|
180
|
+
\"owner\": \"planner\",
|
|
181
|
+
\"action_type\": \"goal-alignment\",
|
|
182
|
+
\"rationale\": \"CEO intake always starts with COO alignment.\",
|
|
183
|
+
\"evidence\": [],
|
|
184
|
+
\"drift_classification\": null,
|
|
185
|
+
\"source_path\": null
|
|
186
|
+
}"
|
|
187
|
+
|
|
188
|
+
elif [ "$meetings_active_count" -gt 0 ] && [ "$current_agent" != "meeting-manager" ]; then
|
|
189
|
+
next="meeting-manager"
|
|
190
|
+
action="spawn:meeting-manager:${meeting_type}"
|
|
191
|
+
new_conductor_state="waiting_meeting"
|
|
192
|
+
|
|
193
|
+
elif [ "$requested_mode" != "null" ] && [ -n "$requested_mode" ] && [ "$current_agent" != "service-ops" ]; then
|
|
194
|
+
next="service-ops"
|
|
195
|
+
action="spawn:service-ops:${requested_mode}"
|
|
196
|
+
|
|
197
|
+
elif [ "$goal_adherence" != "null" ] && awk "BEGIN {exit !($goal_adherence < 0.7)}"; then
|
|
198
|
+
next="meeting-manager"
|
|
199
|
+
action="convene:spec-review:${drift_classification}"
|
|
200
|
+
new_conductor_state="waiting_meeting"
|
|
201
|
+
new_workflow_stage="goal-drift-review"
|
|
202
|
+
meeting_prepare=true
|
|
203
|
+
meeting_filter="
|
|
204
|
+
.meetings.active = [\"meeting-manager\"] |
|
|
205
|
+
.meetings.requested_type = \"spec-review\" |
|
|
206
|
+
.meetings.requested_reason = \"goal-drift\" |
|
|
207
|
+
.service_ops.drift_classification = \"$drift_classification\" |
|
|
208
|
+
.meetings.decision = {
|
|
209
|
+
\"owner\": null,
|
|
210
|
+
\"action_type\": null,
|
|
211
|
+
\"rationale\": null,
|
|
212
|
+
\"evidence\": [],
|
|
213
|
+
\"drift_classification\": \"$drift_classification\",
|
|
214
|
+
\"source_path\": null
|
|
215
|
+
}"
|
|
216
|
+
|
|
217
|
+
elif [ "$current_agent" = "meeting-manager" ] && [ "$agent_status" = "completed" ]; then
|
|
218
|
+
meeting_decision="$(load_meeting_decision)"
|
|
219
|
+
owner=$(jq -r '.owner // "null"' <<<"$meeting_decision")
|
|
220
|
+
action_type=$(jq -r '.action_type // "triage"' <<<"$meeting_decision")
|
|
221
|
+
rationale=$(jq -r '.rationale // ""' <<<"$meeting_decision")
|
|
222
|
+
evidence=$(jq -c '.evidence // []' <<<"$meeting_decision")
|
|
223
|
+
decision_drift=$(jq -r '.drift_classification // "unknown"' <<<"$meeting_decision")
|
|
224
|
+
source_path=$(jq -r '.source_path // "null"' <<<"$meeting_decision")
|
|
225
|
+
|
|
226
|
+
if [ "$owner" = "null" ] || [ -z "$owner" ]; then
|
|
227
|
+
case "$decision_drift" in
|
|
228
|
+
implementation_drift) owner="cto" ;;
|
|
229
|
+
planning_drift) owner="planner" ;;
|
|
230
|
+
ops_drift) owner="service-ops" ;;
|
|
231
|
+
goal_drift) owner="dispatcher" ;;
|
|
232
|
+
*)
|
|
233
|
+
if [ "$plan_artifacts_ready" = false ]; then owner="planner"; else owner="cto"; fi
|
|
234
|
+
;;
|
|
235
|
+
esac
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
next="$owner"
|
|
239
|
+
action="dispatch:${owner}:${action_type}"
|
|
240
|
+
new_workflow_stage="$(meeting_stage_for_owner "$owner")"
|
|
241
|
+
if [ "$owner" = "planner" ]; then
|
|
242
|
+
planner_filter=".planner.requested_mode = \"$action_type\""
|
|
243
|
+
fi
|
|
244
|
+
meeting_filter="
|
|
245
|
+
.meetings.active = [] |
|
|
246
|
+
.meetings.last_type = .meetings.requested_type |
|
|
247
|
+
.meetings.last_reason = .meetings.requested_reason |
|
|
248
|
+
.meetings.last_decision = \"$owner:$action_type\" |
|
|
249
|
+
.meetings.requested_type = null |
|
|
250
|
+
.meetings.requested_reason = null |
|
|
251
|
+
.meetings.decision = {
|
|
252
|
+
\"owner\": \"$owner\",
|
|
253
|
+
\"action_type\": \"$action_type\",
|
|
254
|
+
\"rationale\": $(escape_json_string "$rationale"),
|
|
255
|
+
\"evidence\": $evidence,
|
|
256
|
+
\"drift_classification\": $(escape_json_string "$decision_drift"),
|
|
257
|
+
\"source_path\": $(escape_json_string "$source_path")
|
|
258
|
+
}"
|
|
259
|
+
|
|
260
|
+
elif [ "$current_agent" = "planner" ] && [ "$agent_status" = "completed" ]; then
|
|
261
|
+
next="cto"
|
|
262
|
+
action="handoff:cto:plan-ready"
|
|
263
|
+
new_workflow_stage="cto-review"
|
|
264
|
+
planner_filter='
|
|
265
|
+
.planner.last_brief = (.planner.requested_mode // "goal-alignment") |
|
|
266
|
+
.planner.requested_mode = null'
|
|
267
|
+
|
|
268
|
+
elif [ "$current_agent" = "cto" ]; then
|
|
269
|
+
if [ "${cto_hotfixes:-0}" -gt 0 ] || [ "${ops_recommendations:-0}" -gt 0 ]; then
|
|
270
|
+
next="planner"
|
|
271
|
+
action="dispatch:planner:execution-plan"
|
|
272
|
+
new_workflow_stage="coo-replan"
|
|
273
|
+
planner_filter='.planner.requested_mode = "execution-plan"'
|
|
274
|
+
elif [ "$candidate" = "archive" ] && [ "$workflow_stage" = "ops-review" ]; then
|
|
275
|
+
next="archive"
|
|
276
|
+
action="archive"
|
|
277
|
+
new_conductor_state="completed"
|
|
278
|
+
new_workflow_stage="ops-monitoring"
|
|
279
|
+
else
|
|
280
|
+
next="$first_generator"
|
|
281
|
+
action="dispatch:workers:${first_generator}"
|
|
282
|
+
new_workflow_stage="implementation"
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
elif [ "$candidate" = "archive" ]; then
|
|
286
|
+
if [[ "$current_agent" == evaluator-* ]] && [ "$cqo_verdict" = "pending" ]; then
|
|
287
|
+
next="cqo"
|
|
288
|
+
action="spawn:cqo"
|
|
289
|
+
new_workflow_stage="quality-review"
|
|
290
|
+
elif [ "$current_agent" = "cqo" ]; then
|
|
291
|
+
if [ "$cqo_verdict" = "FAIL" ] || [ "${cqo_regressions:-0}" -gt 0 ] || [ "${cqo_axes_below:-0}" -gt 0 ]; then
|
|
292
|
+
next="meeting-manager"
|
|
293
|
+
action="convene:spec-review:implementation_drift"
|
|
294
|
+
new_conductor_state="waiting_meeting"
|
|
295
|
+
new_workflow_stage="quality-review"
|
|
296
|
+
meeting_prepare=true
|
|
297
|
+
meeting_filter='
|
|
298
|
+
.meetings.active = ["meeting-manager"] |
|
|
299
|
+
.meetings.requested_type = "spec-review" |
|
|
300
|
+
.meetings.requested_reason = "quality-fail" |
|
|
301
|
+
.service_ops.drift_classification = "implementation_drift" |
|
|
302
|
+
.meetings.decision = {
|
|
303
|
+
"owner": null,
|
|
304
|
+
"action_type": null,
|
|
305
|
+
"rationale": null,
|
|
306
|
+
"evidence": [],
|
|
307
|
+
"drift_classification": "implementation_drift",
|
|
308
|
+
"source_path": null
|
|
309
|
+
}'
|
|
310
|
+
else
|
|
311
|
+
next="service-ops"
|
|
312
|
+
action="spawn:service-ops:auto-retro"
|
|
313
|
+
new_workflow_stage="ops-review"
|
|
314
|
+
ops_filter='.service_ops.requested_mode = "auto-retro"'
|
|
315
|
+
fi
|
|
316
|
+
elif [ "$cqo_verdict" != "pending" ] && [ "$ops_report" = "null" ]; then
|
|
317
|
+
next="service-ops"
|
|
318
|
+
action="spawn:service-ops:auto-retro"
|
|
319
|
+
new_workflow_stage="ops-review"
|
|
320
|
+
ops_filter='.service_ops.requested_mode = "auto-retro"'
|
|
321
|
+
elif [ "$current_agent" = "service-ops" ]; then
|
|
322
|
+
local_drift="$drift_classification"
|
|
323
|
+
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
|
|
324
|
+
next="meeting-manager"
|
|
325
|
+
action="convene:sprint-review:${local_drift}"
|
|
326
|
+
new_conductor_state="waiting_meeting"
|
|
327
|
+
new_workflow_stage="ops-review"
|
|
328
|
+
meeting_prepare=true
|
|
329
|
+
meeting_filter="
|
|
330
|
+
.meetings.active = [\"meeting-manager\"] |
|
|
331
|
+
.meetings.requested_type = \"sprint-review\" |
|
|
332
|
+
.meetings.requested_reason = \"ops-batch\" |
|
|
333
|
+
.service_ops.drift_classification = \"$local_drift\" |
|
|
334
|
+
.meetings.decision = {
|
|
335
|
+
\"owner\": null,
|
|
336
|
+
\"action_type\": null,
|
|
337
|
+
\"rationale\": null,
|
|
338
|
+
\"evidence\": [],
|
|
339
|
+
\"drift_classification\": \"$local_drift\",
|
|
340
|
+
\"source_path\": null
|
|
341
|
+
}"
|
|
342
|
+
else
|
|
343
|
+
next="archive"
|
|
344
|
+
action="archive"
|
|
345
|
+
new_conductor_state="completed"
|
|
346
|
+
new_workflow_stage="ops-monitoring"
|
|
347
|
+
ops_filter='.service_ops.requested_mode = null'
|
|
348
|
+
fi
|
|
349
|
+
fi
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
filter=".conductor.state = \"$new_conductor_state\" |
|
|
353
|
+
.conductor.current_action = \"$action\" |
|
|
354
|
+
.conductor.last_tick = (now | todate) |
|
|
355
|
+
.conductor.tick_count = ((.conductor.tick_count // 0) + 1) |
|
|
356
|
+
.next_agent = \"$next\" |
|
|
357
|
+
.workflow.stage = \"$new_workflow_stage\" |
|
|
358
|
+
.workflow.last_transition = (now | todate) |
|
|
359
|
+
.workflow.last_reason = \"$action\""
|
|
360
|
+
|
|
361
|
+
if [ -n "$meeting_filter" ]; then
|
|
362
|
+
filter="$filter | $meeting_filter"
|
|
363
|
+
fi
|
|
364
|
+
if [ -n "$planner_filter" ]; then
|
|
365
|
+
filter="$filter | $planner_filter"
|
|
366
|
+
fi
|
|
367
|
+
if [ -n "$ops_filter" ]; then
|
|
368
|
+
filter="$filter | $ops_filter"
|
|
369
|
+
elif [ "$current_agent" = "service-ops" ] && [ "$next" != "service-ops" ]; then
|
|
370
|
+
filter="$filter | .service_ops.requested_mode = null"
|
|
371
|
+
fi
|
|
372
|
+
if [ "$next" = "meeting-manager" ] && [ "$workflow_stage" = "ops-review" ]; then
|
|
373
|
+
filter="$filter | .workflow.loop_count = ((.workflow.loop_count // 0) + 1)"
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
set_progress "$filter"
|
|
377
|
+
|
|
378
|
+
if [ "$meeting_prepare" = true ] && [ "$next" = "meeting-manager" ]; then
|
|
379
|
+
bash "$SCRIPT_DIR/harness-meeting-doc.sh" "$PROJECT_ROOT" prepare >/dev/null 2>&1 || true
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
echo "$next"
|
|
@@ -89,7 +89,21 @@ jq --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '
|
|
|
89
89
|
| .agent_status = "pending"
|
|
90
90
|
| .completed_agents = []
|
|
91
91
|
| .next_agent = "dispatcher"
|
|
92
|
+
| .workflow = { stage: "goal-intake", last_transition: null, loop_count: 0, last_reason: null }
|
|
93
|
+
| .task_sessions.current = { agent: null, id: null, path: null, source: null }
|
|
92
94
|
| .failure = { agent: null, location: null, message: null, retry_target: null }
|
|
95
|
+
| .meetings.active = []
|
|
96
|
+
| .meetings.current_id = null
|
|
97
|
+
| .meetings.current_record_path = null
|
|
98
|
+
| .meetings.requested_type = null
|
|
99
|
+
| .meetings.requested_reason = null
|
|
100
|
+
| .meetings.last_type = null
|
|
101
|
+
| .meetings.last_reason = null
|
|
102
|
+
| .meetings.last_decision = null
|
|
103
|
+
| .meetings.decision = { owner: null, action_type: null, rationale: null, evidence: [], drift_classification: null, source_path: null }
|
|
104
|
+
| .service_ops.requested_mode = null
|
|
105
|
+
| .service_ops.drift_classification = null
|
|
106
|
+
| .service_ops.evidence = []
|
|
93
107
|
| (.artifacts // {}) as $a
|
|
94
108
|
| .artifacts = ($a | with_entries(
|
|
95
109
|
if (.value | type) == "object"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-meeting-doc.sh — create/read meeting decision documents
|
|
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 1
|
|
9
|
+
MODE="${2:-prepare}"
|
|
10
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
11
|
+
MEETINGS_ROOT="$PROJECT_ROOT/.harness/actions/meetings"
|
|
12
|
+
|
|
13
|
+
command -v jq >/dev/null 2>&1 || exit 1
|
|
14
|
+
mkdir -p "$MEETINGS_ROOT"
|
|
15
|
+
|
|
16
|
+
requested_type=$(jq -r '.meetings.requested_type // "spec-review"' "$PROGRESS")
|
|
17
|
+
requested_reason=$(jq -r '.meetings.requested_reason // "unspecified"' "$PROGRESS")
|
|
18
|
+
drift=$(jq -r '.service_ops.drift_classification // .meetings.decision.drift_classification // "unknown"' "$PROGRESS")
|
|
19
|
+
goal_adherence=$(jq -r '.goals.current_adherence // "unknown"' "$PROGRESS")
|
|
20
|
+
ops_report=$(jq -r '.service_ops.auto_retro.last_report // empty' "$PROGRESS")
|
|
21
|
+
cqo_audit=$(jq -r '.cqo.audit_path // empty' "$PROGRESS")
|
|
22
|
+
cto_review=$(jq -r '.cto.review_path // empty' "$PROGRESS")
|
|
23
|
+
current_path=$(jq -r '.meetings.current_record_path // empty' "$PROGRESS")
|
|
24
|
+
|
|
25
|
+
default_decision_json() {
|
|
26
|
+
local owner="planner"
|
|
27
|
+
local action_type="triage"
|
|
28
|
+
case "$1" in
|
|
29
|
+
implementation_drift) owner="cto"; action_type="implement" ;;
|
|
30
|
+
planning_drift) owner="planner"; action_type="replan" ;;
|
|
31
|
+
ops_drift) owner="service-ops"; action_type="monitor" ;;
|
|
32
|
+
goal_drift) owner="dispatcher"; action_type="escalate-owner" ;;
|
|
33
|
+
esac
|
|
34
|
+
if [ "$requested_reason" = "goal-intake" ]; then
|
|
35
|
+
owner="planner"
|
|
36
|
+
action_type="goal-alignment"
|
|
37
|
+
fi
|
|
38
|
+
jq -n \
|
|
39
|
+
--arg owner "$owner" \
|
|
40
|
+
--arg action_type "$action_type" \
|
|
41
|
+
--arg rationale "initial skeleton created by meeting-manager; replace with evidence-backed conclusion" \
|
|
42
|
+
--arg drift_classification "$1" \
|
|
43
|
+
--arg ops_report "$ops_report" \
|
|
44
|
+
--arg cqo_audit "$cqo_audit" \
|
|
45
|
+
--arg cto_review "$cto_review" '
|
|
46
|
+
{
|
|
47
|
+
owner: $owner,
|
|
48
|
+
action_type: $action_type,
|
|
49
|
+
rationale: $rationale,
|
|
50
|
+
evidence: (
|
|
51
|
+
[
|
|
52
|
+
(if $ops_report != "" then {source:$ops_report, kind:"ops-report"} else empty end),
|
|
53
|
+
(if $cqo_audit != "" then {source:$cqo_audit, kind:"cqo-audit"} else empty end),
|
|
54
|
+
(if $cto_review != "" then {source:$cto_review, kind:"cto-review"} else empty end)
|
|
55
|
+
]
|
|
56
|
+
),
|
|
57
|
+
drift_classification: $drift_classification
|
|
58
|
+
}'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if [ "$MODE" = "prepare" ]; then
|
|
62
|
+
meeting_id="M-$(date -u +%Y%m%dT%H%M%SZ)"
|
|
63
|
+
meeting_dir="$MEETINGS_ROOT/$meeting_id"
|
|
64
|
+
mkdir -p "$meeting_dir"
|
|
65
|
+
record="$meeting_dir/meeting-$meeting_id.md"
|
|
66
|
+
|
|
67
|
+
cat > "$meeting_dir/notice.md" <<EOF
|
|
68
|
+
# Notice — $meeting_id
|
|
69
|
+
|
|
70
|
+
- type: $requested_type
|
|
71
|
+
- reason: $requested_reason
|
|
72
|
+
- goal_adherence: $goal_adherence
|
|
73
|
+
|
|
74
|
+
## Request
|
|
75
|
+
"$requested_reason" 요청이 접수되었습니다. 참석자들은 각자의 전문 시각에서 사실·증거를 제출하고,
|
|
76
|
+
다음 owner와 action_type을 evidence 중심으로 결정하십시오.
|
|
77
|
+
EOF
|
|
78
|
+
|
|
79
|
+
cat > "$meeting_dir/prep-dispatcher.md" <<'EOF'
|
|
80
|
+
# Prep — Dispatcher/CEO
|
|
81
|
+
- Goal 자체가 흔들렸는가?
|
|
82
|
+
- Owner escalation 이 필요한가?
|
|
83
|
+
- 사업 우선순위 또는 KPI 정의를 바꿔야 하는가?
|
|
84
|
+
EOF
|
|
85
|
+
cat > "$meeting_dir/prep-planner.md" <<'EOF'
|
|
86
|
+
# Prep — Planner/COO
|
|
87
|
+
- 기획/가설/웹리서치/레퍼런스 보강이 필요한가?
|
|
88
|
+
- planning_drift 또는 goal_drift 근거는 무엇인가?
|
|
89
|
+
- 수정해야 할 plan/feature/api 는 무엇인가?
|
|
90
|
+
EOF
|
|
91
|
+
cat > "$meeting_dir/prep-cto.md" <<'EOF'
|
|
92
|
+
# Prep — CTO
|
|
93
|
+
- 구현/아키텍처/기술선택이 원인인가?
|
|
94
|
+
- implementation_drift 근거는 무엇인가?
|
|
95
|
+
- 어떤 generator/evaluator를 다시 태워야 하는가?
|
|
96
|
+
EOF
|
|
97
|
+
cat > "$meeting_dir/prep-cqo.md" <<'EOF'
|
|
98
|
+
# Prep — CQO
|
|
99
|
+
- 품질/회귀/검증 부족이 원인인가?
|
|
100
|
+
- 어떤 evidence가 이 결론을 지지하는가?
|
|
101
|
+
- evidence 없는 추정은 금지한다.
|
|
102
|
+
EOF
|
|
103
|
+
cat > "$meeting_dir/prep-service-ops.md" <<'EOF'
|
|
104
|
+
# Prep — Service-Ops
|
|
105
|
+
- 어떤 KPI/로그/incident가 Goal에서 벗어났는가?
|
|
106
|
+
- ops_drift 여부를 먼저 판단하라.
|
|
107
|
+
- 운영 측 evidence를 문서 경로와 함께 적어라.
|
|
108
|
+
EOF
|
|
109
|
+
|
|
110
|
+
decision_json="$(default_decision_json "$drift")"
|
|
111
|
+
cat > "$record" <<EOF
|
|
112
|
+
# Meeting Record — $meeting_id
|
|
113
|
+
|
|
114
|
+
## Context
|
|
115
|
+
- type: $requested_type
|
|
116
|
+
- reason: $requested_reason
|
|
117
|
+
- goal_adherence: $goal_adherence
|
|
118
|
+
|
|
119
|
+
## Decision JSON
|
|
120
|
+
\`\`\`json
|
|
121
|
+
$(jq -n \
|
|
122
|
+
--arg meeting_id "$meeting_id" \
|
|
123
|
+
--arg type "$requested_type" \
|
|
124
|
+
--arg reason "$requested_reason" \
|
|
125
|
+
--arg source_path "${record#$PROJECT_ROOT/}" \
|
|
126
|
+
--argjson decision "$decision_json" \
|
|
127
|
+
'{meeting_id:$meeting_id,type:$type,reason:$reason,decision:($decision + {source_path:$source_path})}')
|
|
128
|
+
\`\`\`
|
|
129
|
+
EOF
|
|
130
|
+
|
|
131
|
+
bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" \
|
|
132
|
+
".meetings.current_id = \"$meeting_id\" |
|
|
133
|
+
.meetings.current_record_path = \"${record#$PROJECT_ROOT/}\" |
|
|
134
|
+
.meetings.decision = ($(jq -c '.decision' <<<"$(
|
|
135
|
+
jq -n --arg meeting_id "$meeting_id" --arg type "$requested_type" --arg reason "$requested_reason" --arg source_path "${record#$PROJECT_ROOT/}" --argjson decision "$decision_json" '{meeting_id:$meeting_id,type:$type,reason:$reason,decision:($decision + {source_path:$source_path})}'
|
|
136
|
+
)"))" >/dev/null
|
|
137
|
+
|
|
138
|
+
echo "${record#$PROJECT_ROOT/}"
|
|
139
|
+
exit 0
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
if [ "$MODE" = "read-decision" ]; then
|
|
143
|
+
[ -n "$current_path" ] || exit 0
|
|
144
|
+
[ -f "$PROJECT_ROOT/$current_path" ] || exit 0
|
|
145
|
+
awk '
|
|
146
|
+
/```json/ {capture=1; next}
|
|
147
|
+
/```/ && capture {capture=0; exit}
|
|
148
|
+
capture {print}
|
|
149
|
+
' "$PROJECT_ROOT/$current_path" | jq -c '.decision'
|
|
150
|
+
exit 0
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
exit 2
|
package/scripts/harness-next.sh
CHANGED
|
@@ -189,6 +189,21 @@ run_pre_eval_gate() {
|
|
|
189
189
|
fi
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
# ─────────────────────────────────────────
|
|
193
|
+
# Conductor Tick — lightweight 6.0 routing bridge
|
|
194
|
+
# next_agent 계산 후, Company loop가 개입해야 하면 next_agent를 재작성.
|
|
195
|
+
# ─────────────────────────────────────────
|
|
196
|
+
run_conductor_tick() {
|
|
197
|
+
local candidate="$1"
|
|
198
|
+
local owner
|
|
199
|
+
owner=$(jq -r '.mode_selection.owner // empty' "$CONFIG" 2>/dev/null || true)
|
|
200
|
+
if [ "$owner" != "conductor" ]; then return 0; fi
|
|
201
|
+
if [ ! -x "$SCRIPT_DIR/conductor-tick.sh" ]; then return 0; fi
|
|
202
|
+
|
|
203
|
+
HARNESS_CONDUCTOR_CANDIDATE="$candidate" bash "$SCRIPT_DIR/conductor-tick.sh" "$PROJECT_ROOT" >/dev/null 2>&1 || true
|
|
204
|
+
next_agent=$(jq -r '.next_agent // "null"' "$PROGRESS" 2>/dev/null || echo "$candidate")
|
|
205
|
+
}
|
|
206
|
+
|
|
192
207
|
# ─────────────────────────────────────────
|
|
193
208
|
# Determine next agent
|
|
194
209
|
# ─────────────────────────────────────────
|
|
@@ -350,6 +365,13 @@ if [ "$agent_status" = "completed" ]; then
|
|
|
350
365
|
fi
|
|
351
366
|
fi
|
|
352
367
|
|
|
368
|
+
# ─────────────────────────────────────────
|
|
369
|
+
# Company routing bridge — handoff 직전 conductor 가 next_agent 조정
|
|
370
|
+
# ─────────────────────────────────────────
|
|
371
|
+
if [ "$next_agent" != "null" ] && [ "$agent_status" != "blocked" ]; then
|
|
372
|
+
run_conductor_tick "$next_agent"
|
|
373
|
+
fi
|
|
374
|
+
|
|
353
375
|
# ─────────────────────────────────────────
|
|
354
376
|
# Render progress
|
|
355
377
|
# ─────────────────────────────────────────
|
|
@@ -376,6 +398,10 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
|
|
|
376
398
|
# ── Read model & thinking mode for next agent ──
|
|
377
399
|
agent_model=$(jq -r ".agents[\"${next_agent}\"].model // \"opus\"" "$CONFIG" 2>/dev/null || echo "opus")
|
|
378
400
|
agent_thinking=$(jq -r ".agents[\"${next_agent}\"].thinking_mode // \"null\"" "$CONFIG" 2>/dev/null || echo "null")
|
|
401
|
+
task_session_path=""
|
|
402
|
+
if [ -x "$SCRIPT_DIR/harness-task-session.sh" ]; then
|
|
403
|
+
task_session_path=$(bash "$SCRIPT_DIR/harness-task-session.sh" "$PROJECT_ROOT" "$next_agent" "${current_agent:-dispatcher}" 2>/dev/null || true)
|
|
404
|
+
fi
|
|
379
405
|
|
|
380
406
|
# ── Build prompt text (embedded in handoff.json) ──
|
|
381
407
|
prompt="/harness-${next_agent} 를 실행하세요."
|
|
@@ -389,6 +415,9 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
|
|
|
389
415
|
fi
|
|
390
416
|
|
|
391
417
|
prompt+=$'\n'".harness/handoff.json을 읽고 컨텍스트를 확인하세요."
|
|
418
|
+
if [ -n "$task_session_path" ]; then
|
|
419
|
+
prompt+=$'\n'"${task_session_path} 를 현재 task session의 단일 기준 문서로 사용하세요."
|
|
420
|
+
fi
|
|
392
421
|
|
|
393
422
|
# Inject thinking mode instruction
|
|
394
423
|
if [ "$agent_thinking" != "null" ]; then
|
|
@@ -492,6 +521,7 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
|
|
|
492
521
|
--argjson eval_config "$eval_config" \
|
|
493
522
|
--argjson cross_val "$cross_validation_data" \
|
|
494
523
|
--argjson cross_val_cq "$cross_validation_from_code_quality" \
|
|
524
|
+
--arg task_session_path "$task_session_path" \
|
|
495
525
|
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
496
526
|
'{
|
|
497
527
|
from: $from,
|
|
@@ -509,6 +539,7 @@ if [ "$next_agent" != "null" ] && [ "$next_agent" != "archive" ] && [ "$agent_st
|
|
|
509
539
|
eval_config: $eval_config,
|
|
510
540
|
cross_validation_from_functional: $cross_val,
|
|
511
541
|
cross_validation_from_code_quality: $cross_val_cq,
|
|
542
|
+
task_session_path: (if $task_session_path == "" then null else $task_session_path end),
|
|
512
543
|
warnings: [],
|
|
513
544
|
timestamp: $timestamp
|
|
514
545
|
}' > "$HANDOFF"
|