Compare commits

...

1 Commits

Author SHA1 Message Date
Jon Chery 70ee21856d feat(P02): backend-enriched tier, chaos engineering, prioritization — IDEATE-04,05,06,09,10
CI / build-and-test (push) Has been cancelled
CI / build-and-test (pull_request) Has been cancelled
---ci---
phase: 2
milestone: v0.10
status: execute
decisions:
  - id: D-087
    decision: All 6 innovative features in v1 (pattern mining, drift detection, layer inversion, cross-project, chaos, spec)
    rationale: User wants bleeding-edge; all uniquely differentiated
    confidence: 0.82
requirements:
  covered:
    - IDEATE-04
    - IDEATE-05
    - IDEATE-06
    - IDEATE-09
    - IDEATE-10
---/ci---

- IDEATE-04: Verification layer inversion (structural, behavioral, security, quality missing detection)
- IDEATE-05: Architectural drift detection (documented vs actual component comparison)
- IDEATE-06: Spec-driven improvement (ambiguity detection, missing category detection)
- IDEATE-09: Backend-enriched analysis (prioritization, novel suggestions, action plans)
- IDEATE-10: Chaos engineering ideation (backend unavailable, requirement change, coverage drop)
- Deduplicated type exports: IdeationSource/Idea/etc now in types/ideation.ts
- 538 tests passing
2026-05-30 20:50:29 +00:00
2 changed files with 262 additions and 107 deletions
+84 -1
View File
@@ -2,7 +2,8 @@ import * as fs from "node:fs";
import * as path from "node:path"; import * as path from "node:path";
import * as os from "node:os"; import * as os from "node:os";
import { IdeationAgent } from "../agents/ideation-agent.js"; import { IdeationAgent } from "../agents/ideation-agent.js";
import { IdeationEngine, resetIdeaCounter, Idea } from "../core/ideation.js"; import { IdeationEngine, resetIdeaCounter } from "../core/ideation.js";
import { Idea, IdeationAction, DEFAULT_IDEATION_CONFIG } from "../types/ideation.js";
describe("IdeationAgent", () => { describe("IdeationAgent", () => {
let tempDir: string; let tempDir: string;
@@ -244,4 +245,86 @@ describe("IdeationEngine", () => {
expect(results.every((r) => r.addedToRequirements || r.addedToRoadmap)).toBe(true); expect(results.every((r) => r.addedToRequirements || r.addedToRoadmap)).toBe(true);
}); });
}); });
describe("Phase 2: Backend-enriched and chaos", () => {
let tempDir: string;
beforeEach(() => {
resetIdeaCounter();
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-p2-test-"));
});
afterEach(() => {
fs.rmSync(tempDir, { recursive: true, force: true });
});
it("runBackendEnriched prioritizes mechanical findings", () => {
const engine = new IdeationEngine(tempDir);
const mechanicalIdeas: Idea[] = [
{
id: "IDEATE-01",
source: "uncovered_requirement",
category: "coverage",
title: "Missing test",
rationale: "No test file",
confidence: 0.7,
actions: ["add_test"],
tier: "mechanical",
},
{
id: "IDEATE-02",
source: "escalation_pattern",
category: "security",
title: "Security issue",
rationale: "Repeated escalation",
confidence: 0.8,
actions: ["add_security_pattern"],
tier: "mechanical",
},
];
const enriched = engine.runBackendEnriched(mechanicalIdeas);
expect(enriched.length).toBeGreaterThanOrEqual(2);
const prioritizedIdeas = enriched.filter((i) => i.source === "uncovered_requirement" || i.source === "escalation_pattern");
expect(prioritizedIdeas.length).toBeGreaterThanOrEqual(2);
for (const idea of prioritizedIdeas) {
expect(idea.tier).toBe("backend-enriched");
}
});
it("runBackendEnriched adds novel suggestions for missing categories", () => {
const engine = new IdeationEngine(tempDir);
const mechanicalIdeas: Idea[] = [
{
id: "IDEATE-01",
source: "uncovered_requirement",
category: "coverage",
title: "Cover this",
rationale: "Missing",
confidence: 0.7,
actions: ["add_test"],
tier: "mechanical",
},
];
const enriched = engine.runBackendEnriched(mechanicalIdeas);
const novelIdeas = enriched.filter((i) => i.source === "improvement_pattern");
expect(novelIdeas.length).toBeGreaterThanOrEqual(1);
});
it("generateChaosScenarios uses default scenarios when enabled", () => {
const engine = new IdeationEngine(tempDir);
const chaosIdeas = engine.generateChaosScenarios();
expect(chaosIdeas.length).toBe(3);
expect(chaosIdeas.every((i) => i.source === "chaos_scenario")).toBe(true);
expect(chaosIdeas.every((i) => i.category === "chaos")).toBe(true);
expect(chaosIdeas.every((i) => i.tier === "backend-enriched")).toBe(true);
expect(chaosIdeas.every((i) => i.confidence >= 0.5)).toBe(true);
const titles = chaosIdeas.map((i) => i.title);
expect(titles.some((t) => t.includes("backend"))).toBe(true);
expect(titles.some((t) => t.includes("requirement"))).toBe(true);
expect(titles.some((t) => t.includes("coverage"))).toBe(true);
});
});
}); });
+178 -106
View File
@@ -3,112 +3,18 @@ import * as path from "node:path";
import { execSync } from "node:child_process"; import { execSync } from "node:child_process";
import { CIAgentFiles } from "./ciagent-files.js"; import { CIAgentFiles } from "./ciagent-files.js";
import { GitContext } from "./git-context.js"; import { GitContext } from "./git-context.js";
import { loadConfig } from "./config.js";
export type IdeationSource = import {
| "uncovered_requirement" IdeationSource,
| "repeated_lesson" IdeationCategory,
| "low_confidence_decision" IdeationAction,
| "escalation_pattern" IdeationTier,
| "compound_pattern" Idea,
| "partial_requirement" IdeationResult,
| "gap_in_coverage" IdeationSummary,
| "improvement_pattern" IdeationConfig,
| "architecture_drift" DEFAULT_IDEATION_CONFIG,
| "verification_inversion" } from "../types/ideation.js";
| "spec_ambiguity"
| "spec_contradiction"
| "spec_missing"
| "external_signal"
| "cross_project_lesson"
| "chaos_scenario";
export type IdeationCategory =
| "security"
| "quality"
| "architecture"
| "coverage"
| "improvement"
| "spec"
| "chaos";
export type IdeationAction =
| "add_requirement"
| "update_architecture"
| "update_roadmap"
| "fix_documentation"
| "add_test"
| "add_security_pattern"
| "refactor"
| "new_milestone_phase";
export type IdeationTier = "mechanical" | "backend-enriched" | "cross-project";
export interface Idea {
id: string;
source: IdeationSource;
category: IdeationCategory;
title: string;
rationale: string;
confidence: number;
relatedReq?: string;
actions: IdeationAction[];
tier: IdeationTier;
}
export interface IdeationResult {
project: string;
milestone: string;
ideas: Idea[];
summary: IdeationSummary;
}
export interface IdeationSummary {
total: number;
accepted: number;
skipped: number;
by_category: Record<string, number>;
by_tier: Record<string, number>;
}
export interface IdeationConfig {
enabled: boolean;
categories: IdeationCategory[];
confidence_threshold: number;
max_ideas: number;
external_signals: {
npm_audit: boolean;
osv_advisories: boolean;
dependency_staleness: boolean;
};
cross_project: {
enabled: boolean;
similarity_weight: number;
};
chaos: {
enabled: boolean;
scenarios: string[];
};
}
export const DEFAULT_IDEATION_CONFIG: IdeationConfig = {
enabled: true,
categories: ["security", "quality", "architecture", "coverage", "improvement"],
confidence_threshold: 0.6,
max_ideas: 20,
external_signals: {
npm_audit: true,
osv_advisories: true,
dependency_staleness: true,
},
cross_project: {
enabled: false,
similarity_weight: 0.5,
},
chaos: {
enabled: true,
scenarios: ["backend_unavailable", "requirement_change", "test_coverage_drop"],
},
};
let ideaCounter = 0; let ideaCounter = 0;
@@ -815,6 +721,172 @@ export class IdeationEngine {
return ideas; return ideas;
} }
runBackendEnriched(mechanicalIdeas: Idea[], context?: string): Idea[] {
resetIdeaCounter();
const enriched: Idea[] = [];
const prioritized = this.prioritizeMechanicalFindings(mechanicalIdeas);
enriched.push(...prioritized);
const novelSuggestions = this.suggestNovelImprovements(mechanicalIdeas);
enriched.push(...novelSuggestions);
const chaosScenarios = this.generateChaosScenarios();
enriched.push(...chaosScenarios);
return enriched.slice(0, DEFAULT_IDEATION_CONFIG.max_ideas);
}
private prioritizeMechanicalFindings(mechanicalIdeas: Idea[]): Idea[] {
const categoryPriority: Record<string, number> = {
security: 1.0,
coverage: 0.9,
architecture: 0.8,
quality: 0.7,
improvement: 0.6,
spec: 0.5,
chaos: 0.4,
};
const sourceBoost: Record<string, number> = {
escalation_pattern: 0.15,
repeated_lesson: 0.1,
uncovered_requirement: 0.1,
compound_pattern: 0.08,
low_confidence_decision: 0.05,
architecture_drift: 0.05,
verification_inversion: 0.03,
};
return mechanicalIdeas
.map((idea) => ({
...idea,
confidence: Math.min(
idea.confidence + (categoryPriority[idea.category] || 0) * 0.1 + (sourceBoost[idea.source] || 0),
1.0
),
tier: "backend-enriched" as IdeationTier,
}))
.sort((a, b) => b.confidence - a.confidence);
}
private suggestNovelImprovements(mechanicalIdeas: Idea[]): Idea[] {
const ideas: Idea[] = [];
const hasCategories = new Set(mechanicalIdeas.map((i) => i.category));
if (!hasCategories.has("security")) {
ideas.push({
id: nextIdeaId(),
source: "improvement_pattern",
category: "security",
title: "Consider adding security threat modeling",
rationale: "No security-related ideas were identified. Projects without explicit security analysis often have blind spots in threat modeling.",
confidence: 0.55,
actions: ["add_security_pattern"],
tier: "backend-enriched",
});
}
if (!hasCategories.has("quality")) {
ideas.push({
id: nextIdeaId(),
source: "improvement_pattern",
category: "quality",
title: "Consider establishing quality baselines",
rationale: "No quality-related ideas were identified. Adding code quality baselines and review standards can prevent regressions.",
confidence: 0.50,
actions: ["add_test", "update_roadmap"],
tier: "backend-enriched",
});
}
if (!hasCategories.has("architecture")) {
ideas.push({
id: nextIdeaId(),
source: "improvement_pattern",
category: "architecture",
title: "Review architectural boundaries and dependencies",
rationale: "No architecture drift detected, but periodic boundary review is a best practice for healthy codebases.",
confidence: 0.45,
actions: ["update_architecture"],
tier: "backend-enriched",
});
}
return ideas;
}
generateChaosScenarios(): Idea[] {
resetIdeaCounter();
const ideas: Idea[] = [];
const config = this.getIdeationConfig();
if (!config.chaos.enabled) return ideas;
for (const scenario of config.chaos.scenarios) {
switch (scenario) {
case "backend_unavailable":
ideas.push({
id: nextIdeaId(),
source: "chaos_scenario",
category: "chaos",
title: "Resilience: Handle backend unavailability",
rationale: "What if the primary intelligence backend is unavailable? Add fallback paths and graceful degradation for all backend-dependent features.",
confidence: 0.7,
actions: ["add_requirement", "add_test"],
tier: "backend-enriched",
});
break;
case "requirement_change":
ideas.push({
id: nextIdeaId(),
source: "chaos_scenario",
category: "chaos",
title: "Resilience: Handle mid-implementation requirement changes",
rationale: "What if requirements change during implementation? Design for adaptability with clear interfaces and minimal coupling.",
confidence: 0.65,
actions: ["add_requirement"],
tier: "backend-enriched",
});
break;
case "test_coverage_drop":
ideas.push({
id: nextIdeaId(),
source: "chaos_scenario",
category: "chaos",
title: "Resilience: Prevent test coverage regression",
rationale: "What if test coverage drops below threshold? Add CI gates that enforce minimum coverage and alert on regression.",
confidence: 0.75,
actions: ["add_test", "update_roadmap"],
tier: "backend-enriched",
});
break;
default:
ideas.push({
id: nextIdeaId(),
source: "chaos_scenario",
category: "chaos",
title: `Resilience: Handle ${scenario} scenario`,
rationale: `Consider adding resilience measures for the "${scenario}" failure scenario.`,
confidence: 0.5,
actions: ["add_requirement"],
tier: "backend-enriched",
});
}
}
return ideas;
}
private getIdeationConfig(): IdeationConfig {
try {
const config = loadConfig(this.projectPath);
return (config as any).ideation || DEFAULT_IDEATION_CONFIG;
} catch {
return DEFAULT_IDEATION_CONFIG;
}
}
formatIdeas(ideas: Idea[]): string { formatIdeas(ideas: Idea[]): string {
if (ideas.length === 0) return "No improvement ideas identified for this project."; if (ideas.length === 0) return "No improvement ideas identified for this project.";