feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* @file stream-json-parser.test.js
|
||||
* @description Unit tests for the newline-delimited JSON line buffer used to
|
||||
* parse `claude --output-format stream-json` output. Verifies chunked input,
|
||||
* partial lines spanning chunks, malformed lines, empty input, multiple
|
||||
* objects per chunk, and flush semantics.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
const { describe, it } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { createLineParser } = require("../lib/stream-json-parser");
|
||||
|
||||
function collect() {
|
||||
const objects = [];
|
||||
const errors = [];
|
||||
const parser = createLineParser(
|
||||
(obj) => objects.push(obj),
|
||||
(err, raw) => errors.push({ message: err.message, raw })
|
||||
);
|
||||
return { parser, objects, errors };
|
||||
}
|
||||
|
||||
describe("stream-json-parser", () => {
|
||||
it("parses a single complete line", () => {
|
||||
const { parser, objects, errors } = collect();
|
||||
parser.push('{"type":"system","subtype":"init"}\n');
|
||||
assert.equal(errors.length, 0);
|
||||
assert.equal(objects.length, 1);
|
||||
assert.equal(objects[0].type, "system");
|
||||
});
|
||||
|
||||
it("parses multiple lines in one chunk", () => {
|
||||
const { parser, objects } = collect();
|
||||
parser.push('{"type":"a"}\n{"type":"b"}\n{"type":"c"}\n');
|
||||
assert.deepEqual(
|
||||
objects.map((o) => o.type),
|
||||
["a", "b", "c"]
|
||||
);
|
||||
});
|
||||
|
||||
it("reassembles a JSON object split across two chunks", () => {
|
||||
const { parser, objects } = collect();
|
||||
parser.push('{"type":"split","val":');
|
||||
parser.push('"hello"}\n');
|
||||
assert.equal(objects.length, 1);
|
||||
assert.equal(objects[0].val, "hello");
|
||||
});
|
||||
|
||||
it("reassembles a JSON object split across many small chunks", () => {
|
||||
const { parser, objects } = collect();
|
||||
const full = '{"type":"chunky","payload":{"deep":{"nested":[1,2,3]}}}\n';
|
||||
for (const ch of full) parser.push(ch);
|
||||
assert.equal(objects.length, 1);
|
||||
assert.deepEqual(objects[0].payload.deep.nested, [1, 2, 3]);
|
||||
});
|
||||
|
||||
it("ignores blank lines between objects", () => {
|
||||
const { parser, objects, errors } = collect();
|
||||
parser.push('{"type":"a"}\n\n\n{"type":"b"}\n');
|
||||
assert.equal(objects.length, 2);
|
||||
assert.equal(errors.length, 0);
|
||||
});
|
||||
|
||||
it("reports malformed JSON via onError without throwing", () => {
|
||||
const { parser, objects, errors } = collect();
|
||||
parser.push("not valid json\n");
|
||||
parser.push('{"type":"ok"}\n');
|
||||
assert.equal(objects.length, 1);
|
||||
assert.equal(objects[0].type, "ok");
|
||||
assert.equal(errors.length, 1);
|
||||
assert.match(errors[0].raw, /not valid json/);
|
||||
});
|
||||
|
||||
it("does not emit a partial line until newline arrives", () => {
|
||||
const { parser, objects } = collect();
|
||||
parser.push('{"type":"unfinished"');
|
||||
assert.equal(objects.length, 0);
|
||||
parser.push("}\n");
|
||||
assert.equal(objects.length, 1);
|
||||
});
|
||||
|
||||
it("flush() emits trailing line without newline", () => {
|
||||
const { parser, objects } = collect();
|
||||
parser.push('{"type":"trailing"}');
|
||||
assert.equal(objects.length, 0);
|
||||
parser.flush();
|
||||
assert.equal(objects.length, 1);
|
||||
assert.equal(objects[0].type, "trailing");
|
||||
});
|
||||
|
||||
it("flush() on empty buffer is a no-op", () => {
|
||||
const { parser, objects, errors } = collect();
|
||||
parser.flush();
|
||||
assert.equal(objects.length, 0);
|
||||
assert.equal(errors.length, 0);
|
||||
});
|
||||
|
||||
it("flush() reports malformed trailing line via onError", () => {
|
||||
const { parser, objects, errors } = collect();
|
||||
parser.push("garbage{not-json");
|
||||
parser.flush();
|
||||
assert.equal(objects.length, 0);
|
||||
assert.equal(errors.length, 1);
|
||||
});
|
||||
|
||||
it("works without onError callback when input is malformed", () => {
|
||||
let count = 0;
|
||||
const parser = createLineParser((_o) => count++);
|
||||
// No throw expected.
|
||||
parser.push("garbage\n");
|
||||
parser.push('{"type":"ok"}\n');
|
||||
assert.equal(count, 1);
|
||||
});
|
||||
|
||||
it("handles CRLF line endings cleanly (\\r is trimmed before parse)", () => {
|
||||
const { parser, objects, errors } = collect();
|
||||
parser.push('{"type":"crlf"}\r\n');
|
||||
// Note: parser only splits on \n; the \r at end of line stays in the
|
||||
// line. JSON.parse tolerates trailing whitespace including \r.
|
||||
assert.equal(errors.length, 0);
|
||||
assert.equal(objects.length, 1);
|
||||
assert.equal(objects[0].type, "crlf");
|
||||
});
|
||||
|
||||
it("handles a stream-json envelope with stream_event sub-event shape", () => {
|
||||
const { parser, objects } = collect();
|
||||
const env = JSON.stringify({
|
||||
type: "stream_event",
|
||||
event: {
|
||||
type: "content_block_delta",
|
||||
index: 0,
|
||||
delta: { type: "text_delta", text: "Hello" },
|
||||
},
|
||||
session_id: "sess",
|
||||
});
|
||||
parser.push(env + "\n");
|
||||
assert.equal(objects.length, 1);
|
||||
assert.equal(objects[0].event.delta.text, "Hello");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user