Files
gstack/lib/pregate/checks/regen.ts
Garry Tanandcapy-ai[bot] 8584d6ff49 v1.91.77.0 feat(ship): two-stage gstack-pregate, gstack-regen generated-file registry, intent-based issue links (multi-agent wave C-pregate) (#3121)
* v1.91.77.0 feat(ship): two-stage gstack-pregate, gstack-regen registry, intent-based issue links (multi-agent wave C-pregate)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(pregate): keep the bin-driving pregate tests off the Windows subset; pin files are free-only for eval selection

The three C-pregate tests that spawn bin/gstack-regen, bin/gstack-pregate,
bin/gstack-issue-links and bin/gstack-ship-receipt do so through
test/helpers/pregate-fixture.ts runBin, so the Windows curation's source
scan of the test file never saw a bin/ spawn and shipped them to
windows-latest, where CreateProcess cannot exec a shebang (14 failures in
windows-free-shard 2 and 5). Each is now a KNOWN_WINDOWS_INCOMPATIBLE entry
with the precise reason. .gstack/generated.json and .gstack/pregate.json
join FREE_ONLY_PR_FILES: no paid case reads gstack's own pin files (the
tools read origin/<base> of the repo they run in, and paid cases run in
fixture repos), so touching them no longer restores every gate case.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(harness-version): record the FREE_ONLY_PR_FILES pin-file entries as non-behavioral

scripts/test-pr-profile.ts is a pinned shared-harness file; the two
.gstack pin-file entries change selection only, never a case verdict.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-11 01:57:16 +00:00

37 lines
3.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* pregate check `regen` (tier 1, preflight): every `.gstack/generated.json`
* entry whose inputs or outputs the branch touched is regenerated in a
* scratch git worktree (lib/regen.ts) and compared with the working tree.
* Incident p0-4-stale-goldens: a template changed, the committed render and
* the golden did not, and the remote gate failed an hour later.
*/
import { renderEntry, runRegen, selectEntries } from '../../regen';
import { baseInputs } from '../context';
import type { CheckDef, CheckResult, PregateContext } from '../types';
function run(ctx: PregateContext): CheckResult {
const inputs = baseInputs(ctx, { registry: ctx.registryLabel });
const touched = ctx.touched.map(t => t.path).concat(ctx.touched.flatMap(t => (t.renamedFrom ? [t.renamedFrom] : [])));
const selected = selectEntries(ctx.registry, touched);
if (ctx.registry.entries.length === 0) return { id: 'regen', stage: 'preflight', status: 'pass', detail: `no ${ctx.registryLabel.includes('(no ') ? 'registry' : 'entries'} (${ctx.registryLabel})`, inputs };
if (selected.length === 0) return { id: 'regen', stage: 'preflight', status: 'pass', detail: `no registered input or output touched (${ctx.registry.entries.length} entries)`, inputs };
const commands = selected.filter(e => e.command);
if (commands.length && !ctx.allowRepoCommands) {
return {
id: 'regen', stage: 'preflight', status: 'incomplete', inputs, code: 'REPO_COMMANDS_NOT_ALLOWED',
detail: `repo commands not executed (pass --allow-repo-commands): ${commands.map(c => `${c.id}: ${c.command}`).join('; ')}`,
fix: 'rerun with --allow-repo-commands (what /ship passes)', lines: commands.map(c => `COMMAND: ${c.id}=${JSON.stringify(c.command)} outputs=${c.outputs.join(',')}`),
};
}
const { results } = runRegen({ repoRoot: ctx.repoRoot, registry: ctx.registry, mode: 'check', only: selected.map(e => e.id), env: ctx.env, timeoutMs: ctx.timeoutMs });
const lines = results.map(renderEntry);
const stale = results.filter(r => r.status === 'stale');
const failed = results.filter(r => r.status === 'failed');
ctx.explain.push(...results.map(r => `regen ${r.id}: selected because touched ∩ (${selected.find(e => e.id === r.id)?.inputs.join(',') || 'always'} ∪ outputs) ≠ ∅`));
if (failed.length) return { id: 'regen', stage: 'preflight', status: 'incomplete', inputs, lines, code: 'REGEN_COMMAND_FAILED', detail: `${failed.length} regen command(s) failed: ${failed.map(f => f.id).join(', ')}`, fix: failed.map(f => f.fix).join('; ') };
if (stale.length) return { id: 'regen', stage: 'preflight', status: 'fail', inputs, lines, code: 'REGEN_STALE', detail: `${stale.length} stale: ${stale.flatMap(s => s.changed.concat(s.outside)).join(' ')}`, fix: stale.map(s => s.fix).join('; ') };
return { id: 'regen', stage: 'preflight', status: 'pass', inputs, lines, detail: `${results.length} entr${results.length === 1 ? 'y' : 'ies'} fresh${results.some(r => r.status === 'listed') ? ' (hand-regenerated outputs listed)' : ''}` };
}
export const regenCheck: CheckDef = { id: 'regen', stage: 'preflight', tier: 1, downgradable: true, needsRepoCommands: true, run };