ai-foreman 1.7.22 → 1.7.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ticketPlanning.d.ts +6 -2
- package/dist/ticketPlanning.js +28 -7
- package/package.json +1 -1
package/dist/ticketPlanning.d.ts
CHANGED
|
@@ -64,11 +64,15 @@ export interface TicketPlanningContext {
|
|
|
64
64
|
}>;
|
|
65
65
|
existingNext: string[];
|
|
66
66
|
}
|
|
67
|
+
/** The tracker facts needed to validate a proposal before it is approved. */
|
|
68
|
+
export interface TicketPlanValidationContext {
|
|
69
|
+
futureWorkIds?: Iterable<number>;
|
|
70
|
+
}
|
|
67
71
|
export declare function readTicketPlanningContext(projectDir: string): TicketPlanningContext;
|
|
68
72
|
export declare function ticketPlanningFingerprint(projectDir: string): PlanningFingerprint[];
|
|
69
73
|
export declare function planningFingerprintChanges(projectDir: string, expected: PlanningFingerprint[]): string[];
|
|
70
|
-
export declare function extractTicketPlanProposal(output: string, existingTickets?: TicketDef[]): TicketPlanProposal;
|
|
71
|
-
export declare function validateTicketPlanProposal(value: unknown, existingTickets?: TicketDef[]): string[];
|
|
74
|
+
export declare function extractTicketPlanProposal(output: string, existingTickets?: TicketDef[], context?: TicketPlanValidationContext): TicketPlanProposal;
|
|
75
|
+
export declare function validateTicketPlanProposal(value: unknown, existingTickets?: TicketDef[], context?: TicketPlanValidationContext): string[];
|
|
72
76
|
export interface ApplyTicketPlanOptions {
|
|
73
77
|
expectedFingerprint: PlanningFingerprint[];
|
|
74
78
|
docsRoot: string;
|
package/dist/ticketPlanning.js
CHANGED
|
@@ -12,6 +12,9 @@ import { normalizeDeliveryConfig, validateDeliveryConfig } from "./tickets/deliv
|
|
|
12
12
|
import { loadTicketSetupConfig, mergeTicketSetup, saveTicketSetupConfig } from "./tickets/setupConfig.js";
|
|
13
13
|
export const PROPOSAL_START = "RAFI_PROPOSAL_START";
|
|
14
14
|
export const PROPOSAL_END = "RAFI_PROPOSAL_END";
|
|
15
|
+
const TICKET_STATUSES = new Set([
|
|
16
|
+
"planned", "next", "in_progress", "blocked", "done", "canceled", "obsolete",
|
|
17
|
+
]);
|
|
15
18
|
export function readTicketPlanningContext(projectDir) {
|
|
16
19
|
const config = loadTicketsConfig(projectDir);
|
|
17
20
|
const paths = resolveTicketPaths(config, projectDir);
|
|
@@ -41,7 +44,7 @@ export function ticketPlanningFingerprint(projectDir) {
|
|
|
41
44
|
export function planningFingerprintChanges(projectDir, expected) {
|
|
42
45
|
return expected.filter((item) => fileHash(join(projectDir, item.path)) !== item.sha256).map((item) => item.path);
|
|
43
46
|
}
|
|
44
|
-
export function extractTicketPlanProposal(output, existingTickets = []) {
|
|
47
|
+
export function extractTicketPlanProposal(output, existingTickets = [], context = {}) {
|
|
45
48
|
const start = output.lastIndexOf(PROPOSAL_START);
|
|
46
49
|
const end = output.lastIndexOf(PROPOSAL_END);
|
|
47
50
|
if (start < 0 || end <= start)
|
|
@@ -54,12 +57,12 @@ export function extractTicketPlanProposal(output, existingTickets = []) {
|
|
|
54
57
|
catch (err) {
|
|
55
58
|
throw new Error(`planner proposal is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
59
|
}
|
|
57
|
-
const issues = validateTicketPlanProposal(parsed, existingTickets);
|
|
60
|
+
const issues = validateTicketPlanProposal(parsed, existingTickets, context);
|
|
58
61
|
if (issues.length)
|
|
59
62
|
throw new Error(`invalid planner proposal:\n${issues.map((issue) => `- ${issue}`).join("\n")}`);
|
|
60
63
|
return parsed;
|
|
61
64
|
}
|
|
62
|
-
export function validateTicketPlanProposal(value, existingTickets = []) {
|
|
65
|
+
export function validateTicketPlanProposal(value, existingTickets = [], context = {}) {
|
|
63
66
|
const issues = [];
|
|
64
67
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
65
68
|
return ["proposal must be an object"];
|
|
@@ -109,9 +112,26 @@ export function validateTicketPlanProposal(value, existingTickets = []) {
|
|
|
109
112
|
if (!ids.has(id))
|
|
110
113
|
issues.push(`supersession references unknown ticket ${id}`);
|
|
111
114
|
}
|
|
112
|
-
for (const change of p.state_changes)
|
|
113
|
-
if (!ids.has(change.id) || !change.reason?.trim())
|
|
114
|
-
issues.push(`invalid state change for ${change.id}`);
|
|
115
|
+
for (const change of p.state_changes) {
|
|
116
|
+
if (!change || typeof change !== "object" || !ids.has(change.id) || !change.reason?.trim()) {
|
|
117
|
+
issues.push(`invalid state change for ${String(change && typeof change === "object" ? change.id : undefined)}`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!TICKET_STATUSES.has(change.status))
|
|
121
|
+
issues.push(`state change for ${change.id} has invalid status ${String(change.status)}`);
|
|
122
|
+
}
|
|
123
|
+
const futureWorkIds = context.futureWorkIds ? new Set(context.futureWorkIds) : undefined;
|
|
124
|
+
for (const disposition of p.future_work) {
|
|
125
|
+
if (!disposition || typeof disposition !== "object" || !Number.isInteger(disposition.id)) {
|
|
126
|
+
issues.push(`future work disposition has invalid id ${String(disposition && typeof disposition === "object" ? disposition.id : undefined)}`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (!["accepted", "rejected", "merged", "queued"].includes(disposition.disposition)) {
|
|
130
|
+
issues.push(`future work item ${disposition.id} has invalid disposition ${String(disposition.disposition)}`);
|
|
131
|
+
}
|
|
132
|
+
if (futureWorkIds && !futureWorkIds.has(disposition.id))
|
|
133
|
+
issues.push(`future work item ${disposition.id} does not exist`);
|
|
134
|
+
}
|
|
115
135
|
for (const item of p.source_reconciliation) {
|
|
116
136
|
if (!item.source?.trim() || !item.item?.trim())
|
|
117
137
|
issues.push("source reconciliation needs source and item");
|
|
@@ -151,7 +171,8 @@ export function applyApprovedTicketPlan(projectDir, proposal, opts) {
|
|
|
151
171
|
const existing = loadTickets(paths.tickets);
|
|
152
172
|
if (existing.length > 0 && !existsSync(paths.stateDb))
|
|
153
173
|
throw new Error("tickets exist but the ignored ticket-state database is missing; restore it before planning so completed work is not treated as new");
|
|
154
|
-
const
|
|
174
|
+
const currentContext = readTicketPlanningContext(root);
|
|
175
|
+
const proposalIssues = validateTicketPlanProposal(proposal, existing, { futureWorkIds: currentContext.futureWork.flatMap((item) => typeof item.id === "number" ? [item.id] : []) });
|
|
155
176
|
if (proposalIssues.length)
|
|
156
177
|
throw new Error(`approved proposal is invalid:\n${proposalIssues.map((issue) => `- ${issue}`).join("\n")}`);
|
|
157
178
|
const lockPath = join(root, ".tickets", "planning.lock");
|
package/package.json
CHANGED