brainclaw 1.9.1 → 1.10.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.
Files changed (71) hide show
  1. package/README.md +47 -1
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +18 -1
  4. package/dist/commands/code-map.js +129 -0
  5. package/dist/commands/codev.js +7 -0
  6. package/dist/commands/mcp.js +121 -0
  7. package/dist/commands/run-profile.js +3 -2
  8. package/dist/commands/switch.js +100 -89
  9. package/dist/core/agent-files.js +12 -0
  10. package/dist/core/code-map/backend.js +123 -0
  11. package/dist/core/code-map/core.js +81 -0
  12. package/dist/core/code-map/drafts.js +2 -0
  13. package/dist/core/code-map/extractor.js +29 -0
  14. package/dist/core/code-map/finalizer.js +191 -0
  15. package/dist/core/code-map/freshness.js +108 -0
  16. package/dist/core/code-map/ids.js +0 -0
  17. package/dist/core/code-map/importable.js +35 -0
  18. package/dist/core/code-map/indexes.js +197 -0
  19. package/dist/core/code-map/lang/java/imports.scm +17 -0
  20. package/dist/core/code-map/lang/java/index.js +254 -0
  21. package/dist/core/code-map/lang/java/tags.scm +48 -0
  22. package/dist/core/code-map/lang/php/imports.scm +21 -0
  23. package/dist/core/code-map/lang/php/index.js +251 -0
  24. package/dist/core/code-map/lang/php/tags.scm +44 -0
  25. package/dist/core/code-map/lang/provider.js +9 -0
  26. package/dist/core/code-map/lang/providers.js +24 -0
  27. package/dist/core/code-map/lang/python/imports.scm +90 -0
  28. package/dist/core/code-map/lang/python/index.js +364 -0
  29. package/dist/core/code-map/lang/python/tags.scm +81 -0
  30. package/dist/core/code-map/lang/query-runtime.js +374 -0
  31. package/dist/core/code-map/lang/registry.js +125 -0
  32. package/dist/core/code-map/lang/typescript/imports.scm +90 -0
  33. package/dist/core/code-map/lang/typescript/index.js +306 -0
  34. package/dist/core/code-map/lang/typescript/tags.js.scm +106 -0
  35. package/dist/core/code-map/lang/typescript/tags.scm +151 -0
  36. package/dist/core/code-map/lock.js +210 -0
  37. package/dist/core/code-map/materialized.js +51 -0
  38. package/dist/core/code-map/memory-reader.js +59 -0
  39. package/dist/core/code-map/paths.js +53 -0
  40. package/dist/core/code-map/query.js +568 -0
  41. package/dist/core/code-map/refresh.js +0 -0
  42. package/dist/core/code-map/resolve.js +177 -0
  43. package/dist/core/code-map/store.js +206 -0
  44. package/dist/core/code-map/types.js +288 -0
  45. package/dist/core/code-map/vocabulary.js +57 -0
  46. package/dist/core/code-map/wasm-loader.js +294 -0
  47. package/dist/core/code-map/work-section.js +206 -0
  48. package/dist/core/codev-rounds.js +4 -0
  49. package/dist/core/execution-adapters.js +11 -10
  50. package/dist/core/execution-profile.js +58 -0
  51. package/dist/core/facade-schema.js +9 -0
  52. package/dist/core/instruction-templates.js +2 -0
  53. package/dist/core/mcp-command-resolution.js +3 -1
  54. package/dist/core/store-resolution.js +41 -4
  55. package/dist/facts.js +9 -5
  56. package/dist/facts.json +8 -4
  57. package/dist/vendor/web-tree-sitter/tree-sitter.js +3980 -0
  58. package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
  59. package/dist/wasm/tree-sitter-java.wasm +0 -0
  60. package/dist/wasm/tree-sitter-javascript.wasm +0 -0
  61. package/dist/wasm/tree-sitter-php.wasm +0 -0
  62. package/dist/wasm/tree-sitter-python.wasm +0 -0
  63. package/dist/wasm/tree-sitter-tsx.wasm +0 -0
  64. package/dist/wasm/tree-sitter-typescript.wasm +0 -0
  65. package/dist/wasm/tree-sitter.wasm +0 -0
  66. package/docs/cli.md +46 -8
  67. package/docs/code-map.md +198 -0
  68. package/docs/integrations/mcp.md +13 -6
  69. package/docs/mcp-schema-changelog.md +7 -3
  70. package/docs/quickstart.md +1 -1
  71. package/package.json +11 -6
