lazycodex-ai 5.0.0-beta.66 → 5.0.0-beta.68

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.
Files changed (69) hide show
  1. package/dist/cli/index.js +15 -15
  2. package/dist/cli-node/index.js +15 -15
  3. package/package.json +1 -1
  4. package/packages/lsp-daemon/dist/cli.js +2 -0
  5. package/packages/lsp-daemon/dist/client.js +2 -0
  6. package/packages/lsp-daemon/dist/index.js +2 -0
  7. package/packages/lsp-tools-mcp/dist/cli.js +2 -0
  8. package/packages/lsp-tools-mcp/dist/lsp/manager.js +2 -0
  9. package/packages/lsp-tools-mcp/dist/mcp.js +2 -0
  10. package/packages/lsp-tools-mcp/dist/tools.js +2 -0
  11. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  12. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  13. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  14. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  15. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  16. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  17. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  18. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  19. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  20. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  21. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +2 -0
  22. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  23. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  25. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  29. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  31. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  32. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  33. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/ulw-loop/CHANGELOG.md +1 -0
  35. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +26 -2
  36. package/packages/omo-codex/plugin/components/ulw-loop/dist/ledger.js +6 -1
  37. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-commit.js +2 -1
  38. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.d.ts +2 -1
  39. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.js +30 -1
  40. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  41. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  42. package/packages/omo-codex/plugin/components/ulw-loop/src/ledger.ts +6 -1
  43. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-commit.ts +9 -1
  44. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-log.ts +30 -2
  45. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-log-legacy-projection.test.ts +228 -0
  46. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  47. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  48. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  49. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  67. package/packages/omo-codex/plugin/package-lock.json +12 -12
  68. package/packages/omo-codex/plugin/package.json +1 -1
  69. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { ulwLoopDir } from "./paths.js";
4
+ import { UlwLoopError } from "./types.js";
4
5
  export function hasCode(error, code) {
5
6
  return error instanceof Error && "code" in error && error.code === code;
6
7
  }
@@ -78,7 +79,35 @@ export function reconcilePlan(dir) {
78
79
  }
79
80
  }
80
81
  const latest = readNewestRecord(dir);
81
- return latest !== undefined && latest.revision >= (cached?.revision ?? 0) ? latest.plan : cached;
82
+ if (latest === undefined)
83
+ return cached;
84
+ if (cached === undefined)
85
+ return latest.plan;
86
+ if ((cached.revision ?? 0) > latest.revision)
87
+ return cached;
88
+ // A goals.json written outside the commit log (the removed tool path rewrote it wholesale, never
89
+ // stamping `revision`) can hold goals no snapshot has. Serving the snapshot would drop them, so the
90
+ // cache wins and is stamped with the snapshot's revision: the next publish derives N+1 from it and
91
+ // folds the whole plan into a real record instead of colliding with an existing revision file.
92
+ // A cache that names an OLDER revision is a lagging view of a force-recreate and never wins.
93
+ if (cached.revision === undefined || cached.revision === latest.revision) {
94
+ const published = new Set(latest.plan.goals.map((goal) => goal.id));
95
+ if (Array.isArray(cached.goals) && cached.goals.some((goal) => !published.has(goal.id)))
96
+ return { ...cached, revision: latest.revision };
97
+ }
98
+ return latest.plan;
99
+ }
100
+ // Goals are never removed from a plan, so every `goal_added` the reconciled ledger still carries must
101
+ // have a goal in the projection. A projection that lacks one is truncated and must not become goals.json.
102
+ export function assertProjectionComplete(plan, ledger) {
103
+ const present = new Set(plan.goals.map((goal) => goal.id));
104
+ const missing = new Set();
105
+ for (const entry of ledger)
106
+ if (entry.kind === "goal_added" && entry.goalId !== undefined && !present.has(entry.goalId))
107
+ missing.add(entry.goalId);
108
+ if (missing.size === 0)
109
+ return;
110
+ throw new UlwLoopError(`Refusing to rewrite goals.json: the ledger records goals the plan projection lacks (${[...missing].join(", ")}). Restore those goals into the newest revisions/ record (keeping plan.revision equal to its file number) before mutating this run.`, "ULW_LOOP_PROJECTION_TRUNCATED", { details: { missingGoalIds: [...missing] } });
82
111
  }
