|
| 1 | +--- |
| 2 | +name: stage-chapters |
| 3 | +description: Generate Stage chapters for the current local git branch and open them in a browser for review. |
| 4 | +user-invocable: true |
| 5 | +--- |
| 6 | + |
| 7 | +# stage-chapters |
| 8 | + |
| 9 | +Generates a Stage chapter run for the current local git branch and opens it in a browser. The skill detects the base ref, computes the diff, generates chapters from it, writes a JSON file matching the `stage-cli` schema, and hands the file to `stage-cli show` to launch the SPA. |
| 10 | + |
| 11 | +## Prerequisites |
| 12 | + |
| 13 | +Run these checks before any other work. If either fails, stop with the error message — do not continue. |
| 14 | + |
| 15 | +1. **`stage-cli` is installed.** Run `which stage-cli`. If it exits non-zero, instruct the user: |
| 16 | + |
| 17 | + ``` |
| 18 | + stage-cli is not installed. Run: |
| 19 | +
|
| 20 | + npm install -g stage-cli |
| 21 | +
|
| 22 | + Then retry /stage-chapters. |
| 23 | + ``` |
| 24 | + |
| 25 | + Stop. |
| 26 | + |
| 27 | +2. **The current directory is a git repo.** Run `git rev-parse --is-inside-work-tree`. If it does not print `true`, stop with: |
| 28 | + |
| 29 | + ``` |
| 30 | + /stage-chapters must be run inside a git repository. |
| 31 | + ``` |
| 32 | + |
| 33 | +## Step 1 — Detect base ref |
| 34 | + |
| 35 | +Find the branch the user reviews against. Try each of the following in order; use the first that succeeds: |
| 36 | + |
| 37 | +1. `git rev-parse --verify origin/HEAD 2>/dev/null` — typically resolves to `refs/remotes/origin/main`. Strip the leading `refs/remotes/origin/` to recover the bare base name. |
| 38 | +2. `git rev-parse --verify main` — fall back to a local `main` branch. |
| 39 | +3. `git rev-parse --verify master` — final fallback for older repos. |
| 40 | + |
| 41 | +If all three fail, stop with: |
| 42 | + |
| 43 | +``` |
| 44 | +No default branch detected. Tried origin/HEAD, main, and master. |
| 45 | +``` |
| 46 | + |
| 47 | +Save the resolved base name (e.g. `main`) as `<base>` for the next steps. |
| 48 | + |
| 49 | +## Step 2 — Get the diff |
| 50 | + |
| 51 | +Compute the merge-base, capture the relevant SHAs, and dump the unified diff: |
| 52 | + |
| 53 | +```bash |
| 54 | +MERGE_BASE=$(git merge-base <base> HEAD) |
| 55 | +BASE_SHA=$(git rev-parse <base>) |
| 56 | +HEAD_SHA=$(git rev-parse HEAD) |
| 57 | + |
| 58 | +git diff "$MERGE_BASE".. |
| 59 | +``` |
| 60 | + |
| 61 | +The trailing `..` is intentional — it includes working-tree changes for tracked files alongside committed changes on the branch. Save the full diff text into context for Step 3. |
| 62 | + |
| 63 | +`BASE_SHA`, `HEAD_SHA`, and `MERGE_BASE` are full 40-character SHAs and feed directly into the JSON `scope` field in Step 4. |
| 64 | + |
| 65 | +## Step 3 — Cluster + narrate |
| 66 | + |
| 67 | +> **TODO:** See Issue 11 for the chapter generation prompt port. For now, leave this step as a placeholder. The next revision of this skill will produce a `chapters` array shaped per the schema documented in Step 4. |
| 68 | +
|
| 69 | +## Step 4 — Write JSON file |
| 70 | + |
| 71 | +Compute a unique temp path. Epoch nanoseconds keep concurrent runs from colliding: |
| 72 | + |
| 73 | +```bash |
| 74 | +TMPFILE="${TMPDIR:-/tmp}/stage-chapters-$(date +%s%N).json" |
| 75 | +``` |
| 76 | + |
| 77 | +The `${TMPDIR:-/tmp}` fallback matters on macOS, where `os.tmpdir()` resolves to `/var/folders/...` but `$TMPDIR` is not always set in every shell. |
| 78 | + |
| 79 | +Write a JSON file at `"$TMPFILE"` matching the shape below. The file must validate against `ChaptersFileSchema` in `packages/cli/src/schema.ts`; mismatched fields will be rejected by `stage-cli show`. |
| 80 | + |
| 81 | +High-level shape: |
| 82 | + |
| 83 | +``` |
| 84 | +{ scope: {...}, chapters: [...], generatedAt: "..." } |
| 85 | +``` |
| 86 | + |
| 87 | +Full example: |
| 88 | + |
| 89 | +```jsonc |
| 90 | +{ |
| 91 | + "scope": { |
| 92 | + // Either committed (no working-tree changes): |
| 93 | + "kind": "committed", |
| 94 | + "baseSha": "<40-char SHA>", |
| 95 | + "headSha": "<40-char SHA>", |
| 96 | + "mergeBaseSha": "<40-char SHA>" |
| 97 | + |
| 98 | + // Or workingTree (when the diff includes uncommitted tracked changes): |
| 99 | + // "kind": "workingTree", |
| 100 | + // "ref": "work" | "staged" | "unstaged", |
| 101 | + // "baseSha": "<40-char SHA>", |
| 102 | + // "headSha": "<40-char SHA>", |
| 103 | + // "mergeBaseSha": "<40-char SHA>" |
| 104 | + }, |
| 105 | + "chapters": [ |
| 106 | + { |
| 107 | + "id": "ch-1", |
| 108 | + "order": 1, |
| 109 | + "title": "Short imperative title", |
| 110 | + "summary": "Why this chapter matters to the reviewer.", |
| 111 | + "hunkRefs": [ |
| 112 | + { "filePath": "path/to/file.ts", "oldStart": 42 } |
| 113 | + ], |
| 114 | + "keyChanges": [ |
| 115 | + { |
| 116 | + "content": "A judgment-call question for the reviewer (not source code).", |
| 117 | + "lineRefs": [ |
| 118 | + { |
| 119 | + "filePath": "path/to/file.ts", |
| 120 | + "side": "additions", |
| 121 | + "startLine": 50, |
| 122 | + "endLine": 55 |
| 123 | + } |
| 124 | + ] |
| 125 | + } |
| 126 | + ] |
| 127 | + } |
| 128 | + ], |
| 129 | + "generatedAt": "2026-05-04T12:34:56.000Z" |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +Field rules: |
| 134 | + |
| 135 | +| Field | Constraint | |
| 136 | +|-------|------------| |
| 137 | +| `scope.kind` | `"committed"` or `"workingTree"` | |
| 138 | +| `scope.ref` | Required when `kind` is `"workingTree"`; one of `"work"`, `"staged"`, `"unstaged"` | |
| 139 | +| `scope.baseSha` / `headSha` / `mergeBaseSha` | Full 40-character lowercase hex SHAs | |
| 140 | +| `chapters[].id` | Non-empty, unique within the run | |
| 141 | +| `chapters[].order` | Positive integer (1-indexed) | |
| 142 | +| `chapters[].hunkRefs[].oldStart` | Non-negative integer — the pre-image start line from the unified-diff `@@` header (`0` for new files) | |
| 143 | +| `chapters[].keyChanges[].lineRefs` | Array with at least one entry | |
| 144 | +| `lineRefs[].side` | `"additions"` (right side) or `"deletions"` (left side) | |
| 145 | +| `lineRefs[].startLine` / `endLine` | Positive integers; `endLine >= startLine` | |
| 146 | +| `generatedAt` | ISO 8601 datetime string | |
| 147 | + |
| 148 | +## Step 5 — Display generated chapters |
| 149 | + |
| 150 | +Hand the file to `stage-cli`: |
| 151 | + |
| 152 | +```bash |
| 153 | +stage-cli show "$TMPFILE" |
| 154 | +``` |
| 155 | + |
| 156 | +`stage-cli show` validates the JSON, inserts a new `chapter_run` plus chapters and key changes into the local SQLite database, boots a loopback HTTP server, and opens the browser to the new run. The command stays running and serves the SPA until the user kills it with Ctrl+C — invoke it as the final command in the workflow rather than expecting it to print a value and exit. |
| 157 | + |
| 158 | +Do not pass a `runId` and do not call a separate `stage-cli ingest`. `show <path>` does ingestion and serving in one step. |
0 commit comments