feat(P03): multi-project support, NFR milestone versioning, phase context reset, install scripts (v0.3.0)

This commit is contained in:
CI
2026-05-29 15:13:45 +00:00
parent e4bb3a9970
commit ddf04792c7
57 changed files with 1748 additions and 59 deletions
+21 -6
View File
@@ -20,6 +20,7 @@ export interface CommitScope {
phase: number;
plan?: string;
task?: string;
project?: string;
isInit: boolean;
isMilestone: boolean;
}
@@ -53,6 +54,7 @@ export interface CommitCompoundMeta {
export interface CiMetadata {
phase: number;
milestone: string;
project?: string;
plan?: string;
task?: string;
status: PipelineStage;
@@ -88,10 +90,19 @@ export function parseCommitScope(scope: string): CommitScope {
return { phase: 0, isInit: false, isMilestone: true };
}
const phaseMatch = scope.match(/^P(\d+)/);
let project: string | undefined;
let cleanScope = scope;
const projectMatch = scope.match(/^([a-z0-9-]+)\/(.+)$/);
if (projectMatch && !scope.startsWith("P")) {
project = projectMatch[1];
cleanScope = projectMatch[2];
}
const phaseMatch = cleanScope.match(/^P(\d+)/);
const phase = phaseMatch ? parseInt(phaseMatch[1], 10) : 0;
const parts = scope.split("-");
const parts = cleanScope.split("-");
let plan: string | undefined;
let task: string | undefined;
@@ -102,7 +113,7 @@ export function parseCommitScope(scope: string): CommitScope {
task = `${plan}-${parts[2]}`;
}
return { phase, plan, task, isInit: false, isMilestone: false };
return { phase, plan, task, project, isInit: false, isMilestone: false };
}
export function formatCommitScope(scope: CommitScope): string {
@@ -110,7 +121,11 @@ export function formatCommitScope(scope: CommitScope): string {
if (scope.isMilestone) return "milestone";
const phaseStr = `P${String(scope.phase).padStart(2, "0")}`;
if (scope.task) return `${phaseStr}-${scope.task.split("-").slice(1).join("-")}`;
if (scope.plan) return `${phaseStr}-${scope.plan.split("-").slice(1).join("-")}`;
return phaseStr;
let suffix: string;
if (scope.task) suffix = `${phaseStr}-${scope.task.split("-").slice(1).join("-")}`;
else if (scope.plan) suffix = `${phaseStr}-${scope.plan.split("-").slice(1).join("-")}`;
else suffix = phaseStr;
if (scope.project) return `${scope.project}/${suffix}`;
return suffix;
}
+51 -1
View File
@@ -1,4 +1,4 @@
import { CIConfig, DEFAULT_CI_CONFIG, AutonomyLevel, ModelProfile } from "../types/config.js";
import { CIConfig, DEFAULT_CI_CONFIG, AutonomyLevel, ModelProfile, ProjectEntry } from "../types/config.js";
describe("CIConfig", () => {
it("DEFAULT_CI_CONFIG has all required fields", () => {
@@ -17,6 +17,11 @@ describe("CIConfig", () => {
expect(DEFAULT_CI_CONFIG.git.auto_push).toBe(false);
});
it("DEFAULT_CI_CONFIG has multi-project fields", () => {
expect(DEFAULT_CI_CONFIG.projects).toEqual([]);
expect(DEFAULT_CI_CONFIG.active_project).toBe("");
});
it("AutonomyLevel accepts all valid levels", () => {
const levels: AutonomyLevel[] = ["full", "supervised", "guided"];
for (const level of levels) {
@@ -46,4 +51,49 @@ describe("CIConfig", () => {
"merge_to_main",
]);
});
describe("ProjectEntry", () => {
it("accepts valid project entries", () => {
const entry: ProjectEntry = { slug: "task-api", name: "Task API", default: true };
expect(entry.slug).toBe("task-api");
expect(entry.name).toBe("Task API");
expect(entry.default).toBe(true);
});
it("default field is optional", () => {
const entry: ProjectEntry = { slug: "task-api", name: "Task API" };
expect(entry.default).toBeUndefined();
});
});
describe("CIConfig with projects", () => {
it("supports multiple projects", () => {
const config: CIConfig = {
...DEFAULT_CI_CONFIG,
projects: [
{ slug: "task-api", name: "Task API", default: true },
{ slug: "auth-svc", name: "Auth Service" },
],
active_project: "task-api",
};
expect(config.projects).toHaveLength(2);
expect(config.active_project).toBe("task-api");
expect(config.projects[0].default).toBe(true);
});
it("supports single project", () => {
const config: CIConfig = {
...DEFAULT_CI_CONFIG,
projects: [{ slug: "my-app", name: "My App", default: true }],
active_project: "my-app",
};
expect(config.projects).toHaveLength(1);
expect(config.active_project).toBe("my-app");
});
it("defaults to empty projects array and empty active_project", () => {
expect(DEFAULT_CI_CONFIG.projects).toEqual([]);
expect(DEFAULT_CI_CONFIG.active_project).toBe("");
});
});
});
+10
View File
@@ -61,7 +61,15 @@ export interface GitConfig {
auto_push: boolean;
}
export interface ProjectEntry {
slug: string;
name: string;
default?: boolean;
}
export interface CIConfig {
projects: ProjectEntry[];
active_project: string;
autonomy: AutonomyConfig;
model_profile: ModelProfile;
parallelization: ParallelizationConfig;
@@ -71,6 +79,8 @@ export interface CIConfig {
}
export const DEFAULT_CI_CONFIG: CIConfig = {
projects: [],
active_project: "",
autonomy: {
level: "full",
escalation_hooks: ["deploy", "delete_data", "merge_to_main"],