gitnexus 1.6.4-rc.60 → 1.6.4-rc.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/core/run-analyze.js +19 -3
  2. package/dist/server/api.js +44 -5
  3. package/dist/server/validation.d.ts +35 -0
  4. package/dist/server/validation.js +45 -0
  5. package/dist/storage/git.d.ts +50 -0
  6. package/dist/storage/git.js +77 -0
  7. package/dist/storage/repo-manager.js +20 -2
  8. package/package.json +2 -1
  9. package/web/assets/{agent-DSzTymIk.js → agent-C9r8n_cf.js} +1 -1
  10. package/web/assets/architecture-YZFGNWBL-S5CXDPWN-CL__bSw6.js +1 -0
  11. package/web/assets/{architectureDiagram-EMZXCZ2Q-UmClXAm6.js → architectureDiagram-EMZXCZ2Q-B7rdIFf-.js} +1 -1
  12. package/web/assets/{blockDiagram-IGV67L2C-Dq9GLiDP.js → blockDiagram-IGV67L2C--OWFEwfO.js} +1 -1
  13. package/web/assets/{c4Diagram-DFAF54RM-BAulQDfB.js → c4Diagram-DFAF54RM-7j_8iiE6.js} +1 -1
  14. package/web/assets/{chunk-3GS5O3IE-DRE1sM7w.js → chunk-3GS5O3IE-DRiJzb4K.js} +1 -1
  15. package/web/assets/{chunk-3YCYZ6SJ-BkrQ98l_.js → chunk-3YCYZ6SJ-DINjuUhe.js} +1 -1
  16. package/web/assets/{chunk-6NTNNK5N-gx0rYVsL.js → chunk-6NTNNK5N-mbpPKRom.js} +1 -1
  17. package/web/assets/{chunk-A34GCYZU-RZiTgPCZ.js → chunk-A34GCYZU-9BNw3Yti.js} +1 -1
  18. package/web/assets/{chunk-DJ7UZH7F-BabYEcUv.js → chunk-DJ7UZH7F-B_7r9FpO.js} +1 -1
  19. package/web/assets/{chunk-DKKBVRCY-TALC3MOe.js → chunk-DKKBVRCY-frEm8KEW.js} +2 -2
  20. package/web/assets/{chunk-DU5LTGQ6-DaoUQLBX.js → chunk-DU5LTGQ6-BfEgiOSb.js} +1 -1
  21. package/web/assets/{chunk-FXACKDTF-C7YCo_Ke.js → chunk-FXACKDTF-DDbh9iQA.js} +1 -1
  22. package/web/assets/{chunk-H3VCZNTA-ClUTKG4X.js → chunk-H3VCZNTA-S6JCDFX6.js} +1 -1
  23. package/web/assets/{chunk-HN6EAY2L-CIB-EAEf.js → chunk-HN6EAY2L-DsuA2uD1.js} +1 -1
  24. package/web/assets/{chunk-O5ABG6QK-pfHdaAl5.js → chunk-O5ABG6QK-L5KyjcYu.js} +1 -1
  25. package/web/assets/{chunk-PK6DOVAG-CP63Y-dO.js → chunk-PK6DOVAG-Cur_aKHW.js} +1 -1
  26. package/web/assets/{chunk-RNJOYNJ4-u0uFowaT.js → chunk-RNJOYNJ4-DcsVbdDR.js} +1 -1
  27. package/web/assets/{chunk-RWUO3TPN-XaxD6IN5.js → chunk-RWUO3TPN-DUDKXAYC.js} +1 -1
  28. package/web/assets/{chunk-TBF5ZNIQ-phr69mzb.js → chunk-TBF5ZNIQ-DEtcUvI1.js} +1 -1
  29. package/web/assets/{chunk-TYMNRAUI-COznwoNo.js → chunk-TYMNRAUI-0TEOcBv3.js} +1 -1
  30. package/web/assets/{chunk-W7ZLLLMY-ktiqXFzv.js → chunk-W7ZLLLMY-DevGErJ8.js} +1 -1
  31. package/web/assets/{chunk-WSB5WSVC-BaodRn5r.js → chunk-WSB5WSVC-CckGrZr6.js} +1 -1
  32. package/web/assets/{chunk-XGPFEOL4-DEWAJYtS.js → chunk-XGPFEOL4-C8lmtgql.js} +1 -1
  33. package/web/assets/classDiagram-PPOCWD7C-CTGiPkXU.js +1 -0
  34. package/web/assets/classDiagram-v2-23LJLIIU-Dzp9gwLx.js +1 -0
  35. package/web/assets/{cose-bilkent-PNC4W37J-CdXmWYUn.js → cose-bilkent-PNC4W37J-DHbxEBIZ.js} +1 -1
  36. package/web/assets/{dagre-E77IOHMT-85yFNa-U.js → dagre-E77IOHMT-BY6pnK09.js} +1 -1
  37. package/web/assets/{diagram-H7BISOXX-DmNNIGoc.js → diagram-H7BISOXX-bqy_URgj.js} +1 -1
  38. package/web/assets/{diagram-JC5VWROH-BoASIDAQ.js → diagram-JC5VWROH-OMAxOz8H.js} +1 -1
  39. package/web/assets/{diagram-LXUTUG65-D7_rWL46.js → diagram-LXUTUG65-DIFIO54C.js} +1 -1
  40. package/web/assets/{diagram-WEHSV5V5-C65Uxmnr.js → diagram-WEHSV5V5-DWsrP1hB.js} +1 -1
  41. package/web/assets/{erDiagram-GCSMX5X6-C6iGqdrz.js → erDiagram-GCSMX5X6-DIqN4pJM.js} +1 -1
  42. package/web/assets/{flowDiagram-OTCZ4VVT-Dwqe7LwQ.js → flowDiagram-OTCZ4VVT-BHlfKH3t.js} +1 -1
  43. package/web/assets/{ganttDiagram-MUNLMDZQ-BdPv-0c6.js → ganttDiagram-MUNLMDZQ-BJGbHagV.js} +1 -1
  44. package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-oeTgf84r.js +1 -0
  45. package/web/assets/{gitGraphDiagram-3HKGZ4G3-CuH1Gh8s.js → gitGraphDiagram-3HKGZ4G3-BlTN08lB.js} +1 -1
  46. package/web/assets/{index-DpMcvzVf.js → index-BcZj1uBK.js} +7 -7
  47. package/web/assets/info-OMHHGYJF-BF2H5H6G-DDl1dEae.js +1 -0
  48. package/web/assets/infoDiagram-MN7RKWGX-QsjEzLKD.js +2 -0
  49. package/web/assets/{ishikawaDiagram-YMYX4NHK-5ASIVfH_.js → ishikawaDiagram-YMYX4NHK-CZbsOXar.js} +1 -1
  50. package/web/assets/{journeyDiagram-SO5T7YLQ-BqHCwWBv.js → journeyDiagram-SO5T7YLQ-DReaD--j.js} +1 -1
  51. package/web/assets/{kanban-definition-LJHFXRCJ-Pf6kpbyo.js → kanban-definition-LJHFXRCJ-BflrNkXo.js} +1 -1
  52. package/web/assets/{mindmap-definition-2EUWGEK5-4cvOOu2O.js → mindmap-definition-2EUWGEK5-BhFh-jY5.js} +1 -1
  53. package/web/assets/packet-4T2RLAQJ-EV4IVRXR-Bzc2XTYv.js +1 -0
  54. package/web/assets/pie-ZZUOXDRM-N23DN5KN-BpY1H6Ka.js +1 -0
  55. package/web/assets/{pieDiagram-3IATQBI2-CZ7v2kzW.js → pieDiagram-3IATQBI2-BM0_qTtj.js} +1 -1
  56. package/web/assets/{quadrantDiagram-E256RVCF-XDUp3YFm.js → quadrantDiagram-E256RVCF-BPuExOtT.js} +1 -1
  57. package/web/assets/radar-PYXPWWZC-P6TP7ZYP-tdqhrT8k.js +1 -0
  58. package/web/assets/{requirementDiagram-M5DCFWZL-DAKpbdmn.js → requirementDiagram-M5DCFWZL-B-eP1h6R.js} +1 -1
  59. package/web/assets/{sankeyDiagram-L3NBLAOT-Cuh_Q_Wz.js → sankeyDiagram-L3NBLAOT-CJxcV3gi.js} +1 -1
  60. package/web/assets/{sequenceDiagram-ZOUHS735-nFqoCrOa.js → sequenceDiagram-ZOUHS735-UyDJSU5_.js} +1 -1
  61. package/web/assets/{stateDiagram-MLPALWAM-Drv05fh-.js → stateDiagram-MLPALWAM-CxB09HjX.js} +1 -1
  62. package/web/assets/stateDiagram-v2-B5LQ5ZB2-CoVPTpMR.js +1 -0
  63. package/web/assets/{timeline-definition-5SPVSISX-BLh-flCC.js → timeline-definition-5SPVSISX-DkQCsILJ.js} +1 -1
  64. package/web/assets/treeView-SZITEDCU-5DXDK3XO-DRdCAWYC.js +1 -0
  65. package/web/assets/treemap-W4RFUUIX-WYLRDWKO-Cwb3nItN.js +1 -0
  66. package/web/assets/{vennDiagram-IE5QUKF5-C5S0N3Qi.js → vennDiagram-IE5QUKF5-DyVkJExs.js} +1 -1
  67. package/web/assets/wardley-RL74JXVD-BCRCBASE-Dd87C4yq.js +1 -0
  68. package/web/assets/{wardleyDiagram-XU3VSMPF-BMGe2aBh.js → wardleyDiagram-XU3VSMPF-DBXK78Eu.js} +1 -1
  69. package/web/assets/{xychartDiagram-ZHJ5623Y-9g_anVQx.js → xychartDiagram-ZHJ5623Y-CZXbnIMV.js} +1 -1
  70. package/web/index.html +1 -1
  71. package/web/assets/architecture-YZFGNWBL-S5CXDPWN-D3kSwRPj.js +0 -1
  72. package/web/assets/classDiagram-PPOCWD7C-DJm9V5Z9.js +0 -1
  73. package/web/assets/classDiagram-v2-23LJLIIU-CONC2q7O.js +0 -1
  74. package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-CHLZv53W.js +0 -1
  75. package/web/assets/info-OMHHGYJF-BF2H5H6G-Yt27NUzo.js +0 -1
  76. package/web/assets/infoDiagram-MN7RKWGX-BDLC_czJ.js +0 -2
  77. package/web/assets/packet-4T2RLAQJ-EV4IVRXR-BGlzFMOr.js +0 -1
  78. package/web/assets/pie-ZZUOXDRM-N23DN5KN-Ui7Mc58L.js +0 -1
  79. package/web/assets/radar-PYXPWWZC-P6TP7ZYP-0jZkUeeE.js +0 -1
  80. package/web/assets/stateDiagram-v2-B5LQ5ZB2-DeIxIjc2.js +0 -1
  81. package/web/assets/treeView-SZITEDCU-5DXDK3XO-TMf1DsPD.js +0 -1
  82. package/web/assets/treemap-W4RFUUIX-WYLRDWKO-DTLiM44i.js +0 -1
  83. package/web/assets/wardley-RL74JXVD-BCRCBASE-BLQ7nRDD.js +0 -1
