@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.
Files changed (61) hide show
  1. package/dist/agent-presets-agent-presets.js +11 -0
  2. package/dist/agent-presets-agent-presets.js.map +9 -0
  3. package/dist/agent-presets-personality-asset-content.js +42 -0
  4. package/dist/agent-presets-personality-asset-content.js.map +10 -0
  5. package/dist/agent-presets.js +17 -2
  6. package/dist/agent-presets.js.map +3 -3
  7. package/dist/channels-public.js +16 -1
  8. package/dist/channels-public.js.map +5 -4
  9. package/dist/gateway-core.js +5 -4
  10. package/dist/gateway-core.js.map +3 -3
  11. package/dist/mcp-client.js +2 -2
  12. package/dist/mcp-client.js.map +1 -1
  13. package/dist/types/agent/create-agent-request.d.ts +3 -0
  14. package/dist/types/agent/create-agent-request.d.ts.map +1 -1
  15. package/dist/types/agent/model.d.ts.map +1 -1
  16. package/dist/types/agent/personality-asset-content.d.ts +3 -0
  17. package/dist/types/agent/personality-asset-content.d.ts.map +1 -0
  18. package/dist/types/agent/skills.d.ts.map +1 -1
  19. package/dist/types/channels/gateway-core.d.ts.map +1 -1
  20. package/dist/types/channels/route-thread-key.d.ts +20 -0
  21. package/dist/types/channels/route-thread-key.d.ts.map +1 -0
  22. package/dist/types/channels-public.d.ts +1 -0
  23. package/dist/types/channels-public.d.ts.map +1 -1
  24. package/dist/types/tools/impl/skill.d.ts.map +1 -1
  25. package/dist/types/types/protocol_v2.d.ts +3 -144
  26. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  27. package/dist/types/types/schedule-protocol.d.ts +151 -1
  28. package/dist/types/types/schedule-protocol.d.ts.map +1 -1
  29. package/dist/types/utils/frontmatter.d.ts.map +1 -1
  30. package/dist/types/websocket/listener/cwd-change.d.ts.map +1 -1
  31. package/letta.js +238 -86
  32. package/package.json +4 -2
  33. package/scripts/builtin-skills-watch/agent-watch.test.ts +104 -0
  34. package/scripts/builtin-skills-watch/agent-watch.ts +337 -0
  35. package/scripts/builtin-skills-watch/aggregate-results.test.ts +79 -0
  36. package/scripts/builtin-skills-watch/aggregate-results.ts +326 -0
  37. package/scripts/builtin-skills-watch/analysis.test.ts +70 -0
  38. package/scripts/builtin-skills-watch/analysis.ts +228 -0
  39. package/scripts/builtin-skills-watch/evidence.test.ts +114 -0
  40. package/scripts/builtin-skills-watch/evidence.ts +145 -0
  41. package/scripts/builtin-skills-watch/github.ts +161 -0
  42. package/scripts/builtin-skills-watch/tracker.test.ts +249 -0
  43. package/scripts/builtin-skills-watch/tracker.ts +506 -0
  44. package/scripts/builtin-skills-watch/update-tracker.test.ts +234 -0
  45. package/scripts/builtin-skills-watch/update-tracker.ts +508 -0
  46. package/scripts/claude-watch/release-source.test.ts +16 -4
  47. package/scripts/claude-watch/release-source.ts +41 -2
  48. package/scripts/codex-watch/agent-watch.ts +8 -5
  49. package/scripts/codex-watch/release-analysis.test.ts +18 -16
  50. package/scripts/codex-watch/release-analysis.ts +31 -9
  51. package/scripts/codex-watch/tracker.test.ts +5 -5
  52. package/scripts/codex-watch/tracker.ts +7 -8
  53. package/scripts/pi-ai-watch/agent-watch.ts +9 -14
  54. package/scripts/pi-ai-watch/release-analysis.test.ts +24 -18
  55. package/scripts/pi-ai-watch/release-analysis.ts +85 -45
  56. package/scripts/pi-ai-watch/tracker.test.ts +25 -18
  57. package/scripts/pi-ai-watch/tracker.ts +7 -19
  58. package/scripts/pi-ai-watch/update-tracker.ts +2 -2
  59. package/scripts/source-file-size-baseline.json +2 -2
  60. package/skills/dispatching-coding-agents/SKILL.md +16 -7
  61. package/skills/syncing-memory-filesystem/SKILL.md +118 -226
