@letta-ai/letta-code 0.30.27 → 0.30.28
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 +17 -17
- package/dist/agent-presets.js.map +1 -1
- package/dist/mcp-client.js +2 -2
- package/dist/mcp-client.js.map +1 -1
- package/dist/types/tools/secret-substitution.d.ts.map +1 -1
- package/dist/types/types/loop-status-protocol.d.ts +17 -0
- package/dist/types/types/loop-status-protocol.d.ts.map +1 -0
- package/dist/types/types/protocol_v2.d.ts +2 -19
- package/dist/types/types/protocol_v2.d.ts.map +1 -1
- package/dist/types/websocket/listener/inbound-queue.d.ts +5 -0
- package/dist/types/websocket/listener/inbound-queue.d.ts.map +1 -0
- package/dist/types/websocket/listener/protocol-outbound-routing.d.ts +9 -0
- package/dist/types/websocket/listener/protocol-outbound-routing.d.ts.map +1 -0
- package/dist/types/websocket/listener/protocol-outbound.d.ts.map +1 -1
- package/dist/types/websocket/listener/runtime.d.ts.map +1 -1
- package/dist/types/websocket/listener/turn-correlation.d.ts +10 -0
- package/dist/types/websocket/listener/turn-correlation.d.ts.map +1 -0
- package/dist/types/websocket/listener/types.d.ts +4 -0
- package/dist/types/websocket/listener/types.d.ts.map +1 -1
- package/letta.js +269 -117
- package/package.json +1 -1
- package/scripts/claude-watch/agent-watch.ts +622 -0
- package/scripts/claude-watch/docs-snapshot.test.ts +259 -0
- package/scripts/claude-watch/docs-snapshot.ts +672 -0
- package/scripts/claude-watch/fixtures/historical-replays.json +52 -0
- package/scripts/claude-watch/github.ts +137 -0
- package/scripts/claude-watch/release-analysis.test.ts +235 -0
- package/scripts/claude-watch/release-analysis.ts +297 -0
- package/scripts/claude-watch/release-source.test.ts +179 -0
- package/scripts/claude-watch/release-source.ts +369 -0
- package/scripts/claude-watch/runtime-observations.ts +98 -0
- package/scripts/claude-watch/runtime-probe.test.ts +576 -0
- package/scripts/claude-watch/runtime-probe.ts +911 -0
- package/scripts/claude-watch/runtime-sandbox.ts +170 -0
- package/scripts/claude-watch/state-branch.test.ts +211 -0
- package/scripts/claude-watch/state-branch.ts +316 -0
- package/scripts/claude-watch/tracker.test.ts +148 -0
- package/scripts/claude-watch/tracker.ts +325 -0
- package/scripts/claude-watch/types.ts +186 -0
- package/scripts/claude-watch/update-tracker.ts +201 -0
- package/scripts/codex-watch/agent-watch.ts +2 -2
- package/scripts/codex-watch/release-analysis.ts +14 -2
- package/scripts/codex-watch/tracker.ts +1 -3
- package/scripts/run-unit-tests.cjs +2 -0
- package/scripts/source-file-size-baseline.json +3 -3
- package/skills/creating-mods/references/commands.md +1 -1
- package/skills/creating-mods/references/ui.md +1 -1
- package/skills/customizing-commands/SKILL.md +1 -1
- package/skills/initializing-memory/SKILL.md +7 -7
- package/skills/self-configuration/SKILL.md +4 -4
- package/scripts/codex-watch/check-release.ts +0 -128
- package/scripts/codex-watch/render-issue.ts +0 -273
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { diffDocsSnapshots } from "./docs-snapshot.ts";
|
|
2
|
+
import { diffClaudeRuntime } from "./runtime-probe.ts";
|
|
3
|
+
import {
|
|
4
|
+
loadStateFilesAtCommit,
|
|
5
|
+
loadStateSnapshotAtCommit,
|
|
6
|
+
} from "./state-branch.ts";
|
|
7
|
+
import {
|
|
8
|
+
CLAUDE_WATCH_STATE_SCHEMA_VERSION,
|
|
9
|
+
type ClaudeDocsSnapshot,
|
|
10
|
+
type ClaudeReleaseCandidate,
|
|
11
|
+
type ClaudeRuntimeSnapshot,
|
|
12
|
+
type ClaudeWatchAnalysis,
|
|
13
|
+
type ClaudeWatchStateSnapshot,
|
|
14
|
+
type ClaudeWatchVerdict,
|
|
15
|
+
} from "./types.ts";
|
|
16
|
+
|
|
17
|
+
export interface AnalyzeClaudeCandidateOptions {
|
|
18
|
+
candidate: ClaudeReleaseCandidate;
|
|
19
|
+
previous: ClaudeWatchStateSnapshot | null;
|
|
20
|
+
currentDocs: ClaudeDocsSnapshot;
|
|
21
|
+
previousSources?: Record<string, string>;
|
|
22
|
+
currentSources?: Record<string, string>;
|
|
23
|
+
currentRuntime: ClaudeRuntimeSnapshot | null;
|
|
24
|
+
workflowRunUrl: string;
|
|
25
|
+
stateBaseSha: string | null;
|
|
26
|
+
errors?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ClaudeVerdictInput {
|
|
30
|
+
analysis: Omit<ClaudeWatchAnalysis, "verdict" | "verdict_reasons">;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function buildClaudeCandidateId(
|
|
34
|
+
version: string,
|
|
35
|
+
docsDigest: string,
|
|
36
|
+
runtimeDigest: string | null = null,
|
|
37
|
+
): string {
|
|
38
|
+
return `claude@${version}:docs:${docsDigest}:runtime:${runtimeDigest ?? "none"}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function buildClaudeStateSnapshot(
|
|
42
|
+
analysis: ClaudeWatchAnalysis,
|
|
43
|
+
docs: ClaudeDocsSnapshot,
|
|
44
|
+
runtime: ClaudeRuntimeSnapshot | null,
|
|
45
|
+
): ClaudeWatchStateSnapshot {
|
|
46
|
+
return {
|
|
47
|
+
schema_version: CLAUDE_WATCH_STATE_SCHEMA_VERSION,
|
|
48
|
+
candidate_id: analysis.candidate_id,
|
|
49
|
+
package_version: analysis.current_version,
|
|
50
|
+
npm_integrity: analysis.npm_integrity,
|
|
51
|
+
npm_published_at: analysis.npm_published_at,
|
|
52
|
+
release_url: analysis.release_url,
|
|
53
|
+
release_notes_md: analysis.release_notes_md,
|
|
54
|
+
release_published_at: analysis.release_published_at,
|
|
55
|
+
dist_tags: analysis.dist_tags,
|
|
56
|
+
docs,
|
|
57
|
+
runtime,
|
|
58
|
+
fetched_at: new Date().toISOString(),
|
|
59
|
+
workflow_run_url: analysis.workflow_run_url,
|
|
60
|
+
state_commit_parent: analysis.state_base_sha,
|
|
61
|
+
analysis_parent_sha: analysis.state_base_sha,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function analyzeClaudeCandidate(
|
|
66
|
+
options: AnalyzeClaudeCandidateOptions,
|
|
67
|
+
): ClaudeWatchAnalysis {
|
|
68
|
+
const docsDiff = diffDocsSnapshots(
|
|
69
|
+
options.previous?.docs ?? null,
|
|
70
|
+
options.currentDocs,
|
|
71
|
+
{
|
|
72
|
+
previousSources: options.previousSources,
|
|
73
|
+
currentSources: options.currentSources,
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
const runtimeDiff =
|
|
77
|
+
options.previous?.runtime && options.currentRuntime
|
|
78
|
+
? diffClaudeRuntime(options.previous.runtime, options.currentRuntime)
|
|
79
|
+
: null;
|
|
80
|
+
const candidateId = buildClaudeCandidateId(
|
|
81
|
+
options.candidate.version,
|
|
82
|
+
options.currentDocs.digest,
|
|
83
|
+
options.currentRuntime?.digest ?? null,
|
|
84
|
+
);
|
|
85
|
+
const base = {
|
|
86
|
+
schema_version: 1,
|
|
87
|
+
candidate_id: candidateId,
|
|
88
|
+
previous_candidate_id: options.previous?.candidate_id ?? null,
|
|
89
|
+
previous_version: options.previous?.package_version ?? null,
|
|
90
|
+
current_version: options.candidate.version,
|
|
91
|
+
is_bootstrap: options.previous === null,
|
|
92
|
+
release_url: options.candidate.release_url,
|
|
93
|
+
release_notes_md: options.candidate.release_notes_md,
|
|
94
|
+
npm_integrity: options.candidate.integrity,
|
|
95
|
+
npm_published_at: options.candidate.published_at,
|
|
96
|
+
release_published_at: options.candidate.release_published_at,
|
|
97
|
+
dist_tags: options.candidate.dist_tags,
|
|
98
|
+
docs_digest: options.currentDocs.digest,
|
|
99
|
+
runtime_digest: options.currentRuntime?.digest ?? null,
|
|
100
|
+
docs_diff: docsDiff,
|
|
101
|
+
previous_runtime_snapshot: options.previous?.runtime ?? null,
|
|
102
|
+
runtime_snapshot: options.currentRuntime,
|
|
103
|
+
runtime_diff: runtimeDiff,
|
|
104
|
+
errors: options.errors ?? [],
|
|
105
|
+
workflow_run_url: options.workflowRunUrl,
|
|
106
|
+
state_base_sha: options.stateBaseSha,
|
|
107
|
+
state_snapshot_candidate_id: options.previous?.candidate_id ?? null,
|
|
108
|
+
} satisfies Omit<ClaudeWatchAnalysis, "verdict" | "verdict_reasons">;
|
|
109
|
+
const { verdict, reasons } = classifyClaudeVerdict({ analysis: base });
|
|
110
|
+
return {
|
|
111
|
+
...base,
|
|
112
|
+
verdict,
|
|
113
|
+
verdict_reasons: reasons,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function rebuildClaudeAnalysisFromState(
|
|
118
|
+
repoPath: string,
|
|
119
|
+
stateCommitSha: string,
|
|
120
|
+
): ClaudeWatchAnalysis {
|
|
121
|
+
const current = loadStateSnapshotAtCommit(repoPath, stateCommitSha);
|
|
122
|
+
if (!current) {
|
|
123
|
+
throw new Error(`No valid Claude state snapshot at ${stateCommitSha}`);
|
|
124
|
+
}
|
|
125
|
+
const analysisParentSha =
|
|
126
|
+
current.analysis_parent_sha === undefined
|
|
127
|
+
? current.state_commit_parent
|
|
128
|
+
: current.analysis_parent_sha;
|
|
129
|
+
const previous = analysisParentSha
|
|
130
|
+
? loadStateSnapshotAtCommit(repoPath, analysisParentSha)
|
|
131
|
+
: null;
|
|
132
|
+
const currentFiles = loadStateFilesAtCommit(repoPath, stateCommitSha);
|
|
133
|
+
const previousFiles = analysisParentSha
|
|
134
|
+
? loadStateFilesAtCommit(repoPath, analysisParentSha)
|
|
135
|
+
: {};
|
|
136
|
+
const candidate: ClaudeReleaseCandidate = {
|
|
137
|
+
version: current.package_version,
|
|
138
|
+
published_at: current.npm_published_at,
|
|
139
|
+
integrity: current.npm_integrity,
|
|
140
|
+
tarball_url: "",
|
|
141
|
+
release_url: current.release_url,
|
|
142
|
+
release_notes_md: current.release_notes_md,
|
|
143
|
+
release_published_at: current.release_published_at,
|
|
144
|
+
dist_tags: current.dist_tags,
|
|
145
|
+
};
|
|
146
|
+
const analysis = analyzeClaudeCandidate({
|
|
147
|
+
candidate,
|
|
148
|
+
previous,
|
|
149
|
+
currentDocs: current.docs,
|
|
150
|
+
previousSources: sourceFiles(previousFiles),
|
|
151
|
+
currentSources: sourceFiles(currentFiles),
|
|
152
|
+
currentRuntime: current.runtime,
|
|
153
|
+
workflowRunUrl: current.workflow_run_url,
|
|
154
|
+
stateBaseSha: analysisParentSha,
|
|
155
|
+
});
|
|
156
|
+
if (analysis.candidate_id !== current.candidate_id) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Rebuilt candidate ${analysis.candidate_id} does not match state ${current.candidate_id}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return analysis;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function sourceFiles(files: Record<string, string>): Record<string, string> {
|
|
165
|
+
return Object.fromEntries(
|
|
166
|
+
Object.entries(files).filter(([path]) => path.startsWith("sources/")),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function classifyClaudeVerdict(input: ClaudeVerdictInput): {
|
|
171
|
+
verdict: ClaudeWatchVerdict;
|
|
172
|
+
reasons: string[];
|
|
173
|
+
} {
|
|
174
|
+
const { analysis } = input;
|
|
175
|
+
const reasons: string[] = [];
|
|
176
|
+
if (analysis.errors.length > 0) {
|
|
177
|
+
return {
|
|
178
|
+
verdict: "manual review required",
|
|
179
|
+
reasons: analysis.errors.map((error) => `capture error: ${error}`),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (analysis.is_bootstrap) {
|
|
183
|
+
return {
|
|
184
|
+
verdict: "manual review required",
|
|
185
|
+
reasons: [
|
|
186
|
+
"bootstrap requires a complete current Claude-vs-local parity audit",
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const runtime = analysis.runtime_diff;
|
|
192
|
+
if (
|
|
193
|
+
runtime &&
|
|
194
|
+
(runtime.tools_added.length > 0 || runtime.tools_removed.length > 0)
|
|
195
|
+
) {
|
|
196
|
+
reasons.push(
|
|
197
|
+
`runtime tool inventory changed (${[
|
|
198
|
+
...runtime.tools_added.map((name) => `+${name}`),
|
|
199
|
+
...runtime.tools_removed.map((name) => `-${name}`),
|
|
200
|
+
].join(", ")})`,
|
|
201
|
+
);
|
|
202
|
+
return { verdict: "tool surface review needed", reasons };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const namedToolChanges = analysis.docs_diff.tools;
|
|
206
|
+
const probeProblems = [
|
|
207
|
+
...(analysis.previous_runtime_snapshot?.probes.map((probe) => ({
|
|
208
|
+
side: "previous",
|
|
209
|
+
probe,
|
|
210
|
+
})) ?? []),
|
|
211
|
+
...(analysis.runtime_snapshot?.probes.map((probe) => ({
|
|
212
|
+
side: "current",
|
|
213
|
+
probe,
|
|
214
|
+
})) ?? []),
|
|
215
|
+
].filter(({ probe }) => probe.status !== "passed");
|
|
216
|
+
if (probeProblems.length > 0) {
|
|
217
|
+
return {
|
|
218
|
+
verdict: "manual review required",
|
|
219
|
+
reasons: probeProblems.map(
|
|
220
|
+
({ side, probe }) =>
|
|
221
|
+
`${side} ${probe.name} probe was ${probe.status}: ${probe.error ?? "unknown"}`,
|
|
222
|
+
),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (
|
|
226
|
+
namedToolChanges.added.length > 0 ||
|
|
227
|
+
namedToolChanges.removed.length > 0 ||
|
|
228
|
+
namedToolChanges.changed.length > 0 ||
|
|
229
|
+
(runtime?.changed_probes.length ?? 0) > 0
|
|
230
|
+
) {
|
|
231
|
+
reasons.push("official tool contracts or fixed probe observations changed");
|
|
232
|
+
return { verdict: "tool contract review needed", reasons };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const releaseText = analysis.release_notes_md.toLowerCase();
|
|
236
|
+
if (
|
|
237
|
+
/\b(system prompt|prompt instruction|claude\.md|memory|output style|model guidance)\b/u.test(
|
|
238
|
+
releaseText,
|
|
239
|
+
)
|
|
240
|
+
) {
|
|
241
|
+
return {
|
|
242
|
+
verdict: "prompt review needed",
|
|
243
|
+
reasons: [
|
|
244
|
+
"release notes describe model-facing prompt or guidance behavior",
|
|
245
|
+
],
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const docs = analysis.docs_diff;
|
|
250
|
+
const watchedDocsChanged = docs.watched_page_diffs.length > 0;
|
|
251
|
+
const anyDocsPageChanged =
|
|
252
|
+
docs.added_pages.length > 0 ||
|
|
253
|
+
docs.removed_pages.length > 0 ||
|
|
254
|
+
docs.changed_pages.length > 0;
|
|
255
|
+
const namedHarnessChanged = [
|
|
256
|
+
docs.cli,
|
|
257
|
+
docs.settings,
|
|
258
|
+
docs.env_vars,
|
|
259
|
+
docs.permission_rules,
|
|
260
|
+
].some(
|
|
261
|
+
(change) =>
|
|
262
|
+
change.added.length > 0 ||
|
|
263
|
+
change.removed.length > 0 ||
|
|
264
|
+
change.changed.length > 0,
|
|
265
|
+
);
|
|
266
|
+
const runtimeChanged = Boolean(
|
|
267
|
+
runtime &&
|
|
268
|
+
(runtime.help_changed ||
|
|
269
|
+
runtime.doctor_changed ||
|
|
270
|
+
runtime.init_changed ||
|
|
271
|
+
runtime.auto_mode_defaults_changed ||
|
|
272
|
+
runtime.event_types_added.length > 0 ||
|
|
273
|
+
runtime.event_types_removed.length > 0),
|
|
274
|
+
);
|
|
275
|
+
const packageChanged = analysis.previous_version !== analysis.current_version;
|
|
276
|
+
if (
|
|
277
|
+
watchedDocsChanged ||
|
|
278
|
+
anyDocsPageChanged ||
|
|
279
|
+
namedHarnessChanged ||
|
|
280
|
+
runtimeChanged ||
|
|
281
|
+
packageChanged
|
|
282
|
+
) {
|
|
283
|
+
if (watchedDocsChanged) reasons.push("watched official docs changed");
|
|
284
|
+
else if (anyDocsPageChanged)
|
|
285
|
+
reasons.push("an unwatched official docs page changed and needs review");
|
|
286
|
+
if (namedHarnessChanged) reasons.push("named harness surfaces changed");
|
|
287
|
+
if (runtimeChanged) reasons.push("observable runtime behavior changed");
|
|
288
|
+
if (packageChanged)
|
|
289
|
+
reasons.push("a new exact Claude package was published");
|
|
290
|
+
return { verdict: "harness behavior review needed", reasons };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
verdict: "no-op",
|
|
295
|
+
reasons: ["only unwatched documentation content or metadata changed"],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ClaudeReleaseSourceDisagreementError,
|
|
3
|
+
parseClaudeGitHubReleases,
|
|
4
|
+
parseClaudeNpmMetadata,
|
|
5
|
+
selectClaudeReleaseCandidate,
|
|
6
|
+
} from "./release-source.ts";
|
|
7
|
+
import type { ClaudeGitHubRelease, ClaudeNpmMetadata } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
function github(version: string, hour: number): ClaudeGitHubRelease {
|
|
10
|
+
return {
|
|
11
|
+
tag_name: version,
|
|
12
|
+
draft: false,
|
|
13
|
+
prerelease: false,
|
|
14
|
+
html_url: `https://github.com/anthropics/claude-code/releases/tag/v${version}`,
|
|
15
|
+
body: `notes ${version}`,
|
|
16
|
+
published_at: `2026-08-01T${String(hour).padStart(2, "0")}:00:00Z`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function npm(latest = "1.2.0"): ClaudeNpmMetadata {
|
|
21
|
+
return {
|
|
22
|
+
dist_tags: { latest, stable: "1.1.0", next: "2.0.0-beta.1" },
|
|
23
|
+
versions: ["1.0.0", "1.1.0", "1.2.0"].map((version, index) => ({
|
|
24
|
+
version,
|
|
25
|
+
published_at: `2026-08-01T0${index}:30:00Z`,
|
|
26
|
+
integrity: `sha512-${version}`,
|
|
27
|
+
tarball_url: `https://registry.npmjs.org/pkg/-/${version}.tgz`,
|
|
28
|
+
})),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const releases = [github("1.0.0", 0), github("1.1.0", 1), github("1.2.0", 2)];
|
|
33
|
+
|
|
34
|
+
describe("parseClaudeGitHubReleases", () => {
|
|
35
|
+
test("filters drafts and prereleases, normalizes tags, and orders oldest first", () => {
|
|
36
|
+
const parsed = parseClaudeGitHubReleases([
|
|
37
|
+
{ ...github("v1.2.0", 2), tag_name: "v1.2.0" },
|
|
38
|
+
{ ...github("1.3.0-beta.1", 3), prerelease: true },
|
|
39
|
+
{ ...github("1.1.0", 1), draft: true },
|
|
40
|
+
{ ...github("v1.0.0", 0), tag_name: "v1.0.0" },
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
expect(parsed.map(({ tag_name }) => tag_name)).toEqual(["1.0.0", "1.2.0"]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("parseClaudeNpmMetadata", () => {
|
|
48
|
+
test("includes channels, all publish times, integrity, and tarballs", () => {
|
|
49
|
+
const parsed = parseClaudeNpmMetadata({
|
|
50
|
+
"dist-tags": { latest: "1.1.0", stable: "1.0.0", next: "1.2.0-beta.1" },
|
|
51
|
+
versions: {
|
|
52
|
+
"1.1.0": {
|
|
53
|
+
dist: { integrity: "sha512-b", tarball: "https://npm/b.tgz" },
|
|
54
|
+
},
|
|
55
|
+
"1.0.0": {
|
|
56
|
+
dist: { integrity: "sha512-a", tarball: "https://npm/a.tgz" },
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
time: {
|
|
60
|
+
"1.0.0": "2026-08-01T00:00:00Z",
|
|
61
|
+
"1.1.0": "2026-08-02T00:00:00Z",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(parsed.dist_tags).toEqual({
|
|
66
|
+
latest: "1.1.0",
|
|
67
|
+
stable: "1.0.0",
|
|
68
|
+
next: "1.2.0-beta.1",
|
|
69
|
+
});
|
|
70
|
+
expect(parsed.versions).toEqual([
|
|
71
|
+
{
|
|
72
|
+
version: "1.0.0",
|
|
73
|
+
published_at: "2026-08-01T00:00:00Z",
|
|
74
|
+
integrity: "sha512-a",
|
|
75
|
+
tarball_url: "https://npm/a.tgz",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
version: "1.1.0",
|
|
79
|
+
published_at: "2026-08-02T00:00:00Z",
|
|
80
|
+
integrity: "sha512-b",
|
|
81
|
+
tarball_url: "https://npm/b.tgz",
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("selectClaudeReleaseCandidate", () => {
|
|
88
|
+
test("throws an actionable typed error when latest sources mismatch", () => {
|
|
89
|
+
expect(() =>
|
|
90
|
+
selectClaudeReleaseCandidate({
|
|
91
|
+
githubReleases: releases,
|
|
92
|
+
npmMetadata: npm("1.1.0"),
|
|
93
|
+
processedPackageVersions: ["1.0.0"],
|
|
94
|
+
}),
|
|
95
|
+
).toThrow(ClaudeReleaseSourceDisagreementError);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
selectClaudeReleaseCandidate({
|
|
99
|
+
githubReleases: releases,
|
|
100
|
+
npmMetadata: npm("1.1.0"),
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
expect(error).toMatchObject({
|
|
104
|
+
code: "CLAUDE_RELEASE_SOURCE_DISAGREEMENT",
|
|
105
|
+
githubVersion: "1.2.0",
|
|
106
|
+
npmVersion: "1.1.0",
|
|
107
|
+
});
|
|
108
|
+
expect((error as Error).message).toContain("rerun the watcher");
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("takes the oldest release after the terminal processed version", () => {
|
|
113
|
+
const candidate = selectClaudeReleaseCandidate({
|
|
114
|
+
githubReleases: releases,
|
|
115
|
+
npmMetadata: npm(),
|
|
116
|
+
processedPackageVersions: ["1.0.0"],
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(candidate?.version).toBe("1.1.0");
|
|
120
|
+
expect(candidate?.integrity).toBe("sha512-1.1.0");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("bootstrap selects only the current npm latest release", () => {
|
|
124
|
+
const candidate = selectClaudeReleaseCandidate({
|
|
125
|
+
githubReleases: releases,
|
|
126
|
+
npmMetadata: npm(),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(candidate?.version).toBe("1.2.0");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("explicit previous and current overrides select the exact release", () => {
|
|
133
|
+
const candidate = selectClaudeReleaseCandidate({
|
|
134
|
+
githubReleases: releases,
|
|
135
|
+
npmMetadata: npm(),
|
|
136
|
+
processedPackageVersions: ["1.0.0"],
|
|
137
|
+
previousVersion: "1.0.0",
|
|
138
|
+
currentVersion: "1.2.0",
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(candidate?.version).toBe("1.2.0");
|
|
142
|
+
expect(candidate?.release_notes_md).toBe("notes 1.2.0");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("validation can replay exact npm versions omitted from the GitHub feed", () => {
|
|
146
|
+
expect(() =>
|
|
147
|
+
selectClaudeReleaseCandidate({
|
|
148
|
+
githubReleases: [github("1.2.0", 2)],
|
|
149
|
+
npmMetadata: npm(),
|
|
150
|
+
previousVersion: "1.0.0",
|
|
151
|
+
currentVersion: "1.1.0",
|
|
152
|
+
}),
|
|
153
|
+
).toThrow(ClaudeReleaseSourceDisagreementError);
|
|
154
|
+
|
|
155
|
+
const candidate = selectClaudeReleaseCandidate({
|
|
156
|
+
githubReleases: [github("1.2.0", 2)],
|
|
157
|
+
npmMetadata: npm(),
|
|
158
|
+
previousVersion: "1.0.0",
|
|
159
|
+
currentVersion: "1.1.0",
|
|
160
|
+
allowNpmOnlyExactVersions: true,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(candidate?.version).toBe("1.1.0");
|
|
164
|
+
expect(candidate?.release_url).toBe(
|
|
165
|
+
"https://github.com/anthropics/claude-code/releases",
|
|
166
|
+
);
|
|
167
|
+
expect(candidate?.release_notes_md).toContain("validation-only npm replay");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("returns null after the latest terminal version", () => {
|
|
171
|
+
expect(
|
|
172
|
+
selectClaudeReleaseCandidate({
|
|
173
|
+
githubReleases: releases,
|
|
174
|
+
npmMetadata: npm(),
|
|
175
|
+
processedPackageVersions: ["1.0.0", "1.1.0", "1.2.0"],
|
|
176
|
+
}),
|
|
177
|
+
).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
});
|