@@ -0,0 +1,90 @@
1
+ ; Brainclaw Code Map — Python IMPORTS query (curated, vendored).
2
+ ; Grammar: tree-sitter-python. See ./README.md for the capture-name convention.
3
+ ;
4
+ ; Python has NO export statement, so this asset emits imports ONLY (no @export.name).
5
+ ; Capabilities declare T2.imports = complete (specifiers); resolution is P1c.
6
+ ;
7
+ ; Capture -> draft mapping performed by the generic query-runtime:
8
+ ; @import.source -> ImportDraft.source (the runtime strips surrounding quotes;
9
+ ; Python module names are bare identifiers/dotted_names, so
10
+ ; there is nothing to strip — the text passes through verbatim,
11
+ ; which is exactly what relative-import dots `.` / `..pkg`
12
+ ; need). ALSO anchors ImportDraft.span = enclosing
13
+ ; import_statement / import_from_statement.
14
+ ; @import.named.name -> a source-side imported name (the `from x import NAME` target,
15
+ ; NOT the local alias; `*` for a wildcard import)
16
+ ; @import.default.name -> (unused for Python — no default-import concept)
17
+ ; @import.namespace.name -> (unused for Python — `import x as y` is a source-side
18
+ ; module node, not a namespace specifier)
19
+ ;
20
+ ; MULTI-SOURCE awareness (spec §3.3 / §6): the runtime groups module nodes PER captured
21
+ ; @import.source NODE, not per enclosing statement. So `import a, b` — one statement with
22
+ ; two `name:` children — yields TWO @import.source captures and therefore TWO module
23
+ ; nodes. For a `from x import a, b` statement the single module_name source node is
24
+ ; captured once per imported `name:` child across N matches; the runtime accumulates all
25
+ ; the names onto the one source node (keyed by node id). The span/ordinal stay anchored
26
+ ; on the enclosing statement.
27
+
28
+ ; ===========================================================================
29
+ ; `import` STATEMENTS (import x / import x.y / import a, b / import x as y)
30
+ ; ===========================================================================
31
+
32
+ ; import x / import x.y / import a, b
33
+ ; Each `name:` child that is a bare dotted_name is one module source. A multi-source
34
+ ; statement (`import a, b`) has multiple `name:` children => multiple matches =>
35
+ ; multiple @import.source captures => multiple module nodes.
36
+ (import_statement
37
+ name: (dotted_name) @import.source)
38
+
39
+ ; import x as y -> source-side module is `x`; the alias `y` is ignored (imported
40
+ ; names are source-side). Capture the INNER dotted_name only (NOT the aliased_import).
41
+ (import_statement
42
+ name: (aliased_import
43
+ name: (dotted_name) @import.source))
44
+
45
+ ; ===========================================================================
46
+ ; `from … import …` STATEMENTS
47
+ ; ===========================================================================
48
+
49
+ ; from x import a / from x.y import a, b
50
+ ; One match per imported `name:` child; all share the single module_name source node,
51
+ ; so the runtime accumulates a, b onto module `x` (grouped by source-node id).
52
+ (import_from_statement
53
+ module_name: (dotted_name) @import.source
54
+ name: (dotted_name) @import.named.name)
55
+
56
+ ; from x import a as c -> source-side imported name is `a` (alias `c` ignored).
57
+ (import_from_statement
58
+ module_name: (dotted_name) @import.source
59
+ name: (aliased_import
60
+ name: (dotted_name) @import.named.name))
61
+
62
+ ; from x import * -> the wildcard_import node's text is `*`, captured as the name.
63
+ (import_from_statement
64
+ module_name: (dotted_name) @import.source
65
+ (wildcard_import) @import.named.name)
66
+
67
+ ; ===========================================================================
68
+ ; RELATIVE `from … import …` (from . import z / from ..pkg import a, b / *)
69
+ ;
70
+ ; The relative_import node's TEXT is the verbatim specifier WITH its leading dots
71
+ ; (`.`, `..`, `..pkg`) — that text IS how the relative-import level survives without a
72
+ ; durable-attribute field (spec §6); P1c resolution consumes the dots. Capturing the
73
+ ; relative_import node directly as @import.source preserves it verbatim.
74
+ ; ===========================================================================
75
+
76
+ ; from . import z / from ..pkg import a, b
77
+ (import_from_statement
78
+ module_name: (relative_import) @import.source
79
+ name: (dotted_name) @import.named.name)
80
+
81
+ ; from .pkg import a as c
82
+ (import_from_statement
83
+ module_name: (relative_import) @import.source
84
+ name: (aliased_import
85
+ name: (dotted_name) @import.named.name))
86
+
87
+ ; from . import *
88
+ (import_from_statement
89
+ module_name: (relative_import) @import.source
90
+ (wildcard_import) @import.named.name)
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Code Map P1b — PythonProvider (provider #2; cadrage §5/§6).
3
+ *
4
+ * Owns `.py` (runtime lang `python`). `extractDraft` delegates to the generic
5
+ * query-runtime; the curated `tags.scm`/`imports.scm` (this dir) drive structural
6
+ * extraction. `refine()` carries what the tree-sitter queries CANNOT express
7
+ * syntactically (cadrage §5, all provider-local):
8
+ * - class methods: a `function_definition` directly in a `class_definition` body →
9
+ * subtype `method` (`__init__` included). Identity span stays the
10
+ * `function_definition` (decorators excluded — the query anchors the inner node).
11
+ * - nested defs (a `function_definition` NOT directly owned by a class body) →
12
+ * stay `function`.
13
+ * - decorator-driven: `@property` → `property`; `@staticmethod`/`@classmethod` →
14
+ * `method`. Decorators are NON-emitting (no node, no persisted attribute).
15
+ * - `async def` → `function`/`method` by context (async is classification-only;
16
+ * NOT persisted — there is no durable attribute field).
17
+ * - module-level `UPPER_CASE` simple/annotated assignment → `constant`
18
+ * (else `variable`; class/instance attrs are NOT symbols, never constants).
19
+ *
20
+ * NO exports edges — Python has no export statement (capabilities: T2 = imports).
21
+ *
22
+ * Identity is owned by the CORE finalizer — this provider mints NO ids. The grammar
23
+ * is loaded through the SHARED engine glue (`loadGrammarWasm` → same initialized
24
+ * web-tree-sitter instance as js-ts), NEVER a fresh `web-tree-sitter` import
25
+ * (trp_8df65ab7).
26
+ */
27
+ import crypto from 'node:crypto';
28
+ import fs from 'node:fs';
29
+ import path from 'node:path';
30
+ import { fileURLToPath } from 'node:url';
31
+ import { loadGrammarWasm, grammarHashForWasm } from '../../wasm-loader.js';
32
+ import { extractWithQueries } from '../query-runtime.js';
33
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
34
+ /** The python grammar .wasm: dist basename + node_modules devDep fallback spec. */
35
+ const PY_WASM_BASENAME = 'tree-sitter-python.wasm';
36
+ const PY_WASM_NODE_MODULES_SPEC = 'tree-sitter-wasms/out/tree-sitter-python.wasm';
37
+ const PY_GRAMMAR_NAME = 'tree-sitter-python';
38
+ /** Resolve a vendored `.scm` next to this module (dist) or from the source tree. */
39
+ function readScm(basename) {
40
+ // Published / dist runtime: this module is dist/core/code-map/lang/python/index.js
41
+ // and the build copies the .scm assets alongside it (copy-code-map-wasm.mjs).
42
+ const local = path.join(HERE, basename);
43
+ if (fs.existsSync(local))
44
+ return fs.readFileSync(local, 'utf-8');
45
+ // From-source / dist-test fallback: tsc emits to dist[-test]/... but does NOT copy
46
+ // .scm, so walk up to the repo root (the dir holding package.json) and read the
47
+ // curated asset from src/core/code-map/lang/python/.
48
+ let dir = HERE;
49
+ for (let i = 0; i < 12; i++) {
50
+ if (fs.existsSync(path.join(dir, 'package.json'))) {
51
+ const fromSrc = path.join(dir, 'src', 'core', 'code-map', 'lang', 'python', basename);
52
+ if (fs.existsSync(fromSrc))
53
+ return fs.readFileSync(fromSrc, 'utf-8');
54
+ break;
55
+ }
56
+ const parent = path.dirname(dir);
57
+ if (parent === dir)
58
+ break;
59
+ dir = parent;
60
+ }
61
+ throw new Error(`code-map: could not locate query asset ${basename} (from ${HERE})`);
62
+ }
63
+ function sha256(s) {
64
+ return `sha256:${crypto.createHash('sha256').update(s, 'utf-8').digest('hex')}`;
65
+ }
66
+ // Load the curated query assets once at module init.
67
+ const TAGS = readScm('tags.scm');
68
+ const IMPORTS = readScm('imports.scm');
69
+ const TAGS_HASH = sha256(TAGS);
70
+ const IMPORTS_HASH = sha256(IMPORTS);
71
+ const parser = {
72
+ grammarForLang: () => loadGrammarWasm(PY_WASM_BASENAME, PY_WASM_NODE_MODULES_SPEC),
73
+ grammarNameForLang: () => PY_GRAMMAR_NAME,
74
+ grammarHashForLang: () => grammarHashForWasm(PY_WASM_BASENAME, PY_WASM_NODE_MODULES_SPEC),
75
+ };
76
+ const queries = {
77
+ tags: {
78
+ name: 'tags',
79
+ sourceForLang: () => TAGS,
80
+ hashForLang: () => TAGS_HASH,
81
+ },
82
+ imports: {
83
+ name: 'imports',
84
+ sourceForLang: () => IMPORTS,
85
+ hashForLang: () => IMPORTS_HASH,
86
+ },
87
+ // Python import statement node types (cadrage §3 / Codex R1): `import a, b` is an
88
+ // `import_statement`; `from x import …` is an `import_from_statement`. Python has
89
+ // no export statement. PROVIDER-LOCAL — runtime gets this per file, no registry.
90
+ // (Same set the old module-global carried for Python → byte-identical output.)
91
+ enclosingStatementNodeTypes: ['import_statement', 'import_from_statement'],
92
+ // P1b §3.4: the runtime drives capture→draft mapping off the HARD-CODED
93
+ // capture-name convention (query-runtime.ts). This captureMap is a declared
94
+ // MIRROR, validated by `assertCaptureMapConforms`. Every entry names a
95
+ // convention-recognized role; Python invents none.
96
+ captureMap: [
97
+ { capture: 'definition.function.node', field: 'node', subtype: 'function' },
98
+ { capture: 'definition.function.name', field: 'name' },
99
+ { capture: 'definition.class.node', field: 'node', subtype: 'class' },
100
+ { capture: 'definition.class.name', field: 'name' },
101
+ { capture: 'definition.variable.node', field: 'node', subtype: 'variable' },
102
+ { capture: 'definition.variable.name', field: 'name' },
103
+ { capture: 'import.source', field: 'source' },
104
+ { capture: 'import.named.name', field: 'imported', optional: true },
105
+ ],
106
+ };
107
+ const vocabulary = {
108
+ nodeSubtypes: ['function', 'method', 'class', 'variable', 'constant', 'property'],
109
+ edgeKinds: ['contains', 'defines', 'imports'],
110
+ captureMap: queries.captureMap,
111
+ };
112
+ const capabilities = {
113
+ tiers: ['T1.definitions', 'T2.imports', 'T3.import_resolution'],
114
+ proven: {
115
+ 'T1.definitions': true,
116
+ 'T2.imports': true,
117
+ 'T3.import_resolution': true,
118
+ 'T4.tests_for': false,
119
+ },
120
+ };
121
+ /**
122
+ * P1c file-level import resolution for Python (cadrage v2 §"Python"). Maps an import
123
+ * source string — as the provider emits it on the `module` node `name` — to a single
124
+ * project-internal target FILE path, or `null` (stdlib / site-packages / unresolved).
125
+ *
126
+ * Source forms (verified against the provider's golden):
127
+ * - absolute dotted: `os`, `os.path`, `collections`, `pkg`, `a.b.c`
128
+ * - relative: leading dots count the package level — `.` / `..` (bare, from
129
+ * `from . import x`), `.mod`, `..pkg`, `..pkg.sub`.
130
+ *
131
+ * Resolution (PEP 328 file-level approximation):
132
+ * - relative: N leading dots ⇒ start at the importer's DIRECTORY and walk up (N-1)
133
+ * levels (1 dot = current package). The remaining dotted tail (after the dots) is
134
+ * a sub-path; empty tail (bare `.`/`..`) targets that package's `__init__.py`.
135
+ * - absolute dotted: resolve project-ROOT-relative (`a.b.c` → `a/b/c.py` |
136
+ * `a/b/c/__init__.py`). Stdlib / third-party names simply won't exist as project
137
+ * files → `null` (no edge). v1 does NOT model `src/`-layout sys.path roots — a
138
+ * documented limitation; symbol/submodule precision (`from . import sib` → the
139
+ * `sib` SUBMODULE rather than the package `__init__`) is the P1c-B follow-up.
140
+ *
141
+ * Candidate order is `module.py` before `package/__init__.py` (a regular module
142
+ * shadows a package of the same dotted name in CPython import order). The CORE
143
+ * verifies existence via `ctx.fileExists` and owns id/edge minting — this returns a
144
+ * path only (dec#108/#109).
145
+ */
146
+ function resolvePyImport(source, fromPath, ctx) {
147
+ const candidates = [];
148
+ if (source.startsWith('.')) {
149
+ let dots = 0;
150
+ while (source[dots] === '.')
151
+ dots++;
152
+ const tail = source.slice(dots); // after the dots: '' | 'mod' | 'pkg.sub'
153
+ let base = path.posix.dirname(toPosixPy(fromPath)); // current package dir (1 dot)
154
+ let escapedRoot = false;
155
+ for (let up = 1; up < dots; up++) {
156
+ if (base === '' || base === '.' || base === '/') {
157
+ escapedRoot = true;
158
+ break;
159
+ }
160
+ base = path.posix.dirname(base); // .. = parent, etc.
161
+ }
162
+ if (escapedRoot)
163
+ return null;
164
+ if (base === '.' || base === '/')
165
+ base = '';
166
+ const prefix = base ? `${base}/` : '';
167
+ if (tail === '') {
168
+ candidates.push(`${prefix}__init__.py`); // the package itself
169
+ }
170
+ else {
171
+ const sub = tail.replace(/\./g, '/');
172
+ candidates.push(`${prefix}${sub}.py`, `${prefix}${sub}/__init__.py`);
173
+ }
174
+ }
175
+ else {
176
+ // Absolute dotted module, resolved project-root-relative.
177
+ const sub = source.replace(/\./g, '/');
178
+ candidates.push(`${sub}.py`, `${sub}/__init__.py`);
179
+ }
180
+ for (const c of candidates) {
181
+ const norm = c.replace(/^\.\//, '');
182
+ if (ctx.fileExists(norm))
183
+ return norm;
184
+ }
185
+ return null;
186
+ }
187
+ function toPosixPy(p) {
188
+ return p.replace(/\\/g, '/');
189
+ }
190
+ function isDefSourceNode(v) {
191
+ return (typeof v === 'object' &&
192
+ v !== null &&
193
+ 'node' in v &&
194
+ 'nameNode' in v &&
195
+ typeof v.node === 'object');
196
+ }
197
+ const UPPER_RE = /^[A-Z_][A-Z0-9_]*$/;
198
+ /**
199
+ * True iff `defNode` (a `function_definition`) is a method — i.e. directly owned by
200
+ * a `class_definition` body. The grammar nests a def's statements under a `block`
201
+ * whose parent is the `class_definition`; a decorated method is wrapped in a
202
+ * `decorated_definition` (also inside that `block`). So walk: function_definition →
203
+ * (optional decorated_definition) → block → class_definition.
204
+ */
205
+ function isClassMethod(defNode) {
206
+ let owner = defNode.parent;
207
+ // A decorated def sits inside a `decorated_definition` wrapper.
208
+ if (owner && owner.type === 'decorated_definition')
209
+ owner = owner.parent;
210
+ if (!owner || owner.type !== 'block')
211
+ return false;
212
+ const blockParent = owner.parent;
213
+ return !!blockParent && blockParent.type === 'class_definition';
214
+ }
215
+ /**
216
+ * Collect decorator names applied to `defNode`. A decorated def's parent is a
217
+ * `decorated_definition` whose `decorator` children carry the applied names (e.g.
218
+ * `@property`, `@staticmethod`, `@app.route(...)`). We read each decorator's leading
219
+ * identifier/attribute text (best-effort) to classify property/staticmethod/classmethod.
220
+ */
221
+ function decoratorNames(defNode) {
222
+ const wrapper = defNode.parent;
223
+ if (!wrapper || wrapper.type !== 'decorated_definition')
224
+ return [];
225
+ const names = [];
226
+ for (let i = 0; i < wrapper.namedChildCount; i++) {
227
+ const child = wrapper.namedChild(i);
228
+ if (!child || child.type !== 'decorator')
229
+ continue;
230
+ // decorator text is like `@property` / `@staticmethod` / `@app.route("/x")`.
231
+ // Strip the leading `@` and any call/attribute tail to get the head name.
232
+ const text = child.text.replace(/^@/, '').trim();
233
+ const head = text.split(/[(\s]/)[0]; // up to first `(` or whitespace
234
+ names.push(head);
235
+ }
236
+ return names;
237
+ }
238
+ export class PythonProvider {
239
+ id = 'python';
240
+ displayName = 'Python';
241
+ languages = ['python'];
242
+ extensions = ['.py'];
243
+ priority = 0;
244
+ version = '0.1.0';
245
+ parser = parser;
246
+ queries = queries;
247
+ vocabulary = vocabulary;
248
+ capabilities = capabilities;
249
+ /** `.py` → `python`. */
250
+ langForPath(_p) {
251
+ return 'python';
252
+ }
253
+ async extractDraft(input, _services) {
254
+ return extractDraftViaRuntime(this.id, input, this.parser.grammarForLang);
255
+ }
256
+ /**
257
+ * Reclassify subtypes the structural query cannot express (cadrage §5). Drafts-only.
258
+ * - function in a class body → method (decorator @property → property;
259
+ * @staticmethod/@classmethod → method); nested/top-level defs stay function.
260
+ * - module-level UPPER_CASE assignment → constant; else stays variable.
261
+ * `async` is never persisted (no attribute field) — it is classification-only and
262
+ * does not change the subtype beyond function/method by context.
263
+ */
264
+ refine(draft, _ctx) {
265
+ const definitions = draft.definitions.map((d) => {
266
+ const src = isDefSourceNode(d.sourceNode) ? d.sourceNode : null;
267
+ if (d.subtype === 'function') {
268
+ if (!src)
269
+ return d;
270
+ const decos = decoratorNames(src.node);
271
+ // @property wins as a value-like member; @staticmethod/@classmethod are methods.
272
+ if (decos.includes('property'))
273
+ return setSubtype(d, 'property');
274
+ const inClass = isClassMethod(src.node);
275
+ if (decos.includes('staticmethod') || decos.includes('classmethod')) {
276
+ return setSubtype(d, 'method');
277
+ }
278
+ return inClass ? setSubtype(d, 'method') : d; // top-level / nested stay function
279
+ }
280
+ if (d.subtype === 'variable') {
281
+ // Module-level UPPER_CASE → constant; the tags.scm anchors variables under
282
+ // (module) only, so any captured variable IS module-level.
283
+ return UPPER_RE.test(d.name) ? setSubtype(d, 'constant') : d;
284
+ }
285
+ return d;
286
+ });
287
+ return { ...draft, definitions };
288
+ }
289
+ /**
290
+ * P1c file-level import resolution (T3). Returns at most one resolution per import:
291
+ * the resolved project-internal target file path, or `resolvedPath: null` when the
292
+ * specifier points outside the project (stdlib / site-packages / unresolved). The
293
+ * CORE pass filters out `null` paths and mints the `resolves_to` edge.
294
+ */
295
+ async resolveImport(req, ctx) {
296
+ const resolvedPath = resolvePyImport(req.source, req.fromPath, ctx);
297
+ return [{ source: req.source, resolvedPath, confidence: resolvedPath ? 1.0 : 0 }];
298
+ }
299
+ /**
300
+ * P1c-B importability for Python (overrides the `exported`-based default).
301
+ * Python has no export statement — `node.exported` is always false — so a symbol
302
+ * is importable iff it is TOP-LEVEL in its module: no OTHER symbol's span STRICTLY
303
+ * contains it. (A class method / nested function is contained by its class/func and
304
+ * is therefore NOT importable as `from .mod import X`.) Cadrage D2: span containment
305
+ * is the SOUND mechanism — the finalizer's file→contains/defines edges are emitted
306
+ * for EVERY symbol (not just top-level), so they cannot prove top-level status.
307
+ * A symbol lacking a usable span is SKIPPED (can't prove top-level → never guess).
308
+ */
309
+ isImportableSymbol(node, fileSymbols) {
310
+ if (node.kind !== 'symbol')
311
+ return false;
312
+ if (node.subtype === 'export')
313
+ return false; // defensive (Python never emits it)
314
+ const span = node.span;
315
+ if (!span)
316
+ return false;
317
+ for (const other of fileSymbols) {
318
+ if (other === node || other.id === node.id)
319
+ continue;
320
+ if (other.kind !== 'symbol')
321
+ continue;
322
+ if (other.span && spanStrictlyContains(other.span, span))
323
+ return false; // nested → not top-level
324
+ }
325
+ return true;
326
+ }
327
+ }
328
+ /** True iff `outer` strictly contains `inner` (covers it on both ends, larger on ≥1). */
329
+ function spanStrictlyContains(outer, inner) {
330
+ const startsAtOrBefore = outer.start_line < inner.start_line ||
331
+ (outer.start_line === inner.start_line && outer.start_col <= inner.start_col);
332
+ const endsAtOrAfter = outer.end_line > inner.end_line ||
333
+ (outer.end_line === inner.end_line && outer.end_col >= inner.end_col);
334
+ if (!startsAtOrBefore || !endsAtOrAfter)
335
+ return false;
336
+ const strictlyLarger = outer.start_line < inner.start_line ||
337
+ outer.start_col < inner.start_col ||
338
+ outer.end_line > inner.end_line ||
339
+ outer.end_col > inner.end_col;
340
+ return strictlyLarger;
341
+ }
342
+ function setSubtype(d, subtype) {
343
+ return d.subtype === subtype ? d : { ...d, subtype };
344
+ }
345
+ async function extractDraftViaRuntime(providerId, input, grammarForLang) {
346
+ return extractWithQueries({
347
+ providerId,
348
+ lang: input.lang,
349
+ source: input.source,
350
+ sizeBytes: input.sizeBytes,
351
+ maxParseFileBytes: input.maxParseFileBytes,
352
+ maxQueryWaitMs: input.maxQueryWaitMs,
353
+ path: input.path,
354
+ grammarForLang,
355
+ tagsSource: TAGS,
356
+ tagsHash: TAGS_HASH,
357
+ importsSource: IMPORTS,
358
+ importsHash: IMPORTS_HASH,
359
+ enclosingStatementNodeTypes: queries.enclosingStatementNodeTypes,
360
+ });
361
+ }
362
+ /** Singleton instance for registry wiring. */
363
+ export const pythonProvider = new PythonProvider();
364
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,81 @@
1
+ ; Brainclaw Code Map — Python DEFINITIONS query (curated, vendored).
2
+ ; Grammar: tree-sitter-python (WASM vendored under dist/wasm/). See ./README.md for
3
+ ; the capture-name convention, the Python→vocabulary mapping, and provenance.
4
+ ;
5
+ ; The generic query-runtime (lang/query-runtime.ts) maps each match's captures:
6
+ ; @definition.<subtype>.node -> DefinitionDraft.span (the LEGACY IDENTITY span)
7
+ ; @definition.<subtype>.name -> DefinitionDraft.name (identifier text + ordinal)
8
+ ; @definition.<subtype>.exported -> presence ⇒ exported=true (UNUSED for Python:
9
+ ; Python has no export statement, so this asset
10
+ ; never emits it — every Python symbol is unexported)
11
+ ;
12
+ ; Structural subtypes emitted here: function | class | variable.
13
+ ; - function_definition is captured UNIFORMLY as @definition.function (whether bare,
14
+ ; decorated, top-level, nested, or sitting directly in a class body). The provider's
15
+ ; refine() pass reclassifies:
16
+ ; * a function_definition whose owner is a class body -> method
17
+ ; (@property -> property; @staticmethod/@classmethod -> method)
18
+ ; * everything else (top-level + nested defs) -> stays function
19
+ ; refine() decides method-vs-function and decorator-driven subtypes because the
20
+ ; structural query cannot inspect the enclosing-scope chain or decorator semantics.
21
+ ; - module-level assignment is captured as @definition.variable; refine() narrows an
22
+ ; all-UPPER_CASE target to `constant`.
23
+ ;
24
+ ; IDENTITY SPAN: every definition pattern anchors @definition.*.node on the INNER
25
+ ; function_definition / class_definition node — NOT on any enclosing decorated_definition
26
+ ; wrapper. A decorated def's identity span therefore EXCLUDES its decorators (spec §5),
27
+ ; and the same single pattern matches the bare and the decorated forms (the inner node
28
+ ; is identical in both trees), so no separate decorated pattern is needed.
29
+
30
+ ; ---------------------------------------------------------------------------
31
+ ; function / async-function definitions (def f(): ... / async def f(): ...)
32
+ ;
33
+ ; Matched at ANY depth (top-level, nested, class-body). `async def` is the same
34
+ ; function_definition node (the grammar marks async with an `async` keyword child,
35
+ ; not a distinct node type), so this one pattern covers sync + async; async-ness is
36
+ ; classification-only and is NOT persisted (no durable attribute field — spec §5).
37
+ ; refine() promotes class-body functions to `method`.
38
+ ; ---------------------------------------------------------------------------
39
+ (function_definition
40
+ name: (identifier) @definition.function.name) @definition.function.node
41
+
42
+ ; ---------------------------------------------------------------------------
43
+ ; class definitions (class C: ... / class C(Base): ...)
44
+ ; ---------------------------------------------------------------------------
45
+ (class_definition
46
+ name: (identifier) @definition.class.name) @definition.class.node
47
+
48
+ ; ---------------------------------------------------------------------------
49
+ ; module-level variables / constants
50
+ ;
51
+ ; Anchored under `module` (the root) so ONLY top-level assignments are emitted —
52
+ ; class/instance attributes and locals are NOT symbols in P1b (spec §5). Covers both
53
+ ; a plain assignment (`X = 1`) and an annotated assignment (`X: int = 1`); both are a
54
+ ; single `assignment` node, the annotation living in its `type:` field. Only a simple
55
+ ; `identifier` target is captured (tuple/attribute/subscript targets are not symbols).
56
+ ; The @definition.variable.node identity span is the `assignment` node. refine()
57
+ ; narrows an all-UPPER_CASE name to `constant`; everything else stays `variable`.
58
+ ; ---------------------------------------------------------------------------
59
+ (module
60
+ (expression_statement
61
+ (assignment
62
+ left: (identifier) @definition.variable.name) @definition.variable.node))
63
+
64
+ ; ---------------------------------------------------------------------------
65
+ ; ERROR-root recovery (parity with the TS asset).
66
+ ;
67
+ ; When tree-sitter cannot parse a file cleanly it may promote a subtree to an `ERROR`
68
+ ; node while still recovering well-formed declarations beneath it. Mirror the def/class
69
+ ; patterns under an `(ERROR ...)` ancestor so top-level symbols in an error-recovered
70
+ ; file are not silently dropped. (Functions/classes match at any depth above already,
71
+ ; but anchoring these explicitly keeps content parity for the ERROR-root shape and
72
+ ; documents the intent.) Module-level variables are intentionally NOT recovered under
73
+ ; ERROR — without the `module` anchor a bare identifier-assignment match is ambiguous.
74
+ ; ---------------------------------------------------------------------------
75
+ (ERROR
76
+ (function_definition
77
+ name: (identifier) @definition.function.name) @definition.function.node)
78
+
79
+ (ERROR
80
+ (class_definition
81
+ name: (identifier) @definition.class.name) @definition.class.node)