@@ -0,0 +1,104 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { listBuiltinSkillsAtCommit } from "./analysis.ts";
6
+
7
+ const AUDIT_AT = "2026-08-26T00:00:00.000Z";
8
+
9
+ describe("built-in skills watch preparation", () => {
10
+ test("emits one candidate for every discovered bundled skill", () => {
11
+ const directory = mkdtempSync(join(tmpdir(), "builtin-skills-watch-"));
12
+ try {
13
+ const manifest = runDryPreparation(directory);
14
+ const inventory = listBuiltinSkillsAtCommit("HEAD");
15
+
16
+ expect(manifest.inventory).toEqual(inventory);
17
+ expect(manifest.candidates.map((candidate) => candidate.skill)).toEqual(
18
+ inventory,
19
+ );
20
+ expect(manifest.candidates).toHaveLength(inventory.length);
21
+ } finally {
22
+ rmSync(directory, { recursive: true, force: true });
23
+ }
24
+ });
25
+
26
+ test("manual skill selection emits only that skill", () => {
27
+ const directory = mkdtempSync(join(tmpdir(), "builtin-skills-watch-"));
28
+ try {
29
+ const manifest = runDryPreparation(directory, "creating-skills");
30
+ expect(manifest.candidates.map((candidate) => candidate.skill)).toEqual([
31
+ "creating-skills",
32
+ ]);
33
+ } finally {
34
+ rmSync(directory, { recursive: true, force: true });
35
+ }
36
+ });
37
+
38
+ test("rebuilds the previous audit passed to a matrix job", () => {
39
+ const directory = mkdtempSync(join(tmpdir(), "builtin-skills-watch-"));
40
+ const previousAudit = {
41
+ candidate_id: `creating-skills@${"a".repeat(12)}-${"b".repeat(16)}`,
42
+ audited_sha: "a".repeat(40),
43
+ skill_digest: "b".repeat(64),
44
+ audited_at: "2026-08-25T00:00:00.000Z",
45
+ };
46
+ try {
47
+ runDryPreparation(directory, "creating-skills", previousAudit);
48
+ const analysis = JSON.parse(
49
+ readFileSync(join(directory, "analyses/creating-skills.json"), "utf8"),
50
+ ) as { previous_audit: unknown };
51
+ expect(analysis.previous_audit).toEqual(previousAudit);
52
+ } finally {
53
+ rmSync(directory, { recursive: true, force: true });
54
+ }
55
+ });
56
+ });
57
+
58
+ interface Manifest {
59
+ inventory: string[];
60
+ candidates: Array<{ skill: string }>;
61
+ }
62
+
63
+ function runDryPreparation(
64
+ directory: string,
65
+ skill?: string,
66
+ previousAudit?: object,
67
+ ): Manifest {
68
+ const analysisDir = join(directory, "analyses");
69
+ const manifestFile = join(directory, "manifest.json");
70
+ const command = [
71
+ "bun",
72
+ "scripts/builtin-skills-watch/agent-watch.ts",
73
+ "--dry-run",
74
+ "--current-sha",
75
+ "HEAD",
76
+ "--audit-at",
77
+ AUDIT_AT,
78
+ "--analysis-dir",
79
+ analysisDir,
80
+ "--manifest-file",
81
+ manifestFile,
82
+ ];
83
+ if (skill) command.push("--skill", skill);
84
+ if (previousAudit) {
85
+ command.push(
86
+ "--previous-audit-base64",
87
+ Buffer.from(JSON.stringify(previousAudit)).toString("base64"),
88
+ );
89
+ }
90
+ const result = Bun.spawnSync(command, {
91
+ env: {
92
+ ...process.env,
93
+ GITHUB_SERVER_URL: "https://github.com",
94
+ GITHUB_REPOSITORY: "letta-ai/letta-code",
95
+ GITHUB_RUN_ID: "1",
96
+ },
97
+ stdout: "pipe",
98
+ stderr: "pipe",
99
+ });
100
+ if (result.exitCode !== 0) {
101
+ throw new Error(result.stderr.toString());
102
+ }
103
+ return JSON.parse(readFileSync(manifestFile, "utf8")) as Manifest;
104
+ }
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env bun
2
+ /** Prepares one candidate for every bundled skill in a daily parallel review. */
3
+
4
+ import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import {
7
+ buildAnalysis,
8
+ DEFAULT_TARGET_REPO,
9
+ listBuiltinSkillsAtCommit,
10
+ type PriorSkillAudit,
11
+ } from "./analysis.ts";
12
+ import {
13
+ createIssueWithBody,
14
+ editIssueBody,
15
+ ensureLabels,
16
+ findIssuesByExactTitle,
17
+ getIssueBody,
18
+ } from "./github.ts";
19
+ import {
20
+ emptyTrackerState,
21
+ isTerminalOutcome,
22
+ type PendingCandidate,
23
+ parseTrackerState,
24
+ renderTrackerBody,
25
+ startCandidate,
26
+ type TrackerState,
27
+ } from "./tracker.ts";
28
+
29
+ const DEFAULT_TRACKER_TITLE = "Built-in skill staleness tracker";
30
+ const DEFAULT_ANALYSIS_DIR = "builtin-skills-watch-analyses";
31
+ const DEFAULT_MANIFEST_FILE = "builtin-skills-watch-manifest.json";
32
+ const MAX_SKILLS_PER_RUN = 50;
33
+
34
+ interface Args {
35
+ dryRun: boolean;
36
+ skill: string | null;
37
+ currentSha: string | null;
38
+ auditAt: string | null;
39
+ previousAuditBase64: string | null;
40
+ repo: string;
41
+ trackerTitle: string;
42
+ analysisDir: string;
43
+ manifestFile: string;
44
+ }
45
+
46
+ interface TrackerIssue {
47
+ number: number;
48
+ url: string;
49
+ body: string;
50
+ }
51
+
52
+ interface MatrixEntry {
53
+ skill: string;
54
+ candidate_id: string;
55
+ current_sha: string;
56
+ skill_digest: string;
57
+ audit_at: string;
58
+ previous_audit_base64: string;
59
+ }
60
+
61
+ interface WatchManifest {
62
+ schema_version: 1;
63
+ tracker_issue: number;
64
+ tracker_issue_url: string;
65
+ inventory: string[];
66
+ candidates: MatrixEntry[];
67
+ }
68
+
69
+ function parseArgs(argv: string[]): Args {
70
+ const args: Args = {
71
+ dryRun: false,
72
+ skill: null,
73
+ currentSha: null,
74
+ auditAt: null,
75
+ previousAuditBase64: null,
76
+ repo: DEFAULT_TARGET_REPO,
77
+ trackerTitle: DEFAULT_TRACKER_TITLE,
78
+ analysisDir: DEFAULT_ANALYSIS_DIR,
79
+ manifestFile: DEFAULT_MANIFEST_FILE,
80
+ };
81
+ for (let index = 0; index < argv.length; index += 1) {
82
+ const arg = argv[index];
83
+ if (arg === "--dry-run") args.dryRun = true;
84
+ else if (arg === "--skill") args.skill = argv[++index] ?? null;
85
+ else if (arg === "--current-sha") {
86
+ args.currentSha = argv[++index] ?? null;
87
+ } else if (arg === "--audit-at") {
88
+ args.auditAt = argv[++index] ?? null;
89
+ } else if (arg === "--previous-audit-base64") {
90
+ args.previousAuditBase64 = argv[++index] ?? null;
91
+ } else if (arg === "--repo") args.repo = argv[++index] ?? args.repo;
92
+ else if (arg === "--tracker-title") {
93
+ args.trackerTitle = argv[++index] ?? args.trackerTitle;
94
+ } else if (arg === "--analysis-dir") {
95
+ args.analysisDir = argv[++index] ?? args.analysisDir;
96
+ } else if (arg === "--manifest-file") {
97
+ args.manifestFile = argv[++index] ?? args.manifestFile;
98
+ } else if (arg === "--help" || arg === "-h") {
99
+ console.log(
100
+ "Usage: bun scripts/builtin-skills-watch/agent-watch.ts [--dry-run] [--skill NAME] [--current-sha SHA] [--audit-at ISO] [--previous-audit-base64 BASE64] [--repo OWNER/REPO] [--tracker-title TITLE] [--analysis-dir DIR] [--manifest-file FILE]",
101
+ );
102
+ process.exit(0);
103
+ } else {
104
+ throw new Error(`Unknown argument: ${arg}`);
105
+ }
106
+ }
107
+ return args;
108
+ }
109
+
110
+ async function main(): Promise<void> {
111
+ const args = parseArgs(process.argv.slice(2));
112
+ if (!args.dryRun && !workflowRunUrl()) {
113
+ throw new Error("Scheduled watcher runs require GitHub workflow metadata");
114
+ }
115
+ const currentSha = args.currentSha ?? gitHead();
116
+ const auditAt = args.auditAt ?? new Date().toISOString();
117
+ const inventory = listBuiltinSkillsAtCommit(currentSha);
118
+ if (inventory.length === 0) {
119
+ throw new Error(`No bundled skills found at ${currentSha}`);
120
+ }
121
+ if (args.skill && !inventory.includes(args.skill)) {
122
+ throw new Error(`Unknown bundled skill: ${args.skill}`);
123
+ }
124
+
125
+ const tracker = args.dryRun ? null : ensureTrackerIssue(args, inventory);
126
+ let state = tracker ? parseTrackerState(tracker.body) : emptyTrackerState();
127
+ const selectedSkills = args.skill ? [args.skill] : inventory;
128
+ if (selectedSkills.length > MAX_SKILLS_PER_RUN) {
129
+ throw new Error(
130
+ `Bundled skill count ${selectedSkills.length} exceeds the explicit daily watcher limit of ${MAX_SKILLS_PER_RUN}`,
131
+ );
132
+ }
133
+ const candidates: MatrixEntry[] = [];
134
+ const explicitPreviousAudit = parsePreviousAudit(args.previousAuditBase64);
135
+ mkdirSync(args.analysisDir, { recursive: true });
136
+
137
+ for (const skill of selectedSkills) {
138
+ if (wasCompletedInCurrentRun(state, skill)) continue;
139
+ const pending = state.pending[skill];
140
+ const analysis = buildAnalysis({
141
+ skill,
142
+ currentSha: pending?.current_sha ?? currentSha,
143
+ auditAt: pending?.audit_at ?? auditAt,
144
+ previousAudit: explicitPreviousAudit ?? priorAudit(state, skill),
145
+ });
146
+ if (pending) {
147
+ assertPendingRebuild(pending, analysis.candidate_id);
148
+ analysis.workflow_run_url = workflowRunUrlFromId(pending.workflow_run_id);
149
+ } else if (!args.dryRun) state = startCandidate(state, analysis);
150
+
151
+ writeFileSync(
152
+ join(args.analysisDir, `${skill}.json`),
153
+ `${JSON.stringify(analysis, null, 2)}\n`,
154
+ );
155
+ candidates.push({
156
+ skill: analysis.skill,
157
+ candidate_id: analysis.candidate_id,
158
+ current_sha: analysis.current_sha,
159
+ skill_digest: analysis.skill_digest,
160
+ audit_at: analysis.audit_at,
161
+ previous_audit_base64: Buffer.from(
162
+ JSON.stringify(analysis.previous_audit),
163
+ ).toString("base64"),
164
+ });
165
+ }
166
+
167
+ if (tracker && candidates.length > 0) {
168
+ editIssueBody(
169
+ args.repo,
170
+ tracker.number,
171
+ renderTrackerBody(state, inventory),
172
+ );
173
+ }
174
+ const manifest: WatchManifest = {
175
+ schema_version: 1,
176
+ tracker_issue: tracker?.number ?? 0,
177
+ tracker_issue_url: tracker?.url ?? "",
178
+ inventory,
179
+ candidates,
180
+ };
181
+ writeFileSync(args.manifestFile, `${JSON.stringify(manifest, null, 2)}\n`);
182
+
183
+ writeOutput("tracker_issue", String(manifest.tracker_issue));
184
+ writeOutput("tracker_issue_url", manifest.tracker_issue_url);
185
+ writeOutput("analysis_dir", args.analysisDir);
186
+ writeOutput("manifest_file", args.manifestFile);
187
+ writeOutput("matrix", JSON.stringify({ include: candidates }));
188
+ writeOutput(
189
+ "should_run_agent",
190
+ !args.dryRun && candidates.length > 0 ? "true" : "false",
191
+ );
192
+
193
+ if (args.dryRun) console.log(JSON.stringify(manifest, null, 2));
194
+ else console.log(`Prepared ${candidates.length} bundled skill reviews`);
195
+ }
196
+
197
+ function parsePreviousAudit(value: string | null): PriorSkillAudit | null {
198
+ if (!value) return null;
199
+ const parsed = JSON.parse(
200
+ Buffer.from(value, "base64").toString("utf8"),
201
+ ) as PriorSkillAudit | null;
202
+ if (parsed === null) return null;
203
+ if (
204
+ typeof parsed.candidate_id !== "string" ||
205
+ typeof parsed.audited_sha !== "string" ||
206
+ typeof parsed.skill_digest !== "string" ||
207
+ typeof parsed.audited_at !== "string"
208
+ ) {
209
+ throw new Error("Previous audit input is invalid");
210
+ }
211
+ return parsed;
212
+ }
213
+
214
+ function priorAudit(
215
+ state: TrackerState,
216
+ skill: string,
217
+ ): PriorSkillAudit | null {
218
+ const previous = state.skills[skill];
219
+ return previous
220
+ ? {
221
+ candidate_id: previous.candidate_id,
222
+ audited_sha: previous.audited_sha,
223
+ skill_digest: previous.skill_digest,
224
+ audited_at: previous.audited_at,
225
+ }
226
+ : null;
227
+ }
228
+
229
+ function wasCompletedInCurrentRun(state: TrackerState, skill: string): boolean {
230
+ const runId = workflowRunUrl().split("/").at(-1);
231
+ const audit = state.skills[skill];
232
+ return Boolean(
233
+ audit &&
234
+ audit.workflow_run_id === runId &&
235
+ isTerminalOutcome(audit.outcome),
236
+ );
237
+ }
238
+
239
+ function assertPendingRebuild(
240
+ pending: PendingCandidate,
241
+ candidateId: string,
242
+ ): void {
243
+ if (pending.candidate_id !== candidateId) {
244
+ throw new Error(
245
+ `Pending candidate ${pending.candidate_id} rebuilt as ${candidateId}`,
246
+ );
247
+ }
248
+ }
249
+
250
+ function ensureTrackerIssue(args: Args, inventory: string[]): TrackerIssue {
251
+ const existing = findExistingTrackerIssue(args);
252
+ if (existing) return existing;
253
+
254
+ const body = renderTrackerBody(emptyTrackerState(), inventory);
255
+ const labels = ["builtin-skills-watch", "automation"];
256
+ ensureLabels(args.repo, labels);
257
+ const issueUrl = createIssueWithBody(
258
+ args.repo,
259
+ args.trackerTitle,
260
+ body,
261
+ labels,
262
+ );
263
+ const issueNumber = Number(issueUrl.trim().split("/").at(-1));
264
+ if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
265
+ throw new Error(`Could not parse issue number from ${issueUrl}`);
266
+ }
267
+ return { number: issueNumber, url: issueUrl, body };
268
+ }
269
+
270
+ function findExistingTrackerIssue(args: Args): TrackerIssue | null {
271
+ const matches = findIssuesByExactTitle(
272
+ args.repo,
273
+ args.trackerTitle,
274
+ "builtin-skills-watch",
275
+ );
276
+ if (matches.length > 1) {
277
+ throw new Error(
278
+ `Found multiple ${args.trackerTitle} issues with the builtin-skills-watch label`,
279
+ );
280
+ }
281
+ const existing = matches[0];
282
+ if (!existing) return null;
283
+ if (existing.state !== "OPEN") {
284
+ throw new Error(
285
+ `Tracker issue #${existing.number} is closed; reopen it before running the watcher`,
286
+ );
287
+ }
288
+ if (
289
+ existing.author.login !== "app/github-actions" &&
290
+ existing.author.login !== "github-actions[bot]"
291
+ ) {
292
+ throw new Error(
293
+ `Tracker issue #${existing.number} was created by unexpected author ${existing.author.login}`,
294
+ );
295
+ }
296
+ return {
297
+ number: existing.number,
298
+ url: `https://github.com/${args.repo}/issues/${existing.number}`,
299
+ body: getIssueBody(args.repo, existing.number),
300
+ };
301
+ }
302
+
303
+ function gitHead(): string {
304
+ const result = Bun.spawnSync(["git", "rev-parse", "HEAD"], {
305
+ stdout: "pipe",
306
+ stderr: "pipe",
307
+ });
308
+ if (result.exitCode !== 0) {
309
+ throw new Error(`git rev-parse HEAD failed:\n${result.stderr.toString()}`);
310
+ }
311
+ return result.stdout.toString().trim();
312
+ }
313
+
314
+ function writeOutput(name: string, value: string): void {
315
+ const outputPath = process.env.GITHUB_OUTPUT;
316
+ if (!outputPath) return;
317
+ appendFileSync(outputPath, `${name}=${value}\n`);
318
+ }
319
+
320
+ function workflowRunUrl(): string {
321
+ const server = process.env.GITHUB_SERVER_URL;
322
+ const repository = process.env.GITHUB_REPOSITORY;
323
+ const runId = process.env.GITHUB_RUN_ID;
324
+ return server && repository && runId
325
+ ? `${server}/${repository}/actions/runs/${runId}`
326
+ : "";
327
+ }
328
+
329
+ function workflowRunUrlFromId(runId: string): string {
330
+ return `https://github.com/letta-ai/letta-code/actions/runs/${runId}`;
331
+ }
332
+
333
+ main().catch((error) => {
334
+ console.error(error);
335
+ writeOutput("should_run_agent", "false");
336
+ process.exit(1);
337
+ });
@@ -0,0 +1,79 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ buildEvidenceCommentBatches,
4
+ type ValidatedOutcome,
5
+ } from "./aggregate-results.ts";
6
+ import type { BuiltinSkillWatchAnalysis } from "./analysis.ts";
7
+ import type { ReviewResult } from "./update-tracker.ts";
8
+
9
+ describe("daily evidence aggregation", () => {
10
+ test("keeps every evidence comment below GitHub's body limit", () => {
11
+ const outcomes = Array.from({ length: 100 }, (_, index) =>
12
+ outcome(`skill-${index}`, index),
13
+ );
14
+ const batches = buildEvidenceCommentBatches(outcomes);
15
+
16
+ expect(batches.length).toBeGreaterThan(1);
17
+ expect(batches.flatMap((batch) => batch.candidateIds)).toEqual(
18
+ outcomes.map((entry) => entry.analysis.candidate_id),
19
+ );
20
+ for (const batch of batches) {
21
+ expect(Buffer.byteLength(batch.body, "utf8")).toBeLessThan(60_000);
22
+ }
23
+ });
24
+
25
+ test("omits failed reviews that have no evidence", () => {
26
+ const failed = outcome("failed-skill", 1);
27
+ failed.result = null;
28
+ expect(buildEvidenceCommentBatches([failed])).toEqual([]);
29
+ });
30
+ });
31
+
32
+ function outcome(skill: string, index: number): ValidatedOutcome {
33
+ const candidateId = `${skill}@${"a".repeat(12)}-${index.toString(16).padStart(16, "0")}`;
34
+ const analysis: BuiltinSkillWatchAnalysis = {
35
+ schema_version: 1,
36
+ candidate_id: candidateId,
37
+ skill,
38
+ skill_path: `src/skills/builtin/${skill}`,
39
+ skill_files: [`src/skills/builtin/${skill}/SKILL.md`],
40
+ skill_digest: "b".repeat(64),
41
+ current_sha: "a".repeat(40),
42
+ audit_at: "2026-08-26T00:00:00.000Z",
43
+ previous_audit: null,
44
+ repository_changes: {
45
+ previous_sha: null,
46
+ changed_files: [],
47
+ commits: [],
48
+ history_available: false,
49
+ truncated: false,
50
+ },
51
+ skill_inventory: [skill],
52
+ workflow_run_url: "https://github.com/letta-ai/letta-code/actions/runs/1",
53
+ };
54
+ const result: ReviewResult = {
55
+ schema_version: 1,
56
+ candidate_id: candidateId,
57
+ skill,
58
+ outcome: "no_drift",
59
+ notes: "current",
60
+ pr_url: null,
61
+ evidence: {
62
+ schema_version: 1,
63
+ candidate_id: candidateId,
64
+ skill,
65
+ sources: [
66
+ {
67
+ locator: analysis.skill_path,
68
+ revision: analysis.current_sha,
69
+ content_digest: analysis.skill_digest,
70
+ retrieved_at: analysis.audit_at,
71
+ excerpt: "x".repeat(200),
72
+ claims: ["checked current source and documentation"],
73
+ },
74
+ ],
75
+ probes: [],
76
+ },
77
+ };
78
+ return { analysis, result };
79
+ }