Skip to content

refactor: index-hook framework — eliminate per-pass CodeGraph mutations#17

Open
mschreib28 wants to merge 2 commits into
mainfrom
upstream/refactor/index-hooks
Open

refactor: index-hook framework — eliminate per-pass CodeGraph mutations#17
mschreib28 wants to merge 2 commits into
mainfrom
upstream/refactor/index-hooks

Conversation

@mschreib28
Copy link
Copy Markdown
Owner

Reviewers: for a 5-minute review path that surfaces only what to verify, see the review checklist comment below. The original description is preserved here for full technical detail.\n\n---\n\nrefactor: index-hook framework — eliminate per-pass CodeGraph mutations\n\nToday every PR adding a derived-signal pass (centrality, churn,\nissue-history, config-refs, sql-refs, cochange) edits the same\n3 spots in src/index.ts:\n\n 1. New imports at the top\n 2. New private method on CodeGraph (e.g. runDerivedSignals,\n runIssueHistoryPass, runConfigRefsPass, runSqlRefsPass)\n 3. New call site in indexAll AFTER resolution\n 4. New call site in sync AFTER resolution\n\n5 PRs collide on every one of those.\n\nAfter this refactor:\n\n Adding a new derived-signal pass:\n 1. Create src/index-hooks/<name>.ts exporting a\n HOOK: IndexHook constant with afterIndexAll and/or\n afterSync methods.\n 2. Add one import + one entry to\n src/index-hooks/registry.ts.\n\nCodeGraph.indexAll and sync invoke the hook runner once;\nadding a new pass touches only the hook file + the registry.\nZero changes to CodeGraph itself.\n\n## What's new\n\n- src/index-hooks/types.tsIndexHook interface\n (afterIndexAll, afterSync, both optional), IndexHookContext\n (projectRoot + config + queries + db), and\n IndexHookOutcome for diagnostic reporting.\n- src/index-hooks/registry.ts — static-import list of every\n registered hook (empty on main today; PRs adding hooks fill it\n in), plus the runAfterIndexAll / runAfterSync runners that\n iterate hooks and catch errors so one broken hook never fails\n indexing.\n- src/index.tsindexAll calls runAfterIndexAll(ctx)\n after resolution. sync calls runAfterSync(ctx, result)\n after resolution. New private buildHookContext() helper\n exposes a stable read-only context.\n- tests/index-hooks.test.ts — 6 tests covering empty\n registry, runner shape, and the afterIndexAll / afterSync\n contracts.\n\n## Why ship the framework on main with zero registered hooks?\n\nThe only consumers of this framework today are 5 unmerged PRs\n(colbymchenry#105 cochange + my colbymchenry#112-colbymchenry#115). Landing the framework now lets\neach of those PRs rebase to a 2-line change instead of 8-10\nlines mutating CodeGraph adjacent-line. Without this, all 5 PRs\ncollide on the same indexAll/sync call sites.\n\nThe framework adds zero behavior on main (no registered hooks =\nno-op runner). 380→386 tests confirm no regression.\n\n## Affected open PRs\n\n| PR | Today | After this lands |\n|---|---|---|\n| colbymchenry#105 cochange | runDerivedSignals helper + 2 call sites | 1 hook file in src/index-hooks/ + 2 lines in registry.ts |\n| colbymchenry#112 centrality+churn | same shape | same shape |\n| colbymchenry#113 issue-history | same shape | same shape |\n| colbymchenry#114 config-refs | same shape | same shape |\n| colbymchenry#115 sql-refs | same shape | same shape |\n\nEach goes from "edit CodeGraph in 4 spots" to "drop a hook file."\n\nCo-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com\n


Copied from colbymchenry/codegraph#119

andreinknv and others added 2 commits April 27, 2026 17:13
Today every PR adding a derived-signal pass (centrality, churn,
issue-history, config-refs, sql-refs, cochange) edits the same
3 spots in src/index.ts:

  1. New imports at the top
  2. New private method on `CodeGraph` (e.g. runDerivedSignals,
     runIssueHistoryPass, runConfigRefsPass, runSqlRefsPass)
  3. New call site in `indexAll` AFTER resolution
  4. New call site in `sync` AFTER resolution

5 PRs collide on every one of those.

After this refactor:

  Adding a new derived-signal pass:
  1. Create `src/index-hooks/<name>.ts` exporting a
     `HOOK: IndexHook` constant with `afterIndexAll` and/or
     `afterSync` methods.
  2. Add one import + one entry to
     `src/index-hooks/registry.ts`.

`CodeGraph.indexAll` and `sync` invoke the hook runner once;
adding a new pass touches only the hook file + the registry.
Zero changes to CodeGraph itself.

## What's new

- **src/index-hooks/types.ts** — `IndexHook` interface
  (`afterIndexAll`, `afterSync`, both optional), `IndexHookContext`
  (projectRoot + config + queries + db), and
  `IndexHookOutcome` for diagnostic reporting.
- **src/index-hooks/registry.ts** — static-import list of every
  registered hook (empty on main today; PRs adding hooks fill it
  in), plus the `runAfterIndexAll` / `runAfterSync` runners that
  iterate hooks and catch errors so one broken hook never fails
  indexing.
- **src/index.ts** — `indexAll` calls `runAfterIndexAll(ctx)`
  after resolution. `sync` calls `runAfterSync(ctx, result)`
  after resolution. New private `buildHookContext()` helper
  exposes a stable read-only context.
- **__tests__/index-hooks.test.ts** — 6 tests covering empty
  registry, runner shape, and the `afterIndexAll` / `afterSync`
  contracts.

## Why ship the framework on main with zero registered hooks?

The only consumers of this framework today are 5 unmerged PRs
(colbymchenry#105 cochange + my colbymchenry#112-colbymchenry#115). Landing the framework now lets
each of those PRs rebase to a 2-line change instead of 8-10
lines mutating CodeGraph adjacent-line. Without this, all 5 PRs
collide on the same indexAll/sync call sites.

The framework adds zero behavior on main (no registered hooks =
no-op runner). 380→386 tests confirm no regression.

## Affected open PRs

| PR | Today | After this lands |
|---|---|---|
| colbymchenry#105 cochange | runDerivedSignals helper + 2 call sites | 1 hook file in src/index-hooks/ + 2 lines in registry.ts |
| colbymchenry#112 centrality+churn | same shape | same shape |
| colbymchenry#113 issue-history | same shape | same shape |
| colbymchenry#114 config-refs | same shape | same shape |
| colbymchenry#115 sql-refs | same shape | same shape |

Each goes from "edit CodeGraph in 4 spots" to "drop a hook file."

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…done heartbeat

A hook awaiting a promise that never resolves (e.g. a fetch with no
abort, mis-handled worker IPC) used to hang the whole indexAll/sync
forever — the runner had no per-hook budget and one stuck await
would freeze every later hook plus the post-hook maintenance + caller.

Wraps each hook in Promise.race against a 5-minute budget, surfaces
the timeout in the IndexHookOutcome.error field, and adds start /
done log lines so a stuck hook is identifiable without sampling the
process. Hook failures are now logWarn rather than logDebug so they
aren't silent without CODEGRAPH_DEBUG=1.

Promise.race only unwinds *async* hangs — fully synchronous code
still blocks the event loop. That's acceptable: hooks doing heavy
work shell out via execFileSync with their own per-call timeout.

Adds a regression test covering the racing primitive on a never-
resolving promise.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants