SLOPSHOPPER

spike-plugin

Development workflow skills for Claude Code — dev-flow, survey, think-tank, CEO mode, retro, learn, context-reduce

newpanebandcommand
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · spike-plugin
│ ┃ spike ✕ › fix the failing auth test and add an audit log call │ ┃ [S5] pane table; vp={"c":160,"fs":true} │ ┃ paneBodyColumns=56 ⏺ Read(src/auth.ts) │ ┃ ╭─────────────────────────────────────────── ⎿ Read 6 lines │ ┃ │ 派工 runner 階段 經過 ⏺ Update(src/auth.ts) │ ┃ │ 實作 hand grok-4.7 實作 0:15 ⎿ Added 2 lines, removed 1 line │ ┃ │ 審查 codex 審查 3:20 ⏺ Bash(bun test) │ ┃ │ 工頭 sonnet 等待處置 12:04 ⎿ 3 pass, 1 fail │ ┃ ╰─────────────────────────────────────────── │ ┃ ruler ● Done. refresh now rejects expired claims and logs an audit event. │ ┃ 0123456789012345678901234567890123456789 │ ┃ ASCII ref: 12345678901234|grok-4.7 | ✻ Worked for 42s · done 4:20 PM │ │ › /spike-pane │ ⎿ spike-plugin: presentation={"isFullscreen":true,"columns":160} o │ │ [band] sel=s123 bodyColumns=100 maxRows=8 vp={"c":160,"fs":true} [S1] verdict badges (inverse+backgroundColor hex) + line 1 + reason line 2 要你決定 疑似卡住 完成待驗收 進行中 gate-sandbox4 │ 實作 │ ██████░░░░ 60% (3/5) │ 12m 要你決定 疑似卡住 完成待驗收 進行中 <- [S1b] backgroundColor + color, NO inverse 等你選 A 或 B(原因行) [S1] color formats (badge, then fg of same value) hex bg=#ff5555 fg=#ff5555 rgb() bg=rgb(255,85,85) fg=rgb(255,85,85) ansi256 bg=ansi256(203) fg=ansi256(203) r [S1] theme keys (inverse bg, then fg) warning fg:warning permission fg:permission inactive fg:inactive success fg:success error fg:error [S2] round ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 要你決定 │ 實作 │ 60% │ │ 等你選 A 或 B │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ [S3] Raster 20x1 truecolor gauge, then 20x2 sparkline ████████████░░░░░░░░ 60% (3/5) ▅▂▆▃ ▅▂▆▃ ▂▅▃█████▆▅▂▅▃█████▆▅ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
[band] sel=s123 bodyColumns=100 maxRows=8 vp={"c":160,"fs":true} [S1] verdict badges (inverse+backgroundColor hex) + line 1 + reason line 2 要你決定 疑似卡住 完成待驗收 進行中 gate-sandbox4 │ 實作 │ ██████░░░░ 60% (3/5) │ 12m 要你決定 疑似卡住 完成待驗收 進行中 <- [S1b] backgroundColor + color, NO inverse 等你選 A 或 B(原因行) [S1] color formats (badge, then fg of same value) hex bg=#ff5555 fg=#ff5555 rgb() bg=rgb(255,85,85) fg=rgb(255,85,85) ansi256 bg=ansi256(203) fg [S1] theme keys (inverse bg, then fg) warning fg:warning permission fg:permission inactive fg:inactive success fg:success error fg:er [S2] round ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 要你決定 │ 實作 │ 60% │ │ 等你選 A 或 B │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ [S3] Raster 20x1 truecolor gauge, then 20x2 sparkline ████████████░░░░░░░░ 60% (3/5) ▅▂▆▃ ▅▂▆▃ ▂▅▃█████▆▅▂▅▃█████▆▅
Pane · spike
[S5] pane table; vp={"c":160,"fs":true} paneBodyColumns=56 ╭─────────────────────────────────────────────────────────── │ 派工 runner 階段 經過 安靜 │ 實作 hand grok-4.7 實作 0:15 — ████████ │ 審查 codex 審查 3:20 3m ⏸ █████░░░ │ 工頭 sonnet 等待處置 12:04 — ██░░░░░░ ╰─────────────────────────────────────────────────────────── ruler 0123456789012345678901234567890123456789 ASCII ref: 12345678901234|grok-4.7 |
Pane · 圖例
圖例 x: [ 關閉 ✕ ] 符號 意思 ▲ 要你決定:有一件事卡在你的批准或選擇 ⏸ 疑似卡住:派工太久沒輸出(行尾 ⏸n = 卡住數) ✓ 完成待驗收:做完了,等你看過 ● 進行中:正常跑,不用管 ◌ 待命:沒有任務在跑 ▰▱ 進度條:已完成 / 總數 ◷ 經過時間 ⚙ 正在跑的派工數 ◆ 待你決定的事項數 ? 未知狀態(資料不足) 目前 ▲ 要你決定 專案 gate-sandbox4 階段 實作 原因 等你批准:Bash: git commit -m …(2 分) runner 角色 階段 經過 安靜 grok-4.7 實作 hand 實作 0:15 — codex 審查 審查 3:20 3m ⏸ sonnet 工頭 等待處置 12:04 —
README

