UNPKG

paqad-ai

Version:

Spec-driven development framework — AI agents that think before they type

362 lines (356 loc) • 11.7 kB
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url); import { detectTicketRefs } from "./chunk-NJE7NS7E.js"; import { computeSessionRowHash } from "./chunk-Q353EDKY.js"; import { FEATURE_DOC_SCHEMA_VERSION, FEATURE_DOC_TYPE, PLAN_DOC_TYPE, REVIEW_DOC_TYPE, deriveSlug, formatFeatureDirName, ulid } from "./chunk-AZLA53A6.js"; import { init_esm_shims } from "./chunk-IDFRWBMN.js"; // src/compliance/markdown.ts init_esm_shims(); import { createHash } from "crypto"; function sha256Hex(input) { return createHash("sha256").update(input).digest("hex"); } function splitLines(source) { return source.replace(/\r\n/g, "\n").split("\n"); } function parseHeadings(lines) { const headings = []; for (let index = 0; index < lines.length; index += 1) { const line = lines[index]; const match = /^(#{1,6})\s+(.*\S)\s*$/.exec(line); if (!match) continue; headings.push({ level: match[1].length, text: match[2], line: index + 1 }); } return headings; } function buildHeadingPath(headings, line) { const stack = []; for (const heading of headings) { if (heading.line > line) break; while (stack.length > 0 && stack[stack.length - 1].level >= heading.level) { stack.pop(); } stack.push(heading); } return stack.map((heading) => heading.text).join(" > "); } function classifySection(sectionPath) { const normalized = sectionPath.toLowerCase(); if (/(?:^|> )(?:\d+(?:\.\d+)*\.?\s*)?(?:acceptance criteria\b|definition of done\b|ac\b)/.test( normalized )) { return { path: sectionPath, category: "acceptance" }; } if (/(?:^|> )(?:\d+(?:\.\d+)*\.?\s*)?edge cases?\b|\bec-/.test(normalized)) { return { path: sectionPath, category: "edge-case" }; } if (/(?:^|> )(?:\d+(?:\.\d+)*\.?\s*)?non-functional(?: requirements?)?\b|\bnfr\b/.test(normalized)) { return { path: sectionPath, category: "non-functional" }; } if (/(?:^|> )(?:\d+(?:\.\d+)*\.?\s*)?functional requirements\b|\bfr-/.test(normalized)) { return { path: sectionPath, category: "functional" }; } return { path: sectionPath, category: "unclassified" }; } function makeDeterministicGeneratedId(sectionPath, index) { const digest = sha256Hex(`${sectionPath}::${index}`); return `GEN-${digest.slice(0, 12).toUpperCase()}`; } function normalizeCell(value) { return value.trim().replace(/\s+/g, " "); } // src/feature-evidence/mint.ts init_esm_shims(); import { createHash as createHash2 } from "crypto"; // src/feature-evidence/envelope.ts init_esm_shims(); var ENVELOPE_HEADER_KEYS = [ "schema_version", "doc_type", "change", "session_id", "recorded_at", "content_hash" ]; var HEADER_KEY_SET = new Set(ENVELOPE_HEADER_KEYS); function assertNoHeaderKeys(body, what) { const clash = Object.keys(body).filter((key) => HEADER_KEY_SET.has(key)); if (clash.length > 0) { throw new Error(`${what} body carries envelope header keys: ${clash.join(", ")}`); } } function stampTime(now) { return (now ?? (() => /* @__PURE__ */ new Date()))().toISOString(); } function buildEnvelopeHeader(identity) { return { schema_version: identity.schemaVersion, doc_type: identity.docType, change: identity.change, session_id: identity.sessionId, recorded_at: stampTime(identity.now), content_hash: identity.contentHash }; } function buildDocumentEnvelope(input) { assertNoHeaderKeys(input.body, input.docType); const identity = { schema_version: input.schemaVersion, doc_type: input.docType, change: input.change, session_id: input.sessionId, ...input.body }; const header = buildEnvelopeHeader({ ...input, contentHash: computeContentHash(identity) }); return { ...header, ...input.body }; } function stampBundleRow(input) { assertNoHeaderKeys(input.row, input.docType); const identity = { schema_version: input.schemaVersion, doc_type: input.docType, change: input.change, session_id: input.sessionId, ...input.row }; const header = buildEnvelopeHeader({ ...input, contentHash: computeSessionRowHash(identity) }); const stamped = { ...header, ...input.row }; const errors = input.validate?.(stamped) ?? []; if (errors.length > 0) { throw new Error(`Invalid ${input.docType} row: ${errors.join("; ")}`); } return stamped; } function stringField(value) { return typeof value === "string" && value.length > 0 ? value : null; } function rowRecordedAt(row) { return stringField(row.recorded_at) ?? stringField(row.ts); } function carriesEnvelopeHeader(value) { if (typeof value !== "object" || value === null || Array.isArray(value)) return false; const doc = value; return typeof doc.doc_type === "string" && typeof doc.change === "string" && typeof doc.recorded_at === "string" && typeof doc.content_hash === "string"; } function documentHashMatches(doc) { if (!carriesEnvelopeHeader(doc)) return null; return computeContentHash(doc) === doc.content_hash; } function rowHashMatches(row) { if (!carriesEnvelopeHeader(row)) return null; return computeSessionRowHash(row) === row.content_hash; } function buildTextHeader(input) { return buildEnvelopeHeader({ ...input, contentHash: sha256Hex(input.body) }); } var FRONT_MATTER_FENCE = "---"; function renderFrontMatter(header, body) { const lines = ENVELOPE_HEADER_KEYS.map((key) => `${key}: ${JSON.stringify(header[key])}`); return `${FRONT_MATTER_FENCE} ${lines.join("\n")} ${FRONT_MATTER_FENCE} ${body}`; } function parseFrontMatterValue(raw) { try { const parsed = JSON.parse(raw); if (typeof parsed === "string" || typeof parsed === "number") { return parsed; } } catch { } return raw; } var FRONT_MATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---\r?\n/; function splitFrontMatter(text) { const match = FRONT_MATTER_PATTERN.exec(text); if (!match) { return { header: null, body: text }; } const header = {}; for (const line of match[1].split(/\r?\n/)) { const colon = line.indexOf(":"); if (colon <= 0) continue; header[line.slice(0, colon).trim()] = parseFrontMatterValue(line.slice(colon + 1).trim()); } return { header, body: text.slice(match[0].length) }; } function textHashMatches(text) { const { header, body } = splitFrontMatter(text); if (header?.change === void 0 || typeof header.content_hash !== "string") return null; return sha256Hex(body) === header.content_hash; } var REPORT_HEADER_SCRIPT_ID = "paqad-header"; function renderHeaderScript(header) { const ordered = Object.fromEntries(ENVELOPE_HEADER_KEYS.map((key) => [key, header[key]])); const json = JSON.stringify(ordered).replace(/</g, "\\u003c"); return `<script type="application/json" id="${REPORT_HEADER_SCRIPT_ID}">${json}</script>`; } var HEADER_SCRIPT_PATTERN = new RegExp( `<script type="application/json" id="${REPORT_HEADER_SCRIPT_ID}">([\\s\\S]*?)</script>` ); var AI_BOM_HEADER_PREFIX = "paqad:"; function toAiBomProperties(header) { return ENVELOPE_HEADER_KEYS.map((key) => ({ name: `${AI_BOM_HEADER_PREFIX}${key}`, value: String(header[key]) })); } function withReceiptHeader(header, block) { assertNoHeaderKeys(block, header.doc_type); return { ...header, ...block }; } // src/feature-evidence/mint.ts var UNTITLED_FEATURE_TITLE = "change"; var HASH_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["content_hash", "created_at", "updated_at", "recorded_at"]); function computeContentHash(record) { const identity = {}; for (const key of Object.keys(record).sort()) { if (!HASH_EXCLUDED_KEYS.has(key)) { identity[key] = record[key]; } } return createHash2("sha256").update(JSON.stringify(identity)).digest("hex"); } function mintFeatureDirName(input) { const issue = resolveIssue(input); const slug = deriveSlug(titleWithoutRef(input.title, issue)); const ulid2 = input.ulid ?? ulid(input.ulidSeed); const dirName = formatFeatureDirName({ issue, slug, ulid: ulid2 }); return { dirName, issue, slug, ulid: ulid2 }; } function resolveIssue(input) { if (input.issue !== void 0) { return normalizeIssue(input.issue); } const refs = detectTicketRefs(input.title, input.trackerKind ?? "generic"); return normalizeIssue(refs[0] ?? null); } function escapeRegExp(text) { return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function titleWithoutRef(title, issue) { if (issue === null) { return title; } const ref = escapeRegExp(issue); const tail = "(?![A-Za-z0-9])[:\\-\\s]*"; const pattern = /^\d+$/.test(issue) ? new RegExp(`(?:(?<![\\w/])#${ref}|^\\s*${ref})${tail}`, "gi") : new RegExp(`(?<![A-Za-z0-9])#?${ref}${tail}`, "gi"); const stripped = title.replace(pattern, " ").trim(); return /[a-z0-9]/i.test(stripped) ? stripped : title; } function normalizeIssue(issue) { if (issue === null) { return null; } const stripped = issue.replace(/^#/, "").trim(); return stripped.length > 0 ? stripped : null; } function buildFeatureRecord(input) { const now = input.now ?? (() => /* @__PURE__ */ new Date()); const updatedAt = now().toISOString(); const openedAt = input.recorded_at; return buildDocumentEnvelope({ docType: FEATURE_DOC_TYPE, change: input.change, sessionId: input.session_id, schemaVersion: FEATURE_DOC_SCHEMA_VERSION, now: openedAt === void 0 ? () => new Date(updatedAt) : () => new Date(openedAt), body: { issue: input.issue, title: input.title, slug: input.slug, lane: input.lane ?? null, status: input.status ?? "active", spec_id: input.spec_id ?? null, adapter: input.adapter, branch: input.branch ?? null, base_branch: input.base_branch ?? null, updated_at: updatedAt, ...input.kind !== void 0 ? { kind: input.kind } : {}, ...input.lane_source !== void 0 ? { lane_source: input.lane_source } : {}, ...input.lane_resized !== void 0 ? { lane_resized: input.lane_resized } : {}, ...input.lane_raised !== void 0 ? { lane_raised: input.lane_raised } : {}, ...input.lane_raised_for_authoring !== void 0 ? { lane_raised_for_authoring: input.lane_raised_for_authoring } : {} } }); } function buildReviewRecord(input) { return buildDocumentEnvelope({ docType: REVIEW_DOC_TYPE, change: input.change, sessionId: input.session_id, schemaVersion: FEATURE_DOC_SCHEMA_VERSION, now: input.now, body: { summary: input.summary, verdict: input.verdict, findings: input.findings ?? [], checked: input.checked ?? [], rollback: input.rollback } }); } function buildPlanRecord(input) { return buildDocumentEnvelope({ docType: PLAN_DOC_TYPE, change: input.change, sessionId: input.session_id, schemaVersion: FEATURE_DOC_SCHEMA_VERSION, now: input.now, body: { summary: input.summary, steps: input.steps ?? [], modules_touched: input.modules_touched ?? [], decisions: input.decisions ?? [], risks: input.risks ?? [], ...input.reuse === void 0 ? {} : { reuse: input.reuse } } }); } export { sha256Hex, splitLines, parseHeadings, buildHeadingPath, classifySection, makeDeterministicGeneratedId, normalizeCell, UNTITLED_FEATURE_TITLE, computeContentHash, mintFeatureDirName, buildFeatureRecord, buildReviewRecord, buildPlanRecord, ENVELOPE_HEADER_KEYS, buildEnvelopeHeader, buildDocumentEnvelope, stampBundleRow, rowRecordedAt, documentHashMatches, rowHashMatches, buildTextHeader, renderFrontMatter, splitFrontMatter, textHashMatches, renderHeaderScript, toAiBomProperties, withReceiptHeader };