v0.2.0: Git-native architecture (#1)
This commit was merged in pull request #1.
This commit is contained in:
+69
-9
@@ -1,5 +1,4 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { execSync } from "node:child_process";
|
||||
import {
|
||||
Escalation,
|
||||
EscalationType,
|
||||
@@ -8,7 +7,8 @@ import {
|
||||
ESCALATION_TYPES,
|
||||
} from "../types/escalation.js";
|
||||
import { CIConfig } from "../types/config.js";
|
||||
import { logEscalation } from "./audit.js";
|
||||
import { CommitBuilder, EscalationCommitInput } from "./commit-builder.js";
|
||||
import { CommitEscalation } from "../types/commit-meta.js";
|
||||
|
||||
export interface EscalationInput {
|
||||
type: EscalationType;
|
||||
@@ -24,25 +24,33 @@ export interface EscalationInput {
|
||||
export class EscalationProtocol {
|
||||
private config: CIConfig;
|
||||
private projectPath: string;
|
||||
private currentMilestone: string;
|
||||
private counter: number;
|
||||
private pendingEscalations: Map<string, Escalation>;
|
||||
private timeoutCallback: (escalation: Escalation, chosenOption: string) => void;
|
||||
private timers: NodeJS.Timeout[];
|
||||
|
||||
constructor(
|
||||
config: CIConfig,
|
||||
projectPath: string,
|
||||
milestone: string = "v1.0",
|
||||
timeoutCallback: (escalation: Escalation, chosenOption: string) => void = () => {}
|
||||
) {
|
||||
this.config = config;
|
||||
this.projectPath = projectPath;
|
||||
this.currentMilestone = milestone;
|
||||
this.counter = 0;
|
||||
this.pendingEscalations = new Map();
|
||||
this.timeoutCallback = timeoutCallback;
|
||||
this.timers = [];
|
||||
}
|
||||
|
||||
setMilestone(milestone: string): void {
|
||||
this.currentMilestone = milestone;
|
||||
}
|
||||
|
||||
escalate(input: EscalationInput): Escalation {
|
||||
const id = `E-${String(++this.counter).padStart(3, "0")}`;
|
||||
const date = new Date().toISOString().split("T")[0];
|
||||
|
||||
const escalation: Escalation = {
|
||||
id,
|
||||
@@ -56,11 +64,28 @@ export class EscalationProtocol {
|
||||
options: input.options,
|
||||
default_option_id: input.default_option_id,
|
||||
resolution: "pending",
|
||||
audit_file: `.ci/audit/${date}-phase${input.phase}-decisions.json`,
|
||||
audit_file: `.ci/audit/deprecated`,
|
||||
};
|
||||
|
||||
this.pendingEscalations.set(id, escalation);
|
||||
logEscalation(this.projectPath, parseInt(input.phase) || 0, escalation);
|
||||
|
||||
if (this.config.git.auto_commit) {
|
||||
const commitEscalation: CommitEscalation = {
|
||||
id,
|
||||
type: input.type,
|
||||
description: input.description,
|
||||
resolution: "pending",
|
||||
};
|
||||
|
||||
const commitMessage = CommitBuilder.buildEscalationCommit({
|
||||
phase: parseInt(input.phase) || 0,
|
||||
milestone: this.currentMilestone,
|
||||
subject: input.description,
|
||||
escalations: [commitEscalation],
|
||||
});
|
||||
|
||||
this.commitEscalation(commitMessage);
|
||||
}
|
||||
|
||||
if (this.config.autonomy.escalation_timeout_ms > 0) {
|
||||
this.scheduleTimeout(escalation);
|
||||
@@ -81,6 +106,24 @@ export class EscalationProtocol {
|
||||
escalation.resolved_at = new Date().toISOString();
|
||||
escalation.resolution_detail = `Chose option: ${chosenOptionId}`;
|
||||
|
||||
if (this.config.git.auto_commit) {
|
||||
const commitEscalation: CommitEscalation = {
|
||||
id: escalation.id,
|
||||
type: escalation.type,
|
||||
description: escalation.description,
|
||||
resolution: resolution === "timeout_auto_proceed" ? "timeout" : resolution === "approved" ? "auto" : resolution === "rejected" ? "human" : resolution === "modified" ? "human" : resolution,
|
||||
};
|
||||
|
||||
const commitMessage = CommitBuilder.buildEscalationCommit({
|
||||
phase: parseInt(escalation.phase) || 0,
|
||||
milestone: this.currentMilestone,
|
||||
subject: `resolved: ${escalation.description}`,
|
||||
escalations: [commitEscalation],
|
||||
});
|
||||
|
||||
this.commitEscalation(commitMessage);
|
||||
}
|
||||
|
||||
this.pendingEscalations.delete(escalationId);
|
||||
return escalation;
|
||||
}
|
||||
@@ -93,6 +136,14 @@ export class EscalationProtocol {
|
||||
return this.pendingEscalations.size > 0;
|
||||
}
|
||||
|
||||
clearAllTimers(): void {
|
||||
for (const timer of this.timers) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
this.timers = [];
|
||||
this.pendingEscalations.clear();
|
||||
}
|
||||
|
||||
formatEscalation(escalation: Escalation): string {
|
||||
const lines: string[] = [
|
||||
`⚠️ ESCALATION [${escalation.id}]`,
|
||||
@@ -126,16 +177,24 @@ export class EscalationProtocol {
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(`\nAudit: ${escalation.audit_file}`);
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
private commitEscalation(commitMessage: string): void {
|
||||
try {
|
||||
execSync(`git add -A && git commit -m "${commitMessage.replace(/"/g, '\\"')}" --allow-empty`, {
|
||||
cwd: this.projectPath,
|
||||
stdio: "pipe",
|
||||
});
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleTimeout(escalation: Escalation): void {
|
||||
const timeout = this.config.autonomy.escalation_timeout_ms;
|
||||
if (timeout <= 0) return;
|
||||
|
||||
setTimeout(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (this.pendingEscalations.has(escalation.id)) {
|
||||
escalation.resolution = "timeout_auto_proceed";
|
||||
escalation.resolved_at = new Date().toISOString();
|
||||
@@ -144,5 +203,6 @@ export class EscalationProtocol {
|
||||
this.timeoutCallback(escalation, escalation.default_option_id);
|
||||
}
|
||||
}, timeout);
|
||||
this.timers.push(timer);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user