lazycodex-ai 5.0.0-beta.46 → 5.0.0-beta.48
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/dist/cli/index.js +15 -15
- package/dist/cli-node/index.js +15 -15
- package/package.json +1 -1
- package/packages/lsp-daemon/dist/cli.js +6 -3
- package/packages/lsp-daemon/dist/client.js +6 -3
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +6 -1
- package/packages/lsp-daemon/dist/ensure-daemon.js +9 -2
- package/packages/lsp-daemon/dist/index.js +6 -3
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +6 -3
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +15 -7
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +294 -96
- package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/constants.js +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +12 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +21 -6
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +10 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.d.ts +4 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +25 -57
- package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.d.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.js +227 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +17 -3
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +10 -7
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +18 -7
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/constants.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +24 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +25 -5
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +11 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +34 -64
- package/packages/omo-codex/plugin/components/ulw-loop/src/state-lock.ts +239 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +16 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +14 -4
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint-continuation.test.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint.test.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-commands.test.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-complete-goals.test.ts +3 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-create-goals.test.ts +10 -8
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-entrypoint.test.ts +12 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-json-errors.test.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-scope-required.test.ts +144 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +7 -5
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-steering-batch.test.ts +8 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-validation-batch.test.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/cli-session.ts +4 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io-cross-process.test.ts +106 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io.test.ts +36 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +37 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/state-lock.test.ts +218 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -1
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +10 -7
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
|
@@ -8,9 +8,11 @@ import { ulwLoopCommand } from "../src/cli-commands.js";
|
|
|
8
8
|
import { writePlan } from "../src/plan-io.js";
|
|
9
9
|
import { validateQualityGate } from "../src/quality-gate.js";
|
|
10
10
|
import { goal, plan } from "./fixtures/checkpoint-builders.js";
|
|
11
|
+
import { CLI_TEST_SCOPE, CLI_TEST_SESSION_ID } from "./fixtures/cli-session.js";
|
|
11
12
|
|
|
12
13
|
let repo: string;
|
|
13
14
|
let output: string[];
|
|
15
|
+
let originalOmoSessionId: string | undefined;
|
|
14
16
|
|
|
15
17
|
beforeEach(async () => {
|
|
16
18
|
repo = await mkdtemp(join(tmpdir(), "ulw-template-"));
|
|
@@ -21,10 +23,14 @@ beforeEach(async () => {
|
|
|
21
23
|
return true;
|
|
22
24
|
});
|
|
23
25
|
delete process.env["OMO_AGENT_TOOLKIT_SURFACE"];
|
|
26
|
+
originalOmoSessionId = process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
27
|
+
process.env["OMO_ULW_LOOP_SESSION_ID"] = CLI_TEST_SESSION_ID;
|
|
24
28
|
});
|
|
25
29
|
|
|
26
30
|
afterEach(async () => {
|
|
27
31
|
vi.restoreAllMocks();
|
|
32
|
+
if (originalOmoSessionId === undefined) delete process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
33
|
+
else process.env["OMO_ULW_LOOP_SESSION_ID"] = originalOmoSessionId;
|
|
28
34
|
await rm(repo, { recursive: true, force: true });
|
|
29
35
|
});
|
|
30
36
|
|
|
@@ -54,6 +60,7 @@ describe("checkpoint --print-template", () => {
|
|
|
54
60
|
activeGoalId: "G001",
|
|
55
61
|
codexObjective: "Exact objective from goals.json",
|
|
56
62
|
}),
|
|
63
|
+
CLI_TEST_SCOPE,
|
|
57
64
|
);
|
|
58
65
|
process.env["OMO_AGENT_TOOLKIT_SURFACE"] = "omo-senpi";
|
|
59
66
|
const result = await run(["--print-template", "--goal-id", "G001", "--json"]);
|
|
@@ -86,6 +93,7 @@ describe("checkpoint --print-template", () => {
|
|
|
86
93
|
activeGoalId: "G001",
|
|
87
94
|
codexObjective: "Exact objective from goals.json",
|
|
88
95
|
}),
|
|
96
|
+
CLI_TEST_SCOPE,
|
|
89
97
|
);
|
|
90
98
|
process.env["OMO_AGENT_TOOLKIT_SURFACE"] = "omo-senpi";
|
|
91
99
|
const result = await run(["--print-template", "--goal-id", "G001", "--json"]);
|
|
@@ -96,7 +104,7 @@ describe("checkpoint --print-template", () => {
|
|
|
96
104
|
expect(result["codexGoalTemplate"]).toEqual({
|
|
97
105
|
goal: { objective: "Exact objective from goals.json", status: "complete" },
|
|
98
106
|
});
|
|
99
|
-
expect(result["attemptDir"]).toBe(".omo/evidence/ulw/
|
|
107
|
+
expect(result["attemptDir"]).toBe(".omo/evidence/ulw/cli-test/G001/a2");
|
|
100
108
|
});
|
|
101
109
|
|
|
102
110
|
it("#given an active v2 plan #when printed without any goal id #then targets the active goal", async () => {
|
|
@@ -107,12 +115,13 @@ describe("checkpoint --print-template", () => {
|
|
|
107
115
|
activeGoalId: "G001",
|
|
108
116
|
codexObjective: "Exact objective from goals.json",
|
|
109
117
|
}),
|
|
118
|
+
CLI_TEST_SCOPE,
|
|
110
119
|
);
|
|
111
120
|
process.env["OMO_AGENT_TOOLKIT_SURFACE"] = "omo-senpi";
|
|
112
121
|
const result = await run(["--print-template", "--json"]);
|
|
113
122
|
const gate = result["qualityGateTemplate"] as Record<string, unknown>;
|
|
114
123
|
expect(Object.keys(gate)).toEqual(["manualQa", "gateReview", "iteration", "criteriaCoverage"]);
|
|
115
|
-
expect(result["attemptDir"]).toBe(".omo/evidence/ulw/
|
|
124
|
+
expect(result["attemptDir"]).toBe(".omo/evidence/ulw/cli-test/G001/a1");
|
|
116
125
|
});
|
|
117
126
|
|
|
118
127
|
it("#given two goals #when printed with an explicit non-active goal id #then targets that goal's attempt dir", async () => {
|
|
@@ -123,13 +132,14 @@ describe("checkpoint --print-template", () => {
|
|
|
123
132
|
activeGoalId: "G001",
|
|
124
133
|
codexObjective: "Exact objective from goals.json",
|
|
125
134
|
}),
|
|
135
|
+
CLI_TEST_SCOPE,
|
|
126
136
|
);
|
|
127
137
|
const result = await run(["--print-template", "--goal-id", "G002", "--json"]);
|
|
128
|
-
expect(result["attemptDir"]).toBe(".omo/evidence/ulw/
|
|
138
|
+
expect(result["attemptDir"]).toBe(".omo/evidence/ulw/cli-test/G002/a3");
|
|
129
139
|
});
|
|
130
140
|
|
|
131
141
|
it("#given a v1 plan #when printed without status #then exits successfully and gives v1 guidance", async () => {
|
|
132
|
-
await writePlan(repo, plan([goal({ id: "G001" })]));
|
|
142
|
+
await writePlan(repo, plan([goal({ id: "G001" })]), CLI_TEST_SCOPE);
|
|
133
143
|
const result = await run(["--print-template", "--goal-id", "G001", "--json"]);
|
|
134
144
|
expect(result["guidance"]).toContain("evidence-layout v1");
|
|
135
145
|
expect(result).not.toHaveProperty("attemptDir");
|
package/packages/omo-codex/plugin/components/ulw-loop/test/cli-checkpoint-continuation.test.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { join } from "node:path";
|
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
6
6
|
import { ulwLoopCommand } from "../src/cli-commands.js";
|
|
7
|
-
import {
|
|
7
|
+
import { aggregateCodexObjectiveForScope } from "../src/goal-status.js";
|
|
8
|
+
import { CLI_TEST_SCOPE, CLI_TEST_SESSION_ID } from "./fixtures/cli-session.js";
|
|
8
9
|
|
|
9
10
|
let testDir: string;
|
|
10
11
|
let out: string[];
|
|
@@ -15,6 +16,7 @@ beforeEach(async () => {
|
|
|
15
16
|
out = [];
|
|
16
17
|
originalOmoSessionId = process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
17
18
|
delete process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
19
|
+
process.env["OMO_ULW_LOOP_SESSION_ID"] = CLI_TEST_SESSION_ID;
|
|
18
20
|
vi.spyOn(process, "cwd").mockReturnValue(testDir);
|
|
19
21
|
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
20
22
|
out.push(chunk.toString());
|
|
@@ -39,7 +41,7 @@ function stdoutJson(): Record<string, unknown> {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
function codexSnapshot(): string {
|
|
42
|
-
return JSON.stringify({ goal: { objective:
|
|
44
|
+
return JSON.stringify({ goal: { objective: aggregateCodexObjectiveForScope(CLI_TEST_SCOPE), status: "active" } });
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
async function passCriterion(criterionId: string): Promise<void> {
|
|
@@ -4,7 +4,8 @@ import { join } from "node:path";
|
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
6
6
|
import { ulwLoopCommand } from "../src/cli-commands.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { aggregateCodexObjectiveForScope } from "../src/goal-status.js";
|
|
8
|
+
import { CLI_TEST_SCOPE, CLI_TEST_SESSION_ID } from "./fixtures/cli-session.js";
|
|
8
9
|
import { QA_DIR, qualityGateJson } from "./fixtures/quality-gate-builder.js";
|
|
9
10
|
|
|
10
11
|
let testDir: string;
|
|
@@ -24,6 +25,7 @@ beforeEach(async () => {
|
|
|
24
25
|
delete process.env["CODEX_SESSION_ID"];
|
|
25
26
|
delete process.env["CODEX_THREAD_ID"];
|
|
26
27
|
delete process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
28
|
+
process.env["OMO_ULW_LOOP_SESSION_ID"] = CLI_TEST_SESSION_ID;
|
|
27
29
|
vi.spyOn(process, "cwd").mockReturnValue(testDir);
|
|
28
30
|
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
29
31
|
out.push(chunk.toString());
|
|
@@ -56,7 +58,7 @@ function stdoutJson(): Record<string, unknown> {
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
function codexSnapshot(status: "active" | "complete" = "active"): string {
|
|
59
|
-
return JSON.stringify({ goal: { objective:
|
|
61
|
+
return JSON.stringify({ goal: { objective: aggregateCodexObjectiveForScope(CLI_TEST_SCOPE), status } });
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
async function createPlan(brief = "- Goal A\n- Goal B"): Promise<Record<string, unknown>> {
|
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
6
6
|
import { ulwLoopCommand } from "../src/cli-commands.ts";
|
|
7
|
+
import { CLI_TEST_SESSION_ID } from "./fixtures/cli-session.js";
|
|
7
8
|
|
|
8
9
|
let testDir: string;
|
|
9
10
|
let out: string[];
|
|
@@ -22,6 +23,7 @@ beforeEach(async () => {
|
|
|
22
23
|
delete process.env["CODEX_SESSION_ID"];
|
|
23
24
|
delete process.env["CODEX_THREAD_ID"];
|
|
24
25
|
delete process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
26
|
+
process.env["OMO_ULW_LOOP_SESSION_ID"] = CLI_TEST_SESSION_ID;
|
|
25
27
|
vi.spyOn(process, "cwd").mockReturnValue(testDir);
|
|
26
28
|
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
27
29
|
out.push(chunk.toString());
|
|
@@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
5
5
|
|
|
6
6
|
import { ulwLoopCommand } from "../src/cli-commands.ts";
|
|
7
7
|
import { ulwLoopLedgerPath } from "../src/paths.ts";
|
|
8
|
+
import { CLI_TEST_SCOPE, CLI_TEST_SESSION_ID } from "./fixtures/cli-session.js";
|
|
8
9
|
|
|
9
10
|
let testDir: string;
|
|
10
11
|
let out: string[];
|
|
@@ -21,6 +22,7 @@ beforeEach(async () => {
|
|
|
21
22
|
delete process.env["CODEX_SESSION_ID"];
|
|
22
23
|
delete process.env["CODEX_THREAD_ID"];
|
|
23
24
|
delete process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
25
|
+
process.env["OMO_ULW_LOOP_SESSION_ID"] = CLI_TEST_SESSION_ID;
|
|
24
26
|
vi.spyOn(process, "cwd").mockReturnValue(testDir);
|
|
25
27
|
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
26
28
|
out.push(chunk.toString());
|
|
@@ -48,7 +50,7 @@ function stdoutJson(): Record<string, unknown> {
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
async function ledgerKinds(): Promise<string[]> {
|
|
51
|
-
const raw = await readFile(ulwLoopLedgerPath(testDir), "utf8");
|
|
53
|
+
const raw = await readFile(ulwLoopLedgerPath(testDir, CLI_TEST_SCOPE), "utf8");
|
|
52
54
|
return raw
|
|
53
55
|
.split(/\r?\n/)
|
|
54
56
|
.filter(Boolean)
|
|
@@ -4,7 +4,8 @@ import { join } from "node:path";
|
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
6
6
|
import { ulwLoopCommand } from "../src/cli-commands.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { aggregateCodexObjectiveForScope } from "../src/goal-status.js";
|
|
8
|
+
import { CLI_TEST_SCOPE, CLI_TEST_SESSION_ID } from "./fixtures/cli-session.js";
|
|
8
9
|
import { qualityGateJson } from "./fixtures/quality-gate-builder.js";
|
|
9
10
|
|
|
10
11
|
let testDir: string;
|
|
@@ -27,6 +28,7 @@ beforeEach(async () => {
|
|
|
27
28
|
delete process.env["CODEX_THREAD_ID"];
|
|
28
29
|
delete process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
29
30
|
delete process.env["PI_SESSION_ID"];
|
|
31
|
+
process.env["PI_SESSION_ID"] = CLI_TEST_SESSION_ID;
|
|
30
32
|
vi.spyOn(process, "cwd").mockReturnValue(testDir);
|
|
31
33
|
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
32
34
|
out.push(chunk.toString());
|
|
@@ -61,7 +63,7 @@ function stdoutJson(): Record<string, unknown> {
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
function codexSnapshot(status: "active" | "complete" = "active"): string {
|
|
64
|
-
return JSON.stringify({ goal: { objective:
|
|
66
|
+
return JSON.stringify({ goal: { objective: aggregateCodexObjectiveForScope(CLI_TEST_SCOPE), status } });
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
async function qualityGate(): Promise<string> {
|
|
@@ -101,9 +103,9 @@ describe("ulwLoopCommand create-goals", () => {
|
|
|
101
103
|
const parsed = stdoutJson();
|
|
102
104
|
expect(parsed).toMatchObject({ ok: true });
|
|
103
105
|
expect(parsed).toHaveProperty("plan.goals.0.successCriteria.0.id", "C001");
|
|
104
|
-
expect(await readFile(join(testDir, ".omo/ulw-loop/brief.md"), "utf8")).toContain("Goal A");
|
|
105
|
-
expect(await readFile(join(testDir, ".omo/ulw-loop/goals.json"), "utf8")).toContain("successCriteria");
|
|
106
|
-
expect(await readFile(join(testDir, ".omo/ulw-loop/ledger.jsonl"), "utf8")).toContain("plan_created");
|
|
106
|
+
expect(await readFile(join(testDir, ".omo/ulw-loop/cli-test/brief.md"), "utf8")).toContain("Goal A");
|
|
107
|
+
expect(await readFile(join(testDir, ".omo/ulw-loop/cli-test/goals.json"), "utf8")).toContain("successCriteria");
|
|
108
|
+
expect(await readFile(join(testDir, ".omo/ulw-loop/cli-test/ledger.jsonl"), "utf8")).toContain("plan_created");
|
|
107
109
|
});
|
|
108
110
|
|
|
109
111
|
it("#given completed default aggregate #when creating another default plan #then guides to a fresh session", async () => {
|
|
@@ -142,7 +144,7 @@ describe("ulwLoopCommand create-goals", () => {
|
|
|
142
144
|
|
|
143
145
|
expect(await ulwLoopCommand(["status", "--json"])).toBe(0);
|
|
144
146
|
|
|
145
|
-
expect(stdoutJson()).toMatchObject({ currentAttemptDir: ".omo/evidence/ulw/
|
|
147
|
+
expect(stdoutJson()).toMatchObject({ currentAttemptDir: ".omo/evidence/ulw/cli-test/G001-goal-a/a1" });
|
|
146
148
|
});
|
|
147
149
|
|
|
148
150
|
it("#given two session ids #when creating goals #then writes isolated session-scoped plans", async () => {
|
|
@@ -212,7 +214,7 @@ describe("ulwLoopCommand create-goals", () => {
|
|
|
212
214
|
|
|
213
215
|
expect(code).toBe(1);
|
|
214
216
|
expect(err.join("")).toContain("--session-id requires a non-empty value");
|
|
215
|
-
await expect(readFile(join(testDir, ".omo/ulw-loop/goals.json"), "utf8")).rejects.toThrow();
|
|
217
|
+
await expect(readFile(join(testDir, ".omo/ulw-loop/cli-test/goals.json"), "utf8")).rejects.toThrow();
|
|
216
218
|
});
|
|
217
219
|
|
|
218
220
|
it("#given an empty --session-id= #when creating goals #then it fails and writes no plan", async () => {
|
|
@@ -220,6 +222,6 @@ describe("ulwLoopCommand create-goals", () => {
|
|
|
220
222
|
|
|
221
223
|
expect(code).toBe(1);
|
|
222
224
|
expect(err.join("")).toContain("--session-id requires a non-empty value");
|
|
223
|
-
await expect(readFile(join(testDir, ".omo/ulw-loop/goals.json"), "utf8")).rejects.toThrow();
|
|
225
|
+
await expect(readFile(join(testDir, ".omo/ulw-loop/cli-test/goals.json"), "utf8")).rejects.toThrow();
|
|
224
226
|
});
|
|
225
227
|
});
|
|
@@ -74,7 +74,7 @@ afterEach(async () => {
|
|
|
74
74
|
|
|
75
75
|
describe("dist/cli.js entrypoint dispatch", () => {
|
|
76
76
|
it("#given no plan #when invoked with bare 'status --json' #then routes into ulw-loop instead of unknown command", async () => {
|
|
77
|
-
const result = await runCli(["status", "--json"]);
|
|
77
|
+
const result = await runCli(["status", "--session-id", "s1", "--json"]);
|
|
78
78
|
|
|
79
79
|
const combined = `${result.stdout}${result.stderr}`;
|
|
80
80
|
expect(combined).toContain("No ulw-loop plan found");
|
|
@@ -83,7 +83,7 @@ describe("dist/cli.js entrypoint dispatch", () => {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
it("#given no plan #when invoked with legacy 'ulw-loop status --json' #then still routes into ulw-loop", async () => {
|
|
86
|
-
const result = await runCli(["ulw-loop", "status", "--json"]);
|
|
86
|
+
const result = await runCli(["ulw-loop", "status", "--session-id", "s1", "--json"]);
|
|
87
87
|
|
|
88
88
|
const combined = `${result.stdout}${result.stderr}`;
|
|
89
89
|
expect(combined).toContain("No ulw-loop plan found");
|
|
@@ -91,6 +91,16 @@ describe("dist/cli.js entrypoint dispatch", () => {
|
|
|
91
91
|
expect(result.code).toBe(1);
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
it("#given no session flag and no session env #when invoked with 'status --json' #then refuses the unscoped root instead of reading it", async () => {
|
|
95
|
+
const result = await runCli(["status", "--json"]);
|
|
96
|
+
|
|
97
|
+
expect(result.code).toBe(1);
|
|
98
|
+
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
99
|
+
ok: false,
|
|
100
|
+
error: { code: "ULW_LOOP_SESSION_SCOPE_REQUIRED", details: { flag: "--session-id" } },
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
94
104
|
it("#given the top-level entrypoint #when invoked with 'help' #then prints the merged hook and subcommand usage and exits 0", async () => {
|
|
95
105
|
const result = await runCli(["help"]);
|
|
96
106
|
|
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
6
6
|
import { ulwLoopCommand } from "../src/cli-commands.ts";
|
|
7
|
+
import { CLI_TEST_SESSION_ID } from "./fixtures/cli-session.js";
|
|
7
8
|
|
|
8
9
|
let testDir: string;
|
|
9
10
|
let out: string[];
|
|
@@ -22,6 +23,7 @@ beforeEach(async () => {
|
|
|
22
23
|
delete process.env["CODEX_SESSION_ID"];
|
|
23
24
|
delete process.env["CODEX_THREAD_ID"];
|
|
24
25
|
delete process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
26
|
+
process.env["OMO_ULW_LOOP_SESSION_ID"] = CLI_TEST_SESSION_ID;
|
|
25
27
|
vi.spyOn(process, "cwd").mockReturnValue(testDir);
|
|
26
28
|
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
27
29
|
out.push(chunk.toString());
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
import { ulwLoopCommand } from "../src/cli-commands.ts";
|
|
8
|
+
import { ulwLoopAttemptEvidenceDir } from "../src/paths.ts";
|
|
9
|
+
|
|
10
|
+
const SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "PI_SESSION_ID"] as const;
|
|
11
|
+
const NOW = "2026-05-23T00:00:00.000Z";
|
|
12
|
+
|
|
13
|
+
let testDir: string;
|
|
14
|
+
let out: string[];
|
|
15
|
+
let err: string[];
|
|
16
|
+
let savedEnv: Record<string, string | undefined>;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
testDir = await mkdtemp(join(tmpdir(), "ug-cli-scope-"));
|
|
20
|
+
out = [];
|
|
21
|
+
err = [];
|
|
22
|
+
savedEnv = {};
|
|
23
|
+
for (const key of SESSION_ENV_KEYS) {
|
|
24
|
+
savedEnv[key] = process.env[key];
|
|
25
|
+
delete process.env[key];
|
|
26
|
+
}
|
|
27
|
+
vi.spyOn(process, "cwd").mockReturnValue(testDir);
|
|
28
|
+
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
29
|
+
out.push(chunk.toString());
|
|
30
|
+
return true;
|
|
31
|
+
});
|
|
32
|
+
vi.spyOn(process.stderr, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
33
|
+
err.push(chunk.toString());
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(async () => {
|
|
39
|
+
vi.restoreAllMocks();
|
|
40
|
+
for (const key of SESSION_ENV_KEYS) {
|
|
41
|
+
const saved = savedEnv[key];
|
|
42
|
+
if (saved === undefined) delete process.env[key];
|
|
43
|
+
else process.env[key] = saved;
|
|
44
|
+
}
|
|
45
|
+
await rm(testDir, { recursive: true, force: true });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function stdoutJson(): Record<string, unknown> {
|
|
49
|
+
return JSON.parse(out.join(""));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function writeLegacyRootPlan(): Promise<void> {
|
|
53
|
+
await mkdir(join(testDir, ".omo", "ulw-loop", "other-session"), { recursive: true });
|
|
54
|
+
await writeFile(
|
|
55
|
+
join(testDir, ".omo", "ulw-loop", "goals.json"),
|
|
56
|
+
JSON.stringify({
|
|
57
|
+
version: 1,
|
|
58
|
+
createdAt: NOW,
|
|
59
|
+
updatedAt: NOW,
|
|
60
|
+
briefPath: ".omo/ulw-loop/brief.md",
|
|
61
|
+
goalsPath: ".omo/ulw-loop/goals.json",
|
|
62
|
+
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
|
|
63
|
+
goals: [],
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe("ulwLoopCommand session scope", () => {
|
|
69
|
+
describe("#given no --session-id and no session env", () => {
|
|
70
|
+
it("#when create-goals runs #then it refuses with ULW_LOOP_SESSION_SCOPE_REQUIRED and writes nothing", async () => {
|
|
71
|
+
const code = await ulwLoopCommand(["create-goals", "--brief", "- Goal A", "--json"]);
|
|
72
|
+
|
|
73
|
+
expect(code).toBe(1);
|
|
74
|
+
expect(err.join("")).toBe("");
|
|
75
|
+
expect(stdoutJson()).toMatchObject({
|
|
76
|
+
ok: false,
|
|
77
|
+
error: {
|
|
78
|
+
code: "ULW_LOOP_SESSION_SCOPE_REQUIRED",
|
|
79
|
+
details: { flag: "--session-id", existingSessionIds: [] },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
expect(existsSync(join(testDir, ".omo"))).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("#when a legacy root plan exists #then status does not read it and lists the real session ids", async () => {
|
|
86
|
+
await writeLegacyRootPlan();
|
|
87
|
+
|
|
88
|
+
const code = await ulwLoopCommand(["status", "--json"]);
|
|
89
|
+
|
|
90
|
+
expect(code).toBe(1);
|
|
91
|
+
expect(stdoutJson()).toMatchObject({
|
|
92
|
+
ok: false,
|
|
93
|
+
error: { code: "ULW_LOOP_SESSION_SCOPE_REQUIRED", details: { existingSessionIds: ["other-session"] } },
|
|
94
|
+
});
|
|
95
|
+
expect(out.join("")).not.toContain('"goals"');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("#when the human-readable form is used #then the error explains the flag and the env keys", async () => {
|
|
99
|
+
const code = await ulwLoopCommand(["status"]);
|
|
100
|
+
|
|
101
|
+
expect(code).toBe(1);
|
|
102
|
+
expect(err.join("")).toContain("--session-id <id>");
|
|
103
|
+
expect(err.join("")).toContain("PI_SESSION_ID");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("#when help is requested #then it prints usage without needing a scope", async () => {
|
|
107
|
+
expect(await ulwLoopCommand(["help"])).toBe(0);
|
|
108
|
+
expect(await ulwLoopCommand(["status", "--help"])).toBe(0);
|
|
109
|
+
expect(err.join("")).toBe("");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("#given a session env key", () => {
|
|
114
|
+
it("#when status runs #then the scope resolves to that session directory", async () => {
|
|
115
|
+
process.env["PI_SESSION_ID"] = "env-session";
|
|
116
|
+
|
|
117
|
+
const code = await ulwLoopCommand(["status", "--json"]);
|
|
118
|
+
|
|
119
|
+
expect(code).toBe(1);
|
|
120
|
+
expect(stdoutJson()).toMatchObject({
|
|
121
|
+
ok: false,
|
|
122
|
+
error: {
|
|
123
|
+
code: "ULW_LOOP_PLAN_MISSING",
|
|
124
|
+
message: expect.stringContaining(".omo/ulw-loop/env-session/goals.json"),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("ulwLoopAttemptEvidenceDir", () => {
|
|
132
|
+
it("#given no session scope #when resolving an attempt dir #then it refuses instead of inventing a placeholder", () => {
|
|
133
|
+
expect(() => ulwLoopAttemptEvidenceDir("G001", 1)).toThrow(
|
|
134
|
+
expect.objectContaining({ code: "ULW_LOOP_SESSION_SCOPE_REQUIRED" }),
|
|
135
|
+
);
|
|
136
|
+
expect(() => ulwLoopAttemptEvidenceDir("G001", 1, { sessionId: " " })).toThrow(
|
|
137
|
+
expect.objectContaining({ code: "ULW_LOOP_SESSION_SCOPE_REQUIRED" }),
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("#given a session scope #when resolving an attempt dir #then it lives under that session", () => {
|
|
142
|
+
expect(ulwLoopAttemptEvidenceDir("G001", 2, { sessionId: "s1" })).toBe(".omo/evidence/ulw/s1/G001/a2");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -7,6 +7,7 @@ import { ulwLoopCommand } from "../src/cli-commands.js";
|
|
|
7
7
|
import { ulwLoopDir } from "../src/paths.js";
|
|
8
8
|
import { writePlan } from "../src/plan-io.js";
|
|
9
9
|
import type { UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "../src/types.js";
|
|
10
|
+
import { CLI_TEST_SCOPE, CLI_TEST_SESSION_ID } from "./fixtures/cli-session.js";
|
|
10
11
|
|
|
11
12
|
const NOW = "2026-05-23T00:00:00.000Z";
|
|
12
13
|
const SESSION_ENV_KEYS = ["OMO_ULW_LOOP_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "PI_SESSION_ID"] as const;
|
|
@@ -25,6 +26,7 @@ beforeEach(async () => {
|
|
|
25
26
|
savedEnv[key] = process.env[key];
|
|
26
27
|
delete process.env[key];
|
|
27
28
|
}
|
|
29
|
+
process.env["OMO_ULW_LOOP_SESSION_ID"] = CLI_TEST_SESSION_ID;
|
|
28
30
|
vi.spyOn(process, "cwd").mockReturnValue(testDir);
|
|
29
31
|
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
30
32
|
out.push(chunk.toString());
|
|
@@ -87,9 +89,9 @@ function plan(goals: UlwLoopItem[], overrides: Partial<UlwLoopPlan> = {}): UlwLo
|
|
|
87
89
|
evidenceLayoutVersion: 2,
|
|
88
90
|
createdAt: NOW,
|
|
89
91
|
updatedAt: NOW,
|
|
90
|
-
briefPath: ".omo/ulw-loop/brief.md",
|
|
91
|
-
goalsPath: ".omo/ulw-loop/goals.json",
|
|
92
|
-
ledgerPath: ".omo/ulw-loop/ledger.jsonl",
|
|
92
|
+
briefPath: ".omo/ulw-loop/cli-test/brief.md",
|
|
93
|
+
goalsPath: ".omo/ulw-loop/cli-test/goals.json",
|
|
94
|
+
ledgerPath: ".omo/ulw-loop/cli-test/ledger.jsonl",
|
|
93
95
|
goals,
|
|
94
96
|
};
|
|
95
97
|
Object.assign(result, overrides);
|
|
@@ -103,8 +105,8 @@ function legacyLayoutPlan(goals: UlwLoopItem[], overrides: Partial<UlwLoopPlan>
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
async function seed(seedPlan: UlwLoopPlan): Promise<void> {
|
|
106
|
-
await mkdir(ulwLoopDir(testDir), { recursive: true });
|
|
107
|
-
await writePlan(testDir, seedPlan);
|
|
108
|
+
await mkdir(ulwLoopDir(testDir, CLI_TEST_SCOPE), { recursive: true });
|
|
109
|
+
await writePlan(testDir, seedPlan, CLI_TEST_SCOPE);
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
describe("#given a plan with no goals", () => {
|
|
@@ -9,6 +9,7 @@ import { parseSteeringProposals } from "../src/cli-steering.js";
|
|
|
9
9
|
import { ulwLoopGoalsPath } from "../src/paths.js";
|
|
10
10
|
import { writePlan } from "../src/plan-io.js";
|
|
11
11
|
import type { UlwLoopPlan } from "../src/types.js";
|
|
12
|
+
import { CLI_TEST_SCOPE } from "./fixtures/cli-session.js";
|
|
12
13
|
|
|
13
14
|
const NOW = "2026-05-23T00:00:00.000Z";
|
|
14
15
|
|
|
@@ -45,9 +46,11 @@ describe("parseSteeringProposals", () => {
|
|
|
45
46
|
it("#given kind and proposals-json #when steering through CLI #then rejects conflict without mutation", async () => {
|
|
46
47
|
const repo = await mkdtemp(join(tmpdir(), "ug-cli-steer-batch-conflict-"));
|
|
47
48
|
const out: string[] = [];
|
|
49
|
+
const originalOmoSessionId = process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
48
50
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
process.env["OMO_ULW_LOOP_SESSION_ID"] = CLI_TEST_SCOPE.sessionId;
|
|
52
|
+
await writePlan(repo, plan(), CLI_TEST_SCOPE);
|
|
53
|
+
const before = await readFile(ulwLoopGoalsPath(repo, CLI_TEST_SCOPE), "utf8");
|
|
51
54
|
vi.spyOn(process, "cwd").mockReturnValue(repo);
|
|
52
55
|
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
53
56
|
out.push(chunk.toString());
|
|
@@ -58,8 +61,10 @@ describe("parseSteeringProposals", () => {
|
|
|
58
61
|
expect(await ulwLoopCommand(["steer", "--kind", "annotate_ledger", "--evidence", "flag", "--rationale", "flag", "--proposals-json", '[{"kind":"annotate_ledger","evidence":"batch","rationale":"batch"}]', "--json"])).toBe(1);
|
|
59
62
|
|
|
60
63
|
expect(JSON.parse(out.join(""))).toHaveProperty("error.code", "ULW_LOOP_STEERING_BATCH_CONFLICT");
|
|
61
|
-
expect(await readFile(ulwLoopGoalsPath(repo), "utf8")).toBe(before);
|
|
64
|
+
expect(await readFile(ulwLoopGoalsPath(repo, CLI_TEST_SCOPE), "utf8")).toBe(before);
|
|
62
65
|
} finally {
|
|
66
|
+
if (originalOmoSessionId === undefined) delete process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
67
|
+
else process.env["OMO_ULW_LOOP_SESSION_ID"] = originalOmoSessionId;
|
|
63
68
|
vi.restoreAllMocks();
|
|
64
69
|
await rm(repo, { recursive: true, force: true });
|
|
65
70
|
}
|
|
@@ -4,13 +4,17 @@ import { join } from "node:path";
|
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
6
6
|
import { ulwLoopCommand } from "../src/cli-commands.js";
|
|
7
|
+
import { CLI_TEST_SESSION_ID } from "./fixtures/cli-session.js";
|
|
7
8
|
|
|
8
9
|
let testDir: string;
|
|
9
10
|
let out: string[];
|
|
11
|
+
let originalOmoSessionId: string | undefined;
|
|
10
12
|
|
|
11
13
|
beforeEach(async () => {
|
|
12
14
|
testDir = await mkdtemp(join(tmpdir(), "ug-cli-validation-batch-"));
|
|
13
15
|
out = [];
|
|
16
|
+
originalOmoSessionId = process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
17
|
+
process.env["OMO_ULW_LOOP_SESSION_ID"] = CLI_TEST_SESSION_ID;
|
|
14
18
|
vi.spyOn(process, "cwd").mockReturnValue(testDir);
|
|
15
19
|
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array): boolean => {
|
|
16
20
|
out.push(chunk.toString());
|
|
@@ -21,6 +25,8 @@ beforeEach(async () => {
|
|
|
21
25
|
|
|
22
26
|
afterEach(async () => {
|
|
23
27
|
vi.restoreAllMocks();
|
|
28
|
+
if (originalOmoSessionId === undefined) delete process.env["OMO_ULW_LOOP_SESSION_ID"];
|
|
29
|
+
else process.env["OMO_ULW_LOOP_SESSION_ID"] = originalOmoSessionId;
|
|
24
30
|
await rm(testDir, { recursive: true, force: true });
|
|
25
31
|
});
|
|
26
32
|
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Every CLI-level test runs inside one explicit session scope: the CLI refuses to
|
|
2
|
+
// touch the unscoped .omo/ulw-loop root, so state and evidence live under this id.
|
|
3
|
+
export const CLI_TEST_SESSION_ID = "cli-test";
|
|
4
|
+
export const CLI_TEST_SCOPE = { sessionId: CLI_TEST_SESSION_ID } as const;
|
|
@@ -2,9 +2,10 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { reviewerRolesFor, type UlwLoopToolkitSurface } from "../../src/surface.js";
|
|
5
|
+
import { CLI_TEST_SESSION_ID } from "./cli-session.js";
|
|
5
6
|
|
|
6
7
|
// v2 evidence layout: artifacts must live inside the goal's current attempt dir
|
|
7
|
-
export function qaDirFor(goalId: string, attempt = 1, sessionId =
|
|
8
|
+
export function qaDirFor(goalId: string, attempt = 1, sessionId = CLI_TEST_SESSION_ID): string {
|
|
8
9
|
return `.omo/evidence/ulw/${sessionId}/${goalId}/a${attempt}`;
|
|
9
10
|
}
|
|
10
11
|
|