/** * Change footprint projection + pairwise hazard classification * (change: add-change-footprint-projection, the foundation of * PARALLEL-WORK-COORDINATION). * * Generalizes `blast_radius` from "one symbol" to "a declared task": given a * caller-supplied {@link TaskDescriptor}, compute a deterministic three-region * {@link Footprint} (write / read / affected) plus a soft co-change annotation, * and a pure {@link classifyHazard} over two footprints returning the strongest * data-hazard between them (WAW / shared-append / RAW / WAR / soft-coupling / * none). * * Design invariants (mirrors the proposal's Decision + Scope contract): * - **Borrow-checker, not a lock.** Everything here is a pure function of the * graph state, the change-coupling store, and the descriptor — no persistence, * no clock, no new MCP tool, no graph-schema change. The consumer * (`plan_parallel_work`, proposal 2) holds any state. * - **Declared, not inferred.** The write-set is the caller's declared seeds, * normalized to enclosing scope — never a prediction of the edits an agent * will make. It is reported as advisory with a known-unknowable disclosure. * - **Reuse, don't reinvent.** The affected-set is the existing backward * reachability (`bfs` over the same adjacency `analyze_impact`/`blast_radius` * use); the read-set is the existing call-distance-scoped forward closure * (`weightedBfs`); coupling-neighbors come from the existing change-coupling * store thresholds. No new traversal semantics. * - **Honesty over coverage.** An unresolved seed yields an empty footprint with * an explicit note, never a fabricated region. Ambient (ubiquitous) symbols * are excluded from the read-set and from generating RAW edges, but a * deliberate *write* of an ambient symbol still creates a hazard. */ import type { SerializedCallGraph } from '../../analyzer/call-graph.js'; import type { FileChangeCoupling } from '../../provenance/change-coupling.js'; /** Whether a declared write is a pure append to a registration site or a modify of existing code. */ export type WriteMode = 'append' | 'modify'; /** * A unit of proposed work, described by the *caller* (an agent or human) — never * invented by the system. At least one seed (symbol or file) is required. */ export interface TaskDescriptor { /** Caller-chosen id, unique within a single planning call. */ id: string; /** Symbol names or canonical ids the task will edit. */ seedSymbols?: string[]; /** File paths the task will edit (every symbol in the file enters the write-set). */ seedFiles?: string[]; /** Optional free text — used only by the caller to widen sparse seeds via semantic search, never to guess edits. */ intent?: string; /** * Caller-declared annotation that this task's seeds are pure *additions* to a * registration site (a new switch case, a new array/registry entry) rather * than a change to existing code. Default `modify` (the conservative * assumption). Never inferred by the system. */ writeMode?: WriteMode; } /** A symbol in a footprint region. */ export interface FootprintMember { id: string; name: string; filePath: string; } /** A write-set member, carrying its declared write mode. */ export interface WriteMember extends FootprintMember { writeMode: WriteMode; } /** * The footprint of one task: three structural regions plus a soft co-change * annotation. A deterministic function of (graph, coupling, descriptor). */ export interface Footprint { taskId: string; /** * The declared region the task is expected to modify: its seeds resolved to * symbols and normalized to enclosing scope, each carrying its declared * `writeMode`. Advisory — see {@link Footprint.advisory}. */ writeSet: WriteMember[]; /** * Forward call closure (callees/dependencies) of the write-set, bounded by the * existing call-distance scoping and with ambient symbols excluded. The region * the task reads to function. */ readSet: string[]; /** * Ambient (ubiquitous, high-fan-in) symbols that were in the forward closure * but excluded from {@link Footprint.readSet}. Disclosed for transparency and * retained so that a peer task's deliberate *write* of one still raises a RAW * hazard (the ambient exclusion applies to read membership, not to a write). */ ambientReadDeps: string[]; /** * Backward reachability (callers) of the write-set — equivalent to the blast * radius of the write-set. Informational human-facing output only; NOT an * input to {@link classifyHazard}. */ affectedSet: string[]; /** * Files that co-change with the write-set's files above the existing support * and confidence thresholds, with no requirement of a static call relation. * A separate advisory annotation — never merged into the static regions. */ couplingNeighbors: string[]; /** Seeds that resolved to nothing in the graph (unknown symbol / untracked file). */ unresolvedSeeds: string[]; /** The write-set is always a *declared* region, not a prediction. */ advisory: true; /** Known-unknowable disclosure carried with every footprint. */ disclosure: string; } /** The data-hazard between two footprints. */ export type HazardKind = 'WAW' | 'shared-append' | 'RAW' | 'WAR' | 'soft-coupling' | 'none'; export interface HazardVerdict { kind: HazardKind; /** * The witnessing symbol ids (or file paths, for `WAR` same-file and * `soft-coupling`) that explain the verdict. Sorted for determinism. */ witnesses: string[]; /** For the ordering hazard `RAW`: which task must run after which. */ direction?: 'A after B' | 'B after A' | 'bidirectional'; } export interface FootprintOptions { /** Hop-depth for the backward (affected) reachability. Default {@link FOOTPRINT_AFFECTED_MAX_DEPTH}. */ affectedMaxDepth?: number; /** Call-distance bound for the forward (read) closure. Default {@link FOOTPRINT_READ_MAX_DISTANCE}. */ readMaxDistance?: number; /** Fan-in percentile above which a symbol is treated as ambient. Default {@link AMBIENT_FANIN_PERCENTILE}. */ ambientFanInPercentile?: number; /** * Absolute fan-in threshold for ambient classification, overriding the * percentile when set (a symbol is ambient iff `fanIn > this`). Primarily for * deterministic tests; production uses the percentile. */ ambientFanInThreshold?: number; /** * Extra seed ids the caller derived from `intent` via semantic search. Kept * out of the pure core so this function stays deterministic and I/O-free; the * caller (proposal 2) performs the search and passes the resulting candidate * ids here. Never fabricates a write target — only widens declared seeds. */ extraSeedIds?: string[]; /** * Change-coupling lookup, injected so the core stays pure and testable. The * real consumer passes `edgeStore.getChangeCouplingForFiles`; tests pass a * fixture. When absent, `couplingNeighbors` is empty. */ couplingLookup?: (files: string[]) => FileChangeCoupling[]; } /** Hop-depth for the backward (affected) reachability — mirrors `analyze_impact`'s default. */ export declare const FOOTPRINT_AFFECTED_MAX_DEPTH = 2; /** * Call-distance bound for the forward (read) closure. Smaller than pathfind's * `PATH_MAX_DISTANCE` (12) because a read-set is the task's near dependencies, * not a whole-program reach: 6 admits a handful of strongly-resolved hops (cost * 1 each) or fewer weakly-resolved ones (cost 2–4). */ export declare const FOOTPRINT_READ_MAX_DISTANCE = 6; /** * Fan-in percentile above which a symbol is "ambient" (ubiquitous infrastructure * — a logger, a directory validator, the call-graph primitives). The top ~1% of * symbols by fan-in carry no real ordering signal and would bloat read-sets * toward the whole graph, so they are excluded from read-sets and from * generating RAW edges. */ export declare const AMBIENT_FANIN_PERCENTILE = 0.99; /** * The deterministic ambient fan-in threshold for a graph: a symbol is ambient * iff its fan-in strictly exceeds this value. Derived from the fan-in * distribution at the configured percentile (or an explicit override). Symbols * with the value at the percentile index are NOT ambient — only those above it. */ export declare function ambientFanInThreshold(graph: SerializedCallGraph, opts?: FootprintOptions): number; /** * Compute the footprint of one task descriptor over a serialized call graph. * Pure and deterministic for a fixed (graph, coupling, descriptor) — the same * inputs always yield a byte-identical footprint. */ export declare function computeFootprint(graph: SerializedCallGraph, descriptor: TaskDescriptor, opts?: FootprintOptions): Footprint; /** * Classify the strongest data-hazard between two footprints. A pure function: * the verdict is a deterministic, byte-identical function of the two footprints. * * Precedence (strongest first): WAW > RAW > shared-append > WAR > soft-coupling * > none. RAW outranks shared-append because an ordering constraint is stronger * than a low-risk "appends merge trivially" advisory. */ export declare function classifyHazard(a: Footprint, b: Footprint): HazardVerdict; //# sourceMappingURL=change-footprint.d.ts.map