gitnexus 1.6.6-rc.153 → 1.6.6-rc.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/cli/ai-context.js +7 -7
- package/dist/cli/skill-gen.js +2 -2
- package/dist/core/ingestion/local-symbol-pruner.d.ts +11 -0
- package/dist/core/ingestion/local-symbol-pruner.js +63 -0
- package/dist/core/ingestion/pipeline-phases/communities.d.ts +1 -1
- package/dist/core/ingestion/pipeline-phases/communities.js +5 -2
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline-phases/mro.d.ts +1 -1
- package/dist/core/ingestion/pipeline-phases/mro.js +2 -2
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +1 -1
- package/dist/core/ingestion/pipeline-phases/processes.js +5 -3
- package/dist/core/ingestion/pipeline-phases/prune-local-symbols.d.ts +14 -0
- package/dist/core/ingestion/pipeline-phases/prune-local-symbols.js +26 -0
- package/dist/core/ingestion/pipeline.d.ts +14 -1
- package/dist/core/ingestion/pipeline.js +4 -2
- package/dist/core/ingestion/variable-extractors/generic.js +27 -2
- package/dist/mcp/local/local-backend.d.ts +37 -5
- package/dist/mcp/local/local-backend.js +213 -128
- package/dist/mcp/resources.js +3 -3
- package/package.json +1 -1
- package/skills/gitnexus-debugging.md +14 -14
- package/skills/gitnexus-exploring.md +10 -10
- package/skills/gitnexus-impact-analysis.md +9 -9
- package/skills/gitnexus-pr-review.md +18 -18
- package/skills/gitnexus-refactoring.md +24 -24
package/README.md
CHANGED
|
@@ -423,6 +423,16 @@ Three env vars expose the pool's resilience layers (respawn budget, cumulative-t
|
|
|
423
423
|
| `GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS` | `5 × subBatchTimeoutMs` | Total retry wall-time budget per job before quarantining. Bounds exponentially-growing retry waits. |
|
|
424
424
|
| `GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD` | `max(3, poolSize)` | Per-slot consecutive deaths before the pool's circuit breaker trips. After tripping, dispatches require a fresh pool. |
|
|
425
425
|
|
|
426
|
+
### Graph cleanup tuning
|
|
427
|
+
|
|
428
|
+
After scope resolution, analyze prunes inert block-local value symbols (a function-local `const`/`let`/`var` that ends up with only its structural `File→DEFINES` edge) to keep the graph focused on cross-symbol relationships. Module/file-scope symbols, class members, and any local with a real edge are always kept.
|
|
429
|
+
|
|
430
|
+
| Variable | Default | Effect |
|
|
431
|
+
| ------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------- |
|
|
432
|
+
| `GITNEXUS_KEEP_LOCAL_VALUE_SYMBOLS` | unset | Set to `1`/`true` to keep inert block-local value symbols instead of pruning them. |
|
|
433
|
+
|
|
434
|
+
Programmatic callers can pass `keepLocalValueSymbols: true` in `PipelineOptions` instead of setting the env var.
|
|
435
|
+
|
|
426
436
|
## Privacy
|
|
427
437
|
|
|
428
438
|
- All processing happens locally on your machine
|
package/dist/cli/ai-context.js
CHANGED
|
@@ -131,18 +131,18 @@ This project is indexed by GitNexus as **${projectName}**${noStats ? '' : ` (${s
|
|
|
131
131
|
|
|
132
132
|
## Always Do
|
|
133
133
|
|
|
134
|
-
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run \`
|
|
135
|
-
- **MUST run \`
|
|
134
|
+
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run \`impact({target: "symbolName", direction: "upstream"})\` and report the blast radius (direct callers, affected processes, risk level) to the user.
|
|
135
|
+
- **MUST run \`detect_changes()\` before committing** to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch: \`detect_changes({scope: "compare", base_ref: ${JSON.stringify(markdownSafeBranch(defaultBranch))}})\`.
|
|
136
136
|
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
|
|
137
|
-
- When exploring unfamiliar code, use \`
|
|
138
|
-
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use \`
|
|
137
|
+
- When exploring unfamiliar code, use \`query({query: "concept"})\` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
|
|
138
|
+
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use \`context({name: "symbolName"})\`.
|
|
139
139
|
|
|
140
140
|
## Never Do
|
|
141
141
|
|
|
142
|
-
- NEVER edit a function, class, or method without first running \`
|
|
142
|
+
- NEVER edit a function, class, or method without first running \`impact\` on it.
|
|
143
143
|
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
|
|
144
|
-
- NEVER rename symbols with find-and-replace — use \`
|
|
145
|
-
- NEVER commit changes without running \`
|
|
144
|
+
- NEVER rename symbols with find-and-replace — use \`rename\` which understands the call graph.
|
|
145
|
+
- NEVER commit changes without running \`detect_changes()\` to check affected scope.
|
|
146
146
|
|
|
147
147
|
## Resources
|
|
148
148
|
|
package/dist/cli/skill-gen.js
CHANGED
|
@@ -482,8 +482,8 @@ const renderSkillMarkdown = (community, projectName, members, files, entryPoints
|
|
|
482
482
|
: community.label;
|
|
483
483
|
lines.push('## How to Explore');
|
|
484
484
|
lines.push('');
|
|
485
|
-
lines.push(`1. \`
|
|
486
|
-
lines.push(`2. \`
|
|
485
|
+
lines.push(`1. \`context({name: "${firstEntry}"})\` \u2014 see callers and callees`);
|
|
486
|
+
lines.push(`2. \`query({query: "${community.label.toLowerCase()}"})\` \u2014 find related execution flows`);
|
|
487
487
|
lines.push('3. Read key files listed above for implementation details');
|
|
488
488
|
lines.push('');
|
|
489
489
|
return lines.join('\n');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { KnowledgeGraph } from '../graph/types.js';
|
|
2
|
+
export interface LocalSymbolPruneStats {
|
|
3
|
+
candidateNodes: number;
|
|
4
|
+
prunedNodes: number;
|
|
5
|
+
keptWithSemanticEdges: number;
|
|
6
|
+
skippedByEnv: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const shouldKeepLocalValueSymbols: () => boolean;
|
|
9
|
+
export declare const pruneLocalValueSymbols: (graph: KnowledgeGraph, options?: {
|
|
10
|
+
keepLocalValueSymbols?: boolean;
|
|
11
|
+
}) => LocalSymbolPruneStats;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { parseTruthyEnv } from './utils/env.js';
|
|
2
|
+
const LOCAL_VALUE_LABELS = new Set(['Const', 'Variable', 'Static']);
|
|
3
|
+
const KEEP_LOCAL_VALUE_SYMBOLS_ENV = 'GITNEXUS_KEEP_LOCAL_VALUE_SYMBOLS';
|
|
4
|
+
export const shouldKeepLocalValueSymbols = () => parseTruthyEnv(process.env[KEEP_LOCAL_VALUE_SYMBOLS_ENV]);
|
|
5
|
+
const emptyStats = (skippedByEnv) => ({
|
|
6
|
+
candidateNodes: 0,
|
|
7
|
+
prunedNodes: 0,
|
|
8
|
+
keptWithSemanticEdges: 0,
|
|
9
|
+
skippedByEnv,
|
|
10
|
+
});
|
|
11
|
+
const isLocalValueCandidate = (node) => {
|
|
12
|
+
if (!LOCAL_VALUE_LABELS.has(node.label))
|
|
13
|
+
return false;
|
|
14
|
+
return node.properties.scope === 'block';
|
|
15
|
+
};
|
|
16
|
+
// True when `rel` is the structural `File -> DEFINES -> candidate` edge. Callers
|
|
17
|
+
// guard on the candidate already being the edge target, so only the source label
|
|
18
|
+
// needs checking here.
|
|
19
|
+
const isFileDefinesEdge = (graph, rel) => {
|
|
20
|
+
if (rel.type !== 'DEFINES')
|
|
21
|
+
return false;
|
|
22
|
+
return graph.getNode(rel.sourceId)?.label === 'File';
|
|
23
|
+
};
|
|
24
|
+
export const pruneLocalValueSymbols = (graph, options = {}) => {
|
|
25
|
+
if (options.keepLocalValueSymbols ?? shouldKeepLocalValueSymbols()) {
|
|
26
|
+
return emptyStats(true);
|
|
27
|
+
}
|
|
28
|
+
const candidateIds = new Set();
|
|
29
|
+
for (const node of graph.iterNodes()) {
|
|
30
|
+
if (isLocalValueCandidate(node))
|
|
31
|
+
candidateIds.add(node.id);
|
|
32
|
+
}
|
|
33
|
+
if (candidateIds.size === 0)
|
|
34
|
+
return emptyStats(false);
|
|
35
|
+
const candidatesWithSemanticEdges = new Set();
|
|
36
|
+
for (const rel of graph.iterRelationships()) {
|
|
37
|
+
// Any outgoing edge from a candidate is a semantic edge: the only structural
|
|
38
|
+
// edge a block-local value symbol carries is the incoming File -> DEFINES, on
|
|
39
|
+
// which the candidate is the target, never the source.
|
|
40
|
+
if (candidateIds.has(rel.sourceId)) {
|
|
41
|
+
candidatesWithSemanticEdges.add(rel.sourceId);
|
|
42
|
+
}
|
|
43
|
+
// An incoming edge is semantic unless it is the structural File -> DEFINES.
|
|
44
|
+
if (candidateIds.has(rel.targetId)) {
|
|
45
|
+
if (!isFileDefinesEdge(graph, rel)) {
|
|
46
|
+
candidatesWithSemanticEdges.add(rel.targetId);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
let prunedNodes = 0;
|
|
51
|
+
for (const candidateId of candidateIds) {
|
|
52
|
+
if (candidatesWithSemanticEdges.has(candidateId))
|
|
53
|
+
continue;
|
|
54
|
+
if (graph.removeNode(candidateId))
|
|
55
|
+
prunedNodes++;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
candidateNodes: candidateIds.size,
|
|
59
|
+
prunedNodes,
|
|
60
|
+
keptWithSemanticEdges: candidatesWithSemanticEdges.size,
|
|
61
|
+
skippedByEnv: false,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Detects code communities via Leiden algorithm and creates
|
|
5
5
|
* Community nodes + MEMBER_OF edges.
|
|
6
6
|
*
|
|
7
|
-
* @deps mro
|
|
7
|
+
* @deps mro, pruneLocalSymbols
|
|
8
8
|
* @reads graph (all nodes and relationships)
|
|
9
9
|
* @writes graph (Community nodes, MEMBER_OF edges)
|
|
10
10
|
*/
|
|
@@ -14,7 +14,10 @@ import { isDev } from '../utils/env.js';
|
|
|
14
14
|
import { logger } from '../../logger.js';
|
|
15
15
|
export const communitiesPhase = {
|
|
16
16
|
name: 'communities',
|
|
17
|
-
|
|
17
|
+
// `pruneLocalSymbols` is declared explicitly (not just transitively via `mro`)
|
|
18
|
+
// so community detection always reads the trimmed graph even if a future option
|
|
19
|
+
// drops `mro` from the phase list.
|
|
20
|
+
deps: ['mro', 'pruneLocalSymbols', 'structure'],
|
|
18
21
|
async execute(ctx, deps) {
|
|
19
22
|
const { totalFiles } = getPhaseOutput(deps, 'structure');
|
|
20
23
|
ctx.onProgress({
|
|
@@ -14,6 +14,7 @@ export { toolsPhase, type ToolsOutput, type ToolDef } from './tools.js';
|
|
|
14
14
|
export { ormPhase, type ORMOutput } from './orm.js';
|
|
15
15
|
export { crossFilePhase, type CrossFileOutput } from './cross-file.js';
|
|
16
16
|
export { scopeResolutionPhase, type ScopeResolutionOutput, } from '../scope-resolution/pipeline/phase.js';
|
|
17
|
+
export { pruneLocalSymbolsPhase, type PruneLocalSymbolsOutput } from './prune-local-symbols.js';
|
|
17
18
|
export { mroPhase, type MROOutput } from './mro.js';
|
|
18
19
|
export { communitiesPhase, type CommunitiesOutput } from './communities.js';
|
|
19
20
|
export { processesPhase, type ProcessesOutput } from './processes.js';
|
|
@@ -15,6 +15,7 @@ export { toolsPhase } from './tools.js';
|
|
|
15
15
|
export { ormPhase } from './orm.js';
|
|
16
16
|
export { crossFilePhase } from './cross-file.js';
|
|
17
17
|
export { scopeResolutionPhase, } from '../scope-resolution/pipeline/phase.js';
|
|
18
|
+
export { pruneLocalSymbolsPhase } from './prune-local-symbols.js';
|
|
18
19
|
export { mroPhase } from './mro.js';
|
|
19
20
|
export { communitiesPhase } from './communities.js';
|
|
20
21
|
export { processesPhase } from './processes.js';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Computes Method Resolution Order (MRO) and creates METHOD_OVERRIDES
|
|
5
5
|
* and METHOD_IMPLEMENTS edges.
|
|
6
6
|
*
|
|
7
|
-
* @deps crossFile, scopeResolution
|
|
7
|
+
* @deps crossFile, scopeResolution, pruneLocalSymbols
|
|
8
8
|
* @reads graph (all nodes and relationships)
|
|
9
9
|
* @writes graph (METHOD_OVERRIDES, METHOD_IMPLEMENTS edges)
|
|
10
10
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Computes Method Resolution Order (MRO) and creates METHOD_OVERRIDES
|
|
5
5
|
* and METHOD_IMPLEMENTS edges.
|
|
6
6
|
*
|
|
7
|
-
* @deps crossFile, scopeResolution
|
|
7
|
+
* @deps crossFile, scopeResolution, pruneLocalSymbols
|
|
8
8
|
* @reads graph (all nodes and relationships)
|
|
9
9
|
* @writes graph (METHOD_OVERRIDES, METHOD_IMPLEMENTS edges)
|
|
10
10
|
*/
|
|
@@ -14,7 +14,7 @@ import { isDev } from '../utils/env.js';
|
|
|
14
14
|
import { logger } from '../../logger.js';
|
|
15
15
|
export const mroPhase = {
|
|
16
16
|
name: 'mro',
|
|
17
|
-
deps: ['crossFile', 'scopeResolution', 'structure'],
|
|
17
|
+
deps: ['crossFile', 'scopeResolution', 'pruneLocalSymbols', 'structure'],
|
|
18
18
|
async execute(ctx, deps) {
|
|
19
19
|
const { totalFiles } = getPhaseOutput(deps, 'structure');
|
|
20
20
|
ctx.onProgress({
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Detects execution flows (processes) and creates Process nodes +
|
|
5
5
|
* STEP_IN_PROCESS edges. Also links Route/Tool nodes to processes.
|
|
6
6
|
*
|
|
7
|
-
* @deps communities, routes, tools
|
|
7
|
+
* @deps communities, routes, tools, pruneLocalSymbols
|
|
8
8
|
* @reads graph (all nodes and relationships), communityResult, routeRegistry, toolDefs
|
|
9
9
|
* @writes graph (Process nodes, STEP_IN_PROCESS edges, ENTRY_POINT_OF edges)
|
|
10
10
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Detects execution flows (processes) and creates Process nodes +
|
|
5
5
|
* STEP_IN_PROCESS edges. Also links Route/Tool nodes to processes.
|
|
6
6
|
*
|
|
7
|
-
* @deps communities, routes, tools
|
|
7
|
+
* @deps communities, routes, tools, pruneLocalSymbols
|
|
8
8
|
* @reads graph (all nodes and relationships), communityResult, routeRegistry, toolDefs
|
|
9
9
|
* @writes graph (Process nodes, STEP_IN_PROCESS edges, ENTRY_POINT_OF edges)
|
|
10
10
|
*/
|
|
@@ -16,8 +16,10 @@ import { logger } from '../../logger.js';
|
|
|
16
16
|
export const processesPhase = {
|
|
17
17
|
name: 'processes',
|
|
18
18
|
// `structure` supplies `totalFiles` (progress counter) without the spurious
|
|
19
|
-
// structural data dependency on `parse`.
|
|
20
|
-
|
|
19
|
+
// structural data dependency on `parse`. `pruneLocalSymbols` is declared
|
|
20
|
+
// explicitly so process extraction always reads the trimmed graph even if a
|
|
21
|
+
// future option drops the intervening `mro`/`communities` phases.
|
|
22
|
+
deps: ['communities', 'routes', 'tools', 'pruneLocalSymbols', 'structure'],
|
|
21
23
|
async execute(ctx, deps) {
|
|
22
24
|
const { totalFiles } = getPhaseOutput(deps, 'structure');
|
|
23
25
|
const { communityResult } = getPhaseOutput(deps, 'communities');
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: pruneLocalSymbols
|
|
3
|
+
*
|
|
4
|
+
* Drops inert function/block-local value symbols after scope resolution has
|
|
5
|
+
* already used them for binding and call resolution.
|
|
6
|
+
*
|
|
7
|
+
* @deps scopeResolution
|
|
8
|
+
* @reads graph (nodes and relationships)
|
|
9
|
+
* @writes graph (removes unreferenced local Const/Variable/Static nodes)
|
|
10
|
+
*/
|
|
11
|
+
import type { PipelinePhase } from './types.js';
|
|
12
|
+
import { type LocalSymbolPruneStats } from '../local-symbol-pruner.js';
|
|
13
|
+
export type PruneLocalSymbolsOutput = LocalSymbolPruneStats;
|
|
14
|
+
export declare const pruneLocalSymbolsPhase: PipelinePhase<PruneLocalSymbolsOutput>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: pruneLocalSymbols
|
|
3
|
+
*
|
|
4
|
+
* Drops inert function/block-local value symbols after scope resolution has
|
|
5
|
+
* already used them for binding and call resolution.
|
|
6
|
+
*
|
|
7
|
+
* @deps scopeResolution
|
|
8
|
+
* @reads graph (nodes and relationships)
|
|
9
|
+
* @writes graph (removes unreferenced local Const/Variable/Static nodes)
|
|
10
|
+
*/
|
|
11
|
+
import { pruneLocalValueSymbols } from '../local-symbol-pruner.js';
|
|
12
|
+
import { isDev } from '../utils/env.js';
|
|
13
|
+
import { logger } from '../../logger.js';
|
|
14
|
+
export const pruneLocalSymbolsPhase = {
|
|
15
|
+
name: 'pruneLocalSymbols',
|
|
16
|
+
deps: ['scopeResolution'],
|
|
17
|
+
async execute(ctx) {
|
|
18
|
+
const stats = pruneLocalValueSymbols(ctx.graph, {
|
|
19
|
+
keepLocalValueSymbols: ctx.options?.keepLocalValueSymbols,
|
|
20
|
+
});
|
|
21
|
+
if (isDev && !stats.skippedByEnv && stats.prunedNodes > 0) {
|
|
22
|
+
logger.info(`Pruned ${stats.prunedNodes}/${stats.candidateNodes} inert local value symbols`);
|
|
23
|
+
}
|
|
24
|
+
return stats;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
import { type PipelineProgress } from '../../_shared/index.js';
|
|
18
18
|
import { PipelineResult } from '../../types/pipeline.js';
|
|
19
19
|
export interface PipelineOptions {
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Skip MRO, community detection, and process extraction for faster test runs.
|
|
22
|
+
* The `pruneLocalSymbols` phase still runs — it is graph construction (it cleans
|
|
23
|
+
* up inert local symbols), not graph analysis — so set `keepLocalValueSymbols`
|
|
24
|
+
* to retain those nodes under `skipGraphPhases`.
|
|
25
|
+
*/
|
|
21
26
|
skipGraphPhases?: boolean;
|
|
22
27
|
/**
|
|
23
28
|
* Request parsing with the worker pool disabled. The sequential parser was
|
|
@@ -90,5 +95,13 @@ export interface PipelineOptions {
|
|
|
90
95
|
* without leaking `process.env` state across invocations.
|
|
91
96
|
*/
|
|
92
97
|
chunkByteBudget?: number;
|
|
98
|
+
/**
|
|
99
|
+
* Keep inert block-local value symbols (Const/Variable/Static) that the
|
|
100
|
+
* `pruneLocalSymbols` phase would otherwise drop. Mirrors the
|
|
101
|
+
* `GITNEXUS_KEEP_LOCAL_VALUE_SYMBOLS` env var, but threaded per-call so
|
|
102
|
+
* long-running hosts (eval-server, MCP daemon) can opt out without leaking
|
|
103
|
+
* `process.env` state across invocations. When undefined, the env var decides.
|
|
104
|
+
*/
|
|
105
|
+
keepLocalValueSymbols?: boolean;
|
|
93
106
|
}
|
|
94
107
|
export declare const runPipelineFromRepo: (repoPath: string, onProgress: (progress: PipelineProgress) => void, options?: PipelineOptions) => Promise<PipelineResult>;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* See ARCHITECTURE.md for the full phase dependency diagram.
|
|
16
16
|
*/
|
|
17
17
|
import { createKnowledgeGraph } from '../graph/graph.js';
|
|
18
|
-
import { runPipeline, getPhaseOutput, scanPhase, structurePhase, markdownPhase, cobolPhase, parsePhase, routesPhase, toolsPhase, ormPhase, crossFilePhase, scopeResolutionPhase, mroPhase, communitiesPhase, processesPhase, } from './pipeline-phases/index.js';
|
|
18
|
+
import { runPipeline, getPhaseOutput, scanPhase, structurePhase, markdownPhase, cobolPhase, parsePhase, routesPhase, toolsPhase, ormPhase, crossFilePhase, scopeResolutionPhase, pruneLocalSymbolsPhase, mroPhase, communitiesPhase, processesPhase, } from './pipeline-phases/index.js';
|
|
19
19
|
// ── Phase registry ─────────────────────────────────────────────────────────
|
|
20
20
|
/**
|
|
21
21
|
* All pipeline phases with their dependency relationships.
|
|
@@ -23,7 +23,8 @@ import { runPipeline, getPhaseOutput, scanPhase, structurePhase, markdownPhase,
|
|
|
23
23
|
* Phase dependency graph:
|
|
24
24
|
*
|
|
25
25
|
* scan → structure → [markdown, cobol] → parse → [routes, tools, orm]
|
|
26
|
-
* → crossFile →
|
|
26
|
+
* → crossFile → scopeResolution → pruneLocalSymbols
|
|
27
|
+
* → mro → communities → processes
|
|
27
28
|
*
|
|
28
29
|
* To add a new phase: create a file in pipeline-phases/, export the phase
|
|
29
30
|
* object, and add it to the appropriate position in this array.
|
|
@@ -40,6 +41,7 @@ function buildPhaseList(options) {
|
|
|
40
41
|
ormPhase,
|
|
41
42
|
crossFilePhase,
|
|
42
43
|
scopeResolutionPhase,
|
|
44
|
+
pruneLocalSymbolsPhase,
|
|
43
45
|
];
|
|
44
46
|
if (!options?.skipGraphPhases) {
|
|
45
47
|
phases.push(mroPhase, communitiesPhase, processesPhase);
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
// gitnexus/src/core/ingestion/variable-extractors/generic.ts
|
|
2
|
+
/**
|
|
3
|
+
* Type-declaration node types whose body can be a bare `block`. tree-sitter-python
|
|
4
|
+
* models a class body as a `block` node — the same node type used for function and
|
|
5
|
+
* control-flow bodies — so a class attribute would otherwise look block-scoped. Most
|
|
6
|
+
* other grammars give class bodies dedicated node types (`class_body`,
|
|
7
|
+
* `declaration_list`, `body_statement`), which are not in the block-scope list below,
|
|
8
|
+
* so this guard is a no-op for them but keeps the rule language-agnostic.
|
|
9
|
+
*/
|
|
10
|
+
const CLASS_LIKE_CONTAINERS = new Set([
|
|
11
|
+
'class_definition',
|
|
12
|
+
'class_declaration',
|
|
13
|
+
'class_specifier',
|
|
14
|
+
'struct_item',
|
|
15
|
+
'impl_item',
|
|
16
|
+
'trait_item',
|
|
17
|
+
'interface_declaration',
|
|
18
|
+
'enum_declaration',
|
|
19
|
+
'object_declaration',
|
|
20
|
+
]);
|
|
2
21
|
/**
|
|
3
22
|
* Create a VariableExtractor from a declarative config.
|
|
4
23
|
*/
|
|
@@ -26,7 +45,7 @@ export function createVariableExtractor(config) {
|
|
|
26
45
|
t === 'compilation_unit') {
|
|
27
46
|
return 'module';
|
|
28
47
|
}
|
|
29
|
-
// Function/method
|
|
48
|
+
// Function/method boundaries indicate block scope
|
|
30
49
|
if (t === 'function_declaration' ||
|
|
31
50
|
t === 'function_definition' ||
|
|
32
51
|
t === 'function_item' ||
|
|
@@ -35,11 +54,17 @@ export function createVariableExtractor(config) {
|
|
|
35
54
|
t === 'arrow_function' ||
|
|
36
55
|
t === 'function_expression' ||
|
|
37
56
|
t === 'lambda' ||
|
|
38
|
-
t === 'block' ||
|
|
39
57
|
t === 'function_body' ||
|
|
40
58
|
t === 'compound_statement') {
|
|
41
59
|
return 'block';
|
|
42
60
|
}
|
|
61
|
+
// A bare `block` is block scope UNLESS it is a class body. A class member
|
|
62
|
+
// (e.g. Python `class C: MAX = 100`) is not an inert function-local — keep
|
|
63
|
+
// walking so it resolves to its true enclosing scope ('module' for a
|
|
64
|
+
// top-level class) instead of being misclassified and pruned.
|
|
65
|
+
if (t === 'block' && !(current.parent && CLASS_LIKE_CONTAINERS.has(current.parent.type))) {
|
|
66
|
+
return 'block';
|
|
67
|
+
}
|
|
43
68
|
current = current.parent;
|
|
44
69
|
}
|
|
45
70
|
return 'file';
|
|
@@ -74,9 +74,16 @@ interface RepoHandle {
|
|
|
74
74
|
*/
|
|
75
75
|
export declare function resolveWorktreeCwd(repoPath: string, launchCwd: string): string;
|
|
76
76
|
/**
|
|
77
|
-
* Length of the
|
|
77
|
+
* Length of the path-derived suffix appended to a colliding repo id.
|
|
78
78
|
* Exported so tests can pin the suffix shape without re-deriving the
|
|
79
|
-
* literal; see `
|
|
79
|
+
* literal; see `assignRepoId()` and the hashed-id resolution tier (#1658).
|
|
80
|
+
*
|
|
81
|
+
* Note: base64url is an *encoding*, not a hash — it preserves byte order, so
|
|
82
|
+
* two paths that share a long common prefix (sibling clones under one parent)
|
|
83
|
+
* collapse to the same sliced suffix. `assignRepoId()` keeps the legacy
|
|
84
|
+
* base64url suffix only for the first colliding duplicate (id compatibility)
|
|
85
|
+
* and falls back to a content hash of the resolved path on a real collision
|
|
86
|
+
* (#2054).
|
|
80
87
|
*/
|
|
81
88
|
export declare const REPO_ID_HASH_LENGTH = 6;
|
|
82
89
|
export declare class LocalBackend {
|
|
@@ -118,10 +125,25 @@ export declare class LocalBackend {
|
|
|
118
125
|
*/
|
|
119
126
|
private refreshRepos;
|
|
120
127
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
128
|
+
* Assign a collision-free in-memory id for a registered repo.
|
|
129
|
+
*
|
|
130
|
+
* - Unique name → the bare lowercased name.
|
|
131
|
+
* - Duplicate name → a path-derived suffix. The *first* colliding clone keeps
|
|
132
|
+
* the legacy `base64url(path)` suffix so ids generated before #2054 still
|
|
133
|
+
* resolve (the #1658 hashed-id tier). base64url is an encoding, not a hash:
|
|
134
|
+
* it preserves byte order, so sibling clones under one parent (e.g.
|
|
135
|
+
* `.../REPO_2` and `.../REPO_3`) yield identical leading characters and thus
|
|
136
|
+
* the same sliced suffix. Any further collision therefore falls back to a
|
|
137
|
+
* content hash of the *resolved* path (order-insensitive), extended
|
|
138
|
+
* deterministically until unique.
|
|
139
|
+
*
|
|
140
|
+
* `assigned` maps every id handed out in this refresh to its resolved path,
|
|
141
|
+
* so a candidate is "free" when it is unused or already owned by this exact
|
|
142
|
+
* path. This method records its own assignment into `assigned` before
|
|
143
|
+
* returning, so the map-update is the function's invariant, not a caller
|
|
144
|
+
* obligation. A returned id never overwrites a different path's handle (#2054).
|
|
123
145
|
*/
|
|
124
|
-
private
|
|
146
|
+
private assignRepoId;
|
|
125
147
|
/**
|
|
126
148
|
* Resolve which repo to use.
|
|
127
149
|
* - If repoParam is given, match by name or path
|
|
@@ -148,6 +170,16 @@ export declare class LocalBackend {
|
|
|
148
170
|
*/
|
|
149
171
|
private pickRepoHandleForCwd;
|
|
150
172
|
private handleToRegistryEntry;
|
|
173
|
+
/**
|
|
174
|
+
* Ensure the LadybugDB pool is open for the *resolved* repo.
|
|
175
|
+
*
|
|
176
|
+
* Takes the `RepoHandle` the caller resolved — NOT a bare id — and keys the
|
|
177
|
+
* pool (and the init/staleness/reinit maps) by the immutable `lbugPath`. Two
|
|
178
|
+
* things matter for multi-clone correctness: (1) the handle is the one the
|
|
179
|
+
* caller resolved, so a concurrent `refreshRepos` can't substitute a different
|
|
180
|
+
* clone; (2) the pool key is the database path, so distinct clones never share
|
|
181
|
+
* a pool entry even when their name-derived id transiently collides (#2067).
|
|
182
|
+
*/
|
|
151
183
|
private ensureInitialized;
|
|
152
184
|
/**
|
|
153
185
|
* Get context for a specific repo (or the single repo if only one).
|