352 lines
9.0 KiB
TypeScript
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"),
|
|
});
|
|
}
|
|
} |