From 4a34bd7d362d4c884de07309ab03cbbd5b3d2ecc Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 14 Aug 2026 13:06:37 -0700 Subject: [PATCH] feat(office-hours): design docs land in the repo, written as decision records (#703, #2000) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Office-hours Phase 5 now dual-writes the design doc: the docs/designs/ copy is what teammates and plan reviews read (committable, visible), while the ~/.gstack copy keeps memory ingest and cross-session discovery working. The repo copy leaves the private store, so it passes the redaction scan-at-sink first (HIGH blocks the repo copy, MEDIUM confirms per finding), and any failure — read-only checkout, non-git dir, unconfirmed finding — degrades to the private copy with a one-line reason, never blocking the handoff. The doc itself is now a decision record, not a transcript: one bullet per decision with its why, ruled-out approaches collapsed to a single line with the rejection reason, settled/empty template sections omitted. No page cap; extra length must come from genuinely open questions. Plan reviews (ceo/eng/devex + the shared review resolver) prefer the repo-local doc (DESIGN.md, then newest docs/designs/*.md) when it's at least as fresh as the private copy — a stale old repo doc never shadows a newer session. Parity guards bumped with measured values (three plan-review skeletons +~0.7KB each; office-hours 1.092x). Judgment ported from time-attack/gstack (GStack 2); scan-at-sink and freshness-preference adaptations ours. Co-authored-by: Sina Matian Co-Authored-By: Claude Fable 5 --- autoplan/SKILL.md | 17 ++++++++-- office-hours/sections/design-and-handoff.md | 28 +++++++++++++-- .../sections/design-and-handoff.md.tmpl | 28 +++++++++++++-- plan-ceo-review/SKILL.md | 34 ++++++++++++++++--- plan-ceo-review/SKILL.md.tmpl | 17 ++++++++-- plan-devex-review/SKILL.md | 34 ++++++++++++++++--- plan-devex-review/SKILL.md.tmpl | 17 ++++++++-- plan-eng-review/SKILL.md | 34 ++++++++++++++++--- plan-eng-review/SKILL.md.tmpl | 17 ++++++++-- scripts/resolvers/review.ts | 17 ++++++++-- test/helpers/carve-guards.ts | 19 +++++++---- 11 files changed, 230 insertions(+), 32 deletions(-) diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index edaa72971..b063b90ee 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -892,8 +892,21 @@ After /office-hours completes, re-run the design doc check: setopt +o nomatch 2>/dev/null || true # zsh compat 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` diff --git a/office-hours/sections/design-and-handoff.md b/office-hours/sections/design-and-handoff.md index 4c253e96a..b7f88fa17 100644 --- a/office-hours/sections/design-and-handoff.md +++ b/office-hours/sections/design-and-handoff.md @@ -19,8 +19,32 @@ If `$PRIOR` exists, the new doc gets a `Supersedes:` field referencing it. This Write to `~/.gstack/projects/{slug}/{user}-{branch}-design-{datetime}.md`. -After writing the design doc, tell the user: -**"Design doc saved to: {full path}. Other skills (/plan-ceo-review, /plan-eng-review) will find it automatically."** +**Repo copy (dual-write, #703 + #2000).** When the session runs inside a git +repository, ALSO write the doc to `docs/designs/{topic-slug}.md` in the repo — +visible, committable, team-shareable. The `~/.gstack` copy is still written +(memory ingest and cross-session discovery depend on it); the repo copy is +what teammates and plan reviews read. Rules: + +1. **Scan at sink first.** The repo copy leaves the private store, so scan the + EXACT bytes before writing: write to a temp file, run + `~/.claude/skills/gstack/bin/gstack-redact --from-file `; exit 3 + (HIGH) blocks the repo copy (keep the ~/.gstack copy, tell the user why); + exit 2 (MEDIUM) confirms per finding before writing. +2. **Fallback is never blocking.** Read-only checkout, non-git directory, a + failed write, or an unconfirmed MEDIUM → keep the `~/.gstack` copy and say + in one line why the repo copy was skipped. The handoff continues either way. +3. **Name the repo path** in the handoff line and any approval questions when + the repo copy exists — that's the copy the user can open and commit. + +**Decision-record concision (#2000).** The doc is a decision record, not a +transcript: one bullet per decision with its why; an approach the user ruled +out DURING the session gets one line (name + rejection reason), never a +resurrected full section that re-argues the case; omit template sections that +are empty or that restate what's already settled. No page cap — extra length +must come from genuinely open questions, not template completeness. + +After writing, tell the user: +**"Design doc saved to: {repo path if written, else ~/.gstack path}{when both: ' (cross-session copy in ~/.gstack)'}. Other skills (/plan-ceo-review, /plan-eng-review) will find it automatically."** ### Startup mode design doc template: diff --git a/office-hours/sections/design-and-handoff.md.tmpl b/office-hours/sections/design-and-handoff.md.tmpl index a8f8c5aac..cd98c000c 100644 --- a/office-hours/sections/design-and-handoff.md.tmpl +++ b/office-hours/sections/design-and-handoff.md.tmpl @@ -17,8 +17,32 @@ If `$PRIOR` exists, the new doc gets a `Supersedes:` field referencing it. This Write to `~/.gstack/projects/{slug}/{user}-{branch}-design-{datetime}.md`. -After writing the design doc, tell the user: -**"Design doc saved to: {full path}. Other skills (/plan-ceo-review, /plan-eng-review) will find it automatically."** +**Repo copy (dual-write, #703 + #2000).** When the session runs inside a git +repository, ALSO write the doc to `docs/designs/{topic-slug}.md` in the repo — +visible, committable, team-shareable. The `~/.gstack` copy is still written +(memory ingest and cross-session discovery depend on it); the repo copy is +what teammates and plan reviews read. Rules: + +1. **Scan at sink first.** The repo copy leaves the private store, so scan the + EXACT bytes before writing: write to a temp file, run + `~/.claude/skills/gstack/bin/gstack-redact --from-file `; exit 3 + (HIGH) blocks the repo copy (keep the ~/.gstack copy, tell the user why); + exit 2 (MEDIUM) confirms per finding before writing. +2. **Fallback is never blocking.** Read-only checkout, non-git directory, a + failed write, or an unconfirmed MEDIUM → keep the `~/.gstack` copy and say + in one line why the repo copy was skipped. The handoff continues either way. +3. **Name the repo path** in the handoff line and any approval questions when + the repo copy exists — that's the copy the user can open and commit. + +**Decision-record concision (#2000).** The doc is a decision record, not a +transcript: one bullet per decision with its why; an approach the user ruled +out DURING the session gets one line (name + rejection reason), never a +resurrected full section that re-argues the case; omit template sections that +are empty or that restate what's already settled. No page cap — extra length +must come from genuinely open questions, not template completeness. + +After writing, tell the user: +**"Design doc saved to: {repo path if written, else ~/.gstack path}{when both: ' (cross-session copy in ~/.gstack)'}. Other skills (/plan-ceo-review, /plan-eng-review) will find it automatically."** ### Startup mode design doc template: diff --git a/plan-ceo-review/SKILL.md b/plan-ceo-review/SKILL.md index d4b6b6fbf..b22d26e64 100644 --- a/plan-ceo-review/SKILL.md +++ b/plan-ceo-review/SKILL.md @@ -949,8 +949,21 @@ Then read CLAUDE.md, TODOS.md, and any existing architecture docs. setopt +o nomatch 2>/dev/null || true # zsh compat 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` If a design doc exists (from `/office-hours`), read it. Use it as the source of truth for the problem statement, constraints, and chosen approach. If it has a `Supersedes:` field, note that this is a revised design. @@ -1020,8 +1033,21 @@ After /office-hours completes, re-run the design doc check: setopt +o nomatch 2>/dev/null || true # zsh compat 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` diff --git a/plan-ceo-review/SKILL.md.tmpl b/plan-ceo-review/SKILL.md.tmpl index c43cfe641..b9ea45d3b 100644 --- a/plan-ceo-review/SKILL.md.tmpl +++ b/plan-ceo-review/SKILL.md.tmpl @@ -138,8 +138,21 @@ Then read CLAUDE.md, TODOS.md, and any existing architecture docs. setopt +o nomatch 2>/dev/null || true # zsh compat 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` If a design doc exists (from `/office-hours`), read it. Use it as the source of truth for the problem statement, constraints, and chosen approach. If it has a `Supersedes:` field, note that this is a revised design. diff --git a/plan-devex-review/SKILL.md b/plan-devex-review/SKILL.md index e47d45907..c492ee95b 100644 --- a/plan-devex-review/SKILL.md +++ b/plan-devex-review/SKILL.md @@ -973,8 +973,21 @@ Then read: setopt +o nomatch 2>/dev/null || true 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` If a design doc exists, read it. @@ -1033,8 +1046,21 @@ After /office-hours completes, re-run the design doc check: setopt +o nomatch 2>/dev/null || true # zsh compat 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` diff --git a/plan-devex-review/SKILL.md.tmpl b/plan-devex-review/SKILL.md.tmpl index 1ef723c10..6c0acae0f 100644 --- a/plan-devex-review/SKILL.md.tmpl +++ b/plan-devex-review/SKILL.md.tmpl @@ -99,8 +99,21 @@ Then read: setopt +o nomatch 2>/dev/null || true 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` If a design doc exists, read it. diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index 151df2fb9..bd8f9cd85 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -921,8 +921,21 @@ sections. Read a section in full before doing its step; do not work from memory. setopt +o nomatch 2>/dev/null || true # zsh compat 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` If a design doc exists, read it. Use it as the source of truth for the problem statement, constraints, and chosen approach. If it has a `Supersedes:` field, note that this is a revised design — check the prior version for context on what changed and why. @@ -976,8 +989,21 @@ After /office-hours completes, re-run the design doc check: setopt +o nomatch 2>/dev/null || true # zsh compat 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` diff --git a/plan-eng-review/SKILL.md.tmpl b/plan-eng-review/SKILL.md.tmpl index c1125bfaa..697ab6539 100644 --- a/plan-eng-review/SKILL.md.tmpl +++ b/plan-eng-review/SKILL.md.tmpl @@ -112,8 +112,21 @@ When evaluating architecture, think "boring by default." When reviewing tests, t setopt +o nomatch 2>/dev/null || true # zsh compat 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` If a design doc exists, read it. Use it as the source of truth for the problem statement, constraints, and chosen approach. If it has a `Supersedes:` field, note that this is a revised design — check the prior version for context on what changed and why. diff --git a/scripts/resolvers/review.ts b/scripts/resolvers/review.ts index 7dccd8e50..142703ce0 100644 --- a/scripts/resolvers/review.ts +++ b/scripts/resolvers/review.ts @@ -309,8 +309,21 @@ After /${first} completes, re-run the design doc check: setopt +o nomatch 2>/dev/null || true # zsh compat 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=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +_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) +# 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 +# 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 +DESIGN="$_LOCALDOC" +if [ -n "$_REPODOC" ] && { [ -z "$_LOCALDOC" ] || [ "$_REPODOC" -nt "$_LOCALDOC" ]; }; then + DESIGN="$_REPODOC" +fi [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" \`\`\` diff --git a/test/helpers/carve-guards.ts b/test/helpers/carve-guards.ts index 0f3de743a..11c3be06a 100644 --- a/test/helpers/carve-guards.ts +++ b/test/helpers/carve-guards.ts @@ -150,7 +150,9 @@ export const CARVE_GUARDS: Record = { }, behavioral: 'external', externalTest: 'test/skill-e2e-plan-ceo-review-section-loading.test.ts', - maxSkeletonBytes: 90_000, + // Fork port wave 2 (#703): the repo-doc-preference block in the design + // check grew every plan-review skeleton ~0.7KB. Measured values noted. + maxSkeletonBytes: 92_000, // measured 90,897 minUnionBytes: 80_000, mustContain: ['SCOPE EXPANSION', 'SELECTIVE EXPANSION', 'HOLD SCOPE', 'SCOPE REDUCTION'], // Default-on Codex outside-voice (codexPreflight block + CODEX_MODE branch @@ -171,7 +173,9 @@ export const CARVE_GUARDS: Record = { behavioral: 'plan', // v1.2.0 activation lift (shared first-run-guidance preamble) + #2077 ask-first scope gate. // +~1 KB: plan-mode auto-select-B scope-gate exceptions (2026-08). - maxSkeletonBytes: 68_000, + // Fork port wave 2 (#703): the repo-doc-preference block in the design + // check grew every plan-review skeleton ~0.7KB. Measured values noted. + maxSkeletonBytes: 70_000, // measured 68,780 minUnionBytes: 70_000, mustContain: ['Architecture', 'Code Quality', 'Test', 'Performance'], // Cross-cutting preamble growth (v1.57.2.0 AUQ-failure prose fallback + the @@ -182,7 +186,7 @@ export const CARVE_GUARDS: Record = { // 1.08 → 1.10: the scope-gate exceptions block (+ its adversarial-review // hardening: host-anchored mode signal, precedence, passing-mention // guards) and the plan-mode preamble reword land the union at 1.092. - maxSizeRatio: 1.10, + maxSizeRatio: 1.12, // measured 1.103 }, 'plan-design-review': { skill: 'plan-design-review', @@ -220,7 +224,9 @@ export const CARVE_GUARDS: Record = { // +Conductor AUQ-default-prose rule + one-way/destructive prose safety + // continuation protocol in the always-loaded AskUserQuestion Format section. // v1.2.0 activation lift: first-run-guidance section in the shared preamble. - maxSkeletonBytes: 80_000, + // Fork port wave 2 (#703): the repo-doc-preference block in the design + // check grew every plan-review skeleton ~0.7KB. Measured values noted. + maxSkeletonBytes: 82_000, // measured 80,493 minUnionBytes: 70_000, mustContain: ['developer experience', 'Getting Started'], // Default-on Codex outside-voice (codexPreflight block + CODEX_MODE branch @@ -245,11 +251,12 @@ export const CARVE_GUARDS: Record = { // plus the P1 office-hours closing handoff (AUQ that launches the next skill). // Fork port wave 2: the third-party web-actions contract sits inline // (judgment must be visible before the workflow directs the user to a - // vendor site) — skeleton 99,780 / ratio 1.079 measured. Tight headroom. + // vendor site), plus the #703 dual-write + repo-doc-preference block — + // ratio 1.092 measured. Tight headroom. maxSkeletonBytes: 101_000, minUnionBytes: 70_000, mustContain: ['design doc', 'problem statement'], - maxSizeRatio: 1.09, + maxSizeRatio: 1.10, }, 'document-release': { skill: 'document-release',