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.
- package/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +135 -0
- package/agents/adversarial-reviewer.md +64 -0
- package/agents/architecture-designer.md +43 -0
- package/agents/architecture-improver.md +46 -0
- package/agents/bdd-scenario-writer.md +37 -0
- package/agents/build-cleaner.md +44 -0
- package/agents/code-assessor.md +24 -0
- package/agents/code-reviewer.md +59 -0
- package/agents/debug-analyzer.md +54 -0
- package/agents/docs-executor.md +49 -0
- package/agents/handoff-writer.md +62 -0
- package/agents/implementer.md +47 -0
- package/agents/orchestrator.md +42 -0
- package/agents/product-designer.md +42 -0
- package/agents/prototype-runner.md +36 -0
- package/agents/qa-agent.md +76 -0
- package/agents/requirements-clarifier.md +58 -0
- package/agents/research-agent.md +33 -0
- package/agents/spec-reviewer.md +46 -0
- package/agents/spec-writer.md +32 -0
- package/agents/tdd-guide.md +51 -0
- package/agents/ui-ux-designer.md +50 -0
- package/package.json +40 -0
- package/skills/super-dev/SKILL.md +35 -0
- package/src/agents.ts +38 -0
- package/src/control.ts +85 -0
- package/src/doc-validators.ts +164 -0
- package/src/extension.ts +164 -0
- package/src/helpers.ts +263 -0
- package/src/nodes.ts +550 -0
- package/src/pi-spawn.ts +296 -0
- package/src/pipeline.ts +15 -0
- package/src/prompts.ts +120 -0
- package/src/session-agent.ts +305 -0
- package/src/setup.ts +141 -0
- package/src/stages/design.ts +33 -0
- package/src/stages/implementation.ts +80 -0
- package/src/stages/index.ts +172 -0
- package/src/stages/prototype.ts +43 -0
- package/src/stages/setup.ts +32 -0
- package/src/stages/writers.ts +105 -0
- package/src/types.ts +235 -0
- package/src/workflow.ts +181 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The super-dev workflow, expressed as a tree of control-flow nodes.
|
|
3
|
+
*
|
|
4
|
+
* This is the declarative pipeline definition. To customize:
|
|
5
|
+
* - Remove a stage: delete the node from the sequence.
|
|
6
|
+
* - Reorder: move nodes (mind data dependencies — a node reads upstream
|
|
7
|
+
* artifacts by state key, e.g. `state.spec` is written by the spec stage).
|
|
8
|
+
* - Add a stage: write a `Stage` (or compose control nodes), insert it.
|
|
9
|
+
* - Replace a stage: swap the node (keep the same output state key).
|
|
10
|
+
* - Change control flow: swap a `task` for `branch`/`gate`/`loop`/`parallel`/
|
|
11
|
+
* `retry`/`map`/`wait`/`tryCatch` from `nodes.ts`.
|
|
12
|
+
*
|
|
13
|
+
* The runner (`workflow.ts`) never changes.
|
|
14
|
+
*
|
|
15
|
+
* setup ─► classify ─► gate(requirements) ─► gate(bdd) ─► gate(research) ─►
|
|
16
|
+
* branch[bug]→debug ─► assessment ─► design ─► prototype ─►
|
|
17
|
+
* gate(spec) ─► gate(specReview) ─► implementation ─►
|
|
18
|
+
* loop{ code-review = parallel[review,adversarial]→merge + fix } ─►
|
|
19
|
+
* docs ─► cleanup ─► branch[!blocked]→merge
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { task, sequence, branch, gate, loop, parallel, noop, gateValidator } from "../nodes.ts";
|
|
23
|
+
import type { ControlObj, PipelineState, StageContext, Workflow } from "../types.ts";
|
|
24
|
+
import { setupStage } from "./setup.ts";
|
|
25
|
+
import { classifyStage, cleanupTask, requirementsWriter, bddWriter, researchWriter, debugWriter, assessmentWriter, specWriter, specReviewWriter, docsWriter, mergeWriter } from "./writers.ts";
|
|
26
|
+
import { designStage } from "./design.ts";
|
|
27
|
+
import { prototypeStage } from "./prototype.ts";
|
|
28
|
+
import { implementationStage } from "./implementation.ts";
|
|
29
|
+
import { buildCodeReviewPrompt, buildAdversarialPrompt, buildFixPrompt } from "../prompts.ts";
|
|
30
|
+
|
|
31
|
+
// ─── Predicates ─────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const isBug = (s: PipelineState) => s.classify?.taskType === "bug";
|
|
34
|
+
|
|
35
|
+
/** Merge only when cleanup actually ran AND found nothing blocking. Treating a
|
|
36
|
+
* missing cleanup result as "safe to merge" is a vacuous pass — cleanup may
|
|
37
|
+
* simply have failed to produce output. */
|
|
38
|
+
const notBlocked = (s: PipelineState) => {
|
|
39
|
+
const c = s.cleanup as { blocked?: boolean } | undefined;
|
|
40
|
+
return !!c && c.blocked !== true;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Only review when there is actually an implementation to review. */
|
|
44
|
+
const hasImplementation = (s: PipelineState) =>
|
|
45
|
+
((s.implementation as { totalPhases?: number } | undefined)?.totalPhases ?? 0) > 0;
|
|
46
|
+
|
|
47
|
+
/** Research is complete once it has actually produced a report. Open issues
|
|
48
|
+
* are NORMAL research output (the prompt asks for them) — they flow forward
|
|
49
|
+
* to the spec/assessment stages, so they must NOT block the pipeline. */
|
|
50
|
+
const researchComplete = async (s: PipelineState, ctx: StageContext) => {
|
|
51
|
+
const r = s.research as { docPath?: string; openIssues?: unknown[] } | undefined;
|
|
52
|
+
if (!r || !r.docPath) {
|
|
53
|
+
ctx.log("Research: no report produced (agent returned nothing or timed out)");
|
|
54
|
+
return { pass: false, errors: ["no research report produced (agent returned nothing or timed out)"] };
|
|
55
|
+
}
|
|
56
|
+
const open = (r.openIssues as unknown[]) ?? [];
|
|
57
|
+
if (open.length > 0) ctx.log(`Research: ${open.length} open issue(s) noted — forwarded to spec/assessment`);
|
|
58
|
+
return { pass: true, errors: [] };
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Code review is approved when the merged verdict is Approved (with or without comments). */
|
|
62
|
+
const reviewApproved = (s: PipelineState) => {
|
|
63
|
+
const v = s.review?.verdict as string | undefined;
|
|
64
|
+
return v === "Approved" || v === "Approved with Comments";
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ─── Code review: parallel reviewers → merge, in a loop with fixes ──────────
|
|
68
|
+
|
|
69
|
+
const setupOf = (s: PipelineState) => s.setup!;
|
|
70
|
+
|
|
71
|
+
const codeReviewNode = loop(
|
|
72
|
+
{ until: reviewApproved, times: 3 },
|
|
73
|
+
sequence([
|
|
74
|
+
// Parallel split + synchronization: two reviewers converge into one merged verdict.
|
|
75
|
+
parallel(
|
|
76
|
+
[
|
|
77
|
+
task({
|
|
78
|
+
id: "codeReview",
|
|
79
|
+
label: "Stage 10a — Code Review",
|
|
80
|
+
async run(s, ctx) {
|
|
81
|
+
if (!ctx.budget.check()) return undefined;
|
|
82
|
+
const r = await ctx.agent({ id: "pipeline.code-review.review", agent: "code-reviewer", prompt: buildCodeReviewPrompt(setupOf(s), s.classify ?? null, ctx.task, s.spec ?? null, s.implementation ?? {}) });
|
|
83
|
+
return r.control ?? {};
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
task({
|
|
87
|
+
id: "adversarialReview",
|
|
88
|
+
label: "Stage 10b — Adversarial Review",
|
|
89
|
+
async run(s, ctx) {
|
|
90
|
+
if (!ctx.budget.check()) return undefined;
|
|
91
|
+
const r = await ctx.agent({ id: "pipeline.code-review.adversarial", agent: "adversarial-reviewer", prompt: buildAdversarialPrompt(setupOf(s), s.classify ?? null, ctx.task, s.spec ?? null, s.implementation ?? {}) });
|
|
92
|
+
return r.control ?? {};
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
],
|
|
96
|
+
{
|
|
97
|
+
into: "review",
|
|
98
|
+
join: async (_results, s, ctx) =>
|
|
99
|
+
(await ctx.helper({ name: "merge-review-verdicts", sources: { "code-review": s.codeReview ?? {}, "adversarial-review": s.adversarialReview ?? {} } })).value,
|
|
100
|
+
},
|
|
101
|
+
),
|
|
102
|
+
// If not approved, address findings before the next review round.
|
|
103
|
+
branch(reviewApproved, {
|
|
104
|
+
yes: noop(),
|
|
105
|
+
no: task({
|
|
106
|
+
id: "reviewFix",
|
|
107
|
+
label: "Stage 10c — Address Review Findings",
|
|
108
|
+
async run(s, ctx) {
|
|
109
|
+
if (!ctx.budget.check()) return undefined;
|
|
110
|
+
const findings = (s.review?.findings as unknown[]) ?? [];
|
|
111
|
+
const r = await ctx.agent({ id: "pipeline.code-review.fix", agent: "implementer", prompt: buildFixPrompt(setupOf(s), s.classify ?? null, findings) });
|
|
112
|
+
return r.control ?? {};
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
}),
|
|
116
|
+
]),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// ─── The pipeline ───────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
const pipeline = sequence(
|
|
122
|
+
[
|
|
123
|
+
task(setupStage),
|
|
124
|
+
task(classifyStage),
|
|
125
|
+
// Quality-gate loops: write → validate → re-write until the gate passes.
|
|
126
|
+
// Retries CONVERGE (the validator's errors are fed into the next attempt's
|
|
127
|
+
// prompt) and exhaustion is NON-FATAL (the pipeline proceeds with the
|
|
128
|
+
// best-available artifact rather than discarding every prior stage's work).
|
|
129
|
+
// Spec review is intentionally NOT gated — its verdict is signal, not a block.
|
|
130
|
+
gate({ validate: gateValidator("gate-requirements", "write-requirements", "requirements"), feedbackKey: "requirements", attempts: 4 }, task(requirementsWriter)),
|
|
131
|
+
gate({ validate: gateValidator("gate-bdd", "write-bdd", "bdd"), feedbackKey: "bdd", attempts: 4 }, task(bddWriter)),
|
|
132
|
+
gate({ validate: researchComplete, feedbackKey: "research", attempts: 4 }, task(researchWriter)),
|
|
133
|
+
// Conditional branch: debug analysis only for bug fixes.
|
|
134
|
+
branch(isBug, { yes: task(debugWriter) }),
|
|
135
|
+
task(assessmentWriter),
|
|
136
|
+
task(designStage),
|
|
137
|
+
task(prototypeStage),
|
|
138
|
+
gate({ validate: gateValidator("gate-spec-trace", "write-spec", "spec"), feedbackKey: "spec", attempts: 4 }, task(specWriter)),
|
|
139
|
+
// Spec review is SIGNAL, not a gate: a "Changes Requested" verdict is a
|
|
140
|
+
// judgment call whose findings flow forward to implementation/code-review.
|
|
141
|
+
// Blocking on it (the old fatal gate) aborted runs on a subjective verdict.
|
|
142
|
+
task(specReviewWriter),
|
|
143
|
+
task(implementationStage),
|
|
144
|
+
// Code review only runs when implementation actually produced phases;
|
|
145
|
+
// otherwise we'd burn ~9 spawns reviewing nothing.
|
|
146
|
+
branch(hasImplementation, { yes: codeReviewNode }),
|
|
147
|
+
task(docsWriter),
|
|
148
|
+
task(cleanupTask),
|
|
149
|
+
// Conditional branch: merge only if cleanup found no sensitive data.
|
|
150
|
+
branch(notBlocked, { yes: task(mergeWriter) }),
|
|
151
|
+
],
|
|
152
|
+
{ tolerant: true }, // best-effort: a non-setup stage failure is logged, not fatal
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
export const SUPER_DEV_WORKFLOW: Workflow = {
|
|
156
|
+
id: "super-dev",
|
|
157
|
+
description:
|
|
158
|
+
"13-stage development pipeline composed from control-flow nodes: classify → requirements → BDD → research → [debug] → assessment → design → [prototype] → spec → spec-review → implementation (TDD) → code review → docs → cleanup → merge.",
|
|
159
|
+
root: pipeline,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Re-exports for users composing custom workflows.
|
|
163
|
+
export { task, sequence, branch, gate, loop, parallel, noop, gateValidator } from "../nodes.ts";
|
|
164
|
+
export { setupStage } from "./setup.ts";
|
|
165
|
+
export {
|
|
166
|
+
classifyStage, cleanupTask, requirementsWriter, bddWriter, researchWriter,
|
|
167
|
+
debugWriter, assessmentWriter, specWriter, specReviewWriter, docsWriter, mergeWriter,
|
|
168
|
+
} from "./writers.ts";
|
|
169
|
+
export { designStage } from "./design.ts";
|
|
170
|
+
export { prototypeStage } from "./prototype.ts";
|
|
171
|
+
export { implementationStage } from "./implementation.ts";
|
|
172
|
+
export type { ControlObj };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage 6.5 — Prototype (conditional + loop).
|
|
3
|
+
* Self-contained task: only runs when the design declares numeric constants
|
|
4
|
+
* (decided by check-prototype-needed); loops up to 3 rounds until pass.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ControlObj, Stage } from "../types.ts";
|
|
8
|
+
import { buildPrototypePrompt } from "../prompts.ts";
|
|
9
|
+
|
|
10
|
+
const MAX_ROUNDS = 3;
|
|
11
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
12
|
+
|
|
13
|
+
export const prototypeStage: Stage = {
|
|
14
|
+
id: "prototype",
|
|
15
|
+
label: "Stage 6.5 — Prototype",
|
|
16
|
+
async run(state, ctx) {
|
|
17
|
+
const design = state.design ?? null;
|
|
18
|
+
if (!design) return null;
|
|
19
|
+
const check = await ctx.helper({ name: "check-prototype-needed", sources: { design } });
|
|
20
|
+
if (!check.value.needed) {
|
|
21
|
+
ctx.log("Prototype not needed — no numeric constants to validate");
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const constants = (check.value.constants as string[]) ?? [];
|
|
25
|
+
const setup = state.setup!;
|
|
26
|
+
let last: ControlObj | null = null;
|
|
27
|
+
for (let round = 1; round <= MAX_ROUNDS; round++) {
|
|
28
|
+
if (!ctx.budget.check()) break;
|
|
29
|
+
const result = await ctx.agent({
|
|
30
|
+
id: `pipeline.prototype.r${pad(round)}`,
|
|
31
|
+
agent: "prototype-runner",
|
|
32
|
+
prompt: buildPrototypePrompt(setup, state.classify ?? null, ctx.task, design, constants, round),
|
|
33
|
+
});
|
|
34
|
+
last = result.control ?? null;
|
|
35
|
+
if (last?.verdict === "pass") {
|
|
36
|
+
ctx.log(`Prototype validation PASS on round ${round}`);
|
|
37
|
+
return last;
|
|
38
|
+
}
|
|
39
|
+
ctx.log(`Prototype round ${round}/${MAX_ROUNDS}: verdict=${last?.verdict ?? "unknown"}`);
|
|
40
|
+
}
|
|
41
|
+
return last;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage 1 — Setup (deterministic, fatal).
|
|
3
|
+
* Detects language/framework, creates a git worktree, creates the spec dir.
|
|
4
|
+
* Fatal: failure aborts the whole workflow.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Stage } from "../types.ts";
|
|
8
|
+
import { runSetup } from "../setup.ts";
|
|
9
|
+
import { abbreviatePath } from "../pi-spawn.ts";
|
|
10
|
+
import { summarizeSlug } from "../session-agent.ts";
|
|
11
|
+
|
|
12
|
+
export const setupStage: Stage = {
|
|
13
|
+
id: "setup",
|
|
14
|
+
label: "Stage 1 — Setup",
|
|
15
|
+
fatal: true,
|
|
16
|
+
async run(_state, ctx) {
|
|
17
|
+
const cwd = ctx.options.cwd ?? process.cwd();
|
|
18
|
+
// Try an LLM-summarized spec slug (concise, meaningful); fall back to the
|
|
19
|
+
// deterministic slugifyTask inside runSetup on any failure/timeout.
|
|
20
|
+
let slug = "";
|
|
21
|
+
try {
|
|
22
|
+
slug = await summarizeSlug(ctx.task, cwd, { signal: ctx.signal });
|
|
23
|
+
} catch { /* fallback below */ }
|
|
24
|
+
const setup = runSetup(ctx.task, { cwd: ctx.options.cwd, skipWorktree: ctx.options.skipWorktree, slug });
|
|
25
|
+
const relWorktree = abbreviatePath(setup.worktreePath, cwd);
|
|
26
|
+
const relSpec = abbreviatePath(setup.specDirectory, setup.worktreePath) || ".";
|
|
27
|
+
ctx.log(`Setup: spec ${setup.specIdentifier} | ${setup.language}${setup.isWebUi ? " (Web UI)" : ""} | branch ${setup.defaultBranch}`);
|
|
28
|
+
ctx.log(`Worktree: ${relWorktree}${setup.worktreeCreated ? " (created)" : " (in-place)"}${setup.initializedRepo ? "; git init'd" : ""}`);
|
|
29
|
+
ctx.log(`Spec dir: ${relSpec}`);
|
|
30
|
+
return setup;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leaf stages built from the convenience builders in `nodes.ts`:
|
|
3
|
+
* - single-shot agent "writer" tasks (wrapped in `gate`/`loop` upstream)
|
|
4
|
+
* - deterministic helper tasks (classify, cleanup)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { writerTask, helperTask } from "../nodes.ts";
|
|
8
|
+
import type { Stage, SetupControl } from "../types.ts";
|
|
9
|
+
import * as P from "../prompts.ts";
|
|
10
|
+
|
|
11
|
+
const S = (s: { setup?: SetupControl }) => s.setup!;
|
|
12
|
+
|
|
13
|
+
export const requirementsWriter: Stage = writerTask({
|
|
14
|
+
id: "requirements",
|
|
15
|
+
label: "Stage 2B — Requirements",
|
|
16
|
+
agent: "requirements-clarifier",
|
|
17
|
+
buildPrompt: (state, ctx) => P.buildRequirementsPrompt(S(state), state.classify ?? null, ctx.task),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const bddWriter: Stage = writerTask({
|
|
21
|
+
id: "bdd",
|
|
22
|
+
label: "Stage 2C — BDD Scenarios",
|
|
23
|
+
agent: "bdd-scenario-writer",
|
|
24
|
+
requires: ["*-requirements.md"],
|
|
25
|
+
buildPrompt: (state, ctx) => P.buildBddPrompt(S(state), state.classify ?? null, ctx.task, state.requirements ?? null),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const researchWriter: Stage = writerTask({
|
|
29
|
+
id: "research",
|
|
30
|
+
label: "Stage 3 — Research",
|
|
31
|
+
agent: "research-agent",
|
|
32
|
+
requires: ["*-requirements.md"],
|
|
33
|
+
buildPrompt: (state, ctx) =>
|
|
34
|
+
P.buildResearchPrompt(S(state), state.classify ?? null, ctx.task, state.requirements ?? null, state.bdd ?? null, state.research ?? null),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const debugWriter: Stage = writerTask({
|
|
38
|
+
id: "debug",
|
|
39
|
+
label: "Stage 4 — Debug Analysis",
|
|
40
|
+
agent: "debug-analyzer",
|
|
41
|
+
requires: ["*-requirements.md"],
|
|
42
|
+
buildPrompt: (state, ctx) => P.buildDebugPrompt(S(state), state.classify ?? null, ctx.task, state.requirements ?? null, state.research ?? null),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const assessmentWriter: Stage = writerTask({
|
|
46
|
+
id: "assessment",
|
|
47
|
+
label: "Stage 5 — Code Assessment",
|
|
48
|
+
agent: "code-assessor",
|
|
49
|
+
buildPrompt: (state, ctx) => P.buildAssessmentPrompt(S(state), state.classify ?? null, ctx.task, state.research ?? null, state.debug ?? null),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const specWriter: Stage = writerTask({
|
|
53
|
+
id: "spec",
|
|
54
|
+
label: "Stage 7 — Specification",
|
|
55
|
+
agent: "spec-writer",
|
|
56
|
+
requires: ["*-requirements.md", "*-bdd-scenarios.md"],
|
|
57
|
+
buildPrompt: (state, ctx) =>
|
|
58
|
+
P.buildSpecPrompt(S(state), state.classify ?? null, ctx.task, state.requirements ?? null, state.bdd ?? null, state.research ?? null, state.assessment ?? null, state.design ?? null),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export const specReviewWriter: Stage = writerTask({
|
|
62
|
+
id: "specReview",
|
|
63
|
+
label: "Stage 8 — Spec Review",
|
|
64
|
+
agent: "spec-reviewer",
|
|
65
|
+
requires: ["*-specification.md", "*-implementation-plan.md", "*-task-list.md"],
|
|
66
|
+
buildPrompt: (state) => P.buildSpecReviewPrompt(S(state), state.classify ?? null, state.spec ?? null),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const docsWriter: Stage = writerTask({
|
|
70
|
+
id: "docs",
|
|
71
|
+
label: "Stage 11 — Documentation",
|
|
72
|
+
agent: "docs-executor",
|
|
73
|
+
requires: ["*-specification.md"],
|
|
74
|
+
buildPrompt: (state, ctx) => P.buildDocsPrompt(S(state), state.classify ?? null, ctx.task, state.spec ?? null),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const mergeWriter: Stage = writerTask({
|
|
78
|
+
id: "merge",
|
|
79
|
+
label: "Stage 13 — Merge",
|
|
80
|
+
agent: "orchestrator",
|
|
81
|
+
buildPrompt: (state) => P.buildMergePrompt(S(state)),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/** Classify the task via a helper. Needs the runtime task text from ctx. */
|
|
85
|
+
export const classifyStage: Stage = {
|
|
86
|
+
id: "classify",
|
|
87
|
+
label: "Stage 2A — Classify Task",
|
|
88
|
+
async run(state, ctx) {
|
|
89
|
+
const result = await ctx.helper({
|
|
90
|
+
name: "classify-task",
|
|
91
|
+
sources: { setup: state.setup },
|
|
92
|
+
options: { runtimeTask: ctx.task },
|
|
93
|
+
});
|
|
94
|
+
return result.value;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Scan the worktree for build artifacts + sensitive data; decide merge blocking. */
|
|
99
|
+
export const cleanupTask: Stage = helperTask({
|
|
100
|
+
id: "cleanup",
|
|
101
|
+
label: "Stage 12 — Cleanup",
|
|
102
|
+
helper: "cleanup",
|
|
103
|
+
sources: (state) => ({ docs: state.docs ?? {} }),
|
|
104
|
+
context: (state) => ({ cwd: state.setup?.worktreePath ?? "" }),
|
|
105
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type system for the self-contained super-dev control-flow engine.
|
|
3
|
+
*
|
|
4
|
+
* Architecture: a pipeline is a tree of `Node`s evaluated over a shared
|
|
5
|
+
* `PipelineState`. Leaf nodes (`task`) wrap a `Stage` (a unit of work that
|
|
6
|
+
* spawns agents / runs helpers). Control nodes (`sequence`, `branch`,
|
|
7
|
+
* `parallel`, `loop`, `retry`, `gate`, `map`, `wait`, `tryCatch`, ...) compose
|
|
8
|
+
* nodes and are self-evaluating: each implements `run(state, ctx)`. The engine
|
|
9
|
+
* itself is just `await root.run(state, ctx)` — adding a new control construct
|
|
10
|
+
* means writing one builder function in `nodes.ts`, never touching the runner.
|
|
11
|
+
*
|
|
12
|
+
* Zero dependency on @agwab/pi-workflow: agents are spawned directly as `pi`
|
|
13
|
+
* child processes (see `pi-spawn.ts`).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { EventEmitter } from "node:events";
|
|
17
|
+
|
|
18
|
+
// ─── Primitive result types ─────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export type ControlObj = Record<string, unknown>;
|
|
21
|
+
|
|
22
|
+
/** Result of parsing an agent's final assistant message. */
|
|
23
|
+
export interface SpawnResult {
|
|
24
|
+
text: string;
|
|
25
|
+
control: ControlObj | null;
|
|
26
|
+
model?: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AgentCall {
|
|
31
|
+
id: string;
|
|
32
|
+
agent: string;
|
|
33
|
+
prompt: string;
|
|
34
|
+
/** Control keys the caller expects back (for the session backend's
|
|
35
|
+
* structured_output schema). Optional; omitted for non-writer calls. */
|
|
36
|
+
controlKeys?: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AgentResult extends SpawnResult {}
|
|
40
|
+
|
|
41
|
+
export interface HelperCall {
|
|
42
|
+
name: string;
|
|
43
|
+
sources: Record<string, unknown>;
|
|
44
|
+
options?: Record<string, unknown>;
|
|
45
|
+
context?: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface HelperResult {
|
|
49
|
+
value: ControlObj;
|
|
50
|
+
digest: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface Budget {
|
|
54
|
+
check(): boolean;
|
|
55
|
+
spent(): void;
|
|
56
|
+
count: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ProgressSink {
|
|
60
|
+
phase(label: string): void;
|
|
61
|
+
log(message: string): void;
|
|
62
|
+
/** Live streaming text from the active agent (typing effect). `partial` is the
|
|
63
|
+
* full accumulated text of the current text block so far. */
|
|
64
|
+
text(partial: string): void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Streaming callbacks from a spawned agent to the progress sink. */
|
|
68
|
+
export interface AgentProgress {
|
|
69
|
+
/** A permanent log line (tool call, turn marker, finalized agent text). */
|
|
70
|
+
event(message: string): void;
|
|
71
|
+
/** Live partial text as the agent generates it (control block stripped). */
|
|
72
|
+
text(partial: string): void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── Domain shapes ──────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
export interface SetupControl {
|
|
78
|
+
worktreePath: string;
|
|
79
|
+
specDirectory: string;
|
|
80
|
+
defaultBranch: string;
|
|
81
|
+
language: string;
|
|
82
|
+
isWebUi: boolean;
|
|
83
|
+
specIdentifier: string;
|
|
84
|
+
/** True when an isolated git worktree was created (vs. operating in cwd). */
|
|
85
|
+
worktreeCreated: boolean;
|
|
86
|
+
/** True when setup had to `git init` the directory first. */
|
|
87
|
+
initializedRepo: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface Classification {
|
|
91
|
+
taskType: "bug" | "feature" | "refactor";
|
|
92
|
+
uiScope: string;
|
|
93
|
+
language: string;
|
|
94
|
+
isWebUi: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── Pipeline state (shared blackboard) ─────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Mutable state threaded through every node. A `task` node stores its return
|
|
101
|
+
* value under `state[stage.id]`. Control nodes read upstream artifacts by key.
|
|
102
|
+
* The index signature allows custom stages without extending the interface.
|
|
103
|
+
*/
|
|
104
|
+
export interface PipelineState {
|
|
105
|
+
setup?: SetupControl;
|
|
106
|
+
classify?: Classification;
|
|
107
|
+
requirements?: ControlObj;
|
|
108
|
+
bdd?: ControlObj;
|
|
109
|
+
research?: ControlObj;
|
|
110
|
+
debug?: ControlObj;
|
|
111
|
+
assessment?: ControlObj;
|
|
112
|
+
design?: ControlObj;
|
|
113
|
+
prototype?: ControlObj;
|
|
114
|
+
spec?: ControlObj;
|
|
115
|
+
specReview?: ControlObj;
|
|
116
|
+
implementation?: ControlObj;
|
|
117
|
+
review?: ControlObj;
|
|
118
|
+
codeReview?: ControlObj;
|
|
119
|
+
adversarialReview?: ControlObj;
|
|
120
|
+
docs?: ControlObj;
|
|
121
|
+
cleanup?: ControlObj;
|
|
122
|
+
merge?: ControlObj;
|
|
123
|
+
[index: string]: unknown;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─── Stage (leaf unit of work) ──────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
/** Outcome of one leaf-stage execution, recorded for honest run reporting. */
|
|
129
|
+
export interface StageResult {
|
|
130
|
+
id: string;
|
|
131
|
+
label: string;
|
|
132
|
+
status: NodeStatus;
|
|
133
|
+
error?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Execution primitives handed to every stage. The runner builds one context
|
|
138
|
+
* and passes the same reference around; `agent()` resolves its cwd from
|
|
139
|
+
* `state.setup` (falling back to the run cwd).
|
|
140
|
+
*/
|
|
141
|
+
export interface StageContext {
|
|
142
|
+
task: string;
|
|
143
|
+
options: RunOptions;
|
|
144
|
+
state: PipelineState;
|
|
145
|
+
agent(call: AgentCall): Promise<AgentResult>;
|
|
146
|
+
helper(call: HelperCall): Promise<HelperResult>;
|
|
147
|
+
parallel(calls: Array<() => Promise<AgentResult>>): Promise<AgentResult[]>;
|
|
148
|
+
budget: Budget;
|
|
149
|
+
log(message: string): void;
|
|
150
|
+
events: EventEmitter;
|
|
151
|
+
signal?: AbortSignal;
|
|
152
|
+
/** Every leaf-stage outcome, appended by `task()`. Used for honest summaries. */
|
|
153
|
+
results: StageResult[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** A leaf unit of work. Its return value is stored under `state[id]`. */
|
|
157
|
+
export interface Stage {
|
|
158
|
+
id: string;
|
|
159
|
+
label: string;
|
|
160
|
+
description?: string;
|
|
161
|
+
enabled?: (state: PipelineState) => boolean;
|
|
162
|
+
run: (state: PipelineState, ctx: StageContext) => Promise<unknown>;
|
|
163
|
+
fatal?: boolean;
|
|
164
|
+
/** Upstream artifact docs this stage needs (filename globs in the spec dir,
|
|
165
|
+
* e.g. "*-requirements.md"). task() checks they exist before running and
|
|
166
|
+
* logs ✓/✗, making inter-stage dependencies visible. Missing artifacts are
|
|
167
|
+
* logged (not fatal) — the tolerant pipeline proceeds and the prompt shows
|
|
168
|
+
* "N/A" for absent upstream. */
|
|
169
|
+
requires?: string[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ─── Control-flow node algebra ──────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
export type NodeStatus = "ok" | "skipped" | "failed" | "cancelled";
|
|
175
|
+
|
|
176
|
+
export interface NodeResult {
|
|
177
|
+
status: NodeStatus;
|
|
178
|
+
/** Stored artifact (for tasks) or aggregate (for some control nodes). */
|
|
179
|
+
value?: unknown;
|
|
180
|
+
error?: string;
|
|
181
|
+
/** Round/attempt count reached (for loop/retry/gate). */
|
|
182
|
+
attempts?: number;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* A self-evaluating pipeline node. Leaf `task` nodes do work; control nodes
|
|
187
|
+
* recursively evaluate children. The runner is `await root.run(state, ctx)`.
|
|
188
|
+
*/
|
|
189
|
+
export interface Node {
|
|
190
|
+
kind: string;
|
|
191
|
+
label?: string;
|
|
192
|
+
run(state: PipelineState, ctx: StageContext): Promise<NodeResult>;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** A workflow: a root node plus metadata. */
|
|
196
|
+
export interface Workflow {
|
|
197
|
+
id: string;
|
|
198
|
+
description?: string;
|
|
199
|
+
root: Node;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ─── Run options + summary ──────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
export interface RunOptions {
|
|
205
|
+
cwd?: string;
|
|
206
|
+
skipWorktree?: boolean;
|
|
207
|
+
skipStages?: string[];
|
|
208
|
+
model?: string;
|
|
209
|
+
maxAgents?: number;
|
|
210
|
+
maxConcurrency?: number;
|
|
211
|
+
progress?: ProgressSink;
|
|
212
|
+
signal?: AbortSignal;
|
|
213
|
+
/** Specialist execution backend. "subprocess" (default) = raw `pi` spawn;
|
|
214
|
+
* "session" = in-process `createAgentSession`. Also set via
|
|
215
|
+
* SUPER_DEV_BACKEND env. */
|
|
216
|
+
backend?: "subprocess" | "session";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Honest, derived overall outcome of a run. */
|
|
220
|
+
export type RunStatus = "success" | "partial" | "failed";
|
|
221
|
+
|
|
222
|
+
export interface RunSummary {
|
|
223
|
+
workflowId: string;
|
|
224
|
+
specIdentifier: string;
|
|
225
|
+
worktreePath: string;
|
|
226
|
+
specDirectory: string;
|
|
227
|
+
agentsSpawned: number;
|
|
228
|
+
state: PipelineState;
|
|
229
|
+
/** Derived overall outcome — never faked. */
|
|
230
|
+
status: RunStatus;
|
|
231
|
+
/** Stages that ended in `failed`, with their error message (deduped). */
|
|
232
|
+
failedStages: { label: string; error?: string }[];
|
|
233
|
+
/** Error message when the run aborted (e.g. a fatal gate threw). */
|
|
234
|
+
error?: string;
|
|
235
|
+
}
|