@harness-engineering/intelligence 0.5.0 → 0.6.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.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _harness_engineering_types from '@harness-engineering/types';
2
- import { Issue, ConcernSignal, ScopeTier, EscalationConfig } from '@harness-engineering/types';
2
+ import { Issue, ConcernSignal, ScopeTier, EscalationConfig, ComplexityLevel, ComplexityVerdict, CapabilityTier, RoutingRisk, RoutingPolicy } from '@harness-engineering/types';
3
3
  import { z } from 'zod';
4
4
  import { GraphStore } from '@harness-engineering/graph';
5
5
 
@@ -1342,4 +1342,153 @@ declare function saveProfiles(projectRoot: string, store: ProfileStore): void;
1342
1342
  */
1343
1343
  declare function refreshProfiles(projectRoot: string, graphStore: GraphStore, opts?: Omit<SpecializationOptions, 'persona'>): ProfileStore;
1344
1344
 
1345
- export { ACCEPTANCE_EVAL_SYSTEM_PROMPT, type AcceptanceEvalInput, AcceptanceEvaluator, type AcceptanceEvaluatorOptions, type AcceptanceVerdict, type AffectedSystem, type AnalysisProvider, type AnalysisRequest, type AnalysisResponse, AnthropicAnalysisProvider, type Authority, type BlastRadius, type BlindSpot, type CanaryAdapter, type CanaryDegradeReason, type CanaryExec, type CanaryFinding, type CanaryProbe, ClaudeCliAnalysisProvider, type ComplexityScore, type Confidence, type EnrichedSpec, type ExecutionOutcome, ExecutionOutcomeConnector, type ExpertiseLevel, type Finding, type FrameworkRecommendation, type GitHubIssue, GraphValidator, IntelligencePipeline, type JiraIssue, type JudgedAgainst, type LinearIssue, type LlmAcceptanceVerdict, type LlmVerdict, type ManualInput, type Measurability, OUTCOME_EVAL_SYSTEM_PROMPT, OpenAICompatibleAnalysisProvider, type OutcomeEvalInput, OutcomeEvaluator, type OutcomeEvaluatorOptions, type OutcomeIngestResult, type OutcomeVerdict, type PersonaEffectivenessScore, type PersonaRecommendation, PeslSimulator, type PreprocessResult, type ProfileStore, type RawWorkItem, type ResolvedSection, type SimulationResult, type SpecializationEntry, type SpecializationProfile, type SpecializationScore, type TaskType, type TemporalConfig, type Verdict, type WeightedRecommendation, acceptanceVerdictSchema, buildUserPrompt as buildAcceptanceUserPrompt, buildSpecializationProfile, buildUserPrompt$1 as buildUserPrompt, computeExpertiseLevel, computeHistoricalComplexity, computePersonaEffectiveness, computeSemanticComplexity, computeSpecialization, computeStructuralComplexity, createCanaryAdapter, decayWeight, deriveAcceptanceAuthority, deriveAuthority, detectBlindSpots, enrich, findingSchema, githubToRawWorkItem, jiraToRawWorkItem, linearToRawWorkItem, loadProfiles, manualToRawWorkItem, recommendPersona, refreshProfiles, resolveSection, runGraphOnlyChecks, runLlmSimulation, saveProfiles, score as scoreCML, scoreToConcernSignals, temporalSuccessRate, toRawWorkItem, verdictSchema, weightedRecommendPersona };
1345
+ /** Which signal set is available at this invocation phase (S3-001). */
1346
+ type Phase = 'pre-diff' | 'post-diff';
1347
+ /** Raw signals gathered for the static pass. Diff-based fields are undefined pre-diff. */
1348
+ interface ComplexitySignals {
1349
+ /** Files touched by the diff/target. Undefined pre-diff. */
1350
+ filesTouched?: number;
1351
+ /** Distinct architectural layers touched. Undefined pre-diff. */
1352
+ layersTouched?: number;
1353
+ /** compute_blast_radius result. Undefined pre-diff. */
1354
+ blastRadius?: number;
1355
+ /** hotspot × churn heat. Undefined pre-diff. */
1356
+ hotspotChurn?: number;
1357
+ /** Text-only fallback signals (always available). */
1358
+ descriptionLength: number;
1359
+ specExists: boolean;
1360
+ acceptanceMeasurable: boolean;
1361
+ }
1362
+ /** Provisional static verdict before any LLM tie-break. */
1363
+ interface StaticVerdict {
1364
+ level: ComplexityLevel;
1365
+ confidence: 'high' | 'medium' | 'low';
1366
+ /** Serialized subset of signals for the ComplexityVerdict.signals map. */
1367
+ signals: Record<string, number | boolean | string>;
1368
+ }
1369
+
1370
+ /** Inputs to the cheap-first complexity cascade (D4). */
1371
+ interface ClassifyInput {
1372
+ signals: ComplexitySignals;
1373
+ phase: Phase;
1374
+ /** Whether the invocation's risk band is high (drives standard-tier escalation). */
1375
+ riskHigh: boolean;
1376
+ /** Prompt handed to the LLM tie-break / escalation. */
1377
+ prompt: string;
1378
+ }
1379
+ /**
1380
+ * D4 cascade: static pass → (if low confidence) fast-tier tie-break → (if still
1381
+ * low AND risk high) standard-tier escalation. Emits a `ComplexityVerdict` whose
1382
+ * `source` records which stage produced it. The static-only path resolves
1383
+ * without any LLM call ("never pay strong to route"); when `provider` is absent
1384
+ * the classifier stays fully offline and returns the static verdict.
1385
+ *
1386
+ * The LLM only sets `level`/`confidence`; the tier is always TS-derived (D3).
1387
+ */
1388
+ declare function classify(input: ClassifyInput, provider?: AnalysisProvider, models?: {
1389
+ fast?: string;
1390
+ standard?: string;
1391
+ }): Promise<ComplexityVerdict>;
1392
+
1393
+ /**
1394
+ * Documented seed weights for the free static pass (D4a). Each raw signal is
1395
+ * normalized against a reference maximum, multiplied by its weight, and the
1396
+ * weighted contributions are summed and re-normalized to a [0,1] score.
1397
+ *
1398
+ * Diff-based signals dominate post-diff; text-only signals carry the pre-diff
1399
+ * pass. Weights are tunable (DEFERRABLE per plan) — this is a documented seed.
1400
+ */
1401
+ declare const STATIC_WEIGHTS: {
1402
+ readonly filesTouched: 0.25;
1403
+ readonly layersTouched: 0.2;
1404
+ readonly blastRadius: 0.3;
1405
+ readonly hotspotChurn: 0.1;
1406
+ /** Text-only signals — the only inputs available pre-diff. */
1407
+ readonly descriptionLength: 0.1;
1408
+ /** A present spec / measurable acceptance *lowers* complexity (well-scoped). */
1409
+ readonly specExists: 0.025;
1410
+ readonly acceptanceMeasurable: 0.025;
1411
+ };
1412
+ /**
1413
+ * D4a: free static pass. Phase-aware (S3-001): pre-diff uses text-only signals
1414
+ * and caps confidence at `medium`; post-diff uses the full signal set. Pure —
1415
+ * never calls an LLM. `source` is stamped by the classifier, not here.
1416
+ */
1417
+ declare function runStaticPass(signals: ComplexitySignals, phase: Phase): StaticVerdict;
1418
+
1419
+ interface TiebreakResult {
1420
+ level: ComplexityLevel;
1421
+ confidence: 'high' | 'medium' | 'low';
1422
+ }
1423
+ /** D4b: fast-tier structured tie-break. Never sets a tier; falls back conservatively on error. */
1424
+ declare function llmTiebreak(provider: AnalysisProvider, prompt: string, fastModel?: string): Promise<TiebreakResult>;
1425
+
1426
+ declare const TIER_RANK: {
1427
+ fast: number;
1428
+ standard: number;
1429
+ strong: number;
1430
+ };
1431
+ declare const RANK_TIER: readonly ["fast", "standard", "strong"];
1432
+ /**
1433
+ * Documented seed threshold for the D5 high-blast veto. The spec lists
1434
+ * sensitive-path / core|types layer / public API explicitly but pins no numeric
1435
+ * blast threshold, so this is a plan-chosen seed — overridable later.
1436
+ */
1437
+ declare const SENSITIVE_BLAST_THRESHOLD = 25;
1438
+ /**
1439
+ * D5: any sensitive-path / core|types layer / public API / high blast → force
1440
+ * `strong`, regardless of complexity (SC5). This is a HARD floor; downstream
1441
+ * budget clamping (D8) must not undercut it.
1442
+ */
1443
+ declare function blastRadiusVeto(risk?: RoutingRisk): boolean;
1444
+ /**
1445
+ * Pre-budget tier resolution (Task 7): skillTierOverride → matrix → D5 veto →
1446
+ * low-confidence bump. Pure; the LLM never influences it (mirrors
1447
+ * outcome-eval/authority.ts). Task 8's `deriveRequiredTier` calls this then
1448
+ * applies the D8 budget clamp and D10 escalation floor.
1449
+ *
1450
+ * SC6: a `low`-confidence verdict degrades the tier UP one step (never below the
1451
+ * matrix default and never to a cheaper tier) — uncertainty routes to a more
1452
+ * capable model, never a Tier-A cheap one.
1453
+ */
1454
+ declare function baseTier(complexity: ComplexityVerdict, risk: RoutingRisk | undefined, policy: RoutingPolicy, skillKey?: string): CapabilityTier;
1455
+ /** Default budget degrade threshold (% of cap) when policy omits one (D8). */
1456
+ declare const DEFAULT_DEGRADE_AT_PCT = 90;
1457
+ /**
1458
+ * D8: budget-pressure tier clamp. Two thresholds:
1459
+ *
1460
+ * - **Soft (`degradeAtPct`, default 90%):** clamp the tier DOWN one step
1461
+ * (`strong→standard→fast`, `fast→fast`) — a lagging degrade signal.
1462
+ * - **Hard (100% of `capUsd`):** for `degrade`/`pause` policies, force the tier
1463
+ * all the way to `fast` — the strongest sound floor on the routing DECISION
1464
+ * (it is not an admission gate; the monotonic accumulator lags under
1465
+ * concurrency, so it cannot prevent overshoot, only route the cheapest tier
1466
+ * once the read crosses the cap). `human` mode is handled one layer up in
1467
+ * `AdaptiveRouter.route()`, which surfaces the unit to a steward instead of
1468
+ * routing; here it falls through to the soft one-step degrade.
1469
+ *
1470
+ * The D5 blast-radius veto is a HARD `strong` floor the clamp must NOT undercut
1471
+ * (SC5 says sensitive-path / core|types / publicApi force `strong` "regardless"),
1472
+ * so a vetoed request stays `strong` even at the hard cap — the veto guard sits
1473
+ * ABOVE both the hard-floor and the soft-step branches. No budget block ⇒ no clamp.
1474
+ */
1475
+ declare function applyBudgetClamp(tier: CapabilityTier, risk: RoutingRisk | undefined, policy: RoutingPolicy, spend: {
1476
+ spentUsd: number;
1477
+ }): CapabilityTier;
1478
+ /**
1479
+ * Pure (complexity × risk × policy × spend × floor) → required tier.
1480
+ *
1481
+ * `baseTier` resolves override → matrix → D5 veto → low-confidence bump; the D8
1482
+ * budget clamp lowers one step under budget pressure (never below the veto
1483
+ * floor); the D10 escalation floor raises the result but never lowers it. The
1484
+ * LLM never influences this — mirrors outcome-eval/authority.ts (TS-derived
1485
+ * authority). Referentially transparent; never mutates `policy`.
1486
+ */
1487
+ declare function deriveRequiredTier(complexity: ComplexityVerdict, risk: RoutingRisk | undefined, policy: RoutingPolicy, spend: {
1488
+ spentUsd: number;
1489
+ }, escalationFloor: CapabilityTier, skillKey?: string): CapabilityTier;
1490
+
1491
+ /** Flatten signals into the ComplexityVerdict.signals map, dropping undefined fields. */
1492
+ declare function serializeSignals(s: ComplexitySignals): Record<string, number | boolean | string>;
1493
+
1494
+ export { ACCEPTANCE_EVAL_SYSTEM_PROMPT, type AcceptanceEvalInput, AcceptanceEvaluator, type AcceptanceEvaluatorOptions, type AcceptanceVerdict, type AffectedSystem, type AnalysisProvider, type AnalysisRequest, type AnalysisResponse, AnthropicAnalysisProvider, type Authority, type BlastRadius, type BlindSpot, type CanaryAdapter, type CanaryDegradeReason, type CanaryExec, type CanaryFinding, type CanaryProbe, type ClassifyInput, ClaudeCliAnalysisProvider, type ComplexityScore, type ComplexitySignals, type Confidence, DEFAULT_DEGRADE_AT_PCT, type EnrichedSpec, type ExecutionOutcome, ExecutionOutcomeConnector, type ExpertiseLevel, type Finding, type FrameworkRecommendation, type GitHubIssue, GraphValidator, IntelligencePipeline, type JiraIssue, type JudgedAgainst, type LinearIssue, type LlmAcceptanceVerdict, type LlmVerdict, type ManualInput, type Measurability, OUTCOME_EVAL_SYSTEM_PROMPT, OpenAICompatibleAnalysisProvider, type OutcomeEvalInput, OutcomeEvaluator, type OutcomeEvaluatorOptions, type OutcomeIngestResult, type OutcomeVerdict, type PersonaEffectivenessScore, type PersonaRecommendation, PeslSimulator, type Phase, type PreprocessResult, type ProfileStore, RANK_TIER, type RawWorkItem, type ResolvedSection, SENSITIVE_BLAST_THRESHOLD, STATIC_WEIGHTS, type SimulationResult, type SpecializationEntry, type SpecializationProfile, type SpecializationScore, type StaticVerdict, TIER_RANK, type TaskType, type TemporalConfig, type TiebreakResult, type Verdict, type WeightedRecommendation, acceptanceVerdictSchema, applyBudgetClamp, baseTier, blastRadiusVeto, buildUserPrompt as buildAcceptanceUserPrompt, buildSpecializationProfile, buildUserPrompt$1 as buildUserPrompt, classify, computeExpertiseLevel, computeHistoricalComplexity, computePersonaEffectiveness, computeSemanticComplexity, computeSpecialization, computeStructuralComplexity, createCanaryAdapter, decayWeight, deriveAcceptanceAuthority, deriveAuthority, deriveRequiredTier, detectBlindSpots, enrich, findingSchema, githubToRawWorkItem, jiraToRawWorkItem, linearToRawWorkItem, llmTiebreak, loadProfiles, manualToRawWorkItem, recommendPersona, refreshProfiles, resolveSection, runGraphOnlyChecks, runLlmSimulation, runStaticPass, saveProfiles, score as scoreCML, scoreToConcernSignals, serializeSignals, temporalSuccessRate, toRawWorkItem, verdictSchema, weightedRecommendPersona };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _harness_engineering_types from '@harness-engineering/types';
2
- import { Issue, ConcernSignal, ScopeTier, EscalationConfig } from '@harness-engineering/types';
2
+ import { Issue, ConcernSignal, ScopeTier, EscalationConfig, ComplexityLevel, ComplexityVerdict, CapabilityTier, RoutingRisk, RoutingPolicy } from '@harness-engineering/types';
3
3
  import { z } from 'zod';
