mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-11 03:00:26 +01:00
fix(evals): shared-libs read evidence counts a literal path loop whose output names each file
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
d0b46d7058
commit
e616a759a8
5 files changed
+568
-3
No files matched your search
@@ -20,6 +20,7 @@ The second Opus gate (run 39161502626) went 127/132. Each of the five reds is no
|
||||
- **qa-only-no-fix:** the fixture prompt now says the 30-second guard starts only after the charter Write has completed, and that local inspection outside the listed calls uses Read/Glob, not `ls`, `wc`, `cat` or `find` (its own `ls -la` entries had read as general permission). The budget and the assertion are unchanged.
|
||||
- **plan-ceo-finding-floor:** Opus saved its D1 decision to the owned working plan before printing any text, as the 0D Save step requires. The permission check identified the parent session only from text or questions, so it never granted that Create and the run timed out after 600 s. A parent with only tool events is now identified by them, still as exactly one session that owns the pending Write. The floor runner also fails within 20 s with `PERMISSION_DIALOG_UNANSWERED` when a permission dialog gets no owned grant.
|
||||
- **plan-ceo-finding-floor, Edit menu** (3 of 10 measured trials): after that Create, Opus edited the plan and the cropped Edit menu stayed unanswered. Twice Claude Code showed the menu before publishing the Edit, and once three Edits were pending at once, so the check found no single published Edit. The plan's own PreToolUse input now covers an unpublished Edit, as it already did for Write, and the visible diff must match exactly one pending Edit on the owned plan. An Edit to another file, a diff matching none or several pending Edits, and a foreign session are still refused.
|
||||
- **shared-libs-review-index-flags** (the fourth gate, run 39173752116): the skip-worktree review read all three evidence files in one `for f in <literal paths>; do echo "=== $f"; cat -n "$f"; done` loop. A loop over literal paths now counts as reading a file only when its output names that file. A loop over a variable list, a file missing from the output, a path outside the repo and the existing loop with no path in its output are still rejected.
|
||||
|
||||
### If you…
|
||||
- **…compare pass rates across the switch:** Fable trials carry `harness_version: 5` and Opus trials `6`; they are separate series.
|
||||
|
||||
@@ -0,0 +1,522 @@
|
||||
{
|
||||
"source": "E2E Evals run 39173752116 slice 12 (job 117814337019), shared-libs-review-index-flags, skip-worktree scenario at d0b46d7: the evidence files were read by one literal for-loop that printed each path before cat -n.",
|
||||
"expected": {
|
||||
"helper": "/__w/gstack/gstack/bin/gstack-review-log",
|
||||
"repo": "/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/repo",
|
||||
"state": "/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/state",
|
||||
"slug": "fixture-shared-libs",
|
||||
"directory": "/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/state/projects/fixture-shared-libs/.review-starts",
|
||||
"branch": "feature/a",
|
||||
"wtree": "288a3521b692882b9936606504a4b7999286702f",
|
||||
"startedAt": "2026-10-18T22:59:58.112Z",
|
||||
"finding": {
|
||||
"severity": "INFORMATIONAL",
|
||||
"confidence": 9,
|
||||
"advisory": true,
|
||||
"path": "src/retry-worker.ts",
|
||||
"line": 2,
|
||||
"category": "shared-libs",
|
||||
"summary": "Use the established Retry-After contract in the changed worker and the authored route sources.",
|
||||
"fix": "Share the tested retrySeconds contract, preserving runtime and deployment boundaries for each caller.",
|
||||
"evidence_paths": [
|
||||
"src/retry-worker.ts",
|
||||
"src/retry-route.ts",
|
||||
"lib/retry-after.ts"
|
||||
],
|
||||
"helper_target": {
|
||||
"path": "lib/retry-after.ts",
|
||||
"symbol": "retrySeconds"
|
||||
},
|
||||
"fingerprint": "shared-libs:af037ba20b8a3674855fb65b04f1855c7f0cb5581d3b31f1c4cf5ab648ee6d33"
|
||||
},
|
||||
"reusable": false,
|
||||
"coveredPaths": [
|
||||
"src/retry-worker.ts",
|
||||
"lib/retry-after.ts"
|
||||
]
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01U6ATd6C2vFeBZCi1KKnQqn",
|
||||
"name": "Read",
|
||||
"input": {
|
||||
"file_path": "/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/review-lifecycle.md"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01U6ATd6C2vFeBZCi1KKnQqn",
|
||||
"type": "tool_result",
|
||||
"content": "1\t---\n2\tname: review\n3\tpreamble-tier: 4\n4\tversion: 1.0.0\n5\tdescription: Pre-landing PR review. (gstack)\n6\tallowed-tools:\n7\t - Bash\n8\t - Read\n9\t - Edit\n10\t - Write\n11\t - Grep\n12\t - Glob\n13\t - Agent\n14\t - AskUserQuestion\n15\t - WebSearch\n16\ttriggers:\n17\t - review this pr\n18\t - code review\n19\t - check my diff\n20\t - pre-landing review\n21\t---\n22\t\n23\t## Step 3: Get the diff\n24\t\n25\tAn invocation is this /review run; a pass reviews one candidate before any fixes.\n26\tOn first entry, initialize one invocation action list and CYCLES=0. Keep both through re-reviews.\n27\t\n28\tEach pass has one direction: collect findings in Steps 3–4.8, approve and apply\n29\tfixes in Step 5, then choose repeat or final persistence in Step 5.8.\n30\tDo not edit reviewed source until Step 5. All readers examine the same candidate.\n31\t\n32\tDiff the working tree against the merge base:\n33\t\n34\t```bash\n35\tDIFF_BASE=$(git merge-base origin/main HEAD)\n36\t/__w/gstack/gstack/bin/gstack-review-log --start review\n37\tgit diff \"$DIFF_BASE\"\n38\t```\n39\t\n40\t1. Save the printed REVIEW_START for this core candidate before reading its diff.\n41\t2. Each re-review captures a new token before reading, never at log time. Earlier\n42\t core tokens remain unused; Step 5.8 finishes only the final core token.\n43\t3. Native/outside reviewer attempts own separate PASS_START tokens, not REVIEW_START.\n44\t4. Read non-ignored untracked source too (`git ls-files --others --exclude-standard`);\n45\t the captured candidate includes it.\n46\t\n47\tKeep the review-record terms separate:\n48\t\n49\t| Value | Purpose and owner |\n50\t|---|---|\n51\t| REVIEW_START / PASS_START | Opaque start receipts from the logger: one for the core pass, one for each other reviewer attempt. |\n52\t| Finding fingerprint | Groups duplicate findings. The installed helper computes shared-code fingerprints; a matching key alone never proves a prior Skip is reusable. |\n53\t| `review_binding` | The logger's proof tying a finished review to its captured candidate, not a finding identifier. |\n54\t| `snapshot_covered_paths` | Supporting advice files the logger proved byte-identical to that candidate. Used by the prior-Skip checker, never supplied by the reviewer. |\n55\t\n56\t## Step 4: Critical pass (core review)\n57\t\n58\t> **STOP.** Before any probe, including plan checks, complete the ordered scope/method Reads in earlier responses. Never batch a probe (capture, guard start, plan check) with a Read.\n59\tStep 4 is read-only: defer charters, setup and probes to Step 4.7.\n60\t\n61\t1. From the installed /review SKILL.md's directory, choose one path:\n62\t- If the caller directory is `review`, Read `../qa/sections/exploratory.md` in full.\n63\t- If the caller directory is prefixed `gstack-review`, use `../gstack-qa/sections/exploratory.md` instead and read it in full.\n64\t- If neither layout applies, report an unresolved QA installation as a setup blocker; do not guess another path.\n65\tUse this host's installation, never the product tree. If missing or unreadable, report a QA setup blocker and its affected probes as blocked; continue other safe probes (independent functional/static checks). Missing/unreadable assets block required QA.\n66\t2. Then Read QA's `sections/scope.md` in full; pick surfaces.\n67\t3. Then Read each listed method in full; the entrypoint alone completes nothing.\n68\t\n69\tQA's `sections/...` and `templates/...` paths resolve from installed QA SKILL.md, not caller/product.\n70\t\n71\tApply both checklist passes in order: CRITICAL, then INFORMATIONAL. Respect its suppressions.\n72\t\n73\t**Sideways sweep (every finding; reads code OUTSIDE the diff).** Grep the finding's pattern at every other ingress, command and call site, Read each match and rate severity on the worst sibling; a new enum value, status, tier or type constant is traced through every consumer the same way (checklist: Sideways sweep). Data loss, a trust-boundary bypass or uncapped spend is forced CRITICAL: no lane, specialist, dedup or saved decision downgrades it. Keep findings anchored to changed code.\n74\t\n75\t**Gate Integrity reads Step 3.5's listing.** Carry the `GATE_SUMMARY:` line and every\n76\tread-level hunk (`[<id>] <tag> <path> @<hunk>`) into the checklist's Gate Integrity\n77\tcategory: read each hunk in the diff with its one question, keep groups as one\n78\tfinding, and record `gate` (the RH tag) and `gate_id` (the tool's id) on each gate\n79\tfinding. A partial or UNAVAILABLE scan is missing coverage for this category, never\n80\ta clean result. Gate findings are ASK in Step 5 regardless of how mechanical the\n81\trevert looks. A test-only flake fix (timeout, retry, looser assertion, skip) states `P(fail | regression)` before and after, from a forced probe; without both numbers it is RH-15.\n82\t\n83\t**Search-before-recommending:** Research proposed fixes through Aside, especiLine truncated
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01Hq6PHHhhNaJuiHwf3SBxMp",
|
||||
"name": "Read",
|
||||
"input": {
|
||||
"file_path": "/__w/gstack/gstack/review/checklist.md"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01Hq6PHHhhNaJuiHwf3SBxMp",
|
||||
"type": "tool_result",
|
||||
"content": "1\t# Pre-Landing Review Checklist\n2\t\n3\t## Instructions\n4\t\n5\tReview the merge-base diff from the caller, including its selected uncommitted and new source. Use the caller's detected base, not a hardcoded branch. Cite `file:line` and suggest fixes. Only flag real problems.\n6\t\n7\t**Two-pass review:**\n8\t- **Pass 1 (CRITICAL):** Run SQL & Data Safety, Race Conditions, LLM Output Trust Boundary, Shell Injection, Enum Completeness, and Gate Integrity first. Highest severity.\n9\t- **Pass 2 (INFORMATIONAL):** Run remaining categories below. Lower severity but still actioned.\n10\t- **Specialist categories (handled by parallel subagents, NOT this checklist):** Test Gaps, Dead Code, Magic Numbers, Conditional Side Effects, Performance & Bundle Impact, Crypto & Entropy, Simplification (unrequested structure). See `review/specialists/` for these.\n11\t\n12\tCompleteness Gaps and Simplification are orthogonal, not contradictory: Completeness pushes coverage UP (tests, edge cases, error paths), Simplification pushes unrequested structure DOWN (one-implementation abstractions, hand-rolled stdlib, dead flexibility). The same diff can legitimately receive both.\n13\t\n14\tAll findings get action via Fix-First Review: obvious mechanical fixes are applied automatically,\n15\tgenuinely ambiguous issues are batched into a single user question.\n16\t\n17\t**Output format:**\n18\t\n19\t```\n20\tPre-Landing Review: N issues (X critical, Y informational)\n21\t\n22\t**AUTO-FIXED:**\n23\t- [file:line] Problem → fix applied\n24\t\n25\t**NEEDS INPUT:**\n26\t- [file:line] Problem description\n27\t Recommended fix: suggested fix\n28\t```\n29\t\n30\tIf no issues found: `Pre-Landing Review: No issues found.`\n31\t\n32\tBe terse. For each issue: one line describing the problem, one line with the fix. No preamble, no summaries, no \"looks good overall.\"\n33\t\n34\t---\n35\t\n36\t## Review Categories\n37\t\n38\t### Pass 1 — CRITICAL\n39\t\n40\t#### SQL & Data Safety\n41\t- String interpolation in SQL (even if values are `.to_i`/`.to_f` — use parameterized queries (Rails: sanitize_sql_array/Arel; Node: prepared statements; Python: parameterized queries))\n42\t- TOCTOU races: check-then-set patterns that should be atomic `WHERE` + `update_all`\n43\t- Bypassing model validations for direct DB writes (Rails: update_column; Django: QuerySet.update(); Prisma: raw queries)\n44\t- N+1 queries: Missing eager loading (Rails: .includes(); SQLAlchemy: joinedload(); Prisma: include) for associations used in loops/views\n45\t\n46\t#### Race Conditions & Concurrency\n47\t- Read-check-write without uniqueness constraint or catch duplicate key error and retry (e.g., `where(hash:).first` then `save!` without handling concurrent insert)\n48\t- find-or-create without unique DB index — concurrent calls can create duplicates\n49\t- Status transitions that don't use atomic `WHERE old_status = ? UPDATE SET new_status` — concurrent updates can skip or double-apply transitions\n50\t- Unsafe HTML rendering (Rails: .html_safe/raw(); React: dangerouslySetInnerHTML; Vue: v-html; Django: |safe/mark_safe) on user-controlled data (XSS)\n51\t\n52\t#### LLM Output Trust Boundary\n53\t- LLM-generated values (emails, URLs, names) written to DB or passed to mailers without format validation. Add lightweight guards (`EMAIL_REGEXP`, `URI.parse`, `.strip`) before persisting.\n54\t- Structured tool output (arrays, hashes) accepted without type/shape checks before database writes.\n55\t- LLM-generated URLs fetched without allowlist — SSRF risk if URL points to internal network (Python: `urllib.parse.urlparse` → check hostname against blocklist before `requests.get`/`httpx.get`)\n56\t- LLM output stored in knowledge bases or vector DBs without sanitization — stored prompt injection risk\n57\t\n58\t#### Shell Injection (Python-specific)\n59\t- `subprocess.run()` / `subprocess.call()` / `subprocess.Popen()` with `shell=True` AND f-string/`.format()` interpolation in the command string — use argument arrays instead\n60\t- `os.system()` with variable interpolation — replace with `subprocess.run()` using argument arrays\n61\t- `eval()` / `exec()` on LLM-generated code without sandboxing\n62\t\n63\t#### Enum & Value Completeness\n64\tWhen the diff introduces a new enum value, status string, tier name, or type constant, or loosens what an input accepts (a new MIME type, extension or flag, or a widened validator/parser/guard):\n65\t- **Trace it through every consumer.** Read (don't just grep — READ) each file that switches on, filters by, or displays that value. If any consumer doesn't handle the new value, flag it. Common miss: adding a value to the frontend dropdown but the backend model/compute method doesn't persist it.\n66\t- **Check allowlists/filter arrays.** Search for arrays or `%w[]` lists containing sibling values (e.g., if adding \"revise\" to tiers, find every `%w[quick lfg mega]` and verify \"revise\" is included where needeLine truncated
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01HWDbPwoSzrsmdpTbi5SLbg",
|
||||
"name": "Read",
|
||||
"input": {
|
||||
"file_path": "/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/current-advisory.jsonl"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01HWDbPwoSzrsmdpTbi5SLbg",
|
||||
"type": "tool_result",
|
||||
"content": "1\t{\"severity\":\"INFORMATIONAL\",\"confidence\":9,\"advisory\":true,\"path\":\"src/retry-worker.ts\",\"line\":2,\"category\":\"shared-libs\",\"summary\":\"Use the established Retry-After contract in the changed worker and the authored route sources.\",\"fix\":\"Share the tested retrySeconds contract, preserving runtime and deployment boundaries for each caller.\",\"evidence_paths\":[\"src/retry-worker.ts\",\"src/retry-route.ts\",\"lib/retry-after.ts\"],\"helper_target\":{\"path\":\"lib/retry-after.ts\",\"symbol\":\"retrySeconds\"},\"fingerprint\":\"shared-libs:af037ba20b8a3674855fb65b04f1855c7f0cb5581d3b31f1c4cf5ab648ee6d33\",\"specialist\":\"maintainability\"}\n2\t"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01VYMVs5xWEb8Ew4Nxvj8Gof",
|
||||
"name": "Read",
|
||||
"input": {
|
||||
"file_path": "/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/resumed-review-prerequisites.json"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01VYMVs5xWEb8Ew4Nxvj8Gof",
|
||||
"type": "tool_result",
|
||||
"content": "1\t{\n2\t \"kind\": \"synthetic-path-review-prerequisites\",\n3\t \"synthetic\": true,\n4\t \"native_coverage\": false,\n5\t \"binding\": {\n6\t \"root\": \"/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3\",\n7\t \"repo\": \"/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/repo\",\n8\t \"state\": \"/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/state\",\n9\t \"branch\": \"feature/a\",\n10\t \"head\": \"992f75f3e20107098e67fb90f1c582fba25a98fa\",\n11\t \"base\": \"992f75f3e20107098e67fb90f1c582fba25a98fa\",\n12\t \"wtree\": \"288a3521b692882b9936606504a4b7999286702f\",\n13\t \"index\": \"H 100644 bb5af942f63559a016674999cc537a73cd78c6a5 0\\t.gitignore\\nH 100644 bd2d0ac4930be6c76142f6a6c8d10e920413fc46 0\\tREADME.md\\nH 100644 00dfe6a156bae81aefc94ff1e9db54e32d774c75 0\\tlib/retry-after.ts\\nS 100644 8f6dd51314e9960b7bc3add9d95b8f530b85087d 0\\tsrc/retry-route.ts\\nH 100644 8ab4b18d9a2bd337433526c96343d251aaee8539 0\\tsrc/retry-worker.ts\\nH 100644 c362fe9878839740d82db85053758be5b95834cd 0\\tsrc/scheduler.ts\\nH 100644 a470f2c203c43e66c47991c64654dd524f9c5c29 0\\ttest/retry-after.test.ts\",\n14\t \"raw\": {\n15\t \".\": \"dir:16877\",\n16\t \".git/config\": \"33188:2550b8371ff38558f3a8c847e5563ca10271d66c0ce1ea8608cc415a959b5854\",\n17\t \".git/info/exclude\": \"33188:6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1\",\n18\t \".gitignore\": \"33188:1578ba3393bbaae78dbda7cc170832528d3bfa4f61690cd07ba8b7075d47540b\",\n19\t \"README.md\": \"33188:df29e8fdc8895e1fe9d824b1697e4ef77ac6686a40dda22cf513074b37c490bc\",\n20\t \"lib\": \"dir:16877\",\n21\t \"lib/retry-after.ts\": \"33188:67d7124d11830e41baa5fca93bd0d45ce64fe58d92799c7d14635b912c5e5942\",\n22\t \"src\": \"dir:16877\",\n23\t \"src/retry-route.ts\": \"33188:9d55bb52531ee1d715cc3313754bd364b300f524297ef9b130cb732bcb32a126\",\n24\t \"src/retry-worker.ts\": \"33188:a17d12e37ae2b07d38dae292156aef6ec83d11b4ec2e295bb14d01f0e8fc9b2e\",\n25\t \"src/scheduler.ts\": \"33188:8a07bfe628ea4f9e679bcab3cfb68482678eebc1e4031887e996d799a6d1198e\",\n26\t \"test\": \"dir:16877\",\n27\t \"test/retry-after.test.ts\": \"33188:653e4090ae4e0d856f4dbf04fb05449d04c44fef599c24ce146d94969a1040a1\"\n28\t }\n29\t },\n30\t \"qa\": {\n31\t \"settled\": true,\n32\t \"required_probes\": [\n33\t {\n34\t \"id\": \"retry-contract\",\n35\t \"status\": \"passed\",\n36\t \"result\": \"Synthetic fixture input: Retry-After seconds/date parsing, ceiling and fallback probes passed.\"\n37\t }\n38\t ],\n39\t \"findings\": []\n40\t },\n41\t \"native_adversarial\": {\n42\t \"settled\": true,\n43\t \"status\": \"completed\",\n44\t \"findings\": [],\n45\t \"result\": \"Synthetic fixture input: native adversarial review returned no findings.\"\n46\t },\n47\t \"structured_review\": {\n48\t \"required\": false,\n49\t \"reason\": \"Tiny diff; no full-review, structured-review or P1 override requested.\"\n50\t }\n51\t}\n52\t"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_015hmwtKnEoHF3vcrLvMWhyu",
|
||||
"name": "Read",
|
||||
"input": {
|
||||
"file_path": "/__w/gstack/gstack/review/sections/shared-code-reuse.md"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_015hmwtKnEoHF3vcrLvMWhyu",
|
||||
"type": "tool_result",
|
||||
"content": "1\t<!-- AUTO-GENERATED from shared-code-reuse.md.tmpl — do not edit directly -->\n2\t<!-- Regenerate: bun run gen:skill-docs -->\n3\t**Reuse a skipped shared-code advisory only with complete structural evidence:**\n4\t\n5\t1. **Read the evidence.** Read all supporting callers and the helper destination.\n6\t Establish first-party authored provenance and whether the current extraction\n7\t is worthwhile; the checker cannot decide that. Retain `evidence_paths`/`helper_target`.\n8\t2. **Run the checker.** From the repository root, pass the current finding as\n9\t literal JSON on stdin. Replace REVIEW_START with this pass's captured token\n10\t and the example paths/symbol with actual evidence. Keep the quoted delimiter.\n11\t\n12\t```bash\n13\t\"$HOME/.claude/skills/gstack/bin/gstack-review-log\" --check-shared-libs REVIEW_START <<'GSTACK_SHARED_LIBS_REUSE_JSON'\n14\t{\"advisory\":true,\"severity\":\"INFORMATIONAL\",\"evidence_paths\":[\"src/caller-a.ts\",\"src/caller-b.ts\"],\"helper_target\":{\"path\":\"src/shared.ts\",\"symbol\":\"sharedHelper\"}}\n15\tGSTACK_SHARED_LIBS_REUSE_JSON\n16\t```\n17\t\n18\t3. **Act on its result.** Read the JSON. Only `reusable: true` permits suppression.\n19\t False, command failure or unreadable output requires fresh source review and a\n20\t new decision, never suppression. Do not supply your own snapshot, prior record or coverage.\n21\t4. **Persist through the logger.** The logger recomputes final coverage; never\n22\t supply proof yourself. Real defects retain normal Fix-First handling independently.\n23\t\n24\t**What a reusable result proves (do not reconstruct these checks yourself):**\n25\t- Identity: `sharedLibsFingerprint` plus the actual repo, raw branch and current snapshot.\n26\t The checker reads REVIEW_START without consuming/replacing it. Sanitized branch names are not identity.\n27\t- Prior decision: completed/converged review, verified binding, explicit Skip and\n28\t logger-versioned `snapshot_covered_paths`; older unversioned coverage needs a fresh decision.\n29\t- Source: `canReuseSharedLibsAdvisory` requires every supporting path's raw file\n30\t byte-for-byte with its blob. Exclude assume-unchanged, skip-worktree and sparse index\n31\t entries; symlinks/ancestors, submodules, ignored/outside or unreadable files;\n32\t active/unknown Git filters, encodings and line conversion.\n33\t- Safe inspection: disables fsmonitor and optional locks; never uses external diff/textconv.\n34\t Unknown evidence fails closed.\n35\t"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01LPd1SYLcWSFqjHCGy4FZHy",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "git fetch origin 2>&1; echo \"--- mergebase\"; git merge-base origin/main HEAD; echo \"--- HEAD\"; git rev-parse HEAD origin/main; git branch --show-current; echo \"--- status\"; git status --porcelain=v1 -uall; echo \"--- ls-files -v (flags)\"; git ls-files -v; echo \"--- ls-files -s\"; git ls-files -s; echo \"--- debug flags\"; git ls-files --debug 2>/dev/null | head -80; echo \"--- local config\"; git config --local --list; echo \"--- attributes\"; cat .gitattributes 2>&1; cat .git/info/attributes 2>&1; cat .git/info/exclude 2>&1; git check-attr -a -- $(git ls-files)",
|
||||
"description": "Gather base metadata, index flags, config, attributes"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01LPd1SYLcWSFqjHCGy4FZHy",
|
||||
"type": "tool_result",
|
||||
"content": "--- mergebase\n992f75f3e20107098e67fb90f1c582fba25a98fa\n--- HEAD\n992f75f3e20107098e67fb90f1c582fba25a98fa\n992f75f3e20107098e67fb90f1c582fba25a98fa\nfeature/a\n--- status\n M src/retry-worker.ts\n--- ls-files -v (flags)\nH .gitignore\nH README.md\nH lib/retry-after.ts\nS src/retry-route.ts\nH src/retry-worker.ts\nH src/scheduler.ts\nH test/retry-after.test.ts\n--- ls-files -s\n100644 bb5af942f63559a016674999cc537a73cd78c6a5 0\t.gitignore\n100644 bd2d0ac4930be6c76142f6a6c8d10e920413fc46 0\tREADME.md\n100644 00dfe6a156bae81aefc94ff1e9db54e32d774c75 0\tlib/retry-after.ts\n100644 8f6dd51314e9960b7bc3add9d95b8f530b85087d 0\tsrc/retry-route.ts\n100644 8ab4b18d9a2bd337433526c96343d251aaee8539 0\tsrc/retry-worker.ts\n100644 c362fe9878839740d82db85053758be5b95834cd 0\tsrc/scheduler.ts\n100644 a470f2c203c43e66c47991c64654dd524f9c5c29 0\ttest/retry-after.test.ts\n--- debug flags\n.gitignore\n ctime: 1792364384:600976434\n mtime: 1792364384:600976434\n dev: 45\tino: 10509187\n uid: 1001\tgid: 1001\n size: 10\tflags: 0\nREADME.md\n ctime: 1792364384:601196602\n mtime: 1792364384:601196602\n dev: 45\tino: 10509188\n uid: 1001\tgid: 1001\n size: 22\tflags: 0\nlib/retry-after.ts\n ctime: 1792364384:609205835\n mtime: 1792364384:609205835\n dev: 45\tino: 10509217\n uid: 1001\tgid: 1001\n size: 629\tflags: 0\nsrc/retry-route.ts\n ctime: 1792364384:609205835\n mtime: 1792364384:609205835\n dev: 45\tino: 10509215\n uid: 1001\tgid: 1001\n size: 673\tflags: 40004000\nsrc/retry-worker.ts\n ctime: 1792364384:619645003\n mtime: 1792364384:619645003\n dev: 45\tino: 10509214\n uid: 1001\tgid: 1001\n size: 51\tflags: 0\nsrc/scheduler.ts\n ctime: 1792364384:609292880\n mtime: 1792364384:609292880\n dev: 45\tino: 10509218\n uid: 1001\tgid: 1001\n size: 126\tflags: 0\ntest/retry-after.test.ts\n ctime: 1792364384:609292880\n mtime: 1792364384:609292880\n dev: 45\tino: 10509220\n uid: 1001\tgid: 1001\n size: 384\tflags: 0\n--- local config\ncore.repositoryformatversion=0\ncore.filemode=true\ncore.bare=false\ncore.logallrefupdates=true\nuser.name=Shared Libs Fixture\nuser.email=shared-libs@example.invalid\nremote.origin.url=https://github.com/fixture/shared-libs.git\nremote.origin.fetch=+refs/heads/*:refs/remotes/origin/*\n--- attributes\ncat: .gitattributes: No such file or directory\ncat: .git/info/attributes: No such file or directory\n# git ls-files --others --exclude-from=.git/info/exclude\n# Lines that start with '#' are comments.\n# For a project mostly in C, the following would be a good set of\n# exclude patterns (uncomment them if you want to use them):\n# *.[oa]\n# *~",
|
||||
"is_error": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01V8CKHUkYVZ7sDRgDFJ44GC",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "'/__w/gstack/gstack/bin/gstack-review-log' --start review",
|
||||
"description": "Capture the review start token"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01V8CKHUkYVZ7sDRgDFJ44GC",
|
||||
"type": "tool_result",
|
||||
"content": "e1734218-7e14-446d-a146-02b4b45c636d",
|
||||
"is_error": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01FK4Tt3uxf1S4jctgue3wH7",
|
||||
"name": "Read",
|
||||
"input": {
|
||||
"file_path": "/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/state/projects/fixture-shared-libs/.review-starts/e1734218-7e14-446d-a146-02b4b45c636d.json"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01FK4Tt3uxf1S4jctgue3wH7",
|
||||
"type": "tool_result",
|
||||
"content": "1\t{\"skill\":\"review\",\"repo\":\"/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/repo\",\"branch\":\"feature/a\",\"wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"started_at\":\"2026-10-18T22:59:58.112Z\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_011jZqf24Z7imm3GbeZQvCvV",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "git diff 992f75f3e20107098e67fb90f1c582fba25a98fa; echo \"--- untracked\"; git ls-files --others --exclude-standard; echo \"--- prior reviews\"; '/__w/gstack/gstack/bin/gstack-review-read'",
|
||||
"description": "Show diff, untracked files, prior review records"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_011jZqf24Z7imm3GbeZQvCvV",
|
||||
"type": "tool_result",
|
||||
"content": "diff --git a/src/retry-worker.ts b/src/retry-worker.ts\nindex 8ab4b18..8f6dd51 100644\n--- a/src/retry-worker.ts\n+++ b/src/retry-worker.ts\n@@ -1 +1,15 @@\n-export { retrySeconds } from '../lib/retry-after';\n+// Both callers require Retry-After seconds/date parsing, 3600-second ceiling and caller fallback.\n+export function retrySeconds(value: string | null, now: number, fallback = 5): number {\n+ if (value == null || value.trim() === '') return fallback;\n+ const normalized = value.trim();\n+ if (/^\\d+$/.test(normalized)) {\n+ const seconds = Number(normalized);\n+ if (!Number.isSafeInteger(seconds)) return fallback;\n+ return Math.min(seconds, 3600);\n+ }\n+ const deadline = Date.parse(normalized);\n+ if (!Number.isFinite(deadline)) return fallback;\n+ const remaining = Math.ceil((deadline - now) / 1000);\n+ if (remaining < 0) return 0;\n+ return Math.min(remaining, 3600);\n+}\n--- untracked\n--- prior reviews\n{\"skill\":\"review\",\"timestamp\":\"2026-10-18T22:59:44.734Z\",\"status\":\"clean\",\"issues_found\":0,\"critical\":0,\"informational\":0,\"quality_score\":10,\"findings\":[{\"severity\":\"INFORMATIONAL\",\"confidence\":9,\"advisory\":true,\"path\":\"src/retry-worker.ts\",\"line\":2,\"category\":\"shared-libs\",\"summary\":\"Use the established Retry-After contract in the changed worker and the authored route sources.\",\"fix\":\"Share the tested retrySeconds contract, preserving runtime and deployment boundaries for each caller.\",\"evidence_paths\":[\"src/retry-worker.ts\",\"src/retry-route.ts\",\"lib/retry-after.ts\"],\"helper_target\":{\"path\":\"lib/retry-after.ts\",\"symbol\":\"retrySeconds\"},\"fingerprint\":\"shared-libs:af037ba20b8a3674855fb65b04f1855c7f0cb5581d3b31f1c4cf5ab648ee6d33\",\"action\":\"skipped\",\"snapshot_covered_paths\":[\"src/retry-worker.ts\",\"src/retry-route.ts\",\"lib/retry-after.ts\"]}],\"completed\":true,\"converged\":true,\"cycles\":0,\"status_source\":\"claimed\",\"commit_full\":\"992f75f3e20107098e67fb90f1c582fba25a98fa\",\"tree\":\"0d6f7d79e489259c97311903f75fe9570a21645d\",\"dirty\":true,\"review_binding\":{\"state\":\"verified\",\"start_wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"end_wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"started_at\":\"2026-10-18T22:59:44.730Z\",\"branch_id\":\"951d42dc02dc743167ac3dd9d8decc5eee71860498c4b1707e1d38816df1ed1d\"},\"wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"shared_libs_coverage_version\":1,\"review_freshness\":{\"status\":\"CURRENT\",\"reason\":\"completed clean pass on unchanged content\"}}\n---CONFIG---\nfalse---HEAD---\n992f75f\n---WTREE---\n288a3521b692882b9936606504a4b7999286702f\n---TREE---\n0d6f7d79e489259c97311903f75fe9570a21645d\n---DIRTY---\ntrue",
|
||||
"is_error": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01KTLcMKgfzbN3eX8NSaXL1k",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "for f in src/retry-worker.ts src/retry-route.ts lib/retry-after.ts src/scheduler.ts test/retry-after.test.ts; do echo \"=== $f (worktree)\"; cat -n \"$f\"; done; echo \"=== src/retry-route.ts (index blob)\"; git show :src/retry-route.ts; echo \"=== src/retry-route.ts (HEAD)\"; git show HEAD:src/retry-route.ts; echo \"=== hashes\"; git hash-object src/retry-worker.ts src/retry-route.ts lib/retry-after.ts; ls -la src lib; git check-attr -a -- src/retry-worker.ts src/retry-route.ts lib/retry-after.ts",
|
||||
"description": "Read evidence sources, index blobs and hashes"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01KTLcMKgfzbN3eX8NSaXL1k",
|
||||
"type": "tool_result",
|
||||
"content": "=== src/retry-worker.ts (worktree)\n 1\t// Both callers require Retry-After seconds/date parsing, 3600-second ceiling and caller fallback.\n 2\texport function retrySeconds(value: string | null, now: number, fallback = 5): number {\n 3\t if (value == null || value.trim() === '') return fallback;\n 4\t const normalized = value.trim();\n 5\t if (/^\\d+$/.test(normalized)) {\n 6\t const seconds = Number(normalized);\n 7\t if (!Number.isSafeInteger(seconds)) return fallback;\n 8\t return Math.min(seconds, 3600);\n 9\t }\n 10\t const deadline = Date.parse(normalized);\n 11\t if (!Number.isFinite(deadline)) return fallback;\n 12\t const remaining = Math.ceil((deadline - now) / 1000);\n 13\t if (remaining < 0) return 0;\n 14\t return Math.min(remaining, 3600);\n 15\t}\n=== src/retry-route.ts (worktree)\n 1\t// Both callers require Retry-After seconds/date parsing, 3600-second ceiling and caller fallback.\n 2\texport function retrySeconds(value: string | null, now: number, fallback = 5): number {\n 3\t if (value == null || value.trim() === '') return fallback;\n 4\t const normalized = value.trim();\n 5\t if (/^\\d+$/.test(normalized)) {\n 6\t const seconds = Number(normalized);\n 7\t if (!Number.isSafeInteger(seconds)) return fallback;\n 8\t return Math.min(seconds, 3600);\n 9\t }\n 10\t const deadline = Date.parse(normalized);\n 11\t if (!Number.isFinite(deadline)) return fallback;\n 12\t const remaining = Math.ceil((deadline - now) / 1000);\n 13\t if (remaining < 0) return 0;\n 14\t return Math.min(remaining, 3600);\n 15\t}\n 16\t\n 17\t// Authored caller changed after the prior decision (skip-worktree).\n=== lib/retry-after.ts (worktree)\n 1\t// Proven shared parser already used by the scheduler.\n 2\texport function retrySeconds(value: string | null, now: number, fallback = 5): number {\n 3\t if (value == null || value.trim() === '') return fallback;\n 4\t const normalized = value.trim();\n 5\t if (/^\\d+$/.test(normalized)) {\n 6\t const seconds = Number(normalized);\n 7\t if (!Number.isSafeInteger(seconds)) return fallback;\n 8\t return Math.min(seconds, 3600);\n 9\t }\n 10\t const deadline = Date.parse(normalized);\n 11\t if (!Number.isFinite(deadline)) return fallback;\n 12\t const remaining = Math.ceil((deadline - now) / 1000);\n 13\t if (remaining < 0) return 0;\n 14\t return Math.min(remaining, 3600);\n 15\t}\n=== src/scheduler.ts (worktree)\n 1\timport { retrySeconds } from '../lib/retry-after';\n 2\texport const nextRun = (value: string) => retrySeconds(value, Date.now());\n=== test/retry-after.test.ts (worktree)\n 1\timport { expect, test } from 'bun:test';\n 2\timport { retrySeconds } from '../lib/retry-after';\n 3\ttest('retry parser contract', () => {\n 4\t expect(retrySeconds(null, 0)).toBe(5);\n 5\t expect(retrySeconds('invalid', 0, 7)).toBe(7);\n 6\t expect(retrySeconds(' 42 ', 0)).toBe(42);\n 7\t expect(retrySeconds('999999', 0)).toBe(3600);\n 8\t expect(retrySeconds('Thu, 01 Jan 1970 00:00:01 GMT', 0)).toBe(1);\n 9\t});\n=== src/retry-route.ts (index blob)\n// Both callers require Retry-After seconds/date parsing, 3600-second ceiling and caller fallback.\nexport function retrySeconds(value: string | null, now: number, fallback = 5): number {\n if (value == null || value.trim() === '') return fallback;\n const normalized = value.trim();\n if (/^\\d+$/.test(normalized)) {\n const seconds = Number(normalized);\n if (!Number.isSafeInteger(seconds)) return fallback;\n return Math.min(seconds, 3600);\n }\n const deadline = Date.parse(normalized);\n if (!Number.isFinite(deadline)) return fallback;\n const remaining = Math.ceil((deadline - now) / 1000);\n if (remaining < 0) return 0;\n return Math.min(remaining, 3600);\n}\n=== src/retry-route.ts (HEAD)\n// Both callers require Retry-After seconds/date parsing, 3600-second ceiling and caller fallback.\nexport function retrySeconds(value: string | null, now: number, fallback = 5): number {\n if (value == null || value.trim() === '') return fallback;\n const normalized = value.trim();\n if (/^\\d+$/.test(normalized)) {\n const seconds = Number(normalized);\n if (!Number.isSafeInteger(seconds)) return fallback;\n return Math.min(seconds, 3600);\n }\n const deadline = Date.parse(normalized);\n if (!Number.isFinite(deadline)) return fallback;\n const remaining = Math.ceil((deadline - now) / 1000);\n if (remaining < 0) return 0;\n return Math.min(remaining, 3600);\n}\n=== hashes\n8f6dd51314e9960b7bc3add9d95b8f530b85087d\n9f7db8e0dd8f3980cfa3c6e4e8ca96270ebf2cd9\n00dfe6a156bae81aefc94ff1e9db54e32d774c75\nlib:\ntotal 12\ndrwxr-xr-x 2 runner runner 4096 Oct 18 22:59 .\ndrwxr-xr-x 6 runner runner 4096 Oct 18 22:59 ..\n-rw-r--r-- 1 runner runnLine truncated
|
||||
"is_error": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01VjoQ7ipVXspDM6Bqtdz2wg",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "'/__w/gstack/gstack/bin/gstack-review-log' --check-shared-libs e1734218-7e14-446d-a146-02b4b45c636d <<'GSTACK_REVALIDATION_FINDING'\n{\"severity\":\"INFORMATIONAL\",\"confidence\":9,\"advisory\":true,\"path\":\"src/retry-worker.ts\",\"line\":2,\"category\":\"shared-libs\",\"summary\":\"Use the established Retry-After contract in the changed worker and the authored route sources.\",\"fix\":\"Share the tested retrySeconds contract, preserving runtime and deployment boundaries for each caller.\",\"evidence_paths\":[\"src/retry-worker.ts\",\"src/retry-route.ts\",\"lib/retry-after.ts\"],\"helper_target\":{\"path\":\"lib/retry-after.ts\",\"symbol\":\"retrySeconds\"},\"fingerprint\":\"shared-libs:af037ba20b8a3674855fb65b04f1855c7f0cb5581d3b31f1c4cf5ab648ee6d33\",\"specialist\":\"maintainability\"}\nGSTACK_REVALIDATION_FINDING",
|
||||
"description": "Run the shared-libs prior-skip reuse checker"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01VjoQ7ipVXspDM6Bqtdz2wg",
|
||||
"type": "tool_result",
|
||||
"content": "{\"reusable\":false,\"fingerprint\":\"shared-libs:af037ba20b8a3674855fb65b04f1855c7f0cb5581d3b31f1c4cf5ab648ee6d33\",\"review_start\":{\"skill\":\"review\",\"repo\":\"/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/repo\",\"branch\":\"feature/a\",\"wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"started_at\":\"2026-10-18T22:59:58.112Z\"},\"snapshot\":{\"wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"branch_id\":\"951d42dc02dc743167ac3dd9d8decc5eee71860498c4b1707e1d38816df1ed1d\",\"covered_paths\":[\"src/retry-worker.ts\",\"lib/retry-after.ts\"]}}",
|
||||
"is_error": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01VruAT4fyTBhb3iZx2XacGP",
|
||||
"name": "AskUserQuestion",
|
||||
"input": {
|
||||
"questions": [
|
||||
{
|
||||
"question": "[ADVISORY] shared-libs (fingerprint shared-libs:af037ba2…6d33): src/retry-worker.ts:2-15 now copies lib/retry-after.ts retrySeconds instead of re-exporting it. src/retry-route.ts:2-15 is the same copy (it's skip-worktree, so the earlier Skip couldn't be reused). Recommended fix: change the worker back to `export { retrySeconds } from '../lib/retry-after';`, and optionally do the same for the route. Implementation lines: worker removes 15, adds 1, saves 14; route removes 15, adds 1, saves 14 (left out of computed savings because the file is snapshot-ineligible). Existing tests cover it (test/retry-after.test.ts) and nothing new is needed, so the total equals the implementation saving. Shared-failure risk: a helper bug would affect scheduler, worker and route together, but the helper is already tested and in use. RECOMMENDATION: A, because it undoes a duplication this diff introduced. Note: choosing A means editing source, which this no-edit replay can't do. The review will be recorded as not completed and the fix left for a follow-up run.",
|
||||
"header": "Shared code",
|
||||
"multiSelect": false,
|
||||
"options": [
|
||||
{
|
||||
"label": "A) Fix as recommended (Recommended)",
|
||||
"description": "Re-export the tested helper from the worker (and the route). Needs source edits, so this no-edit replay stops as not completed."
|
||||
},
|
||||
{
|
||||
"label": "B) Skip",
|
||||
"description": "No code or index change; the Skip is recorded."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_result",
|
||||
"content": "Your questions have been answered: \"[ADVISORY] shared-libs (fingerprint shared-libs:af037ba2…6d33): src/retry-worker.ts:2-15 now copies lib/retry-after.ts retrySeconds instead of re-exporting it. src/retry-route.ts:2-15 is the same copy (it's skip-worktree, so the earlier Skip couldn't be reused). Recommended fix: change the worker back to `export { retrySeconds } from '../lib/retry-after';`, and optionally do the same for the route. Implementation lines: worker removes 15, adds 1, saves 14; route removes 15, adds 1, saves 14 (left out of computed savings because the file is snapshot-ineligible). Existing tests cover it (test/retry-after.test.ts) and nothing new is needed, so the total equals the implementation saving. Shared-failure risk: a helper bug would affect scheduler, worker and route together, but the helper is already tested and in use. RECOMMENDATION: A, because it undoes a duplication this diff introduced. Note: choosing A means editing source, which this no-edit replay can't do. The review will be recorded as not completed and the fix left for a follow-up run.\"=\"B) Skip\". You can now continue with these answers in mind.",
|
||||
"tool_use_id": "toolu_01VruAT4fyTBhb3iZx2XacGP"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01STvyQsCXiZvyCrEfGm3t3G",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "git rev-parse HEAD origin/main; git branch --show-current; git merge-base origin/main HEAD; git status --porcelain=v1 -uall; git ls-files -v; git ls-files -s; git hash-object src/retry-worker.ts src/retry-route.ts lib/retry-after.ts src/scheduler.ts test/retry-after.test.ts; sha256sum .git/config .git/info/exclude; ls .gitattributes .git/info/attributes 2>&1; git rev-parse --short HEAD; date -u +%Y-%m-%dT%H:%M:%SZ",
|
||||
"description": "Final source, index, config and identity checks"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01STvyQsCXiZvyCrEfGm3t3G",
|
||||
"type": "tool_result",
|
||||
"content": "992f75f3e20107098e67fb90f1c582fba25a98fa\n992f75f3e20107098e67fb90f1c582fba25a98fa\nfeature/a\n992f75f3e20107098e67fb90f1c582fba25a98fa\n M src/retry-worker.ts\nH .gitignore\nH README.md\nH lib/retry-after.ts\nS src/retry-route.ts\nH src/retry-worker.ts\nH src/scheduler.ts\nH test/retry-after.test.ts\n100644 bb5af942f63559a016674999cc537a73cd78c6a5 0\t.gitignore\n100644 bd2d0ac4930be6c76142f6a6c8d10e920413fc46 0\tREADME.md\n100644 00dfe6a156bae81aefc94ff1e9db54e32d774c75 0\tlib/retry-after.ts\n100644 8f6dd51314e9960b7bc3add9d95b8f530b85087d 0\tsrc/retry-route.ts\n100644 8ab4b18d9a2bd337433526c96343d251aaee8539 0\tsrc/retry-worker.ts\n100644 c362fe9878839740d82db85053758be5b95834cd 0\tsrc/scheduler.ts\n100644 a470f2c203c43e66c47991c64654dd524f9c5c29 0\ttest/retry-after.test.ts\n8f6dd51314e9960b7bc3add9d95b8f530b85087d\n9f7db8e0dd8f3980cfa3c6e4e8ca96270ebf2cd9\n00dfe6a156bae81aefc94ff1e9db54e32d774c75\nc362fe9878839740d82db85053758be5b95834cd\na470f2c203c43e66c47991c64654dd524f9c5c29\n2550b8371ff38558f3a8c847e5563ca10271d66c0ce1ea8608cc415a959b5854 .git/config\n6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1 .git/info/exclude\nls: cannot access '.gitattributes': No such file or directory\nls: cannot access '.git/info/attributes': No such file or directory\n992f75f\n2026-10-18T23:00:44Z",
|
||||
"is_error": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01RcGcMugohYvvUcoS3g5qZU",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "bun '/__w/gstack/gstack/test/helpers/shared-libs-path-fixture.ts' --check-review-prerequisites '/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/resumed-review-prerequisites.json'",
|
||||
"description": "Verify synthetic QA/adversarial prerequisites are current"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01RcGcMugohYvvUcoS3g5qZU",
|
||||
"type": "tool_result",
|
||||
"content": "{\"synthetic\":true,\"native_coverage\":false,\"settled\":true,\"current\":true,\"input_sha256\":\"0de6adbbb1db98ff49f0c794ab3c45d924d6e62b0ff0398f70489ca1a1e4b48e\",\"context\":{\"kind\":\"synthetic-path-review-prerequisites\",\"synthetic\":true,\"native_coverage\":false,\"binding\":{\"root\":\"/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3\",\"repo\":\"/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/repo\",\"state\":\"/home/runner/.cache/gstack-paid-shard-q4rJPy/tmp/gstack-shared-path-skip-worktree-knmNE3/state\",\"branch\":\"feature/a\",\"head\":\"992f75f3e20107098e67fb90f1c582fba25a98fa\",\"base\":\"992f75f3e20107098e67fb90f1c582fba25a98fa\",\"wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"index\":\"H 100644 bb5af942f63559a016674999cc537a73cd78c6a5 0\\t.gitignore\\nH 100644 bd2d0ac4930be6c76142f6a6c8d10e920413fc46 0\\tREADME.md\\nH 100644 00dfe6a156bae81aefc94ff1e9db54e32d774c75 0\\tlib/retry-after.ts\\nS 100644 8f6dd51314e9960b7bc3add9d95b8f530b85087d 0\\tsrc/retry-route.ts\\nH 100644 8ab4b18d9a2bd337433526c96343d251aaee8539 0\\tsrc/retry-worker.ts\\nH 100644 c362fe9878839740d82db85053758be5b95834cd 0\\tsrc/scheduler.ts\\nH 100644 a470f2c203c43e66c47991c64654dd524f9c5c29 0\\ttest/retry-after.test.ts\",\"raw\":{\".\":\"dir:16877\",\".git/config\":\"33188:2550b8371ff38558f3a8c847e5563ca10271d66c0ce1ea8608cc415a959b5854\",\".git/info/exclude\":\"33188:6671fe83b7a07c8932ee89164d1f2793b2318058eb8b98dc5c06ee0a5a3b0ec1\",\".gitignore\":\"33188:1578ba3393bbaae78dbda7cc170832528d3bfa4f61690cd07ba8b7075d47540b\",\"README.md\":\"33188:df29e8fdc8895e1fe9d824b1697e4ef77ac6686a40dda22cf513074b37c490bc\",\"lib\":\"dir:16877\",\"lib/retry-after.ts\":\"33188:67d7124d11830e41baa5fca93bd0d45ce64fe58d92799c7d14635b912c5e5942\",\"src\":\"dir:16877\",\"src/retry-route.ts\":\"33188:9d55bb52531ee1d715cc3313754bd364b300f524297ef9b130cb732bcb32a126\",\"src/retry-worker.ts\":\"33188:a17d12e37ae2b07d38dae292156aef6ec83d11b4ec2e295bb14d01f0e8fc9b2e\",\"src/scheduler.ts\":\"33188:8a07bfe628ea4f9e679bcab3cfb68482678eebc1e4031887e996d799a6d1198e\",\"test\":\"dir:16877\",\"test/retry-after.test.ts\":\"33188:653e4090ae4e0d856f4dbf04fb05449d04c44fef599c24ce146d94969a1040a1\"}},\"qa\":{\"settled\":true,\"required_probes\":[{\"id\":\"retry-contract\",\"status\":\"passed\",\"result\":\"Synthetic fixture input: Retry-After seconds/date parsing, ceiling and fallback probes passed.\"}],\"findings\":[]},\"native_adversarial\":{\"settled\":true,\"status\":\"completed\",\"findings\":[],\"result\":\"Synthetic fixture input: native adversarial review returned no findings.\"},\"structured_review\":{\"required\":false,\"reason\":\"Tiny diff; no full-review, structured-review or P1 override requested.\"}}}",
|
||||
"is_error": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01YG4WLQgYoNmXmopuGKUuxK",
|
||||
"name": "Bash",
|
||||
"input": {
|
||||
"command": "'/__w/gstack/gstack/bin/gstack-review-log' '{\"skill\":\"review\",\"timestamp\":\"2026-10-18T23:00:44Z\",\"status\":\"clean\",\"issues_found\":0,\"critical\":0,\"informational\":0,\"quality_score\":10.0,\"specialists\":{},\"findings\":[{\"severity\":\"INFORMATIONAL\",\"confidence\":9,\"advisory\":true,\"path\":\"src/retry-worker.ts\",\"line\":2,\"category\":\"shared-libs\",\"summary\":\"Use the established Retry-After contract in the changed worker and the authored route sources.\",\"fix\":\"Share the tested retrySeconds contract, preserving runtime and deployment boundaries for each caller.\",\"evidence_paths\":[\"src/retry-worker.ts\",\"src/retry-route.ts\",\"lib/retry-after.ts\"],\"helper_target\":{\"path\":\"lib/retry-after.ts\",\"symbol\":\"retrySeconds\"},\"fingerprint\":\"shared-libs:af037ba20b8a3674855fb65b04f1855c7f0cb5581d3b31f1c4cf5ab648ee6d33\",\"action\":\"skipped\"}],\"commit\":\"992f75f\",\"completed\":true,\"converged\":true,\"cycles\":0}' --finish e1734218-7e14-446d-a146-02b4b45c636d && '/__w/gstack/gstack/bin/gstack-review-read'",
|
||||
"description": "Persist final review record and read it back"
|
||||
},
|
||||
"caller": {
|
||||
"type": "direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "user",
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"tool_use_id": "toolu_01YG4WLQgYoNmXmopuGKUuxK",
|
||||
"type": "tool_result",
|
||||
"content": "{\"skill\":\"review\",\"timestamp\":\"2026-10-18T22:59:44.734Z\",\"status\":\"clean\",\"issues_found\":0,\"critical\":0,\"informational\":0,\"quality_score\":10,\"findings\":[{\"severity\":\"INFORMATIONAL\",\"confidence\":9,\"advisory\":true,\"path\":\"src/retry-worker.ts\",\"line\":2,\"category\":\"shared-libs\",\"summary\":\"Use the established Retry-After contract in the changed worker and the authored route sources.\",\"fix\":\"Share the tested retrySeconds contract, preserving runtime and deployment boundaries for each caller.\",\"evidence_paths\":[\"src/retry-worker.ts\",\"src/retry-route.ts\",\"lib/retry-after.ts\"],\"helper_target\":{\"path\":\"lib/retry-after.ts\",\"symbol\":\"retrySeconds\"},\"fingerprint\":\"shared-libs:af037ba20b8a3674855fb65b04f1855c7f0cb5581d3b31f1c4cf5ab648ee6d33\",\"action\":\"skipped\",\"snapshot_covered_paths\":[\"src/retry-worker.ts\",\"src/retry-route.ts\",\"lib/retry-after.ts\"]}],\"completed\":true,\"converged\":true,\"cycles\":0,\"status_source\":\"claimed\",\"commit_full\":\"992f75f3e20107098e67fb90f1c582fba25a98fa\",\"tree\":\"0d6f7d79e489259c97311903f75fe9570a21645d\",\"dirty\":true,\"review_binding\":{\"state\":\"verified\",\"start_wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"end_wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"started_at\":\"2026-10-18T22:59:44.730Z\",\"branch_id\":\"951d42dc02dc743167ac3dd9d8decc5eee71860498c4b1707e1d38816df1ed1d\"},\"wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"shared_libs_coverage_version\":1,\"review_freshness\":{\"status\":\"CURRENT\",\"reason\":\"completed clean pass on unchanged content\"}}\n{\"skill\":\"review\",\"timestamp\":\"2026-10-18T23:00:44Z\",\"status\":\"clean\",\"issues_found\":0,\"critical\":0,\"informational\":0,\"quality_score\":10,\"specialists\":{},\"findings\":[{\"severity\":\"INFORMATIONAL\",\"confidence\":9,\"advisory\":true,\"path\":\"src/retry-worker.ts\",\"line\":2,\"category\":\"shared-libs\",\"summary\":\"Use the established Retry-After contract in the changed worker and the authored route sources.\",\"fix\":\"Share the tested retrySeconds contract, preserving runtime and deployment boundaries for each caller.\",\"evidence_paths\":[\"src/retry-worker.ts\",\"src/retry-route.ts\",\"lib/retry-after.ts\"],\"helper_target\":{\"path\":\"lib/retry-after.ts\",\"symbol\":\"retrySeconds\"},\"fingerprint\":\"shared-libs:af037ba20b8a3674855fb65b04f1855c7f0cb5581d3b31f1c4cf5ab648ee6d33\",\"action\":\"skipped\",\"snapshot_covered_paths\":[\"src/retry-worker.ts\",\"lib/retry-after.ts\"]}],\"commit\":\"992f75f\",\"completed\":true,\"converged\":true,\"cycles\":0,\"status_source\":\"claimed\",\"commit_full\":\"992f75f3e20107098e67fb90f1c582fba25a98fa\",\"tree\":\"0d6f7d79e489259c97311903f75fe9570a21645d\",\"dirty\":true,\"review_binding\":{\"state\":\"verified\",\"start_wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"end_wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"started_at\":\"2026-10-18T22:59:58.112Z\",\"branch_id\":\"951d42dc02dc743167ac3dd9d8decc5eee71860498c4b1707e1d38816df1ed1d\"},\"wtree\":\"288a3521b692882b9936606504a4b7999286702f\",\"shared_libs_coverage_version\":1,\"review_freshness\":{\"status\":\"CURRENT\",\"reason\":\"completed clean pass on unchanged content\"}}\n---CONFIG---\nfalse---HEAD---\n992f75f\n---WTREE---\n288a3521b692882b9936606504a4b7999286702f\n---TREE---\n0d6f7d79e489259c97311903f75fe9570a21645d\n---DIRTY---\ntrue",
|
||||
"is_error": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -167,6 +167,7 @@ export const FREE_FIXTURES: Readonly<Record<string, readonly string[]>> = {
|
||||
'test/fixtures/shard-cli-parity/**': ['test/shard-cli-parity.test.ts'],
|
||||
'test/fixtures/shard-equivalence/**': ['test/shard-engine-equivalence.test.ts'],
|
||||
'test/fixtures/shared-libs-claude-version-probe.json': ['test/shared-libs-fixture.test.ts'],
|
||||
'test/fixtures/shared-libs-index-flags-loop-read-39173752116.json': ['test/shared-libs-checker-interface-evidence.test.ts'],
|
||||
'test/fixtures/shared-libs-no-change-ci-public.json': ['test/shared-libs-fixture.test.ts'],
|
||||
'test/fixtures/shared-libs-plan-callers-fixes-behavior-local.json': ['test/shared-libs-plan-actor.test.ts'],
|
||||
'test/fixtures/shared-libs-plan-callers-future-risk-37195203538.json': ['test/shared-libs-plan-actor.test.ts'],
|
||||
|
||||
@@ -353,7 +353,8 @@ function findBindsFile(calls: ReturnType<typeof commands>, file: string, cwd: st
|
||||
});
|
||||
}
|
||||
|
||||
function inspectsFile(source: string, file: string, returnedPath: boolean, expected: StartContext): boolean {
|
||||
function inspectsFile(source: string, file: string, text: string, expected: StartContext): boolean {
|
||||
const returnedPath = containsPath(text, file);
|
||||
const paths = sourcePaths(file);
|
||||
const environment = new Map([['GSTACK_HOME', expected.state], ['SLUG', expected.slug]]);
|
||||
const calls = withDirectories(commands(source), paths.normalize(expected.repo), paths, environment);
|
||||
@@ -379,6 +380,20 @@ function inspectsFile(source: string, file: string, returnedPath: boolean, expec
|
||||
&& (returnedPath || findBindsFile(commands(quoted[1]), file, call.cwd, call.variables))) return true;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
const call = calls[i];
|
||||
// A loop over literal paths counts only when its output names this file.
|
||||
const listed = call.words[0] === 'for' && /^[A-Za-z_]\w*$/.test(call.words[1]) && call.words[2] === 'in'
|
||||
&& call.words.length > 3 && call.words.slice(3).every(word => /^[\w./-]+$/.test(word))
|
||||
? call.words.slice(3).find(word => literalPath(word, call.cwd, paths) === file) : undefined;
|
||||
if (!listed || !containsPath(text, listed)) continue;
|
||||
for (const next of calls.slice(i + 1)) {
|
||||
if (next.words[0] === 'done') break;
|
||||
const words = next.words[0] === 'do' ? next.words.slice(1) : next.words;
|
||||
if (rebinds(words, call.words[1])) break;
|
||||
if (reader(words, `$${call.words[1]}`) || reader(words, `\${${call.words[1]}}`)) return true;
|
||||
}
|
||||
}
|
||||
if (!returnedPath) return false;
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
const call = calls[i];
|
||||
@@ -534,7 +549,7 @@ export function hasTrustedSharedLibsCheck(events: unknown[], expected: CheckerCo
|
||||
return reads.some(read => read.tool === 'Read' && typeof read.input?.file_path === 'string'
|
||||
&& literalPath(read.input.file_path, expected.repo, paths) === file
|
||||
|| read.tool === 'Bash' && typeof read.input?.command === 'string'
|
||||
&& inspectsFile(read.input.command, file, containsPath(read.text, file), expected));
|
||||
&& inspectsFile(read.input.command, file, read.text, expected));
|
||||
})) continue;
|
||||
if (bash.some(finish => finish.at > check.returnedAt && invocations(finish.input.command).some(call =>
|
||||
call.words.length === 4 && call.words[2] === '--finish' && call.words[3] === token))) return true;
|
||||
@@ -573,7 +588,7 @@ export function hasTrustedReviewStartRead(events: unknown[], expected: StartCont
|
||||
// Discovery may return the path only in stdout; bind its cat invocation
|
||||
// to that discovery instead of accepting unrelated reader/find words.
|
||||
const shellRead = read.tool === 'Bash' && (sameCall ? sameCallStartRead(command, file, expected, token)
|
||||
: inspectsFile(command, file, containsPath(read.text, file), expected));
|
||||
: inspectsFile(command, file, read.text, expected));
|
||||
if (!directRead && !shellRead) continue;
|
||||
for (const line of read.text.split('\n')) {
|
||||
// Native Read can prefix the single-line JSON file with a line number.
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from './helpers/shared-libs-path-fixture';
|
||||
import { CAPTURE_LONG_MS } from './helpers/eval-budgets';
|
||||
import capturedChecker from './fixtures/shared-libs-index-flags-r59-checker-public.json';
|
||||
import loopRead from './fixtures/shared-libs-index-flags-loop-read-39173752116.json';
|
||||
|
||||
const helper = path.join(SHARED_LIBS_ROOT, 'bin/gstack-review-log');
|
||||
const fixtures: SharedLibsFixture[] = [];
|
||||
@@ -661,3 +662,28 @@ describe('native executable shared-code checker evidence', () => {
|
||||
expect(hasTrustedSharedLibsCheck(run.events, run.expected)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// Gate 39173752116 slice 12: the skip-worktree review read every evidence file in one
|
||||
// literal for-loop that printed each path before its contents.
|
||||
describe('literal loop reads from the captured skip-worktree review', () => {
|
||||
const loop = (events: any[]) => events.flatMap(event => event.message.content)
|
||||
.find((block: any) => block.type === 'tool_use' && block.input?.command?.startsWith('for f in '));
|
||||
const output = (events: any[], id: string) => events.flatMap(event => event.message.content)
|
||||
.find((block: any) => block.type === 'tool_result' && block.tool_use_id === id);
|
||||
|
||||
test('a literal loop whose output names each evidence file counts as reading it', () => {
|
||||
expect(hasTrustedSharedLibsCheck(structuredClone(loopRead.events), loopRead.expected)).toBe(true);
|
||||
});
|
||||
|
||||
test.each(['variable list', 'file missing from output', 'path outside the repo'])('%s is still rejected', shape => {
|
||||
const events = structuredClone(loopRead.events), call = loop(events), files = 'src/retry-worker.ts src/retry-route.ts lib/retry-after.ts';
|
||||
expect(call.input.command).toContain(`for f in ${files} `);
|
||||
if (shape === 'variable list') call.input.command = `FILES="${files}"; ${call.input.command.replace(files, '$FILES')}`;
|
||||
if (shape === 'file missing from output') {
|
||||
const result = output(events, call.id);
|
||||
result.content = JSON.parse(JSON.stringify(result.content).replaceAll('src/retry-route.ts', 'src/omitted.ts'));
|
||||
}
|
||||
if (shape === 'path outside the repo') call.input.command = call.input.command.replace(' src/retry-route.ts ', ' ../outside/src/retry-route.ts ');
|
||||
expect(hasTrustedSharedLibsCheck(events, loopRead.expected)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user