Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/analysis/src/dead-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ function compareDeadSymbol(a: DeadSymbol, b: DeadSymbol): number {
}

async function fetchSymbols(store: IGraphStore): Promise<readonly SymbolRow[]> {
// AC-A-6b: typed `listNodes({kinds: SYMBOL_KINDS})` replaces a `WHERE kind
// Typed `listNodes({kinds: SYMBOL_KINDS})` replaces a `WHERE kind
// IN (...)` raw SELECT. The narrowed kind set guarantees every returned
// node carries `start_line`/`is_exported` (Function/Method/etc. are all
// LocatedNodes), so the JS-side coercion is a one-shot cast.
Expand Down Expand Up @@ -271,7 +271,7 @@ async function fetchReferrers(
ids: readonly string[],
): Promise<readonly ReferrerRow[]> {
if (ids.length === 0) return [];
// AC-A-6b: typed `listEdges({types, toIds})` replaces a raw `WHERE r.to_id
// Typed `listEdges({types, toIds})` replaces a raw `WHERE r.to_id
// IN (...) AND r.type IN (...)` SELECT joined to nodes. The TS-side join
// hydrates source-file metadata via `listNodes({ids})`.
const edges = await store.listEdges({
Expand Down Expand Up @@ -301,7 +301,7 @@ async function fetchCommunityMembership(
ids: readonly string[],
): Promise<readonly MembershipRow[]> {
if (ids.length === 0) return [];
// AC-A-6b: typed `listEdgesByType("MEMBER_OF", {fromIds})` replaces a
// Typed `listEdgesByType("MEMBER_OF", {fromIds})` replaces a
// `WHERE type = 'MEMBER_OF' AND from_id IN (...)` raw SELECT.
const edges = await store.listEdgesByType("MEMBER_OF", { fromIds: ids });
const out: MembershipRow[] = [];
Expand Down
8 changes: 4 additions & 4 deletions packages/analysis/src/detect-changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function hunkOverlaps(
}

async function symbolsForFile(store: IGraphStore, filePath: string): Promise<readonly SymbolRow[]> {
// AC-A-6b: typed `listNodes({filePath})` replaces a `WHERE file_path = ?
// Typed `listNodes({filePath})` replaces a `WHERE file_path = ?
// AND kind NOT IN ('File','Folder') AND start_line IS NOT NULL AND
// end_line IS NOT NULL` raw SELECT. The finder narrows to one file at the
// adapter layer; the kind exclusion + line-presence guard run in JS.
Expand Down Expand Up @@ -135,7 +135,7 @@ async function processesForSymbols(
// participates in the process. Find the set of distinct Process ids that
// have an edge into any of the affected symbols.
//
// AC-A-6b: typed `listEdgesByType("PROCESS_STEP", {toIds})` replaces the
// Typed `listEdgesByType("PROCESS_STEP", {toIds})` replaces the
// raw `WHERE r.type = 'PROCESS_STEP' AND r.to_id IN (...)` SELECT. The
// `kind = 'Process'` predicate from the JOIN is enforced when we hydrate
// the process metadata below.
Expand All @@ -145,7 +145,7 @@ async function processesForSymbols(
);
if (candidateProcessIds.length === 0) return [];

// AC-A-6b: typed `listNodes({ids, kinds:["Process"]})` replaces the
// Typed `listNodes({ids, kinds:["Process"]})` replaces the
// `WHERE id IN (...) AND kind = 'Process'` lookup.
const processNodes = await store.listNodes({
ids: candidateProcessIds,
Expand All @@ -159,7 +159,7 @@ async function processesForSymbols(
.filter((s) => s.length > 0);
const entryMap = new Map<string, string>();
if (entryIds.length > 0) {
// AC-A-6b: typed `listNodes({ids})` replaces the bulk `WHERE id IN (...)`
// Typed `listNodes({ids})` replaces the bulk `WHERE id IN (...)`
// entry-point file_path lookup.
const entryNodes = await store.listNodes({ ids: entryIds });
for (const node of entryNodes) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Synthetic 2-repo cross-repo-contracts fixture (AC-M6-5 quickcheck).
* Synthetic 2-repo cross-repo-contracts quickcheck fixture.
*
* Models a producer/consumer pair across two repos in the same group:
* - `api-svc` — HTTP route producer + gRPC service producer
Expand All @@ -18,9 +18,9 @@
* consumer to producer; consumer_of points from producer to consumer)
* 4. Two runs on the same input are byte-identical (determinism contract)
*
* All `repo_uri` values follow the Sourcegraph host/path scheme codified
* by AC-M6-1 (`packages/core-types/src/nodes.ts:524-552`) — see ADR 0012
* for the rationale.
* All `repo_uri` values follow the Sourcegraph host/path scheme — see
* `packages/core-types/src/nodes.ts` for the typed RepoNode and ADR
* 0012 for the rationale.
*/

import type { ComputeCrossRepoLinksOpts } from "../cross-repo-links.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Quickcheck — populated-case 2-repo fixture (AC-M6-5).
* Quickcheck — populated-case 2-repo fixture.
*
* The existing `cross-repo-links.test.ts` covers the empty + alpha-sort
* + dedup + skip + error paths. This file pins the populated-case
Expand Down
30 changes: 16 additions & 14 deletions packages/analysis/src/impact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ async function resolveByName(
name: string,
filters: { readonly filePath?: string; readonly kind?: string },
): Promise<readonly NodeRef[]> {
// AC-A-6b: typed finder replaces a `WHERE name = ?` raw SELECT.
// Typed finder replaces a `WHERE name = ?` raw SELECT.
const nodes = await store.listNodesByName(name);
const all = nodes.map(nodeToNodeRef);
// Prefer resolved nodes over unresolved placeholder Property rows when both
Expand All @@ -102,7 +102,7 @@ async function resolveByName(
}

async function resolveById(store: IGraphStore, id: string): Promise<NodeRef | undefined> {
// AC-A-6b: typed `listNodes({ids})` replaces a `WHERE id = ? LIMIT 1` raw SELECT.
// Typed `listNodes({ids})` replaces a `WHERE id = ? LIMIT 1` raw SELECT.
const nodes = await store.listNodes({ ids: [id], limit: 1 });
const first = nodes[0];
return first ? nodeToNodeRef(first) : undefined;
Expand All @@ -124,7 +124,7 @@ async function hydrateNodes(
): Promise<ReadonlyMap<string, NodeRef>> {
const out = new Map<string, NodeRef>();
if (ids.length === 0) return out;
// AC-A-6b: typed `listNodes({ids})` replaces a `WHERE id IN (?,?,...)` raw SELECT.
// Typed `listNodes({ids})` replaces a `WHERE id IN (?,?,...)` raw SELECT.
// The adapter de-dupes the input set internally so callers can pass repeats.
const nodes = await store.listNodes({ ids });
for (const node of nodes) {
Expand Down Expand Up @@ -185,10 +185,11 @@ async function relationsByEdge(
toIds.add(to);
}
if (fromIds.size === 0 || toIds.size === 0) return map;
// AC-A-6b: typed `listEdges({fromIds, toIds})` replaces a `WHERE from_id IN
// (?) AND to_id IN (?)` raw SELECT. The result is filtered down to the
// exact predecessor → successor pairs we walked, since `listEdges` returns
// every edge whose endpoints fall in the AND-combined sets.
// Typed `listEdges({fromIds, toIds})` replaces a `WHERE from_id IN
// (?) AND to_id IN (?)` raw SELECT. The result is filtered down to
// the exact predecessor → successor pairs we walked, since
// `listEdges` returns every edge whose endpoints fall in the AND-
// combined sets.
const edges = await store.listEdges({
fromIds: [...fromIds],
toIds: [...toIds],
Expand Down Expand Up @@ -238,7 +239,7 @@ async function fetchAffectedModules(
): Promise<readonly AffectedModule[]> {
if (allIds.length === 0) return [];
const unique = Array.from(new Set(allIds));
// AC-A-6b: typed `listEdgesByType("MEMBER_OF", {fromIds})` replaces a
// Typed `listEdgesByType("MEMBER_OF", {fromIds})` replaces a
// `WHERE type = 'MEMBER_OF' AND from_id IN (?)` raw SELECT.
const membership = await store.listEdgesByType("MEMBER_OF", { fromIds: unique });
if (membership.length === 0) return [];
Expand All @@ -256,9 +257,10 @@ async function fetchAffectedModules(
if (communityHits.size === 0) return [];

const communityIds = [...communityHits.keys()];
// AC-A-6b: typed `listNodes({ids, kinds:["Community"]})` replaces a raw
// SELECT joined to the kind discriminator. We narrow to Community + cast
// because the `inferred_label` field lives on CommunityNode only.
// Typed `listNodes({ids, kinds:["Community"]})` replaces a raw
// SELECT joined to the kind discriminator. We narrow to Community +
// cast because the `inferred_label` field lives on CommunityNode
// only.
const labelNodes = await store.listNodes({ ids: communityIds, kinds: ["Community"] });
const labelById = new Map<string, string>();
for (const node of labelNodes) {
Expand Down Expand Up @@ -305,11 +307,11 @@ async function fetchAffectedProcesses(
// Process's entry point, then match Process nodes whose `entry_point_id`
// equals any reached ancestor (including the target itself).
//
// AC-A-6b: typed `traverseAncestors` replaces the `WITH RECURSIVE
// Typed `traverseAncestors` replaces the `WITH RECURSIVE
// member_ancestors USING KEY (ancestor_id)` raw query.
// `listNodesByEntryPoint(id)` replaces the `WHERE entry_point_id = ?`
// join. Each ancestor lookup is an independent traversal, so we run them
// in parallel and dedupe the union.
// join. Each ancestor lookup is an independent traversal, so we run
// them in parallel and dedupe the union.
const ancestorIds = new Set<string>();
for (const sid of symbolIds) ancestorIds.add(sid);
// Limit per-target traversal to depth 8 to match the original
Expand Down
6 changes: 3 additions & 3 deletions packages/analysis/src/page-rank.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ test("pageRank: 10-node fixture — mass concentrates on node C, sums to ~1", ()
assert.equal(adj.nodes.length, 10);
const pr = pageRank(adj);
const total = pr.reduce((acc, v) => acc + v, 0);
// Fixed 50 iterations is loose convergence by design (W-M5-3 bans
// tolerance-based termination); the sum stays ~1 within float
// Fixed 50 iterations is loose convergence by design (tolerance-
// based termination is forbidden); the sum stays ~1 within float
// noise on a balanced graph.
assert.ok(Math.abs(total - 1) < 1e-6, `pagerank sum should be ~1.0; got ${total}`);
// C has 4 inbound edges (B->C plus E, G, I -> C); the other nodes
Expand All @@ -60,7 +60,7 @@ test("pageRank: determinism snapshot — hex fingerprint is stable", () => {
// If this hex changes, byte-identity of the kernel has drifted.
// Investigate: did damping, iteration count, dangling-mass math,
// or edge iteration order change? NONE of those are allowed to
// shift without an explicit, documented rev (see W-M5-3).
// shift without an explicit, documented rev.
//
// Captured on V8 (Node 24) from the lifted kernel. Little-endian
// Float64 bytes for the 10-node PageRank output, in adj.nodes
Expand Down
16 changes: 7 additions & 9 deletions packages/analysis/src/page-rank.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/**
* Request-time PageRank kernel for `@opencodehub/analysis`.
*
* Lifted verbatim from `packages/scip-ingest/src/materialize.ts`
* (AC-M5-2). The algorithm uses fixed iterations + fixed damping —
* tolerance-based convergence is banned by W-M5-3, because any
* numerical drift breaks the byte-identity guarantee that the
* AC-M5-4 skeleton BOM item + future graphHash depend on.
* The algorithm uses fixed iterations + fixed damping —
* tolerance-based convergence is forbidden because any numerical drift
* breaks the byte-identity guarantee that the skeleton BOM item +
* future graphHash depend on.
*
* The kernel operates on an adjacency-list snapshot built from a
* stream of directed edges. scip-ingest's `DerivedEdge` is a
Expand Down Expand Up @@ -35,9 +34,8 @@ export interface Adjacency {
* preserves the edge iteration order within each outgoing row so the
* PageRank fold across `outAdj[u]` is reproducible.
*
* Preserves the byte-identity of the pre-lift implementation (see
* `packages/scip-ingest/src/materialize.ts@<lift-commit>` before
* AC-M5-2).
* Preserves the byte-identity of the implementation that originally
* lived in `packages/scip-ingest/src/materialize.ts`.
*/
export function buildAdjacency(edges: readonly EdgeLike[]): Adjacency {
const nodeSet = new Set<string>();
Expand Down Expand Up @@ -81,7 +79,7 @@ export function buildAdjacency(edges: readonly EdgeLike[]): Adjacency {
* Compute PageRank over a directed, weighted adjacency.
*
* Fixed iterations (default 50) and fixed damping (default 0.85) —
* NO tolerance-based convergence (W-M5-3). Returns a Float64Array
* NO tolerance-based convergence — fixed iterations only. Returns a Float64Array
* indexed by `adj.nodes` order.
*
* Dangling-mass distribution: at every iteration, mass held on
Expand Down
6 changes: 3 additions & 3 deletions packages/analysis/src/rename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async function findCandidates(
symbolName: string,
scopeFile: string | undefined,
): Promise<readonly SymbolLocation[]> {
// AC-A-6b: typed `listNodesByName(name, {filePath})` replaces a raw
// Typed `listNodesByName(name, {filePath})` replaces a raw
// `WHERE name = ? [AND file_path = ?]` SELECT. The finder returns full
// GraphNodes; we map onto the local SymbolLocation shape so downstream
// rename logic stays unchanged.
Expand Down Expand Up @@ -78,7 +78,7 @@ async function referrersOf(
store: IGraphStore,
targetId: string,
): Promise<readonly SymbolLocation[]> {
// AC-A-6b: typed `listEdges({types, toIds})` replaces a raw `WHERE
// Typed `listEdges({types, toIds})` replaces a raw `WHERE
// r.to_id = ? AND r.type IN (...)` SELECT joined to nodes. The TS-side
// join hydrates referrer node metadata via `listNodes({ids})`.
const edges = await store.listEdges({
Expand Down Expand Up @@ -106,7 +106,7 @@ async function referrersOf(
}

async function allRepoFiles(store: IGraphStore): Promise<readonly string[]> {
// AC-A-6b: typed `listNodesByKind("File")` replaces a `SELECT DISTINCT
// Typed `listNodesByKind("File")` replaces a `SELECT DISTINCT
// file_path FROM nodes WHERE kind = 'File'` raw SELECT.
const files = await store.listNodesByKind("File");
const seen = new Set<string>();
Expand Down
6 changes: 3 additions & 3 deletions packages/analysis/src/risk-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export async function buildRiskSnapshot(
): Promise<RiskSnapshot> {
const perCommunityRisk: Record<string, CommunityRiskEntry> = {};

// AC-A-6b: typed `listNodesByKind("Community")` replaces a `WHERE kind =
// Typed `listNodesByKind("Community")` replaces a `WHERE kind =
// 'Community'` raw SELECT. The finder rehydrates {@link CommunityNode}
// directly so callers consume `inferredLabel`/`symbolCount`/`cohesion` via
// typed fields rather than column casts.
Expand All @@ -141,7 +141,7 @@ export async function buildRiskSnapshot(
// Community nodes are optional.
}

// AC-A-6b: typed `countNodesByKind` aggregates every kind into a single
// Typed `countNodesByKind` aggregates every kind into a single
// round-trip; we sum the result to mirror the legacy `COUNT(*) FROM nodes`.
// `countEdgesByType` does the same for relations.
let totalNodeCount = 0;
Expand All @@ -165,7 +165,7 @@ export async function buildRiskSnapshot(
note: 0,
};
try {
// AC-A-6b: typed `listFindings()` replaces the
// Typed `listFindings()` replaces the
// `WHERE kind = 'Finding' GROUP BY severity` aggregate. The histogram is
// built JS-side; the finding row count never blows up because Finding
// nodes are bounded by the scanner output (typically O(100s)).
Expand Down
7 changes: 3 additions & 4 deletions packages/analysis/src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
* settings as production code, and so tests can import it without reaching
* across the dist boundary.
*
* `FakeStore` is an in-memory stand-in for {@link IGraphStore}. AC-A-6b
* removed the SQL-regex dispatcher (formerly ~270 lines) and replaced it
* with direct implementations of every typed finder the analysis/ surface
* consumes — `listNodes`, `listNodesByKind`, `listNodesByName`,
* `FakeStore` is an in-memory stand-in for {@link IGraphStore} that
* implements every typed finder the analysis/ surface consumes —
* `listNodes`, `listNodesByKind`, `listNodesByName`,
* `listNodesByEntryPoint`, `listEdges`, `listEdgesByType`, `listFindings`,
* `countNodesByKind`, `countEdgesByType`, `traverseAncestors`,
* `traverseDescendants`, `traverse`, plus the ITemporalStore-compat noops.
Expand Down
12 changes: 6 additions & 6 deletions packages/analysis/src/verdict.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ async function collectCommunities(
): Promise<void> {
if (symbolIds.length === 0) return;
try {
// AC-A-6b: typed `listEdgesByType("MEMBER_OF", {fromIds})` replaces a
// Typed `listEdgesByType("MEMBER_OF", {fromIds})` replaces a
// `WHERE r.type = 'MEMBER_OF' AND r.from_id IN (...)` raw SELECT. The
// community label join becomes a TS-side `listNodes({ids})` lookup.
const edges = await store.listEdgesByType("MEMBER_OF", { fromIds: symbolIds });
Expand Down Expand Up @@ -550,7 +550,7 @@ async function collectFindings(

if (symbolIds.length > 0) {
try {
// AC-A-6b: typed `listEdgesByType("FOUND_IN", {toIds})` replaces a
// Typed `listEdgesByType("FOUND_IN", {toIds})` replaces a
// `WHERE r.type = 'FOUND_IN' AND r.to_id IN (...)` raw SELECT. The
// join to `nodes WHERE kind = 'Finding'` becomes a typed
// `listFindings()` filtered by id post-fetch.
Expand Down Expand Up @@ -581,7 +581,7 @@ async function collectFindings(
// to a specific symbol.
if (files.length > 0) {
try {
// AC-A-6b: typed `listFindings()` replaces a
// Typed `listFindings()` replaces a
// `WHERE kind = 'Finding' AND file_path IN (...)` raw SELECT. The
// file membership filter runs JS-side; finding rows are bounded by the
// scanner output (typically O(100s)) so the filter is cheap.
Expand Down Expand Up @@ -633,7 +633,7 @@ async function collectFileMeta(
if (files.length === 0) return out;
const fileSet = new Set(files);
try {
// AC-A-6b: typed `listNodesByKind("File")` replaces a
// Typed `listNodesByKind("File")` replaces a
// `WHERE kind = 'File' AND file_path IN (...)` raw SELECT. The file
// membership filter runs JS-side because `listNodesByKind` exposes a
// single-file-path option only.
Expand Down Expand Up @@ -673,7 +673,7 @@ async function collectFileMeta(
// separate set of finder calls because `cyclomatic_complexity` is
// populated on child symbol rows, not on the File row itself.
//
// AC-A-6b: typed `listNodesByKind` per callable kind replaces a
// Typed `listNodesByKind` per callable kind replaces a
// `WHERE kind IN ('Function','Method','Constructor') AND file_path IN
// (...) GROUP BY file_path MAX(cyclomatic_complexity)` aggregate. The MAX
// reduction runs JS-side as a single linear sweep.
Expand Down Expand Up @@ -709,7 +709,7 @@ async function collectReviewers(
// Build a list of File node ids — the form `File:<path>:<path>`.
const fileNodeIds = files.map((f) => `File:${f}:${f}`);
try {
// AC-A-6b: typed `listEdgesByType("OWNED_BY", {fromIds})` replaces a
// Typed `listEdgesByType("OWNED_BY", {fromIds})` replaces a
// `WHERE r.type = 'OWNED_BY' AND r.from_id IN (...)` raw SELECT. The
// SUM(confidence) GROUP BY contributor + JOIN to nodes both run TS-side
// — `listNodes({ids})` materializes the contributor metadata.
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/analyze.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ test("resolveSummariesEnabled: explicit --no-summaries turns it off", () => {
assert.equal(resolveSummariesEnabled(false, {}), false);
});

test("resolveSummariesEnabled: CODEHUB_BEDROCK_DISABLED=1 kills the phase (SUM-S-001)", () => {
test("resolveSummariesEnabled: CODEHUB_BEDROCK_DISABLED=1 kills the phase", () => {
assert.equal(resolveSummariesEnabled(undefined, { CODEHUB_BEDROCK_DISABLED: "1" }), false);
});

Expand All @@ -182,7 +182,7 @@ test("resolveSummariesEnabled: CODEHUB_BEDROCK_DISABLED=0 does not kill the phas
});

// ---------------------------------------------------------------------------
// Dirty-tree bypass on the analyze fast-path (T-M1-1 / EARS requirement).
// Dirty-tree bypass on the analyze fast-path.
// ---------------------------------------------------------------------------

test("checkFastPath: dirty working tree bypasses the fast-path even when HEAD matches", async () => {
Expand Down
Loading
Loading