paqad-ai
Version:
Spec-driven development framework — AI agents that think before they type
252 lines (248 loc) • 9.56 kB
JavaScript
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url);
import {
stampFeatureDocument
} from "./chunk-5M55PDWL.js";
import {
dirname,
featureFilePath,
join
} from "./chunk-AZLA53A6.js";
import {
PATHS
} from "./chunk-IHMBVS6H.js";
import {
init_esm_shims
} from "./chunk-IDFRWBMN.js";
// src/hld/copy.ts
init_esm_shims();
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
var MINUTE = 6e4;
var HOUR = 60 * MINUTE;
var DAY = 24 * HOUR;
function plural(count, unit) {
return `${count} ${unit}${count === 1 ? "" : "s"}`;
}
function formatAge(from, now) {
const elapsed = Math.max(0, now.getTime() - Date.parse(from));
if (elapsed < MINUTE) return "less than a minute";
if (elapsed < HOUR) return plural(Math.floor(elapsed / MINUTE), "minute");
if (elapsed < DAY) return plural(Math.floor(elapsed / HOUR), "hour");
return plural(Math.floor(elapsed / DAY), "day");
}
function formatDay(at) {
const date = new Date(at);
return `${date.getUTCDate()} ${MONTHS[date.getUTCMonth()]}`;
}
var FREEZE_NEXT_LINE = "> - \u{1F7E1} next: write the high-level design. Run paqad-ai hld brief.";
var FREEZE_NEXT_V2_LINE = "> - \u{1F7E1} next: update the high-level design. Run paqad-ai hld brief; it goes out as v2 on the same comment.";
function postedWaitingLine(ref) {
return `Posted the high-level design to ${ref}. The build starts once it's signed off there. You can work on something else meanwhile.`;
}
function postedInfoLine(ref) {
return `Posted the high-level design to ${ref} for the team to read. Carrying on with the build.`;
}
function approvedLine(ref, at) {
return `The high-level design for ${ref} was approved on ${formatDay(at)}. Starting the build.`;
}
function changesRequestedLine(ref) {
return `There's a reply on the high-level design for ${ref} asking for changes. Here it is, so we can update the spec:`;
}
function stillWaitingLine(ref, age) {
return `No answer on the high-level design for ${ref} yet (posted ${age} ago).`;
}
function notConnectedLine(target) {
return `I couldn't post the high-level design: ${target} isn't connected. It's in chat for you to share. Connect ${target} to post it automatically next time.`;
}
function gateBlockLine(ref, allowSkip) {
return allowSkip ? `The high-level design for ${ref} is waiting for sign-off. Check for an answer with paqad-ai hld check-approval, or skip with paqad-ai hld skip --reason "<why>".` : `The high-level design for ${ref} is waiting for sign-off. Check for an answer with paqad-ai hld check-approval. The build starts only once an approver answers there.`;
}
var GATE_NO_DESIGN_LINE = "The high-level design for this change hasn't been written yet. Write it and record it first: run paqad-ai hld brief, then paqad-ai hld posted or paqad-ai hld not-posted.";
function sessionWaitingLine(ref, age) {
return `${ref} is waiting for high-level design sign-off (posted ${age} ago).`;
}
function resumeLine(ref) {
return `Resumed the change for ${ref}, which is waiting for high-level design sign-off. Run paqad-ai hld check-approval first.`;
}
function prSignedOffLine(ref, at, link) {
return `High-level design signed off in ${ref} on ${formatDay(at)} (${link}).`;
}
function prSkippedLine(reason) {
return `Built before high-level design sign-off. Reason: ${reason.trim().replace(/\.$/u, "")}.`;
}
function receiptSignedOffLine(link) {
return `\u{1F7E2} High-level design signed off (${link})`;
}
var RECEIPT_POSTED_LINE = "High-level design: \u{1F7E1} posted, not signed off";
var RECEIPT_OFF_LINE = "High-level design: \u26AA off for this team";
var RECEIPT_NOT_WRITTEN_LINE = "High-level design: \u{1F7E1} not written yet";
function receiptSkippedLine(reason) {
return `\u{1F7E1} High-level design skipped before sign-off. Reason: ${reason.trim().replace(/\.$/u, "")}.`;
}
var RECEIPT_NOT_POSTED_LINE = "\u{1F7E1} High-level design not posted, shared in chat";
function detailMissingLine(ref) {
return `The More detail part of the high-level design for ${ref} didn't post. It will be added to the same thread next time; the design still waits for sign-off.`;
}
function moveFailedLine(ref, status) {
return `Couldn't move ${ref} to "${status}". The design is posted and still waits for sign-off.`;
}
function startedOverLine(ref) {
return `Updated the high-level design for ${ref} to v2. Approval starts over, so earlier replies no longer count.`;
}
function alreadyRecordedLine(ref, target) {
return `This version of the high-level design for ${ref} is already recorded as posted to ${target}. Nothing to post again.`;
}
function skippedLine(ref) {
return `Skipped high-level design sign-off for ${ref}. Post the reason back to the team where the design was posted.`;
}
var SKIP_NOT_ALLOWED_LINE = "This team doesn't allow building before sign-off (allow_skip_with_reason is false in design-sign-off.yaml). Wait for an approver to answer.";
var SKIP_NEEDS_REASON_LINE = 'Give a reason to skip sign-off: paqad-ai hld skip --reason "<why>".';
// src/hld/store.ts
init_esm_shims();
import { createHash } from "crypto";
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
var HLD_DOC_TYPE = "paqad.hld";
var HLD_SCHEMA_VERSION = 1;
var HLD_STATES = [
"awaiting_sign_off",
"approved",
"changes_requested",
"skipped",
"not_posted"
];
function sha256(text) {
return createHash("sha256").update(text, "utf8").digest("hex");
}
function digestDesign(text) {
return sha256(text.replace(/\r\n/gu, "\n").trim());
}
function authorDigest(authorId) {
return sha256(authorId.trim());
}
function emptyHldRecord(ref) {
return {
state: "awaiting_sign_off",
ref,
version: 0,
digest: "",
posts: [],
approved_at: null,
approval_link: null,
skip_reason: null,
checked_at: null
};
}
function hldPath(projectRoot, dirName) {
return join(projectRoot, featureFilePath(dirName, "hld"));
}
function hldMarkdownPath(projectRoot, dirName) {
return join(projectRoot, featureFilePath(dirName, "hldMd"));
}
function readHld(projectRoot, dirName) {
try {
const parsed = JSON.parse(readFileSync(hldPath(projectRoot, dirName), "utf8"));
if (!HLD_STATES.includes(parsed.state)) return null;
return {
state: parsed.state,
ref: typeof parsed.ref === "string" ? parsed.ref : "",
version: typeof parsed.version === "number" ? parsed.version : 0,
digest: typeof parsed.digest === "string" ? parsed.digest : "",
posts: Array.isArray(parsed.posts) ? parsed.posts : [],
approved_at: typeof parsed.approved_at === "string" ? parsed.approved_at : null,
approval_link: typeof parsed.approval_link === "string" ? parsed.approval_link : null,
skip_reason: typeof parsed.skip_reason === "string" ? parsed.skip_reason : null,
checked_at: typeof parsed.checked_at === "string" ? parsed.checked_at : null
};
} catch {
return null;
}
}
function writeAtomic(path, text) {
mkdirSync(dirname(path), { recursive: true });
const tmp = `${path}.tmp-${process.pid}`;
writeFileSync(tmp, text, "utf8");
renameSync(tmp, path);
}
function writeHld(projectRoot, dirName, record, options = {}) {
const doc = stampFeatureDocument({
projectRoot,
dirName,
docType: HLD_DOC_TYPE,
schemaVersion: HLD_SCHEMA_VERSION,
sessionId: options.sessionId,
now: options.now,
body: { ...record }
});
const path = hldPath(projectRoot, dirName);
writeAtomic(path, `${JSON.stringify(doc, null, 2)}
`);
updateWaitingIndex(projectRoot, dirName, record, options.waits ?? true);
return path;
}
function writeHldMarkdown(projectRoot, dirName, text) {
const path = hldMarkdownPath(projectRoot, dirName);
writeAtomic(path, text.endsWith("\n") ? text : `${text}
`);
return path;
}
function latestPost(record) {
return record.posts.at(-1) ?? null;
}
function waitingIndexPath(projectRoot) {
return join(projectRoot, PATHS.HLD_WAITING_INDEX);
}
function readWaitingIndex(projectRoot) {
try {
const parsed = JSON.parse(readFileSync(waitingIndexPath(projectRoot), "utf8"));
if (!Array.isArray(parsed)) return [];
return parsed.filter(
(entry) => typeof entry === "object" && entry !== null && typeof entry.dir === "string" && typeof entry.ref === "string" && typeof entry.posted_at === "string"
);
} catch {
return [];
}
}
function updateWaitingIndex(projectRoot, dirName, record, waits) {
const rest = readWaitingIndex(projectRoot).filter((entry) => entry.dir !== dirName);
const post = latestPost(record);
const next = waits && record.state === "awaiting_sign_off" && post ? [...rest, { dir: dirName, ref: record.ref, posted_at: post.at }] : rest;
writeAtomic(waitingIndexPath(projectRoot), `${JSON.stringify(next, null, 2)}
`);
}
export {
formatAge,
FREEZE_NEXT_LINE,
FREEZE_NEXT_V2_LINE,
postedWaitingLine,
postedInfoLine,
approvedLine,
changesRequestedLine,
stillWaitingLine,
notConnectedLine,
gateBlockLine,
GATE_NO_DESIGN_LINE,
sessionWaitingLine,
resumeLine,
prSignedOffLine,
prSkippedLine,
receiptSignedOffLine,
RECEIPT_POSTED_LINE,
RECEIPT_OFF_LINE,
RECEIPT_NOT_WRITTEN_LINE,
receiptSkippedLine,
RECEIPT_NOT_POSTED_LINE,
detailMissingLine,
moveFailedLine,
startedOverLine,
alreadyRecordedLine,
skippedLine,
SKIP_NOT_ALLOWED_LINE,
SKIP_NEEDS_REASON_LINE,
digestDesign,
authorDigest,
emptyHldRecord,
readHld,
writeHld,
writeHldMarkdown,
latestPost,
readWaitingIndex
};