import * as _harness_engineering_types from '@harness-engineering/types'; import { Issue, ConcernSignal, ScopeTier, EscalationConfig, SkillInvocationRecord, ComplexityLevel, ComplexityVerdict, CapabilityTier, RoutingRisk, RoutingPolicy, RoutingTaskText } from '@harness-engineering/types'; import { z } from 'zod'; import { GraphStore } from '@harness-engineering/graph'; /** * Raw work item — generic input from any adapter (roadmap, JIRA, GitHub, etc.) */ interface RawWorkItem { id: string; title: string; description: string | null; labels: string[]; metadata: Record; linkedItems: string[]; comments: string[]; source: 'roadmap' | 'jira' | 'github' | 'linear' | 'manual'; } /** * A system identified by SEL as affected, validated against the knowledge graph. */ interface AffectedSystem { /** Human name from LLM output */ name: string; /** Graph node ID if found, null if not in graph */ graphNodeId: string | null; /** Confidence of graph match (0 if not found) */ confidence: number; /** Transitive dependency IDs from CascadeSimulator */ transitiveDeps: string[]; /** Number of test files covering this system */ testCoverage: number; /** Owning team or individual, if known */ owner: string | null; } /** * Enriched spec — output of the Spec Enrichment Layer (SEL). */ interface EnrichedSpec { id: string; title: string; intent: string; summary: string; affectedSystems: AffectedSystem[]; functionalRequirements: string[]; nonFunctionalRequirements: string[]; apiChanges: string[]; dbChanges: string[]; integrationPoints: string[]; assumptions: string[]; unknowns: string[]; ambiguities: string[]; riskSignals: string[]; initialComplexityHints: { textualComplexity: number; structuralComplexity: number; }; } /** * Blast radius estimate from CML. */ interface BlastRadius { services: number; modules: number; filesEstimated: number; testFilesAffected: number; } /** * Complexity score — output of the Complexity Modeling Layer (CML). */ interface ComplexityScore { overall: number; confidence: number; riskLevel: 'low' | 'medium' | 'high' | 'critical'; blastRadius: BlastRadius; dimensions: { structural: number; semantic: number; historical: number; }; reasoning: string[]; recommendedRoute: 'local' | 'human' | 'simulation-required'; } /** * Simulation result — output of the Pre-Execution Simulation Layer (PESL). */ interface SimulationResult { simulatedPlan: string[]; predictedFailures: string[]; riskHotspots: string[]; missingSteps: string[]; testGaps: string[]; executionConfidence: number; recommendedChanges: string[]; abort: boolean; tier: 'graph-only' | 'full-simulation'; } /** * Convert an orchestrator Issue into a generic RawWorkItem for the intelligence pipeline. */ declare function toRawWorkItem(issue: Issue): RawWorkItem; /** * JIRA issue link reference. */ interface JiraIssueLink { type: { name: string; }; inwardIssue?: { id: string; key: string; }; outwardIssue?: { id: string; key: string; }; } /** * JIRA comment entry. */ interface JiraComment { body: string; author: { displayName: string; }; } /** * Minimal JIRA issue shape accepted by the adapter. * Represents pre-fetched data from the JIRA REST API. */ interface JiraIssue { id: string; key: string; fields: { summary: string; description: string | null; labels: string[]; priority: { id: string; name: string; } | null; status: { id: string; name: string; }; issuetype: { id: string; name: string; }; created: string; updated: string; issuelinks: JiraIssueLink[]; comment: { comments: JiraComment[]; }; }; } /** * Convert a pre-fetched JIRA issue into a generic RawWorkItem. */ declare function jiraToRawWorkItem(issue: JiraIssue): RawWorkItem; /** * GitHub label shape. */ interface GitHubLabel { id: number; name: string; } /** * GitHub comment shape. */ interface GitHubComment { body: string; user: { login: string; }; } /** * Minimal GitHub issue/PR shape accepted by the adapter. * Represents pre-fetched data from the GitHub REST or GraphQL API. */ interface GitHubIssue { id: number; number: number; title: string; body: string | null; labels: GitHubLabel[]; state: string; html_url: string; created_at: string; updated_at: string; pull_request: { url: string; } | null; milestone: { id: number; title: string; } | null; assignees: { login: string; }[]; comments_data: GitHubComment[]; linked_issues: number[]; } /** * Convert a pre-fetched GitHub issue or PR into a generic RawWorkItem. */ declare function githubToRawWorkItem(issue: GitHubIssue): RawWorkItem; /** * Linear label shape. */ interface LinearLabel { id: string; name: string; } /** * Linear comment shape. */ interface LinearComment { body: string; user: { name: string; }; } /** * Linear relation shape (blocking/blocked-by/related). */ interface LinearRelation { type: string; relatedIssue: { id: string; identifier: string; }; } /** * Minimal Linear issue shape accepted by the adapter. * Represents pre-fetched data from the Linear GraphQL API. */ interface LinearIssue { id: string; identifier: string; title: string; description: string | null; priority: number; state: { id: string; name: string; }; labels: { nodes: LinearLabel[]; }; createdAt: string; updatedAt: string; url: string; branchName: string | null; comments: { nodes: LinearComment[]; }; relations: { nodes: LinearRelation[]; }; } /** * Convert a pre-fetched Linear issue into a generic RawWorkItem. */ declare function linearToRawWorkItem(issue: LinearIssue): RawWorkItem; /** * Manual input shape — accepts free-text with optional metadata. */ interface ManualInput { title: string; description?: string; labels?: string[]; } /** * Convert a manual text input into a generic RawWorkItem. * Generates a unique ID with a `manual-` prefix. */ declare function manualToRawWorkItem(input: ManualInput): RawWorkItem; /** * Canary adapter — a total, gracefully-degrading boundary around the deterministic * `canary` test CLI (`canary-test-cli`, declared as an optionalDependency). * * All `canary` / `canary-test-cli` references are confined to this module * (enforced by a boundary test). The adapter never throws on a missing or * misbehaving CLI: every method resolves a degraded/empty result instead. */ /** Why probe() degraded. */ type CanaryDegradeReason = 'not-installed' | 'binary-missing' | 'exec-failed' | 'bad-output'; interface CanaryProbe { status: 'available' | 'degraded'; version?: string; reason?: CanaryDegradeReason; } declare const frameworkRecommendationSchema: z.ZodObject<{ status: z.ZodString; test_type: z.ZodString; framework: z.ZodString; file_extension: z.ZodString; reasoning: z.ZodArray; alternatives: z.ZodArray; }, "strip", z.ZodTypeAny, { status: string; test_type: string; framework: string; file_extension: string; reasoning: string[]; alternatives: string[]; }, { status: string; test_type: string; framework: string; file_extension: string; reasoning: string[]; alternatives: string[]; }>; type FrameworkRecommendation = z.infer; declare const canaryFindingSchema: z.ZodObject<{ file: z.ZodString; line: z.ZodNumber; rule: z.ZodString; severity: z.ZodString; message: z.ZodString; suggestion: z.ZodString; }, "strip", z.ZodTypeAny, { message: string; file: string; line: number; rule: string; severity: string; suggestion: string; }, { message: string; file: string; line: number; rule: string; severity: string; suggestion: string; }>; type CanaryFinding = z.infer; declare const canaryTestResultSchema: z.ZodObject<{ test_name: z.ZodOptional; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, "passthrough", z.ZodTypeAny, z.objectOutputType<{ test_name: z.ZodOptional; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, z.ZodTypeAny, "passthrough">, z.objectInputType<{ test_name: z.ZodOptional; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, z.ZodTypeAny, "passthrough">>; type CanaryTestResult = z.infer; declare const canaryRunRecordSchema: z.ZodObject<{ run_id: z.ZodOptional; suite: z.ZodOptional; repo: z.ZodOptional; branch: z.ZodOptional; commit_sha: z.ZodOptional; timestamp: z.ZodOptional; exit_code: z.ZodOptional; total: z.ZodOptional; passed: z.ZodOptional; failed: z.ZodOptional; flaky: z.ZodOptional; skipped: z.ZodOptional; tests: z.ZodDefault; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, "passthrough", z.ZodTypeAny, z.objectOutputType<{ test_name: z.ZodOptional; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, z.ZodTypeAny, "passthrough">, z.objectInputType<{ test_name: z.ZodOptional; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, z.ZodTypeAny, "passthrough">>, "many">>; }, "passthrough", z.ZodTypeAny, z.objectOutputType<{ run_id: z.ZodOptional; suite: z.ZodOptional; repo: z.ZodOptional; branch: z.ZodOptional; commit_sha: z.ZodOptional; timestamp: z.ZodOptional; exit_code: z.ZodOptional; total: z.ZodOptional; passed: z.ZodOptional; failed: z.ZodOptional; flaky: z.ZodOptional; skipped: z.ZodOptional; tests: z.ZodDefault; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, "passthrough", z.ZodTypeAny, z.objectOutputType<{ test_name: z.ZodOptional; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, z.ZodTypeAny, "passthrough">, z.objectInputType<{ test_name: z.ZodOptional; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, z.ZodTypeAny, "passthrough">>, "many">>; }, z.ZodTypeAny, "passthrough">, z.objectInputType<{ run_id: z.ZodOptional; suite: z.ZodOptional; repo: z.ZodOptional; branch: z.ZodOptional; commit_sha: z.ZodOptional; timestamp: z.ZodOptional; exit_code: z.ZodOptional; total: z.ZodOptional; passed: z.ZodOptional; failed: z.ZodOptional; flaky: z.ZodOptional; skipped: z.ZodOptional; tests: z.ZodDefault; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, "passthrough", z.ZodTypeAny, z.objectOutputType<{ test_name: z.ZodOptional; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, z.ZodTypeAny, "passthrough">, z.objectInputType<{ test_name: z.ZodOptional; name: z.ZodOptional; status: z.ZodOptional; suite: z.ZodOptional; test_file: z.ZodOptional; area: z.ZodOptional; failure_category: z.ZodOptional; error_text: z.ZodOptional; retry_count: z.ZodOptional; duration_ms: z.ZodOptional; flaky: z.ZodOptional; tags: z.ZodOptional>; }, z.ZodTypeAny, "passthrough">>, "many">>; }, z.ZodTypeAny, "passthrough">>; type CanaryRunRecord = z.infer; declare const canaryFrameworkInfoSchema: z.ZodObject<{ name: z.ZodString; languages: z.ZodDefault>; file_extensions: z.ZodDefault>; execution_command: z.ZodDefault>; ci_flags: z.ZodDefault>; status: z.ZodDefault; tier: z.ZodDefault; }, "strip", z.ZodTypeAny, { status: string; name: string; languages: string[]; file_extensions: string[]; execution_command: string | null; ci_flags: string[]; tier: string; }, { name: string; status?: string | undefined; languages?: string[] | undefined; file_extensions?: string[] | undefined; execution_command?: string | null | undefined; ci_flags?: string[] | undefined; tier?: string | undefined; }>; type CanaryFrameworkInfo = z.infer; /** * Pure resolution of a per-file test command from a registry entry. No exec. * - null execution_command → null (catalog-tier frameworks have no runner) * - command without {file} → null (whole-suite / {target}-only scanners are not * resolvable to a per-file test command) * Otherwise substitutes {file} and, under opts.ci, appends the joined ci_flags. */ declare function resolveTestCommand(fw: CanaryFrameworkInfo, file: string, opts?: { ci?: boolean; }): string | null; interface CanaryAdapter { probe(): Promise; recommendFramework(prompt: string): Promise; reviewTest(path: string, framework?: string): Promise; listFrameworks(): Promise; readRunHistory(opts?: { cwd?: string; limit?: number; }): Promise; } /** * The raw exec seam: runs a `canary` subcommand and resolves its stdout, or * rejects with the spawn/exit error (carrying `code` and `stderr`). This is the * single injection point — the default talks to the real CLI; tests pass a fake. * Injecting here (rather than at a higher level) keeps the degrade-classification * in `execCanary` fully under test. */ type CanaryExec = (cmd: string, args: string[]) => Promise<{ stdout: string; }>; /** * The raw file-read seam: resolves the utf8 contents of a path, or rejects * (ENOENT / EACCES). Parallels {@link CanaryExec} — the single injection point for * the documented-artifact acquisition path. The default reads the real file; tests * inject a fake. Keeping the seam here (rather than at a higher level) keeps the * degrade-classification in `readRunHistoryCanary` fully under test. */ type CanaryReader = (filePath: string) => Promise; declare function createCanaryAdapter(exec?: CanaryExec, reader?: CanaryReader): CanaryAdapter; /** * A single image attached to an {@link AnalysisRequest} for a vision-capable * `analyze` call. Supply exactly one of `base64` or `url`. * * Providers that cannot see images (claude-cli, openai-compatible today) * ignore this field and answer from the text prompt alone — vision is * best-effort, never a contract change. Only the Anthropic backend renders * these as image content blocks. */ interface AnalysisImage { /** Base64-encoded image bytes (no `data:` prefix). Mutually exclusive with `url`. */ base64?: string; /** Publicly-fetchable image URL. Mutually exclusive with `base64`. */ url?: string; /** MIME type of the image; defaults to `image/png` when omitted. */ mediaType?: 'image/png' | 'image/jpeg' | 'image/webp'; } interface AnalysisRequest { prompt: string; systemPrompt?: string; responseSchema: z.ZodType; model?: string; maxTokens?: number; /** * Images to attach to the call, in order, ahead of the text prompt. Enables * vision judgment (e.g. scoring a rendered screenshot). Backends that lack a * vision channel ignore this and answer from `prompt` alone. */ images?: AnalysisImage[]; /** * Request that the backend suppress any chain-of-thought / `` reasoning for THIS call. * Intended for narrow structured-extraction calls where a reasoning trace adds latency but not * quality. Advisory + best-effort: a provider that can honor it (e.g. Ollama's native * `think:false`) does; one that cannot ignores it and answers normally. Never changes the * response contract — only whether the model reasons out loud first. */ disableThinking?: boolean; } interface AnalysisResponse { result: T; tokenUsage: { inputTokens: number; outputTokens: number; totalTokens: number; }; model: string; latencyMs: number; } interface AnalysisProvider { analyze(request: AnalysisRequest): Promise>; } interface AnthropicProviderOptions { apiKey: string; defaultModel?: string; } /** * AnalysisProvider implementation backed by the Anthropic Messages API. * * Uses the tool_use pattern to extract structured JSON that conforms to * a caller-supplied Zod schema. */ declare class AnthropicAnalysisProvider implements AnalysisProvider { private readonly client; private readonly defaultModel; constructor(options: AnthropicProviderOptions); analyze(request: AnalysisRequest): Promise>; } interface OpenAICompatibleProviderOptions { /** API key (some local servers accept any string, e.g., 'ollama'). */ apiKey: string; /** Base URL for the OpenAI-compatible endpoint (e.g., http://localhost:11434/v1). */ baseUrl: string; /** Default model name (e.g., 'deepseek-coder-v2'). */ defaultModel?: string; /** * Consumption Phase 1 (T3): live model resolver, read at request time. When * provided and it returns a non-empty name, that name is used in preference to * `defaultModel` so a pool install/swap is consumed by analysis without a * restart. `request.model` (an explicit per-call override) still wins; a * null/undefined/empty return falls through to `defaultModel`. */ getModel?: () => string | null | undefined; /** Request timeout in ms (default: 90000). */ timeoutMs?: number; /** * String appended to user prompts for structured-output requests. * Useful for disabling thinking/reasoning modes (e.g., '/no_think' for Qwen3). */ promptSuffix?: string; /** * Whether to send `response_format: { type: 'json_schema' }` with the full * schema to the server for grammar-constrained decoding. When false, relies * on the system prompt alone to produce valid JSON. Default: true. */ jsonMode?: boolean; } /** * AnalysisProvider for OpenAI-compatible endpoints (Ollama, LM Studio, vLLM, etc.). * * Uses JSON mode with a system prompt instructing structured output. * Falls back to parsing raw text as JSON if the model doesn't support * response_format natively. */ declare class OpenAICompatibleAnalysisProvider implements AnalysisProvider { private readonly client; private readonly baseUrl; private readonly timeoutMs; private readonly defaultModel; private readonly getModel?; private readonly promptSuffix; private readonly jsonMode; constructor(options: OpenAICompatibleProviderOptions); analyze(request: AnalysisRequest): Promise>; /** Build the system + user message content shared by both the OpenAI-compat and native paths. */ private buildMessages; /** Ollama's native chat route, derived from the configured `/v1` base URL. */ private ollamaNativeChatUrl; /** * Take Ollama's NATIVE `/api/chat` with `think:false` — the only way to actually suppress a * Qwen3-style reasoning trace (the OpenAI-compatible `/v1` shim ignores every thinking knob). * Schema is enforced via native `format`; `num_predict` bounds output. Throws on any non-2xx, * missing content, or schema-parse failure so `analyze` can fall back to the OpenAI-compatible * path (e.g. when the endpoint is not actually Ollama). */ private analyzeViaOllamaNative; } interface ClaudeCliProviderOptions { /** Path to the claude binary (default: 'claude') */ command?: string | undefined; /** Model to use (default: let the CLI decide) */ defaultModel?: string | undefined; /** Request timeout in ms (default: 180000) */ timeoutMs?: number | undefined; } /** * AnalysisProvider that uses the Claude CLI for structured analysis. * * This avoids the need for an API key — the CLI manages its own * authentication. Structured output is enforced via --json-schema. */ declare class ClaudeCliAnalysisProvider implements AnalysisProvider { private readonly command; private readonly defaultModel; private readonly timeoutMs; constructor(options?: ClaudeCliProviderOptions); analyze(request: AnalysisRequest): Promise>; /** Args for the text-only path (`-p` prompt, single-shot JSON output). */ private buildTextArgs; /** * Vision path. The CLI's `-p` text prompt cannot carry an image, so image * calls go through the `stream-json` transport: a single user message whose * content is the image block(s) followed by the text prompt, written to * stdin. `--json-schema` still enforces the structured envelope, which the * CLI returns as `structured_output` on the terminal `result` event. * * Verified against Claude Code CLI 2.1.x: image content blocks are read and * `structured_output` conforms to the supplied schema. */ private runClaudeVision; private runClaude; /** * Run the CLI in `stream-json` transport, writing `stdinPayload` to stdin * and reading newline-delimited JSON events from stdout. Resolves from the * terminal `result` event, preferring `structured_output` (schema-conforming * object) over the `result` string (JSON-encoded fallback for older CLIs). */ private runClaudeStream; } /** * Validates affected systems against the knowledge graph. * * For each system name from the LLM output, searches the graph for matching * module or file nodes and enriches with transitive dependencies, test coverage, * and ownership information. */ declare class GraphValidator { private readonly store; private cachedModuleNodes; private cachedFileNodes; constructor(store: GraphStore); /** * Validate and enrich a list of affected system names against the graph. */ validate(systems: Array<{ name: string; }>): AffectedSystem[]; private resolveSystem; /** * Simple fuzzy scoring: exact match = 1, contains = 0.7, substring overlap. */ private fuzzyScore; private resolveTransitiveDeps; private resolveTestCoverage; private resolveOwner; } /** * Enrich a RawWorkItem into a full EnrichedSpec via LLM analysis and graph validation. * * 1. Calls the AnalysisProvider with SEL prompts and response schema * 2. Parses the LLM response into a partial EnrichedSpec * 3. Validates affected systems against the knowledge graph * 4. Returns a fully populated EnrichedSpec */ declare function enrich(item: RawWorkItem, provider: AnalysisProvider, graphValidator: GraphValidator): Promise; /** * Score an enriched spec using the Complexity Modeling Layer (CML). * * Combines structural (graph-based blast radius), semantic (SEL enrichment * fields), and historical (past execution outcomes) dimensions into a single * {@link ComplexityScore}. */ declare function score(spec: EnrichedSpec, store: GraphStore): ComplexityScore; interface StructuralResult { score: number; blastRadius: BlastRadius; } /** * Compute structural complexity by running CascadeSimulator for every * affected system that has a resolved graph node ID. * * The score is the probability-weighted sum of affected nodes across all * systems, normalized against a ceiling of {@link NORMALIZATION_CEILING}. */ declare function computeStructuralComplexity(spec: EnrichedSpec, store: GraphStore): StructuralResult; /** * Compute semantic complexity from the SEL enrichment fields. * * Each dimension uses a diminishing-returns curve `1 - exp(-count * 0.3)` * so that the first few items have the biggest impact and marginal * additions produce less incremental score. * * Returns a value in [0, 1]. */ declare function computeSemanticComplexity(spec: EnrichedSpec): number; /** * Convert a {@link ComplexityScore} into an array of {@link ConcernSignal}s * that downstream routing logic can use for escalation decisions. * * Returns an empty array when the score is below all thresholds. */ declare function scoreToConcernSignals(score: ComplexityScore): ConcernSignal[]; /** * Run graph-only pre-execution simulation checks. * * Uses CascadeSimulator blast radius and impact grouping to produce a * SimulationResult without any LLM calls. Intended for quick-fix and * diagnostic tier issues where speed matters. * * Deterministic and fast (<2s for typical graphs). */ declare function runGraphOnlyChecks(spec: EnrichedSpec, score: ComplexityScore, store: GraphStore): SimulationResult; /** * Run full LLM pre-execution simulation. * * Combines graph-only checks with LLM-driven plan expansion, failure * injection, and test projection. Intended for guided-change and * simulation-required tier issues. */ declare function runLlmSimulation(spec: EnrichedSpec, score: ComplexityScore, store: GraphStore, provider: AnalysisProvider, model?: string): Promise; interface PeslSimulatorOptions { /** Override model for PESL LLM calls. */ model?: string; } /** * Top-level PESL simulator that routes to graph-only or full LLM simulation * based on scope tier and CML recommended route. * * Tiered behavior (per D5 in spec): * - quick-fix / diagnostic: graph-only checks (CascadeSimulator + impact) * - guided-change: full LLM simulation (plan expansion, failure injection, test projection) * - simulation-required override: full LLM simulation regardless of tier */ declare class PeslSimulator { private readonly provider; private readonly store; private readonly options; constructor(provider: AnalysisProvider, store: GraphStore, options?: PeslSimulatorOptions); /** * Run pre-execution simulation for a spec. * * @param spec - Enriched spec from SEL * @param score - Complexity score from CML * @param tier - Scope tier of the issue * @returns SimulationResult with tier, confidence, and abort recommendation */ simulate(spec: EnrichedSpec, score: ComplexityScore, tier: ScopeTier): Promise; } /** Task type categorization for specialization tracking. */ type TaskType = 'feature' | 'bugfix' | 'refactor' | 'docs' | 'test' | 'chore'; /** * Execution outcome -- result of a worker running an issue. * Ingested into the graph as an 'execution_outcome' node. */ interface ExecutionOutcome { /** Unique ID for this outcome (e.g., `outcome::`) */ id: string; /** ID of the issue that was executed */ issueId: string; /** Human-readable identifier (e.g., 'PROJ-123') */ identifier: string; /** Execution result */ result: 'success' | 'failure'; /** Number of retry attempts before this outcome */ retryCount: number; /** Failure reasons (empty for success) */ failureReasons: string[]; /** Execution duration in milliseconds */ durationMs: number; /** ID of the linked EnrichedSpec, if one was produced */ linkedSpecId: string | null; /** Affected system graph node IDs from the enriched spec */ affectedSystemNodeIds: string[]; /** ISO timestamp of when the outcome was recorded */ timestamp: string; /** * Optional persona or agent identifier that produced this outcome * (e.g. 'task-executor'). When present the ingestor records it in * the graph node's metadata so effectiveness analytics can attribute * successes and failures to the responsible agent. */ agentPersona?: string; /** Task type categorization (e.g., 'feature', 'bugfix', 'refactor', 'docs'). */ taskType?: TaskType; /** * Optional caller-supplied metadata merged into the node's metadata. * Used by judgment sources (e.g. outcome-eval) to record verdict-specific * signal -- verdict, confidence, judgedAgainst, source -- without bloating * the core ExecutionOutcome contract. Reserved core keys (id/result/etc.) * always win and are not overridable. */ metadata?: Record; } interface OutcomeIngestResult { nodesAdded: number; edgesAdded: number; errors: string[]; } /** * Ingests execution outcomes into the knowledge graph. * * Creates an 'execution_outcome' node for each outcome with metadata * containing result, retry count, failure reasons, duration, and linked * spec ID. Creates 'outcome_of' edges to each affected system node * that exists in the graph. */ declare class ExecutionOutcomeConnector { private readonly store; constructor(store: GraphStore); ingest(outcome: ExecutionOutcome): OutcomeIngestResult; } /** * Guardian diff-coverage analysis contract (issue #914, checkboxes 1 & 2). * * The `.harness/analyses/` archive is written by multiple producers. Alongside * the intelligence-pipeline `AnalysisRecord` (spec/score/simulation), canary's * PR guardian drops diff-coverage findings — but nothing on the harness side has * ever READ them. This module defines the harness-OWNED, TOLERANT, ADVISORY * contract for those guardian records so both `outcome_eval` and * `pre-merge-brief` can consume them as a review input. * * Ownership + reconciliation: this shape is defined HERE (harness side) as the * documented contract canary conforms to. It is intentionally self-describing * (`schema` + `version` discriminator) and read TOLERANTLY — the reader selects * guardian records by the discriminator and SKIPS anything it cannot validate, * so an intelligence `AnalysisRecord`, a malformed file, or a future/foreign * shape never crashes a consumer and never changes behavior. If canary's * emitted shape drifts, reconcile the schema here (bump `version`) rather than * loosening the tolerant reader. */ /** * Stable discriminator. A `.harness/analyses/*.json` file carrying * `schema: GUARDIAN_ANALYSIS_SCHEMA` is a guardian diff-coverage record; every * other JSON in the directory (e.g. an intelligence `AnalysisRecord`) is ignored * by the guardian reader. */ declare const GUARDIAN_ANALYSIS_SCHEMA: "harness.guardian.diff-coverage"; /** Contract version. Bump on a breaking shape change; the reader validates it. */ declare const GUARDIAN_ANALYSIS_VERSION: 1; /** Overall pass/fail of the guardian diff-coverage gate for a change. */ type GuardianVerdict = 'pass' | 'fail'; /** Advisory severity of the guardian finding. Never derives ship authority. */ type GuardianSeverity = 'info' | 'warn' | 'error'; /** Per-file diff-coverage finding: which added/changed lines are uncovered. */ interface GuardianFileCoverage { /** Repo-relative path of the file whose diff lines are uncovered. */ file: string; /** Specific uncovered line numbers introduced/changed by the diff. */ uncoveredLines: number[]; /** * Optional contiguous uncovered ranges `[startLine, endLine]` (inclusive), * a compact alternative to enumerating every line. */ uncoveredRegions?: Array<[number, number]>; } /** * A single guardian diff-coverage analysis record, as persisted under * `.harness/analyses/`. Self-describing so a tolerant reader can validate it in * a directory shared with other analysis producers. */ interface GuardianAnalysis { schema: typeof GUARDIAN_ANALYSIS_SCHEMA; version: typeof GUARDIAN_ANALYSIS_VERSION; /** ISO timestamp when the guardian produced this record. */ generatedAt: string; /** Overall gate verdict for the diff-coverage check. */ verdict: GuardianVerdict; /** Advisory severity. */ severity: GuardianSeverity; /** * Coverage delta introduced by the diff, in percentage points. Negative is a * regression (coverage went down). */ coverageDelta: number; /** Per-file uncovered diff-coverage findings (empty when nothing uncovered). */ files: GuardianFileCoverage[]; /** Optional human-readable one-line summary from the guardian. */ summary?: string; } /** * outcome-eval contract types. * * `authority` is DERIVED in TypeScript from (verdict, confidence) via * `deriveAuthority` in `./authority.js`. It is NEVER read from the LLM * response — see `verdictSchema` in `./prompts.js`, which omits it. */ type Verdict = 'SATISFIED' | 'NOT_SATISFIED' | 'INCONCLUSIVE'; type Confidence$1 = 'low' | 'medium' | 'high'; type JudgedAgainst = 'success-criteria' | 'user-visible-behavior' | 'overview'; /** Ship authority DERIVED in TS from (verdict, confidence); never from the LLM. */ type Authority = 'blocking' | 'advisory'; interface OutcomeEvalInput { /** Absolute or repo-relative path to the spec markdown. */ specPath: string; /** Unified diff of the change under judgment. */ diff: string; /** Captured test-runner output. */ testOutput: string; /** Pre-resolved judgment section; otherwise the section-resolver runs. */ specSection?: string; /** * Head commit sha of the change under judgment. Persisted onto the * `execution_outcome` node's metadata (`commit`) so downstream consumers * (e.g. the pre-merge brief) can look the verdict up by sha. Optional and * additive: absent leaves the persisted node byte-identical to no-commit * wiring. */ commit?: string; /** * Advisory guardian diff-coverage records read from `.harness/analyses/` * (#914). Absent/empty leaves the verdict byte-identical to no guardian * wiring; when present, a deterministic one-line signal is appended to the * verdict rationale. Never affects ship authority (still TS-derived from * verdict + confidence). */ guardian?: GuardianAnalysis[]; /** * Structured canary run outcome (gate exit code + pass/fail/flaky/skipped * counts). Absent/empty leaves the verdict byte-identical to no canary * wiring; when present, a deterministic one-line signal is appended to the * verdict rationale and `canary*` metadata is stamped onto the * execution_outcome node. Never affects ship authority. Mirrors `guardian?`. */ canaryRun?: CanaryRunOutcome; } /** * Structured outcome of a canary test run, folded additively into outcome-eval. * `exitCode` is canary's gate exit code: 0 clean / 1 findings / 2 surface / * 3 abstained. Absent leaves the verdict byte-identical to no canary wiring; * never affects ship authority (still TS-derived from verdict + confidence). * This is the minimal structured summary the judge needs — a caller derives it * from the adapter's fuller `CanaryRunRecord`, keeping outcome-eval decoupled * from the adapter's record schema. */ interface CanaryRunOutcome { /** Canary gate exit code: 0 clean, 1 findings, 2 surface, 3 abstained. */ exitCode: number; passed: number; failed: number; flaky: number; skipped: number; /** Optional total case count (passed + failed + flaky + skipped). */ total?: number; } interface OutcomeVerdict { verdict: Verdict; confidence: Confidence$1; /** Cites specific met / unmet criteria. */ rationale: string; judgedAgainst: JudgedAgainst; /** Empty when SATISFIED. */ unmetCriteria: string[]; /** DERIVED in TS from (verdict, confidence); never from the LLM. */ authority: Authority; } /** * Pure mapping from (verdict, confidence) to ship authority. * * Blocking iff a NOT_SATISFIED verdict is held with high confidence; every * other combination — including all INCONCLUSIVE and SATISFIED cases — is * advisory. Missing inputs never punish the change. * * This function is the false-positive-critical seam. Authority is computed * here in TypeScript and is NEVER trusted from the LLM response. */ declare function deriveAuthority(verdict: Verdict, confidence: Confidence$1): Authority; /** * Zod schema for the LLM verdict response. * * `authority` is intentionally ABSENT: it is derived in TypeScript by * `deriveAuthority` and must never be supplied by the model. The schema is * `.strict()` so an injected `authority` (or any other extra key) is rejected * at the parse boundary rather than silently passing through. * */ declare const verdictSchema: z.ZodObject<{ verdict: z.ZodEnum<["SATISFIED", "NOT_SATISFIED", "INCONCLUSIVE"]>; confidence: z.ZodEnum<["low", "medium", "high"]>; rationale: z.ZodString; unmetCriteria: z.ZodArray; }, "strict", z.ZodTypeAny, { verdict: "SATISFIED" | "NOT_SATISFIED" | "INCONCLUSIVE"; confidence: "low" | "medium" | "high"; rationale: string; unmetCriteria: string[]; }, { verdict: "SATISFIED" | "NOT_SATISFIED" | "INCONCLUSIVE"; confidence: "low" | "medium" | "high"; rationale: string; unmetCriteria: string[]; }>; type LlmVerdict = z.infer; /** * System prompt for outcome-eval. Conservative-confidence posture copied from * security-craft (SKILL.md): the model defaults to `medium` confidence; `high` * requires naming a specific met or unmet criterion; the bias is toward * advisory, not blocking. `authority` is derived in TypeScript and must never * be supplied by the model — the schema is `.strict()` and rejects it. */ declare const OUTCOME_EVAL_SYSTEM_PROMPT = "You are a post-execution outcome judge. Given a spec acceptance section, a unified diff, and test output, decide whether the change SATISFIED, NOT_SATISFIED, or is INCONCLUSIVE against that section.\n\nConfidence calibration (be conservative \u2014 false alarms are costly):\n- Default to \"medium\" confidence.\n- Use \"high\" ONLY when you can name a SPECIFIC criterion from the section that the diff and test output clearly met or clearly failed to meet, and quote or paraphrase it in the rationale.\n- Use \"low\" when the diff or test output is ambiguous, partial, or insufficient to judge.\n- When the change only PARTIALLY meets the criteria, do not exceed \"medium\" confidence.\n- Bias toward advisory caution: if unsure between two confidence levels, choose the lower one.\n\nRules:\n- The rationale MUST cite specific met or unmet criteria from the section.\n- \"unmetCriteria\" lists the section criteria the change failed to meet; it is empty when the verdict is SATISFIED.\n- Do NOT emit an \"authority\" field. Authority is computed downstream in TypeScript from (verdict, confidence) and must never come from you.\n\nReturn your judgment using the structured_output tool."; /** * Build the user prompt from the resolved spec section body, the change diff, * and the captured test output. Mirrors the labeled-section structure of * sel/pesl prompts. * * The diff and test output are clamped to PROMPT_FIELD_MAX_CHARS and wrapped in * a 4-backtick fence so a triple-backtick sequence inside the diff cannot close * the fence early. */ declare function buildUserPrompt$2(section: string, diff: string, testOutput: string): string; /** * Result of resolving the judgment section from a spec's markdown. * `body` is the matched section's content (heading excluded, blank-trimmed). */ interface ResolvedSection { judgedAgainst: JudgedAgainst; body: string; } /** * Resolve the judgment input from a spec's markdown via the fallback chain * Success Criteria -> User-Visible Behavior -> Overview, returning the matched * section body plus which heading matched. * * Returns `null` when no judgable section exists. The caller (a later phase) * maps that null to an INCONCLUSIVE verdict — this resolver never throws and * never decides verdict authority. * * Self-contained string parsing: imports only the JudgedAgainst type, honoring * the intelligence layer rule (no `core` dependency). */ declare function resolveSection(markdown: string): ResolvedSection | null; interface OutcomeEvaluatorOptions { /** Override model for the outcome-eval LLM call. */ model?: string; } /** * Post-execution spec-satisfaction judge. Mirrors PeslSimulator's * (provider, store, options) constructor shape. The store is held for the * Phase 4 execution_outcome graph write; see `persistOutcome`. */ declare class OutcomeEvaluator { private readonly provider; private readonly store; private readonly options; constructor(provider: AnalysisProvider, store: GraphStore, options?: OutcomeEvaluatorOptions); evaluate(input: OutcomeEvalInput): Promise; /** * Run the provider call and strict re-parse. ANY failure here — provider * rejection (rate limit/network), or a strict-parse rejection of a malformed * or authority-injected payload — degrades safely to INCONCLUSIVE/low/ * advisory rather than throwing. This reconciles Criterion 3 (never block on * noise) with Criterion 4: an injected `authority` key is discarded by the * .strict() re-parse, and the degraded verdict's authority is DERIVED from * INCONCLUSIVE/low = advisory — so the LLM gains nothing by injecting it. */ private judge; /** * Build the safe-degradation verdict. INCONCLUSIVE/low yields advisory * authority via deriveAuthority — never blocking. The rationale names only a * coarse reason category, never a stack trace or secret. */ private degradedVerdict; /** * Fold in the advisory guardian signal, persist (Phase 4 seam), then return * the verdict. Applying guardian here (not per-branch) means every path — * judged, no-section, and degraded — surfaces the guardian signal uniformly, * and the persisted `execution_outcome` node carries the annotated rationale. */ private finish; private resolveJudgmentSection; private buildVerdict; /** * Map an OutcomeVerdict + OutcomeEvalInput to the connector's ExecutionOutcome. * - result: SATISFIED -> 'success'; otherwise 'failure'. INCONCLUSIVE is * 'failure' for type-validity but omits agentPersona/affected systems so * the effectiveness scorer ignores it (plan D2). * - linkedSpecId: input.specPath (metadata only; no spec edge — plan D1). * - affectedSystemNodeIds: [] in v1 (not available from OutcomeEvalInput — D4). * - id: one node per EVALUATION. GraphStore.addNode upserts by id, so the id * carries a collision-free randomUUID() — two evaluate() calls in the same * millisecond can never overwrite each other (data-loss fix). specPath is * included for human readability only. * - taskType: OMITTED. The outcome-eval judge has no task categorization, and * asserting a false 'feature' would mislead specialization analytics (SUG-2). * - metadata: the full verdict carried through the connector's additive * pass-through (verdict/confidence/judgedAgainst/rationale/authority/ * unmetCriteria/source, plus commit when supplied) so the true 3-valued * verdict is durable and self-describing on the node (Truth 3) and a * sha-keyed consumer can reconstruct the OutcomeVerdict. `authority` is a * copy of the TS-derived value, never trusted from the LLM. */ private toExecutionOutcome; /** * Phase 4: writes exactly one execution_outcome node via * ExecutionOutcomeConnector. Degrade-safe (plan D3): a graph-write failure is * swallowed-and-logged, never thrown — the verdict is already computed before * this runs, so swallowing keeps evaluate() total. No secrets/stack frames in * the log message. */ private persistOutcome; } /** * Zod validation for the {@link GuardianAnalysis} contract. Used by the tolerant * reader to accept only well-formed guardian diff-coverage records and skip * everything else (foreign shapes, malformed JSON, intelligence records). */ /** * Strict-enough schema for a guardian diff-coverage record. The `schema` and * `version` literals are the discriminator — a record failing them is not a * guardian record and is silently skipped by the reader. Unknown extra keys are * stripped (not rejected) so a forward-compatible producer that adds fields does * not get dropped wholesale. */ declare const guardianAnalysisSchema: z.ZodObject<{ schema: z.ZodLiteral<"harness.guardian.diff-coverage">; version: z.ZodLiteral<1>; generatedAt: z.ZodString; verdict: z.ZodEnum<["pass", "fail"]>; severity: z.ZodEnum<["info", "warn", "error"]>; coverageDelta: z.ZodNumber; files: z.ZodArray; uncoveredRegions: z.ZodOptional, "many">>; }, "strip", z.ZodTypeAny, { file: string; uncoveredLines: number[]; uncoveredRegions?: [number, number][] | undefined; }, { file: string; uncoveredLines: number[]; uncoveredRegions?: [number, number][] | undefined; }>, "many">; summary: z.ZodOptional; }, "strip", z.ZodTypeAny, { severity: "error" | "info" | "warn"; version: 1; verdict: "pass" | "fail"; schema: "harness.guardian.diff-coverage"; generatedAt: string; coverageDelta: number; files: { file: string; uncoveredLines: number[]; uncoveredRegions?: [number, number][] | undefined; }[]; summary?: string | undefined; }, { severity: "error" | "info" | "warn"; version: 1; verdict: "pass" | "fail"; schema: "harness.guardian.diff-coverage"; generatedAt: string; coverageDelta: number; files: { file: string; uncoveredLines: number[]; uncoveredRegions?: [number, number][] | undefined; }[]; summary?: string | undefined; }>; /** * Tolerant reader for guardian diff-coverage records in `.harness/analyses/`. * * Contract (issue #914): NEVER throw and NEVER change consumer behavior when the * archive is absent, empty, or full of foreign/malformed records. The reader * lists the directory, parses each JSON file best-effort, selects guardian * records by the `schema` discriminator, validates them with zod, and skips * anything that does not validate. The result is advisory input only. */ /** * Read every valid guardian diff-coverage record from `analysesDir` * (conventionally `/.harness/analyses`). * * Degrade-safe: a missing directory yields `[]`; a file that is unreadable, is * not JSON, or is not a valid guardian record is skipped. Order follows * directory listing and is not otherwise guaranteed. */ declare function readGuardianAnalyses(analysesDir: string): Promise; /** * Deterministic, LLM-free projections of guardian diff-coverage records into * human/consumer-facing text. Kept pure so both `outcome_eval` (rationale * annotation) and `pre-merge-brief` (review section) surface the SAME signal. */ /** A guardian record "flags" a change when it failed or is error-severity. */ declare function guardianFlags(a: GuardianAnalysis): boolean; /** * One-line advisory summary of the guardian signal, or `null` when there is no * guardian input at all (the "no signal" case — consumers must treat this as * byte-identical to having no guardian wiring). */ declare function summarizeGuardian(analyses: GuardianAnalysis[]): string | null; /** * Per-file detail lines suitable for a review brief, most impactful producers * first is not attempted — files are listed in record/declaration order. */ declare function guardianFileLines(analyses: GuardianAnalysis[]): string[]; /** * acceptance-eval contract types — the upstream twin of outcome-eval. * * `authority` is DERIVED in TypeScript from (measurability, confidence) via * `deriveAcceptanceAuthority` in `./authority.js`. It is NEVER read from the * LLM response — see `acceptanceVerdictSchema` in `./prompts.js`, which omits * it. `Confidence`, `JudgedAgainst`, and `Authority` are REUSED from the * outcome-eval module — not forked — consistent with the imported section-resolver. */ /** (c) the measurability gate dimension. */ type Measurability = 'MEASURABLE' | 'NOT_MEASURABLE' | 'INCONCLUSIVE'; /** A single advisory observation about one criterion or behavior. */ interface Finding { /** The specific criterion or user-visible behavior this finding references. */ target: string; /** The advisory observation (e.g. 'not observable', 'no covering test'). */ message: string; } interface AcceptanceEvalInput { /** Absolute or repo-relative path to the spec markdown. */ specPath: string; /** Pre-resolved judgment section; otherwise the section-resolver runs. */ specSection?: string; /** * Located test snippets for coverage responsibility (b). Optional: absence * degrades (b) coverageFindings to advisory-empty and never affects the * (c) measurability gate. */ testContent?: string; } interface AcceptanceVerdict { measurability: Measurability; confidence: Confidence$1; /** DERIVED in TS from (measurability, confidence); never from the LLM. */ authority: Authority; /** Which spec section resolved. */ judgedAgainst: JudgedAgainst; /** (a) advisory — observability / testability / completeness critique. */ criteriaFindings: Finding[]; /** (b) advisory — user-visible behaviors with no covering test. */ coverageFindings: Finding[]; rationale: string; } /** * Pure mapping from (measurability, confidence) to gate authority. * * Blocking iff a spec is judged NOT_MEASURABLE with high confidence — i.e. it * objectively lacks measurable success criteria; every other combination, * including all INCONCLUSIVE and MEASURABLE cases, is advisory. Missing or * uncertain inputs never punish the spec. * * This function is the false-positive-critical seam. Authority is computed * here in TypeScript and is NEVER trusted from the LLM response. */ declare function deriveAcceptanceAuthority(measurability: Measurability, confidence: Confidence$1): Authority; /** A single advisory finding. `.strict()` rejects unexpected keys. */ declare const findingSchema: z.ZodObject<{ target: z.ZodString; message: z.ZodString; }, "strict", z.ZodTypeAny, { message: string; target: string; }, { message: string; target: string; }>; /** * Zod schema for the LLM verdict response. * * `authority` is intentionally ABSENT: it is derived in TypeScript by * `deriveAcceptanceAuthority` and must never be supplied by the model. The * schema is `.strict()`, so an injected `authority` (or any other extra key) * is rejected at the parse boundary rather than silently passing through. */ declare const acceptanceVerdictSchema: z.ZodObject<{ measurability: z.ZodEnum<["MEASURABLE", "NOT_MEASURABLE", "INCONCLUSIVE"]>; confidence: z.ZodEnum<["low", "medium", "high"]>; rationale: z.ZodString; criteriaFindings: z.ZodArray, "many">; coverageFindings: z.ZodArray, "many">; }, "strict", z.ZodTypeAny, { confidence: "low" | "medium" | "high"; rationale: string; measurability: "INCONCLUSIVE" | "MEASURABLE" | "NOT_MEASURABLE"; criteriaFindings: { message: string; target: string; }[]; coverageFindings: { message: string; target: string; }[]; }, { confidence: "low" | "medium" | "high"; rationale: string; measurability: "INCONCLUSIVE" | "MEASURABLE" | "NOT_MEASURABLE"; criteriaFindings: { message: string; target: string; }[]; coverageFindings: { message: string; target: string; }[]; }>; type LlmAcceptanceVerdict = z.infer; /** * System prompt for acceptance-eval. Conservative-confidence posture mirrors * outcome-eval: default to medium; high requires naming a specific criterion; * bias toward advisory. `authority` is derived in TypeScript and must never be * supplied by the model — the schema is `.strict()` and rejects it. */ declare const ACCEPTANCE_EVAL_SYSTEM_PROMPT = "You are a PRE-execution acceptance-criteria judge. Given a spec acceptance section (and optionally located test snippets), assess three things:\n(a) criteria quality \u2014 are the success criteria observable, testable, and complete? (advisory findings)\n(b) coverage \u2014 do any user-visible behaviors lack a covering test? (advisory findings)\n(c) measurability \u2014 does the spec state MEASURABLE, NOT_MEASURABLE, or is it INCONCLUSIVE on whether any measurable success criteria exist at all?\n\nConfidence calibration (be conservative \u2014 false alarms are costly):\n- Default to \"medium\" confidence.\n- Use \"high\" ONLY when you can name a SPECIFIC criterion (or its absence) and quote or paraphrase it in the rationale.\n- Use \"low\" when the section is ambiguous, partial, or insufficient to judge.\n- Bias toward advisory caution: if unsure between two confidence levels, choose the lower one.\n\nRules:\n- \"measurability\" is NOT_MEASURABLE only when the section states no observable, testable success criterion at all.\n- \"criteriaFindings\" holds advisory (a) observations; \"coverageFindings\" holds advisory (b) observations; both may be empty.\n- Do NOT emit an \"authority\" field. Authority is computed downstream in TypeScript from (measurability, confidence) and must never come from you.\n\nReturn your judgment using the structured_output tool."; /** * Build the user prompt from the resolved spec section body and optional * located test snippets. Test content is clamped to PROMPT_FIELD_MAX_CHARS and * wrapped in a 4-backtick fence so an inner ``` cannot close the fence early. */ declare function buildUserPrompt$1(section: string, testContent?: string): string; interface AcceptanceEvaluatorOptions { /** Override model for the acceptance-eval LLM call. */ model?: string; } /** * Pre-execution acceptance-criteria judge — the upstream twin of * OutcomeEvaluator. Built on the cli AnalysisProvider. The LLM returns only * measurability/confidence/criteriaFindings/coverageFindings/rationale; * `authority` is derived in TypeScript and never read from the model. * * Unlike OutcomeEvaluator it holds no GraphStore: there is no acceptance * outcome node type and Phase 1 does not persist (see plan D-P1-3). */ declare class AcceptanceEvaluator { private readonly provider; private readonly options; constructor(provider: AnalysisProvider, options?: AcceptanceEvaluatorOptions); evaluate(input: AcceptanceEvalInput): Promise; private judge; private degradedVerdict; private resolveJudgmentSection; private buildVerdict; } /** * skill-regression contract types. * * The golden-fixture evaluation framework that detects when a skill REGRESSES: * given a golden fixture (input + a quality rubric + a recorded golden baseline * score), score one or more candidate outputs of the skill semantically against * the rubric and compare the aggregate score@k to the golden baseline. A score * that drops below `baseline.score - baseline.tolerance` is a regression. * * `authority` is DERIVED in TypeScript from (verdict, confidence) via * `deriveRegressionAuthority` in `./authority.js`. It is NEVER read from the LLM * response — see `criterionJudgmentSchema` in `./prompts.js`, which omits it. * This mirrors the outcome-eval false-positive-critical seam. */ type RegressionVerdictKind = 'REGRESSED' | 'STABLE' | 'INCONCLUSIVE'; type Confidence = 'low' | 'medium' | 'high'; /** Ship authority DERIVED in TS from (verdict, confidence); never from the LLM. */ type RegressionAuthority = 'blocking' | 'advisory'; /** * A single semantic quality check a skill's output must satisfy. `weight` * (default 1) lets a fixture emphasize load-bearing criteria over cosmetic ones. */ interface RubricCriterion { /** Stable slug, unique within a fixture (e.g. `weighs-tradeoffs`). */ id: string; /** The quality property, phrased so a judge can rule met / not-met. */ criterion: string; /** Relative weight in the aggregate score; defaults to 1 when omitted. */ weight?: number; } /** * The recorded golden baseline for a fixture. `score` is the rubric score the * reference output earned at baseline time; `k` records how many samples that * baseline was aggregated over; `tolerance` is the allowed drop before a * candidate counts as a regression. */ interface GoldenBaseline { /** Golden rubric score in [0,1], recorded via `--update-baseline`. */ score: number; /** Number of samples the baseline score was aggregated over (>= 1). */ k: number; /** Allowed downward drift in [0,1] before a candidate counts as regressed. */ tolerance: number; } /** * A golden fixture for one skill. Stored as byte-stable JSON on disk (see * `./fixture.js`). `referenceOutput` is the golden, high-quality output the * baseline was measured against; it doubles as the default self-test candidate * so the gate runs end-to-end with no captured candidate supplied. */ interface SkillRegressionFixture { /** Fixture format version; bumped only on a breaking schema change. */ schemaVersion: 1; /** The skill under test (e.g. `harness-spec-craft`). */ skill: string; /** Fixture id, unique per skill (e.g. `minimal-adr`). */ id: string; /** Human note on what this fixture pins. */ description?: string; /** The canonical input the skill runs against. */ input: string; /** The quality rubric the output is scored against (non-empty). */ rubric: RubricCriterion[]; /** The golden reference output; also the default self-test candidate. */ referenceOutput: string; /** The recorded golden baseline. */ baseline: GoldenBaseline; } /** The judge's per-criterion ruling for one candidate output (from the LLM). */ interface CriterionJudgment { /** Matches a `RubricCriterion.id`. */ id: string; /** Whether the candidate output meets the criterion. */ met: boolean; /** Short justification citing the output; never a secret or stack trace. */ note: string; } interface SkillRegressionInput { fixture: SkillRegressionFixture; /** * Candidate outputs of the skill to score (the k samples). Empty means * "self-test": the fixture's `referenceOutput` is scored as the sole * candidate, which should reproduce the baseline (STABLE). */ candidates?: string[]; } interface SkillRegressionVerdict { /** REGRESSED iff the aggregate score dropped past the tolerance with confidence. */ verdict: RegressionVerdictKind; confidence: Confidence; /** Aggregate rubric score@k over the scored candidates, in [0,1]. */ score: number; /** The golden baseline score the candidate was compared against. */ baselineScore: number; /** `baselineScore - score`; positive means the candidate scored lower. */ delta: number; /** The tolerance applied (from the fixture). */ tolerance: number; /** How many candidate samples were scored (the effective k). */ sampledK: number; /** Cites which criteria drove the score. */ rationale: string; /** DERIVED in TS from (verdict, confidence); never from the LLM. */ authority: RegressionAuthority; } /** * Pure mapping from (verdict, confidence) to ship authority. * * Blocking iff a REGRESSED verdict is held with high confidence; every other * combination — including all INCONCLUSIVE and STABLE cases — is advisory. A * noisy or low-confidence signal never blocks a skill/prompt PR. * * This function is the false-positive-critical seam. Authority is computed here * in TypeScript and is NEVER trusted from the LLM response. Mirrors * outcome-eval's `deriveAuthority`. */ declare function deriveRegressionAuthority(verdict: RegressionVerdictKind, confidence: Confidence): RegressionAuthority; /** * Zod schema for the LLM judge response: one ruling per rubric criterion plus * an overall confidence. * * `authority` and the numeric `score` are intentionally ABSENT: the score is * computed in TypeScript from the weighted rubric rulings (see `./scorer.js`) * and authority is derived from (verdict, confidence) — neither is ever supplied * by the model. The schema is `.strict()` so an injected extra key (e.g. * `authority` or `score`) is rejected at the parse boundary. Mirrors the * outcome-eval seam. */ declare const criterionJudgmentSchema: z.ZodObject<{ id: z.ZodString; met: z.ZodBoolean; note: z.ZodString; }, "strict", z.ZodTypeAny, { id: string; met: boolean; note: string; }, { id: string; met: boolean; note: string; }>; declare const judgeResponseSchema: z.ZodObject<{ criteria: z.ZodArray, "many">; confidence: z.ZodEnum<["low", "medium", "high"]>; }, "strict", z.ZodTypeAny, { confidence: "low" | "medium" | "high"; criteria: { id: string; met: boolean; note: string; }[]; }, { confidence: "low" | "medium" | "high"; criteria: { id: string; met: boolean; note: string; }[]; }>; type JudgeResponse = z.infer; /** * System prompt for the skill-regression judge. Conservative-confidence posture * copied from outcome-eval / security-craft: default to `medium`; `high` only * when the output is unambiguous against the rubric. The judge scores QUALITY * against a rubric — it does not compare against a reference verbatim, so a * differently-worded but equally-good output is not penalized. */ declare const SKILL_REGRESSION_SYSTEM_PROMPT = "You are a skill-output quality judge. Given the input a skill was run on, a quality rubric, and a candidate output the skill produced, rule for EACH rubric criterion whether the candidate output meets it.\n\nJudge SEMANTIC quality against the rubric, not surface similarity to any reference. A differently-worded output that satisfies the criterion is \"met\"; a fluent output that misses the criterion's substance is \"not met\".\n\nConfidence calibration (be conservative \u2014 false alarms block skill PRs):\n- Default to \"medium\" confidence.\n- Use \"high\" ONLY when the candidate output is unambiguous against the rubric.\n- Use \"low\" when the output is truncated, ambiguous, or off-topic.\n- When unsure between two confidence levels, choose the lower one.\n\nRules:\n- Return exactly one ruling per rubric criterion, echoing its \"id\".\n- Each \"note\" briefly cites the output; never include secrets or stack traces.\n- Do NOT emit a numeric score or an \"authority\" field. Both are computed downstream in TypeScript and must never come from you.\n\nReturn your rulings using the structured_output tool."; /** * Build the user prompt from the skill input, the rubric, and one candidate * output. The candidate is clamped and wrapped in a 4-backtick fence so a * triple-backtick sequence inside it cannot close the fence early. */ declare function buildUserPrompt(skill: string, input: string, rubric: RubricCriterion[], candidate: string): string; /** * Weighted fraction of rubric criteria the judge ruled met, in [0,1]. * * Rulings are matched to rubric criteria by `id`; a criterion with no matching * ruling counts as not-met (a judge that skipped it cannot silently inflate the * score). An empty rubric, or a rubric whose weights sum to 0, scores 0. */ declare function weightedScore(rubric: RubricCriterion[], judgments: CriterionJudgment[]): number; /** * Aggregate per-candidate rubric scores into a single score@k: the arithmetic * mean across the k sampled candidates. An empty list scores 0 (nothing to * judge). Mean (not best@k) is deliberate: a skill that regresses on some * samples should see its aggregate drop, not be masked by a single good sample. */ declare function aggregateAtK(scores: number[]): number; /** The regression threshold: a candidate regressed iff it scored this far below baseline. */ declare function regressionFloor(baseline: GoldenBaseline): number; /** * Pure verdict rule (before confidence/authority are applied): a candidate has * REGRESSED iff its aggregate score fell strictly below `baseline.score - * tolerance`; otherwise STABLE. INCONCLUSIVE is never produced here — it comes * only from the evaluator's degrade path (no provider / parse failure). */ declare function deriveRegressionVerdict(score: number, baseline: GoldenBaseline): { verdict: Extract; delta: number; }; declare const fixtureSchema: z.ZodObject<{ schemaVersion: z.ZodLiteral<1>; skill: z.ZodString; id: z.ZodString; description: z.ZodOptional; input: z.ZodString; rubric: z.ZodEffects; }, "strict", z.ZodTypeAny, { id: string; criterion: string; weight?: number | undefined; }, { id: string; criterion: string; weight?: number | undefined; }>, "many">, { id: string; criterion: string; weight?: number | undefined; }[], { id: string; criterion: string; weight?: number | undefined; }[]>; referenceOutput: z.ZodString; baseline: z.ZodObject<{ score: z.ZodNumber; k: z.ZodNumber; tolerance: z.ZodNumber; }, "strict", z.ZodTypeAny, { score: number; k: number; tolerance: number; }, { score: number; k: number; tolerance: number; }>; }, "strict", z.ZodTypeAny, { id: string; skill: string; schemaVersion: 1; input: string; rubric: { id: string; criterion: string; weight?: number | undefined; }[]; referenceOutput: string; baseline: { score: number; k: number; tolerance: number; }; description?: string | undefined; }, { id: string; skill: string; schemaVersion: 1; input: string; rubric: { id: string; criterion: string; weight?: number | undefined; }[]; referenceOutput: string; baseline: { score: number; k: number; tolerance: number; }; description?: string | undefined; }>; /** Parse + validate a fixture object (e.g. from JSON.parse). Throws on invalid input. */ declare function parseFixture(raw: unknown): SkillRegressionFixture; /** * Serialize a fixture to byte-stable JSON: canonical key order, 2-space indent, * trailing newline. A criterion's optional `weight` and the fixture's optional * `description` are emitted only when present, so an absent optional never * churns the bytes. */ declare function serializeFixture(fixture: SkillRegressionFixture): string; interface SkillRegressionEvaluatorOptions { /** Override model for the judge LLM call. */ model?: string; } /** * The golden-fixture skill-regression judge. Scores k candidate outputs of a * skill against the fixture's quality rubric and compares the aggregate score@k * to the recorded golden baseline. Mirrors OutcomeEvaluator's shape: a * (provider, options) constructor, a strict re-parse of the LLM payload, and a * TS-derived ship authority that the LLM can never inject. * * Degrade-safe throughout: no provider, a provider rejection, or a malformed / * authority-injected payload yields an INCONCLUSIVE/low/advisory verdict rather * than throwing — a skill PR is never blocked on infrastructure noise. */ declare class SkillRegressionEvaluator { private readonly provider; private readonly options; constructor(provider: AnalysisProvider, options?: SkillRegressionEvaluatorOptions); evaluate(input: SkillRegressionInput): Promise; /** * Run the judge for one candidate and strict re-parse. ANY failure — provider * rejection or a strict-parse rejection of a malformed / authority-injected * payload — returns null so the caller degrades safely to INCONCLUSIVE. */ private judgeCandidate; private buildRationale; private degradedVerdict; private buildVerdict; } /** * Compute the golden baseline score for a fixture by judging its * `referenceOutput` against its own rubric. Used by `--update-baseline` to * record the number that future candidates are compared against. Returns null * on any degrade (no provider / malformed payload) so the caller can leave the * existing baseline untouched rather than writing a degenerate 0. */ declare function computeBaselineScore(provider: AnalysisProvider, fixture: SkillRegressionFixture, options?: SkillRegressionEvaluatorOptions): Promise<{ score: number; k: number; } | null>; /** The three per-item UAT dispositions a human can record for a BRD item. */ type UatItemDisposition = 'ACCEPT' | 'REJECT' | 'CHANGES_REQUESTED'; /** The single overall UAT verdict the human signs off with. */ type UatOverallDecision = 'ACCEPTED' | 'REJECTED' | 'CHANGES_REQUESTED'; /** One acceptance item the human ruled on during sign-off. */ interface UatSignoffItem { /** * Stable identifier of the item — a Success-Criterion id from the change's * `proposal.md` (e.g. `SC3`). Used verbatim; the recorder never invents ids. */ id: string; /** The human's disposition for this item. */ disposition: UatItemDisposition; /** Optional free-text note the human attached to the disposition. */ note?: string; } /** * A recorded human UAT sign-off for one change. * * This is the HUMAN's decision, captured verbatim: no LLM produces the verdict * and no ship authority is derived. It is the far-end, human-authority mirror of * the lifecycle's machine gates — intent(spec Success Criteria)-vs-shipped-reality, * human-judged. The recorder maps it onto the shared `execution_outcome` node * shape so the existing eval-fail-rate signal and effectiveness baselines consume * it for free. */ interface UatSignoffInput { /** Change slug — the `docs/changes//` owner (same slug as spec/plan/review). */ slug: string; /** The overall human verdict. */ decision: UatOverallDecision; /** Name/identity of the human who signed off. */ signedOffBy: string; /** Per-item dispositions ruled on during the interview. */ items: UatSignoffItem[]; /** Success-Criterion ids the sign-off closes (the accepted acceptance items). */ criteriaRefs?: string[]; /** ISO timestamp of the sign-off; defaults to now when omitted. */ timestamp?: string; } /** * The source tag stamped on every node this recorder writes. The eval-fail-rate * signal and effectiveness scorer key off `metadata.result` + `metadata.timestamp` * only; `source` lets a consumer distinguish a HUMAN UAT sign-off from an * LLM-judged outcome-eval verdict. */ declare const UAT_SIGNOFF_SOURCE: "uat-signoff"; /** * Map a human UAT sign-off onto the shared `execution_outcome` contract. Pure — * no I/O, no LLM. * * UNLIKE outcome-eval there is NO derived authority: the human IS the authority, * so `result` is read straight from the human's overall decision * (`ACCEPTED` -> success; `REJECTED` / `CHANGES_REQUESTED` -> failure). Nothing * here blocks a merge or ship — the record is advisory. * * - `id`: one node per sign-off; a collision-free `randomUUID()` means two * sign-offs in the same millisecond can never overwrite each other. * - `affectedSystemNodeIds`: `[]` — a sign-off records intent-vs-reality * acceptance, not a code-node blast radius, so it seeds no `outcome_of` edges. * - `failureReasons`: the ids of items the human did NOT accept, so a downstream * reader sees what blocked acceptance without re-reading `signoff.md`. * - `metadata`: the human decision carried additively (slug / decision / * signedOffBy / criteriaRefs / items / source). Reserved core keys * (result / timestamp / …) are written by the connector and can never be * shadowed by this metadata. */ declare function toUatExecutionOutcome(input: UatSignoffInput): ExecutionOutcome; /** * Records a human UAT sign-off as a single `execution_outcome` node via the * shared `ExecutionOutcomeConnector`. Record-only / advisory: it never blocks * and never derives a verdict — it durably captures the decision the human * already made so signals and effectiveness baselines can consume it. */ declare class UatSignoffRecorder { private readonly store; constructor(store: GraphStore); record(input: UatSignoffInput): { outcomeId: string; ingest: OutcomeIngestResult; }; } /** * Compute historical complexity from past execution outcomes in the graph. * * For each affected system with a graph node ID, queries the graph for * 'execution_outcome' nodes linked via 'outcome_of' edges. Computes * a smoothed failure rate per system, then returns the maximum across * all systems. * * Returns a value in [0, 1]. Returns 0 when no outcomes exist. */ declare function computeHistoricalComplexity(spec: EnrichedSpec, store: GraphStore): number; /** * Result of preprocessing an issue through the intelligence pipeline. */ interface PreprocessResult { /** Enriched spec from SEL, or null if SEL was skipped */ spec: EnrichedSpec | null; /** Complexity score from CML, or null if CML was skipped */ score: ComplexityScore | null; /** Concern signals derived from complexity score (empty if CML skipped) */ signals: ConcernSignal[]; } /** * Composes SEL, CML, and signal conversion into a single pipeline. * * Tier-based behavior: * - `autoExecute` tiers: skip entirely (no LLM cost) * - `alwaysHuman` tiers: run SEL for enrichment context, skip CML (routing already decided) * - `signalGated` tiers: full pipeline (SEL → CML → signals) */ declare class IntelligencePipeline { private readonly provider; private readonly graphValidator; private readonly store; private readonly simulator; private readonly outcomeConnector; constructor(provider: AnalysisProvider, store: GraphStore, options?: { peslModel?: string; /** * Optional distinct provider for the PESL layer. Defaults to * `provider` (current behavior — sel and pesl share a session). * Spec 2 SC35: when `routing.intelligence.sel !== routing.intelligence.pesl`, * the orchestrator passes a second `AnalysisProvider` here so PESL * runs against a different backend than SEL. */ peslProvider?: AnalysisProvider; }); /** * Enrich a raw work item into an EnrichedSpec via LLM + graph validation. */ enrich(item: RawWorkItem): Promise; /** * Score an enriched spec using graph-based structural + semantic analysis. * Synchronous — no LLM calls. */ score(spec: EnrichedSpec): ComplexityScore; /** * Run pre-execution simulation for a spec. */ simulate(spec: EnrichedSpec, score: ComplexityScore, tier?: ScopeTier): Promise; /** * Record an execution outcome in the knowledge graph. * Called by the orchestrator after a worker exits. */ recordOutcome(outcome: ExecutionOutcome): OutcomeIngestResult; /** * Preprocess an issue through the intelligence pipeline. * * Behavior depends on which escalation tier the issue's scope falls into: * - `autoExecute`: returns immediately with null spec/score and empty signals * - `alwaysHuman`: runs SEL for enrichment context (human gets pre-analyzed view), * skips CML, returns empty signals (routing stays needs-human) * - `signalGated`: runs full SEL → CML → signals pipeline */ preprocessIssue(issue: Issue, scopeTier: _harness_engineering_types.ScopeTier, escalationConfig: EscalationConfig): Promise; } /** * Agent Effectiveness Introspection types. * * Given a graph populated with `execution_outcome` nodes (each carrying an * `agentPersona` tag and linked to affected systems via `outcome_of` edges), * these structures describe per-persona accuracy, blind spots, and * persona recommendations for new issues. */ /** * Smoothed success rate for a single `(persona, systemNodeId)` pair. * * `successRate` uses Laplace smoothing with α = 1: * (successes + 1) / (successes + failures + 2) * * This matches the bias of `computeHistoricalComplexity` and prevents a * single outcome from claiming 0% or 100% certainty. */ interface PersonaEffectivenessScore { persona: string; systemNodeId: string; successes: number; failures: number; /** Laplace-smoothed success rate in [0, 1]. */ successRate: number; /** Total observations (successes + failures). */ sampleSize: number; } /** * A `(persona, system)` pair where the persona consistently fails. * * Uses the *raw* failure rate `failures / (failures + successes)` so the * thresholds remain intuitive (e.g. "at least 50% failure with 2+ failures"). */ interface BlindSpot { persona: string; systemNodeId: string; failures: number; successes: number; /** Raw failure rate: failures / (failures + successes). */ failureRate: number; } /** * Recommendation for which persona to route a new issue to, given the list * of affected systems (graph node IDs) the issue will touch. * * `score` is the mean Laplace-smoothed success rate across the requested * systems. Systems for which the persona has no history contribute the * neutral prior 0.5, preventing over-confidence on partial data. */ interface PersonaRecommendation { persona: string; /** Mean smoothed success rate across the requested systems, in [0, 1]. */ score: number; /** Number of requested systems with at least one observation for this persona. */ coveredSystems: number; /** Number of requested systems with zero history for this persona. */ unknownSystems: number; /** Total observations for this persona across the requested systems. */ totalSamples: number; } /** * Skill-grain effectiveness score derived from `.harness/metrics/adoption.jsonl` * (`SkillInvocationRecord[]`). The skill-catalog counterpart to * `PersonaEffectivenessScore`. * * `successRate` uses the same Laplace smoothing (α = 1) as the persona scorer: * (completed + 1) / (invocations + 2) * * so a skill invoked once does not claim 0% or 100% certainty. `completed`, * `failed`, and `abandonedMidWorkflow` overlap by design — a `failed` run that * had already reached a phase counts as both a failure and an abandonment, * matching the classification the catalog retrospective uses. */ interface SkillEffectivenessScore { skill: string; /** Total invocations (all outcomes). */ invocations: number; /** Invocations with outcome `completed`. */ completed: number; /** Invocations with outcome `failed`. */ failed: number; /** Invocations classified as abandoned mid-workflow. */ abandonedMidWorkflow: number; /** Laplace-smoothed success rate in [0, 1]. */ successRate: number; } /** * A skill that fails often enough to warrant catalog attention. * * Mirrors `BlindSpot`: uses the *raw* failure rate `failed / invocations` so * thresholds stay intuitive, but also carries the Laplace-smoothed success rate * so callers can rank sample-aware (a skill that failed 1/1 should not outrank * one that failed 30/50). */ interface FailingSkill { skill: string; invocations: number; completed: number; failed: number; /** Raw failure rate: failed / invocations. */ failureRate: number; /** Laplace-smoothed success rate in [0, 1]. */ smoothedSuccessRate: number; /** * Count of this skill's non-completed runs by `FailureCategory`, keyed by the * category string. Only categories that actually occurred appear (no zero * entries). Empty when no non-completed run carried a category — e.g. records * predate the field. Lets callers see *why* a skill fails, not just that it does. */ failureCategories: Record; } /** * A skill that users start and bail out of mid-workflow often enough to warrant * attention. `abandonedMidWorkflow` counts explicit `abandoned` outcomes plus * non-completed runs that had already reached ≥1 phase. * * Mirrors `BlindSpot`: raw `abandonmentRate` for intuitive thresholds, plus the * smoothed success rate for sample-aware ranking. */ interface AbandonedSkill { skill: string; invocations: number; completed: number; abandonedMidWorkflow: number; /** Raw abandonment rate: abandonedMidWorkflow / invocations. */ abandonmentRate: number; /** Laplace-smoothed success rate in [0, 1]. */ smoothedSuccessRate: number; } /** * Per-`(persona, systemNodeId)` effectiveness scores. * * Results are sorted by `successRate` descending, then by `sampleSize` * descending, for stable deterministic iteration. */ declare function computePersonaEffectiveness(store: GraphStore, opts?: { persona?: string; systemNodeId?: string; }): PersonaEffectivenessScore[]; /** * Blind spots: `(persona, system)` pairs where failures accumulate. * * Uses the *raw* failure rate (not smoothed) so thresholds stay intuitive. * A pair must satisfy BOTH `failures >= minFailures` AND * `rawFailureRate >= minFailureRate`. * * Results are sorted by `failureRate` descending, then by `failures` descending. */ declare function detectBlindSpots(store: GraphStore, opts?: { persona?: string; minFailures?: number; minFailureRate?: number; }): BlindSpot[]; /** * Recommend personas to route a new issue to, given its affected systems. * * For each candidate persona, compute the mean Laplace-smoothed success rate * across `systemNodeIds`. Systems with no observations for the candidate * contribute the neutral prior 0.5. * * When `candidatePersonas` is omitted, the candidate set is the set of * personas with at least one persona-attributed outcome in the graph. * When none exist and no candidates are passed in, returns `[]`. * * Results are sorted by `score` descending, ties broken by `totalSamples` * descending. */ declare function recommendPersona(store: GraphStore, opts: { systemNodeIds: string[]; candidatePersonas?: string[]; minSamples?: number; }): PersonaRecommendation[]; /** * Per-skill effectiveness scores derived from adoption records. * * Results are sorted by `successRate` descending, then by `invocations` * descending, then by `skill` ascending, for stable deterministic iteration. */ declare function computeSkillEffectiveness(records: SkillInvocationRecord[], opts?: { skill?: string; }): SkillEffectivenessScore[]; /** * Failing skills: those that fail often enough to warrant catalog attention. * * Mirrors `detectBlindSpots` — uses the *raw* failure rate (not smoothed) so * thresholds stay intuitive. A skill must satisfy BOTH `failed >= minFailures` * AND `rawFailureRate >= minFailureRate`. The `minFailures` floor keeps n=1 * noise out. * * Results are sorted by `failureRate` descending, then `failed` descending, * then `skill` ascending. */ declare function detectFailingSkills(records: SkillInvocationRecord[], opts?: { minFailures?: number; minFailureRate?: number; }): FailingSkill[]; /** * Abandoned-mid-workflow skills: those users start and bail out of often enough * to warrant attention. * * Mirrors `detectBlindSpots` — uses the *raw* abandonment rate. A skill must * satisfy BOTH `abandonedMidWorkflow >= minAbandonments` AND * `rawAbandonmentRate >= minAbandonmentRate`. * * Results are sorted by `abandonmentRate` descending, then * `abandonedMidWorkflow` descending, then `skill` ascending. */ declare function detectAbandonedSkills(records: SkillInvocationRecord[], opts?: { minAbandonments?: number; minAbandonmentRate?: number; }): AbandonedSkill[]; /** * Persistent Agent Specialization types. * * Extends the effectiveness module with temporal awareness, task-type * categorization, expertise levels, and dynamic persona weighting. */ /** Expertise tier derived from sample size and success rate. */ type ExpertiseLevel = 'novice' | 'competent' | 'proficient' | 'expert'; /** * Composite specialization score for a (persona, system, taskType) tuple. * All values are in [0, 1]. */ interface SpecializationScore { /** Temporally-weighted success rate (recent outcomes weighted higher). */ temporalSuccessRate: number; /** Consistency score: 1 - stddev of rolling success windows. */ consistencyScore: number; /** Volume bonus: log-scaled sample count, capped at 1.0. */ volumeBonus: number; /** Composite score: weighted combination of the above. */ composite: number; } /** A single specialization entry for a (persona, system, taskType) bucket. */ interface SpecializationEntry { persona: string; systemNodeId: string; taskType: string; score: SpecializationScore; expertiseLevel: ExpertiseLevel; sampleSize: number; /** ISO timestamp of most recent outcome in this bucket. */ lastOutcome: string; } /** Full specialization profile for a persona across all systems/task-types. */ interface SpecializationProfile { persona: string; /** Per-(system, taskType) specialization entries. */ entries: SpecializationEntry[]; /** Top areas of expertise (highest composite scores). */ strengths: SpecializationEntry[]; /** Areas of consistent failure. */ weaknesses: SpecializationEntry[]; /** Overall expertise level across all entries. */ overallLevel: ExpertiseLevel; /** ISO timestamp when this profile was computed. */ computedAt: string; } /** Weighted persona recommendation incorporating specialization scores. */ interface WeightedRecommendation { persona: string; /** Base score from existing recommendPersona(). */ baseScore: number; /** Specialization multiplier [0.5, 1.5]. */ specializationMultiplier: number; /** Final weighted score: baseScore * specializationMultiplier. */ weightedScore: number; /** Expertise level for the requested systems/task-type. */ expertiseLevel: ExpertiseLevel; /** Number of requested systems with specialization data. */ specializedSystems: number; } /** * Temporal decay functions for specialization scoring. * * Uses exponential decay to weight recent outcomes more heavily than old ones. * The decay formula: weight = e^(-ln(2) / halfLifeDays * ageDays) */ /** Configuration for temporal decay calculations. */ interface TemporalConfig { /** Half-life in days (default 30). After this many days, an outcome's weight is halved. */ halfLifeDays: number; /** Reference timestamp for decay calculation (default: now). */ referenceTime?: string; } /** * Compute exponential decay weight for an outcome at a given age. * Returns 1.0 at age 0, 0.5 at halfLifeDays, 0.25 at 2*halfLifeDays, etc. */ declare function decayWeight(ageDays: number, halfLifeDays: number): number; /** * Compute temporally-weighted success rate from timestamped outcomes. * * Returns 0.5 (neutral prior) when no outcomes are provided. * Uses Laplace smoothing with decay-weighted pseudo-counts. */ declare function temporalSuccessRate(outcomes: ReadonlyArray<{ result: 'success' | 'failure'; timestamp: string; }>, config: TemporalConfig): number; /** * Specialization scorer — computes expertise scores for (persona, system, taskType) tuples. * * Builds on the effectiveness module by adding temporal decay, task-type * categorization, consistency scoring, and dynamic persona weighting. */ interface SpecializationOptions { persona?: string; systemNodeId?: string; taskType?: TaskType; temporal?: TemporalConfig; minSamples?: number; } /** * Classify expertise level from sample size and success rate. */ declare function computeExpertiseLevel(sampleSize: number, successRate: number): ExpertiseLevel; /** * Compute specialization entries for (persona, system, taskType) tuples. */ declare function computeSpecialization(store: GraphStore, opts?: SpecializationOptions): SpecializationEntry[]; /** * Build a full specialization profile for a persona. */ declare function buildSpecializationProfile(store: GraphStore, persona: string, opts?: Omit): SpecializationProfile; /** * Weighted persona recommendation incorporating specialization scores. * * Wraps the existing `recommendPersona()` and applies specialization * multipliers to produce weighted scores. */ declare function weightedRecommendPersona(store: GraphStore, opts: { systemNodeIds: string[]; taskType?: TaskType; candidatePersonas?: string[]; minSamples?: number; temporal?: TemporalConfig; }): WeightedRecommendation[]; /** * Profile persistence — load/save specialization profiles to disk. * * Profiles are stored at `.harness/specialization-profiles.json` and survive * across sessions so agents retain their accumulated expertise. */ /** Persisted store of specialization profiles. */ interface ProfileStore { profiles: Record; computedAt: string; version: 1; } /** Load profiles from disk. Returns empty store if file doesn't exist. */ declare function loadProfiles(projectRoot: string): ProfileStore; /** Save profiles to disk at .harness/specialization-profiles.json. */ declare function saveProfiles(projectRoot: string, store: ProfileStore): void; /** * Recompute and persist profiles for all personas with outcomes. * * Discovers all persona names from execution_outcome nodes in the graph, * builds a specialization profile for each, and saves the result. */ declare function refreshProfiles(projectRoot: string, graphStore: GraphStore, opts?: Omit): ProfileStore; /** Which signal set is available at this invocation phase (S3-001). */ type Phase = 'pre-diff' | 'post-diff'; /** Raw signals gathered for the static pass. Diff-based fields are undefined pre-diff. */ interface ComplexitySignals { /** Files touched by the diff/target. Undefined pre-diff. */ filesTouched?: number; /** Distinct architectural layers touched. Undefined pre-diff. */ layersTouched?: number; /** compute_blast_radius result. Undefined pre-diff. */ blastRadius?: number; /** hotspot × churn heat. Undefined pre-diff. */ hotspotChurn?: number; /** Text-only fallback signals (always available). */ descriptionLength: number; specExists: boolean; acceptanceMeasurable: boolean; } /** Provisional static verdict before any LLM tie-break. */ interface StaticVerdict { level: ComplexityLevel; confidence: 'high' | 'medium' | 'low'; /** Serialized subset of signals for the ComplexityVerdict.signals map. */ signals: Record; } /** Inputs to the cheap-first complexity cascade (D4). */ interface ClassifyInput { signals: ComplexitySignals; phase: Phase; /** Whether the invocation's risk band is high (drives standard-tier escalation). */ riskHigh: boolean; /** Prompt handed to the LLM tie-break / escalation. */ prompt: string; } /** * D4 cascade: static pass → (if low confidence) fast-tier tie-break → (if still * low AND risk high) standard-tier escalation. Emits a `ComplexityVerdict` whose * `source` records which stage produced it. The static-only path resolves * without any LLM call ("never pay strong to route"); when `provider` is absent * the classifier stays fully offline and returns the static verdict. * * The LLM only sets `level`/`confidence`; the tier is always TS-derived (D3). */ declare function classify(input: ClassifyInput, provider?: AnalysisProvider, models?: { fast?: string; standard?: string; }): Promise; /** * Documented seed weights for the free static pass (D4a). Each raw signal is * normalized against a reference maximum, multiplied by its weight, and the * weighted contributions are summed and re-normalized to a [0,1] score. * * Diff-based signals dominate post-diff; text-only signals carry the pre-diff * pass. Weights are tunable (DEFERRABLE per plan) — this is a documented seed. */ declare const STATIC_WEIGHTS: { readonly filesTouched: 0.25; readonly layersTouched: 0.2; readonly blastRadius: 0.3; readonly hotspotChurn: 0.1; /** Text-only signals — the only inputs available pre-diff. */ readonly descriptionLength: 0.1; /** A present spec / measurable acceptance *lowers* complexity (well-scoped). */ readonly specExists: 0.025; readonly acceptanceMeasurable: 0.025; }; /** * D4a: free static pass. Phase-aware (S3-001): pre-diff uses text-only signals * and caps confidence at `medium`; post-diff uses the full signal set. Pure — * never calls an LLM. `source` is stamped by the classifier, not here. */ declare function runStaticPass(signals: ComplexitySignals, phase: Phase): StaticVerdict; interface TiebreakResult { level: ComplexityLevel; confidence: 'high' | 'medium' | 'low'; } /** D4b: fast-tier structured tie-break. Never sets a tier; falls back conservatively on error. */ declare function llmTiebreak(provider: AnalysisProvider, prompt: string, fastModel?: string): Promise; declare const TIER_RANK: { fast: number; standard: number; strong: number; }; declare const RANK_TIER: readonly ["fast", "standard", "strong"]; /** * Documented seed threshold for the D5 high-blast veto. The spec lists * sensitive-path / core|types layer / public API explicitly but pins no numeric * blast threshold, so this is a plan-chosen seed — overridable later. */ declare const SENSITIVE_BLAST_THRESHOLD = 25; /** * D5: any sensitive-path / core|types layer / public API / high blast → force * `strong`, regardless of complexity (SC5). This is a HARD floor; downstream * budget clamping (D8) must not undercut it. */ declare function blastRadiusVeto(risk?: RoutingRisk): boolean; /** * Pre-budget tier resolution (Task 7): skillTierOverride → matrix → D5 veto → * low-confidence bump. Pure; the LLM never influences it (mirrors * outcome-eval/authority.ts). Task 8's `deriveRequiredTier` calls this then * applies the D8 budget clamp and D10 escalation floor. * * SC6: a `low`-confidence verdict degrades the tier UP one step (never below the * matrix default and never to a cheaper tier) — uncertainty routes to a more * capable model, never a Tier-A cheap one. */ declare function baseTier(complexity: ComplexityVerdict, risk: RoutingRisk | undefined, policy: RoutingPolicy, skillKey?: string): CapabilityTier; /** Default budget degrade threshold (% of cap) when policy omits one (D8). */ declare const DEFAULT_DEGRADE_AT_PCT = 90; /** * D8: budget-pressure tier clamp. Two thresholds: * * - **Soft (`degradeAtPct`, default 90%):** clamp the tier DOWN one step * (`strong→standard→fast`, `fast→fast`) — a lagging degrade signal. * - **Hard (100% of `capUsd`):** for `degrade`/`pause` policies, force the tier * all the way to `fast` — the strongest sound floor on the routing DECISION * (it is not an admission gate; the monotonic accumulator lags under * concurrency, so it cannot prevent overshoot, only route the cheapest tier * once the read crosses the cap). `human` mode is handled one layer up in * `AdaptiveRouter.route()`, which surfaces the unit to a steward instead of * routing; here it falls through to the soft one-step degrade. * * The D5 blast-radius veto is a HARD `strong` floor the clamp must NOT undercut * (SC5 says sensitive-path / core|types / publicApi force `strong` "regardless"), * so a vetoed request stays `strong` even at the hard cap — the veto guard sits * ABOVE both the hard-floor and the soft-step branches. No budget block ⇒ no clamp. */ declare function applyBudgetClamp(tier: CapabilityTier, risk: RoutingRisk | undefined, policy: RoutingPolicy, spend: { spentUsd: number; }): CapabilityTier; /** * Pure (complexity × risk × policy × spend × floor) → required tier. * * `baseTier` resolves override → matrix → D5 veto → low-confidence bump; the D8 * budget clamp lowers one step under budget pressure (never below the veto * floor); the D10 escalation floor raises the result but never lowers it. The * LLM never influences this — mirrors outcome-eval/authority.ts (TS-derived * authority). Referentially transparent; never mutates `policy`. */ declare function deriveRequiredTier(complexity: ComplexityVerdict, risk: RoutingRisk | undefined, policy: RoutingPolicy, spend: { spentUsd: number; }, escalationFloor: CapabilityTier, skillKey?: string): CapabilityTier; /** Flatten signals into the ComplexityVerdict.signals map, dropping undefined fields. */ declare function serializeSignals(s: ComplexitySignals): Record; /** * Escalation category for a triaged item — the reason a non-dispatched item was * held (SC-F2's closed set) or the routing bucket a dispatched item fell into. * Part of the `shapeKey` so precedent base-rates aggregate like-for-like work. */ type EscalationCategory = 'not-in-band' | 'unresolved-scope' | 'scope-too-large' | 'open-decision' | 'halted-fork' | 'precedent-contradicts' | 'error' | 'dispatchable'; /** Autonomy-ratchet stage in effect at dispatch (D14). 1 = human before execution. */ type RatchetStage = 1 | 2 | 3 | 4; /** * The pre-diff prediction, written by Phase 3 at dispatch. It is the confidence-capped * (S3-001) claim the post-diff retrospective later grades against ground truth. */ interface TriagePrediction { /** The pre-diff prediction being made (level + confidence-capped verdict). */ verdict: ComplexityVerdict; /** * An OPAQUE DIAGNOSTIC SNAPSHOT of the probe's signals at dispatch (today: the * verdict's static `signals` map) — kept for human forensics, NOT a grading input. * It is deliberately NOT the typed Phase-1 `ProbeLevers`: the Phase-4 retrospective * comparator grades on `verdict.level` + `scopeEstimate` ONLY (see * `retrospective.ts`) and never reads this field, so its shape can vary without * affecting the grade. Threading the full typed `ProbeLevers` end-to-end was * rejected as unneeded coupling (the marker's `ReadyCandidate` has no consumer for * it). If a future lever genuinely needs to inform grading, promote it to a typed * field here rather than smuggling it through this untyped bag. */ levers: Record; /** Predicted blast radius (estimated post-diff scope from the scope lever). */ scopeEstimate: number; /** Ratchet stage in effect when this item was dispatched. */ ratchetStage: RatchetStage; } /** * The post-diff outcome, written by Phase 4 at the retrospective. This is the only * gate that sees ground truth; its verdict is what feeds the precedent lever (D13). */ interface TriageOutcome { /** Full-strength post-diff verdict on the ACTUAL diff (confidence may reach high). */ actual: ComplexityVerdict; /** 0 = matched the prediction; >0 = mispredict magnitude (over-scope). */ exceededBy: number; /** True when the actual diff stayed within the predicted band. */ matched: boolean; } /** * The accreting triage record for one roadmap item. `prediction`/`outcome` are * absent until the owning phase writes its slice; a record with a populated * `outcome` is a graded, precedent-eligible record. */ interface TriageRecord { /** Stable item key (roadmap `External-ID`). */ externalId: string; /** Bucketing key for precedent/ratchet aggregation (see {@link shapeKey}). */ shapeKey: string; /** Written by Phase 3 at dispatch. */ prediction?: TriagePrediction; /** Written by Phase 4 at the retrospective. */ outcome?: TriageOutcome; /** ISO timestamp stamped by the writer (not by shapeKey). */ ts: string; } /** * The precedent lever (P1 injects this; P4 implements the real one). A pure read over * records sharing a `shapeKey` with a populated `outcome`: success rate = matched / total. * Absent history ⇒ `unknown` (the P1 degrade-empty path), which is simply "no records * for this shape yet" — never a block on emptiness. */ interface PrecedentLookup { /** * Measured autonomous-success rate for the given shape, or `unknown` when no * outcome-bearing records exist for it (cold-start). */ rateForShape(shapeKey: string): PrecedentRate; } /** * The precedent lever's result: a measured base-rate over recorded outcomes, or * `unknown` on cold-start (no outcome-bearing records for the shape yet). */ type PrecedentRate = { readonly kind: 'unknown'; } | { readonly kind: 'rate'; readonly matched: number; readonly total: number; readonly rate: number; }; /** * The bucketing key for precedent/ratchet aggregation: * `sortedLabels + '|' + escalationCategory + '|' + predictedLevel`. * * Deterministic and label-order-independent: labels are de-duplicated, trimmed of * empties, and sorted before joining, so `['a','b']` and `['b','a']` (and * `['b','a','a']`) all bucket identically. This is the quiet linchpin — too coarse * lumps unlike work (unsafe base-rates), too fine leaves every item its own bucket * (precedent perpetually `unknown`). Phase 4 calibration revisits this granularity * first; the definition lives here so P1 and P4 never disagree. */ declare function shapeKey(labels: readonly string[], category: EscalationCategory, level: ComplexityLevel): string; /** * The `dispatchable`-bucket shapeKey — the ONE convention prediction and outcome must agree on. * * A dispatched/approved item is (by definition) dispatchable, so its precedent/ratchet bucket is * `shapeKey(labels, 'dispatchable', level)`. That spelling was previously re-typed at three sites * (the probe's precedent lookup, the CLI approve gate, the orchestrator marker); if any drifted — * or keyed off a different `level` source — the PREDICTION and the OUTCOME would bucket into * DIFFERENT shapes and silently break precedent aggregation. Centralizing it here makes that * divergence impossible: all three call this with the SAME level (the probe's `verdict.level`). */ declare function dispatchableShapeKey(labels: readonly string[], level: ComplexityLevel): string; /** * Base-rate for a single shape over the supplied records. Counts only outcome-bearing * records matching `shapeKey`; returns `unknown` when there are none (cold-start). */ declare function aggregatePrecedent(records: readonly TriageRecord[], shapeKey: string): PrecedentRate; /** * Build a {@link PrecedentLookup} backed by a fixed record set — the real precedent * lever Phase 4 injects once outcomes exist. With no outcome-bearing records for a * shape it returns `unknown`, so an empty set yields the Phase-1 cold-start behavior * for every shape. */ declare function precedentLookupFromRecords(records: readonly TriageRecord[]): PrecedentLookup; /** * Extract candidate entity mentions (symbol/path names) from roadmap-row text. * * Pure and deterministic: returns de-duplicated candidates in first-seen order across * the four strategies. Returns an EMPTY array when the text carries none of the * structured shapes — the explicit "no entities found" signal P1 relies on (it becomes * `unresolved-scope`, never a fallback). Non-string / empty input ⇒ `[]`. */ declare function extractEntities(text: string): string[]; /** * One lever's result. Every lever either produces a concrete value or degrades to the * literal `'unknown'` (never throwing out of the probe), carrying an optional `reason` * that feeds the verdict's `rationale`. A degraded lever LOWERS corroboration; it never * forces a pass (proposal §"any lever that returns 'unknown' lowers the corroboration * score rather than forcing a pass"). */ interface LeverResult { /** The lever's value, or the literal `'unknown'` when it could not be determined. */ value: T | 'unknown'; /** Human-legible note (why it degraded, or a one-line summary of the finding). */ reason?: string; } /** A single resolved-entity scope datum from the graph seam. */ interface ResolvedEntity { /** The raw candidate that resolved (as emitted by extractEntities). */ candidate: string; /** The graph node id it resolved to. */ nodeId: string; /** Estimated blast radius (affected-node count) from the graph for this entity. */ blastRadius: number; } /** * The scope lever's value: the graph-grounded scope estimate. `resolved` is the subset * of candidates that ACTUALLY resolved in the graph — extractEntities over-generates * (it emits `e.g`/`i.e` noise and other non-symbols), so a non-empty extraction is NOT * itself "scope resolved" (follow-up S3). Only resolved candidates count. When `resolved` * is empty the scope is unresolved and the item is not dispatchable (`unresolved-scope`). */ interface ScopeEstimate { /** Candidates emitted by the extractor (pre-resolution). */ candidates: readonly string[]; /** The subset that resolved against the graph (empty ⇒ unresolved scope). */ resolved: readonly ResolvedEntity[]; /** Aggregate estimated blast radius across resolved entities (max, the worst case). */ blastRadius: number; /** Distinct architectural layers the resolved entities touch (feeds ComplexitySignals). */ layersTouched: number; /** Count of resolved entities (proxy for filesTouched into the static pass). */ filesTouched: number; } /** * The injected graph-resolution seam. The pure probe NEVER touches a GraphStore — it is * handed this. It maps a candidate entity string to a resolved node + blast radius, or * `null` when the candidate does not resolve (vague/non-symbol candidate). The wiring * layer (orchestrator/CLI) implements it over GraphStore + CascadeSimulator; tests stub * it. This is the S3 seam: raw-string resolution is NOT cleanly exposed by the graph * package, so it lives behind this injected boundary rather than inside the probe. */ interface GraphScope { /** * Resolve a candidate entity mention to a graph node + blast radius, or `null` if it * does not resolve. May be sync or async; the probe awaits it. MUST NOT throw — but * the probe still guards it (any throw degrades the scope lever to `unknown`). */ resolve(candidate: string): ResolvedEntity | null | Promise; } /** * The open-decisions self-assessment the semantic-read lever surfaces: the human/agent * boundary. Any open decision requiring human judgment (API shape, product tradeoff, * irreversible/outward action) → not dispatchable, regardless of complexity band. */ interface OpenDecision { /** Short description of the choice requiring human judgment. */ question: string; } /** The closed set of reasons a non-dispatchable item was held (mirrors EscalationCategory). */ type HoldReason$1 = 'not-in-band' | 'unresolved-scope' | 'scope-too-large' | 'open-decision' | 'read-incomplete' | 'precedent-contradicts' | 'error'; /** * Input to the pure probe for one roadmap item. Built from an `Issue` in the wiring * layer (reusing `buildTaskText`) — the probe stays orchestrator-free and unit-testable. */ interface ProbeInput { /** Stable item identity (roadmap External-ID). Absent ⇒ not dispatch-eligible. */ externalId: string; /** Pre-diff text-only signals (title+description length, spec/acceptance hints, prompt). */ taskText: RoutingTaskText; /** Candidate entity mentions from the item text (from `extractEntities`) — pre-resolution. */ entityCandidates: readonly string[]; /** Item labels (for shapeKey bucketing / precedent lookup). */ labels: readonly string[]; /** Whether the item's risk band is high (drives the classifier's standard-tier tie-break). */ riskHigh?: boolean; } /** The per-lever bundle carried on every verdict (SC-O1 traceability). */ interface ProbeLevers { /** Scope lever: graph-grounded scope estimate (or `unknown` on degrade). */ scope: LeverResult; /** Semantic-read lever: the local-model complexity read (or `unknown`). */ semanticRead: LeverResult; /** Open-decisions lever: surfaced human-judgment choices (or `unknown`). */ openDecisions: LeverResult; /** Precedent lever: measured base-rate for this shape (or `unknown` at cold-start). */ precedent: LeverResult; } /** * The probe's output for one item. `dispatchable` is TRUE only when scope is bounded AND * the semantic read agrees trivial|simple (the eligible band) AND confidence ≥ medium AND * there are no open decisions AND precedent does not contradict. Any shortfall or lever * error fails safe to `dispatchable:false` with a legible `holdReason` + `rationale`. */ interface TriageVerdict { /** The item this verdict is for. */ externalId: string; /** The corroborated complexity verdict (from the semantic read, or a conservative degrade). */ verdict: ComplexityVerdict; /** Authorization decision: only true when all levers corroborate (see the gate above). */ dispatchable: boolean; /** The single legible reason it was held (absent when `dispatchable`). SC-F2 closed set. */ holdReason?: HoldReason$1; /** All four levers' results (SC-O1 traceability). */ levers: ProbeLevers; /** Human-legible corroboration narrative (per-lever notes joined). */ rationale: string; } /** Tunable gate seeds (a subset of the Phase-0 `roadmap.autoTriage.thresholds`). */ interface ProbeConfig { /** Blast-radius ceiling for a "bounded" scope (seed). */ boundedScopeMax: number; /** Minimum semantic-read confidence to clear the gate (the S3-001 pre-diff bar). */ dispatchConfidence: 'low' | 'medium' | 'high'; /** * Precedent block bar: a MEASURED rate at-or-below this contradicts and holds the item. * Default 0 — only a shape that has NEVER succeeded autonomously blocks; `unknown` * (cold-start) never blocks. Conservative-by-construction (proposal §precedent lever). */ precedentBlockRate?: number; /** Minimum recorded sample before a precedent rate is trusted to block (seed). Default 1. */ precedentMinSample?: number; } /** Injected dependencies for the pure probe. All optional — absent ⇒ that lever degrades. */ interface ProbeDeps { /** The SEL analysis provider for the semantic-read + open-decisions levers. Absent ⇒ offline. */ provider?: AnalysisProvider; /** * Signals that a model IS available but its levers were intentionally DEFERRED for this run * (e.g. a cheap-first report pass that holds obviously-out-of-band items before spending an LLM * call). Only affects wording: a provider-less lever then reports "not evaluated (held before * the model pass)" instead of "no provider (offline)", so a deferred lever is not mistaken for a * missing/mis-configured provider. No effect on the gate — an unread lever never dispatches. */ modelDeferred?: boolean; /** The graph-resolution seam for the scope lever. Absent ⇒ scope degrades to unknown. */ graph?: GraphScope; /** The precedent lever. Absent ⇒ `unknown` (cold-start), which never blocks. */ precedent?: PrecedentLookup; /** Gate seeds. Absent fields fall back to conservative defaults. */ config?: Partial; /** Optional model overrides threaded to the classifier tie-break. */ models?: { fast?: string; standard?: string; }; } /** * Run the pure four-lever scoping probe for one roadmap item. Never throws. */ declare function runScopingProbe(input: ProbeInput, deps?: ProbeDeps): Promise; /** A candidate to rank: its identity plus the three pilot-score inputs. */ interface RankableCandidate { /** Stable item identity (roadmap External-ID) — the final deterministic tiebreak. */ externalId: string; /** Business impact (higher = more valuable). Also the secondary sort key (D4). */ impact: number; /** Confidence in the estimate (higher = surer). */ confidence: number; /** Estimated effort (higher = costlier). Guarded against divide-by-zero. */ effort: number; } /** * The roadmap-pilot score: `(impact × confidence) ÷ effort`. Total — an effort of 0 (or * negative) is clamped to a small positive so the score is finite rather than Infinity/NaN, * keeping the sort deterministic. */ declare function pilotScore(c: Pick): number; /** * Rank candidates by pilot score descending, breaking ties by IMPACT descending (SC7), * then by `externalId` ascending for a fully deterministic total order. Pure: returns a * new array; the input is never mutated. */ declare function rankTriageCandidates(candidates: readonly T[]): T[]; /** Self-assessed confidence in a fork recommendation. Mirrors the AMR tie-break enum. */ type ForkConfidence = 'high' | 'medium' | 'low'; /** * One decision fork the brainstorm surfaces: a design choice with mutually-exclusive * options. The runner asks the generator to recommend a default for each; a fork it can't * confidently recommend is the no-go trigger (SC2). `id` orders forks deterministically. */ interface Fork { /** Stable fork identity within a brainstorm (e.g. 'storage-backend'). Orders the loop. */ id: string; /** The question this fork decides (human-legible; carried into a halt handoff). */ question: string; /** The mutually-exclusive options considered (at least one; usually 2–3). */ options: readonly string[]; } /** * The generator's decision on a single fork: which option it recommends, how confident it * is (AFTER any self-consistency downgrade), and why. Mirrors `tiebreak.ts` structured * output `{ recommendation, confidence, rationale }` — the schema the SEL provider fills. */ interface ForkDecision { /** The fork this decision answers. */ fork: Fork; /** The recommended option (should be one of `fork.options`). */ recommendation: string; /** * Self-assessed confidence — the gate input. `high` is the ONLY value that auto-accepts; * `medium`/`low` halt (SC2). The generator MUST force this to `low` when self-consistency * sampling shows the recommendation flipped across samples (overconfidence hardening). */ confidence: ForkConfidence; /** Why this option, in one or two sentences (carried into the spec draft / halt handoff). */ rationale: string; } /** * The injected seam that generates + decides one fork at a time. The pure runner calls this * `depth.maxForks` times (or until it returns `null`, signalling "no more forks — done"). * * `index` is the 0-based position in the brainstorm (the runner tracks depth). Returning * `null` means the brainstorm has enumerated every fork it needs — the runner then COMPLETES. * A thrown error is caught by the runner and mapped to `halted{ reason: 'error' }` (SC5): * the generator is allowed to throw; the runner is total. * * Overconfidence hardening (self-consistency) is the GENERATOR's contract: it samples the * underlying model N times per fork and, if the recommendation flips, returns the decision * with `confidence: 'low'`. Keeping it in the generator (behind this seam) makes it testable * with a stub that reports a flip → the pure runner still just reads the enum and halts. */ interface ForkGenerator { /** * Produce the next fork's decision, or `null` when there are no more forks. May be async. * MAY throw — the runner catches it and halts with `reason: 'error'`. */ next(index: number, priorDecisions: readonly ForkDecision[]): ForkDecision | null | Promise; } /** * The depth budget: how many forks the brainstorm may explore, scaled by the Phase-1 * complexity estimate (SC3). A `trivial` item runs a shallow pass; `simple` a fuller one. * Bounded so a typo never gets the full 4-phase treatment. The runner NEVER exceeds * `maxForks` — if the generator keeps producing forks past the budget, the loop stops and * completes with what it has (the budget is a ceiling, not a required count). */ interface DepthBudget { /** Hard ceiling on forks explored. Bounded (e.g. trivial→2, simple→4). Always ≥ 1. */ maxForks: number; } /** Map a Phase-1 complexity level to a bounded brainstorm depth (SC3). */ declare const DEPTH_BY_LEVEL: Record; /** Resolve the bounded depth budget for a complexity level (SC3 depth-scaling). */ declare function depthForLevel(level: ComplexityLevel): DepthBudget; /** * A proposal-shaped spec draft accumulated from the accepted fork decisions on clean * completion. Intentionally lightweight (the wiring layer renders it to a proposal.md); * the pure core only accumulates the resolved decisions + a title/summary. */ interface SpecDraft { /** The item this spec is for (roadmap External-ID / identifier). */ externalId: string; /** A short title (from the item). */ title: string; /** One-line summary of the item. */ summary: string; /** Every fork the brainstorm resolved, in order — the spec's decision record. */ decisions: readonly ForkDecision[]; } /** * The input to one brainstorm run: the item identity + text the generator reasons over, * plus the Phase-1 complexity level that scales the depth. Pure — no provider, no IO. */ interface BrainstormInput { /** Stable item identity (roadmap External-ID / identifier). */ externalId: string; /** Short title (carried into the spec draft). */ title: string; /** One-line summary / description (carried into the spec draft + fed to the generator). */ summary: string; /** The Phase-1 complexity level — scales the depth budget (SC3). */ level: ComplexityLevel; } /** Why a brainstorm halted — the closed no-go set handed to a human. */ type HaltReason = 'low-confidence' | 'error'; /** * The single outcome of a brainstorm run (SC1): either a clean COMPLETION carrying the spec * draft, or a HALT carrying the fork it stopped at + the reason. Exactly one of the two. * A halt is a REAL handoff — never a rubber-stamped stub (proposal §"a halt is a real * handoff, not a rubber stamp"). */ type BrainstormOutcome = { kind: 'completed'; spec: SpecDraft; } | { kind: 'halted'; fork: Fork; reason: HaltReason; detail: string; }; /** * Drive an autonomous brainstorm for one item. Returns exactly one `BrainstormOutcome` * (SC1): `completed{ spec }` when every explored fork was confidently recommended, or * `halted{ fork, reason }` at the first fork it couldn't. NEVER throws (SC5). * * @param input the item identity + text + Phase-1 level * @param generator the injected fork-decision seam (the thing that calls the model) * @param depth the bounded fork budget (SC3 — scaled from the complexity level upstream) */ declare function runAutoBrainstorm(input: BrainstormInput, generator: ForkGenerator, depth: DepthBudget): Promise; /** Ordinal rank of each complexity band; a positive delta = the diff came in harder. */ declare const LEVEL_RANK: Record; /** * Blast-radius overrun tolerance. The actual blast radius may exceed the predicted * scope estimate by up to `BLAST_TOLERANCE_FACTOR×` PLUS `BLAST_TOLERANCE_ABS` before * it counts as over-scope. The additive floor keeps a tiny predicted scope (e.g. 1–2) * from tripping on ordinary noise; the factor bounds the overrun on larger estimates. * Conservative on purpose (plan §Concerns: bias `exceededBy` toward flagging). */ declare const BLAST_TOLERANCE_FACTOR = 1.5; declare const BLAST_TOLERANCE_ABS = 2; /** The comparator's verdict for one graded item. */ interface RetrospectiveComparison { /** True iff the actual diff stayed WITHIN the predicted band + scope (SC2). */ matched: boolean; /** * Mispredict magnitude. `0` when matched. When over-scope it is the level-band * delta (≥ 1) when the level exceeded, else `1` for a blast-only overrun — always * a positive integer so a graded outcome carries a legible severity. */ exceededBy: number; /** `verify` on a match (surface for human verify per stage); `block-escalate` on a mismatch/error. */ action: 'verify' | 'block-escalate'; } /** Tunable comparator thresholds (wired from `roadmap.autoTriage.thresholds`, SC2). */ interface RetrospectiveConfig { /** * The level-band delta that counts as a mispredict. Default 1 (any band over the * prediction blocks). Raising it tolerates a wider band drift before escalating — * conservative default keeps the honesty check tight (plan §Concerns). */ exceededByBands: number; } /** The default comparator config: any band over the prediction is a mispredict. */ declare const DEFAULT_RETROSPECTIVE_CONFIG: RetrospectiveConfig; /** * Compare the stored pre-diff prediction to the full-strength post-diff verdict. * * Returns `{ matched, exceededBy, action }`. Match ⇒ `verify`; mismatch ⇒ * `block-escalate`. A missing/garbled/invalid prediction OR actual fails safe to * `block-escalate` (SC7) — absence is never a pass. */ declare function compareToPrediction(prediction: TriagePrediction | undefined | null, postDiffVerdict: ComplexityVerdict | undefined | null, config?: RetrospectiveConfig): RetrospectiveComparison; /** The autonomy-ratchet stages v1 can resolve. 3/4 are deferred post-v1 (SC4). */ type V1Stage = 1 | 2; /** v1's ceiling: the resolver never returns above this (SC4 — stages 3/4 deferred). */ declare const V1_MAX_STAGE: 2; /** One graded outcome for a shape. `matched` is the grade; `ts` fixes chronology. */ interface RatchetOutcome { /** True iff the post-diff verdict stayed within the prediction (the comparator's `matched`). */ matched: boolean; /** * ISO timestamp the outcome was graded (FOLLOW-UP 1 / safety). The mispredict-reset keys on the * CHRONOLOGICALLY-latest outcome, so `resolveStage` sorts by `ts` first. Optional for * back-compat: the sort is a stable no-op when `ts` is absent (already-chronological callers). */ ts?: string; } /** Advancement rules. Conservative defaults; the caller may override per policy. */ interface RatchetConfig { /** Minimum trailing-window success-rate required to advance from stage 1 → 2 (SC6). */ threshold: number; /** Minimum number of graded outcomes in the window before advancement is even considered. */ minSample: number; /** Trailing-window size: only the most recent `window` outcomes count (recency ⇒ a mispredict resets). */ window: number; } /** * Conservative default policy: a shape must show ≥ 90% matches over its most recent * 10 graded outcomes (at least `minSample` of them) before it earns stage 2. A single * recent mispredict inside a 10-wide window is 90% → still at the edge; two drop it below. */ declare const DEFAULT_RATCHET_CONFIG: RatchetConfig; /** * Resolve the autonomy stage a shape has EARNED from its graded history. * * Returns stage 1 (the safe default) at cold-start, below `minSample`, or whenever * the trailing-window success-rate is under `threshold` (a recent mispredict resets). * Returns stage 2 once the evidence clears the bar. NEVER returns above * {@link V1_MAX_STAGE} (SC4) — the value is clamped as a belt-and-suspenders guard so * a future config change can't accidentally leak a deferred stage. */ declare function resolveStage(history: readonly RatchetOutcome[], config?: RatchetConfig): V1Stage; /** * The escalation categories that may auto-execute (mirror of the orchestrator's * `EscalationConfig.autoExecute` default — proposal §"Dispatch path" reuses the * shipped escalation categories rather than inventing a parallel taxonomy). An * item whose category is NOT in this set stays human even after a human go (SC3): * `guided-change` is signal-gated and `full-exploration` is always-human. * * Defined as a frozen set here (not imported from orchestrator) to keep this gate * in the intelligence layer; the orchestrator's escalation default is the source of * truth for the VALUES, and the T3 wiring is where the two are reconciled against * the live `config.agent.escalation.autoExecute`. */ declare const AUTO_EXECUTE_CATEGORIES: ReadonlySet; /** * One ready candidate presented to the go/no-go gate. Built in the wiring layer from * a Phase-2 spec-bearing, re-scored-dispatchable item; the gate reads only the three * fields that decide authorization. */ interface GoNoGoCandidate { /** Stable item key (roadmap External-ID). */ externalId: string; /** The item's escalation category (the routing bucket it fell into). */ category: ScopeTier; /** * Whether a human has EXPLICITLY approved this item in the batched go/no-go. The * quiet linchpin of stage 1: absent this flag no item is ever approved — the gate * never manufactures a go. Set by the `triage approve` command (Phase 3 T4). */ humanApproved: boolean; } /** Why a candidate was held rather than approved. A closed, legible set (SC-F2 style). */ type HoldReason = /** The candidate's EFFECTIVE stage is deferred post-v1 (3-4); v1 caps at stage 2. */ 'ratchet-stage-unsupported' /** The item's category is not auto-executable (guided-change / full-exploration). */ | 'not-auto-executable' /** Auto-executable, but no human has given the go yet (stage-1 default hold). */ | 'awaiting-human-go'; /** A held candidate: the item plus the single legible reason it did not pass. */ interface HeldCandidate { externalId: string; category: ScopeTier; reason: HoldReason; } /** An approved candidate carrying the EVIDENCE-DERIVED stage its shape earned (SC6). */ interface ApprovedCandidate extends GoNoGoCandidate { /** * The effective autonomy stage this candidate's SHAPE earned from its recorded history * (`min(resolveStage(history), configuredCeiling, 2)`). 1 = human-before-execution (the * cold-start default); 2 = auto-execute + required human-verify (v1's ceiling). This governs * only DOWNSTREAM match-handling/advancement — NOT the authorization above (which is stage- * independent: humanApproved AND auto-executable). The marker stamps it onto the prediction. */ effectiveStage: 1 | 2; } /** The gate's output: the partition of the ready set into approved vs held. */ interface GoNoGoDecision { /** Items cleared for the marker: human-approved AND auto-executable, with the earned stage. */ approved: ApprovedCandidate[]; /** Everything else, each carrying the reason it was held to a human. */ held: HeldCandidate[]; } /** * One ready candidate presented to the PER-SHAPE go/no-go gate: the base candidate plus the * effective autonomy stage its shape earned from recorded evidence. The caller resolves the * stage per-shape (via the pure `resolveStage` over that shape's history) so the ratchet * advances INDEPENDENTLY per shape (SC6), never as one batch stage. */ interface StagedGoNoGoCandidate extends GoNoGoCandidate { /** `min(resolveStage(historyForShape), configuredCeiling, 2)`. Cold-start ⇒ 1. */ effectiveStage: RatchetStage; } /** * Pure go/no-go partition with a PER-CANDIDATE evidence-derived stage (SC6). * * The AUTHORIZATION rule is stage-INDEPENDENT and unchanged from Phase 3: an item is * `approved` iff its category is in {@link AUTO_EXECUTE_CATEGORIES} AND a human explicitly * approved it. The stage does NOT relax this — the human-go requirement holds at every stage. * The stage only rides ALONG on an approved item (`effectiveStage`) to govern downstream * match-handling/advancement; a candidate whose earned stage is deferred post-v1 (3-4) is * refused as `ratchet-stage-unsupported` (a fail-safe belt: the caller already clamps to ≤ 2, * so this should be unreachable, but a mis-set stage must never loosen the gate). * * Ordering of the hold reasons is deliberate and matches Phase 3: the stage guard is checked * first (a deferred stage refuses regardless), then the category gate (structural), then the * human-go gate — so an approved non-autoExecute item still reads `not-auto-executable`. */ declare function resolveGoNoGoStaged(candidates: readonly StagedGoNoGoCandidate[]): GoNoGoDecision; /** * Pure go/no-go partition for a UNIFORM ratchet stage (Phase 3 shape, retained for callers * that have not yet resolved per-shape evidence). Delegates to {@link resolveGoNoGoStaged} by * stamping the SAME `stage` on every candidate — so the authorization + hold-reason ordering * is defined in exactly one place. Passing a deferred stage (3-4) refuses the whole batch, and * stage 1 reproduces Phase-3 behavior byte-for-byte. */ declare function resolveGoNoGo(candidates: readonly GoNoGoCandidate[], stage: RatchetStage): GoNoGoDecision; export { ACCEPTANCE_EVAL_SYSTEM_PROMPT, AUTO_EXECUTE_CATEGORIES, type AbandonedSkill, type AcceptanceEvalInput, AcceptanceEvaluator, type AcceptanceEvaluatorOptions, type AcceptanceVerdict, type AffectedSystem, type AnalysisImage, type AnalysisProvider, type AnalysisRequest, type AnalysisResponse, AnthropicAnalysisProvider, type ApprovedCandidate, type Authority, BLAST_TOLERANCE_ABS, BLAST_TOLERANCE_FACTOR, type BlastRadius, type BlindSpot, type BrainstormInput, type BrainstormOutcome, type CanaryAdapter, type CanaryDegradeReason, type CanaryExec, type CanaryFinding, type CanaryFrameworkInfo, type CanaryProbe, type CanaryReader, type CanaryRunOutcome, type CanaryRunRecord, type CanaryTestResult, type ClassifyInput, ClaudeCliAnalysisProvider, type ComplexityScore, type ComplexitySignals, type Confidence$1 as Confidence, type CriterionJudgment, DEFAULT_DEGRADE_AT_PCT, DEFAULT_RATCHET_CONFIG, DEFAULT_RETROSPECTIVE_CONFIG, DEPTH_BY_LEVEL, type DepthBudget, type EnrichedSpec, type EscalationCategory, type ExecutionOutcome, ExecutionOutcomeConnector, type ExpertiseLevel, type FailingSkill, type Finding, type Fork, type ForkConfidence, type ForkDecision, type ForkGenerator, type FrameworkRecommendation, GUARDIAN_ANALYSIS_SCHEMA, GUARDIAN_ANALYSIS_VERSION, type GitHubIssue, type GoNoGoCandidate, type GoNoGoDecision, type HoldReason as GoNoGoHoldReason, type GoldenBaseline, type GraphScope, GraphValidator, type GuardianAnalysis, type GuardianFileCoverage, type GuardianSeverity, type GuardianVerdict, type HaltReason, type HeldCandidate, type HoldReason$1 as HoldReason, IntelligencePipeline, type JiraIssue, type JudgedAgainst, LEVEL_RANK, type LeverResult, type LinearIssue, type LlmAcceptanceVerdict, type LlmVerdict, type ManualInput, type Measurability, OUTCOME_EVAL_SYSTEM_PROMPT, OpenAICompatibleAnalysisProvider, type OpenDecision, type OutcomeEvalInput, OutcomeEvaluator, type OutcomeEvaluatorOptions, type OutcomeIngestResult, type OutcomeVerdict, type PersonaEffectivenessScore, type PersonaRecommendation, PeslSimulator, type Phase, type PrecedentLookup, type PrecedentRate, type PreprocessResult, type ProbeConfig, type ProbeDeps, type ProbeInput, type ProbeLevers, type ProfileStore, RANK_TIER, type RankableCandidate, type RatchetConfig, type RatchetOutcome, type RatchetStage, type RawWorkItem, type RegressionAuthority, type Confidence as RegressionConfidence, type RegressionVerdictKind, type ResolvedEntity, type ResolvedSection, type RetrospectiveComparison, type RetrospectiveConfig, type RubricCriterion, SENSITIVE_BLAST_THRESHOLD, SKILL_REGRESSION_SYSTEM_PROMPT, STATIC_WEIGHTS, type ScopeEstimate, type SimulationResult, type SkillEffectivenessScore, SkillRegressionEvaluator, type SkillRegressionEvaluatorOptions, type SkillRegressionFixture, type SkillRegressionInput, type JudgeResponse as SkillRegressionJudgeResponse, type SkillRegressionVerdict, type SpecDraft, type SpecializationEntry, type SpecializationProfile, type SpecializationScore, type StagedGoNoGoCandidate, type StaticVerdict, TIER_RANK, type TaskType, type TemporalConfig, type TiebreakResult, type TriageOutcome, type TriagePrediction, type TriageRecord, type TriageVerdict, UAT_SIGNOFF_SOURCE, type UatItemDisposition, type UatOverallDecision, type UatSignoffInput, type UatSignoffItem, UatSignoffRecorder, type V1Stage, V1_MAX_STAGE, type Verdict, type WeightedRecommendation, acceptanceVerdictSchema, aggregateAtK, aggregatePrecedent, applyBudgetClamp, baseTier, blastRadiusVeto, buildUserPrompt$1 as buildAcceptanceUserPrompt, buildUserPrompt as buildSkillRegressionUserPrompt, buildSpecializationProfile, buildUserPrompt$2 as buildUserPrompt, canaryRunRecordSchema, canaryTestResultSchema, classify, compareToPrediction, computeBaselineScore, computeExpertiseLevel, computeHistoricalComplexity, computePersonaEffectiveness, computeSemanticComplexity, computeSkillEffectiveness, computeSpecialization, computeStructuralComplexity, createCanaryAdapter, criterionJudgmentSchema, decayWeight, depthForLevel, deriveAcceptanceAuthority, deriveAuthority, deriveRegressionAuthority, deriveRegressionVerdict, deriveRequiredTier, detectAbandonedSkills, detectBlindSpots, detectFailingSkills, dispatchableShapeKey, enrich, extractEntities, findingSchema, fixtureSchema, githubToRawWorkItem, guardianAnalysisSchema, guardianFileLines, guardianFlags, jiraToRawWorkItem, judgeResponseSchema, linearToRawWorkItem, llmTiebreak, loadProfiles, manualToRawWorkItem, parseFixture, pilotScore, precedentLookupFromRecords, rankTriageCandidates, readGuardianAnalyses, recommendPersona, refreshProfiles, regressionFloor, resolveGoNoGo, resolveGoNoGoStaged, resolveSection, resolveStage, resolveTestCommand, runAutoBrainstorm, runGraphOnlyChecks, runLlmSimulation, runScopingProbe, runStaticPass, saveProfiles, score as scoreCML, scoreToConcernSignals, serializeFixture, serializeSignals, shapeKey, summarizeGuardian, temporalSuccessRate, toRawWorkItem, toUatExecutionOutcome, verdictSchema, weightedRecommendPersona, weightedScore };