feat(P01): add ideation engine + ciagent ideate command — IDEATE-01,02,03,17 + MULTI-01
---ci---
phase: 1
milestone: v0.10
status: execute
decisions:
- id: D-080
decision: Three-tier ideation (mechanical, backend-enriched, cross-project)
rationale: Mechanical tier always produces output without backend
confidence: 0.92
- id: D-089
decision: No separate codebase map command
rationale: Git-native + .ciagent/ covers mapping; avoids tree-sitter dep
confidence: 0.88
requirements:
covered:
- IDEATE-01
- IDEATE-02
- IDEATE-03
- IDEATE-17
- MULTI-01
---/ci---
Add IdeationEngine core module with 15 signal collectors:
- Uncovered/partial requirements from REQUIREMENTS.md
- Coverage gaps (documented but unimplemented agents)
- Repeated lessons from git history
- Low-confidence decisions from ---ci--- blocks
- Escalation patterns from git history
- Compound solution patterns
- Architecture drift (ARCHITECTURE.md vs src/)
- Verification inversion (missing test files)
- Improvement patterns (cross-referencing lessons + requirements)
- Spec ambiguity (should/could/might patterns)
- Spec missing (common requirement categories)
- Cascade impact (--affected from git diff)
- External signals (npm audit, dependency staleness)
- Cross-project lesson mining
Add ciagent ideate CLI command with flags:
--category, --affected, --spec, --external, --cross-project, --output
Add active_projects to CIAgentConfig (backwards compatible with active_project).
Add IDEATE pipeline stage between RESEARCH and PLAN.
Update IdeationAgent to delegate to IdeationEngine.
533 tests passing.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Command } from "commander";
|
||||
import { CIAgentConfig, AutonomyLevel } from "../types/config.js";
|
||||
import { IdeationCategory, Idea } from "../types/ideation.js";
|
||||
import { initCIAgent, loadConfig, isCIAgentInitialized, saveConfig } from "../core/config.js";
|
||||
import { Specification, parseSpecification } from "../types/specification.js";
|
||||
import { saveSpecification } from "../core/clarify.js";
|
||||
@@ -941,4 +942,145 @@ function getPreviousTag(projectPath: string, currentTag: string): string | null
|
||||
} catch {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function createIdeateCommand(): Command {
|
||||
return new Command("ideate")
|
||||
.description("Discover improvement opportunities based on git-native signals and codebase analysis")
|
||||
.option("-c, --category <categories>", "Focus on specific categories: security,quality,architecture,coverage,improvement,spec,chaos (comma-separated)")
|
||||
.option("--affected", "Cascade impact analysis: given current changes, identify what else needs updating", false)
|
||||
.option("--spec", "Analyze specification completeness and ambiguity", false)
|
||||
.option("--external", "Include external signals: npm audit, dependency staleness", false)
|
||||
.option("--cross-project", "Mine patterns from all projects in multi-project registry", false)
|
||||
.option("--output <format>", "Output format: interactive, json, markdown", "interactive")
|
||||
.option("--project <slug>", "Target project slug (comma-separated or 'all')")
|
||||
.action(async (options) => {
|
||||
const projectPath = process.cwd();
|
||||
|
||||
if (!isCIAgentInitialized(projectPath)) {
|
||||
console.error("CIAgent project not initialized in this directory.");
|
||||
console.error("Run 'ciagent init' to get started.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const ciFiles = new CIAgentFiles(projectPath);
|
||||
let slug = options.project || ciFiles.getActiveProject() || "default";
|
||||
const allProjects = slug === "all";
|
||||
|
||||
if (options.project) {
|
||||
ciFiles.setProjectSlug(options.project);
|
||||
}
|
||||
|
||||
const categories: IdeationCategory[] = options.category
|
||||
? options.category.split(",").map((c: string) => c.trim() as IdeationCategory)
|
||||
: [];
|
||||
|
||||
console.log("\n─── CIAgent Ideation ───");
|
||||
console.log(`Project: ${ciFiles.getProjectSlug() || "default"}`);
|
||||
|
||||
const config = loadConfig(projectPath);
|
||||
|
||||
console.log("\nMining git history for patterns...");
|
||||
|
||||
const { IdeationEngine } = await import("../core/ideation.js");
|
||||
const engine = new IdeationEngine(projectPath, ciFiles.getProjectSlug() || undefined);
|
||||
|
||||
let allIdeas: Idea[] = [];
|
||||
|
||||
console.log("Running mechanical analysis (tier 1)...");
|
||||
allIdeas = engine.runMechanical(categories.length > 0 ? categories : undefined);
|
||||
|
||||
if (options.affected) {
|
||||
console.log("Running cascade impact analysis (--affected)...");
|
||||
const affectedIdeas = engine.runAffected();
|
||||
allIdeas = [...allIdeas, ...affectedIdeas];
|
||||
}
|
||||
|
||||
if (options.spec) {
|
||||
console.log("Running specification analysis (--spec)...");
|
||||
const specIdeas = engine.runMechanical(["spec"]);
|
||||
const newSpecIdeas = specIdeas.filter(
|
||||
(idea: Idea) => !allIdeas.some((existing: Idea) => existing.title === idea.title)
|
||||
);
|
||||
allIdeas = [...allIdeas, ...newSpecIdeas];
|
||||
}
|
||||
|
||||
if (options.external) {
|
||||
console.log("Running external signal analysis (--external)...");
|
||||
const externalIdeas = engine.runExternal();
|
||||
allIdeas = [...allIdeas, ...externalIdeas];
|
||||
}
|
||||
|
||||
if (options.crossProject && ciFiles.isMultiProject()) {
|
||||
console.log("Running cross-project pattern mining (--cross-project)...");
|
||||
const crossProjectIdeas = engine.runCrossProject();
|
||||
allIdeas = [...allIdeas, ...crossProjectIdeas];
|
||||
}
|
||||
|
||||
const seen = new Set<string>();
|
||||
allIdeas = allIdeas.filter((idea: Idea) => {
|
||||
if (seen.has(idea.title)) return false;
|
||||
seen.add(idea.title);
|
||||
return true;
|
||||
});
|
||||
|
||||
allIdeas.sort((a: Idea, b: Idea) => b.confidence - a.confidence);
|
||||
|
||||
if (options.output === "json") {
|
||||
const result = engine.formatIdeasJson(allIdeas);
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.output === "markdown") {
|
||||
console.log("\n## Ideation Results\n");
|
||||
if (allIdeas.length === 0) {
|
||||
console.log("No improvement ideas identified for this project.");
|
||||
return;
|
||||
}
|
||||
for (const idea of allIdeas) {
|
||||
console.log(`### ${idea.title}`);
|
||||
console.log(`- **Category**: ${idea.category}`);
|
||||
console.log(`- **Source**: ${idea.source}`);
|
||||
console.log(`- **Confidence**: ${idea.confidence.toFixed(2)}`);
|
||||
console.log(`- **Tier**: ${idea.tier}`);
|
||||
console.log(`- **Rationale**: ${idea.rationale}`);
|
||||
if (idea.relatedReq) console.log(`- **Related Req**: ${idea.relatedReq}`);
|
||||
console.log(`- **Actions**: ${idea.actions.join(", ")}`);
|
||||
console.log("");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\nFound ${allIdeas.length} improvement ${allIdeas.length === 1 ? "idea" : "ideas"}\n`);
|
||||
|
||||
if (allIdeas.length === 0) {
|
||||
console.log("No improvement ideas identified for this project.");
|
||||
console.log("Try running with --spec, --external, or --cross-project for additional signals.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < allIdeas.length; i++) {
|
||||
const idea = allIdeas[i];
|
||||
console.log(`═══ Recommendation ${i + 1} of ${allIdeas.length} ═══\n`);
|
||||
console.log(`Category: ${idea.category.toUpperCase()} | Confidence: ${idea.confidence.toFixed(2)} | Tier: ${idea.tier}`);
|
||||
console.log(`Title: ${idea.title}`);
|
||||
console.log(`Rationale: ${idea.rationale}`);
|
||||
if (idea.relatedReq) console.log(`Related Req: ${idea.relatedReq}`);
|
||||
console.log(`Source: ${idea.source}`);
|
||||
console.log(`Actions: ${idea.actions.join(", ")}`);
|
||||
console.log("");
|
||||
}
|
||||
|
||||
const byCategory: Record<string, number> = {};
|
||||
for (const idea of allIdeas) {
|
||||
byCategory[idea.category] = (byCategory[idea.category] || 0) + 1;
|
||||
}
|
||||
|
||||
console.log("─── Summary ───\n");
|
||||
console.log(`Total ideas: ${allIdeas.length}`);
|
||||
for (const [cat, count] of Object.entries(byCategory)) {
|
||||
console.log(` ${cat}: ${count}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user