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

<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> | <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
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:
/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..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.
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.
/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.
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 with | What you get |
|---|---|---|
| Claude Code user | The two-command install above | The 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/plugin | Autopilot 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.json | Shared skills and methodology agent bodies, with an OpenCode-specific in-process plugin wrapper |
Antigravity (agy) user | scripts/install-antigravity.sh | Guarded import as a Claude Code-source plugin; no loose skills-dir scan |
| Contributor | ./scripts/dev-setup.sh --check | A read-only readiness dashboard for Claude/Codex/OpenCode/agy; mutating non-Claude setup requires --harness <name> --install |
The course-sized idea is simple: teach the agent the collaboration discipline once, then stop retyping it.
| Principle | Autopilot default |
|---|---|
| Clarify the work before coding | dev-flow expands goals into size, branch, plan, and gates |
| Ask for proof, not reassurance | quality-pipeline runs tests, scans for incomplete work, and reviews the diff |
| Preserve context outside the model | project-lifecycle, handoff, and finish-flow keep state readable by the next session |
| Don't let one brain self-approve | Heterogeneous 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 |
30 skills, grouped by what you're trying to do. Each one triggers from natural language — the Try saying lines are real triggers.
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?"
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?" · "要辯論一下"
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 where | Reach for it when | |
|---|---|---|
/l3 | inline, on this thread | full autonomy, but you want to watch it happen |
/l4 | one background, worktree-isolated foreman | a 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 engine | when 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.
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.
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.
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.
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.
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"
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 (
TaskCreatefamily) 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:onboardscaffolds this pin automatically; dev-flow warns once if the tools are still missing.
| Harness | How to start | Supported today | Known limits | |
|---|---|---|---|---|
| Claude Code | /plugin marketplace add cookys/autopilot then /plugin install autopilot@autopilot | Full plugin path: 30 skills, 3 methodology agents, 36 hooks | Primary 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 marketplace | Skills, 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 |
| OpenCode | Open this repo with .agents/skills/; use .opencode/opencode.json for agents | Shared skills, methodology agent bodies, and an OpenCode plugin wrapper | Optional TypeScript deps are only needed when editing the wrapper; hook parity is platform-specific | |
Antigravity (agy) | ./scripts/install-antigravity.sh | Guarded agy plugin validate / install / list flow with export-then-install | Runtime 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.
The deep material, moved out of this page so it stays an onboarding tour:
| Topic | Doc |
|---|---|
| All 30 skills + three modes + how they compose | docs/skills.md |
| Superpowers coexistence — three scenarios, migration | docs/coexistence.md |
Per-project configuration — the .claude/ injection model | docs/configuration.md |
| Installation & development — every platform, dev mode | docs/installation.md |
| Architecture & design — philosophy, methodology agents, credits | docs/architecture.md |
| Hooks — 25 runtime-enforcement hooks (tiers in the doc) | hooks/README.md |
| Changelog | CHANGELOG.md |
MIT — see LICENSE for details.
register.tsx 172 lines1import 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}
172variants.tsx 56 lines1type 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}
56v4.tsx 78 lines1type 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}
78spin.tsx 19 lines1const 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