gitnexus 1.6.3-rc.9 → 1.6.3
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 +21 -5
- package/dist/_shared/graph/types.d.ts +16 -0
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +4 -2
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +2 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/def-index.js +2 -2
- package/dist/_shared/scope-resolution/def-index.js.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
- package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
- package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/parsed-file.js +54 -0
- package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
- package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
- package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/position-index.js +2 -2
- package/dist/_shared/scope-resolution/position-index.js.map +1 -1
- package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
- package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/reference-site.js +24 -0
- package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.js +5 -0
- package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/lookup-core.js +21 -5
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js +3 -2
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
- package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +11 -0
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.js +35 -4
- package/dist/cli/analyze.d.ts +27 -0
- package/dist/cli/analyze.js +31 -1
- package/dist/cli/clean.js +19 -1
- package/dist/cli/group.js +73 -0
- package/dist/cli/index-repo.js +8 -1
- package/dist/cli/index.js +26 -1
- package/dist/cli/list.js +11 -1
- package/dist/cli/remove.d.ts +30 -0
- package/dist/cli/remove.js +99 -0
- package/dist/cli/setup.js +185 -57
- package/dist/cli/tool.d.ts +5 -0
- package/dist/cli/tool.js +42 -0
- package/dist/config/ignore-service.d.ts +9 -0
- package/dist/config/ignore-service.js +80 -13
- package/dist/core/embedding-mode.d.ts +30 -0
- package/dist/core/embedding-mode.js +30 -0
- package/dist/core/embeddings/ast-utils.js +22 -22
- package/dist/core/embeddings/chunker.js +30 -25
- package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
- package/dist/core/embeddings/embedding-pipeline.js +15 -6
- package/dist/core/embeddings/text-generator.d.ts +1 -1
- package/dist/core/embeddings/text-generator.js +33 -24
- package/dist/core/embeddings/types.d.ts +43 -1
- package/dist/core/embeddings/types.js +101 -29
- package/dist/core/git-staleness.d.ts +18 -0
- package/dist/core/git-staleness.js +108 -0
- package/dist/core/graph/graph.js +115 -20
- package/dist/core/graph/types.d.ts +12 -1
- package/dist/core/group/config-parser.d.ts +4 -0
- package/dist/core/group/config-parser.js +18 -1
- package/dist/core/group/cross-impact.d.ts +41 -0
- package/dist/core/group/cross-impact.js +441 -0
- package/dist/core/group/extractors/http-patterns/php.js +126 -18
- package/dist/core/group/group-path-utils.d.ts +17 -0
- package/dist/core/group/group-path-utils.js +40 -0
- package/dist/core/group/resolve-at-member.d.ts +10 -0
- package/dist/core/group/resolve-at-member.js +31 -0
- package/dist/core/group/service.d.ts +9 -0
- package/dist/core/group/service.js +259 -25
- package/dist/core/group/types.d.ts +30 -0
- package/dist/core/ingestion/ast-cache.d.ts +16 -1
- package/dist/core/ingestion/ast-cache.js +14 -2
- package/dist/core/ingestion/call-processor.js +9 -0
- package/dist/core/ingestion/emit-references.d.ts +88 -0
- package/dist/core/ingestion/emit-references.js +229 -0
- package/dist/core/ingestion/filesystem-walker.js +6 -4
- package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
- package/dist/core/ingestion/finalize-orchestrator.js +139 -0
- package/dist/core/ingestion/framework-detection.js +6 -2
- package/dist/core/ingestion/import-processor.js +4 -0
- package/dist/core/ingestion/import-resolvers/python.js +9 -6
- package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
- package/dist/core/ingestion/import-target-adapter.js +95 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -33
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
- package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
- package/dist/core/ingestion/languages/csharp/arity.js +37 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/captures.js +249 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
- package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
- package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
- package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
- package/dist/core/ingestion/languages/csharp/index.js +82 -0
- package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
- package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
- package/dist/core/ingestion/languages/csharp/query.js +515 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
- package/dist/core/ingestion/languages/csharp.js +14 -0
- package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
- package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
- package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
- package/dist/core/ingestion/languages/python/arity.js +38 -0
- package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
- package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
- package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/python/captures.js +106 -0
- package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
- package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
- package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
- package/dist/core/ingestion/languages/python/import-target.js +99 -0
- package/dist/core/ingestion/languages/python/index.d.ts +80 -0
- package/dist/core/ingestion/languages/python/index.js +80 -0
- package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/python/interpret.js +191 -0
- package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
- package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
- package/dist/core/ingestion/languages/python/query.d.ts +9 -0
- package/dist/core/ingestion/languages/python/query.js +267 -0
- package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
- package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
- package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
- package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
- package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
- package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
- package/dist/core/ingestion/languages/python.js +14 -0
- package/dist/core/ingestion/model/method-registry.d.ts +9 -0
- package/dist/core/ingestion/model/method-registry.js +4 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
- package/dist/core/ingestion/model/semantic-model.js +55 -0
- package/dist/core/ingestion/mro-processor.js +38 -22
- package/dist/core/ingestion/parsing-processor.d.ts +18 -1
- package/dist/core/ingestion/parsing-processor.js +45 -11
- 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/parse-impl.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
- package/dist/core/ingestion/pipeline.js +2 -1
- package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
- package/dist/core/ingestion/registry-primary-flag.js +111 -0
- package/dist/core/ingestion/resolve-references.d.ts +63 -0
- package/dist/core/ingestion/resolve-references.js +175 -0
- package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
- package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
- package/dist/core/ingestion/scope-extractor.d.ts +86 -0
- package/dist/core/ingestion/scope-extractor.js +758 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
- package/dist/core/ingestion/shadow-harness.d.ts +113 -0
- package/dist/core/ingestion/shadow-harness.js +148 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
- package/dist/core/ingestion/utils/ast-helpers.js +70 -0
- package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
- package/dist/core/ingestion/utils/max-file-size.js +52 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
- package/dist/core/ingestion/workers/parse-worker.js +57 -21
- package/dist/core/lbug/lbug-adapter.d.ts +22 -2
- package/dist/core/lbug/lbug-adapter.js +58 -14
- package/dist/core/lbug/pool-adapter.d.ts +17 -0
- package/dist/core/lbug/pool-adapter.js +24 -14
- package/dist/core/run-analyze.d.ts +32 -0
- package/dist/core/run-analyze.js +74 -19
- package/dist/core/search/bm25-index.d.ts +18 -0
- package/dist/core/search/bm25-index.js +125 -12
- package/dist/core/tree-sitter/parser-loader.js +6 -1
- package/dist/mcp/local/local-backend.d.ts +67 -3
- package/dist/mcp/local/local-backend.js +296 -34
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +100 -17
- package/dist/mcp/tools.d.ts +4 -1
- package/dist/mcp/tools.js +75 -54
- package/dist/server/api.js +6 -2
- package/dist/storage/git.d.ts +49 -0
- package/dist/storage/git.js +111 -0
- package/dist/storage/repo-manager.d.ts +246 -1
- package/dist/storage/repo-manager.js +391 -9
- package/package.json +7 -6
- package/scripts/bench-scope-resolution.ts +134 -0
- package/scripts/ci-list-migrated-languages.ts +24 -0
- package/skills/gitnexus-cli.md +1 -0
package/dist/mcp/resources.js
CHANGED
|
@@ -65,36 +65,113 @@ export function getResourceTemplates() {
|
|
|
65
65
|
description: 'Step-by-step execution trace',
|
|
66
66
|
mimeType: 'text/yaml',
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
uriTemplate: 'gitnexus://group/{name}/contracts',
|
|
70
|
+
name: 'Group Contract Registry',
|
|
71
|
+
description: 'Cross-repo contract registry for a repository group. Optional query: type, repo, unmatchedOnly (true|false).',
|
|
72
|
+
mimeType: 'text/yaml',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
uriTemplate: 'gitnexus://group/{name}/status',
|
|
76
|
+
name: 'Group Index Status',
|
|
77
|
+
description: 'Per-repo index and contract-registry staleness for a repository group',
|
|
78
|
+
mimeType: 'text/yaml',
|
|
79
|
+
},
|
|
68
80
|
];
|
|
69
81
|
}
|
|
82
|
+
function parseUnmatchedOnlyParam(raw) {
|
|
83
|
+
if (raw === null)
|
|
84
|
+
return undefined;
|
|
85
|
+
const v = raw.trim().toLowerCase();
|
|
86
|
+
if (v === 'true' || v === '1')
|
|
87
|
+
return true;
|
|
88
|
+
if (v === 'false' || v === '0')
|
|
89
|
+
return false;
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
70
92
|
/**
|
|
71
|
-
* Parse a resource URI
|
|
93
|
+
* Parse a GitNexus resource URI (repos, setup, per-repo, or per-group templates).
|
|
94
|
+
* Used by `readResource` and tests (round-trip / dispatch coverage).
|
|
72
95
|
*/
|
|
73
|
-
function
|
|
96
|
+
export function parseResourceUri(uri) {
|
|
74
97
|
if (uri === 'gitnexus://repos')
|
|
75
|
-
return {
|
|
98
|
+
return { kind: 'repos' };
|
|
76
99
|
if (uri === 'gitnexus://setup')
|
|
77
|
-
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
return { kind: 'setup' };
|
|
101
|
+
let u;
|
|
102
|
+
try {
|
|
103
|
+
u = new URL(uri);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
107
|
+
}
|
|
108
|
+
if (u.protocol !== 'gitnexus:') {
|
|
109
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
110
|
+
}
|
|
111
|
+
if (u.hostname === 'group') {
|
|
112
|
+
const segments = u.pathname
|
|
113
|
+
.replace(/^\/+|\/+$/g, '')
|
|
114
|
+
.split('/')
|
|
115
|
+
.filter(Boolean);
|
|
116
|
+
if (segments.length < 2) {
|
|
117
|
+
throw new Error(`Invalid group resource URI (expected gitnexus://group/{name}/contracts or .../status): ${uri}`);
|
|
118
|
+
}
|
|
119
|
+
const tail = segments[segments.length - 1];
|
|
120
|
+
if (tail !== 'contracts' && tail !== 'status') {
|
|
121
|
+
throw new Error(`Unknown group resource path in URI: ${uri}`);
|
|
122
|
+
}
|
|
123
|
+
const groupName = segments
|
|
124
|
+
.slice(0, -1)
|
|
125
|
+
.map((s) => decodeURIComponent(s))
|
|
126
|
+
.join('/');
|
|
127
|
+
if (!groupName) {
|
|
128
|
+
throw new Error(`Invalid group resource URI (empty group name): ${uri}`);
|
|
129
|
+
}
|
|
130
|
+
if (tail === 'status') {
|
|
131
|
+
return { kind: 'group', groupName, resourceType: 'status' };
|
|
132
|
+
}
|
|
133
|
+
const contractsFilter = {};
|
|
134
|
+
const type = u.searchParams.get('type');
|
|
135
|
+
if (type && type.trim())
|
|
136
|
+
contractsFilter.type = type.trim();
|
|
137
|
+
const repo = u.searchParams.get('repo');
|
|
138
|
+
if (repo && repo.trim())
|
|
139
|
+
contractsFilter.repo = repo.trim();
|
|
140
|
+
if (u.searchParams.has('unmatchedOnly')) {
|
|
141
|
+
const coerced = parseUnmatchedOnlyParam(u.searchParams.get('unmatchedOnly'));
|
|
142
|
+
if (coerced !== undefined)
|
|
143
|
+
contractsFilter.unmatchedOnly = coerced;
|
|
144
|
+
}
|
|
145
|
+
return { kind: 'group', groupName, resourceType: 'contracts', contractsFilter };
|
|
146
|
+
}
|
|
147
|
+
if (u.hostname === 'repo') {
|
|
148
|
+
const segments = u.pathname
|
|
149
|
+
.replace(/^\/+|\/+$/g, '')
|
|
150
|
+
.split('/')
|
|
151
|
+
.filter(Boolean);
|
|
152
|
+
if (segments.length < 2) {
|
|
153
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
154
|
+
}
|
|
155
|
+
const repoName = decodeURIComponent(segments[0]);
|
|
156
|
+
const restEncoded = segments.slice(1);
|
|
157
|
+
const rest = restEncoded.map((s) => decodeURIComponent(s)).join('/');
|
|
83
158
|
if (rest.startsWith('cluster/')) {
|
|
84
159
|
return {
|
|
160
|
+
kind: 'repo',
|
|
85
161
|
repoName,
|
|
86
162
|
resourceType: 'cluster',
|
|
87
|
-
param:
|
|
163
|
+
param: rest.replace(/^cluster\//, ''),
|
|
88
164
|
};
|
|
89
165
|
}
|
|
90
166
|
if (rest.startsWith('process/')) {
|
|
91
167
|
return {
|
|
168
|
+
kind: 'repo',
|
|
92
169
|
repoName,
|
|
93
170
|
resourceType: 'process',
|
|
94
|
-
param:
|
|
171
|
+
param: rest.replace(/^process\//, ''),
|
|
95
172
|
};
|
|
96
173
|
}
|
|
97
|
-
return { repoName, resourceType: rest };
|
|
174
|
+
return { kind: 'repo', repoName, resourceType: rest };
|
|
98
175
|
}
|
|
99
176
|
throw new Error(`Unknown resource URI: ${uri}`);
|
|
100
177
|
}
|
|
@@ -102,15 +179,19 @@ function parseUri(uri) {
|
|
|
102
179
|
* Read a resource and return its content
|
|
103
180
|
*/
|
|
104
181
|
export async function readResource(uri, backend) {
|
|
105
|
-
const parsed =
|
|
106
|
-
|
|
107
|
-
if (parsed.resourceType === 'repos') {
|
|
182
|
+
const parsed = parseResourceUri(uri);
|
|
183
|
+
if (parsed.kind === 'repos') {
|
|
108
184
|
return getReposResource(backend);
|
|
109
185
|
}
|
|
110
|
-
|
|
111
|
-
if (parsed.resourceType === 'setup') {
|
|
186
|
+
if (parsed.kind === 'setup') {
|
|
112
187
|
return getSetupResource(backend);
|
|
113
188
|
}
|
|
189
|
+
if (parsed.kind === 'group') {
|
|
190
|
+
if (parsed.resourceType === 'contracts') {
|
|
191
|
+
return backend.readGroupContractsResource(parsed.groupName, parsed.contractsFilter);
|
|
192
|
+
}
|
|
193
|
+
return backend.readGroupStatusResource(parsed.groupName);
|
|
194
|
+
}
|
|
114
195
|
const repoName = parsed.repoName;
|
|
115
196
|
switch (parsed.resourceType) {
|
|
116
197
|
case 'context':
|
|
@@ -202,6 +283,8 @@ async function getContextResource(backend, repoName) {
|
|
|
202
283
|
lines.push(` - gitnexus://repo/${context.projectName}/processes: All execution flows`);
|
|
203
284
|
lines.push(` - gitnexus://repo/${context.projectName}/cluster/{name}: Module details`);
|
|
204
285
|
lines.push(` - gitnexus://repo/${context.projectName}/process/{name}: Process trace`);
|
|
286
|
+
lines.push(' - gitnexus://group/{name}/contracts: Group contract registry (optional ?type=&repo=&unmatchedOnly=)');
|
|
287
|
+
lines.push(' - gitnexus://group/{name}/status: Group index / contract staleness');
|
|
205
288
|
return lines.join('\n');
|
|
206
289
|
}
|
|
207
290
|
/**
|
package/dist/mcp/tools.d.ts
CHANGED
|
@@ -12,11 +12,14 @@ export interface ToolDefinition {
|
|
|
12
12
|
properties: Record<string, {
|
|
13
13
|
type: string;
|
|
14
14
|
description?: string;
|
|
15
|
-
default?:
|
|
15
|
+
default?: unknown;
|
|
16
16
|
items?: {
|
|
17
17
|
type: string;
|
|
18
18
|
};
|
|
19
19
|
enum?: string[];
|
|
20
|
+
minimum?: number;
|
|
21
|
+
maximum?: number;
|
|
22
|
+
minLength?: number;
|
|
20
23
|
}>;
|
|
21
24
|
required: string[];
|
|
22
25
|
};
|
package/dist/mcp/tools.js
CHANGED
|
@@ -35,7 +35,11 @@ Returns results grouped by process (execution flow):
|
|
|
35
35
|
- process_symbols: all symbols in those flows with file locations and module (functional area)
|
|
36
36
|
- definitions: standalone types/interfaces not in any process
|
|
37
37
|
|
|
38
|
-
Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank Fusion
|
|
38
|
+
Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank Fusion.
|
|
39
|
+
|
|
40
|
+
GROUP MODE: set "repo" to "@<groupName>" to search all member repos in that group (merged via RRF), or "@<groupName>/<groupRepoPath>" to run against a single member (same path keys as in group.yaml). If you use "@<groupName>" only, the member repo defaults to the lexicographically first key in group.yaml "repos". Prefer resources for contracts/status (see migration from legacy group_* tools).
|
|
41
|
+
|
|
42
|
+
SERVICE: optional monorepo path prefix (POSIX-style, case-sensitive segments). When "repo" starts with "@", only processes whose symbols fall under that prefix are included. For a normal indexed repo name (no leading @), this field is currently ignored by the server.`,
|
|
39
43
|
inputSchema: {
|
|
40
44
|
type: 'object',
|
|
41
45
|
properties: {
|
|
@@ -48,11 +52,19 @@ Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank
|
|
|
48
52
|
type: 'string',
|
|
49
53
|
description: 'What you want to find (e.g., "existing auth validation logic"). Helps ranking.',
|
|
50
54
|
},
|
|
51
|
-
limit: {
|
|
55
|
+
limit: {
|
|
56
|
+
type: 'number',
|
|
57
|
+
description: 'Max processes to return (default: 5)',
|
|
58
|
+
default: 5,
|
|
59
|
+
minimum: 1,
|
|
60
|
+
maximum: 100,
|
|
61
|
+
},
|
|
52
62
|
max_symbols: {
|
|
53
63
|
type: 'number',
|
|
54
64
|
description: 'Max symbols per process (default: 10)',
|
|
55
65
|
default: 10,
|
|
66
|
+
minimum: 1,
|
|
67
|
+
maximum: 200,
|
|
56
68
|
},
|
|
57
69
|
include_content: {
|
|
58
70
|
type: 'boolean',
|
|
@@ -61,7 +73,12 @@ Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank
|
|
|
61
73
|
},
|
|
62
74
|
repo: {
|
|
63
75
|
type: 'string',
|
|
64
|
-
description: '
|
|
76
|
+
description: 'Indexed repository name or path, or group mode "@<groupName>" / "@<groupName>/<memberPath>" (member path keys from group.yaml). Omit when only one indexed repo exists.',
|
|
77
|
+
},
|
|
78
|
+
service: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
minLength: 1,
|
|
81
|
+
description: 'Optional monorepo service root (relative path, "/" separators). In group mode (@repo), prefix-matches symbol file paths; ignored for a normal repo name. Empty string is rejected server-side.',
|
|
65
82
|
},
|
|
66
83
|
},
|
|
67
84
|
required: ['query'],
|
|
@@ -135,7 +152,11 @@ AFTER THIS: Use impact() if planning changes, or READ gitnexus://repo/{name}/pro
|
|
|
135
152
|
|
|
136
153
|
Handles disambiguation: if multiple symbols share the same name, returns ranked candidates (each with a relevance score) for you to pick from. Use uid for zero-ambiguity lookup, or narrow the search with file_path and/or kind hints.
|
|
137
154
|
|
|
138
|
-
NOTE: ACCESSES edges (field read/write tracking) are included in context results with reason 'read' or 'write'. CALLS edges resolve through field access chains and method-call chains (e.g., user.address.getCity().save() produces CALLS edges at each step)
|
|
155
|
+
NOTE: ACCESSES edges (field read/write tracking) are included in context results with reason 'read' or 'write'. CALLS edges resolve through field access chains and method-call chains (e.g., user.address.getCity().save() produces CALLS edges at each step).
|
|
156
|
+
|
|
157
|
+
GROUP MODE: set "repo" to "@<groupName>" to run context in each member repo (aggregated list), or "@<groupName>/<groupRepoPath>" for one member. If you use "@<groupName>" only, the member defaults to the lexicographically first key in group.yaml "repos".
|
|
158
|
+
|
|
159
|
+
SERVICE: optional monorepo path prefix (case-sensitive path segments). When "repo" starts with "@", prefix-matches resolved symbol file paths; when a hit is outside the prefix, that member returns an empty payload for the symbol. Ignored for a normal indexed repo name.`,
|
|
139
160
|
inputSchema: {
|
|
140
161
|
type: 'object',
|
|
141
162
|
properties: {
|
|
@@ -156,7 +177,12 @@ NOTE: ACCESSES edges (field read/write tracking) are included in context results
|
|
|
156
177
|
},
|
|
157
178
|
repo: {
|
|
158
179
|
type: 'string',
|
|
159
|
-
description: '
|
|
180
|
+
description: 'Indexed repository name or path, or group mode "@<groupName>" / "@<groupName>/<memberPath>". Omit if only one repo is indexed.',
|
|
181
|
+
},
|
|
182
|
+
service: {
|
|
183
|
+
type: 'string',
|
|
184
|
+
minLength: 1,
|
|
185
|
+
description: 'Optional monorepo service root (relative path). Applies in group mode (@repo) only; ignored for a normal repo name. Empty string is rejected server-side.',
|
|
160
186
|
},
|
|
161
187
|
},
|
|
162
188
|
required: [],
|
|
@@ -251,7 +277,11 @@ TIP: Default traversal uses CALLS/IMPORTS/EXTENDS/IMPLEMENTS. For class members,
|
|
|
251
277
|
Handles disambiguation: when multiple symbols share the target name, returns ranked candidates (each with a relevance score) instead of silently picking one. Use target_uid for zero-ambiguity lookup, or narrow with file_path and/or kind hints.
|
|
252
278
|
|
|
253
279
|
EdgeType: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, METHOD_OVERRIDES, METHOD_IMPLEMENTS, ACCESSES
|
|
254
|
-
Confidence: 1.0 = certain, <0.8 = fuzzy match
|
|
280
|
+
Confidence: 1.0 = certain, <0.8 = fuzzy match
|
|
281
|
+
|
|
282
|
+
GROUP MODE: set "repo" to "@<groupName>" for cross-repo impact anchored at the default member (lexicographically first key in group.yaml "repos"), or "@<groupName>/<groupRepoPath>" to choose the member (same path keys as in group.yaml). Phase-1 walk runs in that member; cross-boundary fan-out uses the group bridge.
|
|
283
|
+
|
|
284
|
+
SERVICE: optional monorepo path prefix (case-sensitive path segments). When "repo" starts with "@", scopes the local impact walk and cross-repo symbol paths to files under that prefix; ignored for a normal indexed repo name.`,
|
|
255
285
|
inputSchema: {
|
|
256
286
|
type: 'object',
|
|
257
287
|
properties: {
|
|
@@ -274,8 +304,17 @@ Confidence: 1.0 = certain, <0.8 = fuzzy match`,
|
|
|
274
304
|
},
|
|
275
305
|
maxDepth: {
|
|
276
306
|
type: 'number',
|
|
277
|
-
description: 'Max relationship depth (default: 3)',
|
|
307
|
+
description: 'Max relationship depth (default: 3, server clamps to 1–32)',
|
|
278
308
|
default: 3,
|
|
309
|
+
minimum: 1,
|
|
310
|
+
maximum: 32,
|
|
311
|
+
},
|
|
312
|
+
crossDepth: {
|
|
313
|
+
type: 'number',
|
|
314
|
+
description: 'Cross-repository hop depth via contract bridge (default: 1; values above server maximum are clamped)',
|
|
315
|
+
default: 1,
|
|
316
|
+
minimum: 1,
|
|
317
|
+
maximum: 32,
|
|
279
318
|
},
|
|
280
319
|
relationTypes: {
|
|
281
320
|
type: 'array',
|
|
@@ -283,10 +322,37 @@ Confidence: 1.0 = certain, <0.8 = fuzzy match`,
|
|
|
283
322
|
description: 'Filter: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, METHOD_OVERRIDES, METHOD_IMPLEMENTS, ACCESSES (default: usage-based, ACCESSES excluded by default)',
|
|
284
323
|
},
|
|
285
324
|
includeTests: { type: 'boolean', description: 'Include test files (default: false)' },
|
|
286
|
-
minConfidence: {
|
|
325
|
+
minConfidence: {
|
|
326
|
+
type: 'number',
|
|
327
|
+
description: 'Minimum edge confidence 0–1 (default: 0 when omitted; server clamps to 0–1)',
|
|
328
|
+
default: 0,
|
|
329
|
+
minimum: 0,
|
|
330
|
+
maximum: 1,
|
|
331
|
+
},
|
|
287
332
|
repo: {
|
|
288
333
|
type: 'string',
|
|
289
|
-
description: '
|
|
334
|
+
description: 'Indexed repository name or path, or group mode "@<groupName>" / "@<groupName>/<memberPath>". Omit if only one repo is indexed.',
|
|
335
|
+
},
|
|
336
|
+
service: {
|
|
337
|
+
type: 'string',
|
|
338
|
+
minLength: 1,
|
|
339
|
+
description: 'Optional monorepo service root (relative path). Applies when "repo" is group mode (@…); ignored for a normal repo name. Empty string is rejected server-side.',
|
|
340
|
+
},
|
|
341
|
+
subgroup: {
|
|
342
|
+
type: 'string',
|
|
343
|
+
description: 'Optional group subgroup prefix (member repo paths) limiting which repos participate in cross fan-out.',
|
|
344
|
+
},
|
|
345
|
+
timeoutMs: {
|
|
346
|
+
type: 'number',
|
|
347
|
+
description: 'Wall-clock budget in milliseconds for the Phase-1 local impact leg (default 30000)',
|
|
348
|
+
minimum: 1,
|
|
349
|
+
maximum: 3600000,
|
|
350
|
+
},
|
|
351
|
+
timeout: {
|
|
352
|
+
type: 'number',
|
|
353
|
+
description: 'Alias of timeoutMs (milliseconds) when timeoutMs is omitted',
|
|
354
|
+
minimum: 1,
|
|
355
|
+
maximum: 3600000,
|
|
290
356
|
},
|
|
291
357
|
},
|
|
292
358
|
required: ['target', 'direction'],
|
|
@@ -404,49 +470,4 @@ WHEN TO USE: After changing group.yaml or re-indexing member repos.`,
|
|
|
404
470
|
required: ['name'],
|
|
405
471
|
},
|
|
406
472
|
},
|
|
407
|
-
{
|
|
408
|
-
name: 'group_contracts',
|
|
409
|
-
description: `Inspect contracts and cross-links from the group's contracts.json.
|
|
410
|
-
|
|
411
|
-
WHEN TO USE: Debug cross-repo links after group_sync.`,
|
|
412
|
-
inputSchema: {
|
|
413
|
-
type: 'object',
|
|
414
|
-
properties: {
|
|
415
|
-
name: { type: 'string', description: 'Group name' },
|
|
416
|
-
type: { type: 'string', description: 'Filter by contract type (http, topic, …)' },
|
|
417
|
-
repo: { type: 'string', description: 'Filter by group repo path (e.g. app/backend)' },
|
|
418
|
-
unmatchedOnly: { type: 'boolean', description: 'Only contracts with no cross-link' },
|
|
419
|
-
},
|
|
420
|
-
required: ['name'],
|
|
421
|
-
},
|
|
422
|
-
},
|
|
423
|
-
{
|
|
424
|
-
name: 'group_query',
|
|
425
|
-
description: `Run the query tool across all repos in a group and merge process results via reciprocal rank fusion.
|
|
426
|
-
|
|
427
|
-
WHEN TO USE: Semantic / hybrid search across a whole product group.`,
|
|
428
|
-
inputSchema: {
|
|
429
|
-
type: 'object',
|
|
430
|
-
properties: {
|
|
431
|
-
name: { type: 'string', description: 'Group name' },
|
|
432
|
-
query: { type: 'string', description: 'Search query' },
|
|
433
|
-
subgroup: { type: 'string', description: 'Limit to repo paths under this prefix' },
|
|
434
|
-
limit: { type: 'number', description: 'Max merged results (default 5)' },
|
|
435
|
-
},
|
|
436
|
-
required: ['name', 'query'],
|
|
437
|
-
},
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
name: 'group_status',
|
|
441
|
-
description: `Report index staleness (commit vs HEAD) and Contract Registry staleness (indexedAt) for each repo in a group.
|
|
442
|
-
|
|
443
|
-
WHEN TO USE: Before group_sync or when agents should refresh indexes.`,
|
|
444
|
-
inputSchema: {
|
|
445
|
-
type: 'object',
|
|
446
|
-
properties: {
|
|
447
|
-
name: { type: 'string', description: 'Group name' },
|
|
448
|
-
},
|
|
449
|
-
required: ['name'],
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
473
|
];
|
package/dist/server/api.js
CHANGED
|
@@ -1002,7 +1002,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1002
1002
|
// POST /api/analyze — start a new analysis job
|
|
1003
1003
|
app.post('/api/analyze', async (req, res) => {
|
|
1004
1004
|
try {
|
|
1005
|
-
const { url: repoUrl, path: repoLocalPath, force, embeddings } = req.body;
|
|
1005
|
+
const { url: repoUrl, path: repoLocalPath, force, embeddings, dropEmbeddings } = req.body;
|
|
1006
1006
|
// Input type validation
|
|
1007
1007
|
if (repoUrl !== undefined && typeof repoUrl !== 'string') {
|
|
1008
1008
|
res.status(400).json({ error: '"url" must be a string' });
|
|
@@ -1176,7 +1176,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1176
1176
|
child.send({
|
|
1177
1177
|
type: 'start',
|
|
1178
1178
|
repoPath: targetPath,
|
|
1179
|
-
options: {
|
|
1179
|
+
options: {
|
|
1180
|
+
force: !!force,
|
|
1181
|
+
embeddings: !!embeddings,
|
|
1182
|
+
dropEmbeddings: !!dropEmbeddings,
|
|
1183
|
+
},
|
|
1180
1184
|
});
|
|
1181
1185
|
};
|
|
1182
1186
|
forkWorker();
|
package/dist/storage/git.d.ts
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
export declare const isGitRepo: (repoPath: string) => boolean;
|
|
2
2
|
export declare const getCurrentCommit: (repoPath: string) => string;
|
|
3
|
+
/**
|
|
4
|
+
* Get a stable canonical identifier for the repo's `origin` remote, if any.
|
|
5
|
+
*
|
|
6
|
+
* Used to fingerprint two on-disk clones as the same logical repository
|
|
7
|
+
* (issue #XXX — silent graph drift across sibling clones). `path` alone
|
|
8
|
+
* is unreliable: worktrees, "clean clone for indexing" hygiene, and
|
|
9
|
+
* multi-agent workspaces routinely have the same repo at multiple
|
|
10
|
+
* absolute paths. The remote URL is the only on-disk signal that
|
|
11
|
+
* survives those conventions.
|
|
12
|
+
*
|
|
13
|
+
* Normalisation strategy:
|
|
14
|
+
* - Strip a trailing `.git` so `https://x/y` and `https://x/y.git` collapse.
|
|
15
|
+
* - Strip a trailing `/` for the same reason.
|
|
16
|
+
* - `git@github.com:foo/bar` and `https://github.com/foo/bar` are
|
|
17
|
+
* intentionally NOT collapsed — they are different remotes from
|
|
18
|
+
* git's perspective and we don't want to assert equivalence.
|
|
19
|
+
* - Lower-case the host portion so `GitHub.com` and `github.com`
|
|
20
|
+
* don't desync; preserves case in path because some hosts
|
|
21
|
+
* (Bitbucket Server) treat repo paths case-sensitively.
|
|
22
|
+
*
|
|
23
|
+
* Returns `undefined` when there is no origin remote, the directory
|
|
24
|
+
* isn't a git repo, or git itself isn't available.
|
|
25
|
+
*/
|
|
26
|
+
export declare const getRemoteUrl: (repoPath: string) => string | undefined;
|
|
3
27
|
/**
|
|
4
28
|
* Find the git repository root from any path inside the repo
|
|
5
29
|
*/
|
|
@@ -16,6 +40,31 @@ export declare const getGitRoot: (fromPath: string) => string | null;
|
|
|
16
40
|
* @returns `true` when `.git` is present, `false` otherwise.
|
|
17
41
|
*/
|
|
18
42
|
export declare const hasGitDir: (dirPath: string) => boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Read `remote.origin.url` from a git repository, or `null` if not a
|
|
45
|
+
* git repo, has no `origin` remote, or git is unavailable.
|
|
46
|
+
*
|
|
47
|
+
* Used by the registry-name inference path (#979) to recover a
|
|
48
|
+
* meaningful repo name when `path.basename(repoPath)` is generic
|
|
49
|
+
* (e.g. monorepo subprojects, git worktrees, Gas-Town-style
|
|
50
|
+
* `<rig>/refinery/rig/` layouts).
|
|
51
|
+
*/
|
|
52
|
+
export declare const getRemoteOriginUrl: (repoPath: string) => string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Parse a repository name out of a git remote URL. Handles the common
|
|
55
|
+
* SSH (`git@host:owner/repo.git`), HTTPS (`https://host/owner/repo.git`),
|
|
56
|
+
* `git://`, `ssh://`, and `file://` shapes. Returns `null` for empty /
|
|
57
|
+
* unparseable input.
|
|
58
|
+
*
|
|
59
|
+
* The heuristic: strip a trailing `.git` and trailing slashes, then
|
|
60
|
+
* take the segment after the last `/` or `:`.
|
|
61
|
+
*/
|
|
62
|
+
export declare const parseRepoNameFromUrl: (url: string | null | undefined) => string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Convenience wrapper: derive a registry-friendly name from the repo's
|
|
65
|
+
* `origin` remote, or `null` when it cannot be inferred.
|
|
66
|
+
*/
|
|
67
|
+
export declare const getInferredRepoName: (repoPath: string) => string | null;
|
|
19
68
|
export interface DiffHunk {
|
|
20
69
|
startLine: number;
|
|
21
70
|
endLine: number;
|
package/dist/storage/git.js
CHANGED
|
@@ -19,6 +19,65 @@ export const getCurrentCommit = (repoPath) => {
|
|
|
19
19
|
return '';
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* Get a stable canonical identifier for the repo's `origin` remote, if any.
|
|
24
|
+
*
|
|
25
|
+
* Used to fingerprint two on-disk clones as the same logical repository
|
|
26
|
+
* (issue #XXX — silent graph drift across sibling clones). `path` alone
|
|
27
|
+
* is unreliable: worktrees, "clean clone for indexing" hygiene, and
|
|
28
|
+
* multi-agent workspaces routinely have the same repo at multiple
|
|
29
|
+
* absolute paths. The remote URL is the only on-disk signal that
|
|
30
|
+
* survives those conventions.
|
|
31
|
+
*
|
|
32
|
+
* Normalisation strategy:
|
|
33
|
+
* - Strip a trailing `.git` so `https://x/y` and `https://x/y.git` collapse.
|
|
34
|
+
* - Strip a trailing `/` for the same reason.
|
|
35
|
+
* - `git@github.com:foo/bar` and `https://github.com/foo/bar` are
|
|
36
|
+
* intentionally NOT collapsed — they are different remotes from
|
|
37
|
+
* git's perspective and we don't want to assert equivalence.
|
|
38
|
+
* - Lower-case the host portion so `GitHub.com` and `github.com`
|
|
39
|
+
* don't desync; preserves case in path because some hosts
|
|
40
|
+
* (Bitbucket Server) treat repo paths case-sensitively.
|
|
41
|
+
*
|
|
42
|
+
* Returns `undefined` when there is no origin remote, the directory
|
|
43
|
+
* isn't a git repo, or git itself isn't available.
|
|
44
|
+
*/
|
|
45
|
+
export const getRemoteUrl = (repoPath) => {
|
|
46
|
+
let raw;
|
|
47
|
+
try {
|
|
48
|
+
raw = execSync('git config --get remote.origin.url', {
|
|
49
|
+
cwd: repoPath,
|
|
50
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
51
|
+
})
|
|
52
|
+
.toString()
|
|
53
|
+
.trim();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
if (!raw)
|
|
59
|
+
return undefined;
|
|
60
|
+
let normalised = raw.replace(/\/$/, '').replace(/\.git$/, '');
|
|
61
|
+
// Lower-case the host segment of `scheme://[user@]host[:port]/...`
|
|
62
|
+
// and the host segment of `git@host:owner/repo` SCP form.
|
|
63
|
+
// SSH user-segment regex deliberately accepts the common
|
|
64
|
+
// `git@`/`<alnum>-_@` cases. Less common usernames (e.g. with
|
|
65
|
+
// dots) fall through to the URL-form branch — they will simply
|
|
66
|
+
// not get host-case normalisation, which is acceptable: the raw
|
|
67
|
+
// `git config` output is still a valid fingerprint, just slightly
|
|
68
|
+
// less collapsible across host casings.
|
|
69
|
+
const sshMatch = normalised.match(/^(git@|[a-zA-Z0-9_-]+@)([^:/]+)(:.+)$/);
|
|
70
|
+
if (sshMatch) {
|
|
71
|
+
normalised = `${sshMatch[1]}${sshMatch[2].toLowerCase()}${sshMatch[3]}`;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const urlMatch = normalised.match(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/]+)(\/.*)?$/);
|
|
75
|
+
if (urlMatch) {
|
|
76
|
+
normalised = `${urlMatch[1]}${urlMatch[2].toLowerCase()}${urlMatch[3] ?? ''}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return normalised;
|
|
80
|
+
};
|
|
22
81
|
/**
|
|
23
82
|
* Find the git repository root from any path inside the repo
|
|
24
83
|
*/
|
|
@@ -52,6 +111,58 @@ export const hasGitDir = (dirPath) => {
|
|
|
52
111
|
return false;
|
|
53
112
|
}
|
|
54
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Read `remote.origin.url` from a git repository, or `null` if not a
|
|
116
|
+
* git repo, has no `origin` remote, or git is unavailable.
|
|
117
|
+
*
|
|
118
|
+
* Used by the registry-name inference path (#979) to recover a
|
|
119
|
+
* meaningful repo name when `path.basename(repoPath)` is generic
|
|
120
|
+
* (e.g. monorepo subprojects, git worktrees, Gas-Town-style
|
|
121
|
+
* `<rig>/refinery/rig/` layouts).
|
|
122
|
+
*/
|
|
123
|
+
export const getRemoteOriginUrl = (repoPath) => {
|
|
124
|
+
try {
|
|
125
|
+
const url = execSync('git config --get remote.origin.url', {
|
|
126
|
+
cwd: repoPath,
|
|
127
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
128
|
+
})
|
|
129
|
+
.toString()
|
|
130
|
+
.trim();
|
|
131
|
+
return url || null;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Parse a repository name out of a git remote URL. Handles the common
|
|
139
|
+
* SSH (`git@host:owner/repo.git`), HTTPS (`https://host/owner/repo.git`),
|
|
140
|
+
* `git://`, `ssh://`, and `file://` shapes. Returns `null` for empty /
|
|
141
|
+
* unparseable input.
|
|
142
|
+
*
|
|
143
|
+
* The heuristic: strip a trailing `.git` and trailing slashes, then
|
|
144
|
+
* take the segment after the last `/` or `:`.
|
|
145
|
+
*/
|
|
146
|
+
export const parseRepoNameFromUrl = (url) => {
|
|
147
|
+
if (!url)
|
|
148
|
+
return null;
|
|
149
|
+
const trimmed = url.trim();
|
|
150
|
+
if (!trimmed)
|
|
151
|
+
return null;
|
|
152
|
+
// Strip `.git` suffix (case-insensitive) and any trailing slashes.
|
|
153
|
+
const withoutSuffix = trimmed.replace(/\.git\/*$/i, '').replace(/\/+$/, '');
|
|
154
|
+
// Last path segment, splitting on either `/` or `:` (covers SSH form).
|
|
155
|
+
const m = withoutSuffix.match(/[/:]([^/:]+)$/);
|
|
156
|
+
const candidate = m ? m[1] : withoutSuffix;
|
|
157
|
+
return candidate || null;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Convenience wrapper: derive a registry-friendly name from the repo's
|
|
161
|
+
* `origin` remote, or `null` when it cannot be inferred.
|
|
162
|
+
*/
|
|
163
|
+
export const getInferredRepoName = (repoPath) => {
|
|
164
|
+
return parseRepoNameFromUrl(getRemoteOriginUrl(repoPath));
|
|
165
|
+
};
|
|
55
166
|
/**
|
|
56
167
|
* Parse unified diff output (with -U0) into per-file hunk ranges.
|
|
57
168
|
* Extracts the new-file line ranges from @@ hunk headers.
|