Skip to content
Open
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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ tiers.
- `impact` — dependents of a target up to a configurable depth, with a risk tier.
- `detect_changes` — map an uncommitted or committed diff to affected symbols.
- `list_findings` — browse SARIF findings from the latest scan by severity and rule.
- `sql` — read-only SQL against the local temporal store (cochanges + symbol_summaries), 5 s timeout; the node/edge graph is queried via the typed tools or Cypher via the MCP `sql` tool.
- `sql` — read-only SQL against the local temporal store (cochanges), 5 s timeout; the node/edge graph is queried via the typed tools or Cypher via the MCP `sql` tool.

Run `codehub analyze` after pulling new commits so the index stays aligned
with the working tree. `codehub status` reports staleness.
Expand Down Expand Up @@ -84,7 +84,7 @@ provides a `code-analyst` subagent and 11 skills. Install via

The entire index lives in ONE `<repo>/.codehub/store.sqlite` file (WAL),
via Node's built-in `node:sqlite` — graph nodes, edges, embeddings, and
the temporal tables (cochanges, symbol summaries, the
the temporal tables (cochanges and the
`codehub query --sql` escape hatch). One `SqliteStore` class implements
**both** `IGraphStore` and `ITemporalStore`; `openStore()` returns that
single instance as both the `graph` and `temporal` views, so call sites
Expand Down
1 change: 0 additions & 1 deletion commitlint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ export default {
"scip-ingest",
"search",
"storage",
"summarizer",
"wiki",
"plugin",
"deps",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/agent-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ tiers.
- \`impact\` — dependents of a target up to a configurable depth, with a risk tier.
- \`detect_changes\` — map an uncommitted or committed diff to affected symbols.
- \`list_findings\` — browse SARIF findings from the latest scan by severity and rule.
- \`sql\` — read-only SQL against the local temporal store (cochanges + symbol_summaries), 5 s timeout; the node/edge graph is queried via the typed tools or Cypher via the MCP \`sql\` tool.
- \`sql\` — read-only SQL against the local temporal store (cochanges), 5 s timeout; the node/edge graph is queried via the typed tools or Cypher via the MCP \`sql\` tool.

Run \`codehub analyze\` after pulling new commits so the index stays aligned
with the working tree. \`codehub status\` reports staleness.
Expand Down
141 changes: 0 additions & 141 deletions packages/cli/src/commands/analyze.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
/**
* Unit tests for `codehub analyze` helpers.
*
* P04 deliverables covered here:
* - `resolveMaxSummariesCap` arithmetic for `--max-summaries auto`:
* prior-run seed → floor(count × 0.1) clamped at 500; first-run
* fallback → 50; explicit numbers pass through; summaries disabled
* → 0 regardless of input.
* - `CODEHUB_BEDROCK_DISABLED=1` env kill-switch semantics: when the
* caller short-circuits `summariesEnabled` to false (mirroring how
* `runAnalyze` and the CLI entry point honor the env var) the cap
* collapses to 0 even under `auto`.
*/

import assert from "node:assert/strict";
Expand All @@ -25,10 +15,8 @@ import {
detectCoverageReport,
isWorkingTreeDirty,
resolveCoverageEnabled,
resolveMaxSummariesCap,
resolveSbomEnabled,
resolveScanEnabled,
resolveSummariesEnabled,
} from "./analyze.js";
import { selectScannerIds } from "./scan.js";
import { computeScanFingerprint, shouldSkipScan } from "./scan-fingerprint.js";
Expand Down Expand Up @@ -88,135 +76,6 @@ async function initGitRepo(dir: string): Promise<string> {
return headPromise;
}

test("resolveMaxSummariesCap: auto resolves to floor(count × 0.1) when seed is known", async () => {
// 1234 callables → 10% = 123.4 → floor = 123.
const cap = await resolveMaxSummariesCap("/unused", "auto", true, async () => 1234);
assert.equal(cap, 123);
});

test("resolveMaxSummariesCap: auto clamps at the 500 cap for large repos", async () => {
// 10_000 callables would be 1000 uncapped; the policy clamps at 500.
const cap = await resolveMaxSummariesCap("/unused", "auto", true, async () => 10_000);
assert.equal(cap, 500);
});

test("resolveMaxSummariesCap: auto falls back to 50 on first run (no prior seed)", async () => {
// `undefined` models "no prior store at the expected path".
const cap = await resolveMaxSummariesCap("/unused", "auto", true, async () => undefined);
assert.equal(cap, 50);
});

test("resolveMaxSummariesCap: undefined defaults to the auto path", async () => {
// A caller that doesn't pass --max-summaries at all gets the same
// behavior as explicit `auto`.
const cap = await resolveMaxSummariesCap("/unused", undefined, true, async () => 200);
assert.equal(cap, 20);
});

test("resolveMaxSummariesCap: explicit numbers pass through unchanged", async () => {
const cap = await resolveMaxSummariesCap("/unused", 42, true, async () => 999);
assert.equal(cap, 42);
});

test("resolveMaxSummariesCap: negative integers clamp to 0 (dry-run)", async () => {
const cap = await resolveMaxSummariesCap("/unused", -5, true, async () => 999);
assert.equal(cap, 0);
});

test("resolveMaxSummariesCap: non-integer numbers are floored", async () => {
const cap = await resolveMaxSummariesCap(
"/unused",
7.9 as unknown as number,
true,
async () => 0,
);
assert.equal(cap, 7);
});

test("resolveMaxSummariesCap: summariesEnabled=false collapses to 0 regardless of input", async () => {
// This is the behavior chain the CLI depends on when either
// `--no-summaries` or `CODEHUB_BEDROCK_DISABLED=1` short-circuits
// `summariesEnabled` upstream.
const autoCap = await resolveMaxSummariesCap("/unused", "auto", false, async () => 1_000);
assert.equal(autoCap, 0, "auto + disabled must dry-run");
const numericCap = await resolveMaxSummariesCap("/unused", 100, false, async () => 1_000);
assert.equal(numericCap, 0, "explicit cap + disabled must dry-run");
});

test("resolveMaxSummariesCap: seed of 0 yields a cap of 0 (no callables to summarize)", async () => {
const cap = await resolveMaxSummariesCap("/unused", "auto", true, async () => 0);
assert.equal(cap, 0);
});

test("resolveMaxSummariesCap: seed of 5 yields a cap of 0 under the 10% rule", async () => {
// 5 × 0.1 = 0.5 → floor = 0. Tiny repos dry-run until they grow.
const cap = await resolveMaxSummariesCap("/unused", "auto", true, async () => 5);
assert.equal(cap, 0);
});

// ---------------------------------------------------------------------------
// resolveSummariesEnabled — fast-default contract: LLM summaries are opt-in.
// `codehub analyze` runs tree-sitter + SCIP + cochange phases only by default,
// so a fresh invocation never spends on Bedrock or blocks on a network hop.
// ---------------------------------------------------------------------------

test("resolveSummariesEnabled: default-off when both env and flag are absent", () => {
assert.equal(resolveSummariesEnabled(undefined, {}), false);
});

test("resolveSummariesEnabled: explicit --summaries opts in", () => {
assert.equal(resolveSummariesEnabled(true, {}), true);
});

test("resolveSummariesEnabled: explicit --no-summaries stays off", () => {
assert.equal(resolveSummariesEnabled(false, {}), false);
});

test("resolveSummariesEnabled: CODEHUB_BEDROCK_SUMMARIES=1 opts in (env-only)", () => {
// Operators can enable summaries for a whole CI job without editing every
// invocation. Only the literal "1" triggers — anything else is treated as
// absent, mirroring the kill-switch semantics below.
assert.equal(resolveSummariesEnabled(undefined, { CODEHUB_BEDROCK_SUMMARIES: "1" }), true);
assert.equal(resolveSummariesEnabled(undefined, { CODEHUB_BEDROCK_SUMMARIES: "0" }), false);
assert.equal(resolveSummariesEnabled(undefined, { CODEHUB_BEDROCK_SUMMARIES: "" }), false);
});

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

test("resolveSummariesEnabled: kill-switch wins over --summaries=true", () => {
// Operator passed --summaries explicitly but the env var forces off.
// Required so CI / restricted environments can lock out Bedrock without
// auditing every invocation site.
assert.equal(resolveSummariesEnabled(true, { CODEHUB_BEDROCK_DISABLED: "1" }), false);
});

test("resolveSummariesEnabled: kill-switch wins over CODEHUB_BEDROCK_SUMMARIES=1", () => {
// Both env vars set → disable wins. This lets a CI environment pin the
// opt-in globally while still allowing per-job kill-switch overrides.
assert.equal(
resolveSummariesEnabled(undefined, {
CODEHUB_BEDROCK_SUMMARIES: "1",
CODEHUB_BEDROCK_DISABLED: "1",
}),
false,
);
});

test("resolveSummariesEnabled: --no-summaries wins over CODEHUB_BEDROCK_SUMMARIES=1", () => {
// Explicit CLI false beats env opt-in. Matches how --no-flag usually
// wins against ambient config everywhere else in the CLI.
assert.equal(resolveSummariesEnabled(false, { CODEHUB_BEDROCK_SUMMARIES: "1" }), false);
});

test("resolveSummariesEnabled: CODEHUB_BEDROCK_DISABLED=0 does not enable the phase", () => {
// Only the literal "1" on the opt-in var flips this; anything else leaves
// summaries in their (fast, off) default.
assert.equal(resolveSummariesEnabled(undefined, { CODEHUB_BEDROCK_DISABLED: "0" }), false);
assert.equal(resolveSummariesEnabled(undefined, { CODEHUB_BEDROCK_DISABLED: "" }), false);
});

// ---------------------------------------------------------------------------
// Dirty-tree bypass on the analyze fast-path.
// ---------------------------------------------------------------------------
Expand Down
Loading
Loading