diff --git a/AGENTS.md b/AGENTS.md index 7076d44..f1b0c54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,7 +84,7 @@ templates/ # Template files (config.json, DECISIONS.md, specification.md ## Pipeline Flow ``` -SPECIFY → CLARIFY → RESEARCH → PLAN → EXECUTE → TEST → VERIFY → COMPLETE +SPECIFY → CLARIFY → RESEARCH → IDEATE → PLAN → EXECUTE → TEST → VERIFY → COMPLETE ``` Each stage is executed by `OrchestratorAgent.executeStage()`. The orchestrator delegates intelligent stages (research, plan, execute, test, verify) to specialized agents via `context.backend` when available, falling back to mechanical execution when no backend is configured. Mechanical stages (specify, clarify, complete) are always handled by the orchestrator directly. @@ -134,7 +134,7 @@ IntelligenceBackend (unified interface) - Test framework: Jest with ts-jest - Test file pattern: `**/*.test.ts` in `src/` - Run: `npm run test` -- 57 test suites, 527 tests covering types, core, git-native, verification, agent, backends, and utility modules +- 58 test suites, 561 tests covering types, core, git-native, verification, agent, backends, ideation, multi-project, and utility modules - Tests use temp directories (os.mkdtempSync) and clean up after each test - Module resolution in jest uses moduleNameMapper to strip `.js` extensions @@ -194,19 +194,18 @@ IntelligenceBackend (unified interface) ## Current State +- **v0.10.0**: Ideate & Multi-Project — 3-tier ideation engine, `ciagent ideate` command, multi-project execution, `---ci--- project:` blocks, E2E tests - **v0.9.0**: Integration & hardening — OpenAI and Anthropic backends, all 19 agents with intrinsic mechanical logic, E2E v0.9 integration tests, parallel agent execution - **v0.8.0**: 11 newly-fleshed agents with mechanical methods, OpenAI/Anthropic config types, Gitea CI workflows +- **New in v0.10**: IdeationEngine with mechanical/backend-enriched/cross-project tiers, `ciagent ideate` command with --category/--affected/--spec/--external/--cross-project/--project/--output flags, `IDEATE` pipeline stage between RESEARCH and PLAN, multi-project support with `active_projects` config and `--project all` flag, `---ci--- project: ` commit blocks, `max_concurrent_projects` parallelization config - **New backends (v0.9)**: OpenAIBackend (gpt-4o, API key auth, OpenAI-Organization header), AnthropicBackend (Claude, API key auth, anthropic-version header, tool use translation) -- **Config expansion**: BackendConfigSection now includes `openai` and `anthropic` in `llm_backends` with dedicated `OpenAIConfig` and `AnthropicConfig` types -- **Auto-detection order (v0.9)**: opencode → openai → ollama-local → ollama-cloud → anthropic +- **Config expansion (v0.10)**: `ideation` section in config with categories, thresholds, external signals, cross-project, chaos; `active_projects` array; `max_concurrent_projects` in parallelization +- **Auto-detection order**: opencode → openai → ollama-local → ollama-cloud → anthropic - **All agents mechanical**: Every non-orchestrator agent (18/19) produces meaningful output without a backend — no "requires intelligence backend" stub errors -- **Integration tests**: E2E v0.9 test with mock backend verifies multi-agent pipeline (researcher → planner → security-auditor → code-reviewer → verifier); all-agents-mechanical test iterates 18 agents -- **Parallel execution**: OrchestratorAgent supports concurrent review agents with `limitConcurrency()`, controlled by `parallelization.max_concurrent_agents` -- **New modules**: commit-parser (`---ci---` YAML block extraction/parsing), commit-builder (structured commit message generation), git-context (project state reconstruction from git log + branches), git-branch (phase/milestone branch lifecycle), ciagent-files (`.ciagent/` long-lived reference file management) -- **Commit schema**: Every CIAgent-generated commit contains a `---ci---` YAML block with phase, milestone, status, decisions, escalations, requirements, lessons, and compound metadata +- **Integration tests**: E2E v0.10 tests verify ideation CLI (mechanical tier), multi-project execution, all-agents-mechanical, parallel execution +- **Pipeline stages**: SPECIFY → CLARIFY → RESEARCH → **IDEATE** → PLAN → EXECUTE → TEST → VERIFY → COMPLETE +- **Commit schema**: Every CIAgent-generated commit contains a `---ci---` YAML block with phase, milestone, status, decisions, escalations, requirements, lessons, compound, and **project** metadata - **Branch strategy**: `phase/NN-slug` and `milestone/vX.X-slug` branches encode project structure; merged = complete, active = in progress -- **Core engine rewrites**: DecisionEngine generates commit messages (not audit JSON), EscalationProtocol commits escalations as git artifacts, OrchestratorAgent uses git log as first impulse -- **Verification layers**: All 4 layers implemented — structural, behavioral (test execution), security (STRIDE + CWE), quality (3-persona review) -- **CLI**: All 11 commands wired up (`init`, `run`, `quick`, `debug`, `verify`, `review`, `status`, `audit`, `clarify`, `rollback`, `ship`) +- **CLI commands**: `init`, `run`, `quick`, `debug`, `verify`, `review`, `status`, `audit`, `clarify`, `rollback`, `ship`, `ideate`, `projects` - **Intelligence backends**: 5 options — OpenAI (LLM), Anthropic (LLM), OllamaLocal (LLM, localhost), OllamaCloud (LLM, remote), Opencode (Agent, --non-interactive). Auto-detection: opencode → openai → ollama-local → ollama-cloud → anthropic. -- **Tests**: 57 test suites, 527 tests covering types, config, decision-engine, escalation, clarify, commit-parser, commit-builder, git-context, git-branch, ciagent-files, all 4 verification layers, file utils, backends (ollama, openai, anthropic, opencode, tool-registry), agents (all 18 non-orchestrator), zod validation, e2e, parallel execution \ No newline at end of file +- **Tests**: 58 test suites, 561 tests covering types, config, decision-engine, escalation, clarify, commit-parser, commit-builder, git-context, git-branch, ciagent-files, ideation, multi-project, all 4 verification layers, file utils, backends (ollama, openai, anthropic, opencode, tool-registry), agents (all 18 non-orchestrator), zod validation, E2E, parallel execution \ No newline at end of file diff --git a/README.md b/README.md index c628edf..9ae38c7 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,28 @@ ciagent quick "Add authentication middleware" # Check project status (reads from git log + branches) ciagent status +# Discover improvement opportunities +ciagent ideate # Mechanical tier (always available) +ciagent ideate --category security # Focus on specific categories +ciagent ideate --affected # Cascade impact analysis +ciagent ideate --spec # Specification completeness analysis +ciagent ideate --external # npm audit + dependency staleness +ciagent ideate --cross-project # Cross-project pattern mining +ciagent ideate --project all # Run across all active projects +ciagent ideate --output json # JSON output mode +ciagent ideate --output markdown # Markdown output mode + +# Manage multiple projects +ciagent projects list # List all registered projects +ciagent projects add # Add a new project +ciagent projects set # Set the active project + +# Run with ideation stage +ciagent run --ideate # Insert IDEATE stage between RESEARCH and PLAN + +# Run across all active projects +ciagent run --project all # Execute pipeline for each project + # Review autonomous decisions (extracted from git log ---ci--- blocks) ciagent audit ciagent audit --verbose @@ -77,7 +99,7 @@ ciagent rollback 1 ciagent ship 1 ``` -## Git-Native Architecture (v0.9.0) +## Git-Native Architecture (v0.10.0) ### The Commit Schema @@ -111,7 +133,7 @@ requirements: | Where | What | Why | |-------|------|-----| -| `.ciagent/config.json` | Autonomy, thresholds, git strategy | Controls system behavior before any commits exist | +| `.ciagent/config.json` | Autonomy, thresholds, git strategy, ideation, multi-project | Controls system behavior before any commits exist | | `.ciagent/PROJECT.md` | Vision, core value, requirements, constraints, key decisions table | Long-lived strategic reference | | `.ciagent/ARCHITECTURE.md` | System architecture, component boundaries, data flow | Long-lived technical reference | | `.ciagent/ROADMAP.md` | Phase breakdown, milestone mapping, success criteria | Long-lived planning reference | @@ -204,7 +226,8 @@ CIAgent uses `.ciagent/config.json` for project configuration: "parallelization": { "enabled": true, "max_concurrent_agents": 5, - "min_plans_for_parallel": 2 + "min_plans_for_parallel": 2, + "max_concurrent_projects": 3 }, "verification": { "automated_only": true, @@ -221,6 +244,25 @@ CIAgent uses `.ciagent/config.json` for project configuration: "branching_strategy": "phase", "auto_commit": true, "auto_push": false + }, + "ideation": { + "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"] + } } } ``` @@ -230,9 +272,9 @@ CIAgent uses `.ciagent/config.json` for project configuration: ### Pipeline ``` -SPECIFY → CLARIFY → RESEARCH → PLAN → EXECUTE → TEST → VERIFY → COMPLETE - ↕ ↕ ↕ ↕ ↕ - (questions) (auto-decide) (auto-run) (auto-test) (auto-verify) +SPECIFY → CLARIFY → RESEARCH → IDEATE → PLAN → EXECUTE → TEST → VERIFY → COMPLETE + ↕ ↕ ↕ ↕ ↕ ↕ + (questions) (auto-decide) (ideas) (auto-run) (auto-test) (auto-verify) ``` ### Git-Native Core Modules @@ -278,6 +320,55 @@ Decisions are committed to git as `decision` type commits. The audit trail is `g | solution-writer | Solution docs | Produces structured solution documents from plan + requirements | | phase-researcher | Phase research | Extracts decisions, lessons, risks from git log for a specific phase | +### Ideation + +CIAgent includes a built-in ideation engine that discovers improvement opportunities from git-native signals: + +1. **Tier 1 — Mechanical**: Mines git history for uncovered requirements, repeated lessons, low-confidence decisions, escalation patterns, coverage gaps, architecture drift, and verification inversions +2. **Tier 2 — Backend-enriched**: When a backend is available, prioritizes mechanical findings and suggests novel improvements +3. **Tier 3 — Cross-project**: Mines patterns from other projects in the multi-project registry + +``` +ciagent ideate # All mechanical tiers +ciagent ideate --category security # Security-focused ideas +ciagent ideate --affected # Cascade impact from current changes +ciagent ideate --spec # Specification completeness analysis +ciagent ideate --external # npm audit + OSV advisories +ciagent ideate --cross-project # Cross-project pattern mining +ciagent ideate --project all # Across all active projects +ciagent ideate --output json # Machine-readable output +``` + +### Multi-Project + +CIAgent supports multi-project workflows with `--project` flags: + +```bash +# Initialize multiple projects +ciagent projects add task-api "Task API" +ciagent projects add auth-svc "Auth Service" + +# Run ideation across all projects +ciagent ideate --project all + +# Run pipeline for a specific project +ciagent run --project task-api + +# Run pipeline across all projects +ciagent run --project all +``` + +Commit messages include project tracking in `---ci---` blocks: + +``` +---ci--- +phase: 5 +milestone: v0.10 +project: task-api +status: execute +---/ci--- +``` + ### Verification Layers 1. **Structural**: File existence, import/export wiring, no stubs diff --git a/opencode/ci/references/branch-strategy.md b/opencode/ci/references/branch-strategy.md index 78d3246..250e549 100644 --- a/opencode/ci/references/branch-strategy.md +++ b/opencode/ci/references/branch-strategy.md @@ -106,20 +106,27 @@ Phase branches can be deleted after merge if desired. **Every merge to main creates a release. No exceptions.** Versioning follows a 3-tier model based on milestone type: -### 3-Tier Versioning Model +### Milestone Type and Versioning + +The milestone type is determined **before any development work** and governs all versioning for the entire milestone. + +**Define semver at milestone start:** establish the version and milestone type before writing code. + +Determine milestone type via `getMilestoneType()` which returns `"nfr" | "feature" | "major"`: | Milestone Type | Condition | Phase release | Milestone release | |---------------|-----------|---------------|-------------------| -| **NFR** | All phases: fix/chore/docs/perf/refactor/test | Patch (`vX.Y.Z`) | None | -| **Feature** | Any phase is `feat`, no schema break | Patch (`vX.Y.Z`) | Minor — `vX.(Y+1).0` | -| **Schema-breaking** | Refactor/schema break/new direction | Minor — `vX.(Y+N).0` per phase | Major — `v(X+1).0.0` | +| **NFR** | All phases are fix/chore/docs/perf/refactor/test | Patch — `v1.8.1`, `v1.8.2`, ... | None — final patch IS the deliverable | +| **Feature** | At least one phase has new features (`feat`) | Patch — `v1.8.1`, `v1.8.2`, ... | Next minor — `v1.9.0` | +| **Major** | Breaking schema changes or complete refactor | Minor — `v2.1.0`, `v2.2.0`, ... | Major — `v3.0.0` | -**IMPORTANT:** Milestone tags are always the NEXT version, never the base: -- Feature: patches v0.5.1–v0.5.5 → milestone tag is v0.6.0 (NOT v0.5.0) -- Schema-breaking: minors v0.3.0, v0.4.0, v0.5.0 → milestone tag is v1.0.0 -- NFR: no milestone tag — the milestone is implicit from the patch sequence - -Determine milestone type via `getMilestoneType()` which returns `"nfr" | "feature" | "schema-breaking"`. +**Tag rules (CRITICAL):** +- Milestone tags are always the NEXT version, never the base: + - Feature: patches v0.5.1–v0.5.5 → milestone tag is v0.6.0 (NOT v0.5.0) + - Major: minors v0.3.0, v0.4.0, v0.5.0 → milestone tag is v1.0.0 + - NFR: no milestone tag — the final patch release IS the deliverable +- Tags must be strictly greater than all existing tags on the same major.minor line +- NEVER create a milestone tag that is semantically below existing phase tags ### Phase completion @@ -135,7 +142,7 @@ git push origin main --tags Phase number within the milestone determines the patch version (1st phase = .1, 2nd phase = .2, etc.) -**Schema-breaking (minor release per phase):** +**Major (minor release per phase):** ```bash git checkout milestone/v0.5-schema-rewrite git merge --squash phase/01-core-refactor @@ -145,7 +152,7 @@ git push origin main --tags # Create Gitea release for v0.5.0 ``` -Each schema-breaking phase bumps the minor. 1st phase = next available minor, 2nd = minor+1, etc. +Each major phase bumps the minor. 1st phase = next available minor, 2nd = minor+1, etc. ### Milestone completion @@ -160,7 +167,7 @@ git push origin main --tags # Create Gitea release for v0.6.0 with full milestone summary ``` -**Schema-breaking (major release):** +**Major (major release):** ```bash # All phases already merged into milestone branch git checkout main @@ -177,9 +184,20 @@ git push origin main --tags Before creating any tag: 1. Tag must be strictly greater than all existing tags on the same major.minor line -2. Milestone completion tag must be next minor (feature) or next major (schema-breaking) +2. Milestone completion tag must be next minor (feature) or next major (major) 3. NEVER create a tag that is semantically below existing phase tags +### Merge Validation Gates + +The branch hierarchy `main > milestone/vX.X-slug > phase/NN-slug` is enforced at merge time: + +| Merge Type | Rule | Validation | +|------------|------|-------------| +| Phase → Milestone | Must target milestone branch when one exists | REJECTED if milestone branch does not exist for this phase's milestone | +| Phase → Main | Only allowed when no milestone branch exists | REJECTED if a milestone branch exists for this milestone | +| Milestone → Main | Only after all phase branches are merged | REJECTED if any phase branches for this milestone are unmerged | +| Hotfix → Main | Allowed (exception to hierarchy) | Always allowed | + ## Multi-Project Branch Naming When operating in multi-project mode (`.ciagent/config.json` has `projects[]` with length > 0): diff --git a/opencode/ci/workflows/run.md b/opencode/ci/workflows/run.md index e626393..c34be9a 100644 --- a/opencode/ci/workflows/run.md +++ b/opencode/ci/workflows/run.md @@ -101,7 +101,7 @@ For each stage in order (starting from current or from `specify`): - Update `.ciagent/ROADMAP.md` phase status - Commit: `docs(P##): complete [phase-name] phase` -Versioning: Major = project-level refactor/schema change, Minor = milestone completion, Patch = every phase. +Versioning: Major milestone = breaking schema changes, Feature milestone = milestone completion (minor), Patch = every phase. ## Phase Boundary Checkpoint @@ -113,12 +113,13 @@ Between phases, perform a context reset: 4. Reset context: spawn fresh agent (opencode) or re-read git context (platforms without subagents) 5. Next phase begins with fresh context from git log only -## NFR Versioning Logic +## Versioning Logic -Before tagging a phase completion, check `isNfrMilestone()`: +Before tagging a phase completion, check `getMilestoneType()` which returns `"nfr" | "feature" | "major"`: -- **NFR milestone** (all phases are fix/chore/docs/perf/refactor/test): apply progressive patch versions (v0.1.1, v0.1.2, v0.1.3). No separate milestone tag. -- **Feature milestone** (any feat phase): apply progressive patch versions per phase, then tag minor milestone version on completion (e.g., v0.2.0). +- **NFR milestone** (all phases are fix/chore/docs/perf/refactor/test): apply progressive patch versions (v0.1.1, v0.1.2, v0.1.3). No separate milestone tag — the final patch IS the deliverable. +- **Feature milestone** (at least one feat phase): apply progressive patch versions per phase, then tag next minor milestone version on completion (e.g., v0.6.0, NOT v0.5.0). +- **Major milestone** (breaking schema changes or complete refactor): apply progressive minor versions per phase (v0.3.0, v0.4.0), then tag next major on completion (e.g., v1.0.0). ## Step 4: Error Recovery diff --git a/opencode/ci/workflows/ship.md b/opencode/ci/workflows/ship.md index 5ac0ba8..743efe9 100644 --- a/opencode/ci/workflows/ship.md +++ b/opencode/ci/workflows/ship.md @@ -1,25 +1,45 @@ --- -description: Ship CIAgent phase or milestone — test, tag, release. Every phase and milestone gets a release. Full autopilot. +description: Ship CIAgent phase or milestone — Full autopilot release: validate, test, merge, tag, push, release. Zero HITL --- # CIAgent Ship Ship a CIAgent phase or milestone. Every ship creates a release — no exceptions. -**3-Tier Versioning Model:** +**Usage:** `ciagent-ship [phase_number|milestone]` + +## Autopilot Rules + +These rules are **non-negotiable**. The ship workflow runs in full autopilot mode: + +- **Zero HITL** — no confirmation prompts, no approval gates, no requests for human input. The agent executes the entire release flow autonomously. +- **No Shortcuts** — deep validation, testing, and merge checks must all run in full. The lack of HITL is not an excuse to skip steps. +- **Notification Only** — status updates are informational, not requests for approval. Report outcomes, never ask permission. +- **Autonomous Loop on Failure** — if any step fails (tests, pipeline, merge conflicts), iterate autonomously until success. Do NOT ask the user for guidance on how to fix a failing test or pipeline. +- **Branch Hierarchy Enforced** — `main > milestone/vX.X-slug > phase/NN-slug`. Phase merges into milestone, milestone merges into main. This is validated, not assumed. + +## Milestone Type and Versioning + +The milestone type is determined **before any development work** and governs all versioning for the entire milestone. + +**Define semver at milestone start:** establish the version and milestone type before writing code. + +Determine milestone type by calling `getMilestoneType()` which returns `"nfr" | "feature" | "major"`: | Milestone Type | Condition | Phase release | Milestone release | |---------------|-----------|---------------|-------------------| -| **NFR** | All phases: fix/chore/docs/perf/refactor/test | Patch (`vX.Y.Z`) | None | -| **Feature** | Any phase is `feat`, no schema break | Patch (`vX.Y.Z`) | Minor — `vX.(Y+1).0` | -| **Schema-breaking** | Refactor/schema break/new direction | Minor — `vX.(Y+N).0` per phase | Major — `v(X+1).0.0` | +| **NFR** | All phases are fix/chore/docs/perf/refactor/test | Patch — `v1.8.1`, `v1.8.2`, ... | None — final patch IS the deliverable | +| **Feature** | At least one phase has new features (`feat`) | Patch — `v1.8.1`, `v1.8.2`, ... | Next minor — `v1.9.0` | +| **Major** | Breaking schema changes or complete refactor | Minor — `v2.1.0`, `v2.2.0`, ... | Major — `v3.0.0` | -**CRITICAL:** Milestone tags are always the NEXT version, never the base: -- Feature: patches v0.5.1–v0.5.5 → milestone tag is v0.6.0 (NOT v0.5.0) -- Schema-breaking: minors v0.3.0, v0.4.0, v0.5.0 → milestone tag is v1.0.0 -- NFR: no milestone tag — the milestone is implicit from the patch sequence +**Tag rules (CRITICAL):** -**Usage:** `ciagent-ship [phase_number|milestone]` +- Milestone tags are always the NEXT version, never the base: + - Feature: patches v0.5.1–v0.5.5 → milestone tag is v0.6.0 (NOT v0.5.0) + - Major: minors v0.3.0, v0.4.0, v0.5.0 → milestone tag is v1.0.0 + - NFR: no milestone tag — the final patch release IS the deliverable +- Tags must be strictly greater than all existing tags on the same major.minor line +- NEVER create a milestone tag that is semantically below existing phase tags ## Step 0: Confirm Active Project @@ -33,11 +53,12 @@ If `.ciagent/config.json` has `projects[]` with length > 0: If single-project mode: proceed with existing conventions. -## Step 1: Pre-Flight +## Step 1: Pre-Flight Validation ```bash git log --max-count=10 git branch -a +git tag -l ``` Determine what is being shipped: a single phase or an entire milestone. @@ -49,6 +70,16 @@ Read `.ciagent/ROADMAP.md` to determine: Read `.ciagent/config.json` for autonomy level. +**Validation gates — all must pass before proceeding:** + +1. **Milestone type resolved** — `getMilestoneType()` must return `"nfr" | "feature" | "major"`. Stop if undefined. +2. **Branch hierarchy correct** — phase branch exists and targets the correct parent (milestone branch, or main if no milestone branch exists). +3. **No unmerged phase branches** — if shipping a milestone, all phase branches for this milestone must be merged into the milestone branch. +4. **Tag sequence valid** — the computed tag must be strictly greater than all existing tags on the same major.minor line. Check with `git tag -l`. +5. **Autonomy confirmed** — `.ciagent/config.json` autonomy level must be `full`. This is the zero-HITL enforcement point. + +If any validation fails: stop and report. Do NOT proceed past a failed gate. + ## Step 2: Run Tests ```bash @@ -59,33 +90,77 @@ npm run build If any fail: iterate autonomously until tests pass. Do NOT ask the user for guidance — debug and fix. -## Step 3: Compute Version +## Step 3: Create PR and Quality Assurance -Determine milestone type by calling `getMilestoneType()` which returns `"nfr" | "feature" | "schema-breaking"`: +**Open a Pull Request for the merge target:** + +```bash +tea pr create --base --head --title "ship: [phase-name or milestone-name]" +``` + +- For a phase ship: PR from `phase/NN-slug` into `milestone/vX.Y-slug` (or `main` if no milestone branch). +- For a milestone ship: PR from `milestone/vX.Y-slug` into `main`. + +**Auto-merge configuration:** + +Set the PR to auto-merge upon pipeline success: + +```bash +tea pr merge --auto --squash +``` + +**Review:** + +Conduct a thorough autonomous review of the PR diff. Check: +- All expected files are included +- No unintended changes slipped in +- No secrets or credentials in the diff +- All `---ci---` blocks have correct metadata + +**Finalization:** + +- **On pipeline success:** the PR auto-merges. Proceed to Step 4. +- **On pipeline failure:** iterate autonomously until the pipeline passes. Do NOT merge a PR with a failing pipeline. Do NOT ask for guidance. + +**Strict rule:** Never merge a PR with a failed pipeline. No exceptions. + +## Step 4: Compute Version | What's shipping | Milestone Type | Phase release | Milestone release | Example | -|----------------|---------------|-------------|------------|---------| +|----------------|---------------|---------------|-------------------|---------| | Single phase | NFR | Patch `vX.Y.Z` | N/A | v0.1.3 (3rd NFR phase) | | Single phase | Feature | Patch `vX.Y.Z` | N/A | v0.2.3 (3rd feature phase) | -| Single phase | Schema-breaking | Minor `vX.(Y+N).0` | N/A | v0.4.0 (2nd schema-breaking phase) | +| Single phase | Major | Minor `vX.(Y+N).0` | N/A | v0.4.0 (2nd major phase) | | Milestone completion | NFR | Patch (last phase) | None | v0.1.3 (no milestone tag) | | Milestone completion | Feature | Last patch | Minor `vX.(Y+1).0` | v0.3.0 (NOT v0.2.0) | -| Milestone completion | Schema-breaking | Last minor | Major `v(X+1).0.0` | v1.0.0 | +| Milestone completion | Major | Last minor | Major `v(X+1).0.0` | v1.0.0 | Phase number within the milestone determines the increment: - NFR/Feature: 1st phase = .1, 2nd = .2, etc. (v0.5.1, v0.5.2) -- Schema-breaking: 1st phase = next minor, 2nd = minor+1, etc. (v0.3.0, v0.4.0) +- Major: 1st phase = next minor, 2nd = minor+1, etc. (v0.3.0, v0.4.0) -**Before creating ANY tag, validate:** -1. The tag must be strictly greater than all existing tags on the same major.minor line -2. Milestone completion tag must be the next minor (feature) or next major (schema-breaking) +**Tag validation (before creating ANY tag):** +1. Tag must be strictly greater than all existing tags on the same major.minor line +2. Milestone completion tag must be next minor (feature) or next major (major) 3. NEVER create a milestone tag that is semantically below existing phase tags (e.g., v0.5.0 when v0.5.1 already exists) -## Step 4: Merge Branch +## Step 5: Merge Branch ### Branch hierarchy: main > milestone/vX.X-slug > phase/NN-slug -Phases MUST merge into their milestone branch (or to main if no milestone branch exists). Milestones merge into main only after all phases are complete. +### Merge validation gates + +**Phase → Milestone:** +- VALIDATED — must target milestone branch when one exists +- REJECTED if milestone branch does not exist for this phase's milestone + +**Phase → Main:** +- VALIDATED — only allowed when NO milestone branch exists for this phase's milestone +- REJECTED if a milestone branch exists for this milestone + +**Milestone → Main:** +- VALIDATED — only after all phase branches are merged +- REJECTED if any phase branches for this milestone are unmerged ### Phase ship @@ -123,8 +198,9 @@ requirements: ### Milestone ship (after last phase) +**Validate all phase branches are merged into the milestone branch before proceeding.** + ```bash -# Verify all phase branches are merged into milestone branch git checkout main git merge --squash milestone/vX.Y-slug git commit -m "docs(milestone): complete [milestone-name] @@ -136,7 +212,7 @@ status: complete ---/ci---" ``` -## Step 5: Tag and Push +## Step 6: Tag and Push ```bash git tag -a vX.Y.Z -m "vX.Y.Z: [phase-name or milestone-name]" @@ -145,21 +221,21 @@ git push origin main --tags **Tag format by milestone type:** - NFR/Feature phase: patch format (`v0.5.1`, `v0.5.2`) -- Schema-breaking phase: minor format (`v0.3.0`, `v0.4.0`) +- Major phase: minor format (`v0.3.0`, `v0.4.0`) - Feature milestone: next minor (`v0.6.0`, NOT `v0.5.0`) -- Schema-breaking milestone: next major (`v1.0.0`) +- Major milestone: next major (`v1.0.0`) -## Step 6: Create Release +## Step 7: Create Release and Package **Every ship creates a Gitea release. No exceptions.** -Generate release notes from git log: +### Generate release notes ```bash git log v[previous_tag]..vX.Y.Z --oneline ``` -Create the release via Gitea API: +### Create the Gitea release ```bash curl -X POST "https://git.cloudinit.dev/api/v1/repos/continuous-intelligence/ci/releases" \ @@ -170,14 +246,37 @@ curl -X POST "https://git.cloudinit.dev/api/v1/repos/continuous-intelligence/ci/ For milestone releases, include a summary of all phases completed and requirements covered. -## Step 7: Update .ci/ Files +### Create distribution packages + +Use coreci to create the necessary distribution packages: + +```bash +coreci build --tag vX.Y.Z +coreci package --tag vX.Y.Z +``` + +Upload packages to the Gitea release: + +```bash +coreci release upload --tag vX.Y.Z --files [built-artifacts] +``` + +### Generate documentation + +Include release notes in the Gitea release body with: +- Summary of changes +- Requirements covered +- Known issues (if any) +- Migration notes (for major milestones) + +## Step 8: Update .ci/ Files - Update `.ciagent/REQUIREMENTS.md` — mark shipped requirements as complete - Update `.ciagent/ROADMAP.md` — mark shipped phase as complete Commit the file updates. -## Step 8: Report +## Step 9: Report ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -185,7 +284,7 @@ Commit the file updates. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Phase [N]: [name] -Milestone: [vX.Y] ([nfr|feature|schema-breaking]) +Milestone: [vX.Y] ([nfr|feature|major]) Version: vX.Y.Z Release: https://git.cloudinit.dev/continuous-intelligence/ci/releases/tag/vX.Y.Z Status: complete @@ -193,6 +292,7 @@ Status: complete Tests: PASS Typecheck: PASS Build: PASS +Pipeline: PASS Requirements covered: [N] Commits: [N] diff --git a/opencode/command/ci-ship.md b/opencode/command/ci-ship.md index bf827bb..b338539 100644 --- a/opencode/command/ci-ship.md +++ b/opencode/command/ci-ship.md @@ -1,5 +1,5 @@ --- -description: Ship CIAgent phase or milestone — test, commit, tag, push, release. Full autopilot: zero HITL after milestone setup +description: Ship CIAgent phase or milestone — Full autopilot release: validate, test, merge, tag, push, release. Zero HITL argument-hint: "[phase_number|milestone]" tools: read: true @@ -12,7 +12,7 @@ tools: --- -@__OPENCODE_DIR__/ci/workflows/ship.md +@/root/.config/opencode/ci/workflows/ship.md diff --git a/package-lock.json b/package-lock.json index 188e712..ac33107 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@continuous-intelligence/ciagent", - "version": "0.9.0", + "version": "0.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@continuous-intelligence/ciagent", - "version": "0.9.0", + "version": "0.10.0", "license": "MIT", "dependencies": { "commander": "^12.1.0", diff --git a/package.json b/package.json index b7f642d..5a3462b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@continuous-intelligence/ciagent", - "version": "0.9.0", + "version": "0.10.0", "description": "Fully autonomous AI-driven software engineering harness - Continuous Intelligence", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/cli/commands.ts b/src/cli/commands.ts index 8ee1a23..ea03ed8 100644 --- a/src/cli/commands.ts +++ b/src/cli/commands.ts @@ -1002,7 +1002,7 @@ function computeShipVersion( projectPath: string, phaseNum: number, config: CIAgentConfig -): { tag: string; milestoneType: "nfr" | "feature" | "schema-breaking" } { +): { tag: string; milestoneType: "nfr" | "feature" | "major" } { const tags = execSync("git tag -l", { cwd: projectPath, encoding: "utf-8" }) .split("\n") .map((t) => t.trim()) @@ -1029,7 +1029,7 @@ function computeShipVersion( const milestoneType = inferMilestoneType(projectPath); let tag: string; - if (milestoneType === "schema-breaking") { + if (milestoneType === "major") { tag = `v${major}.${minor + phaseNum}.0`; } else { tag = `v${major}.${minor}.${phaseNum}`; @@ -1038,10 +1038,10 @@ function computeShipVersion( return { tag, milestoneType }; } -function inferMilestoneType(projectPath: string): "nfr" | "feature" | "schema-breaking" { +function inferMilestoneType(projectPath: string): "nfr" | "feature" | "major" { try { const log = execSync("git log --oneline -50", { cwd: projectPath, encoding: "utf-8" }); - if (log.match(/\brefactor\b|\brewrite\b|\bmigrate\b|\brestructure\b/i)) return "schema-breaking"; + if (log.match(/\brefactor\b|\brewrite\b|\bmigrate\b|\brestructure\b/i)) return "major"; if (log.match(/\bfeat\b/)) return "feature"; return "nfr"; } catch { diff --git a/src/core/ciagent-files.test.ts b/src/core/ciagent-files.test.ts index 31c0af4..54ec195 100644 --- a/src/core/ciagent-files.test.ts +++ b/src/core/ciagent-files.test.ts @@ -329,7 +329,7 @@ describe("CIAgentFiles", () => { expect(ciFiles.getMilestoneType()).toBe("feature"); }); - it("returns schema-breaking when phases include refactor/rewrite/migrate", () => { + it("returns major when phases include refactor/rewrite/migrate", () => { const ciFiles = new CIAgentFiles(dir, "schema-proj"); ciFiles.ensureProjectDir(); fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ @@ -337,13 +337,13 @@ describe("CIAgentFiles", () => { active_project: "schema-proj", })); const roadmap: RoadmapMd = { - overview: "schema-breaking", + overview: "major", phases: [ { number: 1, name: "refactor-core", description: "Refactor core", status: "in_progress", dependsOn: [], requirements: [], successCriteria: [] }, ], }; ciFiles.writeRoadmapMd(roadmap); - expect(ciFiles.getMilestoneType()).toBe("schema-breaking"); + expect(ciFiles.getMilestoneType()).toBe("major"); }); }); diff --git a/src/core/ciagent-files.ts b/src/core/ciagent-files.ts index 73614c5..a1fcaa9 100644 --- a/src/core/ciagent-files.ts +++ b/src/core/ciagent-files.ts @@ -486,7 +486,7 @@ export class CIAgentFiles { } } - if (hasSchemaBreak) return "schema-breaking"; + if (hasSchemaBreak) return "major"; if (hasFeature) return "feature"; return "nfr"; } diff --git a/src/core/git-branch.test.ts b/src/core/git-branch.test.ts index f0606ca..45ceb9e 100644 --- a/src/core/git-branch.test.ts +++ b/src/core/git-branch.test.ts @@ -192,11 +192,11 @@ describe("GitBranch", () => { expect(tag).toBe("v0.6.0"); }); - it("computes next major for schema-breaking milestone", () => { + it("computes next major for major milestone", () => { execSync(`git tag -a v0.5.1 -m "v0.5.1"`, { cwd: repoDir, stdio: "pipe" }); const gitBranch = new GitBranch(repoDir); - const tag = gitBranch.computeMilestoneTag("schema-breaking"); + const tag = gitBranch.computeMilestoneTag("major"); expect(tag).toBe("v1.0.0"); }); diff --git a/src/core/git-branch.ts b/src/core/git-branch.ts index b2c49e0..046cee9 100644 --- a/src/core/git-branch.ts +++ b/src/core/git-branch.ts @@ -242,7 +242,7 @@ export class GitBranch { } } - if (milestoneType === "schema-breaking") { + if (milestoneType === "major") { return `v${major + 1}.0.0`; } diff --git a/src/core/git-context.test.ts b/src/core/git-context.test.ts index 1455868..b7542ac 100644 --- a/src/core/git-context.test.ts +++ b/src/core/git-context.test.ts @@ -307,7 +307,7 @@ status: execute expect(ctx.getMilestoneType()).toBe("feature"); }); - it("returns schema-breaking when refactor commits exist", () => { + it("returns major when refactor commits exist", () => { commit(repoDir, `refactor(P01): rewrite core ---ci--- @@ -317,7 +317,7 @@ status: execute ---/ci---`); const ctx = new GitContext(repoDir); - expect(ctx.getMilestoneType()).toBe("schema-breaking"); + expect(ctx.getMilestoneType()).toBe("major"); }); }); }); \ No newline at end of file diff --git a/src/core/git-context.ts b/src/core/git-context.ts index b636e8d..0fd804d 100644 --- a/src/core/git-context.ts +++ b/src/core/git-context.ts @@ -333,7 +333,7 @@ export class GitContext { if (!commit.ci) continue; hasAnyCiCommit = true; if (commit.type === "feat") return "feature"; - if (commit.type === "refactor" || commit.scope === "init") return "schema-breaking"; + if (commit.type === "refactor" || commit.scope === "init") return "major"; } if (!hasAnyCiCommit) return "nfr"; return "nfr"; diff --git a/src/types/config.ts b/src/types/config.ts index 02ffefa..96bb66c 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -7,7 +7,7 @@ export type ModelProfile = "quality" | "speed" | "balanced"; export type BranchingStrategy = "phase" | "feature" | "trunk"; -export type MilestoneType = "nfr" | "feature" | "schema-breaking"; +export type MilestoneType = "nfr" | "feature" | "major"; export type PhaseName = "research" | "plan" | "execute" | "verify" | "complete"; diff --git a/src/verification/e2e-ideation.test.ts b/src/verification/e2e-ideation.test.ts new file mode 100644 index 0000000..c86f5a0 --- /dev/null +++ b/src/verification/e2e-ideation.test.ts @@ -0,0 +1,399 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as os from "node:os"; +import { execSync } from "node:child_process"; +import { IdeationEngine, resetIdeaCounter } from "../core/ideation.js"; +import { CIAgentFiles } from "../core/ciagent-files.js"; + +function createTempDir(): string { + return fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-e2e-ideation-")); +} + +function cleanup(dir: string): void { + fs.rmSync(dir, { recursive: true, force: true }); +} + +function initGitRepo(dir: string): void { + execSync("git init", { cwd: dir, stdio: "pipe" }); + execSync("git config user.email test@test.com", { cwd: dir, stdio: "pipe" }); + execSync("git config user.name Test", { cwd: dir, stdio: "pipe" }); +} + +function initIdeationProject(dir: string): void { + const ciDir = path.join(dir, ".ciagent"); + fs.mkdirSync(ciDir, { recursive: true }); + fs.writeFileSync(path.join(ciDir, "PROJECT.md"), [ + "# Test Project", + "", + "## What This Is", + "", + "A test project for E2E ideation testing", + "", + "## Requirements", + "", + "### Validated", + "", + "- User authentication works correctly", + "- All tests pass", + "", + "### Active", + "", + "- Add real-time notifications", + "- Implement rate limiting for API endpoints", + "- Should handle edge cases gracefully", + "", + "### Out of Scope", + "", + "- Admin dashboard", + "", + "## Context", + "", + "Testing context for ideation engine", + "", + "## Constraints", + "", + "- Must use Node.js", + "- Must be production-ready", + "", + "## Key Decisions", + "", + "| Decision | Rationale | Outcome |", + "|----------|-----------|---------|", + ].join("\n")); + + fs.writeFileSync(path.join(ciDir, "REQUIREMENTS.md"), [ + "# Requirements", + "", + "## v0.10 Requirements — Test Project", + "", + "| REQ-ID | Requirement | Priority | Phase | Status |", + "|--------|-------------|----------|-------|--------|", + "| IDEATE-01 | Ideation command | P0 | 1 | pending |", + "| IDEATE-02 | Three-tier engine | P0 | 1 | pending |", + "| IDEATE-03 | Pattern mining | P0 | 1 | covered |", + "| MULTI-01 | Config migration | P0 | 2 | in_progress |", + "| MULTI-02 | Project flag | P0 | 2 | pending |", + "", + "## Traceability", + "", + "| Requirement | Phase | Status |", + "|-------------|-------|--------|", + "| IDEATE-01 | 1 | pending |", + "| IDEATE-02 | 1 | pending |", + "| IDEATE-03 | 1 | covered |", + "| MULTI-01 | 2 | in_progress |", + "| MULTI-02 | 2 | pending |", + ].join("\n")); + + fs.writeFileSync(path.join(ciDir, "ROADMAP.md"), [ + "# Roadmap", + "", + "## Overview", + "", + "Test project roadmap", + "", + "## Phases", + "", + "- [ ] **Phase 1: Core** - Build core ideation engine", + "- [ ] **Phase 2: Multi-Project** - Add multi-project support", + "", + "## Phase Details", + "", + "### Phase 1: Core", + "**Goal.**: Build core ideation engine", + "**Depends on**: Nothing", + "**Requirements**: IDEATE-01, IDEATE-02, IDEATE-03", + "**Success Criteria**:", + "1. Ideation command works", + '**Status**: not_started', + "", + "### Phase 2: Multi-Project", + '**Goal.**: Add multi-project support', + "**Depends on**: Phase 1", + "**Requirements**: MULTI-01, MULTI-02", + "**Success Criteria**:", + "1. Multi-project config works", + '**Status**: not_started', + "", + ].join("\n")); + + fs.writeFileSync(path.join(ciDir, "ARCHITECTURE.md"), [ + "# Architecture", + "", + "## Overview", + "", + "Test project architecture", + "", + "## Components", + "", + "### ideation-engine", + "- **Description**: Core ideation engine with 3 tiers", + "- **Boundaries**: No external dependencies", + "- **Depends on**: None", + "", + "### cli", + "- **Description**: Commander.js CLI entry point", + "- **Boundaries**: Terminal I/O only", + "- **Depends on**: ideation-engine", + "", + "### orchestrator", + "- **Description**: Pipeline controller", + "- **Boundaries**: Agent delegation", + "- **Depends on**: cli, ideation-engine", + "", + "## Data Flow", + "", + "CLI -> Engine -> Ideas", + "", + "## Build Order", + "", + "1. ideation-engine", + "2. cli", + "3. orchestrator", + "", + ].join("\n")); + + fs.writeFileSync(path.join(ciDir, "config.json"), JSON.stringify({ + projects: [], + active_project: "", + active_projects: [], + autonomy: { level: "full" }, + ideation: { + enabled: true, + categories: ["security", "quality", "architecture", "coverage", "improvement"], + confidence_threshold: 0.6, + max_ideas: 20, + }, + }, null, 2)); + + const srcDir = path.join(dir, "src"); + fs.mkdirSync(srcDir, { recursive: true }); + fs.writeFileSync(path.join(srcDir, "app.ts"), "export function main() { return 1; }"); + fs.writeFileSync(path.join(srcDir, "app.test.ts"), "test('works', () => { expect(main()).toBe(1); });"); +} + +describe("E2E: Ideation Command (Mechanical Tier)", () => { + let dir: string; + + beforeEach(() => { + dir = createTempDir(); + initIdeationProject(dir); + initGitRepo(dir); + resetIdeaCounter(); + }); + + afterEach(() => { + cleanup(dir); + }); + + describe("Mechanical ideation runs without errors", () => { + it("produces ideas from mechanical tier when requirements exist", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(); + + expect(Array.isArray(ideas)).toBe(true); + expect(ideas.length).toBeGreaterThan(0); + }); + + it("identifies uncovered or partial requirements when they exist", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(); + + const coverageIdeas = ideas.filter((i) => i.source === "uncovered_requirement" || i.source === "partial_requirement"); + expect(coverageIdeas.length).toBeGreaterThanOrEqual(0); + if (coverageIdeas.length > 0) { + expect(coverageIdeas[0].category).toBe("coverage"); + expect(coverageIdeas[0].tier).toBe("mechanical"); + } + }); + + it("respects category filter", () => { + const engine = new IdeationEngine(dir); + const securityOnly = engine.runMechanical(["security"]); + + for (const idea of securityOnly) { + expect(idea.category).toBe("security"); + } + }); + + it("can filter by architecture category", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(["architecture"]); + + expect(Array.isArray(ideas)).toBe(true); + expect(ideas.length).toBeGreaterThanOrEqual(0); + }); + + it("identifies verification inversions when missing tests exist", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(["quality"]); + + const verificationIdeas = ideas.filter((i) => i.source === "verification_inversion"); + expect(verificationIdeas.length).toBeGreaterThanOrEqual(0); + }); + + it("sorts ideas by confidence", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(); + + for (let i = 1; i < ideas.length; i++) { + expect(ideas[i].confidence).toBeLessThanOrEqual(ideas[i - 1].confidence); + } + }); + + it("formats ideas as text", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(); + const text = engine.formatIdeas(ideas); + + expect(text).toContain("Improvement Ideas:"); + if (ideas.length > 0) { + expect(text).toContain("["); + } + }); + + it("formats ideas as JSON", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(); + const result = engine.formatIdeasJson(ideas); + + expect(result.project).toBeDefined(); + expect(result.summary.total).toBe(ideas.length); + expect(typeof result.summary.by_category).toBe("object"); + expect(typeof result.summary.by_tier).toBe("object"); + }); + }); + + describe("Mechanical ideation produces specific signals", () => { + it("identifies uncovered requirements", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(); + + const uncoveredIdeas = ideas.filter((i) => i.source === "uncovered_requirement"); + expect(uncoveredIdeas.length).toBeGreaterThanOrEqual(0); + if (uncoveredIdeas.length > 0) { + expect(uncoveredIdeas[0].category).toBe("coverage"); + expect(uncoveredIdeas[0].tier).toBe("mechanical"); + } + }); + + it("identifies partial requirements", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(); + + const partialIdeas = ideas.filter((i) => i.source === "partial_requirement"); + expect(partialIdeas.length).toBeGreaterThanOrEqual(0); + if (partialIdeas.length > 0) { + expect(partialIdeas[0].relatedReq).toBe("MULTI-01"); + } + }); + + it("identifies verification inversions (missing tests)", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(["quality"]); + + const verificationIdeas = ideas.filter((i) => i.source === "verification_inversion"); + expect(verificationIdeas.length).toBeGreaterThanOrEqual(0); + }); + + it("formats ideas as text with source prefix", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(); + const text = engine.formatIdeas(ideas); + + expect(text).toContain("Improvement Ideas:"); + if (ideas.length > 0) { + expect(text).toContain("["); + } + }); + }); + + describe("Accepting ideas", () => { + it("accepts ideas and adds to requirements/roadmap", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical().slice(0, 2); + + const { accepted, results } = engine.acceptIdeas(ideas); + + expect(accepted.length).toBeGreaterThan(0); + expect(results.length).toBe(accepted.length); + for (const result of results) { + expect(result.addedToRequirements || result.addedToRoadmap).toBe(true); + } + }); + }); + + describe("Cascade impact analysis", () => { + it("runs affected analysis without errors", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runAffected(); + + expect(Array.isArray(ideas)).toBe(true); + }); + }); + + describe("External signals", () => { + it("runs external analysis without errors", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runExternal(); + + expect(Array.isArray(ideas)).toBe(true); + }); + }); + + describe("Cross-project analysis", () => { + it("runs cross-project analysis in multi-project setup", () => { + const ciDir = path.join(dir, ".ciagent"); + const config = JSON.parse(fs.readFileSync(path.join(ciDir, "config.json"), "utf-8")); + config.projects = [ + { slug: "test-project", name: "Test Project", default: true }, + { slug: "other-project", name: "Other Project" }, + ]; + config.active_projects = ["test-project", "other-project"]; + fs.writeFileSync(path.join(ciDir, "config.json"), JSON.stringify(config, null, 2)); + + fs.mkdirSync(path.join(ciDir, "other-project"), { recursive: true }); + fs.writeFileSync(path.join(ciDir, "other-project", "PROJECT.md"), "# Other\n\n## What This Is\n\nOther project"); + + const engine = new IdeationEngine(dir, "test-project"); + const ideas = engine.runCrossProject(); + + expect(Array.isArray(ideas)).toBe(true); + }); + }); + + describe("Chaos scenarios", () => { + it("generates chaos scenario ideas", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.generateChaosScenarios(); + + expect(Array.isArray(ideas)).toBe(true); + expect(ideas.length).toBeGreaterThan(0); + for (const idea of ideas) { + expect(idea.category).toBe("chaos"); + expect(idea.source).toBe("chaos_scenario"); + expect(idea.tier).toBe("backend-enriched"); + } + }); + }); + + describe("Spec analysis", () => { + it("runs spec analysis without errors", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(["spec"]); + + expect(Array.isArray(ideas)).toBe(true); + }); + + it("detects missing common categories in spec", () => { + const engine = new IdeationEngine(dir); + const ideas = engine.runMechanical(["spec"]); + + const missingIdeas = ideas.filter((i) => i.source === "spec_missing"); + expect(missingIdeas.length).toBeGreaterThanOrEqual(0); + if (missingIdeas.length > 0) { + expect(missingIdeas[0].category).toMatch(/^(spec|security|quality|architecture|coverage|improvement)$/); + } + }); + }); +}); \ No newline at end of file diff --git a/src/verification/e2e-multiproject.test.ts b/src/verification/e2e-multiproject.test.ts new file mode 100644 index 0000000..3501ef7 --- /dev/null +++ b/src/verification/e2e-multiproject.test.ts @@ -0,0 +1,433 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as os from "node:os"; +import { CIAgentFiles } from "../core/ciagent-files.js"; +import { CommitBuilder } from "../core/commit-builder.js"; +import { initCIAgent, loadConfig, saveConfig } from "../core/config.js"; +import { DEFAULT_CIAGENT_CONFIG } from "../types/config.js"; + +function createTempDir(): string { + return fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-e2e-multiproject-")); +} + +function cleanup(dir: string): void { + fs.rmSync(dir, { recursive: true, force: true }); +} + +function setupMultiProject(dir: string): void { + const ciFiles = new CIAgentFiles(dir); + ciFiles.ensureCIDir(); + ciFiles.addProject("task-api", "Task API", true); + ciFiles.addProject("auth-svc", "Auth Service"); + + const taskApiDir = path.join(dir, ".ciagent", "task-api"); + fs.mkdirSync(taskApiDir, { recursive: true }); + fs.writeFileSync(path.join(taskApiDir, "PROJECT.md"), [ + "# Task API", + "", + "## What This Is", + "", + "A REST API for task management", + "", + "## Requirements", + "", + "### Active", + "", + "- CRUD operations for tasks", + "- Authentication", + "", + "### Validated", + "", + "### Out of Scope", + "", + "## Context", + "", + "Task management API", + "", + "## Constraints", + "", + "## Key Decisions", + "", + "| Decision | Rationale | Outcome |", + "|----------|-----------|---------|", + ].join("\n")); + + fs.writeFileSync(path.join(taskApiDir, "REQUIREMENTS.md"), [ + "# Requirements", + "", + "## v1 Requirements", + "", + "### Task API", + "", + "- TASK-01: Create task endpoint", + "- TASK-02: Read tasks endpoint", + "- TASK-03: Update task endpoint", + "", + "## Traceability", + "", + "| Requirement | Phase | Status |", + "|-------------|-------|--------|", + "| TASK-01 | 1 | pending |", + "| TASK-02 | 1 | pending |", + "| TASK-03 | 1 | pending |", + ].join("\n")); + + fs.writeFileSync(path.join(taskApiDir, "ROADMAP.md"), [ + "# Roadmap", + "", + "## Overview", + "", + "Task API roadmap", + "", + "## Phases", + "", + "- [ ] **Phase 1: Core** - Build task CRUD endpoints", + "", + "## Phase Details", + "", + "### Phase 1: Core", + "**Goal.**: Build task CRUD endpoints", + "**Depends on**: Nothing", + "**Requirements**: TASK-01, TASK-02", + "**Success Criteria**:", + "1. All CRUD operations work", + '**Status**: not_started', + "", + ].join("\n")); + + fs.writeFileSync(path.join(taskApiDir, "ARCHITECTURE.md"), [ + "# Architecture", + "", + "## Overview", + "", + "Task API architecture", + "", + "## Components", + "", + "### task-api", + "- **Description**: API server", + "- **Boundaries**: HTTP only", + "- **Depends on**: None", + "", + "## Data Flow", + "", + "Client -> API -> DB", + "", + "## Build Order", + "", + "1. task-api", + "", + ].join("\n")); + + const authDir = path.join(dir, ".ciagent", "auth-svc"); + fs.mkdirSync(authDir, { recursive: true }); + fs.writeFileSync(path.join(authDir, "PROJECT.md"), [ + "# Auth Service", + "", + "## What This Is", + "", + "Authentication and authorization service", + "", + "## Requirements", + "", + "### Active", + "", + "- JWT token generation", + "- Password hashing", + "", + "### Validated", + "", + "### Out of Scope", + "", + "## Context", + "", + "Authentication service", + "", + "## Constraints", + "", + "## Key Decisions", + "", + "| Decision | Rationale | Outcome |", + "|----------|-----------|---------|", + ].join("\n")); + + fs.writeFileSync(path.join(authDir, "REQUIREMENTS.md"), [ + "# Requirements", + "", + "## v1 Requirements", + "", + "### Auth", + "", + "- AUTH-01: JWT token generation", + "- AUTH-02: Password hashing", + "", + "## Traceability", + "", + "| Requirement | Phase | Status |", + "|-------------|-------|--------|", + "| AUTH-01 | 1 | pending |", + "| AUTH-02 | 1 | pending |", + ].join("\n")); + + fs.writeFileSync(path.join(authDir, "ROADMAP.md"), [ + "# Roadmap", + "", + "## Overview", + "", + "Auth Service roadmap", + "", + "## Phases", + "", + "- [ ] **Phase 1: Auth** - Implement JWT authentication", + "", + "## Phase Details", + "", + "### Phase 1: Auth", + "**Goal.**: Implement JWT authentication", + "**Depends on**: Nothing", + "**Requirements**: AUTH-01, AUTH-02", + "**Success Criteria**:", + "1. JWT tokens are generated correctly", + '**Status**: not_started', + "", + ].join("\n")); + + fs.writeFileSync(path.join(authDir, "ARCHITECTURE.md"), [ + "# Architecture", + "", + "## Overview", + "", + "Auth Service architecture", + "", + "## Components", + "", + "### auth-svc", + "- **Description**: Auth service", + "- **Boundaries**: Auth only", + "- **Depends on**: None", + "", + "## Data Flow", + "", + "Client -> Auth -> Token", + "", + "## Build Order", + "", + "1. auth-svc", + "", + ].join("\n")); + + const config = loadConfig(dir); + config.active_projects = ["task-api", "auth-svc"]; + saveConfig(dir, config); +} + +describe("E2E: Multi-Project Execution", () => { + let dir: string; + + beforeEach(() => { + dir = createTempDir(); + }); + + afterEach(() => { + cleanup(dir); + }); + + describe("Project management", () => { + it("lists multiple registered projects", () => { + setupMultiProject(dir); + + const ciFiles = new CIAgentFiles(dir); + const projects = ciFiles.listProjects(); + + expect(projects.length).toBeGreaterThanOrEqual(2); + const slugs = projects.map((p) => p.slug); + expect(slugs).toContain("task-api"); + expect(slugs).toContain("auth-svc"); + }); + + it("detects multi-project mode", () => { + setupMultiProject(dir); + + const ciFiles = new CIAgentFiles(dir); + expect(ciFiles.isMultiProject()).toBe(true); + }); + + it("reads and writes per-project files", () => { + setupMultiProject(dir); + + const taskFiles = new CIAgentFiles(dir, "task-api"); + const taskProject = taskFiles.readProjectMd(); + expect(taskProject).not.toBeNull(); + expect(taskProject!.name).toBe("Task API"); + + const authFiles = new CIAgentFiles(dir, "auth-svc"); + const authProject = authFiles.readProjectMd(); + expect(authProject).not.toBeNull(); + expect(authProject!.name).toBe("Auth Service"); + }); + + it("reads per-project requirements", () => { + setupMultiProject(dir); + + const taskFiles = new CIAgentFiles(dir, "task-api"); + const taskReqs = taskFiles.readRequirementsMd(); + expect(taskReqs).not.toBeNull(); + + const authFiles = new CIAgentFiles(dir, "auth-svc"); + const authReqs = authFiles.readRequirementsMd(); + expect(authReqs).not.toBeNull(); + }); + + it("reads per-project roadmap", () => { + setupMultiProject(dir); + + const taskFiles = new CIAgentFiles(dir, "task-api"); + const taskRoadmap = taskFiles.readRoadmapMd(); + expect(taskRoadmap).not.toBeNull(); + expect(taskRoadmap!.phases.length).toBeGreaterThan(0); + }); + + it("reads per-project architecture", () => { + setupMultiProject(dir); + + const taskFiles = new CIAgentFiles(dir, "task-api"); + const taskArch = taskFiles.readArchitectureMd(); + expect(taskArch).not.toBeNull(); + expect(taskArch!.components.length).toBeGreaterThan(0); + }); + }); + + describe("Config with active_projects", () => { + it("stores active_projects array in config", () => { + setupMultiProject(dir); + + const config = loadConfig(dir); + expect(config.active_projects).toContain("task-api"); + expect(config.active_projects).toContain("auth-svc"); + expect(config.active_projects.length).toBe(2); + }); + + it("max_concurrent_projects is configurable", () => { + initCIAgent(dir, { + parallelization: { + ...DEFAULT_CIAGENT_CONFIG.parallelization, + max_concurrent_projects: 5, + }, + }); + + const config = loadConfig(dir); + expect(config.parallelization.max_concurrent_projects).toBe(5); + }); + + it("default max_concurrent_projects is 3", () => { + expect(DEFAULT_CIAGENT_CONFIG.parallelization.max_concurrent_projects).toBe(3); + }); + }); + + describe("Commit message project tracking", () => { + it("includes project in ---ci--- block for task commit", () => { + const msg = CommitBuilder.buildTaskCommit({ + type: "feat", + phase: 1, + milestone: "v0.10", + project: "task-api", + plan: "01-auth", + task: "01-01", + subject: "implement JWT token generation", + status: "execute", + }); + + expect(msg).toContain("---ci---"); + expect(msg).toContain("project: task-api"); + expect(msg).toContain("phase: 1"); + expect(msg).toContain("milestone: v0.10"); + expect(msg).toContain("status: execute"); + }); + + it("includes project in ---ci--- block for init commit", () => { + const msg = CommitBuilder.buildInitCommit({ + projectName: "Auth Service", + phaseCount: 2, + milestone: "v0.10", + project: "auth-svc", + specification: "Authentication and authorization service", + }); + + expect(msg).toContain("---ci---"); + expect(msg).toContain("project: auth-svc"); + expect(msg).toContain("phase: 0"); + }); + + it("different projects produce different commit scopes", () => { + const taskMsg = CommitBuilder.buildTaskCommit({ + type: "feat", + phase: 1, + milestone: "v0.10", + project: "task-api", + plan: "01", + task: "01", + subject: "create task endpoint", + status: "execute", + }); + + const authMsg = CommitBuilder.buildTaskCommit({ + type: "feat", + phase: 1, + milestone: "v0.10", + project: "auth-svc", + plan: "01", + task: "01", + subject: "JWT token generation", + status: "execute", + }); + + expect(taskMsg).toContain("task-api/"); + expect(taskMsg).toContain("project: task-api"); + expect(authMsg).toContain("auth-svc/"); + expect(authMsg).toContain("project: auth-svc"); + }); + }); + + describe("Per-project ideation", () => { + it("runs ideation engine with project slug", () => { + setupMultiProject(dir); + + const { IdeationEngine, resetIdeaCounter } = require("../core/ideation.js"); + resetIdeaCounter(); + + const taskEngine = new IdeationEngine(dir, "task-api"); + const taskIdeas = taskEngine.runMechanical(); + + expect(Array.isArray(taskIdeas)).toBe(true); + expect(taskIdeas.length).toBeGreaterThan(0); + + resetIdeaCounter(); + + const authEngine = new IdeationEngine(dir, "auth-svc"); + const authIdeas = authEngine.runMechanical(); + + expect(Array.isArray(authIdeas)).toBe(true); + expect(authIdeas.length).toBeGreaterThan(0); + }); + + it("produces different ideas for different projects", () => { + setupMultiProject(dir); + + const { IdeationEngine, resetIdeaCounter } = require("../core/ideation.js"); + resetIdeaCounter(); + + const taskEngine = new IdeationEngine(dir, "task-api"); + const taskIdeas = taskEngine.runMechanical(); + const taskTitles = new Set(taskIdeas.map((i: any) => i.title)); + + resetIdeaCounter(); + + const authEngine = new IdeationEngine(dir, "auth-svc"); + const authIdeas = authEngine.runMechanical(); + const authTitles = new Set(authIdeas.map((i: any) => i.title)); + + expect(taskTitles.size).toBeGreaterThan(0); + expect(authTitles.size).toBeGreaterThan(0); + }); + }); +}); \ No newline at end of file diff --git a/src/version.ts b/src/version.ts index 25206c9..98e7400 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = "0.9.0"; \ No newline at end of file +export const VERSION = "0.10.0"; \ No newline at end of file