Spaces:
Sleeping
PLAYBOOK β sapptest procedures
Numbered procedures an agent follows verbatim. Shared conventions first β
every procedure references them. Audience: an LLM operator with full repo
access. All paths are relative to _test/ unless they start with _project/
or ./.
Conventions (read once, used by every procedure)
C1. Finding IDs β F-NNNN, stable across runs
IDs are content-hash derived so re-runs update findings instead of duplicating them, and body-wording tweaks never change the ID.
- Title slug: lowercase the finding title; replace every run of
non-alphanumerics with
-; trim leading/trailing-; cut to 40 chars. - ID key:
<primary file>|<slug>where primary file is the first entry in the finding'sfileslist (repo-relative path). Example:src/auth/jwt.ts|login-rejects-valid-jwt - NNNN: decimal CRC of the key, mod 10000, zero-padded to 4 digits:
printf '%s' 'src/auth/jwt.ts|login-rejects-valid-jwt' | cksum | awk '{printf "%04d\n", $1 % 10000}' # β 7204 β id F-7204, file findings/F-7204-login-rejects-valid-jwt.md - Dedup before minting. Grep
findings/for an existing finding with the sameid_key, or any open finding on the same primary file with the same root cause. If found, update that file β never mint a second ID for the same issue, even if you would word the title differently today. - Collision: if the computed NNNN is already used by a finding with a
different
id_key, increment NNNN by 1 (mod 10000) until free. Keepid_keyset to the original key so future dedup still matches. - Filename:
findings/F-NNNN-<slug>.md. If a title is later reworded, keep the originalidandid_key; ID stability beats slug accuracy.
C2. Writing a finding
- Copy the matching template:
templates/finding.template.md(general),templates/bug.template.md(confirmed bug), ortemplates/refactor.template.md(refactor proposal). - Fill every front-matter field. Long fields (
repro,expected,actual,suggested_fix) use YAML|block scalars. - Severity per
config/severity.ymldefinitions β judge impact, not effort. - Validate against
schemas/finding.schema.json: everyrequiredfield present, enum values exact,filesnon-empty. Fix the file, never the schema. - A finding must be self-sufficient: an agent reading only that file can reproduce and fix the issue.
- Findings are live state: when a later pass verifies an issue is fixed,
delete the finding file (and its synced lines, C4). Never edit
reports/history.
C3. Computing the verdict
- Count open findings in
findings/F-*.mdbyseverity. - Apply
config/ship-rules.ymltop-down (first match wins):- any sev1 count >
block_thresholds.sev1βBLOCK, reasonsev1_open - sev2 count >
block_thresholds.sev2βBLOCK, reasonsev2_over_threshold - sev2 count β₯
caveat_thresholds.sev2or sev3 count β₯caveat_thresholds.sev3βSHIP WITH CAVEATS, reasonopen_findings_below_threshold - otherwise β
SHIP, reasonclean(orminor_findings_onlyif any sev3/sev4 remain open)
- any sev1 count >
audit_date= today (YYYY-MM-DD). Every procedure that examined code and reached this step refreshes it. Staleness (audit_staleness_days, and verdict older than the newest commit) is enforced by the reader β/shipβ not pre-computed here.blocking= the findings that triggered aBLOCK(all open sev1s; the sev2 overflow). Empty array otherwise.- Write
verdict/latest.json(shape:templates/verdict.example.json, must validate againstschemas/verdict.schema.json) andverdict/latest.mdfromtemplates/verdict.template.md. Overwrite both.
C4. TODO / CURRENT_STATE sync (one-way, tagged, idempotent)
sapptest owns exactly the lines it tagged <!-- F-NNNN -->. Never touch
untagged lines; never let user edits to tagged lines survive (they are
overwritten).
- For each open finding, upsert one line into
_project/TODO.mdunder the section matchingtodo_priorityinconfig/severity.yml("Next up" / "Backlog" / "Ideas / maybe"):- [sev2] login rejects valid JWT β _test/findings/F-7204-login-rejects-valid-jwt.md <!-- F-7204 --> - Upsert = if a line containing
<!-- F-NNNN -->exists, replace it in place; move it only if its section no longer matches the severity'stodo_priority. Otherwise append to the right section. - Delete any tagged line whose
F-NNNNno longer has afindings/file. - For open sev1/sev2 findings, also upsert into
_project/CURRENT_STATE.mdβ "known broken / flaky" section, same line format, same tag. Remove on close.
C5. Runners β normalized exit codes
config/adapters.yml maps detection files β runner. Run every adapter
that matches (mixed stacks run all). From the project root:
bash _test/runners/run-<lang>.sh .
Exit codes: 0 pass Β· 1 test failures Β· 2 not applicable / no tests Β·
3 environment error. Read the native output β it names the failing tests.
full-audit
Non-destructive. Writes only to findings/, reports/, verdict/, and
tagged _project/ lines.
- Read
prompts/audit.md,config/coverage-goals.yml,config/ship-rules.yml. Note the depth each category demands. - Detect stacks via
config/adapters.yml; run each matching runner (C5). Record exit codes and failing test names. A failing native test is automatically a finding (sev2 unless impact says otherwise). - Map the codebase: entry points, core modules, data writes,
git log --oneline -30for churn hot spots. - Audit per
prompts/audit.md, applying the specialist lenses it names (prompts/security.md,prompts/a11y.md,prompts/perf.md) where coverage-goals require them. Confirm every issue by reading the code β no speculative findings. - For each confirmed issue: dedup (C1.4), then write a finding (C2).
- Sweep existing
findings/: any that the codebase shows are fixed β verify, then delete the file. - Write
reports/audit-YYYY-MM-DD.md: scope covered vs coverage-goals, runner results, table of findings (ID, sev, title), closed findings, verdict line. Append-only β never rewrite old reports. - Compute the verdict (C3). Sync (C4).
- Do not commit. Show the user what changed and the verdict.
bug-hunt
Targeted. Input: a symptom, error message, or suspect file list.
- Read
prompts/bug-hunt.md. Restate the symptom as expected vs actual. - Trace from the symptom to candidates: entry point β handler β data. Read the implicated code paths fully. Run the relevant runner (C5) or a single native test if it shortens the hunt.
- Confirm the root cause β name file and line. If you cannot confirm, report what you ruled out; write no speculative finding.
- Write one finding per confirmed root cause using
templates/bug.template.md(C2), with a minimal repro. - Findings changed β recompute the verdict (C3) and sync (C4).
- Report root cause + finding ID to the user. Do not fix it in this pass β
fixes go through
refactoror normal feature work.
regression-check
Input: a diff, branch, or commit range ("did this change break anything?").
- Read the diff. List behaviors the change could plausibly affect β callers of changed functions, shared state, changed contracts/schemas.
- Run runners (C5) for every affected stack.
- For each at-risk behavior, read the post-change code and confirm it
still holds. Check
reports/for previously closed issues in the touched files β regressions of past findings get priority. - New breakage β findings via
templates/bug.template.md(C2), noting the offending commit inrepro. - Findings changed β recompute verdict (C3), sync (C4). Nothing found β
say so; refresh the verdict only if you audited deeply enough to vouch
for it (C3 sets
audit_date).
refactor
Two phases. Phase A proposes; phase B opens PRs. /refactor runs phase B.
A β propose (code untouched):
- Read
prompts/refactor.md. Identify improvements with concrete payoff (duplication, dead code, complexity hot spots, API awkwardness). - Write each as a finding via
templates/refactor.template.mdwithrefactor: trueand asuggested_fixprecise enough to apply without re-deriving it (C2). Recompute verdict (C3), sync (C4).
B β execute (PR-per-finding):
- Read
refactor_modefromconfig/ship-rules.yml(per-findingdefault,batchedopt-in). - Filter
findings/F-*.mdwhererefactor: true. None β say so, stop. - Per finding (per-finding mode):
a.
git switch -c refactor/F-NNNN-<slug>from the working branch. b. Apply only the finding'ssuggested_fix. Don't improvise beyond it. c. Run the host project's./verify.sh. Fails β abort this PR, switch back, continue with the next finding. d. Commit, push, open a PR titled[F-NNNN] <summary>, body linking_test/findings/F-NNNN-<slug>.md. Switch back to the working branch. - Batched mode: one branch, one commit, one PR listing every
F-NNNN. - Do not mark findings fixed or delete them β the next audit verifies merged fixes and closes them. Never merge your own PRs.
pre-ship
Deeper gate than ./verify.sh, run before /ship commits.
- Read
prompts/pre-ship.md. Identify what is shipping:git status --short+git diffagainst the last shipped state. - Run the host
./verify.sh. Fails β stop, report, no-go. - Run runners (C5) for affected stacks.
- Check
verdict/latest.json: ifaudit_datepredates the newest commit or exceedsaudit_staleness_days, the verdict is stale β runregression-check(small change) orfull-audit(large) first. - Cross-check the diff against open findings: if shipped files appear in any open sev1/sev2 finding, verify the change doesn't worsen it.
- Smoke the changed behavior per
prompts/pre-ship.md. - New issues β findings (C2), recompute verdict (C3), sync (C4).
- Tell the user: go / no-go, the verdict, and any caveats needing
acknowledgment.
/shipenforces the verdict; you never bypass it.
docs-drift
Compares _project/*.md claims against code. sappcode's /sync delegates
here. Cheap first, thorough second.
- Read
prompts/docs-drift.md. Sample reality before opening docs:git log --oneline -30,git status --short,lsat root. - Follow the prompt's claim checklist (env vars, commands, dependencies,
architecture claims, CURRENT_STATE staleness). Open only the docs the
git evidence implicates, plus
_project/CURRENT_STATE.mdalways. - Each confirmed divergence β finding,
category: docs-drift, severity sev3 (misleading runbook/env claims) or sev4 (cosmetic staleness), withsuggested_fixcontaining the corrected doc text (C2). - Findings changed β recompute verdict (C3), sync (C4).
- Propose the doc edits to the user. Apply them only with approval β and then delete the corresponding findings, since the drift is gone.