@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 +151 -2
- package/dist/index.d.ts +151 -2
- package/dist/index.js +237 -0
- package/dist/index.mjs +224 -0
- package/package.json +3 -3
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
|
-
|
|
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
|
-
|
|
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.
|
|
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/
|
|
44
|
-
"@harness-engineering/
|
|
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"
|