@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +306 -367
  3. package/assets/templates/config.json +7 -0
  4. package/assets/templates/progress.json.template +13 -0
  5. package/gotchas/README.md +40 -8
  6. package/gotchas/conductor.md +12 -82
  7. package/gotchas/coo-developer.md +21 -12
  8. package/gotchas/cqo.md +22 -0
  9. package/gotchas/cto.md +22 -0
  10. package/gotchas/dispatcher.md +12 -87
  11. package/gotchas/documentationer.md +21 -12
  12. package/gotchas/evaluator-architecture.md +22 -0
  13. package/gotchas/evaluator-code-quality.md +11 -20
  14. package/gotchas/evaluator-functional.md +10 -30
  15. package/gotchas/evaluator-security.md +22 -0
  16. package/gotchas/evaluator-visual.md +10 -19
  17. package/gotchas/generator-backend-laravel.md +11 -77
  18. package/gotchas/generator-backend.md +19 -2
  19. package/gotchas/generator-designer.md +22 -0
  20. package/gotchas/generator-devops.md +22 -0
  21. package/gotchas/generator-frontend.md +13 -22
  22. package/gotchas/meeting-manager.md +22 -0
  23. package/gotchas/planner.md +19 -2
  24. package/gotchas/service-ops.md +13 -18
  25. package/package.json +1 -1
  26. package/scripts/conductor-tick.sh +167 -21
  27. package/scripts/harness-meeting-doc.sh +146 -2
  28. package/scripts/harness-session-start.sh +61 -0
  29. package/scripts/harness-token-limit.sh +99 -0
  30. package/scripts/harness-user-prompt-submit.sh +23 -0
  31. package/scripts/lib/harness-progress-migrate.sh +58 -0
  32. package/skills/conductor/SKILL.md +1 -0
  33. package/skills/coo-developer/SKILL.md +35 -5
  34. package/skills/documentationer/SKILL.md +55 -9
  35. package/skills/meeting-manager/SKILL.md +114 -4
  36. 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
- - "가설 지지 / 반박 / 추가 데이터 필요" 3분류 결론
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
- - `evidence-summary`
30
- - 다음 액션 제안:
31
- - 정규 Sprint로 승격
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 종합)