83
112
  export function planExists(repoRoot, scope) {
84
113
  const dir = ulwLoopDir(repoRoot, scope);
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit --with-ultrawork",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Checking Ulw-Loop Steering"
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking Ulw-Loop Steering"
11
11
  }
12
12
  ]
13
13
  }
@@ -20,7 +20,7 @@
20
20
  "type": "command",
21
21
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use-spawn",
22
22
  "timeout": 5,
23
- "statusMessage": "(OmO 5.0.0-beta.66) Recording Spawn Admission"
23
+ "statusMessage": "(OmO 5.0.0-beta.68) Recording Spawn Admission"
24
24
  }
25
25
  ]
26
26
  }
@@ -33,7 +33,7 @@
33
33
  "type": "command",
34
34
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
35
35
  "timeout": 5,
36
- "statusMessage": "(OmO 5.0.0-beta.66) Enforcing Unlimited Ulw-Loop Budget"
36
+ "statusMessage": "(OmO 5.0.0-beta.68) Enforcing Unlimited Ulw-Loop Budget"
37
37
  }
38
38
  ]
39
39
  },
@@ -44,7 +44,7 @@
44
44
  "type": "command",
45
45
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use-spawn",
46
46
  "timeout": 5,
47
- "statusMessage": "(OmO 5.0.0-beta.66) Guarding Ulw-Loop Spawns"
47
+ "statusMessage": "(OmO 5.0.0-beta.68) Guarding Ulw-Loop Spawns"
48
48
  }
49
49
  ]
50
50
  }
@@ -56,7 +56,7 @@
56
56
  "type": "command",
57
57
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook stop",
58
58
  "timeout": 10,
59
- "statusMessage": "(OmO 5.0.0-beta.66) Checking Ulw-Loop Resume"
59
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking Ulw-Loop Resume"
60
60
  }
61
61
  ]
62
62
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-ulw-loop",
3
- "version": "5.0.0-beta.66",
3
+ "version": "5.0.0-beta.68",
4
4
  "description": "Codex plugin: durable repo-native multi-goal orchestration with embedded success criteria and observable evidence audit.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -6,11 +6,16 @@ import type { UlwLoopLedgerEntry } from "./types.js";
