@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
757 lines (734 loc) • 30.5 kB
text/typescript
import { randomUUID } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { basename, join, resolve } from "node:path";
import { stringify } from "yaml";
import { openReadOnlyDatabase } from "../sqlite.ts";
import { buildRetrospective, writeRetrospective } from "../retrospective.ts";
import { redactSecrets } from "../redact.ts";
import { postWorkflowSignal, watchGithubChecks } from "../github-watch.ts";
import {
listWorkflowDefinitions,
addWorkflowDefinition,
removeWorkflowDefinition,
modifyWorkflowDefinition,
scaffoldWorkflowDefinition,
WORKFLOW_TEMPLATES,
} from "../workflow-manager.ts";
import {
canonicalWorkflowEvidenceKey,
parseWorkflowDefinitions,
workflowWebhookHeaders,
type WorkflowEvidenceInput,
type WorkflowJournalEntry,
type WorkflowRun,
} from "../workflow.ts";
import { discoverKxmProjectRoot, kxmWorkflowWriteIssues, parseRestrictedYaml } from "../project-config.ts";
import { ensureKxmSupervisor, kxmRuntimeRequest } from "../runtime-supervisor.ts";
import { projectRuntimeOwnsRun } from "../runtime-store.ts";
import type { WorkerOutcome } from "../envelope.ts";
import {
print,
printPlan,
printWorker,
gateOf,
parseEvidencePairs,
maskEnvName,
type CliIo,
type Runtime,
} from "./types.ts";
import { resolvePickItem, type PickCandidate } from "./roles.ts";
import { CloudTokenError } from "../hub-binding.ts";
import { resolveControlPlaneAuthToken } from "../hub-env.ts";
const CLI_NAME = "kxm";
export function localWorkflowSnapshot(dataPath: string, runId?: string): { runs: WorkflowRun[]; journal: WorkflowJournalEntry[] } {
if (!existsSync(dataPath)) throw new Error("state_database_not_found");
const database = openReadOnlyDatabase(dataPath);
try {
const rows = runId
? database.prepare("SELECT record FROM workflow_runs WHERE id = ?").all(runId)
: database.prepare("SELECT record FROM workflow_runs ORDER BY rowid DESC LIMIT 200").all();
const runs = (rows as Array<{ record: string }>).map((row) => JSON.parse(row.record) as WorkflowRun);
const journal = runId
? (database.prepare("SELECT record FROM workflow_journal WHERE run_id = ? ORDER BY rowid").all(runId) as Array<{ record: string }>).map((row) => JSON.parse(row.record) as WorkflowJournalEntry)
: [];
return { runs, journal };
} finally {
database.close();
}
}
export async function postWorkflowStart(input: {
serverUrl: string;
definitionId: string;
secret: string;
deliveryId: string;
event?: string | undefined;
payload: Record<string, unknown>;
fetchImpl: typeof fetch;
}): Promise<{ status: number; runId?: string | undefined; duplicate: boolean }> {
const payload = input.event && input.payload["event"] === undefined
? { ...input.payload, event: input.event }
: input.payload;
const body = JSON.stringify(payload);
const response = await input.fetchImpl(`${input.serverUrl.replace(/\/$/, "")}/v1/webhooks/${encodeURIComponent(input.definitionId)}`, {
method: "POST",
headers: {
"content-type": "application/json",
...workflowWebhookHeaders({
secret: input.secret,
scope: { definitionId: input.definitionId },
deliveryId: input.deliveryId,
body,
}),
...(input.event ? { "x-github-event": input.event } : {}),
},
body,
});
const responseText = (await response.text()).slice(0, 8_000);
let parsed: { runId?: string; duplicate?: boolean } = {};
try {
parsed = JSON.parse(responseText);
} catch {
// fallback
}
if (!response.ok) throw new Error(`workflow_start_http_${response.status}`);
return {
status: response.status,
...(parsed.runId ? { runId: parsed.runId } : {}),
duplicate: parsed.duplicate === true,
};
}
export async function postWorkflowDegradation(input: {
serverUrl: string;
authToken: string;
runId: string;
stageId: string;
requirementKey: string;
reason: string;
fetchImpl: typeof fetch;
}): Promise<{ status: number; duplicate: boolean; approvalId?: string | undefined }> {
const response = await input.fetchImpl(
`${input.serverUrl.replace(/\/$/, "")}/v1/workflows/${encodeURIComponent(input.runId)}/degradations`,
{
method: "POST",
headers: {
authorization: `Bearer ${input.authToken}`,
"content-type": "application/json",
},
body: JSON.stringify({
stageId: input.stageId,
requirementKey: input.requirementKey,
reason: input.reason,
}),
},
);
const responseText = (await response.text()).slice(0, 8_000);
let parsed: { duplicate?: boolean; approval?: { id?: string } } = {};
try {
parsed = JSON.parse(responseText);
} catch {
// fallback
}
if (!response.ok) throw new Error(`workflow_degradation_http_${response.status}`);
return {
status: response.status,
duplicate: parsed.duplicate === true,
...(parsed.approval?.id ? { approvalId: parsed.approval.id } : {}),
};
}
export function activeWorkflowDefinition(runtime: Runtime, definitionId: string) {
const inline = runtime.env.KXM_WEBHOOK_WORKFLOWS?.trim();
const file = runtime.env.KXM_WEBHOOK_WORKFLOWS_FILE?.trim();
if (inline && file) {
throw new Error("configure only one of KXM_WEBHOOK_WORKFLOWS or KXM_WEBHOOK_WORKFLOWS_FILE");
}
if (!inline && !file) return undefined;
let raw: string;
if (file) {
try {
raw = readFileSync(resolve(runtime.cwd, file), "utf8");
} catch {
throw new Error("workflow definition file is unavailable");
}
} else {
raw = inline!;
}
const definition = parseWorkflowDefinitions(raw, runtime.env).find((candidate) => candidate.id === definitionId);
if (!definition) throw new Error(`workflow definition not found: ${definitionId}`);
return definition;
}
export function workflowCredential(runtime: Runtime, definitionId: string, kind: "start" | "signal"): string | undefined {
const definition = activeWorkflowDefinition(runtime, definitionId);
if (definition) return kind === "start" ? definition.secret : definition.signalSecret ?? definition.secret;
return kind === "start"
? runtime.env.KXM_WORKFLOW_SECRET?.trim()
: runtime.env.KXM_WORKFLOW_SIGNAL_SECRET?.trim();
}
export function reportWorkflowConfigError(runtime: Runtime, error: unknown): number {
const message = error instanceof Error ? redactSecrets(error.message) : "invalid workflow configuration";
runtime.io.stderr(`${message}\n`);
return 2;
}
export async function cmdWorkflowDefinitions(
runtime: Runtime,
options: { scope?: "all" | "global" | "local" | undefined },
): Promise<number> {
const workflows = listWorkflowDefinitions({
scope: options.scope,
repoRoot: runtime.cwd,
userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
});
if (runtime.json) {
print(runtime.io, true, { ok: true, command: "workflow definitions", workflows }, "");
return 0;
}
if (workflows.length === 0) {
runtime.io.stdout("No workflow definitions found.\n");
return 0;
}
const lines: string[] = ["WORKFLOW DEFINITIONS:"];
for (const w of workflows) {
const scopeTag = w.scope === "overridden" ? "[local override]" : `[${w.scope}]`;
const rolesTag = w.roles.length > 0 ? `(roles: ${w.roles.join(", ")})` : "";
lines.push(` ${w.id.padEnd(24)} ${scopeTag.padEnd(16)} ${w.stepCount} steps ${rolesTag} ${w.description}`);
}
print(runtime.io, false, {}, `${lines.join("\n")}\n`);
return 0;
}
export async function cmdWorkflowAdd(
runtime: Runtime,
workflowId: string | undefined,
options: {
file?: string | undefined;
description?: string | undefined;
scope?: "global" | "local" | undefined;
overwrite?: boolean | undefined;
pick?: string | boolean | undefined;
template?: string | undefined;
},
): Promise<number> {
const scope = options.scope ?? "local";
const refuse = (error: string, message: string, code = 2): number => {
print(runtime.io, runtime.json, { ok: false, command: "workflow add", error, message }, `workflow add failed: ${message}`);
return code;
};
try {
let content: Record<string, unknown> | string | undefined;
if (options.template !== undefined) {
if (options.file !== undefined || options.pick !== undefined) {
return refuse("workflow_add_conflict", "--template cannot be combined with --file or --pick");
}
if (!workflowId) return refuse("workflow_id_required", "usage: kxm workflow add <workflowId> --template <name>");
const template = Object.hasOwn(WORKFLOW_TEMPLATES, options.template) ? WORKFLOW_TEMPLATES[options.template] : undefined;
if (!template) {
return refuse("workflow_template_unknown", `unknown template ${options.template}; choose ${Object.keys(WORKFLOW_TEMPLATES).join(", ")}`);
}
content = { ...template, ...(options.description !== undefined ? { description: options.description } : {}) };
} else if (!workflowId || options.pick) {
const candidates: PickCandidate[] = Object.entries(WORKFLOW_TEMPLATES).map(([id, tmpl]) => ({
id,
description: String(tmpl.description ?? id),
label: "template",
payload: tmpl,
}));
if (scope === "local") {
const globalDefs = listWorkflowDefinitions({ scope: "global", userConfigDir: runtime.env.KXM_USER_CONFIG_DIR });
for (const gd of globalDefs) {
// Read the listed path when selected: globals may use `.yml`, not only `.yaml`.
if (!candidates.some((c) => c.id === gd.id)) {
candidates.push({ id: gd.id, description: gd.description, label: "global", payload: gd.filePath });
}
}
}
const picked = await resolvePickItem(runtime.io, `Select a workflow template to add (${scope})`, candidates, options.pick, runtime.env);
if (!picked) {
return refuse("workflow_id_required", "provide a workflowId or select an available workflow with --pick <id>", 1);
}
workflowId ??= picked.id;
if (options.file === undefined) {
const pickedContent = typeof picked.payload === "string"
? readFileSync(picked.payload, "utf8")
: picked.payload;
content = options.description !== undefined
? {
...(typeof pickedContent === "string" ? parseRestrictedYaml(pickedContent, String(picked.payload)) : pickedContent),
description: options.description,
}
: pickedContent;
}
}
if (options.file !== undefined) {
const filePath = resolve(runtime.cwd, options.file);
content = readFileSync(filePath, "utf8");
} else if (!content) {
content = scaffoldWorkflowDefinition(options.description || `Workflow ${workflowId}`);
}
const document = typeof content === "string" ? content : stringify(content);
// A local workflow is read by the project loader, which refuses the whole
// project over one bad file, so it is checked by that loader before it lands.
let repoRoot = runtime.cwd;
if (scope === "local") {
const projectRoot = discoverKxmProjectRoot(runtime.cwd);
if (!projectRoot) {
return refuse("project_not_found", "local workflows belong to a KXM project; run kxm init at the repository root, or pass --scope global");
}
const issues = kxmWorkflowWriteIssues(projectRoot, workflowId!, document);
if (issues.length > 0) {
print(
runtime.io,
runtime.json,
{ ok: false, command: "workflow add", error: "workflow_invalid", message: "with this workflow the project would not load, so nothing was written", issues },
`workflow add failed: with this workflow the project would not load, so nothing was written\n${issues.map((entry) => ` ${entry.file}: ${entry.code}: ${entry.message}`).join("\n")}`,
);
return 2;
}
repoRoot = projectRoot;
}
const res = addWorkflowDefinition(workflowId!, document, {
scope,
repoRoot,
userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
overwrite: options.overwrite,
dryRun: runtime.dryRun,
});
if (runtime.dryRun) {
printPlan(runtime, { command: "workflow add", workflowId, ...res }, [{ action: "write", target: res.filePath }], `add workflow '${workflowId}' to ${res.scope}`);
return 0;
}
print(
runtime.io,
runtime.json,
{ ok: true, command: "workflow add", workflowId, ...res },
`Added workflow '${workflowId}' to ${res.scope} (${res.filePath})\n`,
);
return 0;
} catch (err: unknown) {
return refuse("workflow_add_failed", err instanceof Error ? err.message : String(err), 1);
}
}
export async function cmdWorkflowRemove(
runtime: Runtime,
workflowId: string | undefined,
options: { scope?: "global" | "local" | undefined; pick?: string | boolean | undefined },
): Promise<number> {
const scope = options.scope ?? "local";
if (!workflowId || options.pick) {
const workflows = listWorkflowDefinitions({
scope,
repoRoot: runtime.cwd,
userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
});
if (workflows.length === 0) {
runtime.io.stdout(`No workflow definitions found in ${scope} scope to remove.\n`);
return 0;
}
const candidates: PickCandidate[] = workflows.map((w) => ({
id: w.id,
description: w.description,
label: w.scope,
}));
const picked = await resolvePickItem(runtime.io, `Select a workflow to remove (${scope})`, candidates, options.pick, runtime.env);
if (!picked) {
if (!workflowId) {
runtime.io.stderr("workflow remove failed: missing workflowId or pick selection\n");
return 1;
}
} else {
workflowId = picked.id;
}
}
try {
const res = removeWorkflowDefinition(workflowId!, {
scope,
repoRoot: runtime.cwd,
userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
dryRun: runtime.dryRun,
});
if (runtime.dryRun) {
printPlan(runtime, { command: "workflow remove", workflowId, ...res }, [{ action: "delete", target: res.filePath }], `remove workflow '${workflowId}' from ${res.scope}`);
return 0;
}
print(
runtime.io,
runtime.json,
{ ok: true, command: "workflow remove", workflowId, ...res },
`Removed workflow '${workflowId}' from ${res.scope}\n`,
);
return 0;
} catch (err: unknown) {
runtime.io.stderr(`workflow remove failed: ${(err as Error).message}\n`);
return 1;
}
}
export async function cmdWorkflowModify(
runtime: Runtime,
workflowId: string | undefined,
options: {
description?: string | undefined;
scope?: "global" | "local" | undefined;
pick?: string | boolean | undefined;
},
): Promise<number> {
if (!workflowId || options.pick) {
const workflows = listWorkflowDefinitions({
scope: options.scope,
repoRoot: runtime.cwd,
userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
});
if (workflows.length === 0) {
runtime.io.stdout("No workflow definitions found to modify.\n");
return 0;
}
const candidates: PickCandidate[] = workflows.map((w) => ({
id: w.id,
description: w.description,
label: w.scope,
}));
const picked = await resolvePickItem(runtime.io, "Select a workflow to modify", candidates, options.pick, runtime.env);
if (!picked) {
if (!workflowId) {
runtime.io.stderr("workflow modify failed: missing workflowId or pick selection\n");
return 1;
}
} else {
workflowId = picked.id;
}
}
try {
const res = modifyWorkflowDefinition(
workflowId!,
{ ...(options.description ? { description: options.description } : {}) },
{
scope: options.scope,
repoRoot: runtime.cwd,
userConfigDir: runtime.env.KXM_USER_CONFIG_DIR,
dryRun: runtime.dryRun,
},
);
if (runtime.dryRun) {
printPlan(runtime, { command: "workflow modify", workflowId, ...res }, [{ action: "write", target: res.filePath }], `modify workflow '${workflowId}' in ${res.scope}`);
return 0;
}
print(
runtime.io,
runtime.json,
{ ok: true, command: "workflow modify", workflowId, ...res },
`Modified workflow '${workflowId}' in ${res.scope}\n`,
);
return 0;
} catch (err: unknown) {
runtime.io.stderr(`workflow modify failed: ${(err as Error).message}\n`);
return 1;
}
}
export async function cmdWorkflowStart(runtime: Runtime, definitionIdArg: string | undefined, options: { payload?: string | undefined; deliveryId?: string | undefined; event?: string | undefined }): Promise<number> {
const definitionId = definitionIdArg || runtime.env.KXM_WORKFLOW_ID?.trim();
const deliveryId = String(options.deliveryId || `cli-${randomUUID()}`);
const event = options.event;
const payloadFlag = options.payload ?? "{}";
if (!definitionId) {
runtime.io.stderr("workflow start requires <definitionId> (or KXM_WORKFLOW_ID)\n");
return 2;
}
let secret: string | undefined;
try {
secret = workflowCredential(runtime, definitionId, "start");
} catch (error) {
return reportWorkflowConfigError(runtime, error);
}
if (!secret) {
runtime.io.stderr("workflow start requires KXM_WORKFLOW_SECRET when no active definition source is configured\n");
return 2;
}
let payload: Record<string, unknown>;
try {
const raw = payloadFlag.startsWith("@") ? readFileSync(resolve(runtime.cwd, payloadFlag.slice(1)), "utf8") : payloadFlag;
const value = JSON.parse(raw) as unknown;
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("object required");
payload = value as Record<string, unknown>;
} catch {
print(runtime.io, runtime.json, { ok: false, command: "workflow start", error: "invalid_payload" }, "workflow payload must be a JSON object or @file");
return 2;
}
if (runtime.dryRun) {
print(runtime.io, runtime.json, { ok: true, command: "workflow start", dryRun: true, definitionId, deliveryId, event }, "would POST a signed workflow webhook");
return 0;
}
try {
const response = await postWorkflowStart({ serverUrl: runtime.serverUrl, definitionId, secret, deliveryId, ...(event ? { event } : {}), payload, fetchImpl: runtime.fetchImpl });
print(runtime.io, runtime.json, { ok: true, command: "workflow start", definitionId, deliveryId, ...response }, `started workflow ${response.runId ?? "accepted"}`);
return 0;
} catch {
print(runtime.io, runtime.json, { ok: false, command: "workflow start", error: "workflow_start_failed" }, "signed workflow start failed");
return 1;
}
}
export async function cmdWorkflowDegrade(runtime: Runtime, runId: string, stageId: string, options: { requirement?: string | undefined; reason?: string | undefined }): Promise<number> {
const requirementKey = options.requirement?.trim() ?? "";
const reason = options.reason?.trim() ?? "";
let adminToken: string | undefined;
try {
adminToken = resolveControlPlaneAuthToken(runtime.env);
} catch (error) {
if (error instanceof CloudTokenError) {
runtime.io.stderr(`${error.message}\n`);
return 2;
}
throw error;
}
if (!runId || !stageId || !requirementKey || !reason || !adminToken) {
runtime.io.stderr("workflow degrade requires <runId> <stageId>, --requirement, --reason, and KXM_AUTH_TOKEN\n");
return 2;
}
const worker = gateOf(runtime, "degrade");
if (runtime.dryRun) {
printWorker(
runtime,
worker,
{ ok: true, command: "workflow degrade", dryRun: true, runId, stageId, requirementKey },
`would approve configured degraded quorum for ${runId}/${stageId}/${requirementKey}`,
);
return 0;
}
try {
const result = await postWorkflowDegradation({
serverUrl: runtime.serverUrl,
authToken: adminToken,
runId,
stageId,
requirementKey,
reason,
fetchImpl: runtime.fetchImpl,
});
printWorker(
runtime,
worker,
{ ok: true, command: "workflow degrade", runId, stageId, requirementKey, ...result },
result.duplicate ? "degraded quorum was already approved" : "approved configured degraded quorum",
);
return 0;
} catch {
printWorker(
runtime,
worker,
{ ok: false, command: "workflow degrade", error: "workflow_degradation_failed", runId, stageId, requirementKey },
"workflow degradation approval failed",
);
return 1;
}
}
export async function cmdWorkflowInspect(runtime: Runtime, action: "list" | "get", runId?: string | undefined): Promise<number> {
const dataPath = resolve(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join(runtime.dirs.state, "kxm.db"));
if (action === "get" && !runId) {
runtime.io.stderr(`Usage: ${CLI_NAME} workflow get <runId>\n`);
return 2;
}
try {
const snapshot = localWorkflowSnapshot(dataPath, runId);
if (runId && snapshot.runs.length === 0) {
print(runtime.io, runtime.json, { ok: false, command: "workflow get", error: "workflow_not_found" }, "workflow not found");
return 1;
}
const value = action === "get"
? { ok: true, command: "workflow get", run: snapshot.runs[0], journal: snapshot.journal }
: { ok: true, command: "workflow list", runs: snapshot.runs };
print(runtime.io, runtime.json, value, action === "get" ? `workflow ${runId}` : `${snapshot.runs.length} workflow(s)`);
return 0;
} catch {
print(runtime.io, runtime.json, { ok: false, command: `workflow ${action}`, error: "state_unavailable" }, "local workflow state is unavailable");
return 1;
}
}
export async function cmdSignal(runtime: Runtime, runId: string, signalKey: string, status: string, summary: string, evidenceArgs: string[], deliveryIdFlag?: string | undefined, recoveryAction?: string | undefined): Promise<number> {
if (!runId || !signalKey || !status || !summary) {
runtime.io.stderr(`Usage: ${CLI_NAME} gate signal <runId> <signalKey> <passed|warning|failed> <summary> [<required-key>=<evidence> ...]\n`);
return 2;
}
if (status !== "passed" && status !== "warning" && status !== "failed") {
runtime.io.stderr("status must be passed, warning, or failed\n");
return 2;
}
let evidence: WorkflowEvidenceInput;
try {
evidence = parseEvidencePairs(evidenceArgs);
} catch (error) {
runtime.io.stderr(`${error instanceof Error ? error.message : "invalid evidence"}\n`);
return 2;
}
const worker = gateOf(runtime, "signal");
const projectRoot = discoverKxmProjectRoot(runtime.cwd);
if (projectRoot && projectRuntimeOwnsRun(projectRoot, runId, runtime.env)) {
if (runtime.dryRun) {
printWorker(runtime, worker, { ok: true, command: "signal", dryRun: true, runId, signalKey, status, summary, evidence }, "would post signal to KXM run");
return 0;
}
const deliveryId = String(deliveryIdFlag || `cli-signal:${randomUUID()}`);
try {
const supervisor = await ensureKxmSupervisor({ env: runtime.env });
const posted = await kxmRuntimeRequest(
supervisor,
"POST",
`/v1/runs/${encodeURIComponent(runId)}/signal?projectRoot=${encodeURIComponent(projectRoot)}`,
{ signalKey, status, summary, evidence, deliveryId, ...(recoveryAction ? { action: recoveryAction } : {}) },
);
printWorker(runtime, worker, { ok: true, command: "signal", runId, signalKey, status, unblocked: posted.unblocked === true, deliveryId }, "posted signal to KXM run");
return 0;
} catch (error) {
const msg = error instanceof Error ? error.message : "signal_failed";
printWorker(runtime, worker, { ok: false, command: "signal", error: "signal_failed", detail: msg, deliveryId }, "signal to KXM run failed");
return 1;
}
}
const definitionId = runtime.env.KXM_WORKFLOW_ID?.trim();
if (!definitionId) {
runtime.io.stderr("signal requires KXM_WORKFLOW_ID\n");
return 2;
}
let signalSecret: string | undefined;
try {
signalSecret = workflowCredential(runtime, definitionId, "signal");
} catch (error) {
return reportWorkflowConfigError(runtime, error);
}
if (!signalSecret) {
runtime.io.stderr("signal requires KXM_WORKFLOW_SIGNAL_SECRET when no active definition source is configured\n");
return 2;
}
if (runtime.dryRun) {
printWorker(runtime, worker, { ok: true, command: "signal", dryRun: true, runId, signalKey, status, summary, evidence }, "would post signed signal");
return 0;
}
const deliveryId = String(deliveryIdFlag || `cli-signal:${randomUUID()}`);
try {
const posted = await postWorkflowSignal({
serverUrl: runtime.serverUrl,
definitionId,
signalSecret,
runId,
signalKey,
status,
summary,
evidence,
deliveryId,
fetchImpl: runtime.fetchImpl,
});
printWorker(runtime, worker, { ok: true, command: "signal", duplicate: posted.duplicate, deliveryId }, "posted signed signal");
return 0;
} catch {
printWorker(runtime, worker, { ok: false, command: "signal", error: "signal_failed", deliveryId }, "signed signal failed");
return 1;
}
}
export async function cmdGithubWatch(runtime: Runtime, options: {
runId?: string | undefined;
stageId?: string | undefined;
signalKey?: string | undefined;
repo?: string | undefined;
pr?: string | undefined;
required?: string | undefined;
timeoutMs?: string | undefined;
intervalMs?: string | undefined;
deliveryId?: string | undefined;
}): Promise<number> {
const token = runtime.env.GITHUB_TOKEN?.trim() || runtime.env.GH_TOKEN?.trim();
const definitionId = runtime.env.KXM_WORKFLOW_ID?.trim();
const runId = String(options.runId || "");
const stageId = String(options.stageId || "");
const signalKey = String(options.signalKey || "");
const repo = String(options.repo || "");
const pr = Number(options.pr);
if (!definitionId || !runId || !stageId || !signalKey || !repo || !Number.isInteger(pr)) {
runtime.io.stderr("github watch requires KXM_WORKFLOW_ID, --run-id, --stage-id, --signal-key, --repo, --pr\n");
return 2;
}
let signalSecret: string | undefined;
try {
signalSecret = workflowCredential(runtime, definitionId, "signal");
} catch (error) {
return reportWorkflowConfigError(runtime, error);
}
if (!signalSecret) {
runtime.io.stderr("github watch requires KXM_WORKFLOW_SIGNAL_SECRET when no active definition source is configured\n");
return 2;
}
const result = await watchGithubChecks({
serverUrl: runtime.serverUrl,
definitionId,
signalSecret,
runId,
stageId,
signalKey,
repo,
pr,
required: options.required
? [...new Set(String(options.required).split(",").map((name) => name.trim()).filter(Boolean))]
: [],
timeoutMs: Number(options.timeoutMs || 1_800_000),
intervalMs: Number(options.intervalMs || 15_000),
...(options.deliveryId ? { deliveryId: options.deliveryId } : {}),
...(token ? { token } : {}),
dryRun: runtime.dryRun,
fetchImpl: runtime.fetchImpl,
...(runtime.io.now ? { now: runtime.io.now } : {}),
...(runtime.io.sleep ? { sleep: runtime.io.sleep } : {}),
});
const worker = gateOf(runtime, "github-watch");
const payload = {
ok: result.exitCode === 0,
command: "github watch",
posted: result.posted,
status: result.status,
summary: result.summary,
evidence: result.evidence,
deliveryId: result.deliveryId,
skipped: result.skipped,
};
const outcome: WorkerOutcome = result.exitCode === 0
? (result.status === "warning" ? "warning" : "passed")
: "failed";
if (JSON.stringify(payload).includes(token ?? "___never___") || Object.keys(runtime.env).some((key) => maskEnvName(key) && JSON.stringify(payload).includes(String(runtime.env[key])))) {
printWorker(runtime, worker, { ok: false, command: "github watch", error: "redaction_failure" }, "refusing to print a payload that contains a secret");
return 1;
}
printWorker(runtime, worker, payload, result.summary, outcome);
return result.exitCode;
}
export async function cmdRetrospectiveExport(runtime: Runtime, runId: string, options: { input?: string | undefined; outDir?: string | undefined }): Promise<number> {
if (!runId) {
runtime.io.stderr(`Usage: ${CLI_NAME} workflow export <runId>\n`);
return 2;
}
const snapshotFlag = String(options.input || "");
const snapshotPath = snapshotFlag ? resolve(runtime.cwd, snapshotFlag) : "";
let snapshot: { run: WorkflowRun; journal: WorkflowJournalEntry[] };
try {
if (snapshotPath) {
if (!existsSync(snapshotPath)) throw new Error("snapshot_missing");
snapshot = JSON.parse(readFileSync(snapshotPath, "utf8")) as typeof snapshot;
} else {
const dataPath = resolve(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join(runtime.dirs.state, "kxm.db"));
const local = localWorkflowSnapshot(dataPath, runId);
if (!local.runs[0]) throw new Error("workflow_not_found");
snapshot = { run: local.runs[0], journal: local.journal };
}
if (snapshot.run.id !== runId) throw new Error("run_id_mismatch");
} catch (error) {
const reason = error instanceof Error ? error.message : "snapshot_invalid";
print(runtime.io, runtime.json, { ok: false, command: "retrospective export", error: reason }, "retrospective source is invalid or unavailable");
return 1;
}
const doc = buildRetrospective(snapshot.run, snapshot.journal);
const outDir = resolve(runtime.cwd, String(options.outDir || join(runtime.dirs.assets, "retrospectives")));
const assetsRoot = resolve(runtime.dirs.assets);
const assetsPrefix = `${assetsRoot}${process.platform === "win32" ? "\\" : "/"}`;
if (outDir !== assetsRoot && !outDir.startsWith(assetsPrefix)) {
print(runtime.io, runtime.json, { ok: false, command: "retrospective export", error: "output_outside_workspace_assets" }, "retrospectives must stay under the workspace assets directory");
return 2;
}
if (runtime.dryRun) {
print(runtime.io, runtime.json, { ok: true, command: "retrospective export", dryRun: true, runId: doc.runId }, `would export ${doc.runId}`);
return 0;
}
const written = writeRetrospective(outDir, doc);
print(runtime.io, runtime.json, { ok: true, command: "retrospective export", ...written, reviewDecision: doc.reviewDecision }, `exported ${written.jsonPath}`);
return 0;
}