@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.
- package/dist/agent-presets.js +55 -2490
- package/dist/agent-presets.js.map +4 -4
- package/dist/mcp-client.js +2 -2
- package/dist/mcp-client.js.map +1 -1
- package/dist/types/agent/acting-user.d.ts +21 -0
- package/dist/types/agent/acting-user.d.ts.map +1 -0
- package/dist/types/agent/available-models.d.ts +2 -0
- package/dist/types/agent/available-models.d.ts.map +1 -1
- package/dist/types/agent/client-skills.d.ts +3 -0
- package/dist/types/agent/client-skills.d.ts.map +1 -1
- package/dist/types/agent/memory-git.d.ts +3 -11
- package/dist/types/agent/memory-git.d.ts.map +1 -1
- package/dist/types/agent/message.d.ts +1 -0
- package/dist/types/agent/message.d.ts.map +1 -1
- package/dist/types/agent/model-catalog.d.ts +14 -52
- package/dist/types/agent/model-catalog.d.ts.map +1 -1
- package/dist/types/agent/model.d.ts.map +1 -1
- package/dist/types/agent/personality-presets.d.ts +1 -1
- package/dist/types/agent/personality-presets.d.ts.map +1 -1
- package/dist/types/agent/shared-memory-skills.d.ts +18 -0
- package/dist/types/agent/shared-memory-skills.d.ts.map +1 -0
- package/dist/types/agent/subagents/index.d.ts +1 -1
- package/dist/types/agent/subagents/index.d.ts.map +1 -1
- package/dist/types/agent/turn-recovery-policy.d.ts +10 -7
- package/dist/types/agent/turn-recovery-policy.d.ts.map +1 -1
- package/dist/types/agent-presets.d.ts +0 -1
- package/dist/types/agent-presets.d.ts.map +1 -1
- package/dist/types/backend/api/conversations.d.ts +1 -0
- package/dist/types/backend/api/conversations.d.ts.map +1 -1
- package/dist/types/backend/backend.d.ts +4 -2
- package/dist/types/backend/backend.d.ts.map +1 -1
- package/dist/types/backend/dev/pi-model-factory.d.ts +6 -0
- package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
- package/dist/types/backend/dev/pi-provider-registry.d.ts.map +1 -1
- package/dist/types/backend/local/local-model-config.d.ts +3 -0
- package/dist/types/backend/local/local-model-config.d.ts.map +1 -1
- package/dist/types/backend/local/local-provider-auth-store.d.ts.map +1 -1
- package/dist/types/backend/local/system-prompt-compilation.d.ts.map +1 -1
- package/dist/types/cli/helpers/stream-processor.d.ts +1 -0
- package/dist/types/cli/helpers/stream-processor.d.ts.map +1 -1
- package/dist/types/cli/helpers/stream-stall-reconciler.d.ts +2 -1
- package/dist/types/cli/helpers/stream-stall-reconciler.d.ts.map +1 -1
- package/dist/types/cli/helpers/stream.d.ts +1 -1
- package/dist/types/cli/helpers/stream.d.ts.map +1 -1
- package/dist/types/reminders/state.d.ts +6 -1
- package/dist/types/reminders/state.d.ts.map +1 -1
- package/dist/types/tools/impl/bash-output.d.ts +1 -0
- package/dist/types/tools/impl/bash-output.d.ts.map +1 -1
- package/dist/types/tools/impl/github-pull-request-tracker.d.ts +7 -3
- package/dist/types/tools/impl/github-pull-request-tracker.d.ts.map +1 -1
- package/dist/types/tools/impl/glob.d.ts +1 -0
- package/dist/types/tools/impl/glob.d.ts.map +1 -1
- package/dist/types/tools/impl/grep.d.ts +1 -0
- package/dist/types/tools/impl/grep.d.ts.map +1 -1
- package/dist/types/tools/impl/skill.d.ts +10 -5
- package/dist/types/tools/impl/skill.d.ts.map +1 -1
- package/dist/types/tools/impl/task-output.d.ts +1 -0
- package/dist/types/tools/impl/task-output.d.ts.map +1 -1
- package/dist/types/tools/impl/task.d.ts.map +1 -1
- package/dist/types/tools/manager.d.ts.map +1 -1
- package/dist/types/tools/toolset.d.ts.map +1 -1
- package/dist/types/types/approval-classification-protocol.d.ts +13 -0
- package/dist/types/types/approval-classification-protocol.d.ts.map +1 -0
- package/dist/types/types/protocol_v2.d.ts +4 -8
- package/dist/types/types/protocol_v2.d.ts.map +1 -1
- package/image-resize-worker.js +42 -20
- package/letta.js +94957 -151018
- package/package.json +4 -3
- package/scripts/codex-watch/agent-watch.ts +63 -8
- package/scripts/codex-watch/release-analysis.test.ts +57 -0
- package/scripts/codex-watch/release-analysis.ts +31 -1
- package/scripts/codex-watch/tracker.test.ts +243 -6
- package/scripts/codex-watch/tracker.ts +221 -20
- package/scripts/isolated-unit-tests.json +15 -0
- package/scripts/pi-ai-watch/agent-watch.ts +253 -0
- package/scripts/pi-ai-watch/github.ts +145 -0
- package/scripts/pi-ai-watch/release-analysis.test.ts +137 -0
- package/scripts/pi-ai-watch/release-analysis.ts +371 -0
- package/scripts/pi-ai-watch/tracker.test.ts +138 -0
- package/scripts/pi-ai-watch/tracker.ts +303 -0
- package/scripts/pi-ai-watch/update-tracker.ts +215 -0
- 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.
|
|
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": "
|
|
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
|
-
|
|
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
|
|
82
|
-
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 (
|
|
97
|
-
|
|
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
|
-
|
|
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("
|
|
55
|
-
expect(parseTrackerState("plain body")).
|
|
56
|
-
|
|
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
|
-
).
|
|
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(
|
|
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++) {
|