@united-workforce/cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -3
- package/dist/.build-fingerprint +1 -0
- package/dist/__tests__/adapter-json-roundtrip.test.js +16 -6
- package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
- package/dist/__tests__/concurrency.test.d.ts +2 -0
- package/dist/__tests__/concurrency.test.d.ts.map +1 -0
- package/dist/__tests__/concurrency.test.js +196 -0
- package/dist/__tests__/concurrency.test.js.map +1 -0
- package/dist/__tests__/e2e-mock-agent.test.js +23 -7
- package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
- package/dist/__tests__/format-text-default.test.d.ts +2 -0
- package/dist/__tests__/format-text-default.test.d.ts.map +1 -0
- package/dist/__tests__/format-text-default.test.js +43 -0
- package/dist/__tests__/format-text-default.test.js.map +1 -0
- package/dist/__tests__/format-text-registry.test.d.ts +2 -0
- package/dist/__tests__/format-text-registry.test.d.ts.map +1 -0
- package/dist/__tests__/format-text-registry.test.js +158 -0
- package/dist/__tests__/format-text-registry.test.js.map +1 -0
- package/dist/__tests__/log-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/log-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/log-text-renderer.test.js +265 -0
- package/dist/__tests__/log-text-renderer.test.js.map +1 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts +2 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts.map +1 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.js +102 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.js.map +1 -0
- package/dist/__tests__/output-mapper-workflow-add.test.d.ts +2 -0
- package/dist/__tests__/output-mapper-workflow-add.test.d.ts.map +1 -0
- package/dist/__tests__/output-mapper-workflow-add.test.js +22 -0
- package/dist/__tests__/output-mapper-workflow-add.test.js.map +1 -0
- package/dist/__tests__/pid-recycling.test.js +9 -7
- package/dist/__tests__/pid-recycling.test.js.map +1 -1
- package/dist/__tests__/prompt.test.js +46 -4
- package/dist/__tests__/prompt.test.js.map +1 -1
- package/dist/__tests__/resolve-head-hash.test.js +8 -0
- package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
- package/dist/__tests__/solve-issue-tea-worktree.test.js +3 -1
- package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
- package/dist/__tests__/step-ask.test.js +9 -1
- package/dist/__tests__/step-ask.test.js.map +1 -1
- package/dist/__tests__/store-unified-threads.test.js +19 -17
- package/dist/__tests__/store-unified-threads.test.js.map +1 -1
- package/dist/__tests__/thread-cancel-status.test.js +19 -13
- package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
- package/dist/__tests__/thread-cancel-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.js +110 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.js.map +1 -0
- package/dist/__tests__/thread-list-filters.test.js +10 -8
- package/dist/__tests__/thread-list-filters.test.js.map +1 -1
- package/dist/__tests__/thread-list-template-ms-date.test.d.ts +2 -0
- package/dist/__tests__/thread-list-template-ms-date.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js +102 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts +2 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.js +157 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.js.map +1 -0
- package/dist/__tests__/thread-poke.test.js +11 -1
- package/dist/__tests__/thread-poke.test.js.map +1 -1
- package/dist/__tests__/thread-read-xml-tags.test.js +10 -9
- package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -1
- package/dist/__tests__/thread-resume.test.js +11 -1
- package/dist/__tests__/thread-resume.test.js.map +1 -1
- package/dist/__tests__/thread-start-cwd-cli.test.js +15 -3
- package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -1
- package/dist/__tests__/thread-stop-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/thread-stop-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/thread-stop-text-renderer.test.js +148 -0
- package/dist/__tests__/thread-stop-text-renderer.test.js.map +1 -0
- package/dist/__tests__/thread-suspend-step.test.js +5 -2
- package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
- package/dist/__tests__/thread-test-helpers.d.ts +7 -0
- package/dist/__tests__/thread-test-helpers.d.ts.map +1 -1
- package/dist/__tests__/thread-test-helpers.js +13 -0
- package/dist/__tests__/thread-test-helpers.js.map +1 -1
- package/dist/__tests__/thread.test.js +11 -9
- package/dist/__tests__/thread.test.js.map +1 -1
- package/dist/__tests__/validate-semantic.test.js +56 -2
- package/dist/__tests__/validate-semantic.test.js.map +1 -1
- package/dist/__tests__/workflow-list-recursive.test.js +10 -7
- package/dist/__tests__/workflow-list-recursive.test.js.map +1 -1
- package/dist/__tests__/workflow-resolution.test.js +10 -7
- package/dist/__tests__/workflow-resolution.test.js.map +1 -1
- package/dist/__tests__/workflow-show-resolution.test.js +10 -7
- package/dist/__tests__/workflow-show-resolution.test.js.map +1 -1
- package/dist/__tests__/workflow-validate.test.js +75 -55
- package/dist/__tests__/workflow-validate.test.js.map +1 -1
- package/dist/__tests__/write-envelope.test.d.ts +2 -0
- package/dist/__tests__/write-envelope.test.d.ts.map +1 -0
- package/dist/__tests__/write-envelope.test.js +201 -0
- package/dist/__tests__/write-envelope.test.js.map +1 -0
- package/dist/cli.js +58 -35
- package/dist/cli.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +12 -0
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +42 -29
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/setup.d.ts +9 -4
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +51 -7
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/thread.d.ts.map +1 -1
- package/dist/commands/thread.js +44 -2
- package/dist/commands/thread.js.map +1 -1
- package/dist/commands/workflow.d.ts +1 -1
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +2 -6
- package/dist/commands/workflow.js.map +1 -1
- package/dist/concurrency/concurrency.d.ts +34 -0
- package/dist/concurrency/concurrency.d.ts.map +1 -0
- package/dist/concurrency/concurrency.js +216 -0
- package/dist/concurrency/concurrency.js.map +1 -0
- package/dist/concurrency/index.d.ts +3 -0
- package/dist/concurrency/index.d.ts.map +1 -0
- package/dist/concurrency/index.js +2 -0
- package/dist/concurrency/index.js.map +1 -0
- package/dist/concurrency/types.d.ts +19 -0
- package/dist/concurrency/types.d.ts.map +1 -0
- package/dist/concurrency/types.js +2 -0
- package/dist/concurrency/types.js.map +1 -0
- package/dist/format.d.ts +69 -2
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +198 -1
- package/dist/format.js.map +1 -1
- package/dist/output-mappers.d.ts +122 -0
- package/dist/output-mappers.d.ts.map +1 -0
- package/dist/output-mappers.js +134 -0
- package/dist/output-mappers.js.map +1 -0
- package/dist/schemas.d.ts +4 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +31 -4
- package/dist/schemas.js.map +1 -1
- package/dist/text-renderers.d.ts +30 -0
- package/dist/text-renderers.d.ts.map +1 -0
- package/dist/text-renderers.js +251 -0
- package/dist/text-renderers.js.map +1 -0
- package/dist/validate-semantic.d.ts.map +1 -1
- package/dist/validate-semantic.js +28 -11
- package/dist/validate-semantic.js.map +1 -1
- package/examples/brainstorm.yaml +130 -0
- package/examples/debate.yaml +169 -0
- package/examples/socratic-questioning.yaml +112 -0
- package/package.json +5 -4
- package/src/__tests__/adapter-json-roundtrip.test.ts +15 -6
- package/src/__tests__/concurrency.test.ts +266 -0
- package/src/__tests__/e2e-mock-agent.test.ts +45 -7
- package/src/__tests__/format-text-default.test.ts +49 -0
- package/src/__tests__/format-text-registry.test.ts +173 -0
- package/src/__tests__/log-text-renderer.test.ts +294 -0
- package/src/__tests__/output-mapper-thread-list-startedat.test.ts +124 -0
- package/src/__tests__/output-mapper-workflow-add.test.ts +24 -0
- package/src/__tests__/pid-recycling.test.ts +9 -8
- package/src/__tests__/prompt.test.ts +48 -4
- package/src/__tests__/resolve-head-hash.test.ts +7 -0
- package/src/__tests__/solve-issue-tea-worktree.test.ts +3 -1
- package/src/__tests__/step-ask.test.ts +8 -1
- package/src/__tests__/store-unified-threads.test.ts +21 -18
- package/src/__tests__/thread-cancel-status.test.ts +21 -14
- package/src/__tests__/thread-cancel-text-renderer.test.ts +125 -0
- package/src/__tests__/thread-list-filters.test.ts +9 -9
- package/src/__tests__/thread-list-template-ms-date.test.ts +110 -0
- package/src/__tests__/thread-list-workflow-corrupt.test.ts +198 -0
- package/src/__tests__/thread-poke.test.ts +10 -1
- package/src/__tests__/thread-read-xml-tags.test.ts +9 -11
- package/src/__tests__/thread-resume.test.ts +10 -1
- package/src/__tests__/thread-start-cwd-cli.test.ts +15 -3
- package/src/__tests__/thread-stop-text-renderer.test.ts +168 -0
- package/src/__tests__/thread-suspend-step.test.ts +5 -2
- package/src/__tests__/thread-test-helpers.ts +15 -1
- package/src/__tests__/thread.test.ts +10 -10
- package/src/__tests__/validate-semantic.test.ts +59 -2
- package/src/__tests__/workflow-list-recursive.test.ts +9 -9
- package/src/__tests__/workflow-resolution.test.ts +9 -8
- package/src/__tests__/workflow-show-resolution.test.ts +9 -8
- package/src/__tests__/workflow-validate.test.ts +78 -56
- package/src/__tests__/write-envelope.test.ts +257 -0
- package/src/cli.ts +92 -35
- package/src/commands/config.ts +11 -0
- package/src/commands/prompt.ts +42 -29
- package/src/commands/setup.ts +57 -7
- package/src/commands/thread.ts +48 -2
- package/src/commands/workflow.ts +3 -7
- package/src/concurrency/concurrency.ts +245 -0
- package/src/concurrency/index.ts +10 -0
- package/src/concurrency/types.ts +19 -0
- package/src/format.ts +282 -2
- package/src/output-mappers.ts +254 -0
- package/src/schemas.ts +39 -3
- package/src/text-renderers.ts +355 -0
- package/src/validate-semantic.ts +33 -12
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { formatOutput, getTextRenderer, TEXT_RENDERERS } from "../format.js";
|
|
3
|
+
import { renderLogList, renderLogShow } from "../text-renderers.js";
|
|
4
|
+
|
|
5
|
+
describe("log list — text renderer registration", () => {
|
|
6
|
+
test("TEXT_RENDERERS contains 'log list'", () => {
|
|
7
|
+
expect(getTextRenderer("log list")).toBeDefined();
|
|
8
|
+
expect(typeof getTextRenderer("log list")).toBe("function");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("TEXT_RENDERERS['log list'] is the same reference as renderLogList", () => {
|
|
12
|
+
expect(TEXT_RENDERERS["log list"]).toBe(renderLogList);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("renderLogList is exported from text-renderers.ts", () => {
|
|
16
|
+
expect(typeof renderLogList).toBe("function");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("log show — text renderer registration", () => {
|
|
21
|
+
test("TEXT_RENDERERS contains 'log show'", () => {
|
|
22
|
+
expect(getTextRenderer("log show")).toBeDefined();
|
|
23
|
+
expect(typeof getTextRenderer("log show")).toBe("function");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("TEXT_RENDERERS['log show'] is the same reference as renderLogShow", () => {
|
|
27
|
+
expect(TEXT_RENDERERS["log show"]).toBe(renderLogShow);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("renderLogShow is exported from text-renderers.ts", () => {
|
|
31
|
+
expect(typeof renderLogShow).toBe("function");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("renderLogList — output shape", () => {
|
|
36
|
+
test("returns a string for full payload", () => {
|
|
37
|
+
const out = renderLogList([
|
|
38
|
+
{ name: "2026-06-10.jsonl", size: 4096, date: "2026-06-10" },
|
|
39
|
+
{ name: "2026-06-11.jsonl", size: 8192, date: "2026-06-11" },
|
|
40
|
+
]);
|
|
41
|
+
expect(typeof out).toBe("string");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("includes file names, dates, and sizes for each row", () => {
|
|
45
|
+
const out = renderLogList([
|
|
46
|
+
{ name: "2026-06-10.jsonl", size: 4096, date: "2026-06-10" },
|
|
47
|
+
{ name: "2026-06-11.jsonl", size: 8192, date: "2026-06-11" },
|
|
48
|
+
]);
|
|
49
|
+
expect(out).toContain("2026-06-10.jsonl");
|
|
50
|
+
expect(out).toContain("2026-06-10");
|
|
51
|
+
expect(out).toContain("2026-06-11.jsonl");
|
|
52
|
+
expect(out).toContain("2026-06-11");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("includes a header line", () => {
|
|
56
|
+
const out = renderLogList([{ name: "2026-06-10.jsonl", size: 4096, date: "2026-06-10" }]);
|
|
57
|
+
const lines = out.split("\n");
|
|
58
|
+
expect(lines.length).toBeGreaterThanOrEqual(2);
|
|
59
|
+
// header should mention NAME or DATE or SIZE
|
|
60
|
+
const header = lines[0]?.toUpperCase() ?? "";
|
|
61
|
+
const hasHeader = header.includes("NAME") || header.includes("DATE") || header.includes("SIZE");
|
|
62
|
+
expect(hasHeader).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("does NOT begin with '{' or '[' (not raw JSON)", () => {
|
|
66
|
+
const out = renderLogList([{ name: "2026-06-10.jsonl", size: 4096, date: "2026-06-10" }]);
|
|
67
|
+
const trimmed = out.trimStart();
|
|
68
|
+
expect(trimmed.startsWith("{")).toBe(false);
|
|
69
|
+
expect(trimmed.startsWith("[")).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("does NOT contain literal 'undefined'", () => {
|
|
73
|
+
const out = renderLogList([{ name: "2026-06-10.jsonl", size: 4096, date: "2026-06-10" }]);
|
|
74
|
+
expect(out).not.toContain("undefined");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("empty array — returns string, no 'undefined'", () => {
|
|
78
|
+
const out = renderLogList([]);
|
|
79
|
+
expect(typeof out).toBe("string");
|
|
80
|
+
expect(out).not.toContain("undefined");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("null payload — returns string, does not throw", () => {
|
|
84
|
+
expect(() => renderLogList(null)).not.toThrow();
|
|
85
|
+
const out = renderLogList(null);
|
|
86
|
+
expect(typeof out).toBe("string");
|
|
87
|
+
expect(out).not.toContain("undefined");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("non-array payload — returns string, does not throw", () => {
|
|
91
|
+
expect(() => renderLogList({ name: "x" })).not.toThrow();
|
|
92
|
+
const out = renderLogList({ name: "x" });
|
|
93
|
+
expect(typeof out).toBe("string");
|
|
94
|
+
expect(out).not.toContain("undefined");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("partial item (missing size) — returns string, no 'undefined'", () => {
|
|
98
|
+
const out = renderLogList([{ name: "2026-06-10.jsonl", date: "2026-06-10" }]);
|
|
99
|
+
expect(typeof out).toBe("string");
|
|
100
|
+
expect(out).not.toContain("undefined");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("renderLogShow — output shape", () => {
|
|
105
|
+
test("returns a string for full payload", () => {
|
|
106
|
+
const out = renderLogShow([
|
|
107
|
+
{
|
|
108
|
+
ts: "2026-06-12T10:00:00.000Z",
|
|
109
|
+
pid: "12345",
|
|
110
|
+
tag: "4KNMR2PX",
|
|
111
|
+
msg: "Loading workflow...",
|
|
112
|
+
thread: "01JTEST000000000000THREAD1",
|
|
113
|
+
workflow: "WF1234567890A",
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
expect(typeof out).toBe("string");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("includes ts, pid, tag, and msg for each entry", () => {
|
|
120
|
+
const out = renderLogShow([
|
|
121
|
+
{
|
|
122
|
+
ts: "2026-06-12T10:00:00.000Z",
|
|
123
|
+
pid: "12345",
|
|
124
|
+
tag: "4KNMR2PX",
|
|
125
|
+
msg: "Loading workflow...",
|
|
126
|
+
thread: null,
|
|
127
|
+
workflow: null,
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
130
|
+
expect(out).toContain("2026-06-12T10:00:00.000Z");
|
|
131
|
+
expect(out).toContain("12345");
|
|
132
|
+
expect(out).toContain("4KNMR2PX");
|
|
133
|
+
expect(out).toContain("Loading workflow...");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("includes thread id when present", () => {
|
|
137
|
+
const out = renderLogShow([
|
|
138
|
+
{
|
|
139
|
+
ts: "2026-06-12T10:00:00.000Z",
|
|
140
|
+
pid: "12345",
|
|
141
|
+
tag: "4KNMR2PX",
|
|
142
|
+
msg: "Loading workflow...",
|
|
143
|
+
thread: "01JTEST000000000000THREAD1",
|
|
144
|
+
workflow: null,
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
expect(out).toContain("01JTEST000000000000THREAD1");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("does NOT begin with '{' or '[' (not raw JSON)", () => {
|
|
151
|
+
const out = renderLogShow([
|
|
152
|
+
{
|
|
153
|
+
ts: "2026-06-12T10:00:00.000Z",
|
|
154
|
+
pid: "12345",
|
|
155
|
+
tag: "4KNMR2PX",
|
|
156
|
+
msg: "Loading workflow...",
|
|
157
|
+
thread: null,
|
|
158
|
+
workflow: null,
|
|
159
|
+
},
|
|
160
|
+
]);
|
|
161
|
+
const trimmed = out.trimStart();
|
|
162
|
+
expect(trimmed.startsWith("{")).toBe(false);
|
|
163
|
+
expect(trimmed.startsWith("[")).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("does NOT contain literal 'undefined'", () => {
|
|
167
|
+
const out = renderLogShow([
|
|
168
|
+
{
|
|
169
|
+
ts: "2026-06-12T10:00:00.000Z",
|
|
170
|
+
pid: "12345",
|
|
171
|
+
tag: "4KNMR2PX",
|
|
172
|
+
msg: "Loading workflow...",
|
|
173
|
+
thread: null,
|
|
174
|
+
workflow: null,
|
|
175
|
+
},
|
|
176
|
+
]);
|
|
177
|
+
expect(out).not.toContain("undefined");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("empty array — returns string, no 'undefined'", () => {
|
|
181
|
+
const out = renderLogShow([]);
|
|
182
|
+
expect(typeof out).toBe("string");
|
|
183
|
+
expect(out).not.toContain("undefined");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("null payload — returns string, does not throw", () => {
|
|
187
|
+
expect(() => renderLogShow(null)).not.toThrow();
|
|
188
|
+
const out = renderLogShow(null);
|
|
189
|
+
expect(typeof out).toBe("string");
|
|
190
|
+
expect(out).not.toContain("undefined");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("non-array payload — returns string, does not throw", () => {
|
|
194
|
+
expect(() => renderLogShow({ ts: "x" })).not.toThrow();
|
|
195
|
+
const out = renderLogShow({ ts: "x" });
|
|
196
|
+
expect(typeof out).toBe("string");
|
|
197
|
+
expect(out).not.toContain("undefined");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("partial entry (missing thread) — returns string, no 'undefined'", () => {
|
|
201
|
+
const out = renderLogShow([
|
|
202
|
+
{
|
|
203
|
+
ts: "2026-06-12T10:00:00.000Z",
|
|
204
|
+
pid: "12345",
|
|
205
|
+
tag: "4KNMR2PX",
|
|
206
|
+
msg: "hello",
|
|
207
|
+
},
|
|
208
|
+
]);
|
|
209
|
+
expect(typeof out).toBe("string");
|
|
210
|
+
expect(out).not.toContain("undefined");
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("formatOutput integration — log list", () => {
|
|
215
|
+
test("formatOutput(data, 'text', 'log list') uses renderer", () => {
|
|
216
|
+
const data = [{ name: "2026-06-10.jsonl", size: 4096, date: "2026-06-10" }];
|
|
217
|
+
const out = formatOutput(data, "text", "log list");
|
|
218
|
+
expect(typeof out).toBe("string");
|
|
219
|
+
expect(out).not.toContain("undefined");
|
|
220
|
+
expect(out.trimStart().startsWith("{")).toBe(false);
|
|
221
|
+
expect(out.trimStart().startsWith("[")).toBe(false);
|
|
222
|
+
expect(out).toContain("2026-06-10.jsonl");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("formatOutput(data, 'json', 'log list') still emits parseable JSON", () => {
|
|
226
|
+
const data = [{ name: "2026-06-10.jsonl", size: 4096, date: "2026-06-10" }];
|
|
227
|
+
const out = formatOutput(data, "json", "log list");
|
|
228
|
+
const parsed = JSON.parse(out);
|
|
229
|
+
expect(parsed).toEqual(data);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("formatOutput(data, 'yaml', 'log list') still emits YAML", () => {
|
|
233
|
+
const data = [{ name: "2026-06-10.jsonl", size: 4096, date: "2026-06-10" }];
|
|
234
|
+
const out = formatOutput(data, "yaml", "log list");
|
|
235
|
+
expect(typeof out).toBe("string");
|
|
236
|
+
expect(out).toContain("name:");
|
|
237
|
+
expect(out).toContain("2026-06-10.jsonl");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("formatOutput integration — log show", () => {
|
|
242
|
+
test("formatOutput(data, 'text', 'log show') uses renderer", () => {
|
|
243
|
+
const data = [
|
|
244
|
+
{
|
|
245
|
+
ts: "2026-06-12T10:00:00.000Z",
|
|
246
|
+
pid: "12345",
|
|
247
|
+
tag: "4KNMR2PX",
|
|
248
|
+
msg: "hello",
|
|
249
|
+
thread: null,
|
|
250
|
+
workflow: null,
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
const out = formatOutput(data, "text", "log show");
|
|
254
|
+
expect(typeof out).toBe("string");
|
|
255
|
+
expect(out).not.toContain("undefined");
|
|
256
|
+
expect(out.trimStart().startsWith("{")).toBe(false);
|
|
257
|
+
expect(out.trimStart().startsWith("[")).toBe(false);
|
|
258
|
+
expect(out).toContain("4KNMR2PX");
|
|
259
|
+
expect(out).toContain("hello");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("formatOutput(data, 'json', 'log show') still emits parseable JSON", () => {
|
|
263
|
+
const data = [
|
|
264
|
+
{
|
|
265
|
+
ts: "2026-06-12T10:00:00.000Z",
|
|
266
|
+
pid: "12345",
|
|
267
|
+
tag: "4KNMR2PX",
|
|
268
|
+
msg: "hello",
|
|
269
|
+
thread: null,
|
|
270
|
+
workflow: null,
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
const out = formatOutput(data, "json", "log show");
|
|
274
|
+
const parsed = JSON.parse(out);
|
|
275
|
+
expect(parsed).toEqual(data);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("formatOutput(data, 'yaml', 'log show') still emits YAML", () => {
|
|
279
|
+
const data = [
|
|
280
|
+
{
|
|
281
|
+
ts: "2026-06-12T10:00:00.000Z",
|
|
282
|
+
pid: "12345",
|
|
283
|
+
tag: "4KNMR2PX",
|
|
284
|
+
msg: "hello",
|
|
285
|
+
thread: null,
|
|
286
|
+
workflow: null,
|
|
287
|
+
},
|
|
288
|
+
];
|
|
289
|
+
const out = formatOutput(data, "yaml", "log show");
|
|
290
|
+
expect(typeof out).toBe("string");
|
|
291
|
+
expect(out).toContain("ts:");
|
|
292
|
+
expect(out).toContain("pid:");
|
|
293
|
+
});
|
|
294
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import type { CasRef, ThreadId } from "@united-workforce/protocol";
|
|
4
|
+
import { generateUlid } from "@united-workforce/util";
|
|
5
|
+
import { describe, expect, test } from "vitest";
|
|
6
|
+
import type { ThreadListItemWithStatus } from "../commands/thread.js";
|
|
7
|
+
import { toThreadListPayload } from "../output-mappers.js";
|
|
8
|
+
|
|
9
|
+
const OUTPUT_MAPPERS_PATH = fileURLToPath(new URL("../output-mappers.ts", import.meta.url));
|
|
10
|
+
|
|
11
|
+
function makeItem(threadId: string): ThreadListItemWithStatus {
|
|
12
|
+
return {
|
|
13
|
+
thread: threadId as ThreadId,
|
|
14
|
+
workflow: "WORKFLOWHASH1" as CasRef,
|
|
15
|
+
head: "HEADHASH00001" as CasRef,
|
|
16
|
+
status: "idle",
|
|
17
|
+
currentRole: null,
|
|
18
|
+
statusDisplay: "idle",
|
|
19
|
+
workflowName: "test-workflow",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("toThreadListPayload — issue #343 (ULID timestamp decoded with padding stripped)", () => {
|
|
24
|
+
test("startedAt equals the millisecond timestamp originally passed to generateUlid", () => {
|
|
25
|
+
const ts = 1781219097830; // 2026-06-11T23:04:57.830Z
|
|
26
|
+
const ulid = generateUlid(ts);
|
|
27
|
+
|
|
28
|
+
const payload = toThreadListPayload([makeItem(ulid)]);
|
|
29
|
+
|
|
30
|
+
expect(payload.items).toHaveLength(1);
|
|
31
|
+
expect(payload.items[0]!.startedAt).toBe(ts);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("startedAt is NOT the raw 50-bit value (i.e. NOT timestamp << 2)", () => {
|
|
35
|
+
const ts = 1781219097830;
|
|
36
|
+
const ulid = generateUlid(ts);
|
|
37
|
+
|
|
38
|
+
const payload = toThreadListPayload([makeItem(ulid)]);
|
|
39
|
+
|
|
40
|
+
// The buggy decoder produced ts * 4 = 7124876391323, pushing year to 2195.
|
|
41
|
+
expect(payload.items[0]!.startedAt).not.toBe(ts * 4);
|
|
42
|
+
expect(payload.items[0]!.startedAt).not.toBe(7124876391323);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("startedAt decodes to year 2026 for the issue-reported ULID timestamp", () => {
|
|
46
|
+
const ts = 1781219097830;
|
|
47
|
+
const ulid = generateUlid(ts);
|
|
48
|
+
|
|
49
|
+
const payload = toThreadListPayload([makeItem(ulid)]);
|
|
50
|
+
|
|
51
|
+
const startedAt = payload.items[0]!.startedAt;
|
|
52
|
+
expect(startedAt).not.toBeNull();
|
|
53
|
+
if (startedAt === null) return;
|
|
54
|
+
const isoDate = new Date(startedAt).toISOString().slice(0, 10);
|
|
55
|
+
expect(isoDate).toBe("2026-06-11");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("round-trips correctly across several timestamps", () => {
|
|
59
|
+
const timestamps = [
|
|
60
|
+
0,
|
|
61
|
+
Date.UTC(2020, 0, 1, 0, 0, 0),
|
|
62
|
+
Date.UTC(2023, 5, 15, 12, 30, 45),
|
|
63
|
+
Date.UTC(2026, 4, 20, 0, 0, 0),
|
|
64
|
+
Date.UTC(2030, 11, 31, 23, 59, 59),
|
|
65
|
+
];
|
|
66
|
+
const items = timestamps.map((t) => makeItem(generateUlid(t)));
|
|
67
|
+
|
|
68
|
+
const payload = toThreadListPayload(items);
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < timestamps.length; i++) {
|
|
71
|
+
expect(payload.items[i]!.startedAt).toBe(timestamps[i]);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("startedAt is null for thread ids that are not valid 26-char Crockford Base32 ULIDs", () => {
|
|
76
|
+
const cases = ["", "TOOSHORT", "TOOLONGAAAAAAAAAAAAAAAAAA", "INVALID!@#$%^&CHARACTERS"];
|
|
77
|
+
const items = cases.map((id) => makeItem(id));
|
|
78
|
+
|
|
79
|
+
const payload = toThreadListPayload(items);
|
|
80
|
+
|
|
81
|
+
for (const item of payload.items) {
|
|
82
|
+
expect(item.startedAt).toBeNull();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("preserves other thread-list item fields verbatim", () => {
|
|
87
|
+
const ts = 1781219097830;
|
|
88
|
+
const ulid = generateUlid(ts);
|
|
89
|
+
const item: ThreadListItemWithStatus = {
|
|
90
|
+
thread: ulid as ThreadId,
|
|
91
|
+
workflow: "WORKFLOWHASH9" as CasRef,
|
|
92
|
+
head: "HEADHASH99999" as CasRef,
|
|
93
|
+
status: "running",
|
|
94
|
+
currentRole: "developer",
|
|
95
|
+
statusDisplay: "running",
|
|
96
|
+
workflowName: "solve-issue",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const payload = toThreadListPayload([item]);
|
|
100
|
+
|
|
101
|
+
expect(payload.items[0]).toEqual({
|
|
102
|
+
threadId: ulid,
|
|
103
|
+
workflowHash: "WORKFLOWHASH9",
|
|
104
|
+
workflowName: "solve-issue",
|
|
105
|
+
status: "running",
|
|
106
|
+
currentRole: "developer",
|
|
107
|
+
startedAt: ts,
|
|
108
|
+
completedAt: null,
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("output-mappers.ts source — issue #343 refactor", () => {
|
|
114
|
+
test("does NOT contain a local extractUlidTime function (removed in favor of util)", async () => {
|
|
115
|
+
const source = await readFile(OUTPUT_MAPPERS_PATH, "utf8");
|
|
116
|
+
expect(source).not.toMatch(/function\s+extractUlidTime\b/);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("imports extractUlidTimestamp from @united-workforce/util", async () => {
|
|
120
|
+
const source = await readFile(OUTPUT_MAPPERS_PATH, "utf8");
|
|
121
|
+
expect(source).toMatch(/extractUlidTimestamp/);
|
|
122
|
+
expect(source).toMatch(/@united-workforce\/util/);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { toWorkflowAddPayload, type WorkflowAddPayload } from "../output-mappers.js";
|
|
3
|
+
|
|
4
|
+
describe("toWorkflowAddPayload — issue #334", () => {
|
|
5
|
+
test("maps WorkflowAddOutput { name, hash } to plain payload shape", () => {
|
|
6
|
+
const out = toWorkflowAddPayload({ name: "review-pr", hash: "2TBP6T37TZAJZ" });
|
|
7
|
+
expect(out).toEqual({ name: "review-pr", hash: "2TBP6T37TZAJZ" });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("returns a WorkflowAddPayload type with exactly two fields", () => {
|
|
11
|
+
const out: WorkflowAddPayload = toWorkflowAddPayload({
|
|
12
|
+
name: "solve-issue",
|
|
13
|
+
hash: "76C98RVXA5E4F",
|
|
14
|
+
});
|
|
15
|
+
expect(Object.keys(out).sort()).toEqual(["hash", "name"]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("performs no I/O — pure data mapping", () => {
|
|
19
|
+
// Repeated calls produce equal results
|
|
20
|
+
const a = toWorkflowAddPayload({ name: "a", hash: "AAA1234567890" });
|
|
21
|
+
const b = toWorkflowAddPayload({ name: "a", hash: "AAA1234567890" });
|
|
22
|
+
expect(a).toEqual(b);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -13,17 +13,11 @@ import {
|
|
|
13
13
|
listRunningThreads,
|
|
14
14
|
readMarker,
|
|
15
15
|
} from "../background/index.js";
|
|
16
|
-
import {
|
|
16
|
+
import type { UwfStore } from "../store.js";
|
|
17
|
+
import { makeUwfStore } from "./thread-test-helpers.js";
|
|
17
18
|
|
|
18
19
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
19
20
|
|
|
20
|
-
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
|
21
|
-
const casDir = join(storageRoot, "cas");
|
|
22
|
-
await mkdir(casDir, { recursive: true });
|
|
23
|
-
process.env.OCAS_HOME = casDir;
|
|
24
|
-
return createUwfStore(storageRoot);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
21
|
async function createTestWorkflow(uwf: UwfStore): Promise<CasRef> {
|
|
28
22
|
const workflowPayload = {
|
|
29
23
|
name: "test-workflow",
|
|
@@ -42,12 +36,19 @@ async function createTestWorkflow(uwf: UwfStore): Promise<CasRef> {
|
|
|
42
36
|
// ── test setup ────────────────────────────────────────────────────────────────
|
|
43
37
|
|
|
44
38
|
let tmpDir: string;
|
|
39
|
+
let savedOcasHome: string | undefined;
|
|
45
40
|
|
|
46
41
|
beforeEach(async () => {
|
|
42
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
47
43
|
tmpDir = await mkdtemp(join(tmpdir(), "pid-recycling-test-"));
|
|
48
44
|
});
|
|
49
45
|
|
|
50
46
|
afterEach(async () => {
|
|
47
|
+
if (savedOcasHome === undefined) {
|
|
48
|
+
delete process.env.OCAS_HOME;
|
|
49
|
+
} else {
|
|
50
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
51
|
+
}
|
|
51
52
|
await rm(tmpDir, { recursive: true, force: true });
|
|
52
53
|
});
|
|
53
54
|
|
|
@@ -135,8 +135,6 @@ describe("prompt commands", () => {
|
|
|
135
135
|
// Fresh install scenario
|
|
136
136
|
expect(result).toContain("Fresh Install");
|
|
137
137
|
expect(result).toContain("uwf setup");
|
|
138
|
-
expect(result).toContain("--provider");
|
|
139
|
-
expect(result).toContain("--api-key");
|
|
140
138
|
expect(result).toContain("agent adapter");
|
|
141
139
|
// Upgrade scenario
|
|
142
140
|
expect(result).toContain("Upgrade");
|
|
@@ -147,6 +145,49 @@ describe("prompt commands", () => {
|
|
|
147
145
|
expect(result.length).toBeGreaterThan(100);
|
|
148
146
|
});
|
|
149
147
|
|
|
148
|
+
// Skip: pure documentation content assertions on bootstrap prompt text.
|
|
149
|
+
test.skip("prompt bootstrap has no LLM provider/model references", () => {
|
|
150
|
+
const result = cmdPromptBootstrap();
|
|
151
|
+
// Must NOT contain provider/model flags
|
|
152
|
+
expect(result).not.toContain("--provider");
|
|
153
|
+
expect(result).not.toContain("--base-url");
|
|
154
|
+
expect(result).not.toContain("--api-key");
|
|
155
|
+
expect(result).not.toContain("--model");
|
|
156
|
+
// Must NOT contain old Step 2 about provider config
|
|
157
|
+
expect(result).not.toContain("Configure provider and model");
|
|
158
|
+
// Must NOT contain preset providers table
|
|
159
|
+
expect(result).not.toContain("openrouter");
|
|
160
|
+
expect(result).not.toContain("OpenRouter");
|
|
161
|
+
expect(result).not.toContain("xAI");
|
|
162
|
+
expect(result).not.toContain("dashscope");
|
|
163
|
+
// Must NOT show provider/model config keys
|
|
164
|
+
expect(result).not.toContain("providers:");
|
|
165
|
+
expect(result).not.toContain("defaultModel");
|
|
166
|
+
expect(result).not.toContain("models:");
|
|
167
|
+
// Setup step must show only --agent
|
|
168
|
+
expect(result).toContain("uwf setup --agent");
|
|
169
|
+
// Config example must show only agents, defaultAgent, agentOverrides
|
|
170
|
+
expect(result).toContain("agents:");
|
|
171
|
+
expect(result).toContain("defaultAgent:");
|
|
172
|
+
// Must mention per-adapter LLM config
|
|
173
|
+
expect(result).toMatch(/~\/\.uwf\/agents\//);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Skip: pure documentation content assertions on bootstrap prompt text.
|
|
177
|
+
test.skip("prompt bootstrap step numbering has no gaps after removing old Step 2", () => {
|
|
178
|
+
const result = cmdPromptBootstrap();
|
|
179
|
+
// Extract only the Fresh Install section
|
|
180
|
+
const freshStart = result.indexOf("## Scenario A: Fresh Install");
|
|
181
|
+
const freshEnd = result.indexOf("## Scenario B:");
|
|
182
|
+
const freshSection = result.slice(freshStart, freshEnd);
|
|
183
|
+
const stepHeaders = freshSection.match(/### Step \d+/g) ?? [];
|
|
184
|
+
const stepNumbers = stepHeaders.map((h) => Number.parseInt(h.replace("### Step ", ""), 10));
|
|
185
|
+
// Verify sequential numbering (0, 1, 2, 3, ...)
|
|
186
|
+
for (let i = 0; i < stepNumbers.length; i++) {
|
|
187
|
+
expect(stepNumbers[i]).toBe(i);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
150
191
|
test("prompt help subcommand is suppressed", { timeout: 30_000 }, () => {
|
|
151
192
|
const cliPath = join(__dirname, "..", "..", "dist", "cli.js");
|
|
152
193
|
const output = execFileSync("node", [cliPath, "prompt", "--help"], {
|
|
@@ -165,7 +206,9 @@ describe("prompt commands", () => {
|
|
|
165
206
|
});
|
|
166
207
|
});
|
|
167
208
|
|
|
168
|
-
|
|
209
|
+
// Skip: pure documentation content assertions — text changes break these without
|
|
210
|
+
// indicating real bugs. Verified by human review instead. See #299 discussion.
|
|
211
|
+
describe.skip("prompt adapter-developing — issue #214 v0.4 contract", () => {
|
|
169
212
|
const text = cmdPromptAdapterDeveloping();
|
|
170
213
|
const lower = text.toLowerCase();
|
|
171
214
|
|
|
@@ -427,7 +470,8 @@ describe("prompt adapter-developing — issue #214 v0.4 contract", () => {
|
|
|
427
470
|
});
|
|
428
471
|
});
|
|
429
472
|
|
|
430
|
-
|
|
473
|
+
// Skip: pure documentation content assertions on reference text.
|
|
474
|
+
describe.skip("prompt workflow-authoring — issue #226 edge location field", () => {
|
|
431
475
|
const text = cmdPromptWorkflowAuthoring();
|
|
432
476
|
const lower = text.toLowerCase();
|
|
433
477
|
|
|
@@ -7,8 +7,10 @@ import { resolveHeadHash } from "../commands/shared.js";
|
|
|
7
7
|
import { completeThread, createUwfStore, setThread } from "../store.js";
|
|
8
8
|
|
|
9
9
|
let tmpDir: string;
|
|
10
|
+
let savedOcasHome: string | undefined;
|
|
10
11
|
|
|
11
12
|
beforeEach(async () => {
|
|
13
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
12
14
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-resolve-head-"));
|
|
13
15
|
const casDir = join(tmpDir, "cas");
|
|
14
16
|
await mkdir(casDir, { recursive: true });
|
|
@@ -16,6 +18,11 @@ beforeEach(async () => {
|
|
|
16
18
|
});
|
|
17
19
|
|
|
18
20
|
afterEach(async () => {
|
|
21
|
+
if (savedOcasHome === undefined) {
|
|
22
|
+
delete process.env.OCAS_HOME;
|
|
23
|
+
} else {
|
|
24
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
25
|
+
}
|
|
19
26
|
await rm(tmpDir, { recursive: true, force: true });
|
|
20
27
|
});
|
|
21
28
|
|
|
@@ -13,7 +13,9 @@ import { parse } from "yaml";
|
|
|
13
13
|
* which fixes the "path segment [0] is empty" error in worktree directories.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// Skip: pure workflow YAML prose content assertions — procedure text changes
|
|
17
|
+
// break these without indicating real bugs. See #299 discussion.
|
|
18
|
+
describe.skip("solve-issue workflow: Gitea API PR creation", () => {
|
|
17
19
|
// Navigate up from packages/cli/src/__tests__ to repo root
|
|
18
20
|
const workflowPath = join(
|
|
19
21
|
dirname(fileURLToPath(import.meta.url)),
|
|
@@ -41,12 +41,19 @@ const THREAD_ID = "01ASKSTEPTEST000000000" as ThreadId;
|
|
|
41
41
|
const STEP_SESSION_ID = "ses-original-step-001";
|
|
42
42
|
|
|
43
43
|
let tmpDir: string;
|
|
44
|
+
let savedOcasHome: string | undefined;
|
|
44
45
|
|
|
45
46
|
beforeEach(async () => {
|
|
46
|
-
|
|
47
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
48
|
+
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-ask-test-"));
|
|
47
49
|
});
|
|
48
50
|
|
|
49
51
|
afterEach(async () => {
|
|
52
|
+
if (savedOcasHome === undefined) {
|
|
53
|
+
delete process.env.OCAS_HOME;
|
|
54
|
+
} else {
|
|
55
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
56
|
+
}
|
|
50
57
|
await rm(tmpDir, { recursive: true, force: true });
|
|
51
58
|
});
|
|
52
59
|
|