refactor(P06): rename milestone type schema-breaking → major, reinforce release flow

---ci---
phase: 6
milestone: v0.10
status: execute
decisions:
  - id: D-001
    decision: Rename MilestoneType schema-breaking to major for clarity
    rationale: Major better describes the semver impact (major version bump) and aligns with standard semver terminology
    confidence: 0.95
    alternatives: [schema-breaking, breaking, major-change]
  - id: D-002
    decision: Add autopilot rules, PR+QA gates, and merge validation to ship workflow
    rationale: Release flow was documented but not enforced in the workflow. Zero-HITL rules, branch hierarchy validation, and coreci packaging steps ensure consistent releases
    confidence: 0.90
    alternatives: [keep-as-documentation-only, add-to-AGENTS.md-only]
---/ci---
This commit is contained in:
Jon Chery
2026-06-01 15:29:43 +00:00
parent e2b749d42e
commit f478088797
12 changed files with 187 additions and 68 deletions
+31 -13
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):**
- 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) - 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 - Major: 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 - 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
Determine milestone type via `getMilestoneType()` which returns `"nfr" | "feature" | "schema-breaking"`. - 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):**
- 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) - 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 - Major: 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 - 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
**Usage:** `ciagent-ship [phase_number|milestone]` - 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>
+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";