Merge pull request 'feat(P06): Integration & hardening — INTEG-01..05, MULTI-04, v0.10.0' (#9) from phase/06-integration-hardening into main
CI / build-and-test (push) Has been cancelled
Publish to npm / publish (push) Has been cancelled

This commit was merged in pull request #9.
This commit is contained in:
2026-06-01 15:41:20 +00:00
19 changed files with 1131 additions and 90 deletions
+11 -12
View File
@@ -84,7 +84,7 @@ templates/ # Template files (config.json, DECISIONS.md, specification.md
## Pipeline Flow ## 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. 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 framework: Jest with ts-jest
- Test file pattern: `**/*.test.ts` in `src/` - Test file pattern: `**/*.test.ts` in `src/`
- Run: `npm run test` - 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 - Tests use temp directories (os.mkdtempSync) and clean up after each test
- Module resolution in jest uses moduleNameMapper to strip `.js` extensions - Module resolution in jest uses moduleNameMapper to strip `.js` extensions
@@ -194,19 +194,18 @@ IntelligenceBackend (unified interface)
## Current State ## 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.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 - **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: <slug>` 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) - **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 - **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 (v0.9)**: opencode → openai → ollama-local → ollama-cloud → anthropic - **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 - **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 - **Integration tests**: E2E v0.10 tests verify ideation CLI (mechanical tier), multi-project execution, all-agents-mechanical, parallel execution
- **Parallel execution**: OrchestratorAgent supports concurrent review agents with `limitConcurrency()`, controlled by `parallelization.max_concurrent_agents` - **Pipeline stages**: SPECIFY → CLARIFY → RESEARCH → **IDEATE** → PLAN → EXECUTE → TEST → VERIFY → COMPLETE
- **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, compound, and **project** metadata
- **Commit schema**: Every CIAgent-generated commit contains a `---ci---` YAML block with phase, milestone, status, decisions, escalations, requirements, lessons, and compound metadata
- **Branch strategy**: `phase/NN-slug` and `milestone/vX.X-slug` branches encode project structure; merged = complete, active = in progress - **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 - **CLI commands**: `init`, `run`, `quick`, `debug`, `verify`, `review`, `status`, `audit`, `clarify`, `rollback`, `ship`, `ideate`, `projects`
- **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`)
- **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. - **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 - **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
+97 -6
View File
@@ -63,6 +63,28 @@ ciagent quick "Add authentication middleware"
# Check project status (reads from git log + branches) # Check project status (reads from git log + branches)
ciagent status 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 <slug> <name> # Add a new project
ciagent projects set <slug> # 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) # Review autonomous decisions (extracted from git log ---ci--- blocks)
ciagent audit ciagent audit
ciagent audit --verbose ciagent audit --verbose
@@ -77,7 +99,7 @@ ciagent rollback 1
ciagent ship 1 ciagent ship 1
``` ```
## Git-Native Architecture (v0.9.0) ## Git-Native Architecture (v0.10.0)
### The Commit Schema ### The Commit Schema
@@ -111,7 +133,7 @@ requirements:
| Where | What | Why | | 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/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/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 | | `.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": { "parallelization": {
"enabled": true, "enabled": true,
"max_concurrent_agents": 5, "max_concurrent_agents": 5,
"min_plans_for_parallel": 2 "min_plans_for_parallel": 2,
"max_concurrent_projects": 3
}, },
"verification": { "verification": {
"automated_only": true, "automated_only": true,
@@ -221,6 +244,25 @@ CIAgent uses `.ciagent/config.json` for project configuration:
"branching_strategy": "phase", "branching_strategy": "phase",
"auto_commit": true, "auto_commit": true,
"auto_push": false "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 ### Pipeline
``` ```
SPECIFY → CLARIFY → RESEARCH → PLAN → EXECUTE → TEST → VERIFY → COMPLETE SPECIFY → CLARIFY → RESEARCH → IDEATE → PLAN → EXECUTE → TEST → VERIFY → COMPLETE
↕ ↕ ↕ ↕ ↕ ↕ ↕ ↕ ↕ ↕
(questions) (auto-decide) (auto-run) (auto-test) (auto-verify) (questions) (auto-decide) (ideas) (auto-run) (auto-test) (auto-verify)
``` ```
### Git-Native Core Modules ### 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 | | 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 | | 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 ### Verification Layers
1. **Structural**: File existence, import/export wiring, no stubs 1. **Structural**: File existence, import/export wiring, no stubs
+32 -14
View File
@@ -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: **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 | | Milestone Type | Condition | Phase release | Milestone release |
|---------------|-----------|---------------|-------------------| |---------------|-----------|---------------|-------------------|
| **NFR** | All phases: fix/chore/docs/perf/refactor/test | Patch (`vX.Y.Z`) | None | | **NFR** | All phases are fix/chore/docs/perf/refactor/test | Patch `v1.8.1`, `v1.8.2`, ... | None — final patch IS the deliverable |
| **Feature** | Any phase is `feat`, no schema break | Patch (`vX.Y.Z`) | Minor — `vX.(Y+1).0` | | **Feature** | At least one phase has new features (`feat`) | Patch `v1.8.1`, `v1.8.2`, ... | Next minor — `v1.9.0` |
| **Schema-breaking** | Refactor/schema break/new direction | Minor — `vX.(Y+N).0` per phase | Major — `v(X+1).0.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: **Tag rules (CRITICAL):**
- Feature: patches v0.5.1v0.5.5 → milestone tag is v0.6.0 (NOT v0.5.0) - Milestone tags are always the NEXT version, never the base:
- Schema-breaking: minors v0.3.0, v0.4.0, v0.5.0 → milestone tag is v1.0.0 - Feature: patches v0.5.1v0.5.5 → milestone tag is v0.6.0 (NOT v0.5.0)
- NFR: no milestone tag — the milestone is implicit from the patch sequence - 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
Determine milestone type via `getMilestoneType()` which returns `"nfr" | "feature" | "schema-breaking"`. - 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 ### 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.) 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 ```bash
git checkout milestone/v0.5-schema-rewrite git checkout milestone/v0.5-schema-rewrite
git merge --squash phase/01-core-refactor git merge --squash phase/01-core-refactor
@@ -145,7 +152,7 @@ git push origin main --tags
# Create Gitea release for v0.5.0 # 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 ### Milestone completion
@@ -160,7 +167,7 @@ git push origin main --tags
# Create Gitea release for v0.6.0 with full milestone summary # Create Gitea release for v0.6.0 with full milestone summary
``` ```
**Schema-breaking (major release):** **Major (major release):**
```bash ```bash
# All phases already merged into milestone branch # All phases already merged into milestone branch
git checkout main git checkout main
@@ -177,9 +184,20 @@ git push origin main --tags
Before creating any tag: Before creating any tag:
1. Tag must be strictly greater than all existing tags on the same major.minor line 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 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 ## Multi-Project Branch Naming
When operating in multi-project mode (`.ciagent/config.json` has `projects[]` with length > 0): When operating in multi-project mode (`.ciagent/config.json` has `projects[]` with length > 0):
+6 -5
View File
@@ -101,7 +101,7 @@ For each stage in order (starting from current or from `specify`):
- Update `.ciagent/ROADMAP.md` phase status - Update `.ciagent/ROADMAP.md` phase status
- Commit: `docs(P##): complete [phase-name] phase` - 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 ## 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) 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 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. - **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** (any feat phase): apply progressive patch versions per phase, then tag minor milestone version on completion (e.g., v0.2.0). - **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 ## Step 4: Error Recovery
+132 -32
View File
@@ -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 # CIAgent Ship
Ship a CIAgent phase or milestone. Every ship creates a release — no exceptions. 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 | | Milestone Type | Condition | Phase release | Milestone release |
|---------------|-----------|---------------|-------------------| |---------------|-----------|---------------|-------------------|
| **NFR** | All phases: fix/chore/docs/perf/refactor/test | Patch (`vX.Y.Z`) | None | | **NFR** | All phases are fix/chore/docs/perf/refactor/test | Patch `v1.8.1`, `v1.8.2`, ... | None — final patch IS the deliverable |
| **Feature** | Any phase is `feat`, no schema break | Patch (`vX.Y.Z`) | Minor — `vX.(Y+1).0` | | **Feature** | At least one phase has new features (`feat`) | Patch `v1.8.1`, `v1.8.2`, ... | Next minor — `v1.9.0` |
| **Schema-breaking** | Refactor/schema break/new direction | Minor — `vX.(Y+N).0` per phase | Major — `v(X+1).0.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: **Tag rules (CRITICAL):**
- Feature: patches v0.5.1v0.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
**Usage:** `ciagent-ship [phase_number|milestone]` - Milestone tags are always the NEXT version, never the base:
- Feature: patches v0.5.1v0.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 ## 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. If single-project mode: proceed with existing conventions.
## Step 1: Pre-Flight ## Step 1: Pre-Flight Validation
```bash ```bash
git log --max-count=10 git log --max-count=10
git branch -a git branch -a
git tag -l
``` ```
Determine what is being shipped: a single phase or an entire milestone. 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. 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 ## Step 2: Run Tests
```bash ```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. 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 <target-branch> --head <source-branch> --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 <pr-number> --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 | | 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 | 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 | 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 | 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 | 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: Phase number within the milestone determines the increment:
- NFR/Feature: 1st phase = .1, 2nd = .2, etc. (v0.5.1, v0.5.2) - 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:** **Tag validation (before creating ANY tag):**
1. The tag must be strictly greater than all existing tags on the same major.minor line 1. 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) 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) 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 ### 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 ### Phase ship
@@ -123,8 +198,9 @@ requirements:
### Milestone ship (after last phase) ### Milestone ship (after last phase)
**Validate all phase branches are merged into the milestone branch before proceeding.**
```bash ```bash
# Verify all phase branches are merged into milestone branch
git checkout main git checkout main
git merge --squash milestone/vX.Y-slug git merge --squash milestone/vX.Y-slug
git commit -m "docs(milestone): complete [milestone-name] git commit -m "docs(milestone): complete [milestone-name]
@@ -136,7 +212,7 @@ status: complete
---/ci---" ---/ci---"
``` ```
## Step 5: Tag and Push ## Step 6: Tag and Push
```bash ```bash
git tag -a vX.Y.Z -m "vX.Y.Z: [phase-name or milestone-name]" 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:** **Tag format by milestone type:**
- NFR/Feature phase: patch format (`v0.5.1`, `v0.5.2`) - 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`) - 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.** **Every ship creates a Gitea release. No exceptions.**
Generate release notes from git log: ### Generate release notes
```bash ```bash
git log v[previous_tag]..vX.Y.Z --oneline git log v[previous_tag]..vX.Y.Z --oneline
``` ```
Create the release via Gitea API: ### Create the Gitea release
```bash ```bash
curl -X POST "https://git.cloudinit.dev/api/v1/repos/continuous-intelligence/ci/releases" \ 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. 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/REQUIREMENTS.md` — mark shipped requirements as complete
- Update `.ciagent/ROADMAP.md` — mark shipped phase as complete - Update `.ciagent/ROADMAP.md` — mark shipped phase as complete
Commit the file updates. Commit the file updates.
## Step 8: Report ## Step 9: Report
``` ```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -185,7 +284,7 @@ Commit the file updates.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase [N]: [name] Phase [N]: [name]
Milestone: [vX.Y] ([nfr|feature|schema-breaking]) Milestone: [vX.Y] ([nfr|feature|major])
Version: vX.Y.Z Version: vX.Y.Z
Release: https://git.cloudinit.dev/continuous-intelligence/ci/releases/tag/vX.Y.Z Release: https://git.cloudinit.dev/continuous-intelligence/ci/releases/tag/vX.Y.Z
Status: complete Status: complete
@@ -193,6 +292,7 @@ Status: complete
Tests: PASS Tests: PASS
Typecheck: PASS Typecheck: PASS
Build: PASS Build: PASS
Pipeline: PASS
Requirements covered: [N] Requirements covered: [N]
Commits: [N] Commits: [N]
+2 -2
View File
@@ -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]" argument-hint: "[phase_number|milestone]"
tools: tools:
read: true read: true
@@ -12,7 +12,7 @@ tools:
--- ---
<execution_context> <execution_context>
@__OPENCODE_DIR__/ci/workflows/ship.md @/root/.config/opencode/ci/workflows/ship.md
</execution_context> </execution_context>
<context> <context>
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@continuous-intelligence/ciagent", "name": "@continuous-intelligence/ciagent",
"version": "0.9.0", "version": "0.10.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@continuous-intelligence/ciagent", "name": "@continuous-intelligence/ciagent",
"version": "0.9.0", "version": "0.10.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"commander": "^12.1.0", "commander": "^12.1.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@continuous-intelligence/ciagent", "name": "@continuous-intelligence/ciagent",
"version": "0.9.0", "version": "0.10.0",
"description": "Fully autonomous AI-driven software engineering harness - Continuous Intelligence", "description": "Fully autonomous AI-driven software engineering harness - Continuous Intelligence",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
+4 -4
View File
@@ -1002,7 +1002,7 @@ function computeShipVersion(
projectPath: string, projectPath: string,
phaseNum: number, phaseNum: number,
config: CIAgentConfig 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" }) const tags = execSync("git tag -l", { cwd: projectPath, encoding: "utf-8" })
.split("\n") .split("\n")
.map((t) => t.trim()) .map((t) => t.trim())
@@ -1029,7 +1029,7 @@ function computeShipVersion(
const milestoneType = inferMilestoneType(projectPath); const milestoneType = inferMilestoneType(projectPath);
let tag: string; let tag: string;
if (milestoneType === "schema-breaking") { if (milestoneType === "major") {
tag = `v${major}.${minor + phaseNum}.0`; tag = `v${major}.${minor + phaseNum}.0`;
} else { } else {
tag = `v${major}.${minor}.${phaseNum}`; tag = `v${major}.${minor}.${phaseNum}`;
@@ -1038,10 +1038,10 @@ function computeShipVersion(
return { tag, milestoneType }; return { tag, milestoneType };
} }
function inferMilestoneType(projectPath: string): "nfr" | "feature" | "schema-breaking" { function inferMilestoneType(projectPath: string): "nfr" | "feature" | "major" {
try { try {
const log = execSync("git log --oneline -50", { cwd: projectPath, encoding: "utf-8" }); 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"; if (log.match(/\bfeat\b/)) return "feature";
return "nfr"; return "nfr";
} catch { } catch {
+3 -3
View File
@@ -329,7 +329,7 @@ describe("CIAgentFiles", () => {
expect(ciFiles.getMilestoneType()).toBe("feature"); 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"); const ciFiles = new CIAgentFiles(dir, "schema-proj");
ciFiles.ensureProjectDir(); ciFiles.ensureProjectDir();
fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({
@@ -337,13 +337,13 @@ describe("CIAgentFiles", () => {
active_project: "schema-proj", active_project: "schema-proj",
})); }));
const roadmap: RoadmapMd = { const roadmap: RoadmapMd = {
overview: "schema-breaking", overview: "major",
phases: [ phases: [
{ number: 1, name: "refactor-core", description: "Refactor core", status: "in_progress", dependsOn: [], requirements: [], successCriteria: [] }, { number: 1, name: "refactor-core", description: "Refactor core", status: "in_progress", dependsOn: [], requirements: [], successCriteria: [] },
], ],
}; };
ciFiles.writeRoadmapMd(roadmap); ciFiles.writeRoadmapMd(roadmap);
expect(ciFiles.getMilestoneType()).toBe("schema-breaking"); expect(ciFiles.getMilestoneType()).toBe("major");
}); });
}); });
+1 -1
View File
@@ -486,7 +486,7 @@ export class CIAgentFiles {
} }
} }
if (hasSchemaBreak) return "schema-breaking"; if (hasSchemaBreak) return "major";
if (hasFeature) return "feature"; if (hasFeature) return "feature";
return "nfr"; return "nfr";
} }
+2 -2
View File
@@ -192,11 +192,11 @@ describe("GitBranch", () => {
expect(tag).toBe("v0.6.0"); 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" }); execSync(`git tag -a v0.5.1 -m "v0.5.1"`, { cwd: repoDir, stdio: "pipe" });
const gitBranch = new GitBranch(repoDir); const gitBranch = new GitBranch(repoDir);
const tag = gitBranch.computeMilestoneTag("schema-breaking"); const tag = gitBranch.computeMilestoneTag("major");
expect(tag).toBe("v1.0.0"); expect(tag).toBe("v1.0.0");
}); });
+1 -1
View File
@@ -242,7 +242,7 @@ export class GitBranch {
} }
} }
if (milestoneType === "schema-breaking") { if (milestoneType === "major") {
return `v${major + 1}.0.0`; return `v${major + 1}.0.0`;
} }
+2 -2
View File
@@ -307,7 +307,7 @@ status: execute
expect(ctx.getMilestoneType()).toBe("feature"); 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 commit(repoDir, `refactor(P01): rewrite core
---ci--- ---ci---
@@ -317,7 +317,7 @@ status: execute
---/ci---`); ---/ci---`);
const ctx = new GitContext(repoDir); const ctx = new GitContext(repoDir);
expect(ctx.getMilestoneType()).toBe("schema-breaking"); expect(ctx.getMilestoneType()).toBe("major");
}); });
}); });
}); });
+1 -1
View File
@@ -333,7 +333,7 @@ export class GitContext {
if (!commit.ci) continue; if (!commit.ci) continue;
hasAnyCiCommit = true; hasAnyCiCommit = true;
if (commit.type === "feat") return "feature"; 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"; if (!hasAnyCiCommit) return "nfr";
return "nfr"; return "nfr";
+1 -1
View File
@@ -7,7 +7,7 @@ export type ModelProfile = "quality" | "speed" | "balanced";
export type BranchingStrategy = "phase" | "feature" | "trunk"; 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"; export type PhaseName = "research" | "plan" | "execute" | "verify" | "complete";
+399
View File
@@ -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)$/);
}
});
});
});
+433
View File
@@ -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);
});
});
});
+1 -1
View File
@@ -1 +1 @@
export const VERSION = "0.9.0"; export const VERSION = "0.10.0";