Skip to content

feat(cluster): add snapshot-then-merge architecture#29

Open
mvanhorn wants to merge 1 commit intopwrdrvr:mainfrom
mvanhorn:osc/feat-cluster-snapshot-merge
Open

feat(cluster): add snapshot-then-merge architecture#29
mvanhorn wants to merge 1 commit intopwrdrvr:mainfrom
mvanhorn:osc/feat-cluster-snapshot-merge

Conversation

@mvanhorn
Copy link

Summary

Implements the snapshot-then-merge cluster storage model from the design doc. Each cluster rebuild now stores a frozen snapshot of cluster membership, and a repo_cluster_state pointer table tracks active/previous runs per repo.

Why this matters

Follow-up to the PR #19 discussion where @huntharo confirmed this direction. The current prune logic deletes all but the current run on every rebuild (service.ts:pruneOldClusterRuns), which is destructive and prevents run-to-run comparison once the lineage tracking from PR #19 lands. The design doc (docs/designs/cluster-storage-cleanup.md) already proposed repo_cluster_state and an append-only model - this PR implements it.

Changes

New tables (db/migrate.ts):

  • repo_cluster_state - per-repo pointer to active and previous cluster runs
  • cluster_snapshots - frozen cluster membership per run (JSON array of thread IDs)

New module (cluster/snapshot.ts):

  • mergeClusterSnapshots() - Jaccard-based comparison of current vs previous snapshots
  • Greedy matching with configurable threshold (default 0.5)
  • Produces typed outcomes: updated, new, dissolved

Service updates (service.ts):

  • clusterRepository() now calls persistClusterSnapshots() and flipClusterState() after persisting the run
  • getLatestClusterRun() reads from repo_cluster_state first, falls back to raw query for backward compatibility
  • listClusters() uses the updated read path
  • pruneOldClusterRuns() keeps both active and previous runs instead of deleting everything

Tests (cluster/snapshot.test.ts):

  • 9 tests covering: first run, identical clusters, overlapping, non-overlapping, mixed, below-threshold, custom threshold, greedy matching

Testing

  • pnpm typecheck passes
  • All 9 snapshot tests pass (pnpm --filter @ghcrawl/api-core exec tsx --tsconfig tsconfig.test.json --test 'src/cluster/*.test.ts')
  • Pre-existing config test failures on main are unrelated (verified by running config.test.ts on main)

This contribution was developed with AI assistance (Claude Code).

Store per-run cluster snapshots in a new cluster_snapshots table and
track active/previous run pointers in repo_cluster_state. The read
path prefers the state pointer over raw "latest completed run" queries.
Prune now keeps both active and previous runs instead of deleting all
but the current one.

Follow-up to PR pwrdrvr#19 discussion with @huntharo on the cluster lineage
tracking design.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant