@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/agents.js ADDED
@@ -0,0 +1,107 @@
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 agents_exports = {};
20
+ __export(agents_exports, {
21
+ Agent: () => Agent,
22
+ Claude: () => Claude,
23
+ Codex: () => Codex
24
+ });
25
+ module.exports = __toCommonJS(agents_exports);
26
+ class Agent {
27
+ /**
28
+ * Called once per run, before any {@link executeAgent} call. Install the agent's CLI
29
+ * here if the sandbox image doesn't already have it.
30
+ */
31
+ async prepare(_ctx) {
32
+ }
33
+ }
34
+ const PROMPT_FILE = ".control-loop/prompt.txt";
35
+ async function stagePrompt(instructions, ctx) {
36
+ await ctx.writeFile(PROMPT_FILE, instructions);
37
+ return `"$(cat ${PROMPT_FILE})"`;
38
+ }
39
+ class Claude extends Agent {
40
+ constructor(props) {
41
+ super();
42
+ this.props = props;
43
+ }
44
+ async prepare(ctx) {
45
+ const check = await ctx.exec("command -v claude");
46
+ if (check.exitCode === 0) return;
47
+ const install = await ctx.exec("npm install -g @anthropic-ai/claude-code", { timeoutSec: 600 });
48
+ if (install.exitCode !== 0) {
49
+ throw new Error(`failed to install claude CLI: ${install.output}`);
50
+ }
51
+ }
52
+ async executeAgent(instructions, ctx) {
53
+ const prompt = await stagePrompt(instructions, ctx);
54
+ const model = this.props.model ? ` --model ${this.props.model}` : "";
55
+ const result = await ctx.exec(`claude -p ${prompt}${model} --dangerously-skip-permissions`, {
56
+ env: { ANTHROPIC_API_KEY: this.props.apiKey },
57
+ timeoutSec: this.props.timeoutSec ?? 900
58
+ });
59
+ if (result.exitCode !== 0) {
60
+ throw new Error(`claude exited ${result.exitCode}: ${result.output}`);
61
+ }
62
+ }
63
+ }
64
+ class Codex extends Agent {
65
+ constructor(props) {
66
+ super();
67
+ this.props = props;
68
+ }
69
+ async prepare(ctx) {
70
+ const check = await ctx.exec("command -v codex");
71
+ if (check.exitCode !== 0) {
72
+ const install = await ctx.exec("npm install -g @openai/codex", { timeoutSec: 600 });
73
+ if (install.exitCode !== 0) {
74
+ throw new Error(`failed to install codex CLI: ${install.output}`);
75
+ }
76
+ }
77
+ const env = { OPENAI_API_KEY: this.props.apiKey };
78
+ const login = await ctx.exec(`printenv OPENAI_API_KEY | codex login --with-api-key`, { env });
79
+ if (login.exitCode !== 0) {
80
+ const legacy = await ctx.exec(`codex login --api-key "$OPENAI_API_KEY"`, { env });
81
+ if (legacy.exitCode !== 0) {
82
+ throw new Error(`codex login failed: ${login.output} ${legacy.output}`);
83
+ }
84
+ }
85
+ }
86
+ async executeAgent(instructions, ctx) {
87
+ const prompt = await stagePrompt(instructions, ctx);
88
+ const model = this.props.model ? ` --model ${this.props.model}` : "";
89
+ const result = await ctx.exec(
90
+ `codex exec ${prompt}${model} --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check </dev/null`,
91
+ {
92
+ env: { OPENAI_API_KEY: this.props.apiKey },
93
+ timeoutSec: this.props.timeoutSec ?? 900
94
+ }
95
+ );
96
+ if (result.exitCode !== 0) {
97
+ throw new Error(`codex exited ${result.exitCode}: ${result.output}`);
98
+ }
99
+ }
100
+ }
101
+ // Annotate the CommonJS export names for ESM import in node:
102
+ 0 && (module.exports = {
103
+ Agent,
104
+ Claude,
105
+ Codex
106
+ });
107
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/agents.ts"],
4
+ "sourcesContent": ["/**\n * The slice of the sandbox an agent is allowed to touch. Commands run with the repo\n * root as cwd; file paths are relative to the repo root.\n */\nexport interface AgentContext {\n exec: (command: string, options?: { env?: Record<string, string>; timeoutSec?: number }) => Promise<{\n exitCode: number;\n output: string;\n }>;\n writeFile: (path: string, content: string) => Promise<void>;\n}\n\n/**\n * A coding agent that can be driven headlessly inside the sandbox. Implementations\n * know how to install their own CLI and how to invoke it with a prompt.\n */\nexport abstract class Agent {\n /**\n * Called once per run, before any {@link executeAgent} call. Install the agent's CLI\n * here if the sandbox image doesn't already have it.\n */\n async prepare(_ctx: AgentContext): Promise<void> {}\n\n /** Runs the agent against the repo with the given instructions, applying edits in place. */\n abstract executeAgent(instructions: string, ctx: AgentContext): Promise<void>;\n}\n\nconst PROMPT_FILE = \".control-loop/prompt.txt\";\n\n/**\n * Writes the prompt to a scratch file and returns a shell fragment that expands to its\n * content. Instructions contain sensor/user text; going through a file avoids re-parsing\n * shell metacharacters inside them.\n */\nasync function stagePrompt(instructions: string, ctx: AgentContext): Promise<string> {\n await ctx.writeFile(PROMPT_FILE, instructions);\n return `\"$(cat ${PROMPT_FILE})\"`;\n}\n\nexport type ClaudeProps = {\n apiKey: string;\n /** e.g. \"claude-sonnet-5\". Defaults to the CLI's default model. */\n model?: string;\n /** Max seconds for one agent invocation. Default 900. */\n timeoutSec?: number;\n};\n\nexport class Claude extends Agent {\n constructor(private readonly props: ClaudeProps) {\n super();\n }\n\n override async prepare(ctx: AgentContext): Promise<void> {\n const check = await ctx.exec(\"command -v claude\");\n if (check.exitCode === 0) return;\n const install = await ctx.exec(\"npm install -g @anthropic-ai/claude-code\", { timeoutSec: 600 });\n if (install.exitCode !== 0) {\n throw new Error(`failed to install claude CLI: ${install.output}`);\n }\n }\n\n override async executeAgent(instructions: string, ctx: AgentContext): Promise<void> {\n const prompt = await stagePrompt(instructions, ctx);\n const model = this.props.model ? ` --model ${this.props.model}` : \"\";\n const result = await ctx.exec(`claude -p ${prompt}${model} --dangerously-skip-permissions`, {\n env: { ANTHROPIC_API_KEY: this.props.apiKey },\n timeoutSec: this.props.timeoutSec ?? 900,\n });\n if (result.exitCode !== 0) {\n throw new Error(`claude exited ${result.exitCode}: ${result.output}`);\n }\n }\n}\n\nexport type CodexProps = {\n apiKey: string;\n /** e.g. \"gpt-5.1-codex\". Defaults to the CLI's default model. */\n model?: string;\n /** Max seconds for one agent invocation. Default 900. */\n timeoutSec?: number;\n};\n\nexport class Codex extends Agent {\n constructor(private readonly props: CodexProps) {\n super();\n }\n\n override async prepare(ctx: AgentContext): Promise<void> {\n const check = await ctx.exec(\"command -v codex\");\n if (check.exitCode !== 0) {\n const install = await ctx.exec(\"npm install -g @openai/codex\", { timeoutSec: 600 });\n if (install.exitCode !== 0) {\n throw new Error(`failed to install codex CLI: ${install.output}`);\n }\n }\n\n // Recent Codex CLI versions ignore OPENAI_API_KEY in the environment and require\n // an auth file created by `codex login` (\"Missing bearer or basic authentication\n // in header\" otherwise). Piping through printenv keeps the key out of the command\n // line; older CLIs without --with-api-key fall back to the deprecated flag.\n const env = { OPENAI_API_KEY: this.props.apiKey };\n const login = await ctx.exec(`printenv OPENAI_API_KEY | codex login --with-api-key`, { env });\n if (login.exitCode !== 0) {\n const legacy = await ctx.exec(`codex login --api-key \"$OPENAI_API_KEY\"`, { env });\n if (legacy.exitCode !== 0) {\n throw new Error(`codex login failed: ${login.output} ${legacy.output}`);\n }\n }\n }\n\n override async executeAgent(instructions: string, ctx: AgentContext): Promise<void> {\n const prompt = await stagePrompt(instructions, ctx);\n const model = this.props.model ? ` --model ${this.props.model}` : \"\";\n // Codex's own sandbox (--sandbox workspace-write) relies on Landlock/seccomp, which\n // fails inside containers \u2014 and the Daytona sandbox already isolates everything, so\n // bypassing it is what Codex documents for CI/container use. </dev/null stops\n // `codex exec` from waiting on extra stdin input.\n const result = await ctx.exec(\n `codex exec ${prompt}${model} --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check </dev/null`,\n {\n env: { OPENAI_API_KEY: this.props.apiKey },\n timeoutSec: this.props.timeoutSec ?? 900,\n },\n );\n if (result.exitCode !== 0) {\n throw new Error(`codex exited ${result.exitCode}: ${result.output}`);\n }\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBO,MAAe,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,EAK1B,MAAM,QAAQ,MAAmC;AAAA,EAAC;AAIpD;AAEA,MAAM,cAAc;AAOpB,eAAe,YAAY,cAAsB,KAAoC;AACnF,QAAM,IAAI,UAAU,aAAa,YAAY;AAC7C,SAAO,UAAU,WAAW;AAC9B;AAUO,MAAM,eAAe,MAAM;AAAA,EAChC,YAA6B,OAAoB;AAC/C,UAAM;AADqB;AAAA,EAE7B;AAAA,EAEA,MAAe,QAAQ,KAAkC;AACvD,UAAM,QAAQ,MAAM,IAAI,KAAK,mBAAmB;AAChD,QAAI,MAAM,aAAa,EAAG;AAC1B,UAAM,UAAU,MAAM,IAAI,KAAK,4CAA4C,EAAE,YAAY,IAAI,CAAC;AAC9F,QAAI,QAAQ,aAAa,GAAG;AAC1B,YAAM,IAAI,MAAM,iCAAiC,QAAQ,MAAM,EAAE;AAAA,IACnE;AAAA,EACF;AAAA,EAEA,MAAe,aAAa,cAAsB,KAAkC;AAClF,UAAM,SAAS,MAAM,YAAY,cAAc,GAAG;AAClD,UAAM,QAAQ,KAAK,MAAM,QAAQ,YAAY,KAAK,MAAM,KAAK,KAAK;AAClE,UAAM,SAAS,MAAM,IAAI,KAAK,aAAa,MAAM,GAAG,KAAK,mCAAmC;AAAA,MAC1F,KAAK,EAAE,mBAAmB,KAAK,MAAM,OAAO;AAAA,MAC5C,YAAY,KAAK,MAAM,cAAc;AAAA,IACvC,CAAC;AACD,QAAI,OAAO,aAAa,GAAG;AACzB,YAAM,IAAI,MAAM,iBAAiB,OAAO,QAAQ,KAAK,OAAO,MAAM,EAAE;AAAA,IACtE;AAAA,EACF;AACF;AAUO,MAAM,cAAc,MAAM;AAAA,EAC/B,YAA6B,OAAmB;AAC9C,UAAM;AADqB;AAAA,EAE7B;AAAA,EAEA,MAAe,QAAQ,KAAkC;AACvD,UAAM,QAAQ,MAAM,IAAI,KAAK,kBAAkB;AAC/C,QAAI,MAAM,aAAa,GAAG;AACxB,YAAM,UAAU,MAAM,IAAI,KAAK,gCAAgC,EAAE,YAAY,IAAI,CAAC;AAClF,UAAI,QAAQ,aAAa,GAAG;AAC1B,cAAM,IAAI,MAAM,gCAAgC,QAAQ,MAAM,EAAE;AAAA,MAClE;AAAA,IACF;AAMA,UAAM,MAAM,EAAE,gBAAgB,KAAK,MAAM,OAAO;AAChD,UAAM,QAAQ,MAAM,IAAI,KAAK,wDAAwD,EAAE,IAAI,CAAC;AAC5F,QAAI,MAAM,aAAa,GAAG;AACxB,YAAM,SAAS,MAAM,IAAI,KAAK,2CAA2C,EAAE,IAAI,CAAC;AAChF,UAAI,OAAO,aAAa,GAAG;AACzB,cAAM,IAAI,MAAM,uBAAuB,MAAM,MAAM,IAAI,OAAO,MAAM,EAAE;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAe,aAAa,cAAsB,KAAkC;AAClF,UAAM,SAAS,MAAM,YAAY,cAAc,GAAG;AAClD,UAAM,QAAQ,KAAK,MAAM,QAAQ,YAAY,KAAK,MAAM,KAAK,KAAK;AAKlE,UAAM,SAAS,MAAM,IAAI;AAAA,MACvB,cAAc,MAAM,GAAG,KAAK;AAAA,MAC5B;AAAA,QACE,KAAK,EAAE,gBAAgB,KAAK,MAAM,OAAO;AAAA,QACzC,YAAY,KAAK,MAAM,cAAc;AAAA,MACvC;AAAA,IACF;AACA,QAAI,OAAO,aAAa,GAAG;AACzB,YAAM,IAAI,MAAM,gBAAgB,OAAO,QAAQ,KAAK,OAAO,MAAM,EAAE;AAAA,IACrE;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ import type { Signal } from "./types";
2
+ /** Signals are matched across sensor runs by file + message; line numbers shift after edits. */
3
+ export declare function signalKey(signal: Signal): string;
4
+ /**
5
+ * Open PRs "claim" the signals they fix via an invisible marker in their body, so
6
+ * concurrent runs (maxOpenPrCount > 1) don't fix the same signals twice while earlier
7
+ * PRs are still unmerged. A merged PR removes the signals at the source (the sensor
8
+ * stops reporting them); a closed-unmerged PR releases its claims automatically.
9
+ *
10
+ * The write half (`claimMarker`) lives with the PR-opening actuator; the read half
11
+ * (`parseClaimedKeys`) is used by the loop when filtering sensed signals — both share
12
+ * the marker string, so they stay in this one file.
13
+ */
14
+ export declare const CLAIM_MARKER = "control-loop:claimed-signals";
15
+ export declare function claimMarker(signals: Signal[]): string;
16
+ export declare function parseClaimedKeys(body: string): string[];
package/dist/claims.js ADDED
@@ -0,0 +1,51 @@
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 claims_exports = {};
20
+ __export(claims_exports, {
21
+ CLAIM_MARKER: () => CLAIM_MARKER,
22
+ claimMarker: () => claimMarker,
23
+ parseClaimedKeys: () => parseClaimedKeys,
24
+ signalKey: () => signalKey
25
+ });
26
+ module.exports = __toCommonJS(claims_exports);
27
+ function signalKey(signal) {
28
+ return `${signal.location?.file ?? ""} ${signal.message}`;
29
+ }
30
+ const CLAIM_MARKER = "control-loop:claimed-signals";
31
+ function claimMarker(signals) {
32
+ return `<!-- ${CLAIM_MARKER} ${JSON.stringify(signals.map(signalKey))} -->`;
33
+ }
34
+ function parseClaimedKeys(body) {
35
+ const match = body.match(new RegExp(`<!-- ${CLAIM_MARKER} (\\[.*?\\]) -->`, "s"));
36
+ if (!match) return [];
37
+ try {
38
+ const parsed = JSON.parse(match[1]);
39
+ return Array.isArray(parsed) ? parsed.filter((key) => typeof key === "string") : [];
40
+ } catch {
41
+ return [];
42
+ }
43
+ }
44
+ // Annotate the CommonJS export names for ESM import in node:
45
+ 0 && (module.exports = {
46
+ CLAIM_MARKER,
47
+ claimMarker,
48
+ parseClaimedKeys,
49
+ signalKey
50
+ });
51
+ //# sourceMappingURL=claims.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/claims.ts"],
4
+ "sourcesContent": ["import type { Signal } from \"./types\";\n\n/** Signals are matched across sensor runs by file + message; line numbers shift after edits. */\nexport function signalKey(signal: Signal): string {\n return `${signal.location?.file ?? \"\"} ${signal.message}`;\n}\n\n/**\n * Open PRs \"claim\" the signals they fix via an invisible marker in their body, so\n * concurrent runs (maxOpenPrCount > 1) don't fix the same signals twice while earlier\n * PRs are still unmerged. A merged PR removes the signals at the source (the sensor\n * stops reporting them); a closed-unmerged PR releases its claims automatically.\n *\n * The write half (`claimMarker`) lives with the PR-opening actuator; the read half\n * (`parseClaimedKeys`) is used by the loop when filtering sensed signals \u2014 both share\n * the marker string, so they stay in this one file.\n */\nexport const CLAIM_MARKER = \"control-loop:claimed-signals\";\n\nexport function claimMarker(signals: Signal[]): string {\n return `<!-- ${CLAIM_MARKER} ${JSON.stringify(signals.map(signalKey))} -->`;\n}\n\nexport function parseClaimedKeys(body: string): string[] {\n const match = body.match(new RegExp(`<!-- ${CLAIM_MARKER} (\\\\[.*?\\\\]) -->`, \"s\"));\n if (!match) return [];\n try {\n const parsed = JSON.parse(match[1]!) as unknown;\n return Array.isArray(parsed) ? parsed.filter((key): key is string => typeof key === \"string\") : [];\n } catch {\n return [];\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,SAAS,UAAU,QAAwB;AAChD,SAAO,GAAG,OAAO,UAAU,QAAQ,EAAE,IAAI,OAAO,OAAO;AACzD;AAYO,MAAM,eAAe;AAErB,SAAS,YAAY,SAA2B;AACrD,SAAO,QAAQ,YAAY,IAAI,KAAK,UAAU,QAAQ,IAAI,SAAS,CAAC,CAAC;AACvE;AAEO,SAAS,iBAAiB,MAAwB;AACvD,QAAM,QAAQ,KAAK,MAAM,IAAI,OAAO,QAAQ,YAAY,oBAAoB,GAAG,CAAC;AAChF,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,MAAM,CAAC,CAAE;AACnC,WAAO,MAAM,QAAQ,MAAM,IAAI,OAAO,OAAO,CAAC,QAAuB,OAAO,QAAQ,QAAQ,IAAI,CAAC;AAAA,EACnG,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;",
6
+ "names": []
7
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { ControlLoop } from "./control-loop";
2
+ /**
3
+ * Builds and runs the loop's built-in CLI. Exposed as `loop.cli()` — see that method for
4
+ * the intended usage. Kept in its own module so `control-loop.ts` doesn't carry the
5
+ * command-parsing and output-formatting weight.
6
+ *
7
+ * The `run` command exits non-zero when a fix couldn't be verified (`fix-failed`), so a CI
8
+ * job surfaces it; any thrown error also exits non-zero. Every other outcome exits zero.
9
+ */
10
+ export declare function runCli<TData>(loop: ControlLoop<TData>, meta: {
11
+ name: string;
12
+ description: string;
13
+ }, argv?: string[]): Promise<void>;
package/dist/cli.js ADDED
@@ -0,0 +1,94 @@
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 cli_exports = {};
20
+ __export(cli_exports, {
21
+ runCli: () => runCli
22
+ });
23
+ module.exports = __toCommonJS(cli_exports);
24
+ var import_commander = require("commander");
25
+ async function runCli(loop, meta, argv = process.argv) {
26
+ const program = new import_commander.Command();
27
+ program.name(meta.name).description(meta.description).showHelpAfterError();
28
+ program.command("run").description("Run one full cycle: sense \u2192 pick \u2192 actuate (the default actuator opens a PR).").action(async () => {
29
+ const result = await loop.run();
30
+ printRunResult(result);
31
+ if (result.status === "fix-failed") process.exitCode = 1;
32
+ });
33
+ program.command("apply-comments").description("Apply new review comments on a PR to its branch and push the fix in place.").argument("<pr>", "number of the pull request to read new comments from").action(async (pr) => {
34
+ const prNumber = Number(pr);
35
+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
36
+ throw new Error(`expected a positive PR number, got "${pr}"`);
37
+ }
38
+ printApplyResult(await loop.applyPrComments(prNumber));
39
+ });
40
+ program.addHelpText(
41
+ "after",
42
+ `
43
+ Examples:
44
+ $ ${meta.name} run
45
+ $ ${meta.name} apply-comments 1234
46
+ `
47
+ );
48
+ if (argv.slice(2).length === 0) {
49
+ program.outputHelp();
50
+ return;
51
+ }
52
+ try {
53
+ await program.parseAsync(argv);
54
+ } catch (error) {
55
+ console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
56
+ process.exitCode = 1;
57
+ }
58
+ }
59
+ function printRunResult(result) {
60
+ switch (result.status) {
61
+ case "skipped":
62
+ console.log(`skipped: ${result.reason}`);
63
+ break;
64
+ case "clean":
65
+ console.log("clean \u2014 no signals to act on");
66
+ break;
67
+ case "fix-failed":
68
+ console.log(
69
+ `fix failed \u2014 ${result.unresolved.length} signal(s) still present after all attempts`
70
+ );
71
+ break;
72
+ case "pr-opened":
73
+ console.log(`PR opened: ${result.prUrl} (${result.fixed.length} signal(s) fixed)`);
74
+ break;
75
+ }
76
+ }
77
+ function printApplyResult(result) {
78
+ switch (result.status) {
79
+ case "no-new-comments":
80
+ console.log("no comments newer than the PR head \u2014 nothing to apply");
81
+ break;
82
+ case "no-changes":
83
+ console.log(`applied ${result.comments.length} comment(s) \u2014 the agent left the tree untouched`);
84
+ break;
85
+ case "comments-applied":
86
+ console.log(`applied ${result.comments.length} comment(s) \u2192 pushed to ${result.branch}`);
87
+ break;
88
+ }
89
+ }
90
+ // Annotate the CommonJS export names for ESM import in node:
91
+ 0 && (module.exports = {
92
+ runCli
93
+ });
94
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/cli.ts"],
4
+ "sourcesContent": ["import { Command } from \"commander\";\nimport type { ControlLoop } from \"./control-loop\";\nimport type { ApplyCommentsResult, ControlLoopRunResult } from \"./types\";\n\n/**\n * Builds and runs the loop's built-in CLI. Exposed as `loop.cli()` \u2014 see that method for\n * the intended usage. Kept in its own module so `control-loop.ts` doesn't carry the\n * command-parsing and output-formatting weight.\n *\n * The `run` command exits non-zero when a fix couldn't be verified (`fix-failed`), so a CI\n * job surfaces it; any thrown error also exits non-zero. Every other outcome exits zero.\n */\nexport async function runCli<TData>(\n loop: ControlLoop<TData>,\n meta: { name: string; description: string },\n argv: string[] = process.argv,\n): Promise<void> {\n const program = new Command();\n program.name(meta.name).description(meta.description).showHelpAfterError();\n\n program\n .command(\"run\")\n .description(\"Run one full cycle: sense \u2192 pick \u2192 actuate (the default actuator opens a PR).\")\n .action(async () => {\n const result = await loop.run();\n printRunResult(result);\n if (result.status === \"fix-failed\") process.exitCode = 1;\n });\n\n program\n .command(\"apply-comments\")\n .description(\"Apply new review comments on a PR to its branch and push the fix in place.\")\n .argument(\"<pr>\", \"number of the pull request to read new comments from\")\n .action(async (pr: string) => {\n const prNumber = Number(pr);\n if (!Number.isInteger(prNumber) || prNumber <= 0) {\n throw new Error(`expected a positive PR number, got \"${pr}\"`);\n }\n printApplyResult(await loop.applyPrComments(prNumber));\n });\n\n program.addHelpText(\n \"after\",\n `\\nExamples:\\n $ ${meta.name} run\\n $ ${meta.name} apply-comments 1234\\n`,\n );\n\n // No subcommand \u2192 show help rather than silently doing nothing.\n if (argv.slice(2).length === 0) {\n program.outputHelp();\n return;\n }\n\n try {\n await program.parseAsync(argv);\n } catch (error) {\n console.error(`error: ${error instanceof Error ? error.message : String(error)}`);\n process.exitCode = 1;\n }\n}\n\nfunction printRunResult(result: ControlLoopRunResult): void {\n switch (result.status) {\n case \"skipped\":\n console.log(`skipped: ${result.reason}`);\n break;\n case \"clean\":\n console.log(\"clean \u2014 no signals to act on\");\n break;\n case \"fix-failed\":\n console.log(\n `fix failed \u2014 ${result.unresolved.length} signal(s) still present after all attempts`,\n );\n break;\n case \"pr-opened\":\n console.log(`PR opened: ${result.prUrl} (${result.fixed.length} signal(s) fixed)`);\n break;\n }\n}\n\nfunction printApplyResult(result: ApplyCommentsResult): void {\n switch (result.status) {\n case \"no-new-comments\":\n console.log(\"no comments newer than the PR head \u2014 nothing to apply\");\n break;\n case \"no-changes\":\n console.log(`applied ${result.comments.length} comment(s) \u2014 the agent left the tree untouched`);\n break;\n case \"comments-applied\":\n console.log(`applied ${result.comments.length} comment(s) \u2192 pushed to ${result.branch}`);\n break;\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAwB;AAYxB,eAAsB,OACpB,MACA,MACA,OAAiB,QAAQ,MACV;AACf,QAAM,UAAU,IAAI,yBAAQ;AAC5B,UAAQ,KAAK,KAAK,IAAI,EAAE,YAAY,KAAK,WAAW,EAAE,mBAAmB;AAEzE,UACG,QAAQ,KAAK,EACb,YAAY,yFAA+E,EAC3F,OAAO,YAAY;AAClB,UAAM,SAAS,MAAM,KAAK,IAAI;AAC9B,mBAAe,MAAM;AACrB,QAAI,OAAO,WAAW,aAAc,SAAQ,WAAW;AAAA,EACzD,CAAC;AAEH,UACG,QAAQ,gBAAgB,EACxB,YAAY,4EAA4E,EACxF,SAAS,QAAQ,sDAAsD,EACvE,OAAO,OAAO,OAAe;AAC5B,UAAM,WAAW,OAAO,EAAE;AAC1B,QAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,YAAY,GAAG;AAChD,YAAM,IAAI,MAAM,uCAAuC,EAAE,GAAG;AAAA,IAC9D;AACA,qBAAiB,MAAM,KAAK,gBAAgB,QAAQ,CAAC;AAAA,EACvD,CAAC;AAEH,UAAQ;AAAA,IACN;AAAA,IACA;AAAA;AAAA,MAAoB,KAAK,IAAI;AAAA,MAAa,KAAK,IAAI;AAAA;AAAA,EACrD;AAGA,MAAI,KAAK,MAAM,CAAC,EAAE,WAAW,GAAG;AAC9B,YAAQ,WAAW;AACnB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,QAAQ,WAAW,IAAI;AAAA,EAC/B,SAAS,OAAO;AACd,YAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AAChF,YAAQ,WAAW;AAAA,EACrB;AACF;AAEA,SAAS,eAAe,QAAoC;AAC1D,UAAQ,OAAO,QAAQ;AAAA,IACrB,KAAK;AACH,cAAQ,IAAI,YAAY,OAAO,MAAM,EAAE;AACvC;AAAA,IACF,KAAK;AACH,cAAQ,IAAI,mCAA8B;AAC1C;AAAA,IACF,KAAK;AACH,cAAQ;AAAA,QACN,qBAAgB,OAAO,WAAW,MAAM;AAAA,MAC1C;AACA;AAAA,IACF,KAAK;AACH,cAAQ,IAAI,cAAc,OAAO,KAAK,KAAK,OAAO,MAAM,MAAM,mBAAmB;AACjF;AAAA,EACJ;AACF;AAEA,SAAS,iBAAiB,QAAmC;AAC3D,UAAQ,OAAO,QAAQ;AAAA,IACrB,KAAK;AACH,cAAQ,IAAI,4DAAuD;AACnE;AAAA,IACF,KAAK;AACH,cAAQ,IAAI,WAAW,OAAO,SAAS,MAAM,sDAAiD;AAC9F;AAAA,IACF,KAAK;AACH,cAAQ,IAAI,WAAW,OAAO,SAAS,MAAM,gCAA2B,OAAO,MAAM,EAAE;AACvF;AAAA,EACJ;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,50 @@
1
+ import type { ApplyCommentsResult, ControlLoopOptions, ControlLoopRunResult } from "./types";
2
+ export declare class ControlLoop<TData = unknown> {
3
+ private readonly options;
4
+ constructor(options: ControlLoopOptions<TData>);
5
+ /**
6
+ * Executes one full cycle: sense -> pick -> run actuator -> verify -> PR.
7
+ * Invoke this from whatever scheduler you use (cron, CI); the loop itself
8
+ * does not stay resident.
9
+ */
10
+ run(): Promise<ControlLoopRunResult<TData>>;
11
+ /**
12
+ * Creates a sandbox, clones `branch` (default: the base branch), configures git, and
13
+ * runs the `setup` hook — the provisioning every entry point shares — then invokes `fn`
14
+ * with the ready sandbox and its {@link AgentContext}. The sandbox is always torn down
15
+ * afterward, including when provisioning or `fn` throws.
16
+ */
17
+ private withProvisionedSandbox;
18
+ /**
19
+ * Provisions a sandbox the way {@link run} does up through the `setup` hook — clone,
20
+ * configure git, run `config.hooks.setup` — then captures its filesystem as a Daytona
21
+ * snapshot named `name` and returns, without sensing, touching the agent, or opening a
22
+ * PR. Pass the same `name` as `config.sandbox.snapshot` on later runs so they start from
23
+ * the baked-in toolchain instead of re-running `setup` every time.
24
+ *
25
+ * Call this instead of `run` (e.g. as a one-off provisioning step); `name` is required.
26
+ */
27
+ saveSnapshot(name: string): Promise<void>;
28
+ /**
29
+ * Handler for PR comment events: reads the comments left on the PR since its last
30
+ * commit, has the agent apply them on the PR's branch, and pushes — updating the PR
31
+ * in place. Wire this to your webhook (or poller) for PR comment activity.
32
+ *
33
+ * Comments older than the PR's head commit are treated as already addressed, which
34
+ * makes repeated triggers for the same comment thread safe.
35
+ */
36
+ applyPrComments(prNumber: number): Promise<ApplyCommentsResult>;
37
+ /**
38
+ * Ready-made CLI for driving this loop from CI (or a shell). Declare the loop, then call
39
+ * this once at the end of the file:
40
+ *
41
+ * const loop = new ControlLoop({ ... });
42
+ * loop.cli();
43
+ *
44
+ * Two subcommands: `run` (one full cycle → {@link run}) and `apply-comments <pr>`
45
+ * (apply new PR review comments → {@link applyPrComments}). Run with `--help` for usage.
46
+ * Parses `process.argv` by default; the process exits non-zero on error or an unresolved
47
+ * fix, so CI can gate on it.
48
+ */
49
+ cli(argv?: string[]): Promise<void>;
50
+ }