@letta-ai/letta-code 0.31.6 → 0.31.7

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.
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
3
3
  const MAX_SOURCES = 5;
4
4
  const MAX_PROBES = 5;
5
5
  const MAX_CLAIMS_PER_SOURCE = 5;
6
- const MAX_SERIALIZED_BYTES = 650;
6
+ const MAX_SERIALIZED_BYTES = 16 * 1024;
7
7
 
8
8
  export interface EvidenceSource {
9
9
  locator: string;
@@ -51,29 +51,24 @@ export function parseReviewEvidence(value: unknown): ReviewEvidence {
51
51
  if (
52
52
  !Array.isArray(value.sources) ||
53
53
  value.sources.length === 0 ||
54
- value.sources.length > MAX_SOURCES ||
55
- !value.sources.every(isEvidenceSource)
54
+ value.sources.length > MAX_SOURCES
56
55
  ) {
57
- throw new TypeError("review evidence sources are invalid");
56
+ throw new TypeError("review evidence must include between 1 and 5 sources");
58
57
  }
59
- if (
60
- !Array.isArray(value.probes) ||
61
- value.probes.length > MAX_PROBES ||
62
- !value.probes.every(isEvidenceProbe)
63
- ) {
64
- throw new TypeError("review evidence probes are invalid");
58
+ if (!Array.isArray(value.probes) || value.probes.length > MAX_PROBES) {
59
+ throw new TypeError("review evidence must include at most 5 probes");
65
60
  }
66
61
  const evidence: ReviewEvidence = {
67
62
  schema_version: 1,
68
63
  candidate_id: value.candidate_id,
69
64
  skill: value.skill,
70
- sources: value.sources,
71
- probes: value.probes,
65
+ sources: value.sources.map(parseEvidenceSource),
66
+ probes: value.probes.map(parseEvidenceProbe),
72
67
  };
73
68
  if (
74
69
  Buffer.byteLength(JSON.stringify(evidence), "utf8") > MAX_SERIALIZED_BYTES
75
70
  ) {
76
- throw new TypeError("review evidence exceeds 650 bytes");
71
+ throw new TypeError("review evidence exceeds 16384 bytes");
77
72
  }
78
73
  return evidence;
79
74
  }
@@ -82,44 +77,77 @@ export function digestReviewEvidence(evidence: ReviewEvidence): string {
82
77
  return createHash("sha256").update(JSON.stringify(evidence)).digest("hex");
83
78
  }
84
79
 
85
- function isEvidenceSource(value: unknown): value is EvidenceSource {
86
- return (
87
- isRecord(value) &&
88
- hasExactKeys(value, [
80
+ function parseEvidenceSource(value: unknown, index: number): EvidenceSource {
81
+ const description = `review evidence source ${index + 1}`;
82
+ if (
83
+ !isRecord(value) ||
84
+ !hasExactKeys(value, [
89
85
  "locator",
90
86
  "revision",
91
87
  "content_digest",
92
88
  "retrieved_at",
93
89
  "excerpt",
94
90
  "claims",
95
- ]) &&
96
- isBoundedString(value.locator, 500) &&
97
- (value.revision === null || isBoundedString(value.revision, 300)) &&
98
- (value.content_digest === null || isDigest(value.content_digest)) &&
99
- (value.revision !== null || value.content_digest !== null) &&
100
- isIsoTimestamp(value.retrieved_at) &&
101
- isBoundedString(value.excerpt, 300) &&
102
- Array.isArray(value.claims) &&
103
- value.claims.length > 0 &&
104
- value.claims.length <= MAX_CLAIMS_PER_SOURCE &&
105
- value.claims.every((claim) => isBoundedString(claim, 500))
106
- );
91
+ ])
92
+ ) {
93
+ throw new TypeError(`${description} has unknown or missing fields`);
94
+ }
95
+ if (!isBoundedString(value.locator, 500)) {
96
+ throw new TypeError(`${description} locator is invalid`);
97
+ }
98
+ if (value.revision !== null && !isBoundedString(value.revision, 300)) {
99
+ throw new TypeError(`${description} revision is invalid`);
100
+ }
101
+ if (value.content_digest !== null && !isDigest(value.content_digest)) {
102
+ throw new TypeError(`${description} content_digest is invalid`);
103
+ }
104
+ if (value.revision === null && value.content_digest === null) {
105
+ throw new TypeError(`${description} requires a revision or content_digest`);
106
+ }
107
+ if (!isIsoTimestamp(value.retrieved_at)) {
108
+ throw new TypeError(`${description} retrieved_at is not an ISO timestamp`);
109
+ }
110
+ if (!isBoundedString(value.excerpt, 300)) {
111
+ throw new TypeError(`${description} excerpt is invalid`);
112
+ }
113
+ if (
114
+ !Array.isArray(value.claims) ||
115
+ value.claims.length === 0 ||
116
+ value.claims.length > MAX_CLAIMS_PER_SOURCE ||
117
+ !value.claims.every((claim) => isBoundedString(claim, 500))
118
+ ) {
119
+ throw new TypeError(`${description} claims are invalid`);
120
+ }
121
+ return value as unknown as EvidenceSource;
107
122
  }
108
123
 
109
- function isEvidenceProbe(value: unknown): value is EvidenceProbe {
110
- return (
111
- isRecord(value) &&
112
- hasExactKeys(value, ["command", "result_digest", "summary"]) &&
113
- isBoundedString(value.command, 500) &&
114
- isDigest(value.result_digest) &&
115
- isBoundedString(value.summary, 500)
116
- );
124
+ function parseEvidenceProbe(value: unknown, index: number): EvidenceProbe {
125
+ const description = `review evidence probe ${index + 1}`;
126
+ if (
127
+ !isRecord(value) ||
128
+ !hasExactKeys(value, ["command", "result_digest", "summary"])
129
+ ) {
130
+ throw new TypeError(`${description} has unknown or missing fields`);
131
+ }
132
+ if (!isBoundedString(value.command, 500)) {
133
+ throw new TypeError(`${description} command is invalid`);
134
+ }
135
+ if (!isDigest(value.result_digest)) {
136
+ throw new TypeError(`${description} result_digest is invalid`);
137
+ }
138
+ if (!isBoundedString(value.summary, 500)) {
139
+ throw new TypeError(`${description} summary is invalid`);
140
+ }
141
+ return value as unknown as EvidenceProbe;
117
142
  }
118
143
 
119
144
  function isIsoTimestamp(value: unknown): value is string {
120
145
  if (typeof value !== "string") return false;
121
146
  const parsed = new Date(value);
122
- return !Number.isNaN(parsed.getTime()) && parsed.toISOString() === value;
147
+ return (
148
+ !Number.isNaN(parsed.getTime()) &&
149
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(value)
150
+ );
123
151
  }
124
152
 
125
153
  function isDigest(value: unknown): value is string {
@@ -0,0 +1,125 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import type { BuiltinSkillWatchAnalysis } from "./analysis.ts";
6
+
7
+ describe("review result finalization", () => {
8
+ test("validates with the runner parser before encoding the result", () => {
9
+ const directory = mkdtempSync(join(tmpdir(), "skill-result-finalizer-"));
10
+ try {
11
+ const analysis = fakeAnalysis();
12
+ const analysisFile = join(directory, "analysis.json");
13
+ const resultFile = join(directory, "result.json");
14
+ writeFileSync(analysisFile, JSON.stringify(analysis));
15
+ writeFileSync(resultFile, JSON.stringify(fakeResult(analysis)));
16
+
17
+ const finalized = Bun.spawnSync([
18
+ "bun",
19
+ "scripts/builtin-skills-watch/finalize-result.ts",
20
+ "--analysis-file",
21
+ analysisFile,
22
+ "--result-file",
23
+ resultFile,
24
+ ]);
25
+
26
+ expect(finalized.exitCode).toBe(0);
27
+ const line = finalized.stdout.toString().trim();
28
+ expect(line).toStartWith("SKILL_WATCH_RESULT ");
29
+ expect(
30
+ JSON.parse(
31
+ Buffer.from(
32
+ line.slice("SKILL_WATCH_RESULT ".length),
33
+ "base64",
34
+ ).toString("utf8"),
35
+ ),
36
+ ).toEqual(fakeResult(analysis));
37
+ } finally {
38
+ rmSync(directory, { recursive: true, force: true });
39
+ }
40
+ });
41
+
42
+ test("rejects a result for another candidate", () => {
43
+ const directory = mkdtempSync(join(tmpdir(), "skill-result-finalizer-"));
44
+ try {
45
+ const analysis = fakeAnalysis();
46
+ const analysisFile = join(directory, "analysis.json");
47
+ const resultFile = join(directory, "result.json");
48
+ writeFileSync(analysisFile, JSON.stringify(analysis));
49
+ writeFileSync(
50
+ resultFile,
51
+ JSON.stringify({
52
+ ...fakeResult(analysis),
53
+ candidate_id: "creating-skills@bbbbbbbbbbbb-abcdef0123456789",
54
+ }),
55
+ );
56
+
57
+ const finalized = Bun.spawnSync([
58
+ "bun",
59
+ "scripts/builtin-skills-watch/finalize-result.ts",
60
+ "--analysis-file",
61
+ analysisFile,
62
+ "--result-file",
63
+ resultFile,
64
+ ]);
65
+
66
+ expect(finalized.exitCode).not.toBe(0);
67
+ expect(finalized.stderr.toString()).toContain(
68
+ "does not match the pending candidate",
69
+ );
70
+ } finally {
71
+ rmSync(directory, { recursive: true, force: true });
72
+ }
73
+ });
74
+ });
75
+
76
+ function fakeAnalysis(): BuiltinSkillWatchAnalysis {
77
+ const skill = "creating-skills";
78
+ return {
79
+ schema_version: 1,
80
+ candidate_id: `${skill}@${"a".repeat(12)}-abcdef0123456789`,
81
+ skill,
82
+ skill_path: `src/skills/builtin/${skill}`,
83
+ skill_files: [`src/skills/builtin/${skill}/SKILL.md`],
84
+ skill_digest: "b".repeat(64),
85
+ current_sha: "a".repeat(40),
86
+ audit_at: "2026-08-26T00:00:00.000Z",
87
+ previous_audit: null,
88
+ repository_changes: {
89
+ previous_sha: null,
90
+ changed_files: [],
91
+ commits: [],
92
+ history_available: false,
93
+ truncated: false,
94
+ },
95
+ skill_inventory: [skill],
96
+ workflow_run_url: "https://github.com/letta-ai/letta-code/actions/runs/1",
97
+ };
98
+ }
99
+
100
+ function fakeResult(analysis: BuiltinSkillWatchAnalysis) {
101
+ return {
102
+ schema_version: 1,
103
+ candidate_id: analysis.candidate_id,
104
+ skill: analysis.skill,
105
+ outcome: "no_drift",
106
+ notes: "current source and skill agree",
107
+ pr_url: null,
108
+ evidence: {
109
+ schema_version: 1,
110
+ candidate_id: analysis.candidate_id,
111
+ skill: analysis.skill,
112
+ sources: [
113
+ {
114
+ locator: analysis.skill_path,
115
+ revision: analysis.current_sha,
116
+ content_digest: analysis.skill_digest,
117
+ retrieved_at: analysis.audit_at,
118
+ excerpt: "the current skill matches its owning source",
119
+ claims: ["checked current source"],
120
+ },
121
+ ],
122
+ probes: [],
123
+ },
124
+ } as const;
125
+ }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bun
2
+ /** Validates and encodes one agent-authored review result for the runner. */
3
+
4
+ import { readAnalysis, readReviewResult } from "./update-tracker.ts";
5
+
6
+ interface Args {
7
+ analysisFile: string | null;
8
+ resultFile: string | null;
9
+ }
10
+
11
+ function parseArgs(argv: string[]): Args {
12
+ const args: Args = { analysisFile: null, resultFile: null };
13
+ for (let index = 0; index < argv.length; index += 1) {
14
+ const arg = argv[index];
15
+ if (arg === "--analysis-file") {
16
+ args.analysisFile = argv[++index] ?? null;
17
+ } else if (arg === "--result-file") {
18
+ args.resultFile = argv[++index] ?? null;
19
+ } else if (arg === "--help" || arg === "-h") {
20
+ console.log(
21
+ "Usage: bun scripts/builtin-skills-watch/finalize-result.ts --analysis-file FILE --result-file FILE",
22
+ );
23
+ process.exit(0);
24
+ } else {
25
+ throw new Error(`Unknown argument: ${arg}`);
26
+ }
27
+ }
28
+ if (!args.analysisFile || !args.resultFile) {
29
+ throw new Error("--analysis-file and --result-file are required");
30
+ }
31
+ return args;
32
+ }
33
+
34
+ function main(): void {
35
+ const args = parseArgs(process.argv.slice(2));
36
+ const analysis = readAnalysis(args.analysisFile as string);
37
+ const result = readReviewResult(args.resultFile as string, analysis);
38
+ const encoded = Buffer.from(JSON.stringify(result), "utf8").toString(
39
+ "base64",
40
+ );
41
+ console.log(`SKILL_WATCH_RESULT ${encoded}`);
42
+ }
43
+
44
+ if (import.meta.main) main();
@@ -0,0 +1,58 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ type CandidatePullRequest,
4
+ selectCanonicalPullRequest,
5
+ } from "./reconcile-results.ts";
6
+
7
+ describe("pending watcher PR reconciliation", () => {
8
+ test("prefers a merged PR and closes every open duplicate", () => {
9
+ const merged = pullRequest(4067, "MERGED");
10
+ const firstDuplicate = pullRequest(4077, "OPEN");
11
+ const secondDuplicate = pullRequest(4099, "OPEN");
12
+
13
+ expect(
14
+ selectCanonicalPullRequest([firstDuplicate, secondDuplicate, merged]),
15
+ ).toEqual({
16
+ canonical: merged,
17
+ duplicateOpen: [firstDuplicate, secondDuplicate],
18
+ });
19
+ });
20
+
21
+ test("uses the oldest open PR when no candidate was merged", () => {
22
+ const older = pullRequest(4078, "OPEN");
23
+ const newer = pullRequest(4098, "OPEN");
24
+
25
+ expect(selectCanonicalPullRequest([newer, older])).toEqual({
26
+ canonical: older,
27
+ duplicateOpen: [newer],
28
+ });
29
+ });
30
+
31
+ test("ignores closed unmerged PRs and rejects multiple merged PRs", () => {
32
+ expect(selectCanonicalPullRequest([pullRequest(1, "CLOSED")])).toEqual({
33
+ canonical: null,
34
+ duplicateOpen: [],
35
+ });
36
+ expect(() =>
37
+ selectCanonicalPullRequest([
38
+ pullRequest(1, "MERGED"),
39
+ pullRequest(2, "MERGED"),
40
+ ]),
41
+ ).toThrow("Multiple merged watcher PRs");
42
+ });
43
+ });
44
+
45
+ function pullRequest(number: number, state: string): CandidatePullRequest {
46
+ return {
47
+ number,
48
+ author: { login: "amelia-letta" },
49
+ baseRefName: "main",
50
+ body: "Builtin-skill-watch: creating-skills@aaaaaaaaaaaa-0000000000000001",
51
+ files: [{ path: "src/skills/builtin/creating-skills/SKILL.md" }],
52
+ headRefOid: number.toString(16).padStart(40, "0"),
53
+ isDraft: state === "OPEN",
54
+ mergedAt: state === "MERGED" ? "2026-08-27T00:00:00Z" : null,
55
+ state,
56
+ url: `https://github.com/letta-ai/letta-code/pull/${number}`,
57
+ };
58
+ }
@@ -0,0 +1,311 @@
1
+ #!/usr/bin/env bun
2
+ /** Reconciles pending tracker candidates against PR side effects before retries. */
3
+
4
+ import {
5
+ type BuiltinSkillWatchAnalysis,
6
+ buildAnalysis,
7
+ DEFAULT_TARGET_REPO,
8
+ listBuiltinSkillsAtCommit,
9
+ } from "./analysis.ts";
10
+ import type { ReviewEvidence } from "./evidence.ts";
11
+ import { createIssueComment, editIssueBody, ghJson, runGh } from "./github.ts";
12
+ import {
13
+ parseTrackerState,
14
+ recordOutcome,
15
+ renderTrackerBody,
16
+ } from "./tracker.ts";
17
+ import {
18
+ getOpenTrackerIssue,
19
+ type PullRequestView,
20
+ verifyReconciledPullRequest,
21
+ } from "./update-tracker.ts";
22
+
23
+ interface Args {
24
+ repo: string;
25
+ trackerIssue: number | null;
26
+ expectedGithubLogin: string | null;
27
+ dryRun: boolean;
28
+ }
29
+
30
+ export interface CandidatePullRequest extends PullRequestView {
31
+ mergedAt: string | null;
32
+ number: number;
33
+ }
34
+
35
+ export interface PullRequestReconciliation {
36
+ canonical: CandidatePullRequest | null;
37
+ duplicateOpen: CandidatePullRequest[];
38
+ }
39
+
40
+ function parseArgs(argv: string[]): Args {
41
+ const args: Args = {
42
+ repo: DEFAULT_TARGET_REPO,
43
+ trackerIssue: null,
44
+ expectedGithubLogin: null,
45
+ dryRun: false,
46
+ };
47
+ for (let index = 0; index < argv.length; index += 1) {
48
+ const arg = argv[index];
49
+ if (arg === "--repo") args.repo = argv[++index] ?? args.repo;
50
+ else if (arg === "--tracker-issue") {
51
+ args.trackerIssue = Number(argv[++index]);
52
+ } else if (arg === "--expected-github-login") {
53
+ args.expectedGithubLogin = argv[++index] ?? null;
54
+ } else if (arg === "--dry-run") args.dryRun = true;
55
+ else if (arg === "--help" || arg === "-h") {
56
+ console.log(
57
+ "Usage: bun scripts/builtin-skills-watch/reconcile-results.ts --tracker-issue ISSUE --expected-github-login LOGIN [--repo OWNER/REPO] [--dry-run]",
58
+ );
59
+ process.exit(0);
60
+ } else {
61
+ throw new Error(`Unknown argument: ${arg}`);
62
+ }
63
+ }
64
+ if (!args.trackerIssue || Number.isNaN(args.trackerIssue)) {
65
+ throw new Error("--tracker-issue is required");
66
+ }
67
+ if (!args.expectedGithubLogin) {
68
+ throw new Error("--expected-github-login is required");
69
+ }
70
+ return args;
71
+ }
72
+
73
+ function main(): void {
74
+ const args = parseArgs(process.argv.slice(2));
75
+ assertAuthenticatedLogin(args.expectedGithubLogin as string);
76
+ const tracker = getOpenTrackerIssue(args.repo, args.trackerIssue as number);
77
+ let state = parseTrackerState(tracker.body);
78
+ const inventory = listBuiltinSkillsAtCommit("HEAD");
79
+ let reconciled = 0;
80
+
81
+ for (const skill of Object.keys(state.pending).sort()) {
82
+ const pending = state.pending[skill];
83
+ if (!pending) continue;
84
+ const analysis = buildAnalysis({
85
+ skill,
86
+ currentSha: pending.current_sha,
87
+ auditAt: pending.audit_at,
88
+ previousAudit: previousAudit(state, skill),
89
+ });
90
+ analysis.workflow_run_url = workflowRunUrl(pending.workflow_run_id);
91
+ const pullRequests = findCandidatePullRequests(
92
+ args.repo,
93
+ analysis.candidate_id,
94
+ );
95
+ const selected = selectCanonicalPullRequest(pullRequests);
96
+ if (!selected.canonical) continue;
97
+
98
+ verifyReconciledPullRequest(
99
+ args.repo,
100
+ selected.canonical.url,
101
+ analysis,
102
+ args.expectedGithubLogin as string,
103
+ );
104
+ for (const duplicate of selected.duplicateOpen) {
105
+ verifyReconciledPullRequest(
106
+ args.repo,
107
+ duplicate.url,
108
+ analysis,
109
+ args.expectedGithubLogin as string,
110
+ );
111
+ }
112
+
113
+ if (args.dryRun) {
114
+ console.log(
115
+ `${analysis.skill}: would reconcile ${selected.canonical.url}${renderDuplicatePlan(selected.duplicateOpen)}`,
116
+ );
117
+ continue;
118
+ }
119
+
120
+ for (const duplicate of selected.duplicateOpen) {
121
+ closeDuplicatePullRequest(args.repo, duplicate, selected.canonical);
122
+ }
123
+ const evidence = reconciliationEvidence(analysis, selected.canonical);
124
+ const evidenceUrl = createIssueComment(
125
+ args.repo,
126
+ args.trackerIssue as number,
127
+ renderEvidenceComment(analysis, selected.canonical, evidence),
128
+ );
129
+ state = recordOutcome(state, {
130
+ analysis,
131
+ outcome: "pr_created",
132
+ notes: reconciliationNotes(selected.canonical),
133
+ prUrl: selected.canonical.url,
134
+ evidence,
135
+ evidenceUrl,
136
+ });
137
+ reconciled += 1;
138
+ }
139
+
140
+ if (args.dryRun) return;
141
+ const latest = getOpenTrackerIssue(args.repo, args.trackerIssue as number);
142
+ if (latest.body !== tracker.body) {
143
+ throw new Error(
144
+ "Tracker body changed while PR side effects were reconciled",
145
+ );
146
+ }
147
+ if (reconciled > 0) {
148
+ editIssueBody(
149
+ args.repo,
150
+ args.trackerIssue as number,
151
+ renderTrackerBody(state, inventory),
152
+ );
153
+ }
154
+ console.log(`Reconciled ${reconciled} pending built-in skill candidates`);
155
+ }
156
+
157
+ export function selectCanonicalPullRequest(
158
+ pullRequests: CandidatePullRequest[],
159
+ ): PullRequestReconciliation {
160
+ const merged = pullRequests
161
+ .filter((pullRequest) => pullRequest.state === "MERGED")
162
+ .sort(byPullRequestNumber);
163
+ if (merged.length > 1) {
164
+ throw new Error(
165
+ `Multiple merged watcher PRs found for one candidate: ${merged.map((pullRequest) => `#${pullRequest.number}`).join(", ")}`,
166
+ );
167
+ }
168
+ const open = pullRequests
169
+ .filter((pullRequest) => pullRequest.state === "OPEN")
170
+ .sort(byPullRequestNumber);
171
+ const canonical = merged[0] ?? open[0] ?? null;
172
+ return {
173
+ canonical,
174
+ duplicateOpen: canonical
175
+ ? open.filter((pullRequest) => pullRequest.number !== canonical.number)
176
+ : [],
177
+ };
178
+ }
179
+
180
+ function findCandidatePullRequests(
181
+ repo: string,
182
+ candidateId: string,
183
+ ): CandidatePullRequest[] {
184
+ const marker = `Builtin-skill-watch: ${candidateId}`;
185
+ return ghJson<CandidatePullRequest[]>([
186
+ "pr",
187
+ "list",
188
+ "--repo",
189
+ repo,
190
+ "--state",
191
+ "all",
192
+ "--search",
193
+ `${marker} in:body`,
194
+ "--limit",
195
+ "20",
196
+ "--json",
197
+ "number,state,isDraft,mergedAt,author,baseRefName,headRefOid,body,files,url",
198
+ ]).filter((pullRequest) => hasExactMarker(pullRequest.body, marker));
199
+ }
200
+
201
+ function assertAuthenticatedLogin(expectedGithubLogin: string): void {
202
+ const authenticated = ghJson<{ login: string }>(["api", "user"]).login;
203
+ if (authenticated !== expectedGithubLogin) {
204
+ throw new Error(
205
+ `Authenticated GitHub login ${authenticated} does not match ${expectedGithubLogin}`,
206
+ );
207
+ }
208
+ }
209
+
210
+ function closeDuplicatePullRequest(
211
+ repo: string,
212
+ duplicate: CandidatePullRequest,
213
+ canonical: CandidatePullRequest,
214
+ ): void {
215
+ runGh([
216
+ "pr",
217
+ "close",
218
+ String(duplicate.number),
219
+ "--repo",
220
+ repo,
221
+ "--comment",
222
+ `Closing as a duplicate of ${canonical.url}, which has the same exact built-in skill watcher candidate.`,
223
+ ]);
224
+ }
225
+
226
+ function reconciliationEvidence(
227
+ analysis: BuiltinSkillWatchAnalysis,
228
+ pullRequest: CandidatePullRequest,
229
+ ): ReviewEvidence {
230
+ const status = pullRequest.state === "MERGED" ? "merged" : "open draft";
231
+ return {
232
+ schema_version: 1,
233
+ candidate_id: analysis.candidate_id,
234
+ skill: analysis.skill,
235
+ sources: [
236
+ {
237
+ locator: pullRequest.url,
238
+ revision: pullRequest.headRefOid,
239
+ content_digest: null,
240
+ retrieved_at: new Date().toISOString(),
241
+ excerpt: `The ${status} watcher PR has the exact candidate marker and a validated skill-only diff.`,
242
+ claims: [
243
+ "the watcher already created and validated a PR for this exact candidate",
244
+ ],
245
+ },
246
+ ],
247
+ probes: [],
248
+ };
249
+ }
250
+
251
+ function renderEvidenceComment(
252
+ analysis: BuiltinSkillWatchAnalysis,
253
+ pullRequest: CandidatePullRequest,
254
+ evidence: ReviewEvidence,
255
+ ): string {
256
+ return [
257
+ `## Reconciled built-in skill audit: ${analysis.skill}`,
258
+ "",
259
+ `Candidate: \`${analysis.candidate_id}\``,
260
+ `PR: ${pullRequest.url}`,
261
+ `PR state: \`${pullRequest.state.toLowerCase()}\``,
262
+ "",
263
+ "```json",
264
+ JSON.stringify(evidence, null, 2),
265
+ "```",
266
+ ].join("\n");
267
+ }
268
+
269
+ function reconciliationNotes(pullRequest: CandidatePullRequest): string {
270
+ return pullRequest.state === "MERGED"
271
+ ? `reconciled merged watcher PR #${pullRequest.number}`
272
+ : `reconciled open draft watcher PR #${pullRequest.number}`;
273
+ }
274
+
275
+ function renderDuplicatePlan(duplicates: CandidatePullRequest[]): string {
276
+ return duplicates.length === 0
277
+ ? ""
278
+ : ` and close duplicate ${duplicates.map((pullRequest) => `#${pullRequest.number}`).join(", ")}`;
279
+ }
280
+
281
+ function previousAudit(
282
+ state: ReturnType<typeof parseTrackerState>,
283
+ skill: string,
284
+ ): BuiltinSkillWatchAnalysis["previous_audit"] {
285
+ const audit = state.skills[skill];
286
+ return audit
287
+ ? {
288
+ candidate_id: audit.candidate_id,
289
+ audited_sha: audit.audited_sha,
290
+ skill_digest: audit.skill_digest,
291
+ audited_at: audit.audited_at,
292
+ }
293
+ : null;
294
+ }
295
+
296
+ function workflowRunUrl(runId: string): string {
297
+ return `https://github.com/letta-ai/letta-code/actions/runs/${runId}`;
298
+ }
299
+
300
+ function hasExactMarker(body: string, marker: string): boolean {
301
+ return body.split(/\r?\n/).some((line) => line.trim() === marker);
302
+ }
303
+
304
+ function byPullRequestNumber(
305
+ left: CandidatePullRequest,
306
+ right: CandidatePullRequest,
307
+ ): number {
308
+ return left.number - right.number;
309
+ }
310
+
311
+ if (import.meta.main) main();