@davesheffer/hunch 0.40.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![npm version](https://img.shields.io/npm/v/@davesheffer/hunch?color=2742ff&label=npm)](https://www.npmjs.com/package/@davesheffer/hunch)
5
5
  [![npm downloads](https://img.shields.io/npm/dw/@davesheffer/hunch?color=2742ff)](https://www.npmjs.com/package/@davesheffer/hunch)
6
6
  [![license](https://img.shields.io/npm/l/@davesheffer/hunch?color=2742ff)](LICENSE)
7
- [![node](https://img.shields.io/badge/node-%E2%89%A520-2742ff)](https://nodejs.org)
7
+ [![node](https://img.shields.io/badge/node-%E2%89%A522.13-2742ff)](https://nodejs.org)
8
8
  [![MCP](https://img.shields.io/badge/MCP-native-2742ff)](https://modelcontextprotocol.io)
9
9
 
10
10
  > **A linter checks whether code matches a *pattern*. Hunch checks whether code still matches your *architecture*** —
@@ -140,7 +140,7 @@ unchanged.
140
140
  ## Getting started
141
141
 
142
142
  ```bash
143
- npm install -g @davesheffer/hunch # Node ≥ 20; puts `hunch` on your PATH
143
+ npm install -g @davesheffer/hunch # Node ≥ 22.13; puts `hunch` on your PATH
144
144
  cd your-repo
145
145
  hunch init # scaffold .hunch/, index, install hooks, wire up assistants
146
146
  hunch backfill --since 90d # cold start: seed decisions from recent git history
@@ -320,5 +320,5 @@ memory. → [the docs](https://hunch-pi.vercel.app/docs) for the conceptual mode
320
320
 
321
321
  ## Develop
322
322
 
323
- Hunch is open source — pure TypeScript ESM, Node ≥ 20, licensed **Apache-2.0**. Contributions
323
+ Hunch is open source — pure TypeScript ESM, Node ≥ 22.13, licensed **Apache-2.0**. Contributions
324
324
  welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) and the [repo](https://github.com/davesheffer/hunch).
package/dist/store/db.js CHANGED
@@ -1,19 +1,57 @@
1
- /** Thin wrapper around better-sqlite3 for the derived index. */
2
- import Database from "better-sqlite3";
1
+ /** Thin wrapper around node:sqlite for the derived index. */
2
+ import { createRequire } from "node:module";
3
3
  import { mkdirSync } from "node:fs";
4
4
  import { dirname } from "node:path";
5
5
  import { SCHEMA_SQL } from "./schema.js";
6
+ /** Load node:sqlite while swallowing ONLY its ExperimentalWarning (Node 22–24 still
7
+ * emits it on module load). Hunch's stderr reaches humans, hooks, and MCP clients on
8
+ * every invocation, so the noise would land everywhere; all other warnings pass through. */
9
+ function loadSqlite() {
10
+ const require = createRequire(import.meta.url);
11
+ const realEmit = process.emitWarning.bind(process);
12
+ process.emitWarning = ((warning, ...rest) => {
13
+ if (String(warning).includes("SQLite is an experimental feature"))
14
+ return;
15
+ realEmit(warning, ...rest);
16
+ });
17
+ try {
18
+ return require("node:sqlite");
19
+ }
20
+ finally {
21
+ process.emitWarning = realEmit;
22
+ }
23
+ }
24
+ const sqlite = loadSqlite();
6
25
  export function openDb(sqlitePath) {
7
26
  mkdirSync(dirname(sqlitePath), { recursive: true });
8
- const db = new Database(sqlitePath);
9
- db.pragma("busy_timeout = 5000");
27
+ const db = new sqlite.DatabaseSync(sqlitePath);
28
+ db.exec("PRAGMA busy_timeout = 5000");
10
29
  db.exec(SCHEMA_SQL);
11
30
  return db;
12
31
  }
13
32
  /** In-memory db (tests / ephemeral queries). */
14
33
  export function openMemoryDb() {
15
- const db = new Database(":memory:");
34
+ const db = new sqlite.DatabaseSync(":memory:");
16
35
  db.exec(SCHEMA_SQL);
17
36
  return db;
18
37
  }
38
+ /** Run `fn` inside one transaction: BEGIN → fn → COMMIT, ROLLBACK on throw.
39
+ * (node:sqlite has no better-sqlite3-style transaction() helper.) */
40
+ export function withTx(db, fn) {
41
+ db.exec("BEGIN");
42
+ try {
43
+ const out = fn();
44
+ db.exec("COMMIT");
45
+ return out;
46
+ }
47
+ catch (err) {
48
+ try {
49
+ db.exec("ROLLBACK");
50
+ }
51
+ catch {
52
+ /* connection already rolled back */
53
+ }
54
+ throw err;
55
+ }
56
+ }
19
57
  //# sourceMappingURL=db.js.map
@@ -14,7 +14,7 @@ import { resolve, join } from "node:path";
14
14
  import { existsSync, readFileSync } from "node:fs";
15
15
  import { toPosixTarget, hunchPathsForDir } from "../core/paths.js";
16
16
  import { ENTITY_KINDS } from "../core/types.js";
17
- import { openDb } from "./db.js";
17
+ import { openDb, withTx } from "./db.js";
18
18
  import { RESET_SQL, embedHash } from "./schema.js";
19
19
  import { selectEmbedder } from "./embedder.js";
20
20
  import { JsonStore } from "./jsonStore.js";
@@ -188,7 +188,7 @@ export class HunchStore {
188
188
  reindex() {
189
189
  const db = this.db;
190
190
  const counts = {};
191
- const tx = db.transaction(() => {
191
+ withTx(db, () => {
192
192
  db.exec(RESET_SQL);
193
193
  const j = (s) => s; // readability marker for JSON-encoded columns
194
194
  // Prepare the FTS insert ONCE (after RESET created the table), not per row.
@@ -263,7 +263,6 @@ export class HunchStore {
263
263
  counts.runbooks = runbooks.length;
264
264
  void j;
265
265
  });
266
- tx();
267
266
  // Reconcile embeddings AFTER the FTS rebuild (model-free): drop vectors whose
268
267
  // source doc vanished or whose text changed. Embeddings are NOT in RESET_SQL,
269
268
  // so this is what keeps them coherent across the many reindex() call sites.
@@ -317,14 +316,13 @@ export class HunchStore {
317
316
  const rows = this.db.prepare(`SELECT ref, doc_hash FROM embeddings`).all();
318
317
  const del = this.db.prepare(`DELETE FROM embeddings WHERE ref = ?`);
319
318
  let pruned = 0;
320
- const tx = this.db.transaction(() => {
319
+ withTx(this.db, () => {
321
320
  for (const r of rows)
322
321
  if (live.get(r.ref) !== r.doc_hash) {
323
322
  del.run(r.ref);
324
323
  pruned++;
325
324
  }
326
325
  });
327
- tx();
328
326
  return pruned;
329
327
  }
330
328
  /** Embedding coverage for a model: up-to-date vectors vs total docs (doctor). */
@@ -357,7 +355,7 @@ export class HunchStore {
357
355
  for (let i = 0; i < todo.length; i += batchSize) {
358
356
  const slice = todo.slice(i, i + batchSize);
359
357
  const vecs = await embedder.embed(slice.map((d) => `${d.title}\n${d.body}`));
360
- const tx = this.db.transaction(() => {
358
+ withTx(this.db, () => {
361
359
  slice.forEach((d, j) => {
362
360
  const v = vecs[j];
363
361
  if (v) {
@@ -366,7 +364,6 @@ export class HunchStore {
366
364
  }
367
365
  });
368
366
  });
369
- tx();
370
367
  attempted += slice.length;
371
368
  opts.onProgress?.(attempted, todo.length);
372
369
  }
@@ -1167,7 +1164,7 @@ function numEnv(name, dflt) {
1167
1164
  }
1168
1165
  /** Pack a vector's exact bytes for SQLite. Explicit offset+length so a SUBARRAY
1169
1166
  * view (byteOffset != 0) writes only its slice, not the whole backing buffer.
1170
- * better-sqlite3 copies on bind, so the returned view never aliases the row. */
1167
+ * node:sqlite copies on bind, so the returned view never aliases the row. */
1171
1168
  function vecToBlob(v) {
1172
1169
  return Buffer.from(v.buffer, v.byteOffset, v.byteLength);
1173
1170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davesheffer/hunch",
3
- "version": "0.40.0",
3
+ "version": "1.0.0",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Dave Sheffer <dave.sheffer1@gmail.com>",
6
6
  "description": "Architectural Conformance for AI-generated code: a git-native graph that deterministically blocks AI changes which break your architecture — the semantic invariants (layering, must-reach, dependency direction) pattern-SAST can't express — grounded in the decisions and bugs behind each rule, across any MCP assistant (Claude Code, Cursor, Copilot, Windsurf, Codex).",
@@ -35,7 +35,7 @@
35
35
  "developer-tools"
36
36
  ],
37
37
  "engines": {
38
- "node": ">=20"
38
+ "node": ">=22.13.0"
39
39
  },
40
40
  "scripts": {
41
41
  "clean": "node --input-type=commonjs -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
@@ -48,15 +48,13 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@modelcontextprotocol/sdk": "^1.29.0",
51
- "better-sqlite3": "12.9.0",
52
51
  "commander": "^15.0.0",
53
52
  "tree-sitter": "0.21.1",
54
53
  "tree-sitter-typescript": "^0.23.2",
55
54
  "zod": "^4.4.3"
56
55
  },
57
56
  "devDependencies": {
58
- "@types/better-sqlite3": "^7.6.13",
59
- "@types/node": "^20.19.0",
57
+ "@types/node": "^22.13.0",
60
58
  "tsx": "^4.22.4",
61
59
  "typescript": "^5.9.3"
62
60
  },