paqad-ai
Version:
Spec-driven development framework — AI agents that think before they type
1,202 lines (1,093 loc) • 37.6 kB
text/typescript
import { verdictFromEarlierTurn, waitingOnHuman } from './attention';
import { costPill, formatClock } from './cost';
import { NO_RECORD, SUBAGENT_ROW, judgedHeadline, snapshotHeadline } from './headline';
import { IDLE_LIVE, type LiveState } from './live';
import { tableInstrument, type TableInstrument } from './table';
import type {
PaqadChief,
PaqadCi,
PaqadClaim,
PaqadStartPoint,
PaqadCommitGroup,
PaqadCost,
PaqadCostLine,
PaqadCostTokens,
PaqadDeliveryPlan,
PaqadFindingsScan,
PaqadGate,
PaqadGateStrength,
PaqadProofStep,
PaqadRepair,
PaqadStageProof,
PaqadStageRun,
PaqadRung,
PaqadHeadline,
PaqadHighTable,
PaqadHudSnapshot,
PaqadHudStage,
PaqadLastBlock,
PaqadPlanProgress,
PaqadPrState,
PaqadScope,
PaqadSeat,
PaqadShipCommit,
PaqadSignoff,
PaqadSignoffState,
PaqadSeatState,
PaqadTone,
PaqadTimelineEntry,
PaqadTrackRecord,
PaqadWorkflow,
} from './types';
export const NOT_YET = 'not yet';
export const HUD_DIR = '.paqad/session/hud';
export function safeSessionId(sessionId: string): string {
return sessionId.replace(/[^A-Za-z0-9._-]/g, '_').replace(/^\.+/, '_') || 'unknown';
}
export function joinPath(root: string, rel: string): string {
return /[\\/]$/.test(root) ? `${root}${rel}` : `${root}/${rel}`;
}
export function snapshotPath(root: string, sessionId: string): string {
return joinPath(root, `${HUD_DIR}/${safeSessionId(sessionId)}.json`);
}
export function receiptPath(root: string, sessionId: string): string {
return joinPath(root, `${HUD_DIR}/${safeSessionId(sessionId)}.receipt.md`);
}
export function lastBlockPath(root: string, sessionId: string): string {
return joinPath(root, `${HUD_DIR}/${safeSessionId(sessionId)}.last-block.json`);
}
export const PENDING_DECISIONS_DIR = '.paqad/decisions/pending';
export function entrySentinelPath(root: string, sessionId: string): string {
const key = sessionId.trim().replace(/[^A-Za-z0-9_-]/g, '_');
return joinPath(
root,
key === '' ? '.paqad/.agent-entry-loaded' : `.paqad/.agent-entry-loaded.d/${key}`,
);
}
export function heartbeatPath(root: string, sessionId: string): string {
return joinPath(root, `${HUD_DIR}/${safeSessionId(sessionId)}.live`);
}
export const ORG_ENFORCED = "This rule is enforced by your organization's settings.";
export const ALLOW_ONCE_STALE_CLI =
'Allow once could not take effect because paqad-ai is older than the HUD, so the edit stays blocked. Update paqad-ai and try again.';
export const RULE_STOP_FIX = 'Let Claude fix it';
export const RULE_STOP_ALLOW = 'Allow once, recorded';
export const RULE_STOP_SHOW = 'Show the rule';
export const RULE_STOP_NO_REASON = 'No reason';
export const RULE_STOP_FALSE_POSITIVE = 'It is a false positive';
export const RULE_STOP_HEADER = 'Rule stop';
export const RULE_STOP_REASON_HEADER = 'Reason';
export const OWN_QUESTION_HEADERS: readonly string[] = [RULE_STOP_HEADER, RULE_STOP_REASON_HEADER];
export interface RuleStop {
rule: string;
title: string | null;
file: string;
toolUseId: string;
managed: boolean;
sole: boolean;
token: string;
}
type Rec = Record<string, unknown>;
function rec(value: unknown): Rec | null {
return typeof value === 'object' && value !== null && !Array.isArray(value)
? (value as Rec)
: null;
}
function str(value: unknown): string | null {
return typeof value === 'string' && value.length > 0 ? value : null;
}
function num(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) ? value : null;
}
function list(value: unknown): unknown[] {
return Array.isArray(value) ? value : [];
}
const TONES: readonly PaqadTone[] = ['neutral', 'working', 'needs-you', 'good', 'warn', 'bad'];
function readScan(value: unknown): PaqadFindingsScan {
const scan = rec(value);
const status = scan?.status;
return {
status: status === 'open' || status === 'clean' ? status : 'did not run',
total: num(scan?.total),
blocking: scan?.blocking === true,
};
}
function readLastBlock(raw: unknown, block: Rec | null): PaqadLastBlock {
const rule = str(block?.rule);
const file = str(block?.file);
const text =
str(raw) ??
str(block?.text) ??
str(block?.reason) ??
(rule && file ? `${rule} stopped an edit to ${file}.` : 'A rule stopped an edit.');
const title = str(block?.title);
const toolUseId = str(block?.tool_use_id);
const at = str(block?.at);
const token = str(block?.token);
return {
text,
...(rule ? { rule } : {}),
...(title ? { title } : {}),
...(file ? { file } : {}),
...(toolUseId ? { tool_use_id: toolUseId } : {}),
...(typeof block?.managed === 'boolean' ? { managed: block.managed } : {}),
...(at ? { at } : {}),
...(token ? { token } : {}),
...(typeof block?.used === 'boolean' ? { used: block.used } : {}),
};
}
export function freshRuleStop(
text: string | null,
sessionId: string,
toolUseId: string | undefined,
): RuleStop | null {
if (text === null || !toolUseId) return null;
let raw: Rec | null;
try {
raw = rec(JSON.parse(text));
} catch {
return null;
}
const rule = str(raw?.rule);
const file = str(raw?.file);
const token = str(raw?.token);
if (!raw || !rule || !file || !token) return null;
if (raw.used === true || str(raw.session) !== sessionId) return null;
if (str(raw.tool_use_id) !== toolUseId) return null;
return {
rule,
title: str(raw.title),
file,
toolUseId,
managed: raw.managed === true,
sole: raw.sole === true,
token,
};
}
export function isDenied(result: unknown): boolean {
const raw = rec(result);
return raw !== null && (raw.deny !== undefined || raw.isError === true);
}
export type EditOutcome = 'landed' | 'still-blocked';
export function editOutcome(result: unknown): EditOutcome {
return isDenied(result) ? 'still-blocked' : 'landed';
}
export function isAllowedOnce(text: string | null, stop: RuleStop): boolean {
if (text === null) return false;
let raw: Rec | null;
try {
raw = rec(JSON.parse(text));
} catch {
return false;
}
return raw?.allowed_once === true && str(raw.tool_use_id) === stop.toolUseId;
}
export function ruleStopQuestion(stop: RuleStop): string {
return `${stop.rule} stopped this edit to ${stop.file}. What should happen?`;
}
export function ruleTextQuestion(stop: RuleStop): string {
return `${stop.rule}: ${stop.title ?? 'no rule text recorded'}. What should happen?`;
}
export function overrideArgs(
stop: RuleStop,
sessionId: string,
root: string,
reason: string | null,
): string[] {
return [
'--rule',
stop.rule,
'--file',
stop.file,
'--token',
stop.token,
'--tool-use-id',
stop.toolUseId,
'--session',
sessionId,
'--project-root',
root,
...(reason ? [`--reason=${reason}`] : []),
];
}
export function outcomeArgs(
stop: RuleStop,
sessionId: string,
root: string,
outcome: EditOutcome,
): string[] {
return [
'--outcome',
outcome,
'--rule',
stop.rule,
'--file',
stop.file,
'--tool-use-id',
stop.toolUseId,
'--session',
sessionId,
'--project-root',
root,
];
}
function readTokens(value: unknown): PaqadCostTokens {
const tokens = rec(value);
const count = (key: string): number => Math.max(0, num(tokens?.[key]) ?? 0);
return {
input: count('input'),
output: count('output'),
cache_read: count('cache_read'),
cache_write: count('cache_write'),
total: count('total'),
};
}
function readCostLine(value: unknown, dated: boolean): PaqadCostLine {
const line = rec(value);
return { tokens: readTokens(line?.tokens), usd: dated ? num(line?.usd) : null };
}
function readCost(value: unknown): PaqadCost | null {
const cost = rec(value);
if (!cost) return null;
const date = str(cost.price_table_date);
const dated = date !== null;
const chief = rec(cost.chief);
return {
scope: cost.scope === 'change' ? 'change' : 'whole-session',
price_table_date: date ?? '',
label: str(cost.label) ?? 'estimate at API prices',
basis: str(cost.basis) ?? 'API-equivalent estimate',
plan_note: str(cost.plan_note) ?? '',
sealed: rec(cost.window)?.sealed === true,
tokens: readTokens(cost.tokens),
usd: dated ? num(cost.usd) : null,
unpriced_models: list(cost.unpriced_models).filter((m): m is string => typeof m === 'string'),
by_stage: list(cost.by_stage).flatMap((entry) => {
const stage = str(rec(entry)?.stage);
return stage ? [{ stage, ...readCostLine(entry, dated) }] : [];
}),
by_expert: list(cost.by_expert).flatMap((entry) => {
const role = str(rec(entry)?.role);
return role
? [{ role, ...readCostLine(entry, dated), exact: rec(entry)?.exact === true }]
: [];
}),
chief: chief ? { ...readCostLine(chief, dated), exact: chief.exact === true } : null,
not_counted: list(cost.not_counted).filter((t): t is string => typeof t === 'string'),
live: cost.live === true,
};
}
const SEAT_STATES: readonly PaqadSeatState[] = ['seated', 'writing', 'done'];
function strings(value: unknown): string[] {
return list(value).filter((entry): entry is string => typeof entry === 'string');
}
function readTrackRecord(value: unknown): PaqadTrackRecord | null {
const record = rec(value);
const changes = num(record?.changes);
if (!record || changes === null || changes <= 0) return null;
const count = (key: string) => Math.max(0, num(record[key]) ?? 0);
return {
changes,
changed_spec: count('changed_spec'),
empty: count('empty'),
avg_tokens: count('avg_tokens'),
};
}
function readSeat(value: unknown): PaqadSeat | null {
const seat = rec(value);
const role = str(seat?.role);
if (!seat || !role) return null;
return {
role,
reason: str(seat.reason),
origin: str(seat.origin),
state: SEAT_STATES.find((state) => state === seat.state) ?? 'seated',
seated_at: str(seat.seated_at),
started_at: str(seat.started_at),
done_at: str(seat.done_at),
budget_tokens: num(seat.budget_tokens),
clamped: typeof seat.clamped === 'boolean' ? seat.clamped : null,
tokens_estimate: num(seat.tokens_estimate),
tokens: rec(seat.tokens) ? readTokens(seat.tokens) : null,
tokens_exact: seat.tokens_exact === true && rec(seat.tokens) !== null,
findings: num(seat.findings),
late: seat.late === true,
track_record: readTrackRecord(seat.track_record),
};
}
function readChief(value: unknown): PaqadChief | null {
const seat = readSeat(value);
return seat ? { ...seat, verdict: str(rec(value)?.verdict) } : null;
}
function each<T>(value: unknown, pick: (entry: Rec) => T): T[] {
return list(value).flatMap((entry) => {
const record = rec(entry);
return record ? [pick(record)] : [];
});
}
function readTable(value: unknown): PaqadHighTable | null {
const table = rec(value);
if (!table) return null;
const rulings = rec(table.rulings);
const questions = rec(table.questions);
return {
skipped: table.skipped === true,
met_after_freeze: table.met_after_freeze === true,
seats: list(table.seats).flatMap((seat) => readSeat(seat) ?? []),
chief: readChief(table.chief),
rulings: rulings
? {
accepted: strings(rulings.accepted),
declined: each(rulings.declined, (e) => ({
id: str(e.id) ?? '',
reason: str(e.reason) ?? '',
})),
conflicts: each(rulings.conflicts, (e) => ({
target: str(e.target) ?? '',
recommendation: str(e.recommendation) ?? '',
rationale: str(e.rationale) ?? '',
})),
merged: each(rulings.merged, (e) => ({
target: str(e.target) ?? '',
merged_claim: str(e.merged_claim) ?? '',
rationale: str(e.rationale) ?? '',
})),
renames: each(rulings.renames, (e) => ({
id: str(e.id) ?? '',
target: str(e.target) ?? '',
})),
settled: each(rulings.settled, (e) => ({
target: str(e.target) ?? '',
chosen: str(e.chosen) ?? '',
source: str(e.source) ?? '',
})),
}
: null,
gaps: each(table.gaps, (e) => ({
area: str(e.area) ?? '',
why_it_matters: str(e.why_it_matters) ?? '',
question: str(e.question),
})),
questions: questions
? {
asked: num(questions.asked) ?? 0,
auto_answered: each(questions.auto_answered, (e) => ({
question: str(e.question) ?? '',
source: str(e.source) ?? '',
})),
}
: null,
credits: each(table.credits, (e) => ({ line: str(e.line) ?? '', roles: strings(e.roles) })),
};
}
function readStep(value: unknown): string | null {
if (value === null || value === undefined) return null;
const step = rec(value);
return str(value) ?? str(step?.state) ?? str(step?.status) ?? 'recorded';
}
function readPr(value: unknown): string | null {
const pr = rec(value);
const number = num(pr?.number);
if (number === null) return readStep(value);
return `${pr?.draft === true ? 'draft ' : ''}#${number}`;
}
function readCi(value: unknown): string | null {
const ci = rec(value);
const total = num(ci?.total);
if (ci?.state === 'pending' && total !== null) return `${num(ci.done) ?? 0} of ${total} done`;
return readStep(value);
}
function readCiDetail(value: unknown): PaqadCi | null {
const ci = rec(value);
const state = str(ci?.state);
if (!ci || !state) return null;
return {
state,
done: num(ci.done),
total: num(ci.total),
reason: str(ci.reason),
jobs: each(ci.jobs, (job) => ({
name: str(job.name) ?? 'check',
status: str(job.status),
conclusion: str(job.conclusion),
started_at: str(job.started_at),
completed_at: str(job.completed_at),
})),
};
}
const SIGNOFF_STATES: readonly PaqadSignoffState[] = [
'pending',
'writing',
'awaiting_sign_off',
'approved',
'changes_requested',
'skipped',
'not_posted',
];
export function readSignoff(value: unknown): PaqadSignoff | null {
const signoff = rec(value);
const state = signoff?.state;
if (!signoff || !SIGNOFF_STATES.includes(state as PaqadSignoffState)) return null;
return {
state: state as PaqadSignoffState,
ref: str(signoff.ref),
posted_at: str(signoff.posted_at),
checked_at: str(signoff.checked_at),
link: str(signoff.link),
};
}
export function readDiff(
value: unknown,
): { additions: number; deletions: number; partial: boolean } | null {
const diff = rec(value);
const additions = num(diff?.additions);
const deletions = num(diff?.deletions);
if (additions === null || deletions === null) return null;
return { additions, deletions, partial: diff?.partial === true };
}
function readShipCommits(value: unknown): PaqadShipCommit[] {
return each(value, (commit) => ({
sha: str(commit.sha) ?? '',
subject: str(commit.subject) ?? '',
own: typeof commit.own === 'boolean' ? commit.own : null,
})).filter((commit) => commit.sha !== '');
}
const PR_STATES: readonly PaqadPrState[] = ['open', 'merged', 'closed'];
function readPrState(value: unknown): PaqadPrState | null {
return PR_STATES.find((state) => state === value) ?? null;
}
function readScope(value: unknown): PaqadScope {
const scope = rec(value);
return {
qualified: scope?.qualified === true,
reason: str(scope?.reason) ?? 'no scope recorded',
};
}
function readWorkflow(value: unknown): PaqadWorkflow | null {
const workflow = rec(value);
const name = str(workflow?.name);
if (!workflow || !name) return null;
return {
name,
started_at: str(workflow.started_at),
tokens: num(workflow.tokens),
usd: num(workflow.usd),
unpriced: strings(workflow.unpriced),
};
}
const GATE_STRENGTHS: readonly PaqadGateStrength[] = ['deterministic', 'llm-judged'];
function readGate(value: unknown): PaqadGate | null {
const gate = rec(value);
const name = str(gate?.name);
if (!gate || !name) return null;
const verdict = str(gate.verdict) ?? 'pending';
return {
name,
verdict,
required: typeof gate.required === 'boolean' ? gate.required : verdict !== 'not-required',
strength: GATE_STRENGTHS.find((strength) => strength === gate.strength) ?? null,
...(gate.owner === 'work' || gate.owner === 'paqad-record' ? { owner: gate.owner } : {}),
};
}
const VERDICT_KINDS = ['pass', 'fail', 'inconclusive'] as const;
const RUNGS: readonly PaqadRung[] = ['done', 'committed', 'pr-open'];
function readRung(value: unknown): { rung?: PaqadRung } {
const rung = RUNGS.find((known) => known === value);
return rung ? { rung } : {};
}
function readHeadline(headline: Rec | null, tone: PaqadTone): PaqadHeadline {
const row = num(headline?.row);
const word = str(headline?.word);
const note = str(headline?.note);
const gap = str(headline?.gap);
const clockFrom = str(headline?.clock_from);
return {
text: str(headline?.text) ?? '',
tone,
...(row !== null ? { row } : {}),
...(word ? { word } : {}),
...readRung(headline?.rung),
...(note ? { note } : {}),
...(gap ? { gap } : {}),
...(clockFrom ? { clock_from: clockFrom } : {}),
};
}
function readVerdictParts(verdict: Record<string, unknown>): {
work_kind?: (typeof VERDICT_KINDS)[number];
work_reasons?: string[];
record_gaps?: string[];
} {
const workKind = VERDICT_KINDS.find((kind) => kind === verdict.work_kind);
return {
...(workKind ? { work_kind: workKind } : {}),
...(Array.isArray(verdict.work_reasons) ? { work_reasons: strings(verdict.work_reasons) } : {}),
...(Array.isArray(verdict.record_gaps) ? { record_gaps: strings(verdict.record_gaps) } : {}),
};
}
const STEP_STATES: ReadonlyArray<PaqadProofStep['state']> = ['done', 'failed', 'not-yet'];
function readProof(value: unknown): PaqadStageProof[] {
return each(value, (entry) => ({
stage: str(entry.stage) ?? '',
words: str(entry.words) ?? str(entry.stage) ?? '',
state: str(entry.state) ?? 'missing',
steps: each(entry.steps, (row) => ({
id: str(row.id) ?? '',
words: str(row.words) ?? '',
detail: str(row.detail),
state: STEP_STATES.find((state) => state === row.state) ?? 'not-yet',
})).filter((row) => row.words !== ''),
gates: each(entry.gates, (row) => ({
code: str(row.code) ?? '',
words: str(row.words) ?? '',
state: str(row.state) ?? 'not-judged',
})).filter((row) => row.words !== ''),
...readRun(entry.run),
})).filter((entry) => entry.stage !== '');
}
function readRun(value: unknown): { run?: PaqadStageRun } {
const run = rec(value);
if (!run) return {};
const line = str(run.line);
const short = str(run.short_line);
const report = str(run.report);
const recipe = str(run.recipe);
if (!line || !short || !report || !recipe) return {};
const tone = run.tone === 'fail' || run.tone === 'cau' ? run.tone : 'dim';
return {
run: {
line,
short_line: short,
report,
recipe,
tone,
runs: typeof run.runs === 'number' ? run.runs : 1,
needs_look: run.needs_look === true,
footnote: str(run.footnote),
},
};
}
function readRepairs(value: unknown): PaqadRepair[] {
return list(value).flatMap((entry) => {
const what = str(rec(entry)?.what);
return what ? [{ what, at: str(rec(entry)?.at) }] : [];
});
}
const START_SOURCES = ['start_commit', 'fork-point', 'fallback-base'] as const;
function readStartPoint(value: unknown): { start_point?: PaqadStartPoint } {
const start = rec(value);
const source = START_SOURCES.find((known) => known === start?.source);
if (!start || !source) return {};
return {
start_point: { source, base: str(start.base), files_known: start.files_known !== false },
};
}
function readClaim(value: unknown): PaqadClaim | null {
const claim = rec(value);
const kind = str(claim?.kind);
if (!claim || !kind) return null;
return {
kind,
id: str(claim.id),
label: str(claim.label) ?? kind,
verdict: str(claim.verdict) ?? 'inconclusive',
test_id: str(claim.test_id),
};
}
const TIMELINE_TEXT = [
'agent',
'verdict',
'title',
'issue',
'lane',
'sha',
'subject',
'state',
'decision',
] as const;
const TIMELINE_COUNTS = [
'duration_ms',
'count',
'picked',
'standing',
'findings',
'number',
'requirements',
'criteria',
] as const;
function readTimelineEntry(entry: Record<string, unknown>): PaqadTimelineEntry {
const read: PaqadTimelineEntry = {
at: str(entry.at) ?? '',
kind: str(entry.kind) ?? 'unknown',
stage: str(entry.stage),
note: str(entry.note),
};
for (const key of TIMELINE_TEXT) {
const value = str(entry[key]);
if (value !== null) read[key] = value;
}
for (const key of TIMELINE_COUNTS) {
const value = num(entry[key]);
if (value !== null) read[key] = value;
}
if (typeof entry.own_agent === 'boolean') read.own_agent = entry.own_agent;
return read;
}
function readDeliveryPlan(value: unknown): PaqadDeliveryPlan | null {
const plan = rec(value);
if (!plan) return null;
const { commit, push, pr, ci, merge } = plan;
if (![commit, push, pr, ci, merge].every((step) => typeof step === 'boolean')) return null;
return {
commit: commit === true,
push: push === true,
pr: pr === true,
ci: ci === true,
merge: merge === true,
reason: str(plan.reason),
};
}
export const STAGE_TIMELINE_KINDS: ReadonlySet<string> = new Set([
'open',
'close',
'stage_start',
'stage_end',
]);
export function fixProofClaims(snapshot: PaqadHudSnapshot): PaqadClaim[] {
return snapshot.claims.filter((claim) => claim.kind === 'fix-proof');
}
function readPlan(value: unknown): PaqadPlanProgress | null {
const plan = rec(value);
const steps = num(plan?.steps);
const done = num(plan?.done);
return steps !== null && steps > 0 && done !== null ? { steps, done } : null;
}
function readBundleFiles(value: unknown): string[] {
return each(value, (entry) => (entry.present === true ? str(entry.file) : null)).filter(
(file): file is string => file !== null,
);
}
function readCommitGroup(value: unknown): PaqadCommitGroup | null {
const group = rec(value);
if (!group) return null;
return {
count: num(group.count) ?? 0,
tickets: strings(group.tickets),
merges: num(group.merges) ?? 0,
untagged: num(group.untagged) ?? 0,
};
}
function readClosedChange(value: unknown): Pick<PaqadHudSnapshot, 'closed_change'> {
const closed = rec(value);
const at = str(closed?.closed_at);
if (!closed || !at) return {};
return {
closed_change: {
closed_at: at,
word: str(closed.word),
new_commits: num(closed.new_commits) ?? 0,
},
};
}
export function parseSnapshot(text: string | null | undefined): PaqadHudSnapshot | null {
if (typeof text !== 'string' || text.trim() === '') return null;
let raw: unknown;
try {
raw = JSON.parse(text);
} catch {
return null;
}
const snap = rec(raw);
if (!snap || typeof snap.schema !== 'string' || !snap.schema.startsWith('paqad.hud/')) {
return null;
}
const headline = rec(snap.headline);
const tone = TONES.find((t) => t === headline?.tone) ?? 'neutral';
const change = rec(snap.change);
const rules = rec(snap.rules);
const findings = rec(snap.findings);
const decisions = rec(snap.decisions);
const ship = rec(snap.ship);
const verdict = rec(snap.verdict);
const kind = verdict?.kind;
const lastBlock = rec(snap.last_block);
const writtenAt = str(verdict?.written_at);
const receiptFile = str(rec(snap.receipt)?.path);
const scope = readScope(snap.scope);
const parsed: PaqadHudSnapshot = {
session_id: str(snap.session_id) ?? '',
generated_at: str(snap.generated_at) ?? '',
scope,
workflow: readWorkflow(snap.workflow),
active: snap.active === true && scope.qualified,
route: str(snap.route),
lane: str(snap.lane),
...(Array.isArray(snap.lane_stages)
? { lane_stages: list(snap.lane_stages).filter((s): s is string => typeof s === 'string') }
: {}),
lane_reason: str(snap.lane_reason),
headline: readHeadline(headline, tone),
change: change
? {
dir: str(change.dir),
id: str(change.id),
issue: str(change.issue),
title: str(change.title),
branch: str(change.branch),
}
: null,
paused: list(snap.paused).map((entry) => ({
workflow: str(rec(entry)?.workflow) ?? 'unknown',
lane: str(rec(entry)?.lane),
})),
stages: list(snap.stages).flatMap((entry): PaqadHudStage[] => {
const stage = rec(entry);
const name = str(stage?.stage);
if (!stage || !name) return [];
return [
{
stage: name,
state: str(stage.state) ?? 'missing',
mandatory: stage.mandatory === true,
started_at: str(stage.started_at),
ended_at: str(stage.ended_at),
...(stage.first_started_at !== undefined
? { first_started_at: str(stage.first_started_at) }
: {}),
...(stage.last_ended_at !== undefined ? { last_ended_at: str(stage.last_ended_at) } : {}),
...(num(stage.runs) !== null ? { runs: num(stage.runs)! } : {}),
...(stage.record_gap === true ? { record_gap: true } : {}),
...(stage.no_report === true ? { no_report: true } : {}),
duration_unreliable: stage.duration_unreliable === true,
duration_ms: num(stage.duration_ms),
agent: str(stage.agent),
},
];
}),
timeline: each(snap.timeline, readTimelineEntry).filter((entry) => entry.at !== ''),
table: readTable(snap.table),
gates: list(snap.gates).flatMap((entry) => readGate(entry) ?? []),
gates_expected: strings(snap.gates_expected),
claims: list(snap.claims).flatMap((entry) => readClaim(entry) ?? []),
bundle_files: readBundleFiles(snap.evidence_expected),
flags: list(snap.flags).flatMap((entry) => {
const flag = rec(entry);
const id = str(flag?.id);
return id ? [{ id, text: str(flag?.text) ?? id }] : [];
}),
rules: {
recorded: rules?.recorded === true,
applicable: num(rules?.applicable),
loaded_at: str(rules?.loaded_at),
},
findings: findings
? { rules: readScan(findings.rules), near_copies: readScan(findings.near_copies) }
: null,
decisions: {
pending: list(decisions?.pending).map((entry) => ({
id: str(rec(entry)?.id) ?? 'unknown',
category: str(rec(entry)?.category) ?? 'unknown',
question: str(rec(entry)?.question) ?? '',
})),
resolved: num(decisions?.resolved) ?? 0,
},
last_block:
snap.last_block === null || snap.last_block === undefined
? null
: readLastBlock(snap.last_block, lastBlock),
delivery_plan: readDeliveryPlan(snap.delivery_plan),
plan: readPlan(snap.plan),
ship: ship
? {
commits: num(ship.commits) ?? 0,
commit_decision: str(ship.commit_decision),
merge_commit: str(ship.merge_commit),
push: ship.push_current === false ? null : readStep(ship.push),
pr: readPr(ship.pr),
ci: readCi(ship.ci),
push_branch: str(rec(ship.push)?.branch),
pr_url: str(rec(ship.pr)?.url),
ci_detail: readCiDetail(ship.ci),
own: readCommitGroup(ship.own),
inherited: readCommitGroup(ship.inherited),
commit_list: readShipCommits(ship.commit_list),
inherited_count: num(ship.inherited_count) ?? 0,
pr_number: num(rec(ship.pr)?.number),
pr_state: readPrState(ship.pr_state),
pr_head: str(rec(ship.pr)?.head_sha),
pr_title: str(ship.pr_title),
pr_files: num(ship.pr_files),
pr_additions: num(ship.pr_additions),
pr_deletions: num(ship.pr_deletions),
diff: readDiff(ship.diff),
files_changed: num(ship.files_changed),
ci_read_at: str(ship.ci_read_at),
all_green: ship.all_green === true,
merged: ship.merged === true,
}
: null,
verdict:
verdict && (kind === 'pass' || kind === 'fail' || kind === 'inconclusive')
? {
word: str(verdict.word) ?? VERDICT_WORDS[kind],
kind,
reasons: list(verdict.reasons).filter((r): r is string => typeof r === 'string'),
...(writtenAt ? { at: writtenAt } : {}),
...readVerdictParts(verdict),
}
: null,
receipt: receiptFile ? { path: receiptFile } : null,
cost: readCost(snap.cost),
signoff: readSignoff(snap.signoff),
...(Array.isArray(snap.record_gaps) ? { record_gaps: strings(snap.record_gaps) } : {}),
...(Array.isArray(snap.repairs) ? { repairs: readRepairs(snap.repairs) } : {}),
...readRung(snap.rung),
...(snap.verdict_stale === true ? { verdict_stale: true as const } : {}),
...readStartPoint(snap.start_point),
...(Array.isArray(snap.proof) ? { proof: readProof(snap.proof) } : {}),
...(typeof snap.ledger_note === 'string' ? { ledger_note: snap.ledger_note } : {}),
...readClosedChange(snap.closed_change),
};
return scope.qualified ? parsed : idleSnapshot(parsed);
}
function idleSnapshot(snapshot: PaqadHudSnapshot): PaqadHudSnapshot {
return {
...snapshot,
active: false,
change: null,
stages: [],
timeline: [],
table: null,
gates: [],
claims: [],
bundle_files: [],
flags: [],
findings: null,
decisions: { pending: [], resolved: 0 },
plan: null,
ship: null,
verdict: null,
receipt: null,
cost: null,
signoff: null,
record_gaps: undefined,
repairs: undefined,
rung: undefined,
verdict_stale: undefined,
start_point: undefined,
proof: undefined,
};
}
export const VERDICT_WORDS = {
pass: 'Safe to merge',
fail: 'Needs your attention',
inconclusive: 'Inconclusive',
} as const;
export const BAND_STATES = [
'Ready',
'Working',
'Branch warning',
'Waiting for your answer',
'Inconclusive',
'Safe to merge',
'Needs your attention',
'Paused',
] as const;
export type BandState = string;
const STATE_TONE: Record<(typeof BAND_STATES)[number], PaqadTone> = {
Ready: 'neutral',
Working: 'working',
'Branch warning': 'warn',
'Waiting for your answer': 'needs-you',
Inconclusive: 'warn',
'Safe to merge': 'good',
'Needs your attention': 'bad',
Paused: 'neutral',
};
function isBranchFlag(flag: { id: string }): boolean {
return flag.id.startsWith('branch');
}
export function bandState(
snapshot: PaqadHudSnapshot | null,
live: LiveState = IDLE_LIVE,
): BandState {
if (!snapshot) return 'Ready';
if (waitingOnHuman(snapshot, live)) return 'Waiting for your answer';
if (!snapshot.active) {
if (snapshot.paused.length > 0) return 'Paused';
if (snapshot.route && snapshot.route !== 'no-workflow') return 'Working';
return 'Ready';
}
if (judgedHeadline(snapshot)) return snapshot.headline.word ?? 'Working';
if (snapshot.flags.some(isBranchFlag)) return 'Branch warning';
if (snapshot.stages.some((stage) => stage.state === 'running')) return 'Working';
if (snapshot.verdict && !verdictFromEarlierTurn(snapshot, live)) {
return VERDICT_WORDS[snapshot.verdict.kind];
}
return 'Working';
}
export function bandTone(state: BandState, flags: number): PaqadTone {
const tone = STATE_TONE[state as (typeof BAND_STATES)[number]] ?? 'neutral';
if (flags > 0 && (tone === 'good' || tone === 'working' || tone === 'neutral')) return 'warn';
return tone;
}
export const STAGE_ORDER = [
['ticket_intake', 'Intake'],
['planning', 'Planning'],
['specification', 'Spec'],
['development', 'Build'],
['review', 'Review'],
['checks', 'Checks'],
['documentation_sync', 'Docs'],
['delivery', 'Deliver'],
] as const;
export function stageLabel(stage: string): string {
if (stage === 'orchestrator') return 'Between stages';
return STAGE_ORDER.find(([id]) => id === stage)?.[1] ?? stage;
}
export type SegmentState = 'done' | 'running' | 'marked' | 'failed' | 'not required' | 'not yet';
export interface StageSegment {
stage: string;
label: string;
state: SegmentState;
started_at: string | null;
}
function checksBacked(snapshot: PaqadHudSnapshot): boolean {
return snapshot.gates.some((gate) => gate.name === 'code-tests-lint' && gate.verdict === 'pass');
}
const MANDATORY_STAGE_IDS = [
'planning',
'specification',
'development',
'review',
'checks',
'documentation_sync',
];
function outsideLane(stage: string, snapshot: PaqadHudSnapshot): boolean {
if (!MANDATORY_STAGE_IDS.includes(stage)) return false;
if (snapshot.lane_stages) return !snapshot.lane_stages.includes(stage);
return stage === 'specification' && snapshot.lane === 'fast';
}
function segmentState(stage: PaqadHudStage | undefined, snapshot: PaqadHudSnapshot): SegmentState {
if (!stage) return NOT_YET;
if (
(stage.record_gap || stage.no_report) &&
stage.state !== 'running' &&
stage.state !== 'failed'
)
return 'marked';
switch (stage.state) {
case 'complete':
case 'redone':
if (stage.duration_unreliable) return 'marked';
if (stage.stage === 'checks' && !checksBacked(snapshot)) return 'marked';
return 'done';
case 'running':
return 'running';
case 'failed':
return 'failed';
case 'inconclusive':
return 'marked';
case 'skipped':
case 'not-applicable':
return 'not required';
default:
if (outsideLane(stage.stage, snapshot)) return 'not required';
return NOT_YET;
}
}
export function stageSegments(snapshot: PaqadHudSnapshot | null): StageSegment[] {
return STAGE_ORDER.map(([stage, label]) => {
const record = snapshot?.stages.find((entry) => entry.stage === stage);
return {
stage,
label,
state: snapshot && snapshot.active ? segmentState(record, snapshot) : NOT_YET,
started_at: record?.started_at ?? null,
};
});
}
export { formatClock };
export function spanText(ms: number): string {
const minutes = Math.floor(Math.max(0, ms) / 60_000);
const hours = Math.floor(minutes / 60);
return hours > 0 ? `${hours} h ${minutes % 60} min` : `${minutes} min`;
}
export interface StageRuns {
runs: string;
span: string | null;
}
export function stageRuns(stage: PaqadHudStage | undefined, now: number | null): StageRuns | null {
const runs = stage?.runs ?? 1;
if (!stage || runs <= 1) return null;
const first = Date.parse(stage.first_started_at ?? stage.started_at ?? '');
const last =
stage.state === 'running' ? (now ?? Number.NaN) : Date.parse(stage.last_ended_at ?? '');
const span = Number.isFinite(first) && Number.isFinite(last) ? spanText(last - first) : null;
return { runs: `${runs} runs`, span };
}
export function liveClock(segments: StageSegment[], now: number): string | null {
const running = segments.find((segment) => segment.state === 'running');
const started = running?.started_at ? Date.parse(running.started_at) : Number.NaN;
return Number.isFinite(started) ? formatClock(now - started) : null;
}
export interface BandView {
state: BandState;
tone: PaqadTone;
headline: string;
note: string | null;
gap: string | null;
activity: string[];
flags: number;
stages: StageSegment[];
clock: string | null;
cost: string;
table: TableInstrument | null;
}
export function headlineAfterState(view: Pick<BandView, 'state' | 'headline'>): string {
return view.headline.startsWith(view.state)
? view.headline.slice(view.state.length).replace(/^[.:]\s*/, '')
: view.headline;
}
export function flagPill(flags: number): string | null {
return flags > 0 ? `${flags} flag${flags === 1 ? '' : 's'}` : null;
}
export { costPill };
export function buildBandView(
snapshot: PaqadHudSnapshot | null,
now: number,
live: LiveState = IDLE_LIVE,
): BandView {
const state = bandState(snapshot, live);
const flags = snapshot?.flags.length ?? 0;
const stages = stageSegments(snapshot);
const head = snapshot ? snapshotHeadline(snapshot, now, live) : null;
const judged = judgedHeadline(snapshot) || live.waitingOn !== null || head?.row === SUBAGENT_ROW;
return {
state,
tone: judged && head ? head.tone : bandTone(state, flags),
headline: head ? head.text : NO_RECORD,
note: head?.note ?? null,
gap: head?.gap ?? null,
activity: head?.activity ?? [],
flags,
stages,
clock: liveClock(stages, now),
cost: costPill(snapshot?.cost ?? null),
table: tableInstrument(snapshot),
};
}
export function isLive(snapshot: PaqadHudSnapshot | null): boolean {
return snapshot?.active === true && snapshot.stages.some((stage) => stage.state === 'running');
}
export function isStageAgent(agent: { type?: unknown; status?: unknown }): boolean {
return (
typeof agent.type === 'string' &&
/^(paqad:)?paqad-/.test(agent.type) &&
(agent.status === 'running' || agent.status === 'pending' || agent.status === 'waiting')
);
}
export function isRefreshTool(tool: string, input: { command?: unknown }): boolean {
if (tool === 'Edit' || tool === 'Write' || tool === 'NotebookEdit') return true;
if (tool === 'Agent' || tool === 'Task') return true;
return tool === 'Bash' && typeof input.command === 'string' && input.command.includes('paqad-ai');
}