@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.
- package/CHANGELOG.md +43 -0
- package/bin/init.js +121 -4
- package/gotchas/conductor.md +15 -0
- package/gotchas/generator-frontend.md +27 -1
- package/gotchas/service-ops.md +27 -0
- package/package.json +1 -1
- package/skills/conductor/SKILL.md +28 -0
- package/skills/generator-frontend/SKILL.md +23 -3
- package/scripts/harness-archive.sh +0 -109
- package/scripts/harness-dashboard-up.sh +0 -72
- package/scripts/harness-dashboard.sh +0 -509
- package/scripts/harness-goal-init.sh +0 -72
- package/scripts/harness-goal-show.sh +0 -37
- package/scripts/harness-gotcha-memory.sh +0 -348
- package/scripts/harness-gotcha-register.sh +0 -393
- package/scripts/harness-monitor.sh +0 -398
- package/scripts/harness-next.sh +0 -546
- package/scripts/harness-progress-set.sh +0 -41
- package/scripts/harness-prompt-history.sh +0 -164
- package/scripts/harness-queue-manager.sh +0 -694
- package/scripts/harness-session-start.sh +0 -129
- package/scripts/harness-statusline.sh +0 -139
- package/scripts/harness-tmux.sh +0 -372
- package/scripts/harness-user-prompt-submit.sh +0 -105
- package/scripts/init-agents-md.sh +0 -337
- package/scripts/init-ref-docs.sh +0 -262
- package/scripts/lib/harness-audit.sh +0 -113
- package/scripts/lib/harness-guardrail.sh +0 -148
- package/scripts/lib/harness-keywait.sh +0 -57
- package/scripts/lib/harness-render-progress.sh +0 -346
- package/scripts/scan-project.sh +0 -367
package/scripts/init-ref-docs.sh
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# init-ref-docs.sh — 감지된 스택에 따라 .harness/ref/<role>-<stack>.md 를 준비한다.
|
|
3
|
-
#
|
|
4
|
-
# bash 혼자서는 Claude 의 WebSearch/WebFetch 를 호출할 수 없으므로 이 스크립트는:
|
|
5
|
-
# 1. scan-result.json 에서 감지된 스택 목록을 계산
|
|
6
|
-
# 2. --placeholder 모드: 최소 frontmatter + TODO 본문을 저장 (AC 검증용 · 파이프라인 unblock 용)
|
|
7
|
-
# 3. --claude-prompt 모드: Claude 세션이 실제 WebSearch + WebFetch + synthesis 로 채울 수 있도록 프롬프트 템플릿을 stdout 출력
|
|
8
|
-
# 4. --register 모드: Claude 가 실제 본문을 작성한 뒤 호출해 .generated.json 에 sources / generated_at 을 등록
|
|
9
|
-
#
|
|
10
|
-
# 사용 예:
|
|
11
|
-
# bash scripts/init-ref-docs.sh --dry-run .
|
|
12
|
-
# bash scripts/init-ref-docs.sh --yes --placeholder --stack swift --role fe .
|
|
13
|
-
# bash scripts/init-ref-docs.sh --claude-prompt --stack swift --role fe .
|
|
14
|
-
# bash scripts/init-ref-docs.sh --register --stack swift --role fe --sources "https://a,https://b" .
|
|
15
|
-
set -e
|
|
16
|
-
|
|
17
|
-
# ───── 옵션 파싱 ─────
|
|
18
|
-
MODE="interactive" # interactive | dry-run | placeholder | claude-prompt | register
|
|
19
|
-
YES=false
|
|
20
|
-
REFRESH=false
|
|
21
|
-
STACK=""
|
|
22
|
-
ROLE=""
|
|
23
|
-
SOURCES=""
|
|
24
|
-
PROJECT_ROOT="."
|
|
25
|
-
|
|
26
|
-
while [[ $# -gt 0 ]]; do
|
|
27
|
-
case "$1" in
|
|
28
|
-
--dry-run) MODE="dry-run"; shift ;;
|
|
29
|
-
--placeholder) MODE="placeholder"; shift ;;
|
|
30
|
-
--claude-prompt) MODE="claude-prompt"; shift ;;
|
|
31
|
-
--register) MODE="register"; shift ;;
|
|
32
|
-
--yes) YES=true; shift ;;
|
|
33
|
-
--refresh) REFRESH=true; shift ;;
|
|
34
|
-
--stack) STACK="$2"; shift 2 ;;
|
|
35
|
-
--role) ROLE="$2"; shift 2 ;;
|
|
36
|
-
--sources) SOURCES="$2"; shift 2 ;;
|
|
37
|
-
-h|--help)
|
|
38
|
-
sed -n '2,16p' "$0" | sed 's/^# //; s/^#//'
|
|
39
|
-
exit 0 ;;
|
|
40
|
-
*) PROJECT_ROOT="$1"; shift ;;
|
|
41
|
-
esac
|
|
42
|
-
done
|
|
43
|
-
|
|
44
|
-
SCAN="${PROJECT_ROOT}/.harness/actions/scan-result.json"
|
|
45
|
-
REF_DIR="${PROJECT_ROOT}/.harness/ref"
|
|
46
|
-
META="${REF_DIR}/.generated.json"
|
|
47
|
-
ARCHIVE_DIR="${PROJECT_ROOT}/.harness/archive"
|
|
48
|
-
mkdir -p "$REF_DIR" "$ARCHIVE_DIR"
|
|
49
|
-
[ -f "$META" ] || echo "{}" > "$META"
|
|
50
|
-
|
|
51
|
-
# ───── 감지된 스택 목록 ─────
|
|
52
|
-
detect_stacks() {
|
|
53
|
-
if [ ! -f "$SCAN" ]; then
|
|
54
|
-
echo "ERROR: scan-result.json 이 없습니다. 먼저 bash scripts/scan-project.sh ${PROJECT_ROOT} 실행하세요." >&2
|
|
55
|
-
exit 1
|
|
56
|
-
fi
|
|
57
|
-
local fe be native
|
|
58
|
-
fe=$(jq -r '.tech_stack.frontend' "$SCAN")
|
|
59
|
-
be=$(jq -r '.tech_stack.backend' "$SCAN")
|
|
60
|
-
native=$(jq -r '.tech_stack.is_native_app // false' "$SCAN")
|
|
61
|
-
|
|
62
|
-
# 네이티브 앱은 fe 만 기록 (be 는 null)
|
|
63
|
-
if [ "$fe" != "unknown" ] && [ "$fe" != "null" ]; then
|
|
64
|
-
echo "fe:$fe"
|
|
65
|
-
fi
|
|
66
|
-
if [ "$native" != "true" ] && [ "$be" != "unknown" ] && [ "$be" != "null" ]; then
|
|
67
|
-
echo "be:$be"
|
|
68
|
-
fi
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
# ───── placeholder 본문 ─────
|
|
72
|
-
write_placeholder() {
|
|
73
|
-
local role="$1" stack="$2" path="$3"
|
|
74
|
-
local ts
|
|
75
|
-
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
76
|
-
cat > "$path" <<REF
|
|
77
|
-
---
|
|
78
|
-
docmeta:
|
|
79
|
-
id: ref-${role}-${stack}
|
|
80
|
-
stack: ${stack}
|
|
81
|
-
role: ${role}
|
|
82
|
-
language: ${stack}
|
|
83
|
-
generated_at: ${ts}
|
|
84
|
-
generator: init-ref-docs.sh (placeholder)
|
|
85
|
-
sources: []
|
|
86
|
-
version: 0
|
|
87
|
-
runner:
|
|
88
|
-
dev_command: null
|
|
89
|
-
start_command: null
|
|
90
|
-
install_command: null
|
|
91
|
-
paths:
|
|
92
|
-
source_roots: []
|
|
93
|
-
test_roots: []
|
|
94
|
-
config_files: []
|
|
95
|
-
api:
|
|
96
|
-
base_url: null
|
|
97
|
-
gateway: null
|
|
98
|
-
validation:
|
|
99
|
-
pre_eval_gate: []
|
|
100
|
-
functional_tests: []
|
|
101
|
-
visual:
|
|
102
|
-
enabled: false
|
|
103
|
-
reason: "placeholder — not yet filled by Claude"
|
|
104
|
-
manual_check: "bash init.sh refresh-ref ${role} ${stack} 실행 후 Claude 세션에서 WebSearch + WebFetch 로 채우기"
|
|
105
|
-
anti_pattern_rules: []
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
# Ref — ${stack} (${role})
|
|
109
|
-
|
|
110
|
-
> **Status**: PLACEHOLDER — Claude 가 WebSearch + WebFetch 로 채워야 함.
|
|
111
|
-
> 실행: bash scripts/init-ref-docs.sh --claude-prompt --stack ${stack} --role ${role}
|
|
112
|
-
|
|
113
|
-
## 1. Runner
|
|
114
|
-
TODO
|
|
115
|
-
|
|
116
|
-
## 2. Paths / Source Layout
|
|
117
|
-
TODO
|
|
118
|
-
|
|
119
|
-
## 3. Best Practices
|
|
120
|
-
TODO
|
|
121
|
-
|
|
122
|
-
## 4. Anti-Patterns (→ gotchas 시드 후보)
|
|
123
|
-
TODO
|
|
124
|
-
REF
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
# ───── Claude 용 프롬프트 ─────
|
|
128
|
-
# React 계열 스택 (react / nextjs / vite-react) 일 때는
|
|
129
|
-
# skills/generator-frontend/references/_web-react-legacy/ 의 4개 문서를
|
|
130
|
-
# 로컬 seed 로 프롬프트에 포함시킨다. 이 seed 는 새 ref-docs 작성 시
|
|
131
|
-
# "기존 웹 React 가이드" 참조용이며 WebSearch 결과와 병합된다.
|
|
132
|
-
LEGACY_SEED_DIR="$(dirname "$0")/../skills/generator-frontend/references/_web-react-legacy"
|
|
133
|
-
emit_claude_prompt() {
|
|
134
|
-
local role="$1" stack="$2" path="$3"
|
|
135
|
-
local legacy_hint=""
|
|
136
|
-
if [ "$role" = "fe" ] && [ -d "$LEGACY_SEED_DIR" ]; then
|
|
137
|
-
case "$stack" in
|
|
138
|
-
react|nextjs|vite-react|nuxt|vue|svelte|angular|*web*)
|
|
139
|
-
legacy_hint="
|
|
140
|
-
Local seed (병합 대상 · 이미 검증된 웹/React best practice):
|
|
141
|
-
$(ls "$LEGACY_SEED_DIR" 2>/dev/null | sed 's|^| - '"$LEGACY_SEED_DIR"'/|')
|
|
142
|
-
"
|
|
143
|
-
;;
|
|
144
|
-
esac
|
|
145
|
-
fi
|
|
146
|
-
cat <<PROMPT
|
|
147
|
-
───────────────────────────────────────────────
|
|
148
|
-
Claude 세션에 아래 프롬프트를 전달하세요
|
|
149
|
-
(또는 현재 세션이라면 그대로 실행):
|
|
150
|
-
───────────────────────────────────────────────
|
|
151
|
-
|
|
152
|
-
목표: ${path} 를 생성한다.
|
|
153
|
-
|
|
154
|
-
1. WebSearch 로 다음 쿼리 실행: "${stack} best practices 2025 site:docs.*"
|
|
155
|
-
2. 상위 3개 공식 문서 URL 을 WebFetch 로 가져온다.
|
|
156
|
-
3. 결과를 종합해 다음 스키마로 YAML frontmatter + 본문을 작성한다:
|
|
157
|
-
- docmeta: { id, stack, role, language, generated_at, generator, sources, version }
|
|
158
|
-
- runner: { dev_command, start_command, install_command }
|
|
159
|
-
- paths: { source_roots, test_roots, config_files }
|
|
160
|
-
- api: { base_url, gateway }
|
|
161
|
-
- validation:
|
|
162
|
-
pre_eval_gate: [...]
|
|
163
|
-
functional_tests: [...]
|
|
164
|
-
visual: { enabled, reason, manual_check }
|
|
165
|
-
anti_pattern_rules: [{ id, pattern_type: "grep"|"lint_tool", pattern|tool+args, paths, severity }]
|
|
166
|
-
4. 본문에는 Runner / Paths / Best Practices / Anti-Patterns 섹션 작성.
|
|
167
|
-
5. 완료 후: bash scripts/init-ref-docs.sh --register --stack ${stack} --role ${role} --sources "<url1>,<url2>,<url3>" ${PROJECT_ROOT}
|
|
168
|
-
${legacy_hint}
|
|
169
|
-
───────────────────────────────────────────────
|
|
170
|
-
PROMPT
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
# ───── register: 메타 기록 ─────
|
|
174
|
-
register_meta() {
|
|
175
|
-
local role="$1" stack="$2" sources_csv="$3"
|
|
176
|
-
local ts key sources_json
|
|
177
|
-
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
178
|
-
key="${role}-${stack}"
|
|
179
|
-
sources_json=$(echo "$sources_csv" | awk -F',' 'BEGIN{printf "["} {for(i=1;i<=NF;i++){printf (i>1?",":"") "\"" $i "\""}} END{printf "]"}')
|
|
180
|
-
[ -z "$sources_csv" ] && sources_json="[]"
|
|
181
|
-
jq --arg k "$key" --arg ts "$ts" --argjson s "$sources_json" '.[$k] = {"generated_at": $ts, "sources": $s, "status": "filled"}' "$META" > "${META}.tmp" && mv "${META}.tmp" "$META"
|
|
182
|
-
echo "registered: $key (sources=$(echo "$sources_json" | jq 'length'))"
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
# ───── 한 스택 처리 ─────
|
|
186
|
-
process_one() {
|
|
187
|
-
local role="$1" stack="$2"
|
|
188
|
-
local path="${REF_DIR}/${role}-${stack}.md"
|
|
189
|
-
|
|
190
|
-
if [ -f "$path" ] && ! $REFRESH; then
|
|
191
|
-
echo "skip: $path 이미 존재 (--refresh 로 강제 재생성)"
|
|
192
|
-
return 0
|
|
193
|
-
fi
|
|
194
|
-
if [ -f "$path" ] && $REFRESH; then
|
|
195
|
-
local backup="${ARCHIVE_DIR}/ref-${role}-${stack}-$(date +%Y%m%d%H%M%S).md"
|
|
196
|
-
cp "$path" "$backup"
|
|
197
|
-
echo "archived: $backup"
|
|
198
|
-
fi
|
|
199
|
-
|
|
200
|
-
local answer="y"
|
|
201
|
-
if ! $YES; then
|
|
202
|
-
printf "Generate ref-docs for %s-%s? [y/N] " "$role" "$stack"
|
|
203
|
-
read -r answer </dev/tty || answer="n"
|
|
204
|
-
fi
|
|
205
|
-
case "$answer" in
|
|
206
|
-
y|Y|yes|YES) ;;
|
|
207
|
-
*) echo "skipped: $role-$stack"; return 3 ;;
|
|
208
|
-
esac
|
|
209
|
-
|
|
210
|
-
case "$MODE" in
|
|
211
|
-
placeholder)
|
|
212
|
-
write_placeholder "$role" "$stack" "$path"
|
|
213
|
-
local ts; ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
214
|
-
jq --arg k "${role}-${stack}" --arg ts "$ts" '.[$k] = {"generated_at": $ts, "sources": [], "status": "placeholder"}' "$META" > "${META}.tmp" && mv "${META}.tmp" "$META"
|
|
215
|
-
echo "wrote: $path (placeholder)"
|
|
216
|
-
;;
|
|
217
|
-
claude-prompt)
|
|
218
|
-
emit_claude_prompt "$role" "$stack" "$path"
|
|
219
|
-
;;
|
|
220
|
-
*)
|
|
221
|
-
echo "ERROR: interactive 모드는 --placeholder 또는 --claude-prompt 중 하나를 지정하세요." >&2
|
|
222
|
-
return 1
|
|
223
|
-
;;
|
|
224
|
-
esac
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
# ───── main ─────
|
|
228
|
-
case "$MODE" in
|
|
229
|
-
dry-run)
|
|
230
|
-
echo "detected stacks:"
|
|
231
|
-
detect_stacks | sed 's/^/ - /'
|
|
232
|
-
echo ""
|
|
233
|
-
echo "ref files 예상 경로 (미존재 시 생성 대상):"
|
|
234
|
-
while IFS=':' read -r role stack; do
|
|
235
|
-
path="${REF_DIR}/${role}-${stack}.md"
|
|
236
|
-
if [ -f "$path" ]; then
|
|
237
|
-
echo " [exists] $path"
|
|
238
|
-
else
|
|
239
|
-
echo " [create] $path"
|
|
240
|
-
fi
|
|
241
|
-
done < <(detect_stacks)
|
|
242
|
-
;;
|
|
243
|
-
register)
|
|
244
|
-
[ -z "$STACK" ] || [ -z "$ROLE" ] && { echo "ERROR: --register 는 --stack --role --sources 필요" >&2; exit 1; }
|
|
245
|
-
register_meta "$ROLE" "$STACK" "$SOURCES"
|
|
246
|
-
;;
|
|
247
|
-
placeholder|claude-prompt)
|
|
248
|
-
if [ -n "$STACK" ] && [ -n "$ROLE" ]; then
|
|
249
|
-
process_one "$ROLE" "$STACK"
|
|
250
|
-
else
|
|
251
|
-
# 감지된 전체 스택을 순회
|
|
252
|
-
while IFS=':' read -r role stack; do
|
|
253
|
-
process_one "$role" "$stack"
|
|
254
|
-
done < <(detect_stacks)
|
|
255
|
-
fi
|
|
256
|
-
;;
|
|
257
|
-
interactive)
|
|
258
|
-
echo "ERROR: 모드 지정 필요 — --dry-run | --placeholder | --claude-prompt | --register" >&2
|
|
259
|
-
echo "자세한 사용법: bash $0 --help" >&2
|
|
260
|
-
exit 1
|
|
261
|
-
;;
|
|
262
|
-
esac
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# harness-audit.sh — Audit log 헬퍼
|
|
3
|
-
#
|
|
4
|
-
# 라이프사이클: Planner 시작 → Eval 통과 (1 sprint cycle)
|
|
5
|
-
# 새 Planner/Dispatcher 사이클 시작 시 이전 로그는 archive로 이동.
|
|
6
|
-
#
|
|
7
|
-
# 사용법:
|
|
8
|
-
# source scripts/lib/harness-audit.sh
|
|
9
|
-
# audit_log "gen-backend" "develop" "start" "apps/service-user/" "User 서비스 CRUD 구현"
|
|
10
|
-
# audit_log "eval-func" "review" "complete" "POST /api/auth" "FAIL: 409→400 contract 불일치"
|
|
11
|
-
# audit_log "gen-backend" "handoff" "start" "→eval-functional" ""
|
|
12
|
-
|
|
13
|
-
AUDIT_LOG_FILE=""
|
|
14
|
-
|
|
15
|
-
# ─────────────────────────────────────────
|
|
16
|
-
# init_audit — audit.log 경로 설정 + 헤더 생성
|
|
17
|
-
# Args: $1 = project root
|
|
18
|
-
# ─────────────────────────────────────────
|
|
19
|
-
init_audit() {
|
|
20
|
-
local project_root="${1:-.}"
|
|
21
|
-
AUDIT_LOG_FILE="$project_root/.harness/actions/audit.log"
|
|
22
|
-
|
|
23
|
-
# 디렉토리 보장
|
|
24
|
-
mkdir -p "$(dirname "$AUDIT_LOG_FILE")"
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
# ─────────────────────────────────────────
|
|
28
|
-
# reset_audit — 새 사이클 시작 시 로그 초기화
|
|
29
|
-
# 이전 로그가 있으면 archive로 이동
|
|
30
|
-
# Args: $1 = project root, $2 = sprint number (optional)
|
|
31
|
-
# ─────────────────────────────────────────
|
|
32
|
-
reset_audit() {
|
|
33
|
-
local project_root="${1:-.}"
|
|
34
|
-
local sprint_num="${2:-0}"
|
|
35
|
-
init_audit "$project_root"
|
|
36
|
-
|
|
37
|
-
# 기존 로그가 있으면 archive로 이동
|
|
38
|
-
if [ -f "$AUDIT_LOG_FILE" ] && [ -s "$AUDIT_LOG_FILE" ]; then
|
|
39
|
-
local archive_dir="$project_root/.harness/archive"
|
|
40
|
-
mkdir -p "$archive_dir"
|
|
41
|
-
local ts
|
|
42
|
-
ts=$(date +%Y%m%d_%H%M%S)
|
|
43
|
-
mv "$AUDIT_LOG_FILE" "$archive_dir/audit_s${sprint_num}_${ts}.log"
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
# 새 로그 헤더
|
|
47
|
-
cat > "$AUDIT_LOG_FILE" <<HEADER
|
|
48
|
-
# Harness Audit Log
|
|
49
|
-
# Cycle started: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
50
|
-
# Format: TIMESTAMP | AGENT | ACTION | STATUS | TARGET | DETAIL
|
|
51
|
-
#─────────────────────────────────────────────────────────────────────────
|
|
52
|
-
HEADER
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
# ─────────────────────────────────────────
|
|
56
|
-
# audit_log — 로그 엔트리 추가
|
|
57
|
-
#
|
|
58
|
-
# Args:
|
|
59
|
-
# $1 = agent (planner, gen-backend, gen-frontend, eval-func, eval-visual)
|
|
60
|
-
# $2 = action (develop, review, handoff, plan, gate)
|
|
61
|
-
# $3 = status (start, complete, fail, skip)
|
|
62
|
-
# $4 = target (파일 경로, 엔드포인트, 에이전트명 등)
|
|
63
|
-
# $5 = detail (설명, 결과 등)
|
|
64
|
-
# ─────────────────────────────────────────
|
|
65
|
-
audit_log() {
|
|
66
|
-
if [ -z "$AUDIT_LOG_FILE" ] || [ ! -f "$AUDIT_LOG_FILE" ]; then
|
|
67
|
-
return 0 # audit 미초기화 시 silent pass
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
local agent="${1:-unknown}"
|
|
71
|
-
local action="${2:-unknown}"
|
|
72
|
-
local log_status="${3:-unknown}"
|
|
73
|
-
local target="${4:-}"
|
|
74
|
-
local detail="${5:-}"
|
|
75
|
-
local ts
|
|
76
|
-
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
77
|
-
|
|
78
|
-
# 고정폭 포맷
|
|
79
|
-
printf "%-20s | %-14s | %-8s | %-8s | %-30s | %s\n" \
|
|
80
|
-
"$ts" "$agent" "$action" "$log_status" "$target" "$detail" >> "$AUDIT_LOG_FILE"
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
# ─────────────────────────────────────────
|
|
84
|
-
# audit_handoff — 핸즈오프 기록 (편의 함수)
|
|
85
|
-
# Args: $1=from_agent, $2=to_agent, $3=status(start|complete)
|
|
86
|
-
# ─────────────────────────────────────────
|
|
87
|
-
audit_handoff() {
|
|
88
|
-
audit_log "$1" "handoff" "$3" "→${2}" ""
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
# ─────────────────────────────────────────
|
|
92
|
-
# audit_gate — 게이트 체크 기록 (편의 함수)
|
|
93
|
-
# Args: $1=gate_name, $2=status(pass|fail), $3=detail
|
|
94
|
-
# ─────────────────────────────────────────
|
|
95
|
-
audit_gate() {
|
|
96
|
-
audit_log "system" "gate" "$2" "$1" "$3"
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
# ─────────────────────────────────────────
|
|
100
|
-
# render_audit — audit.log를 읽기 좋게 출력
|
|
101
|
-
# Args: $1 = project root (optional)
|
|
102
|
-
# ─────────────────────────────────────────
|
|
103
|
-
render_audit() {
|
|
104
|
-
local project_root="${1:-.}"
|
|
105
|
-
local log_file="$project_root/.harness/actions/audit.log"
|
|
106
|
-
|
|
107
|
-
if [ ! -f "$log_file" ]; then
|
|
108
|
-
echo " (audit log not yet created)"
|
|
109
|
-
return 0
|
|
110
|
-
fi
|
|
111
|
-
|
|
112
|
-
cat "$log_file"
|
|
113
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# harness-guardrail.sh — 런타임 파일 소유권 검증
|
|
3
|
-
# 에이전트 전환 시 이전 에이전트가 권한 밖 파일을 수정했는지 git diff로 검증.
|
|
4
|
-
#
|
|
5
|
-
# 사용법: source lib/harness-guardrail.sh && verify_file_ownership "$PROJECT_ROOT"
|
|
6
|
-
# 반환값: 0=통과, 1=위반 발견 (위반 목록을 stdout에 출력)
|
|
7
|
-
|
|
8
|
-
# ─────────────────────────────────────────
|
|
9
|
-
# Agent → 허용 경로 매핑
|
|
10
|
-
# ─────────────────────────────────────────
|
|
11
|
-
get_allowed_paths() {
|
|
12
|
-
local agent="$1"
|
|
13
|
-
case "$agent" in
|
|
14
|
-
planner)
|
|
15
|
-
echo "AGENTS.md"
|
|
16
|
-
echo ".harness/actions/"
|
|
17
|
-
echo ".harness/progress.json"
|
|
18
|
-
echo ".harness/progress.log"
|
|
19
|
-
;;
|
|
20
|
-
generator-backend)
|
|
21
|
-
echo "apps/gateway/"
|
|
22
|
-
echo "apps/service-"
|
|
23
|
-
echo "libs/"
|
|
24
|
-
echo ".harness/actions/sprint-contract.md"
|
|
25
|
-
echo ".harness/actions/feature-list.json"
|
|
26
|
-
echo ".harness/progress.json"
|
|
27
|
-
echo "package.json"
|
|
28
|
-
echo "nest-cli.json"
|
|
29
|
-
echo "tsconfig"
|
|
30
|
-
echo "docker-compose"
|
|
31
|
-
;;
|
|
32
|
-
generator-frontend)
|
|
33
|
-
echo "apps/web/"
|
|
34
|
-
echo "apps/flutter/"
|
|
35
|
-
echo "lib/"
|
|
36
|
-
echo ".harness/actions/sprint-contract.md"
|
|
37
|
-
echo ".harness/actions/feature-list.json"
|
|
38
|
-
echo ".harness/progress.json"
|
|
39
|
-
echo "package.json"
|
|
40
|
-
echo "tsconfig"
|
|
41
|
-
echo "pubspec"
|
|
42
|
-
;;
|
|
43
|
-
evaluator-code-quality)
|
|
44
|
-
echo ".harness/actions/evaluation-code-quality.md"
|
|
45
|
-
echo ".harness/progress.json"
|
|
46
|
-
;;
|
|
47
|
-
evaluator-functional)
|
|
48
|
-
echo ".harness/actions/evaluation-functional.md"
|
|
49
|
-
echo ".harness/progress.json"
|
|
50
|
-
echo "tests/"
|
|
51
|
-
echo "test/"
|
|
52
|
-
echo "e2e/"
|
|
53
|
-
echo "playwright"
|
|
54
|
-
;;
|
|
55
|
-
evaluator-visual)
|
|
56
|
-
echo ".harness/actions/evaluation-visual.md"
|
|
57
|
-
echo ".harness/progress.json"
|
|
58
|
-
;;
|
|
59
|
-
dispatcher)
|
|
60
|
-
echo ".harness/"
|
|
61
|
-
;;
|
|
62
|
-
*)
|
|
63
|
-
echo "**" # unknown agent — allow all (safe fallback)
|
|
64
|
-
;;
|
|
65
|
-
esac
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
# ─────────────────────────────────────────
|
|
69
|
-
# verify_file_ownership — git diff 기반 검증
|
|
70
|
-
#
|
|
71
|
-
# Args:
|
|
72
|
-
# $1 — project root
|
|
73
|
-
#
|
|
74
|
-
# Reads:
|
|
75
|
-
# .harness/progress.json (current_agent)
|
|
76
|
-
#
|
|
77
|
-
# Returns: 0=pass, 1=violation found
|
|
78
|
-
# ─────────────────────────────────────────
|
|
79
|
-
verify_file_ownership() {
|
|
80
|
-
local PROJECT_ROOT="$1"
|
|
81
|
-
local PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
82
|
-
|
|
83
|
-
if [ ! -f "$PROGRESS" ]; then return 0; fi
|
|
84
|
-
|
|
85
|
-
local agent
|
|
86
|
-
agent=$(jq -r '.current_agent // "null"' "$PROGRESS" 2>/dev/null)
|
|
87
|
-
if [ "$agent" = "null" ] || [ -z "$agent" ]; then return 0; fi
|
|
88
|
-
|
|
89
|
-
# git이 없으면 skip
|
|
90
|
-
if ! command -v git &>/dev/null; then return 0; fi
|
|
91
|
-
|
|
92
|
-
# 마지막 커밋 이후 변경된 파일 목록
|
|
93
|
-
local -a changed_files
|
|
94
|
-
mapfile -t changed_files < <(cd "$PROJECT_ROOT" && git diff --name-only HEAD 2>/dev/null)
|
|
95
|
-
|
|
96
|
-
# staged 파일도 포함
|
|
97
|
-
local -a staged_files
|
|
98
|
-
mapfile -t staged_files < <(cd "$PROJECT_ROOT" && git diff --name-only --cached 2>/dev/null)
|
|
99
|
-
|
|
100
|
-
# 합치기 (중복 제거)
|
|
101
|
-
local -a all_files
|
|
102
|
-
mapfile -t all_files < <(printf '%s\n' "${changed_files[@]}" "${staged_files[@]}" | sort -u | grep -v '^$')
|
|
103
|
-
|
|
104
|
-
if [ ${#all_files[@]} -eq 0 ]; then return 0; fi
|
|
105
|
-
|
|
106
|
-
# 허용 경로 목록
|
|
107
|
-
local -a allowed
|
|
108
|
-
mapfile -t allowed < <(get_allowed_paths "$agent")
|
|
109
|
-
|
|
110
|
-
# 위반 검사
|
|
111
|
-
local -a violations=()
|
|
112
|
-
for file in "${all_files[@]}"; do
|
|
113
|
-
local is_allowed=false
|
|
114
|
-
for pattern in "${allowed[@]}"; do
|
|
115
|
-
if [[ "$file" == $pattern* ]]; then
|
|
116
|
-
is_allowed=true
|
|
117
|
-
break
|
|
118
|
-
fi
|
|
119
|
-
done
|
|
120
|
-
if [ "$is_allowed" = false ]; then
|
|
121
|
-
violations+=("$file")
|
|
122
|
-
fi
|
|
123
|
-
done
|
|
124
|
-
|
|
125
|
-
if [ ${#violations[@]} -eq 0 ]; then
|
|
126
|
-
return 0
|
|
127
|
-
fi
|
|
128
|
-
|
|
129
|
-
# 위반 보고
|
|
130
|
-
echo ""
|
|
131
|
-
echo " ── Guardrail Violation ────────────────"
|
|
132
|
-
echo " Agent '$agent' modified files outside its allowed paths:"
|
|
133
|
-
echo ""
|
|
134
|
-
for v in "${violations[@]}"; do
|
|
135
|
-
echo " ✗ $v"
|
|
136
|
-
done
|
|
137
|
-
echo ""
|
|
138
|
-
echo " Allowed paths for $agent:"
|
|
139
|
-
for p in "${allowed[@]}"; do
|
|
140
|
-
echo " ✓ $p*"
|
|
141
|
-
done
|
|
142
|
-
echo ""
|
|
143
|
-
echo " Action: Review these changes before proceeding."
|
|
144
|
-
echo " ─────────────────────────────────────────"
|
|
145
|
-
echo ""
|
|
146
|
-
|
|
147
|
-
return 1
|
|
148
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# harness-keywait.sh — 패널 refresh/quit 키 대기 헬퍼
|
|
3
|
-
#
|
|
4
|
-
# 사용법:
|
|
5
|
-
# source scripts/lib/harness-keywait.sh
|
|
6
|
-
# while true; do
|
|
7
|
-
# render_panel
|
|
8
|
-
# echo " [r] refresh [q] quit"
|
|
9
|
-
# if wait_or_refresh 3; then
|
|
10
|
-
# # 타임아웃 → 일반 주기 리프레시
|
|
11
|
-
# :
|
|
12
|
-
# else
|
|
13
|
-
# # 'r' 키 즉시 리프레시 (반환값 != 0)
|
|
14
|
-
# :
|
|
15
|
-
# fi
|
|
16
|
-
# done
|
|
17
|
-
#
|
|
18
|
-
# 키 처리:
|
|
19
|
-
# 'r' / 'R' / Enter → 즉시 복귀 (return 1 — 강제 리프레시 신호)
|
|
20
|
-
# 'q' / 'Q' → exit 0 (패널 종료)
|
|
21
|
-
# 그 외 / 타임아웃 → return 0 (정상 주기 리프레시)
|
|
22
|
-
#
|
|
23
|
-
# TTY 가 아니면 plain sleep 으로 동작 (Claude Code Bash 도구 등에서도 안전).
|
|
24
|
-
|
|
25
|
-
wait_or_refresh() {
|
|
26
|
-
local secs="${1:-3}"
|
|
27
|
-
|
|
28
|
-
# Non-TTY fallback
|
|
29
|
-
if ! [ -t 0 ]; then
|
|
30
|
-
sleep "$secs"
|
|
31
|
-
return 0
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
local key=""
|
|
35
|
-
# -t: timeout, -n 1: 1글자, -s: 에코 안함
|
|
36
|
-
# read 는 Enter 에서도 타임아웃 전에 즉시 복귀 (빈 키)
|
|
37
|
-
if IFS= read -rsn 1 -t "$secs" key 2>/dev/null; then
|
|
38
|
-
case "$key" in
|
|
39
|
-
q|Q)
|
|
40
|
-
# 커서 복원 후 종료
|
|
41
|
-
tput cnorm 2>/dev/null || true
|
|
42
|
-
clear
|
|
43
|
-
exit 0
|
|
44
|
-
;;
|
|
45
|
-
r|R|"")
|
|
46
|
-
# 강제 리프레시 시그널
|
|
47
|
-
return 1
|
|
48
|
-
;;
|
|
49
|
-
*)
|
|
50
|
-
# 기타 키는 무시하고 일반 복귀
|
|
51
|
-
return 0
|
|
52
|
-
;;
|
|
53
|
-
esac
|
|
54
|
-
fi
|
|
55
|
-
# 타임아웃
|
|
56
|
-
return 0
|
|
57
|
-
}
|