paqad-ai
Version:
Spec-driven development framework — AI agents that think before they type
1,042 lines (1,024 loc) • 33 kB
JavaScript
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url);
import {
writeFileAtomic
} from "./chunk-3UV6TK6X.js";
import {
DEFAULT_SOURCE_GLOBS,
scanWorkingTree
} from "./chunk-LBNBI3D6.js";
import {
appendRuleRun
} from "./chunk-5ETIY5I3.js";
import {
loadChangeEvidence
} from "./chunk-EQYAXXZO.js";
import {
isPaqadArtifactPath
} from "./chunk-5M55PDWL.js";
import {
require_ajv,
require_dist,
resolveRuleGateConfig
} from "./chunk-ACJQW7SB.js";
import {
resolveSessionId
} from "./chunk-SG6LXLDH.js";
import {
isAbsolute,
normalize,
relative
} from "./chunk-AZLA53A6.js";
import {
PATHS
} from "./chunk-IHMBVS6H.js";
import {
__toESM,
init_esm_shims
} from "./chunk-IDFRWBMN.js";
// src/rule-scripts/validate.ts
init_esm_shims();
var import_ajv = __toESM(require_ajv(), 1);
// src/rule-scripts/schemas/findings.schema.json
var findings_schema_default = {
$id: "rule-script-findings",
type: "object",
additionalProperties: false,
required: ["rule_id", "kind", "findings"],
properties: {
rule_id: { type: "string", pattern: "^RL-[0-9a-f]{4,}$" },
kind: { type: "string", enum: ["deterministic", "heuristic"] },
findings: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["file", "message", "severity"],
properties: {
file: { type: "string", minLength: 1 },
line: { type: "integer", minimum: 0 },
message: { type: "string", minLength: 1 },
severity: {
type: "string",
enum: ["critical", "blocker", "high", "medium", "low", "nit", "info"]
}
}
}
}
}
};
// src/rule-scripts/schemas/script-header.schema.json
var script_header_schema_default = {
$id: "rule-script-header",
type: "object",
additionalProperties: false,
required: ["rule_id", "source", "kind", "scope", "runtime"],
properties: {
rule_id: { type: "string", pattern: "^RL-[0-9a-f]{4,}$" },
source: { type: "string", minLength: 1 },
kind: { type: "string", enum: ["deterministic", "heuristic"] },
scope: {
type: "string",
enum: ["changed-files", "whole-tree", "git-diff", "git-history"]
},
runtime: { type: "string", const: "node" },
requires: {
type: "object",
additionalProperties: false,
properties: {
node: { type: "string" },
binaries: { type: "array", items: { type: "string" } }
}
},
per_file: { type: "boolean" },
last_validated_at: { type: "string" },
false_positive_surface: { type: "string" }
}
};
// src/rule-scripts/validate.ts
var ajv = new import_ajv.default({ allErrors: true });
var headerValidator = null;
var findingsValidator = null;
function run(validator, data) {
const valid = validator(data);
if (valid) {
return { valid: true, errors: [] };
}
const errors = (validator.errors ?? []).map(
(e) => `${e.instancePath || "(root)"} ${e.message ?? "invalid"}`.trim()
);
return { valid: false, errors };
}
function validateScriptHeader(header) {
headerValidator ??= ajv.compile(script_header_schema_default);
return run(headerValidator, header);
}
function validateFindings(report) {
findingsValidator ??= ajv.compile(findings_schema_default);
return run(findingsValidator, report);
}
// src/rule-scripts/execute.ts
init_esm_shims();
import { spawn, spawnSync } from "child_process";
import { accessSync, constants, statSync } from "fs";
import { delimiter, join } from "path";
var DEFAULT_TIMEOUT_MS = 3e4;
var MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
var SCRIPT_ABORTED_ERROR = "script stopped: the rule gate ran out of time";
function executeRuleScript(scriptPath, payload, timeoutMs = DEFAULT_TIMEOUT_MS) {
const proc = spawnSync("node", [scriptPath], {
input: JSON.stringify(payload),
encoding: "utf8",
timeout: timeoutMs,
maxBuffer: MAX_OUTPUT_BYTES
});
const error = proc.error;
return interpretOutcome(
{
...error ? { error: { code: error.code, message: error.message } } : {},
signal: proc.signal,
status: proc.status,
stdout: proc.stdout,
stderr: proc.stderr
},
timeoutMs
);
}
function executeRuleScriptAsync(scriptPath, payload, options = {}) {
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
if (options.signal?.aborted) {
return Promise.resolve({ ok: false, error: SCRIPT_ABORTED_ERROR });
}
return new Promise((resolve) => {
const child = spawn("node", [scriptPath], { stdio: ["pipe", "pipe", "pipe"] });
const stdout = [];
const stderr = [];
const sizes = { stdout: 0, stderr: 0 };
let forced;
let aborted = false;
let settled = false;
const stop = (error) => {
if (forced || aborted) return;
forced = error;
child.kill("SIGTERM");
};
const timer = setTimeout(
() => stop({ code: "ETIMEDOUT", message: "spawn node ETIMEDOUT" }),
timeoutMs
);
const onAbort = () => {
aborted = true;
child.kill("SIGKILL");
};
options.signal?.addEventListener("abort", onAbort, { once: true });
const collect = (stream, sink) => (chunk) => {
sizes[stream] += chunk.length;
sink.push(chunk);
if (sizes[stream] > MAX_OUTPUT_BYTES) {
stop({ code: "ENOBUFS", message: "spawn node ENOBUFS" });
}
};
child.stdout.on("data", collect("stdout", stdout));
child.stderr.on("data", collect("stderr", stderr));
const finish = (outcome) => {
if (settled) return;
settled = true;
clearTimeout(timer);
options.signal?.removeEventListener("abort", onAbort);
resolve(
aborted ? { ok: false, error: SCRIPT_ABORTED_ERROR } : interpretOutcome(outcome, timeoutMs)
);
};
child.on("error", (error) => {
finish({
error: { code: error.code, message: error.message },
signal: null,
status: null,
stdout: "",
stderr: ""
});
});
child.on("close", (status, signal) => {
finish({
...forced ? { error: forced } : {},
signal: forced ? "SIGTERM" : signal,
status: forced ? null : status,
stdout: Buffer.concat(stdout).toString("utf8"),
stderr: Buffer.concat(stderr).toString("utf8")
});
});
child.stdin.on("error", (error) => {
if (error.code !== "EPIPE") stop({ code: error.code, message: error.message });
});
child.stdin.end(JSON.stringify(payload));
});
}
function interpretOutcome(proc, timeoutMs) {
if (proc.error) {
if (proc.signal === "SIGTERM" || proc.error.code === "ETIMEDOUT") {
return { ok: false, error: `script timed out after ${timeoutMs}ms` };
}
return { ok: false, error: `script failed to start: ${proc.error.message}` };
}
if (proc.signal) {
return { ok: false, error: `script killed by signal ${proc.signal}` };
}
let parsed;
let parseFailed = false;
try {
parsed = JSON.parse(proc.stdout);
} catch {
parseFailed = true;
}
if (!parseFailed) {
const validation = validateFindings(parsed);
if (validation.valid) {
const stderr = (proc.stderr || "").trim();
const warning = proc.status !== 0 ? `script exited ${proc.status}${stderr ? `: ${stderr}` : ""}` : stderr || void 0;
return warning ? { ok: true, report: parsed, warning } : { ok: true, report: parsed };
}
if (proc.status === 0) {
return { ok: false, error: `findings failed schema: ${validation.errors.join("; ")}` };
}
}
if (proc.status !== 0) {
const stderr = (proc.stderr || "").trim();
return {
ok: false,
error: `script exited ${proc.status}${stderr ? `: ${stderr}` : " with no parseable findings"}`
};
}
return { ok: false, error: `invalid findings JSON on stdout: ${proc.stdout.slice(0, 200)}` };
}
function isExecutableFile(p) {
try {
if (!statSync(p).isFile()) {
return false;
}
} catch {
return false;
}
if (process.platform === "win32") {
return true;
}
try {
accessSync(p, constants.X_OK);
return true;
} catch {
return false;
}
}
function isOnPath(bin) {
if (bin.includes("/") || bin.includes("\\")) {
return isExecutableFile(bin);
}
const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
const winExts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [];
const alreadyHasExt = process.platform === "win32" && winExts.some((ext) => bin.toLowerCase().endsWith(ext.toLowerCase()));
const exts = process.platform === "win32" ? alreadyHasExt ? [""] : winExts : [""];
return dirs.some((dir) => exts.some((ext) => isExecutableFile(join(dir, `${bin}${ext}`))));
}
function missingBinaries(binaries) {
if (!binaries || binaries.length === 0) {
return [];
}
return binaries.filter((bin) => !isOnPath(bin));
}
// src/rule-scripts/types.ts
init_esm_shims();
var RULE_SCRIPT_MAP_SCHEMA_VERSION = 1;
// src/rule-scripts/map.ts
init_esm_shims();
var import_yaml = __toESM(require_dist(), 1);
import { existsSync, readFileSync } from "fs";
import { join as join2 } from "path";
function ruleScriptMapPath(projectRoot) {
return join2(projectRoot, PATHS.RULE_SCRIPT_MAP);
}
function emptyRuleScriptMap(now = /* @__PURE__ */ new Date()) {
return {
schema_version: RULE_SCRIPT_MAP_SCHEMA_VERSION,
generated_at: now.toISOString(),
rule_files_hash: "",
rules: []
};
}
function loadRuleScriptMap(projectRoot) {
const path = ruleScriptMapPath(projectRoot);
if (!existsSync(path)) {
return null;
}
const parsed = import_yaml.default.parse(readFileSync(path, "utf8"));
return parsed ?? null;
}
function serializeRuleScriptMap(map) {
return import_yaml.default.stringify(map, { indent: 2, lineWidth: 0 });
}
// src/rule-scripts/header.ts
init_esm_shims();
var HEADER_MARKER = "@paqad-rule-script";
var BOOLEAN_WORDS = /* @__PURE__ */ new Map([
["true", true],
["false", false]
]);
var HEADER_LINE_RE = /^\s*\/\/\s*([a-z_]+)\s*:(.*)$/;
function extractHeaderFields(content) {
const lines = content.split("\n");
let sawMarker = false;
const fields = {};
for (const line of lines) {
const trimmed = line.trim();
if (trimmed === "") {
continue;
}
if (!trimmed.startsWith("//")) {
break;
}
if (trimmed.includes(HEADER_MARKER)) {
sawMarker = true;
continue;
}
const m = HEADER_LINE_RE.exec(line);
if (m) {
fields[m[1]] = m[2].trim();
}
}
return sawMarker ? fields : null;
}
function coerce(fields) {
const out = {};
for (const [key, raw] of Object.entries(fields)) {
if (key === "requires") {
try {
out.requires = JSON.parse(raw);
} catch {
out.requires = raw;
}
continue;
}
const value = raw.replace(/^"(.*)"$/, "$1");
if (key === "rule_id") {
out[key] = value.replace(/^RL-([0-9a-fA-F]+)$/, (_, h) => `RL-${h.toLowerCase()}`);
} else if (key === "kind" || key === "scope" || key === "runtime") {
out[key] = value.toLowerCase();
} else if (key === "per_file") {
out[key] = BOOLEAN_WORDS.get(value.toLowerCase()) ?? value;
} else {
out[key] = value;
}
}
return out;
}
function parseScriptHeader(content) {
const fields = extractHeaderFields(content);
if (fields === null) {
return { ok: false, errors: [`missing ${HEADER_MARKER} header`] };
}
const coerced = coerce(fields);
const result = validateScriptHeader(coerced);
if (!result.valid) {
return { ok: false, errors: result.errors };
}
return { ok: true, header: coerced, errors: [] };
}
// src/rule-scripts/runner.ts
init_esm_shims();
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
import { join as join4 } from "path";
// src/core/run-pool.ts
init_esm_shims();
async function runPool(items, limit, runOne) {
const results = new Array(items.length);
let next = 0;
const worker = async () => {
while (next < items.length) {
const index = next++;
results[index] = await runOne(items[index], index);
}
};
const workerCount = Math.max(1, Math.min(limit, items.length));
await Promise.all(Array.from({ length: workerCount }, () => worker()));
return results;
}
// src/rule-scripts/file-cache.ts
init_esm_shims();
import { createHash } from "crypto";
import { readFileSync as readFileSync2 } from "fs";
import { join as join3 } from "path";
var FILE_RESULTS_VERSION = 1;
function sha256Text(text) {
return createHash("sha256").update(text).digest("hex");
}
function entryKey(script, file) {
return `${script}
${file}`;
}
function isFinding(value) {
const finding = value;
return typeof finding === "object" && finding !== null && typeof finding.file === "string" && typeof finding.message === "string" && typeof finding.severity === "string";
}
function isEntry(value) {
const entry = value;
return typeof entry === "object" && entry !== null && typeof entry.script === "string" && typeof entry.script_sha256 === "string" && typeof entry.file === "string" && typeof entry.file_sha256 === "string" && Array.isArray(entry.findings) && entry.findings.every(isFinding);
}
function fileResultsPath(projectRoot) {
return join3(projectRoot, PATHS.RULE_SCRIPTS_FILE_RESULTS);
}
function readFileResults(projectRoot) {
const entries = /* @__PURE__ */ new Map();
let parsed;
try {
parsed = JSON.parse(readFileSync2(fileResultsPath(projectRoot), "utf8"));
} catch {
return entries;
}
const doc = parsed;
if (doc?.version !== FILE_RESULTS_VERSION || !Array.isArray(doc.entries)) {
return entries;
}
for (const entry of doc.entries) {
if (isEntry(entry)) entries.set(entryKey(entry.script, entry.file), entry);
}
return entries;
}
function openFileResultsCache(projectRoot) {
const entries = readFileResults(projectRoot);
const fileShas = /* @__PURE__ */ new Map();
const fileSha = (file) => {
if (!fileShas.has(file)) {
let sha;
try {
sha = sha256Text(readFileSync2(join3(projectRoot, file), "utf8"));
} catch {
sha = null;
}
fileShas.set(file, sha);
}
return fileShas.get(file) ?? null;
};
const keep = (entry) => fileSha(entry.script) === entry.script_sha256 && fileSha(entry.file) === entry.file_sha256;
return {
lookup(script, scriptSha, file) {
const entry = entries.get(entryKey(script, file));
const sha = fileSha(file);
if (!entry || sha === null) return void 0;
return entry.script_sha256 === scriptSha && entry.file_sha256 === sha ? entry.findings : void 0;
},
store(script, scriptSha, file, findings) {
const sha = fileSha(file);
if (sha === null) return;
entries.set(entryKey(script, file), {
script,
script_sha256: scriptSha,
file,
file_sha256: sha,
findings
});
},
save() {
const kept = [...entries.values()].filter(keep).sort((a, b) => entryKey(a.script, a.file).localeCompare(entryKey(b.script, b.file)));
writeFileAtomic(
fileResultsPath(projectRoot),
`${JSON.stringify({ version: FILE_RESULTS_VERSION, entries: kept })}
`
);
}
};
}
// src/rule-scripts/runner.ts
function hashFiles(projectRoot, files) {
const parts = files.slice().sort().map((rel) => {
const abs = join4(projectRoot, rel);
return existsSync2(abs) ? `${rel}
${readFileSync3(abs, "utf8")}` : `${rel}
<absent>`;
});
return `sha256-${sha256Text(parts.join("\n"))}`;
}
function scriptFilesHash(projectRoot, map) {
const paths = map.rules.flatMap((r) => r.scripts.map((s) => s.path)).sort();
return hashFiles(projectRoot, paths);
}
function runFilter(opts) {
if (opts.kinds === void 0 && opts.scopes === void 0) {
return void 0;
}
return {
...opts.kinds ? { kinds: [...opts.kinds] } : {},
...opts.scopes ? { scopes: [...opts.scopes] } : {}
};
}
function selectScripts(map, filter) {
return map.rules.flatMap(
(rule) => rule.scripts.filter(
(script) => (filter?.kinds === void 0 || filter.kinds.includes(script.kind)) && (filter?.scopes === void 0 || filter.scopes.includes(script.scope))
).map((script) => ({ rule, script }))
);
}
function selectedScriptsHash(projectRoot, map, selected, filter) {
if (filter === void 0) {
return scriptFilesHash(projectRoot, map);
}
return hashFiles(
projectRoot,
selected.map((entry) => entry.script.path)
);
}
function emptyReport(opts, now) {
return {
generated_at: now,
mode: opts.mode,
rule_files_hash: "",
script_files_hash: "",
target_files_hash: "",
results: [],
counts: { deterministic: 0, heuristic: 0, skipped: 0 },
blocking: false
};
}
function skippedResult(entry, skipped) {
return {
rule_id: entry.rule.id,
script: entry.script.path,
kind: entry.script.kind,
findings: [],
skipped
};
}
function prepareRun(opts) {
const map = loadRuleScriptMap(opts.projectRoot);
const now = (/* @__PURE__ */ new Date()).toISOString();
if (!map) {
return { done: emptyReport(opts, now) };
}
const filter = runFilter(opts);
const selected = selectScripts(map, filter);
const needsTree = opts.changedFiles === void 0 || selected.some((entry) => entry.script.scope !== "changed-files");
const wholeTreeFiles = needsTree ? scanWorkingTree(opts.projectRoot, opts.wholeTreeGlobs ?? DEFAULT_SOURCE_GLOBS) : [];
const targetUniverse = opts.changedFiles === void 0 ? wholeTreeFiles : needsTree ? Array.from(/* @__PURE__ */ new Set([...opts.changedFiles, ...wholeTreeFiles])) : opts.changedFiles;
const scriptHash = selectedScriptsHash(opts.projectRoot, map, selected, filter);
const targetHash = hashFiles(opts.projectRoot, targetUniverse);
const reportFile = filter === void 0 ? PATHS.RULE_SCRIPTS_REPORT : PATHS.RULE_SCRIPTS_PRE_MUTATION_REPORT;
const cached = readReportFile(opts.projectRoot, reportFile);
if (cached && cached.mode === opts.mode && cached.rule_files_hash === map.rule_files_hash && cached.script_files_hash === scriptHash && cached.target_files_hash === targetHash && JSON.stringify(cached.filter) === JSON.stringify(filter)) {
recordRuleRun(opts.projectRoot, cached, true);
return { done: { ...cached, from_cache: true } };
}
const slots = selected.map((entry, index) => {
const scriptAbs = join4(opts.projectRoot, entry.script.path);
if (!existsSync2(scriptAbs)) {
return skippedResult(entry, "script-file-missing");
}
const source = readFileSync3(scriptAbs, "utf8");
const header = parseScriptHeader(source);
const missing = missingBinaries(header.header?.requires?.binaries);
if (missing.length > 0) {
return skippedResult(entry, `missing dependency: ${missing.join(", ")}`);
}
const targets = entry.script.scope === "changed-files" && opts.changedFiles ? opts.changedFiles : wholeTreeFiles;
return {
...entry,
index,
scriptAbs,
scriptSha: sha256Text(source),
perFile: header.header?.per_file === true,
targets
};
});
return {
run: {
now,
map,
filter,
scriptHash,
targetHash,
reportFile,
slots
}
};
}
function isJob(slot) {
return "scriptAbs" in slot;
}
function resultFor(job, run2) {
if (!run2.ok) {
return skippedResult(job, `error: ${run2.error}`);
}
return {
rule_id: job.rule.id,
script: job.script.path,
kind: job.script.kind,
findings: run2.report?.findings ?? [],
...run2.warning ? { warning: run2.warning } : {}
};
}
function finishRun(opts, prepared, results) {
const deterministic = results.filter((r) => r.kind === "deterministic" && !r.skipped).reduce((n, r) => n + r.findings.length, 0);
const heuristic = results.filter((r) => r.kind === "heuristic" && !r.skipped).reduce((n, r) => n + r.findings.length, 0);
const skipped = results.filter((r) => r.skipped).length;
const report = {
generated_at: prepared.now,
mode: opts.mode,
rule_files_hash: prepared.map.rule_files_hash,
script_files_hash: prepared.scriptHash,
target_files_hash: prepared.targetHash,
...prepared.filter ? { filter: prepared.filter } : {},
results,
counts: { deterministic, heuristic, skipped },
blocking: opts.mode === "strict" && deterministic > 0
};
writeReport(opts.projectRoot, prepared.reportFile, report);
recordRuleRun(opts.projectRoot, report, false);
return report;
}
function runRuleScripts(opts) {
const prepared = prepareRun(opts);
if ("done" in prepared) {
return prepared.done;
}
const results = prepared.run.slots.map(
(slot) => isJob(slot) ? resultFor(
slot,
executeRuleScript(slot.scriptAbs, {
projectRoot: opts.projectRoot,
files: slot.targets
})
) : slot
);
return finishRun(opts, prepared.run, results);
}
function mergeFileFindings(job, cached, fresh) {
const targets = new Set(job.targets);
return [
...job.targets.flatMap(
(file) => cached.get(file) ?? fresh.filter((finding) => finding.file === file)
),
...fresh.filter((finding) => !targets.has(finding.file))
];
}
async function runPerFileJob(job, cache, opts) {
const cached = /* @__PURE__ */ new Map();
const dirty = [];
for (const file of job.targets) {
const hit = cache.lookup(job.script.path, job.scriptSha, file);
if (hit) cached.set(file, hit);
else dirty.push(file);
}
if (dirty.length === 0) {
return resultFor(job, {
ok: true,
report: {
rule_id: job.rule.id,
kind: job.script.kind,
findings: mergeFileFindings(job, cached, [])
}
});
}
const run2 = await executeRuleScriptAsync(
job.scriptAbs,
{ projectRoot: opts.projectRoot, files: dirty },
{ signal: opts.signal, timeoutMs: opts.scriptTimeoutMs }
);
if (!run2.ok || !run2.report) {
return resultFor(job, run2);
}
const fresh = run2.report.findings;
const dirtySet = new Set(dirty);
if (fresh.every((finding) => dirtySet.has(finding.file))) {
for (const file of dirty) {
cache.store(
job.script.path,
job.scriptSha,
file,
fresh.filter((finding) => finding.file === file)
);
}
}
return resultFor(job, {
...run2,
report: { ...run2.report, findings: mergeFileFindings(job, cached, fresh) }
});
}
async function runRuleScriptsAsync(opts) {
const prepared = prepareRun(opts);
if ("done" in prepared) {
return prepared.done;
}
const cache = opts.useFileCache ? openFileResultsCache(opts.projectRoot) : null;
const results = await runPool(prepared.run.slots, opts.concurrency ?? 1, async (slot) => {
if (!isJob(slot)) return slot;
if (cache && slot.perFile) return runPerFileJob(slot, cache, opts);
return resultFor(
slot,
await executeRuleScriptAsync(
slot.scriptAbs,
{ projectRoot: opts.projectRoot, files: slot.targets },
{ signal: opts.signal, timeoutMs: opts.scriptTimeoutMs }
)
);
});
cache?.save();
if (opts.signal?.aborted) {
return { ...emptyReport(opts, prepared.run.now), results, aborted: true };
}
return finishRun(opts, prepared.run, results);
}
function ruleRunFindings(report) {
if (!Array.isArray(report.results)) return void 0;
return report.results.filter((result) => !result.skipped).flatMap(
(result) => result.findings.map((finding) => ({
rule_id: result.rule_id,
file: finding.file,
line: finding.line ?? null,
message: finding.message,
severity: finding.severity
}))
);
}
function recordRuleRun(projectRoot, report, fromCache) {
appendRuleRun(
projectRoot,
resolveSessionId(projectRoot, process.env.SE_SESSION ?? process.env.CLAUDE_SESSION_ID ?? null),
{
kind: "findings",
counts: report.counts,
blocking: report.blocking,
findings: ruleRunFindings(report),
from_cache: fromCache
}
);
}
function readReport(projectRoot) {
return readReportFile(projectRoot, PATHS.RULE_SCRIPTS_REPORT);
}
function readReportFile(projectRoot, reportFile) {
const path = join4(projectRoot, reportFile);
if (!existsSync2(path)) {
return null;
}
try {
return JSON.parse(readFileSync3(path, "utf8"));
} catch {
return null;
}
}
function writeReport(projectRoot, reportFile, report) {
writeFileAtomic(join4(projectRoot, reportFile), `${JSON.stringify(report, null, 2)}
`);
}
// src/rule-scripts/enforce.ts
init_esm_shims();
function projectRelativeTargets(projectRoot, targets) {
return (targets ?? []).map(
(target) => (isAbsolute(target) ? relative(projectRoot, target) : normalize(target)).replace(/^\.\//, "")
).filter(
(rel) => rel !== "" && rel !== "." && rel !== ".." && !rel.startsWith("../") && !isAbsolute(rel) && !isPaqadArtifactPath(rel)
);
}
async function enforceRuleScripts(options) {
const { projectRoot, mode } = options;
const map = loadRuleScriptMap(projectRoot);
const armed = map ? map.rules.reduce((total, rule) => total + (rule.scripts?.length ?? 0), 0) : 0;
if (mode === "off" || armed === 0) {
return { ran: false, mode, blocking: false, violations: [], armed, summary: "" };
}
const scope = options.scope ?? "changed-files";
const changedFiles = Array.from(
/* @__PURE__ */ new Set([
...options.changedFiles ?? (await loadChangeEvidence(projectRoot)).files,
...projectRelativeTargets(projectRoot, options.editTargets)
])
);
const changedFilesOnly = options.scopes !== void 0 && options.scopes.every((entry) => entry === "changed-files");
if ((scope === "changed-files" || changedFilesOnly) && changedFiles.length === 0) {
return {
ran: false,
mode,
blocking: false,
violations: [],
armed,
summary: "**\u25B8 paqad** \xB7 \u26AA scripted rules: skipped (no files changed this turn)"
};
}
const report = await runRuleScriptsAsync({
projectRoot,
mode,
changedFiles: changedFiles.length > 0 ? changedFiles : void 0,
...options.kinds ? { kinds: options.kinds } : {},
...options.scopes ? { scopes: options.scopes } : {},
concurrency: options.concurrency ?? resolveRuleGateConfig(projectRoot).concurrency,
signal: options.signal,
useFileCache: options.useFileCache
});
if (report.aborted) {
return { ran: false, mode, blocking: false, violations: [], armed, summary: "", aborted: true };
}
const titles = new Map((map?.rules ?? []).map((rule) => [rule.id, rule.text]));
const violations = report.results.filter((r) => !r.skipped && r.kind === "deterministic").flatMap(
(r) => r.findings.map((f) => ({
rule_id: r.rule_id,
...titles.has(r.rule_id) ? { title: titles.get(r.rule_id) } : {},
script: r.script,
file: f.file,
line: f.line,
message: f.message,
severity: f.severity
}))
);
return {
ran: true,
mode,
blocking: report.blocking,
violations,
armed,
summary: formatEnforcementSummary({ mode, blocking: report.blocking, violations })
};
}
function formatEnforcementSummary(result) {
if (result.violations.length === 0) {
return "**\u25B8 paqad** \xB7 scripted rules: \u{1F7E2} all clear";
}
const verb = result.blocking ? "Needs your attention" : "Heads up";
const glyph = result.blocking ? "\u{1F534}" : "\u{1F7E1}";
const lines = result.violations.slice(0, 20).map(
(v) => `> - ${glyph} ${v.rule_id} \xB7 ${v.file}${v.line ? `:${v.line}` : ""} \u2014 ${v.message}`
);
const more = result.violations.length > 20 ? `> - \u2026and ${result.violations.length - 20} more
` : "";
const header = result.blocking ? "I caught scripted-rule violations before this could ship \u2014 enforced even though their text was not loaded." : "Scripted-rule findings (warn mode, not blocking):";
return `**\u25B8 paqad** \xB7 ${verb}
> ${header}
${lines.join("\n")}
${more}`.trimEnd();
}
// src/kernel/registry.ts
init_esm_shims();
var CAPABILITY_REGISTRY = Object.freeze([
{
id: "stages",
title: "Feature-development stages",
modeKey: "stages_mode",
enforcementFloor: "block",
seam: ["pre-mutation", "completion"],
ledgerDocType: "stage-evidence",
policySchemaVersion: 1,
recordSchemaVersion: 1
},
{
id: "rule-scripts",
title: "Scripted-rule enforcement",
modeKey: "rule_compliance",
enforcementFloor: "block",
seam: ["pre-mutation", "completion"],
ledgerDocType: null,
policySchemaVersion: 1,
recordSchemaVersion: 1
},
{
id: "rules-loaded",
title: "Rule loading",
modeKey: null,
enforcementFloor: "block",
seam: ["pre-mutation"],
ledgerDocType: null,
policySchemaVersion: 1,
recordSchemaVersion: 1
},
{
id: "decision-pause",
title: "Decision Pause Contract",
modeKey: null,
enforcementFloor: "block",
seam: ["pre-mutation", "completion"],
ledgerDocType: "decision-evidence",
policySchemaVersion: 1,
recordSchemaVersion: 1
},
{
id: "narration",
title: "Narration contract",
modeKey: null,
enforcementFloor: "unbindable",
seam: [],
ledgerDocType: null,
policySchemaVersion: 1,
recordSchemaVersion: 1
},
{
id: "delivery",
title: "Delivery policy",
modeKey: null,
enforcementFloor: "warn",
seam: ["completion"],
ledgerDocType: "delivery-evidence",
policySchemaVersion: 1,
recordSchemaVersion: 1
}
]);
function getCapability(id) {
const found = CAPABILITY_REGISTRY.find((capability) => capability.id === id);
if (!found) {
throw new Error(`Unknown capability id: ${id}`);
}
return found;
}
function capabilitiesForSeam(seam) {
return CAPABILITY_REGISTRY.filter((capability) => capability.seam.includes(seam));
}
// src/rule-scripts/integrity.ts
init_esm_shims();
import { createHash as createHash2 } from "crypto";
function computeRuleScriptsDigest(projectRoot) {
const map = loadRuleScriptMap(projectRoot);
if (!map) {
return null;
}
const hash = createHash2("sha256");
hash.update(serializeRuleScriptMap(map));
hash.update("\0");
hash.update(scriptFilesHash(projectRoot, map));
return hash.digest("hex");
}
// src/kernel/capability-lock.ts
init_esm_shims();
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
import { dirname, join as join5 } from "path";
var CAPABILITY_LOCK_SCHEMA_VERSION = 1;
function capabilityLockPath(projectRoot) {
return join5(projectRoot, PATHS.CAPABILITY_LOCK);
}
function readCapabilityLock(projectRoot) {
const path = capabilityLockPath(projectRoot);
if (!existsSync3(path)) {
return null;
}
try {
const parsed = JSON.parse(readFileSync4(path, "utf8"));
return parsed && typeof parsed === "object" ? parsed : null;
} catch {
return null;
}
}
function readCapabilityDigest(projectRoot, capabilityId) {
const lock = readCapabilityLock(projectRoot);
const entry = lock?.capabilities?.[capabilityId];
return entry && typeof entry.digest === "string" ? entry.digest : null;
}
function readCapabilityVersions(projectRoot, capabilityId) {
const entry = readCapabilityLock(projectRoot)?.capabilities?.[capabilityId];
if (!entry || typeof entry.policy_version !== "number" || typeof entry.record_version !== "number") {
return null;
}
return { policy: entry.policy_version, record: entry.record_version };
}
function writeCapabilityDigest(projectRoot, capabilityId, digest, now = /* @__PURE__ */ new Date()) {
const existing = readCapabilityLock(projectRoot);
const descriptor = CAPABILITY_REGISTRY.find((capability) => capability.id === capabilityId);
const versions = descriptor ? {
policy_version: descriptor.policySchemaVersion,
record_version: descriptor.recordSchemaVersion
} : {};
const lock = {
schema_version: CAPABILITY_LOCK_SCHEMA_VERSION,
generated_at: now.toISOString(),
capabilities: {
...existing?.capabilities ?? {},
[capabilityId]: { ...existing?.capabilities?.[capabilityId] ?? {}, digest, ...versions }
}
};
const path = capabilityLockPath(projectRoot);
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, `${JSON.stringify(lock, null, 2)}
`, "utf8");
}
export {
validateScriptHeader,
validateFindings,
SCRIPT_ABORTED_ERROR,
executeRuleScript,
executeRuleScriptAsync,
isOnPath,
missingBinaries,
RULE_SCRIPT_MAP_SCHEMA_VERSION,
ruleScriptMapPath,
emptyRuleScriptMap,
loadRuleScriptMap,
serializeRuleScriptMap,
runPool,
HEADER_MARKER,
extractHeaderFields,
parseScriptHeader,
scriptFilesHash,
runRuleScripts,
runRuleScriptsAsync,
ruleRunFindings,
readReport,
projectRelativeTargets,
enforceRuleScripts,
formatEnforcementSummary,
computeRuleScriptsDigest,
getCapability,
capabilitiesForSeam,
readCapabilityDigest,
readCapabilityVersions,
writeCapabilityDigest
};