<table border="0" cellspacing="0" cellpadding="0"> <tr> <td valign="middle"><img src="docs/assets/icon.svg" alt="Autopilot" height="180"></td> <td width="24"></td> <td valign="middle"><img src="docs/assets/hero.svg" alt="Autopilot — Claude Code-first lifecycle orchestration with portable paths for Codex, OpenCode, and agy" height="180"></td> </tr> </table>

<img src="https://img.shields.io/badge/Claude_Code-plugin-5A67D8?style=flat-square&logo=anthropic&logoColor=white" alt="Claude Code Plugin"> <img src="https://img.shields.io/badge/version-3.0.0--alpha.2-E8A838?style=flat-square" alt="v3.0.0-alpha.2"> <img src="https://img.shields.io/badge/skills-30-4A90D9?style=flat-square" alt="30 Skills"> <img src="https://img.shields.io/badge/agents-3-7C9E8C?style=flat-square" alt="3 Methodology Agents"> <img src="https://img.shields.io/badge/hooks-36-6B8E6B?style=flat-square" alt="36 Hooks"> <img src="https://img.shields.io/badge/dependencies-zero-A8B5A0?style=flat-square" alt="Zero Dependencies"> <img src="https://img.shields.io/badge/license-MIT-D4A5A5?style=flat-square" alt="MIT License">

<b>English</b> &nbsp;|&nbsp; <a href="README.zh-TW.md">正體中文</a>

<b>The AI project lead for your terminal.</b><br> Claude Code is the full home base. Autopilot plans, delegates, reviews with a second engine, and remembers what it learned — with portable paths for Codex, OpenCode, and agy where their harnesses support them.

<sub>Distilled from 100+ completed AI-development projects.</sub>

# autopilot's optional pre-push hook:
❯ git push
[autopilot] completeness scan …  ✗ TODO stub in auth.py:42
[autopilot] tests …              ✗ 1 skipped (payment flow)
[autopilot] review …             ⚠ unhandled error path
push blocked — fix it, or override with a reason

What Is Autopilot?

Claude Code is still the most complete host. Autopilot makes AI coding agents finish the job — the planning, checking, deciding, and remembering you'd otherwise do by hand:

  • Hand it the goal, get back a result — /l3 /l4 /l5 /l6 and ceo-agent can take a task end-to-end (sized, planned, built, reviewed, closed) and only stop to ask at the decisions that actually matter.
  • A second engine argues with your code — reviews can run on a different model family (GPT, Gemini), so more bugs get caught before your users see them instead of being rubber-stamped by the same model that wrote them.
  • Catches the "done" that isn't — a no-stub/no-TODO scan, your tests, and a real code review, run in the quality gate before you merge (and in the optional pre-push hook above).
  • Remembers, so your repo doesn't rot — captures the lessons, tracks the project, tells you what to do next, and adapts to your repo from a single markdown file in .claude/.

It ships first as a Claude Code plugin — 30 skills, 3 methodology agents, 36 hooks (23 default-on, 13 opt-in), zero dependencies — and keeps the same methodology portable where other harnesses expose compatible skill, agent, or plugin surfaces. It works fully on its own, and also plays nicely with the superpowers plugin if you have it.

This README was written by Claude and adversarially reviewed by GPT-5.5 and Gemini through Autopilot's own second-engine review flow.

New here? This page is the 5-minute tour. Everything deeper lives in Learn More.

A Day With Autopilot

dev-flow is the front door. It sizes the task and routes it — small things go straight through the gate, large things become a tracked project:

<img alt="A day with Autopilot: dev-flow sizes the task and routes it — small tasks go straight through the quality gate to commit; large tasks become a tracked project with a quality gate each phase, then finish-flow closes cleanly. Without Autopilot, the AI greps the codebase immediately — no plan, no phases, no quality gates." src="docs/assets/flow.svg" width="100%">

Without Autopilot, Claude starts grep-ing the codebase immediately — no plan, no phases, no quality gates. With it, the discipline is automatic.

Quick Start

/plugin marketplace add cookys/autopilot
/plugin install autopilot@autopilot

That's it. Now just talk to Claude — Autopilot's skills trigger on what you say:

You: "I'm starting on WebSocket compression"   → sizes it, sets up a plan + branch + quality gates
You: "quick fix for the null check in auth"    → fast path, still gated before commit
You: "what should I work on next?"             → scans your projects and ranks them
You: "搞定這個重構,你決定"                       → full autonomous CEO mode

No commands to memorize — say it in your own words and the right skill steps in.

Choose Your Path

Autopilot is Claude Code-first, but not Claude Code-only. Pick the entry point that matches the harness you actually use:

