@walwal-harness/cli 2.5.0 → 3.2.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/assets/templates/AGENTS.md.template +31 -0
- package/assets/templates/HARNESS.md +180 -6
- package/assets/templates/config.json +182 -10
- package/assets/templates/evaluation-functional.md.template +119 -0
- package/assets/templates/evaluation-visual.md.template +151 -0
- package/assets/templates/memory.md +20 -0
- package/assets/templates/progress.json.template +6 -0
- package/package.json +5 -2
- package/scripts/harness-next.sh +254 -1
- package/scripts/harness-session-start.sh +15 -12
- package/scripts/harness-statusline.sh +93 -0
- package/scripts/harness-user-prompt-submit.sh +31 -72
- package/scripts/lib/harness-guardrail.sh +143 -0
|
@@ -1,55 +1,31 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-user-prompt-submit.sh
|
|
3
|
-
#
|
|
4
|
-
# Claude Code UserPromptSubmit hook
|
|
5
|
-
# 모든 사용자 프롬프트를 harness-dispatcher 로 라우팅하도록 Claude 에게
|
|
6
|
-
# 지시하는 컨텍스트를 stdout 에 주입한다.
|
|
7
|
-
#
|
|
8
|
-
# 활성 조건:
|
|
9
|
-
# 1) 현재 cwd 에 .harness/config.json 존재
|
|
10
|
-
# 2) .harness/config.json 의 behavior.auto_route_dispatcher != false
|
|
11
|
-
#
|
|
12
|
-
# 비활성 상황에서는 아무것도 출력하지 않고 exit 0 (pass-through).
|
|
13
|
-
# ─────────────────────────────────────────
|
|
2
|
+
# harness-user-prompt-submit.sh — UserPromptSubmit hook (compact v3.2)
|
|
3
|
+
# 핵심 상태 + 라우팅 지시만 주입. 장황한 설명 제거.
|
|
14
4
|
set -e
|
|
15
5
|
|
|
16
|
-
# stdin 의 JSON payload 읽기 (Claude Code 가 {prompt, cwd, session_id, ...} 전달)
|
|
17
6
|
INPUT=$(cat)
|
|
18
|
-
|
|
19
|
-
# cwd 추출 (payload 에 없으면 PWD fallback)
|
|
20
7
|
CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)
|
|
21
|
-
if [ -z "$CWD" ]; then
|
|
22
|
-
CWD="$PWD"
|
|
23
|
-
fi
|
|
8
|
+
if [ -z "$CWD" ]; then CWD="$PWD"; fi
|
|
24
9
|
|
|
25
10
|
# 조건 1: 하네스 초기화 확인
|
|
26
|
-
if [ ! -f "$CWD/.harness/config.json" ]; then
|
|
27
|
-
exit 0
|
|
28
|
-
fi
|
|
11
|
+
if [ ! -f "$CWD/.harness/config.json" ]; then exit 0; fi
|
|
29
12
|
|
|
30
|
-
# 조건 2: opt-out 플래그 확인
|
|
13
|
+
# 조건 2: opt-out 플래그 확인
|
|
31
14
|
AUTO_ROUTE="true"
|
|
32
15
|
if command -v jq >/dev/null 2>&1; then
|
|
33
16
|
AUTO_ROUTE=$(jq -r '.behavior.auto_route_dispatcher // true' "$CWD/.harness/config.json" 2>/dev/null || echo "true")
|
|
34
17
|
fi
|
|
35
|
-
if [ "$AUTO_ROUTE" != "true" ]; then
|
|
36
|
-
exit 0
|
|
37
|
-
fi
|
|
18
|
+
if [ "$AUTO_ROUTE" != "true" ]; then exit 0; fi
|
|
38
19
|
|
|
39
|
-
#
|
|
20
|
+
# 사용자 skip 감지
|
|
40
21
|
PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null || true)
|
|
41
|
-
# 사용자가 명시적으로 건너뛰기 요청하면 pass-through
|
|
42
22
|
if echo "$PROMPT" | grep -qiE "harness\s*(skip|off|bypass|없이)|without\s*harness|just\s*(answer|chat|reply)"; then
|
|
43
23
|
exit 0
|
|
44
24
|
fi
|
|
45
25
|
|
|
46
26
|
# 현재 세션 상태 읽기
|
|
47
|
-
PIPELINE="none"
|
|
48
|
-
|
|
49
|
-
NEXT_AGENT="none"
|
|
50
|
-
SPRINT_NUM="0"
|
|
51
|
-
SPRINT_STATUS="init"
|
|
52
|
-
FE_STACK="react"
|
|
27
|
+
PIPELINE="none"; CURRENT_AGENT="none"; NEXT_AGENT="none"
|
|
28
|
+
SPRINT_NUM="0"; SPRINT_STATUS="init"; AGENT_STATUS="pending"
|
|
53
29
|
|
|
54
30
|
if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
|
|
55
31
|
PIPELINE=$(jq -r '.pipeline // "none"' "$CWD/.harness/progress.json" 2>/dev/null || echo "none")
|
|
@@ -57,50 +33,33 @@ if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
|
|
|
57
33
|
NEXT_AGENT=$(jq -r '.next_agent // "none"' "$CWD/.harness/progress.json" 2>/dev/null || echo "none")
|
|
58
34
|
SPRINT_NUM=$(jq -r '.sprint.number // 0' "$CWD/.harness/progress.json" 2>/dev/null || echo "0")
|
|
59
35
|
SPRINT_STATUS=$(jq -r '.sprint.status // "init"' "$CWD/.harness/progress.json" 2>/dev/null || echo "init")
|
|
36
|
+
AGENT_STATUS=$(jq -r '.agent_status // "pending"' "$CWD/.harness/progress.json" 2>/dev/null || echo "pending")
|
|
60
37
|
fi
|
|
61
38
|
|
|
62
|
-
|
|
63
|
-
|
|
39
|
+
# ── 컨텍스트 분리 가드레일 ──
|
|
40
|
+
# 현재 에이전트가 활성인데 다른 에이전트 스킬을 호출하려는 경우 경고
|
|
41
|
+
CONTEXT_WARNING=""
|
|
42
|
+
if [ "$CURRENT_AGENT" != "none" ] && [ "$CURRENT_AGENT" != "null" ] && [ "$AGENT_STATUS" = "running" ]; then
|
|
43
|
+
# 프롬프트에서 /harness-* 패턴 추출
|
|
44
|
+
REQUESTED_SKILL=$(echo "$PROMPT" | grep -oE '/harness-[a-z-]+' | head -1 | sed 's|/harness-||')
|
|
45
|
+
if [ -n "$REQUESTED_SKILL" ] && [ "$REQUESTED_SKILL" != "$CURRENT_AGENT" ]; then
|
|
46
|
+
CONTEXT_WARNING="
|
|
47
|
+
## !! Context Isolation Warning !!
|
|
48
|
+
current_agent=${CURRENT_AGENT} (running) 인데 /harness-${REQUESTED_SKILL} 호출 감지.
|
|
49
|
+
한 세션에서 다른 에이전트를 실행하면 컨텍스트가 오염됩니다.
|
|
50
|
+
현재 에이전트를 먼저 완료(completed)하거나, 새 세션을 시작하세요."
|
|
51
|
+
fi
|
|
64
52
|
fi
|
|
65
53
|
|
|
66
|
-
#
|
|
67
|
-
# Context 주입 (stdout → Claude 컨텍스트)
|
|
68
|
-
# ─────────────────────────────────────────
|
|
54
|
+
# ── Compact context 주입 ──
|
|
69
55
|
cat <<EOF
|
|
70
|
-
[
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- **기능 요청** (만들어줘, 추가, 고쳐줘, PRD, OpenAPI) → Pipeline Flow
|
|
78
|
-
- **실수 지적** (아니, 잘못됐어, 그렇게 하면 안 돼, X로 해야지) → Gotcha Flow
|
|
79
|
-
- **혼합** → Gotcha 먼저 기록 → Pipeline 이어서
|
|
80
|
-
- **메타/인사/Claude 자체 질문** → 짧게 일반 응답 (dispatcher skip 허용)
|
|
81
|
-
|
|
82
|
-
## 현재 harness 세션 상태
|
|
83
|
-
- pipeline: $PIPELINE
|
|
84
|
-
- current_agent: $CURRENT_AGENT
|
|
85
|
-
- next_agent: $NEXT_AGENT
|
|
86
|
-
- sprint: $SPRINT_NUM ($SPRINT_STATUS)
|
|
87
|
-
- fe_stack: $FE_STACK
|
|
88
|
-
|
|
89
|
-
## 지시
|
|
90
|
-
1. 먼저 위 상태를 읽고, 사용자 메시지가 어떤 분류에 해당하는지 판단
|
|
91
|
-
2. **pipeline 이 'none' 또는 dispatcher 미실행 상태**면 harness-dispatcher 스킬 호출
|
|
92
|
-
3. **pipeline 이 이미 활성**이면:
|
|
93
|
-
- 실수 지적이면 → dispatcher 로 gotcha 기록
|
|
94
|
-
- 기능 연속 작업이면 → next_agent 또는 current_agent 의 컨텍스트로 계속
|
|
95
|
-
4. 응답 전 반드시 \`.harness/progress.json\` 을 읽고 세션 경계 프로토콜 준수
|
|
96
|
-
|
|
97
|
-
## 이 라우팅을 건너뛰려면
|
|
98
|
-
- **단일 메시지**: 사용자가 "harness skip", "without harness", "harness 없이", "just answer" 등을 명시
|
|
99
|
-
- **전역 비활성**: \`.harness/config.json\` 에서 \`behavior.auto_route_dispatcher = false\` 설정
|
|
100
|
-
|
|
101
|
-
## 참고
|
|
102
|
-
이 안내는 \`scripts/harness-user-prompt-submit.sh\` UserPromptSubmit 훅에 의해
|
|
103
|
-
매 프롬프트마다 자동 주입됩니다. 중복 안내처럼 보여도 무시하지 말고 위 규칙을 따르세요.
|
|
56
|
+
[harness] S${SPRINT_NUM} | ${PIPELINE} | agent=${CURRENT_AGENT} (${AGENT_STATUS}) | next=${NEXT_AGENT}
|
|
57
|
+
${CONTEXT_WARNING}
|
|
58
|
+
## Route
|
|
59
|
+
- pipeline=none/dispatcher 미실행 → harness-dispatcher 스킬 호출
|
|
60
|
+
- 기능 요청 → pipeline flow | 실수 지적 → gotcha flow | 메타 질문 → 짧게 응답 (skip)
|
|
61
|
+
- 활성 pipeline → next_agent/current_agent 컨텍스트로 계속
|
|
62
|
+
- skip: "harness skip", "just answer" 등 명시 시 단일 메시지 건너뜀
|
|
104
63
|
EOF
|
|
105
64
|
|
|
106
65
|
exit 0
|
|
@@ -0,0 +1,143 @@
|
|
|
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|generator-frontend-flutter)
|
|
33
|
+
echo "apps/web/"
|
|
34
|
+
echo "apps/flutter/"
|
|
35
|
+
echo ".harness/actions/sprint-contract.md"
|
|
36
|
+
echo ".harness/actions/feature-list.json"
|
|
37
|
+
echo ".harness/progress.json"
|
|
38
|
+
echo "package.json"
|
|
39
|
+
echo "tsconfig"
|
|
40
|
+
echo "pubspec"
|
|
41
|
+
;;
|
|
42
|
+
evaluator-functional|evaluator-functional-flutter)
|
|
43
|
+
echo ".harness/actions/evaluation-functional.md"
|
|
44
|
+
echo ".harness/progress.json"
|
|
45
|
+
echo "tests/"
|
|
46
|
+
echo "test/"
|
|
47
|
+
echo "e2e/"
|
|
48
|
+
echo "playwright"
|
|
49
|
+
;;
|
|
50
|
+
evaluator-visual)
|
|
51
|
+
echo ".harness/actions/evaluation-visual.md"
|
|
52
|
+
echo ".harness/progress.json"
|
|
53
|
+
;;
|
|
54
|
+
dispatcher)
|
|
55
|
+
echo ".harness/"
|
|
56
|
+
;;
|
|
57
|
+
*)
|
|
58
|
+
echo "**" # unknown agent — allow all (safe fallback)
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# ─────────────────────────────────────────
|
|
64
|
+
# verify_file_ownership — git diff 기반 검증
|
|
65
|
+
#
|
|
66
|
+
# Args:
|
|
67
|
+
# $1 — project root
|
|
68
|
+
#
|
|
69
|
+
# Reads:
|
|
70
|
+
# .harness/progress.json (current_agent)
|
|
71
|
+
#
|
|
72
|
+
# Returns: 0=pass, 1=violation found
|
|
73
|
+
# ─────────────────────────────────────────
|
|
74
|
+
verify_file_ownership() {
|
|
75
|
+
local PROJECT_ROOT="$1"
|
|
76
|
+
local PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
77
|
+
|
|
78
|
+
if [ ! -f "$PROGRESS" ]; then return 0; fi
|
|
79
|
+
|
|
80
|
+
local agent
|
|
81
|
+
agent=$(jq -r '.current_agent // "null"' "$PROGRESS" 2>/dev/null)
|
|
82
|
+
if [ "$agent" = "null" ] || [ -z "$agent" ]; then return 0; fi
|
|
83
|
+
|
|
84
|
+
# git이 없으면 skip
|
|
85
|
+
if ! command -v git &>/dev/null; then return 0; fi
|
|
86
|
+
|
|
87
|
+
# 마지막 커밋 이후 변경된 파일 목록
|
|
88
|
+
local -a changed_files
|
|
89
|
+
mapfile -t changed_files < <(cd "$PROJECT_ROOT" && git diff --name-only HEAD 2>/dev/null)
|
|
90
|
+
|
|
91
|
+
# staged 파일도 포함
|
|
92
|
+
local -a staged_files
|
|
93
|
+
mapfile -t staged_files < <(cd "$PROJECT_ROOT" && git diff --name-only --cached 2>/dev/null)
|
|
94
|
+
|
|
95
|
+
# 합치기 (중복 제거)
|
|
96
|
+
local -a all_files
|
|
97
|
+
mapfile -t all_files < <(printf '%s\n' "${changed_files[@]}" "${staged_files[@]}" | sort -u | grep -v '^$')
|
|
98
|
+
|
|
99
|
+
if [ ${#all_files[@]} -eq 0 ]; then return 0; fi
|
|
100
|
+
|
|
101
|
+
# 허용 경로 목록
|
|
102
|
+
local -a allowed
|
|
103
|
+
mapfile -t allowed < <(get_allowed_paths "$agent")
|
|
104
|
+
|
|
105
|
+
# 위반 검사
|
|
106
|
+
local -a violations=()
|
|
107
|
+
for file in "${all_files[@]}"; do
|
|
108
|
+
local is_allowed=false
|
|
109
|
+
for pattern in "${allowed[@]}"; do
|
|
110
|
+
if [[ "$file" == $pattern* ]]; then
|
|
111
|
+
is_allowed=true
|
|
112
|
+
break
|
|
113
|
+
fi
|
|
114
|
+
done
|
|
115
|
+
if [ "$is_allowed" = false ]; then
|
|
116
|
+
violations+=("$file")
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
|
|
120
|
+
if [ ${#violations[@]} -eq 0 ]; then
|
|
121
|
+
return 0
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# 위반 보고
|
|
125
|
+
echo ""
|
|
126
|
+
echo " ── Guardrail Violation ────────────────"
|
|
127
|
+
echo " Agent '$agent' modified files outside its allowed paths:"
|
|
128
|
+
echo ""
|
|
129
|
+
for v in "${violations[@]}"; do
|
|
130
|
+
echo " ✗ $v"
|
|
131
|
+
done
|
|
132
|
+
echo ""
|
|
133
|
+
echo " Allowed paths for $agent:"
|
|
134
|
+
for p in "${allowed[@]}"; do
|
|
135
|
+
echo " ✓ $p*"
|
|
136
|
+
done
|
|
137
|
+
echo ""
|
|
138
|
+
echo " Action: Review these changes before proceeding."
|
|
139
|
+
echo " ─────────────────────────────────────────"
|
|
140
|
+
echo ""
|
|
141
|
+
|
|
142
|
+
return 1
|
|
143
|
+
}
|