@walwal-harness/cli 2.5.0 → 3.2.1

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.
@@ -0,0 +1,93 @@
1
+ #!/bin/bash
2
+ # harness-statusline.sh — Claude Code statusline hook
3
+ # 터미널 하단에 항상 고정되는 1줄 compact 상태 표시.
4
+ # stdin: Claude Code JSON payload (model, context_window, cost 등)
5
+ # stdout: 상태 문자열 (Claude Code가 터미널 하단에 렌더링)
6
+
7
+ # Read Claude Code session data from stdin
8
+ input=$(cat)
9
+
10
+ # Extract Claude Code built-in data
11
+ context_pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0' 2>/dev/null | cut -d. -f1)
12
+ cost=$(echo "$input" | jq -r '.cost.total_cost_usd // 0' 2>/dev/null)
13
+
14
+ # Resolve project root (walk up from cwd)
15
+ CWD=$(echo "$input" | jq -r '.workspace.current_dir // empty' 2>/dev/null)
16
+ if [ -z "$CWD" ]; then CWD="$PWD"; fi
17
+
18
+ PROJECT_ROOT="$CWD"
19
+ while [ "$PROJECT_ROOT" != "/" ]; do
20
+ if [ -d "$PROJECT_ROOT/.harness" ]; then break; fi
21
+ PROJECT_ROOT="$(dirname "$PROJECT_ROOT")"
22
+ done
23
+
24
+ PROGRESS="$PROJECT_ROOT/.harness/progress.json"
25
+ FEATURE_LIST="$PROJECT_ROOT/.harness/actions/feature-list.json"
26
+ PIPELINE_JSON="$PROJECT_ROOT/.harness/actions/pipeline.json"
27
+
28
+ # No harness → minimal status
29
+ if [ ! -f "$PROGRESS" ]; then
30
+ echo "harness: not initialized | ctx ${context_pct}%"
31
+ exit 0
32
+ fi
33
+
34
+ # Read harness state
35
+ sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS" 2>/dev/null)
36
+ sprint_status=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null)
37
+ pipeline=$(jq -r '.pipeline // "?"' "$PROGRESS" 2>/dev/null)
38
+ current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
39
+ agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
40
+ next_agent=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null)
41
+ retry_count=$(jq -r '.sprint.retry_count // 0' "$PROGRESS" 2>/dev/null)
42
+
43
+ # Pipeline short name
44
+ case "$pipeline" in
45
+ FULLSTACK) pl="FULL" ;;
46
+ FE-ONLY) pl="FE" ;;
47
+ BE-ONLY) pl="BE" ;;
48
+ null|"?") pl="?" ;;
49
+ *) pl="$pipeline" ;;
50
+ esac
51
+
52
+ # Agent short name (strip harness prefix)
53
+ agent_short="${current_agent#generator-}"
54
+ agent_short="${agent_short#evaluator-}"
55
+ if [ "$current_agent" = "none" ] || [ "$current_agent" = "null" ]; then
56
+ agent_short="$next_agent"
57
+ if [ "$agent_short" = "none" ] || [ "$agent_short" = "null" ]; then
58
+ agent_short="idle"
59
+ fi
60
+ fi
61
+
62
+ # Feature progress
63
+ total_features=0
64
+ completed_features=0
65
+ if [ -f "$FEATURE_LIST" ]; then
66
+ total_features=$(jq '.features | length' "$FEATURE_LIST" 2>/dev/null || echo 0)
67
+ completed_features=$(jq '[.features[]? | select(
68
+ (.passes // []) | (
69
+ (map(select(. == "evaluator-functional")) | length > 0) and
70
+ (map(select(. == "evaluator-visual")) | length > 0)
71
+ )
72
+ )] | length' "$FEATURE_LIST" 2>/dev/null || echo 0)
73
+ fi
74
+
75
+ # Status indicator
76
+ status_icon=""
77
+ case "$agent_status" in
78
+ running) status_icon=">" ;;
79
+ completed) status_icon="v" ;;
80
+ failed) status_icon="x" ;;
81
+ blocked) status_icon="!" ;;
82
+ *) status_icon="-" ;;
83
+ esac
84
+
85
+ # Retry indicator
86
+ retry_str=""
87
+ if [ "$retry_count" -gt 0 ]; then
88
+ retry_str=" R${retry_count}"
89
+ fi
90
+
91
+ # Build compact status line
92
+ # Format: [S1] FULL | >backend | 2/5 feat | R0 | ctx 45% | $1.23
93
+ echo "[S${sprint_num}] ${pl} | ${status_icon}${agent_short}${retry_str} | ${completed_features}/${total_features} feat | ctx ${context_pct}% | \$${cost}"
@@ -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 플래그 확인 (기본값 true)
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
- # 프롬프트 내용 추출 (opt-out 문구 감지용)
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
- CURRENT_AGENT="none"
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
- if [ -f "$CWD/.harness/actions/pipeline.json" ] && command -v jq >/dev/null 2>&1; then
63
- FE_STACK=$(jq -r '.fe_stack // "react"' "$CWD/.harness/actions/pipeline.json" 2>/dev/null || echo "react")
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
- [walwal-harness] Auto-routing is ACTIVE for this project.
71
-
72
- 이 프로젝트는 walwal-harness 가 초기화되어 있으며, auto_route_dispatcher 플래그가
73
- 켜져 있습니다. 사용자의 모든 프롬프트는 **harness-dispatcher** 스킬을 통해
74
- 분류/라우팅된 뒤 처리되어야 합니다.
75
-
76
- ## 분류 규칙 (Dispatcher Request Classification)
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
+ }