@walwal-harness/cli 6.0.5 → 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 +42 -0
- 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 +123 -0
- package/scripts/harness-dashboard-up.sh +72 -0
- package/scripts/harness-dashboard.sh +509 -0
- package/scripts/harness-goal-init.sh +72 -0
- package/scripts/harness-goal-show.sh +37 -0
- package/scripts/harness-gotcha-memory.sh +348 -0
- package/scripts/harness-gotcha-register.sh +393 -0
- package/scripts/harness-meeting-doc.sh +153 -0
- package/scripts/harness-monitor.sh +398 -0
- package/scripts/harness-next.sh +577 -0
- package/scripts/harness-progress-set.sh +41 -0
- package/scripts/harness-prompt-history.sh +164 -0
- package/scripts/harness-queue-manager.sh +694 -0
- package/scripts/harness-session-start.sh +140 -0
- package/scripts/harness-statusline.sh +139 -0
- package/scripts/harness-task-session.sh +48 -0
- package/scripts/harness-tmux.sh +372 -0
- package/scripts/harness-user-prompt-submit.sh +123 -0
- package/scripts/init-agents-md.sh +337 -0
- package/scripts/init-ref-docs.sh +262 -0
- package/scripts/lib/harness-audit.sh +113 -0
- package/scripts/lib/harness-guardrail.sh +148 -0
- package/scripts/lib/harness-keywait.sh +57 -0
- package/scripts/lib/harness-render-progress.sh +346 -0
- package/scripts/scan-project.sh +367 -0
- 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,393 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-gotcha-register.sh — Evaluator → Gotcha 자동 등록 파이프라인
|
|
3
|
+
#
|
|
4
|
+
# 사용법:
|
|
5
|
+
# 1) 단일 항목 등록 (수동/스크립트):
|
|
6
|
+
# bash harness-gotcha-register.sh <project-root> \
|
|
7
|
+
# --target <agent> \
|
|
8
|
+
# --rule-id <rule-id> \
|
|
9
|
+
# --title "제목" \
|
|
10
|
+
# --wrong "잘못된 행동" \
|
|
11
|
+
# --right "올바른 행동" \
|
|
12
|
+
# --why "근거" \
|
|
13
|
+
# --scope "적용 범위" \
|
|
14
|
+
# --source "evaluator-functional:F-003"
|
|
15
|
+
#
|
|
16
|
+
# 2) 일괄 등록 (JSON stdin/파일):
|
|
17
|
+
# bash harness-gotcha-register.sh <project-root> --from-json <path>
|
|
18
|
+
# bash harness-gotcha-register.sh <project-root> --scan-evaluations
|
|
19
|
+
# └ .harness/actions/evaluation-*.md 의 ```gotcha_candidates``` 블록 전부 스캔
|
|
20
|
+
#
|
|
21
|
+
# 동작:
|
|
22
|
+
# - 대상 파일: .harness/gotchas/<target>.md (없으면 생성)
|
|
23
|
+
# - 중복 감지: 동일 rule_id 가 있으면 Occurrences +1, last_seen 갱신 (본문 수정 없음)
|
|
24
|
+
# - 신규: 다음 G-NNN 할당, Status: unverified 로 기록
|
|
25
|
+
# - 등록 성공 로그: .harness/progress.log 에 한 줄 append
|
|
26
|
+
#
|
|
27
|
+
# 등록 형식:
|
|
28
|
+
# ### [G-NNN] <title> <!-- rule_id: <rule-id> -->
|
|
29
|
+
# - **Status**: unverified
|
|
30
|
+
# - **Date**: YYYY-MM-DD
|
|
31
|
+
# - **Source**: <source> (예: "evaluator-functional:F-003")
|
|
32
|
+
# - **Trigger**: Eval 자동 감지
|
|
33
|
+
# - **Wrong**: <wrong>
|
|
34
|
+
# - **Right**: <right>
|
|
35
|
+
# - **Why**: <why>
|
|
36
|
+
# - **Scope**: <scope>
|
|
37
|
+
# - **Occurrences**: 1
|
|
38
|
+
# - **Last-Seen**: YYYY-MM-DD
|
|
39
|
+
|
|
40
|
+
set -euo pipefail
|
|
41
|
+
|
|
42
|
+
if [ $# -lt 1 ]; then
|
|
43
|
+
echo "usage: harness-gotcha-register.sh <project-root> [options]" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
PROJECT_ROOT="$1"
|
|
48
|
+
shift
|
|
49
|
+
PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"
|
|
50
|
+
|
|
51
|
+
command -v jq >/dev/null 2>&1 || { echo "[gotcha-register] ERROR: jq required" >&2; exit 1; }
|
|
52
|
+
|
|
53
|
+
GOTCHAS_DIR="$PROJECT_ROOT/.harness/gotchas"
|
|
54
|
+
ACTIONS_DIR="$PROJECT_ROOT/.harness/actions"
|
|
55
|
+
mkdir -p "$GOTCHAS_DIR"
|
|
56
|
+
|
|
57
|
+
TODAY="$(date +%Y-%m-%d)"
|
|
58
|
+
|
|
59
|
+
# ─────────────────────────────────────────
|
|
60
|
+
# 단일 항목을 .harness/gotchas/<target>.md 에 append
|
|
61
|
+
# $1: target agent (generator-backend, generator-frontend, evaluator-functional, ...)
|
|
62
|
+
# $2: rule_id (dedup key)
|
|
63
|
+
# $3: title
|
|
64
|
+
# $4: wrong
|
|
65
|
+
# $5: right
|
|
66
|
+
# $6: why
|
|
67
|
+
# $7: scope
|
|
68
|
+
# $8: source
|
|
69
|
+
# ─────────────────────────────────────────
|
|
70
|
+
register_one() {
|
|
71
|
+
local target="$1" rule_id="$2" title="$3" wrong="$4" right="$5" why="$6" scope="$7" source="$8"
|
|
72
|
+
local file="$GOTCHAS_DIR/${target}.md"
|
|
73
|
+
|
|
74
|
+
# Ensure file exists with header
|
|
75
|
+
if [ ! -f "$file" ]; then
|
|
76
|
+
cat > "$file" <<EOF
|
|
77
|
+
# Gotchas — $target
|
|
78
|
+
|
|
79
|
+
> Dispatcher + Evaluator 가 관리. $target 은 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
|
|
80
|
+
> 신규 항목은 \`Status: unverified\` 로 기록되고, Planner 리뷰 후 \`verified\` 로 승격됩니다.
|
|
81
|
+
|
|
82
|
+
EOF
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Dedup by rule_id marker comment
|
|
86
|
+
if grep -qE "<!-- rule_id: ${rule_id} -->" "$file" 2>/dev/null; then
|
|
87
|
+
# Bump Occurrences + Last-Seen for matching block
|
|
88
|
+
awk -v rid="$rule_id" -v today="$TODAY" '
|
|
89
|
+
BEGIN { in_block = 0 }
|
|
90
|
+
/^### \[G-[0-9]+\].*<!-- rule_id: / {
|
|
91
|
+
in_block = (index($0, "rule_id: " rid " ") > 0 || index($0, "rule_id: " rid "\n") > 0 || $0 ~ ("rule_id: " rid " -->"))
|
|
92
|
+
}
|
|
93
|
+
in_block && /^- \*\*Occurrences\*\*:/ {
|
|
94
|
+
n = $NF + 0
|
|
95
|
+
print "- **Occurrences**: " (n + 1)
|
|
96
|
+
next
|
|
97
|
+
}
|
|
98
|
+
in_block && /^- \*\*Last-Seen\*\*:/ {
|
|
99
|
+
print "- **Last-Seen**: " today
|
|
100
|
+
next
|
|
101
|
+
}
|
|
102
|
+
/^### / && !/<!-- rule_id: / { in_block = 0 }
|
|
103
|
+
{ print }
|
|
104
|
+
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
|
105
|
+
echo "[gotcha-register] $target: dedup $rule_id (occurrence bumped)"
|
|
106
|
+
return 0
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Allocate next G-NNN (|| true — pipefail-safe when no existing entries)
|
|
110
|
+
local next_num=""
|
|
111
|
+
next_num=$(grep -oE '^### \[G-[0-9]+\]' "$file" 2>/dev/null | grep -oE '[0-9]+' | sort -n | tail -1 || true)
|
|
112
|
+
next_num=$((${next_num:-0} + 1))
|
|
113
|
+
local g_id
|
|
114
|
+
g_id=$(printf 'G-%03d' "$next_num")
|
|
115
|
+
|
|
116
|
+
# Append new block
|
|
117
|
+
{
|
|
118
|
+
echo ""
|
|
119
|
+
echo "### [$g_id] $title <!-- rule_id: $rule_id -->"
|
|
120
|
+
echo "- **Status**: unverified"
|
|
121
|
+
echo "- **Date**: $TODAY"
|
|
122
|
+
echo "- **Source**: $source"
|
|
123
|
+
echo "- **Trigger**: Eval 자동 감지"
|
|
124
|
+
echo "- **Wrong**: $wrong"
|
|
125
|
+
echo "- **Right**: $right"
|
|
126
|
+
echo "- **Why**: $why"
|
|
127
|
+
echo "- **Scope**: $scope"
|
|
128
|
+
echo "- **Occurrences**: 1"
|
|
129
|
+
echo "- **Last-Seen**: $TODAY"
|
|
130
|
+
} >> "$file"
|
|
131
|
+
|
|
132
|
+
echo "[gotcha-register] $target: registered $g_id ($rule_id) — unverified"
|
|
133
|
+
|
|
134
|
+
# Log to progress.log if present
|
|
135
|
+
local progress_log="$PROJECT_ROOT/.harness/progress.log"
|
|
136
|
+
if [ -f "$progress_log" ]; then
|
|
137
|
+
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) [gotcha] $target $g_id unverified — $title (source: $source)" >> "$progress_log"
|
|
138
|
+
fi
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# ─────────────────────────────────────────
|
|
142
|
+
# JSON 배열에서 일괄 등록
|
|
143
|
+
# schema: [{ target, rule_id, title, wrong, right, why, scope, source }]
|
|
144
|
+
# ─────────────────────────────────────────
|
|
145
|
+
register_from_json() {
|
|
146
|
+
local json="$1"
|
|
147
|
+
local count
|
|
148
|
+
count=$(echo "$json" | jq 'length' 2>/dev/null || echo 0)
|
|
149
|
+
if [ "$count" -eq 0 ]; then
|
|
150
|
+
return 0
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
local i
|
|
154
|
+
for ((i=0; i<count; i++)); do
|
|
155
|
+
local t r ti w ri wh sc so
|
|
156
|
+
t=$(echo "$json" | jq -r ".[$i].target // empty")
|
|
157
|
+
r=$(echo "$json" | jq -r ".[$i].rule_id // empty")
|
|
158
|
+
ti=$(echo "$json" | jq -r ".[$i].title // empty")
|
|
159
|
+
w=$(echo "$json" | jq -r ".[$i].wrong // empty")
|
|
160
|
+
ri=$(echo "$json" | jq -r ".[$i].right // empty")
|
|
161
|
+
wh=$(echo "$json" | jq -r ".[$i].why // empty")
|
|
162
|
+
sc=$(echo "$json" | jq -r ".[$i].scope // \"항상\"")
|
|
163
|
+
so=$(echo "$json" | jq -r ".[$i].source // \"evaluator:auto\"")
|
|
164
|
+
|
|
165
|
+
if [ -z "$t" ] || [ -z "$r" ] || [ -z "$ti" ]; then
|
|
166
|
+
echo "[gotcha-register] skip: missing target/rule_id/title at index $i" >&2
|
|
167
|
+
continue
|
|
168
|
+
fi
|
|
169
|
+
register_one "$t" "$r" "$ti" "$w" "$ri" "$wh" "$sc" "$so"
|
|
170
|
+
done
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# ─────────────────────────────────────────
|
|
174
|
+
# Convention 등록 — .harness/conventions/<scope>.md 에 [C-NNN] entry append
|
|
175
|
+
# $1: scope (shared, generator-backend, generator-frontend, ... — 파일명 base)
|
|
176
|
+
# $2: rule_id (dedup key)
|
|
177
|
+
# $3: title
|
|
178
|
+
# $4: rule (긍정 가이드 본문 — "X 는 항상 Y 로")
|
|
179
|
+
# $5: why
|
|
180
|
+
# $6: source
|
|
181
|
+
# ─────────────────────────────────────────
|
|
182
|
+
CONVENTIONS_DIR="$PROJECT_ROOT/.harness/conventions"
|
|
183
|
+
register_convention_one() {
|
|
184
|
+
local scope="$1" rule_id="$2" title="$3" rule="$4" why="$5" source="$6"
|
|
185
|
+
mkdir -p "$CONVENTIONS_DIR"
|
|
186
|
+
local file="$CONVENTIONS_DIR/${scope}.md"
|
|
187
|
+
|
|
188
|
+
if [ ! -f "$file" ]; then
|
|
189
|
+
cat > "$file" <<EOF
|
|
190
|
+
# Conventions — ${scope}
|
|
191
|
+
|
|
192
|
+
> Dispatcher가 관리. 긍정 가이드("~를 사용해", "항상 ~") 자동 등록.
|
|
193
|
+
|
|
194
|
+
EOF
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# dedup: 같은 rule_id 가 있으면 해당 [C-NNN] 블록의 Occurrences +1, Last-Seen 갱신
|
|
198
|
+
if grep -qE "<!-- rule_id: ${rule_id} -->" "$file" 2>/dev/null; then
|
|
199
|
+
awk -v rid="$rule_id" -v today="$TODAY" '
|
|
200
|
+
BEGIN { in_block = 0 }
|
|
201
|
+
/^### \[C-[0-9]+\].*<!-- rule_id: / {
|
|
202
|
+
in_block = ($0 ~ ("rule_id: " rid " -->"))
|
|
203
|
+
}
|
|
204
|
+
in_block && /^- \*\*Occurrences\*\*:/ {
|
|
205
|
+
n = $NF + 0
|
|
206
|
+
print "- **Occurrences**: " (n + 1)
|
|
207
|
+
next
|
|
208
|
+
}
|
|
209
|
+
in_block && /^- \*\*Last-Seen\*\*:/ {
|
|
210
|
+
print "- **Last-Seen**: " today
|
|
211
|
+
next
|
|
212
|
+
}
|
|
213
|
+
/^### / && !/<!-- rule_id: / { in_block = 0 }
|
|
214
|
+
{ print }
|
|
215
|
+
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
|
216
|
+
echo "[gotcha-register] convention/${scope}: dedup ${rule_id} (occurrence bumped)"
|
|
217
|
+
return 0
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# 신규 — 다음 [C-NNN] 번호 할당 (set -e 회피: || true)
|
|
221
|
+
local last_n
|
|
222
|
+
last_n=$(grep -oE '\[C-[0-9]+\]' "$file" 2>/dev/null | grep -oE '[0-9]+' | sort -n | tail -1 || true)
|
|
223
|
+
last_n=${last_n:-0}
|
|
224
|
+
local next_n=$(printf "%03d" $((last_n + 1)))
|
|
225
|
+
|
|
226
|
+
cat >> "$file" <<EOF
|
|
227
|
+
|
|
228
|
+
### [C-${next_n}] ${title} <!-- rule_id: ${rule_id} -->
|
|
229
|
+
- **Status**: unverified
|
|
230
|
+
- **Date**: ${TODAY}
|
|
231
|
+
- **Source**: ${source}
|
|
232
|
+
- **Rule**: ${rule}
|
|
233
|
+
- **Why**: ${why}
|
|
234
|
+
- **Occurrences**: 1
|
|
235
|
+
- **Last-Seen**: ${TODAY}
|
|
236
|
+
EOF
|
|
237
|
+
|
|
238
|
+
echo "[gotcha-register] convention/${scope}: registered C-${next_n} (${rule_id}) — unverified"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
register_conventions_from_json() {
|
|
242
|
+
local json="$1"
|
|
243
|
+
local count
|
|
244
|
+
count=$(echo "$json" | jq 'length' 2>/dev/null || echo 0)
|
|
245
|
+
[ "$count" -eq 0 ] && return 0
|
|
246
|
+
local i
|
|
247
|
+
for ((i=0; i<count; i++)); do
|
|
248
|
+
local sc r ti ru wh so
|
|
249
|
+
sc=$(echo "$json" | jq -r ".[$i].scope // \"shared\"")
|
|
250
|
+
r=$(echo "$json" | jq -r ".[$i].rule_id // empty")
|
|
251
|
+
ti=$(echo "$json" | jq -r ".[$i].title // empty")
|
|
252
|
+
ru=$(echo "$json" | jq -r ".[$i].rule // empty")
|
|
253
|
+
wh=$(echo "$json" | jq -r ".[$i].why // empty")
|
|
254
|
+
so=$(echo "$json" | jq -r ".[$i].source // \"agent:auto\"")
|
|
255
|
+
if [ -z "$r" ] || [ -z "$ti" ] || [ -z "$ru" ]; then
|
|
256
|
+
echo "[gotcha-register] convention skip: missing rule_id/title/rule at index $i" >&2
|
|
257
|
+
continue
|
|
258
|
+
fi
|
|
259
|
+
register_convention_one "$sc" "$r" "$ti" "$ru" "$wh" "$so"
|
|
260
|
+
done
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# ─────────────────────────────────────────
|
|
264
|
+
# 단일 파일에서 fenced block 추출 (block_tag 인자로 종류 지정)
|
|
265
|
+
# $1: file path · $2: block tag (gotcha_candidates 또는 convention_candidates)
|
|
266
|
+
# stdout 으로 각 block JSON 을 한 줄씩 출력 (\0 구분자)
|
|
267
|
+
# ─────────────────────────────────────────
|
|
268
|
+
extract_blocks() {
|
|
269
|
+
local file="$1" tag="$2"
|
|
270
|
+
local blocks_prefix
|
|
271
|
+
blocks_prefix=$(mktemp -d)
|
|
272
|
+
awk -v dir="$blocks_prefix" -v tag="$tag" '
|
|
273
|
+
BEGIN { idx=0; flag=0; buf="" }
|
|
274
|
+
$0 ~ ("^```" tag "[[:space:]]*$") { flag=1; buf=""; next }
|
|
275
|
+
/^```[[:space:]]*$/ && flag {
|
|
276
|
+
flag=0
|
|
277
|
+
idx++
|
|
278
|
+
outfile = sprintf("%s/block-%03d.json", dir, idx)
|
|
279
|
+
print buf > outfile
|
|
280
|
+
close(outfile)
|
|
281
|
+
buf=""
|
|
282
|
+
next
|
|
283
|
+
}
|
|
284
|
+
flag { buf = buf $0 "\n" }
|
|
285
|
+
' "$file"
|
|
286
|
+
echo "$blocks_prefix"
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
process_blocks_in_file() {
|
|
290
|
+
local file="$1"
|
|
291
|
+
# gotcha_candidates
|
|
292
|
+
local g_dir
|
|
293
|
+
g_dir=$(extract_blocks "$file" "gotcha_candidates")
|
|
294
|
+
local b
|
|
295
|
+
for b in "$g_dir"/block-*.json; do
|
|
296
|
+
[ -f "$b" ] || continue
|
|
297
|
+
if jq empty "$b" 2>/dev/null; then
|
|
298
|
+
register_from_json "$(cat "$b")"
|
|
299
|
+
else
|
|
300
|
+
echo "[gotcha-register] skip: invalid gotcha_candidates JSON in $(basename "$file")" >&2
|
|
301
|
+
fi
|
|
302
|
+
done
|
|
303
|
+
rm -rf "$g_dir"
|
|
304
|
+
|
|
305
|
+
# convention_candidates
|
|
306
|
+
local c_dir
|
|
307
|
+
c_dir=$(extract_blocks "$file" "convention_candidates")
|
|
308
|
+
for b in "$c_dir"/block-*.json; do
|
|
309
|
+
[ -f "$b" ] || continue
|
|
310
|
+
if jq empty "$b" 2>/dev/null; then
|
|
311
|
+
register_conventions_from_json "$(cat "$b")"
|
|
312
|
+
else
|
|
313
|
+
echo "[gotcha-register] skip: invalid convention_candidates JSON in $(basename "$file")" >&2
|
|
314
|
+
fi
|
|
315
|
+
done
|
|
316
|
+
rm -rf "$c_dir"
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
# ─────────────────────────────────────────
|
|
320
|
+
# evaluation-*.md 스캔 — ```gotcha_candidates``` + ```convention_candidates``` 둘 다
|
|
321
|
+
# ─────────────────────────────────────────
|
|
322
|
+
scan_evaluations() {
|
|
323
|
+
[ -d "$ACTIONS_DIR" ] || return 0
|
|
324
|
+
local f
|
|
325
|
+
for f in "$ACTIONS_DIR"/evaluation-*.md; do
|
|
326
|
+
[ -f "$f" ] || continue
|
|
327
|
+
process_blocks_in_file "$f"
|
|
328
|
+
done
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
# ─────────────────────────────────────────
|
|
332
|
+
# 모든 worker report 스캔 — evaluation-*.md + gen-report-*.md + lead-report-*.md
|
|
333
|
+
# Generator 는 gen-report-{F-ID}.md 작성, Lead 는 lead-report-{date}.md 작성 가능
|
|
334
|
+
# ─────────────────────────────────────────
|
|
335
|
+
scan_all() {
|
|
336
|
+
[ -d "$ACTIONS_DIR" ] || return 0
|
|
337
|
+
local f
|
|
338
|
+
for f in "$ACTIONS_DIR"/evaluation-*.md "$ACTIONS_DIR"/gen-report-*.md "$ACTIONS_DIR"/lead-report-*.md; do
|
|
339
|
+
[ -f "$f" ] || continue
|
|
340
|
+
process_blocks_in_file "$f"
|
|
341
|
+
done
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
# ─────────────────────────────────────────
|
|
345
|
+
# Parse CLI
|
|
346
|
+
# ─────────────────────────────────────────
|
|
347
|
+
TARGET=""
|
|
348
|
+
RULE_ID=""
|
|
349
|
+
TITLE=""
|
|
350
|
+
WRONG=""
|
|
351
|
+
RIGHT=""
|
|
352
|
+
WHY=""
|
|
353
|
+
SCOPE="항상"
|
|
354
|
+
SOURCE="evaluator:auto"
|
|
355
|
+
MODE="single"
|
|
356
|
+
FROM_JSON=""
|
|
357
|
+
|
|
358
|
+
while [ $# -gt 0 ]; do
|
|
359
|
+
case "$1" in
|
|
360
|
+
--target) TARGET="$2"; shift 2 ;;
|
|
361
|
+
--rule-id) RULE_ID="$2"; shift 2 ;;
|
|
362
|
+
--title) TITLE="$2"; shift 2 ;;
|
|
363
|
+
--wrong) WRONG="$2"; shift 2 ;;
|
|
364
|
+
--right) RIGHT="$2"; shift 2 ;;
|
|
365
|
+
--why) WHY="$2"; shift 2 ;;
|
|
366
|
+
--scope) SCOPE="$2"; shift 2 ;;
|
|
367
|
+
--source) SOURCE="$2"; shift 2 ;;
|
|
368
|
+
--from-json) MODE="json"; FROM_JSON="$2"; shift 2 ;;
|
|
369
|
+
--scan-evaluations) MODE="scan"; shift ;;
|
|
370
|
+
--scan-all) MODE="scan-all"; shift ;;
|
|
371
|
+
*) echo "[gotcha-register] unknown arg: $1" >&2; exit 1 ;;
|
|
372
|
+
esac
|
|
373
|
+
done
|
|
374
|
+
|
|
375
|
+
case "$MODE" in
|
|
376
|
+
single)
|
|
377
|
+
if [ -z "$TARGET" ] || [ -z "$RULE_ID" ] || [ -z "$TITLE" ]; then
|
|
378
|
+
echo "[gotcha-register] usage: --target X --rule-id Y --title Z [...]" >&2
|
|
379
|
+
exit 1
|
|
380
|
+
fi
|
|
381
|
+
register_one "$TARGET" "$RULE_ID" "$TITLE" "$WRONG" "$RIGHT" "$WHY" "$SCOPE" "$SOURCE"
|
|
382
|
+
;;
|
|
383
|
+
json)
|
|
384
|
+
if [ ! -f "$FROM_JSON" ]; then echo "[gotcha-register] file not found: $FROM_JSON" >&2; exit 1; fi
|
|
385
|
+
register_from_json "$(cat "$FROM_JSON")"
|
|
386
|
+
;;
|
|
387
|
+
scan)
|
|
388
|
+
scan_evaluations
|
|
389
|
+
;;
|
|
390
|
+
scan-all)
|
|
391
|
+
scan_all
|
|
392
|
+
;;
|
|
393
|
+
esac
|
|
@@ -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
|