@united-workforce/cli 0.4.0 → 0.6.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__/config-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/config-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/config-text-renderer.test.js +137 -0
- package/dist/__tests__/config-text-renderer.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__/issue-180-workflow-ref-removed.test.js +1 -1
- 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-agent-failure-suspended.test.d.ts +2 -0
- package/dist/__tests__/thread-agent-failure-suspended.test.d.ts.map +1 -0
- package/dist/__tests__/thread-agent-failure-suspended.test.js +332 -0
- package/dist/__tests__/thread-agent-failure-suspended.test.js.map +1 -0
- 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-join.test.d.ts +2 -0
- package/dist/__tests__/thread-join.test.d.ts.map +1 -0
- package/dist/__tests__/thread-join.test.js +77 -0
- package/dist/__tests__/thread-join.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 +15 -2
- 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-paths.test.d.ts +2 -0
- package/dist/__tests__/workflow-paths.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-paths.test.js +261 -0
- package/dist/__tests__/workflow-paths.test.js.map +1 -0
- 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 +76 -36
- package/dist/cli.js.map +1 -1
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +81 -3
- 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 +12 -0
- package/dist/commands/thread.d.ts.map +1 -1
- package/dist/commands/thread.js +226 -9
- package/dist/commands/thread.js.map +1 -1
- package/dist/commands/workflow.d.ts +2 -2
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +26 -10
- 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/store.d.ts +11 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +20 -1
- package/dist/store.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 +12 -11
- package/src/__tests__/adapter-json-roundtrip.test.ts +15 -6
- package/src/__tests__/concurrency.test.ts +266 -0
- package/src/__tests__/config-text-renderer.test.ts +156 -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__/issue-180-workflow-ref-removed.test.ts +1 -1
- 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-agent-failure-suspended.test.ts +406 -0
- 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-join.test.ts +103 -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 +14 -2
- 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-paths.test.ts +337 -0
- 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 +111 -35
- package/src/commands/config.ts +85 -3
- package/src/commands/prompt.ts +42 -29
- package/src/commands/setup.ts +57 -7
- package/src/commands/thread.ts +280 -9
- package/src/commands/workflow.ts +32 -11
- 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 +255 -0
- package/src/schemas.ts +39 -3
- package/src/store.ts +25 -1
- package/src/text-renderers.ts +355 -0
- package/src/validate-semantic.ts +33 -12
- package/LICENSE +0 -21
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { CasRef, WorkflowPayload } from "@united-workforce/protocol";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
6
|
+
import { stringify } from "yaml";
|
|
7
|
+
import { getConfigPath, loadWorkflowPaths } from "../commands/config.js";
|
|
8
|
+
import { cmdThreadStart } from "../commands/thread.js";
|
|
9
|
+
import { cmdWorkflowList } from "../commands/workflow.js";
|
|
10
|
+
import { discoverWorkflowPathsEntries, saveWorkflowRegistry, type UwfStore } from "../store.js";
|
|
11
|
+
import { makeUwfStore } from "./thread-test-helpers.js";
|
|
12
|
+
|
|
13
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
function makeMinimalPayload(name: string, description: string): WorkflowPayload {
|
|
16
|
+
return {
|
|
17
|
+
version: 1,
|
|
18
|
+
name,
|
|
19
|
+
description,
|
|
20
|
+
roles: {
|
|
21
|
+
worker: {
|
|
22
|
+
description: "worker role",
|
|
23
|
+
goal: "do work",
|
|
24
|
+
capabilities: [],
|
|
25
|
+
procedure: "",
|
|
26
|
+
output: "",
|
|
27
|
+
frontmatter: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
$status: { const: "done" },
|
|
31
|
+
},
|
|
32
|
+
required: ["$status"],
|
|
33
|
+
} as unknown as CasRef,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
graph: {
|
|
37
|
+
$START: {
|
|
38
|
+
new: { role: "worker", prompt: "start working", location: null },
|
|
39
|
+
resume: { role: "worker", prompt: "resume working", location: null },
|
|
40
|
+
},
|
|
41
|
+
worker: { done: { role: "$END", prompt: "done", location: null } },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function createWorkflowYaml(name: string, version: string | null = null): Promise<string> {
|
|
47
|
+
const payload = makeMinimalPayload(
|
|
48
|
+
name,
|
|
49
|
+
version !== null ? `Test workflow (${version})` : "Test workflow",
|
|
50
|
+
);
|
|
51
|
+
return stringify(payload);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function storeWorkflow(uwf: UwfStore, name: string): Promise<CasRef> {
|
|
55
|
+
const payload = makeMinimalPayload(name, "Test workflow");
|
|
56
|
+
return await uwf.store.cas.put(uwf.schemas.workflow, payload);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function writeConfigWithPaths(storageRoot: string, paths: string[]): void {
|
|
60
|
+
const { writeFileSync, mkdirSync, existsSync } = require("node:fs") as typeof import("node:fs");
|
|
61
|
+
const configPath = getConfigPath(storageRoot);
|
|
62
|
+
const dir = join(configPath, "..");
|
|
63
|
+
if (!existsSync(dir)) {
|
|
64
|
+
mkdirSync(dir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
const { stringify: yamlStringify } = require("yaml") as typeof import("yaml");
|
|
67
|
+
writeFileSync(configPath, yamlStringify({ workflowPaths: paths }), "utf8");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── fixture ───────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
let tmpDir: string;
|
|
73
|
+
let storageRoot: string;
|
|
74
|
+
let projectRoot: string;
|
|
75
|
+
let savedOcasHome: string | undefined;
|
|
76
|
+
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
79
|
+
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-wfpaths-test-"));
|
|
80
|
+
storageRoot = join(tmpDir, "storage");
|
|
81
|
+
projectRoot = join(tmpDir, "project");
|
|
82
|
+
await mkdir(storageRoot, { recursive: true });
|
|
83
|
+
await mkdir(projectRoot, { recursive: true });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
afterEach(async () => {
|
|
87
|
+
if (savedOcasHome === undefined) {
|
|
88
|
+
delete process.env.OCAS_HOME;
|
|
89
|
+
} else {
|
|
90
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
91
|
+
}
|
|
92
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ── discoverWorkflowPathsEntries ──────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
describe("discoverWorkflowPathsEntries", () => {
|
|
98
|
+
test("should find workflows in specified directories", async () => {
|
|
99
|
+
const dir1 = join(tmpDir, "workflows1");
|
|
100
|
+
await mkdir(dir1, { recursive: true });
|
|
101
|
+
await writeFile(join(dir1, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
|
|
102
|
+
await writeFile(join(dir1, "review-pr.yaml"), await createWorkflowYaml("review-pr"));
|
|
103
|
+
|
|
104
|
+
const entries = await discoverWorkflowPathsEntries([dir1]);
|
|
105
|
+
|
|
106
|
+
expect(entries).toHaveLength(2);
|
|
107
|
+
const names = entries.map((e) => e.name).sort();
|
|
108
|
+
expect(names).toEqual(["review-pr", "solve-issue"]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("should handle multiple directories with first dir winning on collision", async () => {
|
|
112
|
+
const dir1 = join(tmpDir, "workflows1");
|
|
113
|
+
const dir2 = join(tmpDir, "workflows2");
|
|
114
|
+
await mkdir(dir1, { recursive: true });
|
|
115
|
+
await mkdir(dir2, { recursive: true });
|
|
116
|
+
|
|
117
|
+
await writeFile(join(dir1, "solve-issue.yaml"), await createWorkflowYaml("solve-issue", "v1"));
|
|
118
|
+
await writeFile(join(dir2, "solve-issue.yaml"), await createWorkflowYaml("solve-issue", "v2"));
|
|
119
|
+
await writeFile(join(dir2, "deploy.yaml"), await createWorkflowYaml("deploy"));
|
|
120
|
+
|
|
121
|
+
const entries = await discoverWorkflowPathsEntries([dir1, dir2]);
|
|
122
|
+
|
|
123
|
+
expect(entries).toHaveLength(2);
|
|
124
|
+
// solve-issue from dir1 wins
|
|
125
|
+
const solveIssue = entries.find((e) => e.name === "solve-issue");
|
|
126
|
+
expect(solveIssue?.filePath).toContain("workflows1");
|
|
127
|
+
// deploy only in dir2
|
|
128
|
+
expect(entries.find((e) => e.name === "deploy")).toBeDefined();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("should gracefully skip non-existent directories", async () => {
|
|
132
|
+
const dir1 = join(tmpDir, "workflows1");
|
|
133
|
+
await mkdir(dir1, { recursive: true });
|
|
134
|
+
await writeFile(join(dir1, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
|
|
135
|
+
|
|
136
|
+
const entries = await discoverWorkflowPathsEntries([join(tmpDir, "nonexistent"), dir1]);
|
|
137
|
+
|
|
138
|
+
expect(entries).toHaveLength(1);
|
|
139
|
+
expect(entries[0].name).toBe("solve-issue");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("should return empty array for empty dirs list", async () => {
|
|
143
|
+
const entries = await discoverWorkflowPathsEntries([]);
|
|
144
|
+
expect(entries).toHaveLength(0);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("should find folder-based workflows", async () => {
|
|
148
|
+
const dir1 = join(tmpDir, "workflows1");
|
|
149
|
+
const folderWf = join(dir1, "solve-issue");
|
|
150
|
+
await mkdir(folderWf, { recursive: true });
|
|
151
|
+
await writeFile(join(folderWf, "index.yaml"), await createWorkflowYaml("solve-issue"));
|
|
152
|
+
|
|
153
|
+
const entries = await discoverWorkflowPathsEntries([dir1]);
|
|
154
|
+
|
|
155
|
+
expect(entries).toHaveLength(1);
|
|
156
|
+
expect(entries[0].name).toBe("solve-issue");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ── loadWorkflowPaths ─────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
describe("loadWorkflowPaths", () => {
|
|
163
|
+
test("should return empty array when config does not exist", () => {
|
|
164
|
+
const paths = loadWorkflowPaths(join(tmpDir, "nonexistent-storage"));
|
|
165
|
+
expect(paths).toEqual([]);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("should return empty array when workflowPaths key is missing", () => {
|
|
169
|
+
writeConfigWithPaths(storageRoot, []);
|
|
170
|
+
// Write config without workflowPaths
|
|
171
|
+
const { writeFileSync } = require("node:fs") as typeof import("node:fs");
|
|
172
|
+
writeFileSync(getConfigPath(storageRoot), "defaultAgent: hermes\n", "utf8");
|
|
173
|
+
const paths = loadWorkflowPaths(storageRoot);
|
|
174
|
+
expect(paths).toEqual([]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("should resolve paths from config", () => {
|
|
178
|
+
writeConfigWithPaths(storageRoot, ["/absolute/path", "./relative/path"]);
|
|
179
|
+
const paths = loadWorkflowPaths(storageRoot);
|
|
180
|
+
expect(paths[0]).toBe("/absolute/path");
|
|
181
|
+
// relative gets resolved to absolute
|
|
182
|
+
expect(paths[1]).toMatch(/\/relative\/path$/);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ── Thread start resolution with workflowPaths ────────────────────────────────
|
|
187
|
+
|
|
188
|
+
describe("Strategy 3.5: workflowPaths Resolution", () => {
|
|
189
|
+
test("should resolve workflow from workflowPaths when not found locally", async () => {
|
|
190
|
+
await makeUwfStore(storageRoot);
|
|
191
|
+
|
|
192
|
+
// Create workflow in a workflowPaths dir
|
|
193
|
+
const globalDir = join(tmpDir, "global-workflows");
|
|
194
|
+
await mkdir(globalDir, { recursive: true });
|
|
195
|
+
await writeFile(join(globalDir, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
|
|
196
|
+
|
|
197
|
+
// Configure workflowPaths
|
|
198
|
+
writeConfigWithPaths(storageRoot, [globalDir]);
|
|
199
|
+
|
|
200
|
+
// No local .workflows/ — should fall through to workflowPaths
|
|
201
|
+
const result = await cmdThreadStart(storageRoot, "solve-issue", "prompt", projectRoot);
|
|
202
|
+
|
|
203
|
+
expect(result.workflow).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
|
204
|
+
const uwf = await makeUwfStore(storageRoot);
|
|
205
|
+
const node = uwf.store.cas.get(result.workflow);
|
|
206
|
+
expect(node).not.toBeNull();
|
|
207
|
+
if (node !== null) {
|
|
208
|
+
expect((node.payload as WorkflowPayload).name).toBe("solve-issue");
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("should prefer local .workflows/ over workflowPaths", async () => {
|
|
213
|
+
await makeUwfStore(storageRoot);
|
|
214
|
+
|
|
215
|
+
// Create workflow in workflowPaths dir
|
|
216
|
+
const globalDir = join(tmpDir, "global-workflows");
|
|
217
|
+
await mkdir(globalDir, { recursive: true });
|
|
218
|
+
await writeFile(
|
|
219
|
+
join(globalDir, "solve-issue.yaml"),
|
|
220
|
+
await createWorkflowYaml("solve-issue", "global"),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Create workflow in local .workflows/
|
|
224
|
+
const localDir = join(projectRoot, ".workflows");
|
|
225
|
+
await mkdir(localDir, { recursive: true });
|
|
226
|
+
await writeFile(
|
|
227
|
+
join(localDir, "solve-issue.yaml"),
|
|
228
|
+
await createWorkflowYaml("solve-issue", "local"),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
writeConfigWithPaths(storageRoot, [globalDir]);
|
|
232
|
+
|
|
233
|
+
const result = await cmdThreadStart(storageRoot, "solve-issue", "prompt", projectRoot);
|
|
234
|
+
|
|
235
|
+
const uwf = await makeUwfStore(storageRoot);
|
|
236
|
+
const node = uwf.store.cas.get(result.workflow);
|
|
237
|
+
expect(node).not.toBeNull();
|
|
238
|
+
if (node !== null) {
|
|
239
|
+
// Should be the local version
|
|
240
|
+
expect((node.payload as WorkflowPayload).description).toBe("Test workflow (local)");
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("should prefer workflowPaths over global registry", async () => {
|
|
245
|
+
const uwf = await makeUwfStore(storageRoot);
|
|
246
|
+
|
|
247
|
+
// Register in global registry
|
|
248
|
+
const globalHash = await storeWorkflow(uwf, "solve-issue");
|
|
249
|
+
saveWorkflowRegistry(uwf.varStore, "solve-issue", globalHash);
|
|
250
|
+
|
|
251
|
+
// Create workflow in workflowPaths dir
|
|
252
|
+
const pathsDir = join(tmpDir, "paths-workflows");
|
|
253
|
+
await mkdir(pathsDir, { recursive: true });
|
|
254
|
+
await writeFile(
|
|
255
|
+
join(pathsDir, "solve-issue.yaml"),
|
|
256
|
+
await createWorkflowYaml("solve-issue", "from-paths"),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
writeConfigWithPaths(storageRoot, [pathsDir]);
|
|
260
|
+
|
|
261
|
+
const isolatedRoot = join(tmpDir, "isolated");
|
|
262
|
+
await mkdir(isolatedRoot, { recursive: true });
|
|
263
|
+
|
|
264
|
+
const result = await cmdThreadStart(storageRoot, "solve-issue", "prompt", isolatedRoot);
|
|
265
|
+
|
|
266
|
+
const uwf2 = await makeUwfStore(storageRoot);
|
|
267
|
+
const node = uwf2.store.cas.get(result.workflow);
|
|
268
|
+
expect(node).not.toBeNull();
|
|
269
|
+
if (node !== null) {
|
|
270
|
+
expect((node.payload as WorkflowPayload).description).toBe("Test workflow (from-paths)");
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ── cmdWorkflowList with workflowPaths ────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
describe("cmdWorkflowList with workflowPaths", () => {
|
|
278
|
+
test("should include workflowPaths entries with correct origin", async () => {
|
|
279
|
+
await makeUwfStore(storageRoot);
|
|
280
|
+
|
|
281
|
+
// Create workflow in workflowPaths dir
|
|
282
|
+
const globalDir = join(tmpDir, "global-workflows");
|
|
283
|
+
await mkdir(globalDir, { recursive: true });
|
|
284
|
+
await writeFile(join(globalDir, "deploy.yaml"), await createWorkflowYaml("deploy"));
|
|
285
|
+
|
|
286
|
+
writeConfigWithPaths(storageRoot, [globalDir]);
|
|
287
|
+
|
|
288
|
+
const result = await cmdWorkflowList(storageRoot, projectRoot);
|
|
289
|
+
|
|
290
|
+
const deploy = result.find((e) => e.name === "deploy");
|
|
291
|
+
expect(deploy).toBeDefined();
|
|
292
|
+
expect(deploy?.origin).toBe("paths");
|
|
293
|
+
expect(deploy?.hash).toBe("(paths)");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("should show local over paths when names collide", async () => {
|
|
297
|
+
await makeUwfStore(storageRoot);
|
|
298
|
+
|
|
299
|
+
// Create in both local and paths
|
|
300
|
+
const globalDir = join(tmpDir, "global-workflows");
|
|
301
|
+
await mkdir(globalDir, { recursive: true });
|
|
302
|
+
await writeFile(join(globalDir, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
|
|
303
|
+
|
|
304
|
+
const localDir = join(projectRoot, ".workflows");
|
|
305
|
+
await mkdir(localDir, { recursive: true });
|
|
306
|
+
await writeFile(join(localDir, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
|
|
307
|
+
|
|
308
|
+
writeConfigWithPaths(storageRoot, [globalDir]);
|
|
309
|
+
|
|
310
|
+
const result = await cmdWorkflowList(storageRoot, projectRoot);
|
|
311
|
+
|
|
312
|
+
const solveIssue = result.filter((e) => e.name === "solve-issue");
|
|
313
|
+
expect(solveIssue).toHaveLength(1);
|
|
314
|
+
expect(solveIssue[0].origin).toBe("local");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("should show paths over registry when names collide", async () => {
|
|
318
|
+
const uwf = await makeUwfStore(storageRoot);
|
|
319
|
+
|
|
320
|
+
// Register globally
|
|
321
|
+
const hash = await storeWorkflow(uwf, "deploy");
|
|
322
|
+
saveWorkflowRegistry(uwf.varStore, "deploy", hash);
|
|
323
|
+
|
|
324
|
+
// Also in paths
|
|
325
|
+
const pathsDir = join(tmpDir, "paths-workflows");
|
|
326
|
+
await mkdir(pathsDir, { recursive: true });
|
|
327
|
+
await writeFile(join(pathsDir, "deploy.yaml"), await createWorkflowYaml("deploy"));
|
|
328
|
+
|
|
329
|
+
writeConfigWithPaths(storageRoot, [pathsDir]);
|
|
330
|
+
|
|
331
|
+
const result = await cmdWorkflowList(storageRoot, projectRoot);
|
|
332
|
+
|
|
333
|
+
const deploy = result.filter((e) => e.name === "deploy");
|
|
334
|
+
expect(deploy).toHaveLength(1);
|
|
335
|
+
expect(deploy[0].origin).toBe("paths");
|
|
336
|
+
});
|
|
337
|
+
});
|
|
@@ -6,17 +6,11 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
|
6
6
|
import { stringify } from "yaml";
|
|
7
7
|
import { cmdThreadStart } from "../commands/thread.js";
|
|
8
8
|
import type { UwfStore } from "../store.js";
|
|
9
|
-
import {
|
|
9
|
+
import { saveWorkflowRegistry } from "../store.js";
|
|
10
|
+
import { makeUwfStore } from "./thread-test-helpers.js";
|
|
10
11
|
|
|
11
12
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
12
13
|
|
|
13
|
-
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
|
14
|
-
const casDir = join(storageRoot, "cas");
|
|
15
|
-
await mkdir(casDir, { recursive: true });
|
|
16
|
-
process.env.OCAS_HOME = casDir;
|
|
17
|
-
return createUwfStore(storageRoot);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
14
|
function makeMinimalPayload(name: string, description: string): WorkflowPayload {
|
|
21
15
|
return {
|
|
22
16
|
version: 1,
|
|
@@ -67,8 +61,10 @@ async function createWorkflowYaml(name: string, version: string | null = null):
|
|
|
67
61
|
let tmpDir: string;
|
|
68
62
|
let storageRoot: string;
|
|
69
63
|
let projectRoot: string;
|
|
64
|
+
let savedOcasHome: string | undefined;
|
|
70
65
|
|
|
71
66
|
beforeEach(async () => {
|
|
67
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
72
68
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-wf-resolve-test-"));
|
|
73
69
|
storageRoot = join(tmpDir, "storage");
|
|
74
70
|
projectRoot = join(tmpDir, "project");
|
|
@@ -77,6 +73,11 @@ beforeEach(async () => {
|
|
|
77
73
|
});
|
|
78
74
|
|
|
79
75
|
afterEach(async () => {
|
|
76
|
+
if (savedOcasHome === undefined) {
|
|
77
|
+
delete process.env.OCAS_HOME;
|
|
78
|
+
} else {
|
|
79
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
80
|
+
}
|
|
80
81
|
await rm(tmpDir, { recursive: true, force: true });
|
|
81
82
|
});
|
|
82
83
|
|
|
@@ -6,17 +6,11 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
|
6
6
|
import { stringify } from "yaml";
|
|
7
7
|
import { cmdWorkflowShow } from "../commands/workflow.js";
|
|
8
8
|
import type { UwfStore } from "../store.js";
|
|
9
|
-
import {
|
|
9
|
+
import { saveWorkflowRegistry } from "../store.js";
|
|
10
|
+
import { makeUwfStore } from "./thread-test-helpers.js";
|
|
10
11
|
|
|
11
12
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
12
13
|
|
|
13
|
-
async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
|
|
14
|
-
const casDir = join(storageRoot, "cas");
|
|
15
|
-
await mkdir(casDir, { recursive: true });
|
|
16
|
-
process.env.OCAS_HOME = casDir;
|
|
17
|
-
return createUwfStore(storageRoot);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
14
|
function makeMinimalPayload(name: string, description: string): WorkflowPayload {
|
|
21
15
|
return {
|
|
22
16
|
version: 1,
|
|
@@ -67,8 +61,10 @@ async function createWorkflowYaml(name: string, version: string | null = null):
|
|
|
67
61
|
let tmpDir: string;
|
|
68
62
|
let storageRoot: string;
|
|
69
63
|
let projectRoot: string;
|
|
64
|
+
let savedOcasHome: string | undefined;
|
|
70
65
|
|
|
71
66
|
beforeEach(async () => {
|
|
67
|
+
savedOcasHome = process.env.OCAS_HOME;
|
|
72
68
|
tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-wf-show-test-"));
|
|
73
69
|
storageRoot = join(tmpDir, "storage");
|
|
74
70
|
projectRoot = join(tmpDir, "project");
|
|
@@ -77,6 +73,11 @@ beforeEach(async () => {
|
|
|
77
73
|
});
|
|
78
74
|
|
|
79
75
|
afterEach(async () => {
|
|
76
|
+
if (savedOcasHome === undefined) {
|
|
77
|
+
delete process.env.OCAS_HOME;
|
|
78
|
+
} else {
|
|
79
|
+
process.env.OCAS_HOME = savedOcasHome;
|
|
80
|
+
}
|
|
80
81
|
await rm(tmpDir, { recursive: true, force: true });
|
|
81
82
|
});
|
|
82
83
|
|