mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 22:48:57 +02:00
- The DOM-dump Aside block was the only double-quoted `aside repl` script in
the tree (to splice the function text), which put the agent-filled <url>
inside a double-quoted bash string: a same-origin href carrying $(...) would
run in the reviewer's shell when Phase 3 opened that page. The script is
single-quoted like every other Aside script and the function text enters
through a closed-quote segment ('"$_DUMP"'); the fallback line is
`$B js '('"$_DUMP"')()'`. A free test pins that no rendered Aside script
opens with a double quote.
- The persist block capped dumps at 10 MiB but ran gstack-redact with its
1 MiB default, so every real page between the two was deleted as
DOM_DUMP_REDACTION_BLOCKED; the gate passes --max-bytes at the dump cap and
blocks on any exit other than clean (0) or MEDIUM (2), so a redaction tool
that fails to run can no longer fall through to "persist".
- Dump hygiene removes <template> and <noscript> subtrees (invisible to the
attribute walk), inline on* handlers, and the cross-origin <link> nodes
already named in the note, so the file handed to the engine references no
remote stylesheet.
- Doctrine: the Codex design-voice prompts said "2-3 intentional motions"
against the one-authored-moment rule; the overused-display heading scoped
its ban to Persuade/Experience while the catalog and hard rules ban it
everywhere; design-consultation's Important Rule 4 still said "as primary";
design-html's blacklist header is now "Never include by default" with the
mockup/DESIGN.md/user-ask override the catalog grants; the slop gate honors
Decisions Log and Do's and Don'ts blessings like /review does; the landing
"poster" line says poster in stance, not type size; the design binary's
variant dials no longer flip light/dark for variety; gstack's DESIGN.md
rows name data labels (UI labels stay the DM Sans token) and call the
skill-bar fill and hovers functional transitions.
- design-review names how the base branch is found (gh pr view, then the
repo default; never main) for the source-mode scan and the diff-aware mode.
- frontend-scope matches the config globs at the repo root only, like the
bash arm; the parity test carries nested samples.
- Cleanups: renderCatalog's stale style option, an unused import, the
identity-map bannedFontNames, the checklist header's "same entries" claim,
the catalog header's consumer list, the orphaned main() docstring, the
plan doc's IIFE bullet. design-html's skeleton ceiling is re-measured
(54,184) for the two doctrine sentences.
Tests: AUTO-FIX rendering from the catalog, the E2E slice markers checked in
the free suite, the hygiene cases for templates/noscript/handlers/remote
links, and the review E2E counting detector rows separately from the seven
checklist plants.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
91 lines
3.5 KiB
TypeScript
91 lines
3.5 KiB
TypeScript
/**
|
|
* lib/frontend-scope.ts mirrors the m_frontend arm of bin/gstack-diff-scope.
|
|
* Pure cases run everywhere; the parity case runs the bash script in a temp
|
|
* repo (POSIX only) so the two implementations cannot drift silently.
|
|
*/
|
|
import { describe, test, expect } from 'bun:test';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { spawnSync } from 'child_process';
|
|
import { isFrontendPath } from '../lib/frontend-scope';
|
|
|
|
const ROOT = path.join(import.meta.dir, '..');
|
|
const POSIX = process.platform !== 'win32';
|
|
|
|
const SAMPLES: Array<[string, boolean]> = [
|
|
['src/components/Button.tsx', true],
|
|
['src/Button.jsx', true],
|
|
['pages/index.vue', true],
|
|
['app/Widget.svelte', true],
|
|
['site/page.astro', true],
|
|
['styles/main.css', true],
|
|
['css/a.scss', true],
|
|
['x/y/theme.less', true],
|
|
['x/a.sass', true],
|
|
['x/a.pcss', true],
|
|
['app/views/users/show.html.erb', true],
|
|
['templates/a.haml', true],
|
|
['templates/a.slim', true],
|
|
['templates/a.hbs', true],
|
|
['views/a.ejs', true],
|
|
['public/index.html', true],
|
|
['tailwind.config.js', true],
|
|
['postcss.config.cjs', true],
|
|
['src/tailwind.config.js', false], // the bash glob is matched against the whole repo-relative path: root-level configs only
|
|
['packages/ui/postcss.config.cjs', false],
|
|
['app/assets/stylesheets/app.css', true],
|
|
['lib/util/components/helper.rb', true],
|
|
['lib/server.ts', false],
|
|
['src/api/route.js', false],
|
|
['README.md', false],
|
|
['package.json', false],
|
|
['test/foo.test.ts', false],
|
|
['components.md', false],
|
|
['public/Index.HTML', false], // bash globs are case-sensitive; the mirror must agree
|
|
['src/App.TSX', false],
|
|
];
|
|
|
|
describe('isFrontendPath', () => {
|
|
test.each(SAMPLES)('%s → %p', (p, expected) => {
|
|
expect(isFrontendPath(p)).toBe(expected);
|
|
});
|
|
|
|
test('normalizes leading ./ and backslashes', () => {
|
|
expect(isFrontendPath('./styles/a.css')).toBe(true);
|
|
expect(isFrontendPath('src\\components\\A.tsx')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe.skipIf(!POSIX)('parity with bin/gstack-diff-scope', () => {
|
|
test('SCOPE_FRONTEND agrees with isFrontendPath for every sample, one file per diff', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-scope-parity-'));
|
|
const git = (...a: string[]) => {
|
|
const r = spawnSync('git', a, { cwd: dir, encoding: 'utf-8', timeout: 30_000 });
|
|
if (r.status !== 0) throw new Error(r.stderr);
|
|
};
|
|
try {
|
|
git('init', '-q', '-b', 'main');
|
|
git('config', 'user.email', 't@example.com');
|
|
git('config', 'user.name', 't');
|
|
fs.writeFileSync(path.join(dir, 'base.txt'), 'x\n');
|
|
git('add', '-A'); git('commit', '-q', '-m', 'base');
|
|
const mismatches: string[] = [];
|
|
for (const [rel, expected] of SAMPLES) {
|
|
git('checkout', '-q', '-b', 'probe');
|
|
const full = path.join(dir, rel);
|
|
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
fs.writeFileSync(full, '/* x */\n');
|
|
git('add', '-A'); git('commit', '-q', '-m', rel);
|
|
const r = spawnSync('bash', [path.join(ROOT, 'bin', 'gstack-diff-scope'), 'main'], { cwd: dir, encoding: 'utf-8', timeout: 30_000 });
|
|
const bashSays = /SCOPE_FRONTEND=true/.test(r.stdout);
|
|
if (bashSays !== expected) mismatches.push(`${rel}: bash=${bashSays} ts=${expected}`);
|
|
git('checkout', '-q', 'main'); git('branch', '-q', '-D', 'probe');
|
|
}
|
|
expect(mismatches).toEqual([]);
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|