@letta-ai/letta-code 0.30.32 → 0.31.0
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-agent-presets.js +11 -0
- package/dist/agent-presets-agent-presets.js.map +9 -0
- package/dist/agent-presets-personality-asset-content.js +42 -0
- package/dist/agent-presets-personality-asset-content.js.map +10 -0
- package/dist/agent-presets.js +17 -2
- package/dist/agent-presets.js.map +3 -3
- package/dist/channels-public.js +16 -1
- package/dist/channels-public.js.map +5 -4
- package/dist/gateway-core.js +5 -4
- package/dist/gateway-core.js.map +3 -3
- package/dist/mcp-client.js +2 -2
- package/dist/mcp-client.js.map +1 -1
- package/dist/types/agent/create-agent-request.d.ts +3 -0
- package/dist/types/agent/create-agent-request.d.ts.map +1 -1
- package/dist/types/agent/model.d.ts.map +1 -1
- package/dist/types/agent/personality-asset-content.d.ts +3 -0
- package/dist/types/agent/personality-asset-content.d.ts.map +1 -0
- package/dist/types/agent/skills.d.ts.map +1 -1
- package/dist/types/channels/gateway-core.d.ts.map +1 -1
- package/dist/types/channels/route-thread-key.d.ts +20 -0
- package/dist/types/channels/route-thread-key.d.ts.map +1 -0
- package/dist/types/channels-public.d.ts +1 -0
- package/dist/types/channels-public.d.ts.map +1 -1
- package/dist/types/tools/impl/skill.d.ts.map +1 -1
- package/dist/types/types/protocol_v2.d.ts +3 -144
- package/dist/types/types/protocol_v2.d.ts.map +1 -1
- package/dist/types/types/schedule-protocol.d.ts +151 -1
- package/dist/types/types/schedule-protocol.d.ts.map +1 -1
- package/dist/types/utils/frontmatter.d.ts.map +1 -1
- package/dist/types/websocket/listener/cwd-change.d.ts.map +1 -1
- package/letta.js +238 -86
- package/package.json +4 -2
- package/scripts/builtin-skills-watch/agent-watch.test.ts +104 -0
- package/scripts/builtin-skills-watch/agent-watch.ts +337 -0
- package/scripts/builtin-skills-watch/aggregate-results.test.ts +79 -0
- package/scripts/builtin-skills-watch/aggregate-results.ts +326 -0
- package/scripts/builtin-skills-watch/analysis.test.ts +70 -0
- package/scripts/builtin-skills-watch/analysis.ts +228 -0
- package/scripts/builtin-skills-watch/evidence.test.ts +114 -0
- package/scripts/builtin-skills-watch/evidence.ts +145 -0
- package/scripts/builtin-skills-watch/github.ts +161 -0
- package/scripts/builtin-skills-watch/tracker.test.ts +249 -0
- package/scripts/builtin-skills-watch/tracker.ts +506 -0
- package/scripts/builtin-skills-watch/update-tracker.test.ts +234 -0
- package/scripts/builtin-skills-watch/update-tracker.ts +508 -0
- package/scripts/claude-watch/release-source.test.ts +16 -4
- package/scripts/claude-watch/release-source.ts +41 -2
- package/scripts/codex-watch/agent-watch.ts +8 -5
- package/scripts/codex-watch/release-analysis.test.ts +18 -16
- package/scripts/codex-watch/release-analysis.ts +31 -9
- package/scripts/codex-watch/tracker.test.ts +5 -5
- package/scripts/codex-watch/tracker.ts +7 -8
- package/scripts/pi-ai-watch/agent-watch.ts +9 -14
- package/scripts/pi-ai-watch/release-analysis.test.ts +24 -18
- package/scripts/pi-ai-watch/release-analysis.ts +85 -45
- package/scripts/pi-ai-watch/tracker.test.ts +25 -18
- package/scripts/pi-ai-watch/tracker.ts +7 -19
- package/scripts/pi-ai-watch/update-tracker.ts +2 -2
- package/scripts/source-file-size-baseline.json +2 -2
- package/skills/dispatching-coding-agents/SKILL.md +16 -7
- package/skills/syncing-memory-filesystem/SKILL.md +118 -226
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
findNextStableRelease,
|
|
2
|
+
findLatestStableReleaseAfter,
|
|
4
3
|
type Release,
|
|
4
|
+
releaseNotesForRange,
|
|
5
5
|
} from "./release-analysis.ts";
|
|
6
6
|
|
|
7
7
|
function release(tag: string, publishedAt: string): Release {
|
|
@@ -10,7 +10,7 @@ function release(tag: string, publishedAt: string): Release {
|
|
|
10
10
|
draft: false,
|
|
11
11
|
prerelease: false,
|
|
12
12
|
html_url: `https://github.com/openai/codex/releases/tag/${tag}`,
|
|
13
|
-
body:
|
|
13
|
+
body: `Notes for ${tag}`,
|
|
14
14
|
published_at: publishedAt,
|
|
15
15
|
};
|
|
16
16
|
}
|
|
@@ -21,28 +21,30 @@ const STABLES = [
|
|
|
21
21
|
release("rust-v0.3.0", "2026-08-03T00:00:00Z"),
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
-
describe("Codex stable release
|
|
25
|
-
test("selects the
|
|
26
|
-
expect(
|
|
27
|
-
"rust-v0.
|
|
24
|
+
describe("Codex stable release range", () => {
|
|
25
|
+
test("selects the latest release after the durable cursor", () => {
|
|
26
|
+
expect(findLatestStableReleaseAfter(STABLES, "rust-v0.1.0")?.tag_name).toBe(
|
|
27
|
+
"rust-v0.3.0",
|
|
28
28
|
);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test("
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
).
|
|
35
|
-
expect(
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
test("includes release notes for the full cursor-to-latest range", () => {
|
|
32
|
+
const notes = releaseNotesForRange(STABLES, "rust-v0.1.0", "rust-v0.3.0");
|
|
33
|
+
expect(notes).toContain("## [rust-v0.2.0]");
|
|
34
|
+
expect(notes).toContain("Notes for rust-v0.2.0");
|
|
35
|
+
expect(notes).toContain("## [rust-v0.3.0]");
|
|
36
|
+
expect(notes).not.toContain("Notes for rust-v0.1.0");
|
|
37
|
+
expect(() =>
|
|
38
|
+
releaseNotesForRange(STABLES, "rust-v0.3.0", "rust-v0.2.0"),
|
|
39
|
+
).toThrow("Previous release rust-v0.3.0 must precede rust-v0.2.0");
|
|
38
40
|
});
|
|
39
41
|
|
|
40
42
|
test("returns null when the durable cursor is current", () => {
|
|
41
|
-
expect(
|
|
43
|
+
expect(findLatestStableReleaseAfter(STABLES, "rust-v0.3.0")).toBeNull();
|
|
42
44
|
});
|
|
43
45
|
|
|
44
46
|
test("fails instead of skipping an unavailable cursor", () => {
|
|
45
|
-
expect(() =>
|
|
47
|
+
expect(() => findLatestStableReleaseAfter(STABLES, "rust-v0.0.9")).toThrow(
|
|
46
48
|
"Could not find terminal release rust-v0.0.9",
|
|
47
49
|
);
|
|
48
50
|
});
|
|
@@ -45,7 +45,7 @@ export interface PathChangeSummary {
|
|
|
45
45
|
export interface CodexWatchAnalysis {
|
|
46
46
|
previous_tag: string;
|
|
47
47
|
current_tag: string;
|
|
48
|
-
|
|
48
|
+
is_latest_release: boolean;
|
|
49
49
|
release_url: string;
|
|
50
50
|
release_notes_md: string;
|
|
51
51
|
verdict: Verdict;
|
|
@@ -139,13 +139,13 @@ export async function analyzeCodexRelease(
|
|
|
139
139
|
return {
|
|
140
140
|
previous_tag: previous.tag_name,
|
|
141
141
|
current_tag: current.tag_name,
|
|
142
|
-
|
|
142
|
+
is_latest_release: current.tag_name === stables.at(-1)?.tag_name,
|
|
143
|
+
release_url: current.html_url,
|
|
144
|
+
release_notes_md: releaseNotesForRange(
|
|
143
145
|
stables,
|
|
144
146
|
previous.tag_name,
|
|
145
147
|
current.tag_name,
|
|
146
148
|
),
|
|
147
|
-
release_url: current.html_url,
|
|
148
|
-
release_notes_md: current.body ?? "",
|
|
149
149
|
verdict,
|
|
150
150
|
models_diff: modelsDiff,
|
|
151
151
|
prompt_md_changed: promptMdChanged,
|
|
@@ -207,15 +207,37 @@ function findPreviousStable(
|
|
|
207
207
|
return stables[idx - 1] ?? null;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
export function
|
|
210
|
+
export function releaseNotesForRange(
|
|
211
211
|
stables: Release[],
|
|
212
212
|
previousTag: string,
|
|
213
213
|
currentTag: string,
|
|
214
|
-
):
|
|
215
|
-
|
|
214
|
+
): string {
|
|
215
|
+
const currentIndex = stables.findIndex(
|
|
216
|
+
(release) => release.tag_name === currentTag,
|
|
217
|
+
);
|
|
218
|
+
if (currentIndex < 0) {
|
|
219
|
+
throw new Error(`Could not find current release ${currentTag}`);
|
|
220
|
+
}
|
|
221
|
+
const previousIndex = stables.findIndex(
|
|
222
|
+
(release) => release.tag_name === previousTag,
|
|
223
|
+
);
|
|
224
|
+
if (previousIndex >= currentIndex) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Previous release ${previousTag} must precede ${currentTag}`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
const firstIncludedIndex =
|
|
230
|
+
previousIndex < 0 ? currentIndex : previousIndex + 1;
|
|
231
|
+
return stables
|
|
232
|
+
.slice(firstIncludedIndex, currentIndex + 1)
|
|
233
|
+
.map(
|
|
234
|
+
(release) =>
|
|
235
|
+
`## [${release.tag_name}](${release.html_url})\n\n${release.body?.trim() || "_No release notes._"}`,
|
|
236
|
+
)
|
|
237
|
+
.join("\n\n");
|
|
216
238
|
}
|
|
217
239
|
|
|
218
|
-
export function
|
|
240
|
+
export function findLatestStableReleaseAfter(
|
|
219
241
|
stables: Release[],
|
|
220
242
|
terminalTag: string,
|
|
221
243
|
): Release | null {
|
|
@@ -225,7 +247,7 @@ export function findNextStableRelease(
|
|
|
225
247
|
if (terminalIndex < 0) {
|
|
226
248
|
throw new Error(`Could not find terminal release ${terminalTag}`);
|
|
227
249
|
}
|
|
228
|
-
return
|
|
250
|
+
return terminalIndex === stables.length - 1 ? null : (stables.at(-1) ?? null);
|
|
229
251
|
}
|
|
230
252
|
|
|
231
253
|
function cloneCodex(tmp: string): string {
|
|
@@ -21,7 +21,7 @@ function analysis(
|
|
|
21
21
|
return {
|
|
22
22
|
previous_tag: "rust-v0.1.0",
|
|
23
23
|
current_tag: tag,
|
|
24
|
-
|
|
24
|
+
is_latest_release: true,
|
|
25
25
|
release_url: `https://github.com/openai/codex/releases/tag/${tag}`,
|
|
26
26
|
release_notes_md: "",
|
|
27
27
|
verdict,
|
|
@@ -166,7 +166,7 @@ describe("tracker state", () => {
|
|
|
166
166
|
test("fails closed on a cumulative legacy replay", () => {
|
|
167
167
|
const cumulative = {
|
|
168
168
|
...analysis("rust-v0.5.0", "tool-surface review needed"),
|
|
169
|
-
|
|
169
|
+
is_latest_release: false,
|
|
170
170
|
};
|
|
171
171
|
const state = recordAnalysis(emptyTrackerState(), {
|
|
172
172
|
analysis: cumulative,
|
|
@@ -250,14 +250,14 @@ describe("tracker state", () => {
|
|
|
250
250
|
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.2.0");
|
|
251
251
|
});
|
|
252
252
|
|
|
253
|
-
test("does not advance for an explicit non-
|
|
253
|
+
test("does not advance for an explicit non-latest range", () => {
|
|
254
254
|
const initial = upsertTrackerEntry(
|
|
255
255
|
emptyTrackerState(),
|
|
256
256
|
entry(1, "no_local_impact"),
|
|
257
257
|
);
|
|
258
258
|
const cumulative = {
|
|
259
259
|
...analysis("rust-v0.5.0", "tool-surface review needed"),
|
|
260
|
-
|
|
260
|
+
is_latest_release: false,
|
|
261
261
|
};
|
|
262
262
|
const state = recordAnalysis(initial, {
|
|
263
263
|
analysis: cumulative,
|
|
@@ -273,7 +273,7 @@ describe("tracker state", () => {
|
|
|
273
273
|
).toBe("rust-v0.1.0");
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
-
test("advances across an already audited
|
|
276
|
+
test("advances across an already audited latest range", () => {
|
|
277
277
|
let state = upsertTrackerEntry(
|
|
278
278
|
emptyTrackerState(),
|
|
279
279
|
entry(1, "no_local_impact"),
|
|
@@ -119,10 +119,9 @@ export function advanceCodexAuditCursor(
|
|
|
119
119
|
state: TrackerState,
|
|
120
120
|
previousTag: string,
|
|
121
121
|
currentTag: string,
|
|
122
|
-
|
|
122
|
+
isLatestRelease: boolean,
|
|
123
123
|
): TrackerState {
|
|
124
|
-
if (!
|
|
125
|
-
return state;
|
|
124
|
+
if (!isLatestRelease || state.audit_cursor_tag !== previousTag) return state;
|
|
126
125
|
if (!hasProcessedRange(state, previousTag, currentTag)) {
|
|
127
126
|
throw new Error(
|
|
128
127
|
`Cannot advance Codex audit cursor without terminal ${previousTag}...${currentTag}`,
|
|
@@ -149,26 +148,26 @@ export function recordAnalysis(
|
|
|
149
148
|
compare_url: options.analysis.compare_url,
|
|
150
149
|
workflow_run_url: options.analysis.workflow_run_url,
|
|
151
150
|
},
|
|
152
|
-
options.analysis.
|
|
151
|
+
options.analysis.is_latest_release,
|
|
153
152
|
);
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
export function upsertTrackerEntry(
|
|
157
156
|
state: TrackerState,
|
|
158
157
|
entry: TrackerEntry,
|
|
159
|
-
|
|
158
|
+
isLatestRelease = true,
|
|
160
159
|
): TrackerState {
|
|
161
160
|
let auditCursorTag = state.audit_cursor_tag;
|
|
162
|
-
if (auditCursorTag === null && !
|
|
161
|
+
if (auditCursorTag === null && !isLatestRelease) {
|
|
163
162
|
auditCursorTag = entry.previous_tag;
|
|
164
163
|
} else if (
|
|
165
|
-
|
|
164
|
+
isLatestRelease &&
|
|
166
165
|
entry.outcome === "error" &&
|
|
167
166
|
auditCursorTag === null
|
|
168
167
|
) {
|
|
169
168
|
auditCursorTag = entry.previous_tag;
|
|
170
169
|
} else if (
|
|
171
|
-
|
|
170
|
+
isLatestRelease &&
|
|
172
171
|
isTerminalOutcome(entry.outcome) &&
|
|
173
172
|
(auditCursorTag === null || entry.previous_tag === auditCursorTag)
|
|
174
173
|
) {
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { appendFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
import {
|
|
5
5
|
createIssueWithBody,
|
|
6
|
-
editIssueBody,
|
|
7
6
|
ensureLabels,
|
|
8
7
|
findIssueByExactTitle,
|
|
9
8
|
getIssueBody,
|
|
@@ -13,12 +12,11 @@ import {
|
|
|
13
12
|
analyzePiAiRelease,
|
|
14
13
|
compareStableVersions,
|
|
15
14
|
DEFAULT_TARGET_REPO,
|
|
16
|
-
|
|
15
|
+
findLatestStableReleaseAfter,
|
|
17
16
|
listStableReleases,
|
|
18
17
|
readInstalledVersion,
|
|
19
18
|
} from "./release-analysis.ts";
|
|
20
19
|
import {
|
|
21
|
-
advanceMergedPr,
|
|
22
20
|
getPendingPrForCursor,
|
|
23
21
|
hasCompletedRange,
|
|
24
22
|
initialTrackerState,
|
|
@@ -82,7 +80,7 @@ async function main(): Promise<void> {
|
|
|
82
80
|
const args = parseArgs(process.argv.slice(2));
|
|
83
81
|
const installedVersion = readInstalledVersion();
|
|
84
82
|
const tracker = ensureTrackerIssue(args, installedVersion);
|
|
85
|
-
|
|
83
|
+
const state = parseTrackerState(tracker.body);
|
|
86
84
|
const releases = await listStableReleases();
|
|
87
85
|
let previousVersion = args.previousVersion;
|
|
88
86
|
let currentVersion = args.currentVersion;
|
|
@@ -109,19 +107,16 @@ async function main(): Promise<void> {
|
|
|
109
107
|
`Merged pi-ai PR ${status.url} targets ${pending.version}, but main still declares ${installedVersion}`,
|
|
110
108
|
);
|
|
111
109
|
}
|
|
112
|
-
state = advanceMergedPr(state, pending.version);
|
|
113
|
-
if (!args.dryRun) {
|
|
114
|
-
editIssueBody(args.repo, tracker.number, renderTrackerBody(state));
|
|
115
|
-
}
|
|
116
110
|
} else {
|
|
117
|
-
console.log(
|
|
118
|
-
`Retrying ${pending.version}; prior PR was closed unmerged.`,
|
|
119
|
-
);
|
|
111
|
+
console.log(`Prior pi-ai PR ${status.url} was closed unmerged.`);
|
|
120
112
|
}
|
|
121
113
|
}
|
|
122
114
|
|
|
123
|
-
const
|
|
124
|
-
|
|
115
|
+
const latest = findLatestStableReleaseAfter(
|
|
116
|
+
releases,
|
|
117
|
+
state.audit_cursor_version,
|
|
118
|
+
);
|
|
119
|
+
if (!latest) {
|
|
125
120
|
console.log(
|
|
126
121
|
`No stable pi-ai release after ${state.audit_cursor_version}.`,
|
|
127
122
|
);
|
|
@@ -134,7 +129,7 @@ async function main(): Promise<void> {
|
|
|
134
129
|
return;
|
|
135
130
|
}
|
|
136
131
|
previousVersion = state.audit_cursor_version;
|
|
137
|
-
currentVersion =
|
|
132
|
+
currentVersion = latest.version;
|
|
138
133
|
}
|
|
139
134
|
|
|
140
135
|
const analysis = await analyzePiAiRelease({
|
|
@@ -3,10 +3,9 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import {
|
|
6
|
-
areAdjacentStableReleases,
|
|
7
6
|
compareStableVersions,
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
extractChangelogRange,
|
|
8
|
+
findLatestStableReleaseAfter,
|
|
10
9
|
type PackageRelease,
|
|
11
10
|
parseRegistryMetadata,
|
|
12
11
|
readInstalledVersion,
|
|
@@ -59,24 +58,20 @@ describe("pi-ai npm releases", () => {
|
|
|
59
58
|
).toThrow("missing publication time for 0.82.1");
|
|
60
59
|
});
|
|
61
60
|
|
|
62
|
-
test("selects
|
|
61
|
+
test("selects the latest stable version after the cursor", () => {
|
|
63
62
|
const releases = releaseList("0.82.1", "0.83.0", "0.84.0");
|
|
64
|
-
expect(
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
expect(findLatestStableReleaseAfter(releases, "0.82.1")?.version).toBe(
|
|
64
|
+
"0.84.0",
|
|
65
|
+
);
|
|
66
|
+
expect(findLatestStableReleaseAfter(releases, "0.84.0")).toBeNull();
|
|
67
|
+
expect(() => findLatestStableReleaseAfter(releases, "0.80.0")).toThrow(
|
|
67
68
|
"Could not find pi-ai cursor release 0.80.0",
|
|
68
69
|
);
|
|
69
70
|
});
|
|
70
|
-
|
|
71
|
-
test("recognizes only adjacent release pairs", () => {
|
|
72
|
-
const releases = releaseList("0.82.1", "0.83.0", "0.84.0");
|
|
73
|
-
expect(areAdjacentStableReleases(releases, "0.82.1", "0.83.0")).toBe(true);
|
|
74
|
-
expect(areAdjacentStableReleases(releases, "0.82.1", "0.84.0")).toBe(false);
|
|
75
|
-
});
|
|
76
71
|
});
|
|
77
72
|
|
|
78
73
|
describe("pi-ai release evidence", () => {
|
|
79
|
-
test("extracts
|
|
74
|
+
test("extracts the cumulative changelog after the cursor", () => {
|
|
80
75
|
const changelog = `# Changelog
|
|
81
76
|
|
|
82
77
|
## [0.84.0] - 2026-08-06
|
|
@@ -88,16 +83,27 @@ describe("pi-ai release evidence", () => {
|
|
|
88
83
|
## [0.83.0] - 2026-07-29
|
|
89
84
|
|
|
90
85
|
- Previous feature.
|
|
86
|
+
|
|
87
|
+
## [0.82.1] - 2026-07-20
|
|
88
|
+
|
|
89
|
+
- Cursor release.
|
|
91
90
|
`;
|
|
92
91
|
expect(
|
|
93
|
-
|
|
92
|
+
extractChangelogRange(changelog, "0.82.1", "0.84.0"),
|
|
94
93
|
).toBe(`## [0.84.0] - 2026-08-06
|
|
95
94
|
|
|
96
95
|
### Added
|
|
97
96
|
|
|
98
|
-
- Current feature
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
- Current feature.
|
|
98
|
+
|
|
99
|
+
## [0.83.0] - 2026-07-29
|
|
100
|
+
|
|
101
|
+
- Previous feature.`);
|
|
102
|
+
expect(() => extractChangelogRange(changelog, "0.80.0", "0.84.0")).toThrow(
|
|
103
|
+
"Could not find pi-ai changelog section 0.80.0",
|
|
104
|
+
);
|
|
105
|
+
expect(() => extractChangelogRange(changelog, "0.82.1", "0.85.0")).toThrow(
|
|
106
|
+
"Could not find pi-ai changelog section 0.85.0",
|
|
101
107
|
);
|
|
102
108
|
});
|
|
103
109
|
|
|
@@ -42,11 +42,14 @@ export interface PiAiWatchAnalysis {
|
|
|
42
42
|
installed_version: string;
|
|
43
43
|
previous_version: string;
|
|
44
44
|
current_version: string;
|
|
45
|
-
|
|
45
|
+
is_latest_release: boolean;
|
|
46
46
|
published_at: string;
|
|
47
47
|
integrity: string | null;
|
|
48
48
|
tarball_url: string | null;
|
|
49
|
+
previous_git_head: string | null;
|
|
49
50
|
git_head: string | null;
|
|
51
|
+
previous_tag_commit: string;
|
|
52
|
+
current_tag_commit: string;
|
|
50
53
|
release_url: string;
|
|
51
54
|
compare_url: string;
|
|
52
55
|
changelog_md: string;
|
|
@@ -79,6 +82,11 @@ export async function analyzePiAiRelease(
|
|
|
79
82
|
`Could not find previous pi-ai release before ${current.version}`,
|
|
80
83
|
);
|
|
81
84
|
}
|
|
85
|
+
if (compareStableVersions(previous.version, current.version) >= 0) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Previous pi-ai release ${previous.version} must precede ${current.version}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
82
90
|
|
|
83
91
|
const installedVersion = options.installedVersion ?? readInstalledVersion();
|
|
84
92
|
const previousTag = `v${previous.version}`;
|
|
@@ -86,11 +94,13 @@ export async function analyzePiAiRelease(
|
|
|
86
94
|
const temp = mkdtempSync(join(tmpdir(), "pi-ai-watch-"));
|
|
87
95
|
try {
|
|
88
96
|
const repoDir = clonePi(temp);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
const previousSource = resolveReleaseSource(repoDir, previousTag, previous);
|
|
98
|
+
const currentSource = resolveReleaseSource(repoDir, currentTag, current);
|
|
99
|
+
const changelog = showFile(
|
|
100
|
+
repoDir,
|
|
101
|
+
currentSource.sourceCommit,
|
|
102
|
+
"packages/ai/CHANGELOG.md",
|
|
103
|
+
);
|
|
94
104
|
if (changelog === null) {
|
|
95
105
|
throw new Error(`packages/ai/CHANGELOG.md is missing at ${currentTag}`);
|
|
96
106
|
}
|
|
@@ -100,24 +110,35 @@ export async function analyzePiAiRelease(
|
|
|
100
110
|
installed_version: installedVersion,
|
|
101
111
|
previous_version: previous.version,
|
|
102
112
|
current_version: current.version,
|
|
103
|
-
|
|
104
|
-
releases,
|
|
105
|
-
previous.version,
|
|
106
|
-
current.version,
|
|
107
|
-
),
|
|
113
|
+
is_latest_release: current.version === releases.at(-1)?.version,
|
|
108
114
|
published_at: current.published_at,
|
|
109
115
|
integrity: current.integrity,
|
|
110
116
|
tarball_url: current.tarball_url,
|
|
117
|
+
previous_git_head: previous.git_head,
|
|
111
118
|
git_head: current.git_head,
|
|
119
|
+
previous_tag_commit: previousSource.tagCommit,
|
|
120
|
+
current_tag_commit: currentSource.tagCommit,
|
|
112
121
|
release_url: `https://github.com/${PI_AI_REPO}/releases/tag/${currentTag}`,
|
|
113
|
-
compare_url: `https://github.com/${PI_AI_REPO}/compare/${
|
|
114
|
-
changelog_md:
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
compare_url: `https://github.com/${PI_AI_REPO}/compare/${previousSource.sourceCommit}...${currentSource.sourceCommit}`,
|
|
123
|
+
changelog_md: extractChangelogRange(
|
|
124
|
+
changelog,
|
|
125
|
+
previous.version,
|
|
126
|
+
current.version,
|
|
127
|
+
),
|
|
128
|
+
changed_files: changedFiles(
|
|
129
|
+
repoDir,
|
|
130
|
+
previousSource.sourceCommit,
|
|
131
|
+
currentSource.sourceCommit,
|
|
132
|
+
),
|
|
133
|
+
diff_stat: diffStat(
|
|
134
|
+
repoDir,
|
|
135
|
+
previousSource.sourceCommit,
|
|
136
|
+
currentSource.sourceCommit,
|
|
137
|
+
),
|
|
117
138
|
package_json_diff: diffPreview(
|
|
118
139
|
repoDir,
|
|
119
|
-
|
|
120
|
-
|
|
140
|
+
previousSource.sourceCommit,
|
|
141
|
+
currentSource.sourceCommit,
|
|
121
142
|
"packages/ai/package.json",
|
|
122
143
|
),
|
|
123
144
|
workflow_run_url: workflowRunUrl(),
|
|
@@ -182,7 +203,7 @@ export function readInstalledVersion(lockfilePath = "bun.lock"): string {
|
|
|
182
203
|
return match[1];
|
|
183
204
|
}
|
|
184
205
|
|
|
185
|
-
export function
|
|
206
|
+
export function findLatestStableReleaseAfter(
|
|
186
207
|
releases: PackageRelease[],
|
|
187
208
|
cursorVersion: string,
|
|
188
209
|
): PackageRelease | null {
|
|
@@ -192,33 +213,33 @@ export function findNextStableRelease(
|
|
|
192
213
|
if (index < 0) {
|
|
193
214
|
throw new Error(`Could not find pi-ai cursor release ${cursorVersion}`);
|
|
194
215
|
}
|
|
195
|
-
return
|
|
216
|
+
return index === releases.length - 1 ? null : (releases.at(-1) ?? null);
|
|
196
217
|
}
|
|
197
218
|
|
|
198
|
-
export function
|
|
199
|
-
|
|
219
|
+
export function extractChangelogRange(
|
|
220
|
+
changelog: string,
|
|
200
221
|
previousVersion: string,
|
|
201
222
|
currentVersion: string,
|
|
202
|
-
): boolean {
|
|
203
|
-
return (
|
|
204
|
-
findPreviousStableRelease(releases, currentVersion)?.version ===
|
|
205
|
-
previousVersion
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export function extractChangelogSection(
|
|
210
|
-
changelog: string,
|
|
211
|
-
version: string,
|
|
212
223
|
): string {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
224
|
+
const currentHeading = new RegExp(
|
|
225
|
+
`^## \\[${escapeRegExp(currentVersion)}\\].*$`,
|
|
226
|
+
"m",
|
|
227
|
+
).exec(changelog);
|
|
228
|
+
if (!currentHeading) {
|
|
229
|
+
throw new Error(`Could not find pi-ai changelog section ${currentVersion}`);
|
|
230
|
+
}
|
|
231
|
+
const previousHeading = new RegExp(
|
|
232
|
+
`^## \\[${escapeRegExp(previousVersion)}\\].*$`,
|
|
233
|
+
"m",
|
|
234
|
+
).exec(changelog.slice(currentHeading.index + currentHeading[0].length));
|
|
235
|
+
if (!previousHeading) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Could not find pi-ai changelog section ${previousVersion}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const end =
|
|
241
|
+
currentHeading.index + currentHeading[0].length + previousHeading.index;
|
|
242
|
+
return changelog.slice(currentHeading.index, end).trim();
|
|
222
243
|
}
|
|
223
244
|
|
|
224
245
|
function findPreviousStableRelease(
|
|
@@ -276,18 +297,37 @@ function fetchTag(repoDir: string, tag: string): void {
|
|
|
276
297
|
);
|
|
277
298
|
}
|
|
278
299
|
|
|
279
|
-
function
|
|
300
|
+
function resolveReleaseSource(
|
|
280
301
|
repoDir: string,
|
|
281
302
|
tag: string,
|
|
282
303
|
release: PackageRelease,
|
|
283
|
-
):
|
|
284
|
-
|
|
304
|
+
): { sourceCommit: string; tagCommit: string } {
|
|
305
|
+
fetchTag(repoDir, tag);
|
|
285
306
|
const tagCommit = git(["rev-list", "-n", "1", tag], repoDir).trim();
|
|
286
|
-
|
|
307
|
+
const sourceCommit = release.git_head ?? tagCommit;
|
|
308
|
+
if (sourceCommit !== tagCommit) {
|
|
309
|
+
fetchCommit(repoDir, sourceCommit);
|
|
310
|
+
}
|
|
311
|
+
const packageJson = showFile(
|
|
312
|
+
repoDir,
|
|
313
|
+
sourceCommit,
|
|
314
|
+
"packages/ai/package.json",
|
|
315
|
+
);
|
|
316
|
+
if (packageJson === null) {
|
|
317
|
+
throw new Error(`packages/ai/package.json is missing at ${sourceCommit}`);
|
|
318
|
+
}
|
|
319
|
+
const sourceVersion = (JSON.parse(packageJson) as { version?: unknown })
|
|
320
|
+
.version;
|
|
321
|
+
if (sourceVersion !== release.version) {
|
|
287
322
|
throw new Error(
|
|
288
|
-
`pi-ai npm ${release.version}
|
|
323
|
+
`pi-ai npm ${release.version} source ${sourceCommit} declares version ${String(sourceVersion)}`,
|
|
289
324
|
);
|
|
290
325
|
}
|
|
326
|
+
return { sourceCommit, tagCommit };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function fetchCommit(repoDir: string, commit: string): void {
|
|
330
|
+
git(["fetch", "--filter=blob:none", "origin", commit], repoDir);
|
|
291
331
|
}
|
|
292
332
|
|
|
293
333
|
function showFile(repoDir: string, tag: string, path: string): string | null {
|