homegraph 1.6.0 → 1.6.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +64 -14
  3. package/dist/bin/homegraph.js +2 -2
  4. package/dist/db/index.js +1 -0
  5. package/dist/db/migrations.d.ts +6 -1
  6. package/dist/db/migrations.js +26 -1
  7. package/dist/db/queries.d.ts +9 -0
  8. package/dist/db/queries.js +32 -7
  9. package/dist/db/schema.sql +4 -0
  10. package/dist/extraction/grammars.js +2 -1
  11. package/dist/extraction/languages/c-cpp.d.ts +8 -0
  12. package/dist/extraction/languages/c-cpp.js +44 -0
  13. package/dist/extraction/wasm-runtime-flags.d.ts +4 -0
  14. package/dist/extraction/wasm-runtime-flags.js +9 -0
  15. package/dist/graph/evidence-paths.d.ts +2 -0
  16. package/dist/graph/evidence-paths.js +20 -3
  17. package/dist/index.js +7 -1
  18. package/dist/installer/targets/icode.d.ts +43 -0
  19. package/dist/installer/targets/icode.js +631 -0
  20. package/dist/installer/targets/registry.js +2 -0
  21. package/dist/installer/targets/types.d.ts +1 -1
  22. package/dist/mcp/arkts-evidence-packs.d.ts +17 -0
  23. package/dist/mcp/arkts-evidence-packs.js +218 -10
  24. package/dist/mcp/evidence-audit.d.ts +3 -0
  25. package/dist/mcp/evidence-audit.js +40 -0
  26. package/dist/mcp/evidence-rendering.js +1 -1
  27. package/dist/mcp/explore-repeat-guard.d.ts +19 -5
  28. package/dist/mcp/explore-repeat-guard.js +35 -14
  29. package/dist/mcp/explore-session-state.d.ts +5 -0
  30. package/dist/mcp/explore-session-state.js +1 -0
  31. package/dist/mcp/implementation-context.d.ts +35 -0
  32. package/dist/mcp/implementation-context.js +232 -0
  33. package/dist/mcp/locate-contract.js +1 -1
  34. package/dist/mcp/query-cache.d.ts +1 -15
  35. package/dist/mcp/query-cache.js +20 -11
  36. package/dist/mcp/request-evidence.d.ts +69 -0
  37. package/dist/mcp/request-evidence.js +297 -0
  38. package/dist/mcp/server-instructions.d.ts +2 -2
  39. package/dist/mcp/server-instructions.js +15 -15
  40. package/dist/mcp/session.js +3 -2
  41. package/dist/mcp/tools.d.ts +39 -12
  42. package/dist/mcp/tools.js +237 -121
  43. package/dist/resolution/c-feature-variants.d.ts +40 -0
  44. package/dist/resolution/c-feature-variants.js +272 -0
  45. package/dist/resolution/cpp-macro-visibility.d.ts +21 -0
  46. package/dist/resolution/cpp-macro-visibility.js +393 -0
  47. package/dist/resolution/index.js +11 -0
  48. package/dist/resolution/name-matcher.js +49 -2
  49. package/dist/resolution/types.d.ts +8 -0
  50. package/dist/resolution/types.js +8 -0
  51. package/dist/search/literal-evidence.js +11 -3
  52. package/dist/search/query-plan-provider.js +13 -5
  53. package/dist/search/query-plan.d.ts +3 -1
  54. package/dist/search/query-plan.js +11 -4
  55. package/dist/search/request-contract.d.ts +29 -0
  56. package/dist/search/request-contract.js +76 -0
  57. package/dist/sync/watcher.d.ts +30 -0
  58. package/dist/sync/watcher.js +45 -0
  59. package/package.json +1 -1
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Is a C function defined only for a build that turns on a flag nothing in
3
+ * the project turns on? lua-cjson's fpconv.h defines `static inline void
4
+ * fpconv_init()` under `#ifdef USE_INTERNAL_FPCONV` and otherwise declares it
5
+ * `extern`, for fpconv.c to define. Nothing in redis's tree sets
6
+ * USE_INTERNAL_FPCONV, so its build compiles fpconv.c's.
7
+ *
8
+ * The name matcher asks this only when choosing among definitions of one
9
+ * name, where a call has to reach one of them: of a flag nothing sets, the
10
+ * build to follow is the one that leaves it off. Whether a macro is visible
11
+ * at a call (./cpp-macro-visibility) keeps such a flag unknown instead, since
12
+ * there an unknown flag must not suppress anything.
13
+ *
14
+ * A branch is off only when flags decide its test: `#ifdef X`, `#ifndef X`,
15
+ * `#if defined(X)`, `#if X`, `#if 0`, with `!`, `&&`, `||` and parentheses,
16
+ * and the `#elif` and `#else` after them. A flag is set nowhere when the
17
+ * definition's own file doesn't `#define` it, no build file in the
18
+ * directories from the project root down to the definition or the call names
19
+ * it (a Makefile's `-DX`, CMake's `option(X …)`, configure's `AC_DEFINE(X)`)
20
+ * and no C or C++ file of their components does — a component being the
21
+ * tree under the nearest directory with a build file, redis's `deps/lua/src`
22
+ * for fpconv.h. A macro of the compiler or the platform — a reserved name
23
+ * like `_WIN32` or `__linux__`, or `WIN32`, `linux`, `unix` — is unknown, and
24
+ * so is any other test (`#if LEVEL > 1`).
25
+ */
26
+ import type { Node } from '../types';
27
+ import type { ResolutionContext } from './types';
28
+ export declare function clearCFeatureVariantMemos(context: ResolutionContext): void;
29
+ /**
30
+ * Is `fn`'s definition compiled only by a build that sets a flag nothing in
31
+ * the project sets, for the call at `ref`? A branch around both the
32
+ * definition and the call, in the call's own file, decides nothing.
33
+ */
34
+ export declare function isCFeatureVariant(fn: Node, ref: {
35
+ filePath: string;
36
+ line: number;
37
+ }, context: ResolutionContext): boolean;
38
+ /** Is line `line` of `path` compiled only by a build that sets a flag nothing in the project sets? */
39
+ export declare function isOffByDefault(path: string, line: number, context: ResolutionContext): boolean;
40
+ //# sourceMappingURL=c-feature-variants.d.ts.map
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearCFeatureVariantMemos = clearCFeatureVariantMemos;
4
+ exports.isCFeatureVariant = isCFeatureVariant;
5
+ exports.isOffByDefault = isOffByDefault;
6
+ const cpp_macro_visibility_1 = require("./cpp-macro-visibility");
7
+ const MEMO = new WeakMap();
8
+ function clearCFeatureVariantMemos(context) {
9
+ MEMO.delete(context);
10
+ }
11
+ function memoFor(context) {
12
+ let memo = MEMO.get(context);
13
+ if (!memo) {
14
+ memo = { files: new Map(), buildText: new Map(), components: new Map(), cFiles: null, defines: new Map(), named: new Map(), answers: new Map() };
15
+ MEMO.set(context, memo);
16
+ }
17
+ return memo;
18
+ }
19
+ const and = (a, b) => (a === false || b === false ? false : a === true && b === true ? true : undefined);
20
+ const or = (a, b) => (a === true || b === true ? true : a === false && b === false ? false : undefined);
21
+ const not = (a) => (a === undefined ? undefined : !a);
22
+ const C_FAMILY_FILE = /\.(c|h|cc|cpp|cxx|c\+\+|hpp|hh|hxx|h\+\+|inl|ipp|tcc|inc|m|mm)$/i;
23
+ const PLATFORM_FLAGS = new Set(['WIN32', 'WIN64', 'linux', 'unix']);
24
+ const BUILD_FILES = [
25
+ 'Makefile', 'makefile', 'GNUmakefile', 'Makefile.am', 'Makefile.in', 'CMakeLists.txt', 'meson.build',
26
+ 'meson_options.txt', 'configure.ac', 'configure.in', 'config.h.in', 'BUILD', 'BUILD.bazel', 'SConstruct', 'SConscript',
27
+ ];
28
+ const dirOf = (file) => file.slice(0, Math.max(0, file.lastIndexOf('/')));
29
+ /** `a/b/c.h` → `a/b`, `a`, `` (the project root). */
30
+ function ancestors(file) {
31
+ const dirs = [];
32
+ for (let dir = dirOf(file);; dir = dirOf(dir)) {
33
+ dirs.push(dir);
34
+ if (dir === '')
35
+ return dirs;
36
+ }
37
+ }
38
+ /**
39
+ * Is `fn`'s definition compiled only by a build that sets a flag nothing in
40
+ * the project sets, for the call at `ref`? A branch around both the
41
+ * definition and the call, in the call's own file, decides nothing.
42
+ */
43
+ function isCFeatureVariant(fn, ref, context) {
44
+ const memo = memoFor(context);
45
+ const sameFile = fn.filePath === ref.filePath;
46
+ const key = `v\0${fn.id}\0${sameFile ? ref.line : dirOf(ref.filePath)}`;
47
+ const hit = memo.answers.get(key);
48
+ if (hit !== undefined)
49
+ return hit;
50
+ const file = directivesOf(fn.filePath, context, memo);
51
+ let groups = enclosingTests(file.conditionals, fn.startLine);
52
+ if (sameFile) {
53
+ const around = enclosingTests(file.conditionals, ref.line);
54
+ let shared = 0;
55
+ while (shared < groups.length && shared < around.length &&
56
+ groups[shared][0] === around[shared][0] && groups[shared].length === around[shared].length)
57
+ shared++;
58
+ groups = groups.slice(shared);
59
+ }
60
+ const off = groups.length > 0 && isOff(groups, file, [fn.filePath, ref.filePath], context, memo);
61
+ memo.answers.set(key, off);
62
+ return off;
63
+ }
64
+ /** Is line `line` of `path` compiled only by a build that sets a flag nothing in the project sets? */
65
+ function isOffByDefault(path, line, context) {
66
+ const memo = memoFor(context);
67
+ const key = `o\0${path}\0${line}`;
68
+ const hit = memo.answers.get(key);
69
+ if (hit !== undefined)
70
+ return hit;
71
+ const file = directivesOf(path, context, memo);
72
+ const groups = enclosingTests(file.conditionals, line);
73
+ const off = groups.length > 0 && isOff(groups, file, [path], context, memo);
74
+ memo.answers.set(key, off);
75
+ return off;
76
+ }
77
+ /** Is a branch of `groups` off when no flag `scope`'s files see is set? */
78
+ function isOff(groups, file, scope, context, memo) {
79
+ const dirs = [...new Set(scope.flatMap(ancestors))];
80
+ const flag = (name) => {
81
+ if (name.startsWith('_') || PLATFORM_FLAGS.has(name) || file.defines.has(name))
82
+ return undefined;
83
+ let named = memo.named.get(name);
84
+ // A word of its own, or a compiler flag's `-DX` / `/DX`.
85
+ if (!named)
86
+ memo.named.set(name, (named = new RegExp(`(?:^|[^\\w]|[-/]D)${name}\\b`, 'm')));
87
+ if (dirs.some((dir) => named.test(buildTextOf(dir, context, memo))))
88
+ return undefined;
89
+ const components = new Set(scope.map((f) => componentOf(dirOf(f), context, memo)));
90
+ return [...components].some((component) => isDefinedIn(name, component, context, memo)) ? undefined : false;
91
+ };
92
+ return groups.some((tests) => branchTruth(tests, flag) === false);
93
+ }
94
+ /**
95
+ * The branches around `line`, innermost last: for each, the tests of its
96
+ * group's branches up to and including the one `line` is in (`#else` has no
97
+ * test).
98
+ */
99
+ function enclosingTests(conditionals, line) {
100
+ const groups = [];
101
+ for (const d of conditionals) {
102
+ if (d.line >= line)
103
+ break;
104
+ if (d.op === 'if' || d.op === 'ifdef' || d.op === 'ifndef')
105
+ groups.push([d]);
106
+ else if (d.op === 'endif')
107
+ groups.pop();
108
+ else
109
+ groups[groups.length - 1]?.push(d);
110
+ }
111
+ return groups;
112
+ }
113
+ /** Whether the last branch of `tests` is taken: no earlier test held, and its own does. */
114
+ function branchTruth(tests, flag) {
115
+ let earlier = false;
116
+ for (let i = 0; i < tests.length - 1; i++) {
117
+ earlier = or(earlier, testTruth(tests[i], flag));
118
+ if (earlier === true)
119
+ return false;
120
+ }
121
+ return and(not(earlier), testTruth(tests[tests.length - 1], flag));
122
+ }
123
+ function testTruth(d, flag) {
124
+ const name = d.expression.trim().match(/^\w+$/)?.[0];
125
+ switch (d.op) {
126
+ case 'ifdef':
127
+ case 'elifdef':
128
+ return name ? flag(name) : undefined;
129
+ case 'ifndef':
130
+ case 'elifndef':
131
+ return name ? not(flag(name)) : undefined;
132
+ case 'else':
133
+ return true;
134
+ default:
135
+ return evaluate(d.expression, flag);
136
+ }
137
+ }
138
+ /**
139
+ * A `#if` expression of `defined`, flags, integers, `!`, `&&`, `||` and
140
+ * parentheses; anything else is unknown. A flag that is set nowhere reads as
141
+ * `0`, as the preprocessor reads a name no `#define` gave a value.
142
+ */
143
+ function evaluate(expression, flag) {
144
+ const tokens = expression.match(/0x[\da-f]+\w*|\d\w*|[A-Za-z_]\w*|&&|\|\||\S/gi) ?? [];
145
+ // Check the shape first, so an unsupported test reads no flag.
146
+ let i = 0;
147
+ const shape = () => {
148
+ const term = () => {
149
+ const t = tokens[i++];
150
+ if (t === '!')
151
+ return term();
152
+ if (t === '(')
153
+ return shape() && tokens[i++] === ')';
154
+ if (t === 'defined') {
155
+ const paren = tokens[i] === '(';
156
+ if (paren)
157
+ i++;
158
+ return /^[A-Za-z_]\w*$/.test(tokens[i++] ?? '') && (!paren || tokens[i++] === ')');
159
+ }
160
+ return t !== undefined && (/^(0x[\da-f]+|\d+)[ul]*$/i.test(t) || /^[A-Za-z_]\w*$/.test(t));
161
+ };
162
+ if (!term())
163
+ return false;
164
+ while (tokens[i] === '&&' || tokens[i] === '||') {
165
+ i++;
166
+ if (!term())
167
+ return false;
168
+ }
169
+ return true;
170
+ };
171
+ if (tokens.length === 0 || !shape() || i !== tokens.length)
172
+ return undefined;
173
+ i = 0;
174
+ const disjunction = () => {
175
+ let value = conjunction();
176
+ while (tokens[i] === '||') {
177
+ i++;
178
+ value = or(value, conjunction());
179
+ }
180
+ return value;
181
+ };
182
+ const conjunction = () => {
183
+ let value = term();
184
+ while (tokens[i] === '&&') {
185
+ i++;
186
+ value = and(value, term());
187
+ }
188
+ return value;
189
+ };
190
+ const term = () => {
191
+ const t = tokens[i++];
192
+ if (t === '!')
193
+ return not(term());
194
+ if (t === '(') {
195
+ const value = disjunction();
196
+ i++;
197
+ return value;
198
+ }
199
+ if (t === 'defined') {
200
+ if (tokens[i] === '(') {
201
+ const value = flag(tokens[i + 1]);
202
+ i += 3;
203
+ return value;
204
+ }
205
+ return flag(tokens[i++]);
206
+ }
207
+ if (/^\d|^0x/i.test(t))
208
+ return Number.parseInt(t.replace(/[ul]+$/i, ''), /^0x/i.test(t) ? 16 : 10) !== 0;
209
+ return flag(t) === false ? false : undefined;
210
+ };
211
+ return disjunction();
212
+ }
213
+ function directivesOf(file, context, memo) {
214
+ const hit = memo.files.get(file);
215
+ if (hit)
216
+ return hit;
217
+ const lines = (0, cpp_macro_visibility_1.directiveLines)(context.readFile(file) ?? '');
218
+ const conditionals = [];
219
+ const defines = new Set();
220
+ for (let i = 0; i < lines.length; i++) {
221
+ const define = lines[i].match(/^\s*#\s*define\s+(\w+)/);
222
+ if (define) {
223
+ defines.add(define[1]);
224
+ continue;
225
+ }
226
+ const m = lines[i].match(/^\s*#\s*(ifdef|ifndef|if|elifdef|elifndef|elif|else|endif)\b(.*)$/);
227
+ if (!m)
228
+ continue;
229
+ // A test continued with a backslash goes on over the next lines.
230
+ let expression = m[2];
231
+ for (let j = i; /\\\s*$/.test(lines[j]) && j + 1 < lines.length; j++) {
232
+ expression = expression.replace(/\\\s*$/, ' ') + lines[j + 1];
233
+ }
234
+ conditionals.push({ op: m[1], expression, line: i + 1 });
235
+ }
236
+ const directives = { conditionals, defines };
237
+ memo.files.set(file, directives);
238
+ return directives;
239
+ }
240
+ function buildTextOf(dir, context, memo) {
241
+ const hit = memo.buildText.get(dir);
242
+ if (hit !== undefined)
243
+ return hit;
244
+ const text = BUILD_FILES.map((name) => context.readFile(dir ? `${dir}/${name}` : name) ?? '').join('\n');
245
+ memo.buildText.set(dir, text);
246
+ return text;
247
+ }
248
+ /** The nearest directory at or above `dir` with a build file, else the project root. */
249
+ function componentOf(dir, context, memo) {
250
+ const hit = memo.components.get(dir);
251
+ if (hit !== undefined)
252
+ return hit;
253
+ const component = dir === '' || buildTextOf(dir, context, memo).trim() ? dir : componentOf(dirOf(dir), context, memo);
254
+ memo.components.set(dir, component);
255
+ return component;
256
+ }
257
+ /** Does a C or C++ file under `component` `#define` `name`? Read only from files whose bytes hold the name. */
258
+ function isDefinedIn(name, component, context, memo) {
259
+ const key = `${name}\0${component}`;
260
+ const hit = memo.defines.get(key);
261
+ if (hit !== undefined)
262
+ return hit;
263
+ memo.cFiles ??= context.getAllFiles().filter((file) => C_FAMILY_FILE.test(file));
264
+ const prefix = component ? `${component}/` : '';
265
+ const define = new RegExp(`^[ \\t]*#[ \\t]*define[ \\t]+${name}\\b`, 'm');
266
+ const defined = memo.cFiles.some((file) => file.startsWith(prefix) &&
267
+ (context.fileContains ? context.fileContains(file, name) : true) &&
268
+ define.test(context.readFile(file) ?? ''));
269
+ memo.defines.set(key, defined);
270
+ return defined;
271
+ }
272
+ //# sourceMappingURL=c-feature-variants.js.map
@@ -0,0 +1,21 @@
1
+ import { type ResolutionContext, type UnresolvedRef } from './types';
2
+ export declare function clearCppMacroVisibility(context: ResolutionContext): void;
3
+ /**
4
+ * Is this C/C++ `calls` ref a macro expansion rather than a call? True when
5
+ * the index knows the name as a function-like macro (extraction mints a
6
+ * `constant` per `preproc_function_def`) and either nothing but macros bears
7
+ * the name — there is no function to call, and a case-insensitive fuzzy
8
+ * match (`SWAP` → `swap`) must not invent one — or the macro is definitely
9
+ * visible at the call site.
10
+ */
11
+ export declare function isVisibleCppMacro(ref: UnresolvedRef, context: ResolutionContext): boolean;
12
+ /**
13
+ * The file's lines with comments removed — only as far as the preprocessor
14
+ * needs: a line inside a block comment is blank, a directive line loses its
15
+ * trailing `//` / `/* … *\/`, and every other line is kept verbatim (its
16
+ * content is never read, only whether a block comment opens on it). The
17
+ * generic comment stripper is a regex pass over the whole file and this walk
18
+ * touches every header of a translation unit, vendor trees included.
19
+ */
20
+ export declare function directiveLines(source: string): string[];
21
+ //# sourceMappingURL=cpp-macro-visibility.d.ts.map