@letta-ai/letta-code 0.27.20 → 0.27.22

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 (31) hide show
  1. package/dist/agent-presets.js +3768 -0
  2. package/dist/agent-presets.js.map +17 -0
  3. package/dist/types/agent/agent-tags.d.ts +19 -0
  4. package/dist/types/agent/agent-tags.d.ts.map +1 -0
  5. package/dist/types/agent/create-agent-request.d.ts +55 -0
  6. package/dist/types/agent/create-agent-request.d.ts.map +1 -0
  7. package/dist/types/agent/memory-constants.d.ts +2 -0
  8. package/dist/types/agent/memory-constants.d.ts.map +1 -0
  9. package/dist/types/agent/memory.d.ts +29 -0
  10. package/dist/types/agent/memory.d.ts.map +1 -0
  11. package/dist/types/agent/model-catalog.d.ts +362 -0
  12. package/dist/types/agent/model-catalog.d.ts.map +1 -0
  13. package/dist/types/agent/personality-presets.d.ts +64 -0
  14. package/dist/types/agent/personality-presets.d.ts.map +1 -0
  15. package/dist/types/agent/prompt-assets.d.ts +32 -0
  16. package/dist/types/agent/prompt-assets.d.ts.map +1 -0
  17. package/dist/types/agent-presets.d.ts +16 -0
  18. package/dist/types/agent-presets.d.ts.map +1 -0
  19. package/dist/types/constants.d.ts +53 -0
  20. package/dist/types/constants.d.ts.map +1 -0
  21. package/dist/types/types/protocol_v2.d.ts +3 -1
  22. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  23. package/letta.js +2029 -3395
  24. package/package.json +23 -1
  25. package/scripts/codex-watch/agent-watch.ts +186 -0
  26. package/scripts/codex-watch/check-release.ts +18 -316
  27. package/scripts/codex-watch/github.ts +121 -0
  28. package/scripts/codex-watch/release-analysis.ts +288 -0
  29. package/scripts/codex-watch/tracker.test.ts +127 -0
  30. package/scripts/codex-watch/tracker.ts +238 -0
  31. package/scripts/codex-watch/update-tracker.ts +131 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.27.20",
3
+ "version": "0.27.22",
4
4
  "description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.0",
@@ -16,6 +16,8 @@
16
16
  "vendor",
17
17
  "dist/app-server-client.js",
18
18
  "dist/app-server-client.js.map",
19
+ "dist/agent-presets.js",
20
+ "dist/agent-presets.js.map",
19
21
  "dist/types",
20
22
  "docs"
21
23
  ],
@@ -30,6 +32,10 @@
30
32
  },
31
33
  "./protocol": {
32
34
  "types": "./dist/types/protocol.d.ts"
35
+ },
36
+ "./agent-presets": {
37
+ "types": "./dist/types/agent-presets.d.ts",
38
+ "import": "./dist/agent-presets.js"
33
39
  }
34
40
  },
35
41
  "repository": {
@@ -105,5 +111,21 @@
105
111
  "*.{ts,tsx,js,jsx,json}": [
106
112
  "bunx --bun @biomejs/biome@2.2.5 check --write"
107
113
  ]
114
+ },
115
+ "typesVersions": {
116
+ "*": {
117
+ "agent-presets": [
118
+ "./dist/types/agent-presets.d.ts"
119
+ ],
120
+ "app-server-protocol": [
121
+ "./dist/types/app-server-protocol.d.ts"
122
+ ],
123
+ "app-server-client": [
124
+ "./dist/types/app-server-client.d.ts"
125
+ ],
126
+ "protocol": [
127
+ "./dist/types/protocol.d.ts"
128
+ ]
129
+ }
108
130
  }
