@davesheffer/hunch 1.17.0 → 1.18.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 +19 -13
- package/dist/cli/index.js +5 -2
- package/dist/constitution/delta.js +3 -3
- package/dist/constitution/evaluator.js +3 -0
- package/dist/constitution/schema.js +1 -1
- package/dist/core/ids.js +16 -0
- package/dist/core/migrate.js +20 -1
- package/dist/core/types.js +207 -1
- package/dist/extractors/git.js +7 -0
- package/dist/extractors/helm.js +77 -0
- package/dist/extractors/indexer.js +100 -24
- package/dist/extractors/landscapeDiscovery.js +492 -0
- package/dist/extractors/languages.js +36 -1
- package/dist/extractors/nativeTreeSitter.js +17 -8
- package/dist/extractors/parse.js +58 -14
- package/dist/mcp/server.js +18 -4
- package/dist/store/hunchStore.js +79 -1
- package/dist/store/jsonStore.js +8 -1
- package/dist/store/schema.js +25 -2
- package/package.json +4 -3
- package/server.json +2 -2
package/dist/extractors/parse.js
CHANGED
|
@@ -15,10 +15,27 @@ function bundleFor(spec) {
|
|
|
15
15
|
return b;
|
|
16
16
|
}
|
|
17
17
|
const STR_QUOTES = /^['"`]|['"`]$/g;
|
|
18
|
+
/** Cap on a stored symbol's bodyText — large enough for review context, small
|
|
19
|
+
* enough that a huge function/file doesn't bloat every JSON symbol record. */
|
|
20
|
+
export const MAX_BODY_TEXT_CHARS = 4000;
|
|
18
21
|
export function parseSource(file, source) {
|
|
19
22
|
const spec = languageFor(file);
|
|
20
23
|
if (!spec)
|
|
21
24
|
return null;
|
|
25
|
+
// Templated text (Helm chart / Jinja CI config) isn't {spec.id} yet — a real
|
|
26
|
+
// grammar correctly reports ERROR nodes for the delimiters. Still run the
|
|
27
|
+
// parse below (a well-formed anchor elsewhere in the file still contributes
|
|
28
|
+
// a real symbol, same as any other YAML file) — just don't let those
|
|
29
|
+
// expected errors fail-close the whole-repo scan on content that was never
|
|
30
|
+
// meant to stand alone (#33). Heavy top-level templating can break error
|
|
31
|
+
// recovery badly enough that even the whole-file root node never forms
|
|
32
|
+
// (root.type becomes "ERROR", not "stream") — the fallback-symbol synthesis
|
|
33
|
+
// below covers that case so the file doesn't vanish from the component graph.
|
|
34
|
+
// String.prototype.search ignores lastIndex (unlike RegExp.test with a /g or
|
|
35
|
+
// /y flag), so a future templatingMarkers entry can't introduce cross-call
|
|
36
|
+
// statefulness here even if it forgets to keep its pattern flag-free.
|
|
37
|
+
const templated = (spec.alwaysTemplatedExtensions?.some((ext) => file.endsWith(ext)) ?? false)
|
|
38
|
+
|| (spec.templatingMarkers?.some((marker) => source.search(marker) !== -1) ?? false);
|
|
22
39
|
const { parser, query } = bundleFor(spec);
|
|
23
40
|
// The native binding caps its scratch buffer at 32 KB unless bufferSize is
|
|
24
41
|
// given — without this, any source >= 32768 bytes throws "Invalid argument"
|
|
@@ -72,17 +89,34 @@ export function parseSource(file, source) {
|
|
|
72
89
|
}
|
|
73
90
|
}
|
|
74
91
|
for (const { kind, def, name } of pendingDefs.values()) {
|
|
75
|
-
|
|
92
|
+
const resolvedName = name ?? spec.fallbackDefName?.(file);
|
|
93
|
+
if (!resolvedName)
|
|
76
94
|
continue;
|
|
77
95
|
const loc = def.endPosition.row - def.startPosition.row + 1;
|
|
78
96
|
symbols.push({
|
|
79
|
-
name, kind,
|
|
97
|
+
name: resolvedName, kind,
|
|
80
98
|
startByte: def.startIndex, endByte: def.endIndex, loc,
|
|
81
|
-
bodyText: def.text.slice(0,
|
|
99
|
+
bodyText: def.text.slice(0, MAX_BODY_TEXT_CHARS),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// Every other successfully-parsed YAML file gets at least a file-root symbol
|
|
103
|
+
// (fallbackDefName). If templating broke error recovery badly enough that
|
|
104
|
+
// the doc.def capture never fired, synthesize the same fallback here rather
|
|
105
|
+
// than let the file silently drop out of the component graph. Push it before
|
|
106
|
+
// the sort below — parse()'s callers (indexer.ts) rely on symbols staying in
|
|
107
|
+
// start-byte order.
|
|
108
|
+
if (templated && spec.fallbackDefName && !symbols.some((s) => s.kind === "file")) {
|
|
109
|
+
symbols.push({
|
|
110
|
+
name: spec.fallbackDefName(file),
|
|
111
|
+
kind: "file",
|
|
112
|
+
startByte: 0,
|
|
113
|
+
endByte: source.length,
|
|
114
|
+
loc: source.split("\n").length,
|
|
115
|
+
bodyText: source.slice(0, MAX_BODY_TEXT_CHARS),
|
|
82
116
|
});
|
|
83
117
|
}
|
|
84
118
|
symbols.sort((a, b) => a.startByte - b.startByte);
|
|
85
|
-
return { symbols, imports, calls, parseable: isParseable(tree.rootNode, spec) };
|
|
119
|
+
return { symbols, imports, calls, parseable: templated || isParseable(tree.rootNode, spec) };
|
|
86
120
|
}
|
|
87
121
|
/** True when every ERROR/MISSING node in the tree sits in an ancestor shape this
|
|
88
122
|
* language declares as a known grammar limitation (LanguageSpec.toleratedErrorScopes).
|
|
@@ -137,25 +171,35 @@ function ascendToDef(node, defNodeTypes) {
|
|
|
137
171
|
return null;
|
|
138
172
|
}
|
|
139
173
|
/** Map each call site to the innermost symbol whose byte-range contains it.
|
|
140
|
-
* Keyed by the symbol's
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
174
|
+
* Keyed by the symbol's position (index) in `parsed.symbols` — NOT its
|
|
175
|
+
* startByte, which is not a reliable per-symbol identity: a language whose
|
|
176
|
+
* extractor merges a synthetic whole-file symbol with independently-derived
|
|
177
|
+
* symbols (e.g. YAML's fallback-root synthetic symbol alongside Helm's
|
|
178
|
+
* regex-derived `define` blocks) can produce two distinct symbols that both
|
|
179
|
+
* start at byte 0. Indexing by array position is unique by construction,
|
|
180
|
+
* regardless of byte overlap — the caller must consume the exact same
|
|
181
|
+
* `parsed.symbols` array (or an equivalently-ordered copy) to look up a
|
|
182
|
+
* symbol by the index this function returns. The value maps callee name ->
|
|
183
|
+
* `memberOnly` (true iff every occurrence was a `x.foo()` member call, never
|
|
184
|
+
* a direct `foo()`), so the indexer can resolve member calls conservatively. */
|
|
145
185
|
export function attributeCalls(parsed) {
|
|
146
186
|
const out = new Map();
|
|
147
187
|
for (const call of parsed.calls) {
|
|
148
188
|
let best = null;
|
|
149
|
-
|
|
189
|
+
let bestIndex = -1;
|
|
190
|
+
for (let i = 0; i < parsed.symbols.length; i++) {
|
|
191
|
+
const s = parsed.symbols[i];
|
|
150
192
|
if (call.atByte >= s.startByte && call.atByte < s.endByte) {
|
|
151
|
-
if (!best || s.endByte - s.startByte < best.endByte - best.startByte)
|
|
193
|
+
if (!best || s.endByte - s.startByte < best.endByte - best.startByte) {
|
|
152
194
|
best = s;
|
|
195
|
+
bestIndex = i;
|
|
196
|
+
}
|
|
153
197
|
}
|
|
154
198
|
}
|
|
155
199
|
if (best && best.name !== call.callee) {
|
|
156
|
-
if (!out.has(
|
|
157
|
-
out.set(
|
|
158
|
-
const m = out.get(
|
|
200
|
+
if (!out.has(bestIndex))
|
|
201
|
+
out.set(bestIndex, new Map());
|
|
202
|
+
const m = out.get(bestIndex);
|
|
159
203
|
const prev = m.get(call.callee);
|
|
160
204
|
m.set(call.callee, prev === undefined ? call.member : prev && call.member);
|
|
161
205
|
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -73,10 +73,21 @@ const flushNote = (flush, home, mode) => flush === "pushed" ? ` (committed + pus
|
|
|
73
73
|
* lands in the committed store publishes on the next push, and an agent writing
|
|
74
74
|
* strategy/competitive content there is a leak nobody notices until it ships
|
|
75
75
|
* (2026-08-09: 15 roadmap records caught pre-push only by a release sweep). */
|
|
76
|
-
/** Repo-local term list, read once per
|
|
77
|
-
* `.hunch/publication.json` is how a repo opts in (see src/core/publication.ts).
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
/** Repo-local term list, read once per STORE. The package ships none;
|
|
77
|
+
* `.hunch/publication.json` is how a repo opts in (see src/core/publication.ts).
|
|
78
|
+
* Keyed by hunchDir: the previous scalar memoized the FIRST store's vocabulary
|
|
79
|
+
* and served it to every store in the process, so a multi-store consumer would
|
|
80
|
+
* leak-scan project B's records against project A's terms — a wrong answer in
|
|
81
|
+
* the exact subsystem whose job is catching leaks (GATE-P1-UPSTREAM.md). */
|
|
82
|
+
const vocabularyCache = new Map();
|
|
83
|
+
export const publicationVocabulary = (hunchDir) => {
|
|
84
|
+
let vocab = vocabularyCache.get(hunchDir);
|
|
85
|
+
if (!vocab) {
|
|
86
|
+
vocab = loadVocabulary(hunchDir);
|
|
87
|
+
vocabularyCache.set(hunchDir, vocab);
|
|
88
|
+
}
|
|
89
|
+
return vocab;
|
|
90
|
+
};
|
|
80
91
|
const publicHomeNote = (home, hasPrivate, record, hunchDir) => {
|
|
81
92
|
if (home !== "public")
|
|
82
93
|
return "";
|
|
@@ -280,6 +291,9 @@ function prepareRoot(root, explicitOverlay, requireIndex) {
|
|
|
280
291
|
ensureTeamOverlay(root);
|
|
281
292
|
const store = new HunchStore(hunchPaths(root));
|
|
282
293
|
try {
|
|
294
|
+
const overlayWarning = store.overlayResolutionWarning(explicitOverlay && existsSync(teamFile));
|
|
295
|
+
if (overlayWarning)
|
|
296
|
+
console.error(`[hunch-mcp] ⚠ ${overlayWarning}`);
|
|
283
297
|
if (teamAdvertised && (store.mode !== "shared"
|
|
284
298
|
|| !store.privateDir
|
|
285
299
|
|| !existsSync(store.privateDir)
|
package/dist/store/hunchStore.js
CHANGED
|
@@ -55,6 +55,13 @@ export class HunchStore {
|
|
|
55
55
|
/** The resolved private-overlay hunch dir (from env or .hunch/local.json), or undefined
|
|
56
56
|
* when no overlay is configured. Surfaced so `hunch doctor` reflects the true state. */
|
|
57
57
|
privateDir;
|
|
58
|
+
/** How privateDir was selected. Multi-store consumers can use this instead of
|
|
59
|
+
* inferring process-global routing from process.env. */
|
|
60
|
+
overlaySource;
|
|
61
|
+
/** Present only when HUNCH_PRIVATE_DIR redirects this store away from the
|
|
62
|
+
* repo/worktree-local pointer. Precedence is compatibility-sensitive and stays
|
|
63
|
+
* env-first; making the redirection queryable removes the silent footgun. */
|
|
64
|
+
overlayOverride;
|
|
58
65
|
/** Whether captures auto-commit the store they land in — ON by default in EVERY mode;
|
|
59
66
|
* `--no-auto-commit` (hunch init/private/shared) persists `autoCommit: false` in
|
|
60
67
|
* local.json to opt out. Read by the MCP write tools and `hunch sync`. */
|
|
@@ -84,7 +91,36 @@ export class HunchStore {
|
|
|
84
91
|
// local config (.hunch/local.json) so `hunch private` enables it with NO env var, and
|
|
85
92
|
// the MCP server / hook pick it up automatically. Relative paths resolve from root.
|
|
86
93
|
const local = this.localConfig();
|
|
87
|
-
const
|
|
94
|
+
const environmentDir = process.env.HUNCH_PRIVATE_DIR?.trim();
|
|
95
|
+
const configuredDir = local.privateDir
|
|
96
|
+
? resolve(this.paths.root, local.privateDir)
|
|
97
|
+
: undefined;
|
|
98
|
+
const resolvedEnvironmentDir = environmentDir
|
|
99
|
+
? resolve(this.paths.root, environmentDir)
|
|
100
|
+
: undefined;
|
|
101
|
+
const priv = resolvedEnvironmentDir || configuredDir;
|
|
102
|
+
this.overlaySource = resolvedEnvironmentDir
|
|
103
|
+
? "environment"
|
|
104
|
+
: configuredDir
|
|
105
|
+
? "local-config"
|
|
106
|
+
: null;
|
|
107
|
+
if (resolvedEnvironmentDir && configuredDir) {
|
|
108
|
+
const canonical = (path) => {
|
|
109
|
+
try {
|
|
110
|
+
return realpathSync(path);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return resolve(path);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const comparable = (path) => process.platform === "win32" ? canonical(path).toLowerCase() : canonical(path);
|
|
117
|
+
if (comparable(resolvedEnvironmentDir) !== comparable(configuredDir)) {
|
|
118
|
+
this.overlayOverride = {
|
|
119
|
+
configuredDir: canonical(configuredDir),
|
|
120
|
+
environmentDir: canonical(resolvedEnvironmentDir),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
88
124
|
if (priv) {
|
|
89
125
|
const candidate = resolve(this.paths.root, priv);
|
|
90
126
|
const canonical = (path) => { try {
|
|
@@ -118,6 +154,21 @@ export class HunchStore {
|
|
|
118
154
|
this.mode = priv ? (local.mode ?? "private") : "public";
|
|
119
155
|
this.unified = this.mode === "shared" && !!this.privateJson;
|
|
120
156
|
}
|
|
157
|
+
/** Human-facing warning for the compatibility-preserving env-first resolution.
|
|
158
|
+
* Callers decide where it is safe to emit (CLI/MCP stderr, doctor output); the
|
|
159
|
+
* store constructor stays side-effect-free for hooks and embedded consumers. */
|
|
160
|
+
overlayResolutionWarning(teamConfigBypassed = false) {
|
|
161
|
+
if (this.overlayOverride) {
|
|
162
|
+
const effect = this.mode === "shared"
|
|
163
|
+
? "all memory reads and captures in this process use the environment path"
|
|
164
|
+
: "private-overlay reads and private captures in this process use the environment path; public captures remain in the repo";
|
|
165
|
+
return `HUNCH_PRIVATE_DIR redirects this repo from its configured ${this.mode} memory at ${this.overlayOverride.configuredDir} to ${this.overlayOverride.environmentDir}; ${effect} (routing mode remains ${this.mode}). Unset HUNCH_PRIVATE_DIR to use the configured store.`;
|
|
166
|
+
}
|
|
167
|
+
if (this.overlaySource === "environment" && teamConfigBypassed && this.privateDir) {
|
|
168
|
+
return `HUNCH_PRIVATE_DIR bypasses .hunch/team.json and selects ${this.privateDir} in ${this.mode} mode; team-store auto-discovery is disabled for this process. Unset HUNCH_PRIVATE_DIR to use the advertised team store.`;
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
121
172
|
/** Where a capture belongs: an explicit private:true always goes to the overlay
|
|
122
173
|
* (putPrivate throws rather than silently landing public when none is configured);
|
|
123
174
|
* otherwise the overlay in unified ("shared") mode, else the public store. ONE home
|
|
@@ -313,11 +364,35 @@ export class HunchStore {
|
|
|
313
364
|
fts(c.id, "components", c.name, `${c.responsibility} ${c.paths.join(" ")}`);
|
|
314
365
|
}
|
|
315
366
|
counts.components = comps.length;
|
|
367
|
+
const resources = this.recs("resources");
|
|
368
|
+
const insResource = db.prepare(`INSERT INTO resources VALUES (@id,@schema,@kind,@name,@scope,@locator,@lifecycle,@criticality,@contract_version,@currentness,@metadata,@ps,@pc,@pe,@created_at,@updated_at)`);
|
|
369
|
+
for (const resource of resources) {
|
|
370
|
+
insResource.run({
|
|
371
|
+
id: resource.id, schema: resource.schema, kind: resource.kind, name: resource.name,
|
|
372
|
+
scope: JSON.stringify(resource.scope), locator: resource.locator, lifecycle: resource.lifecycle,
|
|
373
|
+
criticality: resource.criticality ?? null, contract_version: resource.contract_version ?? null,
|
|
374
|
+
currentness: JSON.stringify(resource.currentness), metadata: JSON.stringify(resource.metadata),
|
|
375
|
+
ps: resource.provenance.source, pc: resource.provenance.confidence,
|
|
376
|
+
pe: JSON.stringify(resource.provenance.evidence), created_at: resource.created_at, updated_at: resource.updated_at,
|
|
377
|
+
});
|
|
378
|
+
fts(resource.id, "resources", resource.name, `${resource.kind} ${resource.scope.join(" ")} ${resource.locator ?? ""} ${resource.lifecycle} ${resource.contract_version ?? ""}`);
|
|
379
|
+
}
|
|
380
|
+
counts.resources = resources.length;
|
|
316
381
|
const edges = this.recs("edges");
|
|
317
382
|
const insEdge = db.prepare(`INSERT INTO edges VALUES (@id,@from,@to,@type,@reason,@strength,@ps,@pc,@pe)`);
|
|
383
|
+
const insResourceRelationship = db.prepare(`INSERT INTO resource_relationships VALUES (@id,@schema,@from,@to,@type,@reason,@strength,@currentness,@environment,@criticality,@contract_version,@metadata,@ps,@pc,@pe)`);
|
|
318
384
|
for (const e of edges) {
|
|
319
385
|
insEdge.run({ id: e.id, from: e.from, to: e.to, type: e.type, reason: e.reason, strength: e.strength,
|
|
320
386
|
ps: e.provenance.source, pc: e.provenance.confidence, pe: JSON.stringify(e.provenance.evidence) });
|
|
387
|
+
if (e.schema === "hunch.resource-relationship/1") {
|
|
388
|
+
insResourceRelationship.run({
|
|
389
|
+
id: e.id, schema: e.schema, from: e.from, to: e.to, type: e.type, reason: e.reason,
|
|
390
|
+
strength: e.strength, currentness: JSON.stringify(e.currentness), environment: e.environment,
|
|
391
|
+
criticality: e.criticality ?? null, contract_version: e.contract_version ?? null,
|
|
392
|
+
metadata: JSON.stringify(e.metadata), ps: e.provenance.source, pc: e.provenance.confidence,
|
|
393
|
+
pe: JSON.stringify(e.provenance.evidence),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
321
396
|
}
|
|
322
397
|
counts.edges = edges.length;
|
|
323
398
|
const syms = this.recs("symbols");
|
|
@@ -1324,6 +1399,7 @@ export class HunchStore {
|
|
|
1324
1399
|
};
|
|
1325
1400
|
json.put("decisions", closed);
|
|
1326
1401
|
const edge = {
|
|
1402
|
+
schema: "hunch.edge/1",
|
|
1327
1403
|
id: edgeId(by.id, oldId, "supersedes"),
|
|
1328
1404
|
from: by.id,
|
|
1329
1405
|
to: oldId,
|
|
@@ -1331,6 +1407,8 @@ export class HunchStore {
|
|
|
1331
1407
|
reason: `${by.id} supersedes ${oldId}`,
|
|
1332
1408
|
strength: 1,
|
|
1333
1409
|
provenance: { source: "derived", confidence: 1, evidence: [by.id, oldId] },
|
|
1410
|
+
environment: null,
|
|
1411
|
+
metadata: {},
|
|
1334
1412
|
};
|
|
1335
1413
|
json.put("edges", edge);
|
|
1336
1414
|
return closed;
|
package/dist/store/jsonStore.js
CHANGED
|
@@ -12,7 +12,14 @@ import { writeFileAtomic } from "../core/io.js";
|
|
|
12
12
|
* index.json array — there can be thousands, and one file per edge would create
|
|
13
13
|
* enormous git noise. Curated, low-volume entities (components, decisions, bugs,
|
|
14
14
|
* constraints) are one file per record so they're cleanly reviewable in PRs. */
|
|
15
|
-
const SINGLE_FILE = {
|
|
15
|
+
const SINGLE_FILE = {
|
|
16
|
+
symbols: "index.json",
|
|
17
|
+
edges: "index.json",
|
|
18
|
+
// Resource ids remain readable kind-qualified identities (and may contain '/'),
|
|
19
|
+
// so the canonical array avoids lossy filename encoding while keeping Git diffs
|
|
20
|
+
// deterministic through id sorting.
|
|
21
|
+
resources: "index.json",
|
|
22
|
+
};
|
|
16
23
|
const encode = (v) => JSON.stringify(v, null, 2) + "\n";
|
|
17
24
|
// Sleep primitive for the single-file RMW lock's bounded spin (issue #35);
|
|
18
25
|
// same idiom as core/io.ts's rename backoff.
|
package/dist/store/schema.js
CHANGED
|
@@ -29,6 +29,17 @@ CREATE TABLE IF NOT EXISTS components (
|
|
|
29
29
|
created_at TEXT, updated_at TEXT
|
|
30
30
|
);
|
|
31
31
|
|
|
32
|
+
CREATE TABLE IF NOT EXISTS resources (
|
|
33
|
+
id TEXT PRIMARY KEY,
|
|
34
|
+
schema TEXT, kind TEXT, name TEXT, scope TEXT, locator TEXT,
|
|
35
|
+
lifecycle TEXT, criticality TEXT, contract_version TEXT,
|
|
36
|
+
currentness TEXT, metadata TEXT,
|
|
37
|
+
prov_source TEXT, prov_confidence REAL, prov_evidence TEXT,
|
|
38
|
+
created_at TEXT, updated_at TEXT
|
|
39
|
+
);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_resources_kind ON resources(kind);
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_resources_lifecycle ON resources(lifecycle);
|
|
42
|
+
|
|
32
43
|
CREATE TABLE IF NOT EXISTS edges (
|
|
33
44
|
id TEXT PRIMARY KEY,
|
|
34
45
|
"from" TEXT, "to" TEXT, type TEXT, reason TEXT, strength REAL,
|
|
@@ -38,6 +49,18 @@ CREATE INDEX IF NOT EXISTS idx_edges_from ON edges("from");
|
|
|
38
49
|
CREATE INDEX IF NOT EXISTS idx_edges_to ON edges("to");
|
|
39
50
|
CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);
|
|
40
51
|
|
|
52
|
+
-- A rebuildable projection over the subset of the existing edge graph carrying
|
|
53
|
+
-- the resource-relationship contract. JSON edges remain the one authority.
|
|
54
|
+
CREATE TABLE IF NOT EXISTS resource_relationships (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
schema TEXT, "from" TEXT, "to" TEXT, type TEXT, reason TEXT, strength REAL,
|
|
57
|
+
currentness TEXT, environment TEXT, criticality TEXT, contract_version TEXT,
|
|
58
|
+
metadata TEXT, prov_source TEXT, prov_confidence REAL, prov_evidence TEXT
|
|
59
|
+
);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_resource_relationships_from ON resource_relationships("from");
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_resource_relationships_to ON resource_relationships("to");
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_resource_relationships_type ON resource_relationships(type);
|
|
63
|
+
|
|
41
64
|
CREATE TABLE IF NOT EXISTS symbols (
|
|
42
65
|
id TEXT PRIMARY KEY,
|
|
43
66
|
file TEXT, name TEXT, kind TEXT, signature_hash TEXT,
|
|
@@ -89,7 +112,7 @@ CREATE TABLE IF NOT EXISTS embeddings (
|
|
|
89
112
|
export const FTS_SEARCH_SCHEMA_SQL = /* sql */ `
|
|
90
113
|
CREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5(
|
|
91
114
|
ref UNINDEXED, -- entity id
|
|
92
|
-
kind UNINDEXED, -- components | edges | symbols | decisions | bugs | constraints | runbooks | findings
|
|
115
|
+
kind UNINDEXED, -- components | resources | edges | symbols | decisions | bugs | constraints | runbooks | findings
|
|
93
116
|
title,
|
|
94
117
|
body,
|
|
95
118
|
tokenize = 'porter unicode61'
|
|
@@ -110,7 +133,7 @@ CREATE INDEX IF NOT EXISTS idx_search_kind ON search(kind);
|
|
|
110
133
|
/** Drop derived data (used before a full reindex). NOTE: embeddings is omitted on
|
|
111
134
|
* purpose — see the embeddings table comment above. */
|
|
112
135
|
export const RESET_SQL = /* sql */ `
|
|
113
|
-
DELETE FROM components; DELETE FROM edges; DELETE FROM symbols;
|
|
136
|
+
DELETE FROM components; DELETE FROM resources; DELETE FROM edges; DELETE FROM resource_relationships; DELETE FROM symbols;
|
|
114
137
|
DELETE FROM decisions; DELETE FROM bugs; DELETE FROM constraints;
|
|
115
138
|
DELETE FROM search;
|
|
116
139
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davesheffer/hunch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"mcpName": "io.github.davesheffer/hunch",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Dave Sheffer <dave.sheffer1@gmail.com>",
|
|
@@ -71,11 +71,12 @@
|
|
|
71
71
|
"prepublishOnly": "npm run build"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
74
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
75
|
+
"@tree-sitter-grammars/tree-sitter-yaml": "^0.6.1",
|
|
75
76
|
"commander": "^15.0.0",
|
|
76
77
|
"tree-sitter": "0.21.1",
|
|
77
78
|
"tree-sitter-go": "^0.23.4",
|
|
78
|
-
"tree-sitter-python": "
|
|
79
|
+
"tree-sitter-python": "0.23.4",
|
|
79
80
|
"tree-sitter-typescript": "^0.23.2",
|
|
80
81
|
"zod": "^4.4.3"
|
|
81
82
|
},
|
package/server.json
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
9
|
"websiteUrl": "https://hunch-pi.vercel.app",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.18.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
15
|
"identifier": "@davesheffer/hunch",
|
|
16
|
-
"version": "1.
|
|
16
|
+
"version": "1.18.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"packageArguments": [
|
|
19
19
|
{
|