pi-super-dev 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +135 -0
  4. package/agents/adversarial-reviewer.md +64 -0
  5. package/agents/architecture-designer.md +43 -0
  6. package/agents/architecture-improver.md +46 -0
  7. package/agents/bdd-scenario-writer.md +37 -0
  8. package/agents/build-cleaner.md +44 -0
  9. package/agents/code-assessor.md +24 -0
  10. package/agents/code-reviewer.md +59 -0
  11. package/agents/debug-analyzer.md +54 -0
  12. package/agents/docs-executor.md +49 -0
  13. package/agents/handoff-writer.md +62 -0
  14. package/agents/implementer.md +47 -0
  15. package/agents/orchestrator.md +42 -0
  16. package/agents/product-designer.md +42 -0
  17. package/agents/prototype-runner.md +36 -0
  18. package/agents/qa-agent.md +76 -0
  19. package/agents/requirements-clarifier.md +58 -0
  20. package/agents/research-agent.md +33 -0
  21. package/agents/spec-reviewer.md +46 -0
  22. package/agents/spec-writer.md +32 -0
  23. package/agents/tdd-guide.md +51 -0
  24. package/agents/ui-ux-designer.md +50 -0
  25. package/package.json +40 -0
  26. package/skills/super-dev/SKILL.md +35 -0
  27. package/src/agents.ts +38 -0
  28. package/src/control.ts +85 -0
  29. package/src/doc-validators.ts +164 -0
  30. package/src/extension.ts +164 -0
  31. package/src/helpers.ts +263 -0
  32. package/src/nodes.ts +550 -0
  33. package/src/pi-spawn.ts +296 -0
  34. package/src/pipeline.ts +15 -0
  35. package/src/prompts.ts +120 -0
  36. package/src/session-agent.ts +305 -0
  37. package/src/setup.ts +141 -0
  38. package/src/stages/design.ts +33 -0
  39. package/src/stages/implementation.ts +80 -0
  40. package/src/stages/index.ts +172 -0
  41. package/src/stages/prototype.ts +43 -0
  42. package/src/stages/setup.ts +32 -0
  43. package/src/stages/writers.ts +105 -0
  44. package/src/types.ts +235 -0
  45. package/src/workflow.ts +181 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * The workflow runner. Builds a `StageContext` and evaluates the workflow's
