Files
gstack/test/gstack-design-doc-find.test.ts
T
garrytan 8e2beec2de Merge origin/main v1.91.18.0 (#3023) into the v1.91.19.0 wave
Resolutions:
- design-doc discovery: main's bin/gstack-design-doc-find replaces the inline
  block; it keeps #2839 (a root DESIGN.md is the design system, never the
  design doc; only docs/designs/ competes on freshness). Its unit test and
  the F2 harness (now installs the helper) follow.
- design-shotgun: main's $D variants batch, staged under ${TMPDIR:-/tmp}.
- memory ingest: main's scoped consent (scope counts, purge of staged pages
  outside the scope, zero-sessions notice) on the wave's locked state flow;
  the purge runs before the state loads, under the lock.
- setup / render library: --model overlay plus --disabled-skills on every
  Claude render, including main's plain overlay render.
- codex template and constants: the wave's sandbox preflight and unverified
  mode; main's issue-number removals.
- docsync interface: ls-tree (main) and check-ignore (wave) are both reads.
- census pins re-measured on the merged tree: 114 paid files; gate
  behavior census 44 files / 76 shards (periodic-only files skip the gate,
  now including two safety-rule evals); periodic floor 35,742 and release
  sum 58,097 (gate-only files no longer count toward periodic).
- parity caps at measured merged sizes: ship 1.4271, review 1.1981, qa 1.1099.
- generated output regenerated (gen:skill-docs --host all, llms.txt,
  context budget, goldens).
2026-10-04 02:10:47 +00:00

117 lines
5.2 KiB
TypeScript

import { afterEach, describe, expect, test } from 'bun:test';
import { execFileSync } from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
const ROOT = path.resolve(import.meta.dir, '..');
const BIN = path.join(ROOT, 'bin/gstack-design-doc-find');
const temps: string[] = [];
afterEach(() => {
for (const dir of temps.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
});
function fixture() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-doc-find-'));
temps.push(dir);
const home = path.join(dir, 'home');
const repo = path.join(dir, 'repo');
fs.mkdirSync(home, { recursive: true });
fs.mkdirSync(repo, { recursive: true });
execFileSync('git', ['init', '-q', '-b', 'main'], { cwd: repo, timeout: 10_000 });
const baseEnv = { PATH: process.env.PATH!, HOME: home, GIT_CEILING_DIRECTORIES: dir };
const write = (file: string, ageSeconds: number) => {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, '# Design\n');
const t = Date.now() / 1000 - ageSeconds;
fs.utimesSync(file, t, t);
return file;
};
const find = (env: Record<string, string> = {}, args = ['proj', 'feat-x']) =>
execFileSync(BIN, args, { cwd: repo, env: { ...baseEnv, ...env }, encoding: 'utf8', timeout: 10_000 }).trim();
return { dir, home, repo, write, find };
}
describe('gstack-design-doc-find', () => {
test('default root is ~/.gstack and prints nothing when no doc exists', () => {
const f = fixture();
expect(f.find()).toBe('');
const doc = f.write(path.join(f.home, '.gstack/projects/proj/user-main-design-20260101.md'), 100);
expect(f.find()).toBe(doc);
});
test('GSTACK_HOME moves the state root', () => {
const f = fixture();
f.write(path.join(f.home, '.gstack/projects/proj/user-main-design-a.md'), 10);
const custom = path.join(f.dir, 'custom-state');
const doc = f.write(path.join(custom, 'projects/proj/user-main-design-b.md'), 100);
expect(f.find({ GSTACK_HOME: custom })).toBe(doc);
});
test('a branch-specific doc wins over a newer generic doc', () => {
const f = fixture();
const branch = f.write(path.join(f.home, '.gstack/projects/proj/user-feat-x-design-1.md'), 500);
f.write(path.join(f.home, '.gstack/projects/proj/user-main-design-2.md'), 10);
expect(f.find()).toBe(branch);
expect(f.find({}, ['proj', 'other'])).toBe(path.join(f.home, '.gstack/projects/proj/user-main-design-2.md'));
});
test('a repo docs/designs doc wins only when at least as fresh; a root DESIGN.md never does (#2839)', () => {
const f = fixture();
const local = f.write(path.join(f.home, '.gstack/projects/proj/user-feat-x-design-1.md'), 100);
const designs = f.write(path.join(f.repo, 'docs/designs/plan.md'), 10);
expect(f.find()).toBe(designs);
fs.utimesSync(designs, Date.now() / 1000 - 1000, Date.now() / 1000 - 1000);
expect(f.find()).toBe(local);
const top = f.write(path.join(f.repo, 'DESIGN.md'), 0);
expect(f.find()).toBe(local);
fs.rmSync(local);
expect(f.find()).toBe(designs);
fs.rmSync(designs);
expect(top).toBe(path.join(f.repo, 'DESIGN.md'));
expect(f.find()).toBe('');
});
test('a repo doc is used when the state root has none', () => {
const f = fixture();
const designs = f.write(path.join(f.repo, 'docs/designs/plan.md'), 1000);
expect(f.find()).toBe(designs);
});
test('usage errors exit 2', () => {
const f = fixture();
expect(() => f.find({}, [])).toThrow();
});
});
describe('rendered Design Doc Check block', () => {
const rendered = fs.readFileSync(path.join(ROOT, 'plan-ceo-review/SKILL.md'), 'utf8');
const block = rendered.slice(rendered.indexOf('DESIGN=$(~/.claude/skills/gstack/bin/gstack-design-doc-find'))
.split('\n').slice(0, 2).join('\n');
test('the generated skill renders one helper invocation', () => {
expect(block).toContain('gstack-design-doc-find "$SLUG" "$BRANCH"');
expect(block).toContain('echo "Design doc found: $DESIGN"');
expect(block).toContain('echo "No design doc found"');
});
test('finds an office-hours doc under a custom GSTACK_HOME and keeps the default-root result', () => {
const f = fixture();
const installed = path.join(f.home, '.claude/skills/gstack/bin');
fs.mkdirSync(installed, { recursive: true });
for (const bin of ['gstack-design-doc-find', 'gstack-paths', 'gstack-state-root.sh']) {
fs.copyFileSync(path.join(ROOT, 'bin', bin), path.join(installed, bin));
fs.chmodSync(path.join(installed, bin), 0o755);
}
const run = (env: Record<string, string>) => execFileSync('bash', ['-c', `SLUG=proj; BRANCH=main; ${block}`],
{ cwd: f.repo, env: { PATH: process.env.PATH!, HOME: f.home, GIT_CEILING_DIRECTORIES: f.dir, ...env }, encoding: 'utf8', timeout: 10_000 }).trim();
const custom = path.join(f.dir, 'custom-state');
const doc = f.write(path.join(custom, 'projects/proj/garry-main-design-20261003-120000.md'), 60);
expect(run({ GSTACK_HOME: custom })).toBe(`Design doc found: ${doc}`);
expect(run({})).toBe('No design doc found');
const legacy = f.write(path.join(f.home, '.gstack/projects/proj/garry-main-design-20261001-120000.md'), 60);
expect(run({})).toBe(`Design doc found: ${legacy}`);
});
});