@@ -14,7 +14,7 @@ import { runPipelineFromRepo } from './ingestion/pipeline.js';
14
14
  import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, loadCachedEmbeddings, } from './lbug/lbug-adapter.js';
15
15
  import { createSearchFTSIndexes } from './search/fts-indexes.js';
16
16
  import { getStoragePaths, saveMeta, loadMeta, ensureGitNexusIgnored, registerRepo, cleanupOldKuzuFiles, } from '../storage/repo-manager.js';
17
- import { getCurrentCommit, getRemoteUrl, hasGitDir, getInferredRepoName } from '../storage/git.js';
17
+ import { getCurrentCommit, getRemoteUrl, hasGitDir, getInferredRepoName, resolveRepoIdentityRoot, } from '../storage/git.js';
18
18
  import { generateAIContextFiles } from '../cli/ai-context.js';
19
19
  import { EMBEDDING_TABLE_NAME } from './lbug/schema.js';
20
20
  import { STALE_HASH_SENTINEL } from './lbug/schema.js';
@@ -71,7 +71,12 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
71
71
  if (currentCommit !== '') {
72
72
  await ensureGitNexusIgnored(repoPath);
73
73
  return {
74
- repoName: options.registryName ?? getInferredRepoName(repoPath) ?? path.basename(repoPath),
74
+ // `resolveRepoIdentityRoot` collapses worktree roots to the
75
+ // canonical repo basename (#1259) but leaves arbitrary subdirs
76
+ // and `--skip-git` paths unchanged (#1232/#1233 intent preserved).
77
+ repoName: options.registryName ??
78
+ getInferredRepoName(repoPath) ??
79
+ path.basename(resolveRepoIdentityRoot(repoPath)),
75
80
  repoPath,
76
81
  stats: existingMeta.stats ?? {},
77
82
  alreadyUpToDate: true,
@@ -219,7 +224,18 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
219
224
  }
220
225
  }
221
226
  const { readServerMapping } = await import('./embeddings/server-mapping.js');
222
- const projectName = path.basename(repoPath);
227
+ // Mirror the registry's name-resolution chain so the server-mapping
228
+ // lookup key stays aligned with the final registry name (#1259):
229
+ // --name → remote-derived → canonical-root basename
230
+ // (preserved-alias is intentionally NOT consulted here — server
231
+ // mappings are addressed by the operationally-meaningful name the
232
+ // user configures, not by a sticky registry-only alias they may not
233
+ // know about. The previous canonical-only logic ignored both --name
234
+ // and remote-derived names, silently breaking server-mapping for
235
+ // anyone with a `--name` alias or remote-named repo.)
236
+ const projectName = options.registryName ??
237
+ getInferredRepoName(repoPath) ??
238
+ path.basename(resolveRepoIdentityRoot(repoPath));
223
239
  const serverName = await readServerMapping(projectName);
224
240
  const embeddingResult = await runEmbeddingPipeline(executeQuery, executeWithReusedStatement, (p) => {
225
241
  const scaled = 90 + Math.round((p.percent / 100) * 8);
@@ -25,7 +25,7 @@ import { mountMCPEndpoints } from './mcp-http.js';
25
25
  import { fork } from 'child_process';
26
26
  import { fileURLToPath, pathToFileURL } from 'url';
27
27
  import { JobManager } from './analyze-job.js';
28
- import { assertString, escapeRegExp, BadRequestError } from './validation.js';
28
+ import { assertString, escapeRegExp, BadRequestError, createRouteLimiter } from './validation.js';
29
29
  import { extractRepoName, getCloneDir, cloneOrPull } from './git-clone.js';
30
30
  const _require = createRequire(import.meta.url);
31
31
  const pkg = _require('../../package.json');
@@ -186,7 +186,19 @@ export const registerWebUI = (app, staticDir) => {
186
186
  // The regex excludes /api paths AND paths with file extensions (.js, .css, etc.)
187
187
  // so missing assets get real 404s instead of the SPA HTML.
188
188
  // Adding routes below this will be unreachable for non-API, non-asset paths.
189
- app.get(SPA_FALLBACK_REGEX, (_req, res) => {
189
+ // Rate-limited (CodeQL js/missing-rate-limiting): the SPA fallback
190
+ // serves a constant index.html, but the FS access from a route handler
191
+ // is enough to trip the analyzer. The limit is generous (300 rpm/IP =
192
+ // 5 req/s sustained) so that multi-tab browser navigation, prefetch,
193
+ // and service-worker revalidation do not produce 429s for legitimate
194
+ // SPA users. At this rate, real browser navigation is extremely
195
+ // unlikely to hit the limit in practice, so the cosmetic issue of
196
+ // JSON-on-429 to a browser is a low-likelihood path. Content
197
+ // negotiation on the 429 (returning the SPA shell to HTML clients
198
+ // instead of `{ error: '...' }`) would require swapping
199
+ // express-rate-limit's `message` for a `handler` function and is
200
+ // deferred to keep this PR focused on closing the CodeQL alert.
201
+ app.get(SPA_FALLBACK_REGEX, createRouteLimiter({ limit: 300 }), (_req, res) => {
190
202
  res.sendFile(path.join(staticDir, 'index.html'));
191
203
  });
192
204
  }
@@ -524,6 +536,26 @@ export const handleFileRequest = async (req, res, repoPath) => {
524
536
  export const createServer = async (port, host = '127.0.0.1') => {
525
537
  const app = express();
526
538
  app.disable('x-powered-by');
539
+ // Trust X-Forwarded-* headers only when the connection comes from the
540
+ // local loopback or RFC1918 private/link-local addresses — exactly the
541
+ // origins the CORS allowlist accepts. Without this, every request behind
542
+ // any reverse proxy / Docker bridge counts as the same `req.ip` and a
543
+ // single user can trip the per-IP rate limiter for everyone.
544
+ //
545
+ // SCOPE: this setting is process-wide. Every middleware and route in this
546
+ // Express app sees req.ip resolved from X-Forwarded-For when the upstream
547
+ // hop is in the trusted set above — not just the rate-limited routes.
548
+ // Future IP-based middleware (audit logging, IP-bound authz) inherits this
549
+ // behavior.
550
+ //
551
+ // CLOUD-DEPLOY CAVEAT: a public cloud LB (AWS ALB, Cloudflare, Fly.io
552
+ // edge, CGNAT 100.64/10) is NOT in the trusted set. In those topologies
553
+ // req.ip will collapse to the LB hop IP for every request and the per-IP
554
+ // rate limiter degrades to per-server. Add an explicit env-var override
555
+ // and document the cloud-deploy story before binding to a non-loopback
556
+ // host in those topologies (tracked as a follow-up; not blocking for the
557
+ // local-bound default).
558
+ app.set('trust proxy', 'loopback, linklocal, uniquelocal');
527
559
  // CORS: allow localhost, private/LAN networks, and the deployed site.
528
560
  // Non-browser requests (curl, server-to-server) have no origin and are allowed.
529
561
  // Disallowed origins get the response without Access-Control-Allow-Origin,
@@ -716,7 +748,10 @@ export const createServer = async (port, host = '127.0.0.1') => {
716
748
  }
717
749
  });
718
750
  // Delete a repo — removes index, clone dir (if any), and unregisters it
719
- app.delete('/api/repo', async (req, res) => {
751
+ // Rate-limited (CodeQL js/missing-rate-limiting): destructive operation
752
+ // doing fs.rm of clone + storage dirs. Default 60 rpm/IP is generous for
753
+ // delete; tighten if abuse is observed.
754
+ app.delete('/api/repo', createRouteLimiter(), async (req, res) => {
720
755
  try {
721
756
  const repoName = requestedRepo(req);
722
757
  if (!repoName) {
@@ -997,7 +1032,8 @@ export const createServer = async (port, host = '127.0.0.1') => {
997
1032
  }
998
1033
  });
999
1034
  // Read file — with path traversal guard
1000
- app.get('/api/file', async (req, res) => {
1035
+ // Rate-limited (CodeQL js/missing-rate-limiting): per-request fs.readFile.
1036
+ app.get('/api/file', createRouteLimiter(), async (req, res) => {
1001
1037
  const entry = await resolveRepo(requestedRepo(req));
1002
1038
  if (!entry) {
1003
1039
  res.status(404).json({ error: 'Repository not found' });
@@ -1007,7 +1043,10 @@ export const createServer = async (port, host = '127.0.0.1') => {
1007
1043
  });
1008
1044
  // Grep — regex search across file contents in the indexed repo
1009
1045
  // Uses filesystem-based search for memory efficiency (never loads all files into memory)
1010
- app.get('/api/grep', async (req, res) => {
1046
+ // Rate-limited (CodeQL js/missing-rate-limiting): scans every file in
1047
+ // the indexed repo per request — heaviest I/O endpoint. Same default 60
1048
+ // rpm/IP for now; consider tightening if real-world load shows abuse.
1049
+ app.get('/api/grep', createRouteLimiter(), async (req, res) => {
1011
1050
  try {
1012
1051
  const entry = await resolveRepo(requestedRepo(req));
1013
1052
  if (!entry) {
@@ -17,6 +17,7 @@
17
17
  * Helpers added in later units (U3 git-clone hardening, U4 rate-limiting) live
18
18
  * in this module too but are introduced with the dependency they require.
19
19
  */
20
+ import { type RateLimitRequestHandler } from 'express-rate-limit';
20
21
  /**
21
22
  * Thrown by validation helpers when user input is rejected.
22
23
  * Routes catch via existing try/catch and convert with err.status / err.message.
@@ -58,3 +59,37 @@ export declare function assertSafePath(rawPath: string, root: string): string;
58
59
  * and any future endpoint that constructs a regex from caller input.
59
60
  */
60
61
  export declare function escapeRegExp(input: string): string;
62
+ /**
63
+ * Project-specific subset of express-rate-limit options that callers may
64
+ * override. Intentionally narrow — `Partial<RateLimitOptions>` would let a
65
+ * caller pass `{ skip: () => true }` and silently disable limiting on a
66
+ * route. The two knobs below are sufficient for tests and any future
67
+ * legitimate per-route tuning.
68
+ */
69
+ export interface RouteLimiterOverrides {
70
+ windowMs?: number;
71
+ /** Canonical name in express-rate-limit v8+. `max` is the deprecated alias. */
72
+ limit?: number;
73
+ }
74
+ /**
75
+ * Build a per-route rate-limit middleware with project-uniform defaults.
76
+ *
77
+ * Each call returns a NEW limiter instance — independent counters per route,
78
+ * so /api/file traffic doesn't push /api/grep into 429.
79
+ *
80
+ * Defaults:
81
+ * - 60 requests per IP per minute
82
+ * - draft-7 RateLimit-* response headers (no legacy X-RateLimit-* headers)
83
+ * - 429 with a JSON body matching the project's `{ error: '...' }` shape
84
+ * - passOnStoreError: store failures let the request through rather than
85
+ * producing an HTML 500 from Express's default error handler
86
+ * - keyGenerator: req.ip with a socket.remoteAddress fallback so abruptly
87
+ * closed connections do not trigger ERR_ERL_UNDEFINED_IP_ADDRESS
88
+ * (which would 500 the request via Express's default error handler).
89
+ * Caller must wire `app.set('trust proxy', ...)` correctly — see
90
+ * createServer in api.ts.
91
+ *
92
+ * Tests pass `{ windowMs: 100, limit: 3 }` to keep limiter tests fast and
93
+ * deterministic.
94
+ */
95
+ export declare function createRouteLimiter(opts?: RouteLimiterOverrides): RateLimitRequestHandler;
@@ -18,6 +18,7 @@
18
18
  * in this module too but are introduced with the dependency they require.
19
19
  */
20
20
  import path from 'node:path';
21
+ import rateLimit from 'express-rate-limit';
21
22
  /**
22
23
  * Thrown by validation helpers when user input is rejected.
23
24
  * Routes catch via existing try/catch and convert with err.status / err.message.
@@ -89,3 +90,47 @@ export function assertSafePath(rawPath, root) {
89
90
  export function escapeRegExp(input) {
90
91
  return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
91
92
  }
93
+ /**
94
+ * Default rate-limit policy for FS-touching API routes (CodeQL
95
+ * js/missing-rate-limiting). Tuned for the local-bound HTTP server's expected
96
+ * traffic — interactive web UI use stays well under the limit; abusive loops
97
+ * trip 429.
98
+ *
99
+ * Module-internal — not exported. Tests assert the observable behavior
100
+ * (61st request returns 429), not the literal value, so callers don't grow
101
+ * a coupling on this number.
102
+ */
103
+ const DEFAULT_RATE_LIMIT_RPM = 60;
104
+ /**
105
+ * Build a per-route rate-limit middleware with project-uniform defaults.
106
+ *
107
+ * Each call returns a NEW limiter instance — independent counters per route,
108
+ * so /api/file traffic doesn't push /api/grep into 429.
109
+ *
110
+ * Defaults:
111
+ * - 60 requests per IP per minute
112
+ * - draft-7 RateLimit-* response headers (no legacy X-RateLimit-* headers)
113
+ * - 429 with a JSON body matching the project's `{ error: '...' }` shape
114
+ * - passOnStoreError: store failures let the request through rather than
115
+ * producing an HTML 500 from Express's default error handler
116
+ * - keyGenerator: req.ip with a socket.remoteAddress fallback so abruptly
117
+ * closed connections do not trigger ERR_ERL_UNDEFINED_IP_ADDRESS
118
+ * (which would 500 the request via Express's default error handler).
119
+ * Caller must wire `app.set('trust proxy', ...)` correctly — see
120
+ * createServer in api.ts.
121
+ *
122
+ * Tests pass `{ windowMs: 100, limit: 3 }` to keep limiter tests fast and
123
+ * deterministic.
124
+ */
125
+ export function createRouteLimiter(opts) {
126
+ return rateLimit({
127
+ windowMs: 60 * 1000,
128
+ limit: DEFAULT_RATE_LIMIT_RPM,
129
+ standardHeaders: 'draft-7',
130
+ legacyHeaders: false,
131
+ passOnStoreError: true,
132
+ keyGenerator: (req) => req.ip ?? req.socket?.remoteAddress ?? 'unknown',
133
+ message: { error: 'Too many requests, please try again later.' },
134
+ ...opts,
135
+ });
136
+ }
@@ -28,6 +28,56 @@ export declare const getRemoteUrl: (repoPath: string) => string | undefined;
28
28
  * Find the git repository root from any path inside the repo
29
29
  */
30
30
  export declare const getGitRoot: (fromPath: string) => string | null;
31
+ /**
32
+ * Get the *canonical* repository root, dereferencing git worktrees.
33
+ *
34
+ * Unlike `getGitRoot` (which uses `git rev-parse --show-toplevel` and
35
+ * returns the WORKTREE's root when called inside a linked worktree),
36
+ * this uses `git rev-parse --git-common-dir` — the shared `.git`
37
+ * directory, identical for the main checkout and every linked
38
+ * worktree — and returns its parent.
39
+ *
40
+ * Why it matters (#1259): when `gitnexus analyze` runs inside a
41
+ * worktree (e.g. `/repo/wt-feature/`), deriving `repoName` from
42
+ * `path.basename(getGitRoot(cwd))` registers the project under the
43
+ * worktree's directory slug (`wt-feature`) instead of the canonical
44
+ * repo's basename (`repo`). Each worktree then re-registers as a
45
+ * "different" project, AGENTS.md is rewritten with the wrong MCP URI,
46
+ * and Claude-Code-style worktree workflows silently accumulate
47
+ * duplicate registry entries.
48
+ *
49
+ * Returns `null` when the path is not inside a git repository or
50
+ * `git` is not available, so callers can chain safely:
51
+ * `getCanonicalRepoRoot(p) ?? getGitRoot(p) ?? p`.
52
+ *
53
+ * `--path-format=absolute` is required because `--git-common-dir`
54
+ * returns a path *relative to cwd* by default (e.g. `../.git` when
55
+ * called from a worktree), which would resolve to the wrong absolute
56
+ * path if the caller later resolved it from a different directory.
57
+ */
58
+ export declare const getCanonicalRepoRoot: (fromPath: string) => string | null;
59
+ /**
60
+ * Resolve `fromPath` to the directory whose basename should drive the
61
+ * registry name (#1259) — the *identity root*. Three outcomes:
62
+ *
63
+ * 1. `fromPath` IS the canonical checkout root → returns it unchanged.
64
+ * 2. `fromPath` is a linked-worktree root (has its own `.git` entry, but
65
+ * `git rev-parse --git-common-dir` points at a different `.git`) →
66
+ * returns the canonical repo root.
67
+ * 3. `fromPath` is anything else — an arbitrary subdir under a git repo,
68
+ * a non-git folder, a `--skip-git` subdir of an unrelated parent
69
+ * checkout — returns `fromPath` unchanged.
70
+ *
71
+ * Why not just use `getCanonicalRepoRoot` directly? Because `git rev-parse
72
+ * --git-common-dir` resolves the same canonical root for ANY path inside
73
+ * a git repo, including unrelated subdirs. Using it for registry-name
74
+ * derivation would silently re-key a `--skip-git` subdir analyze under
75
+ * the parent git's basename, defeating the user's `--skip-git` intent
76
+ * (regressing the #1232/#1233 fix). The "is this path a tree root"
77
+ * gate confines the canonical-root collapse to exactly the cases where
78
+ * #1259 matters: main checkouts and linked worktrees.
79
+ */
80
+ export declare const resolveRepoIdentityRoot: (fromPath: string) => string;
31
81
  /**
32
82
  * Find a git root by checking only `.git` entries on the ancestor chain.
33
83
  *
@@ -91,6 +91,83 @@ export const getGitRoot = (fromPath) => {
91
91
  return null;
92
92
  }
93
93
  };
94
+ /**
95
+ * Get the *canonical* repository root, dereferencing git worktrees.
96
+ *
97
+ * Unlike `getGitRoot` (which uses `git rev-parse --show-toplevel` and
98
+ * returns the WORKTREE's root when called inside a linked worktree),
99
+ * this uses `git rev-parse --git-common-dir` — the shared `.git`
100
+ * directory, identical for the main checkout and every linked
101
+ * worktree — and returns its parent.
102
+ *
103
+ * Why it matters (#1259): when `gitnexus analyze` runs inside a
104
+ * worktree (e.g. `/repo/wt-feature/`), deriving `repoName` from
105
+ * `path.basename(getGitRoot(cwd))` registers the project under the
106
+ * worktree's directory slug (`wt-feature`) instead of the canonical
107
+ * repo's basename (`repo`). Each worktree then re-registers as a
108
+ * "different" project, AGENTS.md is rewritten with the wrong MCP URI,
109
+ * and Claude-Code-style worktree workflows silently accumulate
110
+ * duplicate registry entries.
111
+ *
112
+ * Returns `null` when the path is not inside a git repository or
113
+ * `git` is not available, so callers can chain safely:
114
+ * `getCanonicalRepoRoot(p) ?? getGitRoot(p) ?? p`.
115
+ *
116
+ * `--path-format=absolute` is required because `--git-common-dir`
117
+ * returns a path *relative to cwd* by default (e.g. `../.git` when
118
+ * called from a worktree), which would resolve to the wrong absolute
119
+ * path if the caller later resolved it from a different directory.
120
+ */
121
+ export const getCanonicalRepoRoot = (fromPath) => {
122
+ try {
123
+ const commonDir = execSync('git rev-parse --path-format=absolute --git-common-dir', {
124
+ cwd: fromPath,
125
+ stdio: ['ignore', 'pipe', 'ignore'],
126
+ })
127
+ .toString()
128
+ .trim();
129
+ if (!commonDir)
130
+ return null;
131
+ // Common dir is `<repo>/.git` for both the main checkout and all
132
+ // linked worktrees. Its parent is the canonical repo root.
133
+ return path.dirname(path.resolve(commonDir));
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ };
139
+ /**
140
+ * Resolve `fromPath` to the directory whose basename should drive the
141
+ * registry name (#1259) — the *identity root*. Three outcomes:
142
+ *
143
+ * 1. `fromPath` IS the canonical checkout root → returns it unchanged.
144
+ * 2. `fromPath` is a linked-worktree root (has its own `.git` entry, but
145
+ * `git rev-parse --git-common-dir` points at a different `.git`) →
146
+ * returns the canonical repo root.
147
+ * 3. `fromPath` is anything else — an arbitrary subdir under a git repo,
148
+ * a non-git folder, a `--skip-git` subdir of an unrelated parent
149
+ * checkout — returns `fromPath` unchanged.
150
+ *
151
+ * Why not just use `getCanonicalRepoRoot` directly? Because `git rev-parse
152
+ * --git-common-dir` resolves the same canonical root for ANY path inside
153
+ * a git repo, including unrelated subdirs. Using it for registry-name
154
+ * derivation would silently re-key a `--skip-git` subdir analyze under
155
+ * the parent git's basename, defeating the user's `--skip-git` intent
156
+ * (regressing the #1232/#1233 fix). The "is this path a tree root"
157
+ * gate confines the canonical-root collapse to exactly the cases where
158
+ * #1259 matters: main checkouts and linked worktrees.
159
+ */
160
+ export const resolveRepoIdentityRoot = (fromPath) => {
161
+ const resolved = path.resolve(fromPath);
162
+ const canonical = getCanonicalRepoRoot(resolved);
163
+ if (!canonical)
164
+ return resolved; // non-git → use as-is
165
+ if (canonical === resolved)
166
+ return canonical; // canonical checkout
167
+ if (hasGitDir(resolved))
168
+ return canonical; // linked worktree (has .git file)
169
+ return resolved; // arbitrary subdir under a git repo → preserve as-is
170
+ };
94
171
  /**
95
172
  * Find a git root by checking only `.git` entries on the ancestor chain.
96
173
  *
@@ -9,7 +9,7 @@ import fs from 'fs/promises';
9
9
  import { realpathSync } from 'fs';
10
10
  import path from 'path';
11
11
  import os from 'os';
12
- import { getInferredRepoName } from './git.js';
12
+ import { getInferredRepoName, resolveRepoIdentityRoot } from './git.js';
13
13
  /**
14
14
  * Normalise a repo path for registry comparison across platforms
15
15
  * (#664 review feedback from @evander-wang).
@@ -296,6 +296,18 @@ const hasCustomAlias = (entry, inferredName) => {
296
296
  const resolved = path.resolve(entry.path);
297
297
  if (entry.name === path.basename(resolved))
298
298
  return false;
299
+ // Canonical-root-derived names are not user aliases either (#1259):
300
+ // a worktree registered under the canonical repo's basename
301
+ // (e.g. `{name: 'repo', path: '/repo/wt-feature'}`) must re-register
302
+ // cleanly without firing the duplicate-name collision guard. Without
303
+ // this check `entry.name = 'repo'` !== `path.basename('/repo/wt-feature') = 'wt-feature'`,
304
+ // so the prior check returns true → `isPreservedAlias = true` → guard
305
+ // throws `RegistryNameCollisionError` against the also-registered
306
+ // canonical checkout entry. The Claude-Code per-task worktree workflow
307
+ // — analyze canonical, then analyze worktree, then re-analyze worktree
308
+ // — would break on the third call.
309
+ if (entry.name === path.basename(resolveRepoIdentityRoot(resolved)))
310
+ return false;
299
311
  if (inferredName && entry.name === inferredName)
300
312
  return false;
301
313
  return true;
@@ -372,7 +384,13 @@ export const registerRepo = async (repoPath, meta, opts) => {
372
384
  isPreservedAlias = true;
373
385
  }
374
386
  else {
375
- name = inferred ?? path.basename(resolved);
387
+ // Canonical-root fallback: when `resolved` is a worktree root,
388
+ // derive the registry name from the canonical repo's basename, not
389
+ // the worktree slug — see #1259. `resolveRepoIdentityRoot` confines
390
+ // the collapse to canonical checkouts and linked worktree roots only,
391
+ // so `--skip-git` subdirs of unrelated parent git repos keep using
392
+ // their own basename (preserves the #1232/#1233 fix's intent).
393
+ name = inferred ?? path.basename(resolveRepoIdentityRoot(resolved));
376
394
  }
377
395
  }
378
396
  // Duplicate-name guard: only fire when the user EXPLICITLY asked for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.4-rc.60",
3
+ "version": "1.6.4-rc.62",
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",
@@ -60,6 +60,7 @@
60
60
  "commander": "^14.0.3",
61
61
  "cors": "^2.8.5",
62
62
  "express": "^4.19.2",
63
+ "express-rate-limit": "^8.4.1",
63
64
  "glob": "^13.0.6",
64
65
  "graphology": "^0.26.0",
65
66
  "graphology-indices": "^0.17.0",
@@ -1,4 +1,4 @@
1
- import{r as e,t}from"./chunk-CilyBKbf.js";import{n,t as r}from"./index-DpMcvzVf.js";import{buildDynamicSystemPrompt as i}from"./context-builder-CqQNhRj1.js";var a=Object.defineProperty,o=(e,t)=>{let n={};for(var r in e)a(n,r,{get:e[r],enumerable:!0});return t||a(n,Symbol.toStringTag,{value:`Module`}),n};function s(e){let t=Symbol.for(e);return{brand(n,r){let i=r?Symbol.for(`${e}.${r}`):t;class a extends n{[i]=!0;constructor(...e){super(...e)}static isInstance(e){return typeof e==`object`&&!!e&&i in e&&e[i]===!0}}return Object.defineProperty(a,`name`,{value:n.name}),a},sub(t){return s(`${e}.${t}`)},isInstance(e){return typeof e==`object`&&!!e&&t in e&&e[t]===!0}}}var c=s(`langchain`),l=o({ContextOverflowError:()=>m,LangChainError:()=>f,ModelAbortError:()=>p,addLangChainErrorFields:()=>u,ns:()=>d});function u(e,t){return e.lc_error_code=t,e.message=`${e.message}\n\nTroubleshooting URL: https://docs.langchain.com/oss/javascript/langchain/errors/${t}/\n`,e}var d=c.sub(`error`),f=class extends d.brand(Error){name=`LangChainError`;constructor(e){super(e),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}},p=class extends d.brand(f,`model-abort`){name=`ModelAbortError`;partialOutput;constructor(e,t){super(e),this.partialOutput=t}},m=class e extends d.brand(f,`context-overflow`){name=`ContextOverflowError`;cause;constructor(e){super(e??`Input exceeded the model's context window.`)}static fromError(t){let n=new e(t.message);return n.cause=t,n}};function h(e){return!!(e&&typeof e==`object`&&`type`in e&&e.type===`tool_call`)}function g(e){return!!(e&&typeof e==`object`&&`toolCall`in e&&e.toolCall!=null&&typeof e.toolCall==`object`&&`id`in e.toolCall&&typeof e.toolCall.id==`string`)}var _=class extends Error{output;constructor(e,t){super(e),this.output=t}};function v(e,t=b){e=e.trim();let n=e.indexOf("```");if(n===-1)return t(e);let r=e.substring(n+3);r.startsWith(`json
1
+ import{r as e,t}from"./chunk-CilyBKbf.js";import{n,t as r}from"./index-BcZj1uBK.js";import{buildDynamicSystemPrompt as i}from"./context-builder-CqQNhRj1.js";var a=Object.defineProperty,o=(e,t)=>{let n={};for(var r in e)a(n,r,{get:e[r],enumerable:!0});return t||a(n,Symbol.toStringTag,{value:`Module`}),n};function s(e){let t=Symbol.for(e);return{brand(n,r){let i=r?Symbol.for(`${e}.${r}`):t;class a extends n{[i]=!0;constructor(...e){super(...e)}static isInstance(e){return typeof e==`object`&&!!e&&i in e&&e[i]===!0}}return Object.defineProperty(a,`name`,{value:n.name}),a},sub(t){return s(`${e}.${t}`)},isInstance(e){return typeof e==`object`&&!!e&&t in e&&e[t]===!0}}}var c=s(`langchain`),l=o({ContextOverflowError:()=>m,LangChainError:()=>f,ModelAbortError:()=>p,addLangChainErrorFields:()=>u,ns:()=>d});function u(e,t){return e.lc_error_code=t,e.message=`${e.message}\n\nTroubleshooting URL: https://docs.langchain.com/oss/javascript/langchain/errors/${t}/\n`,e}var d=c.sub(`error`),f=class extends d.brand(Error){name=`LangChainError`;constructor(e){super(e),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}},p=class extends d.brand(f,`model-abort`){name=`ModelAbortError`;partialOutput;constructor(e,t){super(e),this.partialOutput=t}},m=class e extends d.brand(f,`context-overflow`){name=`ContextOverflowError`;cause;constructor(e){super(e??`Input exceeded the model's context window.`)}static fromError(t){let n=new e(t.message);return n.cause=t,n}};function h(e){return!!(e&&typeof e==`object`&&`type`in e&&e.type===`tool_call`)}function g(e){return!!(e&&typeof e==`object`&&`toolCall`in e&&e.toolCall!=null&&typeof e.toolCall==`object`&&`id`in e.toolCall&&typeof e.toolCall.id==`string`)}var _=class extends Error{output;constructor(e,t){super(e),this.output=t}};function v(e,t=b){e=e.trim();let n=e.indexOf("```");if(n===-1)return t(e);let r=e.substring(n+3);r.startsWith(`json
2
2
  `)?r=r.substring(5):r.startsWith(`json`)?r=r.substring(4):r.startsWith(`
3
3
  `)&&(r=r.substring(1));let i=r.indexOf("```"),a=r;return i!==-1&&(a=r.substring(0,i)),t(a.trim())}function y(e){try{return JSON.parse(e)}catch{}let t=e.trim();if(t.length===0)throw Error(`Unexpected end of JSON input`);let n=0;function r(){for(;n<t.length&&/\s/.test(t[n]);)n+=1}function i(){if(t[n]!==`"`)throw Error(`Expected '"' at position ${n}, got '${t[n]}'`);n+=1;let e=``,r=!1;for(;n<t.length;){let i=t[n];if(r){if(i===`n`)e+=`
4
4
  `;else if(i===`t`)e+=` `;else if(i===`r`)e+=`\r`;else if(i===`\\`)e+=`\\`;else if(i===`"`)e+=`"`;else if(i===`b`)e+=`\b`;else if(i===`f`)e+=`\f`;else if(i===`/`)e+=`/`;else if(i===`u`){let r=t.substring(n+1,n+5);if(/^[0-9A-Fa-f]{0,4}$/.test(r))r.length===4?e+=String.fromCharCode(Number.parseInt(r,16)):e+=`u${r}`,n+=r.length;else throw Error(`Invalid unicode escape sequence '\\u${r}' at position ${n}`)}else throw Error(`Invalid escape sequence '\\${i}' at position ${n}`);r=!1}else if(i===`\\`)r=!0;else if(i===`"`)return n+=1,e;else e+=i;n+=1}return r&&(e+=`\\`),e}function a(){let e=n,r=``;if(t[n]===`-`&&(r+=`-`,n+=1),n<t.length&&t[n]===`0`&&(r+=`0`,n+=1,t[n]>=`0`&&t[n]<=`9`))throw Error(`Invalid number at position ${e}`);if(n<t.length&&t[n]>=`1`&&t[n]<=`9`)for(;n<t.length&&t[n]>=`0`&&t[n]<=`9`;)r+=t[n],n+=1;if(n<t.length&&t[n]===`.`)for(r+=`.`,n+=1;n<t.length&&t[n]>=`0`&&t[n]<=`9`;)r+=t[n],n+=1;if(n<t.length&&(t[n]===`e`||t[n]===`E`))for(r+=t[n],n+=1,n<t.length&&(t[n]===`+`||t[n]===`-`)&&(r+=t[n],n+=1);n<t.length&&t[n]>=`0`&&t[n]<=`9`;)r+=t[n],n+=1;if(r===`-`)return-0;let i=Number.parseFloat(r);if(Number.isNaN(i))throw n=e,Error(`Invalid number '${r}' at position ${e}`);return i}function o(){if(r(),n>=t.length)throw Error(`Unexpected end of input at position ${n}`);let e=t[n];if(e===`{`)return c();if(e===`[`)return s();if(e===`"`)return i();if(`null`.startsWith(t.substring(n,n+4)))return n+=Math.min(4,t.length-n),null;if(`true`.startsWith(t.substring(n,n+4)))return n+=Math.min(4,t.length-n),!0;if(`false`.startsWith(t.substring(n,n+5)))return n+=Math.min(5,t.length-n),!1;if(e===`-`||e>=`0`&&e<=`9`)return a();throw Error(`Unexpected character '${e}' at position ${n}`)}function s(){if(t[n]!==`[`)throw Error(`Expected '[' at position ${n}, got '${t[n]}'`);let e=[];if(n+=1,r(),n>=t.length)return e;if(t[n]===`]`)return n+=1,e;for(;n<t.length;){if(r(),n>=t.length||(e.push(o()),r(),n>=t.length))return e;if(t[n]===`]`)return n+=1,e;if(t[n]===`,`){n+=1;continue}throw Error(`Expected ',' or ']' at position ${n}, got '${t[n]}'`)}return e}function c(){if(t[n]!==`{`)throw Error(`Expected '{' at position ${n}, got '${t[n]}'`);let e={};if(n+=1,r(),n>=t.length)return e;if(t[n]===`}`)return n+=1,e;for(;n<t.length;){if(r(),n>=t.length)return e;let a=i();if(r(),n>=t.length)return e;if(t[n]!==`:`)throw Error(`Expected ':' at position ${n}, got '${t[n]}'`);if(n+=1,r(),n>=t.length||(e[a]=o(),r(),n>=t.length))return e;if(t[n]===`}`)return n+=1,e;if(t[n]===`,`){n+=1;continue}throw Error(`Expected ',' or '}' at position ${n}, got '${t[n]}'`)}return e}let l=o();if(r(),n<t.length)throw Error(`Unexpected character '${t[n]}' at position ${n}`);return l}function b(e){try{return e===void 0?null:y(e)}catch{return null}}var ee=t(((e,t)=>{t.exports=function(e,t){if(typeof e!=`string`)throw TypeError(`Expected a string`);return t=t===void 0?`_`:t,e.replace(/([a-z\d])([A-Z])/g,`$1`+t+`$2`).replace(/([A-Z]+)([A-Z][a-z\d]+)/g,`$1`+t+`$2`).toLowerCase()}})),x=t(((e,t)=>{var n=/[\p{Lu}]/u,r=/[\p{Ll}]/u,i=/^[\p{Lu}](?![\p{Lu}])/gu,a=/([\p{Alpha}\p{N}_]|$)/u,o=/[_.\- ]+/,s=RegExp(`^`+o.source),c=new RegExp(o.source+a.source,`gu`),l=RegExp(`\\d+`+a.source,`gu`),u=(e,t,i)=>{let a=!1,o=!1,s=!1;for(let c=0;c<e.length;c++){let l=e[c];a&&n.test(l)?(e=e.slice(0,c)+`-`+e.slice(c),a=!1,s=o,o=!0,c++):o&&s&&r.test(l)?(e=e.slice(0,c-1)+`-`+e.slice(c-1),s=o,o=!1,a=!0):(a=t(l)===l&&i(l)!==l,s=o,o=i(l)===l&&t(l)!==l)}return e},d=(e,t)=>(i.lastIndex=0,e.replace(i,e=>t(e))),f=(e,t)=>(c.lastIndex=0,l.lastIndex=0,e.replace(c,(e,n)=>t(n)).replace(l,e=>t(e))),p=(e,t)=>{if(!(typeof e==`string`||Array.isArray(e)))throw TypeError("Expected the input to be `string | string[]`");if(t={pascalCase:!1,preserveConsecutiveUppercase:!1,...t},e=Array.isArray(e)?e.map(e=>e.trim()).filter(e=>e.length).join(`-`):e.trim(),e.length===0)return``;let n=t.locale===!1?e=>e.toLowerCase():e=>e.toLocaleLowerCase(t.locale),r=t.locale===!1?e=>e.toUpperCase():e=>e.toLocaleUpperCase(t.locale);return e.length===1?t.pascalCase?r(e):n(e):(e!==n(e)&&(e=u(e,n,r)),e=e.replace(s,``),e=t.preserveConsecutiveUppercase?d(e,n):n(e),t.pascalCase&&(e=r(e.charAt(0))+e.slice(1)),f(e,r))};t.exports=p,t.exports.default=p})),te=e(ee(),1),S=e(x(),1);function ne(e,t){return t?.[e]||(0,te.default)(e)}function re(e,t){return t?.[e]||(0,S.default)(e)}function ie(e,t,n){let r={};for(let i in e)Object.hasOwn(e,i)&&(r[t(i,n)]=e[i]);return r}var ae=`__lc_escaped__`;function oe(e){return`lc`in e||Object.keys(e).length===1&&`__lc_escaped__`in e}function se(e){return{[ae]:e}}function ce(e){return Object.keys(e).length===1&&`__lc_escaped__`in e}function C(e){return typeof e==`object`&&!!e&&`lc_serializable`in e&&typeof e.toJSON==`function`}function le(e){let t;return t=typeof e==`object`&&e?`lc_id`in e&&Array.isArray(e.lc_id)?e.lc_id:[e.constructor?.name??`Object`]:[typeof e],{lc:1,type:`not_implemented`,id:t}}function ue(e,t=new WeakSet){if(typeof e==`object`&&e&&!Array.isArray(e)){if(t.has(e))return le(e);if(C(e))return e;t.add(e);let n=e;if(oe(n))return t.delete(e),se(n);let r={};for(let[e,i]of Object.entries(n))r[e]=ue(i,t);return t.delete(e),r}return Array.isArray(e)?e.map(e=>ue(e,t)):e}function de(e){if(typeof e==`object`&&e&&!Array.isArray(e)){let t=e;if(ce(t))return t[ae];let n={};for(let[e,r]of Object.entries(t))n[e]=de(r);return n}return Array.isArray(e)?e.map(e=>de(e)):e}var fe=o({Serializable:()=>ge,get_lc_unique_name:()=>he});function pe(e){return Array.isArray(e)?[...e]:{...e}}function me(e,t){let n=pe(e);for(let[e,r]of Object.entries(t)){let[t,...i]=e.split(`.`).reverse(),a=n;for(let e of i.reverse()){if(a[e]===void 0)break;a[e]=pe(a[e]),a=a[e]}a[t]!==void 0&&(a[t]={lc:1,type:`secret`,id:[r]})}return n}function he(e){let t=Object.getPrototypeOf(e);return typeof e.lc_name==`function`&&(typeof t.lc_name!=`function`||e.lc_name()!==t.lc_name())?e.lc_name():e.name}var ge=class e{lc_serializable=!1;lc_kwargs;static lc_name(){return this.name}get lc_id(){return[...this.lc_namespace,he(this.constructor)]}get lc_secrets(){}get lc_attributes(){}get lc_aliases(){}get lc_serializable_keys(){}constructor(e,...t){this.lc_serializable_keys===void 0?this.lc_kwargs=e??{}:this.lc_kwargs=Object.fromEntries(Object.entries(e||{}).filter(([e])=>this.lc_serializable_keys?.includes(e)))}toJSON(){if(!this.lc_serializable||this.lc_kwargs instanceof e||typeof this.lc_kwargs!=`object`||Array.isArray(this.lc_kwargs))return this.toJSONNotImplemented();let t={},n={},r=Object.keys(this.lc_kwargs).reduce((e,t)=>(e[t]=t in this?this[t]:this.lc_kwargs[t],e),{});for(let e=Object.getPrototypeOf(this);e;e=Object.getPrototypeOf(e))Object.assign(t,Reflect.get(e,`lc_aliases`,this)),Object.assign(n,Reflect.get(e,`lc_secrets`,this)),Object.assign(r,Reflect.get(e,`lc_attributes`,this));Object.keys(n).forEach(e=>{let t=this,n=r,[i,...a]=e.split(`.`).reverse();for(let e of a.reverse()){if(!(e in t)||t[e]===void 0)return;(!(e in n)||n[e]===void 0)&&(typeof t[e]==`object`&&t[e]!=null?n[e]={}:Array.isArray(t[e])&&(n[e]=[])),t=t[e],n=n[e]}i in t&&t[i]!==void 0&&(n[i]=n[i]||t[i])});let i={},a=new WeakSet;a.add(this);for(let[e,t]of Object.entries(r))i[e]=ue(t,a);let o=ie(Object.keys(n).length?me(i,n):i,ne,t);return{lc:1,type:`constructor`,id:this.lc_id,kwargs:o}}toJSONNotImplemented(){return{lc:1,type:`not_implemented`,id:this.lc_id}}};function _e(e){return typeof e==`object`&&!!e&&`type`in e&&typeof e.type==`string`&&`source_type`in e&&(e.source_type===`url`||e.source_type===`base64`||e.source_type===`text`||e.source_type===`id`)}function ve(e){return _e(e)&&e.source_type===`url`&&`url`in e&&typeof e.url==`string`}function ye(e){return _e(e)&&e.source_type===`base64`&&`data`in e&&typeof e.data==`string`}function be(e){return _e(e)&&e.source_type===`text`&&`text`in e&&typeof e.text==`string`}function xe(e){return _e(e)&&e.source_type===`id`&&`id`in e&&typeof e.id==`string`}function Se(e){if(_e(e)){if(e.source_type===`url`)return{type:`image_url`,image_url:{url:e.url}};if(e.source_type===`base64`){if(!e.mime_type)throw Error(`mime_type key is required for base64 data.`);return{type:`image_url`,image_url:{url:`data:${e.mime_type};base64,${e.data}`}}}}throw Error(`Unsupported source type. Only 'url' and 'base64' are supported.`)}function Ce(e){let t=e.split(`;`)[0].split(`/`);if(t.length!==2)throw Error(`Invalid mime type: "${e}" - does not match type/subtype format.`);let n=t[0].trim(),r=t[1].trim();if(n===``||r===``)throw Error(`Invalid mime type: "${e}" - type or subtype is empty.`);let i={};for(let t of e.split(`;`).slice(1)){let n=t.split(`=`);if(n.length!==2)throw Error(`Invalid parameter syntax in mime type: "${e}".`);let r=n[0].trim(),a=n[1].trim();if(r===``)throw Error(`Invalid parameter syntax in mime type: "${e}".`);i[r]=a}return{type:n,subtype:r,parameters:i}}function we({dataUrl:e,asTypedArray:t=!1}){let n=e.match(/^data:(\w+\/\w+);base64,([A-Za-z0-9+/]+=*)$/),r;if(n){r=n[1].toLowerCase();let e=t?Uint8Array.from(atob(n[2]),e=>e.charCodeAt(0)):n[2];return{mime_type:r,data:e}}}function Te(e,t){if(e.type===`text`){if(!t.fromStandardTextBlock)throw Error(`Converter for ${t.providerName} does not implement \`fromStandardTextBlock\` method.`);return t.fromStandardTextBlock(e)}if(e.type===`image`){if(!t.fromStandardImageBlock)throw Error(`Converter for ${t.providerName} does not implement \`fromStandardImageBlock\` method.`);return t.fromStandardImageBlock(e)}if(e.type===`audio`){if(!t.fromStandardAudioBlock)throw Error(`Converter for ${t.providerName} does not implement \`fromStandardAudioBlock\` method.`);return t.fromStandardAudioBlock(e)}if(e.type===`file`){if(!t.fromStandardFileBlock)throw Error(`Converter for ${t.providerName} does not implement \`fromStandardFileBlock\` method.`);return t.fromStandardFileBlock(e)}throw Error(`Unable to convert content block type '${e.type}' to provider-specific format: not recognized.`)}function w(e,t){return T(e)&&e.type===t}function T(e){return typeof e==`object`&&!!e}function Ee(e){return Array.isArray(e)}function E(e){return typeof e==`string`}function De(e){return typeof e==`number`}function Oe(e){return e instanceof Uint8Array}function D(e){try{return JSON.parse(e)}catch{return}}var ke=e=>e();function Ae(e){if(e.type===`char_location`&&E(e.document_title)&&De(e.start_char_index)&&De(e.end_char_index)&&E(e.cited_text)){let{document_title:t,start_char_index:n,end_char_index:r,cited_text:i,...a}=e;return{...a,type:`citation`,source:`char`,title:t??void 0,startIndex:n,endIndex:r,citedText:i}}if(e.type===`page_location`&&E(e.document_title)&&De(e.start_page_number)&&De(e.end_page_number)&&E(e.cited_text)){let{document_title:t,start_page_number:n,end_page_number:r,cited_text:i,...a}=e;return{...a,type:`citation`,source:`page`,title:t??void 0,startIndex:n,endIndex:r,citedText:i}}if(e.type===`content_block_location`&&E(e.document_title)&&De(e.start_block_index)&&De(e.end_block_index)&&E(e.cited_text)){let{document_title:t,start_block_index:n,end_block_index:r,cited_text:i,...a}=e;return{...a,type:`citation`,source:`block`,title:t??void 0,startIndex:n,endIndex:r,citedText:i}}if(e.type===`web_search_result_location`&&E(e.url)&&E(e.title)&&E(e.encrypted_index)&&E(e.cited_text)){let{url:t,title:n,encrypted_index:r,cited_text:i,...a}=e;return{...a,type:`citation`,source:`url`,url:t,title:n,startIndex:Number(r),endIndex:Number(r),citedText:i}}if(e.type===`search_result_location`&&E(e.source)&&E(e.title)&&De(e.start_block_index)&&De(e.end_block_index)&&E(e.cited_text)){let{source:t,title:n,start_block_index:r,end_block_index:i,cited_text:a,...o}=e;return{...o,type:`citation`,source:`search`,url:t,title:n??void 0,startIndex:r,endIndex:i,citedText:a}}}function je(e){if(w(e,`document`)&&T(e.source)&&`type`in e.source){if(e.source.type===`base64`&&E(e.source.media_type)&&E(e.source.data))return{type:`file`,mimeType:e.source.media_type,data:e.source.data};if(e.source.type===`url`&&E(e.source.url))return{type:`file`,url:e.source.url};if(e.source.type===`file`&&E(e.source.file_id))return{type:`file`,fileId:e.source.file_id};if(e.source.type===`text`&&E(e.source.data))return{type:`file`,mimeType:String(e.source.media_type??`text/plain`),data:e.source.data}}else if(w(e,`image`)&&T(e.source)&&`type`in e.source){if(e.source.type===`base64`&&E(e.source.media_type)&&E(e.source.data))return{type:`image`,mimeType:e.source.media_type,data:e.source.data};if(e.source.type===`url`&&E(e.source.url))return{type:`image`,url:e.source.url};if(e.source.type===`file`&&E(e.source.file_id))return{type:`image`,fileId:e.source.file_id}}}function Me(e){function*t(){for(let t of e){let e=je(t);e?yield e:yield t}}return Array.from(t())}function Ne(e){function*t(){let t=typeof e.content==`string`?[{type:`text`,text:e.content}]:e.content;for(let n of t){if(w(n,`text`)&&E(n.text)){let{text:e,citations:t,...r}=n;if(Ee(t)&&t.length){let n=t.reduce((e,t)=>{let n=Ae(t);return n?[...e,n]:e},[]);yield{...r,type:`text`,text:e,annotations:n};continue}else{yield{...r,type:`text`,text:e};continue}}else if(w(n,`thinking`)&&E(n.thinking)){let{thinking:e,signature:t,...r}=n;yield{...r,type:`reasoning`,reasoning:e,signature:t};continue}else if(w(n,`redacted_thinking`)){yield{type:`non_standard`,value:n};continue}else if(w(n,`tool_use`)&&E(n.name)&&E(n.id)){yield{type:`tool_call`,id:n.id,name:n.name,args:n.input};continue}else if(w(n,`input_json_delta`)){if(Fe(e)&&e.tool_call_chunks?.length){let t=e.tool_call_chunks[0];yield{type:`tool_call_chunk`,id:t.id,name:t.name,args:t.args,index:t.index};continue}}else if(w(n,`server_tool_use`)&&E(n.name)&&E(n.id)){let{name:e,id:t}=n;if(e===`web_search`){yield{id:t,type:`server_tool_call`,name:`web_search`,args:{query:ke(()=>{if(typeof n.input==`string`)return n.input;if(T(n.input)&&E(n.input.query))return n.input.query;if(E(n.partial_json)){let e=D(n.partial_json);if(e?.query)return e.query}return``})}};continue}else if(n.name===`code_execution`){yield{id:t,type:`server_tool_call`,name:`code_execution`,args:{code:ke(()=>{if(typeof n.input==`string`)return n.input;if(T(n.input)&&E(n.input.code))return n.input.code;if(E(n.partial_json)){let e=D(n.partial_json);if(e?.code)return e.code}return``})}};continue}}else if(w(n,`web_search_tool_result`)&&E(n.tool_use_id)&&Ee(n.content)){let{content:e,tool_use_id:t}=n;yield{type:`server_tool_call_result`,name:`web_search`,toolCallId:t,status:`success`,output:{urls:e.reduce((e,t)=>w(t,`web_search_result`)?[...e,t.url]:e,[])}};continue}else if(w(n,`code_execution_tool_result`)&&E(n.tool_use_id)&&T(n.content)){yield{type:`server_tool_call_result`,name:`code_execution`,toolCallId:n.tool_use_id,status:`success`,output:n.content};continue}else if(w(n,`mcp_tool_use`)){yield{id:n.id,type:`server_tool_call`,name:`mcp_tool_use`,args:n.input};continue}else if(w(n,`mcp_tool_result`)&&E(n.tool_use_id)&&T(n.content)){yield{type:`server_tool_call_result`,name:`mcp_tool_use`,toolCallId:n.tool_use_id,status:`success`,output:n.content};continue}else if(w(n,`container_upload`)){yield{type:`server_tool_call`,name:`container_upload`,args:n.input};continue}else if(w(n,`search_result`)){yield{id:n.id,type:`non_standard`,value:n};continue}else if(w(n,`tool_result`)){yield{id:n.id,type:`non_standard`,value:n};continue}else{let e=je(n);if(e){yield e;continue}}yield{type:`non_standard`,value:n}}}return Array.from(t())}var Pe={translateContent:Ne,translateContentChunk:Ne};function Fe(e){return typeof e?._getType==`function`&&typeof e.concat==`function`&&e._getType()===`ai`}function Ie(e){return ve(e)?{type:e.type,mimeType:e.mime_type,url:e.url,metadata:e.metadata}:ye(e)?{type:e.type,mimeType:e.mime_type??`application/octet-stream`,data:e.data,metadata:e.metadata}:xe(e)?{type:e.type,mimeType:e.mime_type,fileId:e.id,metadata:e.metadata}:e}function Le(e){return e.map(Ie)}function Re(e){return!!(w(e,`image_url`)&&T(e.image_url)||w(e,`input_audio`)&&T(e.input_audio)||w(e,`file`)&&T(e.file))}function ze(e){if(w(e,`image_url`)&&T(e.image_url)&&E(e.image_url.url)){let t=we({dataUrl:e.image_url.url});return t?{type:`image`,mimeType:t.mime_type,data:t.data}:{type:`image`,url:e.image_url.url}}else if(w(e,`input_audio`)&&T(e.input_audio)&&E(e.input_audio.data)&&E(e.input_audio.format))return{type:`audio`,data:e.input_audio.data,mimeType:`audio/${e.input_audio.format}`};else if(w(e,`file`)&&T(e.file)&&E(e.file.data)){let t=we({dataUrl:e.file.data});if(t)return{type:`file`,data:t.data,mimeType:t.mime_type};if(E(e.file.file_id))return{type:`file`,fileId:e.file.file_id}}return e}function Be(e){let t=[];typeof e.content==`string`?e.content.length>0&&t.push({type:`text`,text:e.content}):t.push(...He(e.content));for(let n of e.tool_calls??[])t.push({type:`tool_call`,id:n.id,name:n.name,args:n.args});return t}function Ve(e){let t=[];typeof e.content==`string`?e.content.length>0&&t.push({type:`text`,text:e.content}):t.push(...He(e.content));for(let n of e.tool_calls??[])t.push({type:`tool_call`,id:n.id,name:n.name,args:n.args});return t}function He(e){let t=[];for(let n of e)Re(n)?t.push(ze(n)):t.push(n);return t}function Ue(e){if(e.type===`url_citation`){let{url:t,title:n,start_index:r,end_index:i}=e;return{type:`citation`,url:t,title:n,startIndex:r,endIndex:i}}if(e.type===`file_citation`){let{file_id:t,filename:n,index:r}=e;return{type:`citation`,title:n,startIndex:r,endIndex:r,fileId:t}}return e}function We(e){function*t(){T(e.additional_kwargs?.reasoning)&&Ee(e.additional_kwargs.reasoning.summary)&&(yield{type:`reasoning`,reasoning:e.additional_kwargs.reasoning.summary.reduce((e,t)=>T(t)&&E(t.text)?`${e}${t.text}`:e,``)});let t=typeof e.content==`string`?[{type:`text`,text:e.content}]:e.content;for(let e of t)if(w(e,`text`)){let{text:t,annotations:n,phase:r,extras:i,...a}=e,o=T(i)?{...i}:{};E(r)&&(o.phase=r);let s=Object.keys(o).length>0?{extras:o}:{};Array.isArray(n)?yield{...a,...s,type:`text`,text:String(t),annotations:n.map(Ue)}:yield{...a,...s,type:`text`,text:String(t)}}for(let t of e.tool_calls??[])yield{type:`tool_call`,id:t.id,name:t.name,args:t.args};if(T(e.additional_kwargs)&&Ee(e.additional_kwargs.tool_outputs))for(let t of e.additional_kwargs.tool_outputs){if(w(t,`web_search_call`)){let e={};if(T(t.action)&&E(t.action.query)&&(e.query=t.action.query),yield{id:t.id,type:`server_tool_call`,name:`web_search`,args:e},t.status===`completed`||t.status===`failed`){let e={};T(t.action)&&(e.action=t.action),yield{type:`server_tool_call_result`,toolCallId:E(t.id)?t.id:``,status:t.status===`completed`?`success`:`error`,output:e}}continue}else if(w(t,`file_search_call`)){yield{id:t.id,type:`server_tool_call`,name:`file_search`,args:{queries:Ee(t.queries)?t.queries:[]}},(t.status===`completed`||t.status===`failed`)&&(yield{type:`server_tool_call_result`,toolCallId:E(t.id)?t.id:``,status:t.status===`completed`?`success`:`error`,output:Ee(t.results)?{results:t.results}:{}});continue}else if(w(t,`computer_call`)){yield{type:`non_standard`,value:t};continue}else if(w(t,`code_interpreter_call`)){if(E(t.code)&&(yield{id:t.id,type:`server_tool_call`,name:`code_interpreter`,args:{code:t.code}}),Ee(t.outputs)){let e=ke(()=>{if(t.status!==`in_progress`){if(t.status===`completed`)return 0;if(t.status===`incomplete`)return 127;if(t.status!==`interpreting`&&t.status===`failed`)return 1}});for(let n of t.outputs)if(w(n,`logs`)){yield{type:`server_tool_call_result`,toolCallId:t.id??``,status:`success`,output:{type:`code_interpreter_output`,returnCode:e??0,stderr:[0,void 0].includes(e)?void 0:String(n.logs),stdout:[0,void 0].includes(e)?String(n.logs):void 0}};continue}}continue}else if(w(t,`mcp_call`)){yield{id:t.id,type:`server_tool_call`,name:`mcp_call`,args:t.input};continue}else if(w(t,`mcp_list_tools`)){yield{id:t.id,type:`server_tool_call`,name:`mcp_list_tools`,args:t.input};continue}else if(w(t,`mcp_approval_request`)){yield{type:`non_standard`,value:t};continue}else if(w(t,`tool_search_call`)){let e={};T(t.arguments)&&Object.assign(e,t.arguments);let n={};E(t.execution)&&(n.execution=t.execution),E(t.status)&&(n.status=t.status),E(t.call_id)&&(n.call_id=t.call_id),yield{id:E(t.id)?t.id:``,type:`server_tool_call`,name:`tool_search`,args:e,...Object.keys(n).length>0?{extras:n}:{}};continue}else if(w(t,`tool_search_output`)){let e={name:`tool_search`};E(t.execution)&&(e.execution=t.execution),yield{type:`server_tool_call_result`,toolCallId:E(t.id)?t.id:``,status:t.status===`completed`?`success`:t.status===`failed`?`error`:`success`,output:{tools:Ee(t.tools)?t.tools:[]},extras:e};continue}else if(w(t,`image_generation_call`)){E(t.result)&&(yield{type:`image`,mimeType:`image/png`,data:t.result,id:E(t.id)?t.id:void 0,metadata:{status:E(t.status)?t.status:void 0}}),yield{type:`non_standard`,value:t};continue}T(t)&&(yield{type:`non_standard`,value:t})}}return Array.from(t())}function Ge(e){function*t(){yield*We(e);for(let t of e.tool_call_chunks??[])yield{type:`tool_call_chunk`,id:t.id,name:t.name,args:t.args}}return Array.from(t())}var Ke={translateContent:e=>typeof e.content==`string`?Be(e):We(e),translateContentChunk:e=>typeof e.content==`string`?Ve(e):Ge(e)};function qe(e){return typeof e==`object`&&!!e&&`type`in e&&`content`in e&&(typeof e.content==`string`||Array.isArray(e.content))}function Je(e,t=`pretty`){return t===`pretty`?Ye(e):JSON.stringify(e)}function Ye(e){let t=[],n=` ${e.type.charAt(0).toUpperCase()+e.type.slice(1)} Message `,r=Math.floor((80-n.length)/2),i=`=`.repeat(r),a=n.length%2==0?i:`${i}=`;if(t.push(`${i}${n}${a}`),e.type===`ai`){let n=e;if(n.tool_calls&&n.tool_calls.length>0){t.push(`Tool Calls:`);for(let e of n.tool_calls){t.push(` ${e.name} (${e.id})`),t.push(` Call ID: ${e.id}`),t.push(` Args:`);for(let[n,r]of Object.entries(e.args))t.push(` ${n}: ${typeof r==`object`?JSON.stringify(r):r}`)}}}if(e.type===`tool`){let n=e;n.name&&t.push(`Name: ${n.name}`)}return typeof e.content==`string`&&e.content.trim()&&(t.length>1&&t.push(``),t.push(e.content)),t.join(`
@@ -0,0 +1 @@
1
+ import"./index-BcZj1uBK.js";import"./chunk-H3VCZNTA-S6JCDFX6.js";import"./chunk-FXACKDTF-DDbh9iQA.js";import{n as e}from"./chunk-XGPFEOL4-C8lmtgql.js";export{e as createArchitectureServices};