If you are...Start withWhat you get
Claude Code userThe two-command install aboveThe complete path: skills, methodology agents, hooks, /l3-/l6, and plugin-managed defaults
Codex user.agents/skills/ in this repo, or the local package under platforms/codex/pluginAutopilot skills, bundled support payload, and the one production PostCompact recovery hook; no Codex-thread-bound direct-mutation enforcement is shipped (D4=NOT_READY/NO-SHIP)
OpenCode user.agents/skills/ plus .opencode/opencode.jsonShared skills and methodology agent bodies, with an OpenCode-specific in-process plugin wrapper
Antigravity (agy) userscripts/install-antigravity.shGuarded import as a Claude Code-source plugin; no loose skills-dir scan
Contributor./scripts/dev-setup.sh --checkA read-only readiness dashboard for Claude/Codex/OpenCode/agy; mutating non-Claude setup requires --harness <name> --install

From Principle To Default

The course-sized idea is simple: teach the agent the collaboration discipline once, then stop retyping it.

PrincipleAutopilot default
Clarify the work before codingdev-flow expands goals into size, branch, plan, and gates
Ask for proof, not reassurancequality-pipeline runs tests, scans for incomplete work, and reviews the diff
Preserve context outside the modelproject-lifecycle, handoff, and finish-flow keep state readable by the next session
Don't let one brain self-approveHeterogeneous review and qc panels read artifacts, not the implementer's story
Delegate by risk/l3-/l6 scale from inline autonomy to heterogeneous implementation and verification authoring

What It Does

30 skills, grouped by what you're trying to do. Each one triggers from natural language — the Try saying lines are real triggers.

✍️ Build code

dev-flow (start here — sizes & routes the task) · quality-pipeline (test → scan → review) · finish-flow (clean closing sequence, nothing skipped).

Try saying: "let's implement X" · "quick fix for Y" · "is this ready to commit?"

🧭 Make decisions

survey (dual-agent industry research) · think-tank (6-role debate) · brainstorm (pre-code design exploration) · think-tank-dialectic (irreversible, high-stakes calls).

Try saying: "what do others use for X?" · "should we rewrite or patch?" · "要辯論一下"

🤖 Full autopilot

ceo-agent (you set the goal, it executes) · /l3 /l4 /l5 /l6 (terse front-doors that pre-fill the CEO startup so one line ships the goal). They escalate where the work runs:

Runs whereReach for it when
/l3inline, on this threadfull autonomy, but you want to watch it happen
/l4one background, worktree-isolated foremana long run you'd rather offload — your context stays clean, the authoritative quality verdict is held at depth 0
/l5/l4, but the implementer is a different engine (agy / Gemini)cost-arbitrage, or a decorrelated second engine doing the mechanical coding
/l6/l5, plus verification authoring is delegated to a different enginewhen you want implementation and verification labor offloaded, while depth 0 keeps merge authority

Ordinary strict /l5 Engine execution is fail-closed: before workflow dispatch, the CLI must match the exact implementer/reviewer/verification-author/QC roster to Autopilot's frozen provider policy and consume fresh host-owned qualification plus live-readiness evidence. Lower-level and legacy flows remain explicitly non-strict.

/l3 fix the flaky reconnect test, you decide     # inline
/l4 ship the WebSocket reconnect system          # offload to a background foreman
/l5 migrate the config loader to the new schema  # foreman + heterogeneous implementer
/l6 ship the parser rewrite                      # hetero implementer + hetero verification authoring

Try saying: "CEO mode, handle it" · "全權處理" · "/l4 ship the reconnect system"

→ Per-level behaviour, presets, override flags (--expand / -x / --solo), and full examples: docs/skills.md.

Trust Model

Autopilot delegates labor, not authority. Implementer self-report is never evidence; reviewers read the task, diff, logs, and artifacts directly. Deterministic gates stay authoritative, higher-risk work needs decorrelated review coverage, and a no_verdict review never clears a gate.

Capability-Adaptive Guidance

Autopilot remains one product for strong, weak, remote, and local models. It first admits an exact role + task scope + deployment identity, then compiles exactly one guidance payload: guided gives a bounded slice and explicit structure; autonomous removes redundant choreography for a qualified role. Neither profile changes red lines, effects, egress, assurance, or acceptance.

governance.guidance_profile is a project default (guided when omitted), and a task can override it at intake without editing the project. External benchmark data can only create provisional telemetry. Owner/reviewer qualification uses separate host-scored evals; stored JSON cannot recreate session authority. The repository's v2.33.0 cutover receipt remains hold_guided: the autonomous control source is 113 bytes smaller, but exact host-token measurements, an effectful compatibility witness, a current live owner verifier, and five complete independent dogfood receipts are not yet available. The local OpenAI-compatible adapter passed fake-contract transport tests, but no live local runtime or agentic local runner is claimed.

🔌 Add another engine (optional)

