@uzysjung/agent-harness 26.136.0 → 26.139.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/dist/{chunk-WCH7E3G6.js → chunk-4VDIAMSM.js} +2 -2
- package/dist/{chunk-WCH7E3G6.js.map → chunk-4VDIAMSM.js.map} +1 -1
- package/dist/index.js +27 -9
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +1 -1
- package/templates/CLAUDE.md +7 -0
- package/templates/agents/implementer.md +48 -0
- package/templates/hooks/session-start.sh +16 -3
- package/templates/rules/benchmark-parity.md +3 -2
- package/templates/rules/change-management.md +29 -50
- package/templates/rules/doc-governance.md +26 -30
- package/templates/rules/no-false-ship.md +2 -2
- package/templates/rules/playwright-launch.md +15 -57
- package/templates/skills/codex-consult/SKILL.md +9 -2
- package/templates/skills/gemini-consult/SKILL.md +79 -20
- package/templates/skills/gemini-consult/scripts/gemini-ask.sh +74 -11
- package/templates/skills/ui-visual-review/SKILL.md +51 -1
|
@@ -4,14 +4,22 @@
|
|
|
4
4
|
# on stdout, wrapped in <untrusted-gemini-output> tags; progress → stderr.
|
|
5
5
|
#
|
|
6
6
|
# Usage:
|
|
7
|
-
# gemini-ask.sh [-m
|
|
8
|
-
# gemini-ask.sh [-m
|
|
7
|
+
# gemini-ask.sh [-t TIER] [-m MODEL] "PROMPT" # text (Korean polish / persona review)
|
|
8
|
+
# gemini-ask.sh [-t TIER] [-m MODEL] <<'EOF' # multi-line prompt via stdin
|
|
9
9
|
# ...multi-line prompt...
|
|
10
10
|
# EOF
|
|
11
|
-
# gemini-ask.sh -g OUT_DIR [-
|
|
12
|
-
# gemini-ask.sh -- "-leading-dash prompt"
|
|
11
|
+
# gemini-ask.sh -g OUT_DIR [-t TIER] "PROMPT" # image gen: files copied to OUT_DIR
|
|
12
|
+
# gemini-ask.sh -- "-leading-dash prompt" # `--` ends option parsing
|
|
13
13
|
#
|
|
14
|
-
#
|
|
14
|
+
# TIER (default: pro) selects a model FAMILY; the concrete model is resolved
|
|
15
|
+
# from `agy models` at call time, so no version string is pinned in here:
|
|
16
|
+
# pro — gemini-*-pro-* : every Gemini call this skill makes
|
|
17
|
+
# claude — claude-* : a non-Gemini reply, or the Gemini quota is spent
|
|
18
|
+
# `-m SLUG` skips tier resolution entirely — pass an exact slug from
|
|
19
|
+
# `agy models`, or a custom model configured in agy's settings.
|
|
20
|
+
#
|
|
21
|
+
# Exit codes: 2 usage · 3 agy missing / no model matches TIER ·
|
|
22
|
+
# 4 secret-shaped prompt refused ·
|
|
15
23
|
# 5 image mode produced no files · 124 timed out ·
|
|
16
24
|
# otherwise agy's own exit code.
|
|
17
25
|
#
|
|
@@ -25,20 +33,22 @@
|
|
|
25
33
|
set -euo pipefail
|
|
26
34
|
|
|
27
35
|
AGY="${AGY_BIN:-$(command -v agy || echo "$HOME/.local/bin/agy")}"
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
|
|
36
|
+
# Pick a FAMILY (see resolution below), never a version. Empty MODEL means
|
|
37
|
+
# "resolve TIER against `agy models`"; a non-empty one is used verbatim.
|
|
38
|
+
TIER="${GEMINI_CONSULT_TIER:-pro}"
|
|
39
|
+
MODEL="${GEMINI_CONSULT_MODEL:-}"
|
|
31
40
|
TIMEOUT_S="${GEMINI_CONSULT_TIMEOUT:-300}"
|
|
32
41
|
# Where agy drops generated artifacts (images land here, not in cwd, because
|
|
33
42
|
# headless mode denies the shell "command" permission a cwd-save would need).
|
|
34
43
|
BRAIN_DIR="${GEMINI_CONSULT_BRAIN:-$HOME/.gemini/antigravity-cli/brain}"
|
|
35
44
|
OUTDIR=""
|
|
36
45
|
|
|
37
|
-
while getopts "m:g:" opt; do
|
|
46
|
+
while getopts "m:g:t:" opt; do
|
|
38
47
|
case "$opt" in
|
|
39
48
|
m) MODEL="$OPTARG" ;;
|
|
40
49
|
g) OUTDIR="$OPTARG" ;;
|
|
41
|
-
|
|
50
|
+
t) TIER="$OPTARG" ;;
|
|
51
|
+
*) echo 'usage: gemini-ask.sh [-t pro|claude] [-m MODEL] [-g OUT_DIR] "PROMPT"' >&2; exit 2 ;;
|
|
42
52
|
esac
|
|
43
53
|
done
|
|
44
54
|
shift $((OPTIND - 1))
|
|
@@ -99,9 +109,62 @@ fi
|
|
|
99
109
|
# Neutral cwd → agy won't pull the current repo into its workspace context.
|
|
100
110
|
WORKDIR="$(mktemp -d)"
|
|
101
111
|
MSGFILE="$(mktemp)"
|
|
102
|
-
|
|
112
|
+
MODELS_ERR=""
|
|
113
|
+
trap 'rm -rf "$WORKDIR" "$MSGFILE" ${MARKER:+"$MARKER"} ${MODELS_ERR:+"$MODELS_ERR"}' EXIT
|
|
103
114
|
cd "$WORKDIR"
|
|
104
115
|
|
|
116
|
+
# Tier → concrete model, resolved from `agy models` on every call. Pinning a
|
|
117
|
+
# version here rots: agy retires slugs (an unknown one dies with "invalid model
|
|
118
|
+
# selection", exit 1), and the families do NOT share a version line — measured
|
|
119
|
+
# 2026-07-26, a flash line was 3.6 while pro was still 3.1 — so neither "newest" nor
|
|
120
|
+
# "best" can be spelled as a constant.
|
|
121
|
+
if [ -z "$MODEL" ]; then
|
|
122
|
+
case "$TIER" in
|
|
123
|
+
pro) TIER_RE='^gemini-.*-pro(-|$)' ;;
|
|
124
|
+
claude) TIER_RE='^claude-' ;;
|
|
125
|
+
*) echo "gemini-ask.sh: unknown tier '$TIER' (expected pro | claude; use -m for an exact slug)" >&2; exit 2 ;;
|
|
126
|
+
esac
|
|
127
|
+
MODELS_ERR="$(mktemp)"
|
|
128
|
+
MODELS="$(env -i PATH="$PATH" HOME="$HOME" TERM="${TERM:-dumb}" "$AGY" models 2>"$MODELS_ERR")" || MODELS=""
|
|
129
|
+
# Rank candidates: version desc, then capability (opus > sonnet), then effort
|
|
130
|
+
# (high/thinking > medium > low). Digits are read positionally so both slug
|
|
131
|
+
# shapes rank alike — `gemini-3.1-pro-high` and `claude-opus-4-6-thinking`.
|
|
132
|
+
# Fixed-width keys mean a plain `sort -r` suffices (BSD sort has no -V), and
|
|
133
|
+
# `awk NR==1` (not `head -1`) avoids a SIGPIPE under `set -o pipefail`.
|
|
134
|
+
MODEL="$(printf '%s\n' "$MODELS" | grep -E "$TIER_RE" | awk -F- '
|
|
135
|
+
{
|
|
136
|
+
maj = 0; min = 0; qual = 0; eff = 0
|
|
137
|
+
for (i = 1; i <= NF; i++) {
|
|
138
|
+
if ($i ~ /^[0-9]+(\.[0-9]+)?$/) {
|
|
139
|
+
if (maj == 0) { split($i, v, "."); maj = v[1] + 0; min = v[2] + 0 }
|
|
140
|
+
else if (min == 0) { min = $i + 0 }
|
|
141
|
+
}
|
|
142
|
+
if ($i == "opus") qual = 2
|
|
143
|
+
else if ($i == "sonnet") qual = 1
|
|
144
|
+
if ($i == "high" || $i == "thinking") eff = 3
|
|
145
|
+
else if ($i == "medium") eff = 2
|
|
146
|
+
else if ($i == "low") eff = 1
|
|
147
|
+
}
|
|
148
|
+
printf "%03d%03d%d%d %s\n", maj, min, qual, eff, $0
|
|
149
|
+
}' | sort -r | awk 'NR == 1 { print $2 }')" || MODEL=""
|
|
150
|
+
if [ -z "$MODEL" ]; then
|
|
151
|
+
# Never silently fall back to some other tier: the caller asked for this
|
|
152
|
+
# family for a reason, and a wrong-family answer looks like a right one.
|
|
153
|
+
echo "gemini-ask.sh: no '$TIER' model offered by \`agy models\` — available:" >&2
|
|
154
|
+
printf '%s\n' "$MODELS" | sed 's/^/ /' >&2
|
|
155
|
+
if [ -s "$MODELS_ERR" ]; then
|
|
156
|
+
# Surface agy's own stderr — an auth/network failure here looks exactly
|
|
157
|
+
# like "tier not offered" if you only see the empty list.
|
|
158
|
+
echo "gemini-ask.sh: \`agy models\` stderr:" >&2
|
|
159
|
+
cat "$MODELS_ERR" >&2
|
|
160
|
+
fi
|
|
161
|
+
exit 3
|
|
162
|
+
fi
|
|
163
|
+
echo "gemini-ask.sh: tier '$TIER' → model '$MODEL'" >&2
|
|
164
|
+
else
|
|
165
|
+
echo "gemini-ask.sh: model '$MODEL' (explicit)" >&2
|
|
166
|
+
fi
|
|
167
|
+
|
|
105
168
|
# Env allowlist: don't hand agy ambient tokens (GH_TOKEN, AWS_*, ...). HOME
|
|
106
169
|
# stays so agy auth (~/.gemini) keeps working.
|
|
107
170
|
# NOTE: --model must precede -p, and the prompt must be the value right after
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ui-visual-review
|
|
3
|
-
description: "Captures screenshots of key UI flows after E2E tests pass, runs an agent-side first-pass diff (regressions, console errors, layout shifts), then surfaces a checklist for the user's final approval. Use after E2E tests pass on a UI track (csr-*, ssr-*, full)."
|
|
3
|
+
description: "Captures screenshots of key UI flows after E2E tests pass, runs an agent-side first-pass diff (regressions, console errors, layout shifts), then surfaces a checklist for the user's final approval. Also owns the browser-launch procedure the `playwright-launch` rule delegates here: use it whenever a browser must be opened for a human to drive or for automated capture — manual E2E checks, UX/fidelity comparison against a reference product, or a one-time OAuth login. Use after E2E tests pass on a UI track (csr-*, ssr-*, full)."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# UI Visual Review
|
|
@@ -31,6 +31,56 @@ E2E 테스트가 PASS 했어도 시각적 회귀(layout shift, 색상/간격 변
|
|
|
31
31
|
- 앱이 로컬에서 기동 가능 (예: `pnpm dev`, `docker-compose up`)
|
|
32
32
|
- 핵심 화면 URL 리스트가 정의됨 (없으면 본 skill 첫 실행 시 사용자 질의)
|
|
33
33
|
|
|
34
|
+
## 브라우저를 띄우는 법 (영속 profile)
|
|
35
|
+
|
|
36
|
+
`playwright-launch` 룰이 **금지**를 소유하고, 여기가 **절차**를 소유한다. 금지문은 상주해야
|
|
37
|
+
하지만(위반은 작업 도중에 일어난다) 아래 절차는 실제로 브라우저를 띄울 때만 필요하다.
|
|
38
|
+
|
|
39
|
+
핵심은 셋이다. ⓐ **영속 profile dir** — 프로젝트별로 분리해 매 iteration 재사용한다. cookie ·
|
|
40
|
+
IndexedDB · Service Worker 가 보존돼야 재로그인이 사라진다. ⓑ **Chrome for Testing 별도 binary**
|
|
41
|
+
(`npx playwright install chromium`) — 사용자의 일반 Chrome 과 완전히 분리한다. ⓒ **사용자가 키를
|
|
42
|
+
입력하는 동안 자동화 layer 0** — main session 은 창을 띄우기만 하고, capture·검증은 입력이 끝난 뒤
|
|
43
|
+
별도 process 에서 돈다.
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
import { chromium } from 'playwright';
|
|
47
|
+
|
|
48
|
+
const PROFILE_DIR = process.env.PROJECT_PROFILE_DIR || `${process.env.HOME}/.<project>-audit-profile`;
|
|
49
|
+
const TARGET = process.env.PROJECT_URL || 'http://localhost:<port>';
|
|
50
|
+
|
|
51
|
+
const ctx = await chromium.launchPersistentContext(PROFILE_DIR, {
|
|
52
|
+
headless: false,
|
|
53
|
+
viewport: { width: 1440, height: 900 },
|
|
54
|
+
args: ['--disable-blink-features=AutomationControlled', '--no-first-run', '--no-default-browser-check'],
|
|
55
|
+
});
|
|
56
|
+
const page = ctx.pages()[0] ?? (await ctx.newPage());
|
|
57
|
+
|
|
58
|
+
// (선택) dev bypass auth — OAuth 의 webdriver 차단을 피한다. same-origin cookie 로 저장된다.
|
|
59
|
+
await page.goto(`${TARGET}/`, { waitUntil: 'domcontentloaded' });
|
|
60
|
+
await ctx.request.post(`${TARGET}/api/v1/_dev/test-login`, {
|
|
61
|
+
data: { key: process.env.E2E_TEST_KEY ?? 'e2e-dev-key', email: 'e2e@example.local' },
|
|
62
|
+
});
|
|
63
|
+
await page.reload({ waitUntil: 'networkidle' });
|
|
64
|
+
|
|
65
|
+
// 창을 띄운 채 자동화는 detach — 사용자가 직접 쓴다. ctx.close() 를 부르지 않는다.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**두 가지 사용 형태**
|
|
69
|
+
|
|
70
|
+
0. **기존 launcher 를 먼저 정리한다** — `pkill -f "playwright|Chrome for Testing"`. 같은 profile
|
|
71
|
+
dir 을 잡고 있는 창이 살아 있으면 `launchPersistentContext` 가 기동하지 못한다. 첫 실행은 되고
|
|
72
|
+
**2회차부터** 실패하기 때문에 프로필 잠금이 아니라 스크립트 버그로 오진하기 쉽다.
|
|
73
|
+
1. **사용자가 직접 보는 경우** — `node scripts/<project>-launch.mjs` 를 포그라운드로 띄우고
|
|
74
|
+
"URL / 로그인됨 / 직접 쓰세요"를 보고한 뒤, 추가 navigation·evaluate 를 **하지 않는다**.
|
|
75
|
+
사용자 입력과 충돌한다.
|
|
76
|
+
2. **자동 capture (fidelity·audit)** — 같은 `launchPersistentContext` 로 시나리오를 자동 click 하고
|
|
77
|
+
산출물을 `docs/research/<area>_audit_<sprint>/iter_<N>/` 에 남긴다.
|
|
78
|
+
|
|
79
|
+
> **이 launcher 로 띄운 창은 Playwright API 로 캡처한다**(`page.screenshot({ fullPage: true })`,
|
|
80
|
+
> 콘솔은 `page.on('console')`). 아래 §2·§4 의 chrome-devtools MCP 예시는 launcher 없이 MCP 로
|
|
81
|
+
> 작업하는 경우다 — Playwright 는 CDP 를 파이프로 물어 외부 프로세스가 붙을 엔드포인트를 열지
|
|
82
|
+
> 않으므로, 이 둘을 한 세션에서 섞으려 하면 붙을 대상이 없다. **하나를 고르고 끝까지 그걸로 간다.**
|
|
83
|
+
|
|
34
84
|
## Process
|
|
35
85
|
|
|
36
86
|
### 1. 화면 리스트업 (한 번만)
|