4
4
  import { GraphStore } from '@harness-engineering/graph';
5
5
 
@@ -1342,4 +1342,153 @@ declare function saveProfiles(projectRoot: string, store: ProfileStore): void;
1342
1342
  */
1343
1343
  declare function refreshProfiles(projectRoot: string, graphStore: GraphStore, opts?: Omit<SpecializationOptions, 'persona'>): ProfileStore;
1344
1344
 
1345
- export { ACCEPTANCE_EVAL_SYSTEM_PROMPT, type AcceptanceEvalInput, AcceptanceEvaluator, type AcceptanceEvaluatorOptions, type AcceptanceVerdict, type AffectedSystem, type AnalysisProvider, type AnalysisRequest, type AnalysisResponse, AnthropicAnalysisProvider, type Authority, type BlastRadius, type BlindSpot, type CanaryAdapter, type CanaryDegradeReason, type CanaryExec, type CanaryFinding, type CanaryProbe, ClaudeCliAnalysisProvider, type ComplexityScore, type Confidence, type EnrichedSpec, type ExecutionOutcome, ExecutionOutcomeConnector, type ExpertiseLevel, type Finding, type FrameworkRecommendation, type GitHubIssue, GraphValidator, IntelligencePipeline, type JiraIssue, type JudgedAgainst, type LinearIssue, type LlmAcceptanceVerdict, type LlmVerdict, type ManualInput, type Measurability, OUTCOME_EVAL_SYSTEM_PROMPT, OpenAICompatibleAnalysisProvider, type OutcomeEvalInput, OutcomeEvaluator, type OutcomeEvaluatorOptions, type OutcomeIngestResult, type OutcomeVerdict, type PersonaEffectivenessScore, type PersonaRecommendation, PeslSimulator, type PreprocessResult, type ProfileStore, type RawWorkItem, type ResolvedSection, type SimulationResult, type SpecializationEntry, type SpecializationProfile, type SpecializationScore, type TaskType, type TemporalConfig, type Verdict, type WeightedRecommendation, acceptanceVerdictSchema, buildUserPrompt as buildAcceptanceUserPrompt, buildSpecializationProfile, buildUserPrompt$1 as buildUserPrompt, computeExpertiseLevel, computeHistoricalComplexity, computePersonaEffectiveness, computeSemanticComplexity, computeSpecialization, computeStructuralComplexity, createCanaryAdapter, decayWeight, deriveAcceptanceAuthority, deriveAuthority, detectBlindSpots, enrich, findingSchema, githubToRawWorkItem, jiraToRawWorkItem, linearToRawWorkItem, loadProfiles, manualToRawWorkItem, recommendPersona, refreshProfiles, resolveSection, runGraphOnlyChecks, runLlmSimulation, saveProfiles, score as scoreCML, scoreToConcernSignals, temporalSuccessRate, toRawWorkItem, verdictSchema, weightedRecommendPersona };
1345
+ /** Which signal set is available at this invocation phase (S3-001). */
1346
+ type Phase = 'pre-diff' | 'post-diff';
1347
+ /** Raw signals gathered for the static pass. Diff-based fields are undefined pre-diff. */
1348
+ interface ComplexitySignals {
1349
+ /** Files touched by the diff/target. Undefined pre-diff. */
1350
+ filesTouched?: number;
1351
+ /** Distinct architectural layers touched. Undefined pre-diff. */
1352
+ layersTouched?: number;
1353
+ /** compute_blast_radius result. Undefined pre-diff. */
1354
+ blastRadius?: number;
1355
+ /** hotspot × churn heat. Undefined pre-diff. */
1356
+ hotspotChurn?: number;
1357
+ /** Text-only fallback signals (always available). */
1358
+ descriptionLength: number;
1359
+ specExists: boolean;
1360
+ acceptanceMeasurable: boolean;
1361
+ }
1362
+ /** Provisional static verdict before any LLM tie-break. */
1363
+ interface StaticVerdict {
1364
+ level: ComplexityLevel;
1365
+ confidence: 'high' | 'medium' | 'low';
1366
+ /** Serialized subset of signals for the ComplexityVerdict.signals map. */
1367
+ signals: Record<string, number | boolean | string>;
1368
+ }
1369
+
1370
+ /** Inputs to the cheap-first complexity cascade (D4). */
1371
+ interface ClassifyInput {
1372
+ signals: ComplexitySignals;
1373
+ phase: Phase;
1374
+ /** Whether the invocation's risk band is high (drives standard-tier escalation). */
1375
+ riskHigh: boolean;
1376
+ /** Prompt handed to the LLM tie-break / escalation. */
1377
+ prompt: string;
1378
+ }
1379
+ /**
1380
+ * D4 cascade: static pass → (if low confidence) fast-tier tie-break → (if still
1381
+ * low AND risk high) standard-tier escalation. Emits a `ComplexityVerdict` whose
1382
+ * `source` records which stage produced it. The static-only path resolves
1383
+ * without any LLM call ("never pay strong to route"); when `provider` is absent
1384
+ * the classifier stays fully offline and returns the static verdict.
1385
+ *
1386
+ * The LLM only sets `level`/`confidence`; the tier is always TS-derived (D3).
1387
+ */
1388
+ declare function classify(input: ClassifyInput, provider?: AnalysisProvider, models?: {
1389
+ fast?: string;
1390
+ standard?: string;
1391
+ }): Promise<ComplexityVerdict>;
1392
+
1393
+ /**
1394
+ * Documented seed weights for the free static pass (D4a). Each raw signal is
1395
+ * normalized against a reference maximum, multiplied by its weight, and the
1396
+ * weighted contributions are summed and re-normalized to a [0,1] score.
1397
+ *
1398
+ * Diff-based signals dominate post-diff; text-only signals carry the pre-diff
1399
+ * pass. Weights are tunable (DEFERRABLE per plan) — this is a documented seed.
1400
+ */
1401
+ declare const STATIC_WEIGHTS: {
1402
+ readonly filesTouched: 0.25;
1403
+ readonly layersTouched: 0.2;
1404
+ readonly blastRadius: 0.3;
1405
+ readonly hotspotChurn: 0.1;
1406
+ /** Text-only signals — the only inputs available pre-diff. */
1407
+ readonly descriptionLength: 0.1;
1408
+ /** A present spec / measurable acceptance *lowers* complexity (well-scoped). */
1409
+ readonly specExists: 0.025;
1410
+ readonly acceptanceMeasurable: 0.025;
1411
+ };
1412
+ /**
1413
+ * D4a: free static pass. Phase-aware (S3-001): pre-diff uses text-only signals
1414
+ * and caps confidence at `medium`; post-diff uses the full signal set. Pure —
1415
+ * never calls an LLM. `source` is stamped by the classifier, not here.
1416
+ */
1417
+ declare function runStaticPass(signals: ComplexitySignals, phase: Phase): StaticVerdict;
1418
+
1419
+ interface TiebreakResult {
1420
+ level: ComplexityLevel;
1421
+ confidence: 'high' | 'medium' | 'low';
1422
+ }
1423
+ /** D4b: fast-tier structured tie-break. Never sets a tier; falls back conservatively on error. */
1424
+ declare function llmTiebreak(provider: AnalysisProvider, prompt: string, fastModel?: string): Promise<TiebreakResult>;
1425
+
1426
+ declare const TIER_RANK: {
1427
+ fast: number;
1428
+ standard: number;
1429
+ strong: number;
1430
+ };
1431
+ declare const RANK_TIER: readonly ["fast", "standard", "strong"];
1432
+ /**
1433
+ * Documented seed threshold for the D5 high-blast veto. The spec lists
1434
+ * sensitive-path / core|types layer / public API explicitly but pins no numeric
1435
+ * blast threshold, so this is a plan-chosen seed — overridable later.
1436
+ */
1437
+ declare const SENSITIVE_BLAST_THRESHOLD = 25;
1438
+ /**
1439
+ * D5: any sensitive-path / core|types layer / public API / high blast → force
1440
+ * `strong`, regardless of complexity (SC5). This is a HARD floor; downstream
1441
+ * budget clamping (D8) must not undercut it.
1442
+ */
1443
+ declare function blastRadiusVeto(risk?: RoutingRisk): boolean;
1444
+ /**
1445
+ * Pre-budget tier resolution (Task 7): skillTierOverride → matrix → D5 veto →
1446
+ * low-confidence bump. Pure; the LLM never influences it (mirrors
1447
+ * outcome-eval/authority.ts). Task 8's `deriveRequiredTier` calls this then
1448
+ * applies the D8 budget clamp and D10 escalation floor.
1449
+ *
1450
+ * SC6: a `low`-confidence verdict degrades the tier UP one step (never below the
1451
+ * matrix default and never to a cheaper tier) — uncertainty routes to a more
1452
+ * capable model, never a Tier-A cheap one.
1453
+ */
1454
+ declare function baseTier(complexity: ComplexityVerdict, risk: RoutingRisk | undefined, policy: RoutingPolicy, skillKey?: string): CapabilityTier;
1455
+ /** Default budget degrade threshold (% of cap) when policy omits one (D8). */
1456
+ declare const DEFAULT_DEGRADE_AT_PCT = 90;
1457
+ /**
1458
+ * D8: budget-pressure tier clamp. Two thresholds:
1459
+ *
1460
+ * - **Soft (`degradeAtPct`, default 90%):** clamp the tier DOWN one step
1461
+ * (`strong→standard→fast`, `fast→fast`) — a lagging degrade signal.
1462
+ * - **Hard (100% of `capUsd`):** for `degrade`/`pause` policies, force the tier
1463
+ * all the way to `fast` — the strongest sound floor on the routing DECISION
1464
+ * (it is not an admission gate; the monotonic accumulator lags under
1465
+ * concurrency, so it cannot prevent overshoot, only route the cheapest tier
1466
+ * once the read crosses the cap). `human` mode is handled one layer up in
1467
+ * `AdaptiveRouter.route()`, which surfaces the unit to a steward instead of
1468
+ * routing; here it falls through to the soft one-step degrade.
1469
+ *
1470
+ * The D5 blast-radius veto is a HARD `strong` floor the clamp must NOT undercut
1471
+ * (SC5 says sensitive-path / core|types / publicApi force `strong` "regardless"),
1472
+ * so a vetoed request stays `strong` even at the hard cap — the veto guard sits
1473
+ * ABOVE both the hard-floor and the soft-step branches. No budget block ⇒ no clamp.
1474
+ */
1475
+ declare function applyBudgetClamp(tier: CapabilityTier, risk: RoutingRisk | undefined, policy: RoutingPolicy, spend: {
1476
+ spentUsd: number;
1477
+ }): CapabilityTier;
1478
+ /**
1479
+ * Pure (complexity × risk × policy × spend × floor) → required tier.
1480
+ *
1481
+ * `baseTier` resolves override → matrix → D5 veto → low-confidence bump; the D8
1482
+ * budget clamp lowers one step under budget pressure (never below the veto
1483
+ * floor); the D10 escalation floor raises the result but never lowers it. The
1484
+ * LLM never influences this — mirrors outcome-eval/authority.ts (TS-derived
1485
+ * authority). Referentially transparent; never mutates `policy`.
1486
+ */
1487
+ declare function deriveRequiredTier(complexity: ComplexityVerdict, risk: RoutingRisk | undefined, policy: RoutingPolicy, spend: {
1488
+ spentUsd: number;
1489
+ }, escalationFloor: CapabilityTier, skillKey?: string): CapabilityTier;
1490
+
1491
+ /** Flatten signals into the ComplexityVerdict.signals map, dropping undefined fields. */
1492
+ declare function serializeSignals(s: ComplexitySignals): Record<string, number | boolean | string>;
1493
+
1494
+ export { ACCEPTANCE_EVAL_SYSTEM_PROMPT, type AcceptanceEvalInput, AcceptanceEvaluator, type AcceptanceEvaluatorOptions, type AcceptanceVerdict, type AffectedSystem, type AnalysisProvider, type AnalysisRequest, type AnalysisResponse, AnthropicAnalysisProvider, type Authority, type BlastRadius, type BlindSpot, type CanaryAdapter, type CanaryDegradeReason, type CanaryExec, type CanaryFinding, type CanaryProbe, type ClassifyInput, ClaudeCliAnalysisProvider, type ComplexityScore, type ComplexitySignals, type Confidence, DEFAULT_DEGRADE_AT_PCT, type EnrichedSpec, type ExecutionOutcome, ExecutionOutcomeConnector, type ExpertiseLevel, type Finding, type FrameworkRecommendation, type GitHubIssue, GraphValidator, IntelligencePipeline, type JiraIssue, type JudgedAgainst, type LinearIssue, type LlmAcceptanceVerdict, type LlmVerdict, type ManualInput, type Measurability, OUTCOME_EVAL_SYSTEM_PROMPT, OpenAICompatibleAnalysisProvider, type OutcomeEvalInput, OutcomeEvaluator, type OutcomeEvaluatorOptions, type OutcomeIngestResult, type OutcomeVerdict, type PersonaEffectivenessScore, type PersonaRecommendation, PeslSimulator, type Phase, type PreprocessResult, type ProfileStore, RANK_TIER, type RawWorkItem, type ResolvedSection, SENSITIVE_BLAST_THRESHOLD, STATIC_WEIGHTS, type SimulationResult, type SpecializationEntry, type SpecializationProfile, type SpecializationScore, type StaticVerdict, TIER_RANK, type TaskType, type TemporalConfig, type TiebreakResult, type Verdict, type WeightedRecommendation, acceptanceVerdictSchema, applyBudgetClamp, baseTier, blastRadiusVeto, buildUserPrompt as buildAcceptanceUserPrompt, buildSpecializationProfile, buildUserPrompt$1 as buildUserPrompt, classify, computeExpertiseLevel, computeHistoricalComplexity, computePersonaEffectiveness, computeSemanticComplexity, computeSpecialization, computeStructuralComplexity, createCanaryAdapter, decayWeight, deriveAcceptanceAuthority, deriveAuthority, deriveRequiredTier, detectBlindSpots, enrich, findingSchema, githubToRawWorkItem, jiraToRawWorkItem, linearToRawWorkItem, llmTiebreak, loadProfiles, manualToRawWorkItem, recommendPersona, refreshProfiles, resolveSection, runGraphOnlyChecks, runLlmSimulation, runStaticPass, saveProfiles, score as scoreCML, scoreToConcernSignals, serializeSignals, temporalSuccessRate, toRawWorkItem, verdictSchema, weightedRecommendPersona };
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  AcceptanceEvaluator: () => AcceptanceEvaluator,
35
35
  AnthropicAnalysisProvider: () => AnthropicAnalysisProvider,
