From c3d63db6e7423c2005de4c3cfad6e621cee984fb Mon Sep 17 00:00:00 2001 From: garrytan Date: Fri, 2 Oct 2026 23:52:51 +0000 Subject: [PATCH 01/22] office-hours: fix stale refs, state-root path, profile query, SESSION_ID (s07 H1, H2, H3, M7, M10; s01 H1 office-hours note) - Phase 4.5 points at Founder Resources instead of the nonexistent Beat 3.5 (H1). - builder-journey.md is written to $GSTACK_STATE_ROOT, where it is opened (H2). - builder-profile context query reads developer-profile.json, the file the migration leaves behind; drop meaningless tail (H3). - Drop issue numbers from three headings (M7) and the MANDATORY/NOT optional markers on Phase 4 (M10). - Handoff telemetry uses the echoed SESSION_ID placeholder; $_SESSION_ID is never set in a fresh shell. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- office-hours/SKILL.md.tmpl | 9 ++++---- .../sections/design-and-handoff.md.tmpl | 22 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/office-hours/SKILL.md.tmpl b/office-hours/SKILL.md.tmpl index 58780ecfa..1300ce7a0 100644 --- a/office-hours/SKILL.md.tmpl +++ b/office-hours/SKILL.md.tmpl @@ -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). --- diff --git a/office-hours/sections/design-and-handoff.md.tmpl b/office-hours/sections/design-and-handoff.md.tmpl index 0f74cafe8..b75cd4f97 100644 --- a/office-hours/sections/design-and-handoff.md.tmpl +++ b/office-hours/sections/design-and-handoff.md.tmpl @@ -18,7 +18,7 @@ If `$PRIOR` exists, the new doc gets a `Supersedes:` field referencing it. This Write to `/{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. From 3a74e671403533acbf49f20527a44b83f03c8953 Mon Sep 17 00:00:00 2001 From: garrytan Date: Fri, 2 Oct 2026 23:52:51 +0000 Subject: [PATCH 02/22] spec: plain Phase 3 gate, drop duplicates, dead telemetry and review IDs (s07 H4, M1, M2, M3, M4, M5, M6) - H4: Standard template heading no longer names nonexistent --bug/--feature/--refactor flags. - M1: Phase 3 read-code-first gate at normal volume, constraint and reason kept. - M2: drop the in-phase repeat of the read-first rule (Rules recap stays). - M3: drop the duplicate audit-sink paragraph. - M4: drop TTHW telemetry block and its never-set frontmatter keys. - M5/M6: drop plan-review finding IDs from Phase 5 headings and the /ship handoff. W7a pins: spec-template-invariants:314 (Mandatory/MUST) becomes a Phase 3 section check for the read-evidence-before-asking constraint; :203 matches 'raw spec must not be persisted' across line wraps so no duplicate paragraph is needed to satisfy it. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- spec/SKILL.md.tmpl | 20 ++++++------- spec/sections/gate-and-file.md.tmpl | 41 +++++++-------------------- test/spec-template-invariants.test.ts | 6 ++-- 3 files changed, 22 insertions(+), 45 deletions(-) diff --git a/spec/SKILL.md.tmpl b/spec/SKILL.md.tmpl index fdfff41df..bfb59007f 100644 --- a/spec/SKILL.md.tmpl +++ b/spec/SKILL.md.tmpl @@ -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: `) AND the PR delivers the full spec (acceptance criteria checked off per `/ship`'s existing plan-completion gate), `/ship` adds `Closes #` 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. --- diff --git a/spec/sections/gate-and-file.md.tmpl b/spec/sections/gate-and-file.md.tmpl index 9bd0ec7f8..3c48f745b 100644 --- a/spec/sections/gate-and-file.md.tmpl +++ b/spec/sections/gate-and-file.md.tmpl @@ -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 `` below +**Sanitized body to the archive.** If auto-redact fired, the `` 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:-} --- # @@ -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. diff --git a/test/spec-template-invariants.test.ts b/test/spec-template-invariants.test.ts index f135a77f3..9380492e1 100644 --- a/test/spec-template-invariants.test.ts +++ b/test/spec-template-invariants.test.ts @@ -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/); From c66b3af8513303e01941400aa3abb6ec784ad01f Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Fri, 2 Oct 2026 23:52:51 +0000 Subject: [PATCH 03/22] investigate: drop issue numbers from freeze-script comment (s07 M8) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- investigate/SKILL.md.tmpl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/investigate/SKILL.md.tmpl b/investigate/SKILL.md.tmpl index 1ccd3d0a2..eb0846c24 100644 --- a/investigate/SKILL.md.tmpl +++ b/investigate/SKILL.md.tmpl @@ -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" ``` From 0d9f77bd08d202898d24e7150c4beae754ce914f Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Fri, 2 Oct 2026 23:52:51 +0000 Subject: [PATCH 04/22] qa: replace WTF point table with named signals and a plain stop rule (s07 M9) The self-regulation check keeps its cadence (every 5 fixes or after a revert), its signals (reverts, unrelated files, sprawl, long runs, low-severity tail), STOP-and-ask, and the 50-fix hard cap. The percentage table goes; the stop threshold (two reverts, or one unrelated-file edit) stops at least as early. W7a pins: qa-browser-preservation:220 drops 'WTF > 20%' and checks the signals case-insensitively; skill-validation:1250 checks the test-only exclusion rule instead of the heading name; carve-guards qa anchor 'WTF-LIKELIHOOD' becomes the '### 8f. Self-Regulation' heading. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- qa/SKILL.md.tmpl | 17 +++++------------ test/helpers/carve-guards.ts | 2 +- test/qa-browser-preservation.test.ts | 3 ++- test/skill-validation.test.ts | 2 +- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/qa/SKILL.md.tmpl b/qa/SKILL.md.tmpl index 391102800..b2451924c 100644 --- a/qa/SKILL.md.tmpl +++ b/qa/SKILL.md.tmpl @@ -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. diff --git a/test/helpers/carve-guards.ts b/test/helpers/carve-guards.ts index 0a1febe8e..a27fbed59 100644 --- a/test/helpers/carve-guards.ts +++ b/test/helpers/carve-guards.ts @@ -649,7 +649,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', ], diff --git a/test/qa-browser-preservation.test.ts b/test/qa-browser-preservation.test.ts index 0cd24f262..3d1599ecd 100644 --- a/test/qa-browser-preservation.test.ts +++ b/test/qa-browser-preservation.test.ts @@ -217,7 +217,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); diff --git a/test/skill-validation.test.ts b/test/skill-validation.test.ts index 1401a5d43..ff1faccc7 100644 --- a/test/skill-validation.test.ts +++ b/test/skill-validation.test.ts @@ -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', () => { From 294198008308b22a2a0b24f643774e99a320cfe6 Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Fri, 2 Oct 2026 23:55:06 +0000 Subject: [PATCH 05/22] design-html: match Pretext docs to the vendored bundle; fix cross-block server state (s08 H1, H2, H3, H4, H7, M6) - H1-H3: walkLineRanges, layoutNextLine and layoutWithLines signatures and return shapes now match design-html/vendor/pretext.js; patterns 2-4 rewritten. - H4: inline the ES-module bundle in <script type="module"> with page code in the same module; no window.Pretext global exists. - H7: remember the printed server URL and PID; later blocks restate them. - M6: drop the 2-3 line cap on the refinement summary. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- design-html/SKILL.md.tmpl | 14 ++-- design-html/sections/pretext-patterns.md.tmpl | 64 ++++++++----------- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/design-html/SKILL.md.tmpl b/design-html/SKILL.md.tmpl index 11eeb0d3a..58d5dd277 100644 --- a/design-html/SKILL.md.tmpl +++ b/design-html/SKILL.md.tmpl @@ -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 diff --git a/design-html/sections/pretext-patterns.md.tmpl b/design-html/sections/pretext-patterns.md.tmpl index 15d33171c..527443fc1 100644 --- a/design-html/sections/pretext-patterns.md.tmpl +++ b/design-html/sections/pretext-patterns.md.tmpl @@ -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. From a4834634866eec37bb25d72d0bec87e20aa10f7c Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Fri, 2 Oct 2026 23:55:06 +0000 Subject: [PATCH 06/22] make-pdf: drop nonexistent --no-syntax flag, trigger list and relative phrasing (s08 H5, M7, M9) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- make-pdf/SKILL.md.tmpl | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/make-pdf/SKILL.md.tmpl b/make-pdf/SKILL.md.tmpl index cd7b319b5..4019c61a7 100644 --- a/make-pdf/SKILL.md.tmpl +++ b/make-pdf/SKILL.md.tmpl @@ -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 From 206a7d3f9760a7b47fd4891b543576768ec8e5ff Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Fri, 2 Oct 2026 23:55:06 +0000 Subject: [PATCH 07/22] design-shotgun: DESIGN.md decides what variants vary; fix $SLUG; drop history (s08 H6, M1, M2, M3; W5.6) - H6: the office-hours lookup block evals gstack-slug before using $SLUG. - M1 + W5.6: with a DESIGN.md, variants keep its fonts and palette and vary layout and composition; without one they vary font, palette and layout. The swap-the-headline test and the 'Anti-convergence directive' heading (carve-guards anchor) stay. - M2: drop the Claude Code version pin; run_in_background: false and its reason stay. - M3: /tmp-then-cp rule and reason without the incident story. G1 (replace executor subagents) deferred to TODOS.md per plan. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- design-shotgun/SKILL.md.tmpl | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/design-shotgun/SKILL.md.tmpl b/design-shotgun/SKILL.md.tmpl index 07cee2764..16c7a8ccb 100644 --- a/design-shotgun/SKILL.md.tmpl +++ b/design-shotgun/SKILL.md.tmpl @@ -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 From 280fc05d6e81c8f7d497dde3adbbee033bc0d513 Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Fri, 2 Oct 2026 23:55:06 +0000 Subject: [PATCH 08/22] design-review: plain identity line; scope 'never read source' to the audit (s08 M4, M10) M5 (risk-percentage stop conditions) is a UC2 gate and keeps its wording. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- design-review/SKILL.md.tmpl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/design-review/SKILL.md.tmpl b/design-review/SKILL.md.tmpl index 49f26241d..d673acc96 100644 --- a/design-review/SKILL.md.tmpl +++ b/design-review/SKILL.md.tmpl @@ -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. From d299fc8548b0df631ee123c0e9990ebb9c506dea Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Fri, 2 Oct 2026 23:55:06 +0000 Subject: [PATCH 09/22] design-consultation: plain CLAUDE.md design-system snippet with its reason (s08 M8) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- design-consultation/sections/proposal-and-preview.md.tmpl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/design-consultation/sections/proposal-and-preview.md.tmpl b/design-consultation/sections/proposal-and-preview.md.tmpl index 61c22429e..e511a0610 100644 --- a/design-consultation/sections/proposal-and-preview.md.tmpl +++ b/design-consultation/sections/proposal-and-preview.md.tmpl @@ -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 From 6f12766db6d011152c02980680f0b59225fcc7ca Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:02:29 +0000 Subject: [PATCH 10/22] plan-tune: describe the never-ask hook truthfully; drop plan codes (s09 H8, H9, H10, M10; W4 plan-tune) W4: make the text true rather than change the hook. The consent copy and scope now say never-ask questions are auto-decided with gstack's recommendation by the question-preference hook (one-way doors still ask), and the profile itself never changes defaults. H10 removes the nonexistent --dismiss command. M10 strips cathedral/Layer/D-codes and keeps mechanisms. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- plan-tune/SKILL.md.tmpl | 54 ++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/plan-tune/SKILL.md.tmpl b/plan-tune/SKILL.md.tmpl index 9b0f82077..b632a6a69 100644 --- a/plan-tune/SKILL.md.tmpl +++ b/plan-tune/SKILL.md.tmpl @@ -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 From ff6a6d1f841ccd28de2e210f5b14e1cd5d83251f Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:02:29 +0000 Subject: [PATCH 11/22] context-save/restore: drop rename history; fix command-list branch rule (s09 H11, M1, M2, M3, M4, M5, M6) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- context-restore/SKILL.md.tmpl | 12 +++++------- context-save/SKILL.md.tmpl | 10 +++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/context-restore/SKILL.md.tmpl b/context-restore/SKILL.md.tmpl index 9c5fef1fa..bfcdb13d7 100644 --- a/context-restore/SKILL.md.tmpl +++ b/context-restore/SKILL.md.tmpl @@ -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. diff --git a/context-save/SKILL.md.tmpl b/context-save/SKILL.md.tmpl index b0d3a1a52..cb9e4d9f8 100644 --- a/context-save/SKILL.md.tmpl +++ b/context-save/SKILL.md.tmpl @@ -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. From c9372314f6af60145e4347871ad71e1d8471302f Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:02:29 +0000 Subject: [PATCH 12/22] retro: correct what the skill writes; drop word cap and archaeology (s09 H13, M7, M8, M9, M25) H13 and M7 touch neighbouring lines and were merged by hand in that order. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- retro/SKILL.md.tmpl | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/retro/SKILL.md.tmpl b/retro/SKILL.md.tmpl index 8ff30d38a..4810387ba 100644 --- a/retro/SKILL.md.tmpl +++ b/retro/SKILL.md.tmpl @@ -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 From 6053bfc9fac5d94f7a21ce4466dad067d88b3ce1 Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:02:29 +0000 Subject: [PATCH 13/22] health: compute the composite in code; fix example weights; drop D6 codes (s09 H12, M21, M22) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- health/SKILL.md.tmpl | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/health/SKILL.md.tmpl b/health/SKILL.md.tmpl index 1f490169e..92b52df4a 100644 --- a/health/SKILL.md.tmpl +++ b/health/SKILL.md.tmpl @@ -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. From 3c631ed6370a130ba8d64c24e950a0f1c0a19f9c Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:02:29 +0000 Subject: [PATCH 14/22] gstack-upgrade: drop issue numbers and version history, keep rationale (s09 M20) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- gstack-upgrade/SKILL.md.tmpl | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/gstack-upgrade/SKILL.md.tmpl b/gstack-upgrade/SKILL.md.tmpl index 79e931eec..32f2b03dc 100644 --- a/gstack-upgrade/SKILL.md.tmpl +++ b/gstack-upgrade/SKILL.md.tmpl @@ -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 From 56d1bf34029f9edf69c0f023d597882c8d8517a8 Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:02:29 +0000 Subject: [PATCH 15/22] canary: replace the 30-second/don't-over-analyze rule with the goal (s09 M23) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- canary/SKILL.md.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canary/SKILL.md.tmpl b/canary/SKILL.md.tmpl index 2aaf7d709..05c363051 100644 --- a/canary/SKILL.md.tmpl +++ b/canary/SKILL.md.tmpl @@ -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. From ed42dc55c446ce6b57ab3b90fdd065fc6257f21c Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:02:29 +0000 Subject: [PATCH 16/22] benchmark: self-contained page discovery instead of a /canary reference (s09 M24) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- benchmark/SKILL.md.tmpl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/benchmark/SKILL.md.tmpl b/benchmark/SKILL.md.tmpl index 61c79aa94..fa1125904 100644 --- a/benchmark/SKILL.md.tmpl +++ b/benchmark/SKILL.md.tmpl @@ -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 From 16b42db7129b0d730971951c2b1fe596d787568a Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:07:35 +0000 Subject: [PATCH 17/22] codex: drop judge vocabulary and the maintainer known-bad-list note (s10 M1, M2) The synthesis line keeps its Recommendation: <action> because <reason> shape. M16 (consult-mode embed rule) is a UC2 gate and keeps its wording; the outside-voice prompt bodies sent to Codex are unchanged. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- codex/SKILL.md.tmpl | 6 +----- codex/sections/challenge-mode.md.tmpl | 2 +- codex/sections/consult-mode.md.tmpl | 4 ++-- codex/sections/review-mode.md.tmpl | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/codex/SKILL.md.tmpl b/codex/SKILL.md.tmpl index 9d45b5a39..028587f92 100644 --- a/codex/SKILL.md.tmpl +++ b/codex/SKILL.md.tmpl @@ -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> diff --git a/codex/sections/challenge-mode.md.tmpl b/codex/sections/challenge-mode.md.tmpl index ee51379c4..e63db70da 100644 --- a/codex/sections/challenge-mode.md.tmpl +++ b/codex/sections/challenge-mode.md.tmpl @@ -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> diff --git a/codex/sections/consult-mode.md.tmpl b/codex/sections/consult-mode.md.tmpl index aa668a111..59b71c6be 100644 --- a/codex/sections/consult-mode.md.tmpl +++ b/codex/sections/consult-mode.md.tmpl @@ -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> diff --git a/codex/sections/review-mode.md.tmpl b/codex/sections/review-mode.md.tmpl index 7a821055b..855abad66 100644 --- a/codex/sections/review-mode.md.tmpl +++ b/codex/sections/review-mode.md.tmpl @@ -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> From c57a769a798f0fe714f8ee792fc0719d5f7e3dee Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:07:35 +0000 Subject: [PATCH 18/22] document-generate: true redaction-fence behavior; drop thoroughness booster (s10 H8, M3) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- document-generate/SKILL.md.tmpl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/document-generate/SKILL.md.tmpl b/document-generate/SKILL.md.tmpl index d3ef0cbc3..33f846038 100644 --- a/document-generate/SKILL.md.tmpl +++ b/document-generate/SKILL.md.tmpl @@ -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) From ffd5ece2f2f4f13e417f0a4a6e0da86a413ad411 Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:07:35 +0000 Subject: [PATCH 19/22] document-release: changelog sell test as three questions, not a 0-3 score (s10 M6) The threshold stays (fewer than two answered = needs attention, not replacement). M4/M5 (NEVER CLOBBER / NEVER BUMP) keep their wording; the integrator owns the eval-gated rewrite. W7a pin: workflow-excerpt:170 'Entries scoring <2 need attention, not replacement' becomes a case-insensitive check that weak entries get attention, not replacement. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- document-release/sections/release-body.md.tmpl | 10 +++++----- test/workflow-excerpt.test.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/document-release/sections/release-body.md.tmpl b/document-release/sections/release-body.md.tmpl index 25ca63d8d..a44484870 100644 --- a/document-release/sections/release-body.md.tmpl +++ b/document-release/sections/release-body.md.tmpl @@ -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. diff --git a/test/workflow-excerpt.test.ts b/test/workflow-excerpt.test.ts index 50d0090aa..dbedd67e8 100644 --- a/test/workflow-excerpt.test.ts +++ b/test/workflow-excerpt.test.ts @@ -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'); }); From 305a83f1d875a67f49b8e4a0ce7d612e0af8ac8c Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:07:35 +0000 Subject: [PATCH 20/22] ios-clean/fix/sync: match the generator's real header, package shape and regen command (s10 H2, H3, H4, H5, M9, M12) - H2: inventory greps the header the generator actually writes. - H4/H5: remove the local .package(path:) entry and DebugBridge* product deps; delete the generated DebugBridge/ and DebugBridgeGenerated/ dirs so the Phase 3 check can pass. - H3: regenerate accessors with /ios-sync (gstack-ios-qa-regen). - M12: ios-fix writes the regression with the project's own test runner. - M9: drop migration-relative sentence. ios-qa DEMO MODE (M8) is a UC2 gate and keeps its wording. Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- ios-clean/SKILL.md.tmpl | 16 ++++++++++------ ios-fix/SKILL.md.tmpl | 10 ++++++---- ios-sync/SKILL.md.tmpl | 3 +-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/ios-clean/SKILL.md.tmpl b/ios-clean/SKILL.md.tmpl index 5b74a9d7e..dcb1f7be2 100644 --- a/ios-clean/SKILL.md.tmpl +++ b/ios-clean/SKILL.md.tmpl @@ -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 diff --git a/ios-fix/SKILL.md.tmpl b/ios-fix/SKILL.md.tmpl index 91694fc16..7c83d299b 100644 --- a/ios-fix/SKILL.md.tmpl +++ b/ios-fix/SKILL.md.tmpl @@ -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 | diff --git a/ios-sync/SKILL.md.tmpl b/ios-sync/SKILL.md.tmpl index a3a7ab6cb..07f358560 100644 --- a/ios-sync/SKILL.md.tmpl +++ b/ios-sync/SKILL.md.tmpl @@ -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 From d53a0e673e3c3deed717e8f1a7f939873baf6b9a Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:07:35 +0000 Subject: [PATCH 21/22] setup-deploy: drop User-invocable boilerplate; say default branch, not main (s10 M10, M11) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- setup-deploy/SKILL.md.tmpl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/setup-deploy/SKILL.md.tmpl b/setup-deploy/SKILL.md.tmpl index 8bf42e65e..6ac0d4092 100644 --- a/setup-deploy/SKILL.md.tmpl +++ b/setup-deploy/SKILL.md.tmpl @@ -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} ``` From c27f5622c7dd2f668285bdf2a3c96b7a4fba9e32 Mon Sep 17 00:00:00 2001 From: garrytan <garrytan@gmail.com> Date: Sat, 3 Oct 2026 00:07:35 +0000 Subject: [PATCH 22/22] skillify: test staged skills directly; stop after Aside scrapes; drop roadmap notes (s10 H1, H6, H7, M14, M15) Template-only commit: generated SKILL.md is expected red until the regeneration commit. --- skillify/SKILL.md.tmpl | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/skillify/SKILL.md.tmpl b/skillify/SKILL.md.tmpl index b1384bf5e..50f317d7b 100644 --- a/skillify/SKILL.md.tmpl +++ b/skillify/SKILL.md.tmpl @@ -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