gitnexus 1.6.4-rc.100 → 1.6.4-rc.102

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.
@@ -48,6 +48,8 @@ export { buildScopeTree, canParentScope, ScopeTreeInvariantError, } from './scop
48
48
  export type { ScopeTree } from './scope-resolution/scope-tree.js';
49
49
  export { buildPositionIndex } from './scope-resolution/position-index.js';
50
50
  export type { PositionIndex } from './scope-resolution/position-index.js';
51
+ export { UNDERSTAND_QUICKLY_DISPATCH_URL, UNDERSTAND_QUICKLY_EVENT_TYPE, UNDERSTAND_QUICKLY_TOKEN_ENV, buildUqDispatchPayload, isValidOwnerRepo, parseOwnerRepoFromRemote, stripGitSuffix, } from './integrations/understand-quickly.js';
52
+ export type { UqDispatchPayload } from './integrations/understand-quickly.js';
51
53
  export { diffResolutions } from './scope-resolution/shadow/diff.js';
52
54
  export type { ShadowAgreement, ShadowCallsite, ShadowDiff, } from './scope-resolution/shadow/diff.js';
53
55
  export { aggregateDiffs } from './scope-resolution/shadow/aggregate.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -33,6 +33,8 @@ export { CLASS_KINDS, METHOD_KINDS, FIELD_KINDS } from './scope-resolution/regis
33
33
  export { makeScopeId, clearScopeIdInternPool } from './scope-resolution/scope-id.js';
34
34
  export { buildScopeTree, canParentScope, ScopeTreeInvariantError, } from './scope-resolution/scope-tree.js';
35
35
  export { buildPositionIndex } from './scope-resolution/position-index.js';
36
+ // Understand-Quickly registry integration (opt-in)
37
+ export { UNDERSTAND_QUICKLY_DISPATCH_URL, UNDERSTAND_QUICKLY_EVENT_TYPE, UNDERSTAND_QUICKLY_TOKEN_ENV, buildUqDispatchPayload, isValidOwnerRepo, parseOwnerRepoFromRemote, stripGitSuffix, } from './integrations/understand-quickly.js';
36
38
  // Shadow-mode diff + aggregation (RFC §6.3; Ring 2 SHARED #918)
37
39
  export { diffResolutions } from './scope-resolution/shadow/diff.js';
38
40
  export { aggregateDiffs } from './scope-resolution/shadow/aggregate.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,mBAAmB;AACnB,OAAO,EACL,WAAW,EACX,cAAc,EACd,SAAS,EACT,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAGpC,mBAAmB;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AAiCjG,8DAA8D;AAC9D,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAC;AACnG,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAGxE,yDAAyD;AACzD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,+CAA+C,CAAC;AAGvD,sEAAsE;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEhE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AAEjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AAGrF,gEAAgE;AAChE,yEAAyE;AACzE,2DAA2D;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAOxE,oFAAoF;AACpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,6CAA6C,CAAC;AAMvF,uEAAuE;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,0CAA0C,CAAC;AAUpE,sEAAsE;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iDAAiD,CAAC;AAErF,OAAO,EAAE,mBAAmB,EAAE,MAAM,kDAAkD,CAAC;AAKvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iDAAiD,CAAC;AAKrF,OAAO,EAAE,UAAU,EAAE,MAAM,8CAA8C,CAAC;AAE1E,OAAO,EAAE,eAAe,EAAE,MAAM,mDAAmD,CAAC;AAEpF,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAEpG,OAAO,EACL,gCAAgC,EAChC,kBAAkB,GACnB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAQlG,2EAA2E;AAC3E,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAErF,OAAO,EACL,cAAc,EACd,cAAc,EACd,uBAAuB,GACxB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAG1E,gEAAgE;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAMpE,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,mBAAmB;AACnB,OAAO,EACL,WAAW,EACX,cAAc,EACd,SAAS,EACT,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAGpC,mBAAmB;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AAiCjG,8DAA8D;AAC9D,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAC;AACnG,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAGxE,yDAAyD;AACzD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,+CAA+C,CAAC;AAGvD,sEAAsE;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEhE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AAEjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AAGrF,gEAAgE;AAChE,yEAAyE;AACzE,2DAA2D;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAOxE,oFAAoF;AACpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,6CAA6C,CAAC;AAMvF,uEAAuE;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,0CAA0C,CAAC;AAUpE,sEAAsE;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iDAAiD,CAAC;AAErF,OAAO,EAAE,mBAAmB,EAAE,MAAM,kDAAkD,CAAC;AAKvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iDAAiD,CAAC;AAKrF,OAAO,EAAE,UAAU,EAAE,MAAM,8CAA8C,CAAC;AAE1E,OAAO,EAAE,eAAe,EAAE,MAAM,mDAAmD,CAAC;AAEpF,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAEpG,OAAO,EACL,gCAAgC,EAChC,kBAAkB,GACnB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAQlG,2EAA2E;AAC3E,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAErF,OAAO,EACL,cAAc,EACd,cAAc,EACd,uBAAuB,GACxB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAG1E,mDAAmD;AACnD,OAAO,EACL,+BAA+B,EAC/B,6BAA6B,EAC7B,4BAA4B,EAC5B,sBAAsB,EACtB,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,GACf,MAAM,sCAAsC,CAAC;AAG9C,gEAAgE;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAMpE,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Understand-Quickly registry integration helpers.
