mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 22:45:02 +01:00
* pregate: starter policy, benign-finding allowlist, environment failures, per-run scratch
A repository with no .gstack/pregate.json now gets a warn (PREGATE_NO_POLICY)
with a starter policy printed, instead of PREGATE_NO_LANE on every touched
file; gstack-pregate init remains the explicit write. The policy gains an
allowlist for benign secrets/pii findings (env.value and secret never
allowlistable). Checks that cannot run because of the machine report
PREGATE_ENVIRONMENT (incomplete, with kind and evidence) apart from diff
failures. Every run writes its scratch under the state root, per run id.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* pregate: rename run-scratch module (strays rule) and map starter.ts to its test
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* pregate literals: match a module as a path fragment, require the constant name for a one-character value
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: pre-landing review fixes
guards: an exited guard is the machine's only on exit 127, never from its output;
secrets: clear an interrupted run's added-line copies before scanning;
environment: one fix table per kind, one ANSI strip, hoisted not-found vocabulary;
starter returns its lines; allowlistDigest reuses sha256; stale headers and the
PREGATE_INCOMPLETE fix clause updated; tests for the mixed verify branch, disk
precedence, allowlist boundaries, a positionless finding, head-policy strictness,
run-id edges and the unwritable state root.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* pregate: count a module's full file name without a directory as a consumer
consumersOf anchored both the stem and the full basename on a path
separator, so run('install.sh') or bash install.sh without a directory
stopped counting for a multi-character old value. Only the bare stem
needs the separator; the full file name is specific on its own.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* pregate: a diff cannot clear itself as an environment failure; a taken --run id gets its own directory
Adversarial review of the environment classifier and the scratch layout:
- exit 127 and 'command not found' are missing-binary only when the
command's own binary is off PATH (runner, guard and remote gate alike);
a script the diff controls that exits 127 is the diff's failure
- a local lane with no test summary is classified from its output only
when the runner was not seen starting a test file; a remote gate only
from its first 40 lines, before any test output could stream
- environment.proof records launch (certain) or output (inferred); an
output-inferred cause never says 'the diff is not at fault', its line
says '(inferred from output)', and verify names such checks
- a runner killed by a signal is PREGATE_INCOMPLETE, not missing-binary
- a --run id whose directory exists takes <id>-2 .. -9 instead of
sharing (and truncating) a live run's logs
- engine.* findings are never allowlistable; a pattern entry the scanner
gave no position for is reported as not tested instead of silently inert
- docs: synopsis --run, no-policy lanes wording, verify's code list,
guard exit 127 carve-out, PREGATE_SECRETS allowlist pointer, scratch
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(pregate): PREGATE_INCOMPLETE names a signal-killed runner; CHANGELOG lists every doc edit
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>
56 lines
3.7 KiB
TypeScript
56 lines
3.7 KiB
TypeScript
/**
|
|
* pregate/starter — the policy a repository with none would start from
|
|
* (Lane D, GSTA-112; FOUN #292, harness-evals #81/#82). When no
|
|
* `.gstack/pregate.json` exists on the base branch and no user-owned file is
|
|
* present, the lanes check does not fail a touched file with no lane
|
|
* (PREGATE_NO_LANE); it warns (PREGATE_NO_POLICY) and prints this starter:
|
|
* the detected runner, a `dependencies` entry for each unmapped touched file
|
|
* (the tests nearest it, else the tests sharing its stem; never `[]`, which
|
|
* would clear a file without a test), the test directories and the CI lanes
|
|
* seen. Nothing is written here; `gstack-pregate init` is the explicit write.
|
|
*/
|
|
import * as path from 'node:path';
|
|
import { PREGATE_CONFIG_PATH, PREGATE_CONFIG_SCHEMA } from './config';
|
|
import { isTestFile, listRepoFiles } from './import-graph';
|
|
import { MAX_SUGGESTED, nearestTests } from './init-draft';
|
|
import type { PregateContext } from './types';
|
|
|
|
const MAX_DIRS = 8;
|
|
|
|
|
|
/** `init`'s nearest-directory suggestion, else the tests whose basename starts with the file's stem. */
|
|
export function nearestTestsFor(tests: readonly string[], file: string): string[] {
|
|
const nearest = nearestTests(tests, file);
|
|
if (nearest.length) return nearest;
|
|
const stem = path.posix.basename(file).replace(/\.[^.]+$/, '');
|
|
if (!stem) return [];
|
|
const byStem = tests.filter(t => { const b = path.posix.basename(t); return b === stem || b.startsWith(`${stem}.`) || b.startsWith(`${stem}-`) || b.startsWith(`${stem}_`); });
|
|
return byStem.length <= MAX_SUGGESTED ? byStem : [];
|
|
}
|
|
|
|
/** The starter for `noLane` (touched files with no lane) under `runner` (detected by the lanes check; null when none). */
|
|
export function starterPolicy(ctx: Pick<PregateContext, 'repoRoot' | 'lanes' | 'config' | 'baseRef' | 'absentUserPolicy'>, noLane: readonly string[], runner: string | null): string[] {
|
|
const tests = listRepoFiles(ctx.repoRoot).filter(isTestFile).sort();
|
|
const dependencies: Record<string, string[]> = {};
|
|
const todo: string[] = [];
|
|
for (const file of noLane) {
|
|
const suggested = nearestTestsFor(tests, file);
|
|
if (suggested.length) dependencies[file] = suggested; else todo.push(file);
|
|
}
|
|
const json = JSON.stringify({ $schema: PREGATE_CONFIG_SCHEMA, runner, dependencies }, null, 2);
|
|
const dirCounts = new Map<string, number>();
|
|
for (const t of tests) { const d = path.posix.dirname(t); dirCounts.set(d, (dirCounts.get(d) ?? 0) + 1); }
|
|
const dirs = [...dirCounts].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_DIRS).map(([d]) => d);
|
|
const lanes = ctx.lanes.filter(l => l.runsTests && !l.manual).map(l => `${l.id} (${l.platform}${l.matrixExpression && l.testFiles.length === 0 && !ctx.config.selection[l.id] ? ', matrix computed; declare selection' : ''})`);
|
|
return [
|
|
`PREGATE_STARTER: no ${PREGATE_CONFIG_PATH} on ${ctx.baseRef}${ctx.absentUserPolicy ? ` and no user-owned policy at ${ctx.absentUserPolicy}` : ''}; a starter drafted from this repository follows. Review it, write it with gstack-pregate init, edit each TODO, then commit it on the base branch (from then on an unmapped file is PREGATE_NO_LANE)`,
|
|
`PREGATE_STARTER: runner ${runner ?? 'null (none detected; set runner with {files})'}`,
|
|
`PREGATE_STARTER: test directories: ${dirs.join(', ') || '(no test files found)'}${dirCounts.size > MAX_DIRS ? ` (+${dirCounts.size - MAX_DIRS} more)` : ''}`,
|
|
`PREGATE_STARTER: CI lanes: ${lanes.join(', ') || '(no test job in .github/workflows)'}`,
|
|
`PREGATE_STARTER: begin ${PREGATE_CONFIG_PATH}`,
|
|
...json.split('\n'),
|
|
'PREGATE_STARTER: end',
|
|
...todo.map(f => `TODO ${f}: no test lane and no nearby test; add a test, map it under dependencies, or declare [] if no test can cover it`),
|
|
];
|
|
}
|