mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-07 05:56:41 +02:00
0926e4b994
Splits scripts/resolvers/preamble.ts (841 lines, 18 generator functions + composition root) into one file per generator under scripts/resolvers/preamble/. Root preamble.ts becomes a thin composition layer (~80 lines of imports + generatePreamble). Before: scripts/resolvers/preamble.ts 841 lines After: scripts/resolvers/preamble.ts 83 lines scripts/resolvers/preamble/generate-preamble-bash.ts 97 lines scripts/resolvers/preamble/generate-upgrade-check.ts 48 lines scripts/resolvers/preamble/generate-lake-intro.ts 16 lines scripts/resolvers/preamble/generate-telemetry-prompt.ts 37 lines scripts/resolvers/preamble/generate-proactive-prompt.ts 25 lines scripts/resolvers/preamble/generate-routing-injection.ts 49 lines scripts/resolvers/preamble/generate-vendoring-deprecation.ts 36 lines scripts/resolvers/preamble/generate-spawned-session-check.ts 11 lines scripts/resolvers/preamble/generate-ask-user-format.ts 16 lines scripts/resolvers/preamble/generate-completeness-section.ts 19 lines scripts/resolvers/preamble/generate-repo-mode-section.ts 12 lines scripts/resolvers/preamble/generate-test-failure-triage.ts 108 lines scripts/resolvers/preamble/generate-search-before-building.ts 14 lines scripts/resolvers/preamble/generate-completion-status.ts 161 lines scripts/resolvers/preamble/generate-voice-directive.ts 60 lines scripts/resolvers/preamble/generate-context-recovery.ts 51 lines scripts/resolvers/preamble/generate-continuous-checkpoint.ts 48 lines scripts/resolvers/preamble/generate-context-health.ts 31 lines Byte-identity verification (the real gate per Codex correction): - Before refactor: snapshotted 135 generated SKILL.md files via `find -name SKILL.md -type f | grep -v /gstack/` across all hosts. - After refactor: regenerated with `bun run gen:skill-docs --host all` and re-snapshotted. - `diff -r baseline after` returned zero differences and exit 0. The `--host all --dry-run` gate passes too. No template or host behavior changes — purely a code-organization refactor. Test fix: audit-compliance.test.ts's telemetry check previously grepped preamble.ts directly for `_TEL != "off"`. After the refactor that logic lives in preamble/generate-preamble-bash.ts. Test now concatenates all preamble submodule sources before asserting — tracks the semantic contract, not the file layout. Doing the minimum rewrite preserves the test's intent (conditional telemetry) without coupling it to file boundaries. Why now: we were in-session with full context. Codex had downgraded this from mandatory to optional, but the preamble had grown to 841 lines and was getting harder to navigate. User asked "why not?" given the context was hot. Shipping it as a clean bisectable commit while all the prior preamble.ts changes are fresh reduces rebase pain later. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
125 lines
5.8 KiB
TypeScript
125 lines
5.8 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
import { join } from 'path';
|
|
|
|
const ROOT = join(import.meta.dir, '..');
|
|
|
|
function getAllSkillMds(): Array<{ name: string; content: string }> {
|
|
const results: Array<{ name: string; content: string }> = [];
|
|
const rootPath = join(ROOT, 'SKILL.md');
|
|
if (existsSync(rootPath)) {
|
|
results.push({ name: 'root', content: readFileSync(rootPath, 'utf-8') });
|
|
}
|
|
for (const entry of readdirSync(ROOT, { withFileTypes: true })) {
|
|
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
const skillPath = join(ROOT, entry.name, 'SKILL.md');
|
|
if (existsSync(skillPath)) {
|
|
results.push({ name: entry.name, content: readFileSync(skillPath, 'utf-8') });
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
describe('Audit compliance', () => {
|
|
// Fix 1: W007 — No hardcoded credentials in documentation
|
|
test('no hardcoded credential patterns in SKILL.md.tmpl', () => {
|
|
const tmpl = readFileSync(join(ROOT, 'SKILL.md.tmpl'), 'utf-8');
|
|
expect(tmpl).not.toContain('"password123"');
|
|
expect(tmpl).not.toContain('"test@example.com"');
|
|
expect(tmpl).not.toContain('"test@test.com"');
|
|
expect(tmpl).toContain('$TEST_EMAIL');
|
|
expect(tmpl).toContain('$TEST_PASSWORD');
|
|
});
|
|
|
|
// Fix 2: Conditional telemetry — binary calls wrapped with existence check
|
|
test('preamble telemetry calls are conditional on _TEL and binary existence', () => {
|
|
// After the preamble.ts refactor (Item 9), the bash/telemetry logic lives
|
|
// in submodules under scripts/resolvers/preamble/. Concatenate all preamble
|
|
// source (root + submodules) and assert against the combined text so this
|
|
// test tracks the semantic contract, not the file layout.
|
|
const preambleDir = join(ROOT, 'scripts/resolvers/preamble');
|
|
const submoduleFiles = existsSync(preambleDir)
|
|
? readdirSync(preambleDir).filter(f => f.endsWith('.ts')).map(f => readFileSync(join(preambleDir, f), 'utf-8'))
|
|
: [];
|
|
const rootPreamble = readFileSync(join(ROOT, 'scripts/resolvers/preamble.ts'), 'utf-8');
|
|
const preamble = [rootPreamble, ...submoduleFiles].join('\n');
|
|
// Pending finalization must check _TEL and binary existence
|
|
expect(preamble).toContain('_TEL" != "off"');
|
|
expect(preamble).toContain('-x ');
|
|
expect(preamble).toContain('gstack-telemetry-log');
|
|
// End-of-skill telemetry must also be conditional
|
|
const completionIdx = preamble.indexOf('Telemetry (run last)');
|
|
expect(completionIdx).toBeGreaterThan(-1);
|
|
const completionSection = preamble.slice(completionIdx);
|
|
expect(completionSection).toContain('_TEL" != "off"');
|
|
});
|
|
|
|
// Round 2 Fix 1: W012 — Bun install uses checksum verification
|
|
test('bun install uses checksum-verified method', () => {
|
|
const browseResolver = readFileSync(join(ROOT, 'scripts/resolvers/browse.ts'), 'utf-8');
|
|
expect(browseResolver).toContain('shasum -a 256');
|
|
expect(browseResolver).toContain('BUN_INSTALL_SHA');
|
|
const setup = readFileSync(join(ROOT, 'setup'), 'utf-8');
|
|
// Setup error message should not have unverified curl|bash
|
|
const lines = setup.split('\n');
|
|
for (const line of lines) {
|
|
if (line.includes('bun.sh/install') && line.includes('| bash') && !line.includes('shasum')) {
|
|
throw new Error(`Unverified bun install found: ${line.trim()}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Fix 4: W011 — Untrusted content warning in command reference
|
|
test('command reference includes untrusted content warning after Navigation', () => {
|
|
const rootSkill = readFileSync(join(ROOT, 'SKILL.md'), 'utf-8');
|
|
const navIdx = rootSkill.indexOf('### Navigation');
|
|
const readingIdx = rootSkill.indexOf('### Reading');
|
|
expect(navIdx).toBeGreaterThan(-1);
|
|
expect(readingIdx).toBeGreaterThan(navIdx);
|
|
const between = rootSkill.slice(navIdx, readingIdx);
|
|
expect(between.toLowerCase()).toContain('untrusted');
|
|
});
|
|
|
|
// Round 2 Fix 2: Trust boundary markers + helper + wrapping in all paths
|
|
test('browse wraps untrusted content with trust boundary markers', () => {
|
|
const commands = readFileSync(join(ROOT, 'browse/src/commands.ts'), 'utf-8');
|
|
expect(commands).toContain('PAGE_CONTENT_COMMANDS');
|
|
expect(commands).toContain('wrapUntrustedContent');
|
|
const server = readFileSync(join(ROOT, 'browse/src/server.ts'), 'utf-8');
|
|
expect(server).toContain('wrapUntrustedContent');
|
|
const meta = readFileSync(join(ROOT, 'browse/src/meta-commands.ts'), 'utf-8');
|
|
expect(meta).toContain('wrapUntrustedContent');
|
|
});
|
|
|
|
// Fix 5: Data flow documentation in review.ts
|
|
test('review.ts has data flow documentation', () => {
|
|
const review = readFileSync(join(ROOT, 'scripts/resolvers/review.ts'), 'utf-8');
|
|
expect(review).toContain('Data sent');
|
|
expect(review).toContain('Data NOT sent');
|
|
});
|
|
|
|
// Round 2 Fix 3: Extension sender validation + message type allowlist
|
|
test('extension background.js validates message sender', () => {
|
|
const bg = readFileSync(join(ROOT, 'extension/background.js'), 'utf-8');
|
|
expect(bg).toContain('sender.id !== chrome.runtime.id');
|
|
expect(bg).toContain('ALLOWED_TYPES');
|
|
});
|
|
|
|
// Round 2 Fix 4: Chrome CDP binds to localhost only
|
|
test('chrome-cdp binds to localhost only', () => {
|
|
const cdp = readFileSync(join(ROOT, 'bin/chrome-cdp'), 'utf-8');
|
|
expect(cdp).toContain('--remote-debugging-address=127.0.0.1');
|
|
expect(cdp).toContain('--remote-allow-origins=');
|
|
});
|
|
|
|
// Fix 2+6: All generated SKILL.md files with telemetry are conditional
|
|
test('all generated SKILL.md files with telemetry calls use conditional pattern', () => {
|
|
const skills = getAllSkillMds();
|
|
for (const { name, content } of skills) {
|
|
if (content.includes('gstack-telemetry-log')) {
|
|
expect(content).toContain('_TEL" != "off"');
|
|
}
|
|
}
|
|
});
|
|
});
|