fix(state-root): design-doc discovery reads $GSTACK_STATE_ROOT/projects

DESIGN_DOC_DISCOVERY and autoplan's Step 2 design-doc lookup read the literal
~/.gstack/projects path, so under a non-default state root they missed the
docs office-hours writes. The block now globs "$GSTACK_STATE_ROOT"/projects,
and each caller's bash block (autoplan, plan-ceo/devex/eng-review, the
spec-review prerequisite recheck) resolves the root with the guarded
gstack-paths eval first.

Allowlist: drops the autoplan ls row, both _LOCALDOC rows and the resolver
source-comment row from STATE_ROOT_ALLOWLIST.
Tests: the eng Design Doc Check fixture installs gstack-paths and seeds docs
under a non-default root; the CEO discovery fixture passes an explicit
GSTACK_STATE_ROOT.
This commit is contained in:
garrytan committed 2026-10-03 02:14:37 +00:00
1 parent f7958d8d5d
commit 569cdae04f
9 files changed
+19 -15

No files matched your search

+7 -6
View File
@@ -2,10 +2,10 @@
* {{DESIGN_DOC_DISCOVERY}} — the canonical design-doc discovery block (#703).
*
* Finds the design doc a plan review should read: newest branch-scoped doc
* under ~/.gstack/projects/<slug>/, falling back to newest project-scoped
* under $GSTACK_STATE_ROOT/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
* ~/.gstack, and the committed copy is what teammates see — but a stale old
* the state root, and the committed copy is what teammates see — but a stale old
* repo doc must never shadow a newer private session.
*
* Single source of truth for the freshness-preference logic that previously
@@ -16,7 +16,8 @@
*
* The fragment carries no code fences — the {{DESIGN_DOC_DISCOVERY}} token
* sits inside each caller's ```bash block. Callers must set $SLUG and
* $BRANCH first (and `setopt +o nomatch` for zsh); the block sets $DESIGN
* $BRANCH and resolve $GSTACK_STATE_ROOT through the guarded gstack-paths
* eval first (and `setopt +o nomatch` for zsh); the block sets $DESIGN
* and prints "Design doc found: ..." or "No design doc found".
*/
@@ -27,10 +28,10 @@ import type { TemplateContext } from './types';
* re-check) can interpolate it into their own template strings instead of
* embedding a drifting copy.
*/
export const DESIGN_DOC_DISCOVERY_BLOCK = `_LOCALDOC=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
[ -z "$_LOCALDOC" ] && _LOCALDOC=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1)
export const DESIGN_DOC_DISCOVERY_BLOCK = `_LOCALDOC=$(ls -t "$GSTACK_STATE_ROOT"/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
[ -z "$_LOCALDOC" ] && _LOCALDOC=$(ls -t "$GSTACK_STATE_ROOT"/projects/$SLUG/*-design-*.md 2>/dev/null | head -1)
# Repo-local docs win when at least as fresh (#703): office-hours dual-writes
# docs/designs/ alongside ~/.gstack, and the committed copy is what teammates
# docs/designs/ alongside the state root, and the committed copy is what teammates
# see. A stale old repo doc never shadows a newer private session.
_REPOTOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
_REPODOC=""
+1
View File
@@ -258,6 +258,7 @@ Read the resulting doc if found; otherwise continue the standard review.
Do not rerun the preamble or re-offer the prerequisite.` : `After /${first} completes, re-run the design doc check:
\`\`\`bash
setopt +o nomatch 2>/dev/null || true # zsh compat
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"; : "\${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch')
${DESIGN_DOC_DISCOVERY_BLOCK}