原始内容
pi-super-dev
A self-contained, modular development pipeline for the Pi coding agent, built on a composable control-flow node algebra (branch / parallel / loop / retry / gate / map / wait).
Runs the 13-stage super-dev workflow — requirements → BDD → research →
[debug] → assessment → design → [prototype] → spec → spec-review → TDD
implementation → parallel code review → docs → cleanup → merge — by spawning
21 specialist pi subagents directly. No dependency on @agwab/pi-workflow
or any other external workflow engine.
Install
Install it from npm or GitHub (your choice):
# 1) npm — published package
pi install npm:pi-super-dev
# 2) GitHub — latest on the default branch
pi install git:github.com/jenningsloy318/pi-super-dev
# …or pinned to a release tag:
pi install git:github.com/jenningsloy318/pi-super-dev@v0.1.2
Try it without installing (temporary, this run only):
pi -e npm:pi-super-dev
pi -e git:github.com/jenningsloy318/pi-super-dev
# from a local checkout:
pi -e /path/to/pi-super-dev
Project-scoped install (writes .pi/settings.json instead of user settings, so
your team shares it):
pi install -l npm:pi-super-dev
Requires the Pi coding agent.
pi install runs npm install, but this package has only peerDependencies
(pi bundles them), so there's nothing extra to download.
Use
# From the pi TUI:
/super-dev implement user authentication with OAuth2
# Or directly via the tool call the agent will make:
super_dev({ task: "fix the crash on large file upload" })
Tool options: skipWorktree, skipStages, model, maxAgents.
Configuration
The deterministic build gate (Stage 9 verify / 9.2 implementation / 11 merge)
runs build, test, and typecheck (and Rust clippy) against your
worktree. It is scope-aware: on Rust workspaces it can narrow all three
commands to the crates the current branch actually touched and treat
pre-existing out-of-scope failures as ignorable, so it stops false-failing
and false-aborting on messy real-world monorepos. Three optional
environment variables tune its timeout and scope without editing any
stage call site — the harness resolves them internally, so the three
callers keep passing only { signal }.
SUPER_DEV_BUILD_TIMEOUT_MS — per-command timeout override in
milliseconds (base-10 integer). The default is 600_000 ms (10 minutes); a
too-short timeout previously caused false FAILs on slow first-time Rust
compiles before the build finished. Falls back to the default when the var is
unset, empty, not-a-number, or <= 0. Give every cargo / build / typecheck
command up to 15 minutes:
SUPER_DEV_BUILD_TIMEOUT_MS=900000 pi super-dev fix ...
SUPER_DEV_BUILD_TEST_PACKAGES — a comma-separated list of Cargo crate
names that forces the gate to scope all three commands (build, test,
clippy) to those packages (-p <pkg> ...) instead of running
workspace-wide. Crate names are bare package names, not paths — e.g.
api,store, not crates/api,crates/store (a leading crates/ is passed
verbatim to cargo and produces an invalid package spec). This lets the gate
reach green on a Rust workspace whose other crates carry pre-existing,
unrelated failures — without mutating the target repo (no #[ignore],
no quarantine, no file writes). Applied only when the detected language is
rust and the list is non-empty; go/python/node/mixed stacks ignore it
entirely, and empty/missing falls back to auto-detection (below) or a
workspace-wide build. Scope the gate to two crates, ignoring the rest of the
workspace:
SUPER_DEV_BUILD_TEST_PACKAGES="api,store" pi super-dev fix ...
SUPER_DEV_GATE_BASE_REF — the git ref the gate diffs against to
auto-detect touched crates when neither an explicit package list nor
SUPER_DEV_BUILD_TEST_PACKAGES is set. The gate runs
git -C <worktree> diff --merge-base <baseRef> --name-only, maps every
crates/<dir>/... path to its directory segment, then resolves each segment
to the real cargo package name via a cached cargo metadata --no-deps
lookup (so prefixed-crate workspaces resolve correctly — crates/data/ →
stockfan-data, not data), de-dupes (first-seen order), and scopes all
three commands to that set. If cargo is missing or metadata fails / times
out, it degrades to the directory name verbatim — identical to the old
behavior, so dir == name workspaces are unaffected. Defaults to main; set it for repos whose
default branch is develop, master, or trunk:
SUPER_DEV_GATE_BASE_REF=develop pi super-dev fix ...
Auto-detection is a safe degradation: on any git error, empty diff, a
non-crates/<pkg>/ layout (top-level member dirs, members=["*"]), or a
base ref that does not resolve, it returns [] and the gate falls back to
the byte-identical workspace-wide behavior (no -p flags). Note: this means
the feature is silently inactive on repos that don't follow the
crates/<pkg>/ convention — see the scope-aware build-gate spec for known
limitations and future work (full baseline-diff).
SUPER_DEV_CARGO_METADATA_TIMEOUT_MS — timeout (ms) for the single cached
cargo metadata --no-deps lookup the auto-detect path uses to resolve touched
crate directories to real package names. Defaults to 30_000 (30 s); a
metadata read is a cheap manifest-graph scan, not a build, so it gets a far
shorter envelope than SUPER_DEV_BUILD_TIMEOUT_MS. On any error or timeout
it degrades to the directory name verbatim (a no-op where directory already
equals package name). Override only if a cold toolchain needs more headroom.
Package-set precedence (highest → lowest): explicit opts argument →
SUPER_DEV_BUILD_TEST_PACKAGES → auto-detected touched crates →
workspace-wide. The git-diff spawn is skipped when a higher-precedence source
supplies a value. All three variables can be combined on a Rust workspace:
SUPER_DEV_BUILD_TIMEOUT_MS=900000 \
SUPER_DEV_BUILD_TEST_PACKAGES="api,store" \
SUPER_DEV_GATE_BASE_REF=develop \
pi super-dev fix "add OAuth2 login"
Internals: timeout resolution lives in resolveTimeoutMs(), package scoping
in scopedCargoArgs() / scopedCargoBuildArgs() / scopedCargoTestArgs() /
scopedCargoClippyArgs(), directory→package-name resolution in
resolveCargoPackageNames() (backed by a per-cwd cargo metadata cache),
auto-detection in detectTouchedCargoPackages(),
and in-scope classification in classifyOutOfScopeErrors() (all in
src/build-runner.ts). The implementation retry loop (Stage 9.2) treats a
gate result as GREEN when gate.pass || gate.inScopePass, logging any ignored
pre-existing out-of-scope failures, and terminates early only on genuine
in-scope failures. See the JSDoc on DEFAULT_TIMEOUT_MS for the full timeout
fallback matrix.
Phase-green trust (claimed-vs-actual cross-check)
Beyond the deterministic build/test/typecheck gate and the spec-declared
deliverable check (Layer 2: runDeliverableCheck(cwd, phase.deliverables)
AND-ed into phase-green), every run also captures a git change-tracker
that brackets every stage (start + end) and every implementation
phase (start + end) with a git snapshot, persisting an append-only
<specDir>/change-tracker.jsonl. Each phase-end record carries a
git-derived gitActual {created, modified, deleted}, the agent's claimed
{filesCreated, filesModified, filesDeleted} (returned by the implementer
and fix prompts under the structured <control> contract), and a
one-directional cross-check (claimedNotChanged vs changedNotClaimed).
A phase is GREEN only when all three gates pass:
(gate.pass || gate.inScopePass) && deliverableCheck.pass && changeGate.pass
changeGate.pass === false iff the phase claimed to create or modify a
file that git does not show changed — closing the false-green root cause
a second way: a phase that claims to create/wire a file but changed nothing
in git can no longer go green even when the build and deliverable checks
both pass. changedNotClaimed (git edits the agent under-reported) stays
advisory (logged, never fails) — only claimedNotChanged (claimed but
not done) hard-fails, and the miss is fed back into the next implementer
attempt as a ## Claimed changes not present in git block, bounded by
MAX_ATTEMPTS.
The subsystem never throws and degrades when git is unavailable
(gitUnavailable → record + changeGate.pass = true, never block), and is
backward compatible: legacy flat filesModified arrays are still
accepted, and non-git worktrees unblock. Tracking and deliverable
assertions collapse into one enforcement path — claimed.filesCreated
auto-unions into deliverables.requireFiles (a created file must exist),
and a 📝 N files changed (C/M/D) evidence line is surfaced in the
implementation summary. See src/tracking.ts (ChangeTracker,
computeChangeGate in src/build-runner.ts) and the spec-11 change-tracker
specification in docs/specifications/11-git-change-tracker-crosscheck-gate/.
Architecture
extension.ts ──► registers super_dev tool + /super-dev command
│
▼
pipeline.ts / workflow.ts ──► runs a tree of Nodes
│
▼
stages/index.ts ──► the pipeline expressed with control nodes
│
├─ nodes.ts the control-flow algebra
├─ helpers.ts 12 deterministic helpers (classify, gates, routing)
├─ prompts.ts prompt builders for every specialist
├─ agents.ts loads agents/<name>.md (21 specialists)
├─ pi-spawn.ts spawns `pi` subprocesses (self-contained)
└─ control.ts tolerant <control> JSON extractor
Control-flow node algebra (src/nodes.ts)
| Node | Purpose |
|---|---|
task(stage) |
Leaf — runs a Stage, stores return value at state[stage.id] |
sequence([...], {tolerant?}) |
Ordered composition — fail-fast by default, tolerant continues |
branch(pred, {yes, no?}) |
Conditional — take one path or skip |
choose([{when, run}, ...]) |
Multi-way switch — first matching case |
parallel([...], {into?, join?}) |
Fork-join — run branches concurrently, merge results |
loop({while?, until?, times?}) |
Iterate a body until a condition holds |
retry({attempts, backoff?}) |
Re-run a node on failure (AWS Step Functions "Retry" semantics) |
gate({validate, attempts}) |
Write → validate → re-write (quality-gate loop for LLM outputs) |
map({over, as, concurrency?}) |
Fan out a body over a collection |
wait(ms) / waitForEvent(name) |
Time or event synchronization |
tryCatch(body, {catch, finally}) |
Error boundary (catches thrown fatal-task errors) |
noop() |
Identity |
Grounded in AWS Step Functions ASL, the Workflow Control Patterns taxonomy (van der Aalst), Temporal workflows, and LangGraph.
The pipeline (src/stages/index.ts)
sequence([
task(setupStage), // fatal
task(classifyStage),
gate({ validate: gateValidator(...), attempts: 3 }, task(requirementsWriter)),
gate({ validate: gateValidator(...), attempts: 3 }, task(bddWriter)),
gate({ validate: researchComplete, attempts: 3 }, task(researchWriter)),
branch(isBug, { yes: task(debugWriter) }),
task(assessmentWriter),
task(designStage),
task(prototypeStage),
gate({ validate: gateValidator(...), attempts: 3 }, task(specWriter)),
gate({ validate: gateValidator(...), attempts: 3 }, task(specReviewWriter)),
task(implementationStage), // per-phase TDD loop
loop({ until: reviewApproved, times: 3 },
sequence([
parallel([codeReview, adversarialReview], { into: "review", join: mergeVerdicts }),
branch(reviewApproved, { no: reviewFix }),
])),
task(docsWriter),
task(cleanupTask),
branch(notBlocked, { yes: task(mergeWriter) }),
], { tolerant: true })
Customize
Compose your own pipeline by importing the node builders:
import { runWorkflow, sequence, task, gate, gateValidator, /* ... */ } from "pi-super-dev/pipeline";
import { requirementsWriter, specWriter, implementationStage } from "pi-super-dev/stages";
const custom = {
id: "quick",
root: sequence([
gate({ validate: gateValidator("gate-requirements", "write-requirements", "requirements"), attempts: 2 },
task(requirementsWriter)),
task(specWriter),
task(implementationStage),
]),
};
await runWorkflow(custom, "add a health endpoint", { cwd: process.cwd() });
Testing
npm run typecheck # tsc --noEmit
npm test # vitest — LLM-free unit tests
The test suite is fully hermetic (no pi spawns, no network): control-flow
algebra semantics, deterministic helpers, control-JSON parsing, workflow
composition integrity, package structure.
License
MIT