F2: design-doc discovery reads docs/designs, never a root DESIGN.md

A root DESIGN.md is the design system /design-consultation writes, so
preferring it unconditionally reviewed plans against a token file while
printing "Design doc found" and never looked at docs/designs/ feature
docs. Discovery now considers only docs/designs/*.md as the repo-local
design doc (still freshness-compared with the private ~/.gstack copy).
The CEO finding fixture seeds its supplied design as a docs/designs doc
and its test follows.

Co-authored-by: Bit0ps <alex.dreamscaper@gmail.com>
This commit is contained in:
garrytanandBit0ps committed 2026-10-03 20:56:17 +00:00
1 parent e99c1fc7a0
commit 998f116b6d
3 files changed
+44 -10

No files matched your search

+6 -6
View File
@@ -3,8 +3,10 @@
*
* Finds the design doc a plan review should read: newest branch-scoped doc
* under ~/.gstack/projects/<slug>/, falling back to newest project-scoped
* doc, then lets a repo-local doc (DESIGN.md or docs/designs/*.md) win when
* it is at least as fresh. office-hours dual-writes docs/designs/ alongside
* doc, then lets a repo-local feature doc (docs/designs/*.md) win when
* it is at least as fresh. A root DESIGN.md is never a design doc: it is the
* design system /design-consultation writes, and preferring it reviewed
* plans against a token file while printing "Design doc found" (#2839). office-hours dual-writes docs/designs/ alongside
* ~/.gstack, and the committed copy is what teammates see — but a stale old
* repo doc must never shadow a newer private session.
*
@@ -34,10 +36,8 @@ export const DESIGN_DOC_DISCOVERY_BLOCK = `_LOCALDOC=$(ls -t ~/.gstack/projects/
# see. A stale old repo doc never shadows a newer private session.
_REPOTOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
_REPODOC=""
if [ -n "$_REPOTOP" ]; then
[ -f "$_REPOTOP/DESIGN.md" ] && _REPODOC="$_REPOTOP/DESIGN.md"
[ -z "$_REPODOC" ] && _REPODOC=$(ls -t "$_REPOTOP"/docs/designs/*.md 2>/dev/null | head -1)
fi
# Only docs/designs/ holds feature design docs; a root DESIGN.md is the design system (#2839).
[ -n "$_REPOTOP" ] && _REPODOC=$(ls -t "$_REPOTOP"/docs/designs/*.md 2>/dev/null | head -1)
DESIGN="$_LOCALDOC"
if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then
DESIGN="$_REPODOC"
+32 -2
View File
@@ -291,6 +291,36 @@ test.each([
expect(pickSuppliedCeoPlanStart({ options })).toBe(expected);
});
describe('F2: design-doc discovery prefers docs/designs over a root DESIGN.md (#2839)', () => {
function discover(files: Record<string, string>): string {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'design-doc-f2-'));
try {
const cwd = path.join(root, 'project');
const home = path.join(root, 'home');
fs.mkdirSync(cwd); fs.mkdirSync(home);
execFileSync('git', ['init', '-q', '-b', 'main'], { cwd, timeout: 30_000 });
for (const [rel, body] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(cwd, rel)), { recursive: true });
fs.writeFileSync(path.join(cwd, rel), body);
}
// The design system is the newest file, so mtime alone would pick it.
if (files['DESIGN.md']) fs.utimesSync(path.join(cwd, 'DESIGN.md'), new Date(), new Date(Date.now() + 60_000));
return execFileSync('bash', ['-c', `SLUG=fixture; BRANCH=main; ${DESIGN_DOC_DISCOVERY_BLOCK}`], {
cwd, env: { PATH: process.env.PATH!, HOME: home }, encoding: 'utf8', timeout: 10_000,
}).replace(fs.realpathSync(cwd), '<repo>').replace(cwd, '<repo>');
} finally { fs.rmSync(root, { recursive: true, force: true }); }
}
test('a feature doc in docs/designs/ wins over a newer root DESIGN.md', () => {
expect(discover({ 'DESIGN.md': '# Design system\ncolors: {}\n', 'docs/designs/pricing.md': '# Pricing design\n' }))
.toBe('Design doc found: <repo>/docs/designs/pricing.md\n');
});
test('a root DESIGN.md alone is a design system, not a design doc', () => {
expect(discover({ 'DESIGN.md': '# Design system\n' })).toBe('No design doc found\n');
});
});
describe('CEO finding fixture establishes scope before launch', () => {
test('a supplied design satisfies actual prerequisite discovery without becoming a branch change', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ceo-design-seed-'));
@@ -304,8 +334,8 @@ describe('CEO finding fixture establishes scope before launch', () => {
const output = execFileSync('bash', ['-c', `SLUG=fixture; BRANCH=main; ${DESIGN_DOC_DISCOVERY_BLOCK}`], {
cwd, env: { PATH: process.env.PATH!, HOME: home }, encoding: 'utf8', timeout: 10_000,
});
expect(output).toBe(`Design doc found: ${path.join(cwd, 'DESIGN.md')}\n`);
expect(execFileSync('git', ['show', 'HEAD:DESIGN.md'], { cwd, encoding: 'utf8', timeout: 30_000 })).toBe(design);
expect(output).toBe(`Design doc found: ${path.join(cwd, 'docs', 'designs', 'feature-design.md')}\n`);
expect(execFileSync('git', ['show', 'HEAD:docs/designs/feature-design.md'], { cwd, encoding: 'utf8', timeout: 30_000 })).toBe(design);
expect(execFileSync('git', ['show', 'HEAD:review-input.md'], { cwd, encoding: 'utf8', timeout: 30_000 })).toBe(plan);
expect(execFileSync('git', ['status', '--porcelain'], { cwd, encoding: 'utf8', timeout: 30_000 })).toBe('');
expect(execFileSync('git', ['diff', 'origin/main...HEAD'], { cwd, encoding: 'utf8', timeout: 30_000 })).toBe('');
+6 -2
View File
@@ -59,7 +59,11 @@ export function seedPlanReviewProject(projectDir: string, plan: string, skill: '
throw new Error('Plan review fixture requires a fresh private directory');
}
fs.writeFileSync(path.join(projectDir, 'review-input.md'), plan, { flag: 'wx' });
if (design !== undefined) fs.writeFileSync(path.join(projectDir, 'DESIGN.md'), design, { flag: 'wx' });
// A feature design doc lives in docs/designs/; a root DESIGN.md is a design system (F2, #2839).
if (design !== undefined) {
fs.mkdirSync(path.join(projectDir, 'docs', 'designs'), { recursive: true });
fs.writeFileSync(path.join(projectDir, 'docs', 'designs', 'feature-design.md'), design, { flag: 'wx' });
}
fs.writeFileSync(path.join(projectDir, 'README.md'), `# ${skill} fixture\n`, { flag: 'wx' });
fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), [
`# ${skill}`, '',
@@ -73,7 +77,7 @@ export function seedPlanReviewProject(projectDir: string, plan: string, skill: '
].join('\n'), { flag: 'wx' });
const git = (args: string[]) => execFileSync('git', args, { cwd: projectDir, stdio: 'pipe', timeout: 10_000 });
git(['init', '-b', 'main']);
git(['add', 'README.md', 'CLAUDE.md', 'review-input.md', ...(design === undefined ? [] : ['DESIGN.md'])]);
git(['add', 'README.md', 'CLAUDE.md', 'review-input.md', ...(design === undefined ? [] : ['docs/designs/feature-design.md'])]);
git(['-c', 'user.name=Finding fixture', '-c', 'user.email=fixture@gstack.test', 'commit', '-m', 'Seed review input']);
git(['update-ref', 'refs/remotes/origin/main', 'HEAD']);
}