@letta-ai/letta-code 0.30.28 → 0.30.30

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 (82) hide show
  1. package/dist/agent-presets.js +55 -2490
  2. package/dist/agent-presets.js.map +4 -4
  3. package/dist/mcp-client.js +2 -2
  4. package/dist/mcp-client.js.map +1 -1
  5. package/dist/types/agent/acting-user.d.ts +21 -0
  6. package/dist/types/agent/acting-user.d.ts.map +1 -0
  7. package/dist/types/agent/available-models.d.ts +2 -0
  8. package/dist/types/agent/available-models.d.ts.map +1 -1
  9. package/dist/types/agent/client-skills.d.ts +3 -0
  10. package/dist/types/agent/client-skills.d.ts.map +1 -1
  11. package/dist/types/agent/memory-git.d.ts +3 -11
  12. package/dist/types/agent/memory-git.d.ts.map +1 -1
  13. package/dist/types/agent/message.d.ts +1 -0
  14. package/dist/types/agent/message.d.ts.map +1 -1
  15. package/dist/types/agent/model-catalog.d.ts +14 -52
  16. package/dist/types/agent/model-catalog.d.ts.map +1 -1
  17. package/dist/types/agent/model.d.ts.map +1 -1
  18. package/dist/types/agent/personality-presets.d.ts +1 -1
  19. package/dist/types/agent/personality-presets.d.ts.map +1 -1
  20. package/dist/types/agent/shared-memory-skills.d.ts +18 -0
  21. package/dist/types/agent/shared-memory-skills.d.ts.map +1 -0
  22. package/dist/types/agent/subagents/index.d.ts +1 -1
  23. package/dist/types/agent/subagents/index.d.ts.map +1 -1
  24. package/dist/types/agent/turn-recovery-policy.d.ts +10 -7
  25. package/dist/types/agent/turn-recovery-policy.d.ts.map +1 -1
  26. package/dist/types/agent-presets.d.ts +0 -1
  27. package/dist/types/agent-presets.d.ts.map +1 -1
  28. package/dist/types/backend/api/conversations.d.ts +1 -0
  29. package/dist/types/backend/api/conversations.d.ts.map +1 -1
  30. package/dist/types/backend/backend.d.ts +4 -2
  31. package/dist/types/backend/backend.d.ts.map +1 -1
  32. package/dist/types/backend/dev/pi-model-factory.d.ts +6 -0
  33. package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
  34. package/dist/types/backend/dev/pi-provider-registry.d.ts.map +1 -1
  35. package/dist/types/backend/local/local-model-config.d.ts +3 -0
  36. package/dist/types/backend/local/local-model-config.d.ts.map +1 -1
  37. package/dist/types/backend/local/local-provider-auth-store.d.ts.map +1 -1
  38. package/dist/types/backend/local/system-prompt-compilation.d.ts.map +1 -1
  39. package/dist/types/cli/helpers/stream-processor.d.ts +1 -0
  40. package/dist/types/cli/helpers/stream-processor.d.ts.map +1 -1
  41. package/dist/types/cli/helpers/stream-stall-reconciler.d.ts +2 -1
  42. package/dist/types/cli/helpers/stream-stall-reconciler.d.ts.map +1 -1
  43. package/dist/types/cli/helpers/stream.d.ts +1 -1
  44. package/dist/types/cli/helpers/stream.d.ts.map +1 -1
  45. package/dist/types/reminders/state.d.ts +6 -1
  46. package/dist/types/reminders/state.d.ts.map +1 -1
  47. package/dist/types/tools/impl/bash-output.d.ts +1 -0
  48. package/dist/types/tools/impl/bash-output.d.ts.map +1 -1
  49. package/dist/types/tools/impl/github-pull-request-tracker.d.ts +7 -3
  50. package/dist/types/tools/impl/github-pull-request-tracker.d.ts.map +1 -1
  51. package/dist/types/tools/impl/glob.d.ts +1 -0
  52. package/dist/types/tools/impl/glob.d.ts.map +1 -1
  53. package/dist/types/tools/impl/grep.d.ts +1 -0
  54. package/dist/types/tools/impl/grep.d.ts.map +1 -1
  55. package/dist/types/tools/impl/skill.d.ts +10 -5
  56. package/dist/types/tools/impl/skill.d.ts.map +1 -1
  57. package/dist/types/tools/impl/task-output.d.ts +1 -0
  58. package/dist/types/tools/impl/task-output.d.ts.map +1 -1
  59. package/dist/types/tools/impl/task.d.ts.map +1 -1
  60. package/dist/types/tools/manager.d.ts.map +1 -1
  61. package/dist/types/tools/toolset.d.ts.map +1 -1
  62. package/dist/types/types/approval-classification-protocol.d.ts +13 -0
  63. package/dist/types/types/approval-classification-protocol.d.ts.map +1 -0
  64. package/dist/types/types/protocol_v2.d.ts +4 -8
  65. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  66. package/image-resize-worker.js +42 -20
  67. package/letta.js +94957 -151018
  68. package/package.json +4 -3
  69. package/scripts/codex-watch/agent-watch.ts +63 -8
  70. package/scripts/codex-watch/release-analysis.test.ts +57 -0
  71. package/scripts/codex-watch/release-analysis.ts +31 -1
  72. package/scripts/codex-watch/tracker.test.ts +243 -6
  73. package/scripts/codex-watch/tracker.ts +221 -20
  74. package/scripts/isolated-unit-tests.json +15 -0
  75. package/scripts/pi-ai-watch/agent-watch.ts +253 -0
  76. package/scripts/pi-ai-watch/github.ts +145 -0
  77. package/scripts/pi-ai-watch/release-analysis.test.ts +137 -0
  78. package/scripts/pi-ai-watch/release-analysis.ts +371 -0
  79. package/scripts/pi-ai-watch/tracker.test.ts +138 -0
  80. package/scripts/pi-ai-watch/tracker.ts +303 -0
  81. package/scripts/pi-ai-watch/update-tracker.ts +215 -0
  82. package/scripts/source-file-size-baseline.json +6 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.30.28",
3
+ "version": "0.30.30",
4
4
  "description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.10",
@@ -119,14 +119,15 @@
119
119
  "access": "public"
120
120
  },
121
121
  "dependencies": {
122
- "@earendil-works/pi-ai": "^0.82.1",
122
+ "@earendil-works/pi-ai": "0.84.2",
123
+ "@janhapke/sharp-electron": "0.35.3-electron.1",
123
124
  "@letta-ai/letta-client": "^1.10.2",
124
125
  "@letta-ai/trajectory": "0.2.0",
125
126
  "@modelcontextprotocol/sdk": "1.30.0",
126
127
  "@pierre/diffs": "1.2.2",
127
128
  "@scarf/scarf": "^1.4.0",
128
- "cross-spawn": "^7.0.6",
129
129
  "cron-parser": "^5.6.1",
130
+ "cross-spawn": "^7.0.6",
130
131
  "glob": "^13.0.0",
131
132
  "ink-link": "^5.0.0",
132
133
  "node-pty": "^1.1.0",
@@ -17,13 +17,19 @@ import {
17
17
  import {
18
18
  analyzeCodexRelease,
19
19
  DEFAULT_TARGET_REPO,
20
+ findNextStableRelease,
21
+ listStableReleases,
22
+ type Release,
20
23
  } from "./release-analysis.ts";
21
24
  import {
25
+ advanceCodexAuditCursor,
22
26
  emptyTrackerState,
23
- hasProcessedTag,
27
+ getCodexAuditCursorTag,
28
+ hasProcessedRange,
24
29
  parseTrackerState,
25
30
  recordAnalysis,
26
31
  renderTrackerBody,
32
+ validateLegacyCodexAuditCursor,
27
33
  } from "./tracker.ts";
28
34
 
29
35
  const DEFAULT_TRACKER_TITLE = "Codex upstream drift tracker";
@@ -77,15 +83,50 @@ function parseArgs(argv: string[]): Args {
77
83
 
78
84
  async function main() {
79
85
  const args = parseArgs(process.argv.slice(2));
86
+ const tracker = ensureTrackerIssue(args);
87
+ let state = parseTrackerState(tracker.body);
88
+ let sinceTag = args.sinceTag;
89
+ let currentTag = args.currentTag;
90
+ let stableReleases: Release[] | undefined;
91
+
92
+ if (!state.audit_cursor_validated) {
93
+ stableReleases = await listStableReleases();
94
+ state = validateLegacyCodexAuditCursor(
95
+ state,
96
+ stableReleases.map((release) => release.tag_name),
97
+ );
98
+ if (!args.dryRun) {
99
+ editIssueBody(args.repo, tracker.number, renderTrackerBody(state));
100
+ }
101
+ }
102
+
103
+ if (!sinceTag && !currentTag) {
104
+ const cursorTag = getCodexAuditCursorTag(state);
105
+ if (cursorTag) {
106
+ stableReleases ??= await listStableReleases();
107
+ const nextRelease = findNextStableRelease(stableReleases, cursorTag);
108
+ if (!nextRelease) {
109
+ console.log(`No stable release after ${cursorTag}; nothing to do.`);
110
+ writeOutput("tracker_issue", String(tracker.number));
111
+ writeOutput("tracker_issue_url", tracker.url);
112
+ writeOutput("current_tag", cursorTag);
113
+ writeOutput("previous_tag", cursorTag);
114
+ writeOutput("verdict", "no-op");
115
+ writeOutput("should_run_agent", "false");
116
+ return;
117
+ }
118
+ sinceTag = cursorTag;
119
+ currentTag = nextRelease.tag_name;
120
+ }
121
+ }
122
+
80
123
  const analysis = await analyzeCodexRelease({
81
- sinceTag: args.sinceTag,
82
- currentTag: args.currentTag,
124
+ sinceTag,
125
+ currentTag,
126
+ stableReleases,
83
127
  });
84
128
  writeFileSync(args.analysisFile, `${JSON.stringify(analysis, null, 2)}\n`);
85
129
 
86
- const tracker = ensureTrackerIssue(args);
87
- const state = parseTrackerState(tracker.body);
88
-
89
130
  writeOutput("tracker_issue", String(tracker.number));
90
131
  writeOutput("tracker_issue_url", tracker.url);
91
132
  writeOutput("analysis_file", args.analysisFile);
@@ -93,8 +134,22 @@ async function main() {
93
134
  writeOutput("previous_tag", analysis.previous_tag);
94
135
  writeOutput("verdict", analysis.verdict);
95
136
 
96
- if (!args.dryRun && hasProcessedTag(state, analysis.current_tag)) {
97
- console.log(`Already processed ${analysis.current_tag}; nothing to do.`);
137
+ if (
138
+ !args.dryRun &&
139
+ hasProcessedRange(state, analysis.previous_tag, analysis.current_tag)
140
+ ) {
141
+ const next = advanceCodexAuditCursor(
142
+ state,
143
+ analysis.previous_tag,
144
+ analysis.current_tag,
145
+ analysis.is_adjacent_release,
146
+ );
147
+ if (next !== state) {
148
+ editIssueBody(args.repo, tracker.number, renderTrackerBody(next));
149
+ }
150
+ console.log(
151
+ `Already processed ${analysis.previous_tag}...${analysis.current_tag}; nothing to do.`,
152
+ );
98
153
  writeOutput("should_run_agent", "false");
99
154
  return;
100
155
  }
@@ -0,0 +1,57 @@
1
+ import {
2
+ areAdjacentStableReleases,
3
+ findNextStableRelease,
4
+ type Release,
5
+ } from "./release-analysis.ts";
6
+
7
+ function release(tag: string, publishedAt: string): Release {
8
+ return {
9
+ tag_name: tag,
10
+ draft: false,
11
+ prerelease: false,
12
+ html_url: `https://github.com/openai/codex/releases/tag/${tag}`,
13
+ body: null,
14
+ published_at: publishedAt,
15
+ };
16
+ }
17
+
18
+ const STABLES = [
19
+ release("rust-v0.1.0", "2026-08-01T00:00:00Z"),
20
+ release("rust-v0.2.0", "2026-08-02T00:00:00Z"),
21
+ release("rust-v0.3.0", "2026-08-03T00:00:00Z"),
22
+ ];
23
+
24
+ describe("Codex stable release backlog", () => {
25
+ test("selects the first release after the durable cursor", () => {
26
+ expect(
27
+ findNextStableRelease(STABLES, "rust-v0.1.0")?.tag_name,
28
+ ).toBe("rust-v0.2.0");
29
+ });
30
+
31
+ test("identifies only adjacent stable release pairs", () => {
32
+ expect(
33
+ areAdjacentStableReleases(
34
+ STABLES,
35
+ "rust-v0.1.0",
36
+ "rust-v0.2.0",
37
+ ),
38
+ ).toBe(true);
39
+ expect(
40
+ areAdjacentStableReleases(
41
+ STABLES,
42
+ "rust-v0.1.0",
43
+ "rust-v0.3.0",
44
+ ),
45
+ ).toBe(false);
46
+ });
47
+
48
+ test("returns null when the durable cursor is current", () => {
49
+ expect(findNextStableRelease(STABLES, "rust-v0.3.0")).toBeNull();
50
+ });
51
+
52
+ test("fails instead of skipping an unavailable cursor", () => {
53
+ expect(() =>
54
+ findNextStableRelease(STABLES, "rust-v0.0.9"),
55
+ ).toThrow("Could not find terminal release rust-v0.0.9");
56
+ });
57
+ });
@@ -34,6 +34,7 @@ export interface Release {
34
34
  export interface AnalyzeCodexReleaseOptions {
35
35
  sinceTag: string | null;
36
36
  currentTag: string | null;
37
+ stableReleases?: Release[];
37
38
  }
38
39
 
39
40
  export interface PathChangeSummary {
@@ -44,6 +45,7 @@ export interface PathChangeSummary {
44
45
  export interface CodexWatchAnalysis {
45
46
  previous_tag: string;
46
47
  current_tag: string;
48
+ is_adjacent_release: boolean;
47
49
  release_url: string;
48
50
  release_notes_md: string;
49
51
  verdict: Verdict;
@@ -59,7 +61,7 @@ export interface CodexWatchAnalysis {
59
61
  export async function analyzeCodexRelease(
60
62
  options: AnalyzeCodexReleaseOptions,
61
63
  ): Promise<CodexWatchAnalysis> {
62
- const stables = await listStableReleases();
64
+ const stables = options.stableReleases ?? (await listStableReleases());
63
65
  if (stables.length === 0) throw new Error("No stable Codex releases found");
64
66
 
65
67
  const current = options.currentTag
@@ -137,6 +139,11 @@ export async function analyzeCodexRelease(
137
139
  return {
138
140
  previous_tag: previous.tag_name,
139
141
  current_tag: current.tag_name,
142
+ is_adjacent_release: areAdjacentStableReleases(
143
+ stables,
144
+ previous.tag_name,
145
+ current.tag_name,
146
+ ),
140
147
  release_url: current.html_url,
141
148
  release_notes_md: current.body ?? "",
142
149
  verdict,
@@ -200,6 +207,29 @@ function findPreviousStable(
200
207
  return stables[idx - 1] ?? null;
201
208
  }
202
209
 
210
+ export function areAdjacentStableReleases(
211
+ stables: Release[],
212
+ previousTag: string,
213
+ currentTag: string,
214
+ ): boolean {
215
+ return (
216
+ findPreviousStable(stables, currentTag)?.tag_name === previousTag
217
+ );
218
+ }
219
+
220
+ export function findNextStableRelease(
221
+ stables: Release[],
222
+ terminalTag: string,
223
+ ): Release | null {
224
+ const terminalIndex = stables.findIndex(
225
+ (release) => release.tag_name === terminalTag,
226
+ );
227
+ if (terminalIndex < 0) {
228
+ throw new Error(`Could not find terminal release ${terminalTag}`);
229
+ }
230
+ return stables[terminalIndex + 1] ?? null;
231
+ }
232
+
203
233
  function cloneCodex(tmp: string): string {
204
234
  const dir = join(tmp, "codex");
205
235
  git([
@@ -1,14 +1,17 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import type { CodexWatchAnalysis } from "./release-analysis.ts";
3
3
  import {
4
+ advanceCodexAuditCursor,
4
5
  emptyTrackerState,
5
- hasProcessedTag,
6
+ getCodexAuditCursorTag,
7
+ hasProcessedRange,
6
8
  parseTrackerState,
7
9
  recordAnalysis,
8
10
  renderTrackerBody,
9
11
  serializeTrackerState,
10
12
  type TrackerEntry,
11
13
  upsertTrackerEntry,
14
+ validateLegacyCodexAuditCursor,
12
15
  } from "./tracker.ts";
13
16
 
14
17
  function analysis(
@@ -18,6 +21,7 @@ function analysis(
18
21
  return {
19
22
  previous_tag: "rust-v0.1.0",
20
23
  current_tag: tag,
24
+ is_adjacent_release: true,
21
25
  release_url: `https://github.com/openai/codex/releases/tag/${tag}`,
22
26
  release_notes_md: "",
23
27
  verdict,
@@ -31,6 +35,12 @@ function analysis(
31
35
  };
32
36
  }
33
37
 
38
+ function withoutAuditCursor(body: string): string {
39
+ return body
40
+ .replace(/ "audit_cursor_tag": (?:null|"[^"]+"),\n/, "")
41
+ .replace(' "audit_cursor_validated": true,\n', "");
42
+ }
43
+
34
44
  function entry(index: number, outcome: TrackerEntry["outcome"]): TrackerEntry {
35
45
  const verdict =
36
46
  outcome === "recorded_noop" ? "no-op" : "tool-surface review needed";
@@ -51,11 +61,38 @@ function entry(index: number, outcome: TrackerEntry["outcome"]): TrackerEntry {
51
61
  }
52
62
 
53
63
  describe("tracker state", () => {
54
- test("returns empty state when hidden state is absent or malformed", () => {
55
- expect(parseTrackerState("plain body")).toEqual(emptyTrackerState());
56
- expect(
64
+ test("fails closed when hidden state is absent or malformed", () => {
65
+ expect(() => parseTrackerState("plain body")).toThrow(
66
+ "Codex tracker hidden state is missing",
67
+ );
68
+ expect(() =>
57
69
  parseTrackerState("<!-- codex-agent-watch-state\nnot json\n-->"),
58
- ).toEqual(emptyTrackerState());
70
+ ).toThrow("Codex tracker hidden state is invalid");
71
+ expect(() =>
72
+ parseTrackerState(
73
+ '<!-- codex-agent-watch-state\n{"last_checked_tag":null,"last_checked_at":null,"processed":[{}]}\n-->',
74
+ ),
75
+ ).toThrow("Codex tracker hidden state is invalid");
76
+ expect(() =>
77
+ parseTrackerState(
78
+ '<!-- codex-agent-watch-state\n{"audit_cursor_tag":null,"last_checked_tag":"rust-v0.2.0","last_checked_at":"2026-08-20T00:00:00Z","processed":[]}\n-->',
79
+ ),
80
+ ).toThrow("Codex tracker hidden state is invalid");
81
+ });
82
+
83
+ test("fails closed on an unsupported durable cursor", () => {
84
+ const state = upsertTrackerEntry(
85
+ emptyTrackerState(),
86
+ entry(1, "no_local_impact"),
87
+ );
88
+ const body = renderTrackerBody(state).replace(
89
+ '"audit_cursor_tag": "rust-v0.1.0"',
90
+ '"audit_cursor_tag": "rust-v0.5.0"',
91
+ );
92
+
93
+ expect(() => parseTrackerState(body)).toThrow(
94
+ "Codex tracker hidden state is invalid",
95
+ );
59
96
  });
60
97
 
61
98
  test("round-trips hidden state through rendered body", () => {
@@ -72,6 +109,85 @@ describe("tracker state", () => {
72
109
  expect(body).toContain("upstream-only router change");
73
110
  });
74
111
 
112
+ test("derives the durable cursor from legacy hidden state", () => {
113
+ const state = recordAnalysis(emptyTrackerState(), {
114
+ analysis: analysis("rust-v0.2.0"),
115
+ outcome: "recorded_noop",
116
+ notes: "no watched changes",
117
+ processedAt: "2026-07-02T00:00:00.000Z",
118
+ });
119
+ const legacyBody = withoutAuditCursor(renderTrackerBody(state));
120
+
121
+ const legacy = parseTrackerState(legacyBody);
122
+ expect(legacy.audit_cursor_validated).toBe(false);
123
+ const migrated = validateLegacyCodexAuditCursor(legacy, [
124
+ "rust-v0.1.0",
125
+ "rust-v0.2.0",
126
+ ]);
127
+ expect(migrated.audit_cursor_validated).toBe(true);
128
+ expect(getCodexAuditCursorTag(migrated)).toBe("rust-v0.2.0");
129
+ });
130
+
131
+ test("migrates a legacy cursor independently of an older replay", () => {
132
+ let state = upsertTrackerEntry(
133
+ emptyTrackerState(),
134
+ entry(1, "no_local_impact"),
135
+ );
136
+ state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
137
+ state = upsertTrackerEntry(state, entry(3, "no_local_impact"));
138
+ state = upsertTrackerEntry(state, entry(1, "no_local_impact"));
139
+
140
+ const legacy = parseTrackerState(
141
+ withoutAuditCursor(renderTrackerBody(state)),
142
+ );
143
+ const migrated = validateLegacyCodexAuditCursor(legacy, [
144
+ "rust-v0.0.0",
145
+ "rust-v0.1.0",
146
+ "rust-v0.2.0",
147
+ "rust-v0.3.0",
148
+ ]);
149
+ expect(migrated.last_checked_tag).toBe("rust-v0.1.0");
150
+ expect(getCodexAuditCursorTag(migrated)).toBe("rust-v0.3.0");
151
+ });
152
+
153
+ test("fails closed on a disconnected future legacy replay", () => {
154
+ let state = upsertTrackerEntry(
155
+ emptyTrackerState(),
156
+ entry(1, "no_local_impact"),
157
+ );
158
+ state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
159
+ state = upsertTrackerEntry(state, entry(5, "no_local_impact"));
160
+
161
+ expect(() =>
162
+ parseTrackerState(withoutAuditCursor(renderTrackerBody(state))),
163
+ ).toThrow("Codex tracker hidden state is invalid");
164
+ });
165
+
166
+ test("fails closed on a cumulative legacy replay", () => {
167
+ const cumulative = {
168
+ ...analysis("rust-v0.5.0", "tool-surface review needed"),
169
+ is_adjacent_release: false,
170
+ };
171
+ const state = recordAnalysis(emptyTrackerState(), {
172
+ analysis: cumulative,
173
+ outcome: "no_local_impact",
174
+ notes: "legacy cumulative replay",
175
+ });
176
+ const legacy = parseTrackerState(
177
+ withoutAuditCursor(renderTrackerBody(state)),
178
+ );
179
+
180
+ expect(() =>
181
+ validateLegacyCodexAuditCursor(legacy, [
182
+ "rust-v0.1.0",
183
+ "rust-v0.2.0",
184
+ "rust-v0.3.0",
185
+ "rust-v0.4.0",
186
+ "rust-v0.5.0",
187
+ ]),
188
+ ).toThrow("Legacy Codex tracker range is not adjacent");
189
+ });
190
+
75
191
  test("records noops in hidden state without adding visible table rows", () => {
76
192
  const state = recordAnalysis(emptyTrackerState(), {
77
193
  analysis: analysis("rust-v0.2.0"),
@@ -81,11 +197,132 @@ describe("tracker state", () => {
81
197
  });
82
198
 
83
199
  const body = renderTrackerBody(state);
84
- expect(hasProcessedTag(parseTrackerState(body), "rust-v0.2.0")).toBe(true);
200
+ expect(
201
+ hasProcessedRange(
202
+ parseTrackerState(body),
203
+ "rust-v0.1.0",
204
+ "rust-v0.2.0",
205
+ ),
206
+ ).toBe(true);
85
207
  expect(body).toContain("_No actionable releases recorded yet._");
86
208
  expect(body).toContain("no watched changes");
87
209
  });
88
210
 
211
+ test("retries an errored release from its previous tag", () => {
212
+ let state = upsertTrackerEntry(
213
+ emptyTrackerState(),
214
+ entry(1, "recorded_noop"),
215
+ );
216
+ state = upsertTrackerEntry(state, entry(2, "error"));
217
+
218
+ expect(
219
+ hasProcessedRange(state, "rust-v0.1.0", "rust-v0.2.0"),
220
+ ).toBe(false);
221
+ expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
222
+
223
+ state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
224
+ expect(
225
+ hasProcessedRange(state, "rust-v0.1.0", "rust-v0.2.0"),
226
+ ).toBe(true);
227
+ expect(getCodexAuditCursorTag(state)).toBe("rust-v0.2.0");
228
+ });
229
+
230
+ test("uses an errored release baseline when no terminal entry exists", () => {
231
+ const state = upsertTrackerEntry(
232
+ emptyTrackerState(),
233
+ entry(2, "error"),
234
+ );
235
+
236
+ expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
237
+ });
238
+
239
+ test("does not regress the cursor after an older replay", () => {
240
+ let state = upsertTrackerEntry(
241
+ emptyTrackerState(),
242
+ entry(10, "no_local_impact"),
243
+ );
244
+ state = upsertTrackerEntry(state, entry(5, "no_local_impact"));
245
+
246
+ expect(state.last_checked_tag).toBe("rust-v0.5.0");
247
+ expect(getCodexAuditCursorTag(state)).toBe("rust-v0.10.0");
248
+ });
249
+
250
+ test("does not let a future replay skip an errored release", () => {
251
+ let state = upsertTrackerEntry(
252
+ emptyTrackerState(),
253
+ entry(1, "no_local_impact"),
254
+ );
255
+ state = upsertTrackerEntry(state, entry(2, "error"));
256
+ state = upsertTrackerEntry(state, entry(5, "no_local_impact"));
257
+
258
+ expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
259
+
260
+ state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
261
+ expect(getCodexAuditCursorTag(state)).toBe("rust-v0.2.0");
262
+ });
263
+
264
+ test("does not advance for an explicit non-adjacent range", () => {
265
+ const initial = upsertTrackerEntry(
266
+ emptyTrackerState(),
267
+ entry(1, "no_local_impact"),
268
+ );
269
+ const cumulative = {
270
+ ...analysis("rust-v0.5.0", "tool-surface review needed"),
271
+ is_adjacent_release: false,
272
+ };
273
+ const state = recordAnalysis(initial, {
274
+ analysis: cumulative,
275
+ outcome: "no_local_impact",
276
+ notes: "explicit cumulative replay",
277
+ });
278
+
279
+ expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
280
+ expect(
281
+ getCodexAuditCursorTag(
282
+ advanceCodexAuditCursor(
283
+ state,
284
+ "rust-v0.1.0",
285
+ "rust-v0.5.0",
286
+ false,
287
+ ),
288
+ ),
289
+ ).toBe("rust-v0.1.0");
290
+ });
291
+
292
+ test("advances across an already audited adjacent range", () => {
293
+ let state = upsertTrackerEntry(
294
+ emptyTrackerState(),
295
+ entry(1, "no_local_impact"),
296
+ );
297
+ state = upsertTrackerEntry(state, entry(3, "no_local_impact"));
298
+ state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
299
+
300
+ expect(getCodexAuditCursorTag(state)).toBe("rust-v0.2.0");
301
+ state = advanceCodexAuditCursor(
302
+ state,
303
+ "rust-v0.2.0",
304
+ "rust-v0.3.0",
305
+ true,
306
+ );
307
+ expect(getCodexAuditCursorTag(state)).toBe("rust-v0.3.0");
308
+ });
309
+
310
+ test("retains the durable cursor entry when history is truncated", () => {
311
+ let state = upsertTrackerEntry(
312
+ emptyTrackerState(),
313
+ entry(100, "no_local_impact"),
314
+ );
315
+ for (let index = 1; index <= 60; index += 1) {
316
+ state = upsertTrackerEntry(state, entry(index, "no_local_impact"));
317
+ }
318
+
319
+ expect(state.processed).toHaveLength(50);
320
+ expect(state.processed.some(({ tag }) => tag === "rust-v0.100.0")).toBe(
321
+ true,
322
+ );
323
+ expect(getCodexAuditCursorTag(state)).toBe("rust-v0.100.0");
324
+ });
325
+
89
326
  test("keeps the last 50 processed releases in hidden state", () => {
90
327
  let state = emptyTrackerState();
91
328
  for (let i = 1; i <= 60; i++) {