test: registries, budgets and suite reconciled for Aside-first with the $B fallback

Touchfiles + E2E tiers gain the Aside keys, coverage matrix and eval baselines updated, size budget re-baselined to parity-baseline-v1.80.0.0.json (the contract plus fallback ride in every browsing skill), parity ceilings ratcheted with measured values, LLM-judge prompts and the E2E fixtures speak Aside-first, browse-fallback.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sina
2026-09-05 16:48:38 -04:00
co-authored by Claude Fable 5.1
parent 993692cd54
commit 886217894f
21 changed files with 1281 additions and 166 deletions
+28 -24
View File
@@ -150,7 +150,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
},
behavioral: 'external',
externalTest: 'test/skill-e2e-ship-section-loading.test.ts',
maxSkeletonBytes: 77_650, // + v1.78 AUQ spawned-trigger objectivity (explicit declaration + interactive fence); measured 77_236
maxSkeletonBytes: 79_300, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 78_275
minUnionBytes: 181_000, // token-reduction Phases 1-2 (v1.69.x branch); measured union 201,464
mustContain: ['VERSION', 'CHANGELOG', 'review', 'merge', 'PR'],
// v1.58.5.0: pre-push-guard install (#2077) stacks on the shared first-run-guidance preamble.
@@ -181,7 +181,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
// v1.65 merge: provisional larger-of-both-waves budget; re-measured below.
// Fork port wave 2 (#703): the repo-doc-preference block in the design
// check grew every plan-review skeleton ~0.7KB. Measured values noted.
maxSkeletonBytes: 76_000, // + v1.78 AUQ objectivity + v1.79 foreground-dispatch sweep (merged); measured 75_586
maxSkeletonBytes: 79_000, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 77_657
minUnionBytes: 123_600, // token-reduction Phases 1-2 (v1.69.x branch): preamble bash -> bin/gstack-skill-start, onboarding -> gated emission; measured union 137,346
mustContain: ['SCOPE EXPANSION', 'SELECTIVE EXPANSION', 'HOLD SCOPE', 'SCOPE REDUCTION'],
// Default-on Codex outside-voice (codexPreflight block + CODEX_MODE branch
@@ -207,7 +207,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
// check grew every plan-review skeleton ~0.7KB. Measured values noted.
// #2499 project-scope MCP jq in the brain-sync block grew every tier-2+
// skeleton ~1.5KB (entry resolution emitted once per SKILL.md).
maxSkeletonBytes: 54_200, // + v1.78 AUQ spawned-trigger objectivity (explicit declaration + interactive fence); measured 53_773
maxSkeletonBytes: 56_500, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 55_457
minUnionBytes: 99_800, // token-reduction Phases 1-2 (v1.69.x branch); measured union 110,910
mustContain: ['Architecture', 'Code Quality', 'Test', 'Performance'],
// Cross-cutting preamble growth (v1.57.2.0 AUQ-failure prose fallback + the
@@ -264,7 +264,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
// check grew every plan-review skeleton ~0.7KB. Measured values noted.
// #2499 project-scope MCP jq in the brain-sync block grew every tier-2+
// skeleton ~1.5KB (entry resolution emitted once per SKILL.md).
maxSkeletonBytes: 65_900, // + v1.78 AUQ spawned-trigger objectivity (explicit declaration + interactive fence); measured 65_486
maxSkeletonBytes: 68_500, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 67_129
minUnionBytes: 99_700, // token-reduction Phases 1-2 (v1.69.x branch); measured union 110,833
mustContain: ['developer experience', 'Getting Started'],
// Default-on Codex outside-voice (codexPreflight block + CODEX_MODE branch
@@ -295,7 +295,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
// the #538 opt-out + D1 evidence directive — ratio 1.104 measured.
// #2499 project-scope MCP jq in the brain-sync block grew every tier-2+
// skeleton ~1.5KB (entry resolution emitted once per SKILL.md).
maxSkeletonBytes: 73_450, // + v1.78 AUQ objectivity + v1.79 foreground-dispatch sweep (merged); measured 73_040
maxSkeletonBytes: 76_800, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 75_804
minUnionBytes: 115_800, // Phase 4 wave 4; measured union 118,175
mustContain: ['design doc', 'problem statement'],
maxSizeRatio: 1.12,
@@ -347,7 +347,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
// v1.65 merge: provisional larger-of-both-waves budget; re-measured below.
// v1.64.1.0: shared-preamble prose from the two parallel v1.64 waves lands
// the skeleton at 69,022 B; +~1 KB headroom.
maxSkeletonBytes: 53_750, // + v1.78 AUQ objectivity + v1.79 foreground-dispatch sweep (merged); measured 53_342
maxSkeletonBytes: 66_500, // + v2.0 {{ASIDE_SETUP}}/{{BROWSE_FALLBACK}} for the research phase; measured 65_506
minUnionBytes: 65_000, // token-reduction Phases 1-2 (v1.69.x branch): preamble bash -> bin/gstack-skill-start, onboarding -> gated emission; measured union 72,252
mustContain: ['Typography', 'Color', 'Aesthetic Direction'],
// Cross-cutting preamble growth (v1.57.2.0 AUQ-failure prose fallback ~2KB +
@@ -387,7 +387,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
// +Conductor AUQ-default-prose rule + one-way/continuation safety in the
// always-loaded AskUserQuestion Format section.
// v1.2.0 activation lift: first-run-guidance section in the shared preamble.
maxSkeletonBytes: 58_800, // + v1.78 AUQ objectivity + v1.79 foreground-dispatch sweep (merged); measured 58_364
maxSkeletonBytes: 61_800, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 60_628
minUnionBytes: 64_200, // token-reduction Phases 1-2 (v1.69.x branch); measured union 71,379
mustContain: ['OWASP', 'STRIDE', 'daily', 'comprehensive', 'verif'],
// cso keeps its mode-dispatch + FP-filtering phases always-loaded, so the
@@ -424,7 +424,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
gateAfterStop: undefined, // operational multi-STOP skill, like ship
},
behavioral: 'plan',
maxSkeletonBytes: 59_150, // + v1.78 AUQ spawned-trigger objectivity (explicit declaration + interactive fence); measured 58_722
maxSkeletonBytes: 61_500, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 60_309
minUnionBytes: 89_000, // Phase 4 wave 1; measured union 93,357
mustContain: ['confidence', 'P1', 'P2', 'Review Army', 'adversarial'],
},
@@ -477,7 +477,8 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
gateAfterStop: undefined, // operational skill
},
behavioral: 'prompt',
maxSkeletonBytes: 62_450, // + v1.78 AUQ spawned-trigger objectivity (explicit declaration + interactive fence); measured 62_021
maxSkeletonBytes: 74_500, // + Aside browser contract for Step 7 canary ({{ASIDE_SETUP}}); measured 73_523
maxSizeRatio: 1.10, // + v1.80 Aside contract + gstack-browser fallback block; measured 1.077
minUnionBytes: 91_000, // Phase 4 wave 1; estimated union ~94.9KB
mustContain: ['readiness', 'merge', 'canary', 'revert', 'staging'],
},
@@ -541,7 +542,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
gateAfterStop: undefined,
},
behavioral: 'prompt',
maxSkeletonBytes: 57_100, // + v1.78 AUQ spawned-trigger objectivity (explicit declaration + interactive fence); measured 56_697
maxSkeletonBytes: 58_400, // + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 57_356
minUnionBytes: 64_500, // measured union 67,430
mustContain: ['HARD GATE', 'dedupe', 'quality gate', 'acceptance criteria', 'archive'],
},
@@ -581,11 +582,11 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
expectedSections: ['test-bootstrap.md', 'qa-patterns.md'],
requiredReads: ['qa-patterns.md'],
scenario:
'Walk /qa in SIMULATION — do not launch a browser, run any $B command, or execute bash; treat the working tree as clean, the tier as Quick, and the target app as http://localhost:3000 with a small feature-branch diff touching one page. Skip the test-framework bootstrap (assume CLAUDE.md documents the test command). Read each pointed section before doing its step, then produce the QA plan as the report: the mode you selected and why, the Phase 1-6 steps you would run, and a worked health-score computation from the rubric. Do NOT use AskUserQuestion.',
'Walk /qa in SIMULATION — do not launch a browser, run any aside command, or execute bash; treat the working tree as clean, the tier as Quick, and the target app as http://localhost:3000 with a small feature-branch diff touching one page. Skip the test-framework bootstrap (assume CLAUDE.md documents the test command). Read each pointed section before doing its step, then produce the QA plan as the report: the mode you selected and why, the Phase 1-6 steps you would run, and a worked health-score computation from the rubric. Do NOT use AskUserQuestion.',
staticInvariants: {
mustStayInSkeleton: [
'## Setup',
'## SETUP (run this check BEFORE any browse command)',
'## BROWSER SETUP (Aside',
'## Phases 1-6: QA Baseline',
'## Phase 7: Triage',
'## Phase 8: Fix Loop',
@@ -605,29 +606,32 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
gateAfterStop: undefined,
},
behavioral: 'prompt',
maxSkeletonBytes: 52_550, // + v1.78 AUQ spawned-trigger objectivity (explicit declaration + interactive fence); measured 52_150
maxSkeletonBytes: 63_500, // + v2.0 {{ASIDE_SETUP}}/{{BROWSE_FALLBACK}} (replaces the browse setup block); measured 61_253
maxSizeRatio: 1.08, // + v1.80 Aside contract + gstack-browser fallback block; measured 1.063
minUnionBytes: 69_500, // measured union 70,385
mustContain: ['bug', 'browse', 'fix', 'Health Score Rubric', 'regression'],
// 'aside repl' pins the Aside contract; '$B goto' pins the fallback block in the always-loaded skeleton.
mustContain: ['bug', 'aside repl', '$B goto', 'fix', 'Health Score Rubric', 'regression'],
},
browse: {
skill: 'browse',
expectedSections: ['command-list.md'],
requiredReads: ['command-list.md'],
scenario:
'QA a static page: before driving it, plan the full audit — enumerate which browse commands and snapshot flags you would use, including extraction/tab/dialog commands beyond the Most-Used table, reading the full command reference first. Do not launch the browser or run any $B command; produce the command plan as the report.',
'QA a static page: before driving it, plan the full audit — enumerate which browser steps you would run (the aside repl shapes, and the $B commands and snapshot flags the fallback would need), including extraction/tab/dialog commands beyond the basics, reading the full command reference first. Do not launch any browser or run any aside/$B command; produce the plan as the report.',
staticInvariants: {
mustStayInSkeleton: ['## SETUP', '## Core QA Patterns', '## CSS Inspector', '## Most-Used Commands'],
mustPrecedeStop: ['## SETUP'],
// Aside is the primary browser: the skeleton carries the Aside contract;
// the $B command reference + snapshot flags are the carved section.
mustStayInSkeleton: ['## BROWSER SETUP (Aside'],
mustPrecedeStop: ['## BROWSER SETUP (Aside'],
mustMoveToSection: ['## Full Command List', '## Snapshot Flags', '### Navigation'],
gateAfterStop: undefined,
},
behavioral: 'prompt',
maxSkeletonBytes: 27_500, // Phase 4 wave 3; measured 26,875
minUnionBytes: 39_500, // measured union 41,115
// 'BEGIN/END UNTRUSTED EXTERNAL' pins the untrusted-content warning; the full
// envelope phrase wraps across lines in the rendered blockquote, so the
// contiguous-substring check needs the single-line prefix form.
mustContain: ['BEGIN/END UNTRUSTED EXTERNAL', 'snapshot -i', '@e refs', 'deviceScaleFactor', 'handoff'],
maxSkeletonBytes: 32_000, // + v2.0 {{ASIDE_SETUP}}/{{BROWSE_FALLBACK}} skeleton + the STOP-Read pointer; measured 29_986
minUnionBytes: 36_000, // skeleton + command-list section (14,240); measured union ~38.5 KB
// 'BEGIN/END UNTRUSTED EXTERNAL' pins the untrusted-content warning in the
// command reference; 'aside repl' pins the Aside contract in the skeleton.
mustContain: ['BEGIN/END UNTRUSTED EXTERNAL', 'snapshot -i', '@e refs', 'aside repl'],
},
retro: {
skill: 'retro',
@@ -701,7 +705,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
gateAfterStop: undefined,
},
behavioral: 'prompt',
maxSkeletonBytes: 53_100, // + v1.78 AUQ objectivity + v1.79 foreground-dispatch sweep (merged); measured 52_685
maxSkeletonBytes: 54_000, // + v2.0 preamble growth (merged); measured 52_962
minUnionBytes: 53_200, // Phase 4 wave 4; measured union 54,290
mustContain: ["Don't make me think", "Users scan, they don't read", 'trunk test', '44px minimum'],
},
+22 -11
View File
@@ -17,7 +17,7 @@ import * as path from 'path';
/** CLI-only commands: valid $B invocations that are handled by the CLI, not the server */
const CLI_COMMANDS = new Set([
'status', 'pair-agent', 'tunnel',
'status', 'pair-agent', 'tunnel', '--help',
]);
export interface BrowseCommand {
@@ -42,6 +42,7 @@ export function extractBrowseCommands(skillPath: string): BrowseCommand[] {
const lines = content.split('\n');
const commands: BrowseCommand[] = [];
let inCodeBlock = false;
let inBashBlock = false;
for (let i = 0; i < lines.length; i++) {
@@ -49,20 +50,28 @@ export function extractBrowseCommands(skillPath: string): BrowseCommand[] {
// Detect code block boundaries
if (line.trimStart().startsWith('```')) {
if (inBashBlock) {
inBashBlock = false;
} else if (line.trimStart().startsWith('```bash')) {
inBashBlock = true;
}
// Non-bash code blocks (```json, ```, ```js, etc.) are skipped
inCodeBlock = !inCodeBlock;
inBashBlock = inCodeBlock && line.trimStart().startsWith('```bash');
continue;
}
if (!inBashBlock) continue;
let source: string;
if (!inCodeBlock) {
// Prose and table rows: the {{BROWSE_FALLBACK}} mapping table carries its
// `$B` shapes in backticks — validate them like code-block commands.
// `[flags]`-style placeholders are documentation, not arguments.
const spans = [...line.matchAll(/`(\$B\s+[^`]+)`/g)].map(m => m[1].replace(/\[[^\]]*\]/g, ''));
if (spans.length === 0) continue;
source = spans.join(' ');
} else if (inBashBlock) {
source = line;
} else {
continue; // Non-bash code blocks (```json, ```, ```js, etc.) are skipped
}
// Match lines with $B command invocations
// Match $B command invocations
// Handle multiple $B commands on one line (e.g., "$B click @e3 $B fill @e4 "value"")
const matches = line.matchAll(/\$B\s+(\S+)(?:\s+([^\$]*))?/g);
const matches = source.matchAll(/\$B\s+(\S+)(?:\s+([^\$]*))?/g);
for (const match of matches) {
const command = match[1];
let argsStr = (match[2] || '').trim();
@@ -144,7 +153,9 @@ export function validateSkill(skillPath: string): ValidationResult {
*/
export function extractRemoteSlugPatterns(rootDir: string, subdirs: string[]): Map<string, string[]> {
const results = new Map<string, string[]>();
const pattern = /^REMOTE_SLUG=\$\(.*\)$/;
// Accepts both the bare `REMOTE_SLUG=$(...)` form and the gstack-slug form
// (`eval "$(...gstack-slug)"; REMOTE_SLUG="${SLUG:-...}"`).
const pattern = /^(?:eval\s[^;]*;\s*)?REMOTE_SLUG=\S/;
for (const subdir of subdirs) {
const dir = path.join(rootDir, subdir);
+45 -18
View File
@@ -25,6 +25,14 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
'browse-basic': ['browse/src/**', 'browse/test/test-server.ts', 'test/skill-e2e-bws.test.ts'],
'browse-snapshot': ['browse/src/**', 'browse/test/test-server.ts', 'test/skill-e2e-bws.test.ts'],
// Aside-driven browsing skills — live E2E against the Aside AI browser, the
// primary browser (test/skill-e2e-aside.test.ts self-skips without a running Aside)
'aside-browse-basic': ['browse/**', 'scripts/resolvers/browse.ts', 'scripts/resolvers/aside.ts', 'browse/test/test-server.ts', 'browse/test/fixtures/basic.html', 'test/helpers/aside-available.ts', 'test/skill-e2e-aside.test.ts'],
'aside-browse-flow': ['browse/**', 'scripts/resolvers/browse.ts', 'scripts/resolvers/aside.ts', 'browse/test/test-server.ts', 'browse/test/fixtures/forms.html', 'test/helpers/aside-available.ts', 'test/skill-e2e-aside.test.ts'],
'aside-qa-quick': ['qa/**', 'scripts/resolvers/browse.ts', 'scripts/resolvers/aside.ts', 'browse/test/test-server.ts', 'browse/test/fixtures/basic.html', 'test/helpers/aside-available.ts', 'test/skill-e2e-aside.test.ts'],
'aside-scrape-json': ['scrape/**', 'scripts/resolvers/aside.ts', 'browse/test/test-server.ts', 'browse/test/fixtures/basic.html', 'test/helpers/aside-available.ts', 'test/skill-e2e-aside.test.ts'],
'aside-canary-quick': ['canary/**', 'scripts/resolvers/aside.ts', 'browse/test/test-server.ts', 'browse/test/fixtures/basic.html', 'test/helpers/aside-available.ts', 'test/skill-e2e-aside.test.ts'],
// Hermetic isolation canaries (hermetic-env.ts is also a GLOBAL touchfile;
// these entries exist so the canaries themselves stay tier-classified)
'hermetic-canary': ['test/helpers/hermetic-env.ts', 'test/helpers/session-runner.ts', 'test/skill-e2e-hermetic-canary.test.ts', 'lib/conductor-env-shim.ts'],
@@ -44,13 +52,14 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
'session-awareness': ['SKILL.md', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-bws.test.ts'],
'operational-learning': ['scripts/resolvers/preamble.ts', 'bin/gstack-learnings-log', 'test/skill-e2e-bws.test.ts'],
// QA (+ test-server dependency)
'qa-quick': ['qa/**', 'browse/src/**', 'browse/test/test-server.ts', 'test/skill-e2e-qa-workflow.test.ts'],
'qa-b6-static': ['qa/**', 'browse/src/**', 'browse/test/test-server.ts', 'test/helpers/llm-judge.ts', 'browse/test/fixtures/qa-eval.html', 'test/fixtures/qa-eval-ground-truth.json', 'test/skill-e2e-qa-bugs.test.ts'],
'qa-b7-spa': ['qa/**', 'browse/src/**', 'browse/test/test-server.ts', 'test/helpers/llm-judge.ts', 'browse/test/fixtures/qa-eval-spa.html', 'test/fixtures/qa-eval-spa-ground-truth.json', 'test/skill-e2e-qa-bugs.test.ts'],
'qa-b8-checkout': ['qa/**', 'browse/src/**', 'browse/test/test-server.ts', 'test/helpers/llm-judge.ts', 'browse/test/fixtures/qa-eval-checkout.html', 'test/fixtures/qa-eval-checkout-ground-truth.json', 'test/skill-e2e-qa-bugs.test.ts'],
'qa-only-no-fix': ['qa-only/**', 'qa/templates/**', 'test/skill-e2e-qa-workflow.test.ts'],
'qa-fix-loop': ['qa/**', 'browse/src/**', 'browse/test/test-server.ts', 'test/skill-e2e-qa-workflow.test.ts'],
// QA (+ test-server dependency). /qa drives Aside first (the resolver) and
// the browse binary as fallback (browse/src), so both are deps.
'qa-quick': ['qa/**', 'scripts/resolvers/browse.ts', 'scripts/resolvers/aside.ts', 'browse/src/**', 'browse/test/test-server.ts', 'test/skill-e2e-qa-workflow.test.ts'],
'qa-b6-static': ['qa/**', 'scripts/resolvers/aside.ts', 'browse/src/**', 'browse/test/test-server.ts', 'test/helpers/llm-judge.ts', 'browse/test/fixtures/qa-eval.html', 'test/fixtures/qa-eval-ground-truth.json', 'test/skill-e2e-qa-bugs.test.ts'],
'qa-b7-spa': ['qa/**', 'scripts/resolvers/aside.ts', 'browse/src/**', 'browse/test/test-server.ts', 'test/helpers/llm-judge.ts', 'browse/test/fixtures/qa-eval-spa.html', 'test/fixtures/qa-eval-spa-ground-truth.json', 'test/skill-e2e-qa-bugs.test.ts'],
'qa-b8-checkout': ['qa/**', 'scripts/resolvers/aside.ts', 'browse/src/**', 'browse/test/test-server.ts', 'test/helpers/llm-judge.ts', 'browse/test/fixtures/qa-eval-checkout.html', 'test/fixtures/qa-eval-checkout-ground-truth.json', 'test/skill-e2e-qa-bugs.test.ts'],
'qa-only-no-fix': ['qa-only/**', 'qa/templates/**', 'scripts/resolvers/aside.ts', 'browse/src/**', 'browse/test/test-server.ts', 'test/skill-e2e-qa-workflow.test.ts'],
'qa-fix-loop': ['qa/**', 'scripts/resolvers/aside.ts', 'browse/src/**', 'browse/test/test-server.ts', 'test/skill-e2e-qa-workflow.test.ts'],
'qa-bootstrap': ['qa/**', 'ship/**', 'test/skill-e2e-qa-workflow.test.ts'],
// Review
@@ -298,15 +307,19 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
// Design
'design-consultation-core': ['design-consultation/**', 'scripts/gen-skill-docs.ts', 'test/helpers/llm-judge.ts', 'test/skill-e2e-design.test.ts'],
'design-consultation-existing': ['design-consultation/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-design.test.ts'],
'design-consultation-research': ['design-consultation/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-design.test.ts'],
'design-consultation-research': ['design-consultation/**', 'scripts/resolvers/aside.ts', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-design.test.ts'],
'design-consultation-preview': ['design-consultation/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-design.test.ts'],
'plan-design-review-no-ui-scope': ['plan-design-review/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-design.test.ts'],
'design-review-fix': ['design-review/**', 'browse/src/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-design.test.ts'],
'design-review-fix': ['design-review/**', 'scripts/resolvers/aside.ts', 'scripts/resolvers/design.ts', 'browse/src/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-design.test.ts'],
// /diagram (diagram-render bundle consumers). Triplet = deterministic
// functional (gate); authoring quality = LLM-judged benchmark (periodic).
'diagram-triplet': ['diagram/**', 'lib/diagram-render/**', 'browse/src/write-commands.ts', 'browse/src/read-commands.ts', 'test/skill-e2e-diagram.test.ts'],
'diagram-authoring-quality': ['diagram/**', 'lib/diagram-render/**', 'test/helpers/llm-judge.ts', 'test/skill-e2e-diagram.test.ts'],
// Both render the triplet through gstack-render (lib/aside-render.ts +
// bin/gstack-render.ts): Aside when it is running, the browse daemon
// otherwise — so both engines are deps. Triplet = deterministic functional
// (gate); authoring quality = LLM-judged benchmark (periodic).
'diagram-triplet': ['diagram/**', 'lib/diagram-render/**', 'lib/aside-render.ts', 'bin/gstack-render.ts', 'test/helpers/aside-available.ts', 'browse/src/**', 'test/skill-e2e-diagram.test.ts'],
'diagram-authoring-quality': ['diagram/**', 'lib/diagram-render/**', 'lib/aside-render.ts', 'bin/gstack-render.ts', 'test/helpers/aside-available.ts', 'browse/src/**', 'test/helpers/llm-judge.ts', 'test/skill-e2e-diagram.test.ts'],
// gstack-upgrade
'gstack-upgrade-happy-path': ['gstack-upgrade/**', 'test/skill-e2e-workflow.test.ts'],
@@ -315,8 +328,8 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
'land-and-deploy-workflow': ['land-and-deploy/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-deploy.test.ts'],
'land-and-deploy-first-run': ['land-and-deploy/**', 'scripts/gen-skill-docs.ts', 'bin/gstack-slug', 'test/skill-e2e-deploy.test.ts'],
'land-and-deploy-review-gate': ['land-and-deploy/**', 'bin/gstack-review-read', 'test/skill-e2e-deploy.test.ts'],
'canary-workflow': ['canary/**', 'browse/src/**', 'test/skill-e2e-deploy.test.ts'],
'benchmark-workflow': ['benchmark/**', 'browse/src/**', 'test/skill-e2e-deploy.test.ts'],
'canary-workflow': ['canary/**', 'scripts/resolvers/aside.ts', 'browse/src/**', 'test/skill-e2e-deploy.test.ts'],
'benchmark-workflow': ['benchmark/**', 'scripts/resolvers/aside.ts', 'browse/src/**', 'test/skill-e2e-deploy.test.ts'],
'setup-deploy-workflow': ['setup-deploy/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-deploy.test.ts'],
@@ -483,6 +496,14 @@ export const E2E_TIERS: Record<string, 'gate' | 'periodic'> = {
'browse-basic': 'gate',
'browse-snapshot': 'gate',
// Aside-driven browsing — periodic (external app: the Aside browser; the
// file self-gates on 'periodic' and skips without a live Aside)
'aside-browse-basic': 'periodic',
'aside-browse-flow': 'periodic',
'aside-qa-quick': 'periodic',
'aside-scrape-json': 'periodic',
'aside-canary-quick': 'periodic',
// Hermetic isolation — gate (deterministic env/config assertions; if the
// clean room breaks, every other eval's signal is contaminated)
'hermetic-canary': 'gate',
@@ -718,7 +739,8 @@ export const E2E_TIERS: Record<string, 'gate' | 'periodic'> = {
'plan-design-review-no-ui-scope': 'gate',
'design-review-fix': 'periodic',
// /diagram — triplet is deterministic functional, judge is a quality benchmark
// /diagram — triplet is deterministic functional (gstack-render falls back
// to the browse daemon, so CI runs it); judge is a quality benchmark
'diagram-triplet': 'gate',
'diagram-authoring-quality': 'periodic',
@@ -740,9 +762,14 @@ export const E2E_TIERS: Record<string, 'gate' | 'periodic'> = {
// Multi-provider benchmark — periodic (requires external CLIs + auth, paid)
'benchmark-providers-live': 'periodic',
// Browser-skills Phase 2a — gate (D1/D3 contracts must not silently break)
'scrape-match-path': 'gate',
'scrape-prototype-path': 'gate',
// Browser-skills Phase 2a — skillify keys gate (D1/D3 contracts must not
// silently break). The two scrape keys are periodic: /scrape is Aside-first
// and its fallback no longer prescribes the `$B skill list` / `skill run`
// match + prototype flow the tests assert, so a pass rides on prompt
// compliance (non-deterministic). Flip back to gate when scrape's fallback
// carries the browser-skills flow again.
'scrape-match-path': 'periodic',
'scrape-prototype-path': 'periodic',
'skillify-happy-path': 'gate',
'skillify-provenance-refusal': 'gate',
'skillify-approval-reject': 'gate',
@@ -796,7 +823,7 @@ export const LLM_JUDGE_TOUCHFILES: Record<string, string[]> = {
'command reference table': ['browse/sections/**', 'SKILL.md', 'SKILL.md.tmpl', 'browse/src/commands.ts', 'test/skill-llm-eval.test.ts'],
'snapshot flags reference': ['browse/sections/**', 'SKILL.md', 'SKILL.md.tmpl', 'browse/src/snapshot.ts', 'test/skill-llm-eval.test.ts'],
'browse/SKILL.md reference': ['browse/sections/**', 'browse/SKILL.md', 'browse/SKILL.md.tmpl', 'browse/src/**', 'test/skill-llm-eval.test.ts'],
'setup block': ['SKILL.md', 'SKILL.md.tmpl', 'test/skill-llm-eval.test.ts'],
'setup block': ['browse/SKILL.md', 'browse/SKILL.md.tmpl', 'scripts/resolvers/aside.ts', 'scripts/resolvers/browse.ts', 'test/skill-llm-eval.test.ts'],
'regression vs baseline': ['browse/sections/**', 'SKILL.md', 'SKILL.md.tmpl', 'browse/src/commands.ts', 'test/fixtures/eval-baselines.json', 'test/skill-llm-eval.test.ts'],
'qa/SKILL.md workflow': ['qa/sections/**', 'qa/SKILL.md', 'qa/SKILL.md.tmpl', 'test/skill-llm-eval.test.ts'],
'qa/SKILL.md health rubric': ['qa/sections/**', 'qa/SKILL.md', 'qa/SKILL.md.tmpl', 'test/skill-llm-eval.test.ts'],