@mmerterden/multi-agent-pipeline 11.4.0 → 12.0.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/CHANGELOG.md +172 -0
- package/README.md +1 -0
- package/docs/adr/0007-multi-tool-adapter-framework.md +2 -2
- package/index.js +9 -2
- package/install/_common.mjs +107 -5
- package/install/_telemetry.mjs +5 -23
- package/install/claude.mjs +95 -11
- package/install/copilot.mjs +75 -6
- package/install/index.mjs +34 -1
- package/package.json +4 -10
- package/pipeline/commands/multi-agent/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/channels/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/create-jira/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/dev/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +2 -1
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/diff-explain/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/finish/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +29 -3
- package/pipeline/commands/multi-agent/help/SKILL.md +3 -2
- package/pipeline/commands/multi-agent/issue/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/jira/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/kill/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/language/SKILL.md +20 -1
- package/pipeline/commands/multi-agent/local/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/log/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/prune-logs/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/purge/SKILL.md +27 -24
- package/pipeline/commands/multi-agent/refactor/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/review/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/review-issue/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/review-jira/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/scan/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/search/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/stack/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/status/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +20 -2
- package/pipeline/commands/multi-agent/test/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/uninstall/SKILL.md +87 -0
- package/pipeline/commands/multi-agent/update/SKILL.md +1 -0
- package/pipeline/lib/account-resolver.sh +61 -23
- package/pipeline/lib/channels-multi-repo.sh +7 -2
- package/pipeline/lib/count-lib.sh +27 -0
- package/pipeline/lib/credential-store.sh +23 -6
- package/pipeline/lib/extract-conventions.sh +27 -21
- package/pipeline/lib/fetch-confluence.sh +25 -13
- package/pipeline/lib/fetch-crashlytics.sh +30 -8
- package/pipeline/lib/fetch-fortify.sh +11 -2
- package/pipeline/lib/fetch-swagger.sh +18 -7
- package/pipeline/lib/figma-mcp-refresh.sh +26 -11
- package/pipeline/lib/figma-screenshot.sh +11 -2
- package/pipeline/lib/issue-fetcher.sh +31 -6
- package/pipeline/lib/multi-repo-pipeline.sh +84 -20
- package/pipeline/lib/plan-todos.sh +12 -3
- package/pipeline/lib/post-pr-review.sh +5 -3
- package/pipeline/lib/repo-cache.sh +53 -9
- package/pipeline/lib/review-watch.sh +26 -6
- package/pipeline/lib/shadow-git.sh +45 -4
- package/pipeline/lib/vercel-deploy.sh +10 -1
- package/pipeline/multi-agent-refs/cross-cli-contract.md +5 -5
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +12 -1
- package/pipeline/scripts/audit-log-rotate.sh +38 -10
- package/pipeline/scripts/check-md-links.mjs +34 -9
- package/pipeline/scripts/diff-risk-score.mjs +4 -1
- package/pipeline/scripts/evidence-gate.mjs +10 -1
- package/pipeline/scripts/fixtures/install-layout.tsv +6 -4
- package/pipeline/scripts/fixtures/pack-expected-count.txt +1 -0
- package/pipeline/scripts/gc-tmp.sh +67 -8
- package/pipeline/scripts/gc-worktrees.sh +140 -0
- package/pipeline/scripts/keychain-save.sh +13 -9
- package/pipeline/scripts/lint-skills.mjs +40 -2
- package/pipeline/scripts/localize-commands.mjs +130 -0
- package/pipeline/scripts/migrate-prefs.mjs +26 -7
- package/pipeline/scripts/phase-tracker.sh +71 -0
- package/pipeline/scripts/pre-commit-check.sh +39 -0
- package/pipeline/scripts/prune-logs.sh +100 -15
- package/pipeline/scripts/purge.sh +235 -0
- package/pipeline/scripts/scan-skills.sh +217 -127
- package/pipeline/scripts/smoke-bitbucket-contract.sh +21 -5
- package/pipeline/scripts/smoke-description-tr.sh +82 -0
- package/pipeline/scripts/smoke-fetchers-offline.sh +382 -0
- package/pipeline/scripts/smoke-gc-tmp.sh +50 -4
- package/pipeline/scripts/smoke-gc-worktrees.sh +125 -0
- package/pipeline/scripts/smoke-install-layout.sh +11 -3
- package/pipeline/scripts/smoke-lib-scripts.sh +54 -1
- package/pipeline/scripts/smoke-pack-contents.sh +140 -0
- package/pipeline/scripts/smoke-plugin-validate.sh +64 -0
- package/pipeline/scripts/smoke-prune-logs.sh +57 -7
- package/pipeline/scripts/smoke-purge.sh +138 -0
- package/pipeline/scripts/smoke-shadow-git.sh +48 -1
- package/pipeline/scripts/smoke-skill-authoring.sh +1 -1
- package/pipeline/scripts/smoke-update-check.sh +13 -0
- package/pipeline/scripts/smoke-workflow-audit.sh +69 -0
- package/pipeline/scripts/test-gap-scan.mjs +12 -1
- package/pipeline/scripts/triage-memory.mjs +10 -1
- package/pipeline/scripts/uninstall.mjs +146 -42
- package/pipeline/scripts/update-check.sh +9 -0
- package/pipeline/scripts/update-issue-progress.sh +60 -41
- package/pipeline/scripts/write-state.mjs +14 -4
- package/pipeline/skills/.skill-manifest.json +7 -7
- package/pipeline/skills/.skills-index.json +9 -9
- package/pipeline/skills/shared/README.md +19 -10
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +1 -0
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +74 -0
- package/pipeline/skills/skills-index.md +1 -1
- package/pipeline/commands/multi-agent/delete/SKILL.md +0 -67
- package/pipeline/skills/shared/core/multi-agent-delete/SKILL.md +0 -66
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# smoke-fetchers-offline.sh - offline invocation tests for the pipeline/lib
|
|
4
|
+
# fetchers. No network, no real tokens.
|
|
5
|
+
#
|
|
6
|
+
# Asserts, per fetcher:
|
|
7
|
+
# 1. usage / arg-validation paths exit with the documented code
|
|
8
|
+
# 2. URL/host parsing succeeds for a representative URL. This is the
|
|
9
|
+
# permanent regression guard for the env-prefix bug where
|
|
10
|
+
# `VAR=x PARSED=$(python3 ...)` never exported VAR to the child, so
|
|
11
|
+
# fetch-confluence always failed with "could not determine Confluence
|
|
12
|
+
# host" before reaching the credential stage.
|
|
13
|
+
# 3. with stub credentials (fake credential-store in a temp HOME, fake curl
|
|
14
|
+
# on PATH) the failure moves past parsing to the documented
|
|
15
|
+
# credential/network exit code
|
|
16
|
+
# 4. static source checks: no bash-4-only constructs (macOS /bin/bash is
|
|
17
|
+
# 3.2) and no secret-bearing argv patterns (curl -H auth headers,
|
|
18
|
+
# --data-urlencode secrets, security add-generic-password -w on argv)
|
|
19
|
+
#
|
|
20
|
+
# Fetchers run under /bin/bash when available so bash-4-isms fail loudly on
|
|
21
|
+
# macOS instead of passing under a newer Homebrew bash.
|
|
22
|
+
#
|
|
23
|
+
# Exit codes: 0=pass, 1=failure
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
28
|
+
LIB="$REPO_ROOT/pipeline/lib"
|
|
29
|
+
SCRIPTS="$REPO_ROOT/pipeline/scripts"
|
|
30
|
+
|
|
31
|
+
# Prefer the system bash (3.2 on macOS) so bash-4-isms regress loudly here.
|
|
32
|
+
SH="/bin/bash"
|
|
33
|
+
[ -x "$SH" ] || SH="$(command -v bash)"
|
|
34
|
+
|
|
35
|
+
PASS=0
|
|
36
|
+
FAIL=0
|
|
37
|
+
note() { echo " -> $*"; }
|
|
38
|
+
pass() { echo " ok $*"; PASS=$((PASS + 1)); }
|
|
39
|
+
fail() { echo " FAIL $*"; FAIL=$((FAIL + 1)); }
|
|
40
|
+
|
|
41
|
+
WORK=$(mktemp -d "${TMPDIR:-/tmp}/smoke-fetchers.XXXXXX")
|
|
42
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
43
|
+
|
|
44
|
+
OUT="$WORK/out.log"
|
|
45
|
+
ERR="$WORK/err.log"
|
|
46
|
+
|
|
47
|
+
# expect_rc <expected-rc> <label> <cmd...>
|
|
48
|
+
# Runs cmd, captures stdout/stderr to $OUT/$ERR, asserts the exit code.
|
|
49
|
+
expect_rc() {
|
|
50
|
+
local expected="$1" label="$2"
|
|
51
|
+
shift 2
|
|
52
|
+
local rc=0
|
|
53
|
+
"$@" >"$OUT" 2>"$ERR" || rc=$?
|
|
54
|
+
if [ "$rc" -eq "$expected" ]; then
|
|
55
|
+
pass "$label (rc=$rc)"
|
|
56
|
+
else
|
|
57
|
+
fail "$label (rc=$rc, expected $expected)"
|
|
58
|
+
tail -5 "$ERR" | sed 's/^/ /'
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
assert_in_file() {
|
|
63
|
+
local needle="$1" file="$2" label="$3"
|
|
64
|
+
if grep -q -e "$needle" "$file" 2>/dev/null; then
|
|
65
|
+
pass "$label"
|
|
66
|
+
else
|
|
67
|
+
fail "$label (pattern '$needle' not found)"
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
assert_not_in_file() {
|
|
72
|
+
local needle="$1" file="$2" label="$3"
|
|
73
|
+
if grep -q -e "$needle" "$file" 2>/dev/null; then
|
|
74
|
+
fail "$label (pattern '$needle' present)"
|
|
75
|
+
else
|
|
76
|
+
pass "$label"
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# -----------------------------------------------------------------------------
|
|
81
|
+
note "0. fixtures: temp HOME with resolver + fake credential store, fake curls"
|
|
82
|
+
# -----------------------------------------------------------------------------
|
|
83
|
+
FAKE_HOME="$WORK/home"
|
|
84
|
+
mkdir -p "$FAKE_HOME/.claude/lib"
|
|
85
|
+
cp "$LIB/credential-store-resolver.sh" "$FAKE_HOME/.claude/lib/credential-store-resolver.sh"
|
|
86
|
+
|
|
87
|
+
# Fake credential store: empty by default, returns FAKE_CRED_VALUE when set.
|
|
88
|
+
cat > "$FAKE_HOME/.claude/lib/credential-store.sh" <<'FAKE'
|
|
89
|
+
#!/bin/bash
|
|
90
|
+
case "${1:-}" in
|
|
91
|
+
get)
|
|
92
|
+
if [ -n "${FAKE_CRED_VALUE:-}" ]; then printf '%s' "$FAKE_CRED_VALUE"; exit 0; fi
|
|
93
|
+
exit 1 ;;
|
|
94
|
+
platform) echo "fake" ;;
|
|
95
|
+
*) exit 3 ;;
|
|
96
|
+
esac
|
|
97
|
+
FAKE
|
|
98
|
+
chmod +x "$FAKE_HOME/.claude/lib/credential-store.sh"
|
|
99
|
+
|
|
100
|
+
# Fake curl variants, each in its own PATH-prepend dir.
|
|
101
|
+
BIN_CURL_FAIL="$WORK/bin-curl-fail"
|
|
102
|
+
mkdir -p "$BIN_CURL_FAIL"
|
|
103
|
+
cat > "$BIN_CURL_FAIL/curl" <<'FAKE'
|
|
104
|
+
#!/bin/bash
|
|
105
|
+
echo "curl: (7) offline stub - connection refused" >&2
|
|
106
|
+
exit 7
|
|
107
|
+
FAKE
|
|
108
|
+
chmod +x "$BIN_CURL_FAIL/curl"
|
|
109
|
+
|
|
110
|
+
# Swagger meta stub: honours --output, emits the --write-out triple with a
|
|
111
|
+
# content type that matches neither json nor yaml, forcing the URL-suffix
|
|
112
|
+
# lowercase path (the former bash-4 ${URL,,} site).
|
|
113
|
+
BIN_CURL_META="$WORK/bin-curl-meta"
|
|
114
|
+
mkdir -p "$BIN_CURL_META"
|
|
115
|
+
cat > "$BIN_CURL_META/curl" <<'FAKE'
|
|
116
|
+
#!/bin/bash
|
|
117
|
+
out=""
|
|
118
|
+
prev=""
|
|
119
|
+
for a in "$@"; do
|
|
120
|
+
if [ "$prev" = "--output" ]; then out="$a"; fi
|
|
121
|
+
prev="$a"
|
|
122
|
+
done
|
|
123
|
+
if [ -n "$out" ]; then printf '{}' > "$out"; fi
|
|
124
|
+
printf '200\napplication/octet-stream\n2\n'
|
|
125
|
+
exit 0
|
|
126
|
+
FAKE
|
|
127
|
+
chmod +x "$BIN_CURL_META/curl"
|
|
128
|
+
|
|
129
|
+
# 401 stub: honours -o, prints the HTTP code the way curl -w '%{http_code}' does.
|
|
130
|
+
BIN_CURL_401="$WORK/bin-curl-401"
|
|
131
|
+
mkdir -p "$BIN_CURL_401"
|
|
132
|
+
cat > "$BIN_CURL_401/curl" <<'FAKE'
|
|
133
|
+
#!/bin/bash
|
|
134
|
+
out=""
|
|
135
|
+
prev=""
|
|
136
|
+
for a in "$@"; do
|
|
137
|
+
if [ "$prev" = "-o" ] || [ "$prev" = "--output" ]; then out="$a"; fi
|
|
138
|
+
prev="$a"
|
|
139
|
+
done
|
|
140
|
+
if [ -n "$out" ]; then printf '{}' > "$out"; fi
|
|
141
|
+
printf '401'
|
|
142
|
+
exit 0
|
|
143
|
+
FAKE
|
|
144
|
+
chmod +x "$BIN_CURL_401/curl"
|
|
145
|
+
|
|
146
|
+
# Fake security + uname (forces the macOS branch of credential-store.sh) so
|
|
147
|
+
# the no-secret-on-argv property of `security -i` writes can be asserted on
|
|
148
|
+
# any platform without touching a real keychain.
|
|
149
|
+
BIN_DARWIN="$WORK/bin-darwin"
|
|
150
|
+
mkdir -p "$BIN_DARWIN"
|
|
151
|
+
cat > "$BIN_DARWIN/uname" <<'FAKE'
|
|
152
|
+
#!/bin/bash
|
|
153
|
+
echo "Darwin"
|
|
154
|
+
FAKE
|
|
155
|
+
cat > "$BIN_DARWIN/security" <<'FAKE'
|
|
156
|
+
#!/bin/bash
|
|
157
|
+
printf '%s\n' "$*" >> "$SMOKE_CAPTURE_DIR/security-argv.log"
|
|
158
|
+
if [ "${1:-}" = "-i" ]; then
|
|
159
|
+
cat >> "$SMOKE_CAPTURE_DIR/security-stdin.log"
|
|
160
|
+
fi
|
|
161
|
+
exit 0
|
|
162
|
+
FAKE
|
|
163
|
+
chmod +x "$BIN_DARWIN/uname" "$BIN_DARWIN/security"
|
|
164
|
+
|
|
165
|
+
# -----------------------------------------------------------------------------
|
|
166
|
+
note "1. fetch-confluence.sh"
|
|
167
|
+
# -----------------------------------------------------------------------------
|
|
168
|
+
expect_rc 4 "no args exits 4 (usage)" \
|
|
169
|
+
"$SH" "$LIB/fetch-confluence.sh"
|
|
170
|
+
expect_rc 4 "--help exits 4 (usage)" \
|
|
171
|
+
"$SH" "$LIB/fetch-confluence.sh" --help
|
|
172
|
+
|
|
173
|
+
# Regression guard for the env-prefix parse bug: a plain page URL must get
|
|
174
|
+
# past host/pageId resolution and fail at the credential stage (exit 2),
|
|
175
|
+
# never with the parse error.
|
|
176
|
+
expect_rc 2 "URL parse reaches credential stage (viewpage.action?pageId)" \
|
|
177
|
+
env HOME="$FAKE_HOME" USER=smoke \
|
|
178
|
+
"$SH" "$LIB/fetch-confluence.sh" \
|
|
179
|
+
"https://confluence.example.com/pages/viewpage.action?pageId=12345"
|
|
180
|
+
assert_in_file "missing-token" "$ERR" "blocked JSON names missing-token"
|
|
181
|
+
assert_not_in_file "could not determine Confluence host" "$ERR" \
|
|
182
|
+
"no host-parse error (env-prefix regression)"
|
|
183
|
+
|
|
184
|
+
expect_rc 2 "URL parse reaches credential stage (cloud /wiki/spaces form)" \
|
|
185
|
+
env HOME="$FAKE_HOME" USER=smoke \
|
|
186
|
+
"$SH" "$LIB/fetch-confluence.sh" \
|
|
187
|
+
"https://tenant.atlassian.net/wiki/spaces/ENG/pages/99887/Some+Page"
|
|
188
|
+
assert_in_file "missing-token" "$ERR" "cloud form also blocked on token only"
|
|
189
|
+
|
|
190
|
+
# With a stub token and a failing curl, the failure must be the documented
|
|
191
|
+
# network error (3), proving parsing and auth wiring are past.
|
|
192
|
+
expect_rc 3 "stub token + offline curl exits 3 (network stage)" \
|
|
193
|
+
env HOME="$FAKE_HOME" USER=smoke FAKE_CRED_VALUE=fake-token \
|
|
194
|
+
PATH="$BIN_CURL_FAIL:$PATH" \
|
|
195
|
+
"$SH" "$LIB/fetch-confluence.sh" \
|
|
196
|
+
"https://confluence.example.com/pages/viewpage.action?pageId=12345"
|
|
197
|
+
assert_in_file "Confluence GET failed" "$ERR" "network-stage error message"
|
|
198
|
+
|
|
199
|
+
# -----------------------------------------------------------------------------
|
|
200
|
+
note "2. fetch-crashlytics.sh"
|
|
201
|
+
# -----------------------------------------------------------------------------
|
|
202
|
+
expect_rc 4 "no args exits 4 (usage)" \
|
|
203
|
+
"$SH" "$LIB/fetch-crashlytics.sh"
|
|
204
|
+
expect_rc 4 "--help exits 4 (usage)" \
|
|
205
|
+
"$SH" "$LIB/fetch-crashlytics.sh" --help
|
|
206
|
+
|
|
207
|
+
CRASH_URL="https://console.firebase.google.com/project/my-proj/crashlytics/app/ios:com.example.app/issues/abc123"
|
|
208
|
+
expect_rc 2 "URL parse reaches credential stage" \
|
|
209
|
+
env HOME="$FAKE_HOME" USER=smoke \
|
|
210
|
+
"$SH" "$LIB/fetch-crashlytics.sh" "$CRASH_URL"
|
|
211
|
+
assert_in_file "missing-token" "$ERR" "blocked JSON names missing-token"
|
|
212
|
+
|
|
213
|
+
# Stub SA JSON whose project_id mismatches the URL: proves parse + credential
|
|
214
|
+
# read + base64 decode + JSON validation all pass, fully offline (exit 5).
|
|
215
|
+
MISMATCH_SA_B64=$(printf '{"project_id":"other-proj"}' | base64 | tr -d '\n')
|
|
216
|
+
expect_rc 5 "stub SA JSON reaches project-mismatch check (exit 5)" \
|
|
217
|
+
env HOME="$FAKE_HOME" USER=smoke FAKE_CRED_VALUE="$MISMATCH_SA_B64" \
|
|
218
|
+
"$SH" "$LIB/fetch-crashlytics.sh" "$CRASH_URL"
|
|
219
|
+
assert_in_file "project_id" "$ERR" "mismatch error mentions project_id"
|
|
220
|
+
|
|
221
|
+
# The decoded SA JSON must never touch a predictable /tmp path.
|
|
222
|
+
assert_not_in_file "/tmp/.fc-sa" "$LIB/fetch-crashlytics.sh" \
|
|
223
|
+
"predictable /tmp SA path removed from source"
|
|
224
|
+
|
|
225
|
+
# -----------------------------------------------------------------------------
|
|
226
|
+
note "3. fetch-swagger.sh"
|
|
227
|
+
# -----------------------------------------------------------------------------
|
|
228
|
+
expect_rc 4 "no args exits 4 (usage)" \
|
|
229
|
+
"$SH" "$LIB/fetch-swagger.sh"
|
|
230
|
+
|
|
231
|
+
expect_rc 2 "offline curl exits 2 (network stage)" \
|
|
232
|
+
env PATH="$BIN_CURL_FAIL:$PATH" \
|
|
233
|
+
"$SH" "$LIB/fetch-swagger.sh" "https://spec.example.com/openapi.json"
|
|
234
|
+
|
|
235
|
+
# Uppercase .YAML suffix with a non-matching content type exercises the
|
|
236
|
+
# lowercase-URL branch (formerly bash-4 ${URL,,}, dies on /bin/bash 3.2).
|
|
237
|
+
expect_rc 0 "uppercase .YAML suffix detected via lowercase branch" \
|
|
238
|
+
env PATH="$BIN_CURL_META:$PATH" \
|
|
239
|
+
"$SH" "$LIB/fetch-swagger.sh" "https://spec.example.com/openapi.YAML"
|
|
240
|
+
assert_in_file '"format": "yaml"' "$OUT" "output reports yaml format"
|
|
241
|
+
|
|
242
|
+
# -----------------------------------------------------------------------------
|
|
243
|
+
note "4. fetch-fortify.sh"
|
|
244
|
+
# -----------------------------------------------------------------------------
|
|
245
|
+
expect_rc 6 "no host configured exits 6" \
|
|
246
|
+
env HOME="$FAKE_HOME" USER=smoke \
|
|
247
|
+
"$SH" "$LIB/fetch-fortify.sh" --version-id 123
|
|
248
|
+
expect_rc 4 "host override + no version id exits 4 (usage)" \
|
|
249
|
+
env HOME="$FAKE_HOME" USER=smoke FORTIFY_HOST_OVERRIDE=ssc.example.com \
|
|
250
|
+
"$SH" "$LIB/fetch-fortify.sh"
|
|
251
|
+
|
|
252
|
+
FORTIFY_URL="https://ssc.example.com/ssc/html/ssc/version/123/fix/ABCD-1"
|
|
253
|
+
expect_rc 2 "URL parse reaches credential stage" \
|
|
254
|
+
env HOME="$FAKE_HOME" USER=smoke FORTIFY_HOST_OVERRIDE=ssc.example.com \
|
|
255
|
+
"$SH" "$LIB/fetch-fortify.sh" "$FORTIFY_URL"
|
|
256
|
+
assert_in_file "missing-token" "$ERR" "blocked JSON names missing-token"
|
|
257
|
+
|
|
258
|
+
expect_rc 0 "stub token + offline curl degrades to empty result (exit 0)" \
|
|
259
|
+
env HOME="$FAKE_HOME" USER=smoke FORTIFY_HOST_OVERRIDE=ssc.example.com \
|
|
260
|
+
FAKE_CRED_VALUE=fake-token PATH="$BIN_CURL_FAIL:$PATH" \
|
|
261
|
+
"$SH" "$LIB/fetch-fortify.sh" "$FORTIFY_URL"
|
|
262
|
+
assert_in_file '"gateOutcome"' "$OUT" "normalized JSON still emitted"
|
|
263
|
+
|
|
264
|
+
# -----------------------------------------------------------------------------
|
|
265
|
+
note "5. figma-screenshot.sh"
|
|
266
|
+
# -----------------------------------------------------------------------------
|
|
267
|
+
expect_rc 1 "no args exits 1 (usage)" \
|
|
268
|
+
"$SH" "$LIB/figma-screenshot.sh"
|
|
269
|
+
expect_rc 0 "--help exits 0" \
|
|
270
|
+
"$SH" "$LIB/figma-screenshot.sh" --help
|
|
271
|
+
|
|
272
|
+
expect_rc 3 "no PAT resolvable exits 3 (Tier 3 required)" \
|
|
273
|
+
env -u FIGMA_PAT HOME="$FAKE_HOME" USER=smoke \
|
|
274
|
+
"$SH" "$LIB/figma-screenshot.sh" --file-key abc123 --node-id 1-2 \
|
|
275
|
+
--output-dir "$WORK/shots-no-pat"
|
|
276
|
+
|
|
277
|
+
expect_rc 2 "stub PAT + 401 curl exits 2 (auth stage, past parsing)" \
|
|
278
|
+
env HOME="$FAKE_HOME" USER=smoke FIGMA_PAT=fake-pat \
|
|
279
|
+
PATH="$BIN_CURL_401:$PATH" \
|
|
280
|
+
"$SH" "$LIB/figma-screenshot.sh" --file-key abc123 --node-id 1-2 \
|
|
281
|
+
--output-dir "$WORK/shots-401"
|
|
282
|
+
|
|
283
|
+
# -----------------------------------------------------------------------------
|
|
284
|
+
note "6. figma-mcp-refresh.sh"
|
|
285
|
+
# -----------------------------------------------------------------------------
|
|
286
|
+
expect_rc 2 "missing prefs exits 2" \
|
|
287
|
+
env HOME="$FAKE_HOME" USER=smoke \
|
|
288
|
+
"$SH" "$LIB/figma-mcp-refresh.sh"
|
|
289
|
+
|
|
290
|
+
if command -v jq >/dev/null 2>&1; then
|
|
291
|
+
PREFS_HOME="$WORK/home-prefs"
|
|
292
|
+
mkdir -p "$PREFS_HOME/.claude"
|
|
293
|
+
printf '{"global":{"keychainMapping":{"figma_mcp":"Smoke_Figma_MCP"}}}\n' \
|
|
294
|
+
> "$PREFS_HOME/.claude/multi-agent-preferences.json"
|
|
295
|
+
export SMOKE_CAPTURE_DIR="$WORK"
|
|
296
|
+
expect_rc 2 "mapped key + empty keychain exits 2 (no refresh token)" \
|
|
297
|
+
env HOME="$PREFS_HOME" USER=smoke SMOKE_CAPTURE_DIR="$WORK" \
|
|
298
|
+
PATH="$BIN_DARWIN:$PATH" \
|
|
299
|
+
"$SH" "$LIB/figma-mcp-refresh.sh"
|
|
300
|
+
else
|
|
301
|
+
note " (jq not installed - skipping mapped-key case)"
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
# -----------------------------------------------------------------------------
|
|
305
|
+
note "7. credential-store.sh + keychain-save.sh"
|
|
306
|
+
# -----------------------------------------------------------------------------
|
|
307
|
+
expect_rc 0 "platform subcommand works" \
|
|
308
|
+
"$SH" "$LIB/credential-store.sh" platform
|
|
309
|
+
expect_rc 3 "get without key exits 3 (usage)" \
|
|
310
|
+
"$SH" "$LIB/credential-store.sh" get
|
|
311
|
+
expect_rc 1 "keychain-save without service name exits 1 (usage)" \
|
|
312
|
+
"$SH" "$SCRIPTS/keychain-save.sh"
|
|
313
|
+
|
|
314
|
+
# set <key> - : the secret must travel via stdin to `security -i`, never on
|
|
315
|
+
# the security argv. Fake uname forces the macOS branch; fake security logs
|
|
316
|
+
# argv and stdin separately.
|
|
317
|
+
rm -f "$WORK/security-argv.log" "$WORK/security-stdin.log"
|
|
318
|
+
SECRET_VALUE="topsecret-smoke-value-123"
|
|
319
|
+
rc=0
|
|
320
|
+
printf '%s' "$SECRET_VALUE" \
|
|
321
|
+
| env PATH="$BIN_DARWIN:$PATH" KEYCHAIN_DELEGATE=0 SMOKE_CAPTURE_DIR="$WORK" \
|
|
322
|
+
"$SH" "$LIB/credential-store.sh" set smoke-test-key - >"$OUT" 2>"$ERR" || rc=$?
|
|
323
|
+
if [ "$rc" -eq 0 ]; then
|
|
324
|
+
pass "set <key> - via stdin succeeds (rc=0)"
|
|
325
|
+
else
|
|
326
|
+
fail "set <key> - via stdin failed (rc=$rc)"
|
|
327
|
+
tail -5 "$ERR" | sed 's/^/ /'
|
|
328
|
+
fi
|
|
329
|
+
assert_in_file "^-i" "$WORK/security-argv.log" "security invoked in -i stdin mode"
|
|
330
|
+
assert_not_in_file "$SECRET_VALUE" "$WORK/security-argv.log" "secret absent from security argv"
|
|
331
|
+
assert_in_file "$SECRET_VALUE" "$WORK/security-stdin.log" "secret delivered via stdin"
|
|
332
|
+
|
|
333
|
+
# -----------------------------------------------------------------------------
|
|
334
|
+
note "8. static source checks (bash-3.2 compat + no secrets on argv)"
|
|
335
|
+
# -----------------------------------------------------------------------------
|
|
336
|
+
OWNED_FILES=(
|
|
337
|
+
"$LIB/fetch-confluence.sh"
|
|
338
|
+
"$LIB/fetch-crashlytics.sh"
|
|
339
|
+
"$LIB/fetch-swagger.sh"
|
|
340
|
+
"$LIB/fetch-fortify.sh"
|
|
341
|
+
"$LIB/figma-mcp-refresh.sh"
|
|
342
|
+
"$LIB/figma-screenshot.sh"
|
|
343
|
+
"$LIB/credential-store.sh"
|
|
344
|
+
"$SCRIPTS/keychain-save.sh"
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
if grep -nE '\$\{[A-Za-z_0-9]+(,,|\^\^)|mapfile |readarray |declare -A' "${OWNED_FILES[@]}"; then
|
|
348
|
+
fail "bash-4-only construct found (must run on /bin/bash 3.2)"
|
|
349
|
+
else
|
|
350
|
+
pass "no bash-4-only constructs"
|
|
351
|
+
fi
|
|
352
|
+
|
|
353
|
+
if grep -nE -- '-H "(Authorization|X-Figma-Token)' "${OWNED_FILES[@]}"; then
|
|
354
|
+
fail "auth header passed to curl via argv (-H)"
|
|
355
|
+
else
|
|
356
|
+
pass "no auth headers on curl argv"
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
if grep -nE -- '--data-urlencode "(refresh_token|client_secret)' "${OWNED_FILES[@]}"; then
|
|
360
|
+
fail "OAuth secret passed to curl via argv (--data-urlencode)"
|
|
361
|
+
else
|
|
362
|
+
pass "no OAuth secrets on curl argv"
|
|
363
|
+
fi
|
|
364
|
+
|
|
365
|
+
if grep -nE 'add-generic-password[^|]*-w "\$' "${OWNED_FILES[@]}"; then
|
|
366
|
+
fail "keychain secret passed to security via argv (-w)"
|
|
367
|
+
else
|
|
368
|
+
pass "no keychain secrets on security argv"
|
|
369
|
+
fi
|
|
370
|
+
|
|
371
|
+
# shellcheck disable=SC2016 # literal $HOME is the pattern being hunted
|
|
372
|
+
if grep -nE '\$HOME/\.claude/lib/credential-store\.sh|~/\.claude/lib/credential-store\.sh' "${OWNED_FILES[@]}"; then
|
|
373
|
+
fail "hardcoded credential-store path (must go through the resolver)"
|
|
374
|
+
else
|
|
375
|
+
pass "credential-store access routed through the resolver"
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
# -----------------------------------------------------------------------------
|
|
379
|
+
echo ""
|
|
380
|
+
echo "smoke-fetchers-offline: $PASS passed, $FAIL failed"
|
|
381
|
+
[ "$FAIL" -eq 0 ] || exit 1
|
|
382
|
+
exit 0
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# smoke-gc-tmp.sh - gc-tmp.sh sweeps only known scratch prefixes, is dry-run
|
|
3
|
-
# by default,
|
|
3
|
+
# by default, never touches unrelated files, refuses unsafe scratch roots,
|
|
4
|
+
# never deletes the root itself, and spares fresh scratch by default.
|
|
4
5
|
#
|
|
5
6
|
# Exit 0 = all pass, 1 = any failure.
|
|
6
7
|
|
|
@@ -40,7 +41,7 @@ seed() {
|
|
|
40
41
|
echo "→ 1. dry-run lists matches and deletes nothing"
|
|
41
42
|
seed
|
|
42
43
|
before=$(find "$SB" -mindepth 1 | wc -l | tr -d ' ')
|
|
43
|
-
out=$(GC_TMP_ROOT="$SB" bash "$GC" 2>&1)
|
|
44
|
+
out=$(GC_TMP_ROOT="$SB" GC_TMP_GRACE_MIN=0 bash "$GC" 2>&1)
|
|
44
45
|
after=$(find "$SB" -mindepth 1 | wc -l | tr -d ' ')
|
|
45
46
|
echo "$out" | grep -q "DRY-RUN" && pass "dry-run banner shown" || fail "dry-run banner missing"
|
|
46
47
|
[ "$before" = "$after" ] && pass "dry-run deleted nothing ($after entries intact)" || fail "dry-run changed the tree ($before -> $after)"
|
|
@@ -49,7 +50,7 @@ echo "$out" | grep -q "unrelated-file.txt" && fail "dry-run matched an unrelated
|
|
|
49
50
|
echo ""
|
|
50
51
|
echo "→ 2. --yes removes only matched scratch"
|
|
51
52
|
seed
|
|
52
|
-
GC_TMP_ROOT="$SB" bash "$GC" --yes >/dev/null 2>&1
|
|
53
|
+
GC_TMP_ROOT="$SB" GC_TMP_GRACE_MIN=0 bash "$GC" --yes >/dev/null 2>&1
|
|
53
54
|
[ -f "$SB/unrelated-file.txt" ] && pass "unrelated file preserved" || fail "unrelated file was deleted"
|
|
54
55
|
[ ! -e "$SB/multi-agent-picker-PROJ-1.json" ] && pass "multi-agent-* file removed" || fail "multi-agent-* file survived"
|
|
55
56
|
[ ! -e "$SB/multi-agent-review-PROJ-1-x" ] && pass "scratch dir removed" || fail "scratch dir survived"
|
|
@@ -61,7 +62,7 @@ remaining=$(find "$SB" -mindepth 1 | wc -l | tr -d ' ')
|
|
|
61
62
|
echo ""
|
|
62
63
|
echo "→ 3. --older-than excludes fresh scratch"
|
|
63
64
|
seed
|
|
64
|
-
out=$(GC_TMP_ROOT="$SB" bash "$GC" --older-than=1 2>&1)
|
|
65
|
+
out=$(GC_TMP_ROOT="$SB" GC_TMP_GRACE_MIN=0 bash "$GC" --older-than=1 2>&1)
|
|
65
66
|
echo "$out" | grep -q "nothing to do" && pass "freshly-created scratch excluded by --older-than=1" || fail "--older-than did not exclude fresh files"
|
|
66
67
|
|
|
67
68
|
echo ""
|
|
@@ -79,6 +80,51 @@ rc=$?
|
|
|
79
80
|
rm -rf "$EMPTY"
|
|
80
81
|
{ [ "$rc" = "0" ] && echo "$out" | grep -q "nothing to do"; } && pass "empty root exits 0 with no-op message" || fail "empty root not a clean no-op"
|
|
81
82
|
|
|
83
|
+
echo ""
|
|
84
|
+
echo "→ 6. unsafe scratch roots are refused (exit 2)"
|
|
85
|
+
GC_TMP_ROOT="$HOME" bash "$GC" >/dev/null 2>&1
|
|
86
|
+
[ "$?" = "2" ] && pass "\$HOME as scratch root refused" || fail "\$HOME as scratch root not refused"
|
|
87
|
+
GC_TMP_ROOT="/" bash "$GC" >/dev/null 2>&1
|
|
88
|
+
[ "$?" = "2" ] && pass "/ as scratch root refused" || fail "/ as scratch root not refused"
|
|
89
|
+
GITROOT="$(mktemp -d)"
|
|
90
|
+
mkdir -p "$GITROOT/.git"
|
|
91
|
+
touch "$GITROOT/multi-agent-bait.json"
|
|
92
|
+
GC_TMP_ROOT="$GITROOT" bash "$GC" --yes >/dev/null 2>&1
|
|
93
|
+
rc=$?
|
|
94
|
+
[ "$rc" = "2" ] && pass "git work tree as scratch root refused" || fail "git work tree as scratch root not refused"
|
|
95
|
+
[ -f "$GITROOT/multi-agent-bait.json" ] && pass "bait file inside refused root untouched" || fail "refused root was still swept"
|
|
96
|
+
rm -rf "$GITROOT"
|
|
97
|
+
|
|
98
|
+
echo ""
|
|
99
|
+
echo "→ 7. a root itself named like scratch is never deleted"
|
|
100
|
+
SR="$(mktemp -d)"
|
|
101
|
+
mkdir -p "$SR/multi-agent-root"
|
|
102
|
+
touch "$SR/multi-agent-root/multi-agent-inner.json"
|
|
103
|
+
GC_TMP_ROOT="$SR/multi-agent-root" GC_TMP_GRACE_MIN=0 bash "$GC" --yes >/dev/null 2>&1
|
|
104
|
+
[ -d "$SR/multi-agent-root" ] && pass "matching-named root survives --yes" || fail "root dir itself was deleted"
|
|
105
|
+
[ ! -e "$SR/multi-agent-root/multi-agent-inner.json" ] && pass "inner scratch still removed" || fail "inner scratch survived"
|
|
106
|
+
rm -rf "$SR"
|
|
107
|
+
|
|
108
|
+
echo ""
|
|
109
|
+
echo "→ 8. symlinked scratch is unlinked without following it"
|
|
110
|
+
seed
|
|
111
|
+
VICTIM="$(mktemp -d)"
|
|
112
|
+
echo keep >"$VICTIM/keep.txt"
|
|
113
|
+
ln -s "$VICTIM" "$SB/multi-agent-linked"
|
|
114
|
+
GC_TMP_ROOT="$SB" GC_TMP_GRACE_MIN=0 bash "$GC" --yes >/dev/null 2>&1
|
|
115
|
+
[ ! -e "$SB/multi-agent-linked" ] && pass "symlink itself removed" || fail "symlink survived"
|
|
116
|
+
[ -f "$VICTIM/keep.txt" ] && pass "symlink target untouched" || fail "symlink target was deleted"
|
|
117
|
+
rm -rf "$VICTIM"
|
|
118
|
+
|
|
119
|
+
echo ""
|
|
120
|
+
echo "→ 9. default grace spares fresh scratch"
|
|
121
|
+
seed
|
|
122
|
+
out=$(GC_TMP_ROOT="$SB" bash "$GC" --yes 2>&1)
|
|
123
|
+
rc=$?
|
|
124
|
+
[ "$rc" = "0" ] && pass "grace run exits 0" || fail "grace run exited $rc"
|
|
125
|
+
[ -f "$SB/multi-agent-picker-PROJ-1.json" ] && pass "fresh scratch spared by default grace" || fail "default grace deleted fresh scratch"
|
|
126
|
+
echo "$out" | grep -qi "spared" && pass "output mentions spared fresh items" || fail "no spared note in output"
|
|
127
|
+
|
|
82
128
|
echo ""
|
|
83
129
|
echo "══ gc-tmp smoke: $PASS passed, $FAIL failed ══"
|
|
84
130
|
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# smoke-gc-worktrees.sh - contract of the worktree residue sweeper.
|
|
3
|
+
#
|
|
4
|
+
# Verifies gc-worktrees.sh: dry-run-by-default, orphan-dir removal only with
|
|
5
|
+
# --yes, registered worktrees untouched, gitlink residue unstaged, exclude
|
|
6
|
+
# guard added idempotently, --older-than sparing, non-repo no-op, and flag
|
|
7
|
+
# validation.
|
|
8
|
+
|
|
9
|
+
set -uo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
SCRIPT="$SCRIPT_DIR/gc-worktrees.sh"
|
|
13
|
+
|
|
14
|
+
PASS=0
|
|
15
|
+
FAIL=0
|
|
16
|
+
record_pass() { PASS=$((PASS + 1)); printf ' \342\234\223 %s\n' "$1"; }
|
|
17
|
+
record_fail() { FAIL=$((FAIL + 1)); printf ' \342\234\227 %s\n' "$1"; }
|
|
18
|
+
|
|
19
|
+
printf '\342\206\222 smoke-gc-worktrees: worktree residue sweep contract\n'
|
|
20
|
+
|
|
21
|
+
tmpdir=$(mktemp -d)
|
|
22
|
+
trap 'rm -rf "$tmpdir"' EXIT
|
|
23
|
+
|
|
24
|
+
# Fixture repo: one registered worktree, one orphan dir, one gitlink residue.
|
|
25
|
+
REPO="$tmpdir/repo"
|
|
26
|
+
mkdir -p "$REPO"
|
|
27
|
+
git -C "$REPO" init -q -b main
|
|
28
|
+
git -C "$REPO" config user.email smoke@test
|
|
29
|
+
git -C "$REPO" config user.name smoke
|
|
30
|
+
echo hi > "$REPO/f.txt"
|
|
31
|
+
git -C "$REPO" add f.txt
|
|
32
|
+
git -C "$REPO" commit -qm init
|
|
33
|
+
git -C "$REPO" worktree add -q "$REPO/.worktrees/REG" -b reg-branch
|
|
34
|
+
mkdir -p "$REPO/.worktrees/ORPHAN"
|
|
35
|
+
echo x > "$REPO/.worktrees/ORPHAN/file"
|
|
36
|
+
git -C "$REPO" update-index --add --cacheinfo "160000,$(git -C "$REPO" rev-parse HEAD),.worktrees/GONE"
|
|
37
|
+
|
|
38
|
+
# 1. Script parses
|
|
39
|
+
if bash -n "$SCRIPT" 2>/dev/null; then
|
|
40
|
+
record_pass "gc-worktrees.sh parses (bash -n)"
|
|
41
|
+
else
|
|
42
|
+
record_fail "gc-worktrees.sh has syntax errors"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# 2. Dry-run lists all three findings and deletes nothing
|
|
46
|
+
out=$(bash "$SCRIPT" --repo "$REPO" 2>&1); rc=$?
|
|
47
|
+
if [ "$rc" -eq 0 ] \
|
|
48
|
+
&& printf '%s' "$out" | grep -q "would remove orphan worktree dir: .worktrees/ORPHAN" \
|
|
49
|
+
&& printf '%s' "$out" | grep -q "would unstage gitlink residue from the index: .worktrees/GONE" \
|
|
50
|
+
&& printf '%s' "$out" | grep -q "would add .worktrees/ to .git/info/exclude"; then
|
|
51
|
+
record_pass "dry-run lists orphan dir + gitlink + missing guard"
|
|
52
|
+
else
|
|
53
|
+
record_fail "dry-run should list all three findings (rc=$rc): $out"
|
|
54
|
+
fi
|
|
55
|
+
if [ -d "$REPO/.worktrees/ORPHAN" ] && git -C "$REPO" ls-files -s -- .worktrees | grep -q 160000; then
|
|
56
|
+
record_pass "dry-run changed nothing"
|
|
57
|
+
else
|
|
58
|
+
record_fail "dry-run must not delete or unstage anything"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# 3. --older-than spares the freshly-touched orphan
|
|
62
|
+
out=$(bash "$SCRIPT" --repo "$REPO" --older-than=60 2>&1)
|
|
63
|
+
if printf '%s' "$out" | grep -q "would remove orphan"; then
|
|
64
|
+
record_fail "--older-than=60 should spare the fresh orphan dir"
|
|
65
|
+
else
|
|
66
|
+
record_pass "--older-than spares recently-touched orphan dirs"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# 4. --yes applies all three
|
|
70
|
+
out=$(bash "$SCRIPT" --repo "$REPO" --yes 2>&1); rc=$?
|
|
71
|
+
if [ "$rc" -eq 0 ] && [ ! -d "$REPO/.worktrees/ORPHAN" ]; then
|
|
72
|
+
record_pass "--yes removes the orphan dir"
|
|
73
|
+
else
|
|
74
|
+
record_fail "--yes should remove the orphan dir (rc=$rc): $out"
|
|
75
|
+
fi
|
|
76
|
+
if git -C "$REPO" ls-files -s -- .worktrees | grep -q 160000; then
|
|
77
|
+
record_fail "--yes should unstage the gitlink residue"
|
|
78
|
+
else
|
|
79
|
+
record_pass "--yes unstages the gitlink residue"
|
|
80
|
+
fi
|
|
81
|
+
if [ "$(grep -cxF '.worktrees/' "$REPO/.git/info/exclude" 2>/dev/null)" = "1" ]; then
|
|
82
|
+
record_pass "--yes adds the exclude guard exactly once"
|
|
83
|
+
else
|
|
84
|
+
record_fail "exclude guard missing or duplicated"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# 5. Registered worktree untouched
|
|
88
|
+
if [ -d "$REPO/.worktrees/REG" ] && git -C "$REPO" worktree list | grep -q "REG"; then
|
|
89
|
+
record_pass "registered worktree left untouched"
|
|
90
|
+
else
|
|
91
|
+
record_fail "registered worktree must never be touched"
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# 6. Re-run -> nothing to do, guard not duplicated
|
|
95
|
+
out=$(bash "$SCRIPT" --repo "$REPO" 2>&1); rc=$?
|
|
96
|
+
if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q "nothing to do"; then
|
|
97
|
+
record_pass "clean repo -> nothing to do (exit 0)"
|
|
98
|
+
else
|
|
99
|
+
record_fail "clean repo should be a no-op (rc=$rc): $out"
|
|
100
|
+
fi
|
|
101
|
+
bash "$SCRIPT" --repo "$REPO" --yes >/dev/null 2>&1
|
|
102
|
+
if [ "$(grep -cxF '.worktrees/' "$REPO/.git/info/exclude")" = "1" ]; then
|
|
103
|
+
record_pass "exclude guard is idempotent across --yes reruns"
|
|
104
|
+
else
|
|
105
|
+
record_fail "exclude guard duplicated on rerun"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# 7. Non-repo -> silent no-op exit 0
|
|
109
|
+
out=$(bash "$SCRIPT" --repo "$tmpdir" 2>&1); rc=$?
|
|
110
|
+
if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q "not a git work tree"; then
|
|
111
|
+
record_pass "non-repo -> exit 0 no-op"
|
|
112
|
+
else
|
|
113
|
+
record_fail "non-repo should exit 0 with a no-op message (rc=$rc)"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# 8. Unknown flag -> usage error exit 2
|
|
117
|
+
bash "$SCRIPT" --definitely-not-a-flag >/dev/null 2>&1; rc=$?
|
|
118
|
+
if [ "$rc" -eq 2 ]; then
|
|
119
|
+
record_pass "unknown flag -> exit 2"
|
|
120
|
+
else
|
|
121
|
+
record_fail "unknown flag should exit 2 (got $rc)"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
printf '\n\342\225\220\342\225\220 smoke-gc-worktrees: %d passed, %d failed \342\225\220\342\225\220\n' "$PASS" "$FAIL"
|
|
125
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -110,11 +110,19 @@ assert_file "$TMP_CLAUDE/.claude/CLAUDE.md" "~/.claude/CLAUDE.md"
|
|
|
110
110
|
assert_file "$TMP_CLAUDE/.claude/multi-agent-preferences.json" "~/.claude/multi-agent-preferences.json"
|
|
111
111
|
assert_file "$TMP_CLAUDE/.claude/settings.json" "~/.claude/settings.json"
|
|
112
112
|
|
|
113
|
-
# settings.json must have the secret-detection hook
|
|
113
|
+
# settings.json must have the secret-detection hook. v11.4.1 changed the
|
|
114
|
+
# matcher from the dead "Bash(git commit:*)" (tool-name matchers never matched
|
|
115
|
+
# it) to "Bash"; command-level filtering happens inside pre-commit-check.sh.
|
|
116
|
+
if grep -q '"matcher": "Bash"' "$TMP_CLAUDE/.claude/settings.json" \
|
|
117
|
+
&& grep -q "pre-commit-check.sh" "$TMP_CLAUDE/.claude/settings.json"; then
|
|
118
|
+
pass "settings.json registers PreToolUse secret-scan hook (matcher Bash)"
|
|
119
|
+
else
|
|
120
|
+
fail "settings.json missing PreToolUse secret-scan hook with matcher Bash"
|
|
121
|
+
fi
|
|
114
122
|
if grep -q "Bash(git commit:\*)" "$TMP_CLAUDE/.claude/settings.json"; then
|
|
115
|
-
|
|
123
|
+
fail "settings.json still contains the dead Bash(git commit:*) matcher"
|
|
116
124
|
else
|
|
117
|
-
|
|
125
|
+
pass "settings.json has no dead Bash(git commit:*) matcher"
|
|
118
126
|
fi
|
|
119
127
|
if grep -q "CLAUDE_AUTOCOMPACT_PCT_OVERRIDE" "$TMP_CLAUDE/.claude/settings.json"; then
|
|
120
128
|
pass "settings.json sets CLAUDE_AUTOCOMPACT_PCT_OVERRIDE"
|