@walwal-harness/cli 6.1.1 → 6.1.3
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 +29 -0
- package/README.md +306 -367
- package/assets/templates/config.json +7 -0
- package/assets/templates/progress.json.template +13 -0
- package/gotchas/README.md +40 -8
- package/gotchas/conductor.md +12 -82
- package/gotchas/coo-developer.md +21 -12
- package/gotchas/cqo.md +22 -0
- package/gotchas/cto.md +22 -0
- package/gotchas/dispatcher.md +12 -87
- package/gotchas/documentationer.md +21 -12
- package/gotchas/evaluator-architecture.md +22 -0
- package/gotchas/evaluator-code-quality.md +11 -20
- package/gotchas/evaluator-functional.md +10 -30
- package/gotchas/evaluator-security.md +22 -0
- package/gotchas/evaluator-visual.md +10 -19
- package/gotchas/generator-backend-laravel.md +11 -77
- package/gotchas/generator-backend.md +19 -2
- package/gotchas/generator-designer.md +22 -0
- package/gotchas/generator-devops.md +22 -0
- package/gotchas/generator-frontend.md +13 -22
- package/gotchas/meeting-manager.md +22 -0
- package/gotchas/planner.md +19 -2
- package/gotchas/service-ops.md +13 -18
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +167 -21
- package/scripts/harness-meeting-doc.sh +146 -2
- package/scripts/harness-session-start.sh +61 -0
- package/scripts/harness-token-limit.sh +99 -0
- package/scripts/harness-user-prompt-submit.sh +23 -0
- package/scripts/lib/harness-progress-migrate.sh +58 -0
- package/skills/conductor/SKILL.md +1 -0
- package/skills/coo-developer/SKILL.md +35 -5
- package/skills/documentationer/SKILL.md +55 -9
- package/skills/meeting-manager/SKILL.md +114 -4
- package/skills/planner/SKILL.md +45 -0
|
@@ -21,6 +21,13 @@ ops_report=$(jq -r '.service_ops.auto_retro.last_report // empty' "$PROGRESS")
|
|
|
21
21
|
cqo_audit=$(jq -r '.cqo.audit_path // empty' "$PROGRESS")
|
|
22
22
|
cto_review=$(jq -r '.cto.review_path // empty' "$PROGRESS")
|
|
23
23
|
current_path=$(jq -r '.meetings.current_record_path // empty' "$PROGRESS")
|
|
24
|
+
# v6.2 — parallel tracks hints (set by conductor before convene). length >= 2 = fork.
|
|
25
|
+
requested_tracks=$(jq -c '.meetings.requested_tracks // []' "$PROGRESS")
|
|
26
|
+
requested_rendezvous=$(jq -c '.meetings.requested_rendezvous // null' "$PROGRESS")
|
|
27
|
+
fork_context=$(jq -c '.meetings.fork_context // null' "$PROGRESS")
|
|
28
|
+
fallback_fork_meeting_id=$(jq -r '.meetings.fork_meeting_id // .conductor.fork_meeting_id // ""' "$PROGRESS")
|
|
29
|
+
fallback_conductor_tracks=$(jq -c '.conductor.tracks // []' "$PROGRESS")
|
|
30
|
+
fallback_decision_tracks=$(jq -c '.meetings.decision.tracks // []' "$PROGRESS")
|
|
24
31
|
|
|
25
32
|
default_decision_json() {
|
|
26
33
|
local owner="planner"
|
|
@@ -35,6 +42,33 @@ default_decision_json() {
|
|
|
35
42
|
owner="planner"
|
|
36
43
|
action_type="goal-alignment"
|
|
37
44
|
fi
|
|
45
|
+
|
|
46
|
+
# v6.2 — Tracks 가 단일 진실: length >= 2 → fork-join, length == 1 → single.
|
|
47
|
+
# legacy {owner, action_type} 만 들어와도 1-element tracks[] 로 합성.
|
|
48
|
+
local tracks_json="[]"
|
|
49
|
+
local rendezvous_json="null"
|
|
50
|
+
if [ "$(jq 'length' <<<"$requested_tracks")" -gt 1 ]; then
|
|
51
|
+
tracks_json=$(jq -c '
|
|
52
|
+
[ to_entries[] | (.value + {
|
|
53
|
+
id: ((.value.id // null) // ("track-" + ((.key + 1) | tostring))),
|
|
54
|
+
deliverable: (.value.deliverable // "report"),
|
|
55
|
+
deliverable_path: (.value.deliverable_path // null),
|
|
56
|
+
status: (.value.status // "pending")
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
' <<<"$requested_tracks")
|
|
60
|
+
rendezvous_json=$(jq -c '. // {type:"followup-review", when:"next_cadence"}' <<<"$requested_rendezvous")
|
|
61
|
+
# primary owner = first track owner (편의 미러)
|
|
62
|
+
owner=$(jq -r '.[0].owner // "planner"' <<<"$tracks_json")
|
|
63
|
+
action_type=$(jq -r '.[0].action_type // "triage"' <<<"$tracks_json")
|
|
64
|
+
else
|
|
65
|
+
# single — derive a 1-element tracks[] (backward compat 도 동시에 처리)
|
|
66
|
+
tracks_json=$(jq -c -n \
|
|
67
|
+
--arg owner "$owner" \
|
|
68
|
+
--arg action_type "$action_type" \
|
|
69
|
+
'[ {id:"track-1", owner:$owner, action_type:$action_type, deliverable:"report", deliverable_path:null, status:"pending"} ]')
|
|
70
|
+
fi
|
|
71
|
+
|
|
38
72
|
jq -n \
|
|
39
73
|
--arg owner "$owner" \
|
|
40
74
|
--arg action_type "$action_type" \
|
|
@@ -42,7 +76,9 @@ default_decision_json() {
|
|
|
42
76
|
--arg drift_classification "$1" \
|
|
43
77
|
--arg ops_report "$ops_report" \
|
|
44
78
|
--arg cqo_audit "$cqo_audit" \
|
|
45
|
-
--arg cto_review "$cto_review"
|
|
79
|
+
--arg cto_review "$cto_review" \
|
|
80
|
+
--argjson tracks "$tracks_json" \
|
|
81
|
+
--argjson rendezvous "$rendezvous_json" '
|
|
46
82
|
{
|
|
47
83
|
owner: $owner,
|
|
48
84
|
action_type: $action_type,
|
|
@@ -54,26 +90,70 @@ default_decision_json() {
|
|
|
54
90
|
(if $cto_review != "" then {source:$cto_review, kind:"cto-review"} else empty end)
|
|
55
91
|
]
|
|
56
92
|
),
|
|
57
|
-
drift_classification: $drift_classification
|
|
93
|
+
drift_classification: $drift_classification,
|
|
94
|
+
tracks: $tracks,
|
|
95
|
+
rendezvous: $rendezvous
|
|
58
96
|
}'
|
|
59
97
|
}
|
|
60
98
|
|
|
99
|
+
resolve_followup_fork_context() {
|
|
100
|
+
local resolved="$fork_context"
|
|
101
|
+
if [ "$requested_type" != "followup-review" ]; then
|
|
102
|
+
echo "$resolved"
|
|
103
|
+
return
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ "$resolved" != "null" ] && [ -n "$resolved" ]; then
|
|
107
|
+
echo "$resolved"
|
|
108
|
+
return
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
local fallback_tracks="$fallback_conductor_tracks"
|
|
112
|
+
if [ "$(jq 'length' <<<"$fallback_tracks")" -eq 0 ] && [ "$(jq 'length' <<<"$fallback_decision_tracks")" -gt 0 ]; then
|
|
113
|
+
fallback_tracks="$fallback_decision_tracks"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
if [ "$(jq 'length' <<<"$fallback_tracks")" -eq 0 ]; then
|
|
117
|
+
echo "null"
|
|
118
|
+
return
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
jq -c -n \
|
|
122
|
+
--arg fork_meeting_id "$fallback_fork_meeting_id" \
|
|
123
|
+
--argjson prior_tracks "$fallback_tracks" \
|
|
124
|
+
'{fork_meeting_id:$fork_meeting_id, prior_tracks:$prior_tracks, sealed_at:(now | todate)}'
|
|
125
|
+
}
|
|
126
|
+
|
|
61
127
|
if [ "$MODE" = "prepare" ]; then
|
|
62
128
|
meeting_id="M-$(date -u +%Y%m%dT%H%M%SZ)"
|
|
63
129
|
meeting_dir="$MEETINGS_ROOT/$meeting_id"
|
|
64
130
|
mkdir -p "$meeting_dir"
|
|
65
131
|
record="$meeting_dir/meeting-$meeting_id.md"
|
|
66
132
|
|
|
133
|
+
# v6.2 — Notice surfaces tracks/rendezvous for visibility (length >= 2 = fork)
|
|
134
|
+
tracks_summary="(single owner)"
|
|
135
|
+
rendezvous_summary="-"
|
|
136
|
+
is_fork="false"
|
|
137
|
+
if [ "$(jq 'length' <<<"$requested_tracks")" -gt 1 ]; then
|
|
138
|
+
is_fork="true"
|
|
139
|
+
tracks_summary=$(jq -r '[.[] | "\(.id // "?"):\(.owner)/\(.action_type)→\(.deliverable // "report")"] | join(", ")' <<<"$requested_tracks")
|
|
140
|
+
rendezvous_summary=$(jq -r '"\(.type // "followup-review")@\(.when // "next_cadence")"' <<<"$requested_rendezvous")
|
|
141
|
+
fi
|
|
67
142
|
cat > "$meeting_dir/notice.md" <<EOF
|
|
68
143
|
# Notice — $meeting_id
|
|
69
144
|
|
|
70
145
|
- type: $requested_type
|
|
71
146
|
- reason: $requested_reason
|
|
72
147
|
- goal_adherence: $goal_adherence
|
|
148
|
+
- fork: $is_fork
|
|
149
|
+
- tracks: $tracks_summary
|
|
150
|
+
- rendezvous: $rendezvous_summary
|
|
73
151
|
|
|
74
152
|
## Request
|
|
75
153
|
"$requested_reason" 요청이 접수되었습니다. 참석자들은 각자의 전문 시각에서 사실·증거를 제출하고,
|
|
76
154
|
다음 owner와 action_type을 evidence 중심으로 결정하십시오.
|
|
155
|
+
|
|
156
|
+
> **Parallel tracks (v6.2)**: \`tracks[]\` 의 길이 ≥ 2 면 fork-join. \`rendezvous\` 시점에 followup-review 로 합쳐집니다. 단일 트랙 회의는 기존과 동일하게 동작합니다.
|
|
77
157
|
EOF
|
|
78
158
|
|
|
79
159
|
cat > "$meeting_dir/prep-dispatcher.md" <<'EOF'
|
|
@@ -108,6 +188,26 @@ EOF
|
|
|
108
188
|
EOF
|
|
109
189
|
|
|
110
190
|
decision_json="$(default_decision_json "$drift")"
|
|
191
|
+
|
|
192
|
+
# v6.2 — followup-review: surface prior_tracks + fork_meeting_id from fork_context
|
|
193
|
+
resolved_fork_context="$(resolve_followup_fork_context)"
|
|
194
|
+
|
|
195
|
+
fork_section=""
|
|
196
|
+
if [ "$requested_type" = "followup-review" ] && [ "$resolved_fork_context" != "null" ] && [ -n "$resolved_fork_context" ]; then
|
|
197
|
+
fork_id=$(jq -r '.fork_meeting_id // ""' <<<"$resolved_fork_context")
|
|
198
|
+
fork_section="
|
|
199
|
+
|
|
200
|
+
## Fork Context (v6.2)
|
|
201
|
+
- fork_meeting_id: $fork_id
|
|
202
|
+
- prior_tracks (rendezvous 입력):
|
|
203
|
+
|
|
204
|
+
\`\`\`json
|
|
205
|
+
$(jq '.prior_tracks' <<<"$resolved_fork_context")
|
|
206
|
+
\`\`\`
|
|
207
|
+
|
|
208
|
+
> 결정자(기본 CTO) 는 위 트랙별 deliverable 을 통합 검토 후 \`apply-now / backlog / more-validation\` 중 하나를 선택해 아래 Decision JSON 의 \`tracks\` 를 비우고 \`owner / action_type\` 만 채운다 (followup 에서 또 fork 금지)."
|
|
209
|
+
fi
|
|
210
|
+
|
|
111
211
|
cat > "$record" <<EOF
|
|
112
212
|
# Meeting Record — $meeting_id
|
|
113
213
|
|
|
@@ -115,6 +215,7 @@ EOF
|
|
|
115
215
|
- type: $requested_type
|
|
116
216
|
- reason: $requested_reason
|
|
117
217
|
- goal_adherence: $goal_adherence
|
|
218
|
+
$fork_section
|
|
118
219
|
|
|
119
220
|
## Decision JSON
|
|
120
221
|
\`\`\`json
|
|
@@ -135,6 +236,10 @@ EOF
|
|
|
135
236
|
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
237
|
)"))" >/dev/null
|
|
137
238
|
|
|
239
|
+
if [ "$requested_type" = "followup-review" ] && [ "$resolved_fork_context" != "null" ] && [ -n "$resolved_fork_context" ]; then
|
|
240
|
+
bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" ".meetings.fork_context = $resolved_fork_context" >/dev/null
|
|
241
|
+
fi
|
|
242
|
+
|
|
138
243
|
echo "${record#$PROJECT_ROOT/}"
|
|
139
244
|
exit 0
|
|
140
245
|
fi
|
|
@@ -150,4 +255,43 @@ if [ "$MODE" = "read-decision" ]; then
|
|
|
150
255
|
exit 0
|
|
151
256
|
fi
|
|
152
257
|
|
|
258
|
+
# v6.2 — read-tracks: emit normalized tracks[] (length>=2 = fork, length==1 = single).
|
|
259
|
+
# Legacy decisions without tracks → synthesize 1-element tracks[] from {owner, action_type}.
|
|
260
|
+
if [ "$MODE" = "read-tracks" ]; then
|
|
261
|
+
jq -c '
|
|
262
|
+
if ((.meetings.decision.tracks // []) | length) > 0 then
|
|
263
|
+
.meetings.decision.tracks
|
|
264
|
+
elif ((.meetings.fork_context.prior_tracks // []) | length) > 0 then
|
|
265
|
+
.meetings.fork_context.prior_tracks
|
|
266
|
+
elif ((.conductor.tracks // []) | length) > 0 then
|
|
267
|
+
.conductor.tracks
|
|
268
|
+
else
|
|
269
|
+
[ {
|
|
270
|
+
id: "track-1",
|
|
271
|
+
owner: (.meetings.decision.owner // "planner"),
|
|
272
|
+
action_type: (.meetings.decision.action_type // "triage"),
|
|
273
|
+
deliverable: "report",
|
|
274
|
+
deliverable_path: null,
|
|
275
|
+
status: "pending"
|
|
276
|
+
}
|
|
277
|
+
]
|
|
278
|
+
end
|
|
279
|
+
' "$PROGRESS"
|
|
280
|
+
exit 0
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
# v6.2 — read-rendezvous: emit rendezvous{} or null
|
|
284
|
+
if [ "$MODE" = "read-rendezvous" ]; then
|
|
285
|
+
jq -c '.meetings.decision.rendezvous // .conductor.rendezvous // null' "$PROGRESS"
|
|
286
|
+
exit 0
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
# v6.2 — read-fork: "true" if tracks.length >= 2, else "false"
|
|
290
|
+
if [ "$MODE" = "read-fork" ]; then
|
|
291
|
+
decision="$(bash "$0" "$PROJECT_ROOT" read-decision 2>/dev/null || true)"
|
|
292
|
+
if [ -z "$decision" ]; then decision='{}'; fi
|
|
293
|
+
jq -r '((.tracks // []) | length) > 1' <<<"$decision"
|
|
294
|
+
exit 0
|
|
295
|
+
fi
|
|
296
|
+
|
|
153
297
|
exit 2
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
9
|
LIB="$SCRIPT_DIR/lib/harness-render-progress.sh"
|
|
10
10
|
AUDIT_LIB="$SCRIPT_DIR/lib/harness-audit.sh"
|
|
11
|
+
MIGRATE_LIB="$SCRIPT_DIR/lib/harness-progress-migrate.sh"
|
|
11
12
|
|
|
12
13
|
if [ ! -f "$LIB" ]; then exit 0; fi
|
|
13
14
|
source "$LIB"
|
|
14
15
|
[ -f "$AUDIT_LIB" ] && source "$AUDIT_LIB"
|
|
16
|
+
[ -f "$MIGRATE_LIB" ] && source "$MIGRATE_LIB"
|
|
15
17
|
command -v jq &>/dev/null || exit 0
|
|
16
18
|
|
|
17
19
|
PROJECT_ROOT="$(resolve_harness_root "." 2>/dev/null)" || exit 0
|
|
@@ -20,6 +22,11 @@ CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
|
20
22
|
HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
|
|
21
23
|
[ -f "$PROGRESS" ] || exit 0
|
|
22
24
|
|
|
25
|
+
# v6.2 — idempotent schema migration (parallel tracks fields, etc.)
|
|
26
|
+
if declare -f migrate_progress_schema >/dev/null 2>&1; then
|
|
27
|
+
migrate_progress_schema "$PROGRESS" || true
|
|
28
|
+
fi
|
|
29
|
+
|
|
23
30
|
sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null)
|
|
24
31
|
sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
|
|
25
32
|
current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
@@ -27,6 +34,23 @@ next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
|
27
34
|
agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
|
|
28
35
|
mode=$(jq -r '.mode // "team"' "$PROGRESS" 2>/dev/null)
|
|
29
36
|
conductor_state=$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null)
|
|
37
|
+
task_stop_active=$(jq -r '.task_stop.active // false' "$PROGRESS" 2>/dev/null)
|
|
38
|
+
task_stop_reason=$(jq -r '.task_stop.reason // "null"' "$PROGRESS" 2>/dev/null)
|
|
39
|
+
task_stop_resume_after=$(jq -r '.task_stop.resume_after // "null"' "$PROGRESS" 2>/dev/null)
|
|
40
|
+
task_stop_notified_at=$(jq -r '.task_stop.resume_notified_at // "null"' "$PROGRESS" 2>/dev/null)
|
|
41
|
+
task_stop_wake_target=$(jq -r '.task_stop.wake_target // .next_agent // "none"' "$PROGRESS" 2>/dev/null)
|
|
42
|
+
task_stop_task_session=$(jq -r '.task_stop.task_session_path // "null"' "$PROGRESS" 2>/dev/null)
|
|
43
|
+
|
|
44
|
+
parse_iso_epoch() {
|
|
45
|
+
local iso="${1:-}"
|
|
46
|
+
[ -n "$iso" ] && [ "$iso" != "null" ] || return 1
|
|
47
|
+
date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso" "+%s" 2>/dev/null \
|
|
48
|
+
|| date -u -d "$iso" "+%s" 2>/dev/null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
now_epoch() {
|
|
52
|
+
date -u "+%s"
|
|
53
|
+
}
|
|
30
54
|
|
|
31
55
|
# ─────────────────────────────────────────
|
|
32
56
|
# Auto-heal mode drift — Team 상태 유실 복구
|
|
@@ -79,6 +103,43 @@ if [ "$mode" = "paused" ]; then
|
|
|
79
103
|
exit 0
|
|
80
104
|
fi
|
|
81
105
|
|
|
106
|
+
# ─────────────────────────────────────────
|
|
107
|
+
# TokenLimit hold — zero-token resume reminder
|
|
108
|
+
# ─────────────────────────────────────────
|
|
109
|
+
if [ "$task_stop_active" = "true" ] && [ "$task_stop_reason" = "TokenLimit" ]; then
|
|
110
|
+
retry_interval=$(jq -r '.token_limit.re_notify_every_seconds // 3600' "$CONFIG" 2>/dev/null || echo 3600)
|
|
111
|
+
resume_after_epoch=$(parse_iso_epoch "$task_stop_resume_after" || echo 0)
|
|
112
|
+
now_ts=$(now_epoch)
|
|
113
|
+
notified_epoch=$(parse_iso_epoch "$task_stop_notified_at" || echo 0)
|
|
114
|
+
|
|
115
|
+
if [ "$now_ts" -lt "$resume_after_epoch" ]; then
|
|
116
|
+
echo "# Harness paused — TokenLimit hold"
|
|
117
|
+
echo "# Wake target: /harness-${task_stop_wake_target}"
|
|
118
|
+
echo "# Retry after: ${task_stop_resume_after}"
|
|
119
|
+
if [ "$task_stop_task_session" != "null" ] && [ -n "$task_stop_task_session" ]; then
|
|
120
|
+
echo "# Task session: ${task_stop_task_session}"
|
|
121
|
+
fi
|
|
122
|
+
exit 0
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if [ "$notified_epoch" -eq 0 ] || [ $((now_ts - notified_epoch)) -ge "$retry_interval" ]; then
|
|
126
|
+
bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" \
|
|
127
|
+
".agent_status = \"pending\" |
|
|
128
|
+
.current_agent = null |
|
|
129
|
+
.next_agent = \"${task_stop_wake_target}\" |
|
|
130
|
+
.task_stop.active = false |
|
|
131
|
+
.task_stop.resume_ready = true |
|
|
132
|
+
.task_stop.resume_notified_at = (now | todate)" \
|
|
133
|
+
>/dev/null 2>&1 || true
|
|
134
|
+
echo "# Harness resume ready — TokenLimit hold expired"
|
|
135
|
+
echo "# Resume target: /harness-${task_stop_wake_target}"
|
|
136
|
+
if [ "$task_stop_task_session" != "null" ] && [ -n "$task_stop_task_session" ]; then
|
|
137
|
+
echo "# Resume with task session: ${task_stop_task_session}"
|
|
138
|
+
fi
|
|
139
|
+
exit 0
|
|
140
|
+
fi
|
|
141
|
+
fi
|
|
142
|
+
|
|
82
143
|
# ─────────────────────────────────────────
|
|
83
144
|
# init 상태: 첫 안내
|
|
84
145
|
# ─────────────────────────────────────────
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-token-limit.sh — mark/resume token-limit interruptions
|
|
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
|
+
CMD="${2:-mark}"
|
|
10
|
+
|
|
11
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
12
|
+
CONFIG="$PROJECT_ROOT/.harness/config.json"
|
|
13
|
+
|
|
14
|
+
[ -f "$PROGRESS" ] || exit 1
|
|
15
|
+
command -v jq >/dev/null 2>&1 || exit 1
|
|
16
|
+
|
|
17
|
+
parse_iso_epoch() {
|
|
18
|
+
local iso="${1:-}"
|
|
19
|
+
[ -n "$iso" ] && [ "$iso" != "null" ] || return 1
|
|
20
|
+
date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso" "+%s" 2>/dev/null \
|
|
21
|
+
|| date -u -d "$iso" "+%s" 2>/dev/null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
mark_token_limit() {
|
|
25
|
+
local retry_secs current next task_path target stop_reason
|
|
26
|
+
retry_secs="${3:-$(jq -r '.token_limit.retry_after_seconds // 3600' "$CONFIG" 2>/dev/null || echo 3600)}"
|
|
27
|
+
stop_reason=$(jq -r '.token_limit.stop_reason_flag // "TokenLimit"' "$CONFIG" 2>/dev/null || echo "TokenLimit")
|
|
28
|
+
current=$(jq -r '.current_agent // "null"' "$PROGRESS")
|
|
29
|
+
next=$(jq -r '.next_agent // "null"' "$PROGRESS")
|
|
30
|
+
task_path=$(jq -r '.task_sessions.current.path // "null"' "$PROGRESS")
|
|
31
|
+
target="$current"
|
|
32
|
+
if [ "$target" = "null" ] || [ -z "$target" ]; then
|
|
33
|
+
target="$next"
|
|
34
|
+
fi
|
|
35
|
+
[ "$target" != "null" ] || target="dispatcher"
|
|
36
|
+
|
|
37
|
+
local stopped_at resume_after
|
|
38
|
+
stopped_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
39
|
+
resume_after="$(date -u -v+"${retry_secs}"S +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|
|
40
|
+
|| date -u -d "+${retry_secs} seconds" +%Y-%m-%dT%H:%M:%SZ)"
|
|
41
|
+
|
|
42
|
+
bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" "
|
|
43
|
+
.agent_status = \"paused\" |
|
|
44
|
+
.next_agent = \"$target\" |
|
|
45
|
+
.task_stop.active = true |
|
|
46
|
+
.task_stop.reason = \"$stop_reason\" |
|
|
47
|
+
.task_stop.stopped_at = \"$stopped_at\" |
|
|
48
|
+
.task_stop.resume_after = \"$resume_after\" |
|
|
49
|
+
.task_stop.resume_ready = false |
|
|
50
|
+
.task_stop.resume_notified_at = null |
|
|
51
|
+
.task_stop.wake_target = \"$target\" |
|
|
52
|
+
.task_stop.stopped_agent = $(jq -Rn --arg v "$current" '$v') |
|
|
53
|
+
.task_stop.stopped_next_agent = $(jq -Rn --arg v "$next" '$v') |
|
|
54
|
+
.task_stop.task_session_path = $(jq -Rn --arg v "$task_path" '$v') |
|
|
55
|
+
.task_stop.hold_count = ((.task_stop.hold_count // 0) + 1)
|
|
56
|
+
" >/dev/null
|
|
57
|
+
|
|
58
|
+
echo "[token-limit] marked: target=$target resume_after=$resume_after"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
resume_probe() {
|
|
62
|
+
local active reason resume_after wake_target
|
|
63
|
+
active=$(jq -r '.task_stop.active // false' "$PROGRESS")
|
|
64
|
+
reason=$(jq -r '.task_stop.reason // "null"' "$PROGRESS")
|
|
65
|
+
resume_after=$(jq -r '.task_stop.resume_after // "null"' "$PROGRESS")
|
|
66
|
+
wake_target=$(jq -r '.task_stop.wake_target // .next_agent // "null"' "$PROGRESS")
|
|
67
|
+
|
|
68
|
+
if [ "$active" != "true" ] || [ "$reason" != "TokenLimit" ]; then
|
|
69
|
+
echo "[token-limit] no active TokenLimit hold"
|
|
70
|
+
exit 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
local resume_epoch now_ts
|
|
74
|
+
resume_epoch=$(parse_iso_epoch "$resume_after" || echo 0)
|
|
75
|
+
now_ts=$(date -u "+%s")
|
|
76
|
+
if [ "$now_ts" -lt "$resume_epoch" ]; then
|
|
77
|
+
echo "[token-limit] still held until $resume_after"
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" "
|
|
82
|
+
.agent_status = \"pending\" |
|
|
83
|
+
.current_agent = null |
|
|
84
|
+
.next_agent = \"$wake_target\" |
|
|
85
|
+
.task_stop.active = false |
|
|
86
|
+
.task_stop.resume_ready = true |
|
|
87
|
+
.task_stop.resume_notified_at = (now | todate)
|
|
88
|
+
" >/dev/null
|
|
89
|
+
echo "[token-limit] resume ready: wake_target=$wake_target"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case "$CMD" in
|
|
93
|
+
mark) mark_token_limit "$@" ;;
|
|
94
|
+
resume-probe) resume_probe ;;
|
|
95
|
+
*)
|
|
96
|
+
echo "usage: $0 <project-root> <mark|resume-probe> [retry_seconds]" >&2
|
|
97
|
+
exit 2
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
@@ -42,6 +42,17 @@ if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
|
|
|
42
42
|
AGENT_STATUS=$(jq -r '.agent_status // "pending"' "$CWD/.harness/progress.json" 2>/dev/null || echo "pending")
|
|
43
43
|
fi
|
|
44
44
|
|
|
45
|
+
TASK_STOP_ACTIVE="false"
|
|
46
|
+
TASK_STOP_REASON="null"
|
|
47
|
+
TASK_STOP_RESUME_AFTER="null"
|
|
48
|
+
TASK_STOP_WAKE_TARGET="none"
|
|
49
|
+
if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
|
|
50
|
+
TASK_STOP_ACTIVE=$(jq -r '.task_stop.active // false' "$CWD/.harness/progress.json" 2>/dev/null || echo "false")
|
|
51
|
+
TASK_STOP_REASON=$(jq -r '.task_stop.reason // "null"' "$CWD/.harness/progress.json" 2>/dev/null || echo "null")
|
|
52
|
+
TASK_STOP_RESUME_AFTER=$(jq -r '.task_stop.resume_after // "null"' "$CWD/.harness/progress.json" 2>/dev/null || echo "null")
|
|
53
|
+
TASK_STOP_WAKE_TARGET=$(jq -r '.task_stop.wake_target // .next_agent // "none"' "$CWD/.harness/progress.json" 2>/dev/null || echo "none")
|
|
54
|
+
fi
|
|
55
|
+
|
|
45
56
|
# ── 명령 히스토리 기록 (모든 모드 공통) ──
|
|
46
57
|
PROGRESS_LOG="$CWD/.harness/progress.log"
|
|
47
58
|
if [ -n "$PROMPT" ] && [ -d "$CWD/.harness" ]; then
|
|
@@ -79,6 +90,18 @@ EOF
|
|
|
79
90
|
exit 0
|
|
80
91
|
fi
|
|
81
92
|
|
|
93
|
+
if [ "$TASK_STOP_ACTIVE" = "true" ] && [ "$TASK_STOP_REASON" = "TokenLimit" ]; then
|
|
94
|
+
cat <<EOF
|
|
95
|
+
[harness] token-limit hold | wake_target=${TASK_STOP_WAKE_TARGET}
|
|
96
|
+
## TokenLimit Hold
|
|
97
|
+
- 모든 작업은 일시중지 상태입니다.
|
|
98
|
+
- 재개 대상: /harness-${TASK_STOP_WAKE_TARGET}
|
|
99
|
+
- retry_after: ${TASK_STOP_RESUME_AFTER}
|
|
100
|
+
- 이 상태는 문서 기반으로만 복구되며, 별도 모델 probe는 수행하지 않습니다.
|
|
101
|
+
EOF
|
|
102
|
+
exit 0
|
|
103
|
+
fi
|
|
104
|
+
|
|
82
105
|
# ── Mode 기반 분기 ──
|
|
83
106
|
MODE="team"
|
|
84
107
|
if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-progress-migrate.sh — Idempotent progress.json normalizer.
|
|
3
|
+
#
|
|
4
|
+
# 목적: 새 스키마 필드를 누락하지 않도록 SessionStart 마다 안전하게 채운다.
|
|
5
|
+
# 항상 jq 의 alternative operator(`//`) 또는 `if has` 패턴으로 작성하여
|
|
6
|
+
# 기존 값을 절대 덮어쓰지 않는다.
|
|
7
|
+
#
|
|
8
|
+
# Usage (sourced):
|
|
9
|
+
# source "$SCRIPT_DIR/lib/harness-progress-migrate.sh"
|
|
10
|
+
# migrate_progress_schema "$PROGRESS"
|
|
11
|
+
#
|
|
12
|
+
# Usage (standalone):
|
|
13
|
+
# bash scripts/lib/harness-progress-migrate.sh /path/to/.harness/progress.json
|
|
14
|
+
|
|
15
|
+
migrate_progress_schema() {
|
|
16
|
+
local progress="$1"
|
|
17
|
+
[ -f "$progress" ] || return 0
|
|
18
|
+
command -v jq >/dev/null 2>&1 || return 0
|
|
19
|
+
|
|
20
|
+
# v6.2 — Parallel tracks (fork-join) fields. tracks.length >= 2 → fork.
|
|
21
|
+
# Note: there is intentionally NO `meetings.decision.mode` field — single/parallel is derived
|
|
22
|
+
# from tracks.length. Runtime progress.mode is preserved because it still governs solo/team/paused.
|
|
23
|
+
local filter='
|
|
24
|
+
.conductor.tracks = (.conductor.tracks // [])
|
|
25
|
+
| .conductor.rendezvous = (.conductor.rendezvous // null)
|
|
26
|
+
| .conductor.fork_meeting_id = (.conductor.fork_meeting_id // null)
|
|
27
|
+
|
|
28
|
+
| .meetings = (.meetings // {})
|
|
29
|
+
| .meetings.requested_tracks = (.meetings.requested_tracks // [])
|
|
30
|
+
| .meetings.requested_rendezvous = (.meetings.requested_rendezvous // null)
|
|
31
|
+
| .meetings.fork_meeting_id = (.meetings.fork_meeting_id // null)
|
|
32
|
+
|
|
33
|
+
| .meetings.decision = (.meetings.decision // {})
|
|
34
|
+
| .meetings.decision.tracks = (.meetings.decision.tracks // [])
|
|
35
|
+
| .meetings.decision.rendezvous = (.meetings.decision.rendezvous // null)
|
|
36
|
+
| del(.meetings.decision.mode)
|
|
37
|
+
| del(.meetings.requested_mode)
|
|
38
|
+
'
|
|
39
|
+
|
|
40
|
+
local tmp
|
|
41
|
+
tmp="$(mktemp)" || return 1
|
|
42
|
+
if jq "$filter" "$progress" > "$tmp" 2>/dev/null; then
|
|
43
|
+
mv "$tmp" "$progress"
|
|
44
|
+
else
|
|
45
|
+
rm -f "$tmp"
|
|
46
|
+
return 1
|
|
47
|
+
fi
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Standalone invocation
|
|
51
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
52
|
+
target="${1:-}"
|
|
53
|
+
if [ -z "$target" ]; then
|
|
54
|
+
echo "usage: $0 <path/to/.harness/progress.json>" >&2
|
|
55
|
+
exit 2
|
|
56
|
+
fi
|
|
57
|
+
migrate_progress_schema "$target"
|
|
58
|
+
fi
|
|
@@ -152,6 +152,7 @@ idle ─► running ─► (waiting_meeting | waiting_owner | running) ─► co
|
|
|
152
152
|
- 리서치·정리 중심 → `documentationer`
|
|
153
153
|
- 빠른 실험·백데이터 코드 중심 → `coo-developer`
|
|
154
154
|
- 둘 다 필요 → 같은 tick 에 2명 병렬 spawn 가능
|
|
155
|
+
- `hypothesis-verdict` 는 terminal 단계이며 완료 시 `meeting-manager` / followup-review 로 되돌린다.
|
|
155
156
|
|
|
156
157
|
### 5.1 Team mode 병렬 spawn (G-005)
|
|
157
158
|
|
|
@@ -21,12 +21,20 @@ disable-model-invocation: false
|
|
|
21
21
|
- Planner의 `requested_mode = "hypothesis"`
|
|
22
22
|
- 기존 코드베이스, 로컬 데이터, 백데이터, 샘플 CSV/JSON/DB dump
|
|
23
23
|
|
|
24
|
-
## 3. 출력
|
|
24
|
+
## 3. 출력 (산출물 경로 표준)
|
|
25
25
|
|
|
26
|
-
- 실험 코드 또는 스크립트
|
|
27
|
-
- 재현 절차
|
|
28
|
-
- 관찰 결과와 한계
|
|
29
|
-
-
|
|
26
|
+
- `.harness/actions/hypothesis/<id>/spike/` — 실험 코드 또는 스크립트
|
|
27
|
+
- `.harness/actions/hypothesis/<id>/repro.md` — 재현 절차
|
|
28
|
+
- `.harness/actions/hypothesis/<id>/observations.md` — 관찰 결과와 한계
|
|
29
|
+
- `<id>` 는 `H-YYYYMMDDTHHMMSSZ` 형식. Planner 가 fork 시점에 발급.
|
|
30
|
+
|
|
31
|
+
`progress.json` 업데이트 (완료 시):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bash scripts/harness-progress-set.sh . \
|
|
35
|
+
'.coo_developer.last_spike_path = "actions/hypothesis/<id>/spike/" |
|
|
36
|
+
.coo_developer.last_observations = "actions/hypothesis/<id>/observations.md"'
|
|
37
|
+
```
|
|
30
38
|
|
|
31
39
|
## 4. 작업 원칙
|
|
32
40
|
|
|
@@ -40,3 +48,25 @@ disable-model-invocation: false
|
|
|
40
48
|
- 실험 결과만으로 운영 가능 판정
|
|
41
49
|
- 정규 팀 평가 없이 배포 코드로 승격
|
|
42
50
|
- 근거 없는 직감성 결론
|
|
51
|
+
|
|
52
|
+
## 6. Parallel-Tracks 컨텍스트 (v6.2)
|
|
53
|
+
|
|
54
|
+
Hypothesis Cell 은 fork 회의 (결정 JSON 의 `tracks[]` 길이 ≥ 2) 에서 **track-N** 의 owner 로 지명되어 활동한다 (보통 `track-2: planner/hypothesis-validation` 의 sub-step).
|
|
55
|
+
|
|
56
|
+
- 자기 활동이 fork 회의에서 시작되었는지는 `progress.json.conductor.fork_meeting_id` 와 `progress.json.conductor.tracks[]` 에서 확인.
|
|
57
|
+
- 형제 트랙 (예: `track-1: cto/bugfix`) 의 진행은 차단 사유가 아니다 — 평행 진행.
|
|
58
|
+
- 완료 시 spike/observations 경로를 documentationer 에 인계 (Planner 가 이어 받아 followup-review 산출물 `validation-report` 로 정리).
|
|
59
|
+
- followup-review 결정 = `apply-now` 면 spike 경로의 일부가 sprint artifact 로 승격될 수 있다. 그 시점부터는 정규 Generator 가 다시 짠다 — Hypothesis Cell 코드는 SoT 가 아니다.
|
|
60
|
+
|
|
61
|
+
## 7. Session Boundary
|
|
62
|
+
|
|
63
|
+
### On Start
|
|
64
|
+
1. `.harness/progress.json` 읽기 — `conductor.fork_meeting_id` / `conductor.tracks[]` / `planner.last_brief` 확인
|
|
65
|
+
2. 자기 트랙 식별 (owner 가 `coo-developer` 또는 `planner` + brief=`hypothesis:experiment`)
|
|
66
|
+
3. `.harness/conventions/coo-developer.md`, `.harness/gotchas/coo-developer.md` 읽기
|
|
67
|
+
4. (v6.2) parallel 모드면 형제 트랙 owner 의 gotcha 도 한 번 훑기
|
|
68
|
+
|
|
69
|
+
### On Complete
|
|
70
|
+
1. `actions/hypothesis/<id>/` 산출물 경로 확정
|
|
71
|
+
2. partial update: `coo_developer.last_spike_path`, `agent_status = "completed"`
|
|
72
|
+
3. `next_agent = "documentationer"` (Planner 의 hypothesis 흐름이 활성화된 경우)
|
|
@@ -22,25 +22,71 @@ disable-model-invocation: false
|
|
|
22
22
|
- `coo-developer`의 실험 결과
|
|
23
23
|
- 필요 시 외부 리서치 소스
|
|
24
24
|
|
|
25
|
-
## 3. 출력
|
|
25
|
+
## 3. 출력 (산출물 경로 표준)
|
|
26
26
|
|
|
27
|
-
- `hypothesis-brief`
|
|
28
|
-
- `validation-report`
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
- `.harness/actions/hypothesis/<id>/brief.md` — `hypothesis-brief` (사전 리서치)
|
|
28
|
+
- `.harness/actions/hypothesis/<id>/report.md` — `validation-report` (실험 후 통합)
|
|
29
|
+
- `.harness/actions/hypothesis/<id>/evidence/` — 출처 스냅샷·캡처
|
|
30
|
+
- `.harness/actions/hypothesis/<id>/verdict.json` — 기계 판독 판정
|
|
31
|
+
|
|
32
|
+
`verdict.json` 스키마 (필수 필드):
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"id": "H-...",
|
|
37
|
+
"hypothesis": "한 줄 가설",
|
|
38
|
+
"verdict": "supported | refuted | inconclusive",
|
|
39
|
+
"confidence": 0.0,
|
|
40
|
+
"key_evidence": [
|
|
41
|
+
{"source": "actions/hypothesis/<id>/spike/run.log", "kind": "experiment"},
|
|
42
|
+
{"source": "https://...", "kind": "external-research"}
|
|
43
|
+
],
|
|
44
|
+
"next_action": "promote-to-sprint | additional-experiment | discard",
|
|
45
|
+
"rationale": "왜 이 판정인가 — evidence 와 직접 연결",
|
|
46
|
+
"open_questions": ["..."],
|
|
47
|
+
"limitations": ["..."]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`progress.json` 업데이트 (완료 시):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bash scripts/harness-progress-set.sh . \
|
|
55
|
+
'.documentationer.last_report = "actions/hypothesis/<id>/report.md" |
|
|
56
|
+
.documentationer.last_verdict = "actions/hypothesis/<id>/verdict.json"'
|
|
57
|
+
```
|
|
34
58
|
|
|
35
59
|
## 4. 보고서 규칙
|
|
36
60
|
|
|
37
61
|
- 결론 먼저, 근거 다음
|
|
38
|
-
- 주장마다 evidence 출처 명시
|
|
62
|
+
- 주장마다 evidence 출처 명시 (URI 또는 artifact path)
|
|
39
63
|
- 운영팀/CTO/CQO가 바로 이어받을 수 있게 열린 질문을 분리
|
|
40
64
|
- "왜 아직 확실하지 않은가"도 반드시 적을 것
|
|
65
|
+
- `verdict` 는 supported / refuted / inconclusive 셋 중 하나로만 — 모호한 표현 금지
|
|
41
66
|
|
|
42
67
|
## 5. 금지
|
|
43
68
|
|
|
44
69
|
- evidence 없는 유효 판정
|
|
45
70
|
- CTO/CQO 검증을 대신했다고 표현
|
|
46
71
|
- 실험 로그를 정제 없이 그대로 보고서로 제출
|
|
72
|
+
|
|
73
|
+
## 6. Parallel-Tracks 컨텍스트 (v6.2)
|
|
74
|
+
|
|
75
|
+
`validation-report` 는 followup-review 회의에서 **track deliverable** 로 사용된다.
|
|
76
|
+
|
|
77
|
+
- followup-review 의 Meeting-Manager 는 `prior_tracks[].deliverable_path` 를 통해 이 보고서를 읽는다.
|
|
78
|
+
- `verdict.json` 의 `next_action` 이 `promote-to-sprint` 면 followup 결정은 보통 `apply-now`, `additional-experiment` 면 `more-validation`, `discard` 면 `backlog` 또는 종결.
|
|
79
|
+
- 형제 트랙 (예: `track-1: cto/bugfix`) 의 결과는 별도 deliverable. 둘을 함께 보고 결정자(CTO 또는 CEO) 가 통합한다 — Documentationer 가 직접 통합 결정을 내리지 않는다.
|
|
80
|
+
|
|
81
|
+
## 7. Session Boundary
|
|
82
|
+
|
|
83
|
+
### On Start
|
|
84
|
+
1. `.harness/progress.json` 읽기 — `planner.last_brief` 확인 (`hypothesis:research` 또는 `hypothesis:report`)
|
|
85
|
+
2. brief=research 면 `brief.md` 작성, brief=report 면 `coo-developer` 의 spike 결과를 `report.md` 로 통합
|
|
86
|
+
3. `.harness/conventions/documentationer.md`, `.harness/gotchas/documentationer.md` 읽기
|
|
87
|
+
4. (v6.2) parallel 모드면 형제 트랙 owner 의 gotcha 도 한 번 훑기
|
|
88
|
+
|
|
89
|
+
### On Complete
|
|
90
|
+
1. `report.md` + `verdict.json` 작성 완료
|
|
91
|
+
2. partial update: `documentationer.last_report`, `documentationer.last_verdict`, `agent_status = "completed"`
|
|
92
|
+
3. `next_agent = "planner"` (hypothesis-verdict 종합)
|