109
131
  }
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Amelia-driven Codex release watcher entrypoint.
4
+ *
5
+ * This runs alongside the legacy per-release issue workflow. It uses a central
6
+ * tracker issue for state and only asks Amelia to review non-noop releases.
7
+ */
8
+
9
+ import { appendFileSync, writeFileSync } from "node:fs";
10
+ import {
11
+ createIssueWithBody,
12
+ editIssueBody,
13
+ ensureLabels,
14
+ findIssueByExactTitle,
15
+ getIssueBody,
16
+ } from "./github.ts";
17
+ import {
18
+ analyzeCodexRelease,
19
+ DEFAULT_TARGET_REPO,
20
+ } from "./release-analysis.ts";
21
+ import {
22
+ emptyTrackerState,
23
+ hasProcessedTag,
24
+ parseTrackerState,
25
+ recordAnalysis,
26
+ renderTrackerBody,
27
+ } from "./tracker.ts";
28
+
29
+ const DEFAULT_TRACKER_TITLE = "Codex upstream drift tracker";
30
+ const DEFAULT_ANALYSIS_FILE = "codex-watch-analysis.json";
31
+
32
+ interface Args {
33
+ dryRun: boolean;
34
+ sinceTag: string | null;
35
+ currentTag: string | null;
36
+ repo: string;
37
+ trackerTitle: string;
38
+ analysisFile: string;
39
+ }
40
+
41
+ interface TrackerIssue {
42
+ number: number;
43
+ url: string;
44
+ body: string;
45
+ }
46
+
47
+ function parseArgs(argv: string[]): Args {
48
+ const args: Args = {
49
+ dryRun: false,
50
+ sinceTag: null,
51
+ currentTag: null,
52
+ repo: DEFAULT_TARGET_REPO,
53
+ trackerTitle: DEFAULT_TRACKER_TITLE,
54
+ analysisFile: DEFAULT_ANALYSIS_FILE,
55
+ };
56
+ for (let i = 0; i < argv.length; i++) {
57
+ const a = argv[i];
58
+ if (a === "--dry-run") args.dryRun = true;
59
+ else if (a === "--since") args.sinceTag = argv[++i] ?? null;
60
+ else if (a === "--current") args.currentTag = argv[++i] ?? null;
61
+ else if (a === "--repo") args.repo = argv[++i] ?? args.repo;
62
+ else if (a === "--tracker-title") {
63
+ args.trackerTitle = argv[++i] ?? args.trackerTitle;
64
+ } else if (a === "--analysis-file") {
65
+ args.analysisFile = argv[++i] ?? args.analysisFile;
66
+ } else if (a === "--help" || a === "-h") {
67
+ console.log(
68
+ "Usage: bun scripts/codex-watch/agent-watch.ts [--dry-run] [--since TAG] [--current TAG] [--repo OWNER/REPO] [--tracker-title TITLE] [--analysis-file FILE]",
69
+ );
70
+ process.exit(0);
71
+ } else {
72
+ throw new Error(`Unknown argument: ${a}`);
73
+ }
74
+ }
75
+ return args;
76
+ }
77
+
78
+ async function main() {
79
+ const args = parseArgs(process.argv.slice(2));
80
+ const analysis = await analyzeCodexRelease({
81
+ sinceTag: args.sinceTag,
82
+ currentTag: args.currentTag,
83
+ });
84
+ writeFileSync(args.analysisFile, `${JSON.stringify(analysis, null, 2)}\n`);
85
+
86
+ const tracker = ensureTrackerIssue(args);
87
+ const state = parseTrackerState(tracker.body);
88
+
89
+ writeOutput("tracker_issue", String(tracker.number));
90
+ writeOutput("tracker_issue_url", tracker.url);
91
+ writeOutput("analysis_file", args.analysisFile);
92
+ writeOutput("current_tag", analysis.current_tag);
93
+ writeOutput("previous_tag", analysis.previous_tag);
94
+ writeOutput("verdict", analysis.verdict);
95
+
96
+ if (!args.dryRun && hasProcessedTag(state, analysis.current_tag)) {
97
+ console.log(`Already processed ${analysis.current_tag}; nothing to do.`);
98
+ writeOutput("should_run_agent", "false");
99
+ return;
100
+ }
101
+
102
+ if (analysis.verdict === "no-op") {
103
+ const next = recordAnalysis(state, {
104
+ analysis,
105
+ outcome: "recorded_noop",
106
+ notes: "no watched tool-surface changes detected",
107
+ });
108
+ const nextBody = renderTrackerBody(next);
109
+ if (args.dryRun) {
110
+ console.log(nextBody);
111
+ } else {
112
+ editIssueBody(args.repo, tracker.number, nextBody);
113
+ }
114
+ console.log(`Recorded ${analysis.current_tag} as no-op.`);
115
+ writeOutput("should_run_agent", "false");
116
+ return;
117
+ }
118
+
119
+ if (args.dryRun) {
120
+ console.log(JSON.stringify(analysis, null, 2));
121
+ writeOutput("should_run_agent", "false");
122
+ return;
123
+ }
124
+
125
+ console.log(
126
+ `Release ${analysis.current_tag} needs Amelia review: ${analysis.verdict}`,
127
+ );
128
+ writeOutput("should_run_agent", "true");
129
+ }
130
+
131
+ function ensureTrackerIssue(args: Args): TrackerIssue {
132
+ const existing = args.dryRun
133
+ ? null
134
+ : findIssueByExactTitle(args.repo, args.trackerTitle);
135
+ if (existing) {
136
+ return {
137
+ number: existing.number,
138
+ url: `https://github.com/${args.repo}/issues/${existing.number}`,
139
+ body: getIssueBody(args.repo, existing.number),
140
+ };
141
+ }
142
+
143
+ const body = renderTrackerBody(emptyTrackerState());
144
+ if (args.dryRun) {
145
+ return {
146
+ number: 0,
147
+ url: `https://github.com/${args.repo}/issues/0`,
148
+ body,
149
+ };
150
+ }
151
+
152
+ const labels = ["codex-watch", "automation"];
153
+ ensureLabels(args.repo, labels);
154
+ const issueUrl = createIssueWithBody(
155
+ args.repo,
156
+ args.trackerTitle,
157
+ body,
158
+ labels,
159
+ );
160
+ const issueNumber = issueNumberFromUrl(issueUrl);
161
+ return {
162
+ number: issueNumber,
163
+ url: issueUrl,
164
+ body,
165
+ };
166
+ }
167
+
168
+ function issueNumberFromUrl(issueUrl: string): number {
169
+ const issueNumber = Number(issueUrl.trim().split("/").at(-1));
170
+ if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
171
+ throw new Error(`Could not parse issue number from ${issueUrl}`);
172
+ }
173
+ return issueNumber;
174
+ }
175
+
176
+ function writeOutput(name: string, value: string): void {
177
+ const outputPath = process.env.GITHUB_OUTPUT;
178
+ if (!outputPath) return;
179
+ appendFileSync(outputPath, `${name}=${value}\n`);
180
+ }
181
+
182
+ main().catch((err) => {
183
+ console.error(err);
184
+ writeOutput("should_run_agent", "false");
185
+ process.exit(1);
186
+ });
@@ -9,41 +9,13 @@
9
9
  * bun scripts/codex-watch/check-release.ts --repo letta-ai/letta-code
