@iceinvein/agent-skills 0.22.0 → 0.23.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/README.md +1 -1
- package/package.json +1 -1
- package/skills/bug-hunter/SKILL.md +5 -0
- package/skills/bug-hunter/evals/README.md +6 -1
- package/skills/bug-hunter/evals/callers-tree-unchanged/graders/output-ignores-itself.md +11 -0
- package/skills/bug-hunter/evals/rules.json +8 -0
- package/skills/bug-hunter/skill.json +1 -1
- package/skills/index.json +4 -4
- package/skills/magpie/README.md +5 -3
- package/skills/magpie/SKILL.md +40 -15
- package/skills/magpie/bin/magpie.ts +83 -7
- package/skills/magpie/evals/README.md +7 -4
- package/skills/magpie/evals/local-mode-hands-back/case.yaml +4 -0
- package/skills/magpie/evals/local-mode-hands-back/fixture.sh +300 -0
- package/skills/magpie/evals/local-mode-hands-back/graders/branch-was-reachable.md +5 -0
- package/skills/magpie/evals/local-mode-hands-back/graders/cleanup-after-handoff.md +6 -0
- package/skills/magpie/evals/local-mode-hands-back/graders/handoff-was-run.md +6 -0
- package/skills/magpie/evals/local-mode-hands-back/graders/nothing-served-or-posted.md +7 -0
- package/skills/magpie/evals/local-mode-hands-back/graders/reply-lists-both-findings.md +10 -0
- package/skills/magpie/evals/local-mode-hands-back/graders/skill-fired.md +5 -0
- package/skills/magpie/evals/local-mode-hands-back/prompt.md +11 -0
- package/skills/magpie/evals/quality/README.md +73 -2
- package/skills/magpie/evals/quality/__tests__/recall.test.ts +234 -0
- package/skills/magpie/evals/quality/__tests__/replay-full.test.ts +164 -0
- package/skills/magpie/evals/quality/corpus.ts +4 -1
- package/skills/magpie/evals/quality/recall.ts +249 -0
- package/skills/magpie/evals/quality/replay-critic.ts +3 -0
- package/skills/magpie/evals/quality/replay-full.ts +168 -6
- package/skills/magpie/evals/rules.json +9 -1
- package/skills/magpie/references/checks.md +78 -0
- package/skills/magpie/references/peer-review.md +7 -0
- package/skills/magpie/references/scout.md +12 -5
- package/skills/magpie/references/specialists.md +10 -8
- package/skills/magpie/scripts/__tests__/cleanup-cmd.test.ts +94 -0
- package/skills/magpie/scripts/__tests__/critic-cmd.test.ts +18 -0
- package/skills/magpie/scripts/__tests__/dedupe-cmd.test.ts +80 -1
- package/skills/magpie/scripts/__tests__/handoff-cmd.test.ts +319 -0
- package/skills/magpie/scripts/__tests__/housekeeping-cmd.test.ts +14 -0
- package/skills/magpie/scripts/__tests__/local-diff.test.ts +275 -0
- package/skills/magpie/scripts/__tests__/pipeline-e2e.test.ts +1 -0
- package/skills/magpie/scripts/__tests__/resolve-run.test.ts +28 -0
- package/skills/magpie/scripts/__tests__/server.test.ts +30 -1
- package/skills/magpie/scripts/__tests__/setup-cmd.test.ts +324 -1
- package/skills/magpie/scripts/__tests__/skill-lint.test.ts +8 -5
- package/skills/magpie/scripts/__tests__/status-cmd.test.ts +44 -0
- package/skills/magpie/scripts/__tests__/target.test.ts +84 -0
- package/skills/magpie/scripts/__tests__/types.test.ts +2 -1
- package/skills/magpie/scripts/cleanup-cmd.ts +5 -1
- package/skills/magpie/scripts/critic-cmd.ts +2 -1
- package/skills/magpie/scripts/dedupe-cmd.ts +15 -5
- package/skills/magpie/scripts/handoff-cmd.ts +132 -0
- package/skills/magpie/scripts/housekeeping-cmd.ts +7 -5
- package/skills/magpie/scripts/local-diff.ts +228 -0
- package/skills/magpie/scripts/post-cmd.ts +1 -0
- package/skills/magpie/scripts/render-annotation.ts +1 -0
- package/skills/magpie/scripts/resolve-run.ts +17 -3
- package/skills/magpie/scripts/server.ts +9 -1
- package/skills/magpie/scripts/setup-cmd.ts +241 -49
- package/skills/magpie/scripts/status-cmd.ts +22 -6
- package/skills/magpie/scripts/target.ts +56 -0
- package/skills/magpie/scripts/types.ts +1 -0
- package/skills/magpie/skill.json +2 -2
- package/skills/sluice/SKILL.md +3 -1
- package/skills/sluice/evals/README.md +10 -2
- package/skills/sluice/evals/fast-leaves-an-unrelated-red-test-alone/case.yaml +4 -0
- package/skills/sluice/evals/fast-leaves-an-unrelated-red-test-alone/fixture.sh +108 -0
- package/skills/sluice/evals/fast-leaves-an-unrelated-red-test-alone/graders/deploy-names-its-steps.md +9 -0
- package/skills/sluice/evals/fast-leaves-an-unrelated-red-test-alone/graders/kept-status-by-changing-code.md +12 -0
- package/skills/sluice/evals/fast-leaves-an-unrelated-red-test-alone/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/fast-leaves-an-unrelated-red-test-alone/graders/status-test-unchanged.md +6 -0
- package/skills/sluice/evals/fast-leaves-an-unrelated-red-test-alone/prompt.md +11 -0
- package/skills/sluice/evals/fast-updates-the-test-the-change-replaces/case.yaml +4 -0
- package/skills/sluice/evals/fast-updates-the-test-the-change-replaces/fixture.sh +73 -0
- package/skills/sluice/evals/fast-updates-the-test-the-change-replaces/graders/proved-after-the-last-edit.md +18 -0
- package/skills/sluice/evals/fast-updates-the-test-the-change-replaces/graders/sluice-fired.md +5 -0
- package/skills/sluice/evals/fast-updates-the-test-the-change-replaces/graders/test-expects-the-new-line.md +5 -0
- package/skills/sluice/evals/fast-updates-the-test-the-change-replaces/graders/updated-without-asking.md +11 -0
- package/skills/sluice/evals/fast-updates-the-test-the-change-replaces/prompt.md +11 -0
- package/skills/sluice/evals/rules.json +11 -0
- package/skills/sluice/references/review.md +6 -4
- package/skills/sluice/references/test-first.md +24 -4
- package/skills/sluice/scripts/content-digest.sh +1 -1
- package/skills/sluice/scripts/run-stats.sh +14 -0
- package/skills/sluice/scripts/session-start.sh +44 -2
- package/skills/sluice/scripts/status.sh +293 -0
- package/skills/sluice/scripts/stop-guard.sh +80 -6
- package/skills/sluice/scripts/test-changes.sh +135 -0
- package/skills/sluice/skill.json +1 -1
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# A local review of the workspace's own branch, done through peer review. The
|
|
3
|
+
# local ladder ends at handoff: print the findings to the caller, archive the
|
|
4
|
+
# run, end the turn. There is no PR, so nothing is served, rendered or posted.
|
|
5
|
+
#
|
|
6
|
+
# The workspace is the repo under review, because local mode reviews the
|
|
7
|
+
# caller's working directory in place. The run directory sits under the
|
|
8
|
+
# workspace rather than ~/.magpie because file graders refuse to follow a link
|
|
9
|
+
# out of the workspace. `magpie --list-runs` is what names the path a resume
|
|
10
|
+
# uses, so the shim reports this one.
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
BRANCH="feat/tenant-settings-cache"
|
|
14
|
+
RUN_ID="local-feat-tenant-settings-cache-1789600000"
|
|
15
|
+
RUN_DIR="$PWD/runs/$RUN_ID"
|
|
16
|
+
ARCHIVE_DIR="$PWD/archive"
|
|
17
|
+
CALLS="$PWD/.magpie-calls.log"
|
|
18
|
+
|
|
19
|
+
mkdir -p "$RUN_DIR"/findings "$HOME/shims"
|
|
20
|
+
: > "$CALLS"
|
|
21
|
+
|
|
22
|
+
cat > "$HOME/shim-config" <<EOF
|
|
23
|
+
RUN_ID="$RUN_ID"
|
|
24
|
+
RUN_DIR="$RUN_DIR"
|
|
25
|
+
ARCHIVE_DIR="$ARCHIVE_DIR"
|
|
26
|
+
CALLS="$CALLS"
|
|
27
|
+
EOF
|
|
28
|
+
|
|
29
|
+
# The real magpie and gh are outside the eval sandbox and cannot be executed
|
|
30
|
+
# from inside it, so the child gets fakes rather than exit 126. The gh fake is
|
|
31
|
+
# here to catch a post that goes around the CLI: a local run has no PR at all.
|
|
32
|
+
mkdir -p "$HOME/tmp"
|
|
33
|
+
cat > "$HOME/.zshenv" <<'RC'
|
|
34
|
+
export PATH="$HOME/shims:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
35
|
+
# /usr/bin/python3 is the Xcode shim, and without a writable TMPDIR it fails
|
|
36
|
+
# trying to create its xcrun cache in a directory the sandbox blocks.
|
|
37
|
+
export TMPDIR="$HOME/tmp"
|
|
38
|
+
RC
|
|
39
|
+
|
|
40
|
+
# Unlike the PR cases' fakes, status reads target.json and picks the ladder the
|
|
41
|
+
# way scripts/status-cmd.ts does, so this run resumes at handoff, not report.
|
|
42
|
+
cat > "$HOME/shims/magpie" <<'SHIM'
|
|
43
|
+
#!/usr/bin/env bash
|
|
44
|
+
. "$HOME/shim-config"
|
|
45
|
+
echo "magpie $*" >> "$CALLS"
|
|
46
|
+
case "${1:-}" in
|
|
47
|
+
--list-runs)
|
|
48
|
+
if [ -d "$RUN_DIR" ]; then
|
|
49
|
+
printf '%s\tactive\t%s\n' "$RUN_ID" "$RUN_DIR"
|
|
50
|
+
else
|
|
51
|
+
printf '%s\tarchived\t%s\n' "$RUN_ID" "$ARCHIVE_DIR/$RUN_ID"
|
|
52
|
+
fi
|
|
53
|
+
;;
|
|
54
|
+
status)
|
|
55
|
+
python3 - "${2:-$RUN_DIR}" <<'STATUS'
|
|
56
|
+
import json, pathlib, sys
|
|
57
|
+
|
|
58
|
+
run = pathlib.Path(sys.argv[1])
|
|
59
|
+
PR = ['setup', 'context', 'specialists', 'dedupe', 'critic', 'peer-review', 'report', 'post']
|
|
60
|
+
LOCAL = ['setup', 'context', 'specialists', 'dedupe', 'critic', 'peer-review', 'handoff']
|
|
61
|
+
target = run / 'target.json'
|
|
62
|
+
mode = json.loads(target.read_text())['mode'] if target.exists() else 'pr'
|
|
63
|
+
ORDER = LOCAL if mode == 'local' else PR
|
|
64
|
+
last, error = None, None
|
|
65
|
+
for line in (run / 'log.jsonl').read_text().splitlines():
|
|
66
|
+
if not line.strip():
|
|
67
|
+
continue
|
|
68
|
+
try:
|
|
69
|
+
entry = json.loads(line)
|
|
70
|
+
except ValueError:
|
|
71
|
+
continue
|
|
72
|
+
if entry.get('status') == 'error':
|
|
73
|
+
error = entry.get('stage')
|
|
74
|
+
break
|
|
75
|
+
if entry.get('status') in ('done', 'skipped') and entry.get('stage') in ORDER:
|
|
76
|
+
last = entry['stage']
|
|
77
|
+
index = ORDER.index(last) + 1 if last else 0
|
|
78
|
+
print(json.dumps({'lastCompleted': last, 'next': ORDER[index] if index < len(ORDER) else 'cleanup', 'error': error}))
|
|
79
|
+
STATUS
|
|
80
|
+
;;
|
|
81
|
+
target)
|
|
82
|
+
python3 -c 'import json, sys; print(json.load(open(sys.argv[1]))["root"])' "${2:-$RUN_DIR}/target.json"
|
|
83
|
+
;;
|
|
84
|
+
handoff)
|
|
85
|
+
python3 - "${2:-$RUN_DIR}" <<'HANDOFF'
|
|
86
|
+
import json, pathlib, sys, time
|
|
87
|
+
|
|
88
|
+
run = pathlib.Path(sys.argv[1])
|
|
89
|
+
if json.loads((run / 'target.json').read_text())['mode'] != 'local':
|
|
90
|
+
sys.stderr.write(f'magpie: handoff only applies to local runs; {run} is a PR run\n')
|
|
91
|
+
sys.exit(2)
|
|
92
|
+
for name in ('findings.final.json', 'local.json'):
|
|
93
|
+
if not (run / name).exists():
|
|
94
|
+
sys.stderr.write(f'magpie: handoff: {run / name} is missing\n')
|
|
95
|
+
sys.exit(4)
|
|
96
|
+
findings = json.loads((run / 'findings.final.json').read_text())
|
|
97
|
+
meta = json.loads((run / 'local.json').read_text())
|
|
98
|
+
subject = 'detached HEAD' if meta['branch'] is None else f"local branch {meta['branch']}"
|
|
99
|
+
# The labels the fixture's findings use, bolded as formatFindingDescriptionMarkdown does.
|
|
100
|
+
LABELS = ('Observation', 'Why it matters', 'Suggested direction')
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def markdown(description):
|
|
104
|
+
paragraphs = description.split('\n\n')
|
|
105
|
+
for label in LABELS:
|
|
106
|
+
paragraphs = [f'**{label}:**' + p[len(label) + 1:] if p.startswith(f'{label}:') else p for p in paragraphs]
|
|
107
|
+
return '\n\n'.join(paragraphs)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
rank = {'must-fix': 0, 'should-fix': 1}
|
|
111
|
+
actionable = sorted((f for f in findings if f['risk']['action'] in rank), key=lambda f: rank[f['risk']['action']])
|
|
112
|
+
optional = [f for f in findings if f['risk']['action'] not in rank]
|
|
113
|
+
counts = {a: sum(f['risk']['action'] == a for f in findings) for a in rank}
|
|
114
|
+
sections = [
|
|
115
|
+
f"# magpie local review: {subject} against {meta['baseRef']}",
|
|
116
|
+
f"{counts['must-fix']} must-fix, {counts['should-fix']} should-fix, {len(optional)} optional",
|
|
117
|
+
]
|
|
118
|
+
for n, f in enumerate(actionable + optional, 1):
|
|
119
|
+
anchor = f['file'] if f.get('line') is None else f"{f['file']}:{f['line']}"
|
|
120
|
+
sections.append(f"{n}. [{f['risk']['action']}] {anchor}: {f['title']}\n\n{markdown(f['description'])}")
|
|
121
|
+
sections.append('Address every must-fix and should-fix, or say why not. The rest are optional.')
|
|
122
|
+
print('\n\n'.join(sections))
|
|
123
|
+
with (run / 'log.jsonl').open('a') as log:
|
|
124
|
+
log.write(json.dumps({'stage': 'handoff', 'status': 'done', 'findings': len(findings), 'ts': int(time.time() * 1000)}) + '\n')
|
|
125
|
+
HANDOFF
|
|
126
|
+
;;
|
|
127
|
+
cleanup)
|
|
128
|
+
run="${2:-$RUN_DIR}"
|
|
129
|
+
mkdir -p "$ARCHIVE_DIR"
|
|
130
|
+
dest="$ARCHIVE_DIR/$(basename "$run")"
|
|
131
|
+
mv "$run" "$dest"
|
|
132
|
+
echo "archived to $dest"
|
|
133
|
+
;;
|
|
134
|
+
serve|render|post) echo "fake magpie: $1 reached the CLI on a local run" ;;
|
|
135
|
+
*) echo "fake magpie: unsupported subcommand: $*" >&2; exit 64 ;;
|
|
136
|
+
esac
|
|
137
|
+
SHIM
|
|
138
|
+
chmod +x "$HOME/shims/magpie"
|
|
139
|
+
|
|
140
|
+
cat > "$HOME/shims/gh" <<'SHIM'
|
|
141
|
+
#!/usr/bin/env bash
|
|
142
|
+
. "$HOME/shim-config"
|
|
143
|
+
echo "gh $*" >> "$CALLS"
|
|
144
|
+
echo "fake gh: no request was made" >&2
|
|
145
|
+
exit 1
|
|
146
|
+
SHIM
|
|
147
|
+
chmod +x "$HOME/shims/gh"
|
|
148
|
+
|
|
149
|
+
# The repo under review: main holds the cache without a reader, the branch adds
|
|
150
|
+
# put/get and a cached load. The run's own files stay out of the diff.
|
|
151
|
+
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null
|
|
152
|
+
git init -q -b main .
|
|
153
|
+
git config commit.gpgsign false
|
|
154
|
+
printf 'runs/\narchive/\n.magpie-calls.log\n' >> .git/info/exclude
|
|
155
|
+
export GIT_AUTHOR_NAME="Asha Platform" GIT_AUTHOR_EMAIL="asha@example.com"
|
|
156
|
+
export GIT_COMMITTER_NAME="Asha Platform" GIT_COMMITTER_EMAIL="asha@example.com"
|
|
157
|
+
mkdir -p src/settings
|
|
158
|
+
|
|
159
|
+
cat > src/settings/cache.ts <<'TS'
|
|
160
|
+
const store = new Map<string, Settings>()
|
|
161
|
+
|
|
162
|
+
export function clear() {
|
|
163
|
+
store.clear()
|
|
164
|
+
}
|
|
165
|
+
TS
|
|
166
|
+
|
|
167
|
+
cat > src/settings/loader.ts <<'TS'
|
|
168
|
+
type Settings = { theme: string }
|
|
169
|
+
|
|
170
|
+
async function fetchSettings(tenantId: string): Promise<Settings> {
|
|
171
|
+
return { theme: 'default' }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function load(tenantId: string) {
|
|
175
|
+
return fetchSettings(tenantId)
|
|
176
|
+
}
|
|
177
|
+
TS
|
|
178
|
+
|
|
179
|
+
git add src
|
|
180
|
+
git commit -q -m "feat: tenant settings loader"
|
|
181
|
+
git update-ref refs/remotes/origin/main HEAD
|
|
182
|
+
git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
|
|
183
|
+
MERGE_BASE=$(git rev-parse HEAD)
|
|
184
|
+
|
|
185
|
+
git checkout -q -b "$BRANCH"
|
|
186
|
+
|
|
187
|
+
cat > src/settings/cache.ts <<'TS'
|
|
188
|
+
const store = new Map<string, Settings>()
|
|
189
|
+
|
|
190
|
+
export function put(tenantId: string, settings: Settings) {
|
|
191
|
+
store.set(tenantId, settings)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function get(tenantId: string): Settings | undefined {
|
|
195
|
+
return store.get(tenantId)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function clear() {
|
|
199
|
+
store.clear()
|
|
200
|
+
}
|
|
201
|
+
TS
|
|
202
|
+
|
|
203
|
+
cat > src/settings/loader.ts <<'TS'
|
|
204
|
+
import { get, put } from './cache'
|
|
205
|
+
|
|
206
|
+
type Settings = { theme: string }
|
|
207
|
+
|
|
208
|
+
async function fetchSettings(tenantId: string): Promise<Settings> {
|
|
209
|
+
return { theme: 'default' }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function load(tenantId: string) {
|
|
213
|
+
const hit = get(tenantId)
|
|
214
|
+
if (hit) return hit
|
|
215
|
+
const fresh = await fetchSettings(tenantId)
|
|
216
|
+
put(tenantId, fresh)
|
|
217
|
+
return fresh
|
|
218
|
+
}
|
|
219
|
+
TS
|
|
220
|
+
|
|
221
|
+
git add src
|
|
222
|
+
git commit -q -m "feat: cache tenant settings in the request path" \
|
|
223
|
+
-m "Settings are read on every request; keep them in memory per tenant."
|
|
224
|
+
HEAD_OID=$(git rev-parse HEAD)
|
|
225
|
+
git diff --no-color --no-ext-diff "$MERGE_BASE" > "$RUN_DIR/diff.patch"
|
|
226
|
+
DIFF_SHA=$(shasum -a 256 "$RUN_DIR/diff.patch" | cut -d' ' -f1)
|
|
227
|
+
|
|
228
|
+
cat > "$RUN_DIR/target.json" <<JSON
|
|
229
|
+
{ "mode": "local", "root": "$PWD" }
|
|
230
|
+
JSON
|
|
231
|
+
|
|
232
|
+
cat > "$RUN_DIR/local.json" <<JSON
|
|
233
|
+
{
|
|
234
|
+
"branch": "$BRANCH",
|
|
235
|
+
"baseRef": "origin/HEAD",
|
|
236
|
+
"mergeBase": "$MERGE_BASE",
|
|
237
|
+
"head": "$HEAD_OID",
|
|
238
|
+
"dirty": false,
|
|
239
|
+
"author": "Asha Platform",
|
|
240
|
+
"commits": [
|
|
241
|
+
{
|
|
242
|
+
"oid": "$HEAD_OID",
|
|
243
|
+
"headline": "feat: cache tenant settings in the request path",
|
|
244
|
+
"body": "Settings are read on every request; keep them in memory per tenant."
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
"intentFile": null
|
|
248
|
+
}
|
|
249
|
+
JSON
|
|
250
|
+
|
|
251
|
+
cat > "$RUN_DIR/diff-source.json" <<JSON
|
|
252
|
+
{
|
|
253
|
+
"source": "local",
|
|
254
|
+
"mergeBase": "$MERGE_BASE",
|
|
255
|
+
"baseRef": "origin/HEAD",
|
|
256
|
+
"head": "$HEAD_OID",
|
|
257
|
+
"diffSha256": "$DIFF_SHA"
|
|
258
|
+
}
|
|
259
|
+
JSON
|
|
260
|
+
|
|
261
|
+
cat > "$RUN_DIR/log.jsonl" <<'LOG'
|
|
262
|
+
{"stage":"preflight","status":"done","missingOptional":["codex"]}
|
|
263
|
+
{"stage":"setup","status":"done"}
|
|
264
|
+
{"stage":"context","status":"done"}
|
|
265
|
+
{"stage":"specialists","status":"done"}
|
|
266
|
+
{"stage":"dedupe","status":"done"}
|
|
267
|
+
{"stage":"critic","status":"running"}
|
|
268
|
+
{"stage":"critic","status":"done","kept":2,"dropped":0,"merged":0,"capped":0}
|
|
269
|
+
{"stage":"peer-review","status":"done","provider":"claude"}
|
|
270
|
+
LOG
|
|
271
|
+
|
|
272
|
+
# Each finding cites a line the branch added, with the evidence that line holds.
|
|
273
|
+
cat > "$RUN_DIR/findings.final.json" <<'JSON'
|
|
274
|
+
[
|
|
275
|
+
{
|
|
276
|
+
"id": "security-1",
|
|
277
|
+
"file": "src/settings/cache.ts",
|
|
278
|
+
"line": 4,
|
|
279
|
+
"evidence": "store.set(tenantId, settings)",
|
|
280
|
+
"severity": "high",
|
|
281
|
+
"risk": { "impact": "high", "likelihood": "likely", "confidence": "high", "action": "must-fix" },
|
|
282
|
+
"domain": "security",
|
|
283
|
+
"title": "Tenant settings cache is a process-global Map with no eviction",
|
|
284
|
+
"description": "Observation: put() writes into a module-level Map with no bound and no TTL.\n\nWhy it matters: a settings change never reaches the cached copy.\n\nSuggested direction: bound the map and give entries a TTL."
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"id": "bugs-1",
|
|
288
|
+
"file": "src/settings/loader.ts",
|
|
289
|
+
"line": 12,
|
|
290
|
+
"evidence": "const fresh = await fetchSettings(tenantId)",
|
|
291
|
+
"severity": "medium",
|
|
292
|
+
"risk": { "impact": "medium", "likelihood": "possible", "confidence": "medium", "action": "should-fix" },
|
|
293
|
+
"domain": "bugs",
|
|
294
|
+
"title": "Concurrent loads for the same tenant each hit the network",
|
|
295
|
+
"description": "Observation: load() awaits fetchSettings before writing back.\n\nWhy it matters: N concurrent first requests produce N fetches.\n\nSuggested direction: cache the in-flight promise."
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
JSON
|
|
299
|
+
|
|
300
|
+
echo '[]' > "$RUN_DIR/findings/tests.json"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: llm
|
|
3
|
+
focus: last_message
|
|
4
|
+
weight: 2
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This is a local review: the caller is an agent working on its own branch, and there is no PR, server or report page. The run has just printed its handoff and archived itself.
|
|
8
|
+
|
|
9
|
+
PASS if the reply gives the caller both findings by title, "Tenant settings cache is a process-global Map with no eviction" and "Concurrent loads for the same tenant each hit the network" (light rewording is fine), followed by a line of the form `Run archived at <path>`, and ends the turn there.
|
|
10
|
+
FAIL if either finding is missing, if the `Run archived at <path>` line is missing, if the reply offers to post, points at a report URL or page, asks the caller to tick or select findings, or starts fixing the code itself.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-mode-hands-back
|
|
3
|
+
description: A local review of the current branch, resumed after peer review, hands the findings back as text, archives the run, and stops. There is no PR, so nothing is served, rendered or posted.
|
|
4
|
+
tags: [magpie, local, handoff, resume, scaffold]
|
|
5
|
+
max_turns: 25
|
|
6
|
+
timeout_seconds: 900
|
|
7
|
+
allowed_tools: [Read, Glob, Grep, Skill, Write, Edit, Bash, Task]
|
|
8
|
+
expected_outcome: Finds the active local run, runs magpie handoff, then magpie cleanup, and replies with both findings and the archive path. Never serves, renders, posts or calls gh.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Peer review's finished on the magpie local review of this branch. Take it from there.
|
|
@@ -118,9 +118,12 @@ the worktree, so cost grows with candidate count. The results file records the
|
|
|
118
118
|
## Full replay
|
|
119
119
|
|
|
120
120
|
```
|
|
121
|
-
bun evals/quality/replay-full.ts --corpus <runId> --repo <path-to-local-clone> [--max-cost-usd N]
|
|
121
|
+
bun evals/quality/replay-full.ts --corpus <runId> --repo <path-to-local-clone> [--max-cost-usd N] [--judge-model M]
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
Pass `--corpus` as a bare run id: a trailing slash empties the run id, and recall is then
|
|
125
|
+
skipped silently.
|
|
126
|
+
|
|
124
127
|
Re-runs stages 3 to 6 (context, specialists, dedupe, critic) on the run's `diff.patch`:
|
|
125
128
|
|
|
126
129
|
1. Scaffolds a scratch run dir: worktree at the PR head as above, `pr.json` and
|
|
@@ -132,10 +135,78 @@ Re-runs stages 3 to 6 (context, specialists, dedupe, critic) on the run's `diff.
|
|
|
132
135
|
is written. Serve, peer review, report, post and cleanup are skipped.
|
|
133
136
|
3. Matches the new kept findings to `findings.final.json` with `matchFindings` and scores
|
|
134
137
|
them; unmatched findings count as `unlabelled` and stay out of precision. Writes
|
|
135
|
-
`results/<ts>-full-<runId>.json
|
|
138
|
+
`results/<ts>-full-<runId>.json` with `recall`, `recallVerdicts`, `judgeModel` and
|
|
139
|
+
`judgeCostUsd` all null, plus `log`, `shardManifest`, `findingsFiles`, `dedupedCount`,
|
|
140
|
+
`criticDropped`, `evidenceDropped` and `thresholdDropped` read from the scratch run dir
|
|
141
|
+
before it is deleted. `shardCoverage` is derived from the manifest and the findings file
|
|
142
|
+
names: a shard counts as reviewed when any specialist wrote a file for it, so it overstates
|
|
143
|
+
a shard only some focuses reviewed. The printed line ends with `, shards <reviewed>/<total>`.
|
|
144
|
+
4. When the run has a recall set (see [Recall](#recall)), judges the new kept findings
|
|
145
|
+
against it, scores them, and rewrites the same result file with those four fields
|
|
146
|
+
filled. The printed line gains `, missed-recall <caught>/<misses>`. Without a recall
|
|
147
|
+
set the fields stay null and nothing extra is printed. A judge failure exits 1 and
|
|
148
|
+
names the result file, which already holds the scored replay.
|
|
136
149
|
|
|
137
150
|
**Cost:** a whole review: a scout, five specialists per shard, and the critic batches.
|
|
138
151
|
`--max-cost-usd` (default 10) is passed to Claude as `--max-budget-usd`, the CLI's own
|
|
139
152
|
spend cap; `claude -p` has no `--max-cost-usd` flag. Hitting the cap stops the session,
|
|
140
153
|
and the replay then fails without scoring, on Claude's error result or on the missing
|
|
141
154
|
`findings.kept.json`.
|
|
155
|
+
|
|
156
|
+
## Recall
|
|
157
|
+
|
|
158
|
+
The labels above only score findings magpie raised. The recall set scores the other side:
|
|
159
|
+
defects a human found in a reviewed PR that no kept finding raised. It lives at
|
|
160
|
+
`~/.magpie/corpus/misses/<runId>.json`, outside the repo for the same reason as the
|
|
161
|
+
corpus, and is written by hand; corpus rebuilds do not touch it.
|
|
162
|
+
|
|
163
|
+
Each file is a JSON array of misses:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"id": "miss-1",
|
|
168
|
+
"file": "src/handler.ts",
|
|
169
|
+
"line": 42,
|
|
170
|
+
"title": "Retry loop never gives up",
|
|
171
|
+
"summary": "What the defect is and why it matters.",
|
|
172
|
+
"ownerDomain": "architecture",
|
|
173
|
+
"bugClass": "spec/intent mismatch",
|
|
174
|
+
"outsideDiff": false,
|
|
175
|
+
"preExisting": false,
|
|
176
|
+
"source": "https://github.com/<owner>/<repo>/pull/50#discussion_r123"
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`ownerDomain` is the specialist domain that should have caught it, `outsideDiff` marks a
|
|
181
|
+
defect in code the diff did not touch, `preExisting` one that predates the PR, and
|
|
182
|
+
`source` is the reviewer comment URL or the fixing commit. `line` may be null. Every
|
|
183
|
+
other field is required; a missing or mistyped one fails `loadMisses`, naming the file
|
|
184
|
+
and index.
|
|
185
|
+
|
|
186
|
+
The set was seeded from the 2026-10-17 miss analysis, which collected defects from three
|
|
187
|
+
sources per run: reviewer comments magpie had not raised, commits pushed after the review
|
|
188
|
+
that fixed something, and follow-up fix PRs.
|
|
189
|
+
|
|
190
|
+
`replay-full.ts` hands the misses and the replay's kept findings to a judge: one
|
|
191
|
+
`claude -p` with tools and MCP servers turned off, which names, for each miss, the kept
|
|
192
|
+
finding that describes the same defect wherever it is anchored, or null. User settings
|
|
193
|
+
still load, so `~/.claude/CLAUDE.md` and hooks reach the judge as they would any session.
|
|
194
|
+
`scoreRecall` turns the verdicts into `caught` over `misses`, broken down by owner
|
|
195
|
+
domain and bug class, plus the same figure excluding pre-existing defects. The judge model
|
|
196
|
+
is pinned to `claude-sonnet-5-5` (`DEFAULT_JUDGE_MODEL` in `recall.ts`) so scores stay
|
|
197
|
+
comparable across replays; `--judge-model` overrides it.
|
|
198
|
+
|
|
199
|
+
**Cost:** one tool-less call per replay, small next to the replay itself. The result file
|
|
200
|
+
records it as `judgeCostUsd`.
|
|
201
|
+
|
|
202
|
+
**Confounds:**
|
|
203
|
+
|
|
204
|
+
- **Small set.** 30 misses across 6 runs, so one verdict moves a run's figure a lot.
|
|
205
|
+
- **PR 50 dominates.** Its run holds 14 of the 30, so pooled recall is mostly PR 50's.
|
|
206
|
+
- **Judge nondeterminism.** The same findings can draw different verdicts on another call,
|
|
207
|
+
most often on partial overlaps. Treat a change of one or two caught misses as noise.
|
|
208
|
+
- **Replay coverage.** Only `pr-50-1789099641` and `pr-70-1790391863` can be replayed
|
|
209
|
+
here, which covers 19 of the 30 misses. `pr-46` and `pr-38` have no corpus run, since
|
|
210
|
+
nothing was posted on them and so they have no labels; their misses can never be scored
|
|
211
|
+
by a replay (and `pr-38` has no local clone either). `pr-231` and `pr-232` are corpus
|
|
212
|
+
runs, but their repo (INX-Software/WorkforceManagement) is not cloned here.
|