@walwal-harness/cli 6.0.3 → 6.0.5

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.
@@ -1,393 +0,0 @@
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