10
10
  */
11
11
 
12
- import { spawnSync } from "node:child_process";
13
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
14
- import { tmpdir } from "node:os";
15
- import { join } from "node:path";
12
+ import { createIssueWithBody, ensureLabels, ghJson } from "./github.ts";
16
13
  import {
17
- decideVerdict,
18
- diffModelsJson,
19
- type ModelsDiff,
20
- type ModelsJson,
21
- } from "./diff-models-json.ts";
22
- import {
23
- type PathChangeSummary,
24
- renderBody,
25
- renderTitle,
26
- } from "./render-issue.ts";
27
-
28
- const CODEX_REPO = "openai/codex";
29
- const DEFAULT_TARGET_REPO =
30
- process.env.GITHUB_REPOSITORY || "letta-ai/letta-code";
31
- const WATCHED_PATHS = [
32
- "codex-rs/models-manager/models.json",
33
- "codex-rs/models-manager/prompt.md",
34
- "codex-rs/core/src/tools",
35
- "codex-rs/apply-patch",
36
- ];
37
- const MAX_COMMITS_PER_PATH = 8;
38
-
39
- interface Release {
40
- tag_name: string;
41
- draft: boolean;
42
- prerelease: boolean;
43
- html_url: string;
44
- body: string | null;
45
- published_at: string | null;
46
- }
14
+ analyzeCodexRelease,
15
+ DEFAULT_TARGET_REPO,
16
+ listStableReleases,
17
+ } from "./release-analysis.ts";
18
+ import { renderBody, renderTitle } from "./render-issue.ts";
47
19
 
48
20
  interface Args {
49
21
  dryRun: boolean;
@@ -77,73 +49,8 @@ function parseArgs(argv: string[]): Args {
77
49
  return args;
78
50
  }
79
51
 
80
- function gh<T>(args: string[], input?: string): T {
81
- const res = spawnSync("gh", args, {
82
- encoding: "utf8",
83
- input,
84
- maxBuffer: 50 * 1024 * 1024,
85
- stdio: input ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"],
86
- });
87
- if (res.status !== 0) {
88
- throw new Error(`gh ${args.join(" ")} failed:\n${res.stderr}`);
89
- }
90
- return JSON.parse(res.stdout) as T;
91
- }
92
-
93
- function git(args: string[], cwd?: string): string {
94
- const res = spawnSync("git", args, {
95
- cwd,
96
- encoding: "utf8",
97
- stdio: ["ignore", "pipe", "pipe"],
98
- });
99
- if (res.status !== 0) {
100
- throw new Error(`git ${args.join(" ")} failed:\n${res.stderr}`);
101
- }
102
- return res.stdout;
103
- }
104
-
105
- function isStableRelease(release: Release): boolean {
106
- if (release.draft || release.prerelease) return false;
107
- return /^(rust-v|v)?\d+\.\d+\.\d+$/.test(release.tag_name);
108
- }
109
-
110
- async function listStableReleases(): Promise<Release[]> {
111
- const releases: Release[] = [];
112
- const headers: Record<string, string> = {
113
- Accept: "application/vnd.github+json",
114
- "X-GitHub-Api-Version": "2022-11-28",
115
- };
116
- const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
117
- if (token) headers.Authorization = `Bearer ${token}`;
118
-
119
- for (let page = 1; page <= 10; page++) {
120
- const url = `https://api.github.com/repos/${CODEX_REPO}/releases?per_page=100&page=${page}`;
121
- const res = await fetch(url, { headers });
122
- if (!res.ok) {
123
- throw new Error(
124
- `GitHub releases API failed (${res.status}): ${await res.text()}`,
125
- );
126
- }
127
- const batch = (await res.json()) as Release[];
128
- releases.push(...batch);
129
- if (batch.length < 100) break;
130
- }
131
- return releases
132
- .filter(isStableRelease)
133
- .sort((a, b) => (a.published_at ?? "").localeCompare(b.published_at ?? ""));
134
- }
135
-
136
- function findPreviousStable(
137
- stables: Release[],
138
- currentTag: string,
139
- ): Release | null {
140
- const idx = stables.findIndex((r) => r.tag_name === currentTag);
141
- if (idx <= 0) return null;
142
- return stables[idx - 1] ?? null;
143
- }
144
-
145
52
  function hasReportedTag(targetRepo: string, tag: string): boolean {
146
- const issues = gh<Array<{ title: string }>>([
53
+ const issues = ghJson<Array<{ title: string }>>([
147
54
  "issue",
148
55
  "list",
149
56
  "--repo",
@@ -162,70 +69,6 @@ function hasReportedTag(targetRepo: string, tag: string): boolean {
162
69
  );
163
70
  }
164
71
 
165
- function cloneCodex(tmp: string): string {
166
- const dir = join(tmp, "codex");
167
- git([
168
- "clone",
169
- "--filter=blob:none",
170
- "--no-checkout",
171
- `https://github.com/${CODEX_REPO}.git`,
172
- dir,
173
- ]);
174
- return dir;
175
- }
176
-
177
- function showFile(repoDir: string, tag: string, path: string): string | null {
178
- const res = spawnSync("git", ["show", `${tag}:${path}`], {
179
- cwd: repoDir,
180
- encoding: "utf8",
181
- stdio: ["ignore", "pipe", "pipe"],
182
- });
183
- if (res.status !== 0) return null;
184
- return res.stdout;
185
- }
186
-
187
- function changedFiles(
188
- repoDir: string,
189
- prevTag: string,
190
- currTag: string,
191
- ): string[] {
192
- return git(["diff", "--name-only", `${prevTag}..${currTag}`], repoDir)
193
- .split("\n")
194
- .filter(Boolean);
195
- }
196
-
197
- function commitsForPath(
198
- repoDir: string,
199
- prevTag: string,
200
- currTag: string,
201
- path: string,
202
- ): string[] {
203
- const out = git(
204
- ["log", "--format=%h %s", `${prevTag}..${currTag}`, "--", path],
205
- repoDir,
206
- );
207
- const commits = out.split("\n").filter(Boolean);
208
- if (commits.length <= MAX_COMMITS_PER_PATH) return commits;
209
- return [
210
- ...commits.slice(0, MAX_COMMITS_PER_PATH),
211
- `…and ${commits.length - MAX_COMMITS_PER_PATH} more commits`,
212
- ];
213
- }
214
-
215
- function diffPreview(
216
- repoDir: string,
217
- prevTag: string,
218
- currTag: string,
219
- path: string,
220
- ): string | null {
221
- const out = git(
222
- ["diff", "--unified=2", `${prevTag}..${currTag}`, "--", path],
223
- repoDir,
224
- );
225
- if (!out.trim()) return null;
226
- return out.split("\n").slice(0, 120).join("\n");
227
- }
228
-
229
72
  function createIssue(
230
73
  repo: string,
231
74
  title: string,
@@ -242,41 +85,7 @@ function createIssue(
242
85
  if (verdict === "no-op") labels.push("informational");
243
86
 
244
87
  ensureLabels(repo, labels);
245
-
246
- const bodyFile = join(tmpdir(), `codex-watch-${Date.now()}.md`);
247
- writeFileSync(bodyFile, body);
248
- try {
249
- const args = [
250
- "issue",
251
- "create",
252
- "--repo",
253
- repo,
254
- "--title",
255
- title,
256
- "--body-file",
257
- bodyFile,
258
- ];
259
- for (const label of labels) args.push("--label", label);
260
- const res = spawnSync("gh", args, { encoding: "utf8" });
261
- if (res.status !== 0) {
262
- throw new Error(`gh ${args.join(" ")} failed:\n${res.stderr}`);
263
- }
264
- console.log(res.stdout.trim());
265
- } finally {
266
- rmSync(bodyFile, { force: true });
267
- }
268
- }
269
-
270
- function ensureLabels(repo: string, labels: string[]): void {
271
- for (const label of labels) {
272
- const res = spawnSync("gh", ["label", "create", label, "--repo", repo], {
273
- encoding: "utf8",
274
- stdio: ["ignore", "pipe", "pipe"],
275
- });
276
- if (res.status !== 0 && !res.stderr.includes("already exists")) {
277
- throw new Error(`gh label create ${label} failed:\n${res.stderr}`);
278
- }
279
- }
88
+ console.log(createIssueWithBody(repo, title, body, labels));
280
89
  }
281
90
 
282
91
  async function main() {
@@ -290,15 +99,6 @@ async function main() {
290
99
  if (!current)
291
100
  throw new Error(`Could not find current release ${args.currentTag}`);
292
101
 
293
- const previous = args.sinceTag
294
- ? (stables.find((r) => r.tag_name === args.sinceTag) ??
295
- ({ tag_name: args.sinceTag } as Release))
296
- : findPreviousStable(stables, current.tag_name);
297
- if (!previous)
298
- throw new Error(
299
- `Could not find previous stable before ${current.tag_name}`,
300
- );
301
-
302
102
  const alreadyReported = args.dryRun
303
103
  ? false
304
104
  : hasReportedTag(args.repo, current.tag_name);
@@ -307,116 +107,18 @@ async function main() {
307
107
  return;
308
108
  }
309
109
 
310
- const tmp = mkdtempSync(join(tmpdir(), "codex-watch-"));
311
- try {
312
- const repoDir = cloneCodex(tmp);
313
- git(
314
- [
315
- "fetch",
316
- "--filter=blob:none",
317
- "origin",
318
- `refs/tags/${previous.tag_name}:refs/tags/${previous.tag_name}`,
319
- ],
320
- repoDir,
321
- );
322
- git(
323
- [
324
- "fetch",
325
- "--filter=blob:none",
326
- "origin",
327
- `refs/tags/${current.tag_name}:refs/tags/${current.tag_name}`,
328
- ],
329
- repoDir,
330
- );
331
-
332
- let modelsDiff: ModelsDiff | null = null;
333
- let parseError = false;
334
- try {
335
- const prevRaw = showFile(
336
- repoDir,
337
- previous.tag_name,
338
- "codex-rs/models-manager/models.json",
339
- );
340
- const currRaw = showFile(
341
- repoDir,
342
- current.tag_name,
343
- "codex-rs/models-manager/models.json",
344
- );
345
- if (!prevRaw || !currRaw)
346
- throw new Error("missing models.json at one tag");
347
- modelsDiff = diffModelsJson(
348
- JSON.parse(prevRaw) as ModelsJson,
349
- JSON.parse(currRaw) as ModelsJson,
350
- );
351
- } catch (err) {
352
- parseError = true;
353
- console.error(`Failed to parse/diff models.json: ${String(err)}`);
354
- }
355
-
356
- const changed = changedFiles(repoDir, previous.tag_name, current.tag_name);
357
- const changedSet = new Set(changed);
358
- const promptMdChanged = changedSet.has("codex-rs/models-manager/prompt.md");
359
- const toolsDirChanged = changed.some((f) =>
360
- f.startsWith("codex-rs/core/src/tools/"),
361
- );
362
- const applyPatchDirChanged = changed.some((f) =>
363
- f.startsWith("codex-rs/apply-patch/"),
364
- );
365
- const verdict = decideVerdict({
366
- models_diff: modelsDiff,
367
- prompt_md_changed: promptMdChanged,
368
- tools_dir_changed: toolsDirChanged,
369
- apply_patch_dir_changed: applyPatchDirChanged,
370
- parse_error: parseError,
371
- });
372
-
373
- const pathChanges: PathChangeSummary[] = WATCHED_PATHS.map((path) => ({
374
- path,
375
- commits: commitsForPath(
376
- repoDir,
377
- previous.tag_name,
378
- current.tag_name,
379
- path,
380
- ),
381
- })).filter((p) => p.commits.length > 0);
382
-
383
- const workflowUrl =
384
- process.env.GITHUB_SERVER_URL &&
385
- process.env.GITHUB_REPOSITORY &&
386
- process.env.GITHUB_RUN_ID
387
- ? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
388
- : "local dry-run";
389
-
390
- const input = {
391
- previous_tag: previous.tag_name,
392
- current_tag: current.tag_name,
393
- release_url: current.html_url,
394
- release_notes_md: current.body ?? "",
395
- verdict,
396
- models_diff: modelsDiff,
397
- prompt_md_changed: promptMdChanged,
398
- prompt_md_diff_preview: promptMdChanged
399
- ? diffPreview(
400
- repoDir,
401
- previous.tag_name,
402
- current.tag_name,
403
- "codex-rs/models-manager/prompt.md",
404
- )
405
- : null,
406
- path_changes: pathChanges,
407
- workflow_run_url: workflowUrl,
408
- };
110
+ const analysis = await analyzeCodexRelease({
111
+ sinceTag: args.sinceTag,
112
+ currentTag: args.currentTag,
113
+ });
409
114
 
410
- const title = renderTitle(input);
411
- const body = renderBody(input);
115
+ const title = renderTitle(analysis);
116
+ const body = renderBody(analysis);
412
117
 
413
- if (args.dryRun) {
414
- console.log(`# ${title}\n\n${body}`);
415
- } else {
416
- createIssue(args.repo, title, body, verdict);
417
- }
418
- } finally {
419
- rmSync(tmp, { recursive: true, force: true });
118
+ if (args.dryRun) {
119
+ console.log(`# ${title}\n\n${body}`);
120
+ } else {
121
+ createIssue(args.repo, title, body, analysis.verdict);
420
122
  }
421
123
  }
422
124