feat(P03): multi-project support, NFR milestone versioning, phase context reset, install scripts (v0.3.0)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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("");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user