diff --git a/.gitignore b/.gitignore
index 7c154ae1..dbb92822 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,6 @@
# Dependencies
node_modules/
-# Build output
-dist/
-
.cmem
# IDE
diff --git a/README.md b/README.md
index f8f39e97..e0c08ece 100644
--- a/README.md
+++ b/README.md
@@ -19,11 +19,9 @@
### Get Started
```bash
-npx @colbymchenry/codegraph
+npm install -g https://github.com/Quon/codegraph/archive/refs/heads/main.tar.gz && codegraph install
```
-Interactive installer configures Claude Code automatically
-
#### Initialize Projects
```bash
@@ -145,7 +143,7 @@ CodeGraph detects web-framework routing files and emits `route` nodes linked by
### 1. Run the Installer
```bash
-npx @colbymchenry/codegraph
+npm install -g https://github.com/Quon/codegraph/archive/refs/heads/main.tar.gz && codegraph install
```
The installer will:
@@ -173,7 +171,7 @@ That's it! Claude Code will use CodeGraph tools automatically when a `.codegraph
**Install globally:**
```bash
-npm install -g @colbymchenry/codegraph
+npm install -g https://github.com/Quon/codegraph/archive/refs/heads/main.tar.gz
```
**Add to `~/.claude.json`:**
@@ -311,8 +309,36 @@ codegraph files [path] # Show file structure (--format, --filter, --m
codegraph context # Build context for AI (--format, --max-nodes)
codegraph affected [files...] # Find test files affected by changes (see below)
codegraph serve --mcp # Start MCP server
+
+# Monorepo sub-projects
+codegraph project list # List registered sub-projects
+codegraph project add # Register a sub-project (relative path from root)
+codegraph project remove # Unregister a sub-project
+codegraph project scan # Auto-discover initialized sub-projects (--depth, default 3)
```
+### Monorepo / multi-project
+
+Initialize each sub-project independently, then register them from the monorepo root so the MCP tools can address them by name.
+
+```bash
+# In each sub-project
+cd packages/foo && codegraph init -i && codegraph index
+
+# Back at the root — auto-discover all initialized sub-projects
+cd ../..
+codegraph project scan # writes .codegraph/projects.json
+
+# Or register manually
+codegraph project add packages/foo
+codegraph project add packages/bar
+
+# Verify
+codegraph project list
+```
+
+MCP tools accept a `project` parameter (`"packages/foo"`) to target a specific sub-project.
+
### `codegraph affected`
Traces import dependencies transitively to find which test files are affected by changed source files.
diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts
index e2e24d1c..2c63fd94 100644
--- a/__tests__/installer.test.ts
+++ b/__tests__/installer.test.ts
@@ -88,6 +88,29 @@ describe('Installer Config Writer', () => {
warnSpy.mockRestore();
});
+ it('should use npx-based command for MCP server (no global install required)', () => {
+ writeMcpConfig('local');
+
+ const claudeJson = path.join(tempDir, '.claude.json');
+ const content = JSON.parse(fs.readFileSync(claudeJson, 'utf-8'));
+ const server = content.mcpServers.codegraph;
+
+ expect(server.type).toBe('stdio');
+ // Should use npx, not bare 'codegraph' command
+ if (process.platform === 'win32') {
+ expect(server.command).toBe('cmd');
+ expect(server.args[0]).toBe('/c');
+ expect(server.args[1]).toBe('npx');
+ } else {
+ expect(server.command).toBe('npx');
+ }
+ // Should reference the GitHub package
+ const args = server.args as string[];
+ expect(args.some((a: string) => a.includes('github:Quon/codegraph'))).toBe(true);
+ expect(args.some((a: string) => a === 'serve')).toBe(true);
+ expect(args.some((a: string) => a === '--mcp')).toBe(true);
+ });
+
it('should preserve existing valid config when adding codegraph', () => {
const claudeJson = path.join(tempDir, '.claude.json');
fs.writeFileSync(claudeJson, JSON.stringify({
diff --git a/__tests__/node-version-check.test.ts b/__tests__/node-version-check.test.ts
index d7b725cb..dc1c6dda 100644
--- a/__tests__/node-version-check.test.ts
+++ b/__tests__/node-version-check.test.ts
@@ -7,36 +7,36 @@
*/
import { describe, it, expect } from 'vitest';
-import { buildNode25BlockBanner } from '../src/bin/node-version-check';
+import { buildNodeBlockBanner } from '../src/bin/node-version-check';
-describe('buildNode25BlockBanner', () => {
+describe('buildNodeBlockBanner', () => {
it('embeds the reported Node version in the header', () => {
- expect(buildNode25BlockBanner('25.9.0')).toContain(
+ expect(buildNodeBlockBanner('25.9.0')).toContain(
'Unsupported Node.js version: 25.9.0'
);
});
it('names the V8 turboshaft WASM root cause and the OOM symptom', () => {
- const banner = buildNode25BlockBanner('25.7.0');
+ const banner = buildNodeBlockBanner('25.7.0');
expect(banner).toContain('V8 WASM JIT');
expect(banner).toContain('turboshaft');
expect(banner).toContain('Fatal process out of memory: Zone');
});
it('points users to Node 22 LTS via nvm and Homebrew', () => {
- const banner = buildNode25BlockBanner('25.7.0');
+ const banner = buildNodeBlockBanner('25.7.0');
expect(banner).toContain('Node.js 22 LTS');
expect(banner).toContain('nvm install 22');
expect(banner).toContain('brew install node@22');
});
it('documents the CODEGRAPH_ALLOW_UNSAFE_NODE override', () => {
- const banner = buildNode25BlockBanner('25.7.0');
+ const banner = buildNodeBlockBanner('25.7.0');
expect(banner).toContain('CODEGRAPH_ALLOW_UNSAFE_NODE=1');
});
it('links to issue #81 for the root-cause writeup', () => {
- expect(buildNode25BlockBanner('25.7.0')).toContain(
+ expect(buildNodeBlockBanner('25.7.0')).toContain(
'github.com/colbymchenry/codegraph/issues/81'
);
});
diff --git a/__tests__/projects.test.ts b/__tests__/projects.test.ts
new file mode 100644
index 00000000..8267ce2c
--- /dev/null
+++ b/__tests__/projects.test.ts
@@ -0,0 +1,293 @@
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import * as fs from 'fs';
+import * as path from 'path';
+import * as os from 'os';
+import {
+ PROJECTS_FILENAME,
+ getProjectsPath,
+ loadProjects,
+ saveProjects,
+ addProject,
+ removeProject,
+ scanForProjects,
+ syncProjects,
+} from '../src/projects';
+import { CodeGraph } from '../src';
+
+function createTempDir(): string {
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'monorepo-test-'));
+}
+
+function cleanupTempDir(dir: string): void {
+ if (fs.existsSync(dir)) {
+ fs.rmSync(dir, { recursive: true, force: true });
+ }
+}
+
+function createProject(root: string, sub: string): void {
+ const dir = path.join(root, sub);
+ fs.mkdirSync(dir, { recursive: true });
+ // Create a minimal .codegraph/codegraph.db to satisfy isInitialized()
+ const cgDir = path.join(dir, '.codegraph');
+ fs.mkdirSync(cgDir, { recursive: true });
+ fs.writeFileSync(path.join(cgDir, 'codegraph.db'), '', 'utf-8');
+}
+
+describe('Projects Registry', () => {
+ let tempDir: string;
+
+ beforeEach(() => {
+ tempDir = createTempDir();
+ });
+
+ afterEach(() => {
+ cleanupTempDir(tempDir);
+ });
+
+ describe('getProjectsPath', () => {
+ it('should return correct path to projects.json', () => {
+ expect(getProjectsPath(tempDir)).toBe(
+ path.join(tempDir, '.codegraph', 'projects.json')
+ );
+ });
+ });
+
+ describe('loadProjects', () => {
+ it('should return empty array when projects.json does not exist', () => {
+ const result = loadProjects(tempDir);
+ expect(result).toEqual([]);
+ });
+
+ it('should return empty array when projects.json has invalid JSON', () => {
+ const cgDir = path.join(tempDir, '.codegraph');
+ fs.mkdirSync(cgDir, { recursive: true });
+ fs.writeFileSync(path.join(cgDir, PROJECTS_FILENAME), 'not json', 'utf-8');
+ const result = loadProjects(tempDir);
+ expect(result).toEqual([]);
+ });
+
+ it('should load projects from projects.json', () => {
+ const cgDir = path.join(tempDir, '.codegraph');
+ fs.mkdirSync(cgDir, { recursive: true });
+ fs.writeFileSync(
+ path.join(cgDir, PROJECTS_FILENAME),
+ JSON.stringify(['packages/foo', 'packages/bar']),
+ 'utf-8'
+ );
+ const result = loadProjects(tempDir);
+ expect(result).toEqual(['packages/foo', 'packages/bar']);
+ });
+
+ it('should filter non-string entries', () => {
+ const cgDir = path.join(tempDir, '.codegraph');
+ fs.mkdirSync(cgDir, { recursive: true });
+ fs.writeFileSync(
+ path.join(cgDir, PROJECTS_FILENAME),
+ JSON.stringify(['valid', 42, null, { bad: true }]),
+ 'utf-8'
+ );
+ const result = loadProjects(tempDir);
+ expect(result).toEqual(['valid']);
+ });
+ });
+
+ describe('saveProjects', () => {
+ it('should save projects to projects.json', () => {
+ saveProjects(tempDir, ['packages/bar', 'packages/foo']);
+ const cgDir = path.join(tempDir, '.codegraph');
+ const content = fs.readFileSync(path.join(cgDir, PROJECTS_FILENAME), 'utf-8');
+ expect(JSON.parse(content)).toEqual(['packages/bar', 'packages/foo']);
+ });
+
+ it('should write atomically (temp file + rename)', () => {
+ saveProjects(tempDir, ['packages/foo']);
+ const cgDir = path.join(tempDir, '.codegraph');
+ // No .tmp file should remain after write
+ const files = fs.readdirSync(cgDir);
+ expect(files.filter(f => f.endsWith('.tmp'))).toEqual([]);
+ });
+ });
+
+ describe('addProject', () => {
+ it('should add a project to the registry', () => {
+ const result = addProject(tempDir, 'packages/foo');
+ expect(result).toEqual(['packages/foo']);
+ // Verify persisted
+ expect(loadProjects(tempDir)).toEqual(['packages/foo']);
+ });
+
+ it('should deduplicate when adding existing project', () => {
+ addProject(tempDir, 'packages/foo');
+ const result = addProject(tempDir, 'packages/foo');
+ expect(result).toEqual(['packages/foo']);
+ });
+
+ it('should append to existing projects', () => {
+ addProject(tempDir, 'packages/foo');
+ const result = addProject(tempDir, 'packages/bar');
+ // Return value preserves insertion order; file on disk is sorted
+ expect(result).toContain('packages/foo');
+ expect(result).toContain('packages/bar');
+ });
+ });
+
+ describe('removeProject', () => {
+ it('should remove a project from the registry', () => {
+ addProject(tempDir, 'packages/foo');
+ addProject(tempDir, 'packages/bar');
+ const result = removeProject(tempDir, 'packages/foo');
+ expect(result).toEqual(['packages/bar']);
+ });
+
+ it('should do nothing when removing non-existent project', () => {
+ addProject(tempDir, 'packages/foo');
+ const result = removeProject(tempDir, 'packages/nonexistent');
+ expect(result).toEqual(['packages/foo']);
+ });
+ });
+
+ describe('scanForProjects', () => {
+ it('should discover initialized sub-projects', () => {
+ createProject(tempDir, 'packages/foo');
+ createProject(tempDir, 'packages/bar');
+ const result = scanForProjects(tempDir);
+ expect(result).toEqual(['packages/bar', 'packages/foo']);
+ });
+
+ it('should respect maxDepth', () => {
+ createProject(tempDir, 'packages/foo');
+ createProject(tempDir, 'packages/group/deep');
+ const result = scanForProjects(tempDir, 1);
+ expect(result).toEqual(['packages/foo']);
+ });
+
+ it('should skip .codegraph/, .git/, node_modules/', () => {
+ createProject(tempDir, '.codegraph'); // <-- inside root .codegraph
+ createProject(tempDir, 'node_modules/pkg');
+ createProject(tempDir, '.git/hooks');
+ createProject(tempDir, 'packages/real');
+ const result = scanForProjects(tempDir);
+ expect(result).toEqual(['packages/real']);
+ });
+
+ it('should return empty array when no sub-projects exist', () => {
+ const result = scanForProjects(tempDir);
+ expect(result).toEqual([]);
+ });
+ });
+
+ describe('syncProjects', () => {
+ it('should merge discovered projects with existing registry', () => {
+ // Pre-register a project that scan will not find
+ addProject(tempDir, 'legacy/manual');
+ // Create discoverable projects
+ createProject(tempDir, 'packages/foo');
+ const result = syncProjects(tempDir);
+ expect(result).toContain('legacy/manual');
+ expect(result).toContain('packages/foo');
+ });
+ });
+});
+
+// ───────────────────────────────────────────────────────
+// Integration tests — these go AFTER the existing tests
+// ───────────────────────────────────────────────────────
+
+describe('Monorepo Integration', () => {
+ let tempDir: string;
+
+ beforeEach(() => {
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'monorepo-int-'));
+ });
+
+ afterEach(() => {
+ if (fs.existsSync(tempDir)) {
+ fs.rmSync(tempDir, { recursive: true, force: true });
+ }
+ });
+
+ it('should init sub-projects and register them', () => {
+ // Create monorepo structure
+ const packagesFoo = path.join(tempDir, 'packages/foo');
+ const packagesBar = path.join(tempDir, 'packages/bar');
+ fs.mkdirSync(packagesFoo, { recursive: true });
+ fs.mkdirSync(packagesBar, { recursive: true });
+
+ // Init root project
+ const rootCg = CodeGraph.initSync(tempDir);
+ rootCg.close();
+
+ // Init sub-projects
+ const fooCg = CodeGraph.initSync(packagesFoo);
+ fooCg.close();
+ const barCg = CodeGraph.initSync(packagesBar);
+ barCg.close();
+
+ // Register sub-projects
+ addProject(tempDir, 'packages/foo');
+ addProject(tempDir, 'packages/bar');
+
+ // Verify
+ const projects = loadProjects(tempDir);
+ expect(projects).toContain('packages/foo');
+ expect(projects).toContain('packages/bar');
+ });
+
+ it('scanForProjects should find sub-projects created after init', () => {
+ // Init root
+ const rootCg = CodeGraph.initSync(tempDir);
+ rootCg.close();
+
+ // No sub-projects yet
+ expect(scanForProjects(tempDir)).toEqual([]);
+
+ // Create and init a sub-project
+ const sub = path.join(tempDir, 'packages/new');
+ fs.mkdirSync(sub, { recursive: true });
+ const subCg = CodeGraph.initSync(sub);
+ subCg.close();
+
+ // Scan should find it
+ const found = scanForProjects(tempDir);
+ expect(found).toContain('packages/new');
+ });
+
+ it('syncProjects preserves manually added projects during scan', () => {
+ // Init root
+ const rootCg = CodeGraph.initSync(tempDir);
+ rootCg.close();
+
+ // Add a manual entry (that won't be found by scan)
+ addProject(tempDir, 'manual/pkg');
+ // Create and init an auto-discoverable project
+ const sub = path.join(tempDir, 'packages/auto');
+ fs.mkdirSync(sub, { recursive: true });
+ const subCg = CodeGraph.initSync(sub);
+ subCg.close();
+
+ // sync
+ const merged = syncProjects(tempDir);
+ expect(merged).toContain('manual/pkg');
+ expect(merged).toContain('packages/auto');
+ });
+
+ it('should handle project add/remove lifecycle', () => {
+ const rootCg = CodeGraph.initSync(tempDir);
+ rootCg.close();
+
+ addProject(tempDir, 'packages/foo');
+ expect(loadProjects(tempDir)).toEqual(['packages/foo']);
+
+ removeProject(tempDir, 'packages/foo');
+ expect(loadProjects(tempDir)).toEqual([]);
+ });
+
+ it('should handle corrupted projects.json gracefully', () => {
+ const cgDir = path.join(tempDir, '.codegraph');
+ fs.mkdirSync(cgDir, { recursive: true });
+ fs.writeFileSync(path.join(cgDir, 'projects.json'), '{broken json', 'utf-8');
+
+ const result = loadProjects(tempDir);
+ expect(result).toEqual([]);
+ });
+});
diff --git a/dist/bin/codegraph.d.ts b/dist/bin/codegraph.d.ts
new file mode 100644
index 00000000..f3b15d9c
--- /dev/null
+++ b/dist/bin/codegraph.d.ts
@@ -0,0 +1,21 @@
+#!/usr/bin/env node
+/**
+ * CodeGraph CLI
+ *
+ * Command-line interface for CodeGraph code intelligence.
+ *
+ * Usage:
+ * codegraph Run interactive installer (when no args)
+ * codegraph install Run interactive installer
+ * codegraph init [path] Initialize CodeGraph in a project
+ * codegraph uninit [path] Remove CodeGraph from a project
+ * codegraph index [path] Index all files in the project
+ * codegraph sync [path] Sync changes since last index
+ * codegraph status [path] Show index status
+ * codegraph query Search for symbols
+ * codegraph files [options] Show project file structure
+ * codegraph context Build context for a task
+ * codegraph affected [files] Find test files affected by changes
+ */
+export {};
+//# sourceMappingURL=codegraph.d.ts.map
\ No newline at end of file
diff --git a/dist/bin/codegraph.d.ts.map b/dist/bin/codegraph.d.ts.map
new file mode 100644
index 00000000..2f349cb4
--- /dev/null
+++ b/dist/bin/codegraph.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"codegraph.d.ts","sourceRoot":"","sources":["../../src/bin/codegraph.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}
\ No newline at end of file
diff --git a/dist/bin/codegraph.js b/dist/bin/codegraph.js
new file mode 100755
index 00000000..5e37fb9a
--- /dev/null
+++ b/dist/bin/codegraph.js
@@ -0,0 +1,24670 @@
+#!/usr/bin/env node
+"use strict";
+var __create = Object.create;
+var __defProp = Object.defineProperty;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __getProtoOf = Object.getPrototypeOf;
+var __hasOwnProp = Object.prototype.hasOwnProperty;
+var __esm = (fn, res) => function __init() {
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
+};
+var __commonJS = (cb, mod) => function __require() {
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
+};
+var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+};
+var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") {
+ for (let key of __getOwnPropNames(from))
+ if (!__hasOwnProp.call(to, key) && key !== except)
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ }
+ return to;
+};
+var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
+ // If the importer is in node compatibility mode or this is not an ESM
+ // file that has been converted to a CommonJS file using a Babel-
+ // compatible transform (i.e. "__esModule" has not been set), then set
+ // "default" to the CommonJS "module.exports" for node compatibility.
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
+ mod
+));
+
+// node_modules/commander/lib/error.js
+var require_error = __commonJS({
+ "node_modules/commander/lib/error.js"(exports2) {
+ var CommanderError2 = class extends Error {
+ /**
+ * Constructs the CommanderError class
+ * @param {number} exitCode suggested exit code which could be used with process.exit
+ * @param {string} code an id string representing the error
+ * @param {string} message human-readable description of the error
+ */
+ constructor(exitCode, code, message) {
+ super(message);
+ Error.captureStackTrace(this, this.constructor);
+ this.name = this.constructor.name;
+ this.code = code;
+ this.exitCode = exitCode;
+ this.nestedError = void 0;
+ }
+ };
+ var InvalidArgumentError2 = class extends CommanderError2 {
+ /**
+ * Constructs the InvalidArgumentError class
+ * @param {string} [message] explanation of why argument is invalid
+ */
+ constructor(message) {
+ super(1, "commander.invalidArgument", message);
+ Error.captureStackTrace(this, this.constructor);
+ this.name = this.constructor.name;
+ }
+ };
+ exports2.CommanderError = CommanderError2;
+ exports2.InvalidArgumentError = InvalidArgumentError2;
+ }
+});
+
+// node_modules/commander/lib/argument.js
+var require_argument = __commonJS({
+ "node_modules/commander/lib/argument.js"(exports2) {
+ var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
+ var Argument2 = class {
+ /**
+ * Initialize a new command argument with the given name and description.
+ * The default is that the argument is required, and you can explicitly
+ * indicate this with <> around the name. Put [] around the name for an optional argument.
+ *
+ * @param {string} name
+ * @param {string} [description]
+ */
+ constructor(name, description) {
+ this.description = description || "";
+ this.variadic = false;
+ this.parseArg = void 0;
+ this.defaultValue = void 0;
+ this.defaultValueDescription = void 0;
+ this.argChoices = void 0;
+ switch (name[0]) {
+ case "<":
+ this.required = true;
+ this._name = name.slice(1, -1);
+ break;
+ case "[":
+ this.required = false;
+ this._name = name.slice(1, -1);
+ break;
+ default:
+ this.required = true;
+ this._name = name;
+ break;
+ }
+ if (this._name.endsWith("...")) {
+ this.variadic = true;
+ this._name = this._name.slice(0, -3);
+ }
+ }
+ /**
+ * Return argument name.
+ *
+ * @return {string}
+ */
+ name() {
+ return this._name;
+ }
+ /**
+ * @package
+ */
+ _collectValue(value, previous) {
+ if (previous === this.defaultValue || !Array.isArray(previous)) {
+ return [value];
+ }
+ previous.push(value);
+ return previous;
+ }
+ /**
+ * Set the default value, and optionally supply the description to be displayed in the help.
+ *
+ * @param {*} value
+ * @param {string} [description]
+ * @return {Argument}
+ */
+ default(value, description) {
+ this.defaultValue = value;
+ this.defaultValueDescription = description;
+ return this;
+ }
+ /**
+ * Set the custom handler for processing CLI command arguments into argument values.
+ *
+ * @param {Function} [fn]
+ * @return {Argument}
+ */
+ argParser(fn) {
+ this.parseArg = fn;
+ return this;
+ }
+ /**
+ * Only allow argument value to be one of choices.
+ *
+ * @param {string[]} values
+ * @return {Argument}
+ */
+ choices(values) {
+ this.argChoices = values.slice();
+ this.parseArg = (arg, previous) => {
+ if (!this.argChoices.includes(arg)) {
+ throw new InvalidArgumentError2(
+ `Allowed choices are ${this.argChoices.join(", ")}.`
+ );
+ }
+ if (this.variadic) {
+ return this._collectValue(arg, previous);
+ }
+ return arg;
+ };
+ return this;
+ }
+ /**
+ * Make argument required.
+ *
+ * @returns {Argument}
+ */
+ argRequired() {
+ this.required = true;
+ return this;
+ }
+ /**
+ * Make argument optional.
+ *
+ * @returns {Argument}
+ */
+ argOptional() {
+ this.required = false;
+ return this;
+ }
+ };
+ function humanReadableArgName(arg) {
+ const nameOutput = arg.name() + (arg.variadic === true ? "..." : "");
+ return arg.required ? "<" + nameOutput + ">" : "[" + nameOutput + "]";
+ }
+ exports2.Argument = Argument2;
+ exports2.humanReadableArgName = humanReadableArgName;
+ }
+});
+
+// node_modules/commander/lib/help.js
+var require_help = __commonJS({
+ "node_modules/commander/lib/help.js"(exports2) {
+ var { humanReadableArgName } = require_argument();
+ var Help2 = class {
+ constructor() {
+ this.helpWidth = void 0;
+ this.minWidthToWrap = 40;
+ this.sortSubcommands = false;
+ this.sortOptions = false;
+ this.showGlobalOptions = false;
+ }
+ /**
+ * prepareContext is called by Commander after applying overrides from `Command.configureHelp()`
+ * and just before calling `formatHelp()`.
+ *
+ * Commander just uses the helpWidth and the rest is provided for optional use by more complex subclasses.
+ *
+ * @param {{ error?: boolean, helpWidth?: number, outputHasColors?: boolean }} contextOptions
+ */
+ prepareContext(contextOptions) {
+ this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
+ }
+ /**
+ * Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.
+ *
+ * @param {Command} cmd
+ * @returns {Command[]}
+ */
+ visibleCommands(cmd) {
+ const visibleCommands = cmd.commands.filter((cmd2) => !cmd2._hidden);
+ const helpCommand = cmd._getHelpCommand();
+ if (helpCommand && !helpCommand._hidden) {
+ visibleCommands.push(helpCommand);
+ }
+ if (this.sortSubcommands) {
+ visibleCommands.sort((a, b) => {
+ return a.name().localeCompare(b.name());
+ });
+ }
+ return visibleCommands;
+ }
+ /**
+ * Compare options for sort.
+ *
+ * @param {Option} a
+ * @param {Option} b
+ * @returns {number}
+ */
+ compareOptions(a, b) {
+ const getSortKey = (option) => {
+ return option.short ? option.short.replace(/^-/, "") : option.long.replace(/^--/, "");
+ };
+ return getSortKey(a).localeCompare(getSortKey(b));
+ }
+ /**
+ * Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one.
+ *
+ * @param {Command} cmd
+ * @returns {Option[]}
+ */
+ visibleOptions(cmd) {
+ const visibleOptions = cmd.options.filter((option) => !option.hidden);
+ const helpOption = cmd._getHelpOption();
+ if (helpOption && !helpOption.hidden) {
+ const removeShort = helpOption.short && cmd._findOption(helpOption.short);
+ const removeLong = helpOption.long && cmd._findOption(helpOption.long);
+ if (!removeShort && !removeLong) {
+ visibleOptions.push(helpOption);
+ } else if (helpOption.long && !removeLong) {
+ visibleOptions.push(
+ cmd.createOption(helpOption.long, helpOption.description)
+ );
+ } else if (helpOption.short && !removeShort) {
+ visibleOptions.push(
+ cmd.createOption(helpOption.short, helpOption.description)
+ );
+ }
+ }
+ if (this.sortOptions) {
+ visibleOptions.sort(this.compareOptions);
+ }
+ return visibleOptions;
+ }
+ /**
+ * Get an array of the visible global options. (Not including help.)
+ *
+ * @param {Command} cmd
+ * @returns {Option[]}
+ */
+ visibleGlobalOptions(cmd) {
+ if (!this.showGlobalOptions) return [];
+ const globalOptions = [];
+ for (let ancestorCmd = cmd.parent; ancestorCmd; ancestorCmd = ancestorCmd.parent) {
+ const visibleOptions = ancestorCmd.options.filter(
+ (option) => !option.hidden
+ );
+ globalOptions.push(...visibleOptions);
+ }
+ if (this.sortOptions) {
+ globalOptions.sort(this.compareOptions);
+ }
+ return globalOptions;
+ }
+ /**
+ * Get an array of the arguments if any have a description.
+ *
+ * @param {Command} cmd
+ * @returns {Argument[]}
+ */
+ visibleArguments(cmd) {
+ if (cmd._argsDescription) {
+ cmd.registeredArguments.forEach((argument) => {
+ argument.description = argument.description || cmd._argsDescription[argument.name()] || "";
+ });
+ }
+ if (cmd.registeredArguments.find((argument) => argument.description)) {
+ return cmd.registeredArguments;
+ }
+ return [];
+ }
+ /**
+ * Get the command term to show in the list of subcommands.
+ *
+ * @param {Command} cmd
+ * @returns {string}
+ */
+ subcommandTerm(cmd) {
+ const args = cmd.registeredArguments.map((arg) => humanReadableArgName(arg)).join(" ");
+ return cmd._name + (cmd._aliases[0] ? "|" + cmd._aliases[0] : "") + (cmd.options.length ? " [options]" : "") + // simplistic check for non-help option
+ (args ? " " + args : "");
+ }
+ /**
+ * Get the option term to show in the list of options.
+ *
+ * @param {Option} option
+ * @returns {string}
+ */
+ optionTerm(option) {
+ return option.flags;
+ }
+ /**
+ * Get the argument term to show in the list of arguments.
+ *
+ * @param {Argument} argument
+ * @returns {string}
+ */
+ argumentTerm(argument) {
+ return argument.name();
+ }
+ /**
+ * Get the longest command term length.
+ *
+ * @param {Command} cmd
+ * @param {Help} helper
+ * @returns {number}
+ */
+ longestSubcommandTermLength(cmd, helper) {
+ return helper.visibleCommands(cmd).reduce((max, command) => {
+ return Math.max(
+ max,
+ this.displayWidth(
+ helper.styleSubcommandTerm(helper.subcommandTerm(command))
+ )
+ );
+ }, 0);
+ }
+ /**
+ * Get the longest option term length.
+ *
+ * @param {Command} cmd
+ * @param {Help} helper
+ * @returns {number}
+ */
+ longestOptionTermLength(cmd, helper) {
+ return helper.visibleOptions(cmd).reduce((max, option) => {
+ return Math.max(
+ max,
+ this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option)))
+ );
+ }, 0);
+ }
+ /**
+ * Get the longest global option term length.
+ *
+ * @param {Command} cmd
+ * @param {Help} helper
+ * @returns {number}
+ */
+ longestGlobalOptionTermLength(cmd, helper) {
+ return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
+ return Math.max(
+ max,
+ this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option)))
+ );
+ }, 0);
+ }
+ /**
+ * Get the longest argument term length.
+ *
+ * @param {Command} cmd
+ * @param {Help} helper
+ * @returns {number}
+ */
+ longestArgumentTermLength(cmd, helper) {
+ return helper.visibleArguments(cmd).reduce((max, argument) => {
+ return Math.max(
+ max,
+ this.displayWidth(
+ helper.styleArgumentTerm(helper.argumentTerm(argument))
+ )
+ );
+ }, 0);
+ }
+ /**
+ * Get the command usage to be displayed at the top of the built-in help.
+ *
+ * @param {Command} cmd
+ * @returns {string}
+ */
+ commandUsage(cmd) {
+ let cmdName = cmd._name;
+ if (cmd._aliases[0]) {
+ cmdName = cmdName + "|" + cmd._aliases[0];
+ }
+ let ancestorCmdNames = "";
+ for (let ancestorCmd = cmd.parent; ancestorCmd; ancestorCmd = ancestorCmd.parent) {
+ ancestorCmdNames = ancestorCmd.name() + " " + ancestorCmdNames;
+ }
+ return ancestorCmdNames + cmdName + " " + cmd.usage();
+ }
+ /**
+ * Get the description for the command.
+ *
+ * @param {Command} cmd
+ * @returns {string}
+ */
+ commandDescription(cmd) {
+ return cmd.description();
+ }
+ /**
+ * Get the subcommand summary to show in the list of subcommands.
+ * (Fallback to description for backwards compatibility.)
+ *
+ * @param {Command} cmd
+ * @returns {string}
+ */
+ subcommandDescription(cmd) {
+ return cmd.summary() || cmd.description();
+ }
+ /**
+ * Get the option description to show in the list of options.
+ *
+ * @param {Option} option
+ * @return {string}
+ */
+ optionDescription(option) {
+ const extraInfo = [];
+ if (option.argChoices) {
+ extraInfo.push(
+ // use stringify to match the display of the default value
+ `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`
+ );
+ }
+ if (option.defaultValue !== void 0) {
+ const showDefault = option.required || option.optional || option.isBoolean() && typeof option.defaultValue === "boolean";
+ if (showDefault) {
+ extraInfo.push(
+ `default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`
+ );
+ }
+ }
+ if (option.presetArg !== void 0 && option.optional) {
+ extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`);
+ }
+ if (option.envVar !== void 0) {
+ extraInfo.push(`env: ${option.envVar}`);
+ }
+ if (extraInfo.length > 0) {
+ const extraDescription = `(${extraInfo.join(", ")})`;
+ if (option.description) {
+ return `${option.description} ${extraDescription}`;
+ }
+ return extraDescription;
+ }
+ return option.description;
+ }
+ /**
+ * Get the argument description to show in the list of arguments.
+ *
+ * @param {Argument} argument
+ * @return {string}
+ */
+ argumentDescription(argument) {
+ const extraInfo = [];
+ if (argument.argChoices) {
+ extraInfo.push(
+ // use stringify to match the display of the default value
+ `choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`
+ );
+ }
+ if (argument.defaultValue !== void 0) {
+ extraInfo.push(
+ `default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`
+ );
+ }
+ if (extraInfo.length > 0) {
+ const extraDescription = `(${extraInfo.join(", ")})`;
+ if (argument.description) {
+ return `${argument.description} ${extraDescription}`;
+ }
+ return extraDescription;
+ }
+ return argument.description;
+ }
+ /**
+ * Format a list of items, given a heading and an array of formatted items.
+ *
+ * @param {string} heading
+ * @param {string[]} items
+ * @param {Help} helper
+ * @returns string[]
+ */
+ formatItemList(heading, items, helper) {
+ if (items.length === 0) return [];
+ return [helper.styleTitle(heading), ...items, ""];
+ }
+ /**
+ * Group items by their help group heading.
+ *
+ * @param {Command[] | Option[]} unsortedItems
+ * @param {Command[] | Option[]} visibleItems
+ * @param {Function} getGroup
+ * @returns {Map}
+ */
+ groupItems(unsortedItems, visibleItems, getGroup) {
+ const result = /* @__PURE__ */ new Map();
+ unsortedItems.forEach((item) => {
+ const group = getGroup(item);
+ if (!result.has(group)) result.set(group, []);
+ });
+ visibleItems.forEach((item) => {
+ const group = getGroup(item);
+ if (!result.has(group)) {
+ result.set(group, []);
+ }
+ result.get(group).push(item);
+ });
+ return result;
+ }
+ /**
+ * Generate the built-in help text.
+ *
+ * @param {Command} cmd
+ * @param {Help} helper
+ * @returns {string}
+ */
+ formatHelp(cmd, helper) {
+ const termWidth = helper.padWidth(cmd, helper);
+ const helpWidth = helper.helpWidth ?? 80;
+ function callFormatItem(term, description) {
+ return helper.formatItem(term, termWidth, description, helper);
+ }
+ let output = [
+ `${helper.styleTitle("Usage:")} ${helper.styleUsage(helper.commandUsage(cmd))}`,
+ ""
+ ];
+ const commandDescription = helper.commandDescription(cmd);
+ if (commandDescription.length > 0) {
+ output = output.concat([
+ helper.boxWrap(
+ helper.styleCommandDescription(commandDescription),
+ helpWidth
+ ),
+ ""
+ ]);
+ }
+ const argumentList = helper.visibleArguments(cmd).map((argument) => {
+ return callFormatItem(
+ helper.styleArgumentTerm(helper.argumentTerm(argument)),
+ helper.styleArgumentDescription(helper.argumentDescription(argument))
+ );
+ });
+ output = output.concat(
+ this.formatItemList("Arguments:", argumentList, helper)
+ );
+ const optionGroups = this.groupItems(
+ cmd.options,
+ helper.visibleOptions(cmd),
+ (option) => option.helpGroupHeading ?? "Options:"
+ );
+ optionGroups.forEach((options, group) => {
+ const optionList = options.map((option) => {
+ return callFormatItem(
+ helper.styleOptionTerm(helper.optionTerm(option)),
+ helper.styleOptionDescription(helper.optionDescription(option))
+ );
+ });
+ output = output.concat(this.formatItemList(group, optionList, helper));
+ });
+ if (helper.showGlobalOptions) {
+ const globalOptionList = helper.visibleGlobalOptions(cmd).map((option) => {
+ return callFormatItem(
+ helper.styleOptionTerm(helper.optionTerm(option)),
+ helper.styleOptionDescription(helper.optionDescription(option))
+ );
+ });
+ output = output.concat(
+ this.formatItemList("Global Options:", globalOptionList, helper)
+ );
+ }
+ const commandGroups = this.groupItems(
+ cmd.commands,
+ helper.visibleCommands(cmd),
+ (sub) => sub.helpGroup() || "Commands:"
+ );
+ commandGroups.forEach((commands, group) => {
+ const commandList = commands.map((sub) => {
+ return callFormatItem(
+ helper.styleSubcommandTerm(helper.subcommandTerm(sub)),
+ helper.styleSubcommandDescription(helper.subcommandDescription(sub))
+ );
+ });
+ output = output.concat(this.formatItemList(group, commandList, helper));
+ });
+ return output.join("\n");
+ }
+ /**
+ * Return display width of string, ignoring ANSI escape sequences. Used in padding and wrapping calculations.
+ *
+ * @param {string} str
+ * @returns {number}
+ */
+ displayWidth(str) {
+ return stripColor(str).length;
+ }
+ /**
+ * Style the title for displaying in the help. Called with 'Usage:', 'Options:', etc.
+ *
+ * @param {string} str
+ * @returns {string}
+ */
+ styleTitle(str) {
+ return str;
+ }
+ styleUsage(str) {
+ return str.split(" ").map((word) => {
+ if (word === "[options]") return this.styleOptionText(word);
+ if (word === "[command]") return this.styleSubcommandText(word);
+ if (word[0] === "[" || word[0] === "<")
+ return this.styleArgumentText(word);
+ return this.styleCommandText(word);
+ }).join(" ");
+ }
+ styleCommandDescription(str) {
+ return this.styleDescriptionText(str);
+ }
+ styleOptionDescription(str) {
+ return this.styleDescriptionText(str);
+ }
+ styleSubcommandDescription(str) {
+ return this.styleDescriptionText(str);
+ }
+ styleArgumentDescription(str) {
+ return this.styleDescriptionText(str);
+ }
+ styleDescriptionText(str) {
+ return str;
+ }
+ styleOptionTerm(str) {
+ return this.styleOptionText(str);
+ }
+ styleSubcommandTerm(str) {
+ return str.split(" ").map((word) => {
+ if (word === "[options]") return this.styleOptionText(word);
+ if (word[0] === "[" || word[0] === "<")
+ return this.styleArgumentText(word);
+ return this.styleSubcommandText(word);
+ }).join(" ");
+ }
+ styleArgumentTerm(str) {
+ return this.styleArgumentText(str);
+ }
+ styleOptionText(str) {
+ return str;
+ }
+ styleArgumentText(str) {
+ return str;
+ }
+ styleSubcommandText(str) {
+ return str;
+ }
+ styleCommandText(str) {
+ return str;
+ }
+ /**
+ * Calculate the pad width from the maximum term length.
+ *
+ * @param {Command} cmd
+ * @param {Help} helper
+ * @returns {number}
+ */
+ padWidth(cmd, helper) {
+ return Math.max(
+ helper.longestOptionTermLength(cmd, helper),
+ helper.longestGlobalOptionTermLength(cmd, helper),
+ helper.longestSubcommandTermLength(cmd, helper),
+ helper.longestArgumentTermLength(cmd, helper)
+ );
+ }
+ /**
+ * Detect manually wrapped and indented strings by checking for line break followed by whitespace.
+ *
+ * @param {string} str
+ * @returns {boolean}
+ */
+ preformatted(str) {
+ return /\n[^\S\r\n]/.test(str);
+ }
+ /**
+ * Format the "item", which consists of a term and description. Pad the term and wrap the description, indenting the following lines.
+ *
+ * So "TTT", 5, "DDD DDDD DD DDD" might be formatted for this.helpWidth=17 like so:
+ * TTT DDD DDDD
+ * DD DDD
+ *
+ * @param {string} term
+ * @param {number} termWidth
+ * @param {string} description
+ * @param {Help} helper
+ * @returns {string}
+ */
+ formatItem(term, termWidth, description, helper) {
+ const itemIndent = 2;
+ const itemIndentStr = " ".repeat(itemIndent);
+ if (!description) return itemIndentStr + term;
+ const paddedTerm = term.padEnd(
+ termWidth + term.length - helper.displayWidth(term)
+ );
+ const spacerWidth = 2;
+ const helpWidth = this.helpWidth ?? 80;
+ const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent;
+ let formattedDescription;
+ if (remainingWidth < this.minWidthToWrap || helper.preformatted(description)) {
+ formattedDescription = description;
+ } else {
+ const wrappedDescription = helper.boxWrap(description, remainingWidth);
+ formattedDescription = wrappedDescription.replace(
+ /\n/g,
+ "\n" + " ".repeat(termWidth + spacerWidth)
+ );
+ }
+ return itemIndentStr + paddedTerm + " ".repeat(spacerWidth) + formattedDescription.replace(/\n/g, `
+${itemIndentStr}`);
+ }
+ /**
+ * Wrap a string at whitespace, preserving existing line breaks.
+ * Wrapping is skipped if the width is less than `minWidthToWrap`.
+ *
+ * @param {string} str
+ * @param {number} width
+ * @returns {string}
+ */
+ boxWrap(str, width) {
+ if (width < this.minWidthToWrap) return str;
+ const rawLines = str.split(/\r\n|\n/);
+ const chunkPattern = /[\s]*[^\s]+/g;
+ const wrappedLines = [];
+ rawLines.forEach((line) => {
+ const chunks = line.match(chunkPattern);
+ if (chunks === null) {
+ wrappedLines.push("");
+ return;
+ }
+ let sumChunks = [chunks.shift()];
+ let sumWidth = this.displayWidth(sumChunks[0]);
+ chunks.forEach((chunk) => {
+ const visibleWidth = this.displayWidth(chunk);
+ if (sumWidth + visibleWidth <= width) {
+ sumChunks.push(chunk);
+ sumWidth += visibleWidth;
+ return;
+ }
+ wrappedLines.push(sumChunks.join(""));
+ const nextChunk = chunk.trimStart();
+ sumChunks = [nextChunk];
+ sumWidth = this.displayWidth(nextChunk);
+ });
+ wrappedLines.push(sumChunks.join(""));
+ });
+ return wrappedLines.join("\n");
+ }
+ };
+ function stripColor(str) {
+ const sgrPattern = /\x1b\[\d*(;\d*)*m/g;
+ return str.replace(sgrPattern, "");
+ }
+ exports2.Help = Help2;
+ exports2.stripColor = stripColor;
+ }
+});
+
+// node_modules/commander/lib/option.js
+var require_option = __commonJS({
+ "node_modules/commander/lib/option.js"(exports2) {
+ var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
+ var Option2 = class {
+ /**
+ * Initialize a new `Option` with the given `flags` and `description`.
+ *
+ * @param {string} flags
+ * @param {string} [description]
+ */
+ constructor(flags, description) {
+ this.flags = flags;
+ this.description = description || "";
+ this.required = flags.includes("<");
+ this.optional = flags.includes("[");
+ this.variadic = /\w\.\.\.[>\]]$/.test(flags);
+ this.mandatory = false;
+ const optionFlags = splitOptionFlags(flags);
+ this.short = optionFlags.shortFlag;
+ this.long = optionFlags.longFlag;
+ this.negate = false;
+ if (this.long) {
+ this.negate = this.long.startsWith("--no-");
+ }
+ this.defaultValue = void 0;
+ this.defaultValueDescription = void 0;
+ this.presetArg = void 0;
+ this.envVar = void 0;
+ this.parseArg = void 0;
+ this.hidden = false;
+ this.argChoices = void 0;
+ this.conflictsWith = [];
+ this.implied = void 0;
+ this.helpGroupHeading = void 0;
+ }
+ /**
+ * Set the default value, and optionally supply the description to be displayed in the help.
+ *
+ * @param {*} value
+ * @param {string} [description]
+ * @return {Option}
+ */
+ default(value, description) {
+ this.defaultValue = value;
+ this.defaultValueDescription = description;
+ return this;
+ }
+ /**
+ * Preset to use when option used without option-argument, especially optional but also boolean and negated.
+ * The custom processing (parseArg) is called.
+ *
+ * @example
+ * new Option('--color').default('GREYSCALE').preset('RGB');
+ * new Option('--donate [amount]').preset('20').argParser(parseFloat);
+ *
+ * @param {*} arg
+ * @return {Option}
+ */
+ preset(arg) {
+ this.presetArg = arg;
+ return this;
+ }
+ /**
+ * Add option name(s) that conflict with this option.
+ * An error will be displayed if conflicting options are found during parsing.
+ *
+ * @example
+ * new Option('--rgb').conflicts('cmyk');
+ * new Option('--js').conflicts(['ts', 'jsx']);
+ *
+ * @param {(string | string[])} names
+ * @return {Option}
+ */
+ conflicts(names) {
+ this.conflictsWith = this.conflictsWith.concat(names);
+ return this;
+ }
+ /**
+ * Specify implied option values for when this option is set and the implied options are not.
+ *
+ * The custom processing (parseArg) is not called on the implied values.
+ *
+ * @example
+ * program
+ * .addOption(new Option('--log', 'write logging information to file'))
+ * .addOption(new Option('--trace', 'log extra details').implies({ log: 'trace.txt' }));
+ *
+ * @param {object} impliedOptionValues
+ * @return {Option}
+ */
+ implies(impliedOptionValues) {
+ let newImplied = impliedOptionValues;
+ if (typeof impliedOptionValues === "string") {
+ newImplied = { [impliedOptionValues]: true };
+ }
+ this.implied = Object.assign(this.implied || {}, newImplied);
+ return this;
+ }
+ /**
+ * Set environment variable to check for option value.
+ *
+ * An environment variable is only used if when processed the current option value is
+ * undefined, or the source of the current value is 'default' or 'config' or 'env'.
+ *
+ * @param {string} name
+ * @return {Option}
+ */
+ env(name) {
+ this.envVar = name;
+ return this;
+ }
+ /**
+ * Set the custom handler for processing CLI option arguments into option values.
+ *
+ * @param {Function} [fn]
+ * @return {Option}
+ */
+ argParser(fn) {
+ this.parseArg = fn;
+ return this;
+ }
+ /**
+ * Whether the option is mandatory and must have a value after parsing.
+ *
+ * @param {boolean} [mandatory=true]
+ * @return {Option}
+ */
+ makeOptionMandatory(mandatory = true) {
+ this.mandatory = !!mandatory;
+ return this;
+ }
+ /**
+ * Hide option in help.
+ *
+ * @param {boolean} [hide=true]
+ * @return {Option}
+ */
+ hideHelp(hide = true) {
+ this.hidden = !!hide;
+ return this;
+ }
+ /**
+ * @package
+ */
+ _collectValue(value, previous) {
+ if (previous === this.defaultValue || !Array.isArray(previous)) {
+ return [value];
+ }
+ previous.push(value);
+ return previous;
+ }
+ /**
+ * Only allow option value to be one of choices.
+ *
+ * @param {string[]} values
+ * @return {Option}
+ */
+ choices(values) {
+ this.argChoices = values.slice();
+ this.parseArg = (arg, previous) => {
+ if (!this.argChoices.includes(arg)) {
+ throw new InvalidArgumentError2(
+ `Allowed choices are ${this.argChoices.join(", ")}.`
+ );
+ }
+ if (this.variadic) {
+ return this._collectValue(arg, previous);
+ }
+ return arg;
+ };
+ return this;
+ }
+ /**
+ * Return option name.
+ *
+ * @return {string}
+ */
+ name() {
+ if (this.long) {
+ return this.long.replace(/^--/, "");
+ }
+ return this.short.replace(/^-/, "");
+ }
+ /**
+ * Return option name, in a camelcase format that can be used
+ * as an object attribute key.
+ *
+ * @return {string}
+ */
+ attributeName() {
+ if (this.negate) {
+ return camelcase(this.name().replace(/^no-/, ""));
+ }
+ return camelcase(this.name());
+ }
+ /**
+ * Set the help group heading.
+ *
+ * @param {string} heading
+ * @return {Option}
+ */
+ helpGroup(heading) {
+ this.helpGroupHeading = heading;
+ return this;
+ }
+ /**
+ * Check if `arg` matches the short or long flag.
+ *
+ * @param {string} arg
+ * @return {boolean}
+ * @package
+ */
+ is(arg) {
+ return this.short === arg || this.long === arg;
+ }
+ /**
+ * Return whether a boolean option.
+ *
+ * Options are one of boolean, negated, required argument, or optional argument.
+ *
+ * @return {boolean}
+ * @package
+ */
+ isBoolean() {
+ return !this.required && !this.optional && !this.negate;
+ }
+ };
+ var DualOptions = class {
+ /**
+ * @param {Option[]} options
+ */
+ constructor(options) {
+ this.positiveOptions = /* @__PURE__ */ new Map();
+ this.negativeOptions = /* @__PURE__ */ new Map();
+ this.dualOptions = /* @__PURE__ */ new Set();
+ options.forEach((option) => {
+ if (option.negate) {
+ this.negativeOptions.set(option.attributeName(), option);
+ } else {
+ this.positiveOptions.set(option.attributeName(), option);
+ }
+ });
+ this.negativeOptions.forEach((value, key) => {
+ if (this.positiveOptions.has(key)) {
+ this.dualOptions.add(key);
+ }
+ });
+ }
+ /**
+ * Did the value come from the option, and not from possible matching dual option?
+ *
+ * @param {*} value
+ * @param {Option} option
+ * @returns {boolean}
+ */
+ valueFromOption(value, option) {
+ const optionKey = option.attributeName();
+ if (!this.dualOptions.has(optionKey)) return true;
+ const preset = this.negativeOptions.get(optionKey).presetArg;
+ const negativeValue = preset !== void 0 ? preset : false;
+ return option.negate === (negativeValue === value);
+ }
+ };
+ function camelcase(str) {
+ return str.split("-").reduce((str2, word) => {
+ return str2 + word[0].toUpperCase() + word.slice(1);
+ });
+ }
+ function splitOptionFlags(flags) {
+ let shortFlag;
+ let longFlag;
+ const shortFlagExp = /^-[^-]$/;
+ const longFlagExp = /^--[^-]/;
+ const flagParts = flags.split(/[ |,]+/).concat("guard");
+ if (shortFlagExp.test(flagParts[0])) shortFlag = flagParts.shift();
+ if (longFlagExp.test(flagParts[0])) longFlag = flagParts.shift();
+ if (!shortFlag && shortFlagExp.test(flagParts[0]))
+ shortFlag = flagParts.shift();
+ if (!shortFlag && longFlagExp.test(flagParts[0])) {
+ shortFlag = longFlag;
+ longFlag = flagParts.shift();
+ }
+ if (flagParts[0].startsWith("-")) {
+ const unsupportedFlag = flagParts[0];
+ const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags}'`;
+ if (/^-[^-][^-]/.test(unsupportedFlag))
+ throw new Error(
+ `${baseError}
+- a short flag is a single dash and a single character
+ - either use a single dash and a single character (for a short flag)
+ - or use a double dash for a long option (and can have two, like '--ws, --workspace')`
+ );
+ if (shortFlagExp.test(unsupportedFlag))
+ throw new Error(`${baseError}
+- too many short flags`);
+ if (longFlagExp.test(unsupportedFlag))
+ throw new Error(`${baseError}
+- too many long flags`);
+ throw new Error(`${baseError}
+- unrecognised flag format`);
+ }
+ if (shortFlag === void 0 && longFlag === void 0)
+ throw new Error(
+ `option creation failed due to no flags found in '${flags}'.`
+ );
+ return { shortFlag, longFlag };
+ }
+ exports2.Option = Option2;
+ exports2.DualOptions = DualOptions;
+ }
+});
+
+// node_modules/commander/lib/suggestSimilar.js
+var require_suggestSimilar = __commonJS({
+ "node_modules/commander/lib/suggestSimilar.js"(exports2) {
+ var maxDistance = 3;
+ function editDistance(a, b) {
+ if (Math.abs(a.length - b.length) > maxDistance)
+ return Math.max(a.length, b.length);
+ const d = [];
+ for (let i = 0; i <= a.length; i++) {
+ d[i] = [i];
+ }
+ for (let j = 0; j <= b.length; j++) {
+ d[0][j] = j;
+ }
+ for (let j = 1; j <= b.length; j++) {
+ for (let i = 1; i <= a.length; i++) {
+ let cost = 1;
+ if (a[i - 1] === b[j - 1]) {
+ cost = 0;
+ } else {
+ cost = 1;
+ }
+ d[i][j] = Math.min(
+ d[i - 1][j] + 1,
+ // deletion
+ d[i][j - 1] + 1,
+ // insertion
+ d[i - 1][j - 1] + cost
+ // substitution
+ );
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
+ d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
+ }
+ }
+ }
+ return d[a.length][b.length];
+ }
+ function suggestSimilar(word, candidates) {
+ if (!candidates || candidates.length === 0) return "";
+ candidates = Array.from(new Set(candidates));
+ const searchingOptions = word.startsWith("--");
+ if (searchingOptions) {
+ word = word.slice(2);
+ candidates = candidates.map((candidate) => candidate.slice(2));
+ }
+ let similar = [];
+ let bestDistance = maxDistance;
+ const minSimilarity = 0.4;
+ candidates.forEach((candidate) => {
+ if (candidate.length <= 1) return;
+ const distance = editDistance(word, candidate);
+ const length = Math.max(word.length, candidate.length);
+ const similarity = (length - distance) / length;
+ if (similarity > minSimilarity) {
+ if (distance < bestDistance) {
+ bestDistance = distance;
+ similar = [candidate];
+ } else if (distance === bestDistance) {
+ similar.push(candidate);
+ }
+ }
+ });
+ similar.sort((a, b) => a.localeCompare(b));
+ if (searchingOptions) {
+ similar = similar.map((candidate) => `--${candidate}`);
+ }
+ if (similar.length > 1) {
+ return `
+(Did you mean one of ${similar.join(", ")}?)`;
+ }
+ if (similar.length === 1) {
+ return `
+(Did you mean ${similar[0]}?)`;
+ }
+ return "";
+ }
+ exports2.suggestSimilar = suggestSimilar;
+ }
+});
+
+// node_modules/commander/lib/command.js
+var require_command = __commonJS({
+ "node_modules/commander/lib/command.js"(exports2) {
+ var EventEmitter = require("node:events").EventEmitter;
+ var childProcess = require("node:child_process");
+ var path20 = require("node:path");
+ var fs14 = require("node:fs");
+ var process2 = require("node:process");
+ var { Argument: Argument2, humanReadableArgName } = require_argument();
+ var { CommanderError: CommanderError2 } = require_error();
+ var { Help: Help2, stripColor } = require_help();
+ var { Option: Option2, DualOptions } = require_option();
+ var { suggestSimilar } = require_suggestSimilar();
+ var Command2 = class _Command extends EventEmitter {
+ /**
+ * Initialize a new `Command`.
+ *
+ * @param {string} [name]
+ */
+ constructor(name) {
+ super();
+ this.commands = [];
+ this.options = [];
+ this.parent = null;
+ this._allowUnknownOption = false;
+ this._allowExcessArguments = false;
+ this.registeredArguments = [];
+ this._args = this.registeredArguments;
+ this.args = [];
+ this.rawArgs = [];
+ this.processedArgs = [];
+ this._scriptPath = null;
+ this._name = name || "";
+ this._optionValues = {};
+ this._optionValueSources = {};
+ this._storeOptionsAsProperties = false;
+ this._actionHandler = null;
+ this._executableHandler = false;
+ this._executableFile = null;
+ this._executableDir = null;
+ this._defaultCommandName = null;
+ this._exitCallback = null;
+ this._aliases = [];
+ this._combineFlagAndOptionalValue = true;
+ this._description = "";
+ this._summary = "";
+ this._argsDescription = void 0;
+ this._enablePositionalOptions = false;
+ this._passThroughOptions = false;
+ this._lifeCycleHooks = {};
+ this._showHelpAfterError = false;
+ this._showSuggestionAfterError = true;
+ this._savedState = null;
+ this._outputConfiguration = {
+ writeOut: (str) => process2.stdout.write(str),
+ writeErr: (str) => process2.stderr.write(str),
+ outputError: (str, write) => write(str),
+ getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : void 0,
+ getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : void 0,
+ getOutHasColors: () => useColor() ?? (process2.stdout.isTTY && process2.stdout.hasColors?.()),
+ getErrHasColors: () => useColor() ?? (process2.stderr.isTTY && process2.stderr.hasColors?.()),
+ stripColor: (str) => stripColor(str)
+ };
+ this._hidden = false;
+ this._helpOption = void 0;
+ this._addImplicitHelpCommand = void 0;
+ this._helpCommand = void 0;
+ this._helpConfiguration = {};
+ this._helpGroupHeading = void 0;
+ this._defaultCommandGroup = void 0;
+ this._defaultOptionGroup = void 0;
+ }
+ /**
+ * Copy settings that are useful to have in common across root command and subcommands.
+ *
+ * (Used internally when adding a command using `.command()` so subcommands inherit parent settings.)
+ *
+ * @param {Command} sourceCommand
+ * @return {Command} `this` command for chaining
+ */
+ copyInheritedSettings(sourceCommand) {
+ this._outputConfiguration = sourceCommand._outputConfiguration;
+ this._helpOption = sourceCommand._helpOption;
+ this._helpCommand = sourceCommand._helpCommand;
+ this._helpConfiguration = sourceCommand._helpConfiguration;
+ this._exitCallback = sourceCommand._exitCallback;
+ this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;
+ this._combineFlagAndOptionalValue = sourceCommand._combineFlagAndOptionalValue;
+ this._allowExcessArguments = sourceCommand._allowExcessArguments;
+ this._enablePositionalOptions = sourceCommand._enablePositionalOptions;
+ this._showHelpAfterError = sourceCommand._showHelpAfterError;
+ this._showSuggestionAfterError = sourceCommand._showSuggestionAfterError;
+ return this;
+ }
+ /**
+ * @returns {Command[]}
+ * @private
+ */
+ _getCommandAndAncestors() {
+ const result = [];
+ for (let command = this; command; command = command.parent) {
+ result.push(command);
+ }
+ return result;
+ }
+ /**
+ * Define a command.
+ *
+ * There are two styles of command: pay attention to where to put the description.
+ *
+ * @example
+ * // Command implemented using action handler (description is supplied separately to `.command`)
+ * program
+ * .command('clone [destination]')
+ * .description('clone a repository into a newly created directory')
+ * .action((source, destination) => {
+ * console.log('clone command called');
+ * });
+ *
+ * // Command implemented using separate executable file (description is second parameter to `.command`)
+ * program
+ * .command('start ', 'start named service')
+ * .command('stop [service]', 'stop named service, or all if no name supplied');
+ *
+ * @param {string} nameAndArgs - command name and arguments, args are `` or `[optional]` and last may also be `variadic...`
+ * @param {(object | string)} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)
+ * @param {object} [execOpts] - configuration options (for executable)
+ * @return {Command} returns new command for action handler, or `this` for executable command
+ */
+ command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
+ let desc = actionOptsOrExecDesc;
+ let opts = execOpts;
+ if (typeof desc === "object" && desc !== null) {
+ opts = desc;
+ desc = null;
+ }
+ opts = opts || {};
+ const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/);
+ const cmd = this.createCommand(name);
+ if (desc) {
+ cmd.description(desc);
+ cmd._executableHandler = true;
+ }
+ if (opts.isDefault) this._defaultCommandName = cmd._name;
+ cmd._hidden = !!(opts.noHelp || opts.hidden);
+ cmd._executableFile = opts.executableFile || null;
+ if (args) cmd.arguments(args);
+ this._registerCommand(cmd);
+ cmd.parent = this;
+ cmd.copyInheritedSettings(this);
+ if (desc) return this;
+ return cmd;
+ }
+ /**
+ * Factory routine to create a new unattached command.
+ *
+ * See .command() for creating an attached subcommand, which uses this routine to
+ * create the command. You can override createCommand to customise subcommands.
+ *
+ * @param {string} [name]
+ * @return {Command} new command
+ */
+ createCommand(name) {
+ return new _Command(name);
+ }
+ /**
+ * You can customise the help with a subclass of Help by overriding createHelp,
+ * or by overriding Help properties using configureHelp().
+ *
+ * @return {Help}
+ */
+ createHelp() {
+ return Object.assign(new Help2(), this.configureHelp());
+ }
+ /**
+ * You can customise the help by overriding Help properties using configureHelp(),
+ * or with a subclass of Help by overriding createHelp().
+ *
+ * @param {object} [configuration] - configuration options
+ * @return {(Command | object)} `this` command for chaining, or stored configuration
+ */
+ configureHelp(configuration) {
+ if (configuration === void 0) return this._helpConfiguration;
+ this._helpConfiguration = configuration;
+ return this;
+ }
+ /**
+ * The default output goes to stdout and stderr. You can customise this for special
+ * applications. You can also customise the display of errors by overriding outputError.
+ *
+ * The configuration properties are all functions:
+ *
+ * // change how output being written, defaults to stdout and stderr
+ * writeOut(str)
+ * writeErr(str)
+ * // change how output being written for errors, defaults to writeErr
+ * outputError(str, write) // used for displaying errors and not used for displaying help
+ * // specify width for wrapping help
+ * getOutHelpWidth()
+ * getErrHelpWidth()
+ * // color support, currently only used with Help
+ * getOutHasColors()
+ * getErrHasColors()
+ * stripColor() // used to remove ANSI escape codes if output does not have colors
+ *
+ * @param {object} [configuration] - configuration options
+ * @return {(Command | object)} `this` command for chaining, or stored configuration
+ */
+ configureOutput(configuration) {
+ if (configuration === void 0) return this._outputConfiguration;
+ this._outputConfiguration = {
+ ...this._outputConfiguration,
+ ...configuration
+ };
+ return this;
+ }
+ /**
+ * Display the help or a custom message after an error occurs.
+ *
+ * @param {(boolean|string)} [displayHelp]
+ * @return {Command} `this` command for chaining
+ */
+ showHelpAfterError(displayHelp = true) {
+ if (typeof displayHelp !== "string") displayHelp = !!displayHelp;
+ this._showHelpAfterError = displayHelp;
+ return this;
+ }
+ /**
+ * Display suggestion of similar commands for unknown commands, or options for unknown options.
+ *
+ * @param {boolean} [displaySuggestion]
+ * @return {Command} `this` command for chaining
+ */
+ showSuggestionAfterError(displaySuggestion = true) {
+ this._showSuggestionAfterError = !!displaySuggestion;
+ return this;
+ }
+ /**
+ * Add a prepared subcommand.
+ *
+ * See .command() for creating an attached subcommand which inherits settings from its parent.
+ *
+ * @param {Command} cmd - new subcommand
+ * @param {object} [opts] - configuration options
+ * @return {Command} `this` command for chaining
+ */
+ addCommand(cmd, opts) {
+ if (!cmd._name) {
+ throw new Error(`Command passed to .addCommand() must have a name
+- specify the name in Command constructor or using .name()`);
+ }
+ opts = opts || {};
+ if (opts.isDefault) this._defaultCommandName = cmd._name;
+ if (opts.noHelp || opts.hidden) cmd._hidden = true;
+ this._registerCommand(cmd);
+ cmd.parent = this;
+ cmd._checkForBrokenPassThrough();
+ return this;
+ }
+ /**
+ * Factory routine to create a new unattached argument.
+ *
+ * See .argument() for creating an attached argument, which uses this routine to
+ * create the argument. You can override createArgument to return a custom argument.
+ *
+ * @param {string} name
+ * @param {string} [description]
+ * @return {Argument} new argument
+ */
+ createArgument(name, description) {
+ return new Argument2(name, description);
+ }
+ /**
+ * Define argument syntax for command.
+ *
+ * The default is that the argument is required, and you can explicitly
+ * indicate this with <> around the name. Put [] around the name for an optional argument.
+ *
+ * @example
+ * program.argument('');
+ * program.argument('[output-file]');
+ *
+ * @param {string} name
+ * @param {string} [description]
+ * @param {(Function|*)} [parseArg] - custom argument processing function or default value
+ * @param {*} [defaultValue]
+ * @return {Command} `this` command for chaining
+ */
+ argument(name, description, parseArg, defaultValue) {
+ const argument = this.createArgument(name, description);
+ if (typeof parseArg === "function") {
+ argument.default(defaultValue).argParser(parseArg);
+ } else {
+ argument.default(parseArg);
+ }
+ this.addArgument(argument);
+ return this;
+ }
+ /**
+ * Define argument syntax for command, adding multiple at once (without descriptions).
+ *
+ * See also .argument().
+ *
+ * @example
+ * program.arguments(' [env]');
+ *
+ * @param {string} names
+ * @return {Command} `this` command for chaining
+ */
+ arguments(names) {
+ names.trim().split(/ +/).forEach((detail) => {
+ this.argument(detail);
+ });
+ return this;
+ }
+ /**
+ * Define argument syntax for command, adding a prepared argument.
+ *
+ * @param {Argument} argument
+ * @return {Command} `this` command for chaining
+ */
+ addArgument(argument) {
+ const previousArgument = this.registeredArguments.slice(-1)[0];
+ if (previousArgument?.variadic) {
+ throw new Error(
+ `only the last argument can be variadic '${previousArgument.name()}'`
+ );
+ }
+ if (argument.required && argument.defaultValue !== void 0 && argument.parseArg === void 0) {
+ throw new Error(
+ `a default value for a required argument is never used: '${argument.name()}'`
+ );
+ }
+ this.registeredArguments.push(argument);
+ return this;
+ }
+ /**
+ * Customise or override default help command. By default a help command is automatically added if your command has subcommands.
+ *
+ * @example
+ * program.helpCommand('help [cmd]');
+ * program.helpCommand('help [cmd]', 'show help');
+ * program.helpCommand(false); // suppress default help command
+ * program.helpCommand(true); // add help command even if no subcommands
+ *
+ * @param {string|boolean} enableOrNameAndArgs - enable with custom name and/or arguments, or boolean to override whether added
+ * @param {string} [description] - custom description
+ * @return {Command} `this` command for chaining
+ */
+ helpCommand(enableOrNameAndArgs, description) {
+ if (typeof enableOrNameAndArgs === "boolean") {
+ this._addImplicitHelpCommand = enableOrNameAndArgs;
+ if (enableOrNameAndArgs && this._defaultCommandGroup) {
+ this._initCommandGroup(this._getHelpCommand());
+ }
+ return this;
+ }
+ const nameAndArgs = enableOrNameAndArgs ?? "help [command]";
+ const [, helpName, helpArgs] = nameAndArgs.match(/([^ ]+) *(.*)/);
+ const helpDescription = description ?? "display help for command";
+ const helpCommand = this.createCommand(helpName);
+ helpCommand.helpOption(false);
+ if (helpArgs) helpCommand.arguments(helpArgs);
+ if (helpDescription) helpCommand.description(helpDescription);
+ this._addImplicitHelpCommand = true;
+ this._helpCommand = helpCommand;
+ if (enableOrNameAndArgs || description) this._initCommandGroup(helpCommand);
+ return this;
+ }
+ /**
+ * Add prepared custom help command.
+ *
+ * @param {(Command|string|boolean)} helpCommand - custom help command, or deprecated enableOrNameAndArgs as for `.helpCommand()`
+ * @param {string} [deprecatedDescription] - deprecated custom description used with custom name only
+ * @return {Command} `this` command for chaining
+ */
+ addHelpCommand(helpCommand, deprecatedDescription) {
+ if (typeof helpCommand !== "object") {
+ this.helpCommand(helpCommand, deprecatedDescription);
+ return this;
+ }
+ this._addImplicitHelpCommand = true;
+ this._helpCommand = helpCommand;
+ this._initCommandGroup(helpCommand);
+ return this;
+ }
+ /**
+ * Lazy create help command.
+ *
+ * @return {(Command|null)}
+ * @package
+ */
+ _getHelpCommand() {
+ const hasImplicitHelpCommand = this._addImplicitHelpCommand ?? (this.commands.length && !this._actionHandler && !this._findCommand("help"));
+ if (hasImplicitHelpCommand) {
+ if (this._helpCommand === void 0) {
+ this.helpCommand(void 0, void 0);
+ }
+ return this._helpCommand;
+ }
+ return null;
+ }
+ /**
+ * Add hook for life cycle event.
+ *
+ * @param {string} event
+ * @param {Function} listener
+ * @return {Command} `this` command for chaining
+ */
+ hook(event, listener) {
+ const allowedValues = ["preSubcommand", "preAction", "postAction"];
+ if (!allowedValues.includes(event)) {
+ throw new Error(`Unexpected value for event passed to hook : '${event}'.
+Expecting one of '${allowedValues.join("', '")}'`);
+ }
+ if (this._lifeCycleHooks[event]) {
+ this._lifeCycleHooks[event].push(listener);
+ } else {
+ this._lifeCycleHooks[event] = [listener];
+ }
+ return this;
+ }
+ /**
+ * Register callback to use as replacement for calling process.exit.
+ *
+ * @param {Function} [fn] optional callback which will be passed a CommanderError, defaults to throwing
+ * @return {Command} `this` command for chaining
+ */
+ exitOverride(fn) {
+ if (fn) {
+ this._exitCallback = fn;
+ } else {
+ this._exitCallback = (err) => {
+ if (err.code !== "commander.executeSubCommandAsync") {
+ throw err;
+ } else {
+ }
+ };
+ }
+ return this;
+ }
+ /**
+ * Call process.exit, and _exitCallback if defined.
+ *
+ * @param {number} exitCode exit code for using with process.exit
+ * @param {string} code an id string representing the error
+ * @param {string} message human-readable description of the error
+ * @return never
+ * @private
+ */
+ _exit(exitCode, code, message) {
+ if (this._exitCallback) {
+ this._exitCallback(new CommanderError2(exitCode, code, message));
+ }
+ process2.exit(exitCode);
+ }
+ /**
+ * Register callback `fn` for the command.
+ *
+ * @example
+ * program
+ * .command('serve')
+ * .description('start service')
+ * .action(function() {
+ * // do work here
+ * });
+ *
+ * @param {Function} fn
+ * @return {Command} `this` command for chaining
+ */
+ action(fn) {
+ const listener = (args) => {
+ const expectedArgsCount = this.registeredArguments.length;
+ const actionArgs = args.slice(0, expectedArgsCount);
+ if (this._storeOptionsAsProperties) {
+ actionArgs[expectedArgsCount] = this;
+ } else {
+ actionArgs[expectedArgsCount] = this.opts();
+ }
+ actionArgs.push(this);
+ return fn.apply(this, actionArgs);
+ };
+ this._actionHandler = listener;
+ return this;
+ }
+ /**
+ * Factory routine to create a new unattached option.
+ *
+ * See .option() for creating an attached option, which uses this routine to
+ * create the option. You can override createOption to return a custom option.
+ *
+ * @param {string} flags
+ * @param {string} [description]
+ * @return {Option} new option
+ */
+ createOption(flags, description) {
+ return new Option2(flags, description);
+ }
+ /**
+ * Wrap parseArgs to catch 'commander.invalidArgument'.
+ *
+ * @param {(Option | Argument)} target
+ * @param {string} value
+ * @param {*} previous
+ * @param {string} invalidArgumentMessage
+ * @private
+ */
+ _callParseArg(target, value, previous, invalidArgumentMessage) {
+ try {
+ return target.parseArg(value, previous);
+ } catch (err) {
+ if (err.code === "commander.invalidArgument") {
+ const message = `${invalidArgumentMessage} ${err.message}`;
+ this.error(message, { exitCode: err.exitCode, code: err.code });
+ }
+ throw err;
+ }
+ }
+ /**
+ * Check for option flag conflicts.
+ * Register option if no conflicts found, or throw on conflict.
+ *
+ * @param {Option} option
+ * @private
+ */
+ _registerOption(option) {
+ const matchingOption = option.short && this._findOption(option.short) || option.long && this._findOption(option.long);
+ if (matchingOption) {
+ const matchingFlag = option.long && this._findOption(option.long) ? option.long : option.short;
+ throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'
+- already used by option '${matchingOption.flags}'`);
+ }
+ this._initOptionGroup(option);
+ this.options.push(option);
+ }
+ /**
+ * Check for command name and alias conflicts with existing commands.
+ * Register command if no conflicts found, or throw on conflict.
+ *
+ * @param {Command} command
+ * @private
+ */
+ _registerCommand(command) {
+ const knownBy = (cmd) => {
+ return [cmd.name()].concat(cmd.aliases());
+ };
+ const alreadyUsed = knownBy(command).find(
+ (name) => this._findCommand(name)
+ );
+ if (alreadyUsed) {
+ const existingCmd = knownBy(this._findCommand(alreadyUsed)).join("|");
+ const newCmd = knownBy(command).join("|");
+ throw new Error(
+ `cannot add command '${newCmd}' as already have command '${existingCmd}'`
+ );
+ }
+ this._initCommandGroup(command);
+ this.commands.push(command);
+ }
+ /**
+ * Add an option.
+ *
+ * @param {Option} option
+ * @return {Command} `this` command for chaining
+ */
+ addOption(option) {
+ this._registerOption(option);
+ const oname = option.name();
+ const name = option.attributeName();
+ if (option.negate) {
+ const positiveLongFlag = option.long.replace(/^--no-/, "--");
+ if (!this._findOption(positiveLongFlag)) {
+ this.setOptionValueWithSource(
+ name,
+ option.defaultValue === void 0 ? true : option.defaultValue,
+ "default"
+ );
+ }
+ } else if (option.defaultValue !== void 0) {
+ this.setOptionValueWithSource(name, option.defaultValue, "default");
+ }
+ const handleOptionValue = (val, invalidValueMessage, valueSource) => {
+ if (val == null && option.presetArg !== void 0) {
+ val = option.presetArg;
+ }
+ const oldValue = this.getOptionValue(name);
+ if (val !== null && option.parseArg) {
+ val = this._callParseArg(option, val, oldValue, invalidValueMessage);
+ } else if (val !== null && option.variadic) {
+ val = option._collectValue(val, oldValue);
+ }
+ if (val == null) {
+ if (option.negate) {
+ val = false;
+ } else if (option.isBoolean() || option.optional) {
+ val = true;
+ } else {
+ val = "";
+ }
+ }
+ this.setOptionValueWithSource(name, val, valueSource);
+ };
+ this.on("option:" + oname, (val) => {
+ const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
+ handleOptionValue(val, invalidValueMessage, "cli");
+ });
+ if (option.envVar) {
+ this.on("optionEnv:" + oname, (val) => {
+ const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
+ handleOptionValue(val, invalidValueMessage, "env");
+ });
+ }
+ return this;
+ }
+ /**
+ * Internal implementation shared by .option() and .requiredOption()
+ *
+ * @return {Command} `this` command for chaining
+ * @private
+ */
+ _optionEx(config, flags, description, fn, defaultValue) {
+ if (typeof flags === "object" && flags instanceof Option2) {
+ throw new Error(
+ "To add an Option object use addOption() instead of option() or requiredOption()"
+ );
+ }
+ const option = this.createOption(flags, description);
+ option.makeOptionMandatory(!!config.mandatory);
+ if (typeof fn === "function") {
+ option.default(defaultValue).argParser(fn);
+ } else if (fn instanceof RegExp) {
+ const regex = fn;
+ fn = (val, def) => {
+ const m = regex.exec(val);
+ return m ? m[0] : def;
+ };
+ option.default(defaultValue).argParser(fn);
+ } else {
+ option.default(fn);
+ }
+ return this.addOption(option);
+ }
+ /**
+ * Define option with `flags`, `description`, and optional argument parsing function or `defaultValue` or both.
+ *
+ * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. A required
+ * option-argument is indicated by `<>` and an optional option-argument by `[]`.
+ *
+ * See the README for more details, and see also addOption() and requiredOption().
+ *
+ * @example
+ * program
+ * .option('-p, --pepper', 'add pepper')
+ * .option('--pt, --pizza-type ', 'type of pizza') // required option-argument
+ * .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default
+ * .option('-t, --tip ', 'add tip to purchase cost', parseFloat) // custom parse function
+ *
+ * @param {string} flags
+ * @param {string} [description]
+ * @param {(Function|*)} [parseArg] - custom option processing function or default value
+ * @param {*} [defaultValue]
+ * @return {Command} `this` command for chaining
+ */
+ option(flags, description, parseArg, defaultValue) {
+ return this._optionEx({}, flags, description, parseArg, defaultValue);
+ }
+ /**
+ * Add a required option which must have a value after parsing. This usually means
+ * the option must be specified on the command line. (Otherwise the same as .option().)
+ *
+ * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space.
+ *
+ * @param {string} flags
+ * @param {string} [description]
+ * @param {(Function|*)} [parseArg] - custom option processing function or default value
+ * @param {*} [defaultValue]
+ * @return {Command} `this` command for chaining
+ */
+ requiredOption(flags, description, parseArg, defaultValue) {
+ return this._optionEx(
+ { mandatory: true },
+ flags,
+ description,
+ parseArg,
+ defaultValue
+ );
+ }
+ /**
+ * Alter parsing of short flags with optional values.
+ *
+ * @example
+ * // for `.option('-f,--flag [value]'):
+ * program.combineFlagAndOptionalValue(true); // `-f80` is treated like `--flag=80`, this is the default behaviour
+ * program.combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
+ *
+ * @param {boolean} [combine] - if `true` or omitted, an optional value can be specified directly after the flag.
+ * @return {Command} `this` command for chaining
+ */
+ combineFlagAndOptionalValue(combine = true) {
+ this._combineFlagAndOptionalValue = !!combine;
+ return this;
+ }
+ /**
+ * Allow unknown options on the command line.
+ *
+ * @param {boolean} [allowUnknown] - if `true` or omitted, no error will be thrown for unknown options.
+ * @return {Command} `this` command for chaining
+ */
+ allowUnknownOption(allowUnknown = true) {
+ this._allowUnknownOption = !!allowUnknown;
+ return this;
+ }
+ /**
+ * Allow excess command-arguments on the command line. Pass false to make excess arguments an error.
+ *
+ * @param {boolean} [allowExcess] - if `true` or omitted, no error will be thrown for excess arguments.
+ * @return {Command} `this` command for chaining
+ */
+ allowExcessArguments(allowExcess = true) {
+ this._allowExcessArguments = !!allowExcess;
+ return this;
+ }
+ /**
+ * Enable positional options. Positional means global options are specified before subcommands which lets
+ * subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions.
+ * The default behaviour is non-positional and global options may appear anywhere on the command line.
+ *
+ * @param {boolean} [positional]
+ * @return {Command} `this` command for chaining
+ */
+ enablePositionalOptions(positional = true) {
+ this._enablePositionalOptions = !!positional;
+ return this;
+ }
+ /**
+ * Pass through options that come after command-arguments rather than treat them as command-options,
+ * so actual command-options come before command-arguments. Turning this on for a subcommand requires
+ * positional options to have been enabled on the program (parent commands).
+ * The default behaviour is non-positional and options may appear before or after command-arguments.
+ *
+ * @param {boolean} [passThrough] for unknown options.
+ * @return {Command} `this` command for chaining
+ */
+ passThroughOptions(passThrough = true) {
+ this._passThroughOptions = !!passThrough;
+ this._checkForBrokenPassThrough();
+ return this;
+ }
+ /**
+ * @private
+ */
+ _checkForBrokenPassThrough() {
+ if (this.parent && this._passThroughOptions && !this.parent._enablePositionalOptions) {
+ throw new Error(
+ `passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)`
+ );
+ }
+ }
+ /**
+ * Whether to store option values as properties on command object,
+ * or store separately (specify false). In both cases the option values can be accessed using .opts().
+ *
+ * @param {boolean} [storeAsProperties=true]
+ * @return {Command} `this` command for chaining
+ */
+ storeOptionsAsProperties(storeAsProperties = true) {
+ if (this.options.length) {
+ throw new Error("call .storeOptionsAsProperties() before adding options");
+ }
+ if (Object.keys(this._optionValues).length) {
+ throw new Error(
+ "call .storeOptionsAsProperties() before setting option values"
+ );
+ }
+ this._storeOptionsAsProperties = !!storeAsProperties;
+ return this;
+ }
+ /**
+ * Retrieve option value.
+ *
+ * @param {string} key
+ * @return {object} value
+ */
+ getOptionValue(key) {
+ if (this._storeOptionsAsProperties) {
+ return this[key];
+ }
+ return this._optionValues[key];
+ }
+ /**
+ * Store option value.
+ *
+ * @param {string} key
+ * @param {object} value
+ * @return {Command} `this` command for chaining
+ */
+ setOptionValue(key, value) {
+ return this.setOptionValueWithSource(key, value, void 0);
+ }
+ /**
+ * Store option value and where the value came from.
+ *
+ * @param {string} key
+ * @param {object} value
+ * @param {string} source - expected values are default/config/env/cli/implied
+ * @return {Command} `this` command for chaining
+ */
+ setOptionValueWithSource(key, value, source) {
+ if (this._storeOptionsAsProperties) {
+ this[key] = value;
+ } else {
+ this._optionValues[key] = value;
+ }
+ this._optionValueSources[key] = source;
+ return this;
+ }
+ /**
+ * Get source of option value.
+ * Expected values are default | config | env | cli | implied
+ *
+ * @param {string} key
+ * @return {string}
+ */
+ getOptionValueSource(key) {
+ return this._optionValueSources[key];
+ }
+ /**
+ * Get source of option value. See also .optsWithGlobals().
+ * Expected values are default | config | env | cli | implied
+ *
+ * @param {string} key
+ * @return {string}
+ */
+ getOptionValueSourceWithGlobals(key) {
+ let source;
+ this._getCommandAndAncestors().forEach((cmd) => {
+ if (cmd.getOptionValueSource(key) !== void 0) {
+ source = cmd.getOptionValueSource(key);
+ }
+ });
+ return source;
+ }
+ /**
+ * Get user arguments from implied or explicit arguments.
+ * Side-effects: set _scriptPath if args included script. Used for default program name, and subcommand searches.
+ *
+ * @private
+ */
+ _prepareUserArgs(argv, parseOptions) {
+ if (argv !== void 0 && !Array.isArray(argv)) {
+ throw new Error("first parameter to parse must be array or undefined");
+ }
+ parseOptions = parseOptions || {};
+ if (argv === void 0 && parseOptions.from === void 0) {
+ if (process2.versions?.electron) {
+ parseOptions.from = "electron";
+ }
+ const execArgv = process2.execArgv ?? [];
+ if (execArgv.includes("-e") || execArgv.includes("--eval") || execArgv.includes("-p") || execArgv.includes("--print")) {
+ parseOptions.from = "eval";
+ }
+ }
+ if (argv === void 0) {
+ argv = process2.argv;
+ }
+ this.rawArgs = argv.slice();
+ let userArgs;
+ switch (parseOptions.from) {
+ case void 0:
+ case "node":
+ this._scriptPath = argv[1];
+ userArgs = argv.slice(2);
+ break;
+ case "electron":
+ if (process2.defaultApp) {
+ this._scriptPath = argv[1];
+ userArgs = argv.slice(2);
+ } else {
+ userArgs = argv.slice(1);
+ }
+ break;
+ case "user":
+ userArgs = argv.slice(0);
+ break;
+ case "eval":
+ userArgs = argv.slice(1);
+ break;
+ default:
+ throw new Error(
+ `unexpected parse option { from: '${parseOptions.from}' }`
+ );
+ }
+ if (!this._name && this._scriptPath)
+ this.nameFromFilename(this._scriptPath);
+ this._name = this._name || "program";
+ return userArgs;
+ }
+ /**
+ * Parse `argv`, setting options and invoking commands when defined.
+ *
+ * Use parseAsync instead of parse if any of your action handlers are async.
+ *
+ * Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!
+ *
+ * Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:
+ * - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
+ * - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
+ * - `'user'`: just user arguments
+ *
+ * @example
+ * program.parse(); // parse process.argv and auto-detect electron and special node flags
+ * program.parse(process.argv); // assume argv[0] is app and argv[1] is script
+ * program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
+ *
+ * @param {string[]} [argv] - optional, defaults to process.argv
+ * @param {object} [parseOptions] - optionally specify style of options with from: node/user/electron
+ * @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'
+ * @return {Command} `this` command for chaining
+ */
+ parse(argv, parseOptions) {
+ this._prepareForParse();
+ const userArgs = this._prepareUserArgs(argv, parseOptions);
+ this._parseCommand([], userArgs);
+ return this;
+ }
+ /**
+ * Parse `argv`, setting options and invoking commands when defined.
+ *
+ * Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!
+ *
+ * Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:
+ * - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
+ * - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
+ * - `'user'`: just user arguments
+ *
+ * @example
+ * await program.parseAsync(); // parse process.argv and auto-detect electron and special node flags
+ * await program.parseAsync(process.argv); // assume argv[0] is app and argv[1] is script
+ * await program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
+ *
+ * @param {string[]} [argv]
+ * @param {object} [parseOptions]
+ * @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron'
+ * @return {Promise}
+ */
+ async parseAsync(argv, parseOptions) {
+ this._prepareForParse();
+ const userArgs = this._prepareUserArgs(argv, parseOptions);
+ await this._parseCommand([], userArgs);
+ return this;
+ }
+ _prepareForParse() {
+ if (this._savedState === null) {
+ this.saveStateBeforeParse();
+ } else {
+ this.restoreStateBeforeParse();
+ }
+ }
+ /**
+ * Called the first time parse is called to save state and allow a restore before subsequent calls to parse.
+ * Not usually called directly, but available for subclasses to save their custom state.
+ *
+ * This is called in a lazy way. Only commands used in parsing chain will have state saved.
+ */
+ saveStateBeforeParse() {
+ this._savedState = {
+ // name is stable if supplied by author, but may be unspecified for root command and deduced during parsing
+ _name: this._name,
+ // option values before parse have default values (including false for negated options)
+ // shallow clones
+ _optionValues: { ...this._optionValues },
+ _optionValueSources: { ...this._optionValueSources }
+ };
+ }
+ /**
+ * Restore state before parse for calls after the first.
+ * Not usually called directly, but available for subclasses to save their custom state.
+ *
+ * This is called in a lazy way. Only commands used in parsing chain will have state restored.
+ */
+ restoreStateBeforeParse() {
+ if (this._storeOptionsAsProperties)
+ throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
+- either make a new Command for each call to parse, or stop storing options as properties`);
+ this._name = this._savedState._name;
+ this._scriptPath = null;
+ this.rawArgs = [];
+ this._optionValues = { ...this._savedState._optionValues };
+ this._optionValueSources = { ...this._savedState._optionValueSources };
+ this.args = [];
+ this.processedArgs = [];
+ }
+ /**
+ * Throw if expected executable is missing. Add lots of help for author.
+ *
+ * @param {string} executableFile
+ * @param {string} executableDir
+ * @param {string} subcommandName
+ */
+ _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
+ if (fs14.existsSync(executableFile)) return;
+ const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
+ const executableMissing = `'${executableFile}' does not exist
+ - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
+ - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
+ - ${executableDirMessage}`;
+ throw new Error(executableMissing);
+ }
+ /**
+ * Execute a sub-command executable.
+ *
+ * @private
+ */
+ _executeSubCommand(subcommand, args) {
+ args = args.slice();
+ let launchWithNode = false;
+ const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
+ function findFile(baseDir, baseName) {
+ const localBin = path20.resolve(baseDir, baseName);
+ if (fs14.existsSync(localBin)) return localBin;
+ if (sourceExt.includes(path20.extname(baseName))) return void 0;
+ const foundExt = sourceExt.find(
+ (ext) => fs14.existsSync(`${localBin}${ext}`)
+ );
+ if (foundExt) return `${localBin}${foundExt}`;
+ return void 0;
+ }
+ this._checkForMissingMandatoryOptions();
+ this._checkForConflictingOptions();
+ let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
+ let executableDir = this._executableDir || "";
+ if (this._scriptPath) {
+ let resolvedScriptPath;
+ try {
+ resolvedScriptPath = fs14.realpathSync(this._scriptPath);
+ } catch {
+ resolvedScriptPath = this._scriptPath;
+ }
+ executableDir = path20.resolve(
+ path20.dirname(resolvedScriptPath),
+ executableDir
+ );
+ }
+ if (executableDir) {
+ let localFile = findFile(executableDir, executableFile);
+ if (!localFile && !subcommand._executableFile && this._scriptPath) {
+ const legacyName = path20.basename(
+ this._scriptPath,
+ path20.extname(this._scriptPath)
+ );
+ if (legacyName !== this._name) {
+ localFile = findFile(
+ executableDir,
+ `${legacyName}-${subcommand._name}`
+ );
+ }
+ }
+ executableFile = localFile || executableFile;
+ }
+ launchWithNode = sourceExt.includes(path20.extname(executableFile));
+ let proc;
+ if (process2.platform !== "win32") {
+ if (launchWithNode) {
+ args.unshift(executableFile);
+ args = incrementNodeInspectorPort(process2.execArgv).concat(args);
+ proc = childProcess.spawn(process2.argv[0], args, { stdio: "inherit" });
+ } else {
+ proc = childProcess.spawn(executableFile, args, { stdio: "inherit" });
+ }
+ } else {
+ this._checkForMissingExecutable(
+ executableFile,
+ executableDir,
+ subcommand._name
+ );
+ args.unshift(executableFile);
+ args = incrementNodeInspectorPort(process2.execArgv).concat(args);
+ proc = childProcess.spawn(process2.execPath, args, { stdio: "inherit" });
+ }
+ if (!proc.killed) {
+ const signals = ["SIGUSR1", "SIGUSR2", "SIGTERM", "SIGINT", "SIGHUP"];
+ signals.forEach((signal) => {
+ process2.on(signal, () => {
+ if (proc.killed === false && proc.exitCode === null) {
+ proc.kill(signal);
+ }
+ });
+ });
+ }
+ const exitCallback = this._exitCallback;
+ proc.on("close", (code) => {
+ code = code ?? 1;
+ if (!exitCallback) {
+ process2.exit(code);
+ } else {
+ exitCallback(
+ new CommanderError2(
+ code,
+ "commander.executeSubCommandAsync",
+ "(close)"
+ )
+ );
+ }
+ });
+ proc.on("error", (err) => {
+ if (err.code === "ENOENT") {
+ this._checkForMissingExecutable(
+ executableFile,
+ executableDir,
+ subcommand._name
+ );
+ } else if (err.code === "EACCES") {
+ throw new Error(`'${executableFile}' not executable`);
+ }
+ if (!exitCallback) {
+ process2.exit(1);
+ } else {
+ const wrappedError = new CommanderError2(
+ 1,
+ "commander.executeSubCommandAsync",
+ "(error)"
+ );
+ wrappedError.nestedError = err;
+ exitCallback(wrappedError);
+ }
+ });
+ this.runningCommand = proc;
+ }
+ /**
+ * @private
+ */
+ _dispatchSubcommand(commandName, operands, unknown) {
+ const subCommand = this._findCommand(commandName);
+ if (!subCommand) this.help({ error: true });
+ subCommand._prepareForParse();
+ let promiseChain;
+ promiseChain = this._chainOrCallSubCommandHook(
+ promiseChain,
+ subCommand,
+ "preSubcommand"
+ );
+ promiseChain = this._chainOrCall(promiseChain, () => {
+ if (subCommand._executableHandler) {
+ this._executeSubCommand(subCommand, operands.concat(unknown));
+ } else {
+ return subCommand._parseCommand(operands, unknown);
+ }
+ });
+ return promiseChain;
+ }
+ /**
+ * Invoke help directly if possible, or dispatch if necessary.
+ * e.g. help foo
+ *
+ * @private
+ */
+ _dispatchHelpCommand(subcommandName) {
+ if (!subcommandName) {
+ this.help();
+ }
+ const subCommand = this._findCommand(subcommandName);
+ if (subCommand && !subCommand._executableHandler) {
+ subCommand.help();
+ }
+ return this._dispatchSubcommand(
+ subcommandName,
+ [],
+ [this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? "--help"]
+ );
+ }
+ /**
+ * Check this.args against expected this.registeredArguments.
+ *
+ * @private
+ */
+ _checkNumberOfArguments() {
+ this.registeredArguments.forEach((arg, i) => {
+ if (arg.required && this.args[i] == null) {
+ this.missingArgument(arg.name());
+ }
+ });
+ if (this.registeredArguments.length > 0 && this.registeredArguments[this.registeredArguments.length - 1].variadic) {
+ return;
+ }
+ if (this.args.length > this.registeredArguments.length) {
+ this._excessArguments(this.args);
+ }
+ }
+ /**
+ * Process this.args using this.registeredArguments and save as this.processedArgs!
+ *
+ * @private
+ */
+ _processArguments() {
+ const myParseArg = (argument, value, previous) => {
+ let parsedValue = value;
+ if (value !== null && argument.parseArg) {
+ const invalidValueMessage = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'.`;
+ parsedValue = this._callParseArg(
+ argument,
+ value,
+ previous,
+ invalidValueMessage
+ );
+ }
+ return parsedValue;
+ };
+ this._checkNumberOfArguments();
+ const processedArgs = [];
+ this.registeredArguments.forEach((declaredArg, index) => {
+ let value = declaredArg.defaultValue;
+ if (declaredArg.variadic) {
+ if (index < this.args.length) {
+ value = this.args.slice(index);
+ if (declaredArg.parseArg) {
+ value = value.reduce((processed, v) => {
+ return myParseArg(declaredArg, v, processed);
+ }, declaredArg.defaultValue);
+ }
+ } else if (value === void 0) {
+ value = [];
+ }
+ } else if (index < this.args.length) {
+ value = this.args[index];
+ if (declaredArg.parseArg) {
+ value = myParseArg(declaredArg, value, declaredArg.defaultValue);
+ }
+ }
+ processedArgs[index] = value;
+ });
+ this.processedArgs = processedArgs;
+ }
+ /**
+ * Once we have a promise we chain, but call synchronously until then.
+ *
+ * @param {(Promise|undefined)} promise
+ * @param {Function} fn
+ * @return {(Promise|undefined)}
+ * @private
+ */
+ _chainOrCall(promise, fn) {
+ if (promise?.then && typeof promise.then === "function") {
+ return promise.then(() => fn());
+ }
+ return fn();
+ }
+ /**
+ *
+ * @param {(Promise|undefined)} promise
+ * @param {string} event
+ * @return {(Promise|undefined)}
+ * @private
+ */
+ _chainOrCallHooks(promise, event) {
+ let result = promise;
+ const hooks = [];
+ this._getCommandAndAncestors().reverse().filter((cmd) => cmd._lifeCycleHooks[event] !== void 0).forEach((hookedCommand) => {
+ hookedCommand._lifeCycleHooks[event].forEach((callback) => {
+ hooks.push({ hookedCommand, callback });
+ });
+ });
+ if (event === "postAction") {
+ hooks.reverse();
+ }
+ hooks.forEach((hookDetail) => {
+ result = this._chainOrCall(result, () => {
+ return hookDetail.callback(hookDetail.hookedCommand, this);
+ });
+ });
+ return result;
+ }
+ /**
+ *
+ * @param {(Promise|undefined)} promise
+ * @param {Command} subCommand
+ * @param {string} event
+ * @return {(Promise|undefined)}
+ * @private
+ */
+ _chainOrCallSubCommandHook(promise, subCommand, event) {
+ let result = promise;
+ if (this._lifeCycleHooks[event] !== void 0) {
+ this._lifeCycleHooks[event].forEach((hook) => {
+ result = this._chainOrCall(result, () => {
+ return hook(this, subCommand);
+ });
+ });
+ }
+ return result;
+ }
+ /**
+ * Process arguments in context of this command.
+ * Returns action result, in case it is a promise.
+ *
+ * @private
+ */
+ _parseCommand(operands, unknown) {
+ const parsed = this.parseOptions(unknown);
+ this._parseOptionsEnv();
+ this._parseOptionsImplied();
+ operands = operands.concat(parsed.operands);
+ unknown = parsed.unknown;
+ this.args = operands.concat(unknown);
+ if (operands && this._findCommand(operands[0])) {
+ return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
+ }
+ if (this._getHelpCommand() && operands[0] === this._getHelpCommand().name()) {
+ return this._dispatchHelpCommand(operands[1]);
+ }
+ if (this._defaultCommandName) {
+ this._outputHelpIfRequested(unknown);
+ return this._dispatchSubcommand(
+ this._defaultCommandName,
+ operands,
+ unknown
+ );
+ }
+ if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
+ this.help({ error: true });
+ }
+ this._outputHelpIfRequested(parsed.unknown);
+ this._checkForMissingMandatoryOptions();
+ this._checkForConflictingOptions();
+ const checkForUnknownOptions = () => {
+ if (parsed.unknown.length > 0) {
+ this.unknownOption(parsed.unknown[0]);
+ }
+ };
+ const commandEvent = `command:${this.name()}`;
+ if (this._actionHandler) {
+ checkForUnknownOptions();
+ this._processArguments();
+ let promiseChain;
+ promiseChain = this._chainOrCallHooks(promiseChain, "preAction");
+ promiseChain = this._chainOrCall(
+ promiseChain,
+ () => this._actionHandler(this.processedArgs)
+ );
+ if (this.parent) {
+ promiseChain = this._chainOrCall(promiseChain, () => {
+ this.parent.emit(commandEvent, operands, unknown);
+ });
+ }
+ promiseChain = this._chainOrCallHooks(promiseChain, "postAction");
+ return promiseChain;
+ }
+ if (this.parent?.listenerCount(commandEvent)) {
+ checkForUnknownOptions();
+ this._processArguments();
+ this.parent.emit(commandEvent, operands, unknown);
+ } else if (operands.length) {
+ if (this._findCommand("*")) {
+ return this._dispatchSubcommand("*", operands, unknown);
+ }
+ if (this.listenerCount("command:*")) {
+ this.emit("command:*", operands, unknown);
+ } else if (this.commands.length) {
+ this.unknownCommand();
+ } else {
+ checkForUnknownOptions();
+ this._processArguments();
+ }
+ } else if (this.commands.length) {
+ checkForUnknownOptions();
+ this.help({ error: true });
+ } else {
+ checkForUnknownOptions();
+ this._processArguments();
+ }
+ }
+ /**
+ * Find matching command.
+ *
+ * @private
+ * @return {Command | undefined}
+ */
+ _findCommand(name) {
+ if (!name) return void 0;
+ return this.commands.find(
+ (cmd) => cmd._name === name || cmd._aliases.includes(name)
+ );
+ }
+ /**
+ * Return an option matching `arg` if any.
+ *
+ * @param {string} arg
+ * @return {Option}
+ * @package
+ */
+ _findOption(arg) {
+ return this.options.find((option) => option.is(arg));
+ }
+ /**
+ * Display an error message if a mandatory option does not have a value.
+ * Called after checking for help flags in leaf subcommand.
+ *
+ * @private
+ */
+ _checkForMissingMandatoryOptions() {
+ this._getCommandAndAncestors().forEach((cmd) => {
+ cmd.options.forEach((anOption) => {
+ if (anOption.mandatory && cmd.getOptionValue(anOption.attributeName()) === void 0) {
+ cmd.missingMandatoryOptionValue(anOption);
+ }
+ });
+ });
+ }
+ /**
+ * Display an error message if conflicting options are used together in this.
+ *
+ * @private
+ */
+ _checkForConflictingLocalOptions() {
+ const definedNonDefaultOptions = this.options.filter((option) => {
+ const optionKey = option.attributeName();
+ if (this.getOptionValue(optionKey) === void 0) {
+ return false;
+ }
+ return this.getOptionValueSource(optionKey) !== "default";
+ });
+ const optionsWithConflicting = definedNonDefaultOptions.filter(
+ (option) => option.conflictsWith.length > 0
+ );
+ optionsWithConflicting.forEach((option) => {
+ const conflictingAndDefined = definedNonDefaultOptions.find(
+ (defined) => option.conflictsWith.includes(defined.attributeName())
+ );
+ if (conflictingAndDefined) {
+ this._conflictingOption(option, conflictingAndDefined);
+ }
+ });
+ }
+ /**
+ * Display an error message if conflicting options are used together.
+ * Called after checking for help flags in leaf subcommand.
+ *
+ * @private
+ */
+ _checkForConflictingOptions() {
+ this._getCommandAndAncestors().forEach((cmd) => {
+ cmd._checkForConflictingLocalOptions();
+ });
+ }
+ /**
+ * Parse options from `argv` removing known options,
+ * and return argv split into operands and unknown arguments.
+ *
+ * Side effects: modifies command by storing options. Does not reset state if called again.
+ *
+ * Examples:
+ *
+ * argv => operands, unknown
+ * --known kkk op => [op], []
+ * op --known kkk => [op], []
+ * sub --unknown uuu op => [sub], [--unknown uuu op]
+ * sub -- --unknown uuu op => [sub --unknown uuu op], []
+ *
+ * @param {string[]} args
+ * @return {{operands: string[], unknown: string[]}}
+ */
+ parseOptions(args) {
+ const operands = [];
+ const unknown = [];
+ let dest = operands;
+ function maybeOption(arg) {
+ return arg.length > 1 && arg[0] === "-";
+ }
+ const negativeNumberArg = (arg) => {
+ if (!/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(arg)) return false;
+ return !this._getCommandAndAncestors().some(
+ (cmd) => cmd.options.map((opt) => opt.short).some((short) => /^-\d$/.test(short))
+ );
+ };
+ let activeVariadicOption = null;
+ let activeGroup = null;
+ let i = 0;
+ while (i < args.length || activeGroup) {
+ const arg = activeGroup ?? args[i++];
+ activeGroup = null;
+ if (arg === "--") {
+ if (dest === unknown) dest.push(arg);
+ dest.push(...args.slice(i));
+ break;
+ }
+ if (activeVariadicOption && (!maybeOption(arg) || negativeNumberArg(arg))) {
+ this.emit(`option:${activeVariadicOption.name()}`, arg);
+ continue;
+ }
+ activeVariadicOption = null;
+ if (maybeOption(arg)) {
+ const option = this._findOption(arg);
+ if (option) {
+ if (option.required) {
+ const value = args[i++];
+ if (value === void 0) this.optionMissingArgument(option);
+ this.emit(`option:${option.name()}`, value);
+ } else if (option.optional) {
+ let value = null;
+ if (i < args.length && (!maybeOption(args[i]) || negativeNumberArg(args[i]))) {
+ value = args[i++];
+ }
+ this.emit(`option:${option.name()}`, value);
+ } else {
+ this.emit(`option:${option.name()}`);
+ }
+ activeVariadicOption = option.variadic ? option : null;
+ continue;
+ }
+ }
+ if (arg.length > 2 && arg[0] === "-" && arg[1] !== "-") {
+ const option = this._findOption(`-${arg[1]}`);
+ if (option) {
+ if (option.required || option.optional && this._combineFlagAndOptionalValue) {
+ this.emit(`option:${option.name()}`, arg.slice(2));
+ } else {
+ this.emit(`option:${option.name()}`);
+ activeGroup = `-${arg.slice(2)}`;
+ }
+ continue;
+ }
+ }
+ if (/^--[^=]+=/.test(arg)) {
+ const index = arg.indexOf("=");
+ const option = this._findOption(arg.slice(0, index));
+ if (option && (option.required || option.optional)) {
+ this.emit(`option:${option.name()}`, arg.slice(index + 1));
+ continue;
+ }
+ }
+ if (dest === operands && maybeOption(arg) && !(this.commands.length === 0 && negativeNumberArg(arg))) {
+ dest = unknown;
+ }
+ if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
+ if (this._findCommand(arg)) {
+ operands.push(arg);
+ unknown.push(...args.slice(i));
+ break;
+ } else if (this._getHelpCommand() && arg === this._getHelpCommand().name()) {
+ operands.push(arg, ...args.slice(i));
+ break;
+ } else if (this._defaultCommandName) {
+ unknown.push(arg, ...args.slice(i));
+ break;
+ }
+ }
+ if (this._passThroughOptions) {
+ dest.push(arg, ...args.slice(i));
+ break;
+ }
+ dest.push(arg);
+ }
+ return { operands, unknown };
+ }
+ /**
+ * Return an object containing local option values as key-value pairs.
+ *
+ * @return {object}
+ */
+ opts() {
+ if (this._storeOptionsAsProperties) {
+ const result = {};
+ const len = this.options.length;
+ for (let i = 0; i < len; i++) {
+ const key = this.options[i].attributeName();
+ result[key] = key === this._versionOptionName ? this._version : this[key];
+ }
+ return result;
+ }
+ return this._optionValues;
+ }
+ /**
+ * Return an object containing merged local and global option values as key-value pairs.
+ *
+ * @return {object}
+ */
+ optsWithGlobals() {
+ return this._getCommandAndAncestors().reduce(
+ (combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()),
+ {}
+ );
+ }
+ /**
+ * Display error message and exit (or call exitOverride).
+ *
+ * @param {string} message
+ * @param {object} [errorOptions]
+ * @param {string} [errorOptions.code] - an id string representing the error
+ * @param {number} [errorOptions.exitCode] - used with process.exit
+ */
+ error(message, errorOptions) {
+ this._outputConfiguration.outputError(
+ `${message}
+`,
+ this._outputConfiguration.writeErr
+ );
+ if (typeof this._showHelpAfterError === "string") {
+ this._outputConfiguration.writeErr(`${this._showHelpAfterError}
+`);
+ } else if (this._showHelpAfterError) {
+ this._outputConfiguration.writeErr("\n");
+ this.outputHelp({ error: true });
+ }
+ const config = errorOptions || {};
+ const exitCode = config.exitCode || 1;
+ const code = config.code || "commander.error";
+ this._exit(exitCode, code, message);
+ }
+ /**
+ * Apply any option related environment variables, if option does
+ * not have a value from cli or client code.
+ *
+ * @private
+ */
+ _parseOptionsEnv() {
+ this.options.forEach((option) => {
+ if (option.envVar && option.envVar in process2.env) {
+ const optionKey = option.attributeName();
+ if (this.getOptionValue(optionKey) === void 0 || ["default", "config", "env"].includes(
+ this.getOptionValueSource(optionKey)
+ )) {
+ if (option.required || option.optional) {
+ this.emit(`optionEnv:${option.name()}`, process2.env[option.envVar]);
+ } else {
+ this.emit(`optionEnv:${option.name()}`);
+ }
+ }
+ }
+ });
+ }
+ /**
+ * Apply any implied option values, if option is undefined or default value.
+ *
+ * @private
+ */
+ _parseOptionsImplied() {
+ const dualHelper = new DualOptions(this.options);
+ const hasCustomOptionValue = (optionKey) => {
+ return this.getOptionValue(optionKey) !== void 0 && !["default", "implied"].includes(this.getOptionValueSource(optionKey));
+ };
+ this.options.filter(
+ (option) => option.implied !== void 0 && hasCustomOptionValue(option.attributeName()) && dualHelper.valueFromOption(
+ this.getOptionValue(option.attributeName()),
+ option
+ )
+ ).forEach((option) => {
+ Object.keys(option.implied).filter((impliedKey) => !hasCustomOptionValue(impliedKey)).forEach((impliedKey) => {
+ this.setOptionValueWithSource(
+ impliedKey,
+ option.implied[impliedKey],
+ "implied"
+ );
+ });
+ });
+ }
+ /**
+ * Argument `name` is missing.
+ *
+ * @param {string} name
+ * @private
+ */
+ missingArgument(name) {
+ const message = `error: missing required argument '${name}'`;
+ this.error(message, { code: "commander.missingArgument" });
+ }
+ /**
+ * `Option` is missing an argument.
+ *
+ * @param {Option} option
+ * @private
+ */
+ optionMissingArgument(option) {
+ const message = `error: option '${option.flags}' argument missing`;
+ this.error(message, { code: "commander.optionMissingArgument" });
+ }
+ /**
+ * `Option` does not have a value, and is a mandatory option.
+ *
+ * @param {Option} option
+ * @private
+ */
+ missingMandatoryOptionValue(option) {
+ const message = `error: required option '${option.flags}' not specified`;
+ this.error(message, { code: "commander.missingMandatoryOptionValue" });
+ }
+ /**
+ * `Option` conflicts with another option.
+ *
+ * @param {Option} option
+ * @param {Option} conflictingOption
+ * @private
+ */
+ _conflictingOption(option, conflictingOption) {
+ const findBestOptionFromValue = (option2) => {
+ const optionKey = option2.attributeName();
+ const optionValue = this.getOptionValue(optionKey);
+ const negativeOption = this.options.find(
+ (target) => target.negate && optionKey === target.attributeName()
+ );
+ const positiveOption = this.options.find(
+ (target) => !target.negate && optionKey === target.attributeName()
+ );
+ if (negativeOption && (negativeOption.presetArg === void 0 && optionValue === false || negativeOption.presetArg !== void 0 && optionValue === negativeOption.presetArg)) {
+ return negativeOption;
+ }
+ return positiveOption || option2;
+ };
+ const getErrorMessage = (option2) => {
+ const bestOption = findBestOptionFromValue(option2);
+ const optionKey = bestOption.attributeName();
+ const source = this.getOptionValueSource(optionKey);
+ if (source === "env") {
+ return `environment variable '${bestOption.envVar}'`;
+ }
+ return `option '${bestOption.flags}'`;
+ };
+ const message = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
+ this.error(message, { code: "commander.conflictingOption" });
+ }
+ /**
+ * Unknown option `flag`.
+ *
+ * @param {string} flag
+ * @private
+ */
+ unknownOption(flag) {
+ if (this._allowUnknownOption) return;
+ let suggestion = "";
+ if (flag.startsWith("--") && this._showSuggestionAfterError) {
+ let candidateFlags = [];
+ let command = this;
+ do {
+ const moreFlags = command.createHelp().visibleOptions(command).filter((option) => option.long).map((option) => option.long);
+ candidateFlags = candidateFlags.concat(moreFlags);
+ command = command.parent;
+ } while (command && !command._enablePositionalOptions);
+ suggestion = suggestSimilar(flag, candidateFlags);
+ }
+ const message = `error: unknown option '${flag}'${suggestion}`;
+ this.error(message, { code: "commander.unknownOption" });
+ }
+ /**
+ * Excess arguments, more than expected.
+ *
+ * @param {string[]} receivedArgs
+ * @private
+ */
+ _excessArguments(receivedArgs) {
+ if (this._allowExcessArguments) return;
+ const expected = this.registeredArguments.length;
+ const s = expected === 1 ? "" : "s";
+ const forSubcommand = this.parent ? ` for '${this.name()}'` : "";
+ const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`;
+ this.error(message, { code: "commander.excessArguments" });
+ }
+ /**
+ * Unknown command.
+ *
+ * @private
+ */
+ unknownCommand() {
+ const unknownName = this.args[0];
+ let suggestion = "";
+ if (this._showSuggestionAfterError) {
+ const candidateNames = [];
+ this.createHelp().visibleCommands(this).forEach((command) => {
+ candidateNames.push(command.name());
+ if (command.alias()) candidateNames.push(command.alias());
+ });
+ suggestion = suggestSimilar(unknownName, candidateNames);
+ }
+ const message = `error: unknown command '${unknownName}'${suggestion}`;
+ this.error(message, { code: "commander.unknownCommand" });
+ }
+ /**
+ * Get or set the program version.
+ *
+ * This method auto-registers the "-V, --version" option which will print the version number.
+ *
+ * You can optionally supply the flags and description to override the defaults.
+ *
+ * @param {string} [str]
+ * @param {string} [flags]
+ * @param {string} [description]
+ * @return {(this | string | undefined)} `this` command for chaining, or version string if no arguments
+ */
+ version(str, flags, description) {
+ if (str === void 0) return this._version;
+ this._version = str;
+ flags = flags || "-V, --version";
+ description = description || "output the version number";
+ const versionOption = this.createOption(flags, description);
+ this._versionOptionName = versionOption.attributeName();
+ this._registerOption(versionOption);
+ this.on("option:" + versionOption.name(), () => {
+ this._outputConfiguration.writeOut(`${str}
+`);
+ this._exit(0, "commander.version", str);
+ });
+ return this;
+ }
+ /**
+ * Set the description.
+ *
+ * @param {string} [str]
+ * @param {object} [argsDescription]
+ * @return {(string|Command)}
+ */
+ description(str, argsDescription) {
+ if (str === void 0 && argsDescription === void 0)
+ return this._description;
+ this._description = str;
+ if (argsDescription) {
+ this._argsDescription = argsDescription;
+ }
+ return this;
+ }
+ /**
+ * Set the summary. Used when listed as subcommand of parent.
+ *
+ * @param {string} [str]
+ * @return {(string|Command)}
+ */
+ summary(str) {
+ if (str === void 0) return this._summary;
+ this._summary = str;
+ return this;
+ }
+ /**
+ * Set an alias for the command.
+ *
+ * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
+ *
+ * @param {string} [alias]
+ * @return {(string|Command)}
+ */
+ alias(alias) {
+ if (alias === void 0) return this._aliases[0];
+ let command = this;
+ if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
+ command = this.commands[this.commands.length - 1];
+ }
+ if (alias === command._name)
+ throw new Error("Command alias can't be the same as its name");
+ const matchingCommand = this.parent?._findCommand(alias);
+ if (matchingCommand) {
+ const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join("|");
+ throw new Error(
+ `cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`
+ );
+ }
+ command._aliases.push(alias);
+ return this;
+ }
+ /**
+ * Set aliases for the command.
+ *
+ * Only the first alias is shown in the auto-generated help.
+ *
+ * @param {string[]} [aliases]
+ * @return {(string[]|Command)}
+ */
+ aliases(aliases) {
+ if (aliases === void 0) return this._aliases;
+ aliases.forEach((alias) => this.alias(alias));
+ return this;
+ }
+ /**
+ * Set / get the command usage `str`.
+ *
+ * @param {string} [str]
+ * @return {(string|Command)}
+ */
+ usage(str) {
+ if (str === void 0) {
+ if (this._usage) return this._usage;
+ const args = this.registeredArguments.map((arg) => {
+ return humanReadableArgName(arg);
+ });
+ return [].concat(
+ this.options.length || this._helpOption !== null ? "[options]" : [],
+ this.commands.length ? "[command]" : [],
+ this.registeredArguments.length ? args : []
+ ).join(" ");
+ }
+ this._usage = str;
+ return this;
+ }
+ /**
+ * Get or set the name of the command.
+ *
+ * @param {string} [str]
+ * @return {(string|Command)}
+ */
+ name(str) {
+ if (str === void 0) return this._name;
+ this._name = str;
+ return this;
+ }
+ /**
+ * Set/get the help group heading for this subcommand in parent command's help.
+ *
+ * @param {string} [heading]
+ * @return {Command | string}
+ */
+ helpGroup(heading) {
+ if (heading === void 0) return this._helpGroupHeading ?? "";
+ this._helpGroupHeading = heading;
+ return this;
+ }
+ /**
+ * Set/get the default help group heading for subcommands added to this command.
+ * (This does not override a group set directly on the subcommand using .helpGroup().)
+ *
+ * @example
+ * program.commandsGroup('Development Commands:);
+ * program.command('watch')...
+ * program.command('lint')...
+ * ...
+ *
+ * @param {string} [heading]
+ * @returns {Command | string}
+ */
+ commandsGroup(heading) {
+ if (heading === void 0) return this._defaultCommandGroup ?? "";
+ this._defaultCommandGroup = heading;
+ return this;
+ }
+ /**
+ * Set/get the default help group heading for options added to this command.
+ * (This does not override a group set directly on the option using .helpGroup().)
+ *
+ * @example
+ * program
+ * .optionsGroup('Development Options:')
+ * .option('-d, --debug', 'output extra debugging')
+ * .option('-p, --profile', 'output profiling information')
+ *
+ * @param {string} [heading]
+ * @returns {Command | string}
+ */
+ optionsGroup(heading) {
+ if (heading === void 0) return this._defaultOptionGroup ?? "";
+ this._defaultOptionGroup = heading;
+ return this;
+ }
+ /**
+ * @param {Option} option
+ * @private
+ */
+ _initOptionGroup(option) {
+ if (this._defaultOptionGroup && !option.helpGroupHeading)
+ option.helpGroup(this._defaultOptionGroup);
+ }
+ /**
+ * @param {Command} cmd
+ * @private
+ */
+ _initCommandGroup(cmd) {
+ if (this._defaultCommandGroup && !cmd.helpGroup())
+ cmd.helpGroup(this._defaultCommandGroup);
+ }
+ /**
+ * Set the name of the command from script filename, such as process.argv[1],
+ * or require.main.filename, or __filename.
+ *
+ * (Used internally and public although not documented in README.)
+ *
+ * @example
+ * program.nameFromFilename(require.main.filename);
+ *
+ * @param {string} filename
+ * @return {Command}
+ */
+ nameFromFilename(filename) {
+ this._name = path20.basename(filename, path20.extname(filename));
+ return this;
+ }
+ /**
+ * Get or set the directory for searching for executable subcommands of this command.
+ *
+ * @example
+ * program.executableDir(__dirname);
+ * // or
+ * program.executableDir('subcommands');
+ *
+ * @param {string} [path]
+ * @return {(string|null|Command)}
+ */
+ executableDir(path21) {
+ if (path21 === void 0) return this._executableDir;
+ this._executableDir = path21;
+ return this;
+ }
+ /**
+ * Return program help documentation.
+ *
+ * @param {{ error: boolean }} [contextOptions] - pass {error:true} to wrap for stderr instead of stdout
+ * @return {string}
+ */
+ helpInformation(contextOptions) {
+ const helper = this.createHelp();
+ const context = this._getOutputContext(contextOptions);
+ helper.prepareContext({
+ error: context.error,
+ helpWidth: context.helpWidth,
+ outputHasColors: context.hasColors
+ });
+ const text = helper.formatHelp(this, helper);
+ if (context.hasColors) return text;
+ return this._outputConfiguration.stripColor(text);
+ }
+ /**
+ * @typedef HelpContext
+ * @type {object}
+ * @property {boolean} error
+ * @property {number} helpWidth
+ * @property {boolean} hasColors
+ * @property {function} write - includes stripColor if needed
+ *
+ * @returns {HelpContext}
+ * @private
+ */
+ _getOutputContext(contextOptions) {
+ contextOptions = contextOptions || {};
+ const error = !!contextOptions.error;
+ let baseWrite;
+ let hasColors;
+ let helpWidth;
+ if (error) {
+ baseWrite = (str) => this._outputConfiguration.writeErr(str);
+ hasColors = this._outputConfiguration.getErrHasColors();
+ helpWidth = this._outputConfiguration.getErrHelpWidth();
+ } else {
+ baseWrite = (str) => this._outputConfiguration.writeOut(str);
+ hasColors = this._outputConfiguration.getOutHasColors();
+ helpWidth = this._outputConfiguration.getOutHelpWidth();
+ }
+ const write = (str) => {
+ if (!hasColors) str = this._outputConfiguration.stripColor(str);
+ return baseWrite(str);
+ };
+ return { error, write, hasColors, helpWidth };
+ }
+ /**
+ * Output help information for this command.
+ *
+ * Outputs built-in help, and custom text added using `.addHelpText()`.
+ *
+ * @param {{ error: boolean } | Function} [contextOptions] - pass {error:true} to write to stderr instead of stdout
+ */
+ outputHelp(contextOptions) {
+ let deprecatedCallback;
+ if (typeof contextOptions === "function") {
+ deprecatedCallback = contextOptions;
+ contextOptions = void 0;
+ }
+ const outputContext = this._getOutputContext(contextOptions);
+ const eventContext = {
+ error: outputContext.error,
+ write: outputContext.write,
+ command: this
+ };
+ this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", eventContext));
+ this.emit("beforeHelp", eventContext);
+ let helpInformation = this.helpInformation({ error: outputContext.error });
+ if (deprecatedCallback) {
+ helpInformation = deprecatedCallback(helpInformation);
+ if (typeof helpInformation !== "string" && !Buffer.isBuffer(helpInformation)) {
+ throw new Error("outputHelp callback must return a string or a Buffer");
+ }
+ }
+ outputContext.write(helpInformation);
+ if (this._getHelpOption()?.long) {
+ this.emit(this._getHelpOption().long);
+ }
+ this.emit("afterHelp", eventContext);
+ this._getCommandAndAncestors().forEach(
+ (command) => command.emit("afterAllHelp", eventContext)
+ );
+ }
+ /**
+ * You can pass in flags and a description to customise the built-in help option.
+ * Pass in false to disable the built-in help option.
+ *
+ * @example
+ * program.helpOption('-?, --help' 'show help'); // customise
+ * program.helpOption(false); // disable
+ *
+ * @param {(string | boolean)} flags
+ * @param {string} [description]
+ * @return {Command} `this` command for chaining
+ */
+ helpOption(flags, description) {
+ if (typeof flags === "boolean") {
+ if (flags) {
+ if (this._helpOption === null) this._helpOption = void 0;
+ if (this._defaultOptionGroup) {
+ this._initOptionGroup(this._getHelpOption());
+ }
+ } else {
+ this._helpOption = null;
+ }
+ return this;
+ }
+ this._helpOption = this.createOption(
+ flags ?? "-h, --help",
+ description ?? "display help for command"
+ );
+ if (flags || description) this._initOptionGroup(this._helpOption);
+ return this;
+ }
+ /**
+ * Lazy create help option.
+ * Returns null if has been disabled with .helpOption(false).
+ *
+ * @returns {(Option | null)} the help option
+ * @package
+ */
+ _getHelpOption() {
+ if (this._helpOption === void 0) {
+ this.helpOption(void 0, void 0);
+ }
+ return this._helpOption;
+ }
+ /**
+ * Supply your own option to use for the built-in help option.
+ * This is an alternative to using helpOption() to customise the flags and description etc.
+ *
+ * @param {Option} option
+ * @return {Command} `this` command for chaining
+ */
+ addHelpOption(option) {
+ this._helpOption = option;
+ this._initOptionGroup(option);
+ return this;
+ }
+ /**
+ * Output help information and exit.
+ *
+ * Outputs built-in help, and custom text added using `.addHelpText()`.
+ *
+ * @param {{ error: boolean }} [contextOptions] - pass {error:true} to write to stderr instead of stdout
+ */
+ help(contextOptions) {
+ this.outputHelp(contextOptions);
+ let exitCode = Number(process2.exitCode ?? 0);
+ if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
+ exitCode = 1;
+ }
+ this._exit(exitCode, "commander.help", "(outputHelp)");
+ }
+ /**
+ * // Do a little typing to coordinate emit and listener for the help text events.
+ * @typedef HelpTextEventContext
+ * @type {object}
+ * @property {boolean} error
+ * @property {Command} command
+ * @property {function} write
+ */
+ /**
+ * Add additional text to be displayed with the built-in help.
+ *
+ * Position is 'before' or 'after' to affect just this command,
+ * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
+ *
+ * @param {string} position - before or after built-in help
+ * @param {(string | Function)} text - string to add, or a function returning a string
+ * @return {Command} `this` command for chaining
+ */
+ addHelpText(position, text) {
+ const allowedValues = ["beforeAll", "before", "after", "afterAll"];
+ if (!allowedValues.includes(position)) {
+ throw new Error(`Unexpected value for position to addHelpText.
+Expecting one of '${allowedValues.join("', '")}'`);
+ }
+ const helpEvent = `${position}Help`;
+ this.on(helpEvent, (context) => {
+ let helpStr;
+ if (typeof text === "function") {
+ helpStr = text({ error: context.error, command: context.command });
+ } else {
+ helpStr = text;
+ }
+ if (helpStr) {
+ context.write(`${helpStr}
+`);
+ }
+ });
+ return this;
+ }
+ /**
+ * Output help information if help flags specified
+ *
+ * @param {Array} args - array of options to search for help flags
+ * @private
+ */
+ _outputHelpIfRequested(args) {
+ const helpOption = this._getHelpOption();
+ const helpRequested = helpOption && args.find((arg) => helpOption.is(arg));
+ if (helpRequested) {
+ this.outputHelp();
+ this._exit(0, "commander.helpDisplayed", "(outputHelp)");
+ }
+ }
+ };
+ function incrementNodeInspectorPort(args) {
+ return args.map((arg) => {
+ if (!arg.startsWith("--inspect")) {
+ return arg;
+ }
+ let debugOption;
+ let debugHost = "127.0.0.1";
+ let debugPort = "9229";
+ let match;
+ if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
+ debugOption = match[1];
+ } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
+ debugOption = match[1];
+ if (/^\d+$/.test(match[3])) {
+ debugPort = match[3];
+ } else {
+ debugHost = match[3];
+ }
+ } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
+ debugOption = match[1];
+ debugHost = match[3];
+ debugPort = match[4];
+ }
+ if (debugOption && debugPort !== "0") {
+ return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
+ }
+ return arg;
+ });
+ }
+ function useColor() {
+ if (process2.env.NO_COLOR || process2.env.FORCE_COLOR === "0" || process2.env.FORCE_COLOR === "false")
+ return false;
+ if (process2.env.FORCE_COLOR || process2.env.CLICOLOR_FORCE !== void 0)
+ return true;
+ return void 0;
+ }
+ exports2.Command = Command2;
+ exports2.useColor = useColor;
+ }
+});
+
+// node_modules/commander/index.js
+var require_commander = __commonJS({
+ "node_modules/commander/index.js"(exports2) {
+ var { Argument: Argument2 } = require_argument();
+ var { Command: Command2 } = require_command();
+ var { CommanderError: CommanderError2, InvalidArgumentError: InvalidArgumentError2 } = require_error();
+ var { Help: Help2 } = require_help();
+ var { Option: Option2 } = require_option();
+ exports2.program = new Command2();
+ exports2.createCommand = (name) => new Command2(name);
+ exports2.createOption = (flags, description) => new Option2(flags, description);
+ exports2.createArgument = (name, description) => new Argument2(name, description);
+ exports2.Command = Command2;
+ exports2.Option = Option2;
+ exports2.Argument = Argument2;
+ exports2.Help = Help2;
+ exports2.CommanderError = CommanderError2;
+ exports2.InvalidArgumentError = InvalidArgumentError2;
+ exports2.InvalidOptionArgumentError = InvalidArgumentError2;
+ }
+});
+
+// src/directory.ts
+function getCodeGraphDir(projectRoot) {
+ return path.join(projectRoot, CODEGRAPH_DIR);
+}
+function isInitialized(projectRoot) {
+ const codegraphDir = getCodeGraphDir(projectRoot);
+ if (!fs.existsSync(codegraphDir) || !fs.statSync(codegraphDir).isDirectory()) {
+ return false;
+ }
+ const dbPath = path.join(codegraphDir, "codegraph.db");
+ return fs.existsSync(dbPath);
+}
+function findNearestCodeGraphRoot(startPath) {
+ let current = path.resolve(startPath);
+ const root = path.parse(current).root;
+ while (current !== root) {
+ if (isInitialized(current)) {
+ return current;
+ }
+ const parent = path.dirname(current);
+ if (parent === current) break;
+ current = parent;
+ }
+ if (isInitialized(current)) {
+ return current;
+ }
+ return null;
+}
+function createDirectory(projectRoot) {
+ const codegraphDir = getCodeGraphDir(projectRoot);
+ const dbPath = path.join(codegraphDir, "codegraph.db");
+ if (fs.existsSync(dbPath)) {
+ throw new Error(`CodeGraph already initialized in ${projectRoot}`);
+ }
+ fs.mkdirSync(codegraphDir, { recursive: true });
+ const gitignorePath = path.join(codegraphDir, ".gitignore");
+ if (!fs.existsSync(gitignorePath)) {
+ const gitignoreContent = `# CodeGraph data files
+# These are local to each machine and should not be committed
+
+# Database
+*.db
+*.db-wal
+*.db-shm
+
+# Cache
+cache/
+
+# Logs
+*.log
+
+# Hook markers
+.dirty
+`;
+ fs.writeFileSync(gitignorePath, gitignoreContent, "utf-8");
+ }
+}
+function removeDirectory(projectRoot) {
+ const codegraphDir = getCodeGraphDir(projectRoot);
+ if (!fs.existsSync(codegraphDir)) {
+ return;
+ }
+ const lstat = fs.lstatSync(codegraphDir);
+ if (lstat.isSymbolicLink()) {
+ fs.unlinkSync(codegraphDir);
+ return;
+ }
+ if (!lstat.isDirectory()) {
+ fs.unlinkSync(codegraphDir);
+ return;
+ }
+ fs.rmSync(codegraphDir, { recursive: true, force: true });
+}
+function validateDirectory(projectRoot) {
+ const errors = [];
+ const codegraphDir = getCodeGraphDir(projectRoot);
+ if (!fs.existsSync(codegraphDir)) {
+ errors.push("CodeGraph directory does not exist");
+ return { valid: false, errors };
+ }
+ if (!fs.statSync(codegraphDir).isDirectory()) {
+ errors.push(".codegraph exists but is not a directory");
+ return { valid: false, errors };
+ }
+ const gitignorePath = path.join(codegraphDir, ".gitignore");
+ if (!fs.existsSync(gitignorePath)) {
+ try {
+ const gitignoreContent = `# CodeGraph data files
+# These are local to each machine and should not be committed
+
+# Database
+*.db
+*.db-wal
+*.db-shm
+
+# Cache
+cache/
+
+# Logs
+*.log
+
+# Hook markers
+.dirty
+`;
+ fs.writeFileSync(gitignorePath, gitignoreContent, "utf-8");
+ } catch {
+ errors.push(".gitignore missing in .codegraph directory and could not be created");
+ }
+ }
+ return {
+ valid: errors.length === 0,
+ errors
+ };
+}
+var fs, path, CODEGRAPH_DIR;
+var init_directory = __esm({
+ "src/directory.ts"() {
+ "use strict";
+ fs = __toESM(require("fs"));
+ path = __toESM(require("path"));
+ CODEGRAPH_DIR = ".codegraph";
+ }
+});
+
+// src/projects.ts
+function nameFromPath(relPath) {
+ const segments = relPath.replace(/\\/g, "/").split("/").filter(Boolean);
+ return segments[segments.length - 1] ?? relPath;
+}
+function parseEntry(raw) {
+ if (typeof raw === "string" && raw) {
+ return { name: nameFromPath(raw), path: raw };
+ }
+ if (raw && typeof raw === "object") {
+ const obj = raw;
+ const p = typeof obj.path === "string" ? obj.path : null;
+ if (!p) return null;
+ const n = typeof obj.name === "string" && obj.name ? obj.name : nameFromPath(p);
+ return { name: n, path: p };
+ }
+ return null;
+}
+function getProjectsPath(projectRoot) {
+ return path2.join(projectRoot, CODEGRAPH_DIR, PROJECTS_FILENAME);
+}
+function loadProjectEntries(projectRoot) {
+ const filePath = getProjectsPath(projectRoot);
+ try {
+ if (!fs2.existsSync(filePath)) return [];
+ const content = fs2.readFileSync(filePath, "utf-8");
+ const parsed = JSON.parse(content);
+ if (!Array.isArray(parsed)) return [];
+ return parsed.map(parseEntry).filter((e) => e !== null);
+ } catch (err) {
+ process.stderr.write(
+ `[CodeGraph] Failed to load projects.json: ${err instanceof Error ? err.message : String(err)}
+`
+ );
+ return [];
+ }
+}
+function loadProjects(projectRoot) {
+ return loadProjectEntries(projectRoot).map((e) => e.path);
+}
+function saveProjects(projectRoot, entries) {
+ const filePath = getProjectsPath(projectRoot);
+ const dir = path2.dirname(filePath);
+ if (!fs2.existsSync(dir)) {
+ fs2.mkdirSync(dir, { recursive: true });
+ }
+ const byPath = /* @__PURE__ */ new Map();
+ for (const e of entries) byPath.set(e.path, e);
+ const unique = [...byPath.values()].sort((a, b) => a.name.localeCompare(b.name));
+ const content = JSON.stringify(unique, null, 2);
+ const tmpPath = filePath + ".tmp";
+ fs2.writeFileSync(tmpPath, content, "utf-8");
+ fs2.renameSync(tmpPath, filePath);
+}
+function addProject(projectRoot, projectPath, name) {
+ const entries = loadProjectEntries(projectRoot);
+ const resolvedName = name || nameFromPath(projectPath);
+ const existing = entries.findIndex((e) => e.path === projectPath);
+ if (existing >= 0) {
+ entries[existing] = { name: resolvedName, path: projectPath };
+ } else {
+ entries.push({ name: resolvedName, path: projectPath });
+ }
+ saveProjects(projectRoot, entries);
+ return entries;
+}
+function removeProject(projectRoot, pathOrName) {
+ const entries = loadProjectEntries(projectRoot).filter(
+ (e) => e.path !== pathOrName && e.name !== pathOrName
+ );
+ saveProjects(projectRoot, entries);
+ return entries;
+}
+function scanForProjects(root, maxDepth = 3) {
+ const results = [];
+ const queue = [[root, 0]];
+ while (queue.length > 0) {
+ const [currentDir, depth] = queue.shift();
+ if (depth > 0 && isInitialized(currentDir)) {
+ const relPath = path2.relative(root, currentDir).replace(/\\/g, "/");
+ results.push({ name: nameFromPath(relPath), path: relPath });
+ }
+ if (depth > maxDepth) continue;
+ try {
+ const dirEntries = fs2.readdirSync(currentDir, { withFileTypes: true });
+ for (const entry of dirEntries) {
+ if (entry.isDirectory() && !SCAN_SKIP_DIRS.has(entry.name)) {
+ queue.push([path2.join(currentDir, entry.name), depth + 1]);
+ }
+ }
+ } catch {
+ }
+ }
+ const byPath = /* @__PURE__ */ new Map();
+ for (const e of results) byPath.set(e.path, e);
+ return [...byPath.values()].sort((a, b) => a.name.localeCompare(b.name));
+}
+function findNearestMonorepoRoot(startPath) {
+ let current = path2.resolve(startPath);
+ const fsRoot = path2.parse(current).root;
+ while (current !== fsRoot) {
+ if (loadProjectEntries(current).length > 0) return current;
+ const parent = path2.dirname(current);
+ if (parent === current) break;
+ current = parent;
+ }
+ if (loadProjectEntries(current).length > 0) return current;
+ return null;
+}
+function syncProjects(root, maxDepth) {
+ const existing = loadProjectEntries(root);
+ const discovered = scanForProjects(root, maxDepth);
+ const byPath = /* @__PURE__ */ new Map();
+ for (const e of discovered) byPath.set(e.path, e);
+ for (const e of existing) byPath.set(e.path, e);
+ const merged = [...byPath.values()].sort((a, b) => a.name.localeCompare(b.name));
+ saveProjects(root, merged);
+ return merged;
+}
+var fs2, path2, PROJECTS_FILENAME, SCAN_SKIP_DIRS;
+var init_projects = __esm({
+ "src/projects.ts"() {
+ "use strict";
+ fs2 = __toESM(require("fs"));
+ path2 = __toESM(require("path"));
+ init_directory();
+ PROJECTS_FILENAME = "projects.json";
+ SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([".codegraph", ".git", "node_modules"]);
+ }
+});
+
+// src/ui/shimmer-progress.ts
+var shimmer_progress_exports = {};
+__export(shimmer_progress_exports, {
+ createShimmerProgress: () => createShimmerProgress
+});
+function createShimmerProgress() {
+ let lastPhase = "";
+ const workerPath = path3.join(__dirname, "shimmer-worker.js");
+ const worker = new import_worker_threads.Worker(workerPath, {
+ workerData: { startTime: Date.now() }
+ });
+ return {
+ onProgress(progress) {
+ const phaseName = PHASE_NAMES[progress.phase] || progress.phase;
+ if (progress.phase !== lastPhase && lastPhase) {
+ worker.postMessage({ type: "finish-phase" });
+ }
+ lastPhase = progress.phase;
+ let percent = -1;
+ let count = 0;
+ if (progress.total > 0) {
+ percent = Math.round(progress.current / progress.total * 100);
+ } else if (progress.current > 0) {
+ count = progress.current;
+ }
+ worker.postMessage({
+ type: "update",
+ phase: progress.phase,
+ phaseName,
+ percent,
+ count
+ });
+ },
+ stop() {
+ return new Promise((resolve10) => {
+ const timeout = setTimeout(() => {
+ worker.terminate().then(() => resolve10());
+ }, 2e3);
+ worker.on("message", (msg) => {
+ if (msg.type === "stopped") {
+ clearTimeout(timeout);
+ worker.terminate().then(() => resolve10());
+ }
+ });
+ worker.postMessage({ type: "stop" });
+ });
+ }
+ };
+}
+var import_worker_threads, path3, PHASE_NAMES;
+var init_shimmer_progress = __esm({
+ "src/ui/shimmer-progress.ts"() {
+ "use strict";
+ import_worker_threads = require("worker_threads");
+ path3 = __toESM(require("path"));
+ PHASE_NAMES = {
+ scanning: "Scanning files",
+ parsing: "Parsing code",
+ storing: "Storing data",
+ resolving: "Resolving refs"
+ };
+ }
+});
+
+// src/db/sqlite-adapter.ts
+function buildWasmFallbackBanner(nativeError) {
+ const sep3 = "\u2500".repeat(72);
+ const lines = [
+ sep3,
+ "[CodeGraph] WASM SQLite fallback active (better-sqlite3 unavailable)",
+ sep3,
+ "Indexing and sync will be 5-10x slower than the native backend.",
+ "",
+ "Fix on macOS:",
+ " xcode-select --install # install C build tools",
+ " npm rebuild better-sqlite3 # rebuild native binding for current Node",
+ "",
+ "Fix on Linux:",
+ " sudo apt install build-essential python3 make # Debian/Ubuntu",
+ ' # or: sudo yum groupinstall "Development Tools" # RHEL/Fedora',
+ " npm rebuild better-sqlite3",
+ "",
+ "Or force-include as a hard dependency on any platform:",
+ " npm install better-sqlite3 --save",
+ "",
+ "Verify after fix: `codegraph status` should show `Backend: native`."
+ ];
+ if (nativeError) {
+ lines.push("", `Native load error: ${nativeError}`);
+ }
+ lines.push(sep3);
+ return lines.join("\n");
+}
+function translateNamedParams(sql) {
+ const paramOrder = [];
+ const rewritten = sql.replace(/@(\w+)/g, (_match, name) => {
+ paramOrder.push(name);
+ return "?";
+ });
+ if (paramOrder.length === 0) {
+ return { sql, paramOrder: null };
+ }
+ return { sql: rewritten, paramOrder };
+}
+function resolveParams(params, paramOrder) {
+ if (params.length === 0) return void 0;
+ if (paramOrder && params.length === 1 && params[0] !== null && typeof params[0] === "object" && !Array.isArray(params[0]) && !(params[0] instanceof Buffer) && !(params[0] instanceof Uint8Array)) {
+ const obj = params[0];
+ return paramOrder.map((name) => obj[name]);
+ }
+ if (params.length === 1) return params[0];
+ return params;
+}
+function createDatabase(dbPath) {
+ let nativeError;
+ let wasmError;
+ try {
+ const Database = require("better-sqlite3");
+ const db = new Database(dbPath);
+ return { db, backend: "native" };
+ } catch (error) {
+ nativeError = error instanceof Error ? error.message : String(error);
+ }
+ try {
+ const db = new WasmDatabaseAdapter(dbPath);
+ console.warn(buildWasmFallbackBanner(nativeError));
+ return { db, backend: "wasm" };
+ } catch (error) {
+ wasmError = error instanceof Error ? error.message : String(error);
+ }
+ throw new Error(
+ `Failed to load any SQLite backend.
+ Native (better-sqlite3): ${nativeError}
+ WASM (node-sqlite3-wasm): ${wasmError}`
+ );
+}
+var WASM_FALLBACK_FIX_RECIPE, WasmDatabaseAdapter;
+var init_sqlite_adapter = __esm({
+ "src/db/sqlite-adapter.ts"() {
+ "use strict";
+ WASM_FALLBACK_FIX_RECIPE = "`xcode-select --install` (macOS) or `apt install build-essential` (Debian/Ubuntu), then `npm rebuild better-sqlite3`, or `npm install better-sqlite3 --save` to force-include it.";
+ WasmDatabaseAdapter = class {
+ _db;
+ // Track raw WASM statements so we can finalize them on close.
+ // node-sqlite3-wasm won't release its file lock if statements are left open.
+ _openStmts = /* @__PURE__ */ new Set();
+ constructor(dbPath) {
+ const { Database } = require("node-sqlite3-wasm");
+ this._db = new Database(dbPath);
+ }
+ get open() {
+ return this._db.isOpen;
+ }
+ prepare(sql) {
+ const { sql: rewrittenSql, paramOrder } = translateNamedParams(sql);
+ const stmt = this._db.prepare(rewrittenSql);
+ this._openStmts.add(stmt);
+ return {
+ run(...params) {
+ const resolved = resolveParams(params, paramOrder);
+ const result = resolved !== void 0 ? stmt.run(resolved) : stmt.run();
+ return {
+ changes: result?.changes ?? 0,
+ lastInsertRowid: result?.lastInsertRowid ?? 0
+ };
+ },
+ get(...params) {
+ const resolved = resolveParams(params, paramOrder);
+ return resolved !== void 0 ? stmt.get(resolved) : stmt.get();
+ },
+ all(...params) {
+ const resolved = resolveParams(params, paramOrder);
+ return resolved !== void 0 ? stmt.all(resolved) : stmt.all();
+ }
+ };
+ }
+ exec(sql) {
+ this._db.exec(sql);
+ }
+ pragma(str) {
+ const trimmed = str.trim();
+ if (trimmed.includes("=")) {
+ const eqIdx = trimmed.indexOf("=");
+ const key = trimmed.substring(0, eqIdx).trim();
+ const value = trimmed.substring(eqIdx + 1).trim();
+ if (key === "journal_mode" && value.toUpperCase() === "WAL") {
+ this._db.exec("PRAGMA journal_mode = DELETE");
+ return;
+ }
+ if (key === "mmap_size") {
+ return;
+ }
+ if (key === "synchronous" && value.toUpperCase() === "NORMAL") {
+ this._db.exec("PRAGMA synchronous = FULL");
+ return;
+ }
+ this._db.exec(`PRAGMA ${key} = ${value}`);
+ return;
+ }
+ const stmt = this._db.prepare(`PRAGMA ${trimmed}`);
+ const result = stmt.get();
+ stmt.finalize();
+ return result;
+ }
+ transaction(fn) {
+ return (...args) => {
+ this._db.exec("BEGIN");
+ try {
+ const result = fn(...args);
+ this._db.exec("COMMIT");
+ return result;
+ } catch (error) {
+ this._db.exec("ROLLBACK");
+ throw error;
+ }
+ };
+ }
+ close() {
+ for (const stmt of this._openStmts) {
+ try {
+ stmt.finalize();
+ } catch {
+ }
+ }
+ this._openStmts.clear();
+ this._db.close();
+ }
+ };
+ }
+});
+
+// src/db/migrations.ts
+function getCurrentVersion(db) {
+ try {
+ const row = db.prepare("SELECT MAX(version) as version FROM schema_versions").get();
+ return row?.version ?? 0;
+ } catch {
+ return 0;
+ }
+}
+function recordMigration(db, version, description) {
+ db.prepare(
+ "INSERT INTO schema_versions (version, applied_at, description) VALUES (?, ?, ?)"
+ ).run(version, Date.now(), description);
+}
+function runMigrations(db, fromVersion) {
+ const pending = migrations.filter((m) => m.version > fromVersion);
+ if (pending.length === 0) {
+ return;
+ }
+ pending.sort((a, b) => a.version - b.version);
+ for (const migration of pending) {
+ db.transaction(() => {
+ migration.up(db);
+ recordMigration(db, migration.version, migration.description);
+ })();
+ }
+}
+var CURRENT_SCHEMA_VERSION, migrations;
+var init_migrations = __esm({
+ "src/db/migrations.ts"() {
+ "use strict";
+ CURRENT_SCHEMA_VERSION = 4;
+ migrations = [
+ {
+ version: 2,
+ description: "Add project metadata, provenance tracking, and unresolved ref context",
+ up: (db) => {
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS unresolved_refs (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ from_node_id TEXT NOT NULL,
+ reference_name TEXT NOT NULL,
+ reference_kind TEXT NOT NULL,
+ line INTEGER NOT NULL,
+ col INTEGER NOT NULL,
+ candidates TEXT,
+ FOREIGN KEY (from_node_id) REFERENCES nodes(id) ON DELETE CASCADE
+ );
+ CREATE TABLE IF NOT EXISTS project_metadata (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL,
+ updated_at INTEGER NOT NULL
+ );
+ `);
+ const unresCols = new Set(
+ db.prepare("PRAGMA table_info(unresolved_refs)").all().map(
+ (c) => c.name
+ )
+ );
+ if (!unresCols.has("file_path")) {
+ db.exec(`ALTER TABLE unresolved_refs ADD COLUMN file_path TEXT NOT NULL DEFAULT '';`);
+ }
+ if (!unresCols.has("language")) {
+ db.exec(
+ `ALTER TABLE unresolved_refs ADD COLUMN language TEXT NOT NULL DEFAULT 'unknown';`
+ );
+ }
+ const edgeCols = new Set(
+ db.prepare("PRAGMA table_info(edges)").all().map(
+ (c) => c.name
+ )
+ );
+ if (!edgeCols.has("provenance")) {
+ db.exec(`ALTER TABLE edges ADD COLUMN provenance TEXT DEFAULT NULL;`);
+ }
+ db.exec(`
+ CREATE INDEX IF NOT EXISTS idx_unresolved_file_path ON unresolved_refs(file_path);
+ CREATE INDEX IF NOT EXISTS idx_edges_provenance ON edges(provenance);
+ `);
+ }
+ },
+ {
+ version: 3,
+ description: "Add lower(name) expression index for memory-efficient case-insensitive lookups",
+ up: (db) => {
+ db.exec(`
+ CREATE INDEX IF NOT EXISTS idx_nodes_lower_name ON nodes(lower(name));
+ `);
+ }
+ },
+ {
+ version: 4,
+ description: "Drop redundant idx_edges_source / idx_edges_target (covered by source_kind / target_kind composites)",
+ up: (db) => {
+ db.exec(`
+ DROP INDEX IF EXISTS idx_edges_source;
+ DROP INDEX IF EXISTS idx_edges_target;
+ `);
+ }
+ }
+ ];
+ }
+});
+
+// src/db/index.ts
+function getDatabasePath(projectRoot) {
+ return path4.join(projectRoot, ".codegraph", DATABASE_FILENAME);
+}
+var fs3, path4, DatabaseConnection, DATABASE_FILENAME;
+var init_db = __esm({
+ "src/db/index.ts"() {
+ "use strict";
+ init_sqlite_adapter();
+ fs3 = __toESM(require("fs"));
+ path4 = __toESM(require("path"));
+ init_migrations();
+ init_sqlite_adapter();
+ DatabaseConnection = class _DatabaseConnection {
+ db;
+ dbPath;
+ backend;
+ constructor(db, dbPath, backend) {
+ this.db = db;
+ this.dbPath = dbPath;
+ this.backend = backend;
+ }
+ /**
+ * Initialize a new database at the given path
+ */
+ static initialize(dbPath) {
+ const dir = path4.dirname(dbPath);
+ if (!fs3.existsSync(dir)) {
+ fs3.mkdirSync(dir, { recursive: true });
+ }
+ const { db, backend } = createDatabase(dbPath);
+ db.pragma("foreign_keys = ON");
+ db.pragma("journal_mode = WAL");
+ db.pragma("busy_timeout = 120000");
+ db.pragma("synchronous = NORMAL");
+ db.pragma("cache_size = -64000");
+ db.pragma("temp_store = MEMORY");
+ db.pragma("mmap_size = 268435456");
+ const schemaPath = path4.join(__dirname, "schema.sql");
+ const schema = fs3.readFileSync(schemaPath, "utf-8");
+ db.exec(schema);
+ const currentVersion = getCurrentVersion(db);
+ if (currentVersion < CURRENT_SCHEMA_VERSION) {
+ db.prepare(
+ "INSERT OR IGNORE INTO schema_versions (version, applied_at, description) VALUES (?, ?, ?)"
+ ).run(CURRENT_SCHEMA_VERSION, Date.now(), "Initial schema includes all migrations");
+ }
+ return new _DatabaseConnection(db, dbPath, backend);
+ }
+ /**
+ * Open an existing database
+ */
+ static open(dbPath) {
+ if (!fs3.existsSync(dbPath)) {
+ throw new Error(`Database not found: ${dbPath}`);
+ }
+ const { db, backend } = createDatabase(dbPath);
+ db.pragma("foreign_keys = ON");
+ db.pragma("journal_mode = WAL");
+ db.pragma("busy_timeout = 120000");
+ db.pragma("synchronous = NORMAL");
+ db.pragma("cache_size = -64000");
+ db.pragma("temp_store = MEMORY");
+ db.pragma("mmap_size = 268435456");
+ const conn = new _DatabaseConnection(db, dbPath, backend);
+ const currentVersion = getCurrentVersion(db);
+ if (currentVersion < CURRENT_SCHEMA_VERSION) {
+ runMigrations(db, currentVersion);
+ }
+ return conn;
+ }
+ /**
+ * Get the underlying database instance
+ */
+ getDb() {
+ return this.db;
+ }
+ /**
+ * Get the SQLite backend serving this connection. Per-instance so
+ * MCP cross-project queries report the right backend even when
+ * multiple project DBs are open in the same process.
+ */
+ getBackend() {
+ return this.backend;
+ }
+ /**
+ * Get database file path
+ */
+ getPath() {
+ return this.dbPath;
+ }
+ /**
+ * Get current schema version
+ */
+ getSchemaVersion() {
+ const row = this.db.prepare("SELECT version, applied_at, description FROM schema_versions ORDER BY version DESC LIMIT 1").get();
+ if (!row) return null;
+ return {
+ version: row.version,
+ appliedAt: row.applied_at,
+ description: row.description ?? void 0
+ };
+ }
+ /**
+ * Execute a function within a transaction
+ */
+ transaction(fn) {
+ return this.db.transaction(fn)();
+ }
+ /**
+ * Get database file size in bytes
+ */
+ getSize() {
+ const stats = fs3.statSync(this.dbPath);
+ return stats.size;
+ }
+ /**
+ * Optimize database (vacuum and analyze)
+ */
+ optimize() {
+ this.db.exec("VACUUM");
+ this.db.exec("ANALYZE");
+ }
+ /**
+ * Close the database connection
+ */
+ close() {
+ this.db.close();
+ }
+ /**
+ * Check if the database connection is open
+ */
+ isOpen() {
+ return this.db.open;
+ }
+ };
+ DATABASE_FILENAME = "codegraph.db";
+ }
+});
+
+// src/utils.ts
+function validatePathWithinRoot(projectRoot, filePath) {
+ const resolved = path5.resolve(projectRoot, filePath);
+ const normalizedRoot = path5.resolve(projectRoot);
+ if (!resolved.startsWith(normalizedRoot + path5.sep) && resolved !== normalizedRoot) {
+ return null;
+ }
+ return resolved;
+}
+function safeJsonParse(value, fallback) {
+ try {
+ return JSON.parse(value);
+ } catch {
+ return fallback;
+ }
+}
+function clamp(value, min, max) {
+ return Math.max(min, Math.min(max, value));
+}
+function normalizePath(filePath) {
+ return filePath.replace(/\\/g, "/");
+}
+async function processInBatches(items, batchSize, processor, onBatchComplete) {
+ const results = [];
+ for (let i = 0; i < items.length; i += batchSize) {
+ const batch = items.slice(i, Math.min(i + batchSize, items.length));
+ const batchResults = await Promise.all(
+ batch.map((item, idx) => processor(item, i + idx))
+ );
+ results.push(...batchResults);
+ if (onBatchComplete) {
+ onBatchComplete(Math.min(i + batchSize, items.length), items.length);
+ }
+ if (global.gc) {
+ global.gc();
+ }
+ }
+ return results;
+}
+function debounce(fn, delay) {
+ let timeoutId = null;
+ return (...args) => {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ timeoutId = setTimeout(() => {
+ fn(...args);
+ timeoutId = null;
+ }, delay);
+ };
+}
+function throttle(fn, limit) {
+ let lastCall = 0;
+ let timeoutId = null;
+ return (...args) => {
+ const now = Date.now();
+ const remaining = limit - (now - lastCall);
+ if (remaining <= 0) {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ timeoutId = null;
+ }
+ lastCall = now;
+ fn(...args);
+ } else if (!timeoutId) {
+ timeoutId = setTimeout(() => {
+ lastCall = Date.now();
+ timeoutId = null;
+ fn(...args);
+ }, remaining);
+ }
+ };
+}
+var fs4, path5, FileLock, Mutex, MemoryMonitor;
+var init_utils = __esm({
+ "src/utils.ts"() {
+ "use strict";
+ fs4 = __toESM(require("fs"));
+ path5 = __toESM(require("path"));
+ FileLock = class _FileLock {
+ lockPath;
+ held = false;
+ /** Locks older than this are considered stale regardless of PID status */
+ static STALE_TIMEOUT_MS = 2 * 60 * 1e3;
+ // 2 minutes
+ constructor(lockPath) {
+ this.lockPath = lockPath;
+ }
+ /**
+ * Acquire the lock. Throws if the lock is held by another live process.
+ */
+ acquire() {
+ if (fs4.existsSync(this.lockPath)) {
+ try {
+ const content = fs4.readFileSync(this.lockPath, "utf-8").trim();
+ const pid = parseInt(content, 10);
+ const stat2 = fs4.statSync(this.lockPath);
+ const lockAge = Date.now() - stat2.mtimeMs;
+ if (lockAge < _FileLock.STALE_TIMEOUT_MS && !isNaN(pid) && this.isProcessAlive(pid)) {
+ throw new Error(
+ `CodeGraph database is locked by another process (PID ${pid}). If this is stale, run 'codegraph unlock' or delete ${this.lockPath}`
+ );
+ }
+ fs4.unlinkSync(this.lockPath);
+ } catch (err) {
+ if (err instanceof Error && err.message.includes("locked by another")) {
+ throw err;
+ }
+ try {
+ fs4.unlinkSync(this.lockPath);
+ } catch {
+ }
+ }
+ }
+ try {
+ fs4.writeFileSync(this.lockPath, String(process.pid), { flag: "wx" });
+ this.held = true;
+ } catch (err) {
+ if (err.code === "EEXIST") {
+ throw new Error(
+ `CodeGraph database is locked by another process. If this is stale, run 'codegraph unlock' or delete ${this.lockPath}`
+ );
+ }
+ throw err;
+ }
+ }
+ /**
+ * Release the lock
+ */
+ release() {
+ if (!this.held) return;
+ try {
+ const content = fs4.readFileSync(this.lockPath, "utf-8").trim();
+ if (parseInt(content, 10) === process.pid) {
+ fs4.unlinkSync(this.lockPath);
+ }
+ } catch {
+ }
+ this.held = false;
+ }
+ /**
+ * Execute a function while holding the lock
+ */
+ withLock(fn) {
+ this.acquire();
+ try {
+ return fn();
+ } finally {
+ this.release();
+ }
+ }
+ /**
+ * Execute an async function while holding the lock
+ */
+ async withLockAsync(fn) {
+ this.acquire();
+ try {
+ return await fn();
+ } finally {
+ this.release();
+ }
+ }
+ /**
+ * Check if a process is still running
+ */
+ isProcessAlive(pid) {
+ try {
+ process.kill(pid, 0);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+ };
+ Mutex = class {
+ locked = false;
+ waitQueue = [];
+ /**
+ * Acquire the lock
+ *
+ * @returns A release function to call when done
+ */
+ async acquire() {
+ while (this.locked) {
+ await new Promise((resolve10) => {
+ this.waitQueue.push(resolve10);
+ });
+ }
+ this.locked = true;
+ return () => {
+ this.locked = false;
+ const next = this.waitQueue.shift();
+ if (next) {
+ next();
+ }
+ };
+ }
+ /**
+ * Execute a function while holding the lock
+ */
+ async withLock(fn) {
+ const release = await this.acquire();
+ try {
+ return await fn();
+ } finally {
+ release();
+ }
+ }
+ /**
+ * Check if the lock is currently held
+ */
+ isLocked() {
+ return this.locked;
+ }
+ };
+ MemoryMonitor = class {
+ checkInterval = null;
+ peakUsage = 0;
+ threshold;
+ onThresholdExceeded;
+ constructor(thresholdMB = 500, onThresholdExceeded) {
+ this.threshold = thresholdMB * 1024 * 1024;
+ this.onThresholdExceeded = onThresholdExceeded;
+ }
+ /**
+ * Start monitoring memory usage
+ */
+ start(intervalMs = 1e3) {
+ this.stop();
+ this.peakUsage = 0;
+ this.checkInterval = setInterval(() => {
+ const usage = process.memoryUsage().heapUsed;
+ if (usage > this.peakUsage) {
+ this.peakUsage = usage;
+ }
+ if (usage > this.threshold && this.onThresholdExceeded) {
+ this.onThresholdExceeded(usage);
+ }
+ }, intervalMs);
+ }
+ /**
+ * Stop monitoring
+ */
+ stop() {
+ if (this.checkInterval) {
+ clearInterval(this.checkInterval);
+ this.checkInterval = null;
+ }
+ }
+ /**
+ * Get peak memory usage in bytes
+ */
+ getPeakUsage() {
+ return this.peakUsage;
+ }
+ /**
+ * Get current memory usage in bytes
+ */
+ getCurrentUsage() {
+ return process.memoryUsage().heapUsed;
+ }
+ };
+ }
+});
+
+// src/search/query-utils.ts
+function getStemVariants(term) {
+ const variants = /* @__PURE__ */ new Set();
+ const t = term.toLowerCase();
+ if (t.endsWith("ing") && t.length > 5) {
+ const base = t.slice(0, -3);
+ variants.add(base);
+ variants.add(base + "e");
+ if (base.length >= 2 && base[base.length - 1] === base[base.length - 2]) {
+ variants.add(base.slice(0, -1));
+ }
+ }
+ if ((t.endsWith("tion") || t.endsWith("sion")) && t.length > 5) {
+ variants.add(t.slice(0, -3));
+ }
+ if (t.endsWith("ment") && t.length > 6) {
+ variants.add(t.slice(0, -4));
+ }
+ if (t.endsWith("ies") && t.length > 4) {
+ variants.add(t.slice(0, -3) + "y");
+ } else if (t.endsWith("es") && t.length > 4) {
+ variants.add(t.slice(0, -2));
+ } else if (t.endsWith("s") && !t.endsWith("ss") && t.length > 4) {
+ variants.add(t.slice(0, -1));
+ }
+ if (t.endsWith("ed") && !t.endsWith("eed") && t.length > 4) {
+ variants.add(t.slice(0, -1));
+ variants.add(t.slice(0, -2));
+ if (t.endsWith("ied") && t.length > 5) {
+ variants.add(t.slice(0, -3) + "y");
+ }
+ }
+ if (t.endsWith("er") && t.length > 4) {
+ const base = t.slice(0, -2);
+ variants.add(base);
+ variants.add(base + "e");
+ if (base.length >= 2 && base[base.length - 1] === base[base.length - 2]) {
+ variants.add(base.slice(0, -1));
+ }
+ }
+ return [...variants].filter((v) => v.length >= 3 && v !== t);
+}
+function extractSearchTerms(query, options) {
+ const includeStems = options?.stems !== false;
+ const tokens = /* @__PURE__ */ new Set();
+ const compoundPattern = /\b([a-zA-Z][a-zA-Z0-9]*(?:[A-Z][a-z]+)+|[A-Z][a-z]+(?:[A-Z][a-z]*)+)\b/g;
+ let match;
+ while ((match = compoundPattern.exec(query)) !== null) {
+ if (match[1] && match[1].length >= 3) {
+ tokens.add(match[1].toLowerCase());
+ }
+ }
+ const snakePattern = /\b([a-zA-Z][a-zA-Z0-9]*(?:_[a-zA-Z0-9]+)+)\b/g;
+ while ((match = snakePattern.exec(query)) !== null) {
+ if (match[1] && match[1].length >= 3) {
+ tokens.add(match[1].toLowerCase());
+ }
+ }
+ const camelSplit = query.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
+ const normalised = camelSplit.replace(/[_.]+/g, " ");
+ const words = normalised.split(/[^a-zA-Z0-9]+/).filter(Boolean);
+ for (const word of words) {
+ const lower = word.toLowerCase();
+ if (lower.length < 3) continue;
+ if (STOP_WORDS.has(lower)) continue;
+ tokens.add(lower);
+ }
+ if (includeStems) {
+ const stems = /* @__PURE__ */ new Set();
+ for (const token of tokens) {
+ for (const variant of getStemVariants(token)) {
+ if (!tokens.has(variant) && !STOP_WORDS.has(variant)) {
+ stems.add(variant);
+ }
+ }
+ }
+ for (const stem of stems) {
+ tokens.add(stem);
+ }
+ }
+ return [...tokens];
+}
+function scorePathRelevance(filePath, query) {
+ const terms = extractSearchTerms(query, { stems: false });
+ if (terms.length === 0) return 0;
+ const pathLower = filePath.toLowerCase();
+ const fileName = path6.basename(filePath).toLowerCase();
+ const dirName = path6.dirname(filePath).toLowerCase();
+ let score = 0;
+ for (const term of terms) {
+ if (fileName.includes(term)) score += 10;
+ if (dirName.includes(term)) score += 5;
+ else if (pathLower.includes(term)) score += 3;
+ }
+ const queryLower = query.toLowerCase();
+ const isTestQuery = queryLower.includes("test") || queryLower.includes("spec");
+ if (!isTestQuery && isTestFile(filePath)) {
+ score -= 15;
+ }
+ return score;
+}
+function isTestFile(filePath) {
+ const lower = filePath.toLowerCase();
+ const fileName = path6.basename(lower);
+ return fileName.startsWith("test_") || fileName.startsWith("test.") || fileName.endsWith(".test.ts") || fileName.endsWith(".test.js") || fileName.endsWith(".test.tsx") || fileName.endsWith(".test.jsx") || fileName.endsWith(".spec.ts") || fileName.endsWith(".spec.js") || fileName.endsWith("_test.go") || fileName.endsWith("_test.py") || fileName.endsWith("_test.rs") || fileName.endsWith("Tests.java") || fileName.endsWith("Test.java") || fileName.endsWith("Tester.java") || fileName.endsWith("TestCase.java") || lower.includes("/tests/") || lower.includes("/test/") || lower.includes("/__tests__/") || lower.includes("/spec/") || lower.includes("/testlib/") || lower.includes("/testing/") || // Non-production directories: examples, samples, benchmarks, fixtures, demos.
+ // Check both mid-path (/integration/) and start-of-path (integration/) since
+ // file paths may be stored as relative paths without a leading slash.
+ matchesNonProductionDir(lower);
+}
+function matchesNonProductionDir(lowerPath) {
+ const dirs = [
+ "integration",
+ "sample",
+ "samples",
+ "example",
+ "examples",
+ "fixture",
+ "fixtures",
+ "benchmark",
+ "benchmarks",
+ "demo",
+ "demos"
+ ];
+ for (const dir of dirs) {
+ if (lowerPath.includes("/" + dir + "/") || lowerPath.startsWith(dir + "/")) {
+ return true;
+ }
+ }
+ return false;
+}
+function nameMatchBonus(nodeName, query) {
+ const nameLower = nodeName.toLowerCase();
+ const rawTerms = query.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[\s_.\-]+/).map((t) => t.toLowerCase()).filter((t) => t.length >= 2);
+ const queryTokens = query.split(/\s+/).map((t) => t.toLowerCase()).filter((t) => t.length >= 2);
+ const queryLower = query.replace(/[\s]+/g, "").toLowerCase();
+ if (nameLower === queryLower) return 80;
+ if (queryTokens.length > 1 && queryTokens.includes(nameLower)) return 60;
+ if (nameLower.startsWith(queryLower)) {
+ const ratio = queryLower.length / nameLower.length;
+ return Math.round(10 + 30 * ratio);
+ }
+ if (rawTerms.length > 1) {
+ const allMatch = rawTerms.every((t) => nameLower.includes(t));
+ if (allMatch) return 15;
+ }
+ if (nameLower.includes(queryLower)) return 10;
+ return 0;
+}
+function kindBonus(kind) {
+ const bonuses = {
+ function: 10,
+ method: 10,
+ class: 8,
+ interface: 9,
+ type_alias: 6,
+ struct: 6,
+ trait: 9,
+ enum: 5,
+ component: 8,
+ route: 9,
+ module: 4,
+ property: 3,
+ field: 3,
+ variable: 2,
+ constant: 3,
+ import: 1,
+ export: 1,
+ parameter: 0,
+ namespace: 4,
+ file: 0,
+ protocol: 9,
+ enum_member: 3
+ };
+ return bonuses[kind] ?? 0;
+}
+var path6, STOP_WORDS;
+var init_query_utils = __esm({
+ "src/search/query-utils.ts"() {
+ "use strict";
+ path6 = __toESM(require("path"));
+ STOP_WORDS = /* @__PURE__ */ new Set([
+ // English
+ "the",
+ "a",
+ "an",
+ "and",
+ "or",
+ "but",
+ "in",
+ "on",
+ "at",
+ "to",
+ "for",
+ "of",
+ "with",
+ "by",
+ "from",
+ "is",
+ "it",
+ "that",
+ "this",
+ "are",
+ "was",
+ "be",
+ "has",
+ "had",
+ "have",
+ "do",
+ "does",
+ "did",
+ "will",
+ "would",
+ "could",
+ "should",
+ "may",
+ "might",
+ "can",
+ "shall",
+ "not",
+ "no",
+ "all",
+ "each",
+ "every",
+ "how",
+ "what",
+ "where",
+ "when",
+ "who",
+ "which",
+ "why",
+ "i",
+ "me",
+ "my",
+ "we",
+ "our",
+ "you",
+ "your",
+ "he",
+ "she",
+ "they",
+ "show",
+ "give",
+ "tell",
+ "been",
+ "done",
+ "made",
+ "used",
+ "using",
+ "work",
+ "works",
+ "found",
+ "also",
+ "into",
+ "then",
+ "than",
+ "just",
+ "more",
+ "some",
+ "such",
+ "over",
+ "only",
+ "out",
+ "its",
+ "so",
+ "up",
+ "as",
+ "if",
+ "look",
+ "need",
+ "needs",
+ "want",
+ "happen",
+ "happens",
+ "affect",
+ "affected",
+ "break",
+ "breaks",
+ "failing",
+ "implemented",
+ "implement",
+ // Code-specific noise (avoid filtering common symbol names like get/set/add/build/find/list)
+ "code",
+ "file",
+ "files",
+ "function",
+ "method",
+ "class",
+ "type",
+ "fix",
+ "bug",
+ "called"
+ ]);
+ }
+});
+
+// src/types.ts
+var NODE_KINDS, LANGUAGES, DEFAULT_CONFIG;
+var init_types = __esm({
+ "src/types.ts"() {
+ "use strict";
+ NODE_KINDS = [
+ "file",
+ "module",
+ "class",
+ "struct",
+ "interface",
+ "trait",
+ "protocol",
+ "function",
+ "method",
+ "property",
+ "field",
+ "variable",
+ "constant",
+ "enum",
+ "enum_member",
+ "type_alias",
+ "namespace",
+ "parameter",
+ "import",
+ "export",
+ "route",
+ "component"
+ ];
+ LANGUAGES = [
+ "typescript",
+ "javascript",
+ "tsx",
+ "jsx",
+ "python",
+ "go",
+ "rust",
+ "java",
+ "c",
+ "cpp",
+ "csharp",
+ "php",
+ "ruby",
+ "swift",
+ "kotlin",
+ "dart",
+ "svelte",
+ "vue",
+ "liquid",
+ "pascal",
+ "scala",
+ "unknown"
+ ];
+ DEFAULT_CONFIG = {
+ version: 1,
+ rootDir: ".",
+ include: [
+ // TypeScript/JavaScript
+ "**/*.ts",
+ "**/*.tsx",
+ "**/*.js",
+ "**/*.jsx",
+ // Python
+ "**/*.py",
+ // Go
+ "**/*.go",
+ // Rust
+ "**/*.rs",
+ // Java
+ "**/*.java",
+ // C/C++
+ "**/*.c",
+ "**/*.h",
+ "**/*.cpp",
+ "**/*.hpp",
+ "**/*.cc",
+ "**/*.cxx",
+ // C#
+ "**/*.cs",
+ // PHP
+ "**/*.php",
+ // Ruby
+ "**/*.rb",
+ // Swift
+ "**/*.swift",
+ // Kotlin
+ "**/*.kt",
+ "**/*.kts",
+ // Dart
+ "**/*.dart",
+ // Svelte
+ "**/*.svelte",
+ // Vue
+ "**/*.vue",
+ // Liquid (Shopify themes)
+ "**/*.liquid",
+ // Pascal / Delphi
+ "**/*.pas",
+ "**/*.dpr",
+ "**/*.dpk",
+ "**/*.lpr",
+ "**/*.dfm",
+ "**/*.fmx",
+ // Scala
+ "**/*.scala",
+ "**/*.sc"
+ ],
+ exclude: [
+ // Version control
+ "**/.git/**",
+ // Dependencies
+ "**/node_modules/**",
+ "**/vendor/**",
+ "**/Pods/**",
+ // Generic build outputs
+ "**/dist/**",
+ "**/build/**",
+ "**/out/**",
+ "**/bin/**",
+ "**/obj/**",
+ "**/target/**",
+ // JavaScript/TypeScript
+ "**/*.min.js",
+ "**/*.bundle.js",
+ "**/.next/**",
+ "**/.nuxt/**",
+ "**/.svelte-kit/**",
+ "**/.output/**",
+ "**/.turbo/**",
+ "**/.cache/**",
+ "**/.parcel-cache/**",
+ "**/.vite/**",
+ "**/.astro/**",
+ "**/.docusaurus/**",
+ "**/.gatsby/**",
+ "**/.webpack/**",
+ "**/.nx/**",
+ "**/.yarn/cache/**",
+ "**/.pnpm-store/**",
+ "**/storybook-static/**",
+ // React Native / Expo
+ "**/.expo/**",
+ "**/web-build/**",
+ "**/ios/Pods/**",
+ "**/ios/build/**",
+ "**/android/build/**",
+ "**/android/.gradle/**",
+ // Python
+ "**/__pycache__/**",
+ "**/.venv/**",
+ "**/venv/**",
+ "**/site-packages/**",
+ "**/dist-packages/**",
+ "**/.pytest_cache/**",
+ "**/.mypy_cache/**",
+ "**/.ruff_cache/**",
+ "**/.tox/**",
+ "**/.nox/**",
+ "**/*.egg-info/**",
+ "**/.eggs/**",
+ // Go
+ "**/go/pkg/mod/**",
+ // Rust
+ "**/target/debug/**",
+ "**/target/release/**",
+ // Java/Kotlin/Gradle
+ "**/.gradle/**",
+ "**/.m2/**",
+ "**/generated-sources/**",
+ "**/.kotlin/**",
+ // Dart/Flutter
+ "**/.dart_tool/**",
+ // C#/.NET
+ "**/.vs/**",
+ "**/.nuget/**",
+ "**/artifacts/**",
+ "**/publish/**",
+ // C/C++
+ "**/cmake-build-*/**",
+ "**/CMakeFiles/**",
+ "**/bazel-*/**",
+ "**/vcpkg_installed/**",
+ "**/.conan/**",
+ "**/Debug/**",
+ "**/Release/**",
+ "**/x64/**",
+ "**/.pio/**",
+ // Platform.io (IoT/embedded build artifacts and library deps)
+ // Electron
+ "**/release/**",
+ "**/*.app/**",
+ "**/*.asar",
+ // Swift/iOS/Xcode
+ "**/DerivedData/**",
+ "**/.build/**",
+ "**/.swiftpm/**",
+ "**/xcuserdata/**",
+ "**/Carthage/Build/**",
+ "**/SourcePackages/**",
+ // Delphi/Pascal
+ "**/__history/**",
+ "**/__recovery/**",
+ "**/*.dcu",
+ // PHP
+ "**/.composer/**",
+ "**/storage/framework/**",
+ "**/bootstrap/cache/**",
+ // Ruby
+ "**/.bundle/**",
+ "**/tmp/cache/**",
+ "**/public/assets/**",
+ "**/public/packs/**",
+ "**/.yardoc/**",
+ // Testing/Coverage
+ "**/coverage/**",
+ "**/htmlcov/**",
+ "**/.nyc_output/**",
+ "**/test-results/**",
+ "**/.coverage/**",
+ // IDE/Editor
+ "**/.idea/**",
+ // Logs and temp
+ "**/logs/**",
+ "**/tmp/**",
+ "**/temp/**",
+ // Documentation build output
+ "**/_build/**",
+ "**/docs/_build/**",
+ "**/site/**"
+ ],
+ languages: [],
+ frameworks: [],
+ maxFileSize: 1024 * 1024,
+ // 1MB
+ extractDocstrings: true,
+ trackCallSites: true
+ };
+ }
+});
+
+// src/search/query-parser.ts
+function unquote(s) {
+ if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) return s.slice(1, -1);
+ return s;
+}
+function parseQuery(raw) {
+ const out = {
+ text: "",
+ kinds: [],
+ languages: [],
+ pathFilters: [],
+ nameFilters: []
+ };
+ const tokens = [];
+ let i = 0;
+ while (i < raw.length) {
+ while (i < raw.length && /\s/.test(raw[i])) i++;
+ if (i >= raw.length) break;
+ const start = i;
+ while (i < raw.length && !/\s/.test(raw[i])) {
+ if (raw[i] === '"') {
+ const end = raw.indexOf('"', i + 1);
+ if (end === -1) {
+ i = raw.length;
+ break;
+ }
+ i = end + 1;
+ continue;
+ }
+ i++;
+ }
+ tokens.push(raw.slice(start, i));
+ }
+ const textParts = [];
+ for (const tok of tokens) {
+ const colon = tok.indexOf(":");
+ if (colon <= 0 || colon === tok.length - 1) {
+ textParts.push(tok);
+ continue;
+ }
+ const key = tok.slice(0, colon).toLowerCase();
+ const valueRaw = unquote(tok.slice(colon + 1));
+ if (!valueRaw) {
+ textParts.push(tok);
+ continue;
+ }
+ switch (key) {
+ case "kind": {
+ if (KIND_VALUES.has(valueRaw)) {
+ out.kinds.push(valueRaw);
+ } else {
+ textParts.push(tok);
+ }
+ break;
+ }
+ case "lang":
+ case "language": {
+ const lower = valueRaw.toLowerCase();
+ if (LANGUAGE_VALUES.has(lower)) {
+ out.languages.push(lower);
+ } else {
+ textParts.push(tok);
+ }
+ break;
+ }
+ case "path":
+ out.pathFilters.push(valueRaw);
+ break;
+ case "name":
+ out.nameFilters.push(valueRaw);
+ break;
+ default:
+ textParts.push(tok);
+ }
+ }
+ out.text = textParts.join(" ").trim();
+ return out;
+}
+function boundedEditDistance(a, b, maxDist) {
+ if (a === b) return 0;
+ const al = a.length;
+ const bl = b.length;
+ if (Math.abs(al - bl) > maxDist) return maxDist + 1;
+ if (al === 0) return bl;
+ if (bl === 0) return al;
+ let prev = new Array(bl + 1);
+ let cur = new Array(bl + 1);
+ for (let j = 0; j <= bl; j++) prev[j] = j;
+ for (let i = 1; i <= al; i++) {
+ cur[0] = i;
+ let rowMin = cur[0];
+ for (let j = 1; j <= bl; j++) {
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
+ const insertion = cur[j - 1] + 1;
+ const deletion = prev[j] + 1;
+ const substitution = prev[j - 1] + cost;
+ cur[j] = Math.min(insertion, deletion, substitution);
+ if (cur[j] < rowMin) rowMin = cur[j];
+ }
+ if (rowMin > maxDist) return maxDist + 1;
+ [prev, cur] = [cur, prev];
+ }
+ return prev[bl];
+}
+var KIND_VALUES, LANGUAGE_VALUES;
+var init_query_parser = __esm({
+ "src/search/query-parser.ts"() {
+ "use strict";
+ init_types();
+ KIND_VALUES = new Set(NODE_KINDS);
+ LANGUAGE_VALUES = new Set(LANGUAGES);
+ }
+});
+
+// src/db/queries.ts
+function rowToNode(row) {
+ return {
+ id: row.id,
+ kind: row.kind,
+ name: row.name,
+ qualifiedName: row.qualified_name,
+ filePath: row.file_path,
+ language: row.language,
+ startLine: row.start_line,
+ endLine: row.end_line,
+ startColumn: row.start_column,
+ endColumn: row.end_column,
+ docstring: row.docstring ?? void 0,
+ signature: row.signature ?? void 0,
+ visibility: row.visibility,
+ isExported: row.is_exported === 1,
+ isAsync: row.is_async === 1,
+ isStatic: row.is_static === 1,
+ isAbstract: row.is_abstract === 1,
+ decorators: row.decorators ? safeJsonParse(row.decorators, void 0) : void 0,
+ typeParameters: row.type_parameters ? safeJsonParse(row.type_parameters, void 0) : void 0,
+ updatedAt: row.updated_at
+ };
+}
+function rowToEdge(row) {
+ return {
+ source: row.source,
+ target: row.target,
+ kind: row.kind,
+ metadata: row.metadata ? safeJsonParse(row.metadata, void 0) : void 0,
+ line: row.line ?? void 0,
+ column: row.col ?? void 0,
+ provenance: row.provenance
+ };
+}
+function rowToFileRecord(row) {
+ return {
+ path: row.path,
+ contentHash: row.content_hash,
+ language: row.language,
+ size: row.size,
+ modifiedAt: row.modified_at,
+ indexedAt: row.indexed_at,
+ nodeCount: row.node_count,
+ errors: row.errors ? safeJsonParse(row.errors, void 0) : void 0
+ };
+}
+var QueryBuilder;
+var init_queries = __esm({
+ "src/db/queries.ts"() {
+ "use strict";
+ init_utils();
+ init_query_utils();
+ init_query_parser();
+ QueryBuilder = class {
+ db;
+ // Node cache for frequently accessed nodes (LRU-style, max 1000 entries)
+ nodeCache = /* @__PURE__ */ new Map();
+ maxCacheSize = 1e3;
+ // Prepared statements (lazily initialized)
+ stmts = {};
+ constructor(db) {
+ this.db = db;
+ }
+ // ===========================================================================
+ // Node Operations
+ // ===========================================================================
+ /**
+ * Insert a new node
+ */
+ insertNode(node) {
+ if (!this.stmts.insertNode) {
+ this.stmts.insertNode = this.db.prepare(`
+ INSERT OR REPLACE INTO nodes (
+ id, kind, name, qualified_name, file_path, language,
+ start_line, end_line, start_column, end_column,
+ docstring, signature, visibility,
+ is_exported, is_async, is_static, is_abstract,
+ decorators, type_parameters, updated_at
+ ) VALUES (
+ @id, @kind, @name, @qualifiedName, @filePath, @language,
+ @startLine, @endLine, @startColumn, @endColumn,
+ @docstring, @signature, @visibility,
+ @isExported, @isAsync, @isStatic, @isAbstract,
+ @decorators, @typeParameters, @updatedAt
+ )
+ `);
+ }
+ if (!node.id || !node.kind || !node.name || !node.filePath || !node.language) {
+ console.error("[CodeGraph] Skipping node with missing required fields:", {
+ id: node.id,
+ kind: node.kind,
+ name: node.name,
+ filePath: node.filePath,
+ language: node.language
+ });
+ return;
+ }
+ try {
+ this.stmts.insertNode.run({
+ id: node.id,
+ kind: node.kind,
+ name: node.name,
+ qualifiedName: node.qualifiedName ?? node.name,
+ filePath: node.filePath,
+ language: node.language,
+ startLine: node.startLine ?? 0,
+ endLine: node.endLine ?? 0,
+ startColumn: node.startColumn ?? 0,
+ endColumn: node.endColumn ?? 0,
+ docstring: node.docstring ?? null,
+ signature: node.signature ?? null,
+ visibility: node.visibility ?? null,
+ isExported: node.isExported ? 1 : 0,
+ isAsync: node.isAsync ? 1 : 0,
+ isStatic: node.isStatic ? 1 : 0,
+ isAbstract: node.isAbstract ? 1 : 0,
+ decorators: node.decorators ? JSON.stringify(node.decorators) : null,
+ typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
+ updatedAt: node.updatedAt ?? Date.now()
+ });
+ } catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Insert multiple nodes in a transaction
+ */
+ insertNodes(nodes) {
+ this.db.transaction(() => {
+ for (const node of nodes) {
+ this.insertNode(node);
+ }
+ })();
+ }
+ /**
+ * Update an existing node
+ */
+ updateNode(node) {
+ if (!this.stmts.updateNode) {
+ this.stmts.updateNode = this.db.prepare(`
+ UPDATE nodes SET
+ kind = @kind,
+ name = @name,
+ qualified_name = @qualifiedName,
+ file_path = @filePath,
+ language = @language,
+ start_line = @startLine,
+ end_line = @endLine,
+ start_column = @startColumn,
+ end_column = @endColumn,
+ docstring = @docstring,
+ signature = @signature,
+ visibility = @visibility,
+ is_exported = @isExported,
+ is_async = @isAsync,
+ is_static = @isStatic,
+ is_abstract = @isAbstract,
+ decorators = @decorators,
+ type_parameters = @typeParameters,
+ updated_at = @updatedAt
+ WHERE id = @id
+ `);
+ }
+ this.nodeCache.delete(node.id);
+ if (!node.id || !node.kind || !node.name || !node.filePath || !node.language) {
+ console.error("[CodeGraph] Skipping node update with missing required fields:", node.id);
+ return;
+ }
+ this.stmts.updateNode.run({
+ id: node.id,
+ kind: node.kind,
+ name: node.name,
+ qualifiedName: node.qualifiedName ?? node.name,
+ filePath: node.filePath,
+ language: node.language,
+ startLine: node.startLine ?? 0,
+ endLine: node.endLine ?? 0,
+ startColumn: node.startColumn ?? 0,
+ endColumn: node.endColumn ?? 0,
+ docstring: node.docstring ?? null,
+ signature: node.signature ?? null,
+ visibility: node.visibility ?? null,
+ isExported: node.isExported ? 1 : 0,
+ isAsync: node.isAsync ? 1 : 0,
+ isStatic: node.isStatic ? 1 : 0,
+ isAbstract: node.isAbstract ? 1 : 0,
+ decorators: node.decorators ? JSON.stringify(node.decorators) : null,
+ typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
+ updatedAt: node.updatedAt ?? Date.now()
+ });
+ }
+ /**
+ * Delete a node by ID
+ */
+ deleteNode(id) {
+ if (!this.stmts.deleteNode) {
+ this.stmts.deleteNode = this.db.prepare("DELETE FROM nodes WHERE id = ?");
+ }
+ this.nodeCache.delete(id);
+ this.stmts.deleteNode.run(id);
+ }
+ /**
+ * Delete all nodes for a file
+ */
+ deleteNodesByFile(filePath) {
+ if (!this.stmts.deleteNodesByFile) {
+ this.stmts.deleteNodesByFile = this.db.prepare("DELETE FROM nodes WHERE file_path = ?");
+ }
+ for (const [id, node] of this.nodeCache) {
+ if (node.filePath === filePath) {
+ this.nodeCache.delete(id);
+ }
+ }
+ this.stmts.deleteNodesByFile.run(filePath);
+ }
+ /**
+ * Get a node by ID
+ */
+ getNodeById(id) {
+ if (this.nodeCache.has(id)) {
+ const cached = this.nodeCache.get(id);
+ this.nodeCache.delete(id);
+ this.nodeCache.set(id, cached);
+ return cached;
+ }
+ if (!this.stmts.getNodeById) {
+ this.stmts.getNodeById = this.db.prepare("SELECT * FROM nodes WHERE id = ?");
+ }
+ const row = this.stmts.getNodeById.get(id);
+ if (!row) {
+ return null;
+ }
+ const node = rowToNode(row);
+ this.cacheNode(node);
+ return node;
+ }
+ /**
+ * Add a node to the cache, evicting oldest if needed
+ */
+ cacheNode(node) {
+ if (this.nodeCache.size >= this.maxCacheSize) {
+ const firstKey = this.nodeCache.keys().next().value;
+ if (firstKey) {
+ this.nodeCache.delete(firstKey);
+ }
+ }
+ this.nodeCache.set(node.id, node);
+ }
+ /**
+ * Clear the node cache
+ */
+ clearCache() {
+ this.nodeCache.clear();
+ }
+ /**
+ * Get all nodes in a file
+ */
+ getNodesByFile(filePath) {
+ if (!this.stmts.getNodesByFile) {
+ this.stmts.getNodesByFile = this.db.prepare(
+ "SELECT * FROM nodes WHERE file_path = ? ORDER BY start_line"
+ );
+ }
+ const rows = this.stmts.getNodesByFile.all(filePath);
+ return rows.map(rowToNode);
+ }
+ /**
+ * Get all nodes of a specific kind
+ */
+ getNodesByKind(kind) {
+ if (!this.stmts.getNodesByKind) {
+ this.stmts.getNodesByKind = this.db.prepare("SELECT * FROM nodes WHERE kind = ?");
+ }
+ const rows = this.stmts.getNodesByKind.all(kind);
+ return rows.map(rowToNode);
+ }
+ /**
+ * Get all nodes in the database
+ */
+ getAllNodes() {
+ const rows = this.db.prepare("SELECT * FROM nodes").all();
+ return rows.map(rowToNode);
+ }
+ /**
+ * Get nodes by exact name match (uses idx_nodes_name index)
+ */
+ getNodesByName(name) {
+ if (!this.stmts.getNodesByName) {
+ this.stmts.getNodesByName = this.db.prepare("SELECT * FROM nodes WHERE name = ?");
+ }
+ const rows = this.stmts.getNodesByName.all(name);
+ return rows.map(rowToNode);
+ }
+ /**
+ * Get nodes by exact qualified name match (uses idx_nodes_qualified_name index)
+ */
+ getNodesByQualifiedNameExact(qualifiedName) {
+ if (!this.stmts.getNodesByQualifiedNameExact) {
+ this.stmts.getNodesByQualifiedNameExact = this.db.prepare(
+ "SELECT * FROM nodes WHERE qualified_name = ?"
+ );
+ }
+ const rows = this.stmts.getNodesByQualifiedNameExact.all(qualifiedName);
+ return rows.map(rowToNode);
+ }
+ /**
+ * Get nodes by lowercase name match (uses idx_nodes_lower_name expression index)
+ */
+ getNodesByLowerName(lowerName) {
+ if (!this.stmts.getNodesByLowerName) {
+ this.stmts.getNodesByLowerName = this.db.prepare(
+ "SELECT * FROM nodes WHERE lower(name) = ?"
+ );
+ }
+ const rows = this.stmts.getNodesByLowerName.all(lowerName);
+ return rows.map(rowToNode);
+ }
+ /**
+ * Search nodes by name using FTS with fallback to LIKE for better matching
+ *
+ * Search strategy:
+ * 1. Try FTS5 prefix match (query*) for word-start matching
+ * 2. If no results, try LIKE for substring matching (e.g., "signIn" finds "signInWithGoogle")
+ * 3. Score results based on match quality
+ */
+ searchNodes(query, options = {}) {
+ const { limit = 100, offset = 0 } = options;
+ const parsed = parseQuery(query);
+ const mergedKinds = parsed.kinds.length > 0 ? Array.from(/* @__PURE__ */ new Set([...options.kinds ?? [], ...parsed.kinds])) : options.kinds;
+ const mergedLanguages = parsed.languages.length > 0 ? Array.from(/* @__PURE__ */ new Set([...options.languages ?? [], ...parsed.languages])) : options.languages;
+ const pathFilters = parsed.pathFilters;
+ const nameFilters = parsed.nameFilters;
+ const text = parsed.text;
+ const kinds = mergedKinds;
+ const languages = mergedLanguages;
+ let results = text ? this.searchNodesFTS(text, { kinds, languages, limit, offset }) : this.searchAllByFilters({ kinds, languages, limit: limit * 5 });
+ if (results.length === 0 && text.length >= 2) {
+ results = this.searchNodesLike(text, { kinds, languages, limit, offset });
+ }
+ if (results.length === 0 && text.length >= 3) {
+ results = this.searchNodesFuzzy(text, { kinds, languages, limit });
+ }
+ if (results.length > 0 && query) {
+ const existingIds = new Set(results.map((r) => r.node.id));
+ const maxFtsScore = Math.max(...results.map((r) => r.score));
+ const terms = query.split(/\s+/).filter((t) => t.length >= 2);
+ for (const term of terms) {
+ let sql = "SELECT * FROM nodes WHERE name = ? COLLATE NOCASE";
+ const params = [term];
+ if (kinds && kinds.length > 0) {
+ sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`;
+ params.push(...kinds);
+ }
+ if (languages && languages.length > 0) {
+ sql += ` AND language IN (${languages.map(() => "?").join(",")})`;
+ params.push(...languages);
+ }
+ sql += " LIMIT 20";
+ const rows = this.db.prepare(sql).all(...params);
+ for (const row of rows) {
+ if (!existingIds.has(row.id)) {
+ results.push({ node: rowToNode(row), score: maxFtsScore });
+ existingIds.add(row.id);
+ }
+ }
+ }
+ }
+ if (results.length > 0 && (text || query)) {
+ const scoringQuery = text || query;
+ results = results.map((r) => ({
+ ...r,
+ score: r.score + kindBonus(r.node.kind) + scorePathRelevance(r.node.filePath, scoringQuery) + nameMatchBonus(r.node.name, scoringQuery)
+ }));
+ results.sort((a, b) => b.score - a.score);
+ if (results.length > limit) {
+ results = results.slice(0, limit);
+ }
+ }
+ if (pathFilters.length > 0) {
+ const lowered = pathFilters.map((p) => p.toLowerCase());
+ results = results.filter((r) => {
+ const fp = r.node.filePath.toLowerCase();
+ return lowered.some((p) => fp.includes(p));
+ });
+ }
+ if (nameFilters.length > 0) {
+ const lowered = nameFilters.map((n) => n.toLowerCase());
+ results = results.filter((r) => {
+ const nm = r.node.name.toLowerCase();
+ return lowered.some((n) => nm.includes(n));
+ });
+ }
+ return results;
+ }
+ /**
+ * Match-everything path used when the user supplied only field
+ * filters (`kind:function lang:typescript`) with no text. Returns
+ * candidates ordered by name; the caller's filter pass narrows to
+ * what was asked for.
+ */
+ searchAllByFilters(options) {
+ const { kinds, languages, limit } = options;
+ let sql = "SELECT * FROM nodes WHERE 1=1";
+ const params = [];
+ if (kinds && kinds.length > 0) {
+ sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`;
+ params.push(...kinds);
+ }
+ if (languages && languages.length > 0) {
+ sql += ` AND language IN (${languages.map(() => "?").join(",")})`;
+ params.push(...languages);
+ }
+ sql += " ORDER BY name LIMIT ?";
+ params.push(limit);
+ const rows = this.db.prepare(sql).all(...params);
+ return rows.map((row) => ({ node: rowToNode(row), score: 1 }));
+ }
+ /**
+ * Fuzzy fallback: when zero FTS/LIKE hits, try an edit-distance
+ * sweep over the distinct symbol-name set. Caps `maxDist` at 2 so
+ * `getUssr` finds `getUser` but `process` doesn't match `prosody`.
+ * Bounded edit distance keeps each comparison cheap; the per-query
+ * scan is O(distinct-name-count) which is far smaller than total
+ * node count on any real codebase.
+ */
+ searchNodesFuzzy(text, options) {
+ const { kinds, languages, limit } = options;
+ const lowered = text.toLowerCase();
+ const maxDist = lowered.length <= 4 ? 1 : 2;
+ const allNames = this.getAllNodeNames();
+ const candidates = [];
+ for (const name of allNames) {
+ const dist = boundedEditDistance(name.toLowerCase(), lowered, maxDist);
+ if (dist <= maxDist) candidates.push({ name, dist });
+ }
+ candidates.sort((a, b) => a.dist - b.dist);
+ const FUZZY_FOLLOWUP_CAP = Math.max(limit * 2, 50);
+ const cappedCandidates = candidates.slice(0, FUZZY_FOLLOWUP_CAP);
+ const results = [];
+ const seen = /* @__PURE__ */ new Set();
+ for (const c of cappedCandidates) {
+ if (results.length >= limit) break;
+ let sql = "SELECT * FROM nodes WHERE name = ?";
+ const params = [c.name];
+ if (kinds && kinds.length > 0) {
+ sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`;
+ params.push(...kinds);
+ }
+ if (languages && languages.length > 0) {
+ sql += ` AND language IN (${languages.map(() => "?").join(",")})`;
+ params.push(...languages);
+ }
+ sql += " LIMIT 5";
+ const rows = this.db.prepare(sql).all(...params);
+ for (const row of rows) {
+ if (seen.has(row.id)) continue;
+ seen.add(row.id);
+ results.push({ node: rowToNode(row), score: 1 / (1 + c.dist) });
+ if (results.length >= limit) break;
+ }
+ }
+ return results;
+ }
+ /**
+ * FTS5 search with prefix matching
+ */
+ searchNodesFTS(query, options) {
+ const { kinds, languages, limit = 100, offset = 0 } = options;
+ const ftsQuery = query.replace(/['"*():^]/g, "").split(/\s+/).filter((term) => term.length > 0).filter((term) => !/^(AND|OR|NOT|NEAR)$/i.test(term)).map((term) => `"${term}"*`).join(" OR ");
+ if (!ftsQuery) {
+ return [];
+ }
+ const ftsLimit = Math.max(limit * 5, 100);
+ let sql = `
+ SELECT nodes.*, bm25(nodes_fts, 0, 20, 5, 1, 2) as score
+ FROM nodes_fts
+ JOIN nodes ON nodes_fts.id = nodes.id
+ WHERE nodes_fts MATCH ?
+ `;
+ const params = [ftsQuery];
+ if (kinds && kinds.length > 0) {
+ sql += ` AND nodes.kind IN (${kinds.map(() => "?").join(",")})`;
+ params.push(...kinds);
+ }
+ if (languages && languages.length > 0) {
+ sql += ` AND nodes.language IN (${languages.map(() => "?").join(",")})`;
+ params.push(...languages);
+ }
+ sql += " ORDER BY score LIMIT ? OFFSET ?";
+ params.push(ftsLimit, offset);
+ try {
+ const rows = this.db.prepare(sql).all(...params);
+ return rows.map((row) => ({
+ node: rowToNode(row),
+ score: Math.abs(row.score)
+ // bm25 returns negative scores
+ }));
+ } catch {
+ return [];
+ }
+ }
+ /**
+ * LIKE-based substring search for cases where FTS doesn't match
+ * Useful for camelCase matching (e.g., "signIn" finds "signInWithGoogle")
+ */
+ searchNodesLike(query, options) {
+ const { kinds, languages, limit = 100, offset = 0 } = options;
+ let sql = `
+ SELECT nodes.*,
+ CASE
+ WHEN name = ? THEN 1.0
+ WHEN name LIKE ? THEN 0.9
+ WHEN name LIKE ? THEN 0.8
+ WHEN qualified_name LIKE ? THEN 0.7
+ ELSE 0.5
+ END as score
+ FROM nodes
+ WHERE (
+ name LIKE ? OR
+ qualified_name LIKE ? OR
+ name LIKE ?
+ )
+ `;
+ const exactMatch = query;
+ const startsWith = `${query}%`;
+ const contains = `%${query}%`;
+ const params = [
+ exactMatch,
+ // Exact match score
+ startsWith,
+ // Starts with score
+ contains,
+ // Contains score
+ contains,
+ // Qualified name score
+ contains,
+ // WHERE: name contains
+ contains,
+ // WHERE: qualified_name contains
+ startsWith
+ // WHERE: name starts with
+ ];
+ if (kinds && kinds.length > 0) {
+ sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`;
+ params.push(...kinds);
+ }
+ if (languages && languages.length > 0) {
+ sql += ` AND language IN (${languages.map(() => "?").join(",")})`;
+ params.push(...languages);
+ }
+ sql += " ORDER BY score DESC, length(name) ASC LIMIT ? OFFSET ?";
+ params.push(limit, offset);
+ const rows = this.db.prepare(sql).all(...params);
+ return rows.map((row) => ({
+ node: rowToNode(row),
+ score: row.score
+ }));
+ }
+ /**
+ * Find nodes by exact name match
+ *
+ * Used for hybrid search - looks up symbols by exact name or case-insensitive match.
+ * Returns high-confidence matches for known symbol names extracted from query.
+ *
+ * @param names - Array of symbol names to look up
+ * @param options - Search options (kinds, languages, limit)
+ * @returns SearchResult array with exact matches scored at 1.0
+ */
+ findNodesByExactName(names, options = {}) {
+ if (names.length === 0) return [];
+ const { kinds, languages, limit = 50 } = options;
+ const nameToFiles = /* @__PURE__ */ new Map();
+ for (const name of names) {
+ let sql = "SELECT DISTINCT file_path FROM nodes WHERE name COLLATE NOCASE = ?";
+ const params = [name];
+ if (kinds && kinds.length > 0) {
+ sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`;
+ params.push(...kinds);
+ }
+ sql += " LIMIT 100";
+ const rows = this.db.prepare(sql).all(...params);
+ nameToFiles.set(name.toLowerCase(), new Set(rows.map((r) => r.file_path)));
+ }
+ const distinctiveFiles = /* @__PURE__ */ new Set();
+ for (const [, files] of nameToFiles) {
+ if (files.size > 0 && files.size < 10) {
+ for (const f of files) distinctiveFiles.add(f);
+ }
+ }
+ const perNameLimit = Math.max(8, Math.ceil(limit / names.length));
+ const allResults = [];
+ const seenIds = /* @__PURE__ */ new Set();
+ for (const name of names) {
+ let sql = `
+ SELECT nodes.*, 1.0 as score
+ FROM nodes
+ WHERE name COLLATE NOCASE = ?
+ `;
+ const params = [name];
+ if (kinds && kinds.length > 0) {
+ sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`;
+ params.push(...kinds);
+ }
+ if (languages && languages.length > 0) {
+ sql += ` AND language IN (${languages.map(() => "?").join(",")})`;
+ params.push(...languages);
+ }
+ sql += " LIMIT ?";
+ params.push(Math.max(perNameLimit * 3, 50));
+ const rows = this.db.prepare(sql).all(...params);
+ const nameResults = [];
+ for (const row of rows) {
+ const node = rowToNode(row);
+ if (seenIds.has(node.id)) continue;
+ const coLocationBoost = distinctiveFiles.has(node.filePath) ? 20 : 0;
+ nameResults.push({ node, score: row.score + coLocationBoost });
+ }
+ nameResults.sort((a, b) => b.score - a.score);
+ for (const r of nameResults.slice(0, perNameLimit)) {
+ seenIds.add(r.node.id);
+ allResults.push(r);
+ }
+ }
+ allResults.sort((a, b) => b.score - a.score);
+ return allResults.slice(0, limit);
+ }
+ /**
+ * Find nodes whose name contains a substring (LIKE-based).
+ * Useful for CamelCase-part matching where FTS fails because
+ * e.g. "TransportSearchAction" is one FTS token, not matchable by "Search"*.
+ *
+ * Results are ordered by name length (shorter = more likely to be the core type).
+ */
+ findNodesByNameSubstring(substring, options = {}) {
+ const { kinds, languages, limit = 30, excludePrefix } = options;
+ let sql = `
+ SELECT nodes.*, 1.0 as score
+ FROM nodes
+ WHERE name LIKE ?
+ `;
+ const params = [`%${substring}%`];
+ if (excludePrefix) {
+ sql += ` AND name NOT LIKE ?`;
+ params.push(`${substring}%`);
+ }
+ if (kinds && kinds.length > 0) {
+ sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`;
+ params.push(...kinds);
+ }
+ if (languages && languages.length > 0) {
+ sql += ` AND language IN (${languages.map(() => "?").join(",")})`;
+ params.push(...languages);
+ }
+ sql += " ORDER BY length(name) ASC LIMIT ?";
+ params.push(limit);
+ const rows = this.db.prepare(sql).all(...params);
+ return rows.map((row) => ({
+ node: rowToNode(row),
+ score: row.score
+ }));
+ }
+ // ===========================================================================
+ // Edge Operations
+ // ===========================================================================
+ /**
+ * Insert a new edge
+ */
+ insertEdge(edge) {
+ if (!this.stmts.insertEdge) {
+ this.stmts.insertEdge = this.db.prepare(`
+ INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance)
+ VALUES (@source, @target, @kind, @metadata, @line, @col, @provenance)
+ `);
+ }
+ this.stmts.insertEdge.run({
+ source: edge.source,
+ target: edge.target,
+ kind: edge.kind,
+ metadata: edge.metadata ? JSON.stringify(edge.metadata) : null,
+ line: edge.line ?? null,
+ col: edge.column ?? null,
+ provenance: edge.provenance ?? null
+ });
+ }
+ /**
+ * Insert multiple edges in a transaction
+ */
+ insertEdges(edges) {
+ this.db.transaction(() => {
+ for (const edge of edges) {
+ this.insertEdge(edge);
+ }
+ })();
+ }
+ /**
+ * Delete all edges from a source node
+ */
+ deleteEdgesBySource(sourceId) {
+ if (!this.stmts.deleteEdgesBySource) {
+ this.stmts.deleteEdgesBySource = this.db.prepare("DELETE FROM edges WHERE source = ?");
+ }
+ this.stmts.deleteEdgesBySource.run(sourceId);
+ }
+ /**
+ * Get outgoing edges from a node
+ */
+ getOutgoingEdges(sourceId, kinds, provenance) {
+ if (kinds && kinds.length > 0 || provenance) {
+ let sql = "SELECT * FROM edges WHERE source = ?";
+ const params = [sourceId];
+ if (kinds && kinds.length > 0) {
+ sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`;
+ params.push(...kinds);
+ }
+ if (provenance) {
+ sql += " AND provenance = ?";
+ params.push(provenance);
+ }
+ const rows2 = this.db.prepare(sql).all(...params);
+ return rows2.map(rowToEdge);
+ }
+ if (!this.stmts.getEdgesBySource) {
+ this.stmts.getEdgesBySource = this.db.prepare("SELECT * FROM edges WHERE source = ?");
+ }
+ const rows = this.stmts.getEdgesBySource.all(sourceId);
+ return rows.map(rowToEdge);
+ }
+ /**
+ * Get incoming edges to a node
+ */
+ getIncomingEdges(targetId, kinds) {
+ if (kinds && kinds.length > 0) {
+ const sql = `SELECT * FROM edges WHERE target = ? AND kind IN (${kinds.map(() => "?").join(",")})`;
+ const rows2 = this.db.prepare(sql).all(targetId, ...kinds);
+ return rows2.map(rowToEdge);
+ }
+ if (!this.stmts.getEdgesByTarget) {
+ this.stmts.getEdgesByTarget = this.db.prepare("SELECT * FROM edges WHERE target = ?");
+ }
+ const rows = this.stmts.getEdgesByTarget.all(targetId);
+ return rows.map(rowToEdge);
+ }
+ /**
+ * Find all edges where both source and target are in the given node set.
+ * Useful for recovering inter-node connectivity after BFS.
+ */
+ findEdgesBetweenNodes(nodeIds, kinds) {
+ if (nodeIds.length === 0) return [];
+ const idsJson = JSON.stringify(nodeIds);
+ let sql = `SELECT * FROM edges WHERE source IN (SELECT value FROM json_each(?)) AND target IN (SELECT value FROM json_each(?))`;
+ const params = [idsJson, idsJson];
+ if (kinds && kinds.length > 0) {
+ sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`;
+ params.push(...kinds);
+ }
+ const rows = this.db.prepare(sql).all(...params);
+ return rows.map(rowToEdge);
+ }
+ // ===========================================================================
+ // File Operations
+ // ===========================================================================
+ /**
+ * Insert or update a file record
+ */
+ upsertFile(file) {
+ if (!this.stmts.upsertFile) {
+ this.stmts.upsertFile = this.db.prepare(`
+ INSERT INTO files (path, content_hash, language, size, modified_at, indexed_at, node_count, errors)
+ VALUES (@path, @contentHash, @language, @size, @modifiedAt, @indexedAt, @nodeCount, @errors)
+ ON CONFLICT(path) DO UPDATE SET
+ content_hash = @contentHash,
+ language = @language,
+ size = @size,
+ modified_at = @modifiedAt,
+ indexed_at = @indexedAt,
+ node_count = @nodeCount,
+ errors = @errors
+ `);
+ }
+ this.stmts.upsertFile.run({
+ path: file.path,
+ contentHash: file.contentHash,
+ language: file.language,
+ size: file.size,
+ modifiedAt: file.modifiedAt,
+ indexedAt: file.indexedAt,
+ nodeCount: file.nodeCount,
+ errors: file.errors ? JSON.stringify(file.errors) : null
+ });
+ }
+ /**
+ * Delete a file record and its nodes
+ */
+ deleteFile(filePath) {
+ this.db.transaction(() => {
+ this.deleteNodesByFile(filePath);
+ if (!this.stmts.deleteFile) {
+ this.stmts.deleteFile = this.db.prepare("DELETE FROM files WHERE path = ?");
+ }
+ this.stmts.deleteFile.run(filePath);
+ })();
+ }
+ /**
+ * Get a file record by path
+ */
+ getFileByPath(filePath) {
+ if (!this.stmts.getFileByPath) {
+ this.stmts.getFileByPath = this.db.prepare("SELECT * FROM files WHERE path = ?");
+ }
+ const row = this.stmts.getFileByPath.get(filePath);
+ return row ? rowToFileRecord(row) : null;
+ }
+ /**
+ * Get all tracked files
+ */
+ getAllFiles() {
+ if (!this.stmts.getAllFiles) {
+ this.stmts.getAllFiles = this.db.prepare("SELECT * FROM files ORDER BY path");
+ }
+ const rows = this.stmts.getAllFiles.all();
+ return rows.map(rowToFileRecord);
+ }
+ /**
+ * Get files that need re-indexing (hash changed)
+ */
+ getStaleFiles(currentHashes) {
+ const files = this.getAllFiles();
+ return files.filter((f) => {
+ const currentHash = currentHashes.get(f.path);
+ return currentHash && currentHash !== f.contentHash;
+ });
+ }
+ // ===========================================================================
+ // Unresolved References
+ // ===========================================================================
+ /**
+ * Insert an unresolved reference
+ */
+ insertUnresolvedRef(ref) {
+ if (!this.stmts.insertUnresolved) {
+ this.stmts.insertUnresolved = this.db.prepare(`
+ INSERT INTO unresolved_refs (from_node_id, reference_name, reference_kind, line, col, candidates, file_path, language)
+ VALUES (@fromNodeId, @referenceName, @referenceKind, @line, @col, @candidates, @filePath, @language)
+ `);
+ }
+ this.stmts.insertUnresolved.run({
+ fromNodeId: ref.fromNodeId,
+ referenceName: ref.referenceName,
+ referenceKind: ref.referenceKind,
+ line: ref.line,
+ col: ref.column,
+ candidates: ref.candidates ? JSON.stringify(ref.candidates) : null,
+ filePath: ref.filePath ?? "",
+ language: ref.language ?? "unknown"
+ });
+ }
+ /**
+ * Insert multiple unresolved references in a transaction
+ */
+ insertUnresolvedRefsBatch(refs) {
+ if (refs.length === 0) return;
+ const insert = this.db.transaction(() => {
+ for (const ref of refs) {
+ this.insertUnresolvedRef(ref);
+ }
+ });
+ insert();
+ }
+ /**
+ * Delete unresolved references from a node
+ */
+ deleteUnresolvedByNode(nodeId) {
+ if (!this.stmts.deleteUnresolvedByNode) {
+ this.stmts.deleteUnresolvedByNode = this.db.prepare(
+ "DELETE FROM unresolved_refs WHERE from_node_id = ?"
+ );
+ }
+ this.stmts.deleteUnresolvedByNode.run(nodeId);
+ }
+ /**
+ * Get unresolved references by name (for resolution)
+ */
+ getUnresolvedByName(name) {
+ if (!this.stmts.getUnresolvedByName) {
+ this.stmts.getUnresolvedByName = this.db.prepare(
+ "SELECT * FROM unresolved_refs WHERE reference_name = ?"
+ );
+ }
+ const rows = this.stmts.getUnresolvedByName.all(name);
+ return rows.map((row) => ({
+ fromNodeId: row.from_node_id,
+ referenceName: row.reference_name,
+ referenceKind: row.reference_kind,
+ line: row.line,
+ column: row.col,
+ candidates: row.candidates ? safeJsonParse(row.candidates, void 0) : void 0,
+ filePath: row.file_path,
+ language: row.language
+ }));
+ }
+ /**
+ * Get all unresolved references
+ */
+ getUnresolvedReferences() {
+ const rows = this.db.prepare("SELECT * FROM unresolved_refs").all();
+ return rows.map((row) => ({
+ fromNodeId: row.from_node_id,
+ referenceName: row.reference_name,
+ referenceKind: row.reference_kind,
+ line: row.line,
+ column: row.col,
+ candidates: row.candidates ? safeJsonParse(row.candidates, void 0) : void 0,
+ filePath: row.file_path,
+ language: row.language
+ }));
+ }
+ /**
+ * Get the count of unresolved references without loading them into memory
+ */
+ getUnresolvedReferencesCount() {
+ if (!this.stmts.getUnresolvedCount) {
+ this.stmts.getUnresolvedCount = this.db.prepare(
+ "SELECT COUNT(*) as count FROM unresolved_refs"
+ );
+ }
+ const row = this.stmts.getUnresolvedCount.get();
+ return row.count;
+ }
+ /**
+ * Get a batch of unresolved references using LIMIT/OFFSET pagination.
+ * Used to process references in bounded memory chunks.
+ */
+ getUnresolvedReferencesBatch(offset, limit) {
+ if (!this.stmts.getUnresolvedBatch) {
+ this.stmts.getUnresolvedBatch = this.db.prepare(
+ "SELECT * FROM unresolved_refs LIMIT ? OFFSET ?"
+ );
+ }
+ const rows = this.stmts.getUnresolvedBatch.all(limit, offset);
+ return rows.map((row) => ({
+ fromNodeId: row.from_node_id,
+ referenceName: row.reference_name,
+ referenceKind: row.reference_kind,
+ line: row.line,
+ column: row.col,
+ candidates: row.candidates ? safeJsonParse(row.candidates, void 0) : void 0,
+ filePath: row.file_path,
+ language: row.language
+ }));
+ }
+ /**
+ * Get all tracked file paths (lightweight — no full FileRecord objects)
+ */
+ getAllFilePaths() {
+ if (!this.stmts.getAllFilePaths) {
+ this.stmts.getAllFilePaths = this.db.prepare("SELECT path FROM files ORDER BY path");
+ }
+ const rows = this.stmts.getAllFilePaths.all();
+ return rows.map((r) => r.path);
+ }
+ /**
+ * Get all distinct node names (lightweight — just name strings for pre-filtering)
+ */
+ getAllNodeNames() {
+ if (!this.stmts.getAllNodeNames) {
+ this.stmts.getAllNodeNames = this.db.prepare("SELECT DISTINCT name FROM nodes");
+ }
+ const rows = this.stmts.getAllNodeNames.all();
+ return rows.map((r) => r.name);
+ }
+ /**
+ * Get unresolved references scoped to specific file paths.
+ * Uses the idx_unresolved_file_path index for efficient lookup.
+ */
+ getUnresolvedReferencesByFiles(filePaths) {
+ if (filePaths.length === 0) return [];
+ const placeholders = filePaths.map(() => "?").join(",");
+ const rows = this.db.prepare(`SELECT * FROM unresolved_refs WHERE file_path IN (${placeholders})`).all(...filePaths);
+ return rows.map((row) => ({
+ fromNodeId: row.from_node_id,
+ referenceName: row.reference_name,
+ referenceKind: row.reference_kind,
+ line: row.line,
+ column: row.col,
+ candidates: row.candidates ? safeJsonParse(row.candidates, void 0) : void 0,
+ filePath: row.file_path,
+ language: row.language
+ }));
+ }
+ /**
+ * Delete all unresolved references (after resolution)
+ */
+ clearUnresolvedReferences() {
+ this.db.exec("DELETE FROM unresolved_refs");
+ }
+ /**
+ * Delete resolved references by their IDs
+ */
+ deleteResolvedReferences(fromNodeIds) {
+ if (fromNodeIds.length === 0) return;
+ const placeholders = fromNodeIds.map(() => "?").join(",");
+ this.db.prepare(`DELETE FROM unresolved_refs WHERE from_node_id IN (${placeholders})`).run(...fromNodeIds);
+ }
+ /**
+ * Delete specific resolved references by (fromNodeId, referenceName, referenceKind) tuples.
+ * More precise than deleteResolvedReferences — only removes refs that were actually resolved.
+ */
+ deleteSpecificResolvedReferences(refs) {
+ if (refs.length === 0) return;
+ const stmt = this.db.prepare(
+ "DELETE FROM unresolved_refs WHERE from_node_id = ? AND reference_name = ? AND reference_kind = ?"
+ );
+ const deleteMany = this.db.transaction((items) => {
+ for (const ref of items) {
+ stmt.run(ref.fromNodeId, ref.referenceName, ref.referenceKind);
+ }
+ });
+ deleteMany(refs);
+ }
+ // ===========================================================================
+ // Statistics
+ // ===========================================================================
+ /**
+ * Get graph statistics
+ */
+ getStats() {
+ const counts = this.db.prepare(`
+ SELECT
+ (SELECT COUNT(*) FROM nodes) AS node_count,
+ (SELECT COUNT(*) FROM edges) AS edge_count,
+ (SELECT COUNT(*) FROM files) AS file_count
+ `).get();
+ const nodesByKind = {};
+ const nodeKindRows = this.db.prepare("SELECT kind, COUNT(*) as count FROM nodes GROUP BY kind").all();
+ for (const row of nodeKindRows) {
+ nodesByKind[row.kind] = row.count;
+ }
+ const edgesByKind = {};
+ const edgeKindRows = this.db.prepare("SELECT kind, COUNT(*) as count FROM edges GROUP BY kind").all();
+ for (const row of edgeKindRows) {
+ edgesByKind[row.kind] = row.count;
+ }
+ const filesByLanguage = {};
+ const languageRows = this.db.prepare("SELECT language, COUNT(*) as count FROM files GROUP BY language").all();
+ for (const row of languageRows) {
+ filesByLanguage[row.language] = row.count;
+ }
+ return {
+ nodeCount: counts.node_count,
+ edgeCount: counts.edge_count,
+ fileCount: counts.file_count,
+ nodesByKind,
+ edgesByKind,
+ filesByLanguage,
+ dbSizeBytes: 0,
+ // Set by caller using DatabaseConnection.getSize()
+ lastUpdated: Date.now()
+ };
+ }
+ // ===========================================================================
+ // Project Metadata
+ // ===========================================================================
+ /**
+ * Get a metadata value by key
+ */
+ getMetadata(key) {
+ const row = this.db.prepare("SELECT value FROM project_metadata WHERE key = ?").get(key);
+ return row?.value ?? null;
+ }
+ /**
+ * Set a metadata key-value pair (upsert)
+ */
+ setMetadata(key, value) {
+ this.db.prepare(
+ "INSERT INTO project_metadata (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at"
+ ).run(key, value, Date.now());
+ }
+ /**
+ * Get all metadata as a key-value record
+ */
+ getAllMetadata() {
+ const rows = this.db.prepare("SELECT key, value FROM project_metadata").all();
+ const result = {};
+ for (const row of rows) {
+ result[row.key] = row.value;
+ }
+ return result;
+ }
+ /**
+ * Clear all data from the database
+ */
+ clear() {
+ this.nodeCache.clear();
+ this.db.transaction(() => {
+ this.db.exec("DELETE FROM unresolved_refs");
+ this.db.exec("DELETE FROM edges");
+ this.db.exec("DELETE FROM nodes");
+ this.db.exec("DELETE FROM files");
+ })();
+ }
+ };
+ }
+});
+
+// node_modules/picomatch/lib/constants.js
+var require_constants = __commonJS({
+ "node_modules/picomatch/lib/constants.js"(exports2, module2) {
+ "use strict";
+ var WIN_SLASH = "\\\\/";
+ var WIN_NO_SLASH = `[^${WIN_SLASH}]`;
+ var DEFAULT_MAX_EXTGLOB_RECURSION = 0;
+ var DOT_LITERAL = "\\.";
+ var PLUS_LITERAL = "\\+";
+ var QMARK_LITERAL = "\\?";
+ var SLASH_LITERAL = "\\/";
+ var ONE_CHAR = "(?=.)";
+ var QMARK = "[^/]";
+ var END_ANCHOR = `(?:${SLASH_LITERAL}|$)`;
+ var START_ANCHOR = `(?:^|${SLASH_LITERAL})`;
+ var DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`;
+ var NO_DOT = `(?!${DOT_LITERAL})`;
+ var NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`;
+ var NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`;
+ var NO_DOTS_SLASH = `(?!${DOTS_SLASH})`;
+ var QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`;
+ var STAR = `${QMARK}*?`;
+ var SEP = "/";
+ var POSIX_CHARS = {
+ DOT_LITERAL,
+ PLUS_LITERAL,
+ QMARK_LITERAL,
+ SLASH_LITERAL,
+ ONE_CHAR,
+ QMARK,
+ END_ANCHOR,
+ DOTS_SLASH,
+ NO_DOT,
+ NO_DOTS,
+ NO_DOT_SLASH,
+ NO_DOTS_SLASH,
+ QMARK_NO_DOT,
+ STAR,
+ START_ANCHOR,
+ SEP
+ };
+ var WINDOWS_CHARS = {
+ ...POSIX_CHARS,
+ SLASH_LITERAL: `[${WIN_SLASH}]`,
+ QMARK: WIN_NO_SLASH,
+ STAR: `${WIN_NO_SLASH}*?`,
+ DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`,
+ NO_DOT: `(?!${DOT_LITERAL})`,
+ NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`,
+ NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`,
+ NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`,
+ QMARK_NO_DOT: `[^.${WIN_SLASH}]`,
+ START_ANCHOR: `(?:^|[${WIN_SLASH}])`,
+ END_ANCHOR: `(?:[${WIN_SLASH}]|$)`,
+ SEP: "\\"
+ };
+ var POSIX_REGEX_SOURCE = {
+ __proto__: null,
+ alnum: "a-zA-Z0-9",
+ alpha: "a-zA-Z",
+ ascii: "\\x00-\\x7F",
+ blank: " \\t",
+ cntrl: "\\x00-\\x1F\\x7F",
+ digit: "0-9",
+ graph: "\\x21-\\x7E",
+ lower: "a-z",
+ print: "\\x20-\\x7E ",
+ punct: "\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",
+ space: " \\t\\r\\n\\v\\f",
+ upper: "A-Z",
+ word: "A-Za-z0-9_",
+ xdigit: "A-Fa-f0-9"
+ };
+ module2.exports = {
+ DEFAULT_MAX_EXTGLOB_RECURSION,
+ MAX_LENGTH: 1024 * 64,
+ POSIX_REGEX_SOURCE,
+ // regular expressions
+ REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g,
+ REGEX_NON_SPECIAL_CHARS: /^[^@![\].,$*+?^{}()|\\/]+/,
+ REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/,
+ REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g,
+ REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g,
+ REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g,
+ // Replace globs with equivalent patterns to reduce parsing time.
+ REPLACEMENTS: {
+ __proto__: null,
+ "***": "*",
+ "**/**": "**",
+ "**/**/**": "**"
+ },
+ // Digits
+ CHAR_0: 48,
+ /* 0 */
+ CHAR_9: 57,
+ /* 9 */
+ // Alphabet chars.
+ CHAR_UPPERCASE_A: 65,
+ /* A */
+ CHAR_LOWERCASE_A: 97,
+ /* a */
+ CHAR_UPPERCASE_Z: 90,
+ /* Z */
+ CHAR_LOWERCASE_Z: 122,
+ /* z */
+ CHAR_LEFT_PARENTHESES: 40,
+ /* ( */
+ CHAR_RIGHT_PARENTHESES: 41,
+ /* ) */
+ CHAR_ASTERISK: 42,
+ /* * */
+ // Non-alphabetic chars.
+ CHAR_AMPERSAND: 38,
+ /* & */
+ CHAR_AT: 64,
+ /* @ */
+ CHAR_BACKWARD_SLASH: 92,
+ /* \ */
+ CHAR_CARRIAGE_RETURN: 13,
+ /* \r */
+ CHAR_CIRCUMFLEX_ACCENT: 94,
+ /* ^ */
+ CHAR_COLON: 58,
+ /* : */
+ CHAR_COMMA: 44,
+ /* , */
+ CHAR_DOT: 46,
+ /* . */
+ CHAR_DOUBLE_QUOTE: 34,
+ /* " */
+ CHAR_EQUAL: 61,
+ /* = */
+ CHAR_EXCLAMATION_MARK: 33,
+ /* ! */
+ CHAR_FORM_FEED: 12,
+ /* \f */
+ CHAR_FORWARD_SLASH: 47,
+ /* / */
+ CHAR_GRAVE_ACCENT: 96,
+ /* ` */
+ CHAR_HASH: 35,
+ /* # */
+ CHAR_HYPHEN_MINUS: 45,
+ /* - */
+ CHAR_LEFT_ANGLE_BRACKET: 60,
+ /* < */
+ CHAR_LEFT_CURLY_BRACE: 123,
+ /* { */
+ CHAR_LEFT_SQUARE_BRACKET: 91,
+ /* [ */
+ CHAR_LINE_FEED: 10,
+ /* \n */
+ CHAR_NO_BREAK_SPACE: 160,
+ /* \u00A0 */
+ CHAR_PERCENT: 37,
+ /* % */
+ CHAR_PLUS: 43,
+ /* + */
+ CHAR_QUESTION_MARK: 63,
+ /* ? */
+ CHAR_RIGHT_ANGLE_BRACKET: 62,
+ /* > */
+ CHAR_RIGHT_CURLY_BRACE: 125,
+ /* } */
+ CHAR_RIGHT_SQUARE_BRACKET: 93,
+ /* ] */
+ CHAR_SEMICOLON: 59,
+ /* ; */
+ CHAR_SINGLE_QUOTE: 39,
+ /* ' */
+ CHAR_SPACE: 32,
+ /* */
+ CHAR_TAB: 9,
+ /* \t */
+ CHAR_UNDERSCORE: 95,
+ /* _ */
+ CHAR_VERTICAL_LINE: 124,
+ /* | */
+ CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279,
+ /* \uFEFF */
+ /**
+ * Create EXTGLOB_CHARS
+ */
+ extglobChars(chars) {
+ return {
+ "!": { type: "negate", open: "(?:(?!(?:", close: `))${chars.STAR})` },
+ "?": { type: "qmark", open: "(?:", close: ")?" },
+ "+": { type: "plus", open: "(?:", close: ")+" },
+ "*": { type: "star", open: "(?:", close: ")*" },
+ "@": { type: "at", open: "(?:", close: ")" }
+ };
+ },
+ /**
+ * Create GLOB_CHARS
+ */
+ globChars(win32) {
+ return win32 === true ? WINDOWS_CHARS : POSIX_CHARS;
+ }
+ };
+ }
+});
+
+// node_modules/picomatch/lib/utils.js
+var require_utils = __commonJS({
+ "node_modules/picomatch/lib/utils.js"(exports2) {
+ "use strict";
+ var {
+ REGEX_BACKSLASH,
+ REGEX_REMOVE_BACKSLASH,
+ REGEX_SPECIAL_CHARS,
+ REGEX_SPECIAL_CHARS_GLOBAL
+ } = require_constants();
+ exports2.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
+ exports2.hasRegexChars = (str) => REGEX_SPECIAL_CHARS.test(str);
+ exports2.isRegexChar = (str) => str.length === 1 && exports2.hasRegexChars(str);
+ exports2.escapeRegex = (str) => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
+ exports2.toPosixSlashes = (str) => str.replace(REGEX_BACKSLASH, "/");
+ exports2.isWindows = () => {
+ if (typeof navigator !== "undefined" && navigator.platform) {
+ const platform = navigator.platform.toLowerCase();
+ return platform === "win32" || platform === "windows";
+ }
+ if (typeof process !== "undefined" && process.platform) {
+ return process.platform === "win32";
+ }
+ return false;
+ };
+ exports2.removeBackslashes = (str) => {
+ return str.replace(REGEX_REMOVE_BACKSLASH, (match) => {
+ return match === "\\" ? "" : match;
+ });
+ };
+ exports2.escapeLast = (input, char, lastIdx) => {
+ const idx = input.lastIndexOf(char, lastIdx);
+ if (idx === -1) return input;
+ if (input[idx - 1] === "\\") return exports2.escapeLast(input, char, idx - 1);
+ return `${input.slice(0, idx)}\\${input.slice(idx)}`;
+ };
+ exports2.removePrefix = (input, state = {}) => {
+ let output = input;
+ if (output.startsWith("./")) {
+ output = output.slice(2);
+ state.prefix = "./";
+ }
+ return output;
+ };
+ exports2.wrapOutput = (input, state = {}, options = {}) => {
+ const prepend = options.contains ? "" : "^";
+ const append = options.contains ? "" : "$";
+ let output = `${prepend}(?:${input})${append}`;
+ if (state.negated === true) {
+ output = `(?:^(?!${output}).*$)`;
+ }
+ return output;
+ };
+ exports2.basename = (path20, { windows } = {}) => {
+ const segs = path20.split(windows ? /[\\/]/ : "/");
+ const last = segs[segs.length - 1];
+ if (last === "") {
+ return segs[segs.length - 2];
+ }
+ return last;
+ };
+ }
+});
+
+// node_modules/picomatch/lib/scan.js
+var require_scan = __commonJS({
+ "node_modules/picomatch/lib/scan.js"(exports2, module2) {
+ "use strict";
+ var utils = require_utils();
+ var {
+ CHAR_ASTERISK,
+ /* * */
+ CHAR_AT,
+ /* @ */
+ CHAR_BACKWARD_SLASH,
+ /* \ */
+ CHAR_COMMA,
+ /* , */
+ CHAR_DOT,
+ /* . */
+ CHAR_EXCLAMATION_MARK,
+ /* ! */
+ CHAR_FORWARD_SLASH,
+ /* / */
+ CHAR_LEFT_CURLY_BRACE,
+ /* { */
+ CHAR_LEFT_PARENTHESES,
+ /* ( */
+ CHAR_LEFT_SQUARE_BRACKET,
+ /* [ */
+ CHAR_PLUS,
+ /* + */
+ CHAR_QUESTION_MARK,
+ /* ? */
+ CHAR_RIGHT_CURLY_BRACE,
+ /* } */
+ CHAR_RIGHT_PARENTHESES,
+ /* ) */
+ CHAR_RIGHT_SQUARE_BRACKET
+ /* ] */
+ } = require_constants();
+ var isPathSeparator = (code) => {
+ return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
+ };
+ var depth = (token) => {
+ if (token.isPrefix !== true) {
+ token.depth = token.isGlobstar ? Infinity : 1;
+ }
+ };
+ var scan = (input, options) => {
+ const opts = options || {};
+ const length = input.length - 1;
+ const scanToEnd = opts.parts === true || opts.scanToEnd === true;
+ const slashes = [];
+ const tokens = [];
+ const parts = [];
+ let str = input;
+ let index = -1;
+ let start = 0;
+ let lastIndex = 0;
+ let isBrace = false;
+ let isBracket = false;
+ let isGlob = false;
+ let isExtglob = false;
+ let isGlobstar = false;
+ let braceEscaped = false;
+ let backslashes = false;
+ let negated = false;
+ let negatedExtglob = false;
+ let finished = false;
+ let braces = 0;
+ let prev;
+ let code;
+ let token = { value: "", depth: 0, isGlob: false };
+ const eos = () => index >= length;
+ const peek = () => str.charCodeAt(index + 1);
+ const advance = () => {
+ prev = code;
+ return str.charCodeAt(++index);
+ };
+ while (index < length) {
+ code = advance();
+ let next;
+ if (code === CHAR_BACKWARD_SLASH) {
+ backslashes = token.backslashes = true;
+ code = advance();
+ if (code === CHAR_LEFT_CURLY_BRACE) {
+ braceEscaped = true;
+ }
+ continue;
+ }
+ if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) {
+ braces++;
+ while (eos() !== true && (code = advance())) {
+ if (code === CHAR_BACKWARD_SLASH) {
+ backslashes = token.backslashes = true;
+ advance();
+ continue;
+ }
+ if (code === CHAR_LEFT_CURLY_BRACE) {
+ braces++;
+ continue;
+ }
+ if (braceEscaped !== true && code === CHAR_DOT && (code = advance()) === CHAR_DOT) {
+ isBrace = token.isBrace = true;
+ isGlob = token.isGlob = true;
+ finished = true;
+ if (scanToEnd === true) {
+ continue;
+ }
+ break;
+ }
+ if (braceEscaped !== true && code === CHAR_COMMA) {
+ isBrace = token.isBrace = true;
+ isGlob = token.isGlob = true;
+ finished = true;
+ if (scanToEnd === true) {
+ continue;
+ }
+ break;
+ }
+ if (code === CHAR_RIGHT_CURLY_BRACE) {
+ braces--;
+ if (braces === 0) {
+ braceEscaped = false;
+ isBrace = token.isBrace = true;
+ finished = true;
+ break;
+ }
+ }
+ }
+ if (scanToEnd === true) {
+ continue;
+ }
+ break;
+ }
+ if (code === CHAR_FORWARD_SLASH) {
+ slashes.push(index);
+ tokens.push(token);
+ token = { value: "", depth: 0, isGlob: false };
+ if (finished === true) continue;
+ if (prev === CHAR_DOT && index === start + 1) {
+ start += 2;
+ continue;
+ }
+ lastIndex = index + 1;
+ continue;
+ }
+ if (opts.noext !== true) {
+ const isExtglobChar = code === CHAR_PLUS || code === CHAR_AT || code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK || code === CHAR_EXCLAMATION_MARK;
+ if (isExtglobChar === true && peek() === CHAR_LEFT_PARENTHESES) {
+ isGlob = token.isGlob = true;
+ isExtglob = token.isExtglob = true;
+ finished = true;
+ if (code === CHAR_EXCLAMATION_MARK && index === start) {
+ negatedExtglob = true;
+ }
+ if (scanToEnd === true) {
+ while (eos() !== true && (code = advance())) {
+ if (code === CHAR_BACKWARD_SLASH) {
+ backslashes = token.backslashes = true;
+ code = advance();
+ continue;
+ }
+ if (code === CHAR_RIGHT_PARENTHESES) {
+ isGlob = token.isGlob = true;
+ finished = true;
+ break;
+ }
+ }
+ continue;
+ }
+ break;
+ }
+ }
+ if (code === CHAR_ASTERISK) {
+ if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true;
+ isGlob = token.isGlob = true;
+ finished = true;
+ if (scanToEnd === true) {
+ continue;
+ }
+ break;
+ }
+ if (code === CHAR_QUESTION_MARK) {
+ isGlob = token.isGlob = true;
+ finished = true;
+ if (scanToEnd === true) {
+ continue;
+ }
+ break;
+ }
+ if (code === CHAR_LEFT_SQUARE_BRACKET) {
+ while (eos() !== true && (next = advance())) {
+ if (next === CHAR_BACKWARD_SLASH) {
+ backslashes = token.backslashes = true;
+ advance();
+ continue;
+ }
+ if (next === CHAR_RIGHT_SQUARE_BRACKET) {
+ isBracket = token.isBracket = true;
+ isGlob = token.isGlob = true;
+ finished = true;
+ break;
+ }
+ }
+ if (scanToEnd === true) {
+ continue;
+ }
+ break;
+ }
+ if (opts.nonegate !== true && code === CHAR_EXCLAMATION_MARK && index === start) {
+ negated = token.negated = true;
+ start++;
+ continue;
+ }
+ if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) {
+ isGlob = token.isGlob = true;
+ if (scanToEnd === true) {
+ while (eos() !== true && (code = advance())) {
+ if (code === CHAR_LEFT_PARENTHESES) {
+ backslashes = token.backslashes = true;
+ code = advance();
+ continue;
+ }
+ if (code === CHAR_RIGHT_PARENTHESES) {
+ finished = true;
+ break;
+ }
+ }
+ continue;
+ }
+ break;
+ }
+ if (isGlob === true) {
+ finished = true;
+ if (scanToEnd === true) {
+ continue;
+ }
+ break;
+ }
+ }
+ if (opts.noext === true) {
+ isExtglob = false;
+ isGlob = false;
+ }
+ let base = str;
+ let prefix = "";
+ let glob = "";
+ if (start > 0) {
+ prefix = str.slice(0, start);
+ str = str.slice(start);
+ lastIndex -= start;
+ }
+ if (base && isGlob === true && lastIndex > 0) {
+ base = str.slice(0, lastIndex);
+ glob = str.slice(lastIndex);
+ } else if (isGlob === true) {
+ base = "";
+ glob = str;
+ } else {
+ base = str;
+ }
+ if (base && base !== "" && base !== "/" && base !== str) {
+ if (isPathSeparator(base.charCodeAt(base.length - 1))) {
+ base = base.slice(0, -1);
+ }
+ }
+ if (opts.unescape === true) {
+ if (glob) glob = utils.removeBackslashes(glob);
+ if (base && backslashes === true) {
+ base = utils.removeBackslashes(base);
+ }
+ }
+ const state = {
+ prefix,
+ input,
+ start,
+ base,
+ glob,
+ isBrace,
+ isBracket,
+ isGlob,
+ isExtglob,
+ isGlobstar,
+ negated,
+ negatedExtglob
+ };
+ if (opts.tokens === true) {
+ state.maxDepth = 0;
+ if (!isPathSeparator(code)) {
+ tokens.push(token);
+ }
+ state.tokens = tokens;
+ }
+ if (opts.parts === true || opts.tokens === true) {
+ let prevIndex;
+ for (let idx = 0; idx < slashes.length; idx++) {
+ const n = prevIndex ? prevIndex + 1 : start;
+ const i = slashes[idx];
+ const value = input.slice(n, i);
+ if (opts.tokens) {
+ if (idx === 0 && start !== 0) {
+ tokens[idx].isPrefix = true;
+ tokens[idx].value = prefix;
+ } else {
+ tokens[idx].value = value;
+ }
+ depth(tokens[idx]);
+ state.maxDepth += tokens[idx].depth;
+ }
+ if (idx !== 0 || value !== "") {
+ parts.push(value);
+ }
+ prevIndex = i;
+ }
+ if (prevIndex && prevIndex + 1 < input.length) {
+ const value = input.slice(prevIndex + 1);
+ parts.push(value);
+ if (opts.tokens) {
+ tokens[tokens.length - 1].value = value;
+ depth(tokens[tokens.length - 1]);
+ state.maxDepth += tokens[tokens.length - 1].depth;
+ }
+ }
+ state.slashes = slashes;
+ state.parts = parts;
+ }
+ return state;
+ };
+ module2.exports = scan;
+ }
+});
+
+// node_modules/picomatch/lib/parse.js
+var require_parse = __commonJS({
+ "node_modules/picomatch/lib/parse.js"(exports2, module2) {
+ "use strict";
+ var constants = require_constants();
+ var utils = require_utils();
+ var {
+ MAX_LENGTH,
+ POSIX_REGEX_SOURCE,
+ REGEX_NON_SPECIAL_CHARS,
+ REGEX_SPECIAL_CHARS_BACKREF,
+ REPLACEMENTS
+ } = constants;
+ var expandRange = (args, options) => {
+ if (typeof options.expandRange === "function") {
+ return options.expandRange(...args, options);
+ }
+ args.sort();
+ const value = `[${args.join("-")}]`;
+ try {
+ new RegExp(value);
+ } catch (ex) {
+ return args.map((v) => utils.escapeRegex(v)).join("..");
+ }
+ return value;
+ };
+ var syntaxError = (type, char) => {
+ return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`;
+ };
+ var splitTopLevel = (input) => {
+ const parts = [];
+ let bracket = 0;
+ let paren = 0;
+ let quote = 0;
+ let value = "";
+ let escaped = false;
+ for (const ch of input) {
+ if (escaped === true) {
+ value += ch;
+ escaped = false;
+ continue;
+ }
+ if (ch === "\\") {
+ value += ch;
+ escaped = true;
+ continue;
+ }
+ if (ch === '"') {
+ quote = quote === 1 ? 0 : 1;
+ value += ch;
+ continue;
+ }
+ if (quote === 0) {
+ if (ch === "[") {
+ bracket++;
+ } else if (ch === "]" && bracket > 0) {
+ bracket--;
+ } else if (bracket === 0) {
+ if (ch === "(") {
+ paren++;
+ } else if (ch === ")" && paren > 0) {
+ paren--;
+ } else if (ch === "|" && paren === 0) {
+ parts.push(value);
+ value = "";
+ continue;
+ }
+ }
+ }
+ value += ch;
+ }
+ parts.push(value);
+ return parts;
+ };
+ var isPlainBranch = (branch) => {
+ let escaped = false;
+ for (const ch of branch) {
+ if (escaped === true) {
+ escaped = false;
+ continue;
+ }
+ if (ch === "\\") {
+ escaped = true;
+ continue;
+ }
+ if (/[?*+@!()[\]{}]/.test(ch)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ var normalizeSimpleBranch = (branch) => {
+ let value = branch.trim();
+ let changed = true;
+ while (changed === true) {
+ changed = false;
+ if (/^@\([^\\()[\]{}|]+\)$/.test(value)) {
+ value = value.slice(2, -1);
+ changed = true;
+ }
+ }
+ if (!isPlainBranch(value)) {
+ return;
+ }
+ return value.replace(/\\(.)/g, "$1");
+ };
+ var hasRepeatedCharPrefixOverlap = (branches) => {
+ const values = branches.map(normalizeSimpleBranch).filter(Boolean);
+ for (let i = 0; i < values.length; i++) {
+ for (let j = i + 1; j < values.length; j++) {
+ const a = values[i];
+ const b = values[j];
+ const char = a[0];
+ if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) {
+ continue;
+ }
+ if (a === b || a.startsWith(b) || b.startsWith(a)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+ var parseRepeatedExtglob = (pattern, requireEnd = true) => {
+ if (pattern[0] !== "+" && pattern[0] !== "*" || pattern[1] !== "(") {
+ return;
+ }
+ let bracket = 0;
+ let paren = 0;
+ let quote = 0;
+ let escaped = false;
+ for (let i = 1; i < pattern.length; i++) {
+ const ch = pattern[i];
+ if (escaped === true) {
+ escaped = false;
+ continue;
+ }
+ if (ch === "\\") {
+ escaped = true;
+ continue;
+ }
+ if (ch === '"') {
+ quote = quote === 1 ? 0 : 1;
+ continue;
+ }
+ if (quote === 1) {
+ continue;
+ }
+ if (ch === "[") {
+ bracket++;
+ continue;
+ }
+ if (ch === "]" && bracket > 0) {
+ bracket--;
+ continue;
+ }
+ if (bracket > 0) {
+ continue;
+ }
+ if (ch === "(") {
+ paren++;
+ continue;
+ }
+ if (ch === ")") {
+ paren--;
+ if (paren === 0) {
+ if (requireEnd === true && i !== pattern.length - 1) {
+ return;
+ }
+ return {
+ type: pattern[0],
+ body: pattern.slice(2, i),
+ end: i
+ };
+ }
+ }
+ }
+ };
+ var getStarExtglobSequenceOutput = (pattern) => {
+ let index = 0;
+ const chars = [];
+ while (index < pattern.length) {
+ const match = parseRepeatedExtglob(pattern.slice(index), false);
+ if (!match || match.type !== "*") {
+ return;
+ }
+ const branches = splitTopLevel(match.body).map((branch2) => branch2.trim());
+ if (branches.length !== 1) {
+ return;
+ }
+ const branch = normalizeSimpleBranch(branches[0]);
+ if (!branch || branch.length !== 1) {
+ return;
+ }
+ chars.push(branch);
+ index += match.end + 1;
+ }
+ if (chars.length < 1) {
+ return;
+ }
+ const source = chars.length === 1 ? utils.escapeRegex(chars[0]) : `[${chars.map((ch) => utils.escapeRegex(ch)).join("")}]`;
+ return `${source}*`;
+ };
+ var repeatedExtglobRecursion = (pattern) => {
+ let depth = 0;
+ let value = pattern.trim();
+ let match = parseRepeatedExtglob(value);
+ while (match) {
+ depth++;
+ value = match.body.trim();
+ match = parseRepeatedExtglob(value);
+ }
+ return depth;
+ };
+ var analyzeRepeatedExtglob = (body, options) => {
+ if (options.maxExtglobRecursion === false) {
+ return { risky: false };
+ }
+ const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants.DEFAULT_MAX_EXTGLOB_RECURSION;
+ const branches = splitTopLevel(body).map((branch) => branch.trim());
+ if (branches.length > 1) {
+ if (branches.some((branch) => branch === "") || branches.some((branch) => /^[*?]+$/.test(branch)) || hasRepeatedCharPrefixOverlap(branches)) {
+ return { risky: true };
+ }
+ }
+ for (const branch of branches) {
+ const safeOutput = getStarExtglobSequenceOutput(branch);
+ if (safeOutput) {
+ return { risky: true, safeOutput };
+ }
+ if (repeatedExtglobRecursion(branch) > max) {
+ return { risky: true };
+ }
+ }
+ return { risky: false };
+ };
+ var parse4 = (input, options) => {
+ if (typeof input !== "string") {
+ throw new TypeError("Expected a string");
+ }
+ input = REPLACEMENTS[input] || input;
+ const opts = { ...options };
+ const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
+ let len = input.length;
+ if (len > max) {
+ throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
+ }
+ const bos = { type: "bos", value: "", output: opts.prepend || "" };
+ const tokens = [bos];
+ const capture = opts.capture ? "" : "?:";
+ const PLATFORM_CHARS = constants.globChars(opts.windows);
+ const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
+ const {
+ DOT_LITERAL,
+ PLUS_LITERAL,
+ SLASH_LITERAL,
+ ONE_CHAR,
+ DOTS_SLASH,
+ NO_DOT,
+ NO_DOT_SLASH,
+ NO_DOTS_SLASH,
+ QMARK,
+ QMARK_NO_DOT,
+ STAR,
+ START_ANCHOR
+ } = PLATFORM_CHARS;
+ const globstar = (opts2) => {
+ return `(${capture}(?:(?!${START_ANCHOR}${opts2.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
+ };
+ const nodot = opts.dot ? "" : NO_DOT;
+ const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT;
+ let star = opts.bash === true ? globstar(opts) : STAR;
+ if (opts.capture) {
+ star = `(${star})`;
+ }
+ if (typeof opts.noext === "boolean") {
+ opts.noextglob = opts.noext;
+ }
+ const state = {
+ input,
+ index: -1,
+ start: 0,
+ dot: opts.dot === true,
+ consumed: "",
+ output: "",
+ prefix: "",
+ backtrack: false,
+ negated: false,
+ brackets: 0,
+ braces: 0,
+ parens: 0,
+ quotes: 0,
+ globstar: false,
+ tokens
+ };
+ input = utils.removePrefix(input, state);
+ len = input.length;
+ const extglobs = [];
+ const braces = [];
+ const stack = [];
+ let prev = bos;
+ let value;
+ const eos = () => state.index === len - 1;
+ const peek = state.peek = (n = 1) => input[state.index + n];
+ const advance = state.advance = () => input[++state.index] || "";
+ const remaining = () => input.slice(state.index + 1);
+ const consume = (value2 = "", num = 0) => {
+ state.consumed += value2;
+ state.index += num;
+ };
+ const append = (token) => {
+ state.output += token.output != null ? token.output : token.value;
+ consume(token.value);
+ };
+ const negate = () => {
+ let count = 1;
+ while (peek() === "!" && (peek(2) !== "(" || peek(3) === "?")) {
+ advance();
+ state.start++;
+ count++;
+ }
+ if (count % 2 === 0) {
+ return false;
+ }
+ state.negated = true;
+ state.start++;
+ return true;
+ };
+ const increment = (type) => {
+ state[type]++;
+ stack.push(type);
+ };
+ const decrement = (type) => {
+ state[type]--;
+ stack.pop();
+ };
+ const push = (tok) => {
+ if (prev.type === "globstar") {
+ const isBrace = state.braces > 0 && (tok.type === "comma" || tok.type === "brace");
+ const isExtglob = tok.extglob === true || extglobs.length && (tok.type === "pipe" || tok.type === "paren");
+ if (tok.type !== "slash" && tok.type !== "paren" && !isBrace && !isExtglob) {
+ state.output = state.output.slice(0, -prev.output.length);
+ prev.type = "star";
+ prev.value = "*";
+ prev.output = star;
+ state.output += prev.output;
+ }
+ }
+ if (extglobs.length && tok.type !== "paren") {
+ extglobs[extglobs.length - 1].inner += tok.value;
+ }
+ if (tok.value || tok.output) append(tok);
+ if (prev && prev.type === "text" && tok.type === "text") {
+ prev.output = (prev.output || prev.value) + tok.value;
+ prev.value += tok.value;
+ return;
+ }
+ tok.prev = prev;
+ tokens.push(tok);
+ prev = tok;
+ };
+ const extglobOpen = (type, value2) => {
+ const token = { ...EXTGLOB_CHARS[value2], conditions: 1, inner: "" };
+ token.prev = prev;
+ token.parens = state.parens;
+ token.output = state.output;
+ token.startIndex = state.index;
+ token.tokensIndex = tokens.length;
+ const output = (opts.capture ? "(" : "") + token.open;
+ increment("parens");
+ push({ type, value: value2, output: state.output ? "" : ONE_CHAR });
+ push({ type: "paren", extglob: true, value: advance(), output });
+ extglobs.push(token);
+ };
+ const extglobClose = (token) => {
+ const literal = input.slice(token.startIndex, state.index + 1);
+ const body = input.slice(token.startIndex + 2, state.index);
+ const analysis = analyzeRepeatedExtglob(body, opts);
+ if ((token.type === "plus" || token.type === "star") && analysis.risky) {
+ const safeOutput = analysis.safeOutput ? (token.output ? "" : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) : void 0;
+ const open = tokens[token.tokensIndex];
+ open.type = "text";
+ open.value = literal;
+ open.output = safeOutput || utils.escapeRegex(literal);
+ for (let i = token.tokensIndex + 1; i < tokens.length; i++) {
+ tokens[i].value = "";
+ tokens[i].output = "";
+ delete tokens[i].suffix;
+ }
+ state.output = token.output + open.output;
+ state.backtrack = true;
+ push({ type: "paren", extglob: true, value, output: "" });
+ decrement("parens");
+ return;
+ }
+ let output = token.close + (opts.capture ? ")" : "");
+ let rest;
+ if (token.type === "negate") {
+ let extglobStar = star;
+ if (token.inner && token.inner.length > 1 && token.inner.includes("/")) {
+ extglobStar = globstar(opts);
+ }
+ if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) {
+ output = token.close = `)$))${extglobStar}`;
+ }
+ if (token.inner.includes("*") && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) {
+ const expression = parse4(rest, { ...options, fastpaths: false }).output;
+ output = token.close = `)${expression})${extglobStar})`;
+ }
+ if (token.prev.type === "bos") {
+ state.negatedExtglob = true;
+ }
+ }
+ push({ type: "paren", extglob: true, value, output });
+ decrement("parens");
+ };
+ if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
+ let backslashes = false;
+ let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
+ if (first === "\\") {
+ backslashes = true;
+ return m;
+ }
+ if (first === "?") {
+ if (esc) {
+ return esc + first + (rest ? QMARK.repeat(rest.length) : "");
+ }
+ if (index === 0) {
+ return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : "");
+ }
+ return QMARK.repeat(chars.length);
+ }
+ if (first === ".") {
+ return DOT_LITERAL.repeat(chars.length);
+ }
+ if (first === "*") {
+ if (esc) {
+ return esc + first + (rest ? star : "");
+ }
+ return star;
+ }
+ return esc ? m : `\\${m}`;
+ });
+ if (backslashes === true) {
+ if (opts.unescape === true) {
+ output = output.replace(/\\/g, "");
+ } else {
+ output = output.replace(/\\+/g, (m) => {
+ return m.length % 2 === 0 ? "\\\\" : m ? "\\" : "";
+ });
+ }
+ }
+ if (output === input && opts.contains === true) {
+ state.output = input;
+ return state;
+ }
+ state.output = utils.wrapOutput(output, state, options);
+ return state;
+ }
+ while (!eos()) {
+ value = advance();
+ if (value === "\0") {
+ continue;
+ }
+ if (value === "\\") {
+ const next = peek();
+ if (next === "/" && opts.bash !== true) {
+ continue;
+ }
+ if (next === "." || next === ";") {
+ continue;
+ }
+ if (!next) {
+ value += "\\";
+ push({ type: "text", value });
+ continue;
+ }
+ const match = /^\\+/.exec(remaining());
+ let slashes = 0;
+ if (match && match[0].length > 2) {
+ slashes = match[0].length;
+ state.index += slashes;
+ if (slashes % 2 !== 0) {
+ value += "\\";
+ }
+ }
+ if (opts.unescape === true) {
+ value = advance();
+ } else {
+ value += advance();
+ }
+ if (state.brackets === 0) {
+ push({ type: "text", value });
+ continue;
+ }
+ }
+ if (state.brackets > 0 && (value !== "]" || prev.value === "[" || prev.value === "[^")) {
+ if (opts.posix !== false && value === ":") {
+ const inner = prev.value.slice(1);
+ if (inner.includes("[")) {
+ prev.posix = true;
+ if (inner.includes(":")) {
+ const idx = prev.value.lastIndexOf("[");
+ const pre = prev.value.slice(0, idx);
+ const rest2 = prev.value.slice(idx + 2);
+ const posix = POSIX_REGEX_SOURCE[rest2];
+ if (posix) {
+ prev.value = pre + posix;
+ state.backtrack = true;
+ advance();
+ if (!bos.output && tokens.indexOf(prev) === 1) {
+ bos.output = ONE_CHAR;
+ }
+ continue;
+ }
+ }
+ }
+ }
+ if (value === "[" && peek() !== ":" || value === "-" && peek() === "]") {
+ value = `\\${value}`;
+ }
+ if (value === "]" && (prev.value === "[" || prev.value === "[^")) {
+ value = `\\${value}`;
+ }
+ if (opts.posix === true && value === "!" && prev.value === "[") {
+ value = "^";
+ }
+ prev.value += value;
+ append({ value });
+ continue;
+ }
+ if (state.quotes === 1 && value !== '"') {
+ value = utils.escapeRegex(value);
+ prev.value += value;
+ append({ value });
+ continue;
+ }
+ if (value === '"') {
+ state.quotes = state.quotes === 1 ? 0 : 1;
+ if (opts.keepQuotes === true) {
+ push({ type: "text", value });
+ }
+ continue;
+ }
+ if (value === "(") {
+ increment("parens");
+ push({ type: "paren", value });
+ continue;
+ }
+ if (value === ")") {
+ if (state.parens === 0 && opts.strictBrackets === true) {
+ throw new SyntaxError(syntaxError("opening", "("));
+ }
+ const extglob = extglobs[extglobs.length - 1];
+ if (extglob && state.parens === extglob.parens + 1) {
+ extglobClose(extglobs.pop());
+ continue;
+ }
+ push({ type: "paren", value, output: state.parens ? ")" : "\\)" });
+ decrement("parens");
+ continue;
+ }
+ if (value === "[") {
+ if (opts.nobracket === true || !remaining().includes("]")) {
+ if (opts.nobracket !== true && opts.strictBrackets === true) {
+ throw new SyntaxError(syntaxError("closing", "]"));
+ }
+ value = `\\${value}`;
+ } else {
+ increment("brackets");
+ }
+ push({ type: "bracket", value });
+ continue;
+ }
+ if (value === "]") {
+ if (opts.nobracket === true || prev && prev.type === "bracket" && prev.value.length === 1) {
+ push({ type: "text", value, output: `\\${value}` });
+ continue;
+ }
+ if (state.brackets === 0) {
+ if (opts.strictBrackets === true) {
+ throw new SyntaxError(syntaxError("opening", "["));
+ }
+ push({ type: "text", value, output: `\\${value}` });
+ continue;
+ }
+ decrement("brackets");
+ const prevValue = prev.value.slice(1);
+ if (prev.posix !== true && prevValue[0] === "^" && !prevValue.includes("/")) {
+ value = `/${value}`;
+ }
+ prev.value += value;
+ append({ value });
+ if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) {
+ continue;
+ }
+ const escaped = utils.escapeRegex(prev.value);
+ state.output = state.output.slice(0, -prev.value.length);
+ if (opts.literalBrackets === true) {
+ state.output += escaped;
+ prev.value = escaped;
+ continue;
+ }
+ prev.value = `(${capture}${escaped}|${prev.value})`;
+ state.output += prev.value;
+ continue;
+ }
+ if (value === "{" && opts.nobrace !== true) {
+ increment("braces");
+ const open = {
+ type: "brace",
+ value,
+ output: "(",
+ outputIndex: state.output.length,
+ tokensIndex: state.tokens.length
+ };
+ braces.push(open);
+ push(open);
+ continue;
+ }
+ if (value === "}") {
+ const brace = braces[braces.length - 1];
+ if (opts.nobrace === true || !brace) {
+ push({ type: "text", value, output: value });
+ continue;
+ }
+ let output = ")";
+ if (brace.dots === true) {
+ const arr = tokens.slice();
+ const range = [];
+ for (let i = arr.length - 1; i >= 0; i--) {
+ tokens.pop();
+ if (arr[i].type === "brace") {
+ break;
+ }
+ if (arr[i].type !== "dots") {
+ range.unshift(arr[i].value);
+ }
+ }
+ output = expandRange(range, opts);
+ state.backtrack = true;
+ }
+ if (brace.comma !== true && brace.dots !== true) {
+ const out = state.output.slice(0, brace.outputIndex);
+ const toks = state.tokens.slice(brace.tokensIndex);
+ brace.value = brace.output = "\\{";
+ value = output = "\\}";
+ state.output = out;
+ for (const t of toks) {
+ state.output += t.output || t.value;
+ }
+ }
+ push({ type: "brace", value, output });
+ decrement("braces");
+ braces.pop();
+ continue;
+ }
+ if (value === "|") {
+ if (extglobs.length > 0) {
+ extglobs[extglobs.length - 1].conditions++;
+ }
+ push({ type: "text", value });
+ continue;
+ }
+ if (value === ",") {
+ let output = value;
+ const brace = braces[braces.length - 1];
+ if (brace && stack[stack.length - 1] === "braces") {
+ brace.comma = true;
+ output = "|";
+ }
+ push({ type: "comma", value, output });
+ continue;
+ }
+ if (value === "/") {
+ if (prev.type === "dot" && state.index === state.start + 1) {
+ state.start = state.index + 1;
+ state.consumed = "";
+ state.output = "";
+ tokens.pop();
+ prev = bos;
+ continue;
+ }
+ push({ type: "slash", value, output: SLASH_LITERAL });
+ continue;
+ }
+ if (value === ".") {
+ if (state.braces > 0 && prev.type === "dot") {
+ if (prev.value === ".") prev.output = DOT_LITERAL;
+ const brace = braces[braces.length - 1];
+ prev.type = "dots";
+ prev.output += value;
+ prev.value += value;
+ brace.dots = true;
+ continue;
+ }
+ if (state.braces + state.parens === 0 && prev.type !== "bos" && prev.type !== "slash") {
+ push({ type: "text", value, output: DOT_LITERAL });
+ continue;
+ }
+ push({ type: "dot", value, output: DOT_LITERAL });
+ continue;
+ }
+ if (value === "?") {
+ const isGroup = prev && prev.value === "(";
+ if (!isGroup && opts.noextglob !== true && peek() === "(" && peek(2) !== "?") {
+ extglobOpen("qmark", value);
+ continue;
+ }
+ if (prev && prev.type === "paren") {
+ const next = peek();
+ let output = value;
+ if (prev.value === "(" && !/[!=<:]/.test(next) || next === "<" && !/<([!=]|\w+>)/.test(remaining())) {
+ output = `\\${value}`;
+ }
+ push({ type: "text", value, output });
+ continue;
+ }
+ if (opts.dot !== true && (prev.type === "slash" || prev.type === "bos")) {
+ push({ type: "qmark", value, output: QMARK_NO_DOT });
+ continue;
+ }
+ push({ type: "qmark", value, output: QMARK });
+ continue;
+ }
+ if (value === "!") {
+ if (opts.noextglob !== true && peek() === "(") {
+ if (peek(2) !== "?" || !/[!=<:]/.test(peek(3))) {
+ extglobOpen("negate", value);
+ continue;
+ }
+ }
+ if (opts.nonegate !== true && state.index === 0) {
+ negate();
+ continue;
+ }
+ }
+ if (value === "+") {
+ if (opts.noextglob !== true && peek() === "(" && peek(2) !== "?") {
+ extglobOpen("plus", value);
+ continue;
+ }
+ if (prev && prev.value === "(" || opts.regex === false) {
+ push({ type: "plus", value, output: PLUS_LITERAL });
+ continue;
+ }
+ if (prev && (prev.type === "bracket" || prev.type === "paren" || prev.type === "brace") || state.parens > 0) {
+ push({ type: "plus", value });
+ continue;
+ }
+ push({ type: "plus", value: PLUS_LITERAL });
+ continue;
+ }
+ if (value === "@") {
+ if (opts.noextglob !== true && peek() === "(" && peek(2) !== "?") {
+ push({ type: "at", extglob: true, value, output: "" });
+ continue;
+ }
+ push({ type: "text", value });
+ continue;
+ }
+ if (value !== "*") {
+ if (value === "$" || value === "^") {
+ value = `\\${value}`;
+ }
+ const match = REGEX_NON_SPECIAL_CHARS.exec(remaining());
+ if (match) {
+ value += match[0];
+ state.index += match[0].length;
+ }
+ push({ type: "text", value });
+ continue;
+ }
+ if (prev && (prev.type === "globstar" || prev.star === true)) {
+ prev.type = "star";
+ prev.star = true;
+ prev.value += value;
+ prev.output = star;
+ state.backtrack = true;
+ state.globstar = true;
+ consume(value);
+ continue;
+ }
+ let rest = remaining();
+ if (opts.noextglob !== true && /^\([^?]/.test(rest)) {
+ extglobOpen("star", value);
+ continue;
+ }
+ if (prev.type === "star") {
+ if (opts.noglobstar === true) {
+ consume(value);
+ continue;
+ }
+ const prior = prev.prev;
+ const before = prior.prev;
+ const isStart = prior.type === "slash" || prior.type === "bos";
+ const afterStar = before && (before.type === "star" || before.type === "globstar");
+ if (opts.bash === true && (!isStart || rest[0] && rest[0] !== "/")) {
+ push({ type: "star", value, output: "" });
+ continue;
+ }
+ const isBrace = state.braces > 0 && (prior.type === "comma" || prior.type === "brace");
+ const isExtglob = extglobs.length && (prior.type === "pipe" || prior.type === "paren");
+ if (!isStart && prior.type !== "paren" && !isBrace && !isExtglob) {
+ push({ type: "star", value, output: "" });
+ continue;
+ }
+ while (rest.slice(0, 3) === "/**") {
+ const after = input[state.index + 4];
+ if (after && after !== "/") {
+ break;
+ }
+ rest = rest.slice(3);
+ consume("/**", 3);
+ }
+ if (prior.type === "bos" && eos()) {
+ prev.type = "globstar";
+ prev.value += value;
+ prev.output = globstar(opts);
+ state.output = prev.output;
+ state.globstar = true;
+ consume(value);
+ continue;
+ }
+ if (prior.type === "slash" && prior.prev.type !== "bos" && !afterStar && eos()) {
+ state.output = state.output.slice(0, -(prior.output + prev.output).length);
+ prior.output = `(?:${prior.output}`;
+ prev.type = "globstar";
+ prev.output = globstar(opts) + (opts.strictSlashes ? ")" : "|$)");
+ prev.value += value;
+ state.globstar = true;
+ state.output += prior.output + prev.output;
+ consume(value);
+ continue;
+ }
+ if (prior.type === "slash" && prior.prev.type !== "bos" && rest[0] === "/") {
+ const end = rest[1] !== void 0 ? "|$" : "";
+ state.output = state.output.slice(0, -(prior.output + prev.output).length);
+ prior.output = `(?:${prior.output}`;
+ prev.type = "globstar";
+ prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`;
+ prev.value += value;
+ state.output += prior.output + prev.output;
+ state.globstar = true;
+ consume(value + advance());
+ push({ type: "slash", value: "/", output: "" });
+ continue;
+ }
+ if (prior.type === "bos" && rest[0] === "/") {
+ prev.type = "globstar";
+ prev.value += value;
+ prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`;
+ state.output = prev.output;
+ state.globstar = true;
+ consume(value + advance());
+ push({ type: "slash", value: "/", output: "" });
+ continue;
+ }
+ state.output = state.output.slice(0, -prev.output.length);
+ prev.type = "globstar";
+ prev.output = globstar(opts);
+ prev.value += value;
+ state.output += prev.output;
+ state.globstar = true;
+ consume(value);
+ continue;
+ }
+ const token = { type: "star", value, output: star };
+ if (opts.bash === true) {
+ token.output = ".*?";
+ if (prev.type === "bos" || prev.type === "slash") {
+ token.output = nodot + token.output;
+ }
+ push(token);
+ continue;
+ }
+ if (prev && (prev.type === "bracket" || prev.type === "paren") && opts.regex === true) {
+ token.output = value;
+ push(token);
+ continue;
+ }
+ if (state.index === state.start || prev.type === "slash" || prev.type === "dot") {
+ if (prev.type === "dot") {
+ state.output += NO_DOT_SLASH;
+ prev.output += NO_DOT_SLASH;
+ } else if (opts.dot === true) {
+ state.output += NO_DOTS_SLASH;
+ prev.output += NO_DOTS_SLASH;
+ } else {
+ state.output += nodot;
+ prev.output += nodot;
+ }
+ if (peek() !== "*") {
+ state.output += ONE_CHAR;
+ prev.output += ONE_CHAR;
+ }
+ }
+ push(token);
+ }
+ while (state.brackets > 0) {
+ if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", "]"));
+ state.output = utils.escapeLast(state.output, "[");
+ decrement("brackets");
+ }
+ while (state.parens > 0) {
+ if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", ")"));
+ state.output = utils.escapeLast(state.output, "(");
+ decrement("parens");
+ }
+ while (state.braces > 0) {
+ if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", "}"));
+ state.output = utils.escapeLast(state.output, "{");
+ decrement("braces");
+ }
+ if (opts.strictSlashes !== true && (prev.type === "star" || prev.type === "bracket")) {
+ push({ type: "maybe_slash", value: "", output: `${SLASH_LITERAL}?` });
+ }
+ if (state.backtrack === true) {
+ state.output = "";
+ for (const token of state.tokens) {
+ state.output += token.output != null ? token.output : token.value;
+ if (token.suffix) {
+ state.output += token.suffix;
+ }
+ }
+ }
+ return state;
+ };
+ parse4.fastpaths = (input, options) => {
+ const opts = { ...options };
+ const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
+ const len = input.length;
+ if (len > max) {
+ throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
+ }
+ input = REPLACEMENTS[input] || input;
+ const {
+ DOT_LITERAL,
+ SLASH_LITERAL,
+ ONE_CHAR,
+ DOTS_SLASH,
+ NO_DOT,
+ NO_DOTS,
+ NO_DOTS_SLASH,
+ STAR,
+ START_ANCHOR
+ } = constants.globChars(opts.windows);
+ const nodot = opts.dot ? NO_DOTS : NO_DOT;
+ const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
+ const capture = opts.capture ? "" : "?:";
+ const state = { negated: false, prefix: "" };
+ let star = opts.bash === true ? ".*?" : STAR;
+ if (opts.capture) {
+ star = `(${star})`;
+ }
+ const globstar = (opts2) => {
+ if (opts2.noglobstar === true) return star;
+ return `(${capture}(?:(?!${START_ANCHOR}${opts2.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
+ };
+ const create = (str) => {
+ switch (str) {
+ case "*":
+ return `${nodot}${ONE_CHAR}${star}`;
+ case ".*":
+ return `${DOT_LITERAL}${ONE_CHAR}${star}`;
+ case "*.*":
+ return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
+ case "*/*":
+ return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`;
+ case "**":
+ return nodot + globstar(opts);
+ case "**/*":
+ return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`;
+ case "**/*.*":
+ return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
+ case "**/.*":
+ return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
+ default: {
+ const match = /^(.*?)\.(\w+)$/.exec(str);
+ if (!match) return;
+ const source2 = create(match[1]);
+ if (!source2) return;
+ return source2 + DOT_LITERAL + match[2];
+ }
+ }
+ };
+ const output = utils.removePrefix(input, state);
+ let source = create(output);
+ if (source && opts.strictSlashes !== true) {
+ source += `${SLASH_LITERAL}?`;
+ }
+ return source;
+ };
+ module2.exports = parse4;
+ }
+});
+
+// node_modules/picomatch/lib/picomatch.js
+var require_picomatch = __commonJS({
+ "node_modules/picomatch/lib/picomatch.js"(exports2, module2) {
+ "use strict";
+ var scan = require_scan();
+ var parse4 = require_parse();
+ var utils = require_utils();
+ var constants = require_constants();
+ var isObject = (val) => val && typeof val === "object" && !Array.isArray(val);
+ var picomatch3 = (glob, options, returnState = false) => {
+ if (Array.isArray(glob)) {
+ const fns = glob.map((input) => picomatch3(input, options, returnState));
+ const arrayMatcher = (str) => {
+ for (const isMatch of fns) {
+ const state2 = isMatch(str);
+ if (state2) return state2;
+ }
+ return false;
+ };
+ return arrayMatcher;
+ }
+ const isState = isObject(glob) && glob.tokens && glob.input;
+ if (glob === "" || typeof glob !== "string" && !isState) {
+ throw new TypeError("Expected pattern to be a non-empty string");
+ }
+ const opts = options || {};
+ const posix = opts.windows;
+ const regex = isState ? picomatch3.compileRe(glob, options) : picomatch3.makeRe(glob, options, false, true);
+ const state = regex.state;
+ delete regex.state;
+ let isIgnored = () => false;
+ if (opts.ignore) {
+ const ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null };
+ isIgnored = picomatch3(opts.ignore, ignoreOpts, returnState);
+ }
+ const matcher = (input, returnObject = false) => {
+ const { isMatch, match, output } = picomatch3.test(input, regex, options, { glob, posix });
+ const result = { glob, state, regex, posix, input, output, match, isMatch };
+ if (typeof opts.onResult === "function") {
+ opts.onResult(result);
+ }
+ if (isMatch === false) {
+ result.isMatch = false;
+ return returnObject ? result : false;
+ }
+ if (isIgnored(input)) {
+ if (typeof opts.onIgnore === "function") {
+ opts.onIgnore(result);
+ }
+ result.isMatch = false;
+ return returnObject ? result : false;
+ }
+ if (typeof opts.onMatch === "function") {
+ opts.onMatch(result);
+ }
+ return returnObject ? result : true;
+ };
+ if (returnState) {
+ matcher.state = state;
+ }
+ return matcher;
+ };
+ picomatch3.test = (input, regex, options, { glob, posix } = {}) => {
+ if (typeof input !== "string") {
+ throw new TypeError("Expected input to be a string");
+ }
+ if (input === "") {
+ return { isMatch: false, output: "" };
+ }
+ const opts = options || {};
+ const format = opts.format || (posix ? utils.toPosixSlashes : null);
+ let match = input === glob;
+ let output = match && format ? format(input) : input;
+ if (match === false) {
+ output = format ? format(input) : input;
+ match = output === glob;
+ }
+ if (match === false || opts.capture === true) {
+ if (opts.matchBase === true || opts.basename === true) {
+ match = picomatch3.matchBase(input, regex, options, posix);
+ } else {
+ match = regex.exec(output);
+ }
+ }
+ return { isMatch: Boolean(match), match, output };
+ };
+ picomatch3.matchBase = (input, glob, options) => {
+ const regex = glob instanceof RegExp ? glob : picomatch3.makeRe(glob, options);
+ return regex.test(utils.basename(input));
+ };
+ picomatch3.isMatch = (str, patterns, options) => picomatch3(patterns, options)(str);
+ picomatch3.parse = (pattern, options) => {
+ if (Array.isArray(pattern)) return pattern.map((p) => picomatch3.parse(p, options));
+ return parse4(pattern, { ...options, fastpaths: false });
+ };
+ picomatch3.scan = (input, options) => scan(input, options);
+ picomatch3.compileRe = (state, options, returnOutput = false, returnState = false) => {
+ if (returnOutput === true) {
+ return state.output;
+ }
+ const opts = options || {};
+ const prepend = opts.contains ? "" : "^";
+ const append = opts.contains ? "" : "$";
+ let source = `${prepend}(?:${state.output})${append}`;
+ if (state && state.negated === true) {
+ source = `^(?!${source}).*$`;
+ }
+ const regex = picomatch3.toRegex(source, options);
+ if (returnState === true) {
+ regex.state = state;
+ }
+ return regex;
+ };
+ picomatch3.makeRe = (input, options = {}, returnOutput = false, returnState = false) => {
+ if (!input || typeof input !== "string") {
+ throw new TypeError("Expected a non-empty string");
+ }
+ let parsed = { negated: false, fastpaths: true };
+ if (options.fastpaths !== false && (input[0] === "." || input[0] === "*")) {
+ parsed.output = parse4.fastpaths(input, options);
+ }
+ if (!parsed.output) {
+ parsed = parse4(input, options);
+ }
+ return picomatch3.compileRe(parsed, options, returnOutput, returnState);
+ };
+ picomatch3.toRegex = (source, options) => {
+ try {
+ const opts = options || {};
+ return new RegExp(source, opts.flags || (opts.nocase ? "i" : ""));
+ } catch (err) {
+ if (options && options.debug === true) throw err;
+ return /$^/;
+ }
+ };
+ picomatch3.constants = constants;
+ module2.exports = picomatch3;
+ }
+});
+
+// node_modules/picomatch/index.js
+var require_picomatch2 = __commonJS({
+ "node_modules/picomatch/index.js"(exports2, module2) {
+ "use strict";
+ var pico = require_picomatch();
+ var utils = require_utils();
+ function picomatch3(glob, options, returnState = false) {
+ if (options && (options.windows === null || options.windows === void 0)) {
+ options = { ...options, windows: utils.isWindows() };
+ }
+ return pico(glob, options, returnState);
+ }
+ Object.assign(picomatch3, pico);
+ module2.exports = picomatch3;
+ }
+});
+
+// src/config.ts
+function getConfigPath(projectRoot) {
+ return path7.join(projectRoot, ".codegraph", CONFIG_FILENAME);
+}
+function isSafeRegex(pattern) {
+ if (pattern.length > 500) return false;
+ if (/([+*}])\s*[+*{]/.test(pattern)) return false;
+ if (/\([^)]*[+*][^)]*\)[+*{]/.test(pattern)) return false;
+ try {
+ new RegExp(pattern);
+ return true;
+ } catch {
+ return false;
+ }
+}
+function validateConfig(config) {
+ if (typeof config !== "object" || config === null) {
+ return false;
+ }
+ const c = config;
+ if (typeof c.version !== "number") return false;
+ if (typeof c.rootDir !== "string") return false;
+ if (!Array.isArray(c.include)) return false;
+ if (!Array.isArray(c.exclude)) return false;
+ if (!Array.isArray(c.languages)) return false;
+ if (!Array.isArray(c.frameworks)) return false;
+ if (typeof c.maxFileSize !== "number") return false;
+ if (typeof c.extractDocstrings !== "boolean") return false;
+ if (typeof c.trackCallSites !== "boolean") return false;
+ if (!c.include.every((p) => typeof p === "string")) return false;
+ if (!c.exclude.every((p) => typeof p === "string")) return false;
+ const validLanguages = [
+ "typescript",
+ "javascript",
+ "python",
+ "go",
+ "rust",
+ "java",
+ "svelte",
+ "unknown"
+ ];
+ if (!c.languages.every((l) => validLanguages.includes(l))) return false;
+ for (const fw of c.frameworks) {
+ if (typeof fw !== "object" || fw === null) return false;
+ const framework = fw;
+ if (typeof framework.name !== "string") return false;
+ }
+ if (c.customPatterns !== void 0) {
+ if (!Array.isArray(c.customPatterns)) return false;
+ for (const pattern of c.customPatterns) {
+ if (typeof pattern !== "object" || pattern === null) return false;
+ const p = pattern;
+ if (typeof p.name !== "string") return false;
+ if (typeof p.pattern !== "string") return false;
+ if (typeof p.kind !== "string") return false;
+ if (!isSafeRegex(p.pattern)) return false;
+ }
+ }
+ return true;
+}
+function mergeConfig(defaults, overrides) {
+ return {
+ version: overrides.version ?? defaults.version,
+ rootDir: overrides.rootDir ?? defaults.rootDir,
+ include: overrides.include ?? defaults.include,
+ exclude: overrides.exclude ?? defaults.exclude,
+ languages: overrides.languages ?? defaults.languages,
+ frameworks: overrides.frameworks ?? defaults.frameworks,
+ maxFileSize: overrides.maxFileSize ?? defaults.maxFileSize,
+ extractDocstrings: overrides.extractDocstrings ?? defaults.extractDocstrings,
+ trackCallSites: overrides.trackCallSites ?? defaults.trackCallSites,
+ customPatterns: overrides.customPatterns ?? defaults.customPatterns
+ };
+}
+function loadConfig(projectRoot) {
+ const configPath = getConfigPath(projectRoot);
+ if (!fs5.existsSync(configPath)) {
+ return {
+ ...DEFAULT_CONFIG,
+ rootDir: projectRoot
+ };
+ }
+ try {
+ const content = fs5.readFileSync(configPath, "utf-8");
+ const parsed = JSON.parse(content);
+ const merged = mergeConfig(DEFAULT_CONFIG, parsed);
+ merged.rootDir = projectRoot;
+ if (!validateConfig(merged)) {
+ throw new Error("Invalid configuration format");
+ }
+ return merged;
+ } catch (error) {
+ if (error instanceof SyntaxError) {
+ throw new Error(`Invalid JSON in config file: ${configPath}`);
+ }
+ throw error;
+ }
+}
+function saveConfig(projectRoot, config) {
+ const configPath = getConfigPath(projectRoot);
+ const dir = path7.dirname(configPath);
+ if (!fs5.existsSync(dir)) {
+ fs5.mkdirSync(dir, { recursive: true });
+ }
+ const toSave = { ...config };
+ delete toSave.rootDir;
+ const content = JSON.stringify(toSave, null, 2);
+ const tmpPath = configPath + ".tmp";
+ fs5.writeFileSync(tmpPath, content, "utf-8");
+ fs5.renameSync(tmpPath, configPath);
+}
+function createDefaultConfig(projectRoot) {
+ return {
+ ...DEFAULT_CONFIG,
+ rootDir: projectRoot
+ };
+}
+var fs5, path7, CONFIG_FILENAME;
+var init_config = __esm({
+ "src/config.ts"() {
+ "use strict";
+ fs5 = __toESM(require("fs"));
+ path7 = __toESM(require("path"));
+ init_types();
+ init_utils();
+ CONFIG_FILENAME = "config.json";
+ }
+});
+
+// src/extraction/grammars.ts
+async function initGrammars() {
+ if (parserInitialized) return;
+ if (!initPromise) {
+ initPromise = import_web_tree_sitter.Parser.init().then(() => {
+ parserInitialized = true;
+ });
+ }
+ await initPromise;
+}
+async function loadGrammarsForLanguages(languages) {
+ if (!parserInitialized) {
+ await initGrammars();
+ }
+ const toLoad = [...new Set(languages)].filter(
+ (lang) => lang in WASM_GRAMMAR_FILES && !languageCache.has(lang) && !unavailableGrammarErrors.has(lang)
+ );
+ if (toLoad.length === 0) return;
+ const promises = toLoad.map((lang) => {
+ if (loadingPromises.has(lang)) {
+ return loadingPromises.get(lang);
+ }
+ const p = loadQueue.then(async () => {
+ if (languageCache.has(lang) || unavailableGrammarErrors.has(lang)) return;
+ const wasmFile = WASM_GRAMMAR_FILES[lang];
+ try {
+ const wasmPath = lang === "pascal" || lang === "scala" ? path8.join(__dirname, "wasm", wasmFile) : require.resolve(`tree-sitter-wasms/out/${wasmFile}`);
+ const language = await import_web_tree_sitter.Language.load(wasmPath);
+ languageCache.set(lang, language);
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ console.warn(
+ `[CodeGraph] Failed to load ${lang} grammar \u2014 parsing will be unavailable: ${message}`
+ );
+ unavailableGrammarErrors.set(lang, message);
+ }
+ }).finally(() => {
+ loadingPromises.delete(lang);
+ });
+ loadQueue = p.catch(() => {
+ });
+ loadingPromises.set(lang, p);
+ return p;
+ });
+ await Promise.all(promises);
+}
+async function loadAllGrammars() {
+ const allLanguages = Object.keys(WASM_GRAMMAR_FILES);
+ await loadGrammarsForLanguages(allLanguages);
+}
+function getParser(language) {
+ if (parserCache.has(language)) {
+ return parserCache.get(language);
+ }
+ const lang = languageCache.get(language);
+ if (!lang) {
+ return null;
+ }
+ const parser = new import_web_tree_sitter.Parser();
+ parser.setLanguage(lang);
+ parserCache.set(language, parser);
+ return parser;
+}
+function detectLanguage(filePath, source) {
+ const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
+ const lang = EXTENSION_MAP[ext] || "unknown";
+ if (lang === "c" && ext === ".h" && source) {
+ if (looksLikeCpp(source)) return "cpp";
+ }
+ return lang;
+}
+function looksLikeCpp(source) {
+ const sample = source.substring(0, 8192);
+ return /\bnamespace\b|\bclass\s+\w+\s*[:{]|\btemplate\s*<|\b(?:public|private|protected)\s*:|\bvirtual\b|\busing\s+(?:namespace\b|\w+\s*=)/.test(sample);
+}
+function isLanguageSupported(language) {
+ if (language === "svelte") return true;
+ if (language === "vue") return true;
+ if (language === "liquid") return true;
+ if (language === "unknown") return false;
+ return language in WASM_GRAMMAR_FILES;
+}
+function isGrammarLoaded(language) {
+ if (language === "svelte" || language === "vue" || language === "liquid") return true;
+ return languageCache.has(language);
+}
+function getSupportedLanguages() {
+ return [...Object.keys(WASM_GRAMMAR_FILES), "svelte", "vue", "liquid"];
+}
+var path8, import_web_tree_sitter, WASM_GRAMMAR_FILES, EXTENSION_MAP, parserCache, languageCache, unavailableGrammarErrors, parserInitialized, initPromise, loadQueue, loadingPromises;
+var init_grammars = __esm({
+ "src/extraction/grammars.ts"() {
+ "use strict";
+ path8 = __toESM(require("path"));
+ import_web_tree_sitter = require("web-tree-sitter");
+ WASM_GRAMMAR_FILES = {
+ typescript: "tree-sitter-typescript.wasm",
+ tsx: "tree-sitter-tsx.wasm",
+ javascript: "tree-sitter-javascript.wasm",
+ jsx: "tree-sitter-javascript.wasm",
+ python: "tree-sitter-python.wasm",
+ go: "tree-sitter-go.wasm",
+ rust: "tree-sitter-rust.wasm",
+ java: "tree-sitter-java.wasm",
+ c: "tree-sitter-c.wasm",
+ cpp: "tree-sitter-cpp.wasm",
+ csharp: "tree-sitter-c_sharp.wasm",
+ php: "tree-sitter-php.wasm",
+ ruby: "tree-sitter-ruby.wasm",
+ swift: "tree-sitter-swift.wasm",
+ kotlin: "tree-sitter-kotlin.wasm",
+ dart: "tree-sitter-dart.wasm",
+ pascal: "tree-sitter-pascal.wasm",
+ scala: "tree-sitter-scala.wasm"
+ };
+ EXTENSION_MAP = {
+ ".ts": "typescript",
+ ".tsx": "tsx",
+ ".js": "javascript",
+ ".mjs": "javascript",
+ ".cjs": "javascript",
+ ".jsx": "jsx",
+ ".py": "python",
+ ".pyw": "python",
+ ".go": "go",
+ ".rs": "rust",
+ ".java": "java",
+ ".c": "c",
+ ".h": "c",
+ // Could also be C++, defaulting to C
+ ".cpp": "cpp",
+ ".cc": "cpp",
+ ".cxx": "cpp",
+ ".hpp": "cpp",
+ ".hxx": "cpp",
+ ".cs": "csharp",
+ ".php": "php",
+ ".rb": "ruby",
+ ".rake": "ruby",
+ ".swift": "swift",
+ ".kt": "kotlin",
+ ".kts": "kotlin",
+ ".dart": "dart",
+ ".liquid": "liquid",
+ ".svelte": "svelte",
+ ".vue": "vue",
+ ".pas": "pascal",
+ ".dpr": "pascal",
+ ".dpk": "pascal",
+ ".lpr": "pascal",
+ ".dfm": "pascal",
+ ".fmx": "pascal",
+ ".scala": "scala",
+ ".sc": "scala"
+ };
+ parserCache = /* @__PURE__ */ new Map();
+ languageCache = /* @__PURE__ */ new Map();
+ unavailableGrammarErrors = /* @__PURE__ */ new Map();
+ parserInitialized = false;
+ initPromise = null;
+ loadQueue = Promise.resolve();
+ loadingPromises = /* @__PURE__ */ new Map();
+ }
+});
+
+// src/extraction/tree-sitter-helpers.ts
+function generateNodeId(filePath, kind, name, line) {
+ const hash = crypto.createHash("sha256").update(`${filePath}:${kind}:${name}:${line}`).digest("hex").substring(0, 32);
+ return `${kind}:${hash}`;
+}
+function getNodeText(node, source) {
+ return source.substring(node.startIndex, node.endIndex);
+}
+function getChildByField(node, fieldName) {
+ return node.childForFieldName(fieldName);
+}
+function getPrecedingDocstring(node, source) {
+ let sibling = node.previousNamedSibling;
+ const comments = [];
+ while (sibling) {
+ if (sibling.type === "comment" || sibling.type === "line_comment" || sibling.type === "block_comment" || sibling.type === "documentation_comment") {
+ comments.unshift(getNodeText(sibling, source));
+ sibling = sibling.previousNamedSibling;
+ } else {
+ break;
+ }
+ }
+ if (comments.length === 0) return void 0;
+ return comments.map(
+ (c) => c.replace(/^\/\*\*?|\*\/$/g, "").replace(/^\/\/\s?/gm, "").replace(/^\s*\*\s?/gm, "").trim()
+ ).join("\n").trim();
+}
+var crypto;
+var init_tree_sitter_helpers = __esm({
+ "src/extraction/tree-sitter-helpers.ts"() {
+ "use strict";
+ crypto = __toESM(require("crypto"));
+ }
+});
+
+// src/extraction/languages/typescript.ts
+var typescriptExtractor;
+var init_typescript = __esm({
+ "src/extraction/languages/typescript.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ typescriptExtractor = {
+ functionTypes: ["function_declaration", "arrow_function", "function_expression"],
+ classTypes: ["class_declaration", "abstract_class_declaration"],
+ methodTypes: ["method_definition", "public_field_definition"],
+ interfaceTypes: ["interface_declaration"],
+ structTypes: [],
+ enumTypes: ["enum_declaration"],
+ enumMemberTypes: ["property_identifier", "enum_assignment"],
+ typeAliasTypes: ["type_alias_declaration"],
+ importTypes: ["import_statement"],
+ callTypes: ["call_expression"],
+ variableTypes: ["lexical_declaration", "variable_declaration"],
+ nameField: "name",
+ bodyField: "body",
+ resolveBody: (node, bodyField) => {
+ if (node.type === "public_field_definition") {
+ for (let i = 0; i < node.namedChildCount; i++) {
+ const child = node.namedChild(i);
+ if (!child) continue;
+ if (child.type === "arrow_function" || child.type === "function_expression") {
+ return getChildByField(child, bodyField);
+ }
+ if (child.type === "call_expression") {
+ const args = getChildByField(child, "arguments");
+ if (args) {
+ for (let j = 0; j < args.namedChildCount; j++) {
+ const arg = args.namedChild(j);
+ if (arg && (arg.type === "arrow_function" || arg.type === "function_expression")) {
+ return getChildByField(arg, bodyField);
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ },
+ paramsField: "parameters",
+ returnField: "return_type",
+ getSignature: (node, source) => {
+ const params = getChildByField(node, "parameters");
+ const returnType = getChildByField(node, "return_type");
+ if (!params) return void 0;
+ let sig = getNodeText(params, source);
+ if (returnType) {
+ sig += ": " + getNodeText(returnType, source).replace(/^:\s*/, "");
+ }
+ return sig;
+ },
+ getVisibility: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "accessibility_modifier") {
+ const text = child.text;
+ if (text === "public") return "public";
+ if (text === "private") return "private";
+ if (text === "protected") return "protected";
+ }
+ }
+ return void 0;
+ },
+ isExported: (node, _source) => {
+ let current = node.parent;
+ while (current) {
+ if (current.type === "export_statement") return true;
+ current = current.parent;
+ }
+ return false;
+ },
+ isAsync: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "async") return true;
+ }
+ return false;
+ },
+ isStatic: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "static") return true;
+ }
+ return false;
+ },
+ isConst: (node) => {
+ if (node.type === "lexical_declaration") {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "const") return true;
+ }
+ }
+ return false;
+ },
+ extractImport: (node, source) => {
+ const sourceField = node.childForFieldName("source");
+ if (sourceField) {
+ const moduleName = source.substring(sourceField.startIndex, sourceField.endIndex).replace(/['"]/g, "");
+ if (moduleName) {
+ return { moduleName, signature: source.substring(node.startIndex, node.endIndex).trim() };
+ }
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/javascript.ts
+var javascriptExtractor;
+var init_javascript = __esm({
+ "src/extraction/languages/javascript.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ javascriptExtractor = {
+ functionTypes: ["function_declaration", "arrow_function", "function_expression"],
+ classTypes: ["class_declaration"],
+ methodTypes: ["method_definition", "field_definition"],
+ interfaceTypes: [],
+ structTypes: [],
+ enumTypes: [],
+ typeAliasTypes: [],
+ importTypes: ["import_statement"],
+ callTypes: ["call_expression"],
+ variableTypes: ["lexical_declaration", "variable_declaration"],
+ nameField: "name",
+ bodyField: "body",
+ resolveBody: (node, bodyField) => {
+ if (node.type === "field_definition") {
+ for (let i = 0; i < node.namedChildCount; i++) {
+ const child = node.namedChild(i);
+ if (!child) continue;
+ if (child.type === "arrow_function" || child.type === "function_expression") {
+ return getChildByField(child, bodyField);
+ }
+ if (child.type === "call_expression") {
+ const args = getChildByField(child, "arguments");
+ if (args) {
+ for (let j = 0; j < args.namedChildCount; j++) {
+ const arg = args.namedChild(j);
+ if (arg && (arg.type === "arrow_function" || arg.type === "function_expression")) {
+ return getChildByField(arg, bodyField);
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ },
+ paramsField: "parameters",
+ getSignature: (node, source) => {
+ const params = getChildByField(node, "parameters");
+ return params ? getNodeText(params, source) : void 0;
+ },
+ isExported: (node, _source) => {
+ let current = node.parent;
+ while (current) {
+ if (current.type === "export_statement") return true;
+ current = current.parent;
+ }
+ return false;
+ },
+ isAsync: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "async") return true;
+ }
+ return false;
+ },
+ isConst: (node) => {
+ if (node.type === "lexical_declaration") {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "const") return true;
+ }
+ }
+ return false;
+ },
+ extractImport: (node, source) => {
+ const sourceField = node.childForFieldName("source");
+ if (sourceField) {
+ const moduleName = source.substring(sourceField.startIndex, sourceField.endIndex).replace(/['"]/g, "");
+ if (moduleName) {
+ return { moduleName, signature: source.substring(node.startIndex, node.endIndex).trim() };
+ }
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/python.ts
+var pythonExtractor;
+var init_python = __esm({
+ "src/extraction/languages/python.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ pythonExtractor = {
+ functionTypes: ["function_definition"],
+ classTypes: ["class_definition"],
+ methodTypes: ["function_definition"],
+ // Methods are functions inside classes
+ interfaceTypes: [],
+ structTypes: [],
+ enumTypes: [],
+ typeAliasTypes: [],
+ importTypes: ["import_statement", "import_from_statement"],
+ callTypes: ["call"],
+ variableTypes: ["assignment"],
+ // Python uses assignment for variable declarations
+ nameField: "name",
+ bodyField: "body",
+ paramsField: "parameters",
+ returnField: "return_type",
+ getSignature: (node, source) => {
+ const params = getChildByField(node, "parameters");
+ const returnType = getChildByField(node, "return_type");
+ if (!params) return void 0;
+ let sig = getNodeText(params, source);
+ if (returnType) {
+ sig += " -> " + getNodeText(returnType, source);
+ }
+ return sig;
+ },
+ isAsync: (node) => {
+ const prev = node.previousSibling;
+ return prev?.type === "async";
+ },
+ isStatic: (node) => {
+ const prev = node.previousNamedSibling;
+ if (prev?.type === "decorator") {
+ const text = prev.text;
+ return text.includes("staticmethod");
+ }
+ return false;
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ if (node.type === "import_from_statement") {
+ const moduleNode = node.childForFieldName("module_name");
+ if (moduleNode) {
+ return { moduleName: source.substring(moduleNode.startIndex, moduleNode.endIndex), signature: importText };
+ }
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/go.ts
+var goExtractor;
+var init_go = __esm({
+ "src/extraction/languages/go.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ goExtractor = {
+ functionTypes: ["function_declaration"],
+ classTypes: [],
+ // Go doesn't have classes
+ methodTypes: ["method_declaration"],
+ interfaceTypes: [],
+ // Handled via type_spec → resolveTypeAliasKind
+ structTypes: [],
+ // Handled via type_spec → resolveTypeAliasKind
+ enumTypes: [],
+ typeAliasTypes: ["type_spec"],
+ // Go type declarations
+ importTypes: ["import_declaration"],
+ callTypes: ["call_expression"],
+ variableTypes: ["var_declaration", "short_var_declaration", "const_declaration"],
+ methodsAreTopLevel: true,
+ nameField: "name",
+ bodyField: "body",
+ paramsField: "parameters",
+ returnField: "result",
+ getSignature: (node, source) => {
+ const params = getChildByField(node, "parameters");
+ const result = getChildByField(node, "result");
+ if (!params) return void 0;
+ let sig = getNodeText(params, source);
+ if (result) {
+ sig += " " + getNodeText(result, source);
+ }
+ return sig;
+ },
+ resolveTypeAliasKind: (node, _source) => {
+ const typeChild = getChildByField(node, "type");
+ if (!typeChild) return void 0;
+ if (typeChild.type === "struct_type") return "struct";
+ if (typeChild.type === "interface_type") return "interface";
+ return void 0;
+ },
+ getReceiverType: (node, source) => {
+ const receiver = getChildByField(node, "receiver");
+ if (!receiver) return void 0;
+ const text = getNodeText(receiver, source);
+ const match = text.match(/\*?\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/);
+ return match?.[1];
+ }
+ };
+ }
+});
+
+// src/extraction/languages/rust.ts
+var rustExtractor;
+var init_rust = __esm({
+ "src/extraction/languages/rust.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ rustExtractor = {
+ functionTypes: ["function_item"],
+ classTypes: [],
+ // Rust has impl blocks
+ methodTypes: ["function_item"],
+ // Methods are functions in impl blocks
+ interfaceTypes: ["trait_item"],
+ structTypes: ["struct_item"],
+ enumTypes: ["enum_item"],
+ enumMemberTypes: ["enum_variant"],
+ typeAliasTypes: ["type_item"],
+ // Rust type aliases
+ importTypes: ["use_declaration"],
+ callTypes: ["call_expression"],
+ variableTypes: ["let_declaration", "const_item", "static_item"],
+ interfaceKind: "trait",
+ nameField: "name",
+ bodyField: "body",
+ paramsField: "parameters",
+ returnField: "return_type",
+ getSignature: (node, source) => {
+ const params = getChildByField(node, "parameters");
+ const returnType = getChildByField(node, "return_type");
+ if (!params) return void 0;
+ let sig = getNodeText(params, source);
+ if (returnType) {
+ sig += " -> " + getNodeText(returnType, source);
+ }
+ return sig;
+ },
+ isAsync: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "async") return true;
+ }
+ return false;
+ },
+ getVisibility: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "visibility_modifier") {
+ return child.text.includes("pub") ? "public" : "private";
+ }
+ }
+ return "private";
+ },
+ getReceiverType: (node, source) => {
+ let parent = node.parent;
+ while (parent) {
+ if (parent.type === "impl_item") {
+ const children = parent.namedChildren;
+ const typeIdents = children.filter(
+ (c) => c.type === "type_identifier"
+ );
+ if (typeIdents.length > 0) {
+ const typeNode = typeIdents[typeIdents.length - 1];
+ return source.substring(typeNode.startIndex, typeNode.endIndex);
+ }
+ const genericType = children.find(
+ (c) => c.type === "generic_type"
+ );
+ if (genericType) {
+ const innerType = genericType.namedChildren.find(
+ (c) => c.type === "type_identifier"
+ );
+ if (innerType) {
+ return source.substring(innerType.startIndex, innerType.endIndex);
+ }
+ }
+ return void 0;
+ }
+ parent = parent.parent;
+ }
+ return void 0;
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ const getRootModule = (scopedNode) => {
+ const firstChild = scopedNode.namedChild(0);
+ if (!firstChild) return source.substring(scopedNode.startIndex, scopedNode.endIndex);
+ if (firstChild.type === "identifier" || firstChild.type === "crate" || firstChild.type === "super" || firstChild.type === "self") {
+ return source.substring(firstChild.startIndex, firstChild.endIndex);
+ } else if (firstChild.type === "scoped_identifier") {
+ return getRootModule(firstChild);
+ }
+ return source.substring(firstChild.startIndex, firstChild.endIndex);
+ };
+ const useArg = node.namedChildren.find(
+ (c) => c.type === "scoped_use_list" || c.type === "scoped_identifier" || c.type === "use_list" || c.type === "identifier"
+ );
+ if (useArg) {
+ return { moduleName: getRootModule(useArg), signature: importText };
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/java.ts
+var javaExtractor;
+var init_java = __esm({
+ "src/extraction/languages/java.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ javaExtractor = {
+ functionTypes: [],
+ classTypes: ["class_declaration"],
+ methodTypes: ["method_declaration", "constructor_declaration"],
+ interfaceTypes: ["interface_declaration"],
+ structTypes: [],
+ enumTypes: ["enum_declaration"],
+ enumMemberTypes: ["enum_constant"],
+ typeAliasTypes: [],
+ importTypes: ["import_declaration"],
+ callTypes: ["method_invocation"],
+ variableTypes: ["local_variable_declaration"],
+ fieldTypes: ["field_declaration"],
+ nameField: "name",
+ bodyField: "body",
+ paramsField: "parameters",
+ returnField: "type",
+ getSignature: (node, source) => {
+ const params = getChildByField(node, "parameters");
+ const returnType = getChildByField(node, "type");
+ if (!params) return void 0;
+ const paramsText = getNodeText(params, source);
+ return returnType ? getNodeText(returnType, source) + " " + paramsText : paramsText;
+ },
+ getVisibility: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "modifiers") {
+ const text = child.text;
+ if (text.includes("public")) return "public";
+ if (text.includes("private")) return "private";
+ if (text.includes("protected")) return "protected";
+ }
+ }
+ return void 0;
+ },
+ isStatic: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "modifiers" && child.text.includes("static")) {
+ return true;
+ }
+ }
+ return false;
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ const scopedId = node.namedChildren.find((c) => c.type === "scoped_identifier");
+ if (scopedId) {
+ const moduleName = source.substring(scopedId.startIndex, scopedId.endIndex);
+ return { moduleName, signature: importText };
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/c-cpp.ts
+var cExtractor, cppExtractor;
+var init_c_cpp = __esm({
+ "src/extraction/languages/c-cpp.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ cExtractor = {
+ functionTypes: ["function_definition"],
+ classTypes: [],
+ methodTypes: [],
+ interfaceTypes: [],
+ structTypes: ["struct_specifier"],
+ enumTypes: ["enum_specifier"],
+ enumMemberTypes: ["enumerator"],
+ typeAliasTypes: ["type_definition"],
+ // typedef
+ importTypes: ["preproc_include"],
+ callTypes: ["call_expression"],
+ variableTypes: ["declaration"],
+ nameField: "declarator",
+ bodyField: "body",
+ paramsField: "parameters",
+ resolveTypeAliasKind: (node, _source) => {
+ for (let i = 0; i < node.namedChildCount; i++) {
+ const child = node.namedChild(i);
+ if (!child) continue;
+ if (child.type === "enum_specifier" && getChildByField(child, "body")) return "enum";
+ if (child.type === "struct_specifier" && getChildByField(child, "body")) return "struct";
+ }
+ return void 0;
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ const systemLib = node.namedChildren.find((c) => c.type === "system_lib_string");
+ if (systemLib) {
+ return { moduleName: getNodeText(systemLib, source).replace(/^<|>$/g, ""), signature: importText };
+ }
+ const stringLiteral = node.namedChildren.find((c) => c.type === "string_literal");
+ if (stringLiteral) {
+ const stringContent = stringLiteral.namedChildren.find((c) => c.type === "string_content");
+ if (stringContent) {
+ return { moduleName: getNodeText(stringContent, source), signature: importText };
+ }
+ }
+ return null;
+ }
+ };
+ cppExtractor = {
+ functionTypes: ["function_definition"],
+ classTypes: ["class_specifier"],
+ methodTypes: ["function_definition"],
+ interfaceTypes: [],
+ structTypes: ["struct_specifier"],
+ enumTypes: ["enum_specifier"],
+ enumMemberTypes: ["enumerator"],
+ typeAliasTypes: ["type_definition", "alias_declaration"],
+ // typedef and using
+ importTypes: ["preproc_include"],
+ callTypes: ["call_expression"],
+ variableTypes: ["declaration"],
+ nameField: "declarator",
+ bodyField: "body",
+ paramsField: "parameters",
+ getVisibility: (node) => {
+ const parent = node.parent;
+ if (parent) {
+ for (let i = 0; i < parent.childCount; i++) {
+ const child = parent.child(i);
+ if (child?.type === "access_specifier") {
+ const text = child.text;
+ if (text.includes("public")) return "public";
+ if (text.includes("private")) return "private";
+ if (text.includes("protected")) return "protected";
+ }
+ }
+ }
+ return void 0;
+ },
+ resolveTypeAliasKind: (node, _source) => {
+ for (let i = 0; i < node.namedChildCount; i++) {
+ const child = node.namedChild(i);
+ if (!child) continue;
+ if (child.type === "enum_specifier" && getChildByField(child, "body")) return "enum";
+ if (child.type === "struct_specifier" && getChildByField(child, "body")) return "struct";
+ }
+ return void 0;
+ },
+ isMisparsedFunction: (name) => {
+ if (name.startsWith("namespace")) return true;
+ const cppKeywords = ["switch", "if", "for", "while", "do", "case", "return"];
+ return cppKeywords.includes(name);
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ const systemLib = node.namedChildren.find((c) => c.type === "system_lib_string");
+ if (systemLib) {
+ return { moduleName: getNodeText(systemLib, source).replace(/^<|>$/g, ""), signature: importText };
+ }
+ const stringLiteral = node.namedChildren.find((c) => c.type === "string_literal");
+ if (stringLiteral) {
+ const stringContent = stringLiteral.namedChildren.find((c) => c.type === "string_content");
+ if (stringContent) {
+ return { moduleName: getNodeText(stringContent, source), signature: importText };
+ }
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/csharp.ts
+var csharpExtractor;
+var init_csharp = __esm({
+ "src/extraction/languages/csharp.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ csharpExtractor = {
+ functionTypes: [],
+ classTypes: ["class_declaration"],
+ methodTypes: ["method_declaration", "constructor_declaration"],
+ interfaceTypes: ["interface_declaration"],
+ structTypes: ["struct_declaration"],
+ enumTypes: ["enum_declaration"],
+ enumMemberTypes: ["enum_member_declaration"],
+ typeAliasTypes: [],
+ importTypes: ["using_directive"],
+ callTypes: ["invocation_expression"],
+ variableTypes: ["local_declaration_statement"],
+ fieldTypes: ["field_declaration"],
+ propertyTypes: ["property_declaration"],
+ nameField: "name",
+ bodyField: "body",
+ paramsField: "parameter_list",
+ getVisibility: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "modifier") {
+ const text = child.text;
+ if (text === "public") return "public";
+ if (text === "private") return "private";
+ if (text === "protected") return "protected";
+ if (text === "internal") return "internal";
+ }
+ }
+ return "private";
+ },
+ isStatic: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "modifier" && child.text === "static") {
+ return true;
+ }
+ }
+ return false;
+ },
+ isAsync: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "modifier" && child.text === "async") {
+ return true;
+ }
+ }
+ return false;
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ const qualifiedName = node.namedChildren.find((c) => c.type === "qualified_name");
+ if (qualifiedName) {
+ return { moduleName: getNodeText(qualifiedName, source), signature: importText };
+ }
+ const identifier = node.namedChildren.find((c) => c.type === "identifier");
+ if (identifier) {
+ return { moduleName: getNodeText(identifier, source), signature: importText };
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/php.ts
+var phpExtractor;
+var init_php = __esm({
+ "src/extraction/languages/php.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ phpExtractor = {
+ functionTypes: ["function_definition"],
+ classTypes: ["class_declaration", "trait_declaration"],
+ methodTypes: ["method_declaration"],
+ interfaceTypes: ["interface_declaration"],
+ structTypes: [],
+ enumTypes: ["enum_declaration"],
+ enumMemberTypes: ["enum_case"],
+ typeAliasTypes: [],
+ importTypes: ["namespace_use_declaration"],
+ callTypes: ["function_call_expression", "member_call_expression", "scoped_call_expression"],
+ variableTypes: ["const_declaration"],
+ fieldTypes: ["property_declaration"],
+ nameField: "name",
+ bodyField: "body",
+ paramsField: "parameters",
+ returnField: "return_type",
+ classifyClassNode: (node) => {
+ return node.type === "trait_declaration" ? "trait" : "class";
+ },
+ getVisibility: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "visibility_modifier") {
+ const text = child.text;
+ if (text === "public") return "public";
+ if (text === "private") return "private";
+ if (text === "protected") return "protected";
+ }
+ }
+ return "public";
+ },
+ isStatic: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "static_modifier") return true;
+ }
+ return false;
+ },
+ visitNode: (node, ctx) => {
+ if (node.type === "const_declaration") {
+ const constElements = node.namedChildren.filter((c) => c.type === "const_element");
+ for (const elem of constElements) {
+ const nameNode = elem.namedChildren.find((c) => c.type === "name");
+ if (!nameNode) continue;
+ const name = getNodeText(nameNode, ctx.source);
+ ctx.createNode("constant", name, elem, {});
+ }
+ return true;
+ }
+ if (node.type === "use_declaration") {
+ const names = node.namedChildren.filter((c) => c.type === "name" || c.type === "qualified_name");
+ const parentId = ctx.nodeStack.length > 0 ? ctx.nodeStack[ctx.nodeStack.length - 1] : void 0;
+ if (parentId) {
+ for (const nameNode of names) {
+ const traitName = getNodeText(nameNode, ctx.source);
+ ctx.addUnresolvedReference({
+ fromNodeId: parentId,
+ referenceName: traitName,
+ referenceKind: "implements",
+ filePath: ctx.filePath,
+ line: node.startPosition.row + 1,
+ column: node.startPosition.column
+ });
+ }
+ }
+ return true;
+ }
+ return false;
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ const namespacePrefix = node.namedChildren.find((c) => c.type === "namespace_name");
+ const useGroup = node.namedChildren.find((c) => c.type === "namespace_use_group");
+ if (namespacePrefix && useGroup) {
+ return null;
+ }
+ const useClause = node.namedChildren.find((c) => c.type === "namespace_use_clause");
+ if (useClause) {
+ const qualifiedName = useClause.namedChildren.find((c) => c.type === "qualified_name");
+ if (qualifiedName) {
+ return { moduleName: getNodeText(qualifiedName, source), signature: importText };
+ }
+ const name = useClause.namedChildren.find((c) => c.type === "name");
+ if (name) {
+ return { moduleName: getNodeText(name, source), signature: importText };
+ }
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/ruby.ts
+var rubyExtractor;
+var init_ruby = __esm({
+ "src/extraction/languages/ruby.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ rubyExtractor = {
+ functionTypes: ["method"],
+ classTypes: ["class"],
+ methodTypes: ["method", "singleton_method"],
+ interfaceTypes: [],
+ // Ruby uses modules (handled via visitNode hook)
+ structTypes: [],
+ enumTypes: [],
+ typeAliasTypes: [],
+ importTypes: ["call"],
+ // require/require_relative
+ callTypes: ["call", "method_call"],
+ variableTypes: ["assignment"],
+ // Ruby uses assignment like Python
+ nameField: "name",
+ bodyField: "body",
+ paramsField: "parameters",
+ visitNode: (node, ctx) => {
+ if (node.type !== "module") return false;
+ const nameNode = node.childForFieldName("name");
+ if (!nameNode) return false;
+ const name = nameNode.text;
+ const moduleNode = ctx.createNode("module", name, node);
+ if (!moduleNode) return false;
+ ctx.pushScope(moduleNode.id);
+ const body = node.childForFieldName("body");
+ if (body) {
+ for (let i = 0; i < body.namedChildCount; i++) {
+ const child = body.namedChild(i);
+ if (child) ctx.visitNode(child);
+ }
+ }
+ ctx.popScope();
+ return true;
+ },
+ extractBareCall: (node, _source) => {
+ if (node.type !== "identifier") return void 0;
+ const parent = node.parent;
+ if (!parent) return void 0;
+ const BLOCK_PARENTS = /* @__PURE__ */ new Set([
+ "body_statement",
+ "then",
+ "else",
+ "do",
+ "begin",
+ "rescue",
+ "ensure",
+ "when"
+ ]);
+ if (!BLOCK_PARENTS.has(parent.type)) return void 0;
+ const name = node.text;
+ const SKIP = /* @__PURE__ */ new Set([
+ "true",
+ "false",
+ "nil",
+ "self",
+ "super",
+ "__FILE__",
+ "__LINE__",
+ "__dir__"
+ ]);
+ if (SKIP.has(name)) return void 0;
+ if (name.length > 0 && name.charCodeAt(0) >= 65 && name.charCodeAt(0) <= 90) return void 0;
+ return name;
+ },
+ getVisibility: (node) => {
+ let sibling = node.previousNamedSibling;
+ while (sibling) {
+ if (sibling.type === "call") {
+ const methodName = getChildByField(sibling, "method");
+ if (methodName) {
+ const text = methodName.text;
+ if (text === "private") return "private";
+ if (text === "protected") return "protected";
+ if (text === "public") return "public";
+ }
+ }
+ sibling = sibling.previousNamedSibling;
+ }
+ return "public";
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ const identifier = node.namedChildren.find((c) => c.type === "identifier");
+ if (!identifier) return null;
+ const methodName = getNodeText(identifier, source);
+ if (methodName !== "require" && methodName !== "require_relative") {
+ return null;
+ }
+ const argList = node.namedChildren.find((c) => c.type === "argument_list");
+ if (argList) {
+ const stringNode = argList.namedChildren.find((c) => c.type === "string");
+ if (stringNode) {
+ const stringContent = stringNode.namedChildren.find((c) => c.type === "string_content");
+ if (stringContent) {
+ return { moduleName: getNodeText(stringContent, source), signature: importText };
+ }
+ }
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/swift.ts
+var swiftExtractor;
+var init_swift = __esm({
+ "src/extraction/languages/swift.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ swiftExtractor = {
+ functionTypes: ["function_declaration"],
+ classTypes: ["class_declaration"],
+ methodTypes: ["function_declaration"],
+ // Methods are functions inside classes
+ interfaceTypes: ["protocol_declaration"],
+ structTypes: ["struct_declaration"],
+ enumTypes: ["enum_declaration"],
+ enumMemberTypes: ["enum_entry"],
+ typeAliasTypes: ["typealias_declaration"],
+ importTypes: ["import_declaration"],
+ callTypes: ["call_expression"],
+ variableTypes: ["property_declaration", "constant_declaration"],
+ nameField: "name",
+ bodyField: "body",
+ paramsField: "parameter",
+ returnField: "return_type",
+ getSignature: (node, source) => {
+ const params = getChildByField(node, "parameter");
+ const returnType = getChildByField(node, "return_type");
+ if (!params) return void 0;
+ let sig = getNodeText(params, source);
+ if (returnType) {
+ sig += " -> " + getNodeText(returnType, source);
+ }
+ return sig;
+ },
+ getVisibility: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "modifiers") {
+ const text = child.text;
+ if (text.includes("public")) return "public";
+ if (text.includes("private")) return "private";
+ if (text.includes("internal")) return "internal";
+ if (text.includes("fileprivate")) return "private";
+ }
+ }
+ return "internal";
+ },
+ isStatic: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "modifiers") {
+ if (child.text.includes("static") || child.text.includes("class")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ classifyClassNode: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "struct") return "struct";
+ if (child?.type === "enum") return "enum";
+ }
+ return "class";
+ },
+ isAsync: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "modifiers" && child.text.includes("async")) {
+ return true;
+ }
+ }
+ return false;
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ const identifier = node.namedChildren.find((c) => c.type === "identifier");
+ if (identifier) {
+ return { moduleName: source.substring(identifier.startIndex, identifier.endIndex), signature: importText };
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/kotlin.ts
+function isFunInterfaceNode(node) {
+ let hasFun = false;
+ let hasInterfaceType = false;
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (!child) continue;
+ if (child.type === "fun" && !child.isNamed) hasFun = true;
+ if (child.type === "user_type") {
+ const typeId = child.namedChildren.find((c) => c.type === "type_identifier");
+ if (typeId && typeId.text === "interface") hasInterfaceType = true;
+ }
+ if (child.type === "ERROR") {
+ for (let j = 0; j < child.childCount; j++) {
+ const gc = child.child(j);
+ if (gc && gc.type === "user_type") {
+ const typeId = gc.namedChildren.find((c) => c.type === "type_identifier");
+ if (typeId && typeId.text === "interface") hasInterfaceType = true;
+ }
+ }
+ }
+ }
+ return hasFun && hasInterfaceType;
+}
+var kotlinExtractor;
+var init_kotlin = __esm({
+ "src/extraction/languages/kotlin.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ kotlinExtractor = {
+ functionTypes: ["function_declaration"],
+ classTypes: ["class_declaration"],
+ methodTypes: ["function_declaration"],
+ // Methods are functions inside classes
+ interfaceTypes: [],
+ // Handled via classifyClassNode
+ structTypes: [],
+ // Kotlin uses data classes
+ enumTypes: [],
+ // Handled via classifyClassNode
+ enumMemberTypes: ["enum_entry"],
+ typeAliasTypes: ["type_alias"],
+ importTypes: ["import_header"],
+ callTypes: ["call_expression"],
+ variableTypes: ["property_declaration"],
+ fieldTypes: ["property_declaration"],
+ extraClassNodeTypes: ["object_declaration"],
+ nameField: "simple_identifier",
+ bodyField: "function_body",
+ visitNode: (node, ctx) => {
+ if (node.type === "lambda_literal") {
+ const prev = node.previousSibling;
+ if (prev && prev.type === "ERROR" && isFunInterfaceNode(prev)) return true;
+ return false;
+ }
+ if (node.type !== "ERROR" && node.type !== "function_declaration") return false;
+ if (node.type === "ERROR") {
+ const firstChild = node.child(0);
+ if (firstChild && firstChild.type === "{") return false;
+ }
+ if (!isFunInterfaceNode(node)) return false;
+ let nameText = null;
+ if (node.type === "function_declaration") {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child && child.type === "ERROR") {
+ for (let j = 0; j < child.childCount; j++) {
+ const gc = child.child(j);
+ if (gc && gc.type === "simple_identifier") {
+ nameText = gc.text;
+ break;
+ }
+ }
+ if (nameText) break;
+ }
+ }
+ }
+ if (!nameText) {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child && child.type === "simple_identifier") {
+ nameText = child.text;
+ break;
+ }
+ }
+ }
+ if (!nameText) return false;
+ const ifaceNode = ctx.createNode("interface", nameText, node);
+ if (!ifaceNode) return false;
+ ctx.pushScope(ifaceNode.id);
+ if (node.type === "ERROR") {
+ const nextSibling = node.nextSibling;
+ if (nextSibling && nextSibling.type === "lambda_literal") {
+ for (let i = 0; i < nextSibling.namedChildCount; i++) {
+ const child = nextSibling.namedChild(i);
+ if (child && child.type === "statements") {
+ for (let j = 0; j < child.namedChildCount; j++) {
+ const stmt = child.namedChild(j);
+ if (stmt) ctx.visitNode(stmt);
+ }
+ }
+ }
+ }
+ }
+ ctx.popScope();
+ return true;
+ },
+ paramsField: "function_value_parameters",
+ returnField: "type",
+ resolveBody: (node, _bodyField) => {
+ for (let i = 0; i < node.namedChildCount; i++) {
+ const child = node.namedChild(i);
+ if (child && child.type === "ERROR") {
+ const firstChild = child.child(0);
+ if (firstChild && firstChild.type === "{") {
+ return child;
+ }
+ }
+ if (child && (child.type === "function_body" || child.type === "class_body" || child.type === "enum_class_body")) {
+ return child;
+ }
+ }
+ return null;
+ },
+ classifyClassNode: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (!child) continue;
+ if (child.type === "interface") return "interface";
+ if (child.type === "enum") return "enum";
+ }
+ return "class";
+ },
+ getReceiverType: (node, source) => {
+ let foundUserType = null;
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (!child) continue;
+ if (child.type === "user_type") {
+ foundUserType = child;
+ } else if (child.type === "." && foundUserType) {
+ const typeId = foundUserType.namedChildren.find((c) => c.type === "type_identifier");
+ return typeId ? getNodeText(typeId, source) : getNodeText(foundUserType, source);
+ } else if (child.type === "simple_identifier" || child.type === "function_value_parameters") {
+ break;
+ }
+ }
+ return void 0;
+ },
+ getSignature: (node, source) => {
+ const params = getChildByField(node, "function_value_parameters");
+ const returnType = getChildByField(node, "type");
+ if (!params) return void 0;
+ let sig = getNodeText(params, source);
+ if (returnType) {
+ sig += ": " + getNodeText(returnType, source);
+ }
+ return sig;
+ },
+ getVisibility: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "modifiers") {
+ const text = child.text;
+ if (text.includes("public")) return "public";
+ if (text.includes("private")) return "private";
+ if (text.includes("protected")) return "protected";
+ if (text.includes("internal")) return "internal";
+ }
+ }
+ return "public";
+ },
+ isStatic: (_node) => {
+ return false;
+ },
+ isAsync: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "modifiers" && child.text.includes("suspend")) {
+ return true;
+ }
+ }
+ return false;
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ const identifier = node.namedChildren.find((c) => c.type === "identifier");
+ if (identifier) {
+ return { moduleName: source.substring(identifier.startIndex, identifier.endIndex), signature: importText };
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/dart.ts
+var dartExtractor;
+var init_dart = __esm({
+ "src/extraction/languages/dart.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ dartExtractor = {
+ functionTypes: ["function_signature"],
+ classTypes: ["class_definition"],
+ methodTypes: ["method_signature"],
+ interfaceTypes: [],
+ structTypes: [],
+ enumTypes: ["enum_declaration"],
+ enumMemberTypes: ["enum_constant"],
+ typeAliasTypes: ["type_alias"],
+ importTypes: ["import_or_export"],
+ callTypes: [],
+ // Dart calls use identifier+selector, handled via extractBareCall
+ variableTypes: [],
+ extraClassNodeTypes: ["mixin_declaration", "extension_declaration"],
+ resolveBody: (node, bodyField) => {
+ if (node.type === "function_signature" || node.type === "method_signature") {
+ const next = node.nextNamedSibling;
+ if (next?.type === "function_body") return next;
+ return null;
+ }
+ const standard = node.childForFieldName(bodyField);
+ if (standard) return standard;
+ return node.namedChildren.find(
+ (c) => c.type === "class_body" || c.type === "extension_body"
+ ) || null;
+ },
+ nameField: "name",
+ bodyField: "body",
+ // class_definition uses 'body' field
+ paramsField: "formal_parameter_list",
+ returnField: "type",
+ getSignature: (node, source) => {
+ let sig = node;
+ if (node.type === "method_signature") {
+ const inner = node.namedChildren.find(
+ (c) => c.type === "function_signature" || c.type === "getter_signature" || c.type === "setter_signature"
+ );
+ if (inner) sig = inner;
+ }
+ const params = sig.namedChildren.find((c) => c.type === "formal_parameter_list");
+ const retType = sig.namedChildren.find(
+ (c) => c.type === "type_identifier" || c.type === "void_type"
+ );
+ if (!params && !retType) return void 0;
+ let result = "";
+ if (retType) result += getNodeText(retType, source) + " ";
+ if (params) result += getNodeText(params, source);
+ return result.trim() || void 0;
+ },
+ getVisibility: (node) => {
+ let nameNode = null;
+ if (node.type === "method_signature") {
+ const inner = node.namedChildren.find(
+ (c) => c.type === "function_signature" || c.type === "getter_signature" || c.type === "setter_signature"
+ );
+ if (inner) nameNode = inner.namedChildren.find((c) => c.type === "identifier") || null;
+ } else {
+ nameNode = node.childForFieldName("name");
+ }
+ if (nameNode && nameNode.text.startsWith("_")) return "private";
+ return "public";
+ },
+ isAsync: (node) => {
+ const nextSibling = node.nextNamedSibling;
+ if (nextSibling?.type === "function_body") {
+ for (let i = 0; i < nextSibling.childCount; i++) {
+ const child = nextSibling.child(i);
+ if (child?.type === "async") return true;
+ }
+ }
+ return false;
+ },
+ isStatic: (node) => {
+ if (node.type === "method_signature") {
+ for (let i = 0; i < node.childCount; i++) {
+ const child = node.child(i);
+ if (child?.type === "static") return true;
+ }
+ }
+ return false;
+ },
+ extractImport: (node, source) => {
+ const importText = source.substring(node.startIndex, node.endIndex).trim();
+ let moduleName = "";
+ const libraryImport = node.namedChildren.find((c) => c.type === "library_import");
+ if (libraryImport) {
+ const importSpec = libraryImport.namedChildren.find((c) => c.type === "import_specification");
+ if (importSpec) {
+ const configurableUri = importSpec.namedChildren.find((c) => c.type === "configurable_uri");
+ if (configurableUri) {
+ const uri = configurableUri.namedChildren.find((c) => c.type === "uri");
+ if (uri) {
+ const stringLiteral = uri.namedChildren.find((c) => c.type === "string_literal");
+ if (stringLiteral) {
+ moduleName = getNodeText(stringLiteral, source).replace(/['"]/g, "");
+ }
+ }
+ }
+ }
+ }
+ if (!moduleName) {
+ const libraryExport = node.namedChildren.find((c) => c.type === "library_export");
+ if (libraryExport) {
+ const configurableUri = libraryExport.namedChildren.find((c) => c.type === "configurable_uri");
+ if (configurableUri) {
+ const uri = configurableUri.namedChildren.find((c) => c.type === "uri");
+ if (uri) {
+ const stringLiteral = uri.namedChildren.find((c) => c.type === "string_literal");
+ if (stringLiteral) {
+ moduleName = getNodeText(stringLiteral, source).replace(/['"]/g, "");
+ }
+ }
+ }
+ }
+ }
+ if (moduleName) {
+ return { moduleName, signature: importText };
+ }
+ return null;
+ },
+ extractBareCall: (node, _source) => {
+ if (node.type === "selector") {
+ const hasArgPart = node.namedChildren.some((c) => c.type === "argument_part");
+ if (!hasArgPart) return void 0;
+ const prev = node.previousNamedSibling;
+ if (!prev) return void 0;
+ if (prev.type === "identifier") {
+ return prev.text;
+ }
+ if (prev.type === "selector") {
+ const accessor = prev.namedChildren.find(
+ (c) => c.type === "unconditional_assignable_selector" || c.type === "conditional_assignable_selector"
+ );
+ if (accessor) {
+ const methodId = accessor.namedChildren.find((c) => c.type === "identifier");
+ if (methodId) {
+ const accessorPrev = prev.previousNamedSibling;
+ if (accessorPrev?.type === "identifier") {
+ return accessorPrev.text + "." + methodId.text;
+ }
+ return methodId.text;
+ }
+ }
+ }
+ if (prev.type === "unconditional_assignable_selector" || prev.type === "conditional_assignable_selector") {
+ const methodId = prev.namedChildren.find((c) => c.type === "identifier");
+ if (methodId) return methodId.text;
+ }
+ return void 0;
+ }
+ if (node.type === "new_expression") {
+ const typeId = node.namedChildren.find((c) => c.type === "type_identifier");
+ if (typeId) return typeId.text;
+ return void 0;
+ }
+ if (node.type === "const_object_expression") {
+ const typeId = node.namedChildren.find((c) => c.type === "type_identifier");
+ const nameId = node.namedChildren.find((c) => c.type === "identifier");
+ if (typeId && nameId) return typeId.text + "." + nameId.text;
+ if (typeId) return typeId.text;
+ return void 0;
+ }
+ return void 0;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/pascal.ts
+var pascalExtractor;
+var init_pascal = __esm({
+ "src/extraction/languages/pascal.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ pascalExtractor = {
+ functionTypes: ["declProc"],
+ classTypes: ["declClass"],
+ methodTypes: ["declProc"],
+ interfaceTypes: ["declIntf"],
+ structTypes: [],
+ enumTypes: ["declEnum"],
+ typeAliasTypes: ["declType"],
+ importTypes: ["declUses"],
+ callTypes: ["exprCall"],
+ variableTypes: ["declField", "declConst"],
+ nameField: "name",
+ bodyField: "body",
+ paramsField: "args",
+ returnField: "type",
+ getSignature: (node, source) => {
+ const args = getChildByField(node, "args");
+ const returnType = node.namedChildren.find(
+ (c) => c.type === "typeref"
+ );
+ if (!args && !returnType) return void 0;
+ let sig = "";
+ if (args) sig = getNodeText(args, source);
+ if (returnType) {
+ sig += ": " + getNodeText(returnType, source);
+ }
+ return sig || void 0;
+ },
+ getVisibility: (node) => {
+ let current = node.parent;
+ while (current) {
+ if (current.type === "declSection") {
+ for (let i = 0; i < current.childCount; i++) {
+ const child = current.child(i);
+ if (child?.type === "kPublic" || child?.type === "kPublished")
+ return "public";
+ if (child?.type === "kPrivate") return "private";
+ if (child?.type === "kProtected") return "protected";
+ }
+ }
+ current = current.parent;
+ }
+ return void 0;
+ },
+ isExported: (_node, _source) => {
+ return false;
+ },
+ isStatic: (node) => {
+ for (let i = 0; i < node.childCount; i++) {
+ if (node.child(i)?.type === "kClass") return true;
+ }
+ return false;
+ },
+ isConst: (node) => {
+ return node.type === "declConst";
+ }
+ };
+ }
+});
+
+// src/extraction/languages/scala.ts
+function getValVarName(node, source) {
+ const patternNode = node.childForFieldName("pattern");
+ if (!patternNode) return null;
+ if (patternNode.type === "identifier") return getNodeText(patternNode, source);
+ const identChild = patternNode.namedChildren.find((c) => c.type === "identifier");
+ return identChild ? getNodeText(identChild, source) : null;
+}
+function extractVisibility(node) {
+ for (let i = 0; i < node.namedChildCount; i++) {
+ const child = node.namedChild(i);
+ if (!child) continue;
+ if (child.type === "modifiers" || child.type === "access_modifier") {
+ const text = child.text;
+ if (text.includes("private")) return "private";
+ if (text.includes("protected")) return "protected";
+ }
+ }
+ return "public";
+}
+var scalaExtractor;
+var init_scala = __esm({
+ "src/extraction/languages/scala.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ scalaExtractor = {
+ // top-level function_definition is handled via methodTypes (same pattern as Kotlin)
+ functionTypes: [],
+ classTypes: ["class_definition", "object_definition", "trait_definition"],
+ methodTypes: ["function_definition", "function_declaration"],
+ interfaceTypes: [],
+ structTypes: [],
+ enumTypes: ["enum_definition"],
+ enumMemberTypes: [],
+ // handled in visitNode — enum_case_definitions wraps the cases
+ typeAliasTypes: ["type_definition"],
+ importTypes: ["import_declaration"],
+ callTypes: ["call_expression"],
+ variableTypes: [],
+ // val/var handled in visitNode (use `pattern` field, not `name`)
+ fieldTypes: [],
+ extraClassNodeTypes: [],
+ nameField: "name",
+ bodyField: "body",
+ paramsField: "parameters",
+ returnField: "return_type",
+ interfaceKind: "trait",
+ classifyClassNode: (node) => {
+ if (node.type === "trait_definition") return "trait";
+ return "class";
+ },
+ getSignature: (node, source) => {
+ const params = node.childForFieldName("parameters");
+ const returnType = node.childForFieldName("return_type");
+ if (!params && !returnType) return void 0;
+ let sig = params ? getNodeText(params, source) : "";
+ if (returnType) sig += ": " + getNodeText(returnType, source);
+ return sig || void 0;
+ },
+ getVisibility: (node) => extractVisibility(node),
+ isAsync: () => false,
+ isStatic: (node) => {
+ for (let i = 0; i < node.namedChildCount; i++) {
+ const child = node.namedChild(i);
+ if (child?.type === "modifiers" && child.text.includes("static")) return true;
+ }
+ return false;
+ },
+ visitNode: (node, ctx) => {
+ const t = node.type;
+ if (t === "val_definition" || t === "var_definition") {
+ const name = getValVarName(node, ctx.source);
+ if (!name) return false;
+ const isInClass = ctx.nodeStack.length > 0 && (() => {
+ const parentId = ctx.nodeStack[ctx.nodeStack.length - 1];
+ const parentNode = ctx.nodes.find((n) => n.id === parentId);
+ return parentNode != null && (parentNode.kind === "class" || parentNode.kind === "trait" || parentNode.kind === "interface" || parentNode.kind === "struct" || parentNode.kind === "enum" || parentNode.kind === "module");
+ })();
+ const kind = isInClass ? "field" : t === "val_definition" ? "constant" : "variable";
+ const typeNode = node.childForFieldName("type");
+ const sig = typeNode ? `${t === "val_definition" ? "val" : "var"} ${name}: ${getNodeText(typeNode, ctx.source)}` : void 0;
+ ctx.createNode(kind, name, node, { signature: sig, visibility: extractVisibility(node) });
+ return true;
+ }
+ if (t === "enum_case_definitions") {
+ for (let i = 0; i < node.namedChildCount; i++) {
+ const child = node.namedChild(i);
+ if (!child) continue;
+ if (child.type === "simple_enum_case" || child.type === "full_enum_case") {
+ const nameNode = child.childForFieldName("name");
+ if (nameNode) ctx.createNode("enum_member", getNodeText(nameNode, ctx.source), child);
+ }
+ }
+ return true;
+ }
+ if (t === "extension_definition") {
+ const body = node.childForFieldName("body");
+ if (body) {
+ for (let i = 0; i < body.namedChildCount; i++) {
+ const child = body.namedChild(i);
+ if (child) ctx.visitNode(child);
+ }
+ }
+ return true;
+ }
+ return false;
+ },
+ extractImport: (node, source) => {
+ const importText = getNodeText(node, source).trim();
+ const pathNode = node.childForFieldName("path");
+ if (pathNode) return { moduleName: getNodeText(pathNode, source), signature: importText };
+ for (let i = 0; i < node.namedChildCount; i++) {
+ const child = node.namedChild(i);
+ if (child?.type === "identifier" || child?.type === "stable_identifier") {
+ return { moduleName: getNodeText(child, source), signature: importText };
+ }
+ }
+ return null;
+ }
+ };
+ }
+});
+
+// src/extraction/languages/index.ts
+var EXTRACTORS;
+var init_languages = __esm({
+ "src/extraction/languages/index.ts"() {
+ "use strict";
+ init_typescript();
+ init_javascript();
+ init_python();
+ init_go();
+ init_rust();
+ init_java();
+ init_c_cpp();
+ init_csharp();
+ init_php();
+ init_ruby();
+ init_swift();
+ init_kotlin();
+ init_dart();
+ init_pascal();
+ init_scala();
+ EXTRACTORS = {
+ typescript: typescriptExtractor,
+ tsx: typescriptExtractor,
+ javascript: javascriptExtractor,
+ jsx: javascriptExtractor,
+ python: pythonExtractor,
+ go: goExtractor,
+ rust: rustExtractor,
+ java: javaExtractor,
+ c: cExtractor,
+ cpp: cppExtractor,
+ csharp: csharpExtractor,
+ php: phpExtractor,
+ ruby: rubyExtractor,
+ swift: swiftExtractor,
+ kotlin: kotlinExtractor,
+ dart: dartExtractor,
+ pascal: pascalExtractor,
+ scala: scalaExtractor
+ };
+ }
+});
+
+// src/extraction/liquid-extractor.ts
+var LiquidExtractor;
+var init_liquid_extractor = __esm({
+ "src/extraction/liquid-extractor.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ LiquidExtractor = class {
+ filePath;
+ source;
+ nodes = [];
+ edges = [];
+ unresolvedReferences = [];
+ errors = [];
+ constructor(filePath, source) {
+ this.filePath = filePath;
+ this.source = source;
+ }
+ /**
+ * Extract from Liquid source
+ */
+ extract() {
+ const startTime = Date.now();
+ try {
+ const fileNode = this.createFileNode();
+ this.extractSnippetReferences(fileNode.id);
+ this.extractSectionReferences(fileNode.id);
+ this.extractSchema(fileNode.id);
+ this.extractAssignments(fileNode.id);
+ } catch (error) {
+ this.errors.push({
+ message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`,
+ severity: "error",
+ code: "parse_error"
+ });
+ }
+ return {
+ nodes: this.nodes,
+ edges: this.edges,
+ unresolvedReferences: this.unresolvedReferences,
+ errors: this.errors,
+ durationMs: Date.now() - startTime
+ };
+ }
+ /**
+ * Create a file node for the Liquid template
+ */
+ createFileNode() {
+ const lines = this.source.split("\n");
+ const id = generateNodeId(this.filePath, "file", this.filePath, 1);
+ const fileNode = {
+ id,
+ kind: "file",
+ name: this.filePath.split("/").pop() || this.filePath,
+ qualifiedName: this.filePath,
+ filePath: this.filePath,
+ language: "liquid",
+ startLine: 1,
+ endLine: lines.length,
+ startColumn: 0,
+ endColumn: lines[lines.length - 1]?.length || 0,
+ updatedAt: Date.now()
+ };
+ this.nodes.push(fileNode);
+ return fileNode;
+ }
+ /**
+ * Extract {% render 'snippet' %} and {% include 'snippet' %} references
+ */
+ extractSnippetReferences(fileNodeId) {
+ const renderRegex = /\{%[-]?\s*(render|include)\s+['"]([^'"]+)['"]/g;
+ let match;
+ while ((match = renderRegex.exec(this.source)) !== null) {
+ const [fullMatch, tagType, snippetName] = match;
+ const line = this.getLineNumber(match.index);
+ const importNodeId = generateNodeId(this.filePath, "import", snippetName, line);
+ const importNode = {
+ id: importNodeId,
+ kind: "import",
+ name: snippetName,
+ qualifiedName: `${this.filePath}::import:${snippetName}`,
+ filePath: this.filePath,
+ language: "liquid",
+ signature: fullMatch,
+ startLine: line,
+ endLine: line,
+ startColumn: match.index - this.getLineStart(line),
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
+ updatedAt: Date.now()
+ };
+ this.nodes.push(importNode);
+ this.edges.push({
+ source: fileNodeId,
+ target: importNodeId,
+ kind: "contains"
+ });
+ const nodeId = generateNodeId(this.filePath, "component", `${tagType}:${snippetName}`, line);
+ const node = {
+ id: nodeId,
+ kind: "component",
+ name: snippetName,
+ qualifiedName: `${this.filePath}::${tagType}:${snippetName}`,
+ filePath: this.filePath,
+ language: "liquid",
+ startLine: line,
+ endLine: line,
+ startColumn: match.index - this.getLineStart(line),
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
+ updatedAt: Date.now()
+ };
+ this.nodes.push(node);
+ this.edges.push({
+ source: fileNodeId,
+ target: nodeId,
+ kind: "contains"
+ });
+ this.unresolvedReferences.push({
+ fromNodeId: fileNodeId,
+ referenceName: `snippets/${snippetName}.liquid`,
+ referenceKind: "references",
+ line,
+ column: match.index - this.getLineStart(line)
+ });
+ }
+ }
+ /**
+ * Extract {% section 'name' %} references
+ */
+ extractSectionReferences(fileNodeId) {
+ const sectionRegex = /\{%[-]?\s*section\s+['"]([^'"]+)['"]/g;
+ let match;
+ while ((match = sectionRegex.exec(this.source)) !== null) {
+ const [fullMatch, sectionName] = match;
+ const line = this.getLineNumber(match.index);
+ const importNodeId = generateNodeId(this.filePath, "import", sectionName, line);
+ const importNode = {
+ id: importNodeId,
+ kind: "import",
+ name: sectionName,
+ qualifiedName: `${this.filePath}::import:${sectionName}`,
+ filePath: this.filePath,
+ language: "liquid",
+ signature: fullMatch,
+ startLine: line,
+ endLine: line,
+ startColumn: match.index - this.getLineStart(line),
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
+ updatedAt: Date.now()
+ };
+ this.nodes.push(importNode);
+ this.edges.push({
+ source: fileNodeId,
+ target: importNodeId,
+ kind: "contains"
+ });
+ const nodeId = generateNodeId(this.filePath, "component", `section:${sectionName}`, line);
+ const node = {
+ id: nodeId,
+ kind: "component",
+ name: sectionName,
+ qualifiedName: `${this.filePath}::section:${sectionName}`,
+ filePath: this.filePath,
+ language: "liquid",
+ startLine: line,
+ endLine: line,
+ startColumn: match.index - this.getLineStart(line),
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
+ updatedAt: Date.now()
+ };
+ this.nodes.push(node);
+ this.edges.push({
+ source: fileNodeId,
+ target: nodeId,
+ kind: "contains"
+ });
+ this.unresolvedReferences.push({
+ fromNodeId: fileNodeId,
+ referenceName: `sections/${sectionName}.liquid`,
+ referenceKind: "references",
+ line,
+ column: match.index - this.getLineStart(line)
+ });
+ }
+ }
+ /**
+ * Extract {% schema %}...{% endschema %} blocks
+ */
+ extractSchema(fileNodeId) {
+ const schemaRegex = /\{%[-]?\s*schema\s*[-]?%\}([\s\S]*?)\{%[-]?\s*endschema\s*[-]?%\}/g;
+ let match;
+ while ((match = schemaRegex.exec(this.source)) !== null) {
+ const [fullMatch, schemaContent] = match;
+ const startLine = this.getLineNumber(match.index);
+ const endLine = this.getLineNumber(match.index + fullMatch.length);
+ let schemaName = "schema";
+ try {
+ const schemaJson = JSON.parse(schemaContent);
+ if (schemaJson.name) {
+ schemaName = typeof schemaJson.name === "string" ? schemaJson.name : schemaJson.name.en || Object.values(schemaJson.name)[0] || "schema";
+ }
+ } catch {
+ }
+ const nodeId = generateNodeId(this.filePath, "constant", `schema:${schemaName}`, startLine);
+ const node = {
+ id: nodeId,
+ kind: "constant",
+ name: schemaName,
+ qualifiedName: `${this.filePath}::schema:${schemaName}`,
+ filePath: this.filePath,
+ language: "liquid",
+ startLine,
+ endLine,
+ startColumn: match.index - this.getLineStart(startLine),
+ endColumn: 0,
+ docstring: schemaContent?.trim().substring(0, 200),
+ // Store first 200 chars as docstring
+ updatedAt: Date.now()
+ };
+ this.nodes.push(node);
+ this.edges.push({
+ source: fileNodeId,
+ target: nodeId,
+ kind: "contains"
+ });
+ }
+ }
+ /**
+ * Extract {% assign var = value %} statements
+ */
+ extractAssignments(fileNodeId) {
+ const assignRegex = /\{%[-]?\s*assign\s+(\w+)\s*=/g;
+ let match;
+ while ((match = assignRegex.exec(this.source)) !== null) {
+ const [, variableName] = match;
+ const line = this.getLineNumber(match.index);
+ const nodeId = generateNodeId(this.filePath, "variable", variableName, line);
+ const node = {
+ id: nodeId,
+ kind: "variable",
+ name: variableName,
+ qualifiedName: `${this.filePath}::${variableName}`,
+ filePath: this.filePath,
+ language: "liquid",
+ startLine: line,
+ endLine: line,
+ startColumn: match.index - this.getLineStart(line),
+ endColumn: match.index - this.getLineStart(line) + match[0].length,
+ updatedAt: Date.now()
+ };
+ this.nodes.push(node);
+ this.edges.push({
+ source: fileNodeId,
+ target: nodeId,
+ kind: "contains"
+ });
+ }
+ }
+ /**
+ * Get the line number for a character index
+ */
+ getLineNumber(index) {
+ const substring = this.source.substring(0, index);
+ return (substring.match(/\n/g) || []).length + 1;
+ }
+ /**
+ * Get the character index of the start of a line
+ */
+ getLineStart(lineNumber) {
+ const lines = this.source.split("\n");
+ let index = 0;
+ for (let i = 0; i < lineNumber - 1 && i < lines.length; i++) {
+ index += lines[i].length + 1;
+ }
+ return index;
+ }
+ };
+ }
+});
+
+// src/extraction/svelte-extractor.ts
+var SVELTE_RUNES, SvelteExtractor;
+var init_svelte_extractor = __esm({
+ "src/extraction/svelte-extractor.ts"() {
+ "use strict";
+ init_tree_sitter_helpers();
+ init_tree_sitter();
+ init_grammars();
+ SVELTE_RUNES = /* @__PURE__ */ new Set([
+ "$props",
+ "$state",
+ "$derived",
+ "$effect",
+ "$bindable",
+ "$inspect",
+ "$host",
+ "$snippet"
+ ]);
+ SvelteExtractor = class {
+ filePath;
+ source;
+ nodes = [];
+ edges = [];
+ unresolvedReferences = [];
+ errors = [];
+ constructor(filePath, source) {
+ this.filePath = filePath;
+ this.source = source;
+ }
+ /**
+ * Extract from Svelte source
+ */
+ extract() {
+ const startTime = Date.now();
+ try {
+ const componentNode = this.createComponentNode();
+ const scriptBlocks = this.extractScriptBlocks();
+ for (const block of scriptBlocks) {
+ this.processScriptBlock(block, componentNode.id);
+ }
+ this.extractTemplateCalls(componentNode.id, scriptBlocks);
+ this.extractTemplateComponents(componentNode.id);
+ this.unresolvedReferences = this.unresolvedReferences.filter(
+ (ref) => !SVELTE_RUNES.has(ref.referenceName)
+ );
+ } catch (error) {
+ this.errors.push({
+ message: `Svelte extraction error: ${error instanceof Error ? error.message : String(error)}`,
+ severity: "error",
+ code: "parse_error"
+ });
+ }
+ return {
+ nodes: this.nodes,
+ edges: this.edges,
+ unresolvedReferences: this.unresolvedReferences,
+ errors: this.errors,
+ durationMs: Date.now() - startTime
+ };
+ }
+ /**
+ * Create a component node for the .svelte file
+ */
+ createComponentNode() {
+ const lines = this.source.split("\n");
+ const fileName = this.filePath.split(/[/\\]/).pop() || this.filePath;
+ const componentName = fileName.replace(/\.svelte$/, "");
+ const id = generateNodeId(this.filePath, "component", componentName, 1);
+ const node = {
+ id,
+ kind: "component",
+ name: componentName,
+ qualifiedName: `${this.filePath}::${componentName}`,
+ filePath: this.filePath,
+ language: "svelte",
+ startLine: 1,
+ endLine: lines.length,
+ startColumn: 0,
+ endColumn: lines[lines.length - 1]?.length || 0,
+ isExported: true,
+ // Svelte components are always importable
+ updatedAt: Date.now()
+ };
+ this.nodes.push(node);
+ return node;
+ }
+ /**
+ * Extract and ranges
+ const tagRegex = /<(script|style)(\s[^>]*)?>[\s\S]*?<\/\1>/g;
+ let tagMatch;
+ while ((tagMatch = tagRegex.exec(this.source)) !== null) {
+ const startLine = (this.source.substring(0, tagMatch.index).match(/\n/g) || []).length;
+ const endLine = startLine + (tagMatch[0].match(/\n/g) || []).length;
+ coveredRanges.push([startLine, endLine]);
+ }
+ // Find template expressions: {...} outside of script/style blocks
+ // Matches curly-brace expressions, excluding Svelte block syntax ({#if}, {:else}, {/if}, {@html}, {@render})
+ const lines = this.source.split('\n');
+ const exprRegex = /\{([^}#/:@][^}]*)\}/g;
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
+ // Skip lines inside script/style blocks
+ if (coveredRanges.some(([start, end]) => lineIdx >= start && lineIdx <= end))
+ continue;
+ const line = lines[lineIdx];
+ let exprMatch;
+ while ((exprMatch = exprRegex.exec(line)) !== null) {
+ const expr = exprMatch[1];
+ // Extract function calls: identifiers followed by (
+ // Matches: cn(...), buttonVariants(...), obj.method(...)
+ const callRegex = /\b([a-zA-Z_$][\w$.]*)\s*\(/g;
+ let callMatch;
+ while ((callMatch = callRegex.exec(expr)) !== null) {
+ const calleeName = callMatch[1];
+ // Skip Svelte runes, control flow keywords, and common non-function patterns
+ if (SVELTE_RUNES.has(calleeName))
+ continue;
+ if (calleeName === 'if' || calleeName === 'else' || calleeName === 'each' || calleeName === 'await')
+ continue;
+ this.unresolvedReferences.push({
+ fromNodeId: componentNodeId,
+ referenceName: calleeName,
+ referenceKind: 'calls',
+ line: lineIdx + 1, // 1-indexed
+ column: exprMatch.index + callMatch.index,
+ filePath: this.filePath,
+ language: 'svelte',
+ });
+ }
+ }
+ }
+ }
+ /**
+ * Extract component usages from the Svelte template.
+ *
+ * PascalCase tags like , , represent
+ * component instantiations — analogous to function calls in imperative code.
+ * Capturing these creates graph edges from parent to child components and
+ * gives codegraph_explore anchor points in the template markup.
+ */
+ extractTemplateComponents(componentNodeId) {
+ // Build ranges covered by