Skip to content

Commit 02acd21

Browse files
khaliqgantclaude
andcommitted
Code Storybook spec program + runbook
Spec-driven program (14 specs, 5 waves) for a read-only, AI-narrated code storybook across Planning (Sage) / Review (MSD) / Runtime (NightCTO), backed by relayfile artifacts and one shared renderer extracted from MSD (@code-story/react). Includes the overnight Ricky runner (implement -> review -> fix per spec) and the cross-repo runbook. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 parents  commit 02acd21

20 files changed

Lines changed: 876 additions & 0 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
.DS_Store
3+
/tmp/
4+
*.log

README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Code Storybook
2+
3+
A read-only, AI-narrated **storybook of the codebase** that keeps humans close to the code
4+
across three moments — **Planning** (Sage), **Code Review** (MSD), and **Runtime issues**
5+
(NightCTO) — with ASCII/architecture diagrams, syntax-highlighted code flows, and code-health
6+
metrics (churn, hotspots, coverage). Agents do the writing; humans read and stay aware.
7+
8+
This repo holds the **spec program** and the **runbook**. It is built spec-by-spec by Ricky
9+
(the workflow agent) via an overnight runner — not hand-written all at once.
10+
11+
## How it works
12+
13+
```
14+
Sage / MSD / NightCTO workflows
15+
│ (call the shared story-writer skill as a side-effect of their work)
16+
17+
@code-story/skill ──writes──▶ relayfile /stories/<type>/<id>.json (the artifact)
18+
▲ ▲
19+
code-narrator persona ┘ └ code-health persona (churn/hotspots/coverage)
20+
21+
@code-story/react (one shared, artifact-driven, read-only renderer,
22+
▲ extracted from MSD's renderer + PRStory)
23+
┌──────────────┼───────────────┐
24+
MSD webapp Pear `story` web guided tour (stepped)
25+
(primary web) view mode hosted by MSD webapp + Pear
26+
```
27+
28+
- **relayfile** is the artifact substrate — stories are JSON files; "the filesystem is the protocol."
29+
- The **renderer is extracted from MSD** (custom tokenizer highlighting, `diffUtils`, and the
30+
`PRStory` slide-deck), generalized into one backend-free `@code-story/react` package that MSD's
31+
webapp, Pear, and the web tour all consume — so no surface reimplements code rendering.
32+
- Two helper **personas** (narrator, health) enrich stories; the three product agents emit them.
33+
34+
See [`specs/storybook/PROGRAM.md`](specs/storybook/PROGRAM.md) for the full architecture and
35+
[`docs/storybook-boundary.md`] (produced by spec `000`) for the frozen scope.
36+
37+
## Why its own repo
38+
39+
Storybook spans repos — Sage, MSD, Pear, and a shared `@code-story` package all participate.
40+
It does not belong inside any one of them, so the spec program + orchestration live here.
41+
42+
## Spec program (14 specs, 5 waves)
43+
44+
| Wave | Specs | Target |
45+
|---|---|---|
46+
| 0 — Contract | `000` boundary · `001` `CodeStory` schema | shared `@code-story` |
47+
| 1 — Writer | `010` story-writer skill · `011` index + ACL | shared `@code-story` |
48+
| 2 — Emit | `020` Sage planning · `021` MSD review · `022` NightCTO runtime | sage · My-Senior-Dev/app · nightcto |
49+
| 3 — Renderer | `030` extract `@code-story/react` from MSD · `031` Pear mount · `032` MSD webapp surface · `033` web tour | My-Senior-Dev/app → shared · pear |
50+
| 4 — Narrate/health/proof | `040` narrator persona · `041` health persona · `042` e2e proof | shared (cross-repo e2e) |
51+
52+
Each spec is bounded (`Goal / Context / In scope / Out of scope / Acceptance / Review / Handoff`)
53+
and ends with a reviewer-checkable PASS gate.
54+
55+
## Runbook
56+
57+
Specs are implemented one at a time by Ricky, in dependency order, with an
58+
**implement → review → fix** cycle per spec (the reviewer is a *different* persona; see
59+
`specs/storybook/_review.md` / `_fix.md`).
60+
61+
```bash
62+
# inspect the plan (no side effects)
63+
./scripts/run-overnight.sh storybook --dry-run
64+
65+
# run it: implement → review → fix each spec, commit, open a draft PR per wave
66+
./scripts/run-overnight.sh storybook
67+
```
68+
69+
Flags / env:
70+
- `--from <spec-id>` — resume from a spec (e.g. `--from 030`)
71+
- `--no-pr` — skip per-wave draft PRs
72+
- `MAX_REVIEW_ITERS` (default 3) — review/fix loop cap
73+
- `REVIEW_CMD` / `FIX_CMD` — override the reviewer/fixer invocation (default: Ricky local with `_review.md` / `_fix.md`)
74+
75+
### Cross-repo execution (important)
76+
77+
The runner commits in whichever repo it is invoked from. Because storybook is cross-repo:
78+
79+
- Build the **shared pieces first** (`@code-story/schema`, `@code-story/skill`, `@code-story/react`)
80+
from waves 0–1 and the extract in `030`, publish the package, **then** run the per-product
81+
emit/mount specs (waves 2–3) from each target repo — or via Ricky cloud with that repo materialized.
82+
- The target repo for each spec is in its header and in `PROGRAM.md`'s cross-repo plan.
83+
84+
Treat each wave (or each target repo's slice of a wave) as a separate Ricky handoff; let the
85+
review cycle gate each before moving on.
86+
87+
## Related
88+
89+
- `AgentWorkforce/nightcto``specs/persona-migration/` — the reference persona migration this
90+
storybook program assumes (NightCTO emits runtime stories from its migrated watch handler).
91+
- `AgentWorkforce/nightcto``specs/INVENTORY.md` — front door indexing both programs.

scripts/run-overnight.sh

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env bash
2+
set -uo pipefail
3+
# NightCTO overnight spec runner
4+
# Feeds bounded specs to Ricky one at a time, in dependency order.
5+
# Usage: ./scripts/run-overnight.sh <program> [--from <spec-id>] [--dry-run] [--no-pr]
6+
# Example: ./scripts/run-overnight.sh persona-migration
7+
# ./scripts/run-overnight.sh persona-migration --from 030
8+
#
9+
# Each spec is implemented by: ricky local --spec-file <spec> --run
10+
# A draft PR is opened per wave (specs sharing the NNx hundreds digit).
11+
12+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
13+
PROGRAM="${1:?Usage: run-overnight.sh <program> [--from <spec-id>] [--dry-run] [--no-pr]}"; shift || true
14+
SPEC_DIR="$ROOT/specs/$PROGRAM"
15+
[ -d "$SPEC_DIR" ] || { echo "No spec dir: $SPEC_DIR"; exit 1; }
16+
17+
FROM=""; DRY_RUN=0; OPEN_PR=1
18+
while [ $# -gt 0 ]; do
19+
case "$1" in
20+
--from) FROM="$2"; shift 2 ;;
21+
--dry-run) DRY_RUN=1; shift ;;
22+
--no-pr) OPEN_PR=0; shift ;;
23+
*) echo "Unknown arg: $1"; exit 1 ;;
24+
esac
25+
done
26+
27+
# Review cycle config. The reviewer is a DIFFERENT persona from the implementer.
28+
# REVIEW_CMD receives the spec path as $1 and the diff range as $2; exits 0 on PASS,
29+
# non-zero with findings on stdout otherwise. FIX_CMD receives the spec path as $1
30+
# and the reviewer findings file as $2. Both are overridable for local/cloud/BYOH.
31+
MAX_REVIEW_ITERS="${MAX_REVIEW_ITERS:-3}"
32+
# Each program carries its own _review.md / _fix.md (parameterized by TARGET_SPEC).
33+
REVIEW_CMD="${REVIEW_CMD:-ricky local --spec-file specs/${PROGRAM}/_review.md --run --input TARGET_SPEC=}"
34+
FIX_CMD="${FIX_CMD:-ricky local --spec-file specs/${PROGRAM}/_fix.md --run --input TARGET_SPEC=}"
35+
36+
STATE_FILE="/tmp/nightcto-${PROGRAM}-state.env"
37+
RUN_LOG="/tmp/nightcto-${PROGRAM}.log"
38+
SPEC_LOG_DIR="/tmp/nightcto-${PROGRAM}-logs"
39+
BRANCH="results/${PROGRAM}"
40+
mkdir -p "$SPEC_LOG_DIR"; touch "$RUN_LOG"
41+
42+
log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$RUN_LOG"; }
43+
save_state() { printf 'LAST_SPEC=%s\nLAST_STATUS=%s\nBRANCH=%s\nUPDATED_AT=%s\n' \
44+
"$1" "$2" "$BRANCH" "$(date +%s)" > "$STATE_FILE"; }
45+
46+
wave_of() { basename "$1" | sed -E 's/^([0-9])[0-9]{2}-.*/\1/'; }
47+
48+
open_wave_pr() {
49+
local wave="$1" specs_md="$2"
50+
[ "$OPEN_PR" -eq 1 ] || return 0
51+
git push origin "$BRANCH" 2>/dev/null || git push origin "$BRANCH" --force-with-lease 2>/dev/null || true
52+
local existing; existing=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || echo "")
53+
if [ -n "$existing" ] && [ "$existing" != "null" ]; then
54+
log "PR #$existing exists for $BRANCH — updated via push"; return 0
55+
fi
56+
gh pr create --base main --head "$BRANCH" --draft \
57+
--title "feat(${PROGRAM}): wave ${wave}" \
58+
--body "## ${PROGRAM} — wave ${wave}
59+
60+
### Specs implemented this wave
61+
${specs_md}
62+
63+
### Verify
64+
\`\`\`bash
65+
tail -40 ${RUN_LOG}
66+
npm run build && npm test
67+
\`\`\`
68+
69+
*Auto-opened by \`scripts/run-overnight.sh ${PROGRAM}\`*" 2>&1 | tee -a "$RUN_LOG" || true
70+
}
71+
72+
# Review → fix loop for one spec. Returns 0 only when the reviewer reaches PASS.
73+
review_cycle() {
74+
local spec="$1" id="$2" verdict="$SPEC_LOG_DIR/$id.review.log"
75+
local before; before="$(git rev-parse HEAD)"
76+
local i
77+
for ((i=1; i<=MAX_REVIEW_ITERS; i++)); do
78+
log "REVIEW $id (iter $i/$MAX_REVIEW_ITERS)"
79+
if [ "$DRY_RUN" -eq 1 ]; then log "DRY-RUN would run REVIEW_CMD for $id"; return 0; fi
80+
if eval "${REVIEW_CMD}${spec}" "$before..HEAD" 2>&1 | tee "$verdict" >> "$RUN_LOG"; then
81+
log "REVIEW PASS $id (iter $i)"; return 0
82+
fi
83+
log "REVIEW found issues on $id — running fix pass (iter $i)"
84+
eval "${FIX_CMD}${spec}" "$verdict" 2>&1 | tee -a "$SPEC_LOG_DIR/$id.log" >> "$RUN_LOG" || true
85+
git add -A && git commit -m "fix(${PROGRAM}): ${id} review iter $i" 2>/dev/null || true
86+
done
87+
log "REVIEW FAILED $id after $MAX_REVIEW_ITERS iters — stopping; resume with --from $id"
88+
return 1
89+
}
90+
91+
run_spec() {
92+
local spec="$1" id; id="$(basename "$spec" .md)"
93+
local spec_log="$SPEC_LOG_DIR/$id.log"
94+
log "START $id"
95+
if [ "$DRY_RUN" -eq 1 ]; then log "DRY-RUN would run: ricky local --spec-file $spec --run"; return 0; fi
96+
if ! ricky local --spec-file "$spec" --run 2>&1 | tee "$spec_log" >> "$RUN_LOG"; then
97+
log "IMPLEMENT FAILED $id (see $spec_log) — stopping; resume with --from $id"
98+
save_state "$id" impl-failed; return 1
99+
fi
100+
git add -A && git commit -m "feat(${PROGRAM}): ${id}" 2>/dev/null || true
101+
# implement → review → fix until the reviewer reaches PASS
102+
if ! review_cycle "$spec" "$id"; then save_state "$id" review-failed; return 1; fi
103+
log "DONE $id (implemented + reviewed PASS)"; save_state "$id" success
104+
return 0
105+
}
106+
107+
# ── branch setup ─────────────────────────────────────────────────────────────
108+
cd "$ROOT"
109+
if [ "$DRY_RUN" -eq 1 ]; then
110+
log "DRY-RUN — not creating/switching branches"
111+
elif git rev-parse --verify "$BRANCH" >/dev/null 2>&1; then
112+
git checkout "$BRANCH"; log "Resuming branch $BRANCH"
113+
else
114+
git checkout -b "$BRANCH" main; log "Created branch $BRANCH from main"
115+
fi
116+
117+
# ── walk specs in order ──────────────────────────────────────────────────────
118+
mapfile -t SPECS < <(find "$SPEC_DIR" -maxdepth 1 -name '[0-9][0-9][0-9]-*.md' | sort)
119+
[ "${#SPECS[@]}" -gt 0 ] || { log "No numbered specs in $SPEC_DIR"; exit 1; }
120+
121+
started=0; [ -z "$FROM" ] && started=1
122+
current_wave=""; wave_specs_md=""
123+
for spec in "${SPECS[@]}"; do
124+
id="$(basename "$spec" .md)"
125+
if [ "$started" -eq 0 ]; then
126+
[[ "$id" == ${FROM}* ]] && started=1 || { log "SKIP $id (before --from $FROM)"; continue; }
127+
fi
128+
w="$(wave_of "$spec")"
129+
if [ -n "$current_wave" ] && [ "$w" != "$current_wave" ]; then
130+
open_wave_pr "$current_wave" "$wave_specs_md"; wave_specs_md=""
131+
fi
132+
current_wave="$w"; wave_specs_md="${wave_specs_md}- \`${id}\`
133+
"
134+
run_spec "$spec" || exit 1
135+
done
136+
[ -n "$current_wave" ] && open_wave_pr "$current_wave" "$wave_specs_md"
137+
138+
log "ALL_DONE ${PROGRAM}"
139+
log "Branch: $BRANCH — review at https://github.com/AgentWorkforce/nightcto/pulls"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# 000 — Storybook program & boundary
2+
3+
**Status:** PROPOSED | **Target repo:** shared `@code-story` package | **Depends on:**
4+
5+
## Goal
6+
Freeze the storybook scope as a checked-in document: the three layers, the artifact
7+
location, the cross-repo build plan, and what is explicitly out.
8+
9+
## Context
10+
Storybook is a read-only, AI-narrated view of the code across Planning (Sage), Review
11+
(MSD), and Runtime (NightCTO). Artifacts are relayfile files; rendering reuses Pear's
12+
existing Shiki/`@xyflow/react`/`react-diff-view`. See `PROGRAM.md`.
13+
14+
## In scope
15+
- Create `docs/storybook-boundary.md` (in the shared package's repo) containing:
16+
- the three layers and which agent emits each,
17+
- the artifact path convention `/stories/<type>/<id>.json` (`type ∈ planning|review|runtime`),
18+
- the cross-repo plan (shared schema/skill first, then per-agent emit, then Pear renderer),
19+
- the principle: **agents write, humans read** — the storybook is never an editing surface,
20+
- the "stay close to the code" goals: surface duplicated logic, smells, churn/hotspots/coverage.
21+
- A one-paragraph "why not just a PR diff" rationale (narration + cross-layer continuity + health metrics).
22+
23+
## Out of scope
24+
- Any schema/code (001+). Choosing the renderer's exact components (030).
25+
26+
## Acceptance
27+
- `docs/storybook-boundary.md` exists with the three layers, path convention, and cross-repo plan.
28+
29+
## Review
30+
Reviewer confirms the three layers map 1:1 to Sage/MSD/NightCTO, the path convention is
31+
unambiguous, and "read-only for humans" is stated as a hard rule. PASS when the doc is the
32+
faithful authority for later specs.
33+
34+
## Handoff
35+
001 defines the artifact schema against this boundary.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# 001 — Code-story artifact schema
2+
3+
**Status:** PROPOSED | **Target repo:** shared `@code-story` package | **Depends on:** 000
4+
5+
## Goal
6+
Define the versioned `CodeStory` artifact: the single structured document every layer
7+
writes and the renderer reads. This is the contract the whole program hangs on.
8+
9+
## Context
10+
Stored as relayfile JSON at `/stories/<type>/<id>.json`. Must carry enough for a rich,
11+
read-only narrated view without the reader needing repo access beyond what relayfile mounts.
12+
13+
## In scope
14+
- `packages/code-story/src/schema.ts` exporting a `CodeStory` type + a runtime validator:
15+
- `version`, `id`, `type: 'planning'|'review'|'runtime'`, `title`, `createdBy` (agent), `createdAt`
16+
- `subject`: `{ repo, ref?, prNumber?, issueUrl?, incidentId? }`
17+
- `sections: Section[]` where a `Section` is one of:
18+
- `narrative` — markdown prose (the AI narration track lives here, 040)
19+
- `diagram``{ kind:'ascii'|'mermaid'|'flow', body }` (flow → `@xyflow/react` nodes/edges)
20+
- `code``{ file, startLine, endLine, lang, snippet, note? }` (renders with Shiki + clickable `file:line`)
21+
- `diff``{ file, patch }` (renders with `react-diff-view`)
22+
- `metrics``{ churn?, hotspots?, coverage? }` (filled by 041)
23+
- `links`: `{ slackThread?, linearUrl?, notionUrl?, prUrl? }` (the dream's "traceable path")
24+
- `schema.test.ts` — valid stories of each type round-trip; an invalid section is rejected; unknown `version` is rejected.
25+
- A short `README.md` with one example story per type.
26+
27+
## Out of scope
28+
- Writing stories (010). Rendering (030). Narration/metric *generation* (040/041).
29+
30+
## Acceptance
31+
- `npm test -- schema` passes (per-type valid + invalid + version cases).
32+
- The type is exported and importable as `@code-story/schema`.
33+
34+
## Review
35+
Reviewer confirms every section kind needed by the three layers is representable, `links`
36+
covers the traceable path (Slack/Linear/Notion/PR), and the validator actually rejects
37+
malformed/unknown-version artifacts (not just types-only). PASS on green tests.
38+
39+
## Handoff
40+
010 (writer) and 030 (renderer) both program against `@code-story/schema`.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# 010 — Story-writer skill
2+
3+
**Status:** PROPOSED | **Target repo:** shared `@code-story` package | **Depends on:** 001
4+
5+
## Goal
6+
A single callable skill all three agents use to assemble and persist a `CodeStory` to
7+
relayfile — so emitting a story is one call, not bespoke per agent.
8+
9+
## Context
10+
The skill is the only writer of `/stories/**`. It validates against `@code-story/schema`
11+
(001) before writing. Reuse `@relayfile/sdk` `writeFile`. Keep assembly helpers so agents
12+
build sections ergonomically (add a code ref by `file:line`, add an ascii diagram, etc.).
13+
14+
## In scope
15+
- `packages/code-story/src/writer.ts`:
16+
- `createStory({ type, title, subject, createdBy }): StoryBuilder`
17+
- builder methods: `.narrative(md)`, `.ascii(body)`, `.flow(nodes,edges)`, `.code(file,start,end,lang,snippet,note?)`, `.diff(file,patch)`, `.metrics(m)`, `.links(l)`
18+
- `.write(ctx): Promise<{ path, id }>` — validates, then `writeFile` to `/stories/<type>/<id>.json`; idempotent on `id`.
19+
- `writer.test.ts` — build each section type, write to a fake relayfile, read back, assert schema-valid; re-write same `id` does not duplicate.
20+
21+
## Out of scope
22+
- Deciding *what* goes in a story (per-agent emit, 020–022). Narration generation (040). The index/ACL (011).
23+
24+
## Acceptance
25+
- `npm test -- writer` passes including the idempotency case.
26+
- A written artifact validates against `@code-story/schema`.
27+
28+
## Review
29+
Reviewer confirms the writer is the sole `/stories` write path, always validates before
30+
writing, is idempotent on `id`, and the builder covers every schema section. PASS on green tests.
31+
32+
## Handoff
33+
020–022 import the builder; 030 reads what it writes.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# 011 — Story index & ACL
2+
3+
**Status:** PROPOSED | **Target repo:** shared `@code-story` package | **Depends on:** 010
4+
5+
## Goal
6+
Make stories discoverable (an index the renderer lists) and access-correct (agents write,
7+
humans/Pear read) on the shared relayfile mount.
8+
9+
## Context
10+
The renderer needs to list stories without scanning; multiple agents write concurrently.
11+
Relayfile ACLs (`.relayfile.acl`) + a maintained index file fit both needs.
12+
13+
## In scope
14+
- `writer.ts`: on `.write`, upsert an entry into `/stories/_index.json` (`{ id, type, title, subject, createdBy, createdAt, path }`), append-or-replace by `id`.
15+
- `packages/code-story/src/index-reader.ts`: `listStories(ctx, { type?, repo?, limit? }): StoryIndexEntry[]`.
16+
- `.relayfile.acl` for `/stories/**`:
17+
- write: agents `sage`, `my-senior-dev`, `nightcto`, `code-narrator`, `code-health`
18+
- read: human/Pear scope + all the above
19+
- deny others by default
20+
- `index.test.ts` — concurrent writes from two fake agents both land in the index; `listStories` filters by type/repo.
21+
22+
## Out of scope
23+
- Rendering (030). Generating story content. Token issuance (relayauth).
24+
25+
## Acceptance
26+
- `npm test -- index` passes including the concurrent-write case (no lost entry).
27+
- ACL parses; a write from an unlisted agent is denied in a local relayfile test.
28+
29+
## Review
30+
Reviewer confirms default-deny, the writer agents match the three products + two helper
31+
personas exactly, and the index upsert is race-safe (concurrent writes don't clobber).
32+
PASS when index tests + the negative ACL check pass.
33+
34+
## Handoff
35+
030's renderer lists via `listStories`; 040/041 write within this ACL.

0 commit comments

Comments
 (0)