@harness-engineering/orchestrator 0.8.4 → 0.9.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.
package/dist/index.mjs CHANGED
@@ -2209,11 +2209,10 @@ var WorkflowLoader = class {
2209
2209
  };
2210
2210
 
2211
2211
  // src/tracker/adapters/roadmap.ts
2212
- import * as fs8 from "fs/promises";
2213
2212
  import { createHash as createHash2 } from "crypto";
2214
2213
  import {
2215
- parseRoadmap,
2216
- serializeRoadmap,
2214
+ resolveRoadmapStoreForFile,
2215
+ applyRoadmapDiff,
2217
2216
  claim as claimFeature,
2218
2217
  setStatus as setFeatureStatus,
2219
2218
  isClaimableBy
@@ -2249,8 +2248,7 @@ var RoadmapTrackerAdapter = class {
2249
2248
  async fetchIssuesByStates(stateNames) {
2250
2249
  try {
2251
2250
  if (!this.config.filePath) return Err3(new Error("Missing filePath"));
2252
- const content = await fs8.readFile(this.config.filePath, "utf-8");
2253
- const roadmapResult = parseRoadmap(content);
2251
+ const roadmapResult = await this.loadRoadmap();
2254
2252
  if (!roadmapResult.ok) return roadmapResult;
2255
2253
  const issues = [];
2256
2254
  for (const milestone of roadmapResult.value.milestones) {
@@ -2281,17 +2279,19 @@ var RoadmapTrackerAdapter = class {
2281
2279
  if (!terminal) {
2282
2280
  return Err3(new Error("Tracker config has no terminalStates; cannot mark complete"));
2283
2281
  }
2284
- const content = await fs8.readFile(this.config.filePath, "utf-8");
2285
- const roadmapResult = parseRoadmap(content);
2282
+ const store = this.resolveStore();
2283
+ const roadmapResult = await store.load();
2286
2284
  if (!roadmapResult.ok) return roadmapResult;
2287
2285
  const roadmap = roadmapResult.value;
2286
+ const before = structuredClone(roadmap);
2288
2287
  const target = this.findFeatureById(roadmap.milestones, issueId);
2289
2288
  if (!target) return Ok4(void 0);
2290
2289
  const normalizedTerminal = this.config.terminalStates.map((s) => s.toLowerCase());
2291
2290
  if (normalizedTerminal.includes(target.status.toLowerCase())) return Ok4(void 0);
2292
2291
  const now = (/* @__PURE__ */ new Date()).toISOString();
2293
2292
  setFeatureStatus(roadmap, target, terminal, now.slice(0, 10));
2294
- await fs8.writeFile(this.config.filePath, serializeRoadmap(roadmap), "utf-8");
2293
+ const persisted = await applyRoadmapDiff(store, before, roadmap);
2294
+ if (!persisted.ok) return persisted;
2295
2295
  return Ok4(void 0);
2296
2296
  } catch (error) {
2297
2297
  return Err3(error instanceof Error ? error : new Error(String(error)));
@@ -2305,10 +2305,11 @@ var RoadmapTrackerAdapter = class {
2305
2305
  async claimIssue(issueId, orchestratorId) {
2306
2306
  try {
2307
2307
  if (!this.config.filePath) return Err3(new Error("Missing filePath"));
2308
- const content = await fs8.readFile(this.config.filePath, "utf-8");
2309
- const roadmapResult = parseRoadmap(content);
2308
+ const store = this.resolveStore();
2309
+ const roadmapResult = await store.load();
2310
2310
  if (!roadmapResult.ok) return roadmapResult;
2311
2311
  const roadmap = roadmapResult.value;
2312
+ const before = structuredClone(roadmap);
2312
2313
  const target = this.findFeatureById(roadmap.milestones, issueId);
2313
2314
  if (!target) return Ok4(void 0);
2314
2315
  if (!isClaimableBy(target, orchestratorId)) {
@@ -2320,7 +2321,8 @@ var RoadmapTrackerAdapter = class {
2320
2321
  const now = (/* @__PURE__ */ new Date()).toISOString();
2321
2322
  claimFeature(roadmap, target, orchestratorId, now.slice(0, 10));
2322
2323
  target.updatedAt = now;
2323
- await fs8.writeFile(this.config.filePath, serializeRoadmap(roadmap), "utf-8");
2324
+ const persisted = await applyRoadmapDiff(store, before, roadmap);
2325
+ if (!persisted.ok) return persisted;
2324
2326
  return Ok4(void 0);
2325
2327
  } catch (error) {
2326
2328
  return Err3(error instanceof Error ? error : new Error(String(error)));
@@ -2337,10 +2339,11 @@ var RoadmapTrackerAdapter = class {
2337
2339
  if (!activeState) {
2338
2340
  return Err3(new Error("Tracker config has no activeStates; cannot release"));
2339
2341
  }
2340
- const content = await fs8.readFile(this.config.filePath, "utf-8");
2341
- const roadmapResult = parseRoadmap(content);
2342
+ const store = this.resolveStore();
2343
+ const roadmapResult = await store.load();
2342
2344
  if (!roadmapResult.ok) return roadmapResult;
2343
2345
  const roadmap = roadmapResult.value;
2346
+ const before = structuredClone(roadmap);
2344
2347
  const target = this.findFeatureById(roadmap.milestones, issueId);
2345
2348
  if (!target) return Ok4(void 0);
2346
2349
  if (this.config.activeStates.includes(target.status) && target.assignee === null) {
@@ -2349,12 +2352,26 @@ var RoadmapTrackerAdapter = class {
2349
2352
  const now = (/* @__PURE__ */ new Date()).toISOString();
2350
2353
  setFeatureStatus(roadmap, target, activeState, now.slice(0, 10));
2351
2354
  target.updatedAt = null;
2352
- await fs8.writeFile(this.config.filePath, serializeRoadmap(roadmap), "utf-8");
2355
+ const persisted = await applyRoadmapDiff(store, before, roadmap);
2356
+ if (!persisted.ok) return persisted;
2353
2357
  return Ok4(void 0);
2354
2358
  } catch (error) {
2355
2359
  return Err3(error instanceof Error ? error : new Error(String(error)));
2356
2360
  }
2357
2361
  }
2362
+ /**
2363
+ * Resolve the roadmap store anchored on the configured aggregate file path.
2364
+ * The shard backend is chosen when a sibling `roadmap.d/` exists next to the
2365
+ * file; otherwise the monolith file is read/written whole. Callers guard
2366
+ * `this.config.filePath` before invoking.
2367
+ */
2368
+ resolveStore() {
2369
+ return resolveRoadmapStoreForFile({ roadmapPath: this.config.filePath });
2370
+ }
2371
+ /** Load the roadmap via the store (sharded or monolith), returning a Result. */
2372
+ loadRoadmap() {
2373
+ return this.resolveStore().load();
2374
+ }
2358
2375
  findFeatureById(milestones, issueId) {
2359
2376
  for (const milestone of milestones) {
2360
2377
  for (const feature of milestone.features) {
@@ -2371,8 +2388,7 @@ var RoadmapTrackerAdapter = class {
2371
2388
  async fetchIssueStatesByIds(issueIds) {
2372
2389
  try {
2373
2390
  if (!this.config.filePath) return Err3(new Error("Missing filePath"));
2374
- const content = await fs8.readFile(this.config.filePath, "utf-8");
2375
- const roadmapResult = parseRoadmap(content);
2391
+ const roadmapResult = await this.loadRoadmap();
2376
2392
  if (!roadmapResult.ok) return roadmapResult;
2377
2393
  const issueMap = /* @__PURE__ */ new Map();
2378
2394
  for (const milestone of roadmapResult.value.milestones) {
@@ -2427,7 +2443,57 @@ var RoadmapTrackerAdapter = class {
2427
2443
  };
2428
2444
 
2429
2445
  // src/tracker/extensions/linear.ts
2430
- import { Ok as Ok5 } from "@harness-engineering/types";
2446
+ import { Ok as Ok5, Err as Err4 } from "@harness-engineering/types";
2447
+ var LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
2448
+ var LinearGraphQLClient = class {
2449
+ apiKey;
2450
+ endpoint;
2451
+ fetchFn;
2452
+ constructor(opts) {
2453
+ this.apiKey = opts.apiKey;
2454
+ this.endpoint = opts.endpoint ?? LINEAR_GRAPHQL_ENDPOINT;
2455
+ this.fetchFn = opts.fetchFn ?? globalThis.fetch;
2456
+ }
2457
+ async query(query, variables) {
2458
+ let res;
2459
+ try {
2460
+ res = await this.fetchFn(this.endpoint, {
2461
+ method: "POST",
2462
+ headers: {
2463
+ Authorization: this.apiKey,
2464
+ "Content-Type": "application/json"
2465
+ },
2466
+ body: JSON.stringify({ query, variables: variables ?? {} })
2467
+ });
2468
+ } catch (err) {
2469
+ return Err4(
2470
+ new Error(
2471
+ `Linear GraphQL request failed: ${err instanceof Error ? err.message : String(err)}`
2472
+ )
2473
+ );
2474
+ }
2475
+ if (!res.ok) {
2476
+ const body = await res.text().catch(() => "");
2477
+ const detail = body ? `: ${body.slice(0, 500)}` : "";
2478
+ return Err4(new Error(`Linear GraphQL HTTP ${res.status}${detail}`));
2479
+ }
2480
+ let envelope;
2481
+ try {
2482
+ envelope = await res.json();
2483
+ } catch (err) {
2484
+ return Err4(
2485
+ new Error(
2486
+ `Linear GraphQL response was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
2487
+ )
2488
+ );
2489
+ }
2490
+ if (envelope.errors && envelope.errors.length > 0) {
2491
+ const message = envelope.errors.map((e) => e.message ?? "unknown error").join("; ");
2492
+ return Err4(new Error(`Linear GraphQL error: ${message}`));
2493
+ }
2494
+ return Ok5(envelope.data ?? {});
2495
+ }
2496
+ };
2431
2497
  var LinearGraphQLStub = class {
2432
2498
  async query(query, _variables) {
2433
2499
  console.log("Linear GraphQL query (stub):", query);
@@ -2436,11 +2502,11 @@ var LinearGraphQLStub = class {
2436
2502
  };
2437
2503
 
2438
2504
  // src/workspace/manager.ts
2439
- import * as fs9 from "fs/promises";
2505
+ import * as fs8 from "fs/promises";
2440
2506
  import * as path8 from "path";
2441
2507
  import { execFile as execFile2 } from "child_process";
2442
2508
  import { promisify as promisify2 } from "util";
2443
- import { Ok as Ok6, Err as Err4 } from "@harness-engineering/types";
2509
+ import { Ok as Ok6, Err as Err5 } from "@harness-engineering/types";
2444
2510
  var WorkspaceManager = class _WorkspaceManager {
2445
2511
  config;
2446
2512
  /** Absolute path to the git repository root (resolved lazily). */
@@ -2476,7 +2542,7 @@ var WorkspaceManager = class _WorkspaceManager {
2476
2542
  async getRepoRoot() {
2477
2543
  if (this.repoRoot) return this.repoRoot;
2478
2544
  const root = path8.resolve(this.config.root);
2479
- await fs9.mkdir(root, { recursive: true });
2545
+ await fs8.mkdir(root, { recursive: true });
2480
2546
  const stdout = await this.git(["rev-parse", "--show-toplevel"], root);
2481
2547
  this.repoRoot = stdout.trim();
2482
2548
  return this.repoRoot;
@@ -2489,21 +2555,21 @@ var WorkspaceManager = class _WorkspaceManager {
2489
2555
  try {
2490
2556
  const workspacePath = path8.resolve(this.resolvePath(identifier));
2491
2557
  try {
2492
- await fs9.access(path8.join(workspacePath, ".git"));
2558
+ await fs8.access(path8.join(workspacePath, ".git"));
2493
2559
  const repoRoot2 = await this.getRepoRoot();
2494
2560
  try {
2495
2561
  await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
2496
2562
  } catch {
2497
- await fs9.rm(workspacePath, { recursive: true, force: true });
2563
+ await fs8.rm(workspacePath, { recursive: true, force: true });
2498
2564
  }
2499
2565
  } catch {
2500
2566
  try {
2501
- await fs9.access(workspacePath);
2567
+ await fs8.access(workspacePath);
2502
2568
  const repoRoot2 = await this.getRepoRoot();
2503
2569
  try {
2504
2570
  await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
2505
2571
  } catch {
2506
- await fs9.rm(workspacePath, { recursive: true, force: true });
2572
+ await fs8.rm(workspacePath, { recursive: true, force: true });
2507
2573
  }
2508
2574
  } catch {
2509
2575
  }
@@ -2515,7 +2581,7 @@ var WorkspaceManager = class _WorkspaceManager {
2515
2581
  await this.seedWorkspace(workspacePath, repoRoot);
2516
2582
  return Ok6(workspacePath);
2517
2583
  } catch (error) {
2518
- return Err4(error instanceof Error ? error : new Error(String(error)));
2584
+ return Err5(error instanceof Error ? error : new Error(String(error)));
2519
2585
  }
2520
2586
  }
2521
2587
  /**
@@ -2556,13 +2622,13 @@ var WorkspaceManager = class _WorkspaceManager {
2556
2622
  }
2557
2623
  const src = path8.join(repoRoot, rel);
2558
2624
  try {
2559
- await fs9.access(src);
2625
+ await fs8.access(src);
2560
2626
  } catch {
2561
2627
  continue;
2562
2628
  }
2563
2629
  const dest = path8.join(workspacePath, rel);
2564
2630
  try {
2565
- await fs9.cp(src, dest, { recursive: true, force: true });
2631
+ await fs8.cp(src, dest, { recursive: true, force: true });
2566
2632
  } catch {
2567
2633
  }
2568
2634
  }
@@ -2638,7 +2704,7 @@ var WorkspaceManager = class _WorkspaceManager {
2638
2704
  async exists(identifier) {
2639
2705
  try {
2640
2706
  const workspacePath = this.resolvePath(identifier);
2641
- await fs9.access(workspacePath);
2707
+ await fs8.access(workspacePath);
2642
2708
  return true;
2643
2709
  } catch {
2644
2710
  return false;
@@ -2653,7 +2719,7 @@ var WorkspaceManager = class _WorkspaceManager {
2653
2719
  try {
2654
2720
  const workspacePath = path8.resolve(this.resolvePath(identifier));
2655
2721
  try {
2656
- await fs9.access(path8.join(workspacePath, ".git"));
2722
+ await fs8.access(path8.join(workspacePath, ".git"));
2657
2723
  } catch {
2658
2724
  return null;
2659
2725
  }
@@ -2764,18 +2830,18 @@ var WorkspaceManager = class _WorkspaceManager {
2764
2830
  const repoRoot = await this.getRepoRoot();
2765
2831
  await this.git(["worktree", "remove", "--force", workspacePath], repoRoot);
2766
2832
  } catch {
2767
- await fs9.rm(workspacePath, { recursive: true, force: true });
2833
+ await fs8.rm(workspacePath, { recursive: true, force: true });
2768
2834
  }
2769
2835
  return Ok6(void 0);
2770
2836
  } catch (error) {
2771
- return Err4(error instanceof Error ? error : new Error(String(error)));
2837
+ return Err5(error instanceof Error ? error : new Error(String(error)));
2772
2838
  }
2773
2839
  }
2774
2840
  };
2775
2841
 
2776
2842
  // src/workspace/hooks.ts
2777
2843
  import { spawn } from "child_process";
2778
- import { Ok as Ok7, Err as Err5 } from "@harness-engineering/types";
2844
+ import { Ok as Ok7, Err as Err6 } from "@harness-engineering/types";
2779
2845
  var WorkspaceHooks = class {
2780
2846
  config;
2781
2847
  constructor(config) {
@@ -2802,19 +2868,19 @@ var WorkspaceHooks = class {
2802
2868
  });
2803
2869
  const timeout = setTimeout(() => {
2804
2870
  child.kill();
2805
- resolve8(Err5(new Error(`Hook ${hookName} timed out after ${this.config.timeoutMs}ms`)));
2871
+ resolve8(Err6(new Error(`Hook ${hookName} timed out after ${this.config.timeoutMs}ms`)));
2806
2872
  }, this.config.timeoutMs);
2807
2873
  child.on("exit", (code) => {
2808
2874
  clearTimeout(timeout);
2809
2875
  if (code === 0) {
2810
2876
  resolve8(Ok7(void 0));
2811
2877
  } else {
2812
- resolve8(Err5(new Error(`Hook ${hookName} failed with exit code ${code}`)));
2878
+ resolve8(Err6(new Error(`Hook ${hookName} failed with exit code ${code}`)));
2813
2879
  }
2814
2880
  });
2815
2881
  child.on("error", (error) => {
2816
2882
  clearTimeout(timeout);
2817
- resolve8(Err5(error));
2883
+ resolve8(Err6(error));
2818
2884
  });
2819
2885
  });
2820
2886
  }
@@ -3494,7 +3560,7 @@ import {
3494
3560
  } from "@harness-engineering/core";
3495
3561
 
3496
3562
  // src/tracker/adapters/github-issues-issue-tracker.ts
3497
- import { Ok as Ok9, Err as Err6 } from "@harness-engineering/types";
3563
+ import { Ok as Ok9, Err as Err7 } from "@harness-engineering/types";
3498
3564
  var GitHubIssuesIssueTrackerAdapter = class {
3499
3565
  client;
3500
3566
  config;
@@ -3509,12 +3575,12 @@ var GitHubIssuesIssueTrackerAdapter = class {
3509
3575
  const r = await this.client.fetchByStatus(
3510
3576
  stateNames
3511
3577
  );
3512
- if (!r.ok) return Err6(r.error);
3578
+ if (!r.ok) return Err7(r.error);
3513
3579
  return Ok9(r.value.map((f) => this.mapTrackedToIssue(f)));
3514
3580
  }
3515
3581
  async fetchIssueStatesByIds(issueIds) {
3516
3582
  const r = await this.client.fetchAll();
3517
- if (!r.ok) return Err6(r.error);
3583
+ if (!r.ok) return Err7(r.error);
3518
3584
  const wanted = new Set(issueIds);
3519
3585
  const out = /* @__PURE__ */ new Map();
3520
3586
  for (const f of r.value.features) {
@@ -3524,17 +3590,17 @@ var GitHubIssuesIssueTrackerAdapter = class {
3524
3590
  }
3525
3591
  async claimIssue(issueId, orchestratorId) {
3526
3592
  const r = await this.client.claim(issueId, orchestratorId);
3527
- if (!r.ok) return Err6(r.error);
3593
+ if (!r.ok) return Err7(r.error);
3528
3594
  return Ok9(void 0);
3529
3595
  }
3530
3596
  async releaseIssue(issueId) {
3531
3597
  const r = await this.client.release(issueId);
3532
- if (!r.ok) return Err6(r.error);
3598
+ if (!r.ok) return Err7(r.error);
3533
3599
  return Ok9(void 0);
3534
3600
  }
3535
3601
  async markIssueComplete(issueId) {
3536
3602
  const r = await this.client.complete(issueId);
3537
- if (!r.ok) return Err6(r.error);
3603
+ if (!r.ok) return Err7(r.error);
3538
3604
  return Ok9(void 0);
3539
3605
  }
3540
3606
  /**
@@ -4185,14 +4251,14 @@ import * as readline from "readline";
4185
4251
  import { randomUUID as randomUUID2 } from "crypto";
4186
4252
  import {
4187
4253
  Ok as Ok10,
4188
- Err as Err7
4254
+ Err as Err8
4189
4255
  } from "@harness-engineering/types";
4190
4256
  function resolveExitCode(code, command, resolve8) {
4191
4257
  if (code === 0) {
4192
4258
  resolve8(Ok10(void 0));
4193
4259
  } else {
4194
4260
  resolve8(
4195
- Err7({
4261
+ Err8({
4196
4262
  category: "agent_not_found",
4197
4263
  message: `Claude command '${command}' not found or failed`
4198
4264
  })
@@ -4200,7 +4266,7 @@ function resolveExitCode(code, command, resolve8) {
4200
4266
  }
4201
4267
  }
4202
4268
  function resolveSpawnError(command, resolve8) {
4203
- resolve8(Err7({ category: "agent_not_found", message: `Claude command '${command}' not found` }));
4269
+ resolve8(Err8({ category: "agent_not_found", message: `Claude command '${command}' not found` }));
4204
4270
  }
4205
4271
  var JUST_PAST_GRACE_MS = 5 * 6e4;
4206
4272
  var PRIMARY_LIMIT_RE = /You[\u2019']ve hit your limit.*resets\s+(\d{1,2}(?::\d{2})?\s*(?:am|pm))\s*\(([^)]+)\)/i;
@@ -4550,7 +4616,7 @@ var ClaudeBackend = class {
4550
4616
  import Anthropic from "@anthropic-ai/sdk";
4551
4617
  import {
4552
4618
  Ok as Ok11,
4553
- Err as Err8
4619
+ Err as Err9
4554
4620
  } from "@harness-engineering/types";
4555
4621
  import { AnthropicCacheAdapter } from "@harness-engineering/core";
4556
4622
  var AnthropicBackend = class {
@@ -4569,7 +4635,7 @@ var AnthropicBackend = class {
4569
4635
  }
4570
4636
  async startSession(params) {
4571
4637
  if (!this.config.apiKey) {
4572
- return Err8({
4638
+ return Err9({
4573
4639
  category: "agent_not_found",
4574
4640
  message: "ANTHROPIC_API_KEY is not set"
4575
4641
  });
@@ -4650,7 +4716,7 @@ var AnthropicBackend = class {
4650
4716
  }
4651
4717
  async healthCheck() {
4652
4718
  if (!this.config.apiKey) {
4653
- return Err8({
4719
+ return Err9({
4654
4720
  category: "response_error",
4655
4721
  message: "ANTHROPIC_API_KEY is not set"
4656
4722
  });
@@ -4663,7 +4729,7 @@ var AnthropicBackend = class {
4663
4729
  import OpenAI from "openai";
4664
4730
  import {
4665
4731
  Ok as Ok12,
4666
- Err as Err9
4732
+ Err as Err10
4667
4733
  } from "@harness-engineering/types";
4668
4734
  import { OpenAICacheAdapter } from "@harness-engineering/core";
4669
4735
  var OpenAIBackend = class {
@@ -4681,7 +4747,7 @@ var OpenAIBackend = class {
4681
4747
  }
4682
4748
  async startSession(params) {
4683
4749
  if (!this.config.apiKey) {
4684
- return Err9({
4750
+ return Err10({
4685
4751
  category: "agent_not_found",
4686
4752
  message: "OPENAI_API_KEY is not set"
4687
4753
  });
@@ -4776,7 +4842,7 @@ var OpenAIBackend = class {
4776
4842
  await this.client.models.list();
4777
4843
  return Ok12(void 0);
4778
4844
  } catch (err) {
4779
- return Err9({
4845
+ return Err10({
4780
4846
  category: "response_error",
4781
4847
  message: err instanceof Error ? err.message : "OpenAI health check failed"
4782
4848
  });
@@ -4788,7 +4854,7 @@ var OpenAIBackend = class {
4788
4854
  import { GoogleGenAI } from "@google/genai";
4789
4855
  import {
4790
4856
  Ok as Ok13,
4791
- Err as Err10
4857
+ Err as Err11
4792
4858
  } from "@harness-engineering/types";
4793
4859
  import { GeminiCacheAdapter } from "@harness-engineering/core";
4794
4860
  var GeminiBackend = class {
@@ -4804,7 +4870,7 @@ var GeminiBackend = class {
4804
4870
  }
4805
4871
  async startSession(params) {
4806
4872
  if (!this.config.apiKey) {
4807
- return Err10({
4873
+ return Err11({
4808
4874
  category: "agent_not_found",
4809
4875
  message: "GEMINI_API_KEY is not set"
4810
4876
  });
@@ -4904,7 +4970,7 @@ var GeminiBackend = class {
4904
4970
  }
4905
4971
  async healthCheck() {
4906
4972
  if (!this.config.apiKey) {
4907
- return Err10({
4973
+ return Err11({
4908
4974
  category: "agent_not_found",
4909
4975
  message: "GEMINI_API_KEY is not set"
4910
4976
  });
@@ -4913,7 +4979,7 @@ var GeminiBackend = class {
4913
4979
  new GoogleGenAI({ apiKey: this.config.apiKey });
4914
4980
  return Ok13(void 0);
4915
4981
  } catch (err) {
4916
- return Err10({
4982
+ return Err11({
4917
4983
  category: "response_error",
4918
4984
  message: err instanceof Error ? err.message : "Gemini health check failed"
4919
4985
  });
@@ -4925,7 +4991,7 @@ var GeminiBackend = class {
4925
4991
  import OpenAI2 from "openai";
4926
4992
  import {
4927
4993
  Ok as Ok14,
4928
- Err as Err11
4994
+ Err as Err12
4929
4995
  } from "@harness-engineering/types";
4930
4996
  var DEFAULT_TIMEOUT_MS = 9e4;
4931
4997
  var LocalBackend = class {
@@ -4952,7 +5018,7 @@ var LocalBackend = class {
4952
5018
  if (this.getModel) {
4953
5019
  const candidate = this.getModel();
4954
5020
  if (candidate === null) {
4955
- return Err11({
5021
+ return Err12({
4956
5022
  category: "agent_not_found",
4957
5023
  message: "No local model available; check dashboard for details."
4958
5024
  });
@@ -5041,7 +5107,7 @@ var LocalBackend = class {
5041
5107
  await this.client.models.list();
5042
5108
  return Ok14(void 0);
5043
5109
  } catch (err) {
5044
- return Err11({
5110
+ return Err12({
5045
5111
  category: "response_error",
5046
5112
  message: err instanceof Error ? err.message : "Local backend health check failed"
5047
5113
  });
@@ -5053,7 +5119,7 @@ var LocalBackend = class {
5053
5119
  import { randomUUID as randomUUID3 } from "crypto";
5054
5120
  import {
5055
5121
  Ok as Ok15,
5056
- Err as Err12
5122
+ Err as Err13
5057
5123
  } from "@harness-engineering/types";
5058
5124
  var SILENT_EVENTS = /* @__PURE__ */ new Set([
5059
5125
  "turn_end",
@@ -5171,7 +5237,7 @@ var PiBackend = class {
5171
5237
  if (this.config.getModel) {
5172
5238
  const candidate = this.config.getModel();
5173
5239
  if (candidate === null) {
5174
- return Err12({
5240
+ return Err13({
5175
5241
  category: "agent_not_found",
5176
5242
  message: "No local model available; check dashboard for details."
5177
5243
  });
@@ -5201,7 +5267,7 @@ var PiBackend = class {
5201
5267
  };
5202
5268
  return Ok15(session);
5203
5269
  } catch (err) {
5204
- return Err12({
5270
+ return Err13({
5205
5271
  category: "response_error",
5206
5272
  message: `Failed to create pi session: ${err instanceof Error ? err.message : String(err)}`
5207
5273
  });
@@ -5338,7 +5404,7 @@ var PiBackend = class {
5338
5404
  await import("@earendil-works/pi-coding-agent");
5339
5405
  return Ok15(void 0);
5340
5406
  } catch (err) {
5341
- return Err12({
5407
+ return Err13({
5342
5408
  category: "agent_not_found",
5343
5409
  message: `Pi SDK not available: ${err instanceof Error ? err.message : String(err)}`
5344
5410
  });
@@ -5350,7 +5416,7 @@ var PiBackend = class {
5350
5416
  import { spawn as spawn3 } from "child_process";
5351
5417
  import {
5352
5418
  Ok as Ok16,
5353
- Err as Err13
5419
+ Err as Err14
5354
5420
  } from "@harness-engineering/types";
5355
5421
  var DEFAULT_TIMEOUT_MS2 = 9e4;
5356
5422
  var FORBIDDEN_HOST_CHARS = /[;&|`$()\n\r<>]/;
@@ -5497,7 +5563,7 @@ var SshBackend = class {
5497
5563
  });
5498
5564
  } catch (err) {
5499
5565
  resolve8(
5500
- Err13({
5566
+ Err14({
5501
5567
  category: "agent_not_found",
5502
5568
  message: err instanceof Error ? err.message : "failed to spawn ssh"
5503
5569
  })
@@ -5520,7 +5586,7 @@ var SshBackend = class {
5520
5586
  resolve8(Ok16(void 0));
5521
5587
  } else {
5522
5588
  resolve8(
5523
- Err13({
5589
+ Err14({
5524
5590
  category: "agent_not_found",
5525
5591
  message: `ssh health check failed (exit=${code ?? "null"}): ${stderr.slice(0, 500)}`
5526
5592
  })
@@ -5529,7 +5595,7 @@ var SshBackend = class {
5529
5595
  });
5530
5596
  child.on("error", (err) => {
5531
5597
  clearTimeout(timer);
5532
- resolve8(Err13({ category: "agent_not_found", message: err.message }));
5598
+ resolve8(Err14({ category: "agent_not_found", message: err.message }));
5533
5599
  });
5534
5600
  });
5535
5601
  }
@@ -5591,7 +5657,7 @@ function waitForExit(child) {
5591
5657
  import { spawn as spawn4 } from "child_process";
5592
5658
  import {
5593
5659
  Ok as Ok17,
5594
- Err as Err14
5660
+ Err as Err15
5595
5661
  } from "@harness-engineering/types";
5596
5662
  var ServerlessBackend = class {
5597
5663
  handles = /* @__PURE__ */ new Map();
@@ -5690,7 +5756,7 @@ var OciServerlessBackend = class extends ServerlessBackend {
5690
5756
  if (!result.ok) return result;
5691
5757
  const id = result.value.trim().split(/\s+/)[0] ?? "";
5692
5758
  if (!id) {
5693
- return Err14({
5759
+ return Err15({
5694
5760
  category: "response_error",
5695
5761
  message: "OciServerlessBackend: empty container id from runtime"
5696
5762
  });
@@ -5750,7 +5816,7 @@ var OciServerlessBackend = class extends ServerlessBackend {
5750
5816
  }
5751
5817
  async teardown(handle) {
5752
5818
  if (handle.adapter !== this.name) {
5753
- return Err14({
5819
+ return Err15({
5754
5820
  category: "response_error",
5755
5821
  message: `handle adapter mismatch: got '${handle.adapter}', expected '${this.name}'`
5756
5822
  });
@@ -5781,7 +5847,7 @@ var OciServerlessBackend = class extends ServerlessBackend {
5781
5847
  });
5782
5848
  } catch (err) {
5783
5849
  resolve8(
5784
- Err14({
5850
+ Err15({
5785
5851
  category: "agent_not_found",
5786
5852
  message: err instanceof Error ? err.message : "failed to spawn runtime"
5787
5853
  })
@@ -5808,7 +5874,7 @@ var OciServerlessBackend = class extends ServerlessBackend {
5808
5874
  resolve8(Ok17(stdout));
5809
5875
  } else {
5810
5876
  resolve8(
5811
- Err14({
5877
+ Err15({
5812
5878
  category: "response_error",
5813
5879
  message: `runtime '${binary} ${args.join(" ")}' exited ${code ?? "null"}: ${stderr.slice(0, 500)}`
5814
5880
  })
@@ -5817,7 +5883,7 @@ var OciServerlessBackend = class extends ServerlessBackend {
5817
5883
  });
5818
5884
  child.on("error", (err) => {
5819
5885
  clearTimeout(timer);
5820
- resolve8(Err14({ category: "agent_not_found", message: err.message }));
5886
+ resolve8(Err15({ category: "agent_not_found", message: err.message }));
5821
5887
  });
5822
5888
  });
5823
5889
  }
@@ -5974,7 +6040,7 @@ function createBackend(def, options = {}) {
5974
6040
 
5975
6041
  // src/agent/backends/container.ts
5976
6042
  import {
5977
- Err as Err15
6043
+ Err as Err16
5978
6044
  } from "@harness-engineering/types";
5979
6045
  function toAgentError(message, details) {
5980
6046
  return { category: "response_error", message, details };
@@ -6006,7 +6072,7 @@ var ContainerBackend = class {
6006
6072
  }
6007
6073
  const result = await this.secretBackend.resolveSecrets(this.secretKeys);
6008
6074
  if (!result.ok) {
6009
- return Err15(toAgentError(`Secret resolution failed: ${result.error.message}`, result.error));
6075
+ return Err16(toAgentError(`Secret resolution failed: ${result.error.message}`, result.error));
6010
6076
  }
6011
6077
  return { ok: true, value: result.value };
6012
6078
  }
@@ -6031,7 +6097,7 @@ var ContainerBackend = class {
6031
6097
  const createOpts = this.buildCreateOpts(params, envResult.value);
6032
6098
  const containerResult = await this.runtime.createContainer(createOpts);
6033
6099
  if (!containerResult.ok) {
6034
- return Err15(
6100
+ return Err16(
6035
6101
  toAgentError(
6036
6102
  `Container creation failed: ${containerResult.error.message}`,
6037
6103
  containerResult.error
@@ -6056,7 +6122,7 @@ var ContainerBackend = class {
6056
6122
  this.containerHandles.delete(session.sessionId);
6057
6123
  const removeResult = await this.runtime.removeContainer(handle);
6058
6124
  if (!removeResult.ok) {
6059
- return Err15(
6125
+ return Err16(
6060
6126
  toAgentError(
6061
6127
  `Container removal failed: ${removeResult.error.message}`,
6062
6128
  removeResult.error
@@ -6069,7 +6135,7 @@ var ContainerBackend = class {
6069
6135
  async healthCheck() {
6070
6136
  const runtimeResult = await this.runtime.healthCheck();
6071
6137
  if (!runtimeResult.ok) {
6072
- return Err15({
6138
+ return Err16({
6073
6139
  category: "agent_not_found",
6074
6140
  message: `Container runtime unhealthy: ${runtimeResult.error.message}`,
6075
6141
  details: runtimeResult.error
@@ -6081,7 +6147,7 @@ var ContainerBackend = class {
6081
6147
 
6082
6148
  // src/agent/runtime/docker.ts
6083
6149
  import { execFile as execFile3, spawn as spawn5 } from "child_process";
6084
- import { Ok as Ok18, Err as Err16 } from "@harness-engineering/types";
6150
+ import { Ok as Ok18, Err as Err17 } from "@harness-engineering/types";
6085
6151
  function dockerExec(args) {
6086
6152
  return new Promise((resolve8, reject) => {
6087
6153
  execFile3("docker", args, (error, stdout) => {
@@ -6116,7 +6182,7 @@ var DockerRuntime = class {
6116
6182
  const containerId = await dockerExec(args);
6117
6183
  return Ok18({ containerId, runtime: this.name });
6118
6184
  } catch (error) {
6119
- return Err16({
6185
+ return Err17({
6120
6186
  category: "container_create_failed",
6121
6187
  message: `Failed to create container: ${error instanceof Error ? error.message : String(error)}`,
6122
6188
  details: error
@@ -6162,7 +6228,7 @@ var DockerRuntime = class {
6162
6228
  await dockerExec(["rm", "-f", handle.containerId]);
6163
6229
  return Ok18(void 0);
6164
6230
  } catch (error) {
6165
- return Err16({
6231
+ return Err17({
6166
6232
  category: "container_remove_failed",
6167
6233
  message: `Failed to remove container: ${error instanceof Error ? error.message : String(error)}`,
6168
6234
  details: error
@@ -6174,7 +6240,7 @@ var DockerRuntime = class {
6174
6240
  await dockerExec(["info", "--format", "{{.ServerVersion}}"]);
6175
6241
  return Ok18(void 0);
6176
6242
  } catch (error) {
6177
- return Err16({
6243
+ return Err17({
6178
6244
  category: "runtime_not_found",
6179
6245
  message: `Docker is not available: ${error instanceof Error ? error.message : String(error)}`,
6180
6246
  details: error
@@ -6184,7 +6250,7 @@ var DockerRuntime = class {
6184
6250
  };
6185
6251
 
6186
6252
  // src/agent/secrets/env.ts
6187
- import { Ok as Ok19, Err as Err17 } from "@harness-engineering/types";
6253
+ import { Ok as Ok19, Err as Err18 } from "@harness-engineering/types";
6188
6254
  var EnvSecretBackend = class {
6189
6255
  name = "env";
6190
6256
  async resolveSecrets(keys) {
@@ -6192,7 +6258,7 @@ var EnvSecretBackend = class {
6192
6258
  for (const key of keys) {
6193
6259
  const value = process.env[key];
6194
6260
  if (value === void 0) {
6195
- return Err17({
6261
+ return Err18({
6196
6262
  category: "secret_not_found",
6197
6263
  message: `Environment variable '${key}' is not set`,
6198
6264
  key
@@ -6209,7 +6275,7 @@ var EnvSecretBackend = class {
6209
6275
 
6210
6276
  // src/agent/secrets/onepassword.ts
6211
6277
  import { execFile as execFile4 } from "child_process";
6212
- import { Ok as Ok20, Err as Err18 } from "@harness-engineering/types";
6278
+ import { Ok as Ok20, Err as Err19 } from "@harness-engineering/types";
6213
6279
  function opExec(args) {
6214
6280
  return new Promise((resolve8, reject) => {
6215
6281
  execFile4("op", args, (error, stdout) => {
@@ -6234,7 +6300,7 @@ var OnePasswordSecretBackend = class {
6234
6300
  const value = await opExec(["read", `op://${this.vault}/${key}/password`]);
6235
6301
  secrets[key] = value;
6236
6302
  } catch (error) {
6237
- return Err18({
6303
+ return Err19({
6238
6304
  category: "access_denied",
6239
6305
  message: `Failed to read secret '${key}' from 1Password: ${error instanceof Error ? error.message : String(error)}`,
6240
6306
  key
@@ -6248,7 +6314,7 @@ var OnePasswordSecretBackend = class {
6248
6314
  await opExec(["--version"]);
6249
6315
  return Ok20(void 0);
6250
6316
  } catch (error) {
6251
- return Err18({
6317
+ return Err19({
6252
6318
  category: "provider_unavailable",
6253
6319
  message: `1Password CLI is not available: ${error instanceof Error ? error.message : String(error)}`
6254
6320
  });
@@ -6258,7 +6324,7 @@ var OnePasswordSecretBackend = class {
6258
6324
 
6259
6325
  // src/agent/secrets/vault.ts
6260
6326
  import { execFile as execFile5 } from "child_process";
6261
- import { Ok as Ok21, Err as Err19 } from "@harness-engineering/types";
6327
+ import { Ok as Ok21, Err as Err20 } from "@harness-engineering/types";
6262
6328
  function vaultExec(args, env) {
6263
6329
  return new Promise((resolve8, reject) => {
6264
6330
  execFile5("vault", args, { env: { ...process.env, ...env } }, (error, stdout) => {
@@ -6289,11 +6355,11 @@ var VaultSecretBackend = class {
6289
6355
  } catch (error) {
6290
6356
  const msg = error instanceof Error ? error.message : String(error);
6291
6357
  const category = error instanceof SyntaxError ? "access_denied" : "access_denied";
6292
- return Err19({ category, message: `Failed to read from Vault: ${msg}` });
6358
+ return Err20({ category, message: `Failed to read from Vault: ${msg}` });
6293
6359
  }
6294
6360
  const missing = keys.find((k) => !(k in data));
6295
6361
  if (missing) {
6296
- return Err19({
6362
+ return Err20({
6297
6363
  category: "secret_not_found",
6298
6364
  message: `Secret key '${missing}' not found in Vault path '${this.path}'`,
6299
6365
  key: missing
@@ -6308,7 +6374,7 @@ var VaultSecretBackend = class {
6308
6374
  await vaultExec(["version"]);
6309
6375
  return Ok21(void 0);
6310
6376
  } catch (error) {
6311
- return Err19({
6377
+ return Err20({
6312
6378
  category: "provider_unavailable",
6313
6379
  message: `Vault CLI is not available: ${error instanceof Error ? error.message : String(error)}`
6314
6380
  });
@@ -6437,6 +6503,96 @@ var OrchestratorBackendFactory = class {
6437
6503
  }
6438
6504
  };
6439
6505
 
6506
+ // src/agent/backend-resolver.ts
6507
+ function makeBackendResolver(backends) {
6508
+ return (backendName) => {
6509
+ const def = backends?.[backendName];
6510
+ return def ? createBackend(def) : null;
6511
+ };
6512
+ }
6513
+
6514
+ // src/maintenance/agent-dispatcher.ts
6515
+ function syntheticIssue(branch, skill) {
6516
+ return {
6517
+ id: `maintenance:${branch}`,
6518
+ identifier: branch,
6519
+ title: `Maintenance: ${skill}`,
6520
+ description: null,
6521
+ priority: null,
6522
+ state: "in_progress",
6523
+ branchName: branch,
6524
+ url: null,
6525
+ labels: ["maintenance"],
6526
+ blockedBy: [],
6527
+ spec: null,
6528
+ plans: [],
6529
+ createdAt: null,
6530
+ updatedAt: null,
6531
+ externalId: null
6532
+ };
6533
+ }
6534
+ function buildPrompt(skill, promptContext) {
6535
+ const instruction = `Run the \`${skill}\` skill to detect and fix issues in this repository. Apply the fixes and commit each logical change. If there is nothing to fix, make no commits.`;
6536
+ return promptContext ? `${promptContext}
6537
+
6538
+ ${instruction}` : instruction;
6539
+ }
6540
+ function headOf(git2, cwd) {
6541
+ try {
6542
+ return git2(["rev-parse", "HEAD"], cwd) || null;
6543
+ } catch {
6544
+ return null;
6545
+ }
6546
+ }
6547
+ function createAgentDispatcher(deps) {
6548
+ const maxTurns = deps.maxTurns ?? 10;
6549
+ const dispatch = async (skill, branch, backendName, cwd, options) => {
6550
+ const backend = deps.resolveBackend(backendName);
6551
+ if (!backend) {
6552
+ deps.logger.warn(`Maintenance agent dispatch skipped: unknown backend "${backendName}"`, {
6553
+ skill,
6554
+ branch
6555
+ });
6556
+ return { producedCommits: false, fixed: 0 };
6557
+ }
6558
+ const before = headOf(deps.git, cwd);
6559
+ const runner = new AgentRunner(backend, { maxTurns });
6560
+ const session = runner.runSession(
6561
+ syntheticIssue(branch, skill),
6562
+ cwd,
6563
+ buildPrompt(skill, options?.promptContext)
6564
+ );
6565
+ let step = await session.next();
6566
+ while (!step.done) step = await session.next();
6567
+ const after = headOf(deps.git, cwd);
6568
+ let fixed = 0;
6569
+ if (after && after !== before) {
6570
+ if (before) {
6571
+ try {
6572
+ fixed = parseInt(deps.git(["rev-list", "--count", `${before}..${after}`], cwd), 10) || 0;
6573
+ } catch {
6574
+ fixed = 1;
6575
+ }
6576
+ } else {
6577
+ fixed = 1;
6578
+ }
6579
+ }
6580
+ const producedCommits = fixed > 0;
6581
+ deps.logger.info("Maintenance agent dispatch complete", {
6582
+ skill,
6583
+ branch,
6584
+ backendName,
6585
+ producedCommits,
6586
+ fixed
6587
+ });
6588
+ return { producedCommits, fixed };
6589
+ };
6590
+ return { dispatch };
6591
+ }
6592
+
6593
+ // src/orchestrator.ts
6594
+ import { execFileSync } from "child_process";
6595
+
6440
6596
  // src/agent/intelligence-factory.ts
6441
6597
  import {
6442
6598
  IntelligencePipeline,
@@ -6947,7 +7103,7 @@ function handleV1InteractionsResolveRoute(req, res, queue) {
6947
7103
 
6948
7104
  // src/server/routes/plans.ts
6949
7105
  import { z as z5 } from "zod";
6950
- import * as fs10 from "fs/promises";
7106
+ import * as fs9 from "fs/promises";
6951
7107
  import * as path11 from "path";
6952
7108
  var PlanWriteSchema = z5.object({
6953
7109
  filename: z5.string().min(1),
@@ -6976,9 +7132,9 @@ function handlePlansRoute(req, res, plansDir) {
6976
7132
  );
6977
7133
  return;
6978
7134
  }
6979
- await fs10.mkdir(plansDir, { recursive: true });
7135
+ await fs9.mkdir(plansDir, { recursive: true });
6980
7136
  const filePath = path11.join(plansDir, basename3);
6981
- await fs10.writeFile(filePath, parsed.content, "utf-8");
7137
+ await fs9.writeFile(filePath, parsed.content, "utf-8");
6982
7138
  res.writeHead(201, { "Content-Type": "application/json" });
6983
7139
  res.end(JSON.stringify({ ok: true, filename: basename3 }));
6984
7140
  } catch {
@@ -7353,11 +7509,10 @@ function handleAnalyzeRoute(req, res, pipeline) {
7353
7509
  }
7354
7510
 
7355
7511
  // src/server/routes/roadmap-actions.ts
7356
- import * as fs11 from "fs/promises";
7357
7512
  import * as path12 from "path";
7358
7513
  import {
7359
- parseRoadmap as parseRoadmap2,
7360
- serializeRoadmap as serializeRoadmap2,
7514
+ resolveRoadmapStoreForFile as resolveRoadmapStoreForFile2,
7515
+ applyRoadmapDiff as applyRoadmapDiff2,
7361
7516
  loadProjectRoadmapMode,
7362
7517
  loadTrackerClientConfigFromProject,
7363
7518
  createTrackerClient,
@@ -7443,13 +7598,14 @@ function handleRoadmapActionsRoute(req, res, roadmapPath) {
7443
7598
  sendJSON2(res, 400, { error: "Title must not contain newlines or markdown headings" });
7444
7599
  return;
7445
7600
  }
7446
- const content = await fs11.readFile(roadmapPath, "utf-8");
7447
- const roadmapResult = parseRoadmap2(content);
7448
- if (!roadmapResult.ok) {
7601
+ const store = resolveRoadmapStoreForFile2({ roadmapPath });
7602
+ const loaded = await store.load();
7603
+ if (!loaded.ok) {
7449
7604
  sendJSON2(res, 500, { error: "Failed to parse roadmap file" });
7450
7605
  return;
7451
7606
  }
7452
- const roadmap = roadmapResult.value;
7607
+ const roadmap = loaded.value;
7608
+ const before = structuredClone(roadmap);
7453
7609
  let backlog = roadmap.milestones.find((m) => m.isBacklog);
7454
7610
  if (!backlog) {
7455
7611
  backlog = { name: "Backlog", isBacklog: true, features: [] };
@@ -7472,10 +7628,11 @@ function handleRoadmapActionsRoute(req, res, roadmapPath) {
7472
7628
  updatedAt: null
7473
7629
  });
7474
7630
  roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
7475
- const tmpPath = roadmapPath + ".tmp";
7476
- const serialized = serializeRoadmap2(roadmap);
7477
- await fs11.writeFile(tmpPath, serialized, "utf-8");
7478
- await fs11.rename(tmpPath, roadmapPath);
7631
+ const persisted = await applyRoadmapDiff2(store, before, roadmap);
7632
+ if (!persisted.ok) {
7633
+ sendJSON2(res, 500, { error: persisted.error.message });
7634
+ return;
7635
+ }
7479
7636
  sendJSON2(res, 201, { ok: true, featureName: parsed.title });
7480
7637
  } catch (err) {
7481
7638
  const msg = err instanceof Error ? err.message : "Failed to append to roadmap";
@@ -7741,7 +7898,6 @@ function handleV1JobsMaintenanceRoute(req, res, deps) {
7741
7898
  }
7742
7899
 
7743
7900
  // src/server/routes/v1/events-sse.ts
7744
- import { randomBytes as randomBytes2 } from "crypto";
7745
7901
  var SSE_TOPICS = [
7746
7902
  "state_change",
7747
7903
  "agent_event",
@@ -7757,8 +7913,55 @@ var SSE_TOPICS = [
7757
7913
  "webhook.subscription.deleted"
7758
7914
  ];
7759
7915
  var HEARTBEAT_MS = 15e3;
7760
- function newEventId() {
7761
- return `evt_${randomBytes2(8).toString("hex")}`;
7916
+ var DEFAULT_REPLAY_BUFFER = 1024;
7917
+ var SseEventLog = class {
7918
+ seq = 0;
7919
+ buffer = [];
7920
+ subscribers = /* @__PURE__ */ new Set();
7921
+ constructor(bus, cap = DEFAULT_REPLAY_BUFFER) {
7922
+ this.cap = cap;
7923
+ for (const topic of SSE_TOPICS) {
7924
+ bus.on(topic, (data) => this.record(topic, data));
7925
+ }
7926
+ }
7927
+ cap;
7928
+ record(topic, data) {
7929
+ const event = { id: ++this.seq, topic, data };
7930
+ this.buffer.push(event);
7931
+ if (this.buffer.length > this.cap) this.buffer.shift();
7932
+ for (const fn of this.subscribers) fn(event);
7933
+ }
7934
+ /** Highest assigned id (0 when nothing has been recorded yet). */
7935
+ currentSeq() {
7936
+ return this.seq;
7937
+ }
7938
+ /** Buffered events strictly newer than `lastId`, oldest-first. */
7939
+ replayFrom(lastId) {
7940
+ return this.buffer.filter((e) => e.id > lastId);
7941
+ }
7942
+ /** Register a live listener; returns an unsubscribe function. */
7943
+ subscribe(fn) {
7944
+ this.subscribers.add(fn);
7945
+ return () => {
7946
+ this.subscribers.delete(fn);
7947
+ };
7948
+ }
7949
+ };
7950
+ var logsByBus = /* @__PURE__ */ new WeakMap();
7951
+ function getSseEventLog(bus) {
7952
+ let log = logsByBus.get(bus);
7953
+ if (!log) {
7954
+ log = new SseEventLog(bus);
7955
+ logsByBus.set(bus, log);
7956
+ }
7957
+ return log;
7958
+ }
7959
+ function parseLastEventId(req) {
7960
+ const raw = req.headers["last-event-id"];
7961
+ const value = Array.isArray(raw) ? raw[0] : raw;
7962
+ if (value === void 0) return null;
7963
+ const n = Number(value);
7964
+ return Number.isInteger(n) && n >= 0 ? n : null;
7762
7965
  }
7763
7966
  function handleV1EventsSseRoute(req, res, bus) {
7764
7967
  if (req.method !== "GET" || req.url !== "/api/v1/events") return false;
@@ -7770,22 +7973,28 @@ function handleV1EventsSseRoute(req, res, bus) {
7770
7973
  res.write(`: harness gateway SSE \u2014 connected at ${(/* @__PURE__ */ new Date()).toISOString()}
7771
7974
 
7772
7975
  `);
7773
- const listeners = [];
7774
- for (const topic of SSE_TOPICS) {
7775
- const fn = (data) => {
7776
- try {
7777
- const frame = `event: ${topic}
7778
- data: ${JSON.stringify(data)}
7779
- id: ${newEventId()}
7976
+ const log = getSseEventLog(bus);
7977
+ const send = (e) => {
7978
+ try {
7979
+ res.write(`event: ${e.topic}
7980
+ data: ${JSON.stringify(e.data)}
7981
+ id: ${e.id}
7780
7982
 
7781
- `;
7782
- res.write(frame);
7783
- } catch {
7784
- }
7785
- };
7786
- bus.on(topic, fn);
7787
- listeners.push({ topic, fn });
7983
+ `);
7984
+ } catch {
7985
+ }
7986
+ };
7987
+ const lastId = parseLastEventId(req);
7988
+ let replayedThrough = lastId ?? log.currentSeq();
7989
+ if (lastId !== null) {
7990
+ for (const e of log.replayFrom(lastId)) {
7991
+ send(e);
7992
+ replayedThrough = e.id;
7993
+ }
7788
7994
  }
7995
+ const unsubscribe = log.subscribe((e) => {
7996
+ if (e.id > replayedThrough) send(e);
7997
+ });
7789
7998
  const heartbeat = setInterval(() => {
7790
7999
  try {
7791
8000
  res.write(": heartbeat\n\n");
@@ -7795,7 +8004,7 @@ id: ${newEventId()}
7795
8004
  heartbeat.unref();
7796
8005
  const cleanup = () => {
7797
8006
  clearInterval(heartbeat);
7798
- for (const { topic, fn } of listeners) bus.removeListener(topic, fn);
8007
+ unsubscribe();
7799
8008
  };
7800
8009
  res.on("close", cleanup);
7801
8010
  res.on("finish", cleanup);
@@ -8115,7 +8324,7 @@ async function runGate(projectPath, proposalId) {
8115
8324
  }
8116
8325
 
8117
8326
  // src/proposals/promote.ts
8118
- import * as fs12 from "fs";
8327
+ import * as fs10 from "fs";
8119
8328
  import * as path13 from "path";
8120
8329
  import { parse as parseYaml3, stringify as stringifyYaml } from "yaml";
8121
8330
  import {
@@ -8141,7 +8350,7 @@ function skillDir(projectPath, name) {
8141
8350
  }
8142
8351
  function readIfExists(p) {
8143
8352
  try {
8144
- return fs12.readFileSync(p, "utf-8");
8353
+ return fs10.readFileSync(p, "utf-8");
8145
8354
  } catch {
8146
8355
  return null;
8147
8356
  }
@@ -8187,15 +8396,15 @@ function assertGateReady(proposal) {
8187
8396
  }
8188
8397
  async function promoteNewSkill(projectPath, proposal) {
8189
8398
  const target = skillDir(projectPath, proposal.content.name);
8190
- if (fs12.existsSync(target)) {
8399
+ if (fs10.existsSync(target)) {
8191
8400
  throw new PromotionError(
8192
8401
  `a catalog skill already exists at ${target}; use a refinement proposal to update it`
8193
8402
  );
8194
8403
  }
8195
- fs12.mkdirSync(target, { recursive: true });
8404
+ fs10.mkdirSync(target, { recursive: true });
8196
8405
  const yamlOut = injectProvenanceIntoYaml(proposal.content.skillYaml ?? "", proposal.id);
8197
- fs12.writeFileSync(path13.join(target, "skill.yaml"), yamlOut);
8198
- fs12.writeFileSync(path13.join(target, "SKILL.md"), proposal.content.skillMd ?? "");
8406
+ fs10.writeFileSync(path13.join(target, "skill.yaml"), yamlOut);
8407
+ fs10.writeFileSync(path13.join(target, "SKILL.md"), proposal.content.skillMd ?? "");
8199
8408
  return { skillPath: target };
8200
8409
  }
8201
8410
  async function promoteRefinement(projectPath, proposal) {
@@ -8203,7 +8412,7 @@ async function promoteRefinement(projectPath, proposal) {
8203
8412
  throw new PromotionError("refinement proposal is missing targetSkill");
8204
8413
  }
8205
8414
  const target = skillDir(projectPath, proposal.targetSkill);
8206
- if (!fs12.existsSync(target)) {
8415
+ if (!fs10.existsSync(target)) {
8207
8416
  throw new PromotionError(
8208
8417
  `target skill ${proposal.targetSkill} does not exist at ${target}; cannot refine`
8209
8418
  );
@@ -8216,7 +8425,7 @@ async function promoteRefinement(projectPath, proposal) {
8216
8425
  "no metadata changes detected; check that the reviewer applied the proposed diff before approving"
8217
8426
  );
8218
8427
  }
8219
- fs12.writeFileSync(yamlPath, after);
8428
+ fs10.writeFileSync(yamlPath, after);
8220
8429
  return { skillPath: target };
8221
8430
  }
8222
8431
  async function promote(projectPath, proposalId, decidedBy) {
@@ -8654,7 +8863,7 @@ function handleV1RoutingRoute(req, res, deps) {
8654
8863
  }
8655
8864
 
8656
8865
  // src/server/routes/sessions.ts
8657
- import * as fs13 from "fs/promises";
8866
+ import * as fs11 from "fs/promises";
8658
8867
  import * as path14 from "path";
8659
8868
  import { z as z15 } from "zod";
8660
8869
  var SessionCreateSchema = z15.object({
@@ -8675,12 +8884,12 @@ function extractSessionId(url) {
8675
8884
  }
8676
8885
  async function handleList2(res, sessionsDir) {
8677
8886
  try {
8678
- const entries = await fs13.readdir(sessionsDir, { withFileTypes: true });
8887
+ const entries = await fs11.readdir(sessionsDir, { withFileTypes: true });
8679
8888
  const sessions = [];
8680
8889
  for (const entry of entries) {
8681
8890
  if (!entry.isDirectory()) continue;
8682
8891
  try {
8683
- const content = await fs13.readFile(
8892
+ const content = await fs11.readFile(
8684
8893
  path14.join(sessionsDir, entry.name, "session.json"),
8685
8894
  "utf-8"
8686
8895
  );
@@ -8706,7 +8915,7 @@ async function handleGet2(res, id, sessionsDir) {
8706
8915
  return;
8707
8916
  }
8708
8917
  try {
8709
- const content = await fs13.readFile(path14.join(sessionsDir, id, "session.json"), "utf-8");
8918
+ const content = await fs11.readFile(path14.join(sessionsDir, id, "session.json"), "utf-8");
8710
8919
  jsonResponse(res, 200, JSON.parse(content));
8711
8920
  } catch (err) {
8712
8921
  if (err.code === "ENOENT") {
@@ -8730,8 +8939,8 @@ async function handleCreate(req, res, sessionsDir) {
8730
8939
  return;
8731
8940
  }
8732
8941
  const sessionDir = path14.join(sessionsDir, session.sessionId);
8733
- await fs13.mkdir(sessionDir, { recursive: true });
8734
- await fs13.writeFile(path14.join(sessionDir, "session.json"), JSON.stringify(session, null, 2));
8942
+ await fs11.mkdir(sessionDir, { recursive: true });
8943
+ await fs11.writeFile(path14.join(sessionDir, "session.json"), JSON.stringify(session, null, 2));
8735
8944
  jsonResponse(res, 200, { ok: true });
8736
8945
  } catch {
8737
8946
  jsonResponse(res, 500, { error: "Failed to save session" });
@@ -8747,8 +8956,8 @@ async function handleUpdate(req, res, url, sessionsDir) {
8747
8956
  const body = await readBody(req);
8748
8957
  const updates = z15.record(z15.unknown()).parse(JSON.parse(body));
8749
8958
  const sessionFilePath = path14.join(sessionsDir, id, "session.json");
8750
- const current = JSON.parse(await fs13.readFile(sessionFilePath, "utf-8"));
8751
- await fs13.writeFile(sessionFilePath, JSON.stringify({ ...current, ...updates }, null, 2));
8959
+ const current = JSON.parse(await fs11.readFile(sessionFilePath, "utf-8"));
8960
+ await fs11.writeFile(sessionFilePath, JSON.stringify({ ...current, ...updates }, null, 2));
8752
8961
  jsonResponse(res, 200, { ok: true });
8753
8962
  } catch {
8754
8963
  jsonResponse(res, 500, { error: "Failed to update session" });
@@ -8761,7 +8970,7 @@ async function handleDelete(res, url, sessionsDir) {
8761
8970
  jsonResponse(res, 400, { error: "Missing or invalid sessionId" });
8762
8971
  return;
8763
8972
  }
8764
- await fs13.rm(path14.join(sessionsDir, id), { recursive: true, force: true });
8973
+ await fs11.rm(path14.join(sessionsDir, id), { recursive: true, force: true });
8765
8974
  jsonResponse(res, 200, { ok: true });
8766
8975
  } catch {
8767
8976
  jsonResponse(res, 500, { error: "Failed to delete session" });
@@ -9009,7 +9218,7 @@ function handleLocalModelsRoute(req, res, getStatuses) {
9009
9218
  }
9010
9219
 
9011
9220
  // src/server/static.ts
9012
- import * as fs14 from "fs";
9221
+ import * as fs12 from "fs";
9013
9222
  import * as path15 from "path";
9014
9223
  var MIME_TYPES = {
9015
9224
  ".html": "text/html; charset=utf-8",
@@ -9039,11 +9248,11 @@ function handleStaticFile(req, res, dashboardDir) {
9039
9248
  if (!resolved.startsWith(path15.resolve(dashboardDir))) {
9040
9249
  return serveFile(path15.join(dashboardDir, "index.html"), res);
9041
9250
  }
9042
- if (fs14.existsSync(resolved) && fs14.statSync(resolved).isFile()) {
9251
+ if (fs12.existsSync(resolved) && fs12.statSync(resolved).isFile()) {
9043
9252
  return serveFile(resolved, res);
9044
9253
  }
9045
9254
  const indexPath = path15.join(dashboardDir, "index.html");
9046
- if (fs14.existsSync(indexPath)) {
9255
+ if (fs12.existsSync(indexPath)) {
9047
9256
  return serveFile(indexPath, res);
9048
9257
  }
9049
9258
  return false;
@@ -9052,7 +9261,7 @@ function serveFile(filePath, res) {
9052
9261
  const ext = path15.extname(filePath).toLowerCase();
9053
9262
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
9054
9263
  try {
9055
- const content = fs14.readFileSync(filePath);
9264
+ const content = fs12.readFileSync(filePath);
9056
9265
  res.writeHead(200, { "Content-Type": contentType });
9057
9266
  res.end(content);
9058
9267
  return true;
@@ -9062,7 +9271,7 @@ function serveFile(filePath, res) {
9062
9271
  }
9063
9272
 
9064
9273
  // src/server/plan-watcher.ts
9065
- import * as fs15 from "fs";
9274
+ import * as fs13 from "fs";
9066
9275
  import * as path16 from "path";
9067
9276
  var PlanWatcher = class {
9068
9277
  plansDir;
@@ -9077,11 +9286,11 @@ var PlanWatcher = class {
9077
9286
  * Creates the directory if it does not exist.
9078
9287
  */
9079
9288
  start() {
9080
- fs15.mkdirSync(this.plansDir, { recursive: true });
9081
- this.watcher = fs15.watch(this.plansDir, (eventType, filename) => {
9289
+ fs13.mkdirSync(this.plansDir, { recursive: true });
9290
+ this.watcher = fs13.watch(this.plansDir, (eventType, filename) => {
9082
9291
  if (eventType === "rename" && filename && filename.endsWith(".md")) {
9083
9292
  const filePath = path16.join(this.plansDir, filename);
9084
- if (fs15.existsSync(filePath)) {
9293
+ if (fs13.existsSync(filePath)) {
9085
9294
  void this.handleNewPlan(filename);
9086
9295
  }
9087
9296
  }
@@ -9112,8 +9321,8 @@ var PlanWatcher = class {
9112
9321
  };
9113
9322
 
9114
9323
  // src/auth/tokens.ts
9115
- import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
9116
- import { readFile as readFile8, writeFile as writeFile8, mkdir as mkdir7, rename as rename2 } from "fs/promises";
9324
+ import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
9325
+ import { readFile as readFile6, writeFile as writeFile6, mkdir as mkdir7, rename } from "fs/promises";
9117
9326
  import { dirname as dirname5 } from "path";
9118
9327
  import bcrypt from "bcryptjs";
9119
9328
  import {
@@ -9123,10 +9332,10 @@ import {
9123
9332
  var BCRYPT_ROUNDS = 12;
9124
9333
  var LEGACY_ENV_ID = "tok_legacy_env";
9125
9334
  function genId() {
9126
- return `tok_${randomBytes3(8).toString("hex")}`;
9335
+ return `tok_${randomBytes2(8).toString("hex")}`;
9127
9336
  }
9128
9337
  function genSecret() {
9129
- return randomBytes3(24).toString("base64url");
9338
+ return randomBytes2(24).toString("base64url");
9130
9339
  }
9131
9340
  function parseToken(raw) {
9132
9341
  const dot = raw.indexOf(".");
@@ -9142,7 +9351,7 @@ var TokenStore = class {
9142
9351
  async load() {
9143
9352
  if (this.cache) return this.cache;
9144
9353
  try {
9145
- const raw = await readFile8(this.path, "utf8");
9354
+ const raw = await readFile6(this.path, "utf8");
9146
9355
  const parsed = JSON.parse(raw);
9147
9356
  const list = Array.isArray(parsed) ? parsed : [];
9148
9357
  this.cache = list.map((entry) => {
@@ -9157,9 +9366,9 @@ var TokenStore = class {
9157
9366
  }
9158
9367
  async persist(records) {
9159
9368
  await mkdir7(dirname5(this.path), { recursive: true });
9160
- const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${randomBytes3(4).toString("hex")}`;
9161
- await writeFile8(tmp, JSON.stringify(records, null, 2), "utf8");
9162
- await rename2(tmp, this.path);
9369
+ const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${randomBytes2(4).toString("hex")}`;
9370
+ await writeFile6(tmp, JSON.stringify(records, null, 2), "utf8");
9371
+ await rename(tmp, this.path);
9163
9372
  this.cache = records;
9164
9373
  }
9165
9374
  async create(input) {
@@ -9843,8 +10052,8 @@ var OrchestratorServer = class {
9843
10052
  };
9844
10053
 
9845
10054
  // src/gateway/webhooks/store.ts
9846
- import { randomBytes as randomBytes4 } from "crypto";
9847
- import { readFile as readFile9, writeFile as writeFile9, mkdir as mkdir9, rename as rename3, chmod } from "fs/promises";
10055
+ import { randomBytes as randomBytes3 } from "crypto";
10056
+ import { readFile as readFile7, writeFile as writeFile7, mkdir as mkdir9, rename as rename2, chmod } from "fs/promises";
9848
10057
  import { dirname as dirname7 } from "path";
9849
10058
  import { WebhookSubscriptionSchema } from "@harness-engineering/types";
9850
10059
 
@@ -9870,10 +10079,10 @@ function eventMatches(pattern, type) {
9870
10079
 
9871
10080
  // src/gateway/webhooks/store.ts
9872
10081
  function genId2() {
9873
- return `whk_${randomBytes4(8).toString("hex")}`;
10082
+ return `whk_${randomBytes3(8).toString("hex")}`;
9874
10083
  }
9875
10084
  function genSecret2() {
9876
- return randomBytes4(32).toString("base64url");
10085
+ return randomBytes3(32).toString("base64url");
9877
10086
  }
9878
10087
  var WebhookStore = class {
9879
10088
  constructor(path24) {
@@ -9884,7 +10093,7 @@ var WebhookStore = class {
9884
10093
  async load() {
9885
10094
  if (this.cache) return this.cache;
9886
10095
  try {
9887
- const raw = await readFile9(this.path, "utf8");
10096
+ const raw = await readFile7(this.path, "utf8");
9888
10097
  const parsed = JSON.parse(raw);
9889
10098
  const list = Array.isArray(parsed) ? parsed : [];
9890
10099
  this.cache = list.map((entry) => {
@@ -9898,11 +10107,11 @@ var WebhookStore = class {
9898
10107
  return this.cache;
9899
10108
  }
9900
10109
  async persist(records) {
9901
- const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${randomBytes4(4).toString("hex")}`;
10110
+ const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${randomBytes3(4).toString("hex")}`;
9902
10111
  try {
9903
10112
  await mkdir9(dirname7(this.path), { recursive: true });
9904
- await writeFile9(tmp, JSON.stringify(records, null, 2), { encoding: "utf8", mode: 384 });
9905
- await rename3(tmp, this.path);
10113
+ await writeFile7(tmp, JSON.stringify(records, null, 2), { encoding: "utf8", mode: 384 });
10114
+ await rename2(tmp, this.path);
9906
10115
  await chmod(this.path, 384);
9907
10116
  } catch (err) {
9908
10117
  if (err.code !== "ENOENT") throw err;
@@ -9942,7 +10151,7 @@ var WebhookStore = class {
9942
10151
  };
9943
10152
 
9944
10153
  // src/gateway/webhooks/delivery.ts
9945
- import { randomBytes as randomBytes5 } from "crypto";
10154
+ import { randomBytes as randomBytes4 } from "crypto";
9946
10155
 
9947
10156
  // src/gateway/webhooks/queue.ts
9948
10157
  import Database from "better-sqlite3";
@@ -10152,7 +10361,7 @@ var WebhookDelivery = class {
10152
10361
  enqueue(sub, event) {
10153
10362
  const payload = JSON.stringify(event);
10154
10363
  this.queue.insert({
10155
- id: `dlv_${randomBytes5(8).toString("hex")}`,
10364
+ id: `dlv_${randomBytes4(8).toString("hex")}`,
10156
10365
  subscriptionId: sub.id,
10157
10366
  eventType: event.type,
10158
10367
  payload
@@ -10260,7 +10469,7 @@ var WebhookDelivery = class {
10260
10469
  };
10261
10470
 
10262
10471
  // src/gateway/webhooks/events.ts
10263
- import { randomBytes as randomBytes6 } from "crypto";
10472
+ import { randomBytes as randomBytes5 } from "crypto";
10264
10473
  var WEBHOOK_TOPICS = [
10265
10474
  "interaction.created",
10266
10475
  "interaction.resolved",
@@ -10275,8 +10484,8 @@ var WEBHOOK_TOPICS = [
10275
10484
  "proposal.approved",
10276
10485
  "proposal.rejected"
10277
10486
  ];
10278
- function newEventId2() {
10279
- return `evt_${randomBytes6(8).toString("hex")}`;
10487
+ function newEventId() {
10488
+ return `evt_${randomBytes5(8).toString("hex")}`;
10280
10489
  }
10281
10490
  function wireWebhookFanout({ bus, store, delivery }) {
10282
10491
  const handlers = [];
@@ -10287,7 +10496,7 @@ function wireWebhookFanout({ bus, store, delivery }) {
10287
10496
  const subs = await store.listForEvent(eventType);
10288
10497
  if (subs.length === 0) return;
10289
10498
  const event = {
10290
- id: newEventId2(),
10499
+ id: newEventId(),
10291
10500
  type: eventType,
10292
10501
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10293
10502
  data
@@ -10306,7 +10515,7 @@ function wireWebhookFanout({ bus, store, delivery }) {
10306
10515
  }
10307
10516
 
10308
10517
  // src/gateway/telemetry/fanout.ts
10309
- import { randomBytes as randomBytes7 } from "crypto";
10518
+ import { randomBytes as randomBytes6 } from "crypto";
10310
10519
  import { SpanKind } from "@harness-engineering/core";
10311
10520
  var TOPICS = {
10312
10521
  MAINTENANCE_STARTED: "maintenance:started",
@@ -10329,14 +10538,14 @@ var SPAN_NAME = {
10329
10538
  [TOPICS.DISPATCH_DECISION]: "dispatch_decision",
10330
10539
  [TOPICS.SKILL_INVOCATION]: "skill_invocation"
10331
10540
  };
10332
- function newEventId3() {
10333
- return `evt_${randomBytes7(8).toString("hex")}`;
10541
+ function newEventId2() {
10542
+ return `evt_${randomBytes6(8).toString("hex")}`;
10334
10543
  }
10335
10544
  function newTraceId() {
10336
- return randomBytes7(16).toString("hex");
10545
+ return randomBytes6(16).toString("hex");
10337
10546
  }
10338
10547
  function newSpanId() {
10339
- return randomBytes7(8).toString("hex");
10548
+ return randomBytes6(8).toString("hex");
10340
10549
  }
10341
10550
  function nowNs() {
10342
10551
  return BigInt(Date.now()) * 1000000n;
@@ -10450,7 +10659,7 @@ function wireTelemetryFanout(params) {
10450
10659
  };
10451
10660
  exporter.push(span);
10452
10661
  const gatewayEvent = {
10453
- id: newEventId3(),
10662
+ id: newEventId2(),
10454
10663
  type: TELEMETRY_TYPE[topic],
10455
10664
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10456
10665
  data: payload,
@@ -10644,7 +10853,7 @@ function buildSlackSink(config, options) {
10644
10853
  }
10645
10854
 
10646
10855
  // src/notifications/events.ts
10647
- import { randomBytes as randomBytes8 } from "crypto";
10856
+ import { randomBytes as randomBytes7 } from "crypto";
10648
10857
 
10649
10858
  // src/notifications/envelope.ts
10650
10859
  function asObj(data) {
@@ -10775,8 +10984,8 @@ var NOTIFICATION_TOPICS = [
10775
10984
  "proposal.approved",
10776
10985
  "proposal.rejected"
10777
10986
  ];
10778
- function newEventId4() {
10779
- return `evt_${randomBytes8(8).toString("hex")}`;
10987
+ function newEventId3() {
10988
+ return `evt_${randomBytes7(8).toString("hex")}`;
10780
10989
  }
10781
10990
  function dispatchToEntry(bus, entry, event) {
10782
10991
  const eventType = event.type;
@@ -10811,7 +11020,7 @@ function wireNotificationSinks({ bus, registry }) {
10811
11020
  const entries = registry.list();
10812
11021
  if (entries.length === 0) return;
10813
11022
  const event = {
10814
- id: newEventId4(),
11023
+ id: newEventId3(),
10815
11024
  type: eventType,
10816
11025
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10817
11026
  data
@@ -10922,6 +11131,37 @@ async function scanWorkspaceConfig(workspacePath) {
10922
11131
  return { exitCode: computeScanExitCode(results), results };
10923
11132
  }
10924
11133
 
11134
+ // src/core/lane-persistence.ts
11135
+ import { eventSourcing } from "@harness-engineering/core";
11136
+ import { Err as Err21 } from "@harness-engineering/types";
11137
+ var SIGNAL_TO_LANE = {
11138
+ claim: "claimed",
11139
+ dispatch: "in_progress",
11140
+ success: "in_review",
11141
+ failure: "blocked",
11142
+ abandon: "canceled"
11143
+ };
11144
+ function mapOrchestratorLane(signal) {
11145
+ return SIGNAL_TO_LANE[signal];
11146
+ }
11147
+ async function persistLane(projectPath, issueId, signal) {
11148
+ try {
11149
+ const reg = await eventSourcing.registerTask(projectPath, issueId, []);
11150
+ if (!reg.ok) return reg;
11151
+ return await eventSourcing.transitionLane(projectPath, issueId, mapOrchestratorLane(signal));
11152
+ } catch (err) {
11153
+ return Err21(err instanceof Error ? err : new Error(String(err)));
11154
+ }
11155
+ }
11156
+ async function readPersistedLanes(projectPath) {
11157
+ try {
11158
+ const snap = await eventSourcing.readSnapshot(projectPath);
11159
+ return snap.ok ? snap.value.lanes : { tasks: {} };
11160
+ } catch {
11161
+ return { tasks: {} };
11162
+ }
11163
+ }
11164
+
10925
11165
  // src/maintenance/task-registry.ts
10926
11166
  var BUILT_IN_TASKS = [
10927
11167
  // --- Mechanical-AI ---
@@ -10985,7 +11225,13 @@ var BUILT_IN_TASKS = [
10985
11225
  description: "Detect and fix cross-check violations",
10986
11226
  schedule: "0 6 * * 1",
10987
11227
  branch: "harness-maint/cross-check-fixes",
10988
- checkCommand: ["validate-cross-check"],
11228
+ // `harness cross-check` is a dedicated read-only CLI subcommand that surfaces
11229
+ // JUST cross-artifact consistency (plan→implementation coverage + staleness),
11230
+ // mirroring the `validate_cross_check` MCP tool's core (`runCrossCheck`)
11231
+ // WITHOUT running the full `harness validate` suite. It prints a parseable
11232
+ // `Cross-check: N issues` line and exits 0 (clean) / 1 (N issues), so the
11233
+ // maintenance runner reports real results instead of an honest `failure`.
11234
+ checkCommand: ["cross-check"],
10989
11235
  fixSkill: "harness-cross-check-fix"
10990
11236
  },
10991
11237
  // --- Pure-AI ---
@@ -11044,7 +11290,10 @@ var BUILT_IN_TASKS = [
11044
11290
  description: "Assess overall project health",
11045
11291
  schedule: "0 6 * * *",
11046
11292
  branch: null,
11047
- checkCommand: ["assess_project"]
11293
+ // `assess_project` is an MCP tool name, not a CLI subcommand. The CLI
11294
+ // composite project-health report is `harness insights` (health, entropy,
11295
+ // decay, attention, impact) — a read-only report that records metrics.
11296
+ checkCommand: ["insights"]
11048
11297
  },
11049
11298
  {
11050
11299
  id: "stale-constraints",
@@ -11052,7 +11301,14 @@ var BUILT_IN_TASKS = [
11052
11301
  description: "Detect stale architectural constraints",
11053
11302
  schedule: "0 2 1 * *",
11054
11303
  branch: null,
11055
- checkCommand: ["detect_stale_constraints"]
11304
+ // `harness stale-constraints` is a dedicated read-only CLI subcommand that
11305
+ // surfaces the `detect_stale_constraints` MCP tool's core in-process. It is
11306
+ // precondition-gated on the knowledge graph: with no graph it emits the
11307
+ // "No knowledge graph found. Run `harness scan` first." signature and exits
11308
+ // non-zero, which the runner classifies as `skipped` (not failure). With a
11309
+ // graph it prints a parseable `Stale constraints: N findings` line and exits
11310
+ // 0 (clean) / 1 (N stale), recorded as report-only metrics.
11311
+ checkCommand: ["stale-constraints"]
11056
11312
  },
11057
11313
  {
11058
11314
  id: "graph-refresh",
@@ -11085,7 +11341,8 @@ var BUILT_IN_TASKS = [
11085
11341
  description: "Clean up stale orchestrator sessions",
11086
11342
  schedule: "0 0 * * *",
11087
11343
  branch: null,
11088
- checkCommand: ["cleanup-sessions"]
11344
+ checkCommand: ["cleanup-sessions"],
11345
+ excludeFromHumanSweep: true
11089
11346
  },
11090
11347
  {
11091
11348
  id: "perf-baselines",
@@ -11093,7 +11350,8 @@ var BUILT_IN_TASKS = [
11093
11350
  description: "Update performance baselines",
11094
11351
  schedule: "0 7 * * *",
11095
11352
  branch: null,
11096
- checkCommand: ["perf", "baselines", "update"]
11353
+ checkCommand: ["perf", "baselines", "update"],
11354
+ excludeFromHumanSweep: true
11097
11355
  },
11098
11356
  {
11099
11357
  id: "main-sync",
@@ -11101,7 +11359,8 @@ var BUILT_IN_TASKS = [
11101
11359
  description: "Fast-forward local default branch from origin",
11102
11360
  schedule: "*/15 * * * *",
11103
11361
  branch: null,
11104
- checkCommand: ["harness", "sync-main", "--json"]
11362
+ checkCommand: ["harness", "sync-main", "--json"],
11363
+ excludeFromHumanSweep: true
11105
11364
  },
11106
11365
  // Hermes Phase 4 — one-shot backfill that stamps `provenance: user-authored`
11107
11366
  // on every existing catalog skill. Schedule is Feb 31 (a date that never
@@ -11114,7 +11373,8 @@ var BUILT_IN_TASKS = [
11114
11373
  description: "Backfill provenance: user-authored on every existing skill (one-shot, idempotent)",
11115
11374
  schedule: "0 0 31 2 *",
11116
11375
  branch: null,
11117
- checkCommand: ["backfill-skill-provenance"]
11376
+ checkCommand: ["backfill-skill-provenance"],
11377
+ excludeFromHumanSweep: true
11118
11378
  }
11119
11379
  ];
11120
11380
 
@@ -11181,6 +11441,17 @@ function cronMatchesNow(expression, now) {
11181
11441
  const daysOfWeek = parseField(dowField, 0, 6);
11182
11442
  return minutes.has(minute) && hours.has(hour) && daysOfMonth.has(dayOfMonth) && months.has(month) && daysOfWeek.has(dayOfWeek);
11183
11443
  }
11444
+ function cronMatchesDate(expression, date) {
11445
+ const fields = expression.trim().split(/\s+/);
11446
+ if (fields.length !== 5) {
11447
+ throw new Error(`Invalid cron expression: expected 5 fields, got ${fields.length}`);
11448
+ }
11449
+ const [, , domField, monthField, dowField] = fields;
11450
+ const daysOfMonth = parseField(domField, 1, 31);
11451
+ const months = parseField(monthField, 1, 12);
11452
+ const daysOfWeek = parseField(dowField, 0, 6);
11453
+ return daysOfMonth.has(date.getDate()) && months.has(date.getMonth() + 1) && daysOfWeek.has(date.getDay());
11454
+ }
11184
11455
 
11185
11456
  // src/maintenance/scheduler.ts
11186
11457
  var MaintenanceScheduler = class {
@@ -11430,7 +11701,7 @@ var SingleProcessLeaderElector = class {
11430
11701
  };
11431
11702
 
11432
11703
  // src/maintenance/reporter.ts
11433
- import * as fs16 from "fs";
11704
+ import * as fs14 from "fs";
11434
11705
  import * as path18 from "path";
11435
11706
  import { z as z17 } from "zod";
11436
11707
  var RunResultSchema = z17.object({
@@ -11466,9 +11737,9 @@ var MaintenanceReporter = class {
11466
11737
  */
11467
11738
  async load() {
11468
11739
  try {
11469
- await fs16.promises.mkdir(this.persistDir, { recursive: true });
11740
+ await fs14.promises.mkdir(this.persistDir, { recursive: true });
11470
11741
  const filePath = path18.join(this.persistDir, "history.json");
11471
- const data = await fs16.promises.readFile(filePath, "utf-8");
11742
+ const data = await fs14.promises.readFile(filePath, "utf-8");
11472
11743
  const parsed = z17.array(RunResultSchema).safeParse(JSON.parse(data));
11473
11744
  if (parsed.success) {
11474
11745
  this.history = parsed.data.slice(0, MAX_HISTORY);
@@ -11502,9 +11773,9 @@ var MaintenanceReporter = class {
11502
11773
  */
11503
11774
  async persist() {
11504
11775
  try {
11505
- await fs16.promises.mkdir(this.persistDir, { recursive: true });
11776
+ await fs14.promises.mkdir(this.persistDir, { recursive: true });
11506
11777
  const filePath = path18.join(this.persistDir, "history.json");
11507
- await fs16.promises.writeFile(filePath, JSON.stringify(this.history, null, 2), "utf-8");
11778
+ await fs14.promises.writeFile(filePath, JSON.stringify(this.history, null, 2), "utf-8");
11508
11779
  } catch (err) {
11509
11780
  this.logger.error("MaintenanceReporter: failed to persist history", { error: String(err) });
11510
11781
  }
@@ -11544,20 +11815,20 @@ var TaskRunner = class {
11544
11815
  * @param origin - Hermes Phase 2 trigger-source tag; defaults to `'cron'`
11545
11816
  * when called from the scheduler path.
11546
11817
  */
11547
- async run(task, origin = "cron") {
11818
+ async run(task, origin = "cron", mode = "fix") {
11548
11819
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
11549
11820
  let result;
11550
11821
  let captured;
11551
11822
  try {
11552
11823
  switch (task.type) {
11553
11824
  case "mechanical-ai": {
11554
- const out = await this.runMechanicalAI(task, startedAt);
11825
+ const out = await this.runMechanicalAI(task, startedAt, mode);
11555
11826
  result = out.result;
11556
11827
  captured = out.captured;
11557
11828
  break;
11558
11829
  }
11559
11830
  case "pure-ai":
11560
- result = await this.runPureAI(task, startedAt);
11831
+ result = await this.runPureAI(task, startedAt, mode);
11561
11832
  break;
11562
11833
  case "report-only": {
11563
11834
  const out = await this.runReportOnly(task, startedAt);
@@ -11566,7 +11837,7 @@ var TaskRunner = class {
11566
11837
  break;
11567
11838
  }
11568
11839
  case "housekeeping": {
11569
- const out = await this.runHousekeeping(task, startedAt);
11840
+ const out = await this.runHousekeeping(task, startedAt, mode);
11570
11841
  result = out.result;
11571
11842
  captured = out.captured;
11572
11843
  break;
@@ -11630,7 +11901,8 @@ var TaskRunner = class {
11630
11901
  findings: r2.findings,
11631
11902
  stdout: r2.output,
11632
11903
  stderr: r2.stderr,
11633
- structured: r2.structured ? r2.structured : null
11904
+ structured: r2.structured ? r2.structured : null,
11905
+ executionFailed: false
11634
11906
  };
11635
11907
  }
11636
11908
  if (!task.checkCommand || task.checkCommand.length === 0) {
@@ -11642,7 +11914,8 @@ var TaskRunner = class {
11642
11914
  findings: r.findings,
11643
11915
  stdout: r.output,
11644
11916
  stderr: "",
11645
- structured: null
11917
+ structured: null,
11918
+ executionFailed: r.executionFailed ?? false
11646
11919
  };
11647
11920
  }
11648
11921
  /**
@@ -11667,7 +11940,7 @@ var TaskRunner = class {
11667
11940
  * only if fixable findings exist; persist captured stdout/stderr/context
11668
11941
  * via the output store on the way out.
11669
11942
  */
11670
- async runMechanicalAI(task, startedAt) {
11943
+ async runMechanicalAI(task, startedAt, mode = "fix") {
11671
11944
  if (!task.fixSkill) {
11672
11945
  return wrap(this.failureResult(task.id, startedAt, "mechanical-ai task missing fixSkill"));
11673
11946
  }
@@ -11689,6 +11962,27 @@ var TaskRunner = class {
11689
11962
  } catch (err) {
11690
11963
  return wrap(this.failureResult(task.id, startedAt, String(err)));
11691
11964
  }
11965
+ if (check.executionFailed) {
11966
+ const cls = classifyCheckExecutionFailure(`${check.stdout}
11967
+ ${check.stderr}`);
11968
+ if (cls.kind === "unrunnable") {
11969
+ return wrap(
11970
+ this.failureResult(task.id, startedAt, checkExecutionError(check)),
11971
+ captureFromCheck(check)
11972
+ );
11973
+ }
11974
+ if (cls.kind === "precondition") {
11975
+ return wrap(
11976
+ this.skippedResult(task.id, startedAt, cls.reason ?? "precondition not met"),
11977
+ captureFromCheck(check)
11978
+ );
11979
+ }
11980
+ check = {
11981
+ ...check,
11982
+ executionFailed: false,
11983
+ findings: check.findings > 0 ? check.findings : recoverFindingsCount(check.stdout)
11984
+ };
11985
+ }
11692
11986
  const promptContext = await this.composePromptContext(task);
11693
11987
  const baseCaptured = {
11694
11988
  stdout: check.stdout,
@@ -11697,13 +11991,14 @@ var TaskRunner = class {
11697
11991
  ...promptContext ? { context: promptContext } : {}
11698
11992
  };
11699
11993
  const wakeAgentExplicitlyFalse = check.structured !== null && typeof check.structured === "object" && check.structured.wakeAgent === false;
11700
- if (check.findings === 0 || wakeAgentExplicitlyFalse) {
11994
+ if (check.findings === 0 || wakeAgentExplicitlyFalse || mode === "report") {
11995
+ const reportedWithFindings = mode === "report" && check.findings > 0;
11701
11996
  return {
11702
11997
  result: {
11703
11998
  taskId: task.id,
11704
11999
  startedAt,
11705
12000
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
11706
- status: "no-issues",
12001
+ status: reportedWithFindings ? "success" : "no-issues",
11707
12002
  findings: check.findings,
11708
12003
  fixed: 0,
11709
12004
  prUrl: null,
@@ -11773,7 +12068,19 @@ var TaskRunner = class {
11773
12068
  /**
11774
12069
  * Pure-AI: always dispatch agent with configured skill.
11775
12070
  */
11776
- async runPureAI(task, startedAt) {
12071
+ async runPureAI(task, startedAt, mode = "fix") {
12072
+ if (mode === "report") {
12073
+ return {
12074
+ taskId: task.id,
12075
+ startedAt,
12076
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
12077
+ status: "no-issues",
12078
+ findings: 0,
12079
+ fixed: 0,
12080
+ prUrl: null,
12081
+ prUpdated: false
12082
+ };
12083
+ }
11777
12084
  if (!task.fixSkill) {
11778
12085
  return this.failureResult(task.id, startedAt, "pure-ai task missing fixSkill");
11779
12086
  }
@@ -11847,6 +12154,27 @@ var TaskRunner = class {
11847
12154
  return wrap(this.failureResult(task.id, startedAt, String(err)));
11848
12155
  }
11849
12156
  const parsed = parseStatusLine(check.stdout);
12157
+ if (parsed === null && check.executionFailed) {
12158
+ const cls = classifyCheckExecutionFailure(`${check.stdout}
12159
+ ${check.stderr}`);
12160
+ if (cls.kind === "unrunnable") {
12161
+ return wrap(
12162
+ this.failureResult(task.id, startedAt, checkExecutionError(check)),
12163
+ captureFromCheck(check)
12164
+ );
12165
+ }
12166
+ if (cls.kind === "precondition") {
12167
+ return wrap(
12168
+ this.skippedResult(task.id, startedAt, cls.reason ?? "precondition not met"),
12169
+ captureFromCheck(check)
12170
+ );
12171
+ }
12172
+ check = {
12173
+ ...check,
12174
+ executionFailed: false,
12175
+ findings: check.findings > 0 ? check.findings : recoverFindingsCount(check.stdout)
12176
+ };
12177
+ }
11850
12178
  const status = parsed?.status ?? "success";
11851
12179
  const findings = parsed === null ? check.findings : typeof parsed.candidatesFound === "number" ? parsed.candidatesFound : 0;
11852
12180
  const result = {
@@ -11880,7 +12208,20 @@ var TaskRunner = class {
11880
12208
  * Hermes Phase 2: a `checkScript` may replace `checkCommand` for housekeeping
11881
12209
  * tasks; the runner falls through to the same JSON-status parsing path.
11882
12210
  */
11883
- async runHousekeeping(task, startedAt) {
12211
+ async runHousekeeping(task, startedAt, mode = "fix") {
12212
+ if (mode === "report") {
12213
+ return wrap({
12214
+ taskId: task.id,
12215
+ startedAt,
12216
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
12217
+ status: "skipped",
12218
+ findings: 0,
12219
+ fixed: 0,
12220
+ prUrl: null,
12221
+ prUpdated: false,
12222
+ error: "skipped in report mode: housekeeping may mutate and report runs are read-only"
12223
+ });
12224
+ }
11884
12225
  if (!task.checkCommand && !task.checkScript) {
11885
12226
  return wrap(
11886
12227
  this.failureResult(
@@ -11947,10 +12288,86 @@ var TaskRunner = class {
11947
12288
  error
11948
12289
  };
11949
12290
  }
12291
+ /**
12292
+ * A precondition-gated check (e.g. `predict` with <3 snapshots, or a
12293
+ * graph-backed check before `harness scan`). The command is correctly
12294
+ * configured; the repo just lacks the state it needs. Reported as `skipped`
12295
+ * — distinct from a hard `failure` — carrying the refusal line as the reason
12296
+ * (surfaced in the run-report summary column). ADR 0050.
12297
+ */
12298
+ skippedResult(taskId, startedAt, reason) {
12299
+ return {
12300
+ taskId,
12301
+ startedAt,
12302
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
12303
+ status: "skipped",
12304
+ findings: 0,
12305
+ fixed: 0,
12306
+ prUrl: null,
12307
+ prUpdated: false,
12308
+ error: reason
12309
+ };
12310
+ }
11950
12311
  };
11951
12312
  function wrap(result, captured) {
11952
12313
  return captured ? { result, captured } : { result };
11953
12314
  }
12315
+ function checkExecutionError(check) {
12316
+ const detail = (check.stderr || check.stdout || "").trim();
12317
+ return detail ? `check command failed to execute: ${detail}` : "check command failed to execute";
12318
+ }
12319
+ function captureFromCheck(check) {
12320
+ return { stdout: check.stdout, stderr: check.stderr, structured: check.structured };
12321
+ }
12322
+ var PRECONDITION_PATTERNS = [
12323
+ /requires at least \d+ snapshots?/i,
12324
+ /Run ["'`]?harness snapshot/i,
12325
+ /no knowledge graph found/i,
12326
+ /Run ["'`]?harness scan/i,
12327
+ /no graph (?:available|found)/i
12328
+ ];
12329
+ var UNRUNNABLE_PATTERNS = [
12330
+ /unknown command/i,
12331
+ /unknown option/i,
12332
+ /\bENOENT\b/,
12333
+ /command not found/i,
12334
+ /cannot find module/i,
12335
+ // A timed-out check did not complete — it is a hard failure, never a
12336
+ // "ran-no-count" success. The runners synthesize this message on SIGTERM.
12337
+ /timed out/i,
12338
+ /\bETIMEDOUT\b/
12339
+ ];
12340
+ var TIMEOUT_SIGNATURE = /check timed out after \d+\s*ms/i;
12341
+ function explicitFindingsCount(output) {
12342
+ const after = output.match(/(?:findings?|issues?|violations?|errors?)\s*[:=]\s*(\d+)/i);
12343
+ if (after) return parseInt(after[1], 10);
12344
+ const before = output.match(/(\d+)\s+(?:findings?|issues?|violations?|errors?)\b/i);
12345
+ if (before) return parseInt(before[1], 10);
12346
+ return null;
12347
+ }
12348
+ function recoverFindingsCount(output) {
12349
+ return explicitFindingsCount(output) ?? 1;
12350
+ }
12351
+ function firstMeaningfulLine(output) {
12352
+ const line = output.split("\n").map((l) => l.trim()).find(Boolean);
12353
+ if (!line) return "precondition not met";
12354
+ return line.replace(/^[x✗✓!-]\s*/u, "").trim();
12355
+ }
12356
+ var CLASSIFY_HEAD_LINES = 3;
12357
+ function classifyCheckExecutionFailure(output) {
12358
+ const text = (output ?? "").trim();
12359
+ if (text.length === 0) return { kind: "unrunnable" };
12360
+ if (TIMEOUT_SIGNATURE.test(text)) return { kind: "unrunnable" };
12361
+ if (explicitFindingsCount(text) !== null) return { kind: "ran-no-count" };
12362
+ const head = text.split("\n").filter((l) => l.trim()).slice(0, CLASSIFY_HEAD_LINES).join("\n");
12363
+ for (const re of PRECONDITION_PATTERNS) {
12364
+ if (re.test(head)) return { kind: "precondition", reason: firstMeaningfulLine(text) };
12365
+ }
12366
+ for (const re of UNRUNNABLE_PATTERNS) {
12367
+ if (re.test(head)) return { kind: "unrunnable" };
12368
+ }
12369
+ return { kind: "ran-no-count" };
12370
+ }
11954
12371
  function parseStatusLine(output) {
11955
12372
  const lines = output.split("\n").map((l) => l.trim()).filter(Boolean);
11956
12373
  for (let i = lines.length - 1; i >= 0; i--) {
@@ -12099,8 +12516,49 @@ function heuristicResult(stdout, stderr, exitedAbnormally) {
12099
12516
  };
12100
12517
  }
12101
12518
 
12519
+ // src/maintenance/check-runner.ts
12520
+ import { execFile as nodeExecFile } from "child_process";
12521
+ import { promisify as promisify4 } from "util";
12522
+ var MAINTENANCE_CHECK_MAX_BUFFER = 64 * 1024 * 1024;
12523
+ var MAINTENANCE_CHECK_TIMEOUT_MS = 3e5;
12524
+ var FINDINGS_RE = /(\d+)\s+(?:finding|issue|violation|error)/i;
12525
+ var nodeExecFileAsync = promisify4(nodeExecFile);
12526
+ function isCheckTimeoutError(e) {
12527
+ return e.killed === true || e.signal === "SIGTERM" || e.code === "ETIMEDOUT";
12528
+ }
12529
+ async function runHarnessCheck(spawn7, cwd, opts = {}) {
12530
+ const execFileAsync2 = opts.execFileAsync ?? nodeExecFileAsync;
12531
+ const timeoutMs = opts.timeoutMs ?? MAINTENANCE_CHECK_TIMEOUT_MS;
12532
+ const maxBuffer = opts.maxBuffer ?? MAINTENANCE_CHECK_MAX_BUFFER;
12533
+ try {
12534
+ const { stdout } = await execFileAsync2(spawn7.file, spawn7.args, {
12535
+ cwd,
12536
+ timeout: timeoutMs,
12537
+ maxBuffer
12538
+ });
12539
+ const text = String(stdout);
12540
+ const m = text.match(FINDINGS_RE);
12541
+ const findings = m ? parseInt(m[1], 10) : 0;
12542
+ return { passed: findings === 0, findings, output: text, executionFailed: false };
12543
+ } catch (err) {
12544
+ const e = err;
12545
+ let output = [e.stdout, e.stderr].map((v) => v == null ? "" : String(v)).filter(Boolean).join("\n");
12546
+ if (isCheckTimeoutError(e)) {
12547
+ const note = `check timed out after ${timeoutMs}ms`;
12548
+ output = output.trim() ? `${output}
12549
+ ${note}` : note;
12550
+ return { passed: false, findings: 0, output, executionFailed: true };
12551
+ }
12552
+ const m = output.match(FINDINGS_RE);
12553
+ if (m) {
12554
+ return { passed: false, findings: parseInt(m[1], 10), output, executionFailed: false };
12555
+ }
12556
+ return { passed: false, findings: 0, output, executionFailed: true };
12557
+ }
12558
+ }
12559
+
12102
12560
  // src/maintenance/output-store.ts
12103
- import * as fs17 from "fs";
12561
+ import * as fs15 from "fs";
12104
12562
  import * as path20 from "path";
12105
12563
  var DEFAULT_RETENTION = {
12106
12564
  runs: 50,
@@ -12141,13 +12599,13 @@ var TaskOutputStore = class {
12141
12599
  async write(taskId, entry, retention) {
12142
12600
  this.ensureSafeTaskId(taskId);
12143
12601
  const dir = this.dirFor(taskId);
12144
- await fs17.promises.mkdir(dir, { recursive: true });
12602
+ await fs15.promises.mkdir(dir, { recursive: true });
12145
12603
  const fileName = `${sanitizeIso(entry.completedAt || (/* @__PURE__ */ new Date()).toISOString())}.json`;
12146
12604
  const filePath = path20.join(dir, fileName);
12147
12605
  const tmpPath = `${filePath}.tmp`;
12148
12606
  const payload = JSON.stringify(entry, null, 2);
12149
- await fs17.promises.writeFile(tmpPath, payload, "utf-8");
12150
- await fs17.promises.rename(tmpPath, filePath);
12607
+ await fs15.promises.writeFile(tmpPath, payload, "utf-8");
12608
+ await fs15.promises.rename(tmpPath, filePath);
12151
12609
  try {
12152
12610
  await this.applyRetention(taskId, retention);
12153
12611
  } catch (err) {
@@ -12198,7 +12656,7 @@ var TaskOutputStore = class {
12198
12656
  }
12199
12657
  async readEntry(filePath) {
12200
12658
  try {
12201
- const buf = await fs17.promises.readFile(filePath, "utf-8");
12659
+ const buf = await fs15.promises.readFile(filePath, "utf-8");
12202
12660
  const parsed = JSON.parse(buf);
12203
12661
  return parsed;
12204
12662
  } catch {
@@ -12220,7 +12678,7 @@ var TaskOutputStore = class {
12220
12678
  const toRemove = /* @__PURE__ */ new Set([...overflow, ...aged]);
12221
12679
  for (const name of toRemove) {
12222
12680
  try {
12223
- await fs17.promises.unlink(path20.join(dir, name));
12681
+ await fs15.promises.unlink(path20.join(dir, name));
12224
12682
  } catch {
12225
12683
  }
12226
12684
  }
@@ -12229,7 +12687,7 @@ var TaskOutputStore = class {
12229
12687
  async function listJsonFilesDescending(dir) {
12230
12688
  let names;
12231
12689
  try {
12232
- names = await fs17.promises.readdir(dir);
12690
+ names = await fs15.promises.readdir(dir);
12233
12691
  } catch {
12234
12692
  return [];
12235
12693
  }
@@ -12338,7 +12796,7 @@ var fallbackLogger3 = {
12338
12796
  };
12339
12797
 
12340
12798
  // src/maintenance/custom-task-validator.ts
12341
- import { Ok as Ok23, Err as Err20 } from "@harness-engineering/types";
12799
+ import { Ok as Ok23, Err as Err22 } from "@harness-engineering/types";
12342
12800
  var ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
12343
12801
  var REQUIRED_FIELDS_BY_TYPE = {
12344
12802
  "mechanical-ai": ["branch", "fixSkill"],
@@ -12358,7 +12816,7 @@ function validateCustomTasks(customTasks, builtIns, deps = {}) {
12358
12816
  validateOne(id, task, builtInIds, allIds, deps, errors);
12359
12817
  }
12360
12818
  detectCycles(customTasks, builtIns, errors);
12361
- return errors.length === 0 ? Ok23(void 0) : Err20(errors);
12819
+ return errors.length === 0 ? Ok23(void 0) : Err22(errors);
12362
12820
  }
12363
12821
  function validateOne(id, task, builtInIds, allIds, deps, errors) {
12364
12822
  const prefix = `customTasks.${id}`;
@@ -12547,6 +13005,11 @@ function deriveSeedPaths(config) {
12547
13005
  const roadmapPath = config.tracker.kind === "roadmap" && config.tracker.filePath ? config.tracker.filePath : "docs/roadmap.md";
12548
13006
  return [".harness/proposals", roadmapPath];
12549
13007
  }
13008
+ function normalizeHarnessCommand(command) {
13009
+ if (command.length === 0) return [];
13010
+ if (command[0] === "harness") return command;
13011
+ return ["harness", ...command];
13012
+ }
12550
13013
  var Orchestrator = class extends EventEmitter {
12551
13014
  state;
12552
13015
  config;
@@ -12673,6 +13136,11 @@ var Orchestrator = class extends EventEmitter {
12673
13136
  abortControllers = /* @__PURE__ */ new Map();
12674
13137
  /** Guards against overlapping ticks when a tick takes longer than the polling interval */
12675
13138
  tickInProgress = false;
13139
+ /** Phase 4 (DLane-5): lanes read back from the durable log on first tick, for
13140
+ * observability only — NOT fed into reconciliation. Empty until the first tick. */
13141
+ persistedLanes = { tasks: {} };
13142
+ /** Ensures the lane read-back diagnostic runs at most once (first tick). */
13143
+ laneReadbackDone = false;
12676
13144
  /** Timestamp of the last stale branch sweep (at most once per hour) */
12677
13145
  lastBranchSweepMs = 0;
12678
13146
  /** Current tick-phase activity visible to the dashboard */
@@ -12919,48 +13387,30 @@ var Orchestrator = class extends EventEmitter {
12919
13387
  const logger = this.logger;
12920
13388
  const checkRunner = {
12921
13389
  run: async (command, cwd) => {
12922
- const { execFile: execFile7 } = await import("child_process");
12923
- const { promisify: promisify5 } = await import("util");
12924
- const execFileAsync2 = promisify5(execFile7);
12925
- const [cmd, ...args] = command;
12926
- if (!cmd) return { passed: true, findings: 0, output: "" };
12927
- try {
12928
- const { stdout } = await execFileAsync2(cmd, args, { cwd, timeout: 12e4 });
12929
- const findingsMatch = stdout.match(/(\d+)\s+(?:finding|issue|violation|error)/i);
12930
- const findings = findingsMatch ? parseInt(findingsMatch[1], 10) : 0;
12931
- return { passed: findings === 0, findings, output: stdout };
12932
- } catch (err) {
12933
- const error = err;
12934
- const output = [error.stdout, error.stderr].filter(Boolean).join("\n");
12935
- const findingsMatch = output.match(/(\d+)\s+(?:finding|issue|violation|error)/i);
12936
- const findings = findingsMatch ? parseInt(findingsMatch[1], 10) : 1;
12937
- return { passed: false, findings, output };
12938
- }
12939
- }
12940
- };
12941
- const agentDispatcher = {
12942
- dispatch: async (skill, branch, backendName, cwd) => {
12943
- logger.info(
12944
- "Maintenance agent dispatcher invoked (stub \u2014 skill dispatch integration pending)",
12945
- {
12946
- skill,
12947
- branch,
12948
- backendName,
12949
- cwd
12950
- }
12951
- );
12952
- return { producedCommits: false, fixed: 0 };
13390
+ const [cmd, ...args] = normalizeHarnessCommand(command);
13391
+ if (!cmd) return { passed: true, findings: 0, output: "", executionFailed: false };
13392
+ return runHarnessCheck({ file: cmd, args }, cwd);
12953
13393
  }
12954
13394
  };
13395
+ const resolveBackend2 = makeBackendResolver(this.getBackends());
13396
+ const agentDispatcher = createAgentDispatcher({
13397
+ resolveBackend: resolveBackend2,
13398
+ git: (args, cwd) => execFileSync("git", args, { cwd, encoding: "utf-8" }).toString().trim(),
13399
+ logger
13400
+ });
12955
13401
  const commandExecutor = {
12956
13402
  exec: async (command, cwd) => {
12957
13403
  const { execFile: execFile7 } = await import("child_process");
12958
- const { promisify: promisify5 } = await import("util");
12959
- const execFileAsync2 = promisify5(execFile7);
12960
- const [cmd, ...args] = command;
13404
+ const { promisify: promisify6 } = await import("util");
13405
+ const execFileAsync2 = promisify6(execFile7);
13406
+ const [cmd, ...args] = normalizeHarnessCommand(command);
12961
13407
  if (!cmd) return { stdout: "" };
12962
13408
  try {
12963
- const { stdout } = await execFileAsync2(cmd, args, { cwd, timeout: 12e4 });
13409
+ const { stdout } = await execFileAsync2(cmd, args, {
13410
+ cwd,
13411
+ timeout: MAINTENANCE_CHECK_TIMEOUT_MS,
13412
+ maxBuffer: MAINTENANCE_CHECK_MAX_BUFFER
13413
+ });
12964
13414
  return { stdout: String(stdout) };
12965
13415
  } catch (err) {
12966
13416
  logger.warn("Maintenance command execution failed", {
@@ -13095,6 +13545,10 @@ ${messages}`);
13095
13545
  async asyncTick() {
13096
13546
  await this.ensureClaimManager();
13097
13547
  await this.intelligenceRunner.loadPersistedData();
13548
+ if (!this.laneReadbackDone) {
13549
+ this.laneReadbackDone = true;
13550
+ await this.readBackPersistedLanes();
13551
+ }
13098
13552
  const nowMs = Date.now();
13099
13553
  this.setTickActivity("fetching", "Polling tracker for candidates");
13100
13554
  const candidatesResult = await this.tracker.fetchCandidateIssues();
@@ -13247,12 +13701,48 @@ ${messages}`);
13247
13701
  break;
13248
13702
  case "escalate":
13249
13703
  await this.handleEscalation(effect);
13704
+ await this.persistLaneSafe(effect.issueId, "abandon");
13250
13705
  break;
13251
13706
  case "claim":
13252
13707
  await this.handleClaimEffect(effect);
13253
13708
  break;
13254
13709
  }
13255
13710
  }
13711
+ /**
13712
+ * Phase 4 (DLane-5): persist an orchestrator lane transition to the durable
13713
+ * core event log at the effect boundary. NEVER throws — `persistLane` returns
13714
+ * an `Err` Result on failure, which is logged and swallowed here so a
13715
+ * lane-persistence failure can never break orchestrator dispatch.
13716
+ */
13717
+ async persistLaneSafe(issueId, signal) {
13718
+ const r = await persistLane(this.projectRoot, issueId, signal);
13719
+ if (!r.ok) {
13720
+ this.logger.warn(`lane persist failed for ${issueId} (${signal}): ${r.error.message}`);
13721
+ }
13722
+ }
13723
+ /**
13724
+ * Phase 4 (DLane-5): read persisted task lanes back from the durable log and
13725
+ * log a one-line summary. Stores the projection on `this.persistedLanes` for
13726
+ * observability. Read-only — never feeds reconciliation, never throws.
13727
+ */
13728
+ async readBackPersistedLanes() {
13729
+ const lanes = await readPersistedLanes(this.projectRoot);
13730
+ this.persistedLanes = lanes;
13731
+ const entries = Object.keys(lanes.tasks).map((id) => `${id}:${lanes.tasks[id]?.lane}`);
13732
+ const nonTerminal = entries.filter((e) => !e.endsWith(":done") && !e.endsWith(":canceled"));
13733
+ this.logger.info(
13734
+ `Lane read-back on startup: ${entries.length} persisted task(s), ${nonTerminal.length} non-terminal`,
13735
+ { nonTerminal }
13736
+ );
13737
+ }
13738
+ /**
13739
+ * Phase 4 (DLane-5): the task lanes most recently read back from the durable
13740
+ * log on startup, exposed for external observability. Read-only — a fresh
13741
+ * `{ tasks: {} }` until the first tick's read-back has run.
13742
+ */
13743
+ getPersistedLanes() {
13744
+ return this.persistedLanes;
13745
+ }
13256
13746
  /**
13257
13747
  * Guards workspace cleanup by checking whether the agent pushed a branch
13258
13748
  * that does not yet have a pull request. If so, the worktree is preserved
@@ -13439,6 +13929,7 @@ ${messages}`);
13439
13929
  }
13440
13930
  return;
13441
13931
  }
13932
+ await this.persistLaneSafe(effect.issue.id, "claim");
13442
13933
  await this.postClaimComment(effect.issue);
13443
13934
  await this.dispatchIssue(effect.issue, effect.attempt, effect.backend);
13444
13935
  }
@@ -13499,6 +13990,7 @@ ${messages}`);
13499
13990
  this.logger.info(`Dispatching issue: ${issue.identifier} (attempt ${attempt})`, {
13500
13991
  issueId: issue.id
13501
13992
  });
13993
+ await this.persistLaneSafe(issue.id, "dispatch");
13502
13994
  try {
13503
13995
  const workspaceResult = await this.workspace.ensureWorkspace(issue.identifier);
13504
13996
  if (!workspaceResult.ok) throw workspaceResult.error;
@@ -13707,6 +14199,7 @@ ${messages}`);
13707
14199
  * Informs the state machine that an agent worker has exited.
13708
14200
  */
13709
14201
  async emitWorkerExit(issueId, reason, attempt, error) {
14202
+ await this.persistLaneSafe(issueId, reason === "normal" ? "success" : "failure");
13710
14203
  await this.completionHandler.handleWorkerExit(
13711
14204
  issueId,
13712
14205
  reason,
@@ -14242,11 +14735,11 @@ function launchTUI(orchestrator) {
14242
14735
  }
14243
14736
 
14244
14737
  // src/maintenance/sync-main.ts
14245
- import { execFile as nodeExecFile } from "child_process";
14246
- import { promisify as promisify4 } from "util";
14738
+ import { execFile as nodeExecFile2 } from "child_process";
14739
+ import { promisify as promisify5 } from "util";
14247
14740
  var DEFAULT_TIMEOUT_MS3 = 6e4;
14248
14741
  async function git(execFileFn, args, cwd, timeoutMs) {
14249
- const exec = promisify4(execFileFn);
14742
+ const exec = promisify5(execFileFn);
14250
14743
  const { stdout, stderr } = await exec("git", args, { cwd, timeout: timeoutMs });
14251
14744
  return { stdout: String(stdout), stderr: String(stderr) };
14252
14745
  }
@@ -14308,7 +14801,7 @@ async function isAncestor(execFileFn, a, b, cwd, timeoutMs) {
14308
14801
  }
14309
14802
  }
14310
14803
  async function syncMain(repoRoot, opts = {}) {
14311
- const execFileFn = opts.execFileFn ?? nodeExecFile;
14804
+ const execFileFn = opts.execFileFn ?? nodeExecFile2;
14312
14805
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
14313
14806
  try {
14314
14807
  const originRef = await resolveOriginDefault(execFileFn, repoRoot, timeoutMs);
@@ -14385,8 +14878,56 @@ async function syncMain(repoRoot, opts = {}) {
14385
14878
  }
14386
14879
  }
14387
14880
 
14881
+ // src/maintenance/overdue.ts
14882
+ var LOOKBACK_DAYS = 366;
14883
+ function previousFireTime(schedule, now) {
14884
+ let day = new Date(now.getFullYear(), now.getMonth(), now.getDate());
14885
+ for (let d = 0; d <= LOOKBACK_DAYS; d++) {
14886
+ if (cronMatchesDate(schedule, day)) {
14887
+ const dayStartMs = day.getTime();
14888
+ const scanStart = d === 0 ? new Date(
14889
+ now.getFullYear(),
14890
+ now.getMonth(),
14891
+ now.getDate(),
14892
+ now.getHours(),
14893
+ now.getMinutes()
14894
+ ) : new Date(day.getFullYear(), day.getMonth(), day.getDate(), 23, 59);
14895
+ for (let ms = scanStart.getTime(); ms >= dayStartMs; ms -= 6e4) {
14896
+ const candidate = new Date(ms);
14897
+ if (cronMatchesNow(schedule, candidate)) return candidate;
14898
+ }
14899
+ }
14900
+ day = new Date(day.getFullYear(), day.getMonth(), day.getDate() - 1);
14901
+ }
14902
+ return null;
14903
+ }
14904
+ function isSatisfyingRun(r) {
14905
+ return r.status === "success" || r.status === "no-issues";
14906
+ }
14907
+ function isOverdue(task, history, now) {
14908
+ const satisfyingRuns = history.filter((r) => r.taskId === task.id && isSatisfyingRun(r));
14909
+ const fire = previousFireTime(task.schedule, now);
14910
+ if (fire === null) return satisfyingRuns.length === 0;
14911
+ const fireMs = fire.getTime();
14912
+ const satisfied = satisfyingRuns.some((r) => new Date(r.completedAt).getTime() >= fireMs);
14913
+ return !satisfied;
14914
+ }
14915
+ function selectTasks(tasks, history, filter) {
14916
+ const eligible = tasks.filter((t) => t.excludeFromHumanSweep !== true);
14917
+ switch (filter.mode) {
14918
+ case "all":
14919
+ return eligible;
14920
+ case "ids": {
14921
+ const wanted = new Set(filter.ids ?? []);
14922
+ return eligible.filter((t) => wanted.has(t.id));
14923
+ }
14924
+ case "overdue":
14925
+ return eligible.filter((t) => isOverdue(t, history, filter.now));
14926
+ }
14927
+ }
14928
+
14388
14929
  // src/sessions/search-index.ts
14389
- import * as fs18 from "fs";
14930
+ import * as fs16 from "fs";
14390
14931
  import * as path22 from "path";
14391
14932
  import Database2 from "better-sqlite3";
14392
14933
  import { INDEXED_FILE_KINDS } from "@harness-engineering/types";
@@ -14447,7 +14988,7 @@ var SqliteSearchIndex = class {
14447
14988
  removeSessionStmt;
14448
14989
  totalStmt;
14449
14990
  constructor(dbPath) {
14450
- fs18.mkdirSync(path22.dirname(dbPath), { recursive: true });
14991
+ fs16.mkdirSync(path22.dirname(dbPath), { recursive: true });
14451
14992
  this.db = new Database2(dbPath);
14452
14993
  this.db.pragma("journal_mode = WAL");
14453
14994
  this.db.pragma("synchronous = NORMAL");
@@ -14553,12 +15094,12 @@ function indexSessionDirectory(idx, args) {
14553
15094
  for (const kind of kinds) {
14554
15095
  const fileName = FILE_KIND_TO_FILENAME[kind];
14555
15096
  const filePath = path22.join(args.sessionDir, fileName);
14556
- if (!fs18.existsSync(filePath)) continue;
14557
- let body = fs18.readFileSync(filePath, "utf8");
15097
+ if (!fs16.existsSync(filePath)) continue;
15098
+ let body = fs16.readFileSync(filePath, "utf8");
14558
15099
  if (Buffer.byteLength(body, "utf8") > cap) {
14559
15100
  body = body.slice(0, cap) + "\n\n[TRUNCATED]";
14560
15101
  }
14561
- const stat = fs18.statSync(filePath);
15102
+ const stat = fs16.statSync(filePath);
14562
15103
  const relPath = path22.relative(args.projectPath, filePath).replaceAll("\\", "/");
14563
15104
  idx.upsertSessionDoc({
14564
15105
  sessionId: args.sessionId,
@@ -14580,8 +15121,8 @@ function reindexFromArchive(projectPath, opts = {}) {
14580
15121
  idx.resetArchived();
14581
15122
  let sessionsIndexed = 0;
14582
15123
  let docsWritten = 0;
14583
- if (fs18.existsSync(archiveBase)) {
14584
- const entries = fs18.readdirSync(archiveBase, { withFileTypes: true });
15124
+ if (fs16.existsSync(archiveBase)) {
15125
+ const entries = fs16.readdirSync(archiveBase, { withFileTypes: true });
14585
15126
  for (const entry of entries) {
14586
15127
  if (!entry.isDirectory()) continue;
14587
15128
  const sessionDir = path22.join(archiveBase, entry.name);
@@ -14604,12 +15145,12 @@ function reindexFromArchive(projectPath, opts = {}) {
14604
15145
  }
14605
15146
 
14606
15147
  // src/sessions/summarize.ts
14607
- import * as fs19 from "fs";
15148
+ import * as fs17 from "fs";
14608
15149
  import * as path23 from "path";
14609
15150
  import {
14610
15151
  SessionSummarySchema
14611
15152
  } from "@harness-engineering/types";
14612
- import { Ok as Ok24, Err as Err21 } from "@harness-engineering/types";
15153
+ import { Ok as Ok24, Err as Err23 } from "@harness-engineering/types";
14613
15154
  var LLM_SUMMARY_FILE = "llm-summary.md";
14614
15155
  var SUMMARY_INPUT_FILES = [
14615
15156
  { filename: "summary.md", kind: "summary" },
@@ -14636,9 +15177,9 @@ function readInputCorpus(archiveDir) {
14636
15177
  const parts = [];
14637
15178
  for (const { filename, kind } of SUMMARY_INPUT_FILES) {
14638
15179
  const p = path23.join(archiveDir, filename);
14639
- if (!fs19.existsSync(p)) continue;
15180
+ if (!fs17.existsSync(p)) continue;
14640
15181
  try {
14641
- const content = fs19.readFileSync(p, "utf8");
15182
+ const content = fs17.readFileSync(p, "utf8");
14642
15183
  if (content.trim().length === 0) continue;
14643
15184
  parts.push(`## FILE: ${kind}
14644
15185
 
@@ -14700,17 +15241,17 @@ status: failed
14700
15241
 
14701
15242
  - reason: ${reason}
14702
15243
  `;
14703
- fs19.writeFileSync(filePath, body, "utf8");
15244
+ fs17.writeFileSync(filePath, body, "utf8");
14704
15245
  return filePath;
14705
15246
  }
14706
15247
  async function summarizeArchivedSession(ctx) {
14707
15248
  const writeStubOnError = ctx.writeStubOnError ?? true;
14708
- if (!fs19.existsSync(ctx.archiveDir)) {
14709
- return Err21(new Error(`archive directory not found: ${ctx.archiveDir}`));
15249
+ if (!fs17.existsSync(ctx.archiveDir)) {
15250
+ return Err23(new Error(`archive directory not found: ${ctx.archiveDir}`));
14710
15251
  }
14711
15252
  const corpus = readInputCorpus(ctx.archiveDir);
14712
15253
  if (corpus.trim().length === 0) {
14713
- return Err21(new Error(`no summary input files found in ${ctx.archiveDir}`));
15254
+ return Err23(new Error(`no summary input files found in ${ctx.archiveDir}`));
14714
15255
  }
14715
15256
  const inputBudgetTokens = ctx.config?.inputBudgetTokens ?? DEFAULT_INPUT_BUDGET_TOKENS;
14716
15257
  const truncated = truncateForBudget(corpus, inputBudgetTokens);
@@ -14743,7 +15284,7 @@ async function summarizeArchivedSession(ctx) {
14743
15284
  } catch {
14744
15285
  }
14745
15286
  }
14746
- return Err21(
15287
+ return Err23(
14747
15288
  new Error(`session summary failed: ${reason}` + (stubPath ? ` (stub: ${stubPath})` : ""))
14748
15289
  );
14749
15290
  }
@@ -14757,7 +15298,7 @@ async function summarizeArchivedSession(ctx) {
14757
15298
  } catch {
14758
15299
  }
14759
15300
  }
14760
- return Err21(new Error(reason));
15301
+ return Err23(new Error(reason));
14761
15302
  }
14762
15303
  const meta = {
14763
15304
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -14768,7 +15309,7 @@ async function summarizeArchivedSession(ctx) {
14768
15309
  };
14769
15310
  const filePath = path23.join(ctx.archiveDir, LLM_SUMMARY_FILE);
14770
15311
  const body = renderLlmSummaryMarkdown(parsed.data, meta);
14771
- fs19.writeFileSync(filePath, body, "utf8");
15312
+ fs17.writeFileSync(filePath, body, "utf8");
14772
15313
  return Ok24({ summary: parsed.data, meta, filePath });
14773
15314
  }
14774
15315
  function isSummaryEnabled(config) {
@@ -14848,13 +15389,18 @@ export {
14848
15389
  BUILT_IN_TASKS,
14849
15390
  BackendDefSchema,
14850
15391
  BackendRouter,
15392
+ CheckScriptRunner,
14851
15393
  ClaimManager,
14852
15394
  GateNotReadyError,
14853
15395
  GateRunError,
14854
15396
  InteractionQueue,
15397
+ LinearGraphQLClient,
14855
15398
  LinearGraphQLStub,
14856
15399
  LocalModelResolver,
15400
+ MAINTENANCE_CHECK_MAX_BUFFER,
15401
+ MAINTENANCE_CHECK_TIMEOUT_MS,
14857
15402
  MAX_ATTEMPTS,
15403
+ MaintenanceReporter,
14858
15404
  MockBackend,
14859
15405
  ORCHESTRATOR_IDENTITY_FILE,
14860
15406
  Orchestrator,
@@ -14872,6 +15418,7 @@ export {
14872
15418
  SqliteSearchIndex,
14873
15419
  StreamRecorder,
14874
15420
  TaskOutputStore,
15421
+ TaskRunner,
14875
15422
  TokenStore,
14876
15423
  WebhookQueue,
14877
15424
  WorkflowLoader,
@@ -14882,7 +15429,9 @@ export {
14882
15429
  buildArchiveHooks,
14883
15430
  calculateRetryDelay,
14884
15431
  canDispatch,
15432
+ classifyCheckExecutionFailure,
14885
15433
  computeRateLimitDelay,
15434
+ createAgentDispatcher,
14886
15435
  createBackend,
14887
15436
  createEmptyState,
14888
15437
  crossFieldRoutingIssues,
@@ -14894,22 +15443,27 @@ export {
14894
15443
  emitProposalApproved,
14895
15444
  emitProposalCreated,
14896
15445
  emitProposalRejected,
15446
+ explicitFindingsCount,
14897
15447
  extractHighlights,
14898
15448
  extractTitlePrefix,
14899
15449
  getAvailableSlots,
14900
15450
  getDefaultConfig,
14901
15451
  getPerStateCount,
14902
15452
  indexSessionDirectory,
15453
+ isCheckTimeoutError,
14903
15454
  isEligible,
14904
15455
  isSummaryEnabled,
14905
15456
  launchTUI,
14906
15457
  loadPublishedIndex,
15458
+ makeBackendResolver,
14907
15459
  migrateAgentConfig,
14908
15460
  normalizeFts5Query,
15461
+ normalizeHarnessCommand,
14909
15462
  normalizeLocalModel,
14910
15463
  openSearchIndex,
14911
15464
  promote,
14912
15465
  reconcile,
15466
+ recoverFindingsCount,
14913
15467
  reindexFromArchive,
14914
15468
  renderAnalysisComment,
14915
15469
  renderLlmSummaryMarkdown,
@@ -14919,9 +15473,11 @@ export {
14919
15473
  routeIssue,
14920
15474
  routingWarnings,
14921
15475
  runGate,
15476
+ runHarnessCheck,
14922
15477
  savePublishedIndex,
14923
15478
  searchIndexPath,
14924
15479
  selectCandidates,
15480
+ selectTasks,
14925
15481
  sortCandidates,
14926
15482
  summarizeArchivedSession,
14927
15483
  syncMain,