paqad-ai
Version:
Spec-driven development framework — AI agents that think before they type
1,436 lines (1,416 loc) • 46.5 kB
JavaScript
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url);
import {
ENVELOPE_HEADER_KEYS,
UNTITLED_FEATURE_TITLE,
buildFeatureRecord,
rowRecordedAt
} from "./chunk-RJMJYPHL.js";
import {
STAGE_ORDER,
stageForAgentName,
stripPluginAgentNamespace
} from "./chunk-NCYDV2N5.js";
import {
readGitState,
readHeadRef
} from "./chunk-BOEVU2BD.js";
import {
LANES,
require_ajv
} from "./chunk-ACJQW7SB.js";
import {
renameWithRetrySync
} from "./chunk-Z3NGWHFX.js";
import {
readUnitFile
} from "./chunk-Q353EDKY.js";
import {
readSessionControl,
writeSessionControl
} from "./chunk-FMQ5YRSP.js";
import {
FEATURE_DOC_SCHEMA_VERSION,
FEATURE_DOC_TYPE,
PLAN_DOC_TYPE,
REVIEW_DOC_TYPE,
ULID_BODY,
featureFilePath,
isFeatureDirName,
parseFeatureDirName
} from "./chunk-AZLA53A6.js";
import {
PATHS
} from "./chunk-IHMBVS6H.js";
import {
__toESM,
init_esm_shims
} from "./chunk-IDFRWBMN.js";
// src/feature-evidence/adoption.ts
init_esm_shims();
import { readFileSync as readFileSync2 } from "fs";
import { join as join3 } from "path";
// src/stage-evidence/stage-agent-holds.ts
init_esm_shims();
// src/stage-evidence/agent-identity.ts
init_esm_shims();
var ORCHESTRATOR_AGENT = "orchestrator";
var BACKSTOP_WRITER = "backstop";
function agentForWriter(adapter) {
return adapter === BACKSTOP_WRITER ? BACKSTOP_WRITER : void 0;
}
function nonEmpty(value) {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
function resolveAgentIdentity(input = {}) {
return nonEmpty(input.agentType) ?? nonEmpty(input.agentId) ?? ORCHESTRATOR_AGENT;
}
function isStageAgent(agent) {
return typeof agent === "string" && agent.startsWith("paqad-") && stageForAgentName(agent) !== null;
}
// src/stage-evidence/fold.ts
init_esm_shims();
// src/stage-evidence/report-repair.ts
init_esm_shims();
// src/stage-evidence/types.ts
init_esm_shims();
var STAGE_EVIDENCE_DOC_TYPE = "paqad.stage-evidence";
var STAGE_EVIDENCE_SCHEMA_VERSION = 3;
var STAGE_EVIDENCE_SCHEMA_V2 = 2;
var STAGE_AGENT_KIND = "stage-agent";
var SPEC_STEP_KIND = "spec-step";
var SPEC_CORRECTION_KIND = "spec-correction";
var COMPLETION_DEFERRED_KIND = "completion-deferred";
var RECORD_REPAIR_KIND = "record-repair";
var STAGE_HANDBACK_KIND = "stage-handback";
var RECIPE_INJECTED_KIND = "recipe-injected";
var SKILL_USED_KIND = "skill-used";
var REPORT_SOURCES = ["handback", "text", "rollout", "none"];
var SPEC_STEP_OUTCOMES = ["complete", "skipped", "redone"];
var STAGE_FAMILY_KIND_LIST = [
"open",
"stage_start",
"stage_end",
"verify",
"close"
];
var STAGE_FAMILY_KINDS = new Set(STAGE_FAMILY_KIND_LIST);
// src/stage-evidence/report-repair.ts
var REPORT_REPAIR_REASON = "report read from handback after the parser fix";
var ENTRY = /^(\S+) ([a-z_+]+): (DONE_WITH_CONCERNS|DONE|NEEDS_CONTEXT|BLOCKED) \((handback|text)\)$/u;
function isReportRepairRow(row) {
return row.kind === RECORD_REPAIR_KIND && row.reason === REPORT_REPAIR_REASON;
}
function readRepairedReports(rows) {
const byAgent = /* @__PURE__ */ new Map();
for (const row of rows) {
if (!isReportRepairRow(row) || typeof row.note !== "string") continue;
for (const part of row.note.split("; ")) {
const match = ENTRY.exec(part.trim());
if (match === null) continue;
byAgent.set(match[1], {
agent_id: match[1],
stages: match[2].split("+"),
report_status: match[3],
source: match[4]
});
}
}
return [...byAgent.values()];
}
// src/stage-evidence/stages.ts
init_esm_shims();
var STAGE_EVIDENCE_STAGES = [...STAGE_ORDER];
var MANDATORY_STAGES = [
"planning",
"specification",
"development",
"review",
"checks",
"documentation_sync"
];
var PRE_CODE_STAGES = MANDATORY_STAGES.slice(
0,
MANDATORY_STAGES.indexOf("development")
);
var EDIT_STAGES = ["development", "checks", "documentation_sync"];
function isEditStage(stage) {
return EDIT_STAGES.includes(stage);
}
var FAST_LANE_STAGES = ["planning", "development", "checks"];
function laneStages(lane2) {
return lane2 === "fast" ? [...FAST_LANE_STAGES] : [...MANDATORY_STAGES];
}
function isLaneStage(lane2, stage) {
return laneStages(lane2).includes(stage);
}
function requiredPreCodeStages(lane2) {
return laneStages(lane2).filter((stage) => PRE_CODE_STAGES.includes(stage));
}
var COMPLETION_ANCHORED_STAGES = ["review"];
var ARTIFACT_BEARING_STAGES = ["planning", "specification", "review"];
function isKnownStage(stage) {
return STAGE_EVIDENCE_STAGES.includes(stage);
}
function isArtifactBearingStage(stage) {
return ARTIFACT_BEARING_STAGES.includes(stage);
}
function isCompletionAnchoredStage(stage) {
return COMPLETION_ANCHORED_STAGES.includes(stage);
}
function isMandatoryStage(stage) {
return MANDATORY_STAGES.includes(stage);
}
function stageIndex(stage) {
return STAGE_EVIDENCE_STAGES.indexOf(stage);
}
// src/stage-evidence/fold.ts
function foldRowsWithKey(rows, identity) {
const { sessionId, changeKey: change_key, promptOrdinal: ordinal } = identity;
const stageRows2 = rows.filter((row) => STAGE_FAMILY_KINDS.has(String(row.kind)));
const lane2 = identity.lane !== void 0 ? identity.lane : readRecordedLane(stageRows2);
const stages = STAGE_EVIDENCE_STAGES.map((stage) => foldStage(stage, stageRows2, lane2));
const orderingViolations = computeOrderingViolations(stages);
const required = stages.filter((stage) => isLaneStage(lane2, stage.stage));
const isDone = (stage) => stage.state === "complete" || stage.state === "redone";
const passed = required.filter(isDone);
const missing = required.filter((stage) => !isDone(stage) && stage.state !== "not-applicable").map((stage) => stage.stage);
const hadRedo = stages.some((stage) => stage.state === "redone");
const verdict = computeVerdict(stageRows2, missing, orderingViolations.length > 0, hadRedo);
const repaired = readRepairedReports(rows);
return {
session_id: sessionId,
change_key,
prompt_ordinal: ordinal,
stages,
lane: lane2,
completeness: {
verdict,
missing_stages: missing,
required_passed: passed.length,
required_total: required.length,
ordering_violations: orderingViolations
},
...repaired.length > 0 ? { repaired_reports: repaired } : {}
};
}
function foldStage(stage, rows, lane2) {
const events = rows.filter((row) => row.stage === stage);
const start = lastOf(events, "stage_start");
const lastEnd = lastOf(events, "stage_end");
const end = lastEnd && startedAfterLastEnd(events) ? void 0 : lastEnd;
const startedAt = start ? rowRecordedAt(start) : null;
const endedAt = end ? rowRecordedAt(end) : null;
const firstStart = events.find((row) => row.kind === "stage_start");
let durationMs = null;
let unreliable = false;
if (startedAt && endedAt) {
durationMs = Date.parse(endedAt) - Date.parse(startedAt);
if (!Number.isFinite(durationMs) || durationMs < 0) {
durationMs = 0;
unreliable = true;
}
}
return {
stage,
state: events.length === 0 && isMandatoryStage(stage) && !isLaneStage(lane2, stage) ? "not-applicable" : deriveState(stage, events, start, end),
started_at: startedAt,
ended_at: endedAt,
first_started_at: firstStart ? rowRecordedAt(firstStart) : null,
last_ended_at: lastEnd ? rowRecordedAt(lastEnd) : null,
runs: events.filter((row) => row.kind === "stage_start").length,
duration_ms: durationMs,
duration_unreliable: unreliable,
evidence_source: coerceEvidenceSource((end ?? start)?.evidence_source),
artifact_digest: typeof end?.artifact_digest === "string" ? end.artifact_digest : null
};
}
function startedAfterLastEnd(events) {
let lastStart = -1;
let lastEnd = -1;
events.forEach((row, index) => {
if (row.kind === "stage_start") lastStart = index;
if (row.kind === "stage_end") lastEnd = index;
});
return lastStart > lastEnd;
}
function readRecordedLane(rows) {
const openRow = rows.find((row) => row.kind === "open");
const value = openRow?.lane;
return value === "fast" || value === "graduated" || value === "full" ? value : null;
}
function coerceEvidenceSource(value) {
return value === "live-mark" || value === "inferred-artifact" || value === "inferred-git" || value === "redo" ? value : null;
}
function deriveState(stage, events, start, end) {
const status = (end ?? start)?.event_status;
if (status === "skipped") return "skipped";
if (status === "failed") return "failed";
if (events.some((event) => event.event_status === "redone")) return "redone";
if (end) {
if (!start && end.evidence_source !== "inferred-git") return "inconclusive";
if (isArtifactBearingStage(stage) && typeof end.artifact_digest !== "string") {
return "inconclusive";
}
return "complete";
}
if (start) return "running";
return "missing";
}
function computeOrderingViolations(stages) {
const violations = [];
const ran = stages.filter((stage) => stage.started_at);
for (const stage of ran) {
if (stage.duration_unreliable) {
violations.push({ before: stage.stage, after: stage.stage });
}
}
for (const a of ran) {
for (const b of ran) {
if (isCompletionAnchoredStage(a.stage) || isCompletionAnchoredStage(b.stage)) {
continue;
}
if (isEditStage(a.stage) && isEditStage(b.stage)) {
continue;
}
if (stageIndex(a.stage) < stageIndex(b.stage) && a.ended_at && b.started_at) {
if (Date.parse(b.started_at) < Date.parse(a.ended_at)) {
violations.push({ before: a.stage, after: b.stage });
}
}
}
}
return violations;
}
function computeVerdict(rows, missing, hasOrderingViolation, hadRedo) {
if (rows.length === 0) {
return "cannot-verify";
}
if (missing.length > 0) {
return "incomplete";
}
if (hasOrderingViolation) {
return "incomplete";
}
return hadRedo ? "recovered" : "complete";
}
var RIGID_STAGE_FILE = [
{ stage: "planning", key: "plan" },
{ stage: "specification", key: "specification" }
];
function augmentWithBundleArtifacts(fold, presence) {
const missing = [...fold.completeness.missing_stages];
for (const { stage, key } of RIGID_STAGE_FILE) {
if (!presence[key] && isLaneStage(fold.lane, stage) && !missing.includes(stage)) {
missing.push(stage);
}
}
if (missing.length === fold.completeness.missing_stages.length) {
return fold;
}
const verdict = fold.completeness.verdict === "complete" || fold.completeness.verdict === "recovered" ? "incomplete" : fold.completeness.verdict;
return {
...fold,
completeness: { ...fold.completeness, verdict, missing_stages: missing }
};
}
function lastOf(events, kind) {
let found;
for (const event of events) {
if (event.kind === kind) {
found = event;
}
}
return found;
}
// src/stage-evidence/stage-agent-holds.ts
function rowTime(row) {
return Date.parse(rowRecordedAt(row) ?? "");
}
function stringOrNull(value) {
return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
}
var STAGE_AGENT_SESSION_RELEASE = "a new session began, so stage agents left from the last session no longer hold the turn-end judgement";
function stageAgentIdentity(agent, agentId) {
const name = stringOrNull(agent);
return {
agent: name === null ? null : stripPluginAgentNamespace(name),
agentId: stringOrNull(agentId)
};
}
function identityOf(row) {
return stageAgentIdentity(row.agent, row.agent_id);
}
function startedByAnotherAgent(starter, holder) {
if (starter.agentId !== null) return starter.agentId !== holder.agentId;
return isStageAgent(starter.agent) && starter.agent !== holder.agent;
}
function releasesStartedAgent(later, stage, holder, startedHash) {
const laterStage = stringOrNull(later.stage);
const laterId = stringOrNull(later.agent_id);
const agentId = holder.agentId;
if (later.kind === STAGE_AGENT_KIND && later.event_status === "started") {
return laterStage === stage && (agentId === null || laterId !== agentId);
}
if (later.kind === STAGE_AGENT_KIND) {
return agentId !== null && laterId !== null ? laterId === agentId : laterStage === stage;
}
if (later.kind === "stage_end") return laterStage === stage;
if (later.kind === "stage_start") {
return laterStage !== null && laterStage !== stage && laterId === null && !startedByAnotherAgent(identityOf(later), holder);
}
if (later.kind !== RECORD_REPAIR_KIND || later.reason !== STAGE_AGENT_SESSION_RELEASE) {
return false;
}
return !Array.isArray(later.releases) || later.releases.includes(startedHash);
}
function liveStageAgents(rows) {
const live = [];
rows.forEach((row, index) => {
if (row.kind !== STAGE_AGENT_KIND || row.event_status !== "started") return;
const stage = stringOrNull(row.stage);
if (stage === null) return;
if (Number.isNaN(rowTime(row))) return;
const holder = identityOf(row);
const released = rows.slice(index + 1).some((later) => releasesStartedAgent(later, stage, holder, row.content_hash));
if (released) return;
live.push({
stage,
agent: holder.agent,
agentId: holder.agentId,
startedAt: rowRecordedAt(row),
sessionId: row.session_id,
hostPid: typeof row.host_pid === "number" ? row.host_pid : null,
rowHash: row.content_hash
});
});
return live;
}
var RUNNING_STAGE_RECENT_MS = 30 * 60 * 1e3;
function newestRowTimes(rows) {
const newest = /* @__PURE__ */ new Map();
for (const row of rows) {
const at = rowTime(row);
if (Number.isNaN(at)) continue;
if (at > (newest.get(row.session_id) ?? Number.NEGATIVE_INFINITY)) {
newest.set(row.session_id, at);
}
}
return newest;
}
function openStageStarts(rows) {
const byStage = /* @__PURE__ */ new Map();
for (const row of rows) {
if (row.kind !== "stage_start" && row.kind !== "stage_end") continue;
const stage = stringOrNull(row.stage);
if (stage === null) continue;
byStage.set(stage, [...byStage.get(stage) ?? [], row]);
}
const open = [];
for (const events of byStage.values()) {
if (!startedAfterLastEnd(events)) continue;
open.push(events.filter((row) => row.kind === "stage_start").at(-1));
}
return open.sort((a, b) => rows.indexOf(a) - rows.indexOf(b));
}
function runningStageOfOtherSession(rows, askingSessionId, nowMs) {
const agent = liveStageAgents(rows).find((live) => live.sessionId !== askingSessionId);
if (agent) return agent.stage;
const newest = newestRowTimes(rows);
const running = openStageStarts(rows).filter((started) => {
if (started.session_id === askingSessionId) return false;
const last = newest.get(started.session_id);
return last !== void 0 && nowMs - last < RUNNING_STAGE_RECENT_MS;
});
return running.length === 0 ? null : stringOrNull(running.at(-1).stage);
}
// src/feature-evidence/enumerate.ts
init_esm_shims();
import { readdirSync } from "fs";
import { join } from "path";
function listFeatureDirs(projectRoot) {
try {
return readdirSync(join(projectRoot, PATHS.FEATURE_EVIDENCE_DIR), { withFileTypes: true }).filter((entry) => entry.isDirectory() && isFeatureDirName(entry.name)).map((entry) => entry.name).sort();
} catch {
return [];
}
}
// src/feature-evidence/feature-record.ts
init_esm_shims();
import { mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, join as join2 } from "path";
// src/feature-evidence/lane-rank.ts
init_esm_shims();
function laneRank(lane2) {
return lane2 === null ? -1 : LANES.indexOf(lane2);
}
function higherLane(current, incoming) {
return laneRank(incoming) > laneRank(current) ? incoming : current;
}
// src/feature-evidence/schema.ts
init_esm_shims();
var import_ajv = __toESM(require_ajv(), 1);
// src/visual-evidence/types.ts
init_esm_shims();
var VISUAL_EVIDENCE_DOC_TYPE = "paqad.visual-evidence";
var VISUAL_EVIDENCE_SCHEMA_VERSION = 2;
var VE_SKIP_REASONS = [
"not-frontend",
"flag-off",
"no-documented-flow",
"no-capture-script",
"capture-script-invalid",
"playwright-not-provisioned",
"app-preview-not-configured",
"app-not-reachable",
"env-var-missing",
"selector-not-found"
];
var VE_STEP_STATUSES = ["captured", "failed", "skipped"];
var VE_RESULTS = ["captured", "partial", "skipped"];
var VE_SOURCES = ["agent-attached", "mixed"];
var AGENT_ATTACHED_JOURNEY = "agent-attached";
// src/feature-evidence/schema.ts
var nullableString = { type: ["string", "null"] };
var ENVELOPE_HEADER_PROPERTIES = {
schema_version: { type: "integer", minimum: 1 },
doc_type: { type: "string", pattern: "^paqad\\.[a-z0-9-]+$" },
change: { type: "string", pattern: `^${ULID_BODY}$` },
session_id: { type: "string", minLength: 1 },
recorded_at: { type: "string", minLength: 1 },
content_hash: { type: "string", pattern: "^[0-9a-f]{64}$" }
};
var ENVELOPE_SCHEMA_FRAGMENT = {
type: "object",
required: [...ENVELOPE_HEADER_KEYS],
properties: ENVELOPE_HEADER_PROPERTIES
};
function envelopedDocumentSchema(docType, schemaVersion, required, properties) {
return {
allOf: [
ENVELOPE_SCHEMA_FRAGMENT,
{
type: "object",
additionalProperties: false,
required: [...required],
properties: {
...ENVELOPE_HEADER_PROPERTIES,
schema_version: { type: "integer", const: schemaVersion },
doc_type: { const: docType },
...properties
}
}
]
};
}
function versionedSchema(id, legacy, current) {
return {
$id: id,
if: {
type: "object",
required: ["schema_version"],
properties: { schema_version: { const: 1 } }
},
then: legacy,
else: current
};
}
var lane = { type: ["string", "null"], enum: ["fast", "graduated", "full", null] };
var laneMove = {
type: "object",
additionalProperties: false,
required: ["from", "to", "reason", "files", "at"],
properties: {
from: { enum: ["fast", "graduated", "full"] },
to: { enum: ["fast", "graduated", "full"] },
reason: { type: "string", minLength: 1 },
files: { type: "integer", minimum: 0 },
at: { type: "string", minLength: 1 }
}
};
var authoringLaneRaise = {
type: "object",
additionalProperties: false,
required: ["from", "to", "reason", "path", "at"],
properties: {
from: { enum: ["fast", "graduated", "full"] },
to: { enum: ["fast", "graduated", "full"] },
reason: { type: "string", minLength: 1 },
path: { type: "string", minLength: 1 },
paths: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["kind", "path"],
properties: {
kind: { enum: ["skill", "agent"] },
path: { type: "string", minLength: 1 }
}
}
},
at: { type: "string", minLength: 1 }
}
};
var FEATURE_SCHEMA_V1 = {
type: "object",
additionalProperties: false,
required: [
"schema_version",
"doc_type",
"issue",
"title",
"slug",
"ulid",
"created_at",
"updated_at",
"lane",
"status",
"spec_id",
"session_first_seen",
"adapter",
"content_hash"
],
properties: {
schema_version: { type: "integer", const: 1 },
doc_type: { const: FEATURE_DOC_TYPE },
issue: nullableString,
title: { type: "string", minLength: 1 },
slug: { type: "string", minLength: 1 },
ulid: { type: "string", minLength: 1 },
created_at: { type: "string", minLength: 1 },
updated_at: { type: "string", minLength: 1 },
lane,
status: { enum: ["active", "paused", "done", "spec-only", "abandoned"] },
spec_id: nullableString,
session_first_seen: { type: "string", minLength: 1 },
adapter: { type: "string", minLength: 1 },
branch: nullableString,
base_branch: nullableString,
content_hash: { type: "string", minLength: 1 }
}
};
var FEATURE_SCHEMA = versionedSchema(
"paqad://schemas/feature.json",
FEATURE_SCHEMA_V1,
envelopedDocumentSchema(
FEATURE_DOC_TYPE,
FEATURE_DOC_SCHEMA_VERSION,
[
...ENVELOPE_HEADER_KEYS,
"issue",
"title",
"slug",
"lane",
"status",
"spec_id",
"adapter",
"branch",
"base_branch",
"updated_at"
],
{
issue: nullableString,
title: { type: "string", minLength: 1 },
slug: { type: "string", minLength: 1 },
lane,
status: { enum: ["active", "paused", "done", "spec-only", "abandoned"] },
spec_id: nullableString,
adapter: { type: "string", minLength: 1 },
branch: nullableString,
base_branch: nullableString,
updated_at: { type: "string", minLength: 1 },
kind: { enum: ["fix", "other"] },
lane_source: { enum: ["classifier", "manual", "plan"] },
lane_resized: laneMove,
lane_raised: laneMove,
lane_raised_for_authoring: authoringLaneRaise
}
)
);
var PLAN_BODY_PROPERTIES = {
summary: { type: "string" },
steps: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["id", "description"],
properties: {
id: { type: "string", minLength: 1 },
description: { type: "string", minLength: 1 },
module: { type: "string", minLength: 1 },
classification: {
enum: ["ac-satisfying", "necessary-setup", "scaffolding", "over-build"]
},
files: { type: "array", items: { type: "string", minLength: 1 } }
}
}
},
modules_touched: { type: "array", items: { type: "string", minLength: 1 } },
decisions: { type: "array", items: { type: "string", minLength: 1 } },
risks: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["description", "mitigation"],
properties: {
description: { type: "string", minLength: 1 },
mitigation: { type: "string", minLength: 1 }
}
}
},
reuse: {
type: "object",
additionalProperties: false,
required: ["consulted", "reusing", "new_constructs"],
properties: {
consulted: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["source", "query", "hits"],
properties: {
source: {
enum: [
"existing-surface",
"index-query",
"reuse-catalog",
"module-doc",
"grep",
"framework-api",
"framework-docs"
]
},
query: { type: "string", minLength: 1 },
target: { type: "string", minLength: 1 },
hits: { type: "integer", minimum: 0 }
}
}
},
reusing: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["symbol", "how"],
properties: {
symbol: { type: "string", minLength: 1 },
file: { type: "string", minLength: 1 },
how: { type: "string", minLength: 1 },
package: { type: "string", minLength: 1 },
version: { type: "string", minLength: 1 },
provenance: { enum: ["asserted", "unknown-dynamic", "doc-derived"] }
}
}
},
new_constructs: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["name", "justification"],
properties: {
name: { type: "string", minLength: 1 },
justification: { type: "string", minLength: 1 },
framework_checked: {
type: "object",
additionalProperties: false,
required: ["package", "nearest", "verdict"],
properties: {
package: { type: "string", minLength: 1 },
nearest: { type: "string", minLength: 1 },
verdict: { enum: ["reuse", "extend", "insufficient", "absent"] }
}
}
}
}
}
}
}
};
var PLAN_SCHEMA_V1 = {
type: "object",
additionalProperties: false,
required: [
"schema_version",
"doc_type",
"issue",
"title",
"slug",
"ulid",
"summary",
"steps",
"modules_touched",
"decisions",
"risks",
"created_at",
"updated_at",
"content_hash"
],
properties: {
schema_version: { type: "integer", const: 1 },
doc_type: { const: PLAN_DOC_TYPE },
issue: nullableString,
title: { type: "string", minLength: 1 },
slug: { type: "string", minLength: 1 },
ulid: { type: "string", minLength: 1 },
...PLAN_BODY_PROPERTIES,
created_at: { type: "string", minLength: 1 },
updated_at: { type: "string", minLength: 1 },
content_hash: { type: "string", minLength: 1 }
}
};
var PLAN_SCHEMA = versionedSchema(
"paqad://schemas/plan.json",
PLAN_SCHEMA_V1,
envelopedDocumentSchema(
PLAN_DOC_TYPE,
FEATURE_DOC_SCHEMA_VERSION,
[...ENVELOPE_HEADER_KEYS, "summary", "steps", "modules_touched", "decisions", "risks"],
PLAN_BODY_PROPERTIES
)
);
var REVIEW_BODY_PROPERTIES = {
summary: { type: "string", minLength: 1 },
verdict: { enum: ["safe-to-merge", "needs-attention", "inconclusive"] },
findings: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["severity", "description"],
properties: {
severity: { enum: ["blocker", "major", "minor"] },
description: { type: "string", minLength: 1 },
file: { type: "string", minLength: 1 },
deferred_to: { type: "string", pattern: `^TD-${ULID_BODY}$` }
}
}
},
checked: { type: "array", items: { type: "string", minLength: 1 } },
rollback: { type: "string", minLength: 1 }
};
var REVIEW_SCHEMA_V1 = {
type: "object",
additionalProperties: false,
required: [
"schema_version",
"doc_type",
"issue",
"title",
"slug",
"ulid",
"summary",
"verdict",
"findings",
"checked",
"rollback",
"created_at",
"updated_at",
"content_hash"
],
properties: {
schema_version: { type: "integer", const: 1 },
doc_type: { const: REVIEW_DOC_TYPE },
issue: nullableString,
title: { type: "string", minLength: 1 },
slug: { type: "string", minLength: 1 },
ulid: { type: "string", minLength: 1 },
...REVIEW_BODY_PROPERTIES,
created_at: { type: "string", minLength: 1 },
updated_at: { type: "string", minLength: 1 },
content_hash: { type: "string", minLength: 1 }
}
};
var REVIEW_SCHEMA = versionedSchema(
"paqad://schemas/review.json",
REVIEW_SCHEMA_V1,
envelopedDocumentSchema(
REVIEW_DOC_TYPE,
FEATURE_DOC_SCHEMA_VERSION,
[...ENVELOPE_HEADER_KEYS, "summary", "verdict", "findings", "checked", "rollback"],
REVIEW_BODY_PROPERTIES
)
);
function visualEvidenceStepsSchema(timeKey) {
return {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["index", "journey_id", "journey_step", "caption", "dir", timeKey, "status"],
properties: {
index: { type: "integer", minimum: 1 },
journey_id: { type: "string", minLength: 1 },
journey_step: { type: "integer", minimum: 1 },
caption: { type: "string" },
dir: { type: "string", minLength: 1 },
route: { type: "string" },
[timeKey]: { type: "string", minLength: 1 },
image_sha256: { type: "string" },
image_bytes: { type: "integer", minimum: 0 },
status: { enum: [...VE_STEP_STATUSES] },
failure: { type: "string" },
ac: { type: "string", minLength: 1 }
}
}
};
}
var VISUAL_EVIDENCE_BODY_PROPERTIES = {
trigger: {
type: "object",
additionalProperties: false,
required: ["changed_files", "matched_globs", "packs"],
properties: {
changed_files: { type: "array", items: { type: "string" } },
matched_globs: { type: "array", items: { type: "string" } },
packs: { type: "array", items: { type: "string" } }
}
},
plan: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["journey_id", "capture_script", "matched_by"],
properties: {
journey_id: { type: "string", minLength: 1 },
capture_script: { type: "string", minLength: 1 },
matched_by: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["file", "surface", "module"],
properties: {
file: { type: "string" },
surface: { type: "string" },
module: { type: "string" }
}
}
}
}
}
},
gif: {
oneOf: [
{ type: "null" },
{
type: "object",
additionalProperties: false,
required: ["file", "frames", "frame_ms", "sha256", "bytes"],
properties: {
file: { type: "string", minLength: 1 },
frames: { type: "integer", minimum: 0 },
frame_ms: { type: "integer", minimum: 1 },
sha256: { type: "string", minLength: 1 },
bytes: { type: "integer", minimum: 0 }
}
}
]
},
skips: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["reason", "detail"],
properties: {
reason: { enum: [...VE_SKIP_REASONS] },
detail: { type: "string" }
}
}
},
result: { enum: [...VE_RESULTS] },
source: { enum: [...VE_SOURCES] }
};
var VISUAL_EVIDENCE_SCHEMA_V1 = {
type: "object",
additionalProperties: false,
required: [
"schema_version",
"doc_type",
"generated_at",
"content_hash",
"trigger",
"plan",
"steps",
"gif",
"skips",
"result"
],
properties: {
schema_version: { type: "integer", const: 1 },
doc_type: { const: VISUAL_EVIDENCE_DOC_TYPE },
generated_at: { type: "string", minLength: 1 },
content_hash: { type: "string", minLength: 1 },
...VISUAL_EVIDENCE_BODY_PROPERTIES,
steps: visualEvidenceStepsSchema("captured_at")
}
};
var VISUAL_EVIDENCE_SCHEMA = versionedSchema(
"paqad://schemas/visual-evidence.json",
VISUAL_EVIDENCE_SCHEMA_V1,
envelopedDocumentSchema(
VISUAL_EVIDENCE_DOC_TYPE,
VISUAL_EVIDENCE_SCHEMA_VERSION,
[...ENVELOPE_HEADER_KEYS, "trigger", "plan", "steps", "gif", "skips", "result"],
{ ...VISUAL_EVIDENCE_BODY_PROPERTIES, steps: visualEvidenceStepsSchema("recorded_at") }
)
);
var ajv = new import_ajv.default({ allErrors: true, allowUnionTypes: true });
var compiledFeature;
var compiledPlan;
var compiledReview;
var compiledVisualEvidence;
function formatValidationError(error) {
return `${error.instancePath || "(root)"} ${error.message ?? "invalid"}`;
}
function runValidator(validate, row) {
if (validate(row)) {
return [];
}
const errors = validate.errors;
return errors.map(formatValidationError);
}
function validateFeatureRecord(row) {
if (!compiledFeature) {
compiledFeature = ajv.compile(FEATURE_SCHEMA);
}
return runValidator(compiledFeature, row);
}
function validatePlanRecord(row) {
if (!compiledPlan) {
compiledPlan = ajv.compile(PLAN_SCHEMA);
}
return runValidator(compiledPlan, row);
}
function validateReviewRecord(row) {
if (!compiledReview) {
compiledReview = ajv.compile(REVIEW_SCHEMA);
}
return runValidator(compiledReview, row);
}
function validateVisualEvidenceRecord(row) {
if (!compiledVisualEvidence) {
compiledVisualEvidence = ajv.compile(VISUAL_EVIDENCE_SCHEMA);
}
return runValidator(compiledVisualEvidence, row);
}
// src/feature-evidence/feature-record.ts
var FINISHED_FEATURE_STATUSES = /* @__PURE__ */ new Set(["done", "abandoned"]);
function isFeatureFinished(status) {
return status !== null && status !== void 0 && FINISHED_FEATURE_STATUSES.has(status);
}
function atomicWriteJson(absPath, value) {
mkdirSync(dirname(absPath), { recursive: true });
const tmp = `${absPath}.tmp`;
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
`, "utf8");
renameWithRetrySync(tmp, absPath);
}
function fromLegacyFeatureRecord(legacy) {
return {
schema_version: legacy.schema_version,
doc_type: legacy.doc_type,
change: legacy.ulid,
session_id: legacy.session_first_seen,
recorded_at: legacy.created_at,
content_hash: legacy.content_hash,
issue: legacy.issue,
title: legacy.title,
slug: legacy.slug,
lane: legacy.lane,
status: legacy.status,
spec_id: legacy.spec_id,
adapter: legacy.adapter,
branch: legacy.branch ?? null,
base_branch: legacy.base_branch ?? null,
updated_at: legacy.updated_at
};
}
function readFeatureRecord(projectRoot, dirName) {
try {
const parsed = JSON.parse(
readFileSync(join2(projectRoot, featureFilePath(dirName, "feature")), "utf8")
);
if (validateFeatureRecord(parsed).length === 0) {
const record = parsed;
return record.schema_version === 1 ? fromLegacyFeatureRecord(record) : record;
}
} catch {
}
return null;
}
function writeFeatureRecord(projectRoot, dirName, record) {
const errors = validateFeatureRecord(record);
if (errors.length > 0) {
throw new Error(`Invalid feature.json: ${errors.join("; ")}`);
}
atomicWriteJson(join2(projectRoot, featureFilePath(dirName, "feature")), record);
}
function seedFeatureRecord(projectRoot, dirName, input) {
const existing = readFeatureRecord(projectRoot, dirName);
if (existing) {
return existing;
}
const parts = parseFeatureDirName(dirName);
if (!parts) {
return null;
}
const record = buildFeatureRecord({
issue: parts.issue,
title: parts.slug,
slug: parts.slug,
change: parts.ulid,
lane: input.lane ?? null,
status: input.status ?? "active",
session_id: input.sessionId,
adapter: input.adapter,
branch: input.branch ?? null,
base_branch: input.baseBranch ?? null,
kind: input.kind,
lane_source: input.lane ? "classifier" : void 0,
now: input.now
});
try {
writeFeatureRecord(projectRoot, dirName, record);
return record;
} catch {
return null;
}
}
function higherKind(current, next) {
return current === "fix" ? "fix" : next;
}
function updateFeatureRecord(projectRoot, dirName, patch, now = () => /* @__PURE__ */ new Date()) {
const parts = parseFeatureDirName(dirName);
if (!parts) {
return null;
}
const current = readFeatureRecord(projectRoot, dirName) ?? buildFeatureRecord({
issue: parts.issue,
title: parts.slug,
slug: parts.slug,
change: parts.ulid,
status: "active",
session_id: "unknown",
adapter: "unknown",
now
});
const laneState = nextLaneState(projectRoot, dirName, current, patch);
const next = buildFeatureRecord({
change: current.change,
session_id: current.session_id,
recorded_at: current.recorded_at,
issue: patch.issue !== void 0 ? patch.issue : current.issue,
title: patch.title ?? current.title,
slug: patch.slug ?? current.slug,
lane: laneState.lane,
status: patch.status ?? current.status,
spec_id: patch.spec_id !== void 0 ? patch.spec_id : current.spec_id,
adapter: patch.adapter ?? current.adapter,
branch: patch.branch !== void 0 ? patch.branch : current.branch ?? null,
base_branch: patch.base_branch !== void 0 ? patch.base_branch : current.base_branch ?? null,
kind: patch.kind !== void 0 ? higherKind(current.kind, patch.kind) : current.kind,
lane_source: laneState.lane_source,
lane_resized: laneState.lane_resized,
lane_raised: laneState.lane_raised,
lane_raised_for_authoring: laneState.lane_raised_for_authoring,
now: () => new Date(current.updated_at)
});
if (next.content_hash === current.content_hash) {
return current;
}
next.updated_at = now().toISOString();
try {
writeFeatureRecord(projectRoot, dirName, next);
return next;
} catch {
return null;
}
}
var PRE_SPEC_ONLY_STAGES = /* @__PURE__ */ new Set([
"specification",
"development",
"checks",
"documentation_sync"
]);
function hasStartedPastPlanning(projectRoot, dirName) {
return readUnitFile(projectRoot, featureFilePath(dirName, "stageEvidence")).some(
(row) => row.kind === "stage_start" && PRE_SPEC_ONLY_STAGES.has(String(row.stage))
);
}
function canLowerFromPlan(projectRoot, dirName, current) {
return current.lane === "full" && current.lane_source === "classifier" && current.lane_resized === void 0 && !hasStartedPastPlanning(projectRoot, dirName);
}
function canRaiseAfterResize(current) {
const resizedByPlan = current.lane_source === "plan" && current.lane_resized !== void 0;
return current.lane === "graduated" && (resizedByPlan || current.lane_raised_for_authoring !== void 0) && current.lane_raised === void 0;
}
function canRaiseForAuthoring(current) {
return current.lane === "fast" && current.lane_raised_for_authoring === void 0;
}
function nextLaneState(projectRoot, dirName, current, patch) {
const kept = {
lane: current.lane,
lane_source: current.lane_source,
lane_resized: current.lane_resized,
lane_raised: current.lane_raised,
lane_raised_for_authoring: current.lane_raised_for_authoring
};
const authoring = patch.raiseLaneForAuthoring;
if (authoring !== void 0) {
if (!canRaiseForAuthoring(current)) return kept;
return {
...kept,
lane: "graduated",
lane_raised_for_authoring: { from: "fast", to: "graduated", ...authoring }
};
}
const raise = patch.raiseLaneFromChange;
if (raise !== void 0) {
if (!canRaiseAfterResize(current)) return kept;
return { ...kept, lane: "full", lane_raised: { from: "graduated", to: "full", ...raise } };
}
const resize = patch.resizeLaneFromPlan;
if (resize !== void 0) {
if (!canLowerFromPlan(projectRoot, dirName, current)) return kept;
return {
lane: "graduated",
lane_source: "plan",
lane_resized: { from: "full", to: "graduated", ...resize }
};
}
if (patch.lane === void 0) return kept;
const source = patch.laneSource ?? "classifier";
if (current.lane_source === "plan" && source !== "manual") return kept;
const lane2 = higherLane(current.lane, patch.lane);
if (source === "manual" && laneRank(patch.lane) >= laneRank(current.lane)) {
return { ...kept, lane: lane2, lane_source: "manual" };
}
return lane2 === current.lane ? kept : { ...kept, lane: lane2, lane_source: source };
}
function featureRecordIsUntitled(record) {
return record.title === UNTITLED_FEATURE_TITLE && record.issue === null;
}
var UNKNOWN_ADAPTER = "unknown";
function knownString(value) {
return typeof value === "string" && value.length > 0 && value !== UNKNOWN_ADAPTER ? value : null;
}
function knownLane(value) {
return value === "fast" || value === "graduated" || value === "full" ? value : null;
}
function readChangeConstants(projectRoot, dirName, rows) {
const record = readFeatureRecord(projectRoot, dirName);
const fromRecord = {
adapter: knownString(record?.adapter),
branch: knownString(record?.branch),
base_branch: knownString(record?.base_branch),
lane: knownLane(record?.lane)
};
if (fromRecord.adapter !== null && fromRecord.branch !== null && fromRecord.base_branch !== null && fromRecord.lane !== null) {
return fromRecord;
}
const openRow = (rows ?? readUnitFile(projectRoot, featureFilePath(dirName, "stageEvidence"))).find((row) => row.kind === "open");
return {
adapter: fromRecord.adapter ?? knownString(openRow?.adapter),
branch: fromRecord.branch ?? knownString(openRow?.branch),
base_branch: fromRecord.base_branch ?? knownString(openRow?.base_branch),
lane: fromRecord.lane ?? knownLane(openRow?.lane)
};
}
// src/feature-evidence/adoption.ts
function stageRows(projectRoot, dirName) {
return readUnitFile(projectRoot, featureFilePath(dirName, "stageEvidence"));
}
function isBundleMaterialized(projectRoot, dirName) {
return stageRows(projectRoot, dirName).length > 0;
}
function isBundleClosed(projectRoot, dirName) {
let closed = false;
for (const row of stageRows(projectRoot, dirName)) {
if (row.kind === "open") {
closed = false;
} else if (row.kind === "close") {
closed = true;
}
}
return closed;
}
function featureBranch(projectRoot, dirName, rows = stageRows(projectRoot, dirName)) {
return readChangeConstants(projectRoot, dirName, rows).branch ?? deliveryBranch(projectRoot, dirName);
}
function deliveryBranch(projectRoot, dirName) {
try {
const raw = JSON.parse(
readFileSync2(join3(projectRoot, featureFilePath(dirName, "delivery")), "utf8")
);
const branch = raw.branch;
return typeof branch === "string" && branch.length > 0 ? branch : null;
} catch {
return null;
}
}
function listAdoptableFeatures(projectRoot, branch) {
return listFeatureDirs(projectRoot).filter((dirName) => {
const rows = stageRows(projectRoot, dirName);
if (rows.length === 0 || rows.some((row) => row.kind === "close")) {
return false;
}
return branch === null || featureBranch(projectRoot, dirName, rows) === branch;
});
}
function listInFlightFeatures(projectRoot) {
return listAdoptableFeatures(projectRoot, null);
}
function sessionClosedAnyFeature(projectRoot, sessionId) {
return listFeatureDirs(projectRoot).some(
(dirName) => stageRows(projectRoot, dirName).some(
(row) => row.kind === "close" && row.session_id === sessionId
)
);
}
function sessionReattachableFeature(projectRoot, sessionId, branch) {
if (branch === null) {
return null;
}
let picked = null;
let pickedAt = "";
for (const dirName of listFeatureDirs(projectRoot)) {
const rows = stageRows(projectRoot, dirName);
if (!rows.some((row) => row.kind === "open" && row.session_id === sessionId)) continue;
if (!isBundleClosed(projectRoot, dirName)) continue;
if (featureBranch(projectRoot, dirName, rows) !== branch) continue;
if (readFeatureRecord(projectRoot, dirName)?.status === "abandoned") continue;
const lastAt = rows.reduce((newest, row) => {
const at = rowRecordedAt(row) ?? "";
return at > newest ? at : newest;
}, "");
if (picked === null || lastAt > pickedAt) {
picked = dirName;
pickedAt = lastAt;
}
}
return picked;
}
function anotherSessionRunningStage(projectRoot, sessionId, dirName, now = () => /* @__PURE__ */ new Date()) {
const rows = stageRows(projectRoot, dirName);
const wroteWork = rows.some(
(row) => row.session_id === sessionId && row.kind !== RECORD_REPAIR_KIND
);
if (wroteWork) return null;
return runningStageOfOtherSession(rows, sessionId, now().getTime());
}
function onAnotherBranch(projectRoot, dirName) {
const head = readHeadRef(projectRoot);
if (head.branch === null) return false;
const recorded = featureBranch(projectRoot, dirName);
return recorded !== null && recorded !== head.branch;
}
function reconcileSessionControl(projectRoot, sessionId, now) {
let control = readSessionControl(projectRoot, sessionId, now);
const activeClosed = control.active !== null && isBundleMaterialized(projectRoot, control.active) && isBundleClosed(projectRoot, control.active);
if (control.active !== null && isBundleMaterialized(projectRoot, control.active) && !activeClosed) {
const keep = !onAnotherBranch(projectRoot, control.active) && anotherSessionRunningStage(projectRoot, sessionId, control.active, now) === null;
if (keep) return control.active;
control = writeSessionControl(projectRoot, { ...control, active: null }, now, "reconcile");
}
const candidates = adoptableInFlightOnBranch(projectRoot, sessionId, now);
const fallback = activeClosed ? null : control.active;
if (candidates.length !== 1) {
if (activeClosed) {
writeSessionControl(projectRoot, { ...control, active: null }, now, "reconcile");
}
return fallback;
}
const adopted = candidates[0];
writeSessionControl(projectRoot, { ...control, active: adopted }, now, "reconcile");
return adopted;
}
function adoptableInFlightOnBranch(projectRoot, sessionId, now) {
return inFlightOnBranch(projectRoot, sessionId, now).filter(
(name) => anotherSessionRunningStage(projectRoot, sessionId, name, now) === null
);
}
function heldInFlightOnBranch(projectRoot, sessionId, now) {
return inFlightOnBranch(projectRoot, sessionId, now).flatMap((dirName) => {
const stage = anotherSessionRunningStage(projectRoot, sessionId, dirName, now);
return stage === null ? [] : [{ dirName, stage }];
});
}
function inFlightOnBranch(projectRoot, sessionId, now) {
const control = readSessionControl(projectRoot, sessionId, now);
const branch = readGitState(projectRoot).branch ?? null;
return listAdoptableFeatures(projectRoot, branch).filter(
(name) => !control.paused.includes(name)
);
}
export {
STAGE_EVIDENCE_DOC_TYPE,
STAGE_EVIDENCE_SCHEMA_VERSION,
STAGE_EVIDENCE_SCHEMA_V2,
STAGE_AGENT_KIND,
SPEC_STEP_KIND,
SPEC_CORRECTION_KIND,
COMPLETION_DEFERRED_KIND,
RECORD_REPAIR_KIND,
STAGE_HANDBACK_KIND,
RECIPE_INJECTED_KIND,
SKILL_USED_KIND,
REPORT_SOURCES,
SPEC_STEP_OUTCOMES,
STAGE_FAMILY_KIND_LIST,
REPORT_REPAIR_REASON,
readRepairedReports,
STAGE_EVIDENCE_STAGES,
PRE_CODE_STAGES,
isEditStage,
laneStages,
isLaneStage,
requiredPreCodeStages,
isKnownStage,
isArtifactBearingStage,
isMandatoryStage,
foldRowsWithKey,
startedAfterLastEnd,
augmentWithBundleArtifacts,
ORCHESTRATOR_AGENT,
BACKSTOP_WRITER,
agentForWriter,
resolveAgentIdentity,
isStageAgent,
STAGE_AGENT_SESSION_RELEASE,
stageAgentIdentity,
startedByAnotherAgent,
liveStageAgents,
runningStageOfOtherSession,
listFeatureDirs,
laneRank,
VISUAL_EVIDENCE_DOC_TYPE,
VISUAL_EVIDENCE_SCHEMA_VERSION,
AGENT_ATTACHED_JOURNEY,
ENVELOPE_HEADER_PROPERTIES,
validatePlanRecord,
validateReviewRecord,
validateVisualEvidenceRecord,
isFeatureFinished,
readFeatureRecord,
seedFeatureRecord,
updateFeatureRecord,
featureRecordIsUntitled,
readChangeConstants,
isBundleMaterialized,
isBundleClosed,
featureBranch,
listAdoptableFeatures,
listInFlightFeatures,
sessionClosedAnyFeature,
sessionReattachableFeature,
anotherSessionRunningStage,
reconcileSessionControl,
adoptableInFlightOnBranch,
heldInFlightOnBranch
};