Files
ci/src/core/commit-builder.ts
T

352 lines
9.0 KiB
TypeScript

import {
CiMetadata,
CommitType,
CommitScope,
CommitDecision,
CommitEscalation,
CommitRequirements,
CommitCompoundMeta,
formatCommitScope,
} from "../types/commit-meta.js";
import { PipelineStage } from "../types/pipeline.js";
const CI_BLOCK_START = "---ci---";
const CI_BLOCK_END = "---/ci---";
export interface CommitMessageInput {
type: CommitType;
scope: CommitScope;
subject: string;
ci: CiMetadata;
body?: string;
}
export interface InitCommitInput {
projectName: string;
phaseCount: number;
milestone: string;
specification: string;
requirements?: string[];
constraints?: string[];
outOfScope?: string[];
decisions?: CommitDecision[];
}
export interface TaskCommitInput {
type: CommitType;
phase: number;
milestone: string;
plan: string;
task: string;
subject: string;
status: PipelineStage;
decisions?: CommitDecision[];
requirements?: CommitRequirements;
body?: string;
}
export interface PhaseCompletionInput {
phase: number;
milestone: string;
phaseName: string;
tasksCompleted: number;
tasksTotal: number;
taskNames: string[];
decisions?: CommitDecision[];
requirements?: CommitRequirements;
lessons?: string[];
securitySummary?: string;
}
export interface DecisionCommitInput {
phase: number;
milestone: string;
subject: string;
decisions: CommitDecision[];
}
export interface EscalationCommitInput {
phase: number;
milestone: string;
subject: string;
escalations: CommitEscalation[];
}
export interface CompoundCommitInput {
phase: number;
milestone: string;
category: string;
problem: string;
solution: string;
lessons?: string[];
}
export interface VerifyCommitInput {
phase: number;
milestone: string;
subject: string;
requirements: CommitRequirements;
lessons?: string[];
}
export class CommitBuilder {
static buildCiBlock(ci: CiMetadata): string {
const lines: string[] = [];
lines.push(`phase: ${ci.phase}`);
lines.push(`milestone: ${ci.milestone}`);
if (ci.plan) lines.push(`plan: ${ci.plan}`);
if (ci.task) lines.push(`task: ${ci.task}`);
lines.push(`status: ${ci.status}`);
if (ci.decisions && ci.decisions.length > 0) {
lines.push("decisions:");
for (const d of ci.decisions) {
lines.push(` - id: ${d.id}`);
lines.push(` decision: ${d.decision}`);
lines.push(` rationale: ${d.rationale}`);
lines.push(` confidence: ${d.confidence}`);
lines.push(` alternatives: [${d.alternatives.join(", ")}]`);
}
}
if (ci.escalations && ci.escalations.length > 0) {
lines.push("escalations:");
for (const e of ci.escalations) {
lines.push(` - id: ${e.id}`);
lines.push(` type: ${e.type}`);
lines.push(` description: ${e.description}`);
lines.push(` resolution: ${e.resolution}`);
}
}
if (ci.requirements) {
lines.push("requirements:");
lines.push(` covered: [${ci.requirements.covered.join(", ")}]`);
lines.push(` partial: [${ci.requirements.partial.join(", ")}]`);
}
if (ci.lessons && ci.lessons.length > 0) {
lines.push("lessons:");
for (const l of ci.lessons) {
lines.push(` - ${l}`);
}
}
if (ci.compound) {
lines.push("compound:");
lines.push(` category: ${ci.compound.category}`);
lines.push(` problem: ${ci.compound.problem}`);
lines.push(` solution: ${ci.compound.solution}`);
}
return lines.join("\n");
}
static buildCommitMessage(input: CommitMessageInput): string {
const scopeStr = formatCommitScope(input.scope);
const subjectLine = `${input.type}(${scopeStr}): ${input.subject}`;
const ciBlock = CommitBuilder.buildCiBlock(input.ci);
const parts = [subjectLine, "", CI_BLOCK_START, ciBlock, CI_BLOCK_END];
if (input.body) {
parts.push("", input.body);
}
return parts.join("\n");
}
static buildInitCommit(input: InitCommitInput): string {
const ci: CiMetadata = {
phase: 0,
milestone: input.milestone,
status: "specify",
decisions: input.decisions,
};
const scope: CommitScope = { phase: 0, isInit: true, isMilestone: false };
const subjectLine = `docs(init): initialize ${input.projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-")} (${input.phaseCount} phases)`;
const ciBlock = CommitBuilder.buildCiBlock(ci);
const bodyLines: string[] = [
`Specification: ${input.specification}`,
];
if (input.requirements?.length) {
bodyLines.push("", `Requirements: ${input.requirements.join(", ")}`);
}
if (input.constraints?.length) {
bodyLines.push("", `Constraints: ${input.constraints.join(", ")}`);
}
if (input.outOfScope?.length) {
bodyLines.push("", `Out of scope: ${input.outOfScope.join(", ")}`);
}
const parts = [subjectLine, "", CI_BLOCK_START, ciBlock, CI_BLOCK_END, "", ...bodyLines];
return parts.join("\n");
}
static buildTaskCommit(input: TaskCommitInput): string {
const ci: CiMetadata = {
phase: input.phase,
milestone: input.milestone,
plan: input.plan,
task: input.task,
status: input.status,
decisions: input.decisions,
requirements: input.requirements,
};
const scope: CommitScope = {
phase: input.phase,
plan: input.plan,
task: input.task,
isInit: false,
isMilestone: false,
};
return CommitBuilder.buildCommitMessage({
type: input.type,
scope,
subject: input.subject,
ci,
body: input.body,
});
}
static buildPhaseCompletionCommit(input: PhaseCompletionInput): string {
const ci: CiMetadata = {
phase: input.phase,
milestone: input.milestone,
status: "complete",
decisions: input.decisions,
requirements: input.requirements,
lessons: input.lessons,
};
const scope: CommitScope = { phase: input.phase, isInit: false, isMilestone: false };
const subjectLine = `docs(P${String(input.phase).padStart(2, "0")}): complete ${input.phaseName} phase`;
const ciBlock = CommitBuilder.buildCiBlock(ci);
const bodyLines: string[] = [
`Tasks completed: ${input.tasksCompleted}/${input.tasksTotal}`,
];
for (const name of input.taskNames) {
bodyLines.push(`- ${name}`);
}
bodyLines.push("");
if (input.securitySummary) {
bodyLines.push(input.securitySummary);
}
return [subjectLine, "", CI_BLOCK_START, ciBlock, CI_BLOCK_END, "", ...bodyLines].join("\n");
}
static buildDecisionCommit(input: DecisionCommitInput): string {
const ci: CiMetadata = {
phase: input.phase,
milestone: input.milestone,
status: "plan",
decisions: input.decisions,
};
const scope: CommitScope = { phase: input.phase, isInit: false, isMilestone: false };
return CommitBuilder.buildCommitMessage({
type: "decision",
scope,
subject: input.subject,
ci,
});
}
static buildEscalationCommit(input: EscalationCommitInput): string {
const ci: CiMetadata = {
phase: input.phase,
milestone: input.milestone,
status: "execute",
escalations: input.escalations,
};
const scope: CommitScope = { phase: input.phase, isInit: false, isMilestone: false };
return CommitBuilder.buildCommitMessage({
type: "escalation",
scope,
subject: input.subject,
ci,
});
}
static buildCompoundCommit(input: CompoundCommitInput): string {
const ci: CiMetadata = {
phase: input.phase,
milestone: input.milestone,
status: "complete",
compound: {
category: input.category,
problem: input.problem,
solution: input.solution,
},
lessons: input.lessons,
};
const scope: CommitScope = { phase: input.phase, isInit: false, isMilestone: false };
return CommitBuilder.buildCommitMessage({
type: "compound",
scope,
subject: `${input.category}: ${input.problem.slice(0, 60)}`,
ci,
body: `Discovered during ${input.category} work. Problem: ${input.problem}. Solution: ${input.solution}.`,
});
}
static buildVerifyCommit(input: VerifyCommitInput): string {
const ci: CiMetadata = {
phase: input.phase,
milestone: input.milestone,
status: "verify",
requirements: input.requirements,
lessons: input.lessons,
};
const scope: CommitScope = { phase: input.phase, isInit: false, isMilestone: false };
return CommitBuilder.buildCommitMessage({
type: "verify",
scope,
subject: input.subject,
ci,
});
}
static buildResearchCommit(
phase: number,
milestone: string,
subject: string,
findings: string[],
decisions?: CommitDecision[]
): string {
const ci: CiMetadata = {
phase,
milestone,
status: "research",
decisions,
};
const scope: CommitScope = { phase, isInit: false, isMilestone: false };
return CommitBuilder.buildCommitMessage({
type: "docs",
scope,
subject,
ci,
body: findings.join("\n"),
});
}
}