@davesheffer/hunch 1.8.0 → 1.8.2
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 +56 -334
- package/dist/cli/index.js +37 -37
- package/dist/cli/invocation.js +55 -2
- package/dist/extractors/diff.js +19 -3
- package/dist/extractors/indexer.js +65 -6
- package/dist/extractors/languages.js +114 -0
- package/dist/extractors/nativeTreeSitter.js +9 -7
- package/dist/extractors/parse.js +25 -68
- package/dist/integrations/claudemd.js +2 -1
- package/dist/store/embedder.js +1 -1
- package/dist/synthesis/provider.js +271 -51
- package/dist/synthesis/synthesize.js +19 -7
- package/dist/wiki/wiki.js +2 -2
- package/package.json +2 -1
package/dist/extractors/parse.js
CHANGED
|
@@ -1,62 +1,25 @@
|
|
|
1
|
+
import { languageFor } from "./languages.js";
|
|
1
2
|
import { loadNativeTreeSitter } from "./nativeTreeSitter.js";
|
|
2
|
-
const { Parser
|
|
3
|
-
/** Extremely common builtin/array/object/string/promise method names. Member
|
|
4
|
-
* calls to these (e.g. `arr.map(...)`) must NOT create call edges to unrelated
|
|
5
|
-
* repo symbols that happen to share the name (DESIGN: keep the graph clean). */
|
|
6
|
-
const BUILTIN_METHODS = new Set([
|
|
7
|
-
"map", "filter", "forEach", "reduce", "find", "findIndex", "some", "every", "includes",
|
|
8
|
-
"push", "pop", "shift", "unshift", "slice", "splice", "concat", "join", "split", "flat", "flatMap",
|
|
9
|
-
"indexOf", "lastIndexOf", "keys", "values", "entries", "sort", "reverse", "fill", "at",
|
|
10
|
-
"get", "set", "has", "add", "delete", "clear",
|
|
11
|
-
"then", "catch", "finally", "all", "race", "resolve", "reject",
|
|
12
|
-
"toString", "valueOf", "toJSON", "hasOwnProperty",
|
|
13
|
-
"replace", "replaceAll", "trim", "trimStart", "trimEnd", "padStart", "padEnd", "startsWith", "endsWith",
|
|
14
|
-
"toLowerCase", "toUpperCase", "charAt", "charCodeAt", "substring", "substr", "repeat", "match", "matchAll",
|
|
15
|
-
"call", "apply", "bind", "test", "exec", "now", "parse", "stringify", "from", "of", "isArray", "assign",
|
|
16
|
-
"log", "error", "warn", "info", "debug",
|
|
17
|
-
]);
|
|
18
|
-
/** Tree-sitter query capturing every construct we care about in one pass. */
|
|
19
|
-
const QUERY_SRC = `
|
|
20
|
-
(function_declaration name: (identifier) @fn.name) @fn.def
|
|
21
|
-
(generator_function_declaration name: (identifier) @fn.name) @fn.def
|
|
22
|
-
(method_definition name: (property_identifier) @method.name) @method.def
|
|
23
|
-
(class_declaration name: (type_identifier) @class.name) @class.def
|
|
24
|
-
(interface_declaration name: (type_identifier) @iface.name) @iface.def
|
|
25
|
-
(type_alias_declaration name: (type_identifier) @type.name) @type.def
|
|
26
|
-
(variable_declarator
|
|
27
|
-
name: (identifier) @arrow.name
|
|
28
|
-
value: [(arrow_function) (function_expression)]) @arrow.def
|
|
29
|
-
(import_statement source: (string) @import.src)
|
|
30
|
-
(call_expression function: (identifier) @call.id)
|
|
31
|
-
(call_expression function: (member_expression property: (property_identifier) @call.member))
|
|
32
|
-
`;
|
|
3
|
+
const { Parser } = loadNativeTreeSitter();
|
|
33
4
|
const cache = new Map();
|
|
34
|
-
function bundleFor(
|
|
35
|
-
let b = cache.get(
|
|
5
|
+
function bundleFor(spec) {
|
|
6
|
+
let b = cache.get(spec.grammarKey);
|
|
36
7
|
if (!b) {
|
|
37
8
|
const parser = new Parser();
|
|
38
|
-
|
|
39
|
-
|
|
9
|
+
const grammar = spec.loadGrammar();
|
|
10
|
+
parser.setLanguage(grammar);
|
|
11
|
+
const query = new Parser.Query(grammar, spec.query);
|
|
40
12
|
b = { parser, query };
|
|
41
|
-
cache.set(
|
|
13
|
+
cache.set(spec.grammarKey, b);
|
|
42
14
|
}
|
|
43
15
|
return b;
|
|
44
16
|
}
|
|
45
|
-
function pickLanguage(file) {
|
|
46
|
-
if (file.endsWith(".tsx") || file.endsWith(".jsx"))
|
|
47
|
-
return { lang: tsx, key: "tsx" };
|
|
48
|
-
if (file.endsWith(".ts") || file.endsWith(".mts") || file.endsWith(".cts"))
|
|
49
|
-
return { lang: typescript, key: "ts" };
|
|
50
|
-
if (file.endsWith(".js") || file.endsWith(".mjs") || file.endsWith(".cjs"))
|
|
51
|
-
return { lang: typescript, key: "ts" };
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
17
|
const STR_QUOTES = /^['"`]|['"`]$/g;
|
|
55
18
|
export function parseSource(file, source) {
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
19
|
+
const spec = languageFor(file);
|
|
20
|
+
if (!spec)
|
|
58
21
|
return null;
|
|
59
|
-
const { parser, query } = bundleFor(
|
|
22
|
+
const { parser, query } = bundleFor(spec);
|
|
60
23
|
// The native binding caps its scratch buffer at 32 KB unless bufferSize is
|
|
61
24
|
// given — without this, any source >= 32768 bytes throws "Invalid argument"
|
|
62
25
|
// and would abort the whole index run. Guard with try/catch as a backstop.
|
|
@@ -73,29 +36,27 @@ export function parseSource(file, source) {
|
|
|
73
36
|
// group captures by their enclosing @*.def via a quick pass: we record names
|
|
74
37
|
// keyed by the def node, then emit a symbol per def.
|
|
75
38
|
const pendingDefs = new Map();
|
|
76
|
-
const defKind = {
|
|
77
|
-
"fn.def": "function", "method.def": "method", "class.def": "class",
|
|
78
|
-
"iface.def": "interface", "type.def": "type", "arrow.def": "function",
|
|
79
|
-
};
|
|
80
|
-
const nameToDef = {
|
|
81
|
-
"fn.name": "fn.def", "method.name": "method.def", "class.name": "class.def",
|
|
82
|
-
"iface.name": "iface.def", "type.name": "type.def", "arrow.name": "arrow.def",
|
|
83
|
-
};
|
|
84
39
|
for (const cap of query.captures(tree.rootNode)) {
|
|
85
40
|
const cname = cap.name;
|
|
86
41
|
const node = cap.node;
|
|
87
42
|
if (cname.endsWith(".def")) {
|
|
88
|
-
|
|
43
|
+
// Keep the FIRST classification a node id receives: a query may have
|
|
44
|
+
// several patterns matching the same node at different specificity
|
|
45
|
+
// (e.g. a Python method inside a class body matches both a class-nested
|
|
46
|
+
// "method.def" pattern and a general "fn.def" pattern — Task 4 relies on
|
|
47
|
+
// this to classify methods correctly without special-casing Python here).
|
|
48
|
+
if (!pendingDefs.has(node.id))
|
|
49
|
+
pendingDefs.set(node.id, { kind: spec.defKindOf[cname], def: node });
|
|
89
50
|
}
|
|
90
|
-
else if (nameToDef[cname]) {
|
|
51
|
+
else if (spec.nameToDef[cname]) {
|
|
91
52
|
// name capture: find its parent def node id by walking up to the def type
|
|
92
|
-
const defNode = ascendToDef(node);
|
|
53
|
+
const defNode = ascendToDef(node, spec.defNodeTypes);
|
|
93
54
|
if (defNode) {
|
|
94
55
|
const existing = pendingDefs.get(defNode.id);
|
|
95
56
|
if (existing)
|
|
96
57
|
existing.name = node.text;
|
|
97
58
|
else
|
|
98
|
-
pendingDefs.set(defNode.id, { kind:
|
|
59
|
+
pendingDefs.set(defNode.id, { kind: spec.defKindOf[spec.nameToDef[cname]], def: defNode, name: node.text });
|
|
99
60
|
}
|
|
100
61
|
}
|
|
101
62
|
else if (cname === "import.src") {
|
|
@@ -106,7 +67,7 @@ export function parseSource(file, source) {
|
|
|
106
67
|
}
|
|
107
68
|
else if (cname === "call.member") {
|
|
108
69
|
// skip builtin method names to avoid false edges to similarly-named symbols
|
|
109
|
-
if (!
|
|
70
|
+
if (!spec.builtinMethods.has(node.text))
|
|
110
71
|
calls.push({ callee: node.text, atByte: node.startIndex, endByte: node.endIndex, member: true });
|
|
111
72
|
}
|
|
112
73
|
}
|
|
@@ -123,15 +84,11 @@ export function parseSource(file, source) {
|
|
|
123
84
|
symbols.sort((a, b) => a.startByte - b.startByte);
|
|
124
85
|
return { symbols, imports, calls, parseable: !tree.rootNode.hasError };
|
|
125
86
|
}
|
|
126
|
-
/** Walk up to the nearest node whose type is a definition
|
|
127
|
-
function ascendToDef(node) {
|
|
128
|
-
const defTypes = new Set([
|
|
129
|
-
"function_declaration", "generator_function_declaration", "method_definition",
|
|
130
|
-
"class_declaration", "interface_declaration", "type_alias_declaration", "variable_declarator",
|
|
131
|
-
]);
|
|
87
|
+
/** Walk up to the nearest node whose type is a definition this language recognizes. */
|
|
88
|
+
function ascendToDef(node, defNodeTypes) {
|
|
132
89
|
let cur = node.parent;
|
|
133
90
|
while (cur) {
|
|
134
|
-
if (
|
|
91
|
+
if (defNodeTypes.has(cur.type))
|
|
135
92
|
return cur;
|
|
136
93
|
cur = cur.parent;
|
|
137
94
|
}
|
|
@@ -35,7 +35,8 @@ export function renderHunchSection(store, root) {
|
|
|
35
35
|
lines.push("- `hunch_context(target_or_task)` — the minimal relevant slice for what you're about to do; a task phrase falls back to the closest graph matches. **Call FIRST.**");
|
|
36
36
|
lines.push("- `hunch_structure(target?)` — the indexed shape of the repo/dir/file/symbol — orient from the graph, not grep rounds.");
|
|
37
37
|
lines.push("- `hunch_runbook(task)` — the proven steps for a recurring task, before re-deriving them.");
|
|
38
|
-
lines.push("- `
|
|
38
|
+
lines.push("- `hunch_escalations()` — the decisions only the HUMAN can make (topic conflicts, candidate/proposed rules, repaired rules needing a re-prove). Normally empty; when it isn't, ASK the user inline — an entry is a question, never an approval.");
|
|
39
|
+
lines.push("- `hunch now` (CLI) — recent decisions + the live roadmap; `hunch log` — the memory-move timeline (every capture/adopt/supersede/prune/repair, each revertable).");
|
|
39
40
|
lines.push("");
|
|
40
41
|
lines.push("**Before designing / choosing an approach:**");
|
|
41
42
|
lines.push("- `hunch_why(target)` — why a file/symbol is shaped this way (decisions, bugs, constraints) — including what was already REJECTED.");
|
package/dist/store/embedder.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* "add embeddings once keyword search proves insufficient" upgrade).
|
|
4
4
|
*
|
|
5
5
|
* Embeddings are LOCAL and FREE. Anthropic has no embeddings endpoint and the
|
|
6
|
-
* project
|
|
6
|
+
* project avoids implicit metered inference (see synthesis/provider.ts), so we run a small
|
|
7
7
|
* sentence-transformer locally via transformers.js. That library is an OPTIONAL
|
|
8
8
|
* dependency, dynamically imported — if it isn't installed, `selectEmbedder()`
|
|
9
9
|
* returns null and the whole feature degrades to pure FTS (the lean-install
|