62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import * as fs from "node:fs";
|
|
import * as path from "node:path";
|
|
import type { PlanPhase } from "./plan-receipt.js";
|
|
|
|
export interface ResearchMapReceipt {
|
|
schema: "fable.research_map.receipt.v1";
|
|
createdAt: string;
|
|
taskLabel: string;
|
|
task: string;
|
|
constraints: string[];
|
|
existingPrimitives: string[];
|
|
recommendedPlan: PlanPhase[];
|
|
skipped: string[];
|
|
decision: "ready" | "blocked";
|
|
reasons: string[];
|
|
deployAttempted: false;
|
|
}
|
|
|
|
export interface ResearchMapOptions {
|
|
taskLabel: string;
|
|
task: string;
|
|
constraints?: string[];
|
|
existingPrimitives?: string[];
|
|
recommendedPlan: PlanPhase[];
|
|
skipped?: string[];
|
|
now?: Date;
|
|
}
|
|
|
|
const FORBIDDEN_SCOPE = ["deploy", "dashboard", "vendor", "vendoring", "new model architecture"];
|
|
|
|
export function createResearchMapReceipt(opts: ResearchMapOptions): ResearchMapReceipt {
|
|
const reasons: string[] = [];
|
|
if (!opts.taskLabel.trim()) reasons.push("missing task label");
|
|
if (!opts.task.trim()) reasons.push("missing task");
|
|
if (opts.recommendedPlan.length === 0) reasons.push("missing recommended phases");
|
|
if (opts.recommendedPlan.some((phase) => phase.acceptanceCriteria.length === 0)) reasons.push("each phase needs acceptance criteria");
|
|
if (opts.recommendedPlan.some((phase) => phase.verification.length === 0)) reasons.push("each phase needs verification steps");
|
|
const searchable = [opts.task, opts.taskLabel, ...opts.recommendedPlan.flatMap((phase) => [phase.name, phase.goal])].join("\n").toLowerCase();
|
|
const forbidden = FORBIDDEN_SCOPE.filter((term) => searchable.includes(term));
|
|
if (forbidden.length > 0) reasons.push(`forbidden scope: ${forbidden.join(", ")}`);
|
|
|
|
return {
|
|
schema: "fable.research_map.receipt.v1",
|
|
createdAt: (opts.now ?? new Date()).toISOString(),
|
|
taskLabel: opts.taskLabel,
|
|
task: opts.task,
|
|
constraints: opts.constraints ?? [],
|
|
existingPrimitives: opts.existingPrimitives ?? [],
|
|
recommendedPlan: opts.recommendedPlan,
|
|
skipped: opts.skipped ?? [],
|
|
decision: reasons.length === 0 ? "ready" : "blocked",
|
|
reasons,
|
|
deployAttempted: false,
|
|
};
|
|
}
|
|
|
|
export function writeResearchMapReceipt(file: string, receipt: ResearchMapReceipt): string {
|
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
fs.writeFileSync(file, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
return file;
|
|
}
|