3
+ *
4
+ * Pure, runtime-agnostic logic for opting in to publishing a GitNexus
5
+ * index to the [`looptech-ai/understand-quickly`](https://github.com/looptech-ai/understand-quickly)
6
+ * registry. Lives in `gitnexus-shared` so both the Node CLI and any
7
+ * future browser-side surface can construct identical dispatch payloads.
8
+ *
9
+ * Network I/O lives in the CLI command (`gitnexus/src/cli/publish.ts`)
10
+ * to keep this module free of Node-only imports — see the comment at
11
+ * the top of `gitnexus-shared/src/graph/types.ts`.
12
+ *
13
+ * The protocol contract (single dispatch event, no graph upload) is
14
+ * documented at:
15
+ * https://github.com/looptech-ai/understand-quickly/blob/main/docs/integrations/protocol.md
16
+ */
17
+ /**
18
+ * URL of the registry repo's repository_dispatch endpoint. Hardcoded
19
+ * because the registry is the canonical home for this integration —
20
+ * users who want a private registry can fork and patch.
21
+ */
22
+ export declare const UNDERSTAND_QUICKLY_DISPATCH_URL = "https://api.github.com/repos/looptech-ai/understand-quickly/dispatches";
23
+ /**
24
+ * Event type the registry's sync workflow listens for.
25
+ * See `looptech-ai/understand-quickly/.github/workflows/sync.yml`.
26
+ */
27
+ export declare const UNDERSTAND_QUICKLY_EVENT_TYPE = "sync-entry";
28
+ /** Environment variable that gates the dispatch. */
29
+ export declare const UNDERSTAND_QUICKLY_TOKEN_ENV = "UNDERSTAND_QUICKLY_TOKEN";
30
+ export interface UqDispatchPayload {
31
+ event_type: typeof UNDERSTAND_QUICKLY_EVENT_TYPE;
32
+ client_payload: {
33
+ /** `<owner>/<repo>` shape — must match the registered entry. */
34
+ id: string;
35
+ };
36
+ }
37
+ /**
38
+ * Build the JSON body for the `repository_dispatch` ping. Pure — no
39
+ * env reads, no network. Validates that `id` looks like `owner/repo`
40
+ * (one slash, no whitespace, both halves non-empty) so a misconfigured
41
+ * caller fails loudly before the round-trip.
42
+ */
43
+ export declare function buildUqDispatchPayload(id: string): UqDispatchPayload;
44
+ /**
45
+ * `owner/repo` validation. Conservative on purpose: GitHub's actual
46
+ * naming rules are looser, but we want to catch local paths
47
+ * (`/Users/...`), bare slugs (`my-repo`), and accidental whitespace.
48
+ *
49
+ * Matches GitHub's published slug rules:
50
+ * owner: starts with alnum, then alnum/hyphen only, must end with
51
+ * alnum (no trailing hyphen — GitHub rejects this at account
52
+ * creation, so a `my-org-/repo` input would otherwise pass us
53
+ * and 422 from GitHub). No underscore, no dot. Length cap 39.
54
+ * repo: any of alnum/dot/hyphen/underscore. Length cap 100.
55
+ */
56
+ export declare function isValidOwnerRepo(id: string): boolean;
57
+ /**
58
+ * Strip a single trailing `.git` (case-insensitive) and any trailing
59
+ * slashes from a URL-ish string. Bounded linear: each character is
60
+ * visited at most twice, no backtracking.
61
+ *
62
+ * Replaces `s.replace(/\.git\/*$/i, '').replace(/\/+$/, '')` which
63
+ * CodeQL's polynomial-regex check (codeql/js/polynomial-redos) flags as
64
+ * a worst-case O(n²) on adversarial input like "////.../x".
65
+ */
66
+ export declare function stripGitSuffix(input: string): string;
67
+ /**
68
+ * Parse `owner/repo` out of a git remote URL. Mirrors the heuristic in
69
+ * `gitnexus/src/storage/git.ts:parseRepoNameFromUrl` but keeps both
70
+ * halves so we can build a registry id. Returns `null` on shapes we
71
+ * don't recognise.
72
+ *
73
+ * Examples:
74
+ * git@github.com:looptech-ai/understand-quickly.git
75
+ * https://github.com/looptech-ai/understand-quickly
76
+ * ssh://git@github.com/looptech-ai/understand-quickly.git
77
+ */
78
+ export declare function parseOwnerRepoFromRemote(url: string | null | undefined): string | null;
79
+ //# sourceMappingURL=understand-quickly.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"understand-quickly.d.ts","sourceRoot":"","sources":["../../src/integrations/understand-quickly.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,2EAC8B,CAAC;AAE3E;;;GAGG;AACH,eAAO,MAAM,6BAA6B,eAAe,CAAC;AAE1D,oDAAoD;AACpD,eAAO,MAAM,4BAA4B,6BAA6B,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,OAAO,6BAA6B,CAAC;IACjD,cAAc,EAAE;QACd,gEAAgE;QAChE,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;CACH;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB,CAYpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAsCtF"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Understand-Quickly registry integration helpers.
3
+ *
4
+ * Pure, runtime-agnostic logic for opting in to publishing a GitNexus
5
+ * index to the [`looptech-ai/understand-quickly`](https://github.com/looptech-ai/understand-quickly)
6
+ * registry. Lives in `gitnexus-shared` so both the Node CLI and any
7
+ * future browser-side surface can construct identical dispatch payloads.
8
+ *
9
+ * Network I/O lives in the CLI command (`gitnexus/src/cli/publish.ts`)
10
+ * to keep this module free of Node-only imports — see the comment at
11
+ * the top of `gitnexus-shared/src/graph/types.ts`.
12
+ *
13
+ * The protocol contract (single dispatch event, no graph upload) is
14
+ * documented at:
15
+ * https://github.com/looptech-ai/understand-quickly/blob/main/docs/integrations/protocol.md
16
+ */
17
+ /**
18
+ * URL of the registry repo's repository_dispatch endpoint. Hardcoded
19
+ * because the registry is the canonical home for this integration —
20
+ * users who want a private registry can fork and patch.
21
+ */
22
+ export const UNDERSTAND_QUICKLY_DISPATCH_URL = 'https://api.github.com/repos/looptech-ai/understand-quickly/dispatches';
23
+ /**
24
+ * Event type the registry's sync workflow listens for.
25
+ * See `looptech-ai/understand-quickly/.github/workflows/sync.yml`.
26
+ */
27
+ export const UNDERSTAND_QUICKLY_EVENT_TYPE = 'sync-entry';
28
+ /** Environment variable that gates the dispatch. */
29
+ export const UNDERSTAND_QUICKLY_TOKEN_ENV = 'UNDERSTAND_QUICKLY_TOKEN';
30
+ /**
31
+ * Build the JSON body for the `repository_dispatch` ping. Pure — no
32
+ * env reads, no network. Validates that `id` looks like `owner/repo`
33
+ * (one slash, no whitespace, both halves non-empty) so a misconfigured
34
+ * caller fails loudly before the round-trip.
35
+ */
36
+ export function buildUqDispatchPayload(id) {
37
+ if (!isValidOwnerRepo(id)) {
38
+ throw new Error(`[understand-quickly] expected id of the form "owner/repo", got "${id}". ` +
39
+ `The registry uses this string to look up your entry in registry.json — ` +
40
+ `it must match the GitHub owner/repo of the source code, not a local path.`);
41
+ }
42
+ return {
43
+ event_type: UNDERSTAND_QUICKLY_EVENT_TYPE,
44
+ client_payload: { id },
45
+ };
46
+ }
47
+ /**
48
+ * `owner/repo` validation. Conservative on purpose: GitHub's actual
49
+ * naming rules are looser, but we want to catch local paths
50
+ * (`/Users/...`), bare slugs (`my-repo`), and accidental whitespace.
51
+ *
52
+ * Matches GitHub's published slug rules:
53
+ * owner: starts with alnum, then alnum/hyphen only, must end with
54
+ * alnum (no trailing hyphen — GitHub rejects this at account
55
+ * creation, so a `my-org-/repo` input would otherwise pass us
56
+ * and 422 from GitHub). No underscore, no dot. Length cap 39.
57
+ * repo: any of alnum/dot/hyphen/underscore. Length cap 100.
58
+ */
59
+ export function isValidOwnerRepo(id) {
60
+ return /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?\/[A-Za-z0-9._-]{1,100}$/.test(id);
61
+ }
62
+ /**
63
+ * Strip a single trailing `.git` (case-insensitive) and any trailing
64
+ * slashes from a URL-ish string. Bounded linear: each character is
65
+ * visited at most twice, no backtracking.
66
+ *
67
+ * Replaces `s.replace(/\.git\/*$/i, '').replace(/\/+$/, '')` which
68
+ * CodeQL's polynomial-regex check (codeql/js/polynomial-redos) flags as
69
+ * a worst-case O(n²) on adversarial input like "////.../x".
70
+ */
71
+ export function stripGitSuffix(input) {
72
+ let end = input.length;
73
+ // Trim trailing '/'.
74
+ while (end > 0 && input.charCodeAt(end - 1) === 0x2f)
75
+ end--;
76
+ // Drop one trailing '.git' (case-insensitive).
77
+ if (end >= 4) {
78
+ const tail = input.slice(end - 4, end).toLowerCase();
79
+ if (tail === '.git')
80
+ end -= 4;
81
+ }
82
+ // Trim trailing '/' that may have sat between '.git' and the rest.
83
+ while (end > 0 && input.charCodeAt(end - 1) === 0x2f)
84
+ end--;
85
+ return input.slice(0, end);
86
+ }
87
+ /**
88
+ * Parse `owner/repo` out of a git remote URL. Mirrors the heuristic in
89
+ * `gitnexus/src/storage/git.ts:parseRepoNameFromUrl` but keeps both
90
+ * halves so we can build a registry id. Returns `null` on shapes we
91
+ * don't recognise.
92
+ *
93
+ * Examples:
94
+ * git@github.com:looptech-ai/understand-quickly.git
95
+ * https://github.com/looptech-ai/understand-quickly
96
+ * ssh://git@github.com/looptech-ai/understand-quickly.git
97
+ */
98
+ export function parseOwnerRepoFromRemote(url) {
99
+ if (!url)
100
+ return null;
101
+ const trimmed = url.trim();
102
+ if (!trimmed)
103
+ return null;
104
+ // Strip a trailing `.git` (case-insensitive) and any trailing slashes
105
+ // so https://h/o/r and https://h/o/r.git collapse to the same id.
106
+ // Bounded-linear helper avoids the polynomial-regex CodeQL alert.
107
+ const stripped = stripGitSuffix(trimmed);
108
+ // SCP-form SSH (`git@host:owner/repo`). Capture host so we can reject
109
+ // non-GitHub remotes — a GitLab origin like
110
+ // `https://gitlab.example.com/group/sub/project.git` would otherwise
111
+ // silently dispatch the wrong id (LOW 9).
112
+ const ssh = stripped.match(/^[^@]+@([^:]+):([^/]+)\/([^/]+)$/);
113
+ if (ssh) {
114
+ const host = ssh[1].toLowerCase();
115
+ if (host !== 'github.com' && host !== 'www.github.com')
116
+ return null;
117
+ return `${ssh[2]}/${ssh[3]}`;
118
+ }
119
+ // URL forms (https://, ssh://, git://, file://) — last two path segments.
120
+ const url2 = stripped.match(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/([^/]+)\/(.+)$/);
121
+ if (url2) {
122
+ // Strip optional `userinfo@` (e.g. `ssh://git@github.com/...`).
123
+ const authority = url2[1];
124
+ const atIdx = authority.lastIndexOf('@');
125
+ const hostAndPort = atIdx >= 0 ? authority.slice(atIdx + 1) : authority;
126
+ // Strip `:port` suffix if present.
127
+ const colonIdx = hostAndPort.indexOf(':');
128
+ const host = (colonIdx >= 0 ? hostAndPort.slice(0, colonIdx) : hostAndPort).toLowerCase();
129
+ if (host !== 'github.com' && host !== 'www.github.com')
130
+ return null;
131
+ const segments = url2[2].split('/').filter(Boolean);
132
+ if (segments.length >= 2) {
133
+ const [owner, repo] = segments.slice(-2);
134
+ return `${owner}/${repo}`;
135
+ }
136
+ }
137
+ return null;
138
+ }
139
+ //# sourceMappingURL=understand-quickly.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"understand-quickly.js","sourceRoot":"","sources":["../../src/integrations/understand-quickly.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -106,6 +106,15 @@ program
106
106
  .command('augment <pattern>')
107
107
  .description('Augment a search pattern with knowledge graph context (used by hooks)')
108
108
  .action(createLazyAction(() => import('./augment.js'), 'augmentCommand'));
109
+ program
110
+ .command('publish [path]')
111
+ .description('Notify the understand-quickly registry that this repo has a fresh GitNexus index. ' +
112
+ 'Opt-in: requires UNDERSTAND_QUICKLY_TOKEN (fine-grained PAT with ' +
113
+ '`Repository dispatches: write` on looptech-ai/understand-quickly). ' +
114
+ 'No-op without the token. See https://github.com/looptech-ai/understand-quickly.')
115
+ .option('--id <owner/repo>', 'Override the registry id (defaults to the origin remote)')
116
+ .option('--skip-git', 'Treat cwd as the repo root and skip parent git-root discovery')
117
+ .action(createLazyAction(() => import('./publish.js'), 'publishCommand'));
109
118
  // ─── Direct Tool Commands (no MCP overhead) ────────────────────────
110
119
  // These invoke LocalBackend directly for use in eval, scripts, and CI.
111
120
  program
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `gitnexus publish` — opt-in ping to the understand-quickly registry.
3
+ *
4
+ * Fires a single `repository_dispatch` event at
5
+ * `looptech-ai/understand-quickly` so the registry knows to refresh its
6
+ * entry for the current repo. Does NOT upload anything: per the
7
+ * understand-quickly protocol, the registry pulls the graph from a
8
+ * raw-GitHub URL the user controls.
9
+ *
10
+ * https://github.com/looptech-ai/understand-quickly/blob/main/docs/integrations/protocol.md
11
+ *
12
+ * Defaults:
13
+ * - Without `UNDERSTAND_QUICKLY_TOKEN` in the env, this is a no-op
14
+ * (prints one informational line, exit 0). Same shape as the
15
+ * `--publish` patterns in sibling tools.
16
+ * - With the token, fires the dispatch and reports the response code.
17
+ *
18
+ * The `id` is derived from the repo's `origin` remote unless the caller
19
+ * passes `--id <owner/repo>` explicitly. We deliberately do NOT auto-add
20
+ * the repo to the registry — registration is one-time and uses the
21
+ * `npx @understand-quickly/cli add` path documented in the protocol.
22
+ */
23
+ export interface PublishOptions {
24
+ /** Override the auto-derived `owner/repo` id. */
25
+ id?: string;
26
+ /** Treat the cwd as the repo root (skip git-root walk). */
27
+ skipGit?: boolean;
28
+ }
29
+ export declare const publishCommand: (inputPath?: string, options?: PublishOptions) => Promise<void>;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * `gitnexus publish` — opt-in ping to the understand-quickly registry.
3
+ *
4
+ * Fires a single `repository_dispatch` event at
5
+ * `looptech-ai/understand-quickly` so the registry knows to refresh its
6
+ * entry for the current repo. Does NOT upload anything: per the
7
+ * understand-quickly protocol, the registry pulls the graph from a
8
+ * raw-GitHub URL the user controls.
9
+ *
10
+ * https://github.com/looptech-ai/understand-quickly/blob/main/docs/integrations/protocol.md
11
+ *
12
+ * Defaults:
13
+ * - Without `UNDERSTAND_QUICKLY_TOKEN` in the env, this is a no-op
14
+ * (prints one informational line, exit 0). Same shape as the
15
+ * `--publish` patterns in sibling tools.
16
+ * - With the token, fires the dispatch and reports the response code.
17
+ *
18
+ * The `id` is derived from the repo's `origin` remote unless the caller
19
+ * passes `--id <owner/repo>` explicitly. We deliberately do NOT auto-add
20
+ * the repo to the registry — registration is one-time and uses the
21
+ * `npx @understand-quickly/cli add` path documented in the protocol.
22
+ */
23
+ import path from 'path';
24
+ import { UNDERSTAND_QUICKLY_DISPATCH_URL, UNDERSTAND_QUICKLY_TOKEN_ENV, buildUqDispatchPayload, isValidOwnerRepo, parseOwnerRepoFromRemote, } from '../_shared/index.js';
25
+ import { getGitRoot, getRemoteOriginUrl, getCurrentCommit } from '../storage/git.js';
26
+ import { hasIndex } from '../storage/repo-manager.js';
27
+ import { cliInfo, cliError } from './cli-message.js';
28
+ const REGISTER_HINT = 'Register your repo once with: npx @understand-quickly/cli add\n' +
29
+ 'Or use the wizard: https://looptech-ai.github.io/understand-quickly/add.html';
30
+ /**
31
+ * Hard cap on the dispatch fetch to keep CI publish steps from stalling
32
+ * for the OS TCP timeout (~2 min) when api.github.com is unreachable.
33
+ * Matches the pattern used in `src/core/embeddings/http-client.ts`.
34
+ */
35
+ const DISPATCH_TIMEOUT_MS = 15_000;
36
+ export const publishCommand = async (inputPath, options = {}) => {
37
+ // ── 0. Token gate FIRST — guarantees true no-op without the token. ──
38
+ // The README, CLI --help, and PR body all promise "exit 0 without
39
+ // UNDERSTAND_QUICKLY_TOKEN". Doing the index/repo-root checks before
40
+ // the token gate would make those promises false for users who haven't
41
+ // run `gitnexus analyze` yet but want to verify the command is wired.
42
+ const token = process.env[UNDERSTAND_QUICKLY_TOKEN_ENV];
43
+ if (!token) {
44
+ cliInfo(`[understand-quickly] ${UNDERSTAND_QUICKLY_TOKEN_ENV} is not set — skipping dispatch.\n` +
45
+ `Set it to a fine-grained PAT with "Repository dispatches: write" on ` +
46
+ `looptech-ai/understand-quickly to enable instant resync.\n` +
47
+ `(Without the token, the registry's nightly sync still picks up your entry.)`, { skipped: 'no-token' });
48
+ return;
49
+ }
50
+ // ── 1. Resolve the repo root (same precedence as `analyze`) ──────────
51
+ let repoPath;
52
+ if (inputPath) {
53
+ repoPath = path.resolve(inputPath);
54
+ }
55
+ else if (options.skipGit) {
56
+ repoPath = path.resolve(process.cwd());
57
+ }
58
+ else {
59
+ const gitRoot = getGitRoot(process.cwd());
60
+ if (!gitRoot) {
61
+ cliError('[understand-quickly] not inside a git repository.\n' +
62
+ 'Run from a repo, or pass --skip-git to publish from the current directory.');
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+ repoPath = gitRoot;
67
+ }
68
+ // ── 2. Confirm a GitNexus index exists ───────────────────────────────
69
+ // Publishing without an index is almost always a mistake — the
70
+ // registry's nightly sync would fetch a stale or missing graph file
71
+ // and mark the entry `missing`. Refuse loudly with a fix-it hint.
72
+ if (!(await hasIndex(repoPath))) {
73
+ cliError(`[understand-quickly] no GitNexus index found at ${repoPath}/.gitnexus.\n` +
74
+ 'Run `gitnexus analyze` first, then re-run `gitnexus publish`.');
75
+ process.exitCode = 1;
76
+ return;
77
+ }
78
+ // ── 3. Derive the registry id ─────────────────────────────────────────
79
+ const id = options.id ?? parseOwnerRepoFromRemote(getRemoteOriginUrl(repoPath) ?? undefined) ?? null;
80
+ if (!id || !isValidOwnerRepo(id)) {
81
+ cliError(`[understand-quickly] could not derive a registry id from this repo.\n` +
82
+ `Pass --id <owner/repo> explicitly (e.g. --id looptech-ai/${path.basename(repoPath)}).\n` +
83
+ REGISTER_HINT);
84
+ process.exitCode = 1;
85
+ return;
86
+ }
87
+ // ── 4. Fire the dispatch ─────────────────────────────────────────────
88
+ const payload = buildUqDispatchPayload(id);
89
+ let response;
90
+ try {
91
+ response = await fetch(UNDERSTAND_QUICKLY_DISPATCH_URL, {
92
+ method: 'POST',
93
+ headers: {
94
+ Accept: 'application/vnd.github+json',
95
+ Authorization: `Bearer ${token}`,
96
+ 'X-GitHub-Api-Version': '2022-11-28',
97
+ 'Content-Type': 'application/json',
98
+ 'User-Agent': 'gitnexus-cli',
99
+ },
100
+ body: JSON.stringify(payload),
101
+ signal: AbortSignal.timeout(DISPATCH_TIMEOUT_MS),
102
+ });
103
+ }
104
+ catch (err) {
105
+ // `AbortSignal.timeout()` throws a `DOMException` with `name ===
106
+ // 'TimeoutError'` on Node 18.14+ (and on browsers/Bun). It is NOT
107
+ // a plain `AbortError`. Match the pattern used in
108
+ // gitnexus/src/core/embeddings/http-client.ts so the user sees the
109
+ // targeted "timed out" message instead of a generic "operation
110
+ // was aborted".
111
+ const isTimeout = err instanceof DOMException && err.name === 'TimeoutError';
112
+ if (isTimeout) {
113
+ cliError(`[understand-quickly] dispatch timed out after ${DISPATCH_TIMEOUT_MS}ms. ` +
114
+ `Check network access to api.github.com and retry.`, { id });
115
+ }
116
+ else {
117
+ const msg = err instanceof Error ? err.message : String(err);
118
+ cliError(`[understand-quickly] dispatch network error: ${msg}`, { id });
119
+ }
120
+ process.exitCode = 1;
121
+ return;
122
+ }
123
+ // GitHub returns 204 on success. Distinct branches for 401/403/404/422
124
+ // so users debug without checking the docs.
125
+ if (response.status === 204) {
126
+ await response.body?.cancel().catch(() => { });
127
+ // `getCurrentCommit` is only meaningful in the success path — moving
128
+ // it inside this branch removes a wasted child-process spawn on every
129
+ // error response (LOW 7).
130
+ const commit = getCurrentCommit(repoPath);
131
+ cliInfo(`[understand-quickly] dispatched sync-entry for ${id}` +
132
+ (commit ? ` @ ${commit.slice(0, 7)}` : '') +
133
+ '.\n' +
134
+ `Note: a 204 only confirms GitHub accepted the dispatch. Whether the ` +
135
+ `registry workflow finds an entry for "${id}" is logged at ` +
136
+ `https://github.com/looptech-ai/understand-quickly/actions/workflows/sync.yml`, { id, commit, status: response.status });
137
+ return;
138
+ }
139
+ if (response.status === 401) {
140
+ cliError(`[understand-quickly] dispatch returned 401 — the ${UNDERSTAND_QUICKLY_TOKEN_ENV} value is invalid or expired.\n` +
141
+ `Regenerate a fine-grained PAT at https://github.com/settings/personal-access-tokens ` +
142
+ `with Repository access scoped to looptech-ai/understand-quickly and the ` +
143
+ `"Repository dispatches: write" permission, then retry.`, { id, status: response.status });
144
+ process.exitCode = 1;
145
+ return;
146
+ }
147
+ if (response.status === 403) {
148
+ cliError(`[understand-quickly] dispatch returned 403 — the token authenticated but ` +
149
+ `lacks the "Repository dispatches: write" permission on ` +
150
+ `looptech-ai/understand-quickly. Edit the PAT scopes and retry.`, { id, status: response.status });
151
+ process.exitCode = 1;
152
+ return;
153
+ }
154
+ if (response.status === 404) {
155
+ cliError(`[understand-quickly] dispatch returned 404 — the token cannot reach ` +
156
+ `looptech-ai/understand-quickly. Verify the PAT has Repository access to ` +
157
+ `that exact repo (not just your own org).`, { id, status: response.status });
158
+ process.exitCode = 1;
159
+ return;
160
+ }
161
+ if (response.status === 422) {
162
+ // Malformed event_type / client_payload — a code bug in this CLI,
163
+ // not a user mistake. Surface so we get bug reports.
164
+ const body422 = await response.text().catch(() => '');
165
+ cliError(`[understand-quickly] dispatch returned 422 (this is a CLI bug; please report).\n` +
166
+ `Body: ${body422 || '(empty)'}`, { id, status: response.status });
167
+ process.exitCode = 1;
168
+ return;
169
+ }
170
+ // 5xx and anything else → bubble the body so the user has something to act on.
171
+ const body = await response.text().catch(() => '');
172
+ cliError(`[understand-quickly] dispatch failed with HTTP ${response.status}: ${body || '(empty body)'}`, { id, status: response.status });
173
+ process.exitCode = 1;
174
+ };
@@ -612,9 +612,10 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
612
612
  await loadLanguage(language, file.path);
613
613
  let tree = astCache.get(file.path);
614
614
  if (!tree) {
615
+ const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
615
616
  try {
616
- tree = parser.parse(file.content, undefined, {
617
- bufferSize: getTreeSitterBufferSize(file.content),
617
+ tree = parser.parse(parseContent, undefined, {
618
+ bufferSize: getTreeSitterBufferSize(parseContent),
618
619
  });
619
620
  }
620
621
  catch (parseError) {
@@ -2583,9 +2584,10 @@ export const extractFetchCallsFromFiles = async (files, astCache) => {
2583
2584
  await loadLanguage(language, file.path);
2584
2585
  let tree = astCache.get(file.path);
2585
2586
  if (!tree) {
2587
+ const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
2586
2588
  try {
2587
- tree = parser.parse(file.content, undefined, {
2588
- bufferSize: getTreeSitterBufferSize(file.content),
2589
+ tree = parser.parse(parseContent, undefined, {
2590
+ bufferSize: getTreeSitterBufferSize(parseContent),
2589
2591
  });
2590
2592
  }
2591
2593
  catch {
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Strip Unreal Engine reflection macros from C++ source, length-preserving.
3
+ *
4
+ * Returns the original string unchanged if no strong UE marker is detected,
5
+ * so non-UE C++ files (including ones that contain `*_API`-suffixed
6
+ * identifiers like `REST_API` or `HTTP_API`) incur only a single regex test.
7
+ *
8
+ * The `_filePath` parameter is part of the `LanguageProvider.preprocessSource`
9
+ * contract but is unused — UE detection is purely content-based. Accepted and
10
+ * ignored here so the function matches the hook signature exactly.
11
+ */
12
+ export declare function stripUeMacros(source: string, _filePath?: string): string;
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Unreal Engine reflection-macro preprocessor for C++ source.
3
+ *
4
+ * Tree-sitter does not expand C preprocessor macros, so Unreal's reflection
5
+ * markers (`UCLASS(...)`, `UFUNCTION(...)`, `MODULENAME_API`, ...) are parsed
6
+ * verbatim. The result is mis-parsed declarations: in `class BRAWLUI_API
7
+ * UMyClass : public UObject`, tree-sitter-cpp captures `BRAWLUI_API` as the
8
+ * class name and the rest of the declaration becomes structurally wrong.
9
+ *
10
+ * This module elides those macros from the source text BEFORE tree-sitter
11
+ * parses it. Replacement is **length-preserving** (each elided byte becomes
12
+ * a space, newlines preserved) so byte offsets and line/column positions
13
+ * tree-sitter reports remain identical to the original file. Symbol
14
+ * locations in the graph stay accurate.
15
+ *
16
+ * A cheap detection guard short-circuits files that don't look like UE
17
+ * sources, so non-UE C++ codebases pay no cost.
18
+ *
19
+ * Pure function — no tree-sitter dependency, safe for worker threads.
20
+ */
21
+ /**
22
+ * Strong UE markers — reflection macros that only Unreal Engine projects use.
23
+ * Presence of one of these is sufficient evidence that the file is a UE source
24
+ * and that `MODULENAME_API` tokens in it are intended as export macros.
25
+ *
26
+ * Importantly, `_API` tokens are NOT in this guard — `REST_API`, `HTTP_API`,
27
+ * `MY_LIB_API` and similar identifiers appear in plenty of non-UE C++ codebases
28
+ * as constants/enums/parameter names. We must not erase them just because the
29
+ * file mentions an `_API` token.
30
+ */
31
+ const HAS_UE_HINT = /\b(?:UCLASS|UFUNCTION|UPROPERTY|USTRUCT|UENUM|UINTERFACE|GENERATED_BODY|GENERATED_[A-Z_]+_BODY|UE_DEPRECATED|DECLARE_(?:DYNAMIC_)?(?:MULTICAST_)?DELEGATE)/;
32
+ const SIMPLE_MACROS_NO_ARGS = [
33
+ 'GENERATED_BODY',
34
+ 'GENERATED_UCLASS_BODY',
35
+ 'GENERATED_USTRUCT_BODY',
36
+ 'GENERATED_UINTERFACE_BODY',
37
+ 'GENERATED_IINTERFACE_BODY',
38
+ 'DECLARE_CLASS',
39
+ 'GENERATED_BODY_LEGACY',
40
+ ];
41
+ const PARENTHESIZED_MACROS = [
42
+ 'UCLASS',
43
+ 'UFUNCTION',
44
+ 'UPROPERTY',
45
+ 'USTRUCT',
46
+ 'UENUM',
47
+ 'UINTERFACE',
48
+ 'UMETA',
49
+ 'UE_DEPRECATED',
50
+ ];
51
+ const DELEGATE_MACRO_RE = /\bDECLARE_(?:DYNAMIC_)?(?:MULTICAST_)?DELEGATE(?:_(?:RetVal_OneParam|RetVal_TwoParams|RetVal_ThreeParams|RetVal_FourParams|RetVal_FiveParams|RetVal_SixParams|RetVal_SevenParams|RetVal_EightParams|RetVal_NineParams|RetVal|OneParam|TwoParams|ThreeParams|FourParams|FiveParams|SixParams|SevenParams|EightParams|NineParams|TenParams))?(?=\s*\()/g;
52
+ /**
53
+ * Module export tokens like `BRAWLUI_API`, `ENGINE_API`, `COREUOBJECT_API`.
54
+ * Pattern: ALL_CAPS identifier ending in `_API`. The leading word boundary
55
+ * (`\b`) prevents matching mid-identifier.
56
+ */
57
+ const API_MACRO_RE = /\b[A-Z][A-Z0-9_]*_API\b/g;
58
+ /** Replace `[start, end)` of `chars` with spaces, preserving newlines. */
59
+ function eraseRange(chars, start, end) {
60
+ for (let i = start; i < end; i++) {
61
+ if (chars[i] !== '\n' && chars[i] !== '\r') {
62
+ chars[i] = ' ';
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Find the matching close paren for an opening paren at index `openIdx`.
68
+ * Returns the index of `)` (inclusive end), or -1 if unbalanced.
69
+ *
70
+ * Handles nested parens and string/char literals so commas/parens inside
71
+ * strings don't throw off the match. Does not attempt to handle raw string
72
+ * literals (`R"(...)"`); UE reflection-macro arguments do not use them in
73
+ * practice.
74
+ */
75
+ function findMatchingParen(source, openIdx) {
76
+ if (source.charCodeAt(openIdx) !== 0x28)
77
+ return -1;
78
+ let depth = 1;
79
+ let i = openIdx + 1;
80
+ const len = source.length;
81
+ while (i < len && depth > 0) {
82
+ const ch = source.charCodeAt(i);
83
+ // String literal
84
+ if (ch === 0x22) {
85
+ i++;
86
+ while (i < len) {
87
+ const c = source.charCodeAt(i);
88
+ if (c === 0x5c) {
89
+ i += 2;
90
+ continue;
91
+ }
92
+ if (c === 0x22) {
93
+ i++;
94
+ break;
95
+ }
96
+ i++;
97
+ }
98
+ continue;
99
+ }
100
+ // Char literal
101
+ if (ch === 0x27) {
102
+ i++;
103
+ while (i < len) {
104
+ const c = source.charCodeAt(i);
105
+ if (c === 0x5c) {
106
+ i += 2;
107
+ continue;
108
+ }
109
+ if (c === 0x27) {
110
+ i++;
111
+ break;
112
+ }
113
+ i++;
114
+ }
115
+ continue;
116
+ }
117
+ // Line comment
118
+ if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2f) {
119
+ while (i < len && source.charCodeAt(i) !== 0x0a)
120
+ i++;
121
+ continue;
122
+ }
123
+ // Block comment
124
+ if (ch === 0x2f && source.charCodeAt(i + 1) === 0x2a) {
125
+ i += 2;
126
+ while (i < len) {
127
+ if (source.charCodeAt(i) === 0x2a && source.charCodeAt(i + 1) === 0x2f) {
128
+ i += 2;
129
+ break;
130
+ }
131
+ i++;
132
+ }
133
+ continue;
134
+ }
135
+ if (ch === 0x28)
136
+ depth++;
137
+ else if (ch === 0x29) {
138
+ depth--;
139
+ if (depth === 0)
140
+ return i;
141
+ }
142
+ i++;
143
+ }
144
+ return -1;
145
+ }
146
+ /** Match a whole-word identifier at `idx`. Returns the byte after the identifier, or -1 on miss. */
147
+ function matchIdentifierAt(source, idx, name) {
148
+ if (idx > 0) {
149
+ const prev = source.charCodeAt(idx - 1);
150
+ if ((prev >= 0x30 && prev <= 0x39) ||
151
+ (prev >= 0x41 && prev <= 0x5a) ||
152
+ (prev >= 0x61 && prev <= 0x7a) ||
153
+ prev === 0x5f) {
154
+ return -1;
155
+ }
156
+ }
157
+ for (let k = 0; k < name.length; k++) {
158
+ if (source.charCodeAt(idx + k) !== name.charCodeAt(k))
159
+ return -1;
160
+ }
161
+ const after = idx + name.length;
162
+ if (after < source.length) {
163
+ const next = source.charCodeAt(after);
164
+ if ((next >= 0x30 && next <= 0x39) ||
165
+ (next >= 0x41 && next <= 0x5a) ||
166
+ (next >= 0x61 && next <= 0x7a) ||
167
+ next === 0x5f) {
168
+ return -1;
169
+ }
170
+ }
171
+ return after;
172
+ }
173
+ /** Skip ASCII whitespace forward from `idx`. Returns the next non-whitespace byte index. */
174
+ function skipWhitespace(source, idx) {
175
+ const len = source.length;
176
+ while (idx < len) {
177
+ const ch = source.charCodeAt(idx);
178
+ if (ch === 0x20 || ch === 0x09 || ch === 0x0a || ch === 0x0d) {
179
+ idx++;
180
+ continue;
181
+ }
182
+ break;
183
+ }
184
+ return idx;
185
+ }
186
+ /**
187
+ * Strip Unreal Engine reflection macros from C++ source, length-preserving.
188
+ *
189
+ * Returns the original string unchanged if no strong UE marker is detected,
190
+ * so non-UE C++ files (including ones that contain `*_API`-suffixed
191
+ * identifiers like `REST_API` or `HTTP_API`) incur only a single regex test.
192
+ *
193
+ * The `_filePath` parameter is part of the `LanguageProvider.preprocessSource`
194
+ * contract but is unused — UE detection is purely content-based. Accepted and
195
+ * ignored here so the function matches the hook signature exactly.
196
+ */
197
+ export function stripUeMacros(source, _filePath) {
198
+ if (!HAS_UE_HINT.test(source))
199
+ return source;
200
+ const chars = source.split('');
201
+ for (const macro of PARENTHESIZED_MACROS) {
202
+ let searchFrom = 0;
203
+ while (true) {
204
+ const hit = source.indexOf(macro, searchFrom);
205
+ if (hit < 0)
206
+ break;
207
+ searchFrom = hit + 1;
208
+ const after = matchIdentifierAt(source, hit, macro);
209
+ if (after < 0)
210
+ continue;
211
+ const parenIdx = skipWhitespace(source, after);
212
+ if (source.charCodeAt(parenIdx) !== 0x28)
213
+ continue;
214
+ const close = findMatchingParen(source, parenIdx);
215
+ if (close < 0)
216
+ continue;
217
+ eraseRange(chars, hit, close + 1);
218
+ }
219
+ }
220
+ for (const macro of SIMPLE_MACROS_NO_ARGS) {
221
+ let searchFrom = 0;
222
+ while (true) {
223
+ const hit = source.indexOf(macro, searchFrom);
224
+ if (hit < 0)
225
+ break;
226
+ searchFrom = hit + 1;
227
+ const after = matchIdentifierAt(source, hit, macro);
228
+ if (after < 0)
229
+ continue;
230
+ const parenIdx = skipWhitespace(source, after);
231
+ if (source.charCodeAt(parenIdx) === 0x28) {
232
+ const close = findMatchingParen(source, parenIdx);
233
+ if (close < 0)
234
+ continue;
235
+ eraseRange(chars, hit, close + 1);
236
+ }
237
+ else {
238
+ eraseRange(chars, hit, after);
239
+ }
240
+ }
241
+ }
242
+ for (const re of [DELEGATE_MACRO_RE, API_MACRO_RE]) {
243
+ re.lastIndex = 0;
244
+ let match;
245
+ while ((match = re.exec(source)) !== null) {
246
+ const start = match.index;
247
+ let end = start + match[0].length;
248
+ if (re === DELEGATE_MACRO_RE) {
249
+ const parenIdx = skipWhitespace(source, end);
250
+ if (source.charCodeAt(parenIdx) === 0x28) {
251
+ const close = findMatchingParen(source, parenIdx);
252
+ if (close >= 0)
253
+ end = close + 1;
254
+ }
255
+ }
256
+ eraseRange(chars, start, end);
257
+ }
258
+ }
259
+ return chars.join('');
260
+ }
@@ -147,9 +147,13 @@ export const processHeritage = async (graph, files, astCache, ctx, onProgress) =
147
147
  let tree = astCache.get(file.path);
148
148
  if (!tree) {
149
149
  // Use larger bufferSize for files > 32KB
150
+ // Per-language source preprocessor (length-preserving, e.g. UE macro
151
+ // stripping for C++). MUST mirror parsing-processor on cache miss so
152
+ // re-parses see the same input as the cached AST.
153
+ const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
150
154
  try {
151
- tree = parser.parse(file.content, undefined, {
152
- bufferSize: getTreeSitterBufferSize(file.content),
155
+ tree = parser.parse(parseContent, undefined, {
156
+ bufferSize: getTreeSitterBufferSize(parseContent),
153
157
  });
154
158
  }
155
159
  catch (parseError) {
@@ -294,9 +298,10 @@ export async function extractExtractedHeritageFromFiles(files, astCache) {
294
298
  await loadLanguage(language, file.path);
295
299
  let tree = astCache.get(file.path);
296
300
  if (!tree) {
301
+ const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
297
302
  try {
298
- tree = parser.parse(file.content, undefined, {
299
- bufferSize: getTreeSitterBufferSize(file.content),
303
+ tree = parser.parse(parseContent, undefined, {
304
+ bufferSize: getTreeSitterBufferSize(parseContent),
300
305
  });
301
306
  }
302
307
  catch {
@@ -241,9 +241,10 @@ export const processImports = async (graph, files, astCache, ctx, onProgress, re
241
241
  let tree = astCache.get(file.path);
242
242
  let wasReparsed = false;
243
243
  if (!tree) {
244
+ const parseContent = provider.preprocessSource?.(file.content, file.path) ?? file.content;
244
245
  try {
245
- tree = parser.parse(file.content, undefined, {
246
- bufferSize: getTreeSitterBufferSize(file.content),
246
+ tree = parser.parse(parseContent, undefined, {
247
+ bufferSize: getTreeSitterBufferSize(parseContent),
247
248
  });
248
249
  }
249
250
  catch (parseError) {
@@ -74,6 +74,38 @@ interface LanguageProviderConfig {
74
74
  /** Tree-sitter query strings for definitions, imports, calls, heritage.
75
75
  * Required for tree-sitter languages; empty string for standalone processors. */
76
76
  readonly treeSitterQueries: string;
77
+ /**
78
+ * Optional source-text transform that runs **before** tree-sitter parses the file.
79
+ *
80
+ * Used to elide language constructs that confuse the grammar without affecting
81
+ * source-position fidelity — e.g., Unreal Engine reflection macros (`UCLASS`,
82
+ * `UFUNCTION`, `MODULENAME_API`) in C++ headers that prevent the parser from
83
+ * recognising class/function names correctly.
84
+ *
85
+ * **Length / position preservation:** the returned string MUST have the same
86
+ * JavaScript `.length` as the input AND preserve every newline (`\n`/`\r`)
87
+ * position byte-for-byte. Implementations replace elided characters with
88
+ * ASCII spaces while leaving newlines untouched. With this contract:
89
+ *
90
+ * - tree-sitter's reported `startPosition.row`/`startPosition.column`
91
+ * match the original file exactly (line/column come from newline counts)
92
+ * - `startIndex`/`endIndex` byte offsets match the original file exactly
93
+ * **when the elided range is pure ASCII** (UTF-16 `.length` equals UTF-8
94
+ * byte length only for ASCII).
95
+ *
96
+ * Implementations targeting languages where elided ranges may contain
97
+ * non-ASCII content must therefore preserve byte length, not just `.length`,
98
+ * if downstream code uses `startIndex` to slice the original UTF-8 bytes.
99
+ * The current C++ UE-macro preprocessor relies on the practical fact that
100
+ * UE reflection macros and module-export tokens are ASCII-only.
101
+ *
102
+ * Must be a pure function — same input always yields the same output. Called
103
+ * once per file, on every code path that re-parses (parsing-processor, import
104
+ * processor, heritage processor, call processor, parse worker).
105
+ *
106
+ * Default: undefined (no preprocessing — `file.content` is parsed verbatim).
107
+ */
108
+ readonly preprocessSource?: (sourceText: string, filePath: string) => string;
77
109
  /** Type extraction: declarations, initializers, for-loop bindings */
78
110
  readonly typeConfig: LanguageTypeConfig;
79
111
  /** Export detection: is this AST node a public/exported symbol? */
@@ -36,6 +36,7 @@ import { cVariableConfig, cppVariableConfig } from '../variable-extractors/confi
36
36
  import { createCallExtractor } from '../call-extractors/generic.js';
37
37
  import { cCallConfig, cppCallConfig } from '../call-extractors/configs/c-cpp.js';
38
38
  import { createHeritageExtractor } from '../heritage-extractors/generic.js';
39
+ import { stripUeMacros } from '../cpp-ue-preprocessor.js';
39
40
  const C_BUILT_INS = new Set([
40
41
  'printf',
41
42
  'fprintf',
@@ -380,6 +381,7 @@ export const cppProvider = defineLanguage({
380
381
  },
381
382
  ],
382
383
  treeSitterQueries: CPP_QUERIES,
384
+ preprocessSource: stripUeMacros,
383
385
  typeConfig: cCppConfig,
384
386
  exportChecker: cCppExportChecker,
385
387
  importResolver: createImportResolver(cppImportConfig),
@@ -262,6 +262,10 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
262
262
  lineOffset = extracted.lineOffset;
263
263
  isVueSetup = extracted.isSetup;
264
264
  }
265
+ // Per-language source-text transform (e.g., UE macro stripping for C++).
266
+ // Length-preserving — see LanguageProvider.preprocessSource contract.
267
+ parseContent =
268
+ getProvider(language).preprocessSource?.(parseContent, file.path) ?? parseContent;
265
269
  try {
266
270
  await loadLanguage(language, file.path);
267
271
  }
@@ -1019,6 +1019,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1019
1019
  lineOffset = extracted.lineOffset;
1020
1020
  isVueSetup = extracted.isSetup;
1021
1021
  }
1022
+ // Per-language source-text transform (e.g., UE macro stripping for C++).
1023
+ // Length-preserving — see LanguageProvider.preprocessSource contract.
1024
+ parseContent =
1025
+ getProvider(language).preprocessSource?.(parseContent, file.path) ?? parseContent;
1022
1026
  clearCaches(); // Reset memoization before each new file
1023
1027
  let tree;
1024
1028
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.4-rc.100",
3
+ "version": "1.6.4-rc.102",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",