@davesheffer/hunch 0.1.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/LICENSE +21 -0
- package/README.md +241 -0
- package/dist/cli/index.js +587 -0
- package/dist/cli/invocation.js +23 -0
- package/dist/core/format.js +46 -0
- package/dist/core/glob.js +62 -0
- package/dist/core/ids.js +39 -0
- package/dist/core/io.js +44 -0
- package/dist/core/migrate.js +59 -0
- package/dist/core/paths.js +41 -0
- package/dist/core/types.js +142 -0
- package/dist/extractors/diff.js +198 -0
- package/dist/extractors/git.js +136 -0
- package/dist/extractors/indexer.js +271 -0
- package/dist/extractors/parse.js +176 -0
- package/dist/integrations/claudemd.js +77 -0
- package/dist/integrations/hooks.js +80 -0
- package/dist/integrations/mergeDriver.js +41 -0
- package/dist/integrations/scaffold.js +74 -0
- package/dist/mcp/server.js +233 -0
- package/dist/store/compact.js +100 -0
- package/dist/store/db.js +19 -0
- package/dist/store/embedder.js +133 -0
- package/dist/store/hunchStore.js +469 -0
- package/dist/store/jsonStore.js +268 -0
- package/dist/store/merge.js +179 -0
- package/dist/store/schema.js +100 -0
- package/dist/synthesis/provider.js +488 -0
- package/dist/synthesis/synthesize.js +312 -0
- package/package.json +68 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { commitMeta, commitDiff, headSha } from "../extractors/git.js";
|
|
2
|
+
import { analyzeDiff } from "../extractors/diff.js";
|
|
3
|
+
import { selectProvider, DeterministicProvider } from "./provider.js";
|
|
4
|
+
import { decisionId, bugId, constraintId } from "../core/ids.js";
|
|
5
|
+
import { pathMatchesGlob } from "../core/glob.js";
|
|
6
|
+
const CODE_RE = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
7
|
+
const SKIP_SUBJECT = /^(merge|revert|bump|chore\(deps\)|format|lint|wip)\b/i;
|
|
8
|
+
// Below this many changed code lines, a commit with no structural change and no
|
|
9
|
+
// explanatory body isn't worth a paid LLM call. Tunable via HUNCH_SIG_MIN_LINES.
|
|
10
|
+
const SIG_MIN_LINES = Number(process.env.HUNCH_SIG_MIN_LINES) || 12;
|
|
11
|
+
const SIG_MIN_BODY = 40;
|
|
12
|
+
/** Is a commit substantive enough to spend a paid LLM synthesis call on? Pure and
|
|
13
|
+
* deterministic. Any structural change (symbol/dependency delta), non-trivial
|
|
14
|
+
* churn, several files, OR an explanatory commit body signals a real decision
|
|
15
|
+
* worth the model. Everything below (typo/tweak/one-liner with no message) falls
|
|
16
|
+
* to the free deterministic draft — shallower but honestly low-confidence. */
|
|
17
|
+
export function isSignificant(meta, a, codeFiles) {
|
|
18
|
+
const structural = a.addedSymbols.length + a.removedSymbols.length + a.changedSymbols.length + a.addedDeps.length + a.removedDeps.length;
|
|
19
|
+
if (structural > 0)
|
|
20
|
+
return true;
|
|
21
|
+
if (a.addedLines + a.removedLines >= SIG_MIN_LINES)
|
|
22
|
+
return true;
|
|
23
|
+
if (codeFiles.length >= 3)
|
|
24
|
+
return true;
|
|
25
|
+
if (meta.body.trim().length >= SIG_MIN_BODY)
|
|
26
|
+
return true;
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
/** Capture a Decision from a commit. Defaults to HEAD. Pass `{ force: true }` to
|
|
30
|
+
* re-synthesize a commit that already has an (auto-drafted) decision. */
|
|
31
|
+
export async function syncCommit(store, root, sha, opts = {}) {
|
|
32
|
+
const target = sha || headSha(root);
|
|
33
|
+
if (!target)
|
|
34
|
+
return { status: "skipped", reason: "no HEAD commit" };
|
|
35
|
+
const meta = commitMeta(target, root);
|
|
36
|
+
if (!meta)
|
|
37
|
+
return { status: "skipped", reason: "commit not found" };
|
|
38
|
+
if (SKIP_SUBJECT.test(meta.subject))
|
|
39
|
+
return { status: "skipped", reason: `trivial subject: ${meta.subject}` };
|
|
40
|
+
const codeFiles = meta.files.filter((f) => CODE_RE.test(f));
|
|
41
|
+
if (codeFiles.length === 0)
|
|
42
|
+
return { status: "skipped", reason: "no code files changed" };
|
|
43
|
+
// Seed the id from the COMMIT (stable across runs), not the LLM-generated title
|
|
44
|
+
// (which varies) — so re-syncing a commit updates rather than dupes.
|
|
45
|
+
const id = decisionId(meta.sha);
|
|
46
|
+
const existing = store.json.get("decisions", id);
|
|
47
|
+
// Never clobber a human-confirmed decision with a low-confidence auto-draft —
|
|
48
|
+
// even under --force. Skip BEFORE synthesizing so we never pay for a draft we'd
|
|
49
|
+
// throw away (the old order drafted first, then discarded it here).
|
|
50
|
+
if (existing && existing.provenance.source.includes("human_confirmed")) {
|
|
51
|
+
return { status: "skipped", reason: "human-confirmed decision exists for this commit", decision: existing };
|
|
52
|
+
}
|
|
53
|
+
// Token-thrift idempotency: a decision is already captured for this commit, so
|
|
54
|
+
// don't re-pay the LLM on a re-run (hook double-fire, overlapping backfill, or
|
|
55
|
+
// a manual replay). Re-synthesize only when explicitly forced.
|
|
56
|
+
if (existing && !opts.force) {
|
|
57
|
+
return {
|
|
58
|
+
status: "skipped",
|
|
59
|
+
reason: "decision already captured for this commit (use --force to re-synthesize)",
|
|
60
|
+
decision: existing,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const diff = commitDiff(target, root);
|
|
64
|
+
const analysis = analyzeDiff(diff);
|
|
65
|
+
// Significance gate: reserve the paid LLM for substantive commits; trivial ones
|
|
66
|
+
// get the FREE deterministic draft (honestly labeled "inferred"/low-confidence,
|
|
67
|
+
// so the Hunch stays accurate-by-provenance). --force always uses the provider.
|
|
68
|
+
const provider = opts.force || isSignificant(meta, analysis, codeFiles)
|
|
69
|
+
? await selectProvider()
|
|
70
|
+
: new DeterministicProvider();
|
|
71
|
+
const input = { subject: meta.subject, body: meta.body, files: codeFiles, diff, analysis };
|
|
72
|
+
const draft = await draftDecisionSafe(provider, input);
|
|
73
|
+
const components = store.json.loadAll("components");
|
|
74
|
+
const relatedComponents = components
|
|
75
|
+
.filter((c) => codeFiles.some((f) => c.paths.some((g) => pathMatchesGlob(f, g))))
|
|
76
|
+
.map((c) => c.id);
|
|
77
|
+
// Surface any do-not-break constraints this commit's files touch (DESIGN §4
|
|
78
|
+
// "constraint touched" flag) right in the decision context, with evidence.
|
|
79
|
+
const touchedConstraints = store.json
|
|
80
|
+
.loadAll("constraints")
|
|
81
|
+
.filter((c) => codeFiles.some((f) => c.scope.some((g) => pathMatchesGlob(f, g))));
|
|
82
|
+
const constraintNote = touchedConstraints.length
|
|
83
|
+
? ` Touches invariant(s): ${touchedConstraints.map((c) => `${c.id} (${c.statement})`).join("; ")}.`
|
|
84
|
+
: "";
|
|
85
|
+
const decision = {
|
|
86
|
+
id,
|
|
87
|
+
title: draft.title,
|
|
88
|
+
status: existing?.status === "accepted" ? "accepted" : "proposed",
|
|
89
|
+
context: draft.context + constraintNote,
|
|
90
|
+
decision: draft.decision,
|
|
91
|
+
consequences: draft.consequences,
|
|
92
|
+
alternatives_rejected: draft.alternatives_rejected,
|
|
93
|
+
related_components: relatedComponents,
|
|
94
|
+
related_files: codeFiles,
|
|
95
|
+
supersedes: existing?.supersedes ?? null,
|
|
96
|
+
caused_by_bug: existing?.caused_by_bug ?? null,
|
|
97
|
+
commit: meta.shortSha,
|
|
98
|
+
provenance: {
|
|
99
|
+
source: draft.source,
|
|
100
|
+
confidence: draft.confidence,
|
|
101
|
+
evidence: [`commit:${meta.shortSha}`, ...codeFiles.slice(0, 8)],
|
|
102
|
+
last_verified: new Date().toISOString(), // when the Hunch last re-derived this
|
|
103
|
+
},
|
|
104
|
+
date: meta.date, // the commit date
|
|
105
|
+
};
|
|
106
|
+
store.json.put("decisions", decision);
|
|
107
|
+
return { status: "written", decision, provider: provider.name };
|
|
108
|
+
}
|
|
109
|
+
/** Capture a Bug from a test failure. Suspects are ranked churn×recency×fan-in. */
|
|
110
|
+
export async function recordFailure(store, root, failure) {
|
|
111
|
+
const symbols = store.json.loadAll("symbols");
|
|
112
|
+
const ranked = rankSuspects(symbols, failure.message);
|
|
113
|
+
// Prefer symbols actually named in the failure — so unrelated failures don't
|
|
114
|
+
// get the same boilerplate suspect list (which would fake a recurrence).
|
|
115
|
+
const msg = failure.message.toLowerCase();
|
|
116
|
+
const mentioned = ranked.filter((s) => msg.includes(s.name.toLowerCase()));
|
|
117
|
+
const suspects = (mentioned.length ? mentioned : ranked).slice(0, 6);
|
|
118
|
+
const provider = await selectProvider();
|
|
119
|
+
const input = {
|
|
120
|
+
test: failure.test,
|
|
121
|
+
message: failure.message,
|
|
122
|
+
// recent commit diff gives the synthesizer context for the root-cause guess
|
|
123
|
+
recentDiff: failure.recentDiff ?? recentDiffFor(root),
|
|
124
|
+
suspects: suspects.map((s) => `${s.name} @ ${s.file}`),
|
|
125
|
+
};
|
|
126
|
+
const draft = await draftBugSafe(provider, input);
|
|
127
|
+
// Seed the id from the test id (stable), not the LLM title — one bug per test.
|
|
128
|
+
const id = bugId(failure.test);
|
|
129
|
+
// recurrence = a DIFFERENT prior bug with a similar symptom (not this same one).
|
|
130
|
+
// Query text mirrors the corpus side (title+symptom+root_cause) for symmetry.
|
|
131
|
+
const prior = findRecurrence(store, `${draft.title} ${draft.symptom} ${draft.root_cause}`, id);
|
|
132
|
+
const affectedFiles = [...new Set(suspects.map((s) => s.file))];
|
|
133
|
+
const bug = {
|
|
134
|
+
id,
|
|
135
|
+
title: draft.title,
|
|
136
|
+
symptom: draft.symptom,
|
|
137
|
+
root_cause: draft.root_cause,
|
|
138
|
+
severity: draft.severity,
|
|
139
|
+
status: "open",
|
|
140
|
+
affected_files: affectedFiles,
|
|
141
|
+
affected_symbols: suspects.map((s) => s.id),
|
|
142
|
+
lineage: {
|
|
143
|
+
introduced_commit: null,
|
|
144
|
+
detected: failure.test,
|
|
145
|
+
fixed_commit: null,
|
|
146
|
+
recurrence_of: prior?.id ?? null,
|
|
147
|
+
spawned_decision: null,
|
|
148
|
+
spawned_constraint: null,
|
|
149
|
+
},
|
|
150
|
+
provenance: {
|
|
151
|
+
source: draft.source,
|
|
152
|
+
confidence: draft.confidence,
|
|
153
|
+
evidence: [`test:${failure.test}`, ...affectedFiles.slice(0, 6)],
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
store.json.put("bugs", bug);
|
|
157
|
+
// Promotion (DESIGN §4): a recurrence or a SUBSTANTIATED high-severity bug raises
|
|
158
|
+
// a regression Constraint to stop it coming back, and bumps fragility.
|
|
159
|
+
let constraint;
|
|
160
|
+
if (shouldPromoteConstraint(draft.severity, bug.root_cause, !!prior)) {
|
|
161
|
+
constraint = promoteConstraint(store, bug);
|
|
162
|
+
bug.lineage.spawned_constraint = constraint.id;
|
|
163
|
+
store.json.put("bugs", bug); // re-persist with the link
|
|
164
|
+
}
|
|
165
|
+
raiseFragility(store, affectedFiles);
|
|
166
|
+
return { status: "written", bug, constraint, provider: provider.name };
|
|
167
|
+
}
|
|
168
|
+
/** Whether a bug should auto-promote a regression Constraint (a do-not-break
|
|
169
|
+
* invariant). A recurrence always does. Otherwise it must be high/critical AND
|
|
170
|
+
* substantiated by a real root cause — a bare severity label with no analysis
|
|
171
|
+
* (e.g. an LLM "test_failure+llm_partial" draft) keeps its severity on the bug
|
|
172
|
+
* record for human review but must not silently mint an invariant from thin air. */
|
|
173
|
+
export function shouldPromoteConstraint(severity, rootCause, isRecurrence) {
|
|
174
|
+
if (isRecurrence)
|
|
175
|
+
return true;
|
|
176
|
+
const severe = severity === "high" || severity === "critical";
|
|
177
|
+
return severe && rootCause.trim().length > 0;
|
|
178
|
+
}
|
|
179
|
+
/** Turn a bug into an advisory regression constraint scoped to its files. */
|
|
180
|
+
function promoteConstraint(store, bug) {
|
|
181
|
+
const scope = bug.affected_files.length ? bug.affected_files : ["**"];
|
|
182
|
+
const statement = `Regression guard: "${bug.title}" must not recur.`;
|
|
183
|
+
const con = {
|
|
184
|
+
id: constraintId(statement),
|
|
185
|
+
type: bug.severity === "critical" ? "security" : "correctness",
|
|
186
|
+
statement,
|
|
187
|
+
scope,
|
|
188
|
+
severity: bug.severity === "critical" ? "blocking" : "warning",
|
|
189
|
+
enforcement: "advisory_v1",
|
|
190
|
+
rationale: `Derived from ${bug.id}: ${bug.root_cause || bug.symptom}`,
|
|
191
|
+
source_decision: null,
|
|
192
|
+
violations: [],
|
|
193
|
+
provenance: { source: "derived", confidence: Math.min(0.9, bug.provenance.confidence + 0.2), evidence: [`bug:${bug.id}`] },
|
|
194
|
+
};
|
|
195
|
+
return store.json.put("constraints", con);
|
|
196
|
+
}
|
|
197
|
+
/** Bump fragility on components owning the affected files. */
|
|
198
|
+
function raiseFragility(store, files) {
|
|
199
|
+
const comps = store.json.loadAll("components");
|
|
200
|
+
for (const c of comps) {
|
|
201
|
+
if (files.some((f) => c.paths.some((g) => pathMatchesGlob(f, g)))) {
|
|
202
|
+
const next = Math.min(1, Math.round((c.fragility + 0.1) * 100) / 100);
|
|
203
|
+
if (next !== c.fragility)
|
|
204
|
+
store.json.put("components", { ...c, fragility: next, updated_at: new Date().toISOString() });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** churn × recency × fan_in, boosted if the symbol name appears in the message. */
|
|
209
|
+
function rankSuspects(symbols, message) {
|
|
210
|
+
const msg = message.toLowerCase();
|
|
211
|
+
return symbols
|
|
212
|
+
.map((s) => {
|
|
213
|
+
const mentioned = msg.includes(s.name.toLowerCase()) || msg.includes(s.file.toLowerCase());
|
|
214
|
+
const score = (s.metrics.churn_90d + 1) * (s.metrics.fan_in + 1) * (mentioned ? 5 : 1);
|
|
215
|
+
return { s, score };
|
|
216
|
+
})
|
|
217
|
+
.sort((a, b) => b.score - a.score)
|
|
218
|
+
.map((x) => x.s);
|
|
219
|
+
}
|
|
220
|
+
// Boilerplate/stopwords that must not drive recurrence matching — includes the
|
|
221
|
+
// deterministic provider's structural tokens (spec/suspected/src/...) and common
|
|
222
|
+
// test/JS filler, so two unrelated failures can't "match" on boilerplate alone.
|
|
223
|
+
const STOPWORDS = new Set([
|
|
224
|
+
"test", "failure", "error", "the", "a", "an", "and", "or", "of", "to", "in", "on", "for",
|
|
225
|
+
"with", "is", "was", "after", "before", "returned", "return", "valid", "expected", "actual",
|
|
226
|
+
// deterministic-provider / path boilerplate
|
|
227
|
+
"spec", "suspected", "src", "lib", "index", "unknown", "available", "llm",
|
|
228
|
+
// generic JS/test filler
|
|
229
|
+
"threw", "throws", "thrown", "null", "undefined", "property", "object", "cannot",
|
|
230
|
+
"read", "value", "failed", "assert", "assertion", "function", "type", "string", "number",
|
|
231
|
+
]);
|
|
232
|
+
/** Neutralize boilerplate that would otherwise drive recurrence matching:
|
|
233
|
+
* - the deterministic provider's synthetic "Suspected in: a @ p, b @ q" list
|
|
234
|
+
* (the SAME churn-ranked suspects get injected into every no-mention failure,
|
|
235
|
+
* so two unrelated bugs would falsely share those identifier names), and
|
|
236
|
+
* - file-path tails ("... @ src/x.ts"). */
|
|
237
|
+
function stripPaths(text) {
|
|
238
|
+
return text
|
|
239
|
+
// Anchored to the deterministic provider's structural shape
|
|
240
|
+
// ("Suspected in: <ident> @ <path>, ...") so we strip the synthetic suspect
|
|
241
|
+
// list but NOT legitimate prose that merely contains the phrase.
|
|
242
|
+
.replace(/suspected in:\s*[\w$]+\s*@[^\n]*/gi, " ")
|
|
243
|
+
.replace(/@\s*\S+/g, " ")
|
|
244
|
+
.replace(/[\w./-]+\.(ts|tsx|js|jsx|mts|cts)\b/g, " ");
|
|
245
|
+
}
|
|
246
|
+
export function salientTerms(text) {
|
|
247
|
+
const out = new Set();
|
|
248
|
+
for (const t of stripPaths(text).toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []) {
|
|
249
|
+
if (t.length > 2 && !STOPWORDS.has(t))
|
|
250
|
+
out.add(t);
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
/** Recurrence = a DIFFERENT prior bug whose salient terms overlap strongly with
|
|
255
|
+
* this one (in-memory, no FTS/reindex dependency, threshold-gated to avoid the
|
|
256
|
+
* over-broad OR false positives). Returns the best match above threshold. */
|
|
257
|
+
function findRecurrence(store, text, excludeId) {
|
|
258
|
+
const want = salientTerms(text);
|
|
259
|
+
if (want.size === 0)
|
|
260
|
+
return undefined;
|
|
261
|
+
let best;
|
|
262
|
+
let bestScore = 0;
|
|
263
|
+
for (const b of store.json.loadAll("bugs")) {
|
|
264
|
+
if (b.id === excludeId)
|
|
265
|
+
continue;
|
|
266
|
+
// symmetric with the query side (which now also includes root_cause)
|
|
267
|
+
const have = salientTerms(`${b.title} ${b.symptom} ${b.root_cause}`);
|
|
268
|
+
if (have.size === 0)
|
|
269
|
+
continue;
|
|
270
|
+
let shared = 0;
|
|
271
|
+
for (const t of want)
|
|
272
|
+
if (have.has(t))
|
|
273
|
+
shared++;
|
|
274
|
+
const jaccard = shared / (want.size + have.size - shared);
|
|
275
|
+
// Scale-aware gate: small term sets need MORE shared terms so coincidental
|
|
276
|
+
// overlap (a couple of common words) can't masquerade as a recurrence —
|
|
277
|
+
// EXCEPT when the overlap is near-total (jaccard >= 0.8), which means the two
|
|
278
|
+
// texts are nearly identical and a terse 2-term match is a real recurrence.
|
|
279
|
+
const minSize = Math.min(want.size, have.size);
|
|
280
|
+
const minShared = jaccard >= 0.8 ? 2 : Math.max(3, Math.ceil(0.5 * minSize));
|
|
281
|
+
if (shared >= minShared && jaccard >= 0.4 && jaccard > bestScore) {
|
|
282
|
+
best = b;
|
|
283
|
+
bestScore = jaccard;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return best;
|
|
287
|
+
}
|
|
288
|
+
/** Best-effort recent commit diff for failure context (empty if not a git repo). */
|
|
289
|
+
function recentDiffFor(root) {
|
|
290
|
+
const head = headSha(root);
|
|
291
|
+
return head ? commitDiff(head, root, 12_000) : "";
|
|
292
|
+
}
|
|
293
|
+
export async function draftDecisionSafe(provider, input) {
|
|
294
|
+
try {
|
|
295
|
+
return await provider.draftDecision(input);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// A provider failure (network, bad creds, CLI crash, unparseable output) must
|
|
299
|
+
// never abort the learning loop — fall back to the deterministic heuristic
|
|
300
|
+
// draft, which is honestly labeled ("inferred") rather than a hollow llm_draft.
|
|
301
|
+
return new DeterministicProvider().draftDecision(input);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
export async function draftBugSafe(provider, input) {
|
|
305
|
+
try {
|
|
306
|
+
return await provider.draftBug(input);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return new DeterministicProvider().draftBug(input);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=synthesize.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@davesheffer/hunch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "Dave Sheffer <dave.sheffer1@gmail.com>",
|
|
6
|
+
"description": "Hunch — an Engineering Memory OS: a persistent, git-native reasoning graph over a codebase, exposed to Claude Code via MCP.",
|
|
7
|
+
"homepage": "https://hunch.sh",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/davesheffer/hunch.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/davesheffer/hunch/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"bin": {
|
|
17
|
+
"hunch": "dist/cli/index.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist/**/*.js"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"claude-code",
|
|
27
|
+
"mcp",
|
|
28
|
+
"engineering-memory",
|
|
29
|
+
"knowledge-graph",
|
|
30
|
+
"code-intelligence",
|
|
31
|
+
"ai",
|
|
32
|
+
"developer-tools"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"clean": "node --input-type=commonjs -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
39
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
40
|
+
"dev": "tsx src/cli/index.ts",
|
|
41
|
+
"hunch": "tsx src/cli/index.ts",
|
|
42
|
+
"test": "tsx --test test/*.test.ts",
|
|
43
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
44
|
+
"prepublishOnly": "npm run build"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
48
|
+
"better-sqlite3": "12.9.0",
|
|
49
|
+
"commander": "^15.0.0",
|
|
50
|
+
"tree-sitter": "0.21.1",
|
|
51
|
+
"tree-sitter-typescript": "^0.23.2",
|
|
52
|
+
"zod": "^4.4.3"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
56
|
+
"@types/node": "^20.19.0",
|
|
57
|
+
"tsx": "^4.22.4",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"@huggingface/transformers": ">=3"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"@huggingface/transformers": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|