holo-codex 0.1.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 (149) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/CONTRIBUTING.md +54 -0
  3. package/LICENSE +21 -0
  4. package/README.md +215 -0
  5. package/README.zh-CN.md +215 -0
  6. package/SECURITY.md +39 -0
  7. package/assets/brand/README.md +35 -0
  8. package/assets/brand/holo-codex-icon.svg +28 -0
  9. package/assets/brand/holo-codex-lockup.svg +49 -0
  10. package/assets/brand/holo-codex-mark.svg +33 -0
  11. package/assets/brand/holo-codex-plugin-card.png +0 -0
  12. package/assets/brand/holo-codex-plugin-card.svg +81 -0
  13. package/assets/brand/holo-codex-readme-hero.png +0 -0
  14. package/assets/brand/holo-codex-readme-hero.svg +140 -0
  15. package/assets/brand/holo-codex-social-preview.png +0 -0
  16. package/assets/brand/holo-codex-social-preview.svg +130 -0
  17. package/assets/brand/holo-codex-wordmark-options.svg +52 -0
  18. package/docs/checklists/agent-loop-first-delivery-audit.md +129 -0
  19. package/docs/examples/generic-loop-repo-hygiene.md +168 -0
  20. package/docs/install.md +190 -0
  21. package/docs/local-release-readiness.md +206 -0
  22. package/docs/release-checklist.md +144 -0
  23. package/docs/self-bootstrap.md +150 -0
  24. package/docs/trust-and-safety.md +45 -0
  25. package/package.json +83 -0
  26. package/plugins/autonomous-pr-loop/.codex-plugin/plugin.json +17 -0
  27. package/plugins/autonomous-pr-loop/.mcp.json +13 -0
  28. package/plugins/autonomous-pr-loop/bin/agent-loop.mjs +31 -0
  29. package/plugins/autonomous-pr-loop/core/artifacts.ts +164 -0
  30. package/plugins/autonomous-pr-loop/core/autonomy-policy.ts +206 -0
  31. package/plugins/autonomous-pr-loop/core/ci.ts +131 -0
  32. package/plugins/autonomous-pr-loop/core/cli-i18n.ts +123 -0
  33. package/plugins/autonomous-pr-loop/core/cli.ts +1413 -0
  34. package/plugins/autonomous-pr-loop/core/command-runner.ts +446 -0
  35. package/plugins/autonomous-pr-loop/core/command.ts +47 -0
  36. package/plugins/autonomous-pr-loop/core/config-editor.ts +140 -0
  37. package/plugins/autonomous-pr-loop/core/config.ts +293 -0
  38. package/plugins/autonomous-pr-loop/core/controller-host.ts +19 -0
  39. package/plugins/autonomous-pr-loop/core/dashboard-server.ts +536 -0
  40. package/plugins/autonomous-pr-loop/core/delivery-work-item.ts +217 -0
  41. package/plugins/autonomous-pr-loop/core/doctor.ts +335 -0
  42. package/plugins/autonomous-pr-loop/core/errors.ts +82 -0
  43. package/plugins/autonomous-pr-loop/core/gate-recovery.ts +176 -0
  44. package/plugins/autonomous-pr-loop/core/gates.ts +26 -0
  45. package/plugins/autonomous-pr-loop/core/generic-lifecycle.ts +399 -0
  46. package/plugins/autonomous-pr-loop/core/git.ts +213 -0
  47. package/plugins/autonomous-pr-loop/core/github.ts +269 -0
  48. package/plugins/autonomous-pr-loop/core/gitnexus.ts +90 -0
  49. package/plugins/autonomous-pr-loop/core/happy.ts +42 -0
  50. package/plugins/autonomous-pr-loop/core/hook-capture.ts +115 -0
  51. package/plugins/autonomous-pr-loop/core/hook-events.ts +22 -0
  52. package/plugins/autonomous-pr-loop/core/hook-installation.ts +85 -0
  53. package/plugins/autonomous-pr-loop/core/hook-observer.ts +84 -0
  54. package/plugins/autonomous-pr-loop/core/hook-policy.ts +423 -0
  55. package/plugins/autonomous-pr-loop/core/hook-router.ts +452 -0
  56. package/plugins/autonomous-pr-loop/core/index.ts +32 -0
  57. package/plugins/autonomous-pr-loop/core/local-install.ts +778 -0
  58. package/plugins/autonomous-pr-loop/core/locale.ts +60 -0
  59. package/plugins/autonomous-pr-loop/core/loop-shapes.ts +190 -0
  60. package/plugins/autonomous-pr-loop/core/mcp-controller.ts +1479 -0
  61. package/plugins/autonomous-pr-loop/core/notification-feed.ts +263 -0
  62. package/plugins/autonomous-pr-loop/core/plan-parser.ts +206 -0
  63. package/plugins/autonomous-pr-loop/core/plugin-paths.ts +32 -0
  64. package/plugins/autonomous-pr-loop/core/policy.ts +65 -0
  65. package/plugins/autonomous-pr-loop/core/pr-lifecycle.ts +464 -0
  66. package/plugins/autonomous-pr-loop/core/pr-selector.ts +284 -0
  67. package/plugins/autonomous-pr-loop/core/profiles.ts +439 -0
  68. package/plugins/autonomous-pr-loop/core/redaction.ts +17 -0
  69. package/plugins/autonomous-pr-loop/core/repo-root.ts +22 -0
  70. package/plugins/autonomous-pr-loop/core/review-comments.ts +77 -0
  71. package/plugins/autonomous-pr-loop/core/scope-guard.ts +179 -0
  72. package/plugins/autonomous-pr-loop/core/state-machine.ts +828 -0
  73. package/plugins/autonomous-pr-loop/core/state-types.ts +130 -0
  74. package/plugins/autonomous-pr-loop/core/storage.ts +2527 -0
  75. package/plugins/autonomous-pr-loop/core/types.ts +567 -0
  76. package/plugins/autonomous-pr-loop/core/worker-events.ts +412 -0
  77. package/plugins/autonomous-pr-loop/core/worker-policy.ts +72 -0
  78. package/plugins/autonomous-pr-loop/core/worker-prompts.ts +182 -0
  79. package/plugins/autonomous-pr-loop/core/worker.ts +809 -0
  80. package/plugins/autonomous-pr-loop/core/workflow-board.ts +1515 -0
  81. package/plugins/autonomous-pr-loop/hooks/dist/permission-request.js +2462 -0
  82. package/plugins/autonomous-pr-loop/hooks/dist/post-compact.js +2462 -0
  83. package/plugins/autonomous-pr-loop/hooks/dist/post-tool-use.js +2462 -0
  84. package/plugins/autonomous-pr-loop/hooks/dist/pre-compact.js +2462 -0
  85. package/plugins/autonomous-pr-loop/hooks/dist/pre-tool-use.js +3460 -0
  86. package/plugins/autonomous-pr-loop/hooks/dist/session-start.js +2462 -0
  87. package/plugins/autonomous-pr-loop/hooks/dist/stop.js +2462 -0
  88. package/plugins/autonomous-pr-loop/hooks/dist/user-prompt-submit.js +2462 -0
  89. package/plugins/autonomous-pr-loop/hooks/hooks.json +106 -0
  90. package/plugins/autonomous-pr-loop/hooks/observe-runner.ts +25 -0
  91. package/plugins/autonomous-pr-loop/hooks/permission-request.ts +4 -0
  92. package/plugins/autonomous-pr-loop/hooks/post-compact.ts +4 -0
  93. package/plugins/autonomous-pr-loop/hooks/post-tool-use.ts +4 -0
  94. package/plugins/autonomous-pr-loop/hooks/pre-compact.ts +4 -0
  95. package/plugins/autonomous-pr-loop/hooks/pre-tool-use.ts +44 -0
  96. package/plugins/autonomous-pr-loop/hooks/session-start.ts +4 -0
  97. package/plugins/autonomous-pr-loop/hooks/stop.ts +4 -0
  98. package/plugins/autonomous-pr-loop/hooks/user-prompt-submit.ts +4 -0
  99. package/plugins/autonomous-pr-loop/mcp-server/src/index.ts +87 -0
  100. package/plugins/autonomous-pr-loop/mcp-server/src/tools.ts +205 -0
  101. package/plugins/autonomous-pr-loop/package.json +9 -0
  102. package/plugins/autonomous-pr-loop/schemas/config.schema.json +74 -0
  103. package/plugins/autonomous-pr-loop/schemas/marketplace.schema.json +46 -0
  104. package/plugins/autonomous-pr-loop/schemas/plugin.schema.json +32 -0
  105. package/plugins/autonomous-pr-loop/schemas/state.schema.json +19 -0
  106. package/plugins/autonomous-pr-loop/schemas/worker-event.schema.json +19 -0
  107. package/plugins/autonomous-pr-loop/schemas/worker-result.schema.json +58 -0
  108. package/plugins/autonomous-pr-loop/scripts/agent-loop.ts +44 -0
  109. package/plugins/autonomous-pr-loop/skills/autonomous-pr-loop/SKILL.md +26 -0
  110. package/plugins/autonomous-pr-loop/skills/autonomous-pr-loop/agents/openai.yaml +6 -0
  111. package/plugins/autonomous-pr-loop/ui/index.html +26 -0
  112. package/plugins/autonomous-pr-loop/ui/public/favicon.svg +7 -0
  113. package/plugins/autonomous-pr-loop/ui/src/api.ts +639 -0
  114. package/plugins/autonomous-pr-loop/ui/src/app.tsx +238 -0
  115. package/plugins/autonomous-pr-loop/ui/src/components/ActivityBadge.tsx +31 -0
  116. package/plugins/autonomous-pr-loop/ui/src/components/BrandMark.tsx +36 -0
  117. package/plugins/autonomous-pr-loop/ui/src/components/Collapsible.tsx +6 -0
  118. package/plugins/autonomous-pr-loop/ui/src/components/CommandPreview.tsx +15 -0
  119. package/plugins/autonomous-pr-loop/ui/src/components/ConfigEditor.tsx +389 -0
  120. package/plugins/autonomous-pr-loop/ui/src/components/EmptyState.tsx +10 -0
  121. package/plugins/autonomous-pr-loop/ui/src/components/ErrorState.tsx +12 -0
  122. package/plugins/autonomous-pr-loop/ui/src/components/List.tsx +7 -0
  123. package/plugins/autonomous-pr-loop/ui/src/components/MetricRow.tsx +6 -0
  124. package/plugins/autonomous-pr-loop/ui/src/components/ResponsiveTable.tsx +65 -0
  125. package/plugins/autonomous-pr-loop/ui/src/components/RiskBadge.tsx +10 -0
  126. package/plugins/autonomous-pr-loop/ui/src/components/StatusBadge.tsx +29 -0
  127. package/plugins/autonomous-pr-loop/ui/src/components/TopMetric.tsx +10 -0
  128. package/plugins/autonomous-pr-loop/ui/src/fixtures.ts +1152 -0
  129. package/plugins/autonomous-pr-loop/ui/src/i18n.ts +1105 -0
  130. package/plugins/autonomous-pr-loop/ui/src/main.tsx +14 -0
  131. package/plugins/autonomous-pr-loop/ui/src/pages/CommandCenter.tsx +470 -0
  132. package/plugins/autonomous-pr-loop/ui/src/pages/CommandCenterParts.tsx +276 -0
  133. package/plugins/autonomous-pr-loop/ui/src/pages/agent-timeline/AgentTimelineView.tsx +73 -0
  134. package/plugins/autonomous-pr-loop/ui/src/pages/artifact-viewer/ArtifactViewer.tsx +44 -0
  135. package/plugins/autonomous-pr-loop/ui/src/pages/dry-run-preview/DryRunPreview.tsx +66 -0
  136. package/plugins/autonomous-pr-loop/ui/src/pages/event-ledger/EventLedger.tsx +17 -0
  137. package/plugins/autonomous-pr-loop/ui/src/pages/gate-center/GateCenter.tsx +34 -0
  138. package/plugins/autonomous-pr-loop/ui/src/pages/mission-control/MissionControl.tsx +104 -0
  139. package/plugins/autonomous-pr-loop/ui/src/pages/mission-control/WorkflowBoard.tsx +577 -0
  140. package/plugins/autonomous-pr-loop/ui/src/pages/notifications/NotificationsView.tsx +30 -0
  141. package/plugins/autonomous-pr-loop/ui/src/pages/plan-navigator/PlanNavigator.tsx +19 -0
  142. package/plugins/autonomous-pr-loop/ui/src/pages/policy-config/PolicyConfig.tsx +22 -0
  143. package/plugins/autonomous-pr-loop/ui/src/pages/pr-inbox/PrInbox.tsx +26 -0
  144. package/plugins/autonomous-pr-loop/ui/src/pages/recovery-center/RecoveryCenter.tsx +125 -0
  145. package/plugins/autonomous-pr-loop/ui/src/pages/scope-guard/ScopeGuard.tsx +16 -0
  146. package/plugins/autonomous-pr-loop/ui/src/pages/worker-runs/WorkerRuns.tsx +39 -0
  147. package/plugins/autonomous-pr-loop/ui/src/styles.css +2673 -0
  148. package/plugins/autonomous-pr-loop/ui/src/theme.ts +57 -0
  149. package/tsconfig.json +18 -0
@@ -0,0 +1,284 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { basename, join } from "node:path";
3
+ import { AgentLoopError } from "./errors.js";
4
+ import { listPullRequests, type GitHubPullRequest } from "./github.js";
5
+ import { parsePlanNavigator, type PlanNavigatorModel, type PlanPrItem } from "./plan-parser.js";
6
+ import { defaultIssueBranch, type DeliveryWorkItem } from "./delivery-work-item.js";
7
+ import type { AgentLoopConfig } from "./types.js";
8
+
9
+ export type PrSelection =
10
+ | {
11
+ mode: "current_pr";
12
+ ambiguous: false;
13
+ plan: PlanNavigatorModel;
14
+ item: PlanPrItem;
15
+ pr: GitHubPullRequest;
16
+ branchName: string;
17
+ evidence: string[];
18
+ }
19
+ | {
20
+ mode: "next_spec";
21
+ ambiguous: false;
22
+ plan: PlanNavigatorModel;
23
+ item: PlanPrItem;
24
+ branchName: string;
25
+ evidence: string[];
26
+ }
27
+ | {
28
+ mode: "ambiguous";
29
+ ambiguous: true;
30
+ plan: PlanNavigatorModel;
31
+ reason: string;
32
+ candidates: Array<Record<string, unknown>>;
33
+ evidence: string[];
34
+ };
35
+
36
+ /** Resolve the current or next PR using specs, legacy plans, and GitHub state. */
37
+ export function resolvePrSelection(
38
+ repoRoot: string,
39
+ config: AgentLoopConfig,
40
+ options: { pullRequests?: GitHubPullRequest[]; githubRequired?: boolean; workItem?: DeliveryWorkItem } = {}
41
+ ): PrSelection {
42
+ const plan = parsePlanNavigator(repoRoot, config.plansDir);
43
+ const pullRequests = options.pullRequests ?? safeListPullRequests(repoRoot, config, options.githubRequired ?? false);
44
+ const openPullRequests = pullRequests.filter((pr) => pr.state.toUpperCase() === "OPEN");
45
+ if (options.workItem) {
46
+ return explicitWorkItemSelection(config, plan, openPullRequests, options.workItem);
47
+ }
48
+ if (openPullRequests.length > 1) {
49
+ return ambiguous(plan, "Multiple open pull requests exist.", openPullRequests.map(prCandidate));
50
+ }
51
+ if (openPullRequests.length === 1) {
52
+ const pr = openPullRequests[0] as GitHubPullRequest;
53
+ const item = itemForPullRequest(plan, pr);
54
+ if (!item) {
55
+ return ambiguous(plan, "Open pull request could not be mapped to a PR spec.", [prCandidate(pr)]);
56
+ }
57
+ return {
58
+ mode: "current_pr",
59
+ ambiguous: false,
60
+ plan,
61
+ item,
62
+ pr,
63
+ branchName: pr.headRefName,
64
+ evidence: [`Mapped open PR #${pr.number} (${pr.headRefName}) to ${item.id}.`]
65
+ };
66
+ }
67
+ const nextItem = nextUncompletedItem(plan, pullRequests);
68
+ if (nextItem && !plan.ambiguous) {
69
+ return {
70
+ mode: "next_spec",
71
+ ambiguous: false,
72
+ plan,
73
+ item: nextItem,
74
+ branchName: branchNameForItem(config, nextItem),
75
+ evidence: [
76
+ ...plan.evidence,
77
+ ...mergedEvidence(pullRequests),
78
+ `Selected ${nextItem.id} as the next unresolved spec.`
79
+ ]
80
+ };
81
+ }
82
+ const legacy = legacyNextPr(repoRoot, config);
83
+ if (legacy) {
84
+ return legacy;
85
+ }
86
+ return ambiguous(plan, "Could not uniquely identify the next PR.", plan.candidates.map(itemCandidate));
87
+ }
88
+
89
+ function explicitWorkItemSelection(
90
+ config: AgentLoopConfig,
91
+ plan: PlanNavigatorModel,
92
+ openPullRequests: GitHubPullRequest[],
93
+ workItem: DeliveryWorkItem
94
+ ): PrSelection {
95
+ const item = itemForWorkItem(workItem);
96
+ const branchName = workItem.branch ?? defaultIssueBranch(workItem.issue, workItem.title, config.branchPrefix);
97
+ const pr = openPullRequests.find((candidate) => candidate.headRefName === branchName);
98
+ if (pr) {
99
+ return {
100
+ mode: "current_pr",
101
+ ambiguous: false,
102
+ plan,
103
+ item,
104
+ pr,
105
+ branchName,
106
+ evidence: [`Bound issue #${workItem.issue} matched open PR #${pr.number} (${branchName}).`]
107
+ };
108
+ }
109
+ const referencedPrs = openPullRequests.filter((candidate) => pullRequestReferencesIssue(candidate, workItem.issue));
110
+ if (referencedPrs.length === 1) {
111
+ const referencedPr = referencedPrs[0]!;
112
+ return {
113
+ mode: "current_pr",
114
+ ambiguous: false,
115
+ plan,
116
+ item,
117
+ pr: referencedPr,
118
+ branchName: referencedPr.headRefName,
119
+ evidence: [`Bound issue #${workItem.issue} matched open PR #${referencedPr.number} by issue reference.`]
120
+ };
121
+ }
122
+ if (referencedPrs.length > 1) {
123
+ return ambiguous(plan, `Multiple open pull requests reference bound issue #${workItem.issue}.`, referencedPrs.map(prCandidate));
124
+ }
125
+ return {
126
+ mode: "next_spec",
127
+ ambiguous: false,
128
+ plan,
129
+ item,
130
+ branchName,
131
+ evidence: [`Bound issue #${workItem.issue} selected as the explicit delivery work item.`]
132
+ };
133
+ }
134
+
135
+ function pullRequestReferencesIssue(pr: GitHubPullRequest, issue: number): boolean {
136
+ const text = `${pr.title ?? ""} ${pr.body ?? ""}`;
137
+ const issuePattern = new RegExp(`(^|[^0-9])#${issue}([^0-9]|$)`);
138
+ const issueSlugPattern = new RegExp(`(^|[^a-z0-9])issue-${issue}([^a-z0-9]|$)`, "i");
139
+ return issuePattern.test(text) || issueSlugPattern.test(pr.headRefName);
140
+ }
141
+
142
+ function itemForWorkItem(workItem: DeliveryWorkItem): PlanPrItem {
143
+ return {
144
+ id: `#${workItem.issue}`,
145
+ title: workItem.title,
146
+ status: "next",
147
+ file: workItem.url,
148
+ dependsOn: [],
149
+ issueRefs: [`#${workItem.issue}`],
150
+ whySelected: "Selected from explicit delivery work item binding."
151
+ };
152
+ }
153
+
154
+ export function branchNameForItem(config: AgentLoopConfig, item: PlanPrItem): string {
155
+ const fileSlug = basename(item.file, ".md");
156
+ const slug = fileSlug.match(/^pr-[a-z0-9]+-/i) ? fileSlug : item.id.toLowerCase().replace(/\s+/g, "-");
157
+ return `${config.branchPrefix}${slugify(slug)}`;
158
+ }
159
+
160
+ function safeListPullRequests(repoRoot: string, config: AgentLoopConfig, required: boolean): GitHubPullRequest[] {
161
+ try {
162
+ return listPullRequests({ repoRoot, config });
163
+ } catch (error) {
164
+ if (error instanceof AgentLoopError) {
165
+ if (required) {
166
+ throw error;
167
+ }
168
+ return [];
169
+ }
170
+ throw error;
171
+ }
172
+ }
173
+
174
+ function itemForPullRequest(plan: PlanNavigatorModel, pr: GitHubPullRequest): PlanPrItem | undefined {
175
+ const id = prIdFromBranch(pr.headRefName);
176
+ if (!id) {
177
+ return undefined;
178
+ }
179
+ return [...plan.completed, ...plan.candidates].find((item) => item.id === id);
180
+ }
181
+
182
+ function nextUncompletedItem(plan: PlanNavigatorModel, pullRequests: GitHubPullRequest[]): PlanPrItem | undefined {
183
+ if (plan.ambiguous) {
184
+ return undefined;
185
+ }
186
+ const completed = new Set([
187
+ ...plan.completed.map((item) => item.id),
188
+ ...pullRequests.flatMap((pr) => mergedPrId(pr) ?? [])
189
+ ]);
190
+ return [...plan.completed, ...plan.candidates].find((item) => !completed.has(item.id));
191
+ }
192
+
193
+ function mergedPrId(pr: GitHubPullRequest): string | undefined {
194
+ if (pr.state.toUpperCase() !== "MERGED" && !pr.mergedAt) {
195
+ return undefined;
196
+ }
197
+ return prIdFromBranch(pr.headRefName);
198
+ }
199
+
200
+ function mergedEvidence(pullRequests: GitHubPullRequest[]): string[] {
201
+ return pullRequests.flatMap((pr) => {
202
+ const id = mergedPrId(pr);
203
+ return id ? [`Observed merged PR #${pr.number} (${pr.headRefName}) as ${id}.`] : [];
204
+ });
205
+ }
206
+
207
+ function prIdFromBranch(branch: string): string | undefined {
208
+ const id = /(?:^|\/)pr-([a-z]+[0-9]*)-/i.exec(branch)?.[1]?.toUpperCase();
209
+ return id ? `PR ${id}` : undefined;
210
+ }
211
+
212
+ function legacyNextPr(repoRoot: string, config: AgentLoopConfig): PrSelection | undefined {
213
+ const path = join(repoRoot, config.plansDir);
214
+ if (!existsSync(path)) {
215
+ return undefined;
216
+ }
217
+ const files = readdirSync(path).filter((name) => /^next-pr.*\.md$/i.test(name));
218
+ if (files.length !== 1) {
219
+ return undefined;
220
+ }
221
+ const file = join(path, files[0] as string);
222
+ const id = /next-pr-([a-z0-9]+)/i.exec(files[0] as string)?.[1]?.toUpperCase() ?? "NEXT";
223
+ const item: PlanPrItem = {
224
+ id: `PR ${id}`,
225
+ title: `PR ${id}`,
226
+ status: "next",
227
+ file,
228
+ dependsOn: [],
229
+ issueRefs: [],
230
+ whySelected: "Selected from legacy next-pr plan file."
231
+ };
232
+ return {
233
+ mode: "next_spec",
234
+ ambiguous: false,
235
+ plan: {
236
+ convention: "Legacy PR docs use next-pr*.md files in the configured plans directory.",
237
+ currentMilestone: item.id,
238
+ selectedNext: item,
239
+ completed: [],
240
+ candidates: [item],
241
+ ambiguous: false,
242
+ evidence: [`Selected ${files[0]} from ${config.plansDir}.`]
243
+ },
244
+ item,
245
+ branchName: `${config.branchPrefix}${slugify(basename(files[0] as string, ".md"))}`,
246
+ evidence: [`Selected ${files[0]} from ${config.plansDir}.`]
247
+ };
248
+ }
249
+
250
+ function ambiguous(
251
+ plan: PlanNavigatorModel,
252
+ reason: string,
253
+ candidates: Array<Record<string, unknown>>
254
+ ): PrSelection {
255
+ return {
256
+ mode: "ambiguous",
257
+ ambiguous: true,
258
+ plan,
259
+ reason,
260
+ candidates,
261
+ evidence: [...plan.evidence, reason]
262
+ };
263
+ }
264
+
265
+ function prCandidate(pr: GitHubPullRequest): Record<string, unknown> {
266
+ return {
267
+ number: pr.number,
268
+ headRefName: pr.headRefName,
269
+ state: pr.state,
270
+ url: pr.url
271
+ };
272
+ }
273
+
274
+ function itemCandidate(item: PlanPrItem): Record<string, unknown> {
275
+ return {
276
+ id: item.id,
277
+ status: item.status,
278
+ file: item.file
279
+ };
280
+ }
281
+
282
+ function slugify(value: string): string {
283
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "next-pr";
284
+ }