@bpinternal/overwatch 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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +451 -0
  3. package/dist/actuators/actuator.d.ts +13 -0
  4. package/dist/actuators/actuator.js +30 -0
  5. package/dist/actuators/actuator.js.map +7 -0
  6. package/dist/actuators/agent-pr.d.ts +25 -0
  7. package/dist/actuators/agent-pr.js +119 -0
  8. package/dist/actuators/agent-pr.js.map +7 -0
  9. package/dist/actuators/comments.d.ts +3 -0
  10. package/dist/actuators/comments.js +42 -0
  11. package/dist/actuators/comments.js.map +7 -0
  12. package/dist/actuators/index.d.ts +54 -0
  13. package/dist/actuators/index.js +40 -0
  14. package/dist/actuators/index.js.map +7 -0
  15. package/dist/actuators/instructions.d.ts +9 -0
  16. package/dist/actuators/instructions.js +62 -0
  17. package/dist/actuators/instructions.js.map +7 -0
  18. package/dist/actuators.d.ts +72 -0
  19. package/dist/actuators.js +173 -0
  20. package/dist/actuators.js.map +7 -0
  21. package/dist/agents.d.ts +53 -0
  22. package/dist/agents.js +107 -0
  23. package/dist/agents.js.map +7 -0
  24. package/dist/claims.d.ts +16 -0
  25. package/dist/claims.js +51 -0
  26. package/dist/claims.js.map +7 -0
  27. package/dist/cli.d.ts +13 -0
  28. package/dist/cli.js +94 -0
  29. package/dist/cli.js.map +7 -0
  30. package/dist/control-loop.d.ts +50 -0
  31. package/dist/control-loop.js +285 -0
  32. package/dist/control-loop.js.map +7 -0
  33. package/dist/github.d.ts +98 -0
  34. package/dist/github.js +258 -0
  35. package/dist/github.js.map +7 -0
  36. package/dist/index.d.ts +11 -0
  37. package/dist/index.js +69 -0
  38. package/dist/index.js.map +7 -0
  39. package/dist/log.d.ts +22 -0
  40. package/dist/log.js +87 -0
  41. package/dist/log.js.map +7 -0
  42. package/dist/pickers/busiest-file.d.ts +9 -0
  43. package/dist/pickers/busiest-file.js +48 -0
  44. package/dist/pickers/busiest-file.js.map +7 -0
  45. package/dist/pickers/count.d.ts +6 -0
  46. package/dist/pickers/count.js +29 -0
  47. package/dist/pickers/count.js.map +7 -0
  48. package/dist/pickers/index.d.ts +12 -0
  49. package/dist/pickers/index.js +41 -0
  50. package/dist/pickers/index.js.map +7 -0
  51. package/dist/pickers.d.ts +21 -0
  52. package/dist/pickers.js +61 -0
  53. package/dist/pickers.js.map +7 -0
  54. package/dist/repo-handle.d.ts +35 -0
  55. package/dist/repo-handle.js +75 -0
  56. package/dist/repo-handle.js.map +7 -0
  57. package/dist/sandbox.d.ts +33 -0
  58. package/dist/sandbox.js +91 -0
  59. package/dist/sandbox.js.map +7 -0
  60. package/dist/sensors/ast-grep.d.ts +41 -0
  61. package/dist/sensors/ast-grep.js +65 -0
  62. package/dist/sensors/ast-grep.js.map +7 -0
  63. package/dist/sensors/grep.d.ts +31 -0
  64. package/dist/sensors/grep.js +39 -0
  65. package/dist/sensors/grep.js.map +7 -0
  66. package/dist/sensors/index.d.ts +27 -0
  67. package/dist/sensors/index.js +38 -0
  68. package/dist/sensors/index.js.map +7 -0
  69. package/dist/sensors/react-doctor.d.ts +43 -0
  70. package/dist/sensors/react-doctor.js +71 -0
  71. package/dist/sensors/react-doctor.js.map +7 -0
  72. package/dist/sensors/script.d.ts +7 -0
  73. package/dist/sensors/script.js +32 -0
  74. package/dist/sensors/script.js.map +7 -0
  75. package/dist/sensors.d.ts +117 -0
  76. package/dist/sensors.js +126 -0
  77. package/dist/sensors.js.map +7 -0
  78. package/dist/types.d.ts +119 -0
  79. package/dist/types.js +17 -0
  80. package/dist/types.js.map +7 -0
  81. package/package.json +64 -0
package/dist/github.js ADDED
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var github_exports = {};
20
+ __export(github_exports, {
21
+ Github: () => Github
22
+ });
23
+ module.exports = __toCommonJS(github_exports);
24
+ var import_octokit = require("octokit");
25
+ class Github {
26
+ branch;
27
+ email;
28
+ repoUrl;
29
+ owner;
30
+ name;
31
+ key;
32
+ octokit;
33
+ constructor(props) {
34
+ this.repoUrl = props.repo;
35
+ this.branch = props.branch;
36
+ this.email = props.email || void 0;
37
+ this.key = props.key || void 0;
38
+ this.octokit = new import_octokit.Octokit({ auth: this.key });
39
+ const match = props.repo.match(/github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?$/);
40
+ if (!match) throw new Error(`could not parse owner/repo from "${props.repo}"`);
41
+ this.owner = match[1];
42
+ this.name = match[2];
43
+ }
44
+ async countOpenPrs(label) {
45
+ return (await this.openPrIssues(label)).length;
46
+ }
47
+ async listOpenPrBodies(label) {
48
+ return (await this.openPrIssues(label)).map((issue) => issue.body ?? "");
49
+ }
50
+ // The issues endpoint covers PRs too (a PR is an issue with a `pull_request` key)
51
+ // and, unlike /pulls, supports filtering by label server-side.
52
+ async openPrIssues(label) {
53
+ const issues = await this.octokit.paginate(this.octokit.rest.issues.listForRepo, {
54
+ owner: this.owner,
55
+ repo: this.name,
56
+ labels: label,
57
+ state: "open",
58
+ per_page: 100
59
+ });
60
+ return issues.filter((issue) => issue.pull_request);
61
+ }
62
+ async cloneInto(sandbox, path, branch) {
63
+ await sandbox.git.clone(
64
+ this.repoUrl,
65
+ path,
66
+ branch ?? this.branch,
67
+ void 0,
68
+ this.key ? "x-access-token" : void 0,
69
+ this.key
70
+ );
71
+ }
72
+ async getPr(prNumber) {
73
+ const { data: pr } = await this.octokit.rest.pulls.get({
74
+ owner: this.owner,
75
+ repo: this.name,
76
+ pull_number: prNumber
77
+ });
78
+ const { data: commit } = await this.octokit.rest.repos.getCommit({
79
+ owner: this.owner,
80
+ repo: this.name,
81
+ ref: pr.head.sha
82
+ });
83
+ const headCommittedAt = commit.commit.committer?.date ?? pr.created_at;
84
+ return { branch: pr.head.ref, headCommittedAt };
85
+ }
86
+ async listPrComments(prNumber) {
87
+ const base = { owner: this.owner, repo: this.name, per_page: 100 };
88
+ const [review, issue, resolved] = await Promise.all([
89
+ this.octokit.paginate(this.octokit.rest.pulls.listReviewComments, {
90
+ ...base,
91
+ pull_number: prNumber
92
+ }),
93
+ this.octokit.paginate(this.octokit.rest.issues.listComments, {
94
+ ...base,
95
+ issue_number: prNumber
96
+ }),
97
+ this.resolvedReviewCommentIds(prNumber)
98
+ ]);
99
+ const comments = [
100
+ ...review.filter((comment) => !resolved.has(comment.id)).map((comment) => ({
101
+ id: comment.id,
102
+ author: comment.user.login,
103
+ body: comment.body,
104
+ file: comment.path,
105
+ line: comment.line ?? comment.original_line ?? void 0,
106
+ createdAt: comment.created_at
107
+ })),
108
+ ...issue.map((comment) => ({
109
+ id: comment.id,
110
+ author: comment.user?.login ?? "unknown",
111
+ body: comment.body ?? "",
112
+ createdAt: comment.created_at
113
+ }))
114
+ ];
115
+ return comments.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
116
+ }
117
+ async push(sandbox, path, branch) {
118
+ this.requireKey("push");
119
+ const setup = await sandbox.process.executeCommand(
120
+ `printf 'https://x-access-token:%s@github.com\\n' "$GIT_TOKEN" > ~/.git-credentials && git config --global credential.helper store`,
121
+ path,
122
+ { GIT_TOKEN: this.key },
123
+ 60
124
+ );
125
+ if (setup.exitCode !== 0) {
126
+ throw new Error(`configuring git credentials failed: ${setup.result}`);
127
+ }
128
+ const quoted = branch.replace(/'/g, `'\\''`);
129
+ const maxAttempts = 5;
130
+ for (let attempt = 1; ; attempt++) {
131
+ const push = await sandbox.process.executeCommand(
132
+ `git push --set-upstream origin '${quoted}'`,
133
+ path,
134
+ void 0,
135
+ 300
136
+ );
137
+ if (push.exitCode === 0) return;
138
+ const rejected = /non-fast-forward|fetch first|\[rejected\]/i.test(push.result ?? "");
139
+ if (!rejected || attempt >= maxAttempts) {
140
+ throw new Error(`git push failed: ${push.result}`);
141
+ }
142
+ const rebase = await sandbox.process.executeCommand(
143
+ `git fetch origin '${quoted}' && git rebase 'origin/${quoted}'`,
144
+ path,
145
+ void 0,
146
+ 300
147
+ );
148
+ if (rebase.exitCode !== 0) {
149
+ await sandbox.process.executeCommand("git rebase --abort", path, void 0, 60);
150
+ throw new Error(
151
+ `git push failed: branch '${branch}' diverged and auto-rebase hit a conflict: ${rebase.result}`
152
+ );
153
+ }
154
+ }
155
+ }
156
+ async openPr(params) {
157
+ this.requireKey("open a PR");
158
+ await this.ensureLabel(params.label);
159
+ const { data: pr } = await this.octokit.rest.pulls.create({
160
+ owner: this.owner,
161
+ repo: this.name,
162
+ title: params.title,
163
+ head: params.branch,
164
+ base: this.branch,
165
+ body: params.body
166
+ });
167
+ try {
168
+ await this.retry(
169
+ () => this.octokit.rest.issues.addLabels({
170
+ owner: this.owner,
171
+ repo: this.name,
172
+ issue_number: pr.number,
173
+ labels: [params.label]
174
+ })
175
+ );
176
+ } catch (error) {
177
+ await this.closePrQuietly(pr.number);
178
+ throw new Error(
179
+ `opened PR #${pr.number} but could not label it "${params.label}"; closed it to avoid an unlabeled orphan the loop can't count or claim. Cause: ${error.message ?? String(error)}`
180
+ );
181
+ }
182
+ return pr.html_url;
183
+ }
184
+ /** Retries a request a few times; the label call must not be abandoned over a transient error. */
185
+ async retry(fn, attempts = 3) {
186
+ let lastError;
187
+ for (let i = 0; i < attempts; i++) {
188
+ try {
189
+ return await fn();
190
+ } catch (error) {
191
+ lastError = error;
192
+ }
193
+ }
194
+ throw lastError;
195
+ }
196
+ /** Best-effort PR close used to roll back a partially-created PR; a failure here is swallowed. */
197
+ async closePrQuietly(prNumber) {
198
+ try {
199
+ await this.octokit.rest.pulls.update({
200
+ owner: this.owner,
201
+ repo: this.name,
202
+ pull_number: prNumber,
203
+ state: "closed"
204
+ });
205
+ } catch {
206
+ }
207
+ }
208
+ async ensureLabel(label) {
209
+ try {
210
+ await this.octokit.rest.issues.createLabel({
211
+ owner: this.owner,
212
+ repo: this.name,
213
+ name: label,
214
+ color: "0e8a16",
215
+ description: "Opened by a control loop"
216
+ });
217
+ } catch (error) {
218
+ if (error.status !== 422) throw error;
219
+ }
220
+ }
221
+ /**
222
+ * IDs of inline review comments whose thread has been resolved. Thread resolution
223
+ * is only exposed by GitHub's GraphQL API, which always requires auth — without a
224
+ * key, nothing is filtered.
225
+ */
226
+ async resolvedReviewCommentIds(prNumber) {
227
+ const ids = /* @__PURE__ */ new Set();
228
+ if (!this.key) return ids;
229
+ const result = await this.octokit.graphql(
230
+ `query ($owner: String!, $name: String!, $number: Int!) {
231
+ repository(owner: $owner, name: $name) {
232
+ pullRequest(number: $number) {
233
+ reviewThreads(first: 100) {
234
+ nodes { isResolved comments(first: 100) { nodes { databaseId } } }
235
+ }
236
+ }
237
+ }
238
+ }`,
239
+ { owner: this.owner, name: this.name, number: prNumber }
240
+ );
241
+ for (const thread of result.repository.pullRequest.reviewThreads.nodes) {
242
+ if (!thread.isResolved) continue;
243
+ for (const comment of thread.comments.nodes) {
244
+ if (comment.databaseId !== null) ids.add(comment.databaseId);
245
+ }
246
+ }
247
+ return ids;
248
+ }
249
+ requireKey(action) {
250
+ if (!this.key)
251
+ throw new Error(`a Github key is required to ${action} on ${this.owner}/${this.name}`);
252
+ }
253
+ }
254
+ // Annotate the CommonJS export names for ESM import in node:
255
+ 0 && (module.exports = {
256
+ Github
257
+ });
258
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/github.ts"],
4
+ "sourcesContent": ["import type { Sandbox } from \"@daytona/sdk\";\nimport { Octokit } from \"octokit\";\n\n/** A human comment on a PR. `file`/`line` are present for inline review comments only. */\nexport interface PrComment {\n id: number;\n author: string;\n body: string;\n file?: string;\n line?: number;\n createdAt: string;\n}\n\n/**\n * Where the code comes from and where the PR goes. Implemented by {@link Github};\n * the interface exists so other forges (GitLab, ...) can slot in later.\n */\nexport interface GitSource {\n /** Base branch the loop clones from and targets with its PR. */\n readonly branch: string;\n /**\n * Committer email stamped on the loop's commits (the ones that make up the PR).\n * Optional \u2014 the loop falls back to a default no-reply address when unset.\n */\n readonly email?: string;\n countOpenPrs(label: string): Promise<number>;\n /** Bodies of the open PRs carrying the loop's label; used to skip already-claimed signals. */\n listOpenPrBodies(label: string): Promise<string[]>;\n /** Clones `branch` (defaults to the base branch) into the sandbox at `path`. */\n cloneInto(sandbox: Sandbox, path: string, branch?: string): Promise<void>;\n push(sandbox: Sandbox, path: string, branch: string): Promise<void>;\n openPr(params: { branch: string; title: string; body: string; label: string }): Promise<string>;\n /** Head branch of the PR and the commit date of its tip, used to skip already-addressed comments. */\n getPr(prNumber: number): Promise<{ branch: string; headCommittedAt: string }>;\n /**\n * Comments on the PR \u2014 inline review comments and PR-level comments \u2014 oldest first.\n * Inline comments whose review thread has been resolved are excluded.\n */\n listPrComments(prNumber: number): Promise<PrComment[]>;\n}\n\nexport type GithubProps = {\n /** e.g. \"https://github.com/botpress/some-repo.git\" */\n repo: string;\n branch: string;\n /**\n * GitHub token. Optional for cloning/counting PRs on public repos, but required to\n * push and open PRs \u2014 a run that reaches the PR stage without one fails there.\n */\n key?: string;\n /**\n * Committer email stamped on the loop's commits. Optional; set it to attribute the\n * PR's commits to a specific bot/user account instead of the default no-reply address.\n */\n email?: string;\n};\n\nexport class Github implements GitSource {\n readonly branch: string;\n readonly email?: string;\n private readonly repoUrl: string;\n private readonly owner: string;\n private readonly name: string;\n private readonly key?: string;\n private readonly octokit: Octokit;\n\n constructor(props: GithubProps) {\n this.repoUrl = props.repo;\n this.branch = props.branch;\n this.email = props.email || undefined;\n this.key = props.key || undefined;\n this.octokit = new Octokit({ auth: this.key });\n\n const match = props.repo.match(/github\\.com[/:]([^/]+)\\/([^/]+?)(?:\\.git)?$/);\n if (!match) throw new Error(`could not parse owner/repo from \"${props.repo}\"`);\n this.owner = match[1]!;\n this.name = match[2]!;\n }\n\n async countOpenPrs(label: string): Promise<number> {\n return (await this.openPrIssues(label)).length;\n }\n\n async listOpenPrBodies(label: string): Promise<string[]> {\n return (await this.openPrIssues(label)).map((issue) => issue.body ?? \"\");\n }\n\n // The issues endpoint covers PRs too (a PR is an issue with a `pull_request` key)\n // and, unlike /pulls, supports filtering by label server-side.\n private async openPrIssues(label: string) {\n const issues = await this.octokit.paginate(this.octokit.rest.issues.listForRepo, {\n owner: this.owner,\n repo: this.name,\n labels: label,\n state: \"open\",\n per_page: 100,\n });\n return issues.filter((issue) => issue.pull_request);\n }\n\n async cloneInto(sandbox: Sandbox, path: string, branch?: string): Promise<void> {\n await sandbox.git.clone(\n this.repoUrl,\n path,\n branch ?? this.branch,\n undefined,\n this.key ? \"x-access-token\" : undefined,\n this.key,\n );\n }\n\n async getPr(prNumber: number): Promise<{ branch: string; headCommittedAt: string }> {\n const { data: pr } = await this.octokit.rest.pulls.get({\n owner: this.owner,\n repo: this.name,\n pull_number: prNumber,\n });\n const { data: commit } = await this.octokit.rest.repos.getCommit({\n owner: this.owner,\n repo: this.name,\n ref: pr.head.sha,\n });\n const headCommittedAt = commit.commit.committer?.date ?? pr.created_at;\n return { branch: pr.head.ref, headCommittedAt };\n }\n\n async listPrComments(prNumber: number): Promise<PrComment[]> {\n const base = { owner: this.owner, repo: this.name, per_page: 100 };\n // Inline code comments live on the pulls endpoint, PR-level discussion on the\n // issues endpoint; a complete picture needs both. Thread resolution only exists\n // in the GraphQL API, so it's fetched separately to drop resolved threads.\n const [review, issue, resolved] = await Promise.all([\n this.octokit.paginate(this.octokit.rest.pulls.listReviewComments, {\n ...base,\n pull_number: prNumber,\n }),\n this.octokit.paginate(this.octokit.rest.issues.listComments, {\n ...base,\n issue_number: prNumber,\n }),\n this.resolvedReviewCommentIds(prNumber),\n ]);\n\n const comments: PrComment[] = [\n ...review\n .filter((comment) => !resolved.has(comment.id))\n .map((comment) => ({\n id: comment.id,\n author: comment.user.login,\n body: comment.body,\n file: comment.path,\n line: comment.line ?? comment.original_line ?? undefined,\n createdAt: comment.created_at,\n })),\n ...issue.map((comment) => ({\n id: comment.id,\n author: comment.user?.login ?? \"unknown\",\n body: comment.body ?? \"\",\n createdAt: comment.created_at,\n })),\n ];\n return comments.sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n }\n\n async push(sandbox: Sandbox, path: string, branch: string): Promise<void> {\n this.requireKey(\"push\");\n // Daytona's toolbox git is go-git, which rejects committed paths it deems invalid\n // (e.g. a file named `\\` left behind by an agent) \u2014 pushing 500s with\n // `invalid path: \"\\\\\"`. The real git binary handles any filename, so push with it.\n // The token goes through git's credential store via an env var, keeping it off the\n // command line and out of error output.\n const setup = await sandbox.process.executeCommand(\n `printf 'https://x-access-token:%s@github.com\\\\n' \"$GIT_TOKEN\" > ~/.git-credentials && git config --global credential.helper store`,\n path,\n { GIT_TOKEN: this.key! },\n 60,\n );\n if (setup.exitCode !== 0) {\n throw new Error(`configuring git credentials failed: ${setup.result}`);\n }\n // Push, rebasing onto the remote tip and retrying if the branch moved under us.\n // Two comment webhooks for the same PR clone the same head independently, so the\n // second push is rejected non-fast-forward; without this, that run's work would be\n // silently lost when its sandbox is torn down. Replaying our commits onto the new\n // tip preserves both updates. A bounded loop guards against a branch churning faster\n // than we can push.\n const quoted = branch.replace(/'/g, `'\\\\''`);\n const maxAttempts = 5;\n for (let attempt = 1; ; attempt++) {\n const push = await sandbox.process.executeCommand(\n `git push --set-upstream origin '${quoted}'`,\n path,\n undefined,\n 300,\n );\n if (push.exitCode === 0) return;\n\n const rejected = /non-fast-forward|fetch first|\\[rejected\\]/i.test(push.result ?? \"\");\n if (!rejected || attempt >= maxAttempts) {\n throw new Error(`git push failed: ${push.result}`);\n }\n\n const rebase = await sandbox.process.executeCommand(\n `git fetch origin '${quoted}' && git rebase 'origin/${quoted}'`,\n path,\n undefined,\n 300,\n );\n if (rebase.exitCode !== 0) {\n // A real content conflict can't be resolved unattended; abort to leave the tree\n // clean and surface it, rather than force-pushing over the other update.\n await sandbox.process.executeCommand(\"git rebase --abort\", path, undefined, 60);\n throw new Error(\n `git push failed: branch '${branch}' diverged and auto-rebase hit a conflict: ${rebase.result}`,\n );\n }\n }\n }\n\n async openPr(params: {\n branch: string;\n title: string;\n body: string;\n label: string;\n }): Promise<string> {\n this.requireKey(\"open a PR\");\n await this.ensureLabel(params.label);\n const { data: pr } = await this.octokit.rest.pulls.create({\n owner: this.owner,\n repo: this.name,\n title: params.title,\n head: params.branch,\n base: this.branch,\n body: params.body,\n });\n\n // The loop counts and discovers its PRs purely by label (see `openPrIssues`), so a PR\n // that exists without the label is invisible: the next run would miss it, exceed\n // `maxOpenPrCount`, and reopen the same signals. Labeling can't be made atomic with\n // creation (the REST API has no combined call), so retry it to ride out a transient\n // blip without discarding the agent's work, and if it still fails, close the PR rather\n // than leave an unlabeled orphan \u2014 the signals stay unclaimed and a later run retries.\n try {\n await this.retry(() =>\n this.octokit.rest.issues.addLabels({\n owner: this.owner,\n repo: this.name,\n issue_number: pr.number,\n labels: [params.label],\n }),\n );\n } catch (error) {\n await this.closePrQuietly(pr.number);\n throw new Error(\n `opened PR #${pr.number} but could not label it \"${params.label}\"; closed it to avoid an ` +\n `unlabeled orphan the loop can't count or claim. Cause: ${(error as Error).message ?? String(error)}`,\n );\n }\n return pr.html_url;\n }\n\n /** Retries a request a few times; the label call must not be abandoned over a transient error. */\n private async retry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {\n let lastError: unknown;\n for (let i = 0; i < attempts; i++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n }\n }\n throw lastError;\n }\n\n /** Best-effort PR close used to roll back a partially-created PR; a failure here is swallowed. */\n private async closePrQuietly(prNumber: number): Promise<void> {\n try {\n await this.octokit.rest.pulls.update({\n owner: this.owner,\n repo: this.name,\n pull_number: prNumber,\n state: \"closed\",\n });\n } catch {\n // Nothing we can do \u2014 the caller is already throwing the underlying labeling error.\n }\n }\n\n private async ensureLabel(label: string): Promise<void> {\n try {\n await this.octokit.rest.issues.createLabel({\n owner: this.owner,\n repo: this.name,\n name: label,\n color: \"0e8a16\",\n description: \"Opened by a control loop\",\n });\n } catch (error) {\n // 422 = label already exists; anything else is a real failure.\n if ((error as { status?: number }).status !== 422) throw error;\n }\n }\n\n /**\n * IDs of inline review comments whose thread has been resolved. Thread resolution\n * is only exposed by GitHub's GraphQL API, which always requires auth \u2014 without a\n * key, nothing is filtered.\n */\n private async resolvedReviewCommentIds(prNumber: number): Promise<Set<number>> {\n const ids = new Set<number>();\n if (!this.key) return ids;\n\n const result = await this.octokit.graphql<{\n repository: {\n pullRequest: {\n reviewThreads: {\n nodes: Array<{\n isResolved: boolean;\n comments: { nodes: Array<{ databaseId: number | null }> };\n }>;\n };\n };\n };\n }>(\n `query ($owner: String!, $name: String!, $number: Int!) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n reviewThreads(first: 100) {\n nodes { isResolved comments(first: 100) { nodes { databaseId } } }\n }\n }\n }\n }`,\n { owner: this.owner, name: this.name, number: prNumber },\n );\n\n for (const thread of result.repository.pullRequest.reviewThreads.nodes) {\n if (!thread.isResolved) continue;\n for (const comment of thread.comments.nodes) {\n if (comment.databaseId !== null) ids.add(comment.databaseId);\n }\n }\n return ids;\n }\n\n private requireKey(action: string): void {\n if (!this.key)\n throw new Error(`a Github key is required to ${action} on ${this.owner}/${this.name}`);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,11 @@
1
+ export { ControlLoop } from "./control-loop";
2
+ export { Agent, Claude, Codex, type AgentContext, type ClaudeProps, type CodexProps, } from "./agents";
3
+ export { Github, type GitSource, type GithubProps, type PrComment } from "./github";
4
+ export { RepoHandle, type ExecResult, type GrepMatch } from "./repo-handle";
5
+ export * as actuators from "./actuators";
6
+ export { Actuator, AgentPrActuator, type ActuatorInput, type AgentPrActuatorProps, type CommentActuator, type InstructionBuilder, } from "./actuators";
7
+ export * as pickers from "./pickers";
8
+ export { type Picker } from "./pickers";
9
+ export * as sensors from "./sensors";
10
+ export { type AstGrepMatch, type AstGrepOptions, type GrepOptions, type ReactDoctorDiagnostic, type ReactDoctorOptions, type Sensor, type SensorFn, type SensorScript, } from "./sensors";
11
+ export { type ApplyCommentsResult, type ControlLoopConfig, type ControlLoopOptions, type ControlLoopRunResult, type Signal, type SignalPriority, } from "./types";
package/dist/index.js ADDED
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var index_exports = {};
30
+ __export(index_exports, {
31
+ Actuator: () => import_actuators.Actuator,
32
+ Agent: () => import_agents.Agent,
33
+ AgentPrActuator: () => import_actuators.AgentPrActuator,
34
+ Claude: () => import_agents.Claude,
35
+ Codex: () => import_agents.Codex,
36
+ ControlLoop: () => import_control_loop.ControlLoop,
37
+ Github: () => import_github.Github,
38
+ RepoHandle: () => import_repo_handle.RepoHandle,
39
+ actuators: () => actuators,
40
+ pickers: () => pickers,
41
+ sensors: () => sensors
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+ var import_control_loop = require("./control-loop");
45
+ var import_agents = require("./agents");
46
+ var import_github = require("./github");
47
+ var import_repo_handle = require("./repo-handle");
48
+ var actuators = __toESM(require("./actuators"));
49
+ var import_actuators = require("./actuators");
50
+ var pickers = __toESM(require("./pickers"));
51
+ var import_pickers = require("./pickers");
52
+ var sensors = __toESM(require("./sensors"));
53
+ var import_sensors = require("./sensors");
54
+ var import_types = require("./types");
55
+ // Annotate the CommonJS export names for ESM import in node:
56
+ 0 && (module.exports = {
57
+ Actuator,
58
+ Agent,
59
+ AgentPrActuator,
60
+ Claude,
61
+ Codex,
62
+ ControlLoop,
63
+ Github,
64
+ RepoHandle,
65
+ actuators,
66
+ pickers,
67
+ sensors
68
+ });
69
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["export { ControlLoop } from \"./control-loop\";\nexport {\n Agent,\n Claude,\n Codex,\n type AgentContext,\n type ClaudeProps,\n type CodexProps,\n} from \"./agents\";\nexport { Github, type GitSource, type GithubProps, type PrComment } from \"./github\";\nexport { RepoHandle, type ExecResult, type GrepMatch } from \"./repo-handle\";\n\nexport * as actuators from \"./actuators\";\nexport {\n Actuator,\n AgentPrActuator,\n type ActuatorInput,\n type AgentPrActuatorProps,\n type CommentActuator,\n type InstructionBuilder,\n} from \"./actuators\";\n\nexport * as pickers from \"./pickers\";\nexport { type Picker } from \"./pickers\";\n\nexport * as sensors from \"./sensors\";\nexport {\n type AstGrepMatch,\n type AstGrepOptions,\n type GrepOptions,\n type ReactDoctorDiagnostic,\n type ReactDoctorOptions,\n type Sensor,\n type SensorFn,\n type SensorScript,\n} from \"./sensors\";\n\nexport {\n type ApplyCommentsResult,\n type ControlLoopConfig,\n type ControlLoopOptions,\n type ControlLoopRunResult,\n type Signal,\n type SignalPriority,\n} from \"./types\";\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAA4B;AAC5B,oBAOO;AACP,oBAAyE;AACzE,yBAA4D;AAE5D,gBAA2B;AAC3B,uBAOO;AAEP,cAAyB;AACzB,qBAA4B;AAE5B,cAAyB;AACzB,qBASO;AAEP,mBAOO;",
6
+ "names": []
7
+ }
package/dist/log.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Tiny, dependency-free run logger. It narrates — to stderr — what a control-loop run
3
+ * is doing to its sandbox, so you can see where the sandbox is at each step. stderr is
4
+ * used so it never mixes with a caller that parses the loop's return value off stdout.
5
+ *
6
+ * Muted with CONTROL_LOOP_SILENT=1; colors follow NO_COLOR and TTY detection.
7
+ */
8
+ export declare class RunLog {
9
+ private scope;
10
+ constructor(label: string);
11
+ /** Once the sandbox exists, tag every subsequent line with its (short) id. */
12
+ attach(sandboxId: string): void;
13
+ /** Wraps an async op: prints "→ label", then "✓ label (elapsed)" — or "✗ label" if it throws. */
14
+ step<T>(label: string, fn: () => Promise<T>): Promise<T>;
15
+ /** A neutral status line — counts, picks, and other in-between context. */
16
+ info(message: string): void;
17
+ /** The run bowed out early (nothing to do, cap hit). */
18
+ skip(message: string): void;
19
+ /** The run's terminal outcome. */
20
+ finish(message: string): void;
21
+ private line;
22
+ }
package/dist/log.js ADDED
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var log_exports = {};
20
+ __export(log_exports, {
21
+ RunLog: () => RunLog
22
+ });
23
+ module.exports = __toCommonJS(log_exports);
24
+ const enabled = process.env.CONTROL_LOOP_SILENT !== "1" && process.env.CONTROL_LOOP_SILENT !== "true";
25
+ const color = enabled && Boolean(process.stderr.isTTY) && !process.env.NO_COLOR;
26
+ const paint = (code) => (text) => color ? `\x1B[${code}m${text}\x1B[0m` : text;
27
+ const dim = paint("2");
28
+ const bold = paint("1");
29
+ const cyan = paint("36");
30
+ const green = paint("32");
31
+ const yellow = paint("33");
32
+ const red = paint("31");
33
+ const magenta = paint("35");
34
+ function elapsed(startMs) {
35
+ const ms = Date.now() - startMs;
36
+ return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
37
+ }
38
+ const PENDING = "\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7";
39
+ class RunLog {
40
+ scope = PENDING;
41
+ constructor(label) {
42
+ if (!enabled) return;
43
+ process.stderr.write(`
44
+ ${dim("\u2501\u2501")} ${bold("control-loop")} ${dim("\xB7")} ${cyan(label)}
45
+ `);
46
+ }
47
+ /** Once the sandbox exists, tag every subsequent line with its (short) id. */
48
+ attach(sandboxId) {
49
+ this.scope = sandboxId.slice(0, 8);
50
+ this.line(magenta("\u25C6"), `sandbox ${bold(this.scope)} ready`);
51
+ }
52
+ /** Wraps an async op: prints "→ label", then "✓ label (elapsed)" — or "✗ label" if it throws. */
53
+ async step(label, fn) {
54
+ const start = Date.now();
55
+ this.line(cyan("\u2192"), label);
56
+ try {
57
+ const result = await fn();
58
+ this.line(green("\u2713"), `${label} ${dim(`(${elapsed(start)})`)}`);
59
+ return result;
60
+ } catch (error) {
61
+ this.line(red("\u2717"), `${label} ${dim(`(${elapsed(start)})`)}`);
62
+ throw error;
63
+ }
64
+ }
65
+ /** A neutral status line — counts, picks, and other in-between context. */
66
+ info(message) {
67
+ this.line(dim("\xB7"), dim(message));
68
+ }
69
+ /** The run bowed out early (nothing to do, cap hit). */
70
+ skip(message) {
71
+ this.line(yellow("\u23ED"), yellow(message));
72
+ }
73
+ /** The run's terminal outcome. */
74
+ finish(message) {
75
+ this.line(green("\u25CF"), bold(message));
76
+ }
77
+ line(symbol, message) {
78
+ if (!enabled) return;
79
+ process.stderr.write(` ${dim(this.scope)} ${symbol} ${message}
80
+ `);
81
+ }
82
+ }
83
+ // Annotate the CommonJS export names for ESM import in node:
84
+ 0 && (module.exports = {
85
+ RunLog
86
+ });
87
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/log.ts"],
4
+ "sourcesContent": ["/**\n * Tiny, dependency-free run logger. It narrates \u2014 to stderr \u2014 what a control-loop run\n * is doing to its sandbox, so you can see where the sandbox is at each step. stderr is\n * used so it never mixes with a caller that parses the loop's return value off stdout.\n *\n * Muted with CONTROL_LOOP_SILENT=1; colors follow NO_COLOR and TTY detection.\n */\n\nconst enabled = process.env.CONTROL_LOOP_SILENT !== \"1\" && process.env.CONTROL_LOOP_SILENT !== \"true\";\nconst color = enabled && Boolean(process.stderr.isTTY) && !process.env.NO_COLOR;\n\nconst paint = (code: string) => (text: string) => (color ? `\\x1b[${code}m${text}\\x1b[0m` : text);\nconst dim = paint(\"2\");\nconst bold = paint(\"1\");\nconst cyan = paint(\"36\");\nconst green = paint(\"32\");\nconst yellow = paint(\"33\");\nconst red = paint(\"31\");\nconst magenta = paint(\"35\");\n\nfunction elapsed(startMs: number): string {\n const ms = Date.now() - startMs;\n return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;\n}\n\n/** Placeholder scope shown before the sandbox exists (nothing to point at yet). */\nconst PENDING = \"\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\u00B7\";\n\nexport class RunLog {\n private scope = PENDING;\n\n constructor(label: string) {\n if (!enabled) return;\n process.stderr.write(`\\n${dim(\"\u2501\u2501\")} ${bold(\"control-loop\")} ${dim(\"\u00B7\")} ${cyan(label)}\\n`);\n }\n\n /** Once the sandbox exists, tag every subsequent line with its (short) id. */\n attach(sandboxId: string): void {\n this.scope = sandboxId.slice(0, 8);\n this.line(magenta(\"\u25C6\"), `sandbox ${bold(this.scope)} ready`);\n }\n\n /** Wraps an async op: prints \"\u2192 label\", then \"\u2713 label (elapsed)\" \u2014 or \"\u2717 label\" if it throws. */\n async step<T>(label: string, fn: () => Promise<T>): Promise<T> {\n const start = Date.now();\n this.line(cyan(\"\u2192\"), label);\n try {\n const result = await fn();\n this.line(green(\"\u2713\"), `${label} ${dim(`(${elapsed(start)})`)}`);\n return result;\n } catch (error) {\n this.line(red(\"\u2717\"), `${label} ${dim(`(${elapsed(start)})`)}`);\n throw error;\n }\n }\n\n /** A neutral status line \u2014 counts, picks, and other in-between context. */\n info(message: string): void {\n this.line(dim(\"\u00B7\"), dim(message));\n }\n\n /** The run bowed out early (nothing to do, cap hit). */\n skip(message: string): void {\n this.line(yellow(\"\u23ED\"), yellow(message));\n }\n\n /** The run's terminal outcome. */\n finish(message: string): void {\n this.line(green(\"\u25CF\"), bold(message));\n }\n\n private line(symbol: string, message: string): void {\n if (!enabled) return;\n process.stderr.write(` ${dim(this.scope)} ${symbol} ${message}\\n`);\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,MAAM,UAAU,QAAQ,IAAI,wBAAwB,OAAO,QAAQ,IAAI,wBAAwB;AAC/F,MAAM,QAAQ,WAAW,QAAQ,QAAQ,OAAO,KAAK,KAAK,CAAC,QAAQ,IAAI;AAEvE,MAAM,QAAQ,CAAC,SAAiB,CAAC,SAAkB,QAAQ,QAAQ,IAAI,IAAI,IAAI,YAAY;AAC3F,MAAM,MAAM,MAAM,GAAG;AACrB,MAAM,OAAO,MAAM,GAAG;AACtB,MAAM,OAAO,MAAM,IAAI;AACvB,MAAM,QAAQ,MAAM,IAAI;AACxB,MAAM,SAAS,MAAM,IAAI;AACzB,MAAM,MAAM,MAAM,IAAI;AACtB,MAAM,UAAU,MAAM,IAAI;AAE1B,SAAS,QAAQ,SAAyB;AACxC,QAAM,KAAK,KAAK,IAAI,IAAI;AACxB,SAAO,KAAK,MAAO,GAAG,EAAE,OAAO,IAAI,KAAK,KAAM,QAAQ,CAAC,CAAC;AAC1D;AAGA,MAAM,UAAU;AAET,MAAM,OAAO;AAAA,EACV,QAAQ;AAAA,EAEhB,YAAY,OAAe;AACzB,QAAI,CAAC,QAAS;AACd,YAAQ,OAAO,MAAM;AAAA,EAAK,IAAI,cAAI,CAAC,IAAI,KAAK,cAAc,CAAC,IAAI,IAAI,MAAG,CAAC,IAAI,KAAK,KAAK,CAAC;AAAA,CAAI;AAAA,EAC5F;AAAA;AAAA,EAGA,OAAO,WAAyB;AAC9B,SAAK,QAAQ,UAAU,MAAM,GAAG,CAAC;AACjC,SAAK,KAAK,QAAQ,QAAG,GAAG,WAAW,KAAK,KAAK,KAAK,CAAC,QAAQ;AAAA,EAC7D;AAAA;AAAA,EAGA,MAAM,KAAQ,OAAe,IAAkC;AAC7D,UAAM,QAAQ,KAAK,IAAI;AACvB,SAAK,KAAK,KAAK,QAAG,GAAG,KAAK;AAC1B,QAAI;AACF,YAAM,SAAS,MAAM,GAAG;AACxB,WAAK,KAAK,MAAM,QAAG,GAAG,GAAG,KAAK,IAAI,IAAI,IAAI,QAAQ,KAAK,CAAC,GAAG,CAAC,EAAE;AAC9D,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,KAAK,IAAI,QAAG,GAAG,GAAG,KAAK,IAAI,IAAI,IAAI,QAAQ,KAAK,CAAC,GAAG,CAAC,EAAE;AAC5D,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA,EAGA,KAAK,SAAuB;AAC1B,SAAK,KAAK,IAAI,MAAG,GAAG,IAAI,OAAO,CAAC;AAAA,EAClC;AAAA;AAAA,EAGA,KAAK,SAAuB;AAC1B,SAAK,KAAK,OAAO,QAAG,GAAG,OAAO,OAAO,CAAC;AAAA,EACxC;AAAA;AAAA,EAGA,OAAO,SAAuB;AAC5B,SAAK,KAAK,MAAM,QAAG,GAAG,KAAK,OAAO,CAAC;AAAA,EACrC;AAAA,EAEQ,KAAK,QAAgB,SAAuB;AAClD,QAAI,CAAC,QAAS;AACd,YAAQ,OAAO,MAAM,KAAK,IAAI,KAAK,KAAK,CAAC,KAAK,MAAM,IAAI,OAAO;AAAA,CAAI;AAAA,EACrE;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,9 @@
1
+ import type { Picker } from "./index";
2
+ /**
3
+ * Picker: takes every signal belonging to the single busiest file — the file with
4
+ * the most signals this run. Focuses one run on one file so all its fixes land in a
5
+ * single, coherent PR. Ties are broken toward the file whose first signal appears
6
+ * earliest in sensor order. Signals without a `location` have no file to group by and
7
+ * are ignored (they can never be the busiest file).
8
+ */
9
+ export declare const busiestFile: <TData = unknown>() => Picker<TData>;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var busiest_file_exports = {};
20
+ __export(busiest_file_exports, {
21
+ busiestFile: () => busiestFile
22
+ });
23
+ module.exports = __toCommonJS(busiest_file_exports);
24
+ const busiestFile = () => (signals) => {
25
+ const byFile = /* @__PURE__ */ new Map();
26
+ for (const signal of signals) {
27
+ const file = signal.location?.file;
28
+ if (file === void 0) continue;
29
+ const group = byFile.get(file);
30
+ if (group) {
31
+ group.push(signal);
32
+ } else {
33
+ byFile.set(file, [signal]);
34
+ }
35
+ }
36
+ let winner = [];
37
+ for (const group of byFile.values()) {
38
+ if (group.length > winner.length) {
39
+ winner = group;
40
+ }
41
+ }
42
+ return winner;
43
+ };
44
+ // Annotate the CommonJS export names for ESM import in node:
45
+ 0 && (module.exports = {
46
+ busiestFile
47
+ });
48
+ //# sourceMappingURL=busiest-file.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/pickers/busiest-file.ts"],
4
+ "sourcesContent": ["import type { Signal } from \"../types\";\nimport type { Picker } from \"./index\";\n\n/**\n * Picker: takes every signal belonging to the single busiest file \u2014 the file with\n * the most signals this run. Focuses one run on one file so all its fixes land in a\n * single, coherent PR. Ties are broken toward the file whose first signal appears\n * earliest in sensor order. Signals without a `location` have no file to group by and\n * are ignored (they can never be the busiest file).\n */\nexport const busiestFile =\n <TData = unknown>(): Picker<TData> =>\n (signals: Signal<TData>[]) => {\n const byFile = new Map<string, Signal<TData>[]>();\n for (const signal of signals) {\n const file = signal.location?.file;\n if (file === undefined) continue;\n const group = byFile.get(file);\n if (group) {\n group.push(signal);\n } else {\n byFile.set(file, [signal]);\n }\n }\n\n let winner: Signal<TData>[] = [];\n for (const group of byFile.values()) {\n if (group.length > winner.length) {\n winner = group;\n }\n }\n return winner;\n };\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAUO,MAAM,cACX,MACA,CAAC,YAA6B;AAC5B,QAAM,SAAS,oBAAI,IAA6B;AAChD,aAAW,UAAU,SAAS;AAC5B,UAAM,OAAO,OAAO,UAAU;AAC9B,QAAI,SAAS,OAAW;AACxB,UAAM,QAAQ,OAAO,IAAI,IAAI;AAC7B,QAAI,OAAO;AACT,YAAM,KAAK,MAAM;AAAA,IACnB,OAAO;AACL,aAAO,IAAI,MAAM,CAAC,MAAM,CAAC;AAAA,IAC3B;AAAA,EACF;AAEA,MAAI,SAA0B,CAAC;AAC/B,aAAW,SAAS,OAAO,OAAO,GAAG;AACnC,QAAI,MAAM,SAAS,OAAO,QAAQ;AAChC,eAAS;AAAA,IACX;AAAA,EACF;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,6 @@
1
+ import type { Picker } from "./index";
2
+ /**
3
+ * Default picker: takes the first `count` signals in sensor order.
4
+ * Sort inside your sensor (or write your own picker) to prioritize differently.
5
+ */
6
+ export declare const count: <TData = unknown>(count: number) => Picker<TData>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var count_exports = {};
20
+ __export(count_exports, {
21
+ count: () => count
22
+ });
23
+ module.exports = __toCommonJS(count_exports);
24
+ const count = (count2) => (signals) => signals.slice(0, count2);
25
+ // Annotate the CommonJS export names for ESM import in node:
26
+ 0 && (module.exports = {
27
+ count
28
+ });
29
+ //# sourceMappingURL=count.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/pickers/count.ts"],
4
+ "sourcesContent": ["import type { Signal } from \"../types\";\nimport type { Picker } from \"./index\";\n\n/**\n * Default picker: takes the first `count` signals in sensor order.\n * Sort inside your sensor (or write your own picker) to prioritize differently.\n */\nexport const count =\n <TData = unknown>(count: number): Picker<TData> =>\n (signals: Signal<TData>[]) =>\n signals.slice(0, count);\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOO,MAAM,QACX,CAAkBA,WAClB,CAAC,YACC,QAAQ,MAAM,GAAGA,MAAK;",
6
+ "names": ["count"]
7
+ }
@@ -0,0 +1,12 @@
1
+ import type { Signal } from "../types";
2
+ /** Chooses which of the sensed signals to act on this run. */
3
+ export type Picker<TData = unknown> = (signals: Signal<TData>[]) => Promise<Signal<TData>[]> | Signal<TData>[];
4
+ export { count } from "./count";
5
+ export { busiestFile } from "./busiest-file";
6
+ /**
7
+ * Runs pickers left-to-right, each narrowing the output of the previous — e.g.
8
+ * `chain(busiestFile(), count(3))` focuses one file, then caps it at 3 signals.
9
+ * Order matters: every step only sees what the one before it kept. With no steps
10
+ * it passes the signals through untouched.
11
+ */
12
+ export declare const chain: <TData = unknown>(...steps: Picker<TData>[]) => Picker<TData>;