@harness-engineering/orchestrator 0.14.0 → 0.15.1

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
@@ -2013,6 +2013,31 @@ var RoutingConfigSchema = z.object({
2013
2013
  // when absent). Previously accepted by the runtime PUT endpoint only. ---
2014
2014
  policy: RoutingPolicySchema.optional()
2015
2015
  }).strict();
2016
+ var CONFIDENCE = z.enum(["low", "medium", "high"]);
2017
+ var V1_MAX_RATCHET_STAGE = 2;
2018
+ var RATCHET_STAGE = z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]).refine((s) => s <= V1_MAX_RATCHET_STAGE, {
2019
+ message: `ratchetStage ${">"} ${V1_MAX_RATCHET_STAGE} is deferred post-v1 (stages 3\u20134 not yet supported); use 1 or 2`
2020
+ });
2021
+ var RoadmapAutoTriageSchema = z.object({
2022
+ enabled: z.boolean(),
2023
+ schedule: z.string().min(1).optional(),
2024
+ ratchetStage: RATCHET_STAGE,
2025
+ thresholds: z.object({
2026
+ dispatchConfidence: CONFIDENCE,
2027
+ boundedScopeMax: z.number(),
2028
+ brainstormConfidence: z.number(),
2029
+ exceededByBands: z.number(),
2030
+ ratchetAdvanceRate: z.number(),
2031
+ ratchetMinSample: z.number()
2032
+ }).strict(),
2033
+ depthBudget: z.object({
2034
+ trivial: z.number(),
2035
+ simple: z.number()
2036
+ }).strict()
2037
+ }).strict();
2038
+ var RoadmapConfigSchema = z.object({
2039
+ autoTriage: RoadmapAutoTriageSchema.optional()
2040
+ }).strict();
2016
2041
  var WorkflowStepSchema = z.object({
2017
2042
  skill: z.string().min(1),
2018
2043
  produces: z.string().min(1),
@@ -2050,12 +2075,12 @@ var BackendsMapSchema = z2.record(z2.string(), BackendDefSchema);
2050
2075
  function crossFieldRoutingIssues(backends, routing) {
2051
2076
  const issues = [];
2052
2077
  const names = new Set(Object.keys(backends));
2053
- const checkRef = (path24, value) => {
2078
+ const checkRef = (path25, value) => {
2054
2079
  if (value === void 0) return;
2055
2080
  const entries = Array.isArray(value) ? value : [value];
2056
2081
  entries.forEach((name, idx) => {
2057
2082
  if (names.has(name)) return;
2058
- const pathWithIdx = Array.isArray(value) ? [...path24, String(idx)] : path24;
2083
+ const pathWithIdx = Array.isArray(value) ? [...path25, String(idx)] : path25;
2059
2084
  issues.push({
2060
2085
  path: pathWithIdx,
2061
2086
  message: `routing.${pathWithIdx.join(".")} references unknown backend '${name}'. Defined: [${[...names].join(", ")}].`
@@ -2152,6 +2177,10 @@ function validateWorkflowConfig(config, options = {}) {
2152
2177
  const parsed = z2.array(StagedWorkflowDeclSchema).safeParse(c.workflows);
2153
2178
  if (!parsed.success) return Err(new Error(`workflows: ${parsed.error.message}`));
2154
2179
  }
2180
+ if (c.roadmap !== void 0) {
2181
+ const parsed = RoadmapConfigSchema.safeParse(c.roadmap);
2182
+ if (!parsed.success) return Err(new Error(`roadmap: ${parsed.error.message}`));
2183
+ }
2155
2184
  return Ok2({ config, warnings });
2156
2185
  }
2157
2186
  function getDefaultConfig() {
@@ -3406,6 +3435,68 @@ import { randomUUID as randomUUID5 } from "crypto";
3406
3435
  import { RoutingError as RoutingError3 } from "@harness-engineering/types";
3407
3436
  import { writeTaint, SecurityScanner as SecurityScanner2 } from "@harness-engineering/core";
3408
3437
  import { OutcomeEvaluator } from "@harness-engineering/intelligence";
3438
+
3439
+ // src/agent/triage-outcome.ts
3440
+ import {
3441
+ classify,
3442
+ compareToPrediction,
3443
+ aggregatePrecedent
3444
+ } from "@harness-engineering/intelligence";
3445
+ function signalsFromDiff(hunks, textOnly) {
3446
+ const files = /* @__PURE__ */ new Set();
3447
+ const layers = /* @__PURE__ */ new Set();
3448
+ let addedLines = 0;
3449
+ for (const h of hunks) {
3450
+ files.add(h.file);
3451
+ layers.add(layerOfPath(h.file));
3452
+ addedLines += h.addedContent === "" ? 0 : h.addedContent.split("\n").length;
3453
+ }
3454
+ return {
3455
+ ...textOnly,
3456
+ filesTouched: files.size,
3457
+ layersTouched: layers.size,
3458
+ blastRadius: addedLines
3459
+ };
3460
+ }
3461
+ function layerOfPath(file) {
3462
+ const parts = file.split("/").filter((p) => p.length > 0);
3463
+ if (parts.length === 0) return "<root>";
3464
+ if (parts[0] === "packages" && parts.length >= 4 && parts[2] === "src") {
3465
+ return `${parts[1] ?? ""}/${parts[3] ?? ""}`;
3466
+ }
3467
+ return parts.length === 1 ? "<root>" : parts[0] ?? "<root>";
3468
+ }
3469
+ async function runRetrospective(deps, provider) {
3470
+ const signals = signalsFromDiff(deps.hunks, deps.textOnly);
3471
+ const actual = await classify(
3472
+ { signals, phase: "post-diff", riskHigh: deps.riskHigh, prompt: deps.prompt },
3473
+ provider
3474
+ );
3475
+ const comparison = deps.comparatorConfig ? compareToPrediction(deps.prediction, actual, deps.comparatorConfig) : compareToPrediction(deps.prediction, actual);
3476
+ return { actual, comparison };
3477
+ }
3478
+ function buildTriageOutcomeInput(externalId, shapeKey, result) {
3479
+ return {
3480
+ externalId,
3481
+ shapeKey,
3482
+ actual: result.actual,
3483
+ exceededBy: result.comparison.exceededBy,
3484
+ matched: result.comparison.matched
3485
+ };
3486
+ }
3487
+ function precedentLookupFromStored(records) {
3488
+ const bridged = records.map((r) => ({
3489
+ externalId: "",
3490
+ shapeKey: r.shapeKey,
3491
+ ts: "",
3492
+ ...r.outcome ? { outcome: { actual: void 0, exceededBy: 0, matched: r.outcome.matched } } : {}
3493
+ }));
3494
+ return {
3495
+ rateForShape: (shapeKey) => aggregatePrecedent(bridged, shapeKey)
3496
+ };
3497
+ }
3498
+
3499
+ // src/orchestrator.ts
3409
3500
  import { GraphStore as GraphStore2 } from "@harness-engineering/graph";
3410
3501
 
3411
3502
  // src/core/stall-detector.ts
@@ -3989,7 +4080,8 @@ var CompletionHandler = class {
3989
4080
  import {
3990
4081
  GitHubIssuesSyncAdapter as GitHubIssuesSyncAdapter3,
3991
4082
  loadTrackerSyncConfig as loadTrackerSyncConfig3,
3992
- createTrackerClient as createTrackerClient2
4083
+ createTrackerClient as createTrackerClient2,
4084
+ eventSourcing as eventSourcing2
3993
4085
  } from "@harness-engineering/core";
3994
4086
 
3995
4087
  // src/tracker/adapters/github-issues-issue-tracker.ts
@@ -4701,11 +4793,11 @@ function detectLegacyFields(agent) {
4701
4793
  }
4702
4794
  function buildCase1Warnings(presentLegacy, suppressLocalGroup) {
4703
4795
  const warnings = [];
4704
- for (const path24 of presentLegacy) {
4705
- if (CASE1_ALWAYS_SUPPRESS.has(path24)) continue;
4706
- if (suppressLocalGroup && CASE1_LOCAL_GROUP.has(path24)) continue;
4796
+ for (const path25 of presentLegacy) {
4797
+ if (CASE1_ALWAYS_SUPPRESS.has(path25)) continue;
4798
+ if (suppressLocalGroup && CASE1_LOCAL_GROUP.has(path25)) continue;
4707
4799
  warnings.push(
4708
- `Ignoring legacy field '${path24}': 'agent.backends' is set and takes precedence. See ${MIGRATION_GUIDE}.`
4800
+ `Ignoring legacy field '${path25}': 'agent.backends' is set and takes precedence. See ${MIGRATION_GUIDE}.`
4709
4801
  );
4710
4802
  }
4711
4803
  return warnings;
@@ -4733,7 +4825,7 @@ function migrateAgentConfig(agent) {
4733
4825
  }
4734
4826
  const { backends, routing } = synthesizeBackendsAndRouting(agent);
4735
4827
  const warnings = presentLegacy.map(
4736
- (path24) => `Deprecated config field '${path24}' is in use. Migrate to 'agent.backends' / 'agent.routing'. See ${MIGRATION_GUIDE}.`
4828
+ (path25) => `Deprecated config field '${path25}' is in use. Migrate to 'agent.backends' / 'agent.routing'. See ${MIGRATION_GUIDE}.`
4737
4829
  );
4738
4830
  return {
4739
4831
  config: { ...agent, backends, routing },
@@ -4818,12 +4910,12 @@ var BackendRouter = class {
4818
4910
  */
4819
4911
  resolve(useCase, opts) {
4820
4912
  const startedAt = performance.now();
4821
- const path24 = [];
4913
+ const path25 = [];
4822
4914
  const tryChain = (source, value) => {
4823
4915
  if (value === void 0) return void 0;
4824
4916
  for (const name of toArray(value)) {
4825
4917
  const step = { source, candidate: name, outcome: "considered" };
4826
- path24.push(step);
4918
+ path25.push(step);
4827
4919
  if (this.backends[name]) {
4828
4920
  step.outcome = "chosen";
4829
4921
  return name;
@@ -4842,7 +4934,7 @@ var BackendRouter = class {
4842
4934
  return {
4843
4935
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4844
4936
  useCase,
4845
- resolutionPath: path24,
4937
+ resolutionPath: path25,
4846
4938
  backendName,
4847
4939
  backendType: def.type,
4848
4940
  durationMs: performance.now() - startedAt
@@ -4875,7 +4967,7 @@ var BackendRouter = class {
4875
4967
  if (fromDefault) return emitAndReturn(decide(fromDefault));
4876
4968
  const knownList = Object.keys(this.backends).join(", ") || "(none)";
4877
4969
  throw new Error(
4878
- `BackendRouter.resolve: routing.default produced no available backend for useCase=${JSON.stringify(useCase)}. Resolution path: ${JSON.stringify(path24)}. Known backends: [${knownList}].`
4970
+ `BackendRouter.resolve: routing.default produced no available backend for useCase=${JSON.stringify(useCase)}. Resolution path: ${JSON.stringify(path25)}. Known backends: [${knownList}].`
4879
4971
  );
4880
4972
  }
4881
4973
  /**
@@ -4968,7 +5060,7 @@ var BackendRouter = class {
4968
5060
  check(`modes.${mode}`, value);
4969
5061
  }
4970
5062
  if (missing.length > 0) {
4971
- const detail = missing.map(({ path: path24, name }) => `routing.${path24} -> '${name}'`).join("; ");
5063
+ const detail = missing.map(({ path: path25, name }) => `routing.${path25} -> '${name}'`).join("; ");
4972
5064
  const known_ = [...known].join(", ") || "(none)";
4973
5065
  throw new Error(
4974
5066
  `BackendRouter: routing references unknown backend(s): ${detail}. Defined backends: [${known_}].`
@@ -8321,7 +8413,7 @@ function buildRoutingUseCase(issue, backendParam, catalog) {
8321
8413
 
8322
8414
  // src/agent/live-classify.ts
8323
8415
  import {
8324
- classify
8416
+ classify as classify2
8325
8417
  } from "@harness-engineering/intelligence";
8326
8418
  var CONSERVATIVE = {
8327
8419
  level: "moderate",
@@ -8345,7 +8437,7 @@ function makeLiveClassify(resolveProvider) {
8345
8437
  riskHigh,
8346
8438
  prompt: taskText.prompt
8347
8439
  };
8348
- return classify(input, resolveProvider());
8440
+ return classify2(input, resolveProvider());
8349
8441
  };
8350
8442
  }
8351
8443
 
@@ -10745,7 +10837,12 @@ function deriveTraceCost(body, decision, def, routing, backends) {
10745
10837
  backends
10746
10838
  );
10747
10839
  const estCostUsd = estimateCost(costedDef, { useCase: body.useCase });
10748
- return { tierRequired, estCostUsd, costedBackendName: costedName };
10840
+ return {
10841
+ tierRequired,
10842
+ estCostUsd,
10843
+ costedBackendName: costedName,
10844
+ costedBackendType: costedDef.type
10845
+ };
10749
10846
  }
10750
10847
  function selectCostedBackend(tierRequired, decision, def, routing, backends) {
10751
10848
  const registry = buildCapabilityRegistry(backends);
@@ -10810,7 +10907,7 @@ async function handleTrace(req, res, deps) {
10810
10907
  opts
10811
10908
  );
10812
10909
  if (r.data.complexity !== void 0 || r.data.risk !== void 0) {
10813
- const { tierRequired, estCostUsd, costedBackendName } = deriveTraceCost(
10910
+ const { tierRequired, estCostUsd, costedBackendName, costedBackendType } = deriveTraceCost(
10814
10911
  r.data,
10815
10912
  decision,
10816
10913
  def,
@@ -10822,9 +10919,12 @@ async function handleTrace(req, res, deps) {
10822
10919
  def: { type: def.type },
10823
10920
  tierRequired,
10824
10921
  estCostUsd,
10825
- // Name the backend the cost belongs to so operators see tier↔cost↔backend
10826
- // are consistent (was implicit + divergent before this fix).
10827
- costedBackendName
10922
+ // The backend AMR would ACTUALLY dispatch to at this tier (cheapest
10923
+ // qualifying) + its type distinct from the identity-chain `decision`
10924
+ // above. The CLI shows this as the effective backend so tier↔cost↔backend
10925
+ // read consistently (was implicit + divergent before).
10926
+ costedBackendName,
10927
+ costedBackendType
10828
10928
  });
10829
10929
  return true;
10830
10930
  }
@@ -11386,8 +11486,8 @@ function parseToken(raw) {
11386
11486
  return { id: raw.slice(0, dot), secret: raw.slice(dot + 1) };
11387
11487
  }
11388
11488
  var TokenStore = class {
11389
- constructor(path24) {
11390
- this.path = path24;
11489
+ constructor(path25) {
11490
+ this.path = path25;
11391
11491
  }
11392
11492
  path;
11393
11493
  cache = null;
@@ -11494,8 +11594,8 @@ import { appendFile, mkdir as mkdir8 } from "fs/promises";
11494
11594
  import { dirname as dirname6 } from "path";
11495
11595
  import { AuthAuditEntrySchema } from "@harness-engineering/types";
11496
11596
  var AuditLogger = class {
11497
- constructor(path24, opts = {}) {
11498
- this.path = path24;
11597
+ constructor(path25, opts = {}) {
11598
+ this.path = path25;
11499
11599
  this.opts = opts;
11500
11600
  }
11501
11601
  path;
@@ -11733,9 +11833,9 @@ var V1_BRIDGE_ROUTES = [
11733
11833
  function isV1Bridge(method, url) {
11734
11834
  return V1_BRIDGE_ROUTES.some((r) => r.method === method && r.pattern.test(url));
11735
11835
  }
11736
- function requiredBridgeScope(method, path24) {
11836
+ function requiredBridgeScope(method, path25) {
11737
11837
  for (const r of V1_BRIDGE_ROUTES) {
11738
- if (r.method === method && r.pattern.test(path24)) return r.scope;
11838
+ if (r.method === method && r.pattern.test(path25)) return r.scope;
11739
11839
  }
11740
11840
  return null;
11741
11841
  }
@@ -11745,11 +11845,11 @@ function hasScope(held, required) {
11745
11845
  if (held.includes("admin")) return true;
11746
11846
  return held.includes(required);
11747
11847
  }
11748
- function exactScopeForRoute(method, path24) {
11749
- if (path24 === "/api/v1/auth/token" && method === "POST") return "admin";
11750
- if (path24 === "/api/v1/auth/tokens" && method === "GET") return "admin";
11751
- if (/^\/api\/v1\/auth\/tokens\/[^/]+$/.test(path24) && method === "DELETE") return "admin";
11752
- if ((path24 === "/api/state" || path24 === "/api/v1/state") && method === "GET") return "read-status";
11848
+ function exactScopeForRoute(method, path25) {
11849
+ if (path25 === "/api/v1/auth/token" && method === "POST") return "admin";
11850
+ if (path25 === "/api/v1/auth/tokens" && method === "GET") return "admin";
11851
+ if (/^\/api\/v1\/auth\/tokens\/[^/]+$/.test(path25) && method === "DELETE") return "admin";
11852
+ if ((path25 === "/api/state" || path25 === "/api/v1/state") && method === "GET") return "read-status";
11753
11853
  return null;
11754
11854
  }
11755
11855
  var PREFIX_SCOPES = [
@@ -11766,19 +11866,19 @@ var PREFIX_SCOPES = [
11766
11866
  ["/api/sessions", "read-status"],
11767
11867
  ["/api/chat-proxy", "trigger-job"]
11768
11868
  ];
11769
- function prefixScopeForPath(path24) {
11770
- if (path24 === "/api/chat") return "trigger-job";
11869
+ function prefixScopeForPath(path25) {
11870
+ if (path25 === "/api/chat") return "trigger-job";
11771
11871
  for (const [prefix, scope] of PREFIX_SCOPES) {
11772
- if (path24.startsWith(prefix)) return scope;
11872
+ if (path25.startsWith(prefix)) return scope;
11773
11873
  }
11774
11874
  return null;
11775
11875
  }
11776
- function requiredScopeForRoute(method, path24) {
11777
- const bridgeScope = requiredBridgeScope(method, path24);
11876
+ function requiredScopeForRoute(method, path25) {
11877
+ const bridgeScope = requiredBridgeScope(method, path25);
11778
11878
  if (bridgeScope) return bridgeScope;
11779
- const exactScope = exactScopeForRoute(method, path24);
11879
+ const exactScope = exactScopeForRoute(method, path25);
11780
11880
  if (exactScope) return exactScope;
11781
- return prefixScopeForPath(path24);
11881
+ return prefixScopeForPath(path25);
11782
11882
  }
11783
11883
 
11784
11884
  // src/server/http.ts
@@ -12307,8 +12407,8 @@ function genSecret2() {
12307
12407
  return randomBytes3(32).toString("base64url");
12308
12408
  }
12309
12409
  var WebhookStore = class {
12310
- constructor(path24) {
12311
- this.path = path24;
12410
+ constructor(path25) {
12411
+ this.path = path25;
12312
12412
  }
12313
12413
  path;
12314
12414
  cache = null;
@@ -15161,8 +15261,8 @@ function validateCheckShape(prefix, task, errors) {
15161
15261
  });
15162
15262
  }
15163
15263
  if (hasScript) {
15164
- const path24 = task.checkScript?.path;
15165
- if (!path24 || path24.trim().length === 0) {
15264
+ const path25 = task.checkScript?.path;
15265
+ if (!path25 || path25.trim().length === 0) {
15166
15266
  errors.push({ path: `${prefix}.checkScript.path`, message: "checkScript.path is required" });
15167
15267
  }
15168
15268
  if (task.checkScript?.timeoutMs !== void 0 && task.checkScript.timeoutMs <= 0) {
@@ -15288,9 +15388,9 @@ function handleEdge(top, next, color, stack, errors, reported) {
15288
15388
  stack.push({ id: next, nextIdx: 0, path: [...top.path, next] });
15289
15389
  }
15290
15390
  }
15291
- function reportCycle(path24, next, errors, reported) {
15292
- const cycleStart = path24.indexOf(next);
15293
- const cyclePath = cycleStart >= 0 ? [...path24.slice(cycleStart), next] : [...path24, next];
15391
+ function reportCycle(path25, next, errors, reported) {
15392
+ const cycleStart = path25.indexOf(next);
15393
+ const cyclePath = cycleStart >= 0 ? [...path25.slice(cycleStart), next] : [...path25, next];
15294
15394
  const key = cyclePath.join("\u2192");
15295
15395
  if (reported.has(key)) return;
15296
15396
  reported.add(key);
@@ -16778,7 +16878,9 @@ ${messages}`);
16778
16878
  await this.emitWorkerExit(issue.id, "error", attempt, "Stopped by reconciliation");
16779
16879
  }
16780
16880
  } else {
16781
- const outcomeClass = await this.deriveSingleAgentQualityVerdict(issue, workspacePath);
16881
+ const qualityClass = await this.deriveSingleAgentQualityVerdict(issue, workspacePath);
16882
+ const retroClass = await this.deriveRoutingRetrospectiveVerdict(issue, workspacePath);
16883
+ const outcomeClass = qualityClass ?? retroClass;
16782
16884
  await this.emitWorkerExit(issue.id, "normal", attempt, void 0, outcomeClass);
16783
16885
  }
16784
16886
  } catch (error) {
@@ -16880,6 +16982,150 @@ ${messages}`);
16880
16982
  return void 0;
16881
16983
  }
16882
16984
  }
16985
+ /**
16986
+ * Roadmap Auto-Triage Phase 4 (SC1–SC3, SC5, SC7): the post-diff routing
16987
+ * retrospective — a SIBLING quality-verdict source to the 4c feeder above,
16988
+ * extending (not replacing) the proven escalation path. On a normal exit, when AMR
16989
+ * is live AND auto-triage is enabled AND this unit carries a stored pre-dispatch
16990
+ * prediction, it classifies the ACTUAL introduced diff at full strength
16991
+ * (phase:'post-diff') and grades it against that prediction:
16992
+ * - MATCH → records the graded outcome + annotates the PR "AI autonomous —
16993
+ * verify" (stage-2 human-verify handling, SC4) → returns `undefined`
16994
+ * (neutral: a match is not an escalation).
16995
+ * - MISMATCH (diff exceeded prediction, or a missing/garbled prediction, or ANY
16996
+ * error) → records the outcome (when a shape is known) + returns
16997
+ * `'quality-fail'`, which climbs the coherence unit's escalation floor
16998
+ * via `recordAmrOutcome`; exhaustion queues `needs-human` (SC3/SC7).
16999
+ *
17000
+ * Fail-safe & guarded (SC7): an error never a silent pass — it takes the MISMATCH
17001
+ * (block+escalate) path. No-op (byte-identical) when AMR is off, auto-triage is
17002
+ * off, or the unit was not dispatched through triage (no stored prediction) — the
17003
+ * last means an ordinary non-triaged run is never graded.
17004
+ */
17005
+ async deriveRoutingRetrospectiveVerdict(issue, _workspacePath) {
17006
+ if (this.adaptiveRouter === null) return void 0;
17007
+ if (this.config.roadmap?.autoTriage?.enabled !== true) return void 0;
17008
+ const externalId = issue.externalId;
17009
+ if (externalId === null || externalId === "") return void 0;
17010
+ let record;
17011
+ try {
17012
+ const loaded = await eventSourcing2.loadTriageRecords(this.projectRoot);
17013
+ if (!loaded.ok) throw loaded.error;
17014
+ record = loaded.value.find((r) => r.externalId === externalId);
17015
+ } catch (err) {
17016
+ this.logger.debug("amr retrospective skipped (record load failed)", {
17017
+ issueId: issue.id,
17018
+ error: err instanceof Error ? err.message : String(err)
17019
+ });
17020
+ if (issue.spec !== null && issue.spec !== void 0 && issue.spec !== "") {
17021
+ this.logger.info(
17022
+ "amr:quality-fail \u2014 spec-bearing (triaged) unit but the triage store is unreadable (fail-safe block: a triaged unit whose prediction we cannot read never silently passes)",
17023
+ { issueId: issue.id, externalId }
17024
+ );
17025
+ return "quality-fail";
17026
+ }
17027
+ return void 0;
17028
+ }
17029
+ if (record === void 0) return void 0;
17030
+ const stored = record.prediction;
17031
+ if (stored === void 0) {
17032
+ this.logger.info(
17033
+ "amr:quality-fail \u2014 triaged unit has no stored prediction (fail-safe block)",
17034
+ {
17035
+ issueId: issue.id,
17036
+ externalId
17037
+ }
17038
+ );
17039
+ return "quality-fail";
17040
+ }
17041
+ const prediction = {
17042
+ verdict: stored.verdict,
17043
+ levers: stored.levers,
17044
+ scopeEstimate: stored.scopeEstimate,
17045
+ ratchetStage: stored.ratchetStage
17046
+ };
17047
+ try {
17048
+ const hunks = await this.workspace.getIntroducedDiff(issue.identifier);
17049
+ const taskText = buildTaskText(issue);
17050
+ const exceededByBands = this.config.roadmap?.autoTriage?.thresholds?.exceededByBands ?? 1;
17051
+ const result = await runRetrospective(
17052
+ {
17053
+ hunks,
17054
+ textOnly: {
17055
+ descriptionLength: taskText.descriptionLength,
17056
+ specExists: taskText.specExists,
17057
+ acceptanceMeasurable: taskText.acceptanceMeasurable
17058
+ },
17059
+ prediction,
17060
+ riskHigh: false,
17061
+ prompt: taskText.prompt,
17062
+ comparatorConfig: { exceededByBands }
17063
+ },
17064
+ this.resolveComplexityProvider()
17065
+ );
17066
+ const outcomeInput = buildTriageOutcomeInput(externalId, record.shapeKey, result);
17067
+ const recorded = await eventSourcing2.recordTriageOutcome(this.projectRoot, outcomeInput);
17068
+ if (!recorded.ok) {
17069
+ this.logger.warn("amr retrospective outcome record failed (best-effort)", {
17070
+ issueId: issue.id,
17071
+ error: recorded.error.message
17072
+ });
17073
+ }
17074
+ if (result.comparison.action === "block-escalate") {
17075
+ this.logger.info("amr:quality-fail \u2014 post-diff retrospective mispredict (block+escalate)", {
17076
+ issueId: issue.id,
17077
+ externalId,
17078
+ predicted: prediction.verdict.level,
17079
+ actual: result.actual.level,
17080
+ exceededBy: result.comparison.exceededBy
17081
+ });
17082
+ return "quality-fail";
17083
+ }
17084
+ await this.annotateRetrospectiveMatch(issue, externalId, result.actual.level);
17085
+ return void 0;
17086
+ } catch (err) {
17087
+ this.logger.info("amr:quality-fail \u2014 post-diff retrospective errored (fail-safe block)", {
17088
+ issueId: issue.id,
17089
+ externalId,
17090
+ error: err instanceof Error ? err.message : String(err)
17091
+ });
17092
+ return "quality-fail";
17093
+ }
17094
+ }
17095
+ /**
17096
+ * v1 stage-2 match handling (SC4): annotate the unit's PR/issue with a "verify this
17097
+ * autonomous change" note so a human reviews every autonomous PR. Best-effort — a
17098
+ * missing tracker config / token / a failed comment never breaks completion (the
17099
+ * grade already recorded above). Mirrors `postLifecycleComment`'s adapter wiring.
17100
+ */
17101
+ async annotateRetrospectiveMatch(issue, externalId, actualLevel) {
17102
+ try {
17103
+ const trackerConfig = loadTrackerSyncConfig3(this.projectRoot);
17104
+ if (!trackerConfig) return;
17105
+ const token = process.env.GITHUB_TOKEN;
17106
+ if (!token) return;
17107
+ const orchestratorId = await this.orchestratorIdPromise;
17108
+ const adapter = new GitHubIssuesSyncAdapter3({ token, config: trackerConfig });
17109
+ const body = [
17110
+ `**AI autonomous change \u2014 please verify** \`${orchestratorId}\``,
17111
+ "",
17112
+ "The post-diff retrospective classified this change WITHIN its pre-dispatch",
17113
+ `prediction (actual complexity: \`${actualLevel}\`). Under autonomy ratchet`,
17114
+ "stage 2, a human must verify every autonomous PR before it merges."
17115
+ ].join("\n");
17116
+ const result = await adapter.addComment(externalId, body);
17117
+ if (!result.ok) {
17118
+ this.logger.warn(`amr retrospective annotation failed for ${issue.identifier}`, {
17119
+ error: result.error.message
17120
+ });
17121
+ }
17122
+ } catch (err) {
17123
+ this.logger.debug("amr retrospective annotation skipped (best-effort)", {
17124
+ issueId: issue.id,
17125
+ error: err instanceof Error ? err.message : String(err)
17126
+ });
17127
+ }
17128
+ }
16883
17129
  /**
16884
17130
  * Informs the state machine that an agent worker has exited.
16885
17131
  */
@@ -17966,6 +18212,341 @@ function launchTUI(orchestrator) {
17966
18212
  return { waitUntilExit };
17967
18213
  }
17968
18214
 
18215
+ // src/agent/triage-wiring.ts
18216
+ import { CascadeSimulator } from "@harness-engineering/graph";
18217
+ import {
18218
+ runScopingProbe,
18219
+ extractEntities,
18220
+ rankTriageCandidates,
18221
+ pilotScore
18222
+ } from "@harness-engineering/intelligence";
18223
+ function buildProbeInput(issue) {
18224
+ const taskText = buildTaskText(issue);
18225
+ const combined = `${issue.title ?? ""}
18226
+ ${issue.description ?? ""}`.trim();
18227
+ return {
18228
+ // An item lacking a stable externalId is not dispatch-eligible (proposal Assumptions);
18229
+ // we still probe it (read-only report), keyed by its identifier as a fallback.
18230
+ externalId: issue.externalId ?? issue.identifier ?? issue.id,
18231
+ taskText,
18232
+ entityCandidates: extractEntities(combined),
18233
+ labels: issue.labels ?? []
18234
+ };
18235
+ }
18236
+ function findNode(store, candidate) {
18237
+ const trimmed = candidate.trim();
18238
+ if (trimmed.length === 0) return null;
18239
+ const byName = store.findNodes({ name: trimmed });
18240
+ if (byName.length > 0) return byName[0] ?? null;
18241
+ const byPath = store.findNodes({ path: trimmed });
18242
+ if (byPath.length > 0) return byPath[0] ?? null;
18243
+ const byId = store.getNode(`file:${trimmed}`);
18244
+ if (byId) return byId;
18245
+ return null;
18246
+ }
18247
+ function makeGraphScope(store) {
18248
+ const simulator = new CascadeSimulator(store);
18249
+ return {
18250
+ resolve(candidate) {
18251
+ try {
18252
+ const node = findNode(store, candidate);
18253
+ if (!node) return null;
18254
+ const result = simulator.simulate(node.id);
18255
+ return {
18256
+ candidate,
18257
+ nodeId: node.id,
18258
+ blastRadius: result.summary.totalAffected
18259
+ };
18260
+ } catch {
18261
+ return null;
18262
+ }
18263
+ }
18264
+ };
18265
+ }
18266
+ async function triageIssue2(issue, deps = {}) {
18267
+ const input = buildProbeInput(issue);
18268
+ const graph = deps.graphStore ? makeGraphScope(deps.graphStore) : void 0;
18269
+ return runScopingProbe(input, {
18270
+ ...deps.provider ? { provider: deps.provider } : {},
18271
+ ...graph ? { graph } : {},
18272
+ ...deps.precedent ? { precedent: deps.precedent } : {},
18273
+ ...deps.config ? { config: deps.config } : {},
18274
+ ...deps.models ? { models: deps.models } : {}
18275
+ });
18276
+ }
18277
+
18278
+ // src/agent/brainstorm-wiring.ts
18279
+ import * as fs16 from "fs";
18280
+ import * as path22 from "path";
18281
+ import { z as z18 } from "zod";
18282
+ import {
18283
+ runAutoBrainstorm,
18284
+ depthForLevel
18285
+ } from "@harness-engineering/intelligence";
18286
+ var ForkStepSchema = z18.object({
18287
+ done: z18.boolean().describe("true when there are no more design forks to decide"),
18288
+ forkId: z18.string().describe('short stable id for this fork, e.g. "storage-backend"').optional(),
18289
+ question: z18.string().describe("the design question this fork decides").optional(),
18290
+ options: z18.array(z18.string()).describe("the mutually-exclusive options considered").optional(),
18291
+ // The AUTHORITATIVE recommendation validity checks (non-empty AND one of `options`) live in
18292
+ // the GENERATOR, where `fork.options` is known and where a degenerate value must map to a
18293
+ // `confidence:'low'` DOWNGRADE (→ runner halts with reason 'low-confidence'), not a hard
18294
+ // schema/provider throw. We deliberately keep this `optional()`/unconstrained so an empty or
18295
+ // absent recommendation still reaches the generator's semantic gate rather than short-
18296
+ // circuiting to `halted{ reason:'error' }`. See makeSelForkGenerator's SAFETY GATE 2.
18297
+ recommendation: z18.string().describe("the recommended option (one of options)").optional(),
18298
+ confidence: z18.enum(["high", "medium", "low"]).describe("self-assessed confidence in the recommendation").optional(),
18299
+ rationale: z18.string().describe("why this option").optional()
18300
+ });
18301
+ var BRAINSTORM_RUBRIC = 'You are running an AUTONOMOUS brainstorm over one backlog item: enumerate each design fork (a decision with mutually-exclusive options), then recommend a default for the NEXT unresolved fork with a self-assessed confidence. Report confidence HIGH only when the recommendation is clearly correct on technical grounds alone. Report LOW (so a human decides) whenever the fork involves ANY of: a product or UX tradeoff, an unrevealed business priority, a security-sensitive or irreversible/outward-facing action, or a genuine judgment call between comparable options. Never invent forks to pad the spec; set done=true once every real fork is resolved. Be conservative \u2014 a false "high" is far more costly than asking a human.';
18302
+ function makeSelForkGenerator(provider, input, opts = {}) {
18303
+ const samples = Math.max(2, opts.samples ?? 3);
18304
+ const maxTokens = opts.maxTokens ?? 512;
18305
+ return {
18306
+ async next(index, priorDecisions) {
18307
+ const prompt = buildForkPrompt(input, priorDecisions, index);
18308
+ const steps = [];
18309
+ for (let s = 0; s < samples; s++) {
18310
+ const { result } = await provider.analyze({
18311
+ prompt,
18312
+ systemPrompt: BRAINSTORM_RUBRIC,
18313
+ responseSchema: ForkStepSchema,
18314
+ maxTokens,
18315
+ ...opts.model !== void 0 ? { model: opts.model } : {}
18316
+ });
18317
+ steps.push(result);
18318
+ }
18319
+ if (steps.some((st) => st.done)) return null;
18320
+ const first = steps[0];
18321
+ if (!first) return null;
18322
+ const fork = {
18323
+ id: first.forkId ?? `fork-${index}`,
18324
+ question: first.question ?? "unspecified fork",
18325
+ options: first.options ?? []
18326
+ };
18327
+ const recommendation = first.recommendation ?? "";
18328
+ const normQ = (q) => (q ?? "").trim().toLowerCase();
18329
+ const firstForkId = first.forkId ?? "";
18330
+ const firstQuestion = normQ(first.question);
18331
+ const identityStable = steps.every(
18332
+ (st) => (st.forkId ?? "") === firstForkId && normQ(st.question) === firstQuestion
18333
+ );
18334
+ const recStable = steps.every((st) => (st.recommendation ?? "") === recommendation);
18335
+ const recEmpty = recommendation.trim() === "";
18336
+ const recNotAnOption = !recEmpty && fork.options.length > 0 && !fork.options.includes(recommendation);
18337
+ const stable = identityStable && recStable && !recEmpty && !recNotAnOption;
18338
+ const reported = first.confidence ?? "low";
18339
+ const confidence = stable ? reported : "low";
18340
+ let downgradeReason = "";
18341
+ if (!identityStable) {
18342
+ downgradeReason = `fork identity drift across ${samples} samples (self-consistency downgrade to low): ` + steps.map((st) => `${st.forkId ?? "\u2205"}:"${(st.question ?? "\u2205").trim()}"`).join(" / ");
18343
+ } else if (recEmpty) {
18344
+ downgradeReason = "recommendation was empty/absent (a content-free recommendation is not a confident decision; downgrade to low)";
18345
+ } else if (recNotAnOption) {
18346
+ downgradeReason = `recommendation '${recommendation}' is not one of the offered options [${fork.options.join(", ")}] (downgrade to low)`;
18347
+ } else if (!recStable) {
18348
+ downgradeReason = `recommendation was unstable across ${samples} samples (self-consistency downgrade to low): ` + steps.map((st) => st.recommendation ?? "\u2205").join(" / ");
18349
+ }
18350
+ return {
18351
+ fork,
18352
+ recommendation,
18353
+ confidence,
18354
+ rationale: stable ? first.rationale ?? "" : downgradeReason
18355
+ };
18356
+ }
18357
+ };
18358
+ }
18359
+ function buildForkPrompt(input, priorDecisions, index) {
18360
+ const resolved = priorDecisions.length === 0 ? "(none yet)" : priorDecisions.map((d, i) => `${i + 1}. ${d.fork.question} \u2192 ${d.recommendation}`).join("\n");
18361
+ return `Item: ${input.title}
18362
+ Summary: ${input.summary}
18363
+ Complexity: ${input.level}
18364
+
18365
+ Forks already resolved:
18366
+ ${resolved}
18367
+
18368
+ Decide fork #${index + 1}: the NEXT unresolved design fork. If every real fork is already resolved, return { "done": true }.`;
18369
+ }
18370
+ function brainstormInputFromIssue(issue, level) {
18371
+ return {
18372
+ externalId: issue.externalId ?? issue.identifier ?? issue.id,
18373
+ title: issue.title ?? issue.identifier ?? issue.id,
18374
+ summary: issue.description ?? "",
18375
+ level
18376
+ };
18377
+ }
18378
+ async function runBrainstormForIssue(issue, level, deps) {
18379
+ const input = brainstormInputFromIssue(issue, level);
18380
+ const generator = deps.generator ?? (deps.provider ? makeSelForkGenerator(deps.provider, input, deps.generatorOptions ?? {}) : void 0);
18381
+ if (!generator) {
18382
+ return {
18383
+ outcome: {
18384
+ kind: "halted",
18385
+ fork: { id: "no-generator", question: "no fork generator or provider wired", options: [] },
18386
+ reason: "error",
18387
+ detail: "runBrainstormForIssue requires either an injected generator or a SEL provider"
18388
+ }
18389
+ };
18390
+ }
18391
+ const depth = depthForLevel(level);
18392
+ const outcome = await runAutoBrainstorm(input, generator, depth);
18393
+ if (outcome.kind !== "completed") {
18394
+ return { outcome };
18395
+ }
18396
+ const result = { outcome };
18397
+ if (deps.docsRoot) {
18398
+ try {
18399
+ result.specPath = writeSpecDoc(deps.docsRoot, outcome.spec);
18400
+ } catch {
18401
+ }
18402
+ }
18403
+ if (deps.rescore) {
18404
+ try {
18405
+ const enrichedIssue = enrichIssueWithSpec(issue, outcome.spec);
18406
+ result.rescore = await triageIssue2(enrichedIssue, {
18407
+ ...deps.rescore.graphStore ? { graphStore: deps.rescore.graphStore } : {},
18408
+ ...deps.rescore.provider ? { provider: deps.rescore.provider } : {},
18409
+ ...deps.rescore.precedent ? { precedent: deps.rescore.precedent } : {},
18410
+ ...deps.rescore.config ? { config: deps.rescore.config } : {},
18411
+ ...deps.rescore.models ? { models: deps.rescore.models } : {}
18412
+ });
18413
+ } catch {
18414
+ }
18415
+ }
18416
+ return result;
18417
+ }
18418
+ function slugFor(spec) {
18419
+ const base = spec.title || spec.externalId;
18420
+ return base.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "item";
18421
+ }
18422
+ function renderSpecMarkdown(spec) {
18423
+ const lines = [];
18424
+ lines.push(`# ${spec.title}`);
18425
+ lines.push("");
18426
+ lines.push("Status: **Auto-drafted (autonomous brainstorm \u2014 awaiting human review)**");
18427
+ lines.push("");
18428
+ lines.push(`Item: \`${spec.externalId}\``);
18429
+ lines.push("");
18430
+ lines.push("## Summary");
18431
+ lines.push("");
18432
+ lines.push(spec.summary || "_No summary provided._");
18433
+ lines.push("");
18434
+ lines.push("## Design decisions");
18435
+ lines.push("");
18436
+ if (spec.decisions.length === 0) {
18437
+ lines.push("_No forks required a decision \u2014 the item was unambiguous._");
18438
+ } else {
18439
+ for (const [i, d] of spec.decisions.entries()) {
18440
+ lines.push(`### ${i + 1}. ${d.fork.question}`);
18441
+ lines.push("");
18442
+ if (d.fork.options.length > 0) {
18443
+ lines.push(`Options considered: ${d.fork.options.map((o) => `\`${o}\``).join(", ")}`);
18444
+ lines.push("");
18445
+ }
18446
+ lines.push(`**Recommendation:** ${d.recommendation} (confidence: ${d.confidence})`);
18447
+ lines.push("");
18448
+ if (d.rationale) {
18449
+ lines.push(d.rationale);
18450
+ lines.push("");
18451
+ }
18452
+ }
18453
+ }
18454
+ lines.push("---");
18455
+ lines.push("");
18456
+ lines.push(
18457
+ "_This spec was drafted autonomously. Every fork above cleared the high-confidence bar; any fork the system could not confidently resolve would have halted the brainstorm and handed that fork to a human instead._"
18458
+ );
18459
+ lines.push("");
18460
+ return lines.join("\n");
18461
+ }
18462
+ function writeSpecDoc(docsRoot, spec) {
18463
+ const dir = path22.join(docsRoot, "changes", slugFor(spec));
18464
+ fs16.mkdirSync(dir, { recursive: true });
18465
+ const file = path22.join(dir, "proposal.md");
18466
+ fs16.writeFileSync(file, renderSpecMarkdown(spec), "utf-8");
18467
+ return file;
18468
+ }
18469
+ function enrichIssueWithSpec(issue, spec) {
18470
+ const decisionText = spec.decisions.map((d) => `${d.fork.question} \u2192 ${d.recommendation}. ${d.rationale}`).join("\n");
18471
+ const enrichedDescription = [issue.description ?? "", decisionText].filter(Boolean).join("\n\n");
18472
+ return {
18473
+ ...issue,
18474
+ description: enrichedDescription,
18475
+ // The item now HAS a drafted spec — reflect it so the re-score's spec-exists signal is true.
18476
+ spec: issue.spec ?? `changes/${slugFor(spec)}/proposal.md`
18477
+ };
18478
+ }
18479
+
18480
+ // src/agent/triage-mark.ts
18481
+ import { slugifyFeatureName } from "@harness-engineering/core";
18482
+ import { dispatchableShapeKey } from "@harness-engineering/intelligence";
18483
+ var ELIGIBLE_STATUS = "planned";
18484
+ async function markApprovedForDispatch(items, deps) {
18485
+ const { store, config, recordPrediction, selfAssignee } = deps;
18486
+ if (!config.enabled) {
18487
+ return { ok: true, value: { marked: [], skipped: [] } };
18488
+ }
18489
+ const loaded = await store.load();
18490
+ if (!loaded.ok) return { ok: false, error: loaded.error };
18491
+ const bySlug = /* @__PURE__ */ new Map();
18492
+ for (const m of loaded.value.milestones) {
18493
+ for (const f of m.features) bySlug.set(slugifyFeatureName(f.name), f);
18494
+ }
18495
+ const marked = [];
18496
+ const skipped = [];
18497
+ for (const it of items) {
18498
+ const externalId = it.candidate.externalId;
18499
+ if (!it.candidate.humanApproved) {
18500
+ skipped.push({ externalId, reason: "not-approved" });
18501
+ continue;
18502
+ }
18503
+ const slug = slugifyFeatureName(it.featureName);
18504
+ const target = bySlug.get(slug);
18505
+ if (!target) {
18506
+ skipped.push({ externalId, reason: "feature-not-found" });
18507
+ continue;
18508
+ }
18509
+ if (selfAssignee !== void 0 && target.assignee != null && target.assignee !== selfAssignee) {
18510
+ skipped.push({ externalId, reason: "assigned-elsewhere" });
18511
+ continue;
18512
+ }
18513
+ const mutate = (f) => ({
18514
+ ...f,
18515
+ spec: it.specPath,
18516
+ status: isActiveStatus(f.status) ? f.status : ELIGIBLE_STATUS
18517
+ });
18518
+ const patched = await store.patchFeature(slug, mutate);
18519
+ if (!patched.ok) return { ok: false, error: patched.error };
18520
+ const key = dispatchableShapeKey(it.labels, it.verdict.level);
18521
+ const prediction = {
18522
+ externalId,
18523
+ shapeKey: key,
18524
+ verdict: it.verdict,
18525
+ // `levers` is an OPAQUE DIAGNOSTIC SNAPSHOT (the verdict's static signals), NOT the
18526
+ // typed ProbeLevers. The Phase-4 comparator grades on `verdict.level` +
18527
+ // `scopeEstimate` ONLY (see retrospective.ts) and never reads `levers`, so this
18528
+ // human-legible signal dump cannot mislead the grade. The full typed ProbeLevers
18529
+ // are not threaded here on purpose (it would balloon ReadyCandidate→marker for a
18530
+ // field nothing downstream consumes); `record.ts` re-documents the field to match.
18531
+ levers: it.verdict.signals,
18532
+ scopeEstimate: it.scopeEstimate,
18533
+ // Prefer the per-shape evidence-derived stage (SC6) when the caller resolved one;
18534
+ // fall back to the uniform config stage (Phase-3 behavior). Cold-start ⇒ the caller
18535
+ // resolves stage 1, so this is byte-identical to `config.ratchetStage` at cold-start.
18536
+ ratchetStage: it.effectiveStage ?? config.ratchetStage
18537
+ };
18538
+ const recorded = await recordPrediction(prediction);
18539
+ if (!recorded.ok) {
18540
+ return { ok: false, error: recorded.error ?? new Error("recordPrediction failed") };
18541
+ }
18542
+ marked.push(externalId);
18543
+ }
18544
+ return { ok: true, value: { marked, skipped } };
18545
+ }
18546
+ function isActiveStatus(status) {
18547
+ return status === "planned" || status === "in-progress";
18548
+ }
18549
+
17969
18550
  // src/maintenance/sync-main.ts
17970
18551
  import { execFile as nodeExecFile2 } from "child_process";
17971
18552
  import { promisify as promisify5 } from "util";
@@ -18159,8 +18740,8 @@ function selectTasks(tasks, history, filter) {
18159
18740
  }
18160
18741
 
18161
18742
  // src/sessions/search-index.ts
18162
- import * as fs16 from "fs";
18163
- import * as path22 from "path";
18743
+ import * as fs17 from "fs";
18744
+ import * as path23 from "path";
18164
18745
  import Database2 from "better-sqlite3";
18165
18746
  import { INDEXED_FILE_KINDS } from "@harness-engineering/types";
18166
18747
  var SEARCH_INDEX_FILE = "search-index.sqlite";
@@ -18205,7 +18786,7 @@ function normalizeFts5Query(query) {
18205
18786
  return query.split(/\s+/).filter((tok) => tok.length > 0).map((tok) => `"${tok.replace(/"/g, '""')}"`).join(" ");
18206
18787
  }
18207
18788
  function searchIndexPath(projectPath) {
18208
- return path22.join(projectPath, ".harness", SEARCH_INDEX_FILE);
18789
+ return path23.join(projectPath, ".harness", SEARCH_INDEX_FILE);
18209
18790
  }
18210
18791
  var FILE_KIND_TO_FILENAME = {
18211
18792
  summary: "summary.md",
@@ -18220,7 +18801,7 @@ var SqliteSearchIndex = class {
18220
18801
  removeSessionStmt;
18221
18802
  totalStmt;
18222
18803
  constructor(dbPath) {
18223
- fs16.mkdirSync(path22.dirname(dbPath), { recursive: true });
18804
+ fs17.mkdirSync(path23.dirname(dbPath), { recursive: true });
18224
18805
  this.db = new Database2(dbPath);
18225
18806
  this.db.pragma("journal_mode = WAL");
18226
18807
  this.db.pragma("synchronous = NORMAL");
@@ -18325,14 +18906,14 @@ function indexSessionDirectory(idx, args) {
18325
18906
  let docsWritten = 0;
18326
18907
  for (const kind of kinds) {
18327
18908
  const fileName = FILE_KIND_TO_FILENAME[kind];
18328
- const filePath = path22.join(args.sessionDir, fileName);
18329
- if (!fs16.existsSync(filePath)) continue;
18330
- let body = fs16.readFileSync(filePath, "utf8");
18909
+ const filePath = path23.join(args.sessionDir, fileName);
18910
+ if (!fs17.existsSync(filePath)) continue;
18911
+ let body = fs17.readFileSync(filePath, "utf8");
18331
18912
  if (Buffer.byteLength(body, "utf8") > cap) {
18332
18913
  body = body.slice(0, cap) + "\n\n[TRUNCATED]";
18333
18914
  }
18334
- const stat = fs16.statSync(filePath);
18335
- const relPath = path22.relative(args.projectPath, filePath).replaceAll("\\", "/");
18915
+ const stat = fs17.statSync(filePath);
18916
+ const relPath = path23.relative(args.projectPath, filePath).replaceAll("\\", "/");
18336
18917
  idx.upsertSessionDoc({
18337
18918
  sessionId: args.sessionId,
18338
18919
  archived: args.archived,
@@ -18347,17 +18928,17 @@ function indexSessionDirectory(idx, args) {
18347
18928
  }
18348
18929
  function reindexFromArchive(projectPath, opts = {}) {
18349
18930
  const start = Date.now();
18350
- const archiveBase = path22.join(projectPath, ".harness", "archive", "sessions");
18931
+ const archiveBase = path23.join(projectPath, ".harness", "archive", "sessions");
18351
18932
  const idx = openSearchIndex(projectPath);
18352
18933
  try {
18353
18934
  idx.resetArchived();
18354
18935
  let sessionsIndexed = 0;
18355
18936
  let docsWritten = 0;
18356
- if (fs16.existsSync(archiveBase)) {
18357
- const entries = fs16.readdirSync(archiveBase, { withFileTypes: true });
18937
+ if (fs17.existsSync(archiveBase)) {
18938
+ const entries = fs17.readdirSync(archiveBase, { withFileTypes: true });
18358
18939
  for (const entry of entries) {
18359
18940
  if (!entry.isDirectory()) continue;
18360
- const sessionDir = path22.join(archiveBase, entry.name);
18941
+ const sessionDir = path23.join(archiveBase, entry.name);
18361
18942
  const result = indexSessionDirectory(idx, {
18362
18943
  sessionId: entry.name,
18363
18944
  sessionDir,
@@ -18377,8 +18958,8 @@ function reindexFromArchive(projectPath, opts = {}) {
18377
18958
  }
18378
18959
 
18379
18960
  // src/sessions/summarize.ts
18380
- import * as fs17 from "fs";
18381
- import * as path23 from "path";
18961
+ import * as fs18 from "fs";
18962
+ import * as path24 from "path";
18382
18963
  import {
18383
18964
  SessionSummarySchema
18384
18965
  } from "@harness-engineering/types";
@@ -18408,10 +18989,10 @@ var USER_PROMPT_PREAMBLE = `Below are the archived files for a single harness-en
18408
18989
  function readInputCorpus(archiveDir) {
18409
18990
  const parts = [];
18410
18991
  for (const { filename, kind } of SUMMARY_INPUT_FILES) {
18411
- const p = path23.join(archiveDir, filename);
18412
- if (!fs17.existsSync(p)) continue;
18992
+ const p = path24.join(archiveDir, filename);
18993
+ if (!fs18.existsSync(p)) continue;
18413
18994
  try {
18414
- const content = fs17.readFileSync(p, "utf8");
18995
+ const content = fs18.readFileSync(p, "utf8");
18415
18996
  if (content.trim().length === 0) continue;
18416
18997
  parts.push(`## FILE: ${kind}
18417
18998
 
@@ -18462,7 +19043,7 @@ function renderLlmSummaryMarkdown(summary, meta) {
18462
19043
  return lines.join("\n");
18463
19044
  }
18464
19045
  function writeStubMarkdown(archiveDir, reason) {
18465
- const filePath = path23.join(archiveDir, LLM_SUMMARY_FILE);
19046
+ const filePath = path24.join(archiveDir, LLM_SUMMARY_FILE);
18466
19047
  const body = `---
18467
19048
  generatedAt: ${(/* @__PURE__ */ new Date()).toISOString()}
18468
19049
  schemaVersion: 1
@@ -18473,12 +19054,12 @@ status: failed
18473
19054
 
18474
19055
  - reason: ${reason}
18475
19056
  `;
18476
- fs17.writeFileSync(filePath, body, "utf8");
19057
+ fs18.writeFileSync(filePath, body, "utf8");
18477
19058
  return filePath;
18478
19059
  }
18479
19060
  async function summarizeArchivedSession(ctx) {
18480
19061
  const writeStubOnError = ctx.writeStubOnError ?? true;
18481
- if (!fs17.existsSync(ctx.archiveDir)) {
19062
+ if (!fs18.existsSync(ctx.archiveDir)) {
18482
19063
  return Err23(new Error(`archive directory not found: ${ctx.archiveDir}`));
18483
19064
  }
18484
19065
  const corpus = readInputCorpus(ctx.archiveDir);
@@ -18539,9 +19120,9 @@ async function summarizeArchivedSession(ctx) {
18539
19120
  outputTokens: response.tokenUsage.outputTokens,
18540
19121
  schemaVersion: 1
18541
19122
  };
18542
- const filePath = path23.join(ctx.archiveDir, LLM_SUMMARY_FILE);
19123
+ const filePath = path24.join(ctx.archiveDir, LLM_SUMMARY_FILE);
18543
19124
  const body = renderLlmSummaryMarkdown(parsed.data, meta);
18544
- fs17.writeFileSync(filePath, body, "utf8");
19125
+ fs18.writeFileSync(filePath, body, "utf8");
18545
19126
  return Ok24({ summary: parsed.data, meta, filePath });
18546
19127
  }
18547
19128
  function isSummaryEnabled(config) {
@@ -18622,6 +19203,7 @@ import { discoverCandidates } from "@harness-engineering/local-models";
18622
19203
  export {
18623
19204
  AdaptiveRouter,
18624
19205
  AnalysisArchive,
19206
+ BRAINSTORM_RUBRIC,
18625
19207
  BUILT_IN_TASKS,
18626
19208
  BackendDefSchema,
18627
19209
  BackendRouter,
@@ -18664,8 +19246,10 @@ export {
18664
19246
  WorkspaceManager,
18665
19247
  applyEvent,
18666
19248
  artifactPresenceFromIssue,
19249
+ brainstormInputFromIssue,
18667
19250
  buildArchiveHooks,
18668
19251
  buildCapabilityRegistry,
19252
+ buildProbeInput,
18669
19253
  buildWorkflowContext,
18670
19254
  calculateRetryDelay,
18671
19255
  canDispatch,
@@ -18685,6 +19269,7 @@ export {
18685
19269
  emitProposalApproved,
18686
19270
  emitProposalCreated,
18687
19271
  emitProposalRejected,
19272
+ enrichIssueWithSpec,
18688
19273
  estimateCost,
18689
19274
  explicitFindingsCount,
18690
19275
  extractHighlights,
@@ -18699,22 +19284,30 @@ export {
18699
19284
  launchTUI,
18700
19285
  loadPublishedIndex,
18701
19286
  makeBackendResolver,
19287
+ makeGraphScope,
19288
+ makeSelForkGenerator,
19289
+ markApprovedForDispatch,
18702
19290
  migrateAgentConfig,
18703
19291
  normalizeFts5Query,
18704
19292
  normalizeHarnessCommand,
18705
19293
  normalizeLocalModel,
18706
19294
  openSearchIndex,
19295
+ pilotScore,
19296
+ precedentLookupFromStored,
18707
19297
  promote,
19298
+ rankTriageCandidates,
18708
19299
  reconcile,
18709
19300
  recoverFindingsCount,
18710
19301
  reindexFromArchive,
18711
19302
  renderAnalysisComment,
18712
19303
  renderLlmSummaryMarkdown,
18713
19304
  renderPRComment,
19305
+ renderSpecMarkdown,
18714
19306
  resolveEscalationConfig,
18715
19307
  resolveOrchestratorId,
18716
19308
  routeIssue,
18717
19309
  routingWarnings,
19310
+ runBrainstormForIssue,
18718
19311
  runGate,
18719
19312
  runHarnessCheck,
18720
19313
  savePublishedIndex,
@@ -18722,10 +19315,11 @@ export {
18722
19315
  selectCandidates,
18723
19316
  selectCheapestQualifying,
18724
19317
  selectTasks,
19318
+ slugFor,
18725
19319
  sortCandidates,
18726
19320
  summarizeArchivedSession,
18727
19321
  syncMain,
18728
- triageIssue,
19322
+ triageIssue2 as triageIssue,
18729
19323
  truncateForBudget,
18730
19324
  validateCustomTasks,
18731
19325
  validateWorkflowConfig,