3
+ * root node: `await workflow.root.run(state, ctx)`. All control logic lives in
4
+ * the node algebra (`nodes.ts`); this file only wires execution primitives.
5
+ *
6
+ * ctx.agent() — spawn a specialist `pi` subprocess (pi-spawn.ts)
7
+ * ctx.helper() — run a deterministic pure helper (helpers.ts)
8
+ * ctx.parallel() — run agent calls with a concurrency cap
9
+ * ctx.budget() — cap total agent spawns
10
+ * ctx.events — EventEmitter for waitForEvent (human-in-loop / signals)
11
+ */
12
+
13
+ import { EventEmitter } from "node:events";
14
+ import { spawnAgent } from "./pi-spawn.ts";
15
+ import { runAgentViaSession } from "./session-agent.ts";
16
+ import { runHelper } from "./helpers.ts";
17
+ import { extractControlKeys } from "./control.ts";
18
+ import type {
19
+ AgentCall,
20
+ AgentResult,
21
+ Budget,
22
+ HelperCall,
23
+ HelperResult,
24
+ PipelineState,
25
+ RunOptions,
26
+ RunStatus,
27
+ RunSummary,
28
+ StageContext,
29
+ Workflow,
30
+ } from "./types.ts";
31
+
32
+ const DEFAULT_MAX_AGENTS = 200;
33
+ const DEFAULT_MAX_CONCURRENCY = 3;
34
+
35
+ function makeBudget(maxAgents: number): Budget {
36
+ const s = { count: 0, max: maxAgents };
37
+ return {
38
+ count: 0,
39
+ check: () => s.count < s.max,
40
+ spent() {
41
+ s.count++;
42
+ this.count = s.count;
43
+ },
44
+ };
45
+ }
46
+
47
+ function makeContext(state: PipelineState, task: string, options: RunOptions, log: (m: string) => void): StageContext {
48
+ const budget = makeBudget(options.maxAgents ?? DEFAULT_MAX_AGENTS);
49
+ const maxConcurrency = options.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY;
50
+ const model = options.model;
51
+ const signal = options.signal;
52
+
53
+ async function agent(call: AgentCall): Promise<AgentResult> {
54
+ budget.spent();
55
+ const agentCwd = state.setup?.worktreePath ?? options.cwd ?? process.cwd();
56
+ // First-principles retry convergence: if a gate rejected a prior attempt,
57
+ // it stored structured errors under state.__feedback[stageId]. Prepend them
58
+ // to this attempt's prompt so the agent fixes the specific failure instead
59
+ // of resampling the same distribution. The writer's call.id is `pipeline.<id>`.
60
+ const stageKey = (call.id ?? "").replace(/^pipeline\./, "");
61
+ const fb = (state as Record<string, unknown>).__feedback as Record<string, string[]> | undefined;
62
+ const feedback = fb?.[stageKey];
63
+ const prompt = feedback?.length
64
+ ? `${call.prompt}\n\n## Previous attempt rejected — fix these\nThe validator rejected the prior attempt for these specific reasons:\n${feedback.map((e) => `- ${e}`).join("\n")}\nAddress every point and re-produce the complete artifact, then call structured_output.`
65
+ : call.prompt;
66
+ const common = {
67
+ agent: call.agent,
68
+ prompt,
69
+ cwd: agentCwd,
70
+ controlKeys: call.controlKeys ?? extractControlKeys(call.prompt),
71
+ model,
72
+ signal,
73
+ id: call.id,
74
+ onProgress: {
75
+ event: (m: string) => log(m),
76
+ text: (partial: string) => options.progress?.text(partial),
77
+ },
78
+ };
79
+ // Backend selectable. Default is 'session' (in-process createAgentSession):
80
+ // same SDK we peer-depend on, structured output via a schema, no spawn/
81
+ // stdout-buffering/<control>-parse fragility. The earlier failure (requirements
82
+ // gate) was NOT a session-backend defect — it was an incomplete control
83
+ // object caused by a permissive structured_output schema; fixed in
84
+ // session-agent.ts (per-stage schema + corrective re-prompt). 'subprocess'
85
+ // remains available via SUPER_DEV_BACKEND=subprocess.
86
+ const backend = options.backend ?? (process.env.SUPER_DEV_BACKEND as "session" | "subprocess" | undefined) ?? "session";
87
+ return backend === "session" ? runAgentViaSession(common) : spawnAgent(common);
88
+ }
89
+ async function helper(call: HelperCall): Promise<HelperResult> {
90
+ return runHelper(call);
91
+ }
92
+ async function parallel(calls: Array<() => Promise<AgentResult>>): Promise<AgentResult[]> {
93
+ const results: AgentResult[] = [];
94
+ const queue = [...calls];
95
+ async function worker(): Promise<void> {
96
+ while (queue.length > 0) {
97
+ const next = queue.shift();
98
+ if (!next) return;
99
+ results.push(await next());
100
+ }
101
+ }
102
+ await Promise.all(Array.from({ length: Math.min(maxConcurrency, calls.length) }, worker));
103
+ return results;
104
+ }
105
+
106
+ return { task, options, state, agent, helper, parallel, budget, log, events: new EventEmitter(), signal, results: [] };
107
+ }
108
+
109
+ /** Run a workflow for a task. */
110
+ export async function runWorkflow(workflow: Workflow, task: string, options: RunOptions = {}): Promise<RunSummary> {
111
+ const progress = options.progress;
112
+ const state: PipelineState = {};
113
+ const ctx = makeContext(
114
+ state,
115
+ task,
116
+ options,
117
+ (msg: string) => progress?.log(msg),
118
+ );
119
+
120
+ // Surface phase banners + stage logs through the progress sink. We re-bind
121
+ // ctx.log so control nodes' ctx.log(...) reach the caller; phase banners are
122
+ // emitted by the top-level sequence via a wrapping node (see stages/index.ts).
123
+ if (progress) {
124
+ ctx.events.on("phase", (label: unknown) => progress.phase(String(label)));
125
+ }
126
+
127
+ let aborted = false;
128
+ let abortError: string | undefined;
129
+ try {
130
+ await workflow.root.run(state, ctx);
131
+ } catch (err) {
132
+ // A fatal gate (or fatal task) threw to abort the run honestly.
133
+ aborted = true;
134
+ abortError = err instanceof Error ? err.message : String(err);
135
+ progress?.log(`Workflow "${workflow.id}" aborted: ${abortError}`);
136
+ }
137
+
138
+ if (!aborted) progress?.log(`Workflow "${workflow.id}" complete`);
139
+
140
+ // Derive an honest overall status from the produced state — never faked.
141
+ const impl = state.implementation as { totalPhases?: number; allGreen?: boolean } | undefined;
142
+ const review = state.review as { verdict?: string } | undefined;
143
+ const phases = impl?.totalPhases ?? 0;
144
+ const green = impl?.allGreen === true;
145
+ const verdict = review?.verdict;
146
+ const approved = verdict === "Approved" || verdict === "Approved with Comments";
147
+ const reviewRan = review !== undefined;
148
+
149
+ let status: RunStatus;
150
+ if (phases === 0) {
151
+ status = "failed"; // no implementation produced (gate aborted, or spec had no phases)
152
+ } else if (green && (!reviewRan || approved)) {
153
+ status = "success";
154
+ } else {
155
+ status = "partial";
156
+ }
157
+
158
+ // Deduped list of stages that ended in `failed` (with their error).
159
+ const seen = new Set<string>();
160
+ const failedStages: { label: string; error?: string }[] = [];
161
+ for (const r of ctx.results) {
162
+ if (r.status === "failed" && !seen.has(r.id)) {
163
+ seen.add(r.id);
164
+ failedStages.push({ label: r.label || r.id, error: r.error });
165
+ }
166
+ }
167
+
168
+ return {
169
+ workflowId: workflow.id,
170
+ specIdentifier: state.setup?.specIdentifier ?? "",
171
+ worktreePath: state.setup?.worktreePath ?? options.cwd ?? process.cwd(),
172
+ specDirectory: state.setup?.specDirectory ?? "",
173
+ agentsSpawned: ctx.budget.count,
174
+ state,
175
+ status,
176
+ failedStages,
177
+ error: abortError,
178
+ };
179
+ }
180
+
181
+ export { makeContext };