You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
runTests in packages/cli/src/rules/verify.ts recovers the pass/fail counts of ast-grep test by regex-scanning its human-readable output:
constoutput=stdoutChunks.join("")+stderrChunks.join("");// sg test outputs: "test result: ok. 3 passed; 0 failed;"constpassedMatch=/(\d+)\s+passed/i.exec(output);constfailedMatch=/(\d+)\s+failed/i.exec(output);
The regexes are unanchored and run against stdout and stderr concatenated, taking the first match anywhere in the stream. ast-grep test echoes the source of a failing test case, so fixture content can be read as the summary.
Reproduction
Measured against the vendored ast-grep 0.41.0. Rule and fixture:
ast-grep test -c sgconfig.yml --skip-snapshot-tests --filter '^no-eval$' prints:
const msg = '7 passed; 0 failed';
FAIL no-eval M
Error: test failed. 0 passed; 1 failed;
exit code 4. Applying the two regexes above to that output yields:
CLI would report: passed = 7 failed = 0
truth from the summary line: 0 passed; 1 failed
The echoed fixture matched before the summary line did.
Severity — what this does and does not do
It corrupts the reported counts. It does not cause a false pass. Validity is decided by the exit code, not the counts, and a passing run never echoes fixture source — so the only match on a clean run is the genuine summary. A rule cannot be made to verify green this way.
What it does produce is a failure reported with nonsense numbers: Tests: ✗ failed (7 passed, 0 failed). That output is not only read by humans — the improve-rule loop feeds it back to an agent iterating on a rule, so wrong counts can steer the next edit.
Two related defects in the same function
UTF-8 chunk boundaries. Both handlers do chunk.toString() per chunk and join afterwards, so a multi-byte character split across a chunk boundary is corrupted. This is the same bug fixed for Vale in vale/run.ts (replaced with a StringDecoder per stream, flushed on close); the ast-grep path never got the same treatment.
Format drift is silent-ish.@ast-grep/cli is pinned exact at 0.41.0. If a future version reworded the summary, both regexes would miss, counts would read 0/0, and the code would fall through to the exit-code snippet — degraded rather than wrong, but nothing would announce that the parser had stopped working.
Why there is no trivial fix
ast-grep test has no structured output option at 0.41.0 — --help offers --test-dir, --snapshot-dir, --skip-snapshot-tests, --update-all, --interactive, --filter, --config, --include-off, and nothing for JSON or machine-readable reporting. So "use --json" is not available, and this cannot simply be deleted the way dist/prompts.js scanning was in the styleguide's worked example.
That makes it a narrower problem: keep parsing, but parse the one line that is a summary rather than the whole stream.
Suggested direction
Anchor to the summary line specifically — test result: ok. N passed; M failed; on success and Error: test failed. N passed; M failed; on failure — rather than scanning all output. Match on the last such line, not the first token that looks numeric.
Strip ANSI escapes before matching; the output is colorized (test result: \e[32mok\e[0m.).
Use a StringDecoder per stream, as vale/run.ts does.
Pin the summary format in the ast-grep vendor-contract test proposed in Add an ast-grep vendor-contract test #108, so a version bump fails loudly instead of quietly returning 0/0.
Related: #108 (ast-grep vendor-contract test). See also the "Verify Build Output In The Build, Not By Parsing It" section of .conventions/STYLEGUIDE-CODE.md — the same failure shape (prose read as structure) as the which engine case documented there.
runTestsinpackages/cli/src/rules/verify.tsrecovers the pass/fail counts ofast-grep testby regex-scanning its human-readable output:The regexes are unanchored and run against stdout and stderr concatenated, taking the first match anywhere in the stream.
ast-grep testechoes the source of a failing test case, so fixture content can be read as the summary.Reproduction
Measured against the vendored
ast-grep 0.41.0. Rule and fixture:ast-grep test -c sgconfig.yml --skip-snapshot-tests --filter '^no-eval$'prints:exit code 4. Applying the two regexes above to that output yields:
The echoed fixture matched before the summary line did.
Severity — what this does and does not do
It corrupts the reported counts. It does not cause a false pass. Validity is decided by the exit code, not the counts, and a passing run never echoes fixture source — so the only match on a clean run is the genuine summary. A rule cannot be made to verify green this way.
What it does produce is a failure reported with nonsense numbers:
Tests: ✗ failed (7 passed, 0 failed). That output is not only read by humans — theimprove-ruleloop feeds it back to an agent iterating on a rule, so wrong counts can steer the next edit.Two related defects in the same function
chunk.toString()per chunk and join afterwards, so a multi-byte character split across a chunk boundary is corrupted. This is the same bug fixed for Vale invale/run.ts(replaced with aStringDecoderper stream, flushed onclose); the ast-grep path never got the same treatment.@ast-grep/cliis pinned exact at0.41.0. If a future version reworded the summary, both regexes would miss, counts would read0/0, and the code would fall through to the exit-code snippet — degraded rather than wrong, but nothing would announce that the parser had stopped working.Why there is no trivial fix
ast-grep testhas no structured output option at 0.41.0 —--helpoffers--test-dir,--snapshot-dir,--skip-snapshot-tests,--update-all,--interactive,--filter,--config,--include-off, and nothing for JSON or machine-readable reporting. So "use--json" is not available, and this cannot simply be deleted the waydist/prompts.jsscanning was in the styleguide's worked example.That makes it a narrower problem: keep parsing, but parse the one line that is a summary rather than the whole stream.
Suggested direction
test result: ok. N passed; M failed;on success andError: test failed. N passed; M failed;on failure — rather than scanning all output. Match on the last such line, not the first token that looks numeric.test result: \e[32mok\e[0m.).StringDecoderper stream, asvale/run.tsdoes.0/0.Related: #108 (ast-grep vendor-contract test). See also the "Verify Build Output In The Build, Not By Parsing It" section of
.conventions/STYLEGUIDE-CODE.md— the same failure shape (prose read as structure) as thewhich enginecase documented there.