@harness-engineering/intelligence 0.6.0 → 0.8.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
@@ -2251,15 +2251,393 @@ function deriveRequiredTier(complexity, risk, policy, spend, escalationFloor, sk
2251
2251
  const clamped = applyBudgetClamp(base, risk, policy, spend);
2252
2252
  return tierAtRank(Math.max(TIER_RANK[escalationFloor], TIER_RANK[clamped]));
2253
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: "scope-too-large" };
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
+ }
2254
2625
  export {
2255
2626
  ACCEPTANCE_EVAL_SYSTEM_PROMPT,
2627
+ AUTO_EXECUTE_CATEGORIES,
2256
2628
  AcceptanceEvaluator,
2257
2629
  AnthropicAnalysisProvider,
2630
+ BLAST_TOLERANCE_ABS,
2631
+ BLAST_TOLERANCE_FACTOR,
2258
2632
  ClaudeCliAnalysisProvider,
2259
2633
  DEFAULT_DEGRADE_AT_PCT,
2634
+ DEFAULT_RATCHET_CONFIG,
2635
+ DEFAULT_RETROSPECTIVE_CONFIG,
2636
+ DEPTH_BY_LEVEL,
2260
2637
  ExecutionOutcomeConnector,
2261
2638
  GraphValidator,
2262
2639
  IntelligencePipeline,
2640
+ LEVEL_RANK,
2263
2641
  OUTCOME_EVAL_SYSTEM_PROMPT,
2264
2642
  OpenAICompatibleAnalysisProvider,
2265
2643
  OutcomeEvaluator,
@@ -2268,7 +2646,9 @@ export {
2268
2646
  SENSITIVE_BLAST_THRESHOLD,
2269
2647
  STATIC_WEIGHTS,
2270
2648
  TIER_RANK,
2649
+ V1_MAX_STAGE,
2271
2650
  acceptanceVerdictSchema,
2651
+ aggregatePrecedent,
2272
2652
  applyBudgetClamp,
2273
2653
  baseTier,
2274
2654
  blastRadiusVeto,
@@ -2276,6 +2656,7 @@ export {
2276
2656
  buildSpecializationProfile,
2277
2657
  buildUserPrompt2 as buildUserPrompt,
2278
2658
  classify,
2659
+ compareToPrediction,
2279
2660
  computeExpertiseLevel,
2280
2661
  computeHistoricalComplexity,
2281
2662
  computePersonaEffectiveness,
@@ -2284,11 +2665,14 @@ export {
2284
2665
  computeStructuralComplexity,
2285
2666
  createCanaryAdapter,
2286
2667
  decayWeight,
2668
+ depthForLevel,
2287
2669
  deriveAcceptanceAuthority,
2288
2670
  deriveAuthority,
2289
2671
  deriveRequiredTier,
2290
2672
  detectBlindSpots,
2673
+ dispatchableShapeKey,
2291
2674
  enrich,
2675
+ extractEntities,
2292
2676
  findingSchema,
2293
2677
  githubToRawWorkItem,
2294
2678
  jiraToRawWorkItem,
@@ -2296,16 +2680,25 @@ export {
2296
2680
  llmTiebreak,
2297
2681
  loadProfiles,
2298
2682
  manualToRawWorkItem,
2683
+ pilotScore,
2684
+ precedentLookupFromRecords,
2685
+ rankTriageCandidates,
2299
2686
  recommendPersona,
2300
2687
  refreshProfiles,
2688
+ resolveGoNoGo,
2689
+ resolveGoNoGoStaged,
2301
2690
  resolveSection,
2691
+ resolveStage,
2692
+ runAutoBrainstorm,
2302
2693
  runGraphOnlyChecks,
2303
2694
  runLlmSimulation,
2695
+ runScopingProbe,
2304
2696
  runStaticPass,
2305
2697
  saveProfiles,
2306
2698
  score as scoreCML,
2307
2699
  scoreToConcernSignals,
2308
2700
  serializeSignals,
2701
+ shapeKey,
2309
2702
  temporalSuccessRate,
2310
2703
  toRawWorkItem,
2311
2704
  verdictSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-engineering/intelligence",
3
- "version": "0.6.0",
3
+ "version": "0.8.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/graph": "0.11.7",
44
- "@harness-engineering/types": "0.21.0"
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"