36
36
  ClaudeCliAnalysisProvider: () => ClaudeCliAnalysisProvider,
37
+ DEFAULT_DEGRADE_AT_PCT: () => DEFAULT_DEGRADE_AT_PCT,
37
38
  ExecutionOutcomeConnector: () => ExecutionOutcomeConnector,
38
39
  GraphValidator: () => GraphValidator,
39
40
  IntelligencePipeline: () => IntelligencePipeline,
@@ -41,10 +42,18 @@ __export(index_exports, {
41
42
  OpenAICompatibleAnalysisProvider: () => OpenAICompatibleAnalysisProvider,
42
43
  OutcomeEvaluator: () => OutcomeEvaluator,
43
44
  PeslSimulator: () => PeslSimulator,
45
+ RANK_TIER: () => RANK_TIER,
46
+ SENSITIVE_BLAST_THRESHOLD: () => SENSITIVE_BLAST_THRESHOLD,
47
+ STATIC_WEIGHTS: () => STATIC_WEIGHTS,
48
+ TIER_RANK: () => TIER_RANK,
44
49
  acceptanceVerdictSchema: () => acceptanceVerdictSchema,
50
+ applyBudgetClamp: () => applyBudgetClamp,
51
+ baseTier: () => baseTier,
52
+ blastRadiusVeto: () => blastRadiusVeto,
45
53
  buildAcceptanceUserPrompt: () => buildUserPrompt3,
46
54
  buildSpecializationProfile: () => buildSpecializationProfile,
47
55
  buildUserPrompt: () => buildUserPrompt2,
56
+ classify: () => classify,
48
57
  computeExpertiseLevel: () => computeExpertiseLevel,
49
58
  computeHistoricalComplexity: () => computeHistoricalComplexity,
50
59
  computePersonaEffectiveness: () => computePersonaEffectiveness,
@@ -55,12 +64,14 @@ __export(index_exports, {
55
64
  decayWeight: () => decayWeight,
56
65
  deriveAcceptanceAuthority: () => deriveAcceptanceAuthority,
57
66
  deriveAuthority: () => deriveAuthority,
67
+ deriveRequiredTier: () => deriveRequiredTier,
58
68
  detectBlindSpots: () => detectBlindSpots,
59
69
  enrich: () => enrich,
60
70
  findingSchema: () => findingSchema,
61
71
  githubToRawWorkItem: () => githubToRawWorkItem,
62
72
  jiraToRawWorkItem: () => jiraToRawWorkItem,
63
73
  linearToRawWorkItem: () => linearToRawWorkItem,
74
+ llmTiebreak: () => llmTiebreak,
64
75
  loadProfiles: () => loadProfiles,
65
76
  manualToRawWorkItem: () => manualToRawWorkItem,
66
77
  recommendPersona: () => recommendPersona,
@@ -68,9 +79,11 @@ __export(index_exports, {
68
79
  resolveSection: () => resolveSection,
69
80
  runGraphOnlyChecks: () => runGraphOnlyChecks,
70
81
  runLlmSimulation: () => runLlmSimulation,
82
+ runStaticPass: () => runStaticPass,
71
83
  saveProfiles: () => saveProfiles,
72
84
  scoreCML: () => score,
73
85
  scoreToConcernSignals: () => scoreToConcernSignals,
86
+ serializeSignals: () => serializeSignals,
74
87
  temporalSuccessRate: () => temporalSuccessRate,
75
88
  toRawWorkItem: () => toRawWorkItem,
76
89
  verdictSchema: () => verdictSchema,
@@ -2120,12 +2133,224 @@ function refreshProfiles(projectRoot, graphStore, opts) {
2120
2133
  saveProfiles(projectRoot, store);
2121
2134
  return store;
2122
2135
  }
2136
+
2137
+ // src/complexity/signals.ts
2138
+ function serializeSignals(s) {
2139
+ const out = {
2140
+ descriptionLength: s.descriptionLength,
2141
+ specExists: s.specExists,
2142
+ acceptanceMeasurable: s.acceptanceMeasurable
2143
+ };
2144
+ if (s.filesTouched !== void 0) out.filesTouched = s.filesTouched;
2145
+ if (s.layersTouched !== void 0) out.layersTouched = s.layersTouched;
2146
+ if (s.blastRadius !== void 0) out.blastRadius = s.blastRadius;
2147
+ if (s.hotspotChurn !== void 0) out.hotspotChurn = s.hotspotChurn;
2148
+ return out;
2149
+ }
2150
+
2151
+ // src/complexity/static-pass.ts
2152
+ var STATIC_WEIGHTS = {
2153
+ filesTouched: 0.25,
2154
+ layersTouched: 0.2,
2155
+ blastRadius: 0.3,
2156
+ hotspotChurn: 0.1,
2157
+ /** Text-only signals — the only inputs available pre-diff. */
2158
+ descriptionLength: 0.1,
2159
+ /** A present spec / measurable acceptance *lowers* complexity (well-scoped). */
2160
+ specExists: 0.025,
2161
+ acceptanceMeasurable: 0.025
2162
+ };
2163
+ var REF_MAX = {
2164
+ filesTouched: 20,
2165
+ layersTouched: 4,
2166
+ blastRadius: 40,
2167
+ hotspotChurn: 1,
2168
+ descriptionLength: 500
2169
+ };
2170
+ var CONFIDENCE_RANK = {
2171
+ low: 0,
2172
+ medium: 1,
2173
+ high: 2
2174
+ };
2175
+ function clamp01(n) {
2176
+ return Math.max(0, Math.min(1, n));
2177
+ }
2178
+ function scoreToLevel(score2) {
2179
+ if (score2 < 0.2) return "trivial";
2180
+ if (score2 < 0.45) return "simple";
2181
+ if (score2 < 0.7) return "moderate";
2182
+ return "complex";
2183
+ }
2184
+ function scoreToConfidence(score2) {
2185
+ const boundaries = [0.2, 0.45, 0.7];
2186
+ const nearest = Math.min(...boundaries.map((b) => Math.abs(score2 - b)));
2187
+ const atExtremeEnd = score2 < 0.2 || score2 >= 0.7;
2188
+ if (nearest < 0.1) return "low";
2189
+ if (nearest < 0.18 && !atExtremeEnd) return "medium";
2190
+ return "high";
2191
+ }
2192
+ function runStaticPass(signals, phase) {
2193
+ const isPostDiff = phase === "post-diff";
2194
+ const contributions = [];
2195
+ const weightsUsed = [];
2196
+ if (isPostDiff) {
2197
+ if (signals.filesTouched !== void 0) {
2198
+ contributions.push(
2199
+ clamp01(signals.filesTouched / REF_MAX.filesTouched) * STATIC_WEIGHTS.filesTouched
2200
+ );
2201
+ weightsUsed.push(STATIC_WEIGHTS.filesTouched);
2202
+ }
2203
+ if (signals.layersTouched !== void 0) {
2204
+ contributions.push(
2205
+ clamp01(signals.layersTouched / REF_MAX.layersTouched) * STATIC_WEIGHTS.layersTouched
2206
+ );
2207
+ weightsUsed.push(STATIC_WEIGHTS.layersTouched);
2208
+ }
2209
+ if (signals.blastRadius !== void 0) {
2210
+ contributions.push(
2211
+ clamp01(signals.blastRadius / REF_MAX.blastRadius) * STATIC_WEIGHTS.blastRadius
2212
+ );
2213
+ weightsUsed.push(STATIC_WEIGHTS.blastRadius);
2214
+ }
2215
+ if (signals.hotspotChurn !== void 0) {
2216
+ contributions.push(
2217
+ clamp01(signals.hotspotChurn / REF_MAX.hotspotChurn) * STATIC_WEIGHTS.hotspotChurn
2218
+ );
2219
+ weightsUsed.push(STATIC_WEIGHTS.hotspotChurn);
2220
+ }
2221
+ }
2222
+ contributions.push(
2223
+ clamp01(signals.descriptionLength / REF_MAX.descriptionLength) * STATIC_WEIGHTS.descriptionLength
2224
+ );
2225
+ weightsUsed.push(STATIC_WEIGHTS.descriptionLength);
2226
+ const scopeReduction = (signals.specExists ? STATIC_WEIGHTS.specExists : 0) + (signals.acceptanceMeasurable ? STATIC_WEIGHTS.acceptanceMeasurable : 0);
2227
+ const totalWeight = weightsUsed.reduce((a, b) => a + b, 0);
2228
+ const rawScore = contributions.reduce((a, b) => a + b, 0);
2229
+ const normalized = totalWeight > 0 ? rawScore / totalWeight : 0;
2230
+ const score2 = clamp01(normalized - scopeReduction);
2231
+ const level = scoreToLevel(score2);
2232
+ let confidence = scoreToConfidence(score2);
2233
+ if (!isPostDiff && CONFIDENCE_RANK[confidence] > CONFIDENCE_RANK.medium) {
2234
+ confidence = "medium";
2235
+ }
2236
+ return {
2237
+ level,
2238
+ confidence,
2239
+ signals: serializeSignals(signals)
2240
+ };
2241
+ }
2242
+
2243
+ // src/complexity/tiebreak.ts
2244
+ var import_zod6 = require("zod");
2245
+ var TiebreakSchema = import_zod6.z.object({
2246
+ level: import_zod6.z.enum(["trivial", "simple", "moderate", "complex"]),
2247
+ confidence: import_zod6.z.enum(["high", "medium", "low"])
2248
+ });
2249
+ async function llmTiebreak(provider, prompt, fastModel) {
2250
+ try {
2251
+ const { result } = await provider.analyze({
2252
+ prompt,
2253
+ responseSchema: TiebreakSchema,
2254
+ // Only include `model` when supplied (exactOptionalPropertyTypes).
2255
+ ...fastModel !== void 0 ? { model: fastModel } : {},
2256
+ maxTokens: 256
2257
+ });
2258
+ return result;
2259
+ } catch {
2260
+ return { level: "moderate", confidence: "low" };
2261
+ }
2262
+ }
2263
+
2264
+ // src/complexity/classifier.ts
2265
+ var CONFIDENCE_RANK2 = { low: 0, medium: 1, high: 2 };
2266
+ function capForPhase(confidence, phase) {
2267
+ if (phase === "pre-diff" && CONFIDENCE_RANK2[confidence] > CONFIDENCE_RANK2.medium) {
2268
+ return "medium";
2269
+ }
2270
+ return confidence;
2271
+ }
2272
+ async function classify(input, provider, models) {
2273
+ const staticVerdict = runStaticPass(input.signals, input.phase);
2274
+ if (staticVerdict.confidence !== "low" || provider === void 0) {
2275
+ return {
2276
+ level: staticVerdict.level,
2277
+ confidence: staticVerdict.confidence,
2278
+ signals: staticVerdict.signals,
2279
+ source: "static"
2280
+ };
2281
+ }
2282
+ const tiebreak = await llmTiebreak(provider, input.prompt, models?.fast);
2283
+ if (tiebreak.confidence === "low" && input.riskHigh) {
2284
+ const escalated = await llmTiebreak(provider, input.prompt, models?.standard);
2285
+ return {
2286
+ level: escalated.level,
2287
+ confidence: capForPhase(escalated.confidence, input.phase),
2288
+ signals: staticVerdict.signals,
2289
+ source: "escalated"
2290
+ };
2291
+ }
2292
+ return {
2293
+ level: tiebreak.level,
2294
+ confidence: capForPhase(tiebreak.confidence, input.phase),
2295
+ signals: staticVerdict.signals,
2296
+ source: "llm-tiebreak"
2297
+ };
2298
+ }
2299
+
2300
+ // src/complexity/derive-tier.ts
2301
+ var TIER_RANK = { fast: 0, standard: 1, strong: 2 };
2302
+ var RANK_TIER = ["fast", "standard", "strong"];
2303
+ var SENSITIVE_BLAST_THRESHOLD = 25;
2304
+ var DEFAULT_MATRIX = {
2305
+ trivial: "fast",
2306
+ simple: "fast",
2307
+ moderate: "standard",
2308
+ complex: "strong"
2309
+ };
2310
+ function tierAtRank(rank) {
2311
+ const clamped = Math.max(0, Math.min(RANK_TIER.length - 1, rank));
2312
+ return RANK_TIER[clamped];
2313
+ }
2314
+ function blastRadiusVeto(risk) {
2315
+ if (!risk) return false;
2316
+ return risk.sensitivePath === true || risk.publicApi === true || risk.layer === "core" || risk.layer === "types" || typeof risk.blastRadius === "number" && risk.blastRadius >= SENSITIVE_BLAST_THRESHOLD;
2317
+ }
2318
+ function baseTier(complexity, risk, policy, skillKey) {
2319
+ const veto = blastRadiusVeto(risk);
2320
+ const override = skillKey !== void 0 ? policy.skillTierOverrides?.[skillKey] : void 0;
2321
+ const matrix = policy.complexityTierMatrix ?? {};
2322
+ const fromMatrix = matrix[complexity.level] ?? DEFAULT_MATRIX[complexity.level];
2323
+ let rank = Math.max(override !== void 0 ? TIER_RANK[override] : 0, TIER_RANK[fromMatrix]);
2324
+ if (complexity.confidence === "low") {
2325
+ rank = rank + 1;
2326
+ }
2327
+ if (veto) rank = Math.max(rank, TIER_RANK.strong);
2328
+ return tierAtRank(rank);
2329
+ }
2330
+ var DEFAULT_DEGRADE_AT_PCT = 90;
2331
+ function applyBudgetClamp(tier, risk, policy, spend) {
2332
+ const budget = policy.budget;
2333
+ if (!budget || budget.capUsd <= 0) return tier;
2334
+ const degradeAt = (budget.degradeAtPct ?? DEFAULT_DEGRADE_AT_PCT) / 100;
2335
+ const spentFraction = spend.spentUsd / budget.capUsd;
2336
+ if (spentFraction < degradeAt) return tier;
2337
+ if (blastRadiusVeto(risk)) return tier;
2338
+ const mode = budget.onBudgetExhausted ?? "degrade";
2339
+ if (spentFraction >= 1 && mode !== "human") return "fast";
2340
+ return tierAtRank(TIER_RANK[tier] - 1);
2341
+ }
2342
+ function deriveRequiredTier(complexity, risk, policy, spend, escalationFloor, skillKey) {
2343
+ const base = baseTier(complexity, risk, policy, skillKey);
2344
+ const clamped = applyBudgetClamp(base, risk, policy, spend);
2345
+ return tierAtRank(Math.max(TIER_RANK[escalationFloor], TIER_RANK[clamped]));
2346
+ }
2123
2347
  // Annotate the CommonJS export names for ESM import in node:
2124
2348
  0 && (module.exports = {
2125
2349
  ACCEPTANCE_EVAL_SYSTEM_PROMPT,
2126
2350
  AcceptanceEvaluator,
2127
2351
  AnthropicAnalysisProvider,
2128
2352
  ClaudeCliAnalysisProvider,
2353
+ DEFAULT_DEGRADE_AT_PCT,
2129
2354
  ExecutionOutcomeConnector,
2130
2355
  GraphValidator,
2131
2356
  IntelligencePipeline,
@@ -2133,10 +2358,18 @@ function refreshProfiles(projectRoot, graphStore, opts) {
2133
2358
  OpenAICompatibleAnalysisProvider,
2134
2359
  OutcomeEvaluator,
2135
2360
  PeslSimulator,
2361
+ RANK_TIER,
2362
+ SENSITIVE_BLAST_THRESHOLD,
2363
+ STATIC_WEIGHTS,
2364
+ TIER_RANK,
2136
2365
  acceptanceVerdictSchema,
2366
+ applyBudgetClamp,
2367
+ baseTier,
2368
+ blastRadiusVeto,
2137
2369
  buildAcceptanceUserPrompt,
2138
2370
  buildSpecializationProfile,
2139
2371
  buildUserPrompt,
2372
+ classify,
2140
2373
  computeExpertiseLevel,
2141
2374
  computeHistoricalComplexity,
2142
2375
  computePersonaEffectiveness,
@@ -2147,12 +2380,14 @@ function refreshProfiles(projectRoot, graphStore, opts) {
2147
2380
  decayWeight,
2148
2381
  deriveAcceptanceAuthority,
2149
2382
  deriveAuthority,
2383
+ deriveRequiredTier,
2150
2384
  detectBlindSpots,
2151
2385
  enrich,
2152
2386
  findingSchema,
2153
2387
  githubToRawWorkItem,
2154
2388
  jiraToRawWorkItem,
2155
2389
  linearToRawWorkItem,
2390
+ llmTiebreak,
2156
2391
  loadProfiles,
2157
2392
  manualToRawWorkItem,
2158
2393
  recommendPersona,
@@ -2160,9 +2395,11 @@ function refreshProfiles(projectRoot, graphStore, opts) {
2160
2395
  resolveSection,
2161
2396
  runGraphOnlyChecks,
2162
2397
  runLlmSimulation,
2398
+ runStaticPass,
2163
2399
  saveProfiles,
2164
2400
  scoreCML,
2165
2401
  scoreToConcernSignals,
2402
+ serializeSignals,
2166
2403
  temporalSuccessRate,
2167
2404
  toRawWorkItem,
2168
2405
  verdictSchema,
package/dist/index.mjs CHANGED
@@ -2040,11 +2040,223 @@ 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
+ }
2043
2254
  export {
2044
2255
  ACCEPTANCE_EVAL_SYSTEM_PROMPT,
2045
2256
  AcceptanceEvaluator,
2046
2257
  AnthropicAnalysisProvider,
2047
2258
  ClaudeCliAnalysisProvider,
2259
+ DEFAULT_DEGRADE_AT_PCT,
2048
2260
  ExecutionOutcomeConnector,
2049
2261
  GraphValidator,
2050
2262
  IntelligencePipeline,
@@ -2052,10 +2264,18 @@ export {
2052
2264
  OpenAICompatibleAnalysisProvider,
2053
2265
  OutcomeEvaluator,
2054
2266
  PeslSimulator,
2267
+ RANK_TIER,
2268
+ SENSITIVE_BLAST_THRESHOLD,
2269
+ STATIC_WEIGHTS,
2270
+ TIER_RANK,
2055
2271
  acceptanceVerdictSchema,
2272
+ applyBudgetClamp,
2273
+ baseTier,
2274
+ blastRadiusVeto,
2056
2275
  buildUserPrompt3 as buildAcceptanceUserPrompt,
2057
2276
  buildSpecializationProfile,
2058
2277
  buildUserPrompt2 as buildUserPrompt,
2278
+ classify,
2059
2279
  computeExpertiseLevel,
2060
2280
  computeHistoricalComplexity,
2061
2281
  computePersonaEffectiveness,
@@ -2066,12 +2286,14 @@ export {
2066
2286
  decayWeight,
2067
2287
  deriveAcceptanceAuthority,
2068
2288
  deriveAuthority,
2289
+ deriveRequiredTier,
2069
2290
  detectBlindSpots,
2070
2291
  enrich,
2071
2292
  findingSchema,
2072
2293
  githubToRawWorkItem,
2073
2294
  jiraToRawWorkItem,
2074
2295
  linearToRawWorkItem,
2296
+ llmTiebreak,
2075
2297
  loadProfiles,
2076
2298
  manualToRawWorkItem,
2077
2299
  recommendPersona,
@@ -2079,9 +2301,11 @@ export {
2079
2301
  resolveSection,
2080
2302
  runGraphOnlyChecks,
2081
2303
  runLlmSimulation,
2304
+ runStaticPass,
2082
2305
  saveProfiles,
2083
2306
  score as scoreCML,
2084
2307
  scoreToConcernSignals,
2308
+ serializeSignals,
2085
2309
  temporalSuccessRate,
2086
2310
  toRawWorkItem,
2087
2311
  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.6.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.7",
44
+ "@harness-engineering/types": "0.21.0"
45
45
  },
46
46
  "optionalDependencies": {
47
47
  "canary-test-cli": "^5.4.0"