Claude alone is enough. But point autopilot at a second engine family and its review/implement pipeline gets stronger — a cross-family qc panel catches what one vendor and its same-family reviewer jointly miss, and you get a heterogeneous implementer for cost-arbitrage. Recommended order: a subscription you already pay for ≻ a metered API key — OAuth-login runners (codex / agy / grok / explicit-only qoderclicn) need no token at all; GLM / MiniMax go in one canonical mode-600 file (~/.autopilot/endpoints.env) and are wired declaratively in .claude/review-loop-config.md.

Try saying: "set up a GLM reviewer" · "use MiniMax as the /l5 implementer"

→ Credential placement, the subscription-≻-API-key ladder, and the copy-paste setup: docs/installation.md.

📈 Improve over time

learn (capture lessons) · retro (git-history retrospective) · next (what to do next) · distill (turn your repeated workflows into personal skills) · plus debug · profiling · test-strategy · audit · doc-sync.

Try saying: "record this for next time" · "回顧這週" · "what's the highest priority?"

→ Full catalog of all 30 skills, the three cognitive modes, and how they compose: docs/skills.md.

🔗 Contact a persistent peer

agent-call contacts an already-running named session. Claude↔Claude uses native ListAgents / SendMessage when the target is exact; other targets go through fleet messaging v2 (the fleet CLI, or the fleet MCP tools). An offline target fails explicitly and never turns into a worker spawn.

Try saying: "ask the running codex session for its status" · "send this to the persistent reviewer"

Install

Claude Code (primary) — the two commands above. All 30 skills are available immediately as autopilot:dev-flow, autopilot:survey, etc.

⚠️ Claude Code ≥ 2.1.233 + Claude 5-era models: the task tools (TaskCreate family) are gated off by default on Opus ≥ 4.8 / Sonnet ≥ 5 / Fable ≥ 5 — which silently disables every dev-flow forcing function. Fix: keep {"env": {"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"}} in your project's .claude/settings.json (tracked, so worktrees inherit it). autopilot:onboard scaffolds this pin automatically; dev-flow warns once if the tools are still missing.

Harness Support

HarnessHow to startSupported todayKnown limits
Claude Code/plugin marketplace add cookys/autopilot then /plugin install autopilot@autopilotFull plugin path: 30 skills, 3 methodology agents, 36 hooksPrimary host; Claude-specific hooks and slash behavior do not automatically transfer to other harnesses
Codex.agents/skills/, or codex plugin add autopilot@autopilot-local after adding platforms/codex as a marketplaceSkills, generated support payload, and one production PostCompact recovery hook (`manual\auto`)This is a Codex-native recovery boundary, not Claude hook parity; no Claude hook bundle, apps, or MCP servers are loaded. Subagent model routing via spawn_agent needs a user opt-in — see platforms/codex/README.md
OpenCodeOpen this repo with .agents/skills/; use .opencode/opencode.json for agentsShared skills, methodology agent bodies, and an OpenCode plugin wrapperOptional TypeScript deps are only needed when editing the wrapper; hook parity is platform-specific
Antigravity (agy)./scripts/install-antigravity.shGuarded agy plugin validate / install / list flow with export-then-installRuntime hook firing is still unverified; install does not imply hook behavior parity

Full per-platform instructions, Windows notes, and the contributor dev-mode workflow are in docs/installation.md. Verified capability boundaries live in references/multi-agent-portability.md.

Learn More

The deep material, moved out of this page so it stays an onboarding tour:

TopicDoc
All 30 skills + three modes + how they composedocs/skills.md
Superpowers coexistence — three scenarios, migrationdocs/coexistence.md
Per-project configuration — the .claude/ injection modeldocs/configuration.md
Installation & development — every platform, dev modedocs/installation.md
Architecture & design — philosophy, methodology agents, creditsdocs/architecture.md
Hooks — 25 runtime-enforcement hooks (tiers in the doc)hooks/README.md
ChangelogCHANGELOG.md

License

MIT — see LICENSE for details.

