feat(P04): Add IDEATE stage to orchestrator pipeline — IDEATE-16
CI / build-and-test (push) Has been cancelled
CI / build-and-test (pull_request) Has been cancelled

- Add ideation-agent to STAGE_AGENT_MAP for ideate stage
- Implement ideate case in executeStage() with mechanical ideation,
  config-aware category filtering, idea deduplication, auto-accept,
  and ---ci--- commit with decision block
- Add test verifying ideate position between research and plan in
  STAGE_ORDER
- 542 tests passing
This commit is contained in:
Jon Chery
2026-05-30 21:17:21 +00:00
parent 30352a3603
commit 895d9f95a1
2 changed files with 69 additions and 0 deletions
+59
View File
@@ -47,6 +47,7 @@ export class OrchestratorAgent extends BaseAgent {
private static readonly STAGE_AGENT_MAP: Partial<Record<PipelineStage, AgentName[]>> = { private static readonly STAGE_AGENT_MAP: Partial<Record<PipelineStage, AgentName[]>> = {
research: ["researcher"], research: ["researcher"],
ideate: ["ideation-agent"],
plan: ["planner"], plan: ["planner"],
execute: ["executor", "code-reviewer", "security-auditor"], execute: ["executor", "code-reviewer", "security-auditor"],
test: ["tester"], test: ["tester"],
@@ -571,6 +572,64 @@ export class OrchestratorAgent extends BaseAgent {
break; break;
} }
case "ideate": {
this.log("Running ideation stage...");
const { IdeationEngine } = await import("../core/ideation.js");
const ideationEngine = new IdeationEngine(context.project_path);
const ideas = ideationEngine.runMechanical();
const ideationConfig = this.config.ideation;
if (ideationConfig?.categories && ideationConfig.categories.length > 0) {
const categoryIdeas = ideationEngine.runMechanical(ideationConfig.categories);
const seenTitles = new Set(ideas.map((i) => i.title));
for (const idea of categoryIdeas) {
if (!seenTitles.has(idea.title)) {
ideas.push(idea);
seenTitles.add(idea.title);
}
}
}
ideas.sort((a, b) => b.confidence - a.confidence);
const maxIdeas = ideationConfig?.max_ideas || 20;
const trimmedIdeas = ideas.slice(0, maxIdeas);
if (this.config.git.auto_commit && this.gitContext!.isGitRepo()) {
const { accepted: savedIdeas, results } = ideationEngine.acceptIdeas(trimmedIdeas);
const savedCount = results.filter((r) => r.addedToRequirements || r.addedToRoadmap).length;
const ideationCommit = CommitBuilder.buildDecisionCommit({
phase: this.pipelineState!.current_phase,
milestone: this.currentMilestone,
subject: `ideation results — ${trimmedIdeas.length} total, ${savedCount} accepted`,
decisions: savedIdeas.map((idea) => ({
id: idea.id,
decision: idea.title,
rationale: idea.rationale,
confidence: idea.confidence,
alternatives: idea.actions,
})),
});
try {
execSync(`git add -A && git commit -m "${ideationCommit.replace(/"/g, '\\"')}" --allow-empty`, {
cwd: context.project_path,
stdio: "pipe",
});
} catch (err) {
this.warn(`Ideation commit failed: ${err instanceof Error ? err.message : String(err)}`);
}
artifactsCreated.push(".ciagent/REQUIREMENTS.md", ".ciagent/ROADMAP.md");
decisionsMade += savedCount;
}
this.pipelineState!.ideate_completed = true;
this.log(`Ideation stage complete: ${trimmedIdeas.length} ideas generated`);
break;
}
case "plan": case "plan":
this.log("Planning phase execution..."); this.log("Planning phase execution...");
+10
View File
@@ -62,4 +62,14 @@ describe("createInitialPipelineState", () => {
expect(state.started_at).toBeTruthy(); expect(state.started_at).toBeTruthy();
expect(state.last_updated).toBeTruthy(); expect(state.last_updated).toBeTruthy();
}); });
});
describe("STAGE_ORDER ideate position", () => {
it("places ideate between research and plan", () => {
const ideateIdx = STAGE_ORDER.indexOf("ideate");
const researchIdx = STAGE_ORDER.indexOf("research");
const planIdx = STAGE_ORDER.indexOf("plan");
expect(ideateIdx).toBeGreaterThan(researchIdx);
expect(ideateIdx).toBeLessThan(planIdx);
});
}); });