Merge remote-tracking branch 'origin/garrytan/opus55-wip-D' into garrytan/opus-5-5-prompt-audit

This commit is contained in:
garrytan committed 2026-10-03 00:52:54 +00:00
commit 841a766eec
36 files changed
+233 -284

No files matched your search

+3 -1
View File
@@ -58,7 +58,9 @@ mkdir -p .gstack/benchmark-reports/baselines
### Phase 2: Page Discovery
Same as /canary — auto-discover from navigation or use `--pages`.
Use `--pages` when given. Otherwise open the URL, collect its same-origin `a[href]`
links (skip logout/delete-style URLs), and propose the homepage plus up to 5 main
navigation targets; confirm the list with the user before measuring.
If `--diff` mode:
```bash
+1 -1
View File
@@ -245,7 +245,7 @@ If the user chooses A, copy the latest screenshots to the baselines directory an
## Important Rules
- **Speed matters.** Start monitoring within 30 seconds of invocation. Don't over-analyze before monitoring.
- **Speed matters.** The first minutes after a deploy are the point: reach the first monitoring round as soon as the page list is settled.
- **Alert on changes, not absolutes.** Compare against baseline, not industry standards.
- **Screenshots are evidence.** Every alert includes a screenshot path. No exceptions.
- **Transient tolerance.** Only alert on patterns that persist across 2+ consecutive checks.
+1 -5
View File
@@ -108,10 +108,6 @@ The probe multi-signal auth logic accepts: `$CODEX_API_KEY` set, `$OPENAI_API_KE
set, or `${CODEX_HOME:-~/.codex}/auth.json` exists. Avoids false-negatives for
env-auth users (CI, platform engineers) that file-only checks would reject.
**Update the known-bad list** in `bin/gstack-codex-probe` when a new Codex CLI version
regresses. Current entries (`0.120.0`, `0.120.1`, `0.120.2`) trace to the stdin
deadlock fixed in #972.
---
## Step 0.6: Resolve portable roots
@@ -190,7 +186,7 @@ mode sections.
## Synthesis recommendation (REQUIRED) — all modes
Every mode ends by emitting ONE synthesis recommendation line after presenting
Codex's verbatim output, in the canonical format the AskUserQuestion judge grades:
Codex's verbatim output, in this format:
```
Recommendation: <action> because <one-line reason that names the most actionable finding>
+1 -1
View File
@@ -107,7 +107,7 @@ Tokens: N | Est. cost: ~$X.XX
3a. **Synthesis recommendation (REQUIRED).** After presenting the full
adversarial output, emit ONE recommendation line summarizing what the user
should do, in the canonical format the AskUserQuestion judge grades:
should do, in this format:
```
Recommendation: <action> because <one-line reason that names the most exploitable finding>
+2 -2
View File
@@ -193,8 +193,8 @@ Session saved — run /codex again to continue this conversation.
"Note: Claude Code disagrees on X because Y."
8. **Synthesis recommendation (REQUIRED).** Emit ONE recommendation line
summarizing what the user should do based on Codex's consult output, in the
canonical format the AskUserQuestion judge grades:
summarizing what the user should do based on Codex's consult output, in this
format:
```
Recommendation: <action> because <one-line reason that names the most actionable insight from Codex>
+1 -1
View File
@@ -176,7 +176,7 @@ GATE: UNVERIFIED (Codex completed and tagged nothing; read the output above)
5a. **Synthesis recommendation (REQUIRED).** After presenting Codex's verbatim
output and the GATE verdict, emit ONE recommendation line summarizing what the
user should do, in the canonical format the AskUserQuestion judge grades:
user should do, in this format:
```
Recommendation: <action> because <one-line reason that names the most actionable finding>
+5 -7
View File
@@ -7,9 +7,7 @@ description: |
saved state (preferring the current branch, falling back across branches) so
you can pick up where you left off — even across Conductor workspace handoffs.
Use when asked to "resume", "restore context", "where was I", or
"pick up where I left off". Pair with /context-save.
Formerly /checkpoint resume — renamed because Claude Code treats /checkpoint
as a native rewind alias in current environments. (gstack)
"pick up where I left off". Pair with /context-save. (gstack)
allowed-tools:
- Bash
- Read
@@ -55,7 +53,7 @@ branch.)
Parse the user's input:
- `/context-restore` → load the most recent saved context (any branch)
- `/context-restore` → load the most recent saved context (current branch first, then any branch)
- `/context-restore <title-fragment-or-number>` → load a specific saved context
- `/context-restore list` → tell the user "Use `/context-save list` — listing
lives on the save side" and exit. No mode detection here.
@@ -234,9 +232,9 @@ in the old bucket until the user copies them with `gstack-slug --adopt-legacy`.
- **Never modify code.** This skill only reads saved files and presents them.
- **Prefer the current branch's own checkpoint, but keep all branches in the
fallback set.** Cross-branch resume (Conductor handoff) still works when the
current branch has no checkpoint; it just no longer lets a sibling worktree's
newer save shadow this branch's own.
fallback set.** Cross-branch resume (Conductor handoff) works when the
current branch has no checkpoint, and a sibling worktree's newer save never
shadows this branch's own.
- **Never present another project's checkpoint as "latest".** Identity is the
canonical remote (the root only when there is no remote); a mismatch is
reported before any summary.
+3 -7
View File
@@ -7,8 +7,6 @@ description: |
so any future session can pick up without losing a beat.
Use when asked to "save progress", "save state", "context save", or
"save my work". Pair with /context-restore to resume later.
Formerly /checkpoint — renamed because Claude Code treats /checkpoint as a
native rewind alias in current environments, which was shadowing this skill.
(gstack)
allowed-tools:
- Bash
@@ -48,7 +46,7 @@ If the user provides a title after the command (e.g., `/context-save auth refact
use it as the title. Otherwise, infer a title from the current work.
If the user types `/context-save resume` or `/context-save restore`, tell them:
"Use `/context-restore` instead — save and restore are separate skills now."
"Use `/context-restore` to resume — restoring is a separate skill."
---
@@ -184,8 +182,7 @@ modified files). Use relative paths from the repo root. Do not write `remote:` o
`project_root:` yourself; the stamp step below adds them.
Then stamp the project identity. Several projects can share one checkpoints
directory (GitLab nested groups did before #3003), so `/context-restore` needs to
know which project wrote each file. The helper adds `remote:` (canonical origin:
directory, so `/context-restore` needs to know which project wrote each file. The helper adds `remote:` (canonical origin:
lowercased host, no credentials, port or `.git`) and `project_root:` to the
frontmatter:
@@ -281,5 +278,4 @@ If there are no saved contexts, tell the user: "No saved contexts yet. Run
- **Infer, don't interrogate.** Use git state and conversation context to fill in
the file. Only use AskUserQuestion if the title genuinely cannot be inferred.
- **This is a gstack skill, not a Claude Code built-in.** When the user types
`/context-save`, invoke this skill via the Skill tool. The old `/checkpoint`
name collided with Claude Code's native `/rewind` alias — the rename fixed that.
`/context-save`, invoke this skill via the Skill tool.
@@ -326,10 +326,9 @@ Use real token values, no placeholders; omit invented `components` entries and u
```markdown
## Design System
Always read DESIGN.md before making any visual or UI decisions.
All font choices, colors, spacing, and aesthetic direction are defined there.
Do not deviate without explicit user approval.
In QA mode, flag any code that doesn't match DESIGN.md.
Read DESIGN.md before visual or UI work: it defines the fonts, colors, spacing, and
aesthetic direction. Ask the user before departing from it. When reviewing or QA-ing
UI, flag code that doesn't match DESIGN.md.
```
After shipping DESIGN.md, if the session produced screen-level mockups or page layouts
+9 -5
View File
@@ -231,8 +231,9 @@ _ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
[ -n "$_PRETEXT_VENDOR" ] && echo "VENDOR: $_PRETEXT_VENDOR" || echo "VENDOR_MISSING"
```
- If `VENDOR` found: read the file and inline it in a `<script>` tag. The HTML file
is fully self-contained with zero network dependencies.
- If `VENDOR` found: inline the file in a `<script type="module">` and put the page's
Pretext code after it in that same script; the bundle ends in `export {...}` and sets
no global. The HTML file is fully self-contained with zero network dependencies.
- If `VENDOR_MISSING`: use CDN import as fallback:
`<script type="module">import { prepare, layout, prepareWithSegments, walkLineRanges, layoutNextLine, layoutWithLines } from 'https://esm.sh/@chenglou/pretext'</script>`
Add a comment: `<!-- FALLBACK: vendor/pretext.js missing, using CDN -->`
@@ -306,17 +307,20 @@ echo "SERVER: http://localhost:$_PORT/finalized.html"
echo "PID: $_SERVER_PID"
```
Remember the printed `SERVER:` URL and `PID:` value; each bash block is a fresh
shell, so later blocks restate them literally.
If python3 is not available, fall back to:
```bash
open <path-to-finalized.html>
```
Tell the user: "Live preview running at http://localhost:$_PORT/finalized.html.
Tell the user: "Live preview running at <SERVER URL>.
After each edit, just refresh the browser (Cmd+R) to see changes."
When the refinement loop ends (Step 4 exits), kill the server:
```bash
kill $_SERVER_PID 2>/dev/null || true
kill <PID> 2>/dev/null || true
```
---
@@ -377,7 +381,7 @@ LOOP:
5. Apply feedback using targeted Edit tool changes on the HTML file
(do NOT regenerate the entire file — surgical edits only)
6. Brief summary of what changed (2-3 lines max)
6. Briefly say what changed
7. If verification screenshots are available, re-take them to confirm the fix
+27 -37
View File
@@ -5,8 +5,8 @@ Pretext API usage patterns. Follow them exactly.
**Pattern 1: Basic height computation (Simple layout, Card/grid)**
```js
import { prepare, layout } from './pretext-inline.js'
// Or if inlined: const { prepare, layout } = window.Pretext
import { prepare, layout } from '@chenglou/pretext'
// Vanilla HTML: omit the import; this code follows the inlined bundle in the same <script type="module">
// 1. PREPARE — one-time, after fonts load
await document.fonts.ready
@@ -45,37 +45,26 @@ for (const el of elements) {
**Pattern 2: Shrinkwrap / tight-fit containers (Chat bubbles)**
```js
import { prepareWithSegments, walkLineRanges } from './pretext-inline.js'
import { prepareWithSegments, walkLineRanges } from '@chenglou/pretext'
// Find the tightest width that produces the same line count
function shrinkwrap(text, font, maxWidth, lineHeight) {
// Tight-fit width: lay out at maxWidth, then shrink the bubble to its widest line
function shrinkwrap(text, font, maxWidth) {
const segs = prepareWithSegments(text, font)
let bestWidth = maxWidth
walkLineRanges(segs, maxWidth, (lineCount, startIdx, endIdx) => {
// walkLineRanges calls back with progressively narrower widths
// The first call gives us the line count at maxWidth
// We want the narrowest width that still produces this line count
let widest = 0
const lineCount = walkLineRanges(segs, maxWidth, (line) => {
widest = Math.max(widest, line.width)
})
// Binary search for tightest width with same line count
const { lineCount: targetLines } = layout(prepare(text, font), maxWidth, lineHeight)
let lo = 0, hi = maxWidth
while (hi - lo > 1) {
const mid = (lo + hi) / 2
const { lineCount } = layout(prepare(text, font), mid, lineHeight)
if (lineCount === targetLines) hi = mid
else lo = mid
}
return hi
return { width: Math.ceil(widest), lineCount }
}
```
**Pattern 3: Text around obstacles (Editorial layout)**
```js
import { prepareWithSegments, layoutNextLine } from './pretext-inline.js'
import { prepareWithSegments, layoutNextLine } from '@chenglou/pretext'
function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles) {
const segs = prepareWithSegments(text, font)
let state = null
let cursor = { segmentIndex: 0, graphemeIndex: 0 }
let y = 0
const lines = []
@@ -88,11 +77,11 @@ function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles
}
}
const result = layoutNextLine(segs, state, availWidth, lineHeight)
const result = layoutNextLine(segs, cursor, availWidth)
if (!result) break
lines.push({ text: result.text, width: result.width, x: 0, y })
state = result.state
cursor = result.end
y += lineHeight
}
@@ -102,21 +91,21 @@ function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles
**Pattern 4: Full line-by-line rendering (Complex editorial)**
```js
import { prepareWithSegments, layoutWithLines } from './pretext-inline.js'
import { prepareWithSegments, layoutWithLines } from '@chenglou/pretext'
const segs = prepareWithSegments(text, font)
const { lines, height } = layoutWithLines(segs, containerWidth, lineHeight)
// lines = [{ text, width, x, y }, ...]
// lines = [{ text, width, start, end }, ...] (no x/y: line i sits at y = i * lineHeight)
// Use for Canvas/SVG rendering or custom DOM positioning
for (const line of lines) {
lines.forEach((line, i) => {
const span = document.createElement('span')
span.textContent = line.text
span.style.position = 'absolute'
span.style.left = `${line.x}px`
span.style.top = `${line.y}px`
span.style.left = '0px'
span.style.top = `${i * lineHeight}px`
container.appendChild(span)
}
})
```
### Pretext API Reference
@@ -134,16 +123,17 @@ layout(prepared, maxWidth, lineHeight) → { height, lineCount }
prepareWithSegments(text, font) → handle
Like prepare() but enables line-level APIs below.
layoutWithLines(segs, maxWidth, lineHeight) → { lines: [{text, width, x, y}...], height }
Full line-by-line breakdown. For Canvas/SVG rendering.
layoutWithLines(segs, maxWidth, lineHeight) → { lineCount, height, lines: [{text, width, start, end}...] }
Full line-by-line breakdown (no x/y: line i is at y = i * lineHeight). For Canvas/SVG rendering.
walkLineRanges(segs, maxWidth, onLine) → void
Calls onLine(lineCount, startIdx, endIdx) for each possible layout.
Find minimum width for N lines. For tight-fit containers.
walkLineRanges(segs, maxWidth, onLine) → lineCount
Calls onLine({ width, start, end }) once per line at maxWidth (no text built).
The widest line's width is the tight-fit container width. For chat bubbles.
layoutNextLine(segs, state, maxWidth, lineHeight) → { text, width, state } | null
layoutNextLine(segs, cursor, maxWidth) → { text, width, start, end } | null
Iterator. Different maxWidth per line = text around obstacles.
Pass null as initial state. Returns null when text is exhausted.
Start with cursor { segmentIndex: 0, graphemeIndex: 0 }; pass result.end next.
Returns null when text is exhausted. Takes no lineHeight: advance y yourself.
clearCache() → void
Clears internal measurement caches. Use when cycling many fonts.
+3 -1
View File
@@ -31,7 +31,7 @@ triggers:
# /design-review: Design Audit → Fix → Verify
You are a senior product designer AND a frontend engineer. Review live sites with exacting visual standards — then fix what you find. You have strong opinions about typography, spacing, and visual hierarchy, and zero tolerance for generic or AI-generated-looking interfaces.
You are a senior product designer AND a frontend engineer. Review live sites with exacting visual standards — then fix what you find. You have strong opinions about typography, spacing, and visual hierarchy, and you spot generic, AI-generated-looking interfaces on sight.
## Setup
@@ -324,6 +324,8 @@ If the repo has a `TODOS.md`:
## Additional Rules (design-review specific)
The audit rule "Never read source code" governs the Phases 1-6 audit only; the Phase 8 fix loop reads and edits source.
11. **Clean working tree required.** If dirty, use AskUserQuestion to offer commit/stash/abort before proceeding.
12. **One commit per fix.** Never bundle multiple design fixes into one commit.
13. **Only modify tests when generating regression tests in Phase 8e.5.** Never modify CI configuration. Never modify existing tests — only create new test files.
+12 -13
View File
@@ -122,6 +122,7 @@ ls src/ app/ pages/ components/ 2>/dev/null | head -30
```
```bash
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"; : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
setopt +o nomatch 2>/dev/null || true
ls "$GSTACK_STATE_ROOT"/projects/$SLUG/*office-hours* 2>/dev/null | head -5
@@ -213,14 +214,14 @@ C) "Name" — one-line visual description of this direction
Draw on DESIGN.md, taste memory, and the user's request to make each concept distinct.
**Anti-convergence directive (hard requirement):** Each variant MUST use a different
font family, color palette, and layout approach. If two variants look like siblings
— same typographic feel, overlapping color temperature, comparable layout rhythm —
one of them failed. Regenerate the weaker one with a deliberately different direction.
**Anti-convergence directive:** each concept is a distinct direction. When a DESIGN.md
exists, it decides what varies: keep its fonts and palette and vary layout and
composition. Without one, vary font family, color palette, and layout approach. If two
variants look like siblings, rework the weaker one with a deliberately different direction.
Concrete test: if someone could swap the headline text between two variants without
noticing, they're too similar. Variants should feel like they came from three
different design teams, not the same team at three different coffee levels.
noticing, they're too similar. Variants should feel like separate design teams made
them, not one team on different days.
### Step 3b: Concept Confirmation
@@ -259,10 +260,9 @@ sees what you're evolving from.
**Launch N Agent subagents in a single message** (parallel execution). Use the Agent
tool with `subagent_type: "general-purpose"` and `run_in_background: false` for each
variant (parallel foreground calls in one message still run concurrently; subagents
default to background since Claude Code v2.1.198, and the comparison board needs every
variant's result). Each agent is independent
and handles its own generation, quality check, verification, and retry.
variant (parallel foreground calls in one message still run concurrently, and the
comparison board needs every variant's result). Each agent is independent and handles
its own generation, quality check, verification, and retry.
**Important: $D path propagation.** The `$D` variable from DESIGN SETUP is a shell
variable that agents do NOT inherit. Substitute the resolved absolute path (from the
@@ -298,9 +298,8 @@ For the evolve path, replace step 1 with:
{$D path} evolve --screenshot {_DESIGN_DIR}/current.png --brief "{brief}" --output /tmp/variant-{letter}.png
```
**Why /tmp/ then cp?** In observed sessions, `$D generate --output ~/.gstack/...`
failed with "The operation was aborted" while `--output /tmp/...` succeeded. This is
a sandbox restriction. Always generate to `/tmp/` first, then `cp`.
**Generate to `/tmp/`, then `cp`:** in sandboxed sessions `$D generate --output` under
`~/.gstack/` can abort ("The operation was aborted"), while `/tmp/` works.
### Step 3d: Results
+5 -5
View File
@@ -78,8 +78,8 @@ any documentation. This prevents the "documentation that describes half the feat
## Step 1: Codebase Archaeology (Research Phase)
**This is the most important step.** Do not skip or rush it. The quality of your documentation
is directly proportional to how well you understand the code.
The quality of the documentation depends on how well you understand the code, so this
step carries the most weight.
1. **Map the project structure:**
@@ -380,9 +380,9 @@ Fix any failures before proceeding.
**Redaction scan before commit.** Generated docs frequently contain example
credentials; scan the staged doc content and block on a HIGH credential (a
live-format secret in committed docs is a leak). Example configs belong in
` ```example ` fences won't excuse a live-format secret, but the per-span
placeholder filter passes obvious docs examples (e.g. `AKIAIOSFODNN7EXAMPLE`):
live-format secret in committed docs is a leak). The per-span placeholder
filter passes obvious docs examples (e.g. `AKIAIOSFODNN7EXAMPLE`); a
live-format secret blocks wherever it appears, fenced or not:
```bash
REDACT_VIS=$(~/.claude/skills/gstack/bin/gstack-config get redact_repo_visibility 2>/dev/null)
@@ -101,11 +101,11 @@ preserved them. This skill must NEVER do that.
**If CHANGELOG was modified in this branch**, review the entry for voice:
- **Sell test (Diataxis rubric):** Score each CHANGELOG entry 0-3:
- **1 point** — answers "What changed?" (reference: names the feature/fix)
- **1 point** — answers "Why should I care?" (explanation: user impact, pain removed)
- **1 point** — answers "How do I use it?" (how-to: command, flag, or link to docs)
- Entries scoring <2 need attention, not replacement. Report missing facts or user impact; polish existing wording only. A score of 3 passes all three checks.
- **Sell test (Diataxis):** a good entry answers "What changed?" (names the
feature/fix), "Why should I care?" (user impact, pain removed) and "How do I use
it?" (command, flag, or link to docs). An entry that answers fewer than two of these needs
attention, not replacement: report the missing facts or user impact, and polish
existing wording only.
- Lead with what the user can now **do** — not implementation details.
- "You can now..." not "Refactored the..."
- Flag commit-message-style entries and polish wording without removing facts.
+7 -8
View File
@@ -127,15 +127,14 @@ Use the install type and directory detected in Step 2:
**For git installs** (global-git, local-git):
Fast-forward first (#2517) — the same policy the session-update auto-upgrade
Fast-forward first — the same policy the session-update auto-upgrade
uses. `--autostash` carries local edits over the pull; render-footprint dirt
is discarded first because it is regenerable and poisons stashes (#2569):
is discarded first because it is regenerable and poisons stashes:
```bash
cd "$INSTALL_DIR"
# Discard render-footprint dirt (#2569): pre-v1.67 gbrain-enabled installs
# ran gen:skill-docs:user IN PLACE, leaving generated SKILL.md / sections
# files permanently modified. They are regenerable (setup re-renders to
# ~/.gstack/render), so discarding is lossless.
# Discard render-footprint dirt: older gbrain-enabled installs rendered
# generated SKILL.md / sections files IN PLACE. They are regenerable (setup
# re-renders to ~/.gstack/render), so discarding is lossless.
git checkout -- 'SKILL.md' '*/SKILL.md' '*/sections/*.md' 2>/dev/null || true
git fetch origin
PRE_UPGRADE_COMMIT=$(git rev-parse HEAD)
@@ -153,7 +152,7 @@ On `SETUP_FAILED`, STOP; keep user changes and report the recovery commit. There
**Fallback (ff-only refused — local commits or divergence).** `git reset
--hard` DESTROYS things: a clean tree with unpushed local commits still loses
those commits. Gate it (#2517):
those commits. Gate it:
1. Run `git status --porcelain` and `git rev-list origin/main..HEAD --oneline`
in `$INSTALL_DIR`.
@@ -283,7 +282,7 @@ for how to add new migrations.
A browse daemon started before the upgrade keeps serving the OLD binary's code
until it is stopped — it survives `git reset --hard` and `./setup` because the
running process holds the old executable (#2551). Always run this step, using
running process holds the old executable. Always run this step, using
the install directory detected in Step 2.
```bash
+22 -17
View File
@@ -70,7 +70,7 @@ command -v knip >/dev/null 2>&1 && echo "DEADCODE: knip"
# Shell linting
command -v shellcheck >/dev/null 2>&1 && ls *.sh scripts/*.sh bin/*.sh 2>/dev/null | head -1 | xargs -I{} echo "SHELL: shellcheck"
# GBrain presence (D6) — only report as a dimension if gbrain is actually
# GBrain presence — only report as a dimension if gbrain is actually
# set up; otherwise skip so machines without gbrain aren't penalized.
if command -v gbrain >/dev/null 2>&1 && [ -f "$HOME/.gbrain/config.json" ]; then
echo "GBRAIN: gbrain doctor --json (wrapped in timeout 5s)"
@@ -177,7 +177,7 @@ Score each category on a 0-10 scale using this rubric:
| Tests | 28% | All pass (exit 0) | >95% pass | >80% pass | <=80% pass |
| Dead code | 13% | Clean (exit 0) | <5 unused exports | <20 unused | >=20 unused |
| Shell lint | 9% | Clean (exit 0) | <5 issues | >=5 issues | N/A (skip) |
| GBrain (D6) | 10% | doctor=ok, queue<10, pushed <24h | doctor=warnings OR queue<100 OR pushed <72h | doctor broken OR queue>=100 OR pushed >=72h | N/A (gbrain not installed) |
| GBrain | 10% | doctor=ok, queue<10, pushed <24h | doctor=warnings OR queue<100 OR pushed <72h | doctor broken OR queue>=100 OR pushed >=72h | N/A (gbrain not installed) |
**Parsing tool output for counts:**
Use the complete captured output, not the displayed tail. A zero match count cannot
@@ -189,21 +189,27 @@ make a non-zero checker exit `CLEAN`; retain its failure and diagnostic output.
- **knip:** Count lines reporting unused exports, files, or dependencies.
- **shellcheck:** Count distinct findings (lines starting with "In ... line").
**Composite score:**
```
composite = (typecheck_score * 0.22) + (lint_score * 0.18) + (test_score * 0.28) + (deadcode_score * 0.13) + (shell_score * 0.09) + (gbrain_score * 0.10)
```
**Composite score:** compute it in code, not by hand. Pass each category's
score, or `null` when it was skipped (tool not available — includes GBrain when
gbrain is not installed); skipped weight is redistributed proportionally among
the scored categories:
If a category is skipped (tool not available — includes GBrain when gbrain
is not installed), redistribute its weight proportionally among the
remaining categories.
```bash
bun -e '
const w = { typecheck: 0.22, lint: 0.18, test: 0.28, deadcode: 0.13, shell: 0.09, gbrain: 0.10 };
const s = { typecheck: <N|null>, lint: <N|null>, test: <N|null>, deadcode: <N|null>, shell: <N|null>, gbrain: <N|null> };
const scored = Object.keys(w).filter(k => s[k] !== null);
const total = scored.reduce((t, k) => t + w[k], 0);
console.log(scored.length ? "COMPOSITE: " + (scored.reduce((t, k) => t + s[k] * w[k], 0) / total).toFixed(1) : "COMPOSITE: N/A");
'
```
Always report coverage: list the checked categories and the unavailable categories
with their reasons. Label a numeric composite with skipped categories as **partial
coverage**. If zero checks executed, report **N/A — no checks ran**, do not compute
a numeric composite, and skip numeric history persistence and trend calculation.
**GBrain sub-score computation (D6):**
**GBrain sub-score computation:**
```
doctor_component: 10 if `gbrain doctor --json | jq -r .status` == "ok";
@@ -298,9 +304,8 @@ Fields:
- `typecheck`, `lint`, `test`, `deadcode`, `shell`, `gbrain` -- individual category scores (integer 0-10)
- `duration_s` -- total time for all tools in seconds
If a category was skipped, set its value to `null`. Pre-D6 history entries
won't have a `gbrain` field — treat them as `null` for trend comparison
and start new tracking from the first post-D6 run.
If a category was skipped, set its value to `null`. Treat a history entry
with no `gbrain` field as `null` for that category.
---
@@ -358,12 +363,12 @@ Prioritize suggestions by impact (weight * score deficit):
```
RECOMMENDATIONS (by impact)
============================
1. [HIGH] Fix 2 failing tests (Tests: 9/10, weight 30%)
Run: bun test --verbose to see failures
2. [MED] Address 12 lint warnings (Lint: 6/10, weight 20%)
1. [HIGH] Address 12 lint warnings (Lint: 6/10, weight 18%)
Run: biome check . --write to auto-fix
3. [LOW] Remove 4 unused exports (Dead code: 7/10, weight 15%)
2. [MED] Remove 4 unused exports (Dead code: 7/10, weight 13%)
Run: knip --fix to auto-remove
3. [LOW] Fix 2 failing tests (Tests: 9/10, weight 28%)
Run: bun test --verbose to see failures
```
Rank by `weight * (10 - score)` descending. Only show categories below 10.
+2 -3
View File
@@ -118,9 +118,8 @@ If any learnings come back, name which one applies to your investigation in one
After forming your root cause hypothesis, lock edits to the affected module to prevent scope creep.
```bash
# $HOME-anchored like the careful/freeze frontmatter hooks (#1871): frontmatter
# hooks and early skill bash run before any runtime var like CLAUDE_SKILL_DIR
# exists, so a ${CLAUDE_SKILL_DIR}-relative path silently never resolves (#2469).
# $HOME-anchored like the careful/freeze frontmatter hooks: early skill bash runs
# before CLAUDE_SKILL_DIR exists, so a relative path would never resolve.
_FREEZE_SCRIPT="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
[ -x "$_FREEZE_SCRIPT" ] && echo "FREEZE_AVAILABLE" || echo "FREEZE_UNAVAILABLE"
```
+10 -6
View File
@@ -52,7 +52,9 @@ Each item is reverted only after AskUserQuestion confirmation:
`DebugBridgeManager.shared.start()`.
3. Any standalone `// @Snapshotable` generator marker comments on the
canonical app state class.
4. Generated `StateAccessor.swift` files anywhere under the app source.
4. The generated `DebugBridge/` package directory and `DebugBridgeGenerated/`
(accessors and `.gstack-version`) that `gstack-ios-qa-regen` wrote under
the app source, plus any other generated `StateAccessor.swift` files.
5. The `gstack-ios-qa.token` file under `NSTemporaryDirectory()` on the
device (best-effort — only works if device is connected when /ios-clean
runs).
@@ -67,8 +69,8 @@ Each item is reverted only after AskUserQuestion confirmation:
1. Glob for `import DebugBridge` across the app source.
2. Glob for `#if DEBUG ... DebugBridgeManager` blocks.
3. Glob for `// Auto-generated state accessor` headers in
`StateAccessor.swift` files.
3. Grep `StateAccessor.swift` files for the generator header
`// AUTO-GENERATED — DO NOT EDIT. Regenerate with /ios-sync.`
4. Parse `Package.swift` for the DebugBridge dependency entry.
5. Show the user what's about to be removed (file list + line counts).
AskUserQuestion: proceed, dry-run, or abort.
@@ -79,9 +81,11 @@ For each item the user approved:
1. Use Edit tool to strip the import + the `#if DEBUG` block (keep the
surrounding code intact).
2. Use Edit tool to remove the `.package(url:...DebugBridge...)` entry
from `Package.swift` and any `targets` referencing `"DebugBridge"`.
3. Delete generated `StateAccessor.swift` files.
2. Use Edit tool to remove the local `.package(path: ...DebugBridge...)`
entry from `Package.swift` and every target dependency on a
`DebugBridge*` product (`DebugBridgeUI`, `DebugBridgeCore`, `DebugBridgeTouch`).
3. Delete the generated `DebugBridge/` and `DebugBridgeGenerated/`
directories and any other generated `StateAccessor.swift` files.
4. Run `xcodebuild -scheme <SchemeName> -destination 'platform=iOS,id=<UDID>'
build install -configuration Release` to verify Release builds without
the bridge. If it fails on a missing DebugBridge symbol, the removal
+6 -4
View File
@@ -82,12 +82,14 @@ user pick the one to fix.
## Phase 5: Add regression test
Write a test in `test/fixtures/ios-fix/<bug-slug>.test.ts` that:
Write a regression test with the project's own test runner and conventions
(the test command in CLAUDE.md; if it has none, ask the user and record the
answer there). The test:
1. Loads the pre-bug snapshot.
2. Restores it via `POST /state/restore`.
3. Asserts the post-fix behavior on a real device (gated
`GSTACK_HAS_IOS_DEVICE=1`, periodic tier).
3. Asserts the post-fix behavior on a real device, skipped when no device is
connected.
Commit the snapshot fixture + test file alongside the fix.
@@ -96,6 +98,6 @@ Commit the snapshot fixture + test file alongside the fix.
| Symptom | Action |
|---|---|
| 3 iterations, bug still present | STOP, report to user with current best hypothesis |
| `409 schema_mismatch` on /state/restore after rebuild | Re-codegen accessors (`swift run gen-accessors`), re-snapshot |
| `409 schema_mismatch` on /state/restore after rebuild | Regenerate accessors with `/ios-sync` (`gstack-ios-qa-regen`), re-snapshot |
| Device disconnects mid-fix | Daemon auto-reconnects; resume from Phase 4 |
| Build fails | Revert Swift edits; investigate compile error before re-applying fix |
+1 -2
View File
@@ -41,8 +41,7 @@ This skill regenerates the relevant artifacts in place.
**Templates live in upstream gstack.** The installed
`gstack-ios-qa-regen` launcher resolves its own gstack root and copies only
the supported bridge files from `ios-qa/templates/`. The fork's HTTP-fetch
and wildcard-copy patterns are gone.
the supported bridge files from `ios-qa/templates/`.
## Phase 1: Detect installed version
+7 -15
View File
@@ -114,7 +114,7 @@ Fence info-string options:
```
```mermaid title="Auth flow" ← caption + aria-label
```mermaid render=false ← keep it as a code block (today's behavior)
```mermaid render=false ← keep it as a code block
```mermaid page=landscape ← force this diagram onto a landscape page
```mermaid page=portrait ← veto auto-landscape for this diagram
```
@@ -215,18 +215,11 @@ Metadata:
--date "..." Date for cover (defaults to today)
```
## When Claude should run it
## When to run it
Watch for markdown-to-PDF intent. Any of these patterns → run `$P generate`:
- "Can you make this markdown a PDF"
- "Export it as a PDF"
- "Turn this letter into a PDF"
- "I need a PDF of the essay"
- "Print this as a PDF for me"
If the user has a `.md` file open and says "make it look nice", propose
`$P generate --cover --toc` and ask before running.
Run `$P generate` when the user wants markdown as a PDF. If the user has a `.md`
file open and says "make it look nice", propose `$P generate --cover --toc` and ask
before running.
## Debugging
@@ -237,9 +230,8 @@ If the user has a `.md` file open and says "make it look nice", propose
- Diagram shows a red "failed to render" block → the parse error is printed in
the block. If EVERY diagram fails with "diagram renderer:", the browser went
away mid-run (Aside closed, or the fallback daemon died).
- Fragmented text on copy-paste → highlight.js output (Phase 4). Retry with
`--no-syntax` once that flag exists. For now, remove fenced code blocks
and regenerate.
- Fragmented text on copy-paste → remove fenced code blocks and regenerate
(no flag turns code styling off).
- Paged.js timeout → probably no headings in the markdown. Drop `--toc`.
- "[remote image blocked]" placeholder in the output → add `--allow-network`
(understand you're giving the markdown file permission to fetch from its
+4 -5
View File
@@ -41,8 +41,7 @@ gbrain:
render_as: "## Prior office-hours sessions in this repo"
- id: builder-profile
kind: filesystem
glob: "~/.gstack/builder-profile.jsonl"
tail: 1
glob: "~/.gstack/developer-profile.json"
render_as: "## Your builder profile snapshot"
- id: design-doc-history
kind: filesystem
@@ -237,9 +236,9 @@ Use AskUserQuestion to confirm. If the user disagrees with a premise, revise und
---
## Phase 4: Alternatives Generation (MANDATORY)
## Phase 4: Alternatives Generation
Produce 2-3 distinct implementation approaches. This is NOT optional.
Produce 2-3 distinct implementation approaches.
For each approach:
```
@@ -319,7 +318,7 @@ Append one JSON line with these fields (substitute actual values from this sessi
The session entry is appended to `developer-profile.json`'s `sessions[]` array. A second
session entry with `mode: "resources"` is appended via `--log-session` after resource
selection in Phase 6 Beat 3.5.
selection in Phase 6 (Founder Resources).
---
@@ -18,7 +18,7 @@ If `$PRIOR` exists, the new doc gets a `Supersedes:` field referencing it. This
Write to `<PROJECT_DIR>/{user}-{branch}-design-{datetime}.md` (`PROJECT_DIR` printed by the setup block above).
**Repo copy (dual-write, #703 + #2000).** When the session runs inside a git
**Repo copy (dual-write).** 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
@@ -35,7 +35,7 @@ what teammates and plan reviews read. Rules:
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
**Decision-record concision.** 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
@@ -313,9 +313,10 @@ Design trajectory with interpretation:
"You started this as a side project. But you've named specific users, pushed back when challenged, and your designs keep getting sharper each time. I don't think this is a side project anymore. Have you thought about whether this could be a company?"
This must feel earned, not broadcast. If the evidence doesn't support it, skip entirely.
**Builder Journey Summary** (session 5+): Auto-generate `~/.gstack/builder-journey.md`
with a narrative arc (not a data table). The arc tells the STORY of their journey in
second person, referencing specific things they said across sessions. Then open it:
**Builder Journey Summary** (session 5+): Auto-generate `builder-journey.md` in the
gstack state root (`$GSTACK_STATE_ROOT`, resolved by the block below) with a narrative
arc (not a data table). The arc tells the STORY of their journey in second person,
referencing specific things they said across sessions. Then open it:
```bash
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"; : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
open "$GSTACK_STATE_ROOT/builder-journey.md"
@@ -333,7 +334,7 @@ The data speaks. No pitch needed.
Full accumulated signal summary from the profile.
Auto-generate updated `~/.gstack/builder-journey.md` with narrative arc. Open it.
Auto-generate updated `$GSTACK_STATE_ROOT/builder-journey.md` with narrative arc (resolve the state root and open it as in the regular tier).
Then proceed to Founder Resources below.
@@ -341,7 +342,7 @@ Then proceed to Founder Resources below.
### Founder Resources (all tiers)
**Standing opt-out check (#538) — run FIRST:**
**Standing opt-out check — run FIRST:**
```bash
~/.claude/skills/gstack/bin/gstack-config get founder_resources 2>/dev/null || echo "true"
@@ -509,13 +510,14 @@ D) Not now — I'll run a review later
Net: 15 minutes of structured review now against rework risk later.
On the user's SELECTION of A/B/C (not on invocation success), log the handoff, then invoke
the chosen skill via the **Skill tool** (it auto-discovers the design doc):
the chosen skill via the **Skill tool** (it auto-discovers the design doc). In both log
commands, replace `SESSION_ID` with the value the skill-start output echoed:
```bash
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type handoff --skill office-hours --outcome accepted --session-id "$_SESSION_ID" 2>/dev/null || true
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type handoff --skill office-hours --outcome accepted --session-id "SESSION_ID" 2>/dev/null || true
```
On D, log declined and stop:
```bash
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type handoff --skill office-hours --outcome declined --session-id "$_SESSION_ID" 2>/dev/null || true
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type handoff --skill office-hours --outcome declined --session-id "SESSION_ID" 2>/dev/null || true
```
The design doc at `~/.gstack/projects/` is automatically discoverable by downstream skills — they will read it during their pre-review system audit.
+27 -27
View File
@@ -42,9 +42,11 @@ this skill in plain English and you interpret. Never require subcommand syntax.
Shortcuts exist (`profile`, `vibe`, `stats`, etc.) but users don't have to
memorize them.
**v1 scope (observational):** typed question registry, per-question explicit
preferences, question logging, dual-track profile (declared + inferred),
plain-English inspection. No skills adapt behavior based on the profile yet.
**Scope:** typed question registry, per-question explicit preferences,
question logging, dual-track profile (declared + inferred), plain-English
inspection. Per-question preferences take effect: the question-preference hook
auto-decides `never-ask` questions (see Recent auto-decisions). The profile
itself never changes a skill's defaults.
Canonical reference: `docs/designs/PLAN_TUNING_V0.md`.
@@ -66,7 +68,7 @@ Each gate is guarded by a marker so the user is prompted at most once per choice
`~/.gstack/developer-profile.json`'s `declared` object is empty AND
`~/.gstack/.declared-setup-prompted` is missing → run `5-Q setup` below.
Touch the marker after setup completes OR is declined.
3. **Dream-cycle gate (Layer 8 / cathedral T10/T11).** If
3. **Dream-cycle gate.** If
`~/.gstack/projects/<slug>/distillation-proposals.json` exists AND has
`applied_at` missing on any proposal → run `Dream cycle review` below.
Marker: each proposal carries its own `applied_at` so re-firing this
@@ -130,8 +132,9 @@ explicit.
> Question tuning is off. gstack can learn which of its prompts you find
> valuable vs noisy — so over time, gstack stops asking questions you've
> already answered the same way. It takes about 2 minutes to set up your
> initial profile. v1 is observational: gstack tracks your preferences
> and shows you a profile, but doesn't silently change skill behavior yet.
> initial profile. Questions you mark never-ask are answered with
> gstack's recommendation (one-way doors still ask); your profile is
> shown to you, not used to change defaults.
> Logs stay local (`~/.gstack/projects/<slug>/question-log.jsonl`).
>
> RECOMMENDATION: Enable and set up your profile. Completeness: A=9/10.
@@ -146,7 +149,7 @@ explicit.
> (skills adapting to your steering style). Enabling logs every
> AskUserQuestion outcome locally to
> `~/.gstack/projects/<slug>/question-log.jsonl` — nothing leaves your
> machine. v1 is observational only.
> machine. Questions auto-decide only where you set never-ask.
>
> RECOMMENDATION: Enable and set up your profile. Completeness: A=9/10.
>
@@ -372,7 +375,7 @@ boil-the-ocean than 0.5 suggests", "I've gotten more careful about architecture"
"bump detail_preference up".
**Always confirm before writing.** Free-form input + direct profile mutation
is a trust boundary (Codex #15 in the design doc).
is a trust boundary.
1. Parse the user's intent. Translate to `(dimension, new_value)`.
- "more boil-the-ocean" → `scope_appetite` → pick a value 0.15 higher than
@@ -426,9 +429,8 @@ the user decides whether declared is wrong or behavior is wrong.
## Stats
Cathedral T13 surfaces: host-aware breakdown (claude hook vs codex import
vs agent-enriched), marked vs hash-only, auto-decided count, and dream
cycle cost-to-date.
Shows: host-aware breakdown (claude hook vs codex import vs agent-enriched),
marked vs hash-only, auto-decided count, and dream cycle cost-to-date.
```bash
~/.claude/skills/gstack/bin/gstack-question-preference --stats
@@ -471,9 +473,8 @@ echo '---DISTILL---'
Present as a compact summary with plain-English calibration status ("5 more
events across 2 more skills and you'll be calibrated" or "you're calibrated").
Surface the source breakdown so the user can see capture is real (Codex
correction — without source columns, the cathedral's "before:0 / after:>0"
claim is invisible).
Surface the source breakdown so the user can see which capture paths are
actually logging.
---
@@ -510,9 +511,9 @@ Run `gstack-question-preference --write '{"question_id":"<id>","preference":
## Audit unmarked questions
Top N hash-only question_ids by frequency. These are AUQ fires the cathedral
hook captured but cannot enforce against (no `<gstack-qid:foo>` marker in
the skill template — D18 progressive markers). Surfacing them drives marker
Top N hash-only question_ids by frequency. These are AUQ fires the
preference hook captured but cannot enforce against (no `<gstack-qid:foo>`
marker in the skill template). Surfacing them drives marker
adoption: high-traffic unmarked questions are the next candidates to retrofit.
```bash
@@ -592,20 +593,19 @@ invokes via `/plan-tune distill` / `dream`.
~/.claude/skills/gstack/bin/gstack-distill-apply --proposal N
```
4. **On decline**: skip without marking. User can re-decide later (the
proposal stays in the file). To dismiss permanently, manually clear:
`gstack-distill-apply --proposal N --dismiss` (not implemented in T11;
for now, regenerate via next distill run with corrected free-text).
4. **On decline**: skip without marking. The proposal stays in the file and
the gate offers it again. `gstack-distill-apply` has no dismiss flag; the
next distill run overwrites the proposals file.
5. **gbrain integration.** When `mcp__gbrain__*` tools are available in
this session:
- On `memory-nugget` apply: `mcp__gbrain__put_page` with the nugget +
`mcp__gbrain__extract_facts` + `mcp__gbrain__add_tag` per the cathedral
plan D9 routing. Then pass `--gbrain-published true` to the bin so
`mcp__gbrain__extract_facts` + `mcp__gbrain__add_tag`. Then pass
`--gbrain-published true` to the bin so
the proposals file records the mirror.
- When gbrain isn't configured (no MCP tools), the bin's local file
write is the durable source-of-truth and the PreToolUse hook reads it
via Layer 8 memory injection.
via memory injection.
---
@@ -650,9 +650,9 @@ For background mode (e.g., the user wants to keep working):
- **One-way doors override never-ask.** Even with a never-ask preference, the
binary returns ASK_NORMALLY for destructive/architectural/security questions.
Surface the safety note to the user whenever it fires.
- **No behavior adaptation in v1.** This skill INSPECTS and CONFIGURES. No
skills currently read the profile to change defaults. That's v2 work, gated
on the registry proving durable.
- **The profile never changes defaults.** This skill inspects and configures.
Per-question preferences can auto-decide questions; the declared/inferred
profile is advisory only.
- **Completion status:**
- DONE — did what the user asked (enable/inspect/set/update/disable)
- DONE_WITH_CONCERNS — action taken but flagging something (e.g., "your
+5 -12
View File
@@ -238,19 +238,12 @@ Healthy-contract commits use `test(qa): regression test for {contract}`.
### 8f. Self-Regulation (STOP AND EVALUATE)
Every 5 fixes (or after any revert), compute the WTF-likelihood:
Every 5 fixes (or after any revert), judge the WTF-likelihood: is the loop now doing
more harm than good? Reverts, edits to unrelated files, fixes spanning many files, a
long run of fixes, and a queue of only Low issues all raise it.
```
WTF-LIKELIHOOD:
Start at 0%
Each revert: +15%
Each fix touching >3 files: +5%
After fix 15: +1% per additional fix
All remaining Low severity: +10%
Touching unrelated files: +20%
```
**If WTF > 20%:** STOP immediately. Show the user what you've done so far. Ask whether to continue.
**If it is high** (two reverts or one unrelated-file edit is enough): STOP immediately.
Show the user what you've done so far. Ask whether to continue.
**Hard cap: 50 fixes.** After 50 fixes, stop regardless of remaining issues.
+6 -8
View File
@@ -23,9 +23,7 @@ gbrain:
context_queries:
- id: prior-retros
kind: filesystem
# #2552: /retro writes .context/retros/*.json (repo-local; see the save
# step below) — the old ~/.gstack/.../retros/*.md glob matched a
# directory and extension nothing ever writes, so this query was dead.
# /retro writes .context/retros/*.json (repo-local; see Step 13).
glob: ".context/retros/*.json"
sort: mtime_desc
limit: 5
@@ -193,7 +191,7 @@ Use the analyzed ref in the commit-count label (not always `main`). Test health
| Test Health | N test files · M changed this period · K regression test commits |
Lead with user-visible features, then commit and logical-SLOC metrics; raw LOC
is only context, not impact (PLAN_TUNING_V1.md, Workstream C).
is only context, not impact.
Then show a **per-author leaderboard** immediately below, from the `AUTHOR:` lines:
@@ -291,7 +289,7 @@ For each contributor (including the current user), the `AUTHOR:` line carries co
- **Praise** (1-2 specifics): cite commits and what was good, not generic praise.
- **Opportunity for growth** (1 specific): tie an actionable suggestion to data, not criticism. Step 14 supplies examples.
**If only one contributor (solo repo):** Skip the team breakdown and proceed as before — the retro is personal.
**If only one contributor (solo repo):** Skip the team breakdown — the retro is personal.
**Co-author credit:** `COAUTHOR:` lines carry human `Co-Authored-By:` trailers — credit those authors for the commit alongside the primary author. AI co-authors (e.g., `noreply@anthropic.com`) are counted in `AI_ASSISTED_COMMITS` instead — track "AI-assisted commits" as a separate metric, never as a team member.
@@ -747,13 +745,13 @@ When the user runs `/retro compare` (or `/retro compare 14d`):
- **Praise should feel like something you'd actually say in a 1:1** — specific, earned, genuine
- **Growth suggestions should feel like investment advice** — "this is worth your time because..." not "you failed at..."
- Never compare teammates against each other negatively. Each person's section stands on its own.
- Keep total output around 3000-4500 words (slightly longer to accommodate team sections)
- Size the report to the window and the team: every section carries specific evidence, and a section with nothing to say is cut, not padded
- Use markdown tables and code blocks for data, prose for narrative
- Output directly to the conversation — do NOT write to filesystem (except the `.context/retros/` JSON snapshot)
- Output the narrative directly to the conversation; the only report file is the JSON snapshot (`.context/retros/` for repo retros, `$GSTACK_STATE_ROOT/retros/` for global)
## Important Rules
- ALL narrative output goes directly to the user in the conversation. The ONLY file written is the `.context/retros/` JSON snapshot.
- ALL narrative output goes directly to the user in the conversation. Besides the JSON snapshot, only the learnings and gbrain capture steps write anything.
- The metrics script analyzes `origin/<default>` (not local main which may be stale); when `RETRO_REF` says otherwise, disclose it
- Display all timestamps in the user's local timezone (do not override `TZ`)
- If `COMMITS: 0`, say so and suggest a different window
+4 -7
View File
@@ -35,9 +35,6 @@ checks, and deploy status commands — then persist everything to CLAUDE.md.
After this runs once, `/land-and-deploy` reads CLAUDE.md and skips detection entirely.
## User-invocable
When the user types `/setup-deploy`, run this skill.
## Instructions
### Step 1: Check existing configuration
@@ -108,7 +105,7 @@ If `render.yaml` detected:
5. Set health check: the inferred URL
Ask the user to confirm. Render uses auto-deploy from the connected git branch — after
merge to main, Render picks it up automatically. The "deploy wait" in /land-and-deploy
merge to the default branch, Render picks it up automatically. The "deploy wait" in /land-and-deploy
should poll the Render URL until it responds with the new version.
#### Vercel
@@ -117,7 +114,7 @@ If vercel.json or .vercel detected:
1. Check for `vercel` CLI: `which vercel 2>/dev/null`
2. If installed: `vercel ls --prod 2>/dev/null | head -3`
3. Vercel deploys automatically on push — preview on PR, production on merge to main
3. Vercel deploys automatically on push — preview on PR, production on merge to the default branch
4. Set health check: the production URL from vercel project settings
Ask for the production URL if not available from the CLI, then confirm it before writing.
@@ -149,7 +146,7 @@ If nothing detected:
Use AskUserQuestion to gather the information:
1. **How are deploys triggered?**
- A) Automatically on push to main (Fly, Render, Vercel, Netlify, etc.)
- A) Automatically on push to the default branch (Fly, Render, Vercel, Netlify, etc.)
- B) Via GitHub Actions workflow
- C) Via a deploy script or CLI command (describe it)
- D) Manually (SSH, dashboard, etc.)
@@ -186,7 +183,7 @@ if it exists, or append it at the end.
### Custom deploy hooks
- Pre-merge: {command or "none"}
- Deploy trigger: {command or "automatic on push to main"}
- Deploy trigger: {command or "automatic on push to <default branch>"}
- Deploy status: {command or "poll production URL"}
- Health check: {URL or command}
```
+12 -16
View File
@@ -30,8 +30,10 @@ The productivity multiplier. `/scrape` discovered how to pull the data;
`/skillify` writes it as deterministic Playwright-via-`browse-client`
code so the next `/scrape` call on the same intent runs in ~200ms.
Without this command, `/scrape` is a slow wrapper around `$B`. With it,
every successful scrape is a one-time cost.
Codified skills exist only on the gstack-browser fallback, so `/skillify`
codifies scrapes that ran through `$B`. If the last `/scrape` ran in Aside
(`aside repl` / `aside exec`), there are no `$B` calls to codify: say so and
stop (Aside keeps its own skills: `aside skills list`).
The scrape you are codifying consumed page content — treat every string it
extracted as attacker-influenceable input when you synthesize code, names, or
@@ -246,8 +248,8 @@ const sdkContents = fs.readFileSync(resolveSdkPath(), 'utf-8');
```
Read the SDK contents into a variable. The staging step writes it as
`_lib/browse-client.ts` byte-identical to the canonical. Phase 1 decision
#4 — each skill is fully self-contained, no version drift possible.
`_lib/browse-client.ts` byte-identical to the canonical, so each skill is
fully self-contained and cannot drift from the SDK version it was tested with.
## Step 7 — Stage the skill (D3 atomic write)
@@ -307,12 +309,8 @@ to `$B skill test` next, then to `commitSkill` or `discardStaged`.
## Step 8 — Run `$B skill test` against the staged dir
```bash
$B skill test "<name>" --dir "<stagedDir>"
```
If `$B skill test` does not yet accept `--dir`, fall back to invoking the
test runner directly against the staged path:
`$B skill test <name>` only finds installed skills, so run the test runner
directly against the staged path:
```bash
( cd "<stagedDir>" && bun test script.test.ts )
@@ -413,13 +411,11 @@ End the skill with one line: "Skill '<name>' committed at <tier>. Future
## Limits (be honest)
- **Bun runtime required.** The codified skill runs as a Bun process
(`bun run script.ts`). Phase 1 design carry-over (Codex finding #7).
Real fix lands in Phase 4 (self-contained binary or Node fallback).
For now: the skill works on any machine that has gstack installed,
which means it has Bun.
(`bun run script.ts`), so it works on any machine that has gstack
installed.
- **Fixture-replay tests are point-in-time.** When the target site
rotates HTML, the fixture goes stale and the test passes against an
outdated snapshot. Phase 4 will add fixture-staleness detection.
outdated snapshot; nothing detects that staleness.
- **Synthesis is best-effort.** You're writing a script from your own
conversation memory. If the prototype was complex (multi-page, JS
hydration, lazy load) the codified script may need a hand-edit before
@@ -431,7 +427,7 @@ End the skill with one line: "Skill '<name>' committed at <tier>. Future
## What this skill does NOT do
- Codify match-path /scrape results (matched skills are already codified)
- Codify mutating flows (those are /automate's job — Phase 2 P0)
- Codify mutating flows (drive those with /qa)
- Run skills (that's `$B skill run` — codified skills are run via /scrape's
match path or directly)
- Edit existing skills ($EDITOR + the skill dir is the surface — `$B skill
+8 -12
View File
@@ -144,13 +144,12 @@ Ask until you can answer:
Do NOT proceed until scope is locked.
### Phase 3: Technical Interrogation (HARD requirement: read code first)
### Phase 3: Technical Interrogation (read code first)
**Mandatory:** Before asking ANY Phase 3 question, you MUST read at least one
piece of evidence from the codebase via Grep, Glob, or Read. This is the magical
moment for the user: they see you grounded in their actual code, not generic
checklists. Do NOT skip. Do NOT ask "what file should I look at?" first — find
it yourself.
Before asking any Phase 3 question, read at least one piece of evidence from the
codebase via Grep, Glob, or Read, and cite it. This is the magical moment for the
user: they see you grounded in their actual code, not generic checklists. Find the
relevant file yourself rather than asking which one to look at.
Mapping the user's request to evidence:
@@ -176,9 +175,6 @@ Then ask about whichever categories apply (skip ones that clearly don't):
- **Infrastructure** — IaC changes, secrets, cost impact
- **Testing** — how to test at each layer, regression risk
Don't ask questions you can answer by reading the code. Read first, then ask
the questions whose answers aren't in the code.
### Phase 4: Draft Review
Present a full draft issue and ask: **"Does this accurately capture what you want?
@@ -334,7 +330,7 @@ this? Even "revert the PR" is worth stating explicitly.
## Issue Structure Templates
### Standard Issues (default; also used for `--bug`, `--feature`, `--refactor` framings)
### Standard Issues (default; bug, feature and refactor framings adapt within it)
```
## Context
@@ -482,8 +478,8 @@ Add to the standard template:
a `/spec` archive (frontmatter `spec_issue_number: <N>`) AND the PR delivers
the full spec (acceptance criteria checked off per `/ship`'s existing
plan-completion gate), `/ship` adds `Closes #<N>` to the PR body so merging
auto-closes the source issue. Conditional — partial PRs do NOT auto-close
(codex F4). Branch-name inference is NOT used (codex F3).
auto-closes the source issue. Conditional — partial PRs do not auto-close, and
the source issue is never inferred from the branch name.
---
+10 -31
View File
@@ -84,12 +84,6 @@ Missing/broken CLI, authentication failure, timeout, refusal, nonzero exit, inva
Max 3 dispatches total. If still <7 after iter 3, AskUserQuestion same options.
**Audit-sink invariant:** When the redaction gate fires, the raw spec must NOT
be persisted anywhere downstream (no archive write, no transcript log). The
`spec-quality-gate-secret-sink.test.ts` enforces this.
### Phase 5: File the Spec (+ optional --execute)
Produce the final spec using the structure defined below. Use `--audit` to
@@ -148,7 +142,7 @@ is consumed by `/ship` for auto-close.
{{REDACT_INVOCATION_BLOCK:pre-archive:brief}}
**D2 — sanitized body to the archive.** If auto-redact fired, the `<body>` below
**Sanitized body to the archive.** If auto-redact fired, the `<body>` below
MUST be the sanitized body (`$REDACT_FILE`), not the original draft — one body for
all sinks. The user's on-disk source draft keeps the original.
@@ -173,8 +167,6 @@ spec_branch: $(git branch --show-current 2>/dev/null || echo unknown)
spec_plan_mode: ${GSTACK_PLAN_MODE:-unset}
spec_executed: ${WILL_EXECUTE:-false}
spec_worktree_path:
ttfc_ms: ${TTFC_MS:-}
tthw_ms: ${TTHW_MS:-}
---
# <title>
@@ -189,14 +181,14 @@ The PID suffix and atomic rename prevent collisions when two `/spec` invocations
run in the same second.
**Sync default:** `/specs/` is auto-excluded from the artifacts-sync allowlist —
archives stay local unless the user opts in via `--sync-archive` (privacy default
per codex review). If `--sync-archive` is passed, append `/specs/<archive_name>`
archives stay local unless the user opts in via `--sync-archive` (privacy default).
If `--sync-archive` is passed, append `/specs/<archive_name>`
to the artifacts-sync allowlist (or symlink into the synced dir, depending on
implementation).
#### Spawn the agent (`--execute` path only)
**E2 dirty-worktree gate:**
**Dirty-worktree gate:**
```bash
DIRTY=$(git status --porcelain 2>/dev/null)
@@ -209,7 +201,7 @@ If `$DIRTY` is non-empty, AskUserQuestion:
- B) Stash and restore (auto-stash now, restore after spawn returns)
- C) Cancel spawn (stop here; issue stays filed, archive stays written)
**E2 TOCTOU re-check (F1):** After the user answers, IMMEDIATELY re-run
**TOCTOU re-check:** After the user answers, IMMEDIATELY re-run
`git status --porcelain` before any worktree operation. If state diverged
from the answer, re-prompt the AskUserQuestion. The check must happen INSIDE
the spawn workflow, not be cached from earlier.
@@ -222,21 +214,21 @@ git stash push -u -m "spec-execute-auto-$$" # untracked YES, ignored NO
STASH_REF="spec-execute-auto-$$"
```
F2 stash policy: `-u` includes untracked; we deliberately do NOT use `--all`
Stash policy: `-u` includes untracked; we deliberately do NOT use `--all`
because ignored files (build artifacts, .env caches) are usually local-by-design
and should stay in the current worktree.
If C: print "Cancelled spawn. Issue filed: $ISSUE_URL, archive: $ARCHIVE_PATH."
Exit /spec.
**F4 SHA pin:** Capture the exact SHA AFTER the final dirty check. Use this
**SHA pin:** Capture the exact SHA AFTER the final dirty check. Use this
SHA (not "HEAD") for the worktree:
```bash
PIN_SHA=$(git rev-parse HEAD)
```
**F5 unique branch + worktree path:** Suffix with `$$` to avoid concurrent
**Unique branch + worktree path:** Suffix with `$$` to avoid concurrent
collisions:
```bash
@@ -245,7 +237,7 @@ SPAWN_PATH="${WORKTREE_PARENT:-../worktrees}/${SLUG_TITLE}-$$"
mkdir -p "$(dirname "$SPAWN_PATH")"
```
**D16 mandatory final-confirm gate:** AskUserQuestion: "Spawn agent now? Last
**Final-confirm gate (required):** AskUserQuestion: "Spawn agent now? Last
chance to revise the spec." Options: A) Spawn. B) Cancel (issue stays filed,
archive stays written).
@@ -272,22 +264,9 @@ echo "Follow with: cd $SPAWN_PATH && claude --resume"
Update archive frontmatter with `spec_worktree_path: $SPAWN_PATH` and
`spec_executed: true` (atomic re-write).
**F3 stash restore safety (when B path was chosen):** Do NOT auto-restore inline
**Stash restore safety (when B path was chosen):** Do NOT auto-restore inline
— the spawned agent may take hours. Instead print: "Stash preserved as
`$STASH_REF`. Restore later with `git stash list` then `git stash apply
stash^{/$STASH_REF}`. Before restore, re-run `git status` to make sure your
worktree is clean." Do NOT drop the stash; user owns it.
#### TTHW telemetry (DX11/F7)
Capture timestamps at three checkpoints, write to telemetry envelope at /spec
exit:
- `T_PHASE1_START` — Phase 1 first AskUserQuestion or first text emit
- `T_FIRST_CITATION` — first file/symbol reference in Phase 3 prose
- `T_FILE_OR_SPAWN` — issue filed OR agent spawned, whichever ends Phase 5
Append the captured timestamps to the local analytics line that the preamble's
end-of-skill telemetry write emits, as `ttfc_ms` (Phase 1 → first citation) and
`tthw_ms` (Phase 1 → file/spawn) JSON fields. Surfacing the aggregates in
`/retro` is a separate follow-up.
+1 -1
View File
@@ -650,7 +650,7 @@ do not launch the downstream skill or open a browser.`,
'## Phase 7: Triage',
'## Phase 8: Fix Loop',
'8e.5. Regression Test',
'WTF-LIKELIHOOD',
'### 8f. Self-Regulation',
'## Additional Rules (qa-specific)',
'## Output Structure',
],
+2 -1
View File
@@ -229,7 +229,8 @@ describe('compact QA browser recipes retain native operations', () => {
const classification = section('### 8e.', '### 8e.5.');
for (const rule of ['passed 8c', 'native regression when available', 'disclose missing test coverage', "undo only this run's repair", 'revert its commit if already committed', 'retain the valid regression/evidence', '"deferred"', 'Never discard user changes']) expect(classification).toContain(rule);
const regulation = section('### 8f.', '## Phase 9:');
for (const rule of ['Every 5 fixes (or after any revert)', 'WTF > 20%', 'STOP immediately', 'Ask whether to continue', 'Hard cap: 50 fixes']) expect(regulation).toContain(rule);
for (const rule of ['Every 5 fixes (or after any revert)', 'STOP immediately', 'Ask whether to continue', 'Hard cap: 50 fixes']) expect(regulation).toContain(rule);
for (const signal of [/WTF-likelihood/i, /revert/i, /unrelated/i]) expect(regulation).toMatch(signal);
expect(source).toContain('When in doubt, stop and ask');
const rules = source.slice(source.indexOf('## Additional Rules'));
for (const rule of ['Outside an explicitly approved browser bootstrap', 'Only create tests through authorized codification in Phase 8a.5', 'Never modify CI configuration or weaken existing tests', 'use new native test files']) expect(rules).toContain(rule);
+1 -1
View File
@@ -1247,7 +1247,7 @@ describe('Phase 8e.5 regression test generation', () => {
const content = fs.readFileSync(path.join(ROOT, 'qa', 'SKILL.md'), 'utf-8');
expect(content).toContain('8e.5. Regression Test');
expect(content).toContain('test(qa): regression test');
expect(content).toContain('WTF-likelihood exclusion');
expect(content).toMatch(/test-only commits do not count/i);
});
test('qa/SKILL.md Rule 13 is amended for regression tests', () => {
+4 -2
View File
@@ -200,7 +200,7 @@ describe('/spec redaction at every sink (scan-at-sink, carved: gate-and-file sec
describe('/spec quality gate secret-sink invariant (carved: gate-and-file section)', () => {
test('declares "raw spec must NOT be persisted" when the scan BLOCKS', () => {
expect(SEC_TMPL).toMatch(/raw spec must NOT[\s\S]*be persisted/i);
expect(SEC_TMPL).toMatch(/raw spec must not\s+be\s+persisted/i);
});
test('BLOCK path stops before dispatch/archive/file', () => {
expect(SEC_TMPL).toMatch(/no archive write, no transcript log, no outside\s*\n?\s*dispatch/i);
@@ -311,7 +311,9 @@ describe('/spec plan-mode-aware Phase 5 (DX7/DX11/F1, carved: gate-and-file sect
describe('/spec Phase 3 hard-grep with fallback', () => {
test('Phase 3 mandates reading evidence before asking', () => {
expect(TMPL).toMatch(/Mandatory:[\s\S]*MUST read at least one[\s\S]*evidence/i);
const phase3 = TMPL.slice(TMPL.indexOf('### Phase 3:'), TMPL.indexOf('### Phase 4:'));
expect(phase3).toMatch(/before asking[\s\S]{0,40}question/i);
expect(phase3).toMatch(/read at least one\s+piece of evidence/i);
});
test('project-level fallback prose for prompts with no concrete file', () => {
expect(TMPL).toMatch(/Project-level prompt/);
+1 -1
View File
@@ -167,7 +167,7 @@ describe('workflow judge excerpts', () => {
expect(review).toContain('present the findings, then use AskUserQuestion ONCE:');
expect(review).toContain('On A or per-finding approvals, make the approved edits yourself');
expect(text).toContain('Step 9 then commits and pushes those edits');
expect(text).toContain('Entries scoring <2 need attention, not replacement');
expect(text).toMatch(/needs?\s+attention,?\s+not\s+replacement/i);
expect(text).not.toContain('Flag and rewrite');
expect(text).toContain('if VERSION is absent, use the completion date only');
});