Source 4 files
register.tsx 172 lines
1import type { Register } from 'claude-code'
2import { Variant } from './variants'
3import { V4, Legend, PANE_ID } from './v4'
4
5let sel = 'v4a1'
6let vp: any = null
7let lastOpen = ''
8let paneCols: number | null = null
9
10function b64(words: number[]): string {
11  const u = new Uint8Array(new Uint32Array(words).buffer)
12  let s = ''
13  for (const b of u) s += String.fromCharCode(b)
14  return btoa(s)
15}
16function gaugeCells(cols: number, frac: number): string {
17  const w: number[] = []
18  for (let i = 0; i < cols; i++) {
19    if (i < Math.round(cols * frac)) w.push(0x2588, 0x33dd55, 0x01000000)
20    else w.push(0x2591, 0x666666, 0x01000000)
21  }
22  return b64(w)
23}
24const BL = [0x20, 0x2581, 0x2582, 0x2583, 0x2584, 0x2585, 0x2586, 0x2587, 0x2588]
25function sparkCells(cols: number, vals: number[]): string {
26  const w: number[] = []
27  for (let r = 0; r < 2; r++) for (let c = 0; c < cols; c++) {
28    const lv = Math.round(vals[c % vals.length] * 16)
29    const inRow = r === 0 ? Math.max(0, lv - 8) : Math.min(8, lv)
30    w.push(BL[inRow], r === 0 ? 0x55aaff : 0x33dd55, 0x01000000)
31  }
32  return b64(w)
33}
34
35const Cap = (el: any, t: string) => <el.Text dimColor>{t}</el.Text>
36
37function S1(el: any, sel = '') {
38  const { Box, Text } = el
39  const badge = (label: string, bg: string) => <Text inverse backgroundColor={bg} bold>{' ' + label + ' '}</Text>
40  const fmt = (label: string, bg: string) => <Box><Text inverse backgroundColor={bg}>{' ' + label + ' '}</Text><Text dimColor>{' bg=' + bg + ' '}</Text><Text color={bg}>{'fg=' + bg}</Text></Box>
41  return (
42    <Box flexDirection="column">
43      {Cap(el, '[S1] verdict badges (inverse+backgroundColor hex) + line 1 + reason line 2')}
44      <Box>{badge('要你決定', '#ff5555')}{badge('疑似卡住', '#f1c40f')}{badge('完成待驗收', '#2ecc71')}{badge('進行中', '#3498db')}<Text>{' gate-sandbox4 │ 實作 │ ██████░░░░ 60% (3/5) │ 12m'}</Text></Box>
45      <Box>{[['要你決定','#ff5555'],['疑似卡住','#f1c40f'],['完成待驗收','#2ecc71'],['進行中','#3498db']].map(([l,c]) => <Text backgroundColor={c} color="#000000" bold>{' ' + l + ' '}</Text>)}<Text dimColor>{'  <- [S1b] backgroundColor + color, NO inverse'}</Text></Box>
46      <Text dimColor>等你選 A 或 B(原因行)</Text>
47      {Cap(el, '[S1] color formats (badge, then fg of same value)')}
48      <Box>
49        {fmt('hex', '#ff5555')}<Text> </Text>{fmt('rgb()', 'rgb(255,85,85)')}<Text> </Text>{fmt('ansi256', 'ansi256(203)')}<Text> </Text>{fmt('red', 'red')}
50      </Box>
51      {Cap(el, '[S1] theme keys (inverse bg, then fg)')}
52      <Box>
53        {['warning', 'permission', 'inactive', 'success', 'error'].map(k => <Box><Text inverse backgroundColor={k}>{' ' + k + ' '}</Text><Text color={k}>{'fg:' + k}</Text><Text> </Text></Box>)}
54      </Box>
55      {sel.includes('x') ? <Box flexDirection="column">{Cap(el, '[S1x] bare inverse+color / bg-only')}
56      <Box><Text inverse color="red">{' red-inverse '}</Text><Text inverse color="#2ecc71">{' hex-inverse '}</Text><Text backgroundColor="#3498db" color="#ffffff">{' bg-only '}</Text></Box></Box> : null}
57    </Box>
58  )
59}
60
61function S2(el: any) {
62  const { Box, Text } = el
63  return (
64    <Box flexDirection="column">
65      <Box flexDirection="row">
66        {['round', 'single', 'double'].map(st => (
67          <Box flexDirection="column" marginRight={1}>
68            {Cap(el, '[S2] ' + st)}
69            <Box borderStyle={st} borderColor="#ff5555" paddingX={1} flexDirection="column"><Text>{'要你決定 │ 實作 │ 60%'}</Text><Text dimColor>等你選 A 或 B</Text></Box>
70          </Box>
71        ))}
72      </Box>
73    </Box>
74  )
75}
76
77function S3(el: any) {
78  const { Box, Text, Raster } = el
79  return (
80    <Box flexDirection="column">
81      {Cap(el, '[S3] Raster 20x1 truecolor gauge, then 20x2 sparkline')}
82      <Box>
83        <Raster key="g1" columns={20} rows={1} cells={gaugeCells(20, 0.6)} />
84        <Text>{' 60% (3/5)'}</Text>
85      </Box>
86      <Raster key="sp" columns={20} rows={2} cells={sparkCells(20, [0.1, 0.3, 0.2, 0.5, 0.8, 0.6, 0.9, 0.7, 0.4, 0.3])} />
87    </Box>
88  )
89}
90
91function S4(el: any) {
92  const { Box, Client } = el
93  return (
94    <Box flexDirection="column">
95      {Cap(el, '[S4] Client module with 1s every spinner')}
96      <Client key="spin" module="./spin.tsx" />
97    </Box>
98  )
99}
100
101function Table(el: any) {
102  const { Box, Text, Raster } = el
103  const W = [14, 10, 10, 8, 8]
104  const row = (cells: string[], hdr: boolean, i: number) => (
105    <Box>
106      {cells.map((c, k) => <Box width={W[k]} flexShrink={0}><Text bold={hdr}>{c}</Text></Box>)}
107      {!hdr ? <Raster key={'rg' + i} columns={10} rows={1} cells={gaugeCells(10, [0.8, 0.5, 0.2][i])} /> : null}
108    </Box>
109  )
110  const rows = [['實作 hand', 'grok-4.7', '實作', '0:15', '—'], ['審查', 'codex', '審查', '3:20', '3m ⏸'], ['工頭', 'sonnet', '等待處置', '12:04', '—']]
111  return (
112    <Box flexDirection="column">
113      <Text dimColor>{'[S5] pane table; vp=' + JSON.stringify(vp) + ' paneBodyColumns=' + paneCols}</Text>
114      <Box borderStyle="round" borderColor="#3498db" flexDirection="column" paddingX={1}>
115        {row(['派工', 'runner', '階段', '經過', '安靜'], true, -1)}
116        {rows.map((r, i) => row(r, false, i))}
117      </Box>
118      <Text dimColor>{'ruler 0123456789012345678901234567890123456789'}</Text>
119      <Text>{'ASCII ref: 12345678901234|grok-4.7  |'}</Text>
120    </Box>
121  )
122}
123
124async function toggle($: any) {
125  const up = (await $.ui.panes()).some((p: any) => p.id === PANE_ID)
126  if (up) { await $.ui.close({ id: PANE_ID }); return 'closed' }
127  const r = await $.ui.open({ id: PANE_ID, title: '圖例', focus: true, closeOnEscape: true })
128  lastOpen = JSON.stringify(r)
129  return 'open ' + lastOpen
130}
131
132export const register: Register = on => {
133  on('session.start', async ($, e, next) => {
134    await $.command.register({ name: 'spike-pane', description: 'open the spike pane' })
135    await $.command.register({ name: 'spike-legend', description: 'toggle the legend pane' })
136    await $.command.register({ name: 'spike-band', description: 'spike-band s123 | s4' })
137    return next(e)
138  })
139  on('command.run', { command: 'spike-pane' }, async ($, e: any) => {
140    const r = await $.ui.open({ id: 'spike', title: 'spike' })
141    return { text: 'presentation=' + JSON.stringify(e.presentation) + ' open=' + JSON.stringify(r) }
142  })
143  on('command.run', { command: 'spike-legend' }, async ($, e: any) => ({ text: 'legend ' + await toggle($) }))
144  on('command.run', { command: 'spike-band' }, async ($, e: any) => {
145    sel = String(e.args ?? '').trim() || 's123'
146    await $.ui.invalidate('ui.render')
147    return { text: 'band sel=' + sel }
148  })
149  on('ui.render', { component: 'AbovePrompt' }, ($, e, next) => {
150    vp = e.viewport ? { c: e.viewport.columns, fs: e.viewport.isFullscreen } : null
151    const el: any = $.ui.resolve(e)
152    const { Box, Text } = el
153    const p: any = e.props
154    return (
155      <Box flexDirection="column">
156        <Text dimColor>{'[band] sel=' + sel + ' bodyColumns=' + p.bodyColumns + ' maxRows=' + p.maxRows + ' vp=' + JSON.stringify(vp)}</Text>
157        {/^v4/.test(sel) ? V4(el, sel, () => { void toggle($) }) : null}
158        {/^v[123]b?$/.test(sel) ? Variant(el, sel, p.bodyColumns) : null}
159        {/^v/.test(sel) ? null : sel.includes('1') ? S1(el, sel) : null}
160        {/^v/.test(sel) ? null : sel.includes('2') ? S2(el) : null}
161        {/^v/.test(sel) ? null : sel.includes('3') ? S3(el) : null}
162        {/^v/.test(sel) ? null : sel.includes('4') ? S4(el) : null}
163      </Box>
164    )
165  })
166  on('ui.render', { component: 'Pane', requestId: 'spike' }, ($, e: any) => {
167    paneCols = e.props?.bodyColumns ?? null
168    return Table($.ui.resolve(e))
169  })
170  on('ui.render', { component: 'Pane', requestId: PANE_ID }, ($, e: any) => Legend($.ui.resolve(e), () => { void $.ui.close({ id: PANE_ID }) }))
171}
172
variants.tsx 56 lines
1type St = { key: string; glyph: string; word: string; color: string; done?: number; total?: number; unfrozen?: number; el?: string; live?: number; stall?: number; dec?: number; nodec?: number; phase?: string; reason?: string }
2const STATES: St[] = [
3  { key: '要你決定', glyph: '▲', word: '要你決定', color: 'warning', done: 3, total: 5, el: '12m', live: 2, dec: 1, phase: '實作', reason: '等你批准:Bash: git commit -m …(2 分)' },
4  { key: '疑似卡住', glyph: '⏸', word: '疑似卡住', color: 'error', done: 3, total: 5, el: '15m', live: 2, stall: 1, phase: '審查', reason: '最久的派工 3 分沒有輸出' },
5  { key: '完成待驗收', glyph: '✓', word: '完成待驗收', color: 'success', done: 5, total: 5, el: '18m', phase: '收尾' },
6  { key: '進行中', glyph: '●', word: '進行中', color: 'suggestion', unfrozen: 2, el: '6m', live: 1, phase: '實作' },
7  { key: '待命', glyph: '◌', word: '待命', color: 'inactive' },
8]
9const PROJ = 'gate-sandbox4'
10
11function Band(el: any, v: string, st: St, cols: number) {
12  const { Box, Text } = el
13  const bar = () => {
14    if (st.total) {
15      const f = '▰'.repeat(st.done!), e = '▱'.repeat(st.total - st.done!)
16      return <Text><Text color="success">{f}</Text><Text color="inactive">{e}</Text><Text>{' ' + st.done + '/' + st.total}</Text></Text>
17    }
18    if (st.unfrozen) return <Text dimColor>{st.unfrozen + ' done*'}</Text>
19    return null
20  }
21  const parts: any[] = []
22  parts.push(<Text bold color={st.color}>{st.glyph + ' ' + st.word}</Text>)
23  if (v !== 'v2') parts.push(<Text>{'  ' + PROJ}</Text>)
24  const b = bar()
25  if (v === 'v2') {
26    if (b) parts.push(<Text>{'  '}{b}{st.phase ? <Text dimColor>{' ' + st.phase}</Text> : null}</Text>)
27  } else {
28    if (st.phase) parts.push(<Text>{'  ' + st.phase}</Text>)
29    if (b) parts.push(<Text>{' '}{b}</Text>)
30  }
31  if (st.el) parts.push(<Text dimColor>{'  ◷ ' + st.el}</Text>)
32  if (st.live) parts.push(<Text>{'  ⚙ ' + st.live}</Text>)
33  if (st.stall) parts.push(<Text color="error">{'  ⏸ ' + st.stall}</Text>)
34  if (st.dec && v !== 'v2') parts.push(<Text color="claude">{'  ◆ ' + st.dec}</Text>)
35  if (st.nodec && v !== 'v2') parts.push(<Text color="inactive">{'  ? ' + st.nodec}</Text>)
36  const withReason = !!st.reason
37  const w = v === 'v3' ? Math.max(10, cols - 4) : cols
38  const inner = (
39    <Box flexDirection="column" width={w}>
40      <Text wrap="truncate">{parts}</Text>
41      {withReason ? <Text wrap="truncate" dimColor>{'  ' + st.reason}</Text> : null}
42    </Box>
43  )
44  return (
45    <Box flexDirection="column" key={st.key}>
46      <Text dimColor>{'[' + v.toUpperCase() + '-' + st.key + ']'}</Text>
47      {v === 'v3' ? <Box borderStyle="round" borderColor={st.color} paddingX={1}>{inner}</Box> : inner}
48    </Box>
49  )
50}
51
52export function Variant(el: any, v: string, cols: number) {
53  const { Box } = el
54  return <Box flexDirection="column">{(v === 'v3b' ? STATES.slice(3) : v === 'v3' ? STATES.slice(0, 3) : STATES).map(s => Band(el, v.slice(0, 2), s, cols))}</Box>
55}
56
v4.tsx 78 lines
1type St = { key: string; glyph: string; word: string; color: string; done?: number; total?: number; el?: string; live?: number; stall?: number; dec?: number; unfrozen?: number }
2export const STATES: St[] = [
3  { key: '要你決定', glyph: '▲', word: '要你決定', color: 'warning', done: 3, total: 5, el: '12m', live: 2, dec: 1 },
4  { key: '疑似卡住', glyph: '⏸', word: '疑似卡住', color: 'error', done: 3, total: 5, el: '15m', live: 2, stall: 1 },
5  { key: '完成待驗收', glyph: '✓', word: '完成待驗收', color: 'success', done: 5, total: 5, el: '18m' },
6  { key: '進行中', glyph: '●', word: '進行中', color: 'suggestion', unfrozen: 2, el: '6m', live: 1 },
7  { key: '待命', glyph: '◌', word: '待命', color: 'inactive' },
8]
9
10export const PANE_ID = 'legend'
11
12function Line(el: any, v: string, st: St, onInfo: () => void, hoverTip: boolean) {
13  const { Box, Text, Button } = el
14  const bar = st.total
15    ? <Text><Text color="success">{'▰'.repeat(st.done!)}</Text><Text color="inactive">{'▱'.repeat(st.total - st.done!)}</Text>{' ' + st.done + '/' + st.total}</Text>
16    : st.unfrozen ? <Text dimColor>{st.unfrozen + ' done*'}</Text> : null
17  const glyph = <Text bold color={st.color}>{st.glyph}</Text>
18  return (
19    <Box flexDirection="column" key={'l' + st.key}>
20      {v === 'v4a1' ? null : <Text dimColor>{'[' + v.toUpperCase() + '-' + st.key + ']'}</Text>}
21      <Box>
22        {hoverTip
23          ? <Box key={'h' + st.key}>{glyph}<Box position="absolute" top={-1} left={2} display="none" hover={{ display: 'flex' }}><Text bold color={st.color}>{'(' + st.key + ')'}</Text></Box></Box>
24          : glyph}
25        {v === 'v4a' || v === 'v4a1' ? <Text bold color={st.color}>{' ' + st.word}</Text> : null}
26        {bar ? <Text>{'  '}{bar}</Text> : null}
27        {st.el ? <Text dimColor>{'  ◷ ' + st.el}</Text> : null}
28        {st.live ? <Text>{'  ⚙ ' + st.live}</Text> : null}
29        {st.stall ? <Text color="error">{'  ⏸ ' + st.stall}</Text> : null}
30        {st.dec ? <Text color="claude">{'  ◆ ' + st.dec}</Text> : null}
31        <Text>{'  '}</Text>
32        <Button key={'i' + st.key} plain dimColor autoFocus hotkey="i" onPress={onInfo}>ⓘ</Button>
33      </Box>
34    </Box>
35  )
36}
37
38export function V4(el: any, v: string, onInfo: () => void) {
39  const { Box } = el
40  const list = v === 'v4a1' ? STATES.slice(0, 1) : STATES
41  return <Box flexDirection="column">{list.map((s, i) => Line(el, v, s, onInfo, i === 0 && v === 'v4a1'))}</Box>
42}
43
44export function Legend(el: any, onClose: () => void) {
45  const { Box, Text, Button } = el
46  const leg: [string, string, string][] = [
47    ['▲', 'warning', '要你決定:有一件事卡在你的批准或選擇'],
48    ['⏸', 'error', '疑似卡住:派工太久沒輸出(行尾 ⏸n = 卡住數)'],
49    ['✓', 'success', '完成待驗收:做完了,等你看過'],
50    ['●', 'suggestion', '進行中:正常跑,不用管'],
51    ['◌', 'inactive', '待命:沒有任務在跑'],
52    ['▰▱', 'success', '進度條:已完成 / 總數'],
53    ['◷', '', '經過時間'],
54    ['⚙', '', '正在跑的派工數'],
55    ['◆', 'claude', '待你決定的事項數'],
56    ['?', 'inactive', '未知狀態(資料不足)'],
57  ]
58  const W = [7, 64]
59  const D = [12, 12, 12, 8, 8]
60  const drows = [['grok-4.7', '實作 hand', '實作', '0:15', '—'], ['codex', '審查', '審查', '3:20', '3m ⏸'], ['sonnet', '工頭', '等待處置', '12:04', '—']]
61  const cell = (c: string, w: number, bold = false, color?: string) => <Box width={w} flexShrink={0}><Text bold={bold} color={color}>{c}</Text></Box>
62  return (
63    <Box flexDirection="column" paddingX={1}>
64      <Box><Text bold>圖例</Text><Text>{'  '}</Text><Button plain dimColor role="dismiss" hotkey="x" onPress={onClose}>[ 關閉 ✕ ]</Button></Box>
65      <Box>{cell('符號', W[0], true)}{cell('意思', W[1], true)}</Box>
66      {leg.map(([g, c, m]) => <Box key={g}>{cell(g, W[0], true, c || undefined)}{cell(m, W[1])}</Box>)}
67      <Text> </Text>
68      <Text bold>目前</Text>
69      <Box><Text bold color="warning">▲ 要你決定</Text></Box>
70      <Text>{'專案  gate-sandbox4    階段  實作'}</Text>
71      <Text>{'原因  等你批准:Bash: git commit -m …(2 分)'}</Text>
72      <Text> </Text>
73      <Box>{['runner', '角色', '階段', '經過', '安靜'].map((h, k) => cell(h, D[k], true))}</Box>
74      {drows.map(r => <Box key={r[0]}>{r.map((c, k) => cell(c, D[k]))}</Box>)}
75    </Box>
76  )
77}
78
spin.tsx 19 lines
1const FR = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
2export default function Spin(_p: any, s: any) {
3  const { Box, Text } = s.elements
4  if (s.state === undefined) { s.setState(0); s.every(1000, () => s.setState(((s.state ?? 0) + 1) % FR.length)) }
5  const i = s.state ?? 0
6  const blink = i % 2 === 0
7  return (
8    <Box flexDirection="column">
9      <Box>
10        <Text inverse backgroundColor="#ff5555" bold>{' 要你決定 '}</Text>
11        <Text>{' gate-sandbox4 │ 實作 │ ██████░░░░ 60% (3/5) │ 12m '}</Text>
12        <Text color={blink ? 'warning' : undefined} dimColor={!blink}>{FR[i]}</Text>
13        <Text dimColor>{' tick=' + i + ' cols=' + s.columns}</Text>
14      </Box>
15      <Text dimColor>等你選 A 或 B</Text>
16    </Box>
17  )
18}
19