6
6
  export function readLedgerAt(dir: string): UlwLoopLedgerEntry[] {
7
7
  const entries = new Map<string, UlwLoopLedgerEntry>();
8
8
  const lines = (readOptional(join(dir, "ledger.jsonl")) ?? "").split(/\r?\n/);
9
+ // A materialized view is sorted by revision, so a line that carries no revision (or revision 0)
10
+ // AFTER a published revision was appended out-of-band (the removed tool path) and belongs to the
11
+ // newest revision before it - not to the pre-store past a later ledgerResetRevision would discard.
12
+ let reached = 0;
9
13
  for (const [index, line] of lines.entries()) {
10
14
  if (line.trim().length === 0) continue;
11
15
  try {
12
16
  const entry: UlwLoopLedgerEntry = JSON.parse(line);
13
- entry.revision ??= 0;
17
+ entry.revision = entry.revision || reached;
18
+ reached = Math.max(reached, entry.revision);
14
19
  entry.id ??= `legacy-${index + 1}`;
15
20
  entries.set(entry.id, entry);
16
21
  } catch (error) {
@@ -6,7 +6,14 @@ import { join } from "node:path";
6
6
  import { readLedgerAt } from "./ledger.js";
7
7
  import { type UlwLoopScope, ulwLoopDir, ulwLoopStateLockPath } from "./paths.js";
8
8
  import { assertStateLockOwned, migrationEntries } from "./plan-io.js";
9
- import { hasCode, type PlanCommitRecord, readOptional, readRecords, reconcilePlan } from "./plan-log.js";
9
+ import {
10
+ assertProjectionComplete,
11
+ hasCode,
12
+ type PlanCommitRecord,
13
+ readOptional,
14
+ readRecords,
15
+ reconcilePlan,
16
+ } from "./plan-log.js";
10
17
  import { UlwLoopError, type UlwLoopLedgerEntry, type UlwLoopPlan } from "./types.js";
11
18
 
12
19
  interface CommitHooks {
@@ -91,6 +98,7 @@ function viewContents(dir: string): [string, string][] {
91
98
  const plan = reconcilePlan(dir);
92
99
  if (plan === undefined || readRecords(dir).length === 0) return [];
93
100
  const ledger = readLedgerAt(dir);
101
+ assertProjectionComplete(plan, ledger);
94
102
  const views: [string, string][] = [
95
103
  ["goals.json", `${JSON.stringify(plan, null, 2)}\n`],
96
104
  ["ledger.jsonl", ledger.length === 0 ? "" : `${ledger.map((entry) => JSON.stringify(entry)).join("\n")}\n`],
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { type UlwLoopScope, ulwLoopDir } from "./paths.js";
4
- import type { UlwLoopLedgerEntry, UlwLoopPlan } from "./types.js";
4
+ import { UlwLoopError, type UlwLoopLedgerEntry, type UlwLoopPlan } from "./types.js";
5
5
 
6
6
  export interface PlanCommitRecord {
7
7
  readonly version: 1;
@@ -76,7 +76,35 @@ export function reconcilePlan(dir: string): UlwLoopPlan | undefined {
76
76
  }
77
77
  }
78
78
  const latest = readNewestRecord(dir);
79
- return latest !== undefined && latest.revision >= (cached?.revision ?? 0) ? latest.plan : cached;
79
+ if (latest === undefined) return cached;
80
+ if (cached === undefined) return latest.plan;
81
+ if ((cached.revision ?? 0) > latest.revision) return cached;
82
+ // A goals.json written outside the commit log (the removed tool path rewrote it wholesale, never
83
+ // stamping `revision`) can hold goals no snapshot has. Serving the snapshot would drop them, so the
84
+ // cache wins and is stamped with the snapshot's revision: the next publish derives N+1 from it and
85
+ // folds the whole plan into a real record instead of colliding with an existing revision file.
86
+ // A cache that names an OLDER revision is a lagging view of a force-recreate and never wins.
87
+ if (cached.revision === undefined || cached.revision === latest.revision) {
88
+ const published = new Set(latest.plan.goals.map((goal) => goal.id));
89
+ if (Array.isArray(cached.goals) && cached.goals.some((goal) => !published.has(goal.id)))
90
+ return { ...cached, revision: latest.revision };
91
+ }
92
+ return latest.plan;
93
+ }
94
+ // Goals are never removed from a plan, so every `goal_added` the reconciled ledger still carries must
95
+ // have a goal in the projection. A projection that lacks one is truncated and must not become goals.json.
96
+ export function assertProjectionComplete(plan: UlwLoopPlan, ledger: readonly UlwLoopLedgerEntry[]): void {
97
+ const present = new Set(plan.goals.map((goal) => goal.id));
98
+ const missing = new Set<string>();
99
+ for (const entry of ledger)
100
+ if (entry.kind === "goal_added" && entry.goalId !== undefined && !present.has(entry.goalId))
101
+ missing.add(entry.goalId);
102
+ if (missing.size === 0) return;
103
+ throw new UlwLoopError(
104
+ `Refusing to rewrite goals.json: the ledger records goals the plan projection lacks (${[...missing].join(", ")}). Restore those goals into the newest revisions/ record (keeping plan.revision equal to its file number) before mutating this run.`,
105
+ "ULW_LOOP_PROJECTION_TRUNCATED",
106
+ { details: { missingGoalIds: [...missing] } },
107
+ );
80
108
  }
81
109
  export function planExists(repoRoot: string, scope?: UlwLoopScope): boolean {
82
110
  const dir = ulwLoopDir(repoRoot, scope);
@@ -0,0 +1,228 @@
1
+ import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+ import { ulwLoopDir } from "../src/paths.js";
7
+ import { readLedger, readUlwLoopPlan } from "../src/plan-io.js";
8
+ import { readNewestRecord } from "../src/plan-log.js";
9
+ import { createAgentToolkit } from "../src/sdk.js";
10
+ import type { UlwLoopItem, UlwLoopLedgerEntry, UlwLoopPlan } from "../src/types.js";
11
+ import { goal } from "./fixtures/checkpoint-builders.js";
12
+
13
+ // Reproduces #8328: a run created under the removed `omo_agent_toolkit` tool path published
14
+ // `revisions/00000004.json` while the plan held one goal, then kept mutating goals.json and appending
15
+ // ledger.jsonl lines DIRECTLY (no `revision`, no `id`) for goals the commit log never saw. The
16
+ // agent-toolkit-sdk runtime then rebuilt the projection from the snapshot and every later goal vanished.
17
+ const SESSION = "legacy-projection";
18
+ const LEGACY_AT = "2026-09-16T01:00:00.000Z";
19
+ let cwd: string;
20
+ let dir: string;
21
+ let legacyGoals: UlwLoopItem[];
22
+
23
+ function toolkit() {
24
+ return createAgentToolkit({ cwd, sessionId: SESSION, surface: "omo-senpi" });
25
+ }
26
+ function ledgerLine(entry: UlwLoopLedgerEntry): string {
27
+ return `${JSON.stringify(entry)}\n`;
28
+ }
29
+ function goalsOnDisk(): UlwLoopPlan {
30
+ return JSON.parse(readFileSync(join(dir, "goals.json"), "utf8"));
31
+ }
32
+ async function seedSnapshotWithOneGoal(): Promise<void> {
33
+ const created = await toolkit().createGoals({ brief: "- alpha goal" });
34
+ if (!created.ok) throw new Error("fixture could not create goals");
35
+ const started = await toolkit().completeGoals({});
36
+ if (!started.ok || started.operation !== "complete-goals" || "done" in started.result)
37
+ throw new Error("fixture could not start alpha");
38
+ const goalId = started.result.goal.id;
39
+ const criteria = await toolkit().criteria({ goalId });
40
+ if (!criteria.ok || criteria.operation !== "criteria") throw new Error("fixture has no criteria");
41
+ // Two evidence captures on alpha -> revisions 3 and 4; the snapshot under test is 00000004.json.
42
+ for (const criterion of criteria.result.criteria.slice(0, 2)) {
43
+ const recorded = await toolkit().recordEvidence({
44
+ goalId,
45
+ criterionId: criterion.id,
46
+ status: "pass",
47
+ evidence: "alpha proof",
48
+ });
49
+ if (!recorded.ok) throw new Error("fixture could not record alpha evidence");
50
+ }
51
+ }
52
+ // The removed tool path rewrote goals.json wholesale (never stamping `revision`) and appended raw
53
+ // ledger lines; this is that write, byte-shape included.
54
+ function appendLegacyGoals(): void {
55
+ const plan = goalsOnDisk();
56
+ delete plan.revision;
57
+ legacyGoals = [
58
+ goal({
59
+ id: "G002-legacy-done",
60
+ title: "legacy done",
61
+ status: "complete",
62
+ completedAt: LEGACY_AT,
63
+ evidence: "legacy done proof",
64
+ }),
65
+ goal({ id: "G003-legacy-active", title: "legacy active", status: "in_progress", startedAt: LEGACY_AT }),
66
+ goal({ id: "G004-legacy-pending", title: "legacy pending", status: "pending", attempt: 0, successCriteria: [] }),
67
+ ];
68
+ plan.goals.push(...legacyGoals);
69
+ plan.activeGoalId = "G003-legacy-active";
70
+ plan.updatedAt = LEGACY_AT;
71
+ writeFileSync(join(dir, "goals.json"), `${JSON.stringify(plan, null, 2)}\n`);
72
+ const entries: UlwLoopLedgerEntry[] = [
73
+ ...legacyGoals.map((item) => ({
74
+ at: LEGACY_AT,
75
+ kind: "goal_added" as const,
76
+ goalId: item.id,
77
+ status: item.status,
78
+ message: item.title,
79
+ })),
80
+ { at: LEGACY_AT, kind: "goal_started", goalId: "G002-legacy-done", status: "in_progress", message: "Attempt 1" },
81
+ {
82
+ at: LEGACY_AT,
83
+ kind: "evidence_captured",
84
+ goalId: "G002-legacy-done",
85
+ criterionId: "C001",
86
+ criterionStatus: "pass",
87
+ evidence: "C001 passed",
88
+ },
89
+ {
90
+ at: LEGACY_AT,
91
+ kind: "goal_completed",
92
+ goalId: "G002-legacy-done",
93
+ status: "complete",
94
+ evidence: "legacy done proof",
95
+ },
96
+ {
97
+ at: LEGACY_AT,
98
+ kind: "goal_started",
99
+ goalId: "G003-legacy-active",
100
+ status: "in_progress",
101
+ message: "Attempt 1",
102
+ },
103
+ {
104
+ at: LEGACY_AT,
105
+ kind: "evidence_captured",
106
+ goalId: "G003-legacy-active",
107
+ criterionId: "C001",
108
+ criterionStatus: "pass",
109
+ evidence: "C001 passed",
110
+ },
111
+ ];
112
+ appendFileSync(join(dir, "ledger.jsonl"), entries.map(ledgerLine).join(""));
113
+ }
114
+
115
+ beforeEach(async () => {
116
+ cwd = await mkdtemp(join(tmpdir(), "ulw-legacy-projection-"));
117
+ dir = ulwLoopDir(cwd, { sessionId: SESSION });
118
+ await seedSnapshotWithOneGoal();
119
+ expect(readNewestRecord(dir)?.revision).toBe(4);
120
+ expect(readNewestRecord(dir)?.plan.goals).toHaveLength(1);
121
+ appendLegacyGoals();
122
+ });
123
+ afterEach(async () => {
124
+ await rm(cwd, { recursive: true, force: true });
125
+ });
126
+
127
+ describe("#given snapshot 4 holds one goal and the legacy tool path appended three goals with evidence afterwards", () => {
128
+ it("#when status rebuilds the projection #then every legacy goal, its evidence and its ledger events survive", async () => {
129
+ const status = await toolkit().status();
130
+
131
+ expect(status.ok).toBe(true);
132
+ if (!status.ok || status.operation !== "status") return;
133
+ expect(status.result.plan.goals.map((item) => item.id)).toEqual([
134
+ "G001-alpha-goal",
135
+ "G002-legacy-done",
136
+ "G003-legacy-active",
137
+ "G004-legacy-pending",
138
+ ]);
139
+ const done = status.result.plan.goals.find((item) => item.id === "G002-legacy-done");
140
+ expect(done?.status).toBe("complete");
141
+ expect(done?.successCriteria.map((criterion) => criterion.capturedEvidence)).toEqual([
142
+ "C001 passed",
143
+ "C002 passed",
144
+ "C003 passed",
145
+ ]);
146
+ expect(status.result.plan.activeGoalId).toBe("G003-legacy-active");
147
+ const ledger = readLedger(cwd, { sessionId: SESSION });
148
+ expect(ledger.filter((entry) => entry.kind === "goal_added").map((entry) => entry.goalId)).toEqual(
149
+ legacyGoals.map((item) => item.id),
150
+ );
151
+ expect(ledger.filter((entry) => entry.goalId === "G002-legacy-done").map((entry) => entry.kind)).toEqual([
152
+ "goal_added",
153
+ "goal_started",
154
+ "evidence_captured",
155
+ "goal_completed",
156
+ ]);
157
+ });
158
+
159
+ it("#when evidence is recorded on a legacy goal #then it succeeds and publishes revision 5 holding all four goals", async () => {
160
+ const recorded = await toolkit().recordEvidence({
161
+ goalId: "G003-legacy-active",
162
+ criterionId: "C002",
163
+ status: "pass",
164
+ evidence: "recorded after the runtime switch",
165
+ });
166
+
167
+ expect(recorded.ok).toBe(true);
168
+ if (!recorded.ok) return;
169
+ const newest = readNewestRecord(dir);
170
+ expect(newest?.revision).toBe(5);
171
+ expect(existsSync(join(dir, "revisions", "00000005.json"))).toBe(true);
172
+ expect(newest?.plan.goals.map((item) => item.id)).toEqual([
173
+ "G001-alpha-goal",
174
+ "G002-legacy-done",
175
+ "G003-legacy-active",
176
+ "G004-legacy-pending",
177
+ ]);
178
+ expect(newest?.plan.revision).toBe(5);
179
+ expect(goalsOnDisk().revision).toBe(5);
180
+ expect(goalsOnDisk().goals).toHaveLength(4);
181
+ // The legacy audit trail is still part of the reconciled ledger after the fold.
182
+ expect(
183
+ readLedger(cwd, { sessionId: SESSION }).some(
184
+ (entry) => entry.kind === "goal_completed" && entry.goalId === "G002-legacy-done",
185
+ ),
186
+ ).toBe(true);
187
+ });
188
+
189
+ it("#when a legacy goal is checkpointed complete #then the checkpoint succeeds and the next legacy goal is still there", async () => {
190
+ const closed = await toolkit().checkpoint({
191
+ goalId: "G003-legacy-active",
192
+ status: "complete",
193
+ evidence: "legacy goal closed after the runtime switch",
194
+ });
195
+
196
+ expect(closed.ok).toBe(true);
197
+ if (!closed.ok) return;
198
+ const plan = await readUlwLoopPlan(cwd, { sessionId: SESSION });
199
+ expect(plan.goals.find((item) => item.id === "G003-legacy-active")?.status).toBe("complete");
200
+ expect(plan.goals.find((item) => item.id === "G004-legacy-pending")?.status).toBe("pending");
201
+ expect(plan.goals).toHaveLength(4);
202
+ });
203
+ });
204
+
205
+ describe("#given the projection has already been truncated below what the ledger records", () => {
206
+ it("#when a locked operation would rewrite goals.json #then it refuses with ULW_LOOP_PROJECTION_TRUNCATED instead of dropping goals", async () => {
207
+ // The pre-fix runtime already rewrote goals.json from the one-goal snapshot; only ledger.jsonl still
208
+ // names the legacy goals.
209
+ const truncated = readNewestRecord(dir);
210
+ if (truncated === undefined) throw new Error("fixture lost its snapshot");
211
+ writeFileSync(join(dir, "goals.json"), `${JSON.stringify(truncated.plan, null, 2)}\n`);
212
+ const before = readFileSync(join(dir, "goals.json"), "utf8");
213
+
214
+ const recorded = await toolkit().recordEvidence({
215
+ goalId: "G001-alpha-goal",
216
+ criterionId: "C003",
217
+ status: "pass",
218
+ evidence: "must not be accepted on a truncated projection",
219
+ });
220
+
221
+ expect(recorded.ok).toBe(false);
222
+ if (recorded.ok) return;
223
+ expect(recorded.error.code).toBe("ULW_LOOP_PROJECTION_TRUNCATED");
224
+ expect(recorded.error.message).toContain("G002-legacy-done");
225
+ expect(readFileSync(join(dir, "goals.json"), "utf8")).toBe(before);
226
+ expect(readNewestRecord(dir)?.revision).toBe(4);
227
+ });
228
+ });
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/git-bash/dist/cli.js\" hook post-compact",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Resetting Git Bash MCP Reminder",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Resetting Git Bash MCP Reminder",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\git-bash\\dist\\cli.js\" hook post-compact"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/lsp/dist/cli.js\" hook post-compact",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Resetting LSP Diagnostics Cache",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Resetting LSP Diagnostics Cache",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\lsp\\dist\\cli.js\" hook post-compact"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/rules/dist/cli.js\" hook post-compact",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Resetting Project Rule Cache",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Resetting Project Rule Cache",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\rules\\dist\\cli.js\" hook post-compact"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/comment-checker/dist/cli.js\" hook post-tool-use",
9
9
  "timeout": 30,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Checking Comments",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking Comments",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\comment-checker\\dist\\cli.js\" hook post-tool-use"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/lsp/dist/cli.js\" hook post-tool-use",
9
9
  "timeout": 60,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Checking LSP Diagnostics",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking LSP Diagnostics",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\lsp\\dist\\cli.js\" hook post-tool-use"
12
12
  }
13
13
  ],
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/teammode/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 10,
11
- "statusMessage": "(OmO 5.0.0-beta.66) Checking Thread Title Hygiene",
11
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking Thread Title Hygiene",
12
12
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\teammode\\dist\\cli.js\" hook post-tool-use"
13
13
  }
14
14
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/rules/dist/cli.js\" hook post-tool-use",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Matching Project Rules",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Matching Project Rules",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\rules\\dist\\cli.js\" hook post-tool-use"
12
12
  }
13
13
  ],
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook post-tool-use-spawn",
10
10
  "timeout": 5,
11
- "statusMessage": "(OmO 5.0.0-beta.66) Recording Spawn Admission",
11
+ "statusMessage": "(OmO 5.0.0-beta.68) Recording Spawn Admission",
12
12
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook post-tool-use-spawn"
13
13
  }
14
14
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook pre-tool-use",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Enforcing Unlimited Goal Budget",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Enforcing Unlimited Goal Budget",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook pre-tool-use"
12
12
  }
13
13
  ],
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook pre-tool-use-spawn",
10
10
  "timeout": 5,
11
- "statusMessage": "(OmO 5.0.0-beta.66) Guarding Ulw-Loop Spawns",
11
+ "statusMessage": "(OmO 5.0.0-beta.68) Guarding Ulw-Loop Spawns",
12
12
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook pre-tool-use-spawn"
13
13
  }
14
14
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/git-bash/dist/cli.js\" hook pre-tool-use",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Recommending Git Bash MCP",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Recommending Git Bash MCP",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\git-bash\\dist\\cli.js\" hook pre-tool-use"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/scripts/auto-update.mjs\" hook session-start",
9
9
  "timeout": 15,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Checking Auto Update",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking Auto Update",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\scripts\\auto-update.mjs\" hook session-start"
12
12
  }
13
13
  ],
@@ -8,7 +8,7 @@
8
8
  "command": "node \"${PLUGIN_ROOT}/components/bootstrap/dist/cli.js\" hook session-start",
9
9
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\bootstrap.ps1\"",
10
10
  "timeout": 30,
11
- "statusMessage": "(OmO 5.0.0-beta.66) Checking Bootstrap Provisioning"
11
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking Bootstrap Provisioning"
12
12
  }
13
13
  ]
14
14
  }
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/rules/dist/cli.js\" hook session-start",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Loading Project Rules",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Loading Project Rules",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\rules\\dist\\cli.js\" hook session-start"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/telemetry/dist/cli.js\" hook session-start",
9
9
  "timeout": 15,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Recording Session Telemetry",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Recording Session Telemetry",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\telemetry\\dist\\cli.js\" hook session-start"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-execute-continuation/dist/cli.js\" hook stop",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Checking Ulw-Execute Continuation",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking Ulw-Execute Continuation",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-execute-continuation\\dist\\cli.js\" hook stop"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook stop",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Checking Ulw-Loop Resume",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking Ulw-Loop Resume",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook stop"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/lazycodex-executor-verify/dist/cli.js\" hook subagent-stop",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Verifying LazyCodex Executor Evidence",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Verifying LazyCodex Executor Evidence",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\lazycodex-executor-verify\\dist\\cli.js\" hook subagent-stop"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ultrawork/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Checking Ultrawork Trigger",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking Ultrawork Trigger",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ultrawork\\dist\\cli.js\" hook user-prompt-submit"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Checking Ulw-Loop Steering",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Checking Ulw-Loop Steering",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook user-prompt-submit"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/rules/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.0.0-beta.66) Loading Project Rules",
10
+ "statusMessage": "(OmO 5.0.0-beta.68) Loading Project Rules",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\rules\\dist\\cli.js\" hook user-prompt-submit"
12
12
  }
13
13
  ]