fix(cli): add --full to rig workflow trace so a long trail comes back complete (#2656) (#2667)

rig workflow trace gains --full, which passes ?full=true to the daemon's existing GET /api/workflow/<id>/trace route; there is no new daemon route. Without the flag the request has no query, as before. --json prints the daemon payload unchanged. Human output adds one line naming the --full command when the trail came back with trailCompleteness recent. docs/as-built/cli-reference.md lists the option.
This commit is contained in:
billbenetti authored and GitHub committed 2026-11-07 05:04:32 -08:00
1 parent 642be39e5b
commit a34cb55ad7
3 files changed
+63 -4

No files matched your search

+1 -1
View File
@@ -600,7 +600,7 @@ Herdr session per request, including session-bound previews and window reuse.
| `rig workflow specs` | — | `--json` |
| `rig workflow guidance <instanceId>` | — | `--packet <id>`<br>`--component <id>`<br>`--full`<br>`--json` |
| `rig workflow show <instanceId>` | — | `--json` |
| `rig workflow trace <instanceId>` | — | `--json` |
| `rig workflow trace <instanceId>` | — | `--full`<br>`--json` |
| `rig workflow continue <instanceId>` | — | `--json` |
| `rig workflow run <specPath>` | — | `--root-objective <text>` **required**<br>`--created-by <session>` **required**<br>`--entry-owner <session>`<br>`--rig <name>`<br>`--project <id>`<br>`--mission <id>`<br>`--slice <id>`<br>`--tags <tags>`<br>`--json` |
| `rig workflow watch <instanceId>` | — | `--json` |
+11 -3
View File
@@ -639,17 +639,22 @@ exception routes only. Existing obligations keep their owners and evidence.
cmd
.command("trace <instanceId>")
.description("Show one workflow instance + its append-only step trail (audit-only verdict)")
.option("--full", "Return the complete step trail instead of the 50 most recent closures (trailCompleteness: complete)")
.option("--json", "JSON output for agents")
.addHelpText("after", `
The default trail holds the 50 most recent closures; trailCompleteness says
"recent" when older ones were left out. --full asks the daemon for all of them.
Examples:
$ rig workflow trace WF01ABC
$ rig workflow trace WF01ABC --json | jq '.trail[] | {step, actor, exit}'
$ rig workflow trace WF01ABC --full --json | jq '.trailCompleteness, (.trail | length)'
`)
.action(async (instanceId: string, opts: { json?: boolean }) => {
.action(async (instanceId: string, opts: { json?: boolean; full?: boolean }) => {
const deps = getDeps();
await withClient(deps, async (client) => {
const res = await client.get<{ instance?: unknown; reconciliation?: unknown; trail?: unknown[]; frontier?: unknown[]; failures?: unknown[]; boundaryObligations?: unknown[]; unknowns?: string[] }>(
`/api/workflow/${encodeURIComponent(instanceId)}/trace`,
const res = await client.get<{ instance?: unknown; reconciliation?: unknown; trail?: unknown[]; trailCompleteness?: string; frontier?: unknown[]; failures?: unknown[]; boundaryObligations?: unknown[]; unknowns?: string[] }>(
`/api/workflow/${encodeURIComponent(instanceId)}/trace${opts.full ? "?full=true" : ""}`,
);
// WF3 FR-2: human mode renders the per-step tree (mini-req 2's
// one-screen bar); --json byte-identical (BR-2).
@@ -667,6 +672,9 @@ Examples:
} as Parameters<typeof renderTraceTree>[0];
const trail = (res.data.trail ?? []) as Parameters<typeof renderTraceTree>[1];
for (const line of renderTraceTree(instance, trail)) console.log(line);
if (res.data.trailCompleteness === "recent") {
console.log(` (the ${trail.length} most recent closures; older ones left out - all of them: rig workflow trace ${instanceId} --full)`);
}
});
});
+51
View File
@@ -331,6 +331,57 @@ describe("rig workflow CLI (PL-004 Phase D)", () => {
expect(calls.find((c) => c.path === "/api/workflow/inst-1/trace")).toBeDefined();
});
it("trace without --full keeps the daemon's default (no ?full=true)", async () => {
const { deps, calls } = makeDeps({
routes: { "GET /api/workflow/inst-1/trace": { status: 200, data: { instance: {}, trail: [], trailCompleteness: "recent" } } },
});
const program = createProgram({ workflowDeps: deps });
program.exitOverride();
await program.parseAsync(["node", "rig", "workflow", "trace", "inst-1", "--json"]);
expect(calls.map((c) => c.path)).toEqual(["/api/workflow/inst-1/trace"]);
});
it("trace --full passes ?full=true through and prints the complete trail", async () => {
const trail = Array.from({ length: 52 }, (_, i) => ({ stepId: `s${i}`, actorSession: "a@r", closureReason: "done" }));
const { deps, calls } = makeDeps({
routes: { "GET /api/workflow/inst-1/trace?full=true": { status: 200, data: { instance: {}, trail, trailCompleteness: "complete" } } },
});
const logs: string[] = [];
const spy = vi.spyOn(console, "log").mockImplementation((...a: unknown[]) => { logs.push(a.join(" ")); });
try {
const program = createProgram({ workflowDeps: deps });
program.exitOverride();
await program.parseAsync(["node", "rig", "workflow", "trace", "inst-1", "--full", "--json"]);
} finally {
spy.mockRestore();
}
expect(calls.map((c) => c.path)).toEqual(["/api/workflow/inst-1/trace?full=true"]);
const out = JSON.parse(logs.join("\n")) as { trail: unknown[]; trailCompleteness: string };
expect(out.trailCompleteness).toBe("complete");
expect(out.trail).toHaveLength(52);
});
it("trace in human mode says when the trail was cut and how to get all of it", async () => {
const { deps } = makeDeps({
routes: {
"GET /api/workflow/inst-1/trace": {
status: 200,
data: { instance: { instanceId: "inst-1", status: "active" }, trail: [], trailCompleteness: "recent" },
},
},
});
const logs: string[] = [];
const spy = vi.spyOn(console, "log").mockImplementation((...a: unknown[]) => { logs.push(a.join(" ")); });
try {
const program = createProgram({ workflowDeps: deps });
program.exitOverride();
await program.parseAsync(["node", "rig", "workflow", "trace", "inst-1"]);
} finally {
spy.mockRestore();
}
expect(logs.join("\n")).toContain("rig workflow trace inst-1 --full");
});
it("continue POSTs /api/workflow/:instanceId/continue", async () => {
const { deps, calls } = makeDeps({
routes: { "POST /api/workflow/inst-1/continue": { status: 200, data: { instance: {}, trail: [] } } },