mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
feat(review): variant G part 2: lean ensemble, one batched specialist at every depth (GSTA-167)
planReview dispatches one 'specialist batched' at standard and full depth (and light, which adds the Red Team checklist): Testing and Security always on code and gate class, plus State-machines, Data-migration, API-contract and Design when scope asks; a forced checklist always joins. Maintainability, Performance and Simplification are read only when forced (SKIP names them). LIGHT_FIXED is [testing, security]; BATCH_SCOPED joins DEPTH_RULES so old receipts re-plan. The Red Team rule and prompt are unchanged; it reads the batched reader's merged findings. specialistsFor stays as the legacy selection the dispatch compare script models. Repinned reviewer briefs with no-loss replay L (live Arm L, scored L∘G1 vs G1; not yet run): ship/sections/review-army.md, lib/review-depth.ts. Merge only on L's pass. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
9c1e0a089d
commit
ed713b4ad1
10 files changed
+122
-43
No files matched your search
@@ -39,7 +39,8 @@ const HELP = `gstack-review-plan — the review depth of the next /ship step (do
|
||||
--adversarial-ran Step 11's native adversarial pass already ran (a repeat skips it)
|
||||
--gate-tagged <n> gate hunks the gate scan tagged (default: run gstack-gate-diff; failure means unavailable)
|
||||
--gated a,b specialists the hit-rate stats tag GATE_CANDIDATE
|
||||
--force a,b|all specialists forced by prompt flags (--all-specialists is all)
|
||||
--force a,b|all checklists forced into the batched specialist by prompt flags (--all-specialists is all); maintainability,
|
||||
performance and simplification are read only when forced
|
||||
--reviewed-numbers <fp> the numeric-claim fingerprint the outside voice already reviewed
|
||||
--depth-floor standard|full raise the depth to at least this (never lowers it); /ship passes its prompt flag on every run
|
||||
--range-base <sha> the fix range's base (this pass's PASS_HEAD): after a fixed blocker on a diff below full depth, a
|
||||
|
||||
+14
-3
@@ -187,9 +187,18 @@ in a data file still schedules the outside voice.
|
||||
| Depth | When | Pass 1 dispatches |
|
||||
|---|---|---|
|
||||
| **light**, runtime 0 | docs and tests only, or only registered outputs | the core checklist only; the outside voice when the diff publishes numbers |
|
||||
| **light**, runtime 1–50 | `SMALL_DIFF_LINES` or fewer runtime lines | core, one **batched** specialist (testing, maintainability and security checklists, the specialists scope selects and the Red Team prompt in one context), the native adversarial pass (kept: backtest inconclusive, GSTA-147 D5) and the outside voice on numbers |
|
||||
| **standard** | more than 50 runtime lines | the code set with every threshold (specialists at 50, simplification at 100, Red Team at 200) read on runtime lines; specialists leave docs and registered outputs out of their diff, and every specialist but Testing leaves tests out too; adversarial and outside voice read everything |
|
||||
| **full** | gate class, or any classifier error | the gate set, unchanged: every reviewer on the whole diff, the Red Team always, no hit-rate gating |
|
||||
| **light**, runtime 1–50 | `SMALL_DIFF_LINES` or fewer runtime lines | core, one **batched** specialist (the batched checklists below plus the Red Team prompt in one context), the native adversarial pass (kept: backtest inconclusive, GSTA-147 D5) and the outside voice on numbers |
|
||||
| **standard** | more than 50 runtime lines | one batched specialist at `medium` and the Red Team at 200 runtime lines (or on a CRITICAL); the batched reader leaves docs and registered outputs out of its diff; adversarial and outside voice read everything |
|
||||
| **full** | gate class, or any classifier error | one batched specialist on the whole diff, the Red Team always, no hit-rate gating |
|
||||
|
||||
**Batched checklists (the lean ensemble, GSTA-167 part 2).** One specialist
|
||||
subagent reads Testing and Security on every code or gate diff, plus
|
||||
State-machines, Data-migration, API-contract and Design when scope asks (a
|
||||
forced checklist always joins). Maintainability, Performance and Simplification
|
||||
are read only when forced (`SKIP:` names them). The Red Team reads the batched
|
||||
reader's merged findings. A free re-score of saved C and D findings lost 0 of
|
||||
the 29 blockers (handoffs/GSTA-167/lean-ensemble-rescore.md); live Arm L must pass
|
||||
before this merges.
|
||||
|
||||
A fixed blocking finding, a CRITICAL or an open blocker raises a light diff to
|
||||
standard for the next step (`step=standard`): its specialists read at least 50 lines
|
||||
@@ -265,6 +274,8 @@ the specialists, audits and watchers at the host default too
|
||||
Team and the native adversarial pass on the range instead of the specialist army.
|
||||
- A tests-only diff (runtime 0) no longer dispatches the Testing specialist: the
|
||||
core checklist and the gate scan review it.
|
||||
- Every depth dispatches one batched specialist instead of one reader per
|
||||
checklist; Maintainability, Performance and Simplification run only when forced.
|
||||
- Light no longer runs a separate Red Team: the batched specialist carries its
|
||||
prompt. At runtime 0 light runs no adversarial pass either.
|
||||
- Standard reads thresholds on runtime lines, not the whole diff, so a test-heavy
|
||||
|
||||
+1
-1
@@ -733,7 +733,7 @@ Every `/ship` run builds a code path map from your diff, searches for correspond
|
||||
|
||||
### Review gate
|
||||
|
||||
`/ship` displays historical review readiness in the [Review Readiness Dashboard](#review-readiness-dashboard) during preflight. A missing Eng Review is reported without an extra question; it does not replace or waive the current pre-landing review. Step 9 still runs the checklist, applicable specialists and bounded exploratory QA, with its existing approval and completion gates.
|
||||
`/ship` displays historical review readiness in the [Review Readiness Dashboard](#review-readiness-dashboard) during preflight. A missing Eng Review is reported without an extra question; it does not replace or waive the current pre-landing review. Step 9 still runs the checklist, one batched specialist (Testing and Security, plus the checklists scope selects) and bounded exploratory QA, with its existing approval and completion gates.
|
||||
|
||||
A lot of branches die when the interesting work is done and only the boring release work is left. Humans procrastinate that part. AI should not.
|
||||
|
||||
|
||||
+32
-8
@@ -29,8 +29,10 @@ export const RED_TEAM_LINES = 200;
|
||||
export const LARGE_DIFF_LINES = 100;
|
||||
/** The largest fix range, in runtime lines, a pass 2 after a blocking fix reviews as a scoped pass (GSTA-152 D6); a starting value. */
|
||||
export const SCOPED_RANGE_LINES = 150;
|
||||
/** The checklists light's batched specialist always reads, whatever the scope. */
|
||||
export const LIGHT_FIXED: readonly Specialist[] = ['testing', 'maintainability', 'security'];
|
||||
/** The checklists the batched specialist always reads on code and gate class, whatever the depth or scope (GSTA-167 part 2). */
|
||||
export const LIGHT_FIXED: readonly Specialist[] = ['testing', 'security'];
|
||||
/** The checklists the batched specialist adds when scope asks; maintainability, performance and simplification are read only when forced. */
|
||||
export const BATCH_SCOPED: readonly Specialist[] = ['data-migration', 'api-contract', 'design', 'state-machines'];
|
||||
/**
|
||||
* Each blocking role's effort (GSTA-167 part 3, CREW 30c): specialists at medium, which is how the no-loss controls ran
|
||||
* them; the Red Team, the native adversarial pass and the fix-check at full (the lane's reasoning); the outside voice at
|
||||
@@ -64,7 +66,7 @@ const DATA_CONFIG = [
|
||||
|
||||
/** A hash of the constants and patterns that decide a depth, not of this file's bytes: a comment edit keeps it, a threshold edit changes it. */
|
||||
export const DEPTH_RULES = createHash('sha256').update(JSON.stringify({
|
||||
SMALL_DIFF_LINES, RED_TEAM_LINES, LARGE_DIFF_LINES, FULL_PASS_CAP, LIGHT_FIXED, SCOPED_RANGE_LINES, ROLE_EFFORT,
|
||||
SMALL_DIFF_LINES, RED_TEAM_LINES, LARGE_DIFF_LINES, FULL_PASS_CAP, LIGHT_FIXED, BATCH_SCOPED, SCOPED_RANGE_LINES, ROLE_EFFORT,
|
||||
patterns: Object.entries({ GATE_PATH, AGENT_PROSE, TEST_PATH, RELEASE_METADATA, DOC_PATH: [DOC_PATH], DATA_PATH: [DATA_PATH], DATA_CONFIG }).map(([k, v]) => [k, v.map(r => `/${r.source}/${r.flags}`)]),
|
||||
})).digest('hex').slice(0, 16);
|
||||
|
||||
@@ -290,7 +292,7 @@ export interface PlanInput extends ClassInput {
|
||||
/** false under --no-tiers or review_effort_tiers off: specialists run at the host's default effort. */
|
||||
tiers?: boolean;
|
||||
}
|
||||
/** One reviewer. `exclude` holds git pathspec argv items the reviewer's diff leaves out; `checklists` names what light's batched specialist reads. */
|
||||
/** One reviewer. `exclude` holds git pathspec argv items the reviewer's diff leaves out; `checklists` names what the batched specialist reads. */
|
||||
export interface Dispatch { role: Role; name: string; effort: Effort; exclude: string[]; checklists?: string[]; reason?: string }
|
||||
export interface ReviewPlan extends DepthResult {
|
||||
/** The depth this step runs at: light raised to standard by a fixed blocker, a CRITICAL or an open blocker. */
|
||||
@@ -332,6 +334,29 @@ export function specialistsFor(cls: DiffClass, i: PlanInput, skipped: string[] =
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The checklists the one batched specialist reads (the lean ensemble, GSTA-167 part 2): the class sets the base (none for docs,
|
||||
* testing for tests, LIGHT_FIXED for code and gate), BATCH_SCOPED joins when scope asks and hit-rate gating allows (gate class
|
||||
* ignores gating), and a forced checklist always joins.
|
||||
*/
|
||||
export function batchChecklists(cls: DiffClass, i: PlanInput, skipped: string[] = []): Specialist[] {
|
||||
const forced = forcedBy(i);
|
||||
const codeLike = cls === 'code' || cls === 'gate';
|
||||
const base: readonly Specialist[] = codeLike ? LIGHT_FIXED : cls === 'tests' ? ['testing'] : [];
|
||||
if (cls === 'docs') skipped.push('specialists (docs class)');
|
||||
if (cls === 'tests') skipped.push('specialists except testing (tests class)');
|
||||
const extra = SPECIALISTS.filter(n => {
|
||||
if (base.includes(n)) return false;
|
||||
if (forced(n)) return true;
|
||||
if (!codeLike || !BATCH_SCOPED.includes(n) || !scopeWants[n]!(i.scope ?? {})) return false;
|
||||
if (cls !== 'gate' && (i.gated ?? []).includes(n)) { skipped.push(`${n} (gated by hit rate)`); return false; }
|
||||
return true;
|
||||
});
|
||||
const dropped = SPECIALISTS.filter(n => codeLike && !BATCH_SCOPED.includes(n) && !base.includes(n) && !extra.includes(n));
|
||||
if (dropped.length) skipped.push(`${dropped.join(', ')} (lean ensemble: read only when forced)`);
|
||||
return [...base, ...extra];
|
||||
}
|
||||
|
||||
/** A path as one git exclude pathspec; a glob character or a leading colon makes it literal. */
|
||||
const excludeSpec = (p: string) => `:(exclude${/[*?[\]\\]/.test(p) || p.startsWith(':') ? ',literal' : ''})${p}`;
|
||||
const shellQuote = (s: string) => `'${s.replace(/'/g, "'\\''")}'`;
|
||||
@@ -355,15 +380,13 @@ export function planReview(i: PlanInput): ReviewPlan {
|
||||
const dispatches: Dispatch[] = [];
|
||||
const add = (role: Role, name: string, effort: Effort, exclude: string[] = [], extra: Partial<Dispatch> = {}) => dispatches.push({ role, name, effort, exclude, ...extra });
|
||||
const docsAndOutputs = i.paths.filter(p => pathClass(i, p) === 'docs' || isOutput(i, p)).map(excludeSpec);
|
||||
const tests = i.paths.filter(p => pathClass(i, p) === 'tests' && !isOutput(i, p)).map(excludeSpec);
|
||||
const numbersChanged = i.numbers.publishes && i.reviewedNumbers !== undefined && i.reviewedNumbers !== i.numbers.fingerprint;
|
||||
const firstVoices = h.fullPasses === 0 || !i.adversarialRan;
|
||||
const specialist: Effort = i.tiers === false ? 'host' : ROLE_EFFORT.specialist;
|
||||
if (next.step === 'full' && stepDepth === 'light') {
|
||||
const forced = forcedBy(i);
|
||||
if (runtimeLines > 0) {
|
||||
const scoped = SPECIALISTS.filter(n => !LIGHT_FIXED.includes(n) && (forced(n) || (scopeWants[n]?.(i.scope ?? {}) && !(i.gated ?? []).includes(n))));
|
||||
add('specialist', 'batched', ROLE_EFFORT['red-team'], docsAndOutputs, { checklists: [...LIGHT_FIXED, ...scoped, 'red-team'] });
|
||||
add('specialist', 'batched', ROLE_EFFORT['red-team'], docsAndOutputs, { checklists: [...batchChecklists('code', i, skipped), 'red-team'] });
|
||||
skipped.push('red-team (light depth: the batched specialist carries the Red Team prompt)');
|
||||
if (firstVoices) add('adversarial-native', 'adversarial', 'full', [], { reason: LIGHT_ADVERSARIAL_REASON });
|
||||
} else {
|
||||
@@ -378,7 +401,8 @@ export function planReview(i: PlanInput): ReviewPlan {
|
||||
const dcls: DiffClass = full ? 'gate' : cls;
|
||||
const raised = !full && runtimeLines <= SMALL_DIFF_LINES;
|
||||
const lines = full ? i.diffLines : Math.max(runtimeLines, raised ? SMALL_DIFF_LINES : 0);
|
||||
for (const n of specialistsFor(dcls, { ...i, diffLines: lines }, skipped)) add('specialist', n, specialist, full ? [] : n === 'testing' ? docsAndOutputs : [...docsAndOutputs, ...tests]);
|
||||
const checklists = batchChecklists(dcls, i, skipped);
|
||||
if (checklists.length) add('specialist', 'batched', specialist, full ? [] : docsAndOutputs, { checklists });
|
||||
const redTeam = dcls === 'gate' || (dcls === 'code' && (lines > RED_TEAM_LINES || !!i.criticalFound || raised));
|
||||
if (redTeam) add('red-team', 'red-team', 'full');
|
||||
else skipped.push(`red-team (${dcls} class${dcls === 'code' ? `, ${lines} runtime lines, no critical` : ''})`);
|
||||
|
||||
@@ -23,8 +23,8 @@ Save its `REVIEW_PLAN:` line and fingerprint, `REVIEW_DEPTH:` (depth,
|
||||
(specialists, Red Team, Step 11's voices), each at its `effort=` on the same model
|
||||
family (`full` is the lane's reasoning, `medium` reasoning medium, `light` low; a
|
||||
host that cannot set it runs its default) and reading its `DIFF_CMD:`, in place of 9.1's and 9.2's own selection rules;
|
||||
`specialist batched` (light depth) is one subagent reading every checklist its
|
||||
`checklists=` names, Red Team included, tagging each finding with its checklist.
|
||||
`specialist batched` is one subagent reading every checklist its
|
||||
`checklists=` names (light adds Red Team), tagging each finding with its checklist.
|
||||
A `SKIP:` reviewer logs `{"dispatched":false,"reason":"scope"}`. Audits run at
|
||||
`AUDIT_EFFORT:`. The core checklist, gate scan and QA always run. Item 6's row adds
|
||||
`"dispatches":[{"role":R,"prompt_bytes":N,"diff_bytes":N}]`, `"depth"`,
|
||||
|
||||
@@ -21,8 +21,8 @@ Save its `REVIEW_PLAN:` line and fingerprint, `REVIEW_DEPTH:` (depth,
|
||||
(specialists, Red Team, Step 11's voices), each at its `effort=` on the same model
|
||||
family (`full` is the lane's reasoning, `medium` reasoning medium, `light` low; a
|
||||
host that cannot set it runs its default) and reading its `DIFF_CMD:`, in place of 9.1's and 9.2's own selection rules;
|
||||
`specialist batched` (light depth) is one subagent reading every checklist its
|
||||
`checklists=` names, Red Team included, tagging each finding with its checklist.
|
||||
`specialist batched` is one subagent reading every checklist its
|
||||
`checklists=` names (light adds Red Team), tagging each finding with its checklist.
|
||||
A `SKIP:` reviewer logs `{"dispatched":false,"reason":"scope"}`. Audits run at
|
||||
`AUDIT_EFFORT:`. The core checklist, gate scan and QA always run. Item 6's row adds
|
||||
`"dispatches":[{"role":R,"prompt_bytes":N,"diff_bytes":N}]`, `"depth"`,
|
||||
|
||||
+54
-16
@@ -10,6 +10,7 @@ const ROOT = path.join(import.meta.dir, '..');
|
||||
const none = scanNumbers({});
|
||||
const base = (o: Partial<PlanInput>): PlanInput => ({ paths: ['src/app.ts'], scope: { SCOPE_BACKEND: 'true', SCOPE_STATE_MACHINE: 'false' }, gateTagged: 0, diffLines: 120, history: { fullPasses: 0, last: 'none' }, numbers: none, ...o });
|
||||
const roles = (p: ReturnType<typeof planReview>) => p.dispatches.map(d => `${d.role}:${d.name}:${d.effort}`);
|
||||
const batch = (p: ReturnType<typeof planReview>) => p.dispatches.find(d => d.role === 'specialist' && d.name === 'batched')?.checklists;
|
||||
|
||||
describe('classifyPath: agent-read markdown is code', () => {
|
||||
test.each([
|
||||
@@ -84,12 +85,14 @@ describe('planReview', () => {
|
||||
});
|
||||
test('code class pass 1: specialists at medium, the native adversarial at full, the outside voice at its own', () => {
|
||||
const p = planReview(base({}));
|
||||
expect(roles(p)).toEqual(['specialist:testing:medium', 'specialist:maintainability:medium', 'specialist:security:medium', 'specialist:performance:medium', 'specialist:simplification:medium', 'adversarial-native:adversarial:full', 'outside-voice:outside-voice:host']);
|
||||
expect(roles(p)).toEqual(['specialist:batched:medium', 'adversarial-native:adversarial:full', 'outside-voice:outside-voice:host']);
|
||||
expect(batch(p)).toEqual(['testing', 'security']);
|
||||
expect(p.skipped).toContain('maintainability, performance, simplification (lean ensemble: read only when forced)');
|
||||
});
|
||||
test('gate class forces red team and ignores hit-rate gating', () => {
|
||||
const p = planReview(base({ gateTagged: 1, gated: ['security'] }));
|
||||
const p = planReview(base({ gateTagged: 1, gated: ['security', 'state-machines'], scope: { SCOPE_BACKEND: 'true' } }));
|
||||
expect(roles(p)).toContain('red-team:red-team:full');
|
||||
expect(roles(p)).toContain('specialist:security:medium');
|
||||
expect(batch(p)).toEqual(['testing', 'security', 'state-machines']);
|
||||
});
|
||||
test('code class honours hit-rate gating and small-diff skip', () => {
|
||||
expect(roles(planReview(base({ gated: ['simplification'] })))).not.toContain('specialist:simplification:medium');
|
||||
@@ -155,7 +158,9 @@ describe('GSTA-131 pass-1 review repairs', () => {
|
||||
expect(scanNumbers({ 'test/fixtures/bench.json': ['{ "ratio": 0.74 }'] }).publishes).toBe(true);
|
||||
});
|
||||
test('--all-specialists forces every specialist; a code diff past 200 lines or with a CRITICAL adds the Red Team', () => {
|
||||
expect(planReview(base({ force: 'all' })).dispatches.filter(d => d.role === 'specialist').length).toBe(9);
|
||||
const all = planReview(base({ force: 'all' }));
|
||||
expect(all.dispatches.filter(d => d.role === 'specialist').length).toBe(1);
|
||||
expect([...batch(all)!].sort()).toEqual(['api-contract', 'data-migration', 'design', 'maintainability', 'performance', 'security', 'simplification', 'state-machines', 'testing']);
|
||||
const has = (o: Partial<PlanInput>) => roles(planReview(base(o))).some(r => r.startsWith('red-team'));
|
||||
expect(has({ diffLines: 120 })).toBe(false);
|
||||
expect(has({ diffLines: 201 })).toBe(true);
|
||||
@@ -172,12 +177,14 @@ describe('GSTA-131 coverage: Red Team and specialist arms', () => {
|
||||
expect(roles(planReview(base({ diffLines: 20, gateTagged: 1 })))).toContain('red-team:red-team');
|
||||
});
|
||||
test('scope arms select their specialist, state machines default on when scope is unknown, and --force all reaches docs class', () => {
|
||||
const picked = (scope: Record<string, string>) => roles(planReview(base({ scope: { SCOPE_STATE_MACHINE: 'false', ...scope } })));
|
||||
expect(picked({ SCOPE_MIGRATIONS: 'true' })).toContain('specialist:data-migration');
|
||||
expect(picked({ SCOPE_API: 'true' })).toContain('specialist:api-contract');
|
||||
expect(picked({ SCOPE_FRONTEND: 'true' })).toContain('specialist:design');
|
||||
expect(roles(planReview(base({ scope: {} })))).toContain('specialist:state-machines');
|
||||
expect(roles(planReview(base({ scope: { SCOPE_STATE_MACHINE: 'false' } })))).not.toContain('specialist:state-machines');
|
||||
const picked = (scope: Record<string, string>) => batch(planReview(base({ scope: { SCOPE_STATE_MACHINE: 'false', ...scope } })));
|
||||
expect(picked({ SCOPE_MIGRATIONS: 'true' })).toContain('data-migration');
|
||||
expect(picked({ SCOPE_API: 'true' })).toContain('api-contract');
|
||||
expect(picked({ SCOPE_FRONTEND: 'true' })).toEqual(['testing', 'security', 'design']);
|
||||
expect(batch(planReview(base({ scope: {} })))).toContain('state-machines');
|
||||
expect(batch(planReview(base({ scope: { SCOPE_STATE_MACHINE: 'false' } })))).not.toContain('state-machines');
|
||||
expect(batch(planReview(base({ force: ['maintainability'] })))).toEqual(['testing', 'security', 'maintainability']);
|
||||
expect(batch(planReview(base({ gated: ['design'], scope: { SCOPE_FRONTEND: 'true', SCOPE_STATE_MACHINE: 'false' } })))).toEqual(['testing', 'security']);
|
||||
const docsAll = planReview(base({ paths: ['docs/a.md'], force: 'all' }));
|
||||
expect(docsAll.class).toBe('docs');
|
||||
expect(docsAll.dispatches.filter(d => d.role === 'specialist').length).toBeGreaterThan(5);
|
||||
@@ -198,7 +205,7 @@ describe('review depth: the matrix', () => {
|
||||
const p = planReview(mixed({ perPath: per({ 'src/app.ts': 48, 'test/a.test.ts': 190, 'docs/a.md': 9 }), diffLines: 247 }));
|
||||
expect([p.depth, p.runtimeLines]).toEqual(['light', 48]);
|
||||
expect(p.dispatches).toEqual([
|
||||
{ role: 'specialist', name: 'batched', effort: 'full', exclude: [':(exclude)docs/a.md'], checklists: ['testing', 'maintainability', 'security', 'performance', 'red-team'] },
|
||||
{ role: 'specialist', name: 'batched', effort: 'full', exclude: [':(exclude)docs/a.md'], checklists: ['testing', 'security', 'red-team'] },
|
||||
{ role: 'adversarial-native', name: 'adversarial', effort: 'full', exclude: [], reason: 'backtest inconclusive (GSTA-147 D5)' },
|
||||
]);
|
||||
expect(p.skipped.join('; ')).toContain('red-team (light depth');
|
||||
@@ -207,10 +214,10 @@ describe('review depth: the matrix', () => {
|
||||
test('runtime 51 is standard: thresholds read runtime lines, reviewers read their own hunks', () => {
|
||||
const p = planReview(mixed({ perPath: per({ 'src/app.ts': 51, 'test/a.test.ts': 400, 'README.md': 3 }), diffLines: 454 }));
|
||||
expect([p.depth, p.runtimeLines]).toEqual(['standard', 51]);
|
||||
expect(roles(p)).toEqual(['specialist:testing:medium', 'specialist:maintainability:medium', 'specialist:performance:medium', 'adversarial-native:adversarial:full', 'outside-voice:outside-voice:host']);
|
||||
expect(roles(p)).toEqual(['specialist:batched:medium', 'adversarial-native:adversarial:full', 'outside-voice:outside-voice:host']);
|
||||
expect(batch(p)).toEqual(['testing', 'security']);
|
||||
const ex = Object.fromEntries(p.dispatches.map(d => [d.name, d.exclude]));
|
||||
expect(ex.testing).toEqual([':(exclude)README.md']);
|
||||
expect(ex.maintainability).toEqual([':(exclude)README.md', ':(exclude)test/a.test.ts']);
|
||||
expect(ex.batched).toEqual([':(exclude)README.md']);
|
||||
expect([ex.adversarial, ex['outside-voice']]).toEqual([[], []]);
|
||||
});
|
||||
test.each([
|
||||
@@ -257,7 +264,7 @@ describe('review depth: the matrix', () => {
|
||||
test('a fixed blocker in light raises the next full step to the standard set, Red Team included', () => {
|
||||
const p = planReview(mixed({ perPath: per({ 'src/app.ts': 30 }), history: { fullPasses: 1, last: 'full', fixedBlocking: true } }));
|
||||
expect([p.depth, p.stepDepth]).toEqual(['light', 'standard']);
|
||||
expect(roles(p)).toEqual(['specialist:testing:medium', 'specialist:maintainability:medium', 'specialist:performance:medium', 'red-team:red-team:full', 'adversarial-native:adversarial:full', 'outside-voice:outside-voice:host']);
|
||||
expect(roles(p)).toEqual(['specialist:batched:medium', 'red-team:red-team:full', 'adversarial-native:adversarial:full', 'outside-voice:outside-voice:host']);
|
||||
});
|
||||
test('--depth-floor raises and never lowers', () => {
|
||||
expect(depthFor({ cls: 'code', runtimeLines: 10, errors: [], floor: 'full' })).toBe('full');
|
||||
@@ -311,7 +318,7 @@ describe('D6: the scoped second pass after a blocking fix (GSTA-152 PR B)', () =
|
||||
test('the #19 shape plans one fix-check, the Red Team and the native adversarial pass, not the specialist army', () => {
|
||||
const before = planReview(n19({ history: { fullPasses: 1, last: 'full', fixedBlocking: true } }));
|
||||
expect(before.next.step).toBe('full');
|
||||
expect(before.dispatches.length).toBeGreaterThanOrEqual(6);
|
||||
expect(roles(before)).toEqual(['specialist:batched:medium', 'red-team:red-team:full']);
|
||||
const p = planReview(n19());
|
||||
expect(p.next.step).toBe('scoped');
|
||||
expect(p.next.reason).toContain('24 runtime lines');
|
||||
@@ -457,3 +464,34 @@ describe('GSTA-167 part 4a: data files no code names are docs (classifyDiff only
|
||||
expect(classifyDiff({ paths: ['src/app.ts', 'wiki/old.json'], deleted: ['wiki/old.json'], consumed: [], gateTagged: 0 }).class).toBe('gate');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lean ensemble: one batched specialist at standard and full depth (GSTA-167 part 2)', () => {
|
||||
const fixture = (name: string): PlanInput => {
|
||||
const x = JSON.parse(fs.readFileSync(path.join(ROOT, 'test/fixtures/review-depth', `${name}.json`), 'utf8'));
|
||||
return { paths: x.paths, deleted: x.deleted, stampOnly: x.stamp_only ?? [], scope: x.scope, gateTagged: x.gate_tagged, diffLines: x.diff_lines, numbers: x.numbers, perPath: x.per_path, registry: x.registry_outputs ? { sha: null, outputs: x.registry_outputs } : undefined, history: { fullPasses: 0, last: 'none' } };
|
||||
};
|
||||
const specialists = (p: ReturnType<typeof planReview>) => p.dispatches.filter(d => d.role === 'specialist');
|
||||
// Value: protects=the lean ensemble's reader set (testing and security always, the four scoped checklists when scope asks,
|
||||
// maintainability, performance and simplification only when forced) on captured diffs; fails_when=a plan dispatches more than
|
||||
// one specialist reader at standard or full, drops testing or security, or the Red Team or voices change; seam=planReview
|
||||
test('the captured gate fixture plans one batched reader, the Red Team and both voices', () => {
|
||||
const p = planReview(fixture('code'));
|
||||
expect([p.class, p.depth]).toEqual(['gate', 'full']);
|
||||
expect(roles(p)).toEqual(['specialist:batched:medium', 'red-team:red-team:full', 'adversarial-native:adversarial:full', 'outside-voice:outside-voice:host']);
|
||||
expect(batch(p)).toEqual(['testing', 'security', 'state-machines']);
|
||||
expect(batch(planReview({ ...fixture('code'), scope: { ...fixture('code').scope, SCOPE_MIGRATIONS: 'true', SCOPE_API: 'true', SCOPE_FRONTEND: 'true', SCOPE_STATE_MACHINE: 'false' } }))).toEqual(['testing', 'security', 'data-migration', 'api-contract', 'design']);
|
||||
});
|
||||
test('the captured docs fixture dispatches no reader at standard and one batched reader when floored to full', () => {
|
||||
expect(specialists(planReview({ ...fixture('docs-only'), floor: 'standard' }))).toEqual([]);
|
||||
const full = planReview({ ...fixture('docs-only'), floor: 'full' });
|
||||
expect(specialists(full).map(d => [d.name, d.effort, d.checklists])).toEqual([['batched', 'medium', ['testing', 'security', 'state-machines']]]);
|
||||
});
|
||||
test('a standard code plan reads one batched reader on its own hunks, and a forced checklist joins it', () => {
|
||||
const p = planReview(base({ diffLines: 400, scope: { SCOPE_BACKEND: 'true', SCOPE_STATE_MACHINE: 'false' } }));
|
||||
expect(specialists(p).map(d => [d.name, d.checklists])).toEqual([['batched', ['testing', 'security']]]);
|
||||
expect(roles(p)).toContain('red-team:red-team:full');
|
||||
expect(batch(planReview(base({ force: ['performance', 'simplification'] })))).toEqual(['testing', 'security', 'performance', 'simplification']);
|
||||
expect(specialists(planReview(base({ tiers: false })))[0]!.effort).toBe('host');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,10 +54,11 @@ const NO_LOSS_REPLAYS = {
|
||||
'#3638': 'no-loss round 3: main restored the pre-#3621 reviewers (v1.91.331.0)',
|
||||
'PR-a': "GSTA-167 variant G Arm 4: structural plan identity at the 14 cohort heads and the free exact re-score of saved C, D and A' findings (parts 4a and 4b)",
|
||||
'G1': "GSTA-167 variant G Arm G1: live no-loss replay of both prose voices with part 1's prompts, 2 passes, normalizer v2, paired rule against pooled C' (parts 1 and 3; specialists at medium as in C and C2)",
|
||||
'L': "GSTA-167 variant G Arm L: live no-loss replay of one batched reader (testing and security plus scoped checklists) and the Red Team on its findings, 2 passes, scored L∘G1 against G1 on the paired rule (part 2)",
|
||||
} as const;
|
||||
type Replay = keyof typeof NO_LOSS_REPLAYS;
|
||||
const REVIEWER_BRIEFS: Record<string, { sha: string; replay: Replay }> = {
|
||||
'ship/sections/review-army.md': { sha: '7ee6cfac4432a83a8a91d77c6adb3574c23a762c7f8b4fb379d5b8a8d7398730', replay: 'G1' },
|
||||
'ship/sections/review-army.md': { sha: 'b7ca291a1cc63750bfb9710a4c12fd971523b9598da2a32ef80fb258eb0eceab', replay: 'L' },
|
||||
'ship/sections/adversarial.md': { sha: 'c48aa7717c7438941fa23418367e19a62e07edda7445d0d5a55924e172dd5d33', replay: 'G1' },
|
||||
'review/sections/review-army.md': { sha: '78329b64640260cd8831ca81a9189f3f8dcade9c406431631d3f2cd798cd3967', replay: '#3638' },
|
||||
'review/sections/adversarial.md': { sha: 'e6962fa25b78f362654f2954f79683e524d2ebd452d1494f8a2b099888e8e255', replay: 'G1' },
|
||||
@@ -74,7 +75,7 @@ const REVIEWER_BRIEFS: Record<string, { sha: string; replay: Replay }> = {
|
||||
'review/specialists/state-machines.md': { sha: '597215e4438d85d9cc71b48097c61eef7cba4f4b09dc14c92b4b94893b3d3ea2', replay: '#3638' },
|
||||
'review/specialists/testing.md': { sha: 'd6cc2bef7be562387d0c0a22fefe646dff9c295f1d34d8453a6cb496584150e4', replay: '#3638' },
|
||||
'scripts/resolvers/fix-check.ts': { sha: '712bf5b0c439e96bac32f19cbe25394dabb4c94aa66600100bfde9b100e15a08', replay: '#3638' },
|
||||
'lib/review-depth.ts': { sha: 'f5599365da06fe6d9829701c3d7e7f716703d87b9b577835e2dc5c938a8f6e6b', replay: 'G1' },
|
||||
'lib/review-depth.ts': { sha: 'd4715a5f534d1470c63da879a3d66bb090a9b4929065d1f06c30acd97ef77bb0', replay: 'L' },
|
||||
};
|
||||
|
||||
describe('reviewer briefs stay at their no-loss-tested bytes (GSTA-167)', () => {
|
||||
|
||||
@@ -41,12 +41,12 @@ describe('gstack-review-plan', () => {
|
||||
expect(plan.dispatches.filter((d: any) => d.role === 'specialist').every((d: any) => d.effort === 'medium')).toBe(true);
|
||||
expect(plan.dispatches.find((d: any) => d.role === 'adversarial-native').effort).toBe('full');
|
||||
expect(plan.audit_effort).toBe('light');
|
||||
expect(run(r, []).stdout).toContain('DISPATCH: specialist testing effort=medium');
|
||||
expect(run(r, []).stdout).toContain('DISPATCH: specialist batched effort=medium checklists=testing,security,state-machines');
|
||||
const host = JSON.parse(run(r, ['--json', '--no-tiers']).stdout);
|
||||
expect(host.audit_effort).toBe('host');
|
||||
expect(host.dispatches.filter((d: any) => d.role === 'specialist').every((d: any) => d.effort === 'host')).toBe(true);
|
||||
expect(host.dispatches.find((d: any) => d.role === 'adversarial-native').effort).toBe('full');
|
||||
expect(run(r, ['--no-tiers']).stdout).toContain('DISPATCH: specialist testing effort=host');
|
||||
expect(run(r, ['--no-tiers']).stdout).toContain('DISPATCH: specialist batched effort=host checklists=testing,security,state-machines');
|
||||
});
|
||||
test('a below-floor fix after pass 1 plans one fix-check', () => {
|
||||
const r = run(repo({ 'src/a.ts': 'export const a = 1;\n' }), ['--pass', '1', '--last', 'full', '--fixed-below']);
|
||||
@@ -134,25 +134,25 @@ describe('gstack-review-plan: review depth', () => {
|
||||
const head = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: r.dir, encoding: 'utf8', timeout: 10_000 }).stdout.trim();
|
||||
expect(out).toMatch(/^REVIEW_DEPTH: light runtime_lines=10 rules=[0-9a-f]{16} reasons=runtime=10<=50 codes=none$/m);
|
||||
expect(out).toContain(`GATE_SCAN: tagged=0 head=${head}`);
|
||||
expect(out).toContain('DISPATCH: specialist batched effort=full checklists=testing,maintainability,security,performance,state-machines,red-team');
|
||||
expect(out).toContain('DISPATCH: specialist batched effort=full checklists=testing,security,state-machines,red-team');
|
||||
expect(out).toContain('DISPATCH: adversarial-native adversarial effort=full reason=backtest inconclusive (GSTA-147 D5)');
|
||||
});
|
||||
test('DIFF_CMD is shell-quoted per dispatch and excludes what the reviewer does not read; DIFF_EXCLUDE: none when empty', () => {
|
||||
const r = repo({ 'src/a.ts': code(80), 'docs/my notes*.md': '# n\n', 'test/a.test.ts': code(3) });
|
||||
const out = run(r, []).stdout;
|
||||
expect(out).toContain("DIFF_CMD: testing git diff <diff-base> -- . ':(exclude,literal)docs/my notes*.md'");
|
||||
expect(out).toContain("DIFF_CMD: maintainability git diff <diff-base> -- . ':(exclude,literal)docs/my notes*.md' ':(exclude)test/a.test.ts'");
|
||||
expect(out).toContain("DIFF_CMD: batched git diff <diff-base> -- . ':(exclude,literal)docs/my notes*.md'\n");
|
||||
expect(out).toContain('SKIP: maintainability, performance, simplification (lean ensemble: read only when forced)');
|
||||
expect(out).toContain('DIFF_CMD: adversarial git diff <diff-base>\nDIFF_EXCLUDE: adversarial none');
|
||||
const cmd = /^DIFF_CMD: maintainability (.*)$/m.exec(out)![1]!.replace('<diff-base>', 'main');
|
||||
const cmd = /^DIFF_CMD: batched (.*)$/m.exec(out)![1]!.replace('<diff-base>', 'main');
|
||||
const diff = spawnSync('bash', ['-c', cmd.replace('git diff main', 'git diff --name-only main')], { cwd: r.dir, encoding: 'utf8' });
|
||||
expect(diff.stdout.trim().split('\n')).toEqual(['src/a.ts']);
|
||||
expect(diff.stdout.trim().split('\n')).toEqual(['src/a.ts', 'test/a.test.ts']);
|
||||
});
|
||||
test('--json carries depth, runtime_lines, rules, errors, gate_scan and every dispatch\'s exclude array', () => {
|
||||
const r = repo({ 'src/a.ts': code(80), 'README.md': '# y\n' });
|
||||
const plan = JSON.parse(run(r, ['--json']).stdout);
|
||||
expect(plan).toMatchObject({ depth: 'standard', step_depth: 'standard', runtime_lines: 80, errors: [], codes: [], gate_scan: { tagged: 0 } });
|
||||
expect(plan.rules).toMatch(/^[0-9a-f]{16}$/);
|
||||
expect(plan.dispatches.find((d: any) => d.name === 'maintainability').exclude).toEqual([':(exclude)README.md']);
|
||||
expect(plan.dispatches.find((d: any) => d.name === 'batched').exclude).toEqual([':(exclude)README.md']);
|
||||
expect(plan.dispatches.find((d: any) => d.role === 'outside-voice').exclude).toEqual([]);
|
||||
});
|
||||
test('--depth-floor full raises a light diff, never lowers a full one, and an invalid floor is a usage error', () => {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"bytes": -11,
|
||||
"why": "GSTA-167 variant G part 2: the batched specialist line covers every depth (light adds Red Team)"
|
||||
}
|
||||
Reference in new issue
Block a user