Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
},
"metadata": {
"description": "Design superpowers for Claude Code — 35 skills that teach your agent to ideate, research trends, generate multi-screen UIs with Stitch MCP, iterate with design systems, and ship to Next.js, Svelte, React, React Native, SwiftUI, or HTML.",
"version": "1.10.0"
"version": "1.11.0"
},
"plugins": [
{
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "stitch-kit",
"description": "Design superpowers for Claude Code — 35 skills that teach your agent to ideate, research, generate, iterate, and ship beautiful UIs using Google Stitch MCP.",
"version": "1.10.0",
"version": "1.11.0",
"author": {
"name": "gabelul"
},
Expand Down
12 changes: 12 additions & 0 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "stitch-kit",
"version": "1.11.0",
"description": "Design superpowers for Codex — skills that ideate, generate, and ship UIs with Google Stitch MCP, with compaction-resilient session state.",
"author": {
"name": "gabelul"
},
"repository": "https://github.com/gabelul/stitch-kit",
"license": "Apache-2.0",
"skills": "./skills/",
"hooks": "./hooks/hooks.json"
}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@

# Node
node_modules/

# Stitch per-user session state (project id, screen ids, PRD drafts, transcript
# snapshots). Runtime state, never source — and the snapshots can contain
# conversation content, so it stays out of git.
.stitch/
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ X-Goog-Api-Key = "YOUR-API-KEY"
Get your API key at [stitch.withgoogle.com/settings](https://stitch.withgoogle.com/settings).

Use `$stitch-kit` to activate the agent or `$stitch-orchestrator` for the full pipeline.

**Compaction resilience (optional):** to keep your project, screens, and PRD draft across a context compaction, stitch-kit needs the `stitch-session` helper on PATH — `npm i -g @booplex/stitch-kit` provides it, and `install-codex.sh` symlinks it. For automatic re-orientation after a compaction, install stitch-kit as a Codex plugin (`codex plugin add`) and trust its hooks. Details: [docs/compaction-resilience.md](docs/compaction-resilience.md).
</details>

---
Expand Down
45 changes: 44 additions & 1 deletion bin/stitch-kit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
* Gemini CLI — extension install (no MCP config file)
*/

import { existsSync, mkdirSync, cpSync, rmSync, readFileSync, writeFileSync, readdirSync, lstatSync, unlinkSync } from 'node:fs';
import { existsSync, mkdirSync, cpSync, rmSync, readFileSync, writeFileSync, readdirSync, lstatSync, unlinkSync, symlinkSync, chmodSync } from 'node:fs';
import { join, dirname, resolve } from 'node:path';
import { homedir } from 'node:os';
import { fileURLToPath } from 'node:url';
Expand All @@ -39,6 +39,7 @@ const PACKAGE_ROOT = resolve(__dirname, '..');
const HOME = homedir();
const SKILLS_SRC = join(PACKAGE_ROOT, 'skills');
const AGENTS_SRC = join(PACKAGE_ROOT, 'agents');
const SESSION_HELPER = join(PACKAGE_ROOT, 'scripts', 'stitch-session.mjs');
const STITCH_MCP_URL = 'https://stitch.googleapis.com/mcp';

const VERSION = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')).version;
Expand Down Expand Up @@ -537,6 +538,44 @@ function installMcp(client, apiKey) {

// ── Generic Install / Uninstall / Status ────────────────────────────────────────

/**
* Put the `stitch-session` helper on PATH so skills can call it by name on any
* host. Codex skills have no way to reference a bundled script (no
* ${CLAUDE_SKILL_DIR} equivalent), so a PATH launcher is the portable answer.
* `npm i -g` already links it via the package `bin` field; this covers npx and
* git-clone installs by symlinking into ~/.local/bin. Best-effort — a failure
* here only means session state won't persist on hosts that rely on the launcher.
*/
function installLauncher() {
if (commandExists('stitch-session')) {
logOk('stitch-session already on PATH');
return;
}
try {
chmodSync(SESSION_HELPER, 0o755); // the shebang script must be executable to run as a command
} catch {
// non-fatal — npm may have already set the mode
}
const binDir = join(HOME, '.local', 'bin');
const link = join(binDir, 'stitch-session');
try {
mkdirSync(binDir, { recursive: true });
// Replace an existing file or (possibly broken) symlink before linking fresh.
if (existsSync(link) || lstatSync(link, { throwIfNoEntry: false })) {
rmSync(link, { force: true });
}
symlinkSync(SESSION_HELPER, link);
logOk(`stitch-session linked → ${link}`);
const onPath = (process.env.PATH || '').split(/[:;]/).includes(binDir);
if (!onPath) {
logWarn(`${binDir} is not on your PATH — add it so skills can persist session state:`);
log(' export PATH="$HOME/.local/bin:$PATH"');
}
} catch (err) {
logWarn(`Could not link stitch-session (${err.message}). Session state may not persist on Codex.`);
}
}

/**
* Install stitch-kit for a single client: agent → skills → MCP → postInstall.
* @param {Client} client - Client to install for
Expand Down Expand Up @@ -784,6 +823,10 @@ async function install() {
installClient(client, apiKey);
}

// Put the session-state helper on PATH (shared across all hosts)
log('');
installLauncher();

// Summary
log('');
log('════════════════════════════════');
Expand Down
72 changes: 72 additions & 0 deletions docs/compaction-resilience.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Compaction resilience

## The problem

stitch-kit flows run inside a host agent — Claude Code, Codex. When that host fills its context window, it compacts the conversation: summarises the history down to free up room, then keeps going. The host decides when this happens; a plugin of skills can't vote on it.

Most stitch-kit flows already survive a compaction by accident of how they're built. The PRD is written to disk, generated screens live in Stitch's own backend (recoverable via `list_screens` / `get_project`), and `stitch-loop` parks all its state in files. The big context windows (Opus 1M, Codex) mean compaction usually never even fires for a single idea-to-screens run.

The one genuine gap is a long **ideation session**: the research and answers gathered across phases live only in the conversation, because `stitch-ideate` historically wrote the PRD only at the final phase. Compact mid-brainstorm and that work was gone.

This feature closes that gap and turns "survives by accident" into "handles it on purpose."

## How it works — two layers

### Layer 1: skills persist state as they go

A single canonical writer, `scripts/stitch-session.mjs`, owns the on-disk schema. Skills call it; nothing else writes the state file, so the shape can't drift.

State lives under `.stitch/session/` in the user's project (gitignored):

| File | What it holds |
|------|---------------|
| `state.json` | flow, phase, active project (numeric id, name, device), generated screen ids, applied design system, pointers to artifacts |
| `prd-draft.md` | running PRD, appended after each ideation phase |
| `snapshots/` | raw transcript backups written by the PreCompact hook |
| `RESUME.md` | human/agent-readable breadcrumb refreshed on each compaction |

`stitch-ideate` calls `set-phase` + `append-prd` at the end of every phase. `stitch-orchestrator` records the project at Step 4, each screen at Step 5, and the design system at Step 7b. Both reference the helper as `${CLAUDE_SKILL_DIR}/../../scripts/stitch-session.mjs` — one copy, reached from the skill's own directory.

### Layer 2: hooks re-orient after a compaction

- `hooks/pre-compact.mjs` (PreCompact, matchers `auto` + `manual`) — copies the raw transcript into `snapshots/` as a backstop and refreshes `RESUME.md`. No-ops when there's no active Stitch session. Always exits 0, because PreCompact can block compaction with exit code 2 and we never want to wedge a session.
- `hooks/session-start.mjs` (SessionStart, matchers `compact` + `resume`) — when the host re-runs SessionStart after a compaction, this prints a one-line re-orientation to stdout (which SessionStart injects into context): which project, which phase, how many screens, where the PRD draft is, and "continue, don't restart." No-ops on a fresh start or when state is stale (older than 24h).

`hooks/hooks.json` wires both up; plugins auto-discover it at the plugin root.

## Verifying it

```bash
# Drive the session helper directly:
node scripts/stitch-session.mjs init ideate
node scripts/stitch-session.mjs set-project 3780 "Velvet Cinema" DESKTOP
node scripts/stitch-session.mjs status # prints the re-orientation line

# Simulate the SessionStart hook firing after a compaction:
echo '{"source":"compact"}' | node hooks/session-start.mjs
```

To confirm the real path end-to-end, run a `/compact` during an active orchestrator or ideation flow and check that the next turn knows which project it was working in.

## Host support

Works on both **Claude Code** and **Codex CLI**, through each one's native plugin + hook system.

- **Claude Code:** hooks auto-register from `hooks/hooks.json`; skills reach the helper via `${CLAUDE_SKILL_DIR}`.
- **Codex CLI:** install as a Codex plugin (`.codex-plugin/plugin.json`) so the hooks register. Codex fires `SessionStart` with `source: "compact"` too, and provides `CLAUDE_PLUGIN_ROOT` as a compatibility env var, so the same `hooks.json` works unchanged. Codex skills have no `${CLAUDE_SKILL_DIR}` equivalent, so the helper is exposed as an on-PATH `stitch-session` command (npm `bin`, or symlinked by the installer); the skills call it through a wrapper that falls back to the Claude Code path.

Two Codex specifics worth knowing:
- Codex does not auto-trust plugin hooks — the user trusts them once, or the `SessionStart` re-orientation won't fire. Until trusted, state is still written to disk; it just isn't auto-resurfaced. The skills also self-check for in-progress state on activation, which covers the untrusted case.
- Codex's `PostCompact` stdout is ignored, so re-orientation rides on `SessionStart:compact` (same event as Claude Code), not `PostCompact`.

On OpenCode and Crush (skills copied, no plugin hooks), the skill calls no-op cleanly when `stitch-session` isn't on PATH — those hosts keep the architectural resilience (PRD on disk, screens in Stitch's backend) without the hook layer.

## Honest limitations

- **Mid-phase reasoning gap.** The per-phase flush captures structured state, not the model's *reasoning* within a phase (why this direction, what surprised it). The durable fix is the [exec plans pattern](https://developers.openai.com/cookbook/articles/codex_exec_plans) — a continuously-updated Decision Log + Surprises file written alongside `prd-draft.md`. Tracked as a follow-up in `docs/dev-docs/exec-plans-followup.md`; the raw-transcript backstop in `snapshots/` is the recoverable-but-ugly stopgap until that lands.
- Hooks only run when stitch-kit is installed as a Claude Code plugin (they need `CLAUDE_PLUGIN_ROOT`, which the host sets only for hook execution).
- Re-orientation depends on the host firing `SessionStart` with `source: "compact"` after an auto-compaction. The docs say it does for both auto and manual; confirm with a real `/compact` in your host.

## Why Node, not bash + jq

The session helper and both hooks are Node (`.mjs`). Node is a hard dependency of Claude Code; `jq` is not on stock macOS or Windows. Node also runs the hooks unchanged on native Windows, where bash scripts wouldn't.
60 changes: 60 additions & 0 deletions docs/dev-docs/exec-plans-followup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Follow-up: exec-plan layer for stitch-ideate

**Status:** designed, not built. Tracked from PR #15.
**Origin:** Luca @ context-coders.com pointed at OpenAI's [exec plans pattern](https://developers.openai.com/cookbook/articles/codex_exec_plans) as the right shape for the one limitation that PR left open.

## The gap this closes

Today's Layer-1 captures structured state (`state.json`) and PRD content (`prd-draft.md`, appended per phase). What it doesn't capture is the reasoning developed *within* a phase: why this direction, what surprised the model during research, what got dropped. That's what dies in a mid-phase compaction.

Exec plans freezes reasoning, not artifacts. Different job from `state.json`, different shape from `prd-draft.md`. It sits alongside both.

## Proposed file

`.stitch/session/plan.md` — single growing markdown file, host-agnostic, written by `stitch-ideate`. Five sections, per the cookbook:

| Section | What goes here |
|---|---|
| Purpose / Big Picture | The pitch + design direction in 2–3 sentences. |
| Progress | Timestamped checkbox list of phases. |
| Surprises & Discoveries | Research findings that changed the plan. |
| Decision Log | The *why* behind each non-obvious call (Decision / Rationale / Date). |
| Outcomes & Retrospective | Filled at the end. |

Revisions allowed; the cookbook's rule is that every revision keeps the plan fully self-contained.

## Where it sits

- `state.json` — structured pointers. Hooks surface it on compact. **Keep.**
- `prd-draft.md` — the actual PRD content, phase by phase. Becomes the final PRD. **Keep.**
- `plan.md` (new) — the reasoning behind the PRD. **Adds the missing layer.**

All three written via the `ss` wrapper; `scripts/stitch-session.mjs` owns the schema so there's still one canonical writer (new subcommand: `ss plan-add <section> <body>`).

## When stitch-ideate writes to it

Not at phase boundaries — at *decision points*. The skill body says: append to the Decision Log whenever a direction is picked, an option dropped, or a screen list committed; append to Surprises whenever research turns up something that shifts the plan. Prompt-level discipline, no new tool.

## How SessionStart surfaces it

`formatStatus` gains one line: when `plan.md` exists, include `Plan at .stitch/session/plan.md (last decision: <most-recent-entry>).`. The model already reads `state.json` and `prd-draft.md` on resume; adding `plan.md` to the list is mechanical.

## Why host-agnostic

Exec plans is a prompt/convention pattern, not a Codex feature. The same `plan.md` works under Claude Code, Codex, OpenCode, and Crush. It rides the `ss` wrapper that already resolves cross-host.

## Non-goals

- Not for `stitch-orchestrator`: the orchestrator is mechanical, `state.json` is enough.
- Not replacing `prd-draft.md`: prd = artifact, plan = reasoning.
- Not a tool/MCP: prompt-level discipline in the skill, no new API.

## Build sequence

1. Add `ss plan-*` subcommands to `scripts/stitch-session.mjs`.
2. Update `stitch-ideate` Session-state section with Decision Log / Surprises calls at the documented moments.
3. Update `formatStatus` to surface `plan.md` when present.
4. Test: simulate a phase with a decision + a surprise; verify SessionStart surfaces the latest decision.
5. Document in `docs/compaction-resilience.md`.

Rough estimate: 1–2 focused hours.
Loading
Loading