@forwardimpact/libeval 0.1.19 → 0.1.20

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.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # libeval
2
+
3
+ Process Claude Code stream-json output into structured traces.
4
+
5
+ ## Getting Started
6
+
7
+ ```js
8
+ import { createTraceCollector, createTraceQuery, createAgentRunner } from '@forwardimpact/libeval';
9
+ ```
package/bin/fit-trace.js CHANGED
@@ -42,7 +42,8 @@ const definition = {
42
42
  },
43
43
  repo: {
44
44
  type: "string",
45
- description: "GitHub repo override (default: git remote)",
45
+ description:
46
+ "GitHub repo override (default: $GITHUB_REPOSITORY or 'origin' git remote)",
46
47
  },
47
48
  },
48
49
  },
@@ -55,7 +56,8 @@ const definition = {
55
56
  artifact: { type: "string", description: "Artifact name override" },
56
57
  repo: {
57
58
  type: "string",
58
- description: "GitHub repo override (default: git remote)",
59
+ description:
60
+ "GitHub repo override (default: $GITHUB_REPOSITORY or 'origin' git remote)",
59
61
  },
60
62
  },
61
63
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/libeval",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "Process Claude Code stream-json output into structured traces",
5
5
  "license": "Apache-2.0",
6
6
  "author": "D. Olsson <hi@senzilla.io>",
@@ -38,7 +38,7 @@ export class AgentRunner {
38
38
  * @param {function} deps.query - SDK query function (injected for testing)
39
39
  * @param {import("stream").Writable} deps.output - Stream to emit NDJSON to
40
40
  * @param {string} [deps.model] - Claude model identifier
41
- * @param {number} [deps.maxTurns] - Maximum agentic turns
41
+ * @param {number} [deps.maxTurns] - Maximum agentic turns; 0 means unlimited
42
42
  * @param {string[]} [deps.allowedTools] - Tools the agent may use
43
43
  * @param {function} [deps.onLine] - Callback invoked with each NDJSON line as it's produced
44
44
  * @param {function} [deps.onBatch] - Async callback invoked with a batch of NDJSON lines at flush boundaries: every `batchSize` assistant text blocks, the terminal `result` message, and — on iterator crash/abort — once more in a final flush carrying any lines that never reached a boundary. Receives `(lines, { abort })` where calling `abort()` stops the in-flight SDK session via the AbortController. Optional; assignable at runtime so the Supervisor can swap it per turn.
@@ -73,7 +73,8 @@ export class AgentRunner {
73
73
  options: {
74
74
  cwd: this.cwd,
75
75
  allowedTools: this.allowedTools,
76
- ...(this.maxTurns > 0 && { maxTurns: this.maxTurns }),
76
+ maxTurns:
77
+ this.maxTurns === 0 ? Number.MAX_SAFE_INTEGER : this.maxTurns,
77
78
  model: this.model,
78
79
  permissionMode: PERMISSION_MODE,
79
80
  allowDangerouslySkipPermissions: true,
package/src/index.js CHANGED
@@ -3,6 +3,7 @@ export { TraceQuery, createTraceQuery } from "./trace-query.js";
3
3
  export {
4
4
  TraceGitHub,
5
5
  createTraceGitHub,
6
+ detectRepoSlug,
6
7
  parseGitRemote,
7
8
  } from "./trace-github.js";
8
9
  export { AgentRunner, createAgentRunner } from "./agent-runner.js";
@@ -1,3 +1,4 @@
1
+ import { execSync } from "node:child_process";
1
2
  import { createWriteStream } from "node:fs";
2
3
  import { mkdir } from "node:fs/promises";
3
4
  import path from "node:path";
@@ -116,7 +117,6 @@ export class TraceGitHub {
116
117
  // Stream to disk then extract.
117
118
  await pipeline(Readable.fromWeb(response.body), createWriteStream(zipPath));
118
119
 
119
- const { execSync } = await import("node:child_process");
120
120
  execSync(
121
121
  `unzip -o -q ${JSON.stringify(zipPath)} -d ${JSON.stringify(dir)}`,
122
122
  );
@@ -182,9 +182,51 @@ export function parseGitRemote(remote) {
182
182
  const simple = remote.match(/^([^/:@]+)\/([^/]+)$/);
183
183
  if (simple) return { owner: simple[1], repo: simple[2] };
184
184
 
185
+ // Generic URL fallback: any remote whose path ends in /owner/repo(.git)?
186
+ // Covers GitHub Enterprise, proxied git URLs, and mirrors.
187
+ const generic = remote.match(/[/:]([^/:@?#]+)\/([^/:@?#]+?)(?:\.git)?\/?$/);
188
+ if (generic) return { owner: generic[1], repo: generic[2] };
189
+
185
190
  throw new Error(`Cannot parse GitHub remote: ${remote}`);
186
191
  }
187
192
 
193
+ /**
194
+ * Detect the current GitHub repository slug as `{owner, repo}`.
195
+ *
196
+ * Resolution order:
197
+ * 1. `GITHUB_REPOSITORY` env var (set automatically by GitHub Actions).
198
+ * 2. `git remote get-url origin` in the current working directory.
199
+ *
200
+ * @returns {{owner: string, repo: string}}
201
+ * @throws {Error} with a clear message if neither source yields a parseable slug.
202
+ */
203
+ export function detectRepoSlug() {
204
+ const env = process.env.GITHUB_REPOSITORY;
205
+ if (env && env.trim()) {
206
+ return parseGitRemote(env.trim());
207
+ }
208
+
209
+ let remote;
210
+ try {
211
+ remote = execSync("git remote get-url origin", {
212
+ encoding: "utf8",
213
+ stdio: ["ignore", "pipe", "ignore"],
214
+ }).trim();
215
+ } catch {
216
+ throw new Error(
217
+ "Cannot detect repository: set --repo <owner/repo>, export GITHUB_REPOSITORY, or run inside a git checkout with an 'origin' remote.",
218
+ );
219
+ }
220
+
221
+ if (!remote) {
222
+ throw new Error(
223
+ "Cannot detect repository: 'git remote get-url origin' returned an empty value. Pass --repo <owner/repo> or set GITHUB_REPOSITORY.",
224
+ );
225
+ }
226
+
227
+ return parseGitRemote(remote);
228
+ }
229
+
188
230
  /**
189
231
  * Create a TraceGitHub instance. The caller is responsible for resolving
190
232
  * the GitHub token — typically via `Config.ghToken()` — so credential
@@ -207,16 +249,9 @@ export async function createTraceGitHub(opts = {}) {
207
249
  );
208
250
  }
209
251
 
210
- let owner, repo;
211
- if (repoOverride) {
212
- ({ owner, repo } = parseGitRemote(repoOverride));
213
- } else {
214
- const { execSync } = await import("node:child_process");
215
- const remote = execSync("git remote get-url origin", {
216
- encoding: "utf8",
217
- }).trim();
218
- ({ owner, repo } = parseGitRemote(remote));
219
- }
252
+ const { owner, repo } = repoOverride
253
+ ? parseGitRemote(repoOverride)
254
+ : detectRepoSlug();
220
255
 
221
256
  return new TraceGitHub({ token, owner, repo });
222
257
  }