@harness-engineering/intelligence 0.5.0 → 0.7.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
@@ -2040,22 +2040,623 @@ function refreshProfiles(projectRoot, graphStore, opts) {
2040
2040
  saveProfiles(projectRoot, store);
2041
2041
  return store;
2042
2042
  }
2043
+
2044
+ // src/complexity/signals.ts
2045
+ function serializeSignals(s) {
2046
+ const out = {
2047
+ descriptionLength: s.descriptionLength,
2048
+ specExists: s.specExists,
2049
+ acceptanceMeasurable: s.acceptanceMeasurable
2050
+ };
2051
+ if (s.filesTouched !== void 0) out.filesTouched = s.filesTouched;
2052
+ if (s.layersTouched !== void 0) out.layersTouched = s.layersTouched;
2053
+ if (s.blastRadius !== void 0) out.blastRadius = s.blastRadius;
2054
+ if (s.hotspotChurn !== void 0) out.hotspotChurn = s.hotspotChurn;
2055
+ return out;
2056
+ }
2057
+
2058
+ // src/complexity/static-pass.ts
2059
+ var STATIC_WEIGHTS = {
2060
+ filesTouched: 0.25,
2061
+ layersTouched: 0.2,
2062
+ blastRadius: 0.3,
2063
+ hotspotChurn: 0.1,
2064
+ /** Text-only signals — the only inputs available pre-diff. */
2065
+ descriptionLength: 0.1,
2066
+ /** A present spec / measurable acceptance *lowers* complexity (well-scoped). */
2067
+ specExists: 0.025,
2068
+ acceptanceMeasurable: 0.025
2069
+ };
2070
+ var REF_MAX = {
2071
+ filesTouched: 20,
2072
+ layersTouched: 4,
2073
+ blastRadius: 40,
2074
+ hotspotChurn: 1,
2075
+ descriptionLength: 500
2076
+ };
2077
+ var CONFIDENCE_RANK = {
2078
+ low: 0,
2079
+ medium: 1,
2080
+ high: 2
2081
+ };
2082
+ function clamp01(n) {
2083
+ return Math.max(0, Math.min(1, n));
2084
+ }
2085
+ function scoreToLevel(score2) {
2086
+ if (score2 < 0.2) return "trivial";
2087
+ if (score2 < 0.45) return "simple";
2088
+ if (score2 < 0.7) return "moderate";
2089
+ return "complex";
2090
+ }
2091
+ function scoreToConfidence(score2) {
2092
+ const boundaries = [0.2, 0.45, 0.7];
2093
+ const nearest = Math.min(...boundaries.map((b) => Math.abs(score2 - b)));
2094
+ const atExtremeEnd = score2 < 0.2 || score2 >= 0.7;
2095
+ if (nearest < 0.1) return "low";
2096
+ if (nearest < 0.18 && !atExtremeEnd) return "medium";
2097
+ return "high";
2098
+ }
2099
+ function runStaticPass(signals, phase) {
2100
+ const isPostDiff = phase === "post-diff";
2101
+ const contributions = [];
2102
+ const weightsUsed = [];
2103
+ if (isPostDiff) {
2104
+ if (signals.filesTouched !== void 0) {
2105
+ contributions.push(
2106
+ clamp01(signals.filesTouched / REF_MAX.filesTouched) * STATIC_WEIGHTS.filesTouched
2107
+ );
2108
+ weightsUsed.push(STATIC_WEIGHTS.filesTouched);
2109
+ }
2110
+ if (signals.layersTouched !== void 0) {
2111
+ contributions.push(
2112
+ clamp01(signals.layersTouched / REF_MAX.layersTouched) * STATIC_WEIGHTS.layersTouched
2113
+ );
2114
+ weightsUsed.push(STATIC_WEIGHTS.layersTouched);
2115
+ }
2116
+ if (signals.blastRadius !== void 0) {
2117
+ contributions.push(
2118
+ clamp01(signals.blastRadius / REF_MAX.blastRadius) * STATIC_WEIGHTS.blastRadius
2119
+ );
2120
+ weightsUsed.push(STATIC_WEIGHTS.blastRadius);
2121
+ }
2122
+ if (signals.hotspotChurn !== void 0) {
2123
+ contributions.push(
2124
+ clamp01(signals.hotspotChurn / REF_MAX.hotspotChurn) * STATIC_WEIGHTS.hotspotChurn
2125
+ );
2126
+ weightsUsed.push(STATIC_WEIGHTS.hotspotChurn);
2127
+ }
2128
+ }
2129
+ contributions.push(
2130
+ clamp01(signals.descriptionLength / REF_MAX.descriptionLength) * STATIC_WEIGHTS.descriptionLength
2131
+ );
2132
+ weightsUsed.push(STATIC_WEIGHTS.descriptionLength);
2133
+ const scopeReduction = (signals.specExists ? STATIC_WEIGHTS.specExists : 0) + (signals.acceptanceMeasurable ? STATIC_WEIGHTS.acceptanceMeasurable : 0);
2134
+ const totalWeight = weightsUsed.reduce((a, b) => a + b, 0);
2135
+ const rawScore = contributions.reduce((a, b) => a + b, 0);
2136
+ const normalized = totalWeight > 0 ? rawScore / totalWeight : 0;
2137
+ const score2 = clamp01(normalized - scopeReduction);
2138
+ const level = scoreToLevel(score2);
2139
+ let confidence = scoreToConfidence(score2);
2140
+ if (!isPostDiff && CONFIDENCE_RANK[confidence] > CONFIDENCE_RANK.medium) {
2141
+ confidence = "medium";
2142
+ }
2143
+ return {
2144
+ level,
2145
+ confidence,
2146
+ signals: serializeSignals(signals)
2147
+ };
2148
+ }
2149
+
2150
+ // src/complexity/tiebreak.ts
2151
+ import { z as z6 } from "zod";
2152
+ var TiebreakSchema = z6.object({
2153
+ level: z6.enum(["trivial", "simple", "moderate", "complex"]),
2154
+ confidence: z6.enum(["high", "medium", "low"])
2155
+ });
2156
+ async function llmTiebreak(provider, prompt, fastModel) {
2157
+ try {
2158
+ const { result } = await provider.analyze({
2159
+ prompt,
2160
+ responseSchema: TiebreakSchema,
2161
+ // Only include `model` when supplied (exactOptionalPropertyTypes).
2162
+ ...fastModel !== void 0 ? { model: fastModel } : {},
2163
+ maxTokens: 256
2164
+ });
2165
+ return result;
2166
+ } catch {
2167
+ return { level: "moderate", confidence: "low" };
2168
+ }
2169
+ }
2170
+
2171
+ // src/complexity/classifier.ts
2172
+ var CONFIDENCE_RANK2 = { low: 0, medium: 1, high: 2 };
2173
+ function capForPhase(confidence, phase) {
2174
+ if (phase === "pre-diff" && CONFIDENCE_RANK2[confidence] > CONFIDENCE_RANK2.medium) {
2175
+ return "medium";
2176
+ }
2177
+ return confidence;
2178
+ }
2179
+ async function classify(input, provider, models) {
2180
+ const staticVerdict = runStaticPass(input.signals, input.phase);
2181
+ if (staticVerdict.confidence !== "low" || provider === void 0) {
2182
+ return {
2183
+ level: staticVerdict.level,
2184
+ confidence: staticVerdict.confidence,
2185
+ signals: staticVerdict.signals,
2186
+ source: "static"
2187
+ };
2188
+ }
2189
+ const tiebreak = await llmTiebreak(provider, input.prompt, models?.fast);
2190
+ if (tiebreak.confidence === "low" && input.riskHigh) {
2191
+ const escalated = await llmTiebreak(provider, input.prompt, models?.standard);
2192
+ return {
2193
+ level: escalated.level,
2194
+ confidence: capForPhase(escalated.confidence, input.phase),
2195
+ signals: staticVerdict.signals,
2196
+ source: "escalated"
2197
+ };
2198
+ }
2199
+ return {
2200
+ level: tiebreak.level,
2201
+ confidence: capForPhase(tiebreak.confidence, input.phase),
2202
+ signals: staticVerdict.signals,
2203
+ source: "llm-tiebreak"
2204
+ };
2205
+ }
2206
+
2207
+ // src/complexity/derive-tier.ts
2208
+ var TIER_RANK = { fast: 0, standard: 1, strong: 2 };
2209
+ var RANK_TIER = ["fast", "standard", "strong"];
2210
+ var SENSITIVE_BLAST_THRESHOLD = 25;
2211
+ var DEFAULT_MATRIX = {
2212
+ trivial: "fast",
2213
+ simple: "fast",
2214
+ moderate: "standard",
2215
+ complex: "strong"
2216
+ };
2217
+ function tierAtRank(rank) {
2218
+ const clamped = Math.max(0, Math.min(RANK_TIER.length - 1, rank));
2219
+ return RANK_TIER[clamped];
2220
+ }
2221
+ function blastRadiusVeto(risk) {
2222
+ if (!risk) return false;
2223
+ return risk.sensitivePath === true || risk.publicApi === true || risk.layer === "core" || risk.layer === "types" || typeof risk.blastRadius === "number" && risk.blastRadius >= SENSITIVE_BLAST_THRESHOLD;
2224
+ }
2225
+ function baseTier(complexity, risk, policy, skillKey) {
2226
+ const veto = blastRadiusVeto(risk);
2227
+ const override = skillKey !== void 0 ? policy.skillTierOverrides?.[skillKey] : void 0;
2228
+ const matrix = policy.complexityTierMatrix ?? {};
2229
+ const fromMatrix = matrix[complexity.level] ?? DEFAULT_MATRIX[complexity.level];
2230
+ let rank = Math.max(override !== void 0 ? TIER_RANK[override] : 0, TIER_RANK[fromMatrix]);
2231
+ if (complexity.confidence === "low") {
2232
+ rank = rank + 1;
2233
+ }
2234
+ if (veto) rank = Math.max(rank, TIER_RANK.strong);
2235
+ return tierAtRank(rank);
2236
+ }
2237
+ var DEFAULT_DEGRADE_AT_PCT = 90;
2238
+ function applyBudgetClamp(tier, risk, policy, spend) {
2239
+ const budget = policy.budget;
2240
+ if (!budget || budget.capUsd <= 0) return tier;
2241
+ const degradeAt = (budget.degradeAtPct ?? DEFAULT_DEGRADE_AT_PCT) / 100;
2242
+ const spentFraction = spend.spentUsd / budget.capUsd;
2243
+ if (spentFraction < degradeAt) return tier;
2244
+ if (blastRadiusVeto(risk)) return tier;
2245
+ const mode = budget.onBudgetExhausted ?? "degrade";
2246
+ if (spentFraction >= 1 && mode !== "human") return "fast";
2247
+ return tierAtRank(TIER_RANK[tier] - 1);
2248
+ }
2249
+ function deriveRequiredTier(complexity, risk, policy, spend, escalationFloor, skillKey) {
2250
+ const base = baseTier(complexity, risk, policy, skillKey);
2251
+ const clamped = applyBudgetClamp(base, risk, policy, spend);
2252
+ return tierAtRank(Math.max(TIER_RANK[escalationFloor], TIER_RANK[clamped]));
2253
+ }
2254
+
2255
+ // src/triage/record.ts
2256
+ function shapeKey(labels, category, level) {
2257
+ const sortedLabels = Array.from(
2258
+ new Set(labels.map((l) => l.trim().replace(/[,|]/g, "")).filter((l) => l.length > 0))
2259
+ ).sort();
2260
+ return `${sortedLabels.join(",")}|${category}|${level}`;
2261
+ }
2262
+ function dispatchableShapeKey(labels, level) {
2263
+ return shapeKey(labels, "dispatchable", level);
2264
+ }
2265
+
2266
+ // src/triage/precedent.ts
2267
+ function aggregatePrecedent(records, shapeKey2) {
2268
+ let matched = 0;
2269
+ let total = 0;
2270
+ for (const rec of records) {
2271
+ if (rec.shapeKey !== shapeKey2) continue;
2272
+ if (!rec.outcome) continue;
2273
+ total += 1;
2274
+ if (rec.outcome.matched) matched += 1;
2275
+ }
2276
+ if (total === 0) return { kind: "unknown" };
2277
+ return { kind: "rate", matched, total, rate: matched / total };
2278
+ }
2279
+ function precedentLookupFromRecords(records) {
2280
+ return {
2281
+ rateForShape: (shapeKey2) => aggregatePrecedent(records, shapeKey2)
2282
+ };
2283
+ }
2284
+
2285
+ // src/triage/entities.ts
2286
+ var BACKTICKED_RE = /`([^`]+)`/g;
2287
+ var PATH_RE = /(?:\.\/)?(?:[A-Za-z0-9_@.-]+\/)+[A-Za-z0-9_.-]+/g;
2288
+ var CAMEL_RE = /\b([A-Z][a-z]+[A-Za-z0-9]*[A-Z][A-Za-z0-9]*|[a-z]+[A-Z][A-Za-z0-9]*)\b/g;
2289
+ var DOTTED_RE = /\b[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)+\b/g;
2290
+ function extractEntities(text) {
2291
+ if (typeof text !== "string") return [];
2292
+ const trimmed = text.trim();
2293
+ if (trimmed.length === 0) return [];
2294
+ const seen = /* @__PURE__ */ new Set();
2295
+ const result = [];
2296
+ const add = (raw) => {
2297
+ const token = raw.trim();
2298
+ if (token.length === 0) return;
2299
+ if (seen.has(token)) return;
2300
+ seen.add(token);
2301
+ result.push(token);
2302
+ };
2303
+ for (const m of trimmed.matchAll(BACKTICKED_RE)) {
2304
+ const inner = m[1];
2305
+ if (inner !== void 0) add(inner);
2306
+ }
2307
+ for (const m of trimmed.matchAll(PATH_RE)) add(m[0]);
2308
+ for (const m of trimmed.matchAll(CAMEL_RE)) add(m[0]);
2309
+ for (const m of trimmed.matchAll(DOTTED_RE)) add(m[0]);
2310
+ return result;
2311
+ }
2312
+
2313
+ // src/triage/probe.ts
2314
+ import { z as z7 } from "zod";
2315
+ var DEFAULT_CONFIG = {
2316
+ boundedScopeMax: 40,
2317
+ dispatchConfidence: "medium",
2318
+ precedentBlockRate: 0,
2319
+ precedentMinSample: 1
2320
+ };
2321
+ var OpenDecisionsSchema = z7.object({
2322
+ openDecisions: z7.array(z7.object({ question: z7.string() })).describe(
2323
+ "Choices requiring human judgment (API shape, product tradeoff, irreversible action)"
2324
+ )
2325
+ });
2326
+ var OPEN_DECISIONS_SYSTEM_PROMPT = "You are triaging a backlog item for autonomous execution. Identify ONLY choices that genuinely require human judgment before an agent could safely proceed \u2014 API/interface shape, product tradeoffs, or irreversible/outward-facing actions. If the item is fully scoped and an agent could proceed without a human decision, return an empty array. Do not invent decisions; be conservative about what truly needs a human.";
2327
+ async function runScopingProbe(input, deps = {}) {
2328
+ const config = { ...DEFAULT_CONFIG, ...deps.config };
2329
+ const scope = await runScopeLever(input, deps.graph);
2330
+ const semanticRead = await runSemanticReadLever(input, scope.value, deps);
2331
+ const openDecisions = await runOpenDecisionsLever(input, deps.provider);
2332
+ const precedent = runPrecedentLever(input, semanticRead.value, deps.precedent);
2333
+ const levers = { scope, semanticRead, openDecisions, precedent };
2334
+ const verdict = semanticRead.value === "unknown" ? { level: "moderate", confidence: "low", signals: {}, source: "static" } : semanticRead.value;
2335
+ const { dispatchable, holdReason } = gate(levers, config);
2336
+ const rationale = buildRationale(levers, dispatchable, holdReason);
2337
+ return {
2338
+ externalId: input.externalId,
2339
+ verdict,
2340
+ dispatchable,
2341
+ ...holdReason !== void 0 ? { holdReason } : {},
2342
+ levers,
2343
+ rationale
2344
+ };
2345
+ }
2346
+ async function runScopeLever(input, graph) {
2347
+ if (graph === void 0) {
2348
+ return { value: "unknown", reason: "scope: no graph seam available" };
2349
+ }
2350
+ try {
2351
+ const resolved = [];
2352
+ for (const candidate of input.entityCandidates) {
2353
+ const hit = await graph.resolve(candidate);
2354
+ if (hit) resolved.push(hit);
2355
+ }
2356
+ const blastRadius = resolved.reduce((max, r) => Math.max(max, r.blastRadius), 0);
2357
+ const estimate = {
2358
+ candidates: input.entityCandidates,
2359
+ resolved,
2360
+ blastRadius,
2361
+ layersTouched: Math.min(resolved.length, 4),
2362
+ filesTouched: resolved.length
2363
+ };
2364
+ const note = resolved.length === 0 ? "scope: no entity resolved in graph (unresolved-scope)" : `scope: ${resolved.length} entity(ies) resolved, blastRadius ${blastRadius}`;
2365
+ return { value: estimate, reason: note };
2366
+ } catch (err) {
2367
+ return { value: "unknown", reason: `scope: graph lookup failed \u2014 ${errMsg(err)}` };
2368
+ }
2369
+ }
2370
+ async function runSemanticReadLever(input, scope, deps) {
2371
+ try {
2372
+ const signals = {
2373
+ descriptionLength: input.taskText.descriptionLength,
2374
+ specExists: input.taskText.specExists,
2375
+ acceptanceMeasurable: input.taskText.acceptanceMeasurable,
2376
+ ...scope !== "unknown" && scope.resolved.length > 0 ? {
2377
+ filesTouched: scope.filesTouched,
2378
+ layersTouched: scope.layersTouched,
2379
+ blastRadius: scope.blastRadius
2380
+ } : {}
2381
+ };
2382
+ const phase = scope !== "unknown" && scope.resolved.length > 0 ? "post-diff" : "pre-diff";
2383
+ const classifyInput = {
2384
+ signals,
2385
+ phase,
2386
+ riskHigh: input.riskHigh === true,
2387
+ prompt: input.taskText.prompt
2388
+ };
2389
+ const verdict = await classify(classifyInput, deps.provider, deps.models);
2390
+ return { value: verdict, reason: `semantic-read: ${verdict.level}/${verdict.confidence}` };
2391
+ } catch (err) {
2392
+ return { value: "unknown", reason: `semantic-read: classify failed \u2014 ${errMsg(err)}` };
2393
+ }
2394
+ }
2395
+ async function runOpenDecisionsLever(input, provider) {
2396
+ if (provider === void 0) {
2397
+ return { value: "unknown", reason: "open-decisions: no provider (offline)" };
2398
+ }
2399
+ try {
2400
+ const { result } = await provider.analyze({
2401
+ prompt: input.taskText.prompt,
2402
+ systemPrompt: OPEN_DECISIONS_SYSTEM_PROMPT,
2403
+ responseSchema: OpenDecisionsSchema,
2404
+ maxTokens: 512
2405
+ });
2406
+ const decisions = result.openDecisions ?? [];
2407
+ const note = decisions.length === 0 ? "open-decisions: none surfaced" : `open-decisions: ${decisions.length} requiring human judgment`;
2408
+ return { value: decisions, reason: note };
2409
+ } catch (err) {
2410
+ return { value: "unknown", reason: `open-decisions: assessment failed \u2014 ${errMsg(err)}` };
2411
+ }
2412
+ }
2413
+ function runPrecedentLever(input, verdict, precedent) {
2414
+ if (precedent === void 0) {
2415
+ return { value: "unknown", reason: "precedent: no store (cold-start)" };
2416
+ }
2417
+ try {
2418
+ const level = verdict === "unknown" ? "moderate" : verdict.level;
2419
+ const key = dispatchableShapeKey(input.labels, level);
2420
+ const rate = precedent.rateForShape(key);
2421
+ const note = rate.kind === "unknown" ? "precedent: unknown for this shape (no graded records)" : `precedent: ${rate.matched}/${rate.total} (${(rate.rate * 100).toFixed(0)}%)`;
2422
+ return { value: rate, reason: note };
2423
+ } catch (err) {
2424
+ return { value: "unknown", reason: `precedent: lookup failed \u2014 ${errMsg(err)}` };
2425
+ }
2426
+ }
2427
+ function gate(levers, config) {
2428
+ const scope = levers.scope.value;
2429
+ if (scope === "unknown" || scope.resolved.length === 0) {
2430
+ return { dispatchable: false, holdReason: "unresolved-scope" };
2431
+ }
2432
+ if (scope.blastRadius > config.boundedScopeMax) {
2433
+ return { dispatchable: false, holdReason: "unresolved-scope" };
2434
+ }
2435
+ const read = levers.semanticRead.value;
2436
+ if (read === "unknown") {
2437
+ return { dispatchable: false, holdReason: "not-in-band" };
2438
+ }
2439
+ const inBand = read.level === "trivial" || read.level === "simple";
2440
+ const confidentEnough = CONFIDENCE_RANK[read.confidence] >= CONFIDENCE_RANK[config.dispatchConfidence];
2441
+ if (!inBand || !confidentEnough) {
2442
+ return { dispatchable: false, holdReason: "not-in-band" };
2443
+ }
2444
+ const decisions = levers.openDecisions.value;
2445
+ if (decisions === "unknown") {
2446
+ return { dispatchable: false, holdReason: "read-incomplete" };
2447
+ }
2448
+ if (decisions.length > 0) {
2449
+ return { dispatchable: false, holdReason: "open-decision" };
2450
+ }
2451
+ const precedent = levers.precedent.value;
2452
+ if (precedent !== "unknown" && precedent.kind === "rate" && precedent.total >= (config.precedentMinSample ?? 1) && precedent.rate <= (config.precedentBlockRate ?? 0)) {
2453
+ return { dispatchable: false, holdReason: "precedent-contradicts" };
2454
+ }
2455
+ return { dispatchable: true };
2456
+ }
2457
+ function buildRationale(levers, dispatchable, holdReason) {
2458
+ const parts = [];
2459
+ for (const lever of [levers.scope, levers.semanticRead, levers.openDecisions, levers.precedent]) {
2460
+ if (lever.reason) parts.push(lever.reason);
2461
+ }
2462
+ const decision = dispatchable ? "DISPATCHABLE \u2014 all levers corroborate." : `HELD (${holdReason}).`;
2463
+ return `${decision} ${parts.join("; ")}`;
2464
+ }
2465
+ function errMsg(err) {
2466
+ return err instanceof Error ? err.message : String(err);
2467
+ }
2468
+
2469
+ // src/triage/rank.ts
2470
+ function pilotScore(c) {
2471
+ const effort = c.effort > 0 ? c.effort : 1e-6;
2472
+ const score2 = c.impact * c.confidence / effort;
2473
+ return Number.isFinite(score2) ? score2 : 0;
2474
+ }
2475
+ function rankTriageCandidates(candidates) {
2476
+ return [...candidates].sort((a, b) => {
2477
+ const sa = pilotScore(a);
2478
+ const sb = pilotScore(b);
2479
+ if (sa !== sb) return sb - sa;
2480
+ if (a.impact !== b.impact) return b.impact - a.impact;
2481
+ return a.externalId < b.externalId ? -1 : a.externalId > b.externalId ? 1 : 0;
2482
+ });
2483
+ }
2484
+
2485
+ // src/triage/brainstorm/types.ts
2486
+ var DEPTH_BY_LEVEL = {
2487
+ trivial: { maxForks: 2 },
2488
+ simple: { maxForks: 4 },
2489
+ // moderate/complex are OUT of the eligible band (the probe holds them to human) — a depth
2490
+ // is defined only so the mapping is total; the brainstorm should never run on them.
2491
+ moderate: { maxForks: 4 },
2492
+ complex: { maxForks: 4 }
2493
+ };
2494
+ function depthForLevel(level) {
2495
+ return DEPTH_BY_LEVEL[level] ?? { maxForks: 2 };
2496
+ }
2497
+
2498
+ // src/triage/brainstorm/runner.ts
2499
+ async function runAutoBrainstorm(input, generator, depth) {
2500
+ const accepted = [];
2501
+ const ceiling = Math.max(0, depth.maxForks);
2502
+ for (let index = 0; index < ceiling; index++) {
2503
+ let decision;
2504
+ try {
2505
+ decision = await generator.next(index, accepted);
2506
+ } catch (err) {
2507
+ return {
2508
+ kind: "halted",
2509
+ fork: { id: `fork-${index}`, question: "brainstorm errored before deciding", options: [] },
2510
+ reason: "error",
2511
+ detail: errMsg2(err)
2512
+ };
2513
+ }
2514
+ if (decision === null || decision === void 0) break;
2515
+ if (decision.confidence !== "high") {
2516
+ return {
2517
+ kind: "halted",
2518
+ fork: decision.fork,
2519
+ reason: "low-confidence",
2520
+ detail: `fork '${decision.fork.id}' recommended '${decision.recommendation}' at confidence '${decision.confidence}' (below the 'high' bar) \u2014 ${decision.rationale}`
2521
+ };
2522
+ }
2523
+ accepted.push(decision);
2524
+ }
2525
+ const spec = {
2526
+ externalId: input.externalId,
2527
+ title: input.title,
2528
+ summary: input.summary,
2529
+ decisions: accepted
2530
+ };
2531
+ return { kind: "completed", spec };
2532
+ }
2533
+ function errMsg2(err) {
2534
+ return err instanceof Error ? err.message : String(err);
2535
+ }
2536
+
2537
+ // src/triage/retrospective.ts
2538
+ var LEVEL_RANK = {
2539
+ trivial: 0,
2540
+ simple: 1,
2541
+ moderate: 2,
2542
+ complex: 3
2543
+ };
2544
+ var BLAST_TOLERANCE_FACTOR = 1.5;
2545
+ var BLAST_TOLERANCE_ABS = 2;
2546
+ var DEFAULT_RETROSPECTIVE_CONFIG = { exceededByBands: 1 };
2547
+ function blockEscalate(exceededBy = 1) {
2548
+ return { matched: false, exceededBy: Math.max(1, exceededBy), action: "block-escalate" };
2549
+ }
2550
+ function isLevel(value) {
2551
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(LEVEL_RANK, value);
2552
+ }
2553
+ function blastRadiusOf(verdict) {
2554
+ const raw = verdict.signals?.blastRadius;
2555
+ return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
2556
+ }
2557
+ function compareToPrediction(prediction, postDiffVerdict, config = DEFAULT_RETROSPECTIVE_CONFIG) {
2558
+ if (prediction == null || postDiffVerdict == null) return blockEscalate();
2559
+ const predictedLevel = prediction.verdict?.level;
2560
+ const actualLevel = postDiffVerdict.level;
2561
+ if (!isLevel(predictedLevel) || !isLevel(actualLevel)) return blockEscalate();
2562
+ const levelDelta = LEVEL_RANK[actualLevel] - LEVEL_RANK[predictedLevel];
2563
+ const bandThreshold = Math.max(1, Math.floor(config.exceededByBands));
2564
+ if (levelDelta >= bandThreshold) return blockEscalate(levelDelta);
2565
+ const predictedScope = typeof prediction.scopeEstimate === "number" && Number.isFinite(prediction.scopeEstimate) ? prediction.scopeEstimate : 0;
2566
+ const actualBlast = blastRadiusOf(postDiffVerdict);
2567
+ if (actualBlast !== void 0) {
2568
+ const threshold = predictedScope * BLAST_TOLERANCE_FACTOR + BLAST_TOLERANCE_ABS;
2569
+ if (actualBlast > threshold) return blockEscalate();
2570
+ }
2571
+ return { matched: true, exceededBy: 0, action: "verify" };
2572
+ }
2573
+
2574
+ // src/triage/ratchet.ts
2575
+ var V1_MAX_STAGE = 2;
2576
+ var DEFAULT_RATCHET_CONFIG = {
2577
+ threshold: 0.9,
2578
+ minSample: 5,
2579
+ window: 10
2580
+ };
2581
+ function resolveStage(history, config = DEFAULT_RATCHET_CONFIG) {
2582
+ const window = Math.max(1, Math.floor(config.window));
2583
+ const ordered = history.length > 1 ? [...history].sort((a, b) => (a.ts ?? "").localeCompare(b.ts ?? "")) : history;
2584
+ const recent = ordered.slice(-window);
2585
+ if (recent.length < config.minSample) return 1;
2586
+ if (recent[recent.length - 1]?.matched === false) return 1;
2587
+ const matched = recent.reduce((n, o) => o.matched ? n + 1 : n, 0);
2588
+ const rate = matched / recent.length;
2589
+ if (rate < config.threshold) return 1;
2590
+ return Math.min(2, V1_MAX_STAGE);
2591
+ }
2592
+
2593
+ // src/triage/gate.ts
2594
+ var AUTO_EXECUTE_CATEGORIES = /* @__PURE__ */ new Set([
2595
+ "quick-fix",
2596
+ "diagnostic"
2597
+ ]);
2598
+ function resolveGoNoGoStaged(candidates) {
2599
+ const approved = [];
2600
+ const held = [];
2601
+ for (const c of candidates) {
2602
+ if (c.effectiveStage !== 1 && c.effectiveStage !== 2) {
2603
+ held.push({
2604
+ externalId: c.externalId,
2605
+ category: c.category,
2606
+ reason: "ratchet-stage-unsupported"
2607
+ });
2608
+ continue;
2609
+ }
2610
+ if (!AUTO_EXECUTE_CATEGORIES.has(c.category)) {
2611
+ held.push({ externalId: c.externalId, category: c.category, reason: "not-auto-executable" });
2612
+ continue;
2613
+ }
2614
+ if (!c.humanApproved) {
2615
+ held.push({ externalId: c.externalId, category: c.category, reason: "awaiting-human-go" });
2616
+ continue;
2617
+ }
2618
+ approved.push({ ...c, effectiveStage: c.effectiveStage });
2619
+ }
2620
+ return { approved, held };
2621
+ }
2622
+ function resolveGoNoGo(candidates, stage) {
2623
+ return resolveGoNoGoStaged(candidates.map((c) => ({ ...c, effectiveStage: stage })));
2624
+ }
2043
2625
  export {
2044
2626
  ACCEPTANCE_EVAL_SYSTEM_PROMPT,
2627
+ AUTO_EXECUTE_CATEGORIES,
2045
2628
  AcceptanceEvaluator,
2046
2629
  AnthropicAnalysisProvider,
2630
+ BLAST_TOLERANCE_ABS,
2631
+ BLAST_TOLERANCE_FACTOR,
2047
2632
  ClaudeCliAnalysisProvider,
2633
+ DEFAULT_DEGRADE_AT_PCT,
2634
+ DEFAULT_RATCHET_CONFIG,
2635
+ DEFAULT_RETROSPECTIVE_CONFIG,
2636
+ DEPTH_BY_LEVEL,
2048
2637
  ExecutionOutcomeConnector,
2049
2638
  GraphValidator,
2050
2639
  IntelligencePipeline,
2640
+ LEVEL_RANK,
2051
2641
  OUTCOME_EVAL_SYSTEM_PROMPT,
2052
2642
  OpenAICompatibleAnalysisProvider,
2053
2643
  OutcomeEvaluator,
2054
2644
  PeslSimulator,
2645
+ RANK_TIER,
2646
+ SENSITIVE_BLAST_THRESHOLD,
2647
+ STATIC_WEIGHTS,
2648
+ TIER_RANK,
2649
+ V1_MAX_STAGE,
2055
2650
  acceptanceVerdictSchema,
2651
+ aggregatePrecedent,
2652
+ applyBudgetClamp,
2653
+ baseTier,
2654
+ blastRadiusVeto,
2056
2655
  buildUserPrompt3 as buildAcceptanceUserPrompt,
2057
2656
  buildSpecializationProfile,
2058
2657
  buildUserPrompt2 as buildUserPrompt,
2658
+ classify,
2659
+ compareToPrediction,
2059
2660
  computeExpertiseLevel,
2060
2661
  computeHistoricalComplexity,
2061
2662
  computePersonaEffectiveness,
@@ -2064,24 +2665,40 @@ export {
2064
2665
  computeStructuralComplexity,
2065
2666
  createCanaryAdapter,
2066
2667
  decayWeight,
2668
+ depthForLevel,
2067
2669
  deriveAcceptanceAuthority,
2068
2670
  deriveAuthority,
2671
+ deriveRequiredTier,
2069
2672
  detectBlindSpots,
2673
+ dispatchableShapeKey,
2070
2674
  enrich,
2675
+ extractEntities,
2071
2676
  findingSchema,
2072
2677
  githubToRawWorkItem,
2073
2678
  jiraToRawWorkItem,
2074
2679
  linearToRawWorkItem,
2680
+ llmTiebreak,
2075
2681
  loadProfiles,
2076
2682
  manualToRawWorkItem,
2683
+ pilotScore,
2684
+ precedentLookupFromRecords,
2685
+ rankTriageCandidates,
2077
2686
  recommendPersona,
2078
2687
  refreshProfiles,
2688
+ resolveGoNoGo,
2689
+ resolveGoNoGoStaged,
2079
2690
  resolveSection,
2691
+ resolveStage,
2692
+ runAutoBrainstorm,
2080
2693
  runGraphOnlyChecks,
2081
2694
  runLlmSimulation,
2695
+ runScopingProbe,
2696
+ runStaticPass,
2082
2697
  saveProfiles,
2083
2698
  score as scoreCML,
2084
2699
  scoreToConcernSignals,
2700
+ serializeSignals,
2701
+ shapeKey,
2085
2702
  temporalSuccessRate,
2086
2703
  toRawWorkItem,
2087
2704
  verdictSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-engineering/intelligence",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Intelligence pipeline for spec enrichment, complexity modeling, and pre-execution simulation",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -40,8 +40,8 @@
40
40
  "@anthropic-ai/sdk": "^0.95.1",
41
41
  "openai": "^6.0.0",
42
42
  "zod": "^3.25.76",
43
- "@harness-engineering/types": "0.20.0",
44
- "@harness-engineering/graph": "0.11.6"
43
+ "@harness-engineering/graph": "0.11.8",
44
+ "@harness-engineering/types": "0.22.0"
45
45
  },
46
46
  "optionalDependencies": {
47
47
  "canary-test-cli": "^5.4.0"