@documentation-robotics/cli 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -14
- package/dist/analyzers/active-analyzer.js +62 -0
- package/dist/analyzers/active-analyzer.js.map +7 -0
- package/dist/analyzers/cards/build.js +21 -1
- package/dist/analyzers/cards/build.js.map +2 -2
- package/dist/analyzers/cards/extract.js +138 -0
- package/dist/analyzers/cards/extract.js.map +7 -0
- package/dist/analyzers/cards/routes.js +96 -0
- package/dist/analyzers/cards/routes.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +76 -4
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +12 -6
- package/dist/analyzers/cbm-responses.js.map +2 -2
- package/dist/analyzers/code-changes.js +159 -0
- package/dist/analyzers/code-changes.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +6 -2
- package/dist/analyzers/edge-relationships.js.map +2 -2
- package/dist/analyzers/extraction-plan.js +541 -0
- package/dist/analyzers/extraction-plan.js.map +7 -0
- package/dist/analyzers/index-job.js +229 -0
- package/dist/analyzers/index-job.js.map +7 -0
- package/dist/cli.js +12 -10
- package/dist/cli.js.map +2 -2
- package/dist/coding-agents/drbot-mcp.js +120 -18
- package/dist/coding-agents/drbot-mcp.js.map +2 -2
- package/dist/commands/add.js +4 -2
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +35 -35
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/changeset-verify.js +16 -8
- package/dist/commands/changeset-verify.js.map +2 -2
- package/dist/commands/changeset.js +41 -11
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/init.js +9 -26
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +35 -31
- package/dist/commands/mcp.js.map +3 -3
- package/dist/commands/relationship.js +2 -12
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +3 -3
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +14 -13
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +28 -120
- package/dist/commands/upgrade.js.map +2 -2
- package/dist/commands/validate.js +19 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +30 -12
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/annotation-store.js +15 -5
- package/dist/core/annotation-store.js.map +2 -2
- package/dist/core/catalog-migration.js +2 -1
- package/dist/core/catalog-migration.js.map +2 -2
- package/dist/core/changeset-base-index.js +7 -0
- package/dist/core/changeset-base-index.js.map +2 -2
- package/dist/core/changeset-brief-compact.js +256 -0
- package/dist/core/changeset-brief-compact.js.map +7 -0
- package/dist/core/changeset-verify.js +356 -26
- package/dist/core/changeset-verify.js.map +3 -3
- package/dist/core/extraction-observability.js +161 -0
- package/dist/core/extraction-observability.js.map +7 -0
- package/dist/core/migration-registry.js +13 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/project-init.js +53 -0
- package/dist/core/project-init.js.map +7 -0
- package/dist/core/relationship-staging.js +94 -1
- package/dist/core/relationship-staging.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +83 -3
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staging-area.js +85 -8
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/upgrade-plan.js +117 -0
- package/dist/core/upgrade-plan.js.map +7 -0
- package/dist/generated/relationship-index.js +124 -15
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
- package/dist/integrations/claude_code/agents/dr-architect.md +19 -5
- package/dist/integrations/claude_code/agents/dr-extractor.md +7 -1
- package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
- package/dist/integrations/claude_code/commands/dr-map.md +7 -1
- package/dist/integrations/claude_code/commands/dr-plan.md +42 -16
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +28 -31
- package/dist/integrations/claude_code/docs/README.md +20 -1
- package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
- package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +3 -0
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +90 -31
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +5 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +5 -0
- package/dist/integrations/github_copilot/agents/dr-architect.md +2 -0
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +3 -0
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +88 -30
- package/dist/mcp/changeset-only.js +14 -3
- package/dist/mcp/changeset-only.js.map +2 -2
- package/dist/mcp/project-root.js +85 -0
- package/dist/mcp/project-root.js.map +7 -0
- package/dist/mcp/server-instructions.js +27 -0
- package/dist/mcp/server-instructions.js.map +7 -0
- package/dist/mcp/strict-schema.js +52 -0
- package/dist/mcp/strict-schema.js.map +7 -0
- package/dist/mcp/tool-registry.js +39 -5
- package/dist/mcp/tool-registry.js.map +2 -2
- package/dist/mcp/tools/changeset-analysis-tools.js +25 -5
- package/dist/mcp/tools/changeset-analysis-tools.js.map +3 -3
- package/dist/mcp/tools/changeset-commit.js +224 -0
- package/dist/mcp/tools/changeset-commit.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +133 -57
- package/dist/mcp/tools/changeset-editing.js.map +3 -3
- package/dist/mcp/tools/changeset-lifecycle.js +1 -1
- package/dist/mcp/tools/changeset-lifecycle.js.map +2 -2
- package/dist/mcp/tools/changeset-verify.js +12 -6
- package/dist/mcp/tools/changeset-verify.js.map +2 -2
- package/dist/mcp/tools/codebase-extract.js +836 -0
- package/dist/mcp/tools/codebase-extract.js.map +7 -0
- package/dist/mcp/tools/codebase-index.js +125 -0
- package/dist/mcp/tools/codebase-index.js.map +7 -0
- package/dist/mcp/tools/codebase-shared.js +265 -0
- package/dist/mcp/tools/codebase-shared.js.map +7 -0
- package/dist/mcp/tools/codebase-verify.js +48 -0
- package/dist/mcp/tools/codebase-verify.js.map +7 -0
- package/dist/mcp/tools/index.js +15 -0
- package/dist/mcp/tools/index.js.map +2 -2
- package/dist/mcp/tools/model-add.js +189 -73
- package/dist/mcp/tools/model-add.js.map +2 -2
- package/dist/mcp/tools/model-update.js +16 -12
- package/dist/mcp/tools/model-update.js.map +2 -2
- package/dist/mcp/tools/model-validate.js +46 -4
- package/dist/mcp/tools/model-validate.js.map +2 -2
- package/dist/mcp/tools/project-status-collectors.js +381 -0
- package/dist/mcp/tools/project-status-collectors.js.map +7 -0
- package/dist/mcp/tools/project-status.js +237 -0
- package/dist/mcp/tools/project-status.js.map +7 -0
- package/dist/mcp/tools/project-tools.js +505 -0
- package/dist/mcp/tools/project-tools.js.map +7 -0
- package/dist/mcp/tools/relationship-tools.js +89 -20
- package/dist/mcp/tools/relationship-tools.js.map +2 -2
- package/dist/mcp/tools/review-open.js +210 -0
- package/dist/mcp/tools/review-open.js.map +7 -0
- package/dist/mcp/tools/shared.js +10 -2
- package/dist/mcp/tools/shared.js.map +2 -2
- package/dist/reports/model-report-orchestrator.js +31 -2
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/schemas/bundled/analyzers/cbm.json +22 -10
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1 -1
- package/dist/schemas/bundled/apm.json +1 -1
- package/dist/schemas/bundled/application.json +1 -1
- package/dist/schemas/bundled/base.json +1 -1
- package/dist/schemas/bundled/business.json +1 -1
- package/dist/schemas/bundled/data-model.json +1 -1
- package/dist/schemas/bundled/data-store.json +1 -1
- package/dist/schemas/bundled/manifest.json +3 -3
- package/dist/schemas/bundled/motivation.json +1 -1
- package/dist/schemas/bundled/navigation.json +1 -1
- package/dist/schemas/bundled/product.json +1 -1
- package/dist/schemas/bundled/security.json +1 -1
- package/dist/schemas/bundled/technology.json +1 -1
- package/dist/schemas/bundled/testing.json +145 -1
- package/dist/schemas/bundled/ux.json +1 -1
- package/dist/server/server.js +508 -237
- package/dist/server/server.js.map +3 -3
- package/dist/server/viewer-registry.js +264 -0
- package/dist/server/viewer-registry.js.map +7 -0
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/id-generator.js +5 -2
- package/dist/utils/id-generator.js.map +2 -2
- package/dist/utils/source-reference.js +58 -27
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +2 -2
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/validators/model-warnings.js +28 -0
- package/dist/validators/model-warnings.js.map +7 -0
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-CUOQGGRA.css +1 -0
- package/dist/viewer/assets/main-DvH3gYrI.js +84 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +1 -1
- package/dist/viewer/assets/main-CxzcuU_o.js +0 -84
- package/dist/viewer/assets/main-j-7ULYpm.css +0 -1
|
@@ -214,12 +214,17 @@ function routeHandlersFromQuery(result) {
|
|
|
214
214
|
}
|
|
215
215
|
class CbmTools {
|
|
216
216
|
client;
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
defaults;
|
|
218
|
+
/**
|
|
219
|
+
* @param defaults.timeout Milliseconds each call waits when the call names none (the client's
|
|
220
|
+
* default is 30s): long reads over a large graph pass more.
|
|
221
|
+
*/
|
|
222
|
+
constructor(client, defaults = {}) {
|
|
223
|
+
this.client = client, this.defaults = defaults;
|
|
219
224
|
}
|
|
220
225
|
/** One JSON tool call, with error payloads raised as {@link CbmToolError}. */
|
|
221
|
-
async call(tool, args = {}) {
|
|
222
|
-
const raw = await this.client.invokeTool(tool, { ...args, format: "json" });
|
|
226
|
+
async call(tool, args = {}, options) {
|
|
227
|
+
const timeout = options?.timeout ?? this.defaults.timeout, raw = await this.client.invokeTool(tool, { ...args, format: "json" }, timeout !== void 0 ? { ...options, timeout } : options);
|
|
223
228
|
return assertToolResult(tool, raw), raw;
|
|
224
229
|
}
|
|
225
230
|
async listProjects() {
|
|
@@ -304,8 +309,9 @@ class CbmTools {
|
|
|
304
309
|
return files;
|
|
305
310
|
}
|
|
306
311
|
/** index_repository; returns the node/edge counts when CBM reports them. */
|
|
307
|
-
|
|
308
|
-
|
|
312
|
+
/** @param options.timeout Milliseconds to wait for the index (the client's default is 30s). */
|
|
313
|
+
async indexRepository(repoPath, options) {
|
|
314
|
+
const raw = await this.call("index_repository", { repo_path: repoPath }, options);
|
|
309
315
|
if (!isObj(raw))
|
|
310
316
|
throw new CLIError("Invalid response from index_repository", ErrorCategory.SYSTEM, [
|
|
311
317
|
"Ensure codebase-memory-mcp is properly installed and functional"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/analyzers/cbm-responses.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * codebase-memory-mcp (CBM) response contract\n *\n * Every CBM tool call made by the analyzer goes through {@link CbmTools}. It asks for JSON\n * (`format: \"json\"`; CBM 0.11+ answers in text otherwise), follows the result pages, turns\n * error payloads into errors, and normalises each tool's JSON into the shapes the analyzer\n * works with. Two response generations are accepted:\n *\n * - CBM 0.11 / 0.12 (grouped, paged):\n * - search_graph: `{ cols, groups: [{ qn_prefix, file, rows }], has_more, next_offset }`\n * - query_graph: `{ columns, rows, has_more, next_cursor, next_offset }`\n * - trace_path: `{ callers|callees: { cols: [name, hop], groups: [{ qn_prefix, rows }] }, next_cursor }`\n * - list_projects: `{ projects: [{ name, root_path }], has_more }`\n * - CBM 0.10 and earlier (flat): search_graph `{ results | nodes: [...] }`, trace_path\n * `{ nodes, edges }`, query_graph `{ columns, rows }` (or `{ records }`), list_projects\n * `{ projects }`. These pass through unchanged and are never paged.\n *\n * A grouped row's qualified name is `qn_prefix ? qn_prefix + \".\" + name : name` (the\n * response's own `qn_rule`); the row's `name` is that qualified-name suffix, which is the\n * symbol name for code nodes but only a fragment for Route nodes, whose path lives in the\n * qualified name (`__route__GET__/vets.html`).\n */\n\nimport * as path from \"path\";\nimport { CLIError, ErrorCategory } from \"../utils/errors.js\";\n\n/**\n * A CBM file path relative to the codebase root. CBM stores repository-relative paths\n * (0.10 and 0.12 alike); absolute ones are relativised, relative ones kept as they are.\n * `path.relative` alone would resolve a relative path against the process cwd.\n */\nexport function relativeToRoot(codebaseRoot: string | undefined, filePath: string): string {\n if (!filePath || !codebaseRoot || !path.isAbsolute(filePath)) return filePath;\n return path.relative(codebaseRoot, filePath);\n}\n\n/** Graph node in the shape the analyzer consumes (same as CBM 0.10's flat search results). */\nexport interface CbmGraphNode {\n id?: string;\n name?: string;\n qualified_name?: string;\n label: string;\n file_path?: string;\n properties?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface CbmProject {\n name: string;\n root_path: string;\n [key: string]: unknown;\n}\n\nexport interface QueryGraphResult {\n columns: string[];\n rows: unknown[][];\n}\n\n/** Call-graph node from trace_path; `depth` is the hop count from the traced symbol. */\nexport interface TraceNode {\n id?: string;\n qualified_name?: string;\n name?: string;\n file_path?: string;\n source_symbol?: string;\n depth?: number;\n [key: string]: unknown;\n}\n\nexport interface TraceEdge {\n from_node: string;\n to_node: string;\n type?: string;\n [key: string]: unknown;\n}\n\nexport interface TraceResult {\n nodes: TraceNode[];\n edges: TraceEdge[];\n /** \"grouped\" for CBM 0.11+ (no per-edge types, no file paths), \"flat\" for 0.10. */\n shape: \"grouped\" | \"flat\";\n}\n\n/** Handler of a Route, from a `(f)-[:HANDLES]->(r:Route)` query. */\nexport interface RouteHandler {\n route_qualified_name: string;\n qualified_name: string;\n name: string;\n file_path?: string;\n start_line?: number;\n end_line?: number;\n}\n\n/** Minimal client surface; StdioClient satisfies it. */\nexport interface CbmToolInvoker {\n invokeTool(name: string, args?: unknown, options?: { timeout?: number }): Promise<unknown>;\n}\n\n/** CBM answered a tool call with an error (bad Cypher, unknown symbol, failed index, ...). */\nexport class CbmToolError extends CLIError {\n constructor(\n public readonly tool: string,\n public readonly detail: string,\n public readonly hint?: string\n ) {\n super(\n `codebase-memory-mcp ${tool} failed: ${detail}`,\n ErrorCategory.SYSTEM,\n hint ? [hint] : undefined\n );\n this.name = \"CbmToolError\";\n }\n}\n\ntype Obj = Record<string, unknown>;\n\nconst isObj = (v: unknown): v is Obj => typeof v === \"object\" && v !== null && !Array.isArray(v);\n\n/** Page sizes: the largest each tool accepts, so most results arrive in one page. */\nconst SEARCH_PAGE = 500;\nconst QUERY_PAGE = 5000;\nconst TRACE_PAGE = 5000;\nconst PROJECTS_PAGE = 500;\n/** Lifts CBM's default 3,200-token output budget, which otherwise truncates a page early. */\nconst MAX_OUTPUT_TOKENS = 1_000_000;\n/** Guard against a server that keeps reporting has_more without advancing. */\nconst MAX_PAGES = 1000;\n\n/**\n * Throw if a tool result is an error payload. Since we always ask for JSON, a bare string is\n * an error message (CBM 0.12 sends Cypher errors as text); objects carry `error` or\n * `status: \"error\"` with an optional `hint`.\n */\nexport function assertToolResult(tool: string, raw: unknown): void {\n if (typeof raw === \"string\") {\n throw new CbmToolError(tool, raw.trim() || \"empty response\");\n }\n if (!isObj(raw)) return;\n const hint = typeof raw.hint === \"string\" ? raw.hint : undefined;\n if (typeof raw.error === \"string\") {\n throw new CbmToolError(tool, raw.error, hint);\n }\n if (raw.status === \"error\") {\n throw new CbmToolError(tool, typeof raw.message === \"string\" ? raw.message : \"status: error\", hint);\n }\n}\n\n/** Qualified name from a grouped row (the response's qn_rule). */\nexport function groupedQualifiedName(prefix: unknown, name: string): string {\n return typeof prefix === \"string\" && prefix !== \"\" ? `${prefix}.${name}` : name;\n}\n\nconst ROUTE_QN_RE = /^__route__([A-Z]+)__(.*)$/;\n\n/** Parse \"12-30\" into start/end lines. */\nfunction parseLines(lines: unknown): { start_line?: number; end_line?: number } {\n if (typeof lines !== \"string\") return {};\n const m = lines.match(/^(\\d+)-(\\d+)$/);\n return m ? { start_line: Number(m[1]), end_line: Number(m[2]) } : {};\n}\n\n/** Paging cursor fields shared by the grouped tools. */\ninterface PageInfo {\n hasMore: boolean;\n nextOffset?: number;\n nextCursor?: string;\n}\n\nfunction pageInfo(raw: Obj): PageInfo {\n return {\n hasMore: raw.has_more === true,\n nextOffset: typeof raw.next_offset === \"number\" ? raw.next_offset : undefined,\n nextCursor:\n typeof raw.next_cursor === \"string\"\n ? raw.next_cursor\n : typeof raw.next === \"string\"\n ? raw.next\n : undefined,\n };\n}\n\n/**\n * Normalise one search_graph page into analyzer nodes.\n *\n * Grouped rows become `{ name, qualified_name, label, file_path, properties }` where\n * properties carry `name`, `start_line`/`end_line` (from `lines`), `fan_in`/`fan_out`\n * (from `in`/`out`) and any extra `fields` columns. Route nodes get the path from their\n * qualified name as `name` and the method as `properties.method` when absent, matching\n * how CBM stores them (name `/vets`, qualified name `__route__GET__/vets`).\n */\nexport function normalizeSearchGraph(raw: unknown): { nodes: CbmGraphNode[] } & PageInfo {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from search_graph\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n\n // CBM 0.10 and earlier: flat node arrays.\n const flat = raw.results ?? raw.nodes;\n if (Array.isArray(flat)) {\n return { nodes: flat as CbmGraphNode[], hasMore: false };\n }\n\n if (!Array.isArray(raw.groups) || !Array.isArray(raw.cols)) {\n throw new CLIError(\"Invalid response from search_graph: no results\", ErrorCategory.SYSTEM, [\n `Expected 'groups' (CBM 0.11+) or 'results'/'nodes' (CBM 0.10), got keys: ${Object.keys(raw).join(\", \") || \"(none)\"}`,\n \"Ensure codebase-memory-mcp is properly installed and returning valid results\",\n ]);\n }\n\n const cols = raw.cols as string[];\n const nodes: CbmGraphNode[] = [];\n for (const group of raw.groups as Obj[]) {\n const file = typeof group.file === \"string\" && group.file !== \"\" ? group.file : undefined;\n for (const row of (group.rows ?? []) as unknown[][]) {\n const cell: Obj = {};\n cols.forEach((c, i) => (cell[c] = row[i]));\n const rowName = String(cell.name ?? \"\");\n const qualifiedName = groupedQualifiedName(group.qn_prefix, rowName);\n const label = String(cell.label ?? \"\");\n\n const properties: Obj = {};\n for (const [k, v] of Object.entries(cell)) {\n if (k === \"name\" || k === \"label\" || k === \"lines\" || k === \"in\" || k === \"out\") continue;\n if (v !== null && v !== undefined) properties[k] = v;\n }\n Object.assign(properties, parseLines(cell.lines));\n if (typeof cell.in === \"number\") properties.fan_in = cell.in;\n if (typeof cell.out === \"number\") properties.fan_out = cell.out;\n\n let name = rowName;\n const route = label === \"Route\" ? qualifiedName.match(ROUTE_QN_RE) : null;\n // The method stays in the qualified name, where the analyzer reads it\n if (route) name = route[2];\n properties.name = name;\n\n nodes.push({ name, qualified_name: qualifiedName, label, file_path: file, properties });\n }\n }\n return { nodes, ...pageInfo(raw) };\n}\n\n/** Normalise one query_graph page. Accepts `{ columns, rows }` and the legacy `{ records }`. */\nexport function normalizeQueryGraph(raw: unknown): QueryGraphResult & PageInfo {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from query_graph\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n if (Array.isArray(raw.columns) && Array.isArray(raw.rows)) {\n return { columns: raw.columns as string[], rows: raw.rows as unknown[][], ...pageInfo(raw) };\n }\n if (Array.isArray(raw.records)) {\n const records = raw.records as Obj[];\n const columns = Array.from(new Set(records.flatMap((r) => Object.keys(r))));\n return { columns, rows: records.map((r) => columns.map((c) => r[c])), hasMore: false };\n }\n throw new CLIError(\"Invalid response from query_graph: no columns/rows\", ErrorCategory.SYSTEM, [\n `Got keys: ${Object.keys(raw).join(\", \") || \"(none)\"}`,\n ]);\n}\n\n/** query_graph rows as objects keyed by column name (use `AS` aliases for clean keys). */\nexport function queryRecords(result: QueryGraphResult): Array<Record<string, unknown>> {\n return result.rows.map((row) => {\n const rec: Record<string, unknown> = {};\n result.columns.forEach((c, i) => (rec[c] = row[i]));\n return rec;\n });\n}\n\n/** Number from a query_graph cell (CBM 0.12 returns integers as strings). */\nexport function cellNumber(v: unknown): number | undefined {\n const n = typeof v === \"number\" ? v : typeof v === \"string\" && v.trim() !== \"\" ? Number(v) : NaN;\n return Number.isFinite(n) ? n : undefined;\n}\n\n/** String from a query_graph cell; empty strings and nulls become undefined. */\nexport function cellString(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\n/** First label from a `labels(n)` cell (CBM 0.12 returns it as a JSON string `[\"Route\"]`). */\nexport function cellLabel(v: unknown): string | undefined {\n if (Array.isArray(v)) return typeof v[0] === \"string\" ? v[0] : undefined;\n if (typeof v !== \"string\" || v === \"\") return undefined;\n if (v.startsWith(\"[\")) {\n try {\n const parsed = JSON.parse(v) as unknown;\n return Array.isArray(parsed) && typeof parsed[0] === \"string\" ? parsed[0] : undefined;\n } catch {\n return undefined;\n }\n }\n return v;\n}\n\n/**\n * RETURN clause yielding the columns {@link nodesFromQuery} reads, for node variable `v`.\n * CBM 0.12 returns only the name for a bare `RETURN n`, so node queries spell the columns out.\n */\nexport function nodeColumns(v: string): string {\n return (\n `${v}.name AS name, ${v}.qualified_name AS qualified_name, ${v}.file_path AS file_path, ` +\n `${v}.start_line AS start_line, ${v}.end_line AS end_line, labels(${v}) AS label`\n );\n}\n\n/**\n * Graph nodes from a query_graph result whose columns come from {@link nodeColumns}.\n * A legacy `{ records: [{ n: {...node} }] }` result (one node object per record) is\n * passed through.\n */\nexport function nodesFromQuery(result: QueryGraphResult): CbmGraphNode[] {\n return queryRecords(result).flatMap((r) => {\n if (result.columns.length === 1 && isObj(r[result.columns[0]])) {\n return [r[result.columns[0]] as CbmGraphNode];\n }\n const name = cellString(r.name);\n if (!name) return [];\n const properties: Obj = { name };\n const start = cellNumber(r.start_line);\n const end = cellNumber(r.end_line);\n if (start !== undefined) properties.start_line = start;\n if (end !== undefined) properties.end_line = end;\n return [\n {\n name,\n qualified_name: cellString(r.qualified_name),\n label: cellLabel(r.label) ?? \"\",\n file_path: cellString(r.file_path),\n properties,\n },\n ];\n });\n}\n\n/**\n * Normalise one trace_path page into call-graph nodes.\n *\n * Returns null when the response holds no call graph in either shape, which is how a\n * backend without trace_path support answers; callers fall back to a Cypher CALLS query.\n */\nexport function normalizeTracePath(\n raw: unknown,\n direction: \"inbound\" | \"outbound\"\n): (TraceResult & PageInfo) | null {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from trace_path\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n if (Array.isArray(raw.nodes)) {\n return {\n nodes: raw.nodes as TraceNode[],\n edges: Array.isArray(raw.edges) ? (raw.edges as TraceEdge[]) : [],\n shape: \"flat\",\n hasMore: false,\n };\n }\n\n const section = raw[direction === \"inbound\" ? \"callers\" : \"callees\"];\n if (!isObj(section) || !Array.isArray(section.groups)) return null;\n const cols = Array.isArray(section.cols) ? (section.cols as string[]) : [\"name\", \"hop\"];\n const nameIdx = cols.indexOf(\"name\");\n const hopIdx = cols.indexOf(\"hop\");\n const nodes: TraceNode[] = [];\n for (const group of section.groups as Obj[]) {\n for (const row of (group.rows ?? []) as unknown[][]) {\n const name = String(row[nameIdx >= 0 ? nameIdx : 0] ?? \"\");\n const hop = hopIdx >= 0 ? cellNumber(row[hopIdx]) : undefined;\n const qualifiedName = groupedQualifiedName(group.qn_prefix, name);\n nodes.push({\n id: qualifiedName,\n qualified_name: qualifiedName,\n name,\n source_symbol: name,\n depth: hop ?? 1,\n });\n }\n }\n const sectionPage = pageInfo(section);\n const topPage = pageInfo(raw);\n return {\n nodes,\n edges: [],\n shape: \"grouped\",\n hasMore: topPage.hasMore || sectionPage.hasMore,\n nextCursor: topPage.nextCursor ?? sectionPage.nextCursor,\n };\n}\n\n/** Normalise one list_projects page. */\nexport function normalizeListProjects(raw: unknown): { projects: CbmProject[] } & PageInfo {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from list_projects\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n // No projects array means nothing is indexed yet.\n const projects = Array.isArray(raw.projects) ? (raw.projects as CbmProject[]) : [];\n return { projects, ...pageInfo(raw) };\n}\n\n/**\n * Attach HANDLES-edge handlers to Route nodes.\n *\n * CBM Route nodes carry only `{ method, source }`; the handler, its symbol, its lines and\n * often the file come from the function that HANDLES the route. CBM keys a Route by method\n * and path, so routers mounted under different prefixes can share one Route node with\n * several handlers (fastapi's `GET /{}` is handled by both `users.read_user_by_id` and\n * `items.read_item`): such a route becomes one node per handler, each with the handler's\n * file. A route with a single handler keeps its own values where it has them.\n */\nexport function attachRouteHandlers(nodes: CbmGraphNode[], handlers: RouteHandler[]): CbmGraphNode[] {\n const byRoute = new Map<string, RouteHandler[]>();\n for (const h of handlers) {\n const list = byRoute.get(h.route_qualified_name) ?? [];\n if (!list.some((x) => x.qualified_name === h.qualified_name)) list.push(h);\n byRoute.set(h.route_qualified_name, list);\n }\n return nodes.flatMap((node) => {\n const list = byRoute.get(String(node.qualified_name ?? \"\")) ?? [];\n if (list.length === 0) return [node];\n const shared = list.length > 1;\n return list.map((h) => {\n const properties: Obj = { ...(node.properties ?? {}) };\n properties.handler_name ??= h.qualified_name;\n properties.symbol ??= h.name;\n if (h.start_line !== undefined) properties.start_line ??= h.start_line;\n if (h.end_line !== undefined) properties.end_line ??= h.end_line;\n const file = shared ? h.file_path || node.file_path : node.file_path || h.file_path;\n return { ...node, file_path: file, properties };\n });\n });\n}\n\n/** Cypher returning every Route handler as {@link RouteHandler} columns. */\nexport const ROUTE_HANDLERS_QUERY =\n \"MATCH (f)-[:HANDLES]->(r:Route) RETURN r.qualified_name AS route_qualified_name, \" +\n \"f.qualified_name AS qualified_name, f.name AS name, f.file_path AS file_path, \" +\n \"f.start_line AS start_line, f.end_line AS end_line\";\n\nexport function routeHandlersFromQuery(result: QueryGraphResult): RouteHandler[] {\n return queryRecords(result).flatMap((r) => {\n const route = cellString(r.route_qualified_name);\n const qn = cellString(r.qualified_name);\n if (!route || !qn) return [];\n return [\n {\n route_qualified_name: route,\n qualified_name: qn,\n name: cellString(r.name) ?? qn.slice(qn.lastIndexOf(\".\") + 1),\n file_path: cellString(r.file_path),\n start_line: cellNumber(r.start_line),\n end_line: cellNumber(r.end_line),\n },\n ];\n });\n}\n\n/**\n * Paged, normalised access to the CBM tools.\n *\n * All calls add `format: \"json\"`; CBM 0.10 ignores the argument.\n */\nexport class CbmTools {\n constructor(private readonly client: CbmToolInvoker) {}\n\n /** One JSON tool call, with error payloads raised as {@link CbmToolError}. */\n async call(tool: string, args: Record<string, unknown> = {}): Promise<unknown> {\n const raw = await this.client.invokeTool(tool, { ...args, format: \"json\" });\n assertToolResult(tool, raw);\n return raw;\n }\n\n async listProjects(): Promise<CbmProject[]> {\n const projects: CbmProject[] = [];\n let offset = 0;\n for (let page = 0; page < MAX_PAGES; page++) {\n const res = normalizeListProjects(\n await this.call(\"list_projects\", { limit: PROJECTS_PAGE, offset })\n );\n projects.push(...res.projects);\n const next = res.nextOffset ?? offset + res.projects.length;\n if (!res.hasMore || next <= offset) break;\n offset = next;\n }\n return projects;\n }\n\n /** search_graph across every page. */\n async searchGraph(args: Record<string, unknown>): Promise<CbmGraphNode[]> {\n const nodes: CbmGraphNode[] = [];\n let offset = 0;\n for (let page = 0; page < MAX_PAGES; page++) {\n const res = normalizeSearchGraph(\n await this.call(\"search_graph\", {\n ...args,\n limit: SEARCH_PAGE,\n offset,\n max_output_tokens: MAX_OUTPUT_TOKENS,\n })\n );\n nodes.push(...res.nodes);\n if (!res.hasMore || res.nextOffset === undefined || res.nextOffset <= offset) break;\n offset = res.nextOffset;\n }\n return nodes;\n }\n\n /** query_graph across every page (cursor continuation, offset as fallback). */\n async queryGraph(query: string, project: string): Promise<QueryGraphResult> {\n const base = { query, project, max_rows: QUERY_PAGE, max_output_tokens: MAX_OUTPUT_TOKENS };\n let res = normalizeQueryGraph(await this.call(\"query_graph\", base));\n const columns = res.columns;\n const rows = [...res.rows];\n let offset = 0;\n for (let page = 1; page < MAX_PAGES && res.hasMore; page++) {\n if (res.nextCursor) {\n res = normalizeQueryGraph(await this.call(\"query_graph\", { ...base, cursor: res.nextCursor }));\n } else if (res.nextOffset !== undefined && res.nextOffset > offset) {\n offset = res.nextOffset;\n res = normalizeQueryGraph(await this.call(\"query_graph\", { ...base, offset }));\n } else {\n break;\n }\n rows.push(...res.rows);\n }\n return { columns, rows };\n }\n\n /**\n * trace_path across every page. Depth is raised to CBM's minimum of 1. Returns null when\n * the backend's response holds no call graph (see {@link normalizeTracePath}).\n */\n async tracePath(args: {\n function_name: string;\n direction: \"inbound\" | \"outbound\";\n depth: number;\n project: string;\n }): Promise<TraceResult | null> {\n const base = {\n ...args,\n depth: Math.max(1, args.depth),\n limit: TRACE_PAGE,\n max_output_tokens: MAX_OUTPUT_TOKENS,\n };\n let res = normalizeTracePath(await this.call(\"trace_path\", base), args.direction);\n if (!res) return null;\n const { shape } = res;\n const nodes = [...res.nodes];\n const edges = [...res.edges];\n for (let page = 1; page < MAX_PAGES && res.hasMore && res.nextCursor; page++) {\n res = normalizeTracePath(\n await this.call(\"trace_path\", { ...base, cursor: res.nextCursor }),\n args.direction\n );\n if (!res) break;\n nodes.push(...res.nodes);\n edges.push(...res.edges);\n }\n return { nodes, edges, shape };\n }\n\n /**\n * File path of each qualified name, for trace_path results (which carry none since 0.11).\n * Names containing quotes or backslashes are skipped rather than escaped.\n */\n async filePathsOf(qualifiedNames: string[], project: string): Promise<Map<string, string>> {\n const files = new Map<string, string>();\n const safe = Array.from(new Set(qualifiedNames)).filter((q) => q && !/[\"'\\\\]/.test(q));\n for (let i = 0; i < safe.length; i += 200) {\n const list = safe.slice(i, i + 200).map((q) => `\"${q}\"`).join(\", \");\n const result = await this.queryGraph(\n `MATCH (n) WHERE n.qualified_name IN [${list}] RETURN n.qualified_name AS qualified_name, n.file_path AS file_path`,\n project\n );\n for (const r of queryRecords(result)) {\n const qn = cellString(r.qualified_name);\n const file = cellString(r.file_path);\n if (qn && file && !files.has(qn)) files.set(qn, file);\n }\n }\n return files;\n }\n\n /** index_repository; returns the node/edge counts when CBM reports them. */\n async indexRepository(repoPath: string): Promise<{ nodes?: number; edges?: number; [k: string]: unknown }> {\n const raw = await this.call(\"index_repository\", { repo_path: repoPath });\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from index_repository\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n return raw;\n }\n}\n"],
|
|
5
|
-
"mappings": "AAuBA,YAAY,UAAU;AACtB,SAAS,UAAU,qBAAqB;AAOlC,SAAU,eAAe,cAAkC,UAAgB;AAC/E,SAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,KAAK,WAAW,QAAQ,IAAU,WAC9D,KAAK,SAAS,cAAc,QAAQ;AAC7C;AAiEM,MAAO,qBAAqB,SAAQ;EAEtB;EACA;EACA;EAHlB,YACkB,MACA,QACA,MAAa;AAE7B,UACE,uBAAuB,IAAI,YAAY,MAAM,IAC7C,cAAc,QACd,OAAO,CAAC,IAAI,IAAI,MAAS,GAPX,KAAA,OAAA,MACA,KAAA,SAAA,QACA,KAAA,OAAA,MAOhB,KAAK,OAAO;EACd;;AAKF,MAAM,QAAQ,CAAC,MAAyB,OAAO,KAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC,GAGzF,cAAc,KACd,aAAa,KACb,aAAa,KACb,gBAAgB,KAEhB,oBAAoB,KAEpB,YAAY;AAOZ,SAAU,iBAAiB,MAAc,KAAY;AACzD,MAAI,OAAO,OAAQ;AACjB,UAAM,IAAI,aAAa,MAAM,IAAI,KAAI,KAAM,gBAAgB;AAE7D,MAAI,CAAC,MAAM,GAAG;AAAG;AACjB,QAAM,OAAO,OAAO,IAAI,QAAS,WAAW,IAAI,OAAO;AACvD,MAAI,OAAO,IAAI,SAAU;AACvB,UAAM,IAAI,aAAa,MAAM,IAAI,OAAO,IAAI;AAE9C,MAAI,IAAI,WAAW;AACjB,UAAM,IAAI,aAAa,MAAM,OAAO,IAAI,WAAY,WAAW,IAAI,UAAU,iBAAiB,IAAI;AAEtG;AAGM,SAAU,qBAAqB,QAAiB,MAAY;AAChE,SAAO,OAAO,UAAW,YAAY,WAAW,KAAK,GAAG,MAAM,IAAI,IAAI,KAAK;AAC7E;AAEA,MAAM,cAAc;AAGpB,SAAS,WAAW,OAAc;AAChC,MAAI,OAAO,SAAU;AAAU,WAAO,CAAA;AACtC,QAAM,IAAI,MAAM,MAAM,eAAe;AACrC,SAAO,IAAI,EAAE,YAAY,OAAO,EAAE,CAAC,CAAC,GAAG,UAAU,OAAO,EAAE,CAAC,CAAC,EAAC,IAAK,CAAA;AACpE;AASA,SAAS,SAAS,KAAQ;AACxB,SAAO;IACL,SAAS,IAAI,aAAa;IAC1B,YAAY,OAAO,IAAI,eAAgB,WAAW,IAAI,cAAc;IACpE,YACE,OAAO,IAAI,eAAgB,WACvB,IAAI,cACJ,OAAO,IAAI,QAAS,WAClB,IAAI,OACJ;;AAEZ;AAWM,SAAU,qBAAqB,KAAY;AAC/C,MAAI,CAAC,MAAM,GAAG;AACZ,UAAM,IAAI,SAAS,sCAAsC,cAAc,QAAQ;MAC7E;KACD;AAIH,QAAM,OAAO,IAAI,WAAW,IAAI;AAChC,MAAI,MAAM,QAAQ,IAAI;AACpB,WAAO,EAAE,OAAO,MAAwB,SAAS,GAAK;AAGxD,MAAI,CAAC,MAAM,QAAQ,IAAI,MAAM,KAAK,CAAC,MAAM,QAAQ,IAAI,IAAI;AACvD,UAAM,IAAI,SAAS,kDAAkD,cAAc,QAAQ;MACzF,4EAA4E,OAAO,KAAK,GAAG,EAAE,KAAK,IAAI,KAAK,QAAQ;MACnH;KACD;AAGH,QAAM,OAAO,IAAI,MACX,QAAwB,CAAA;AAC9B,aAAW,SAAS,IAAI,QAAiB;AACvC,UAAM,OAAO,OAAO,MAAM,QAAS,YAAY,MAAM,SAAS,KAAK,MAAM,OAAO;AAChF,eAAW,OAAQ,MAAM,QAAQ,CAAA,GAAoB;AACnD,YAAM,OAAY,CAAA;AAClB,WAAK,QAAQ,CAAC,GAAG,MAAO,KAAK,CAAC,IAAI,IAAI,CAAC,CAAE;AACzC,YAAM,UAAU,OAAO,KAAK,QAAQ,EAAE,GAChC,gBAAgB,qBAAqB,MAAM,WAAW,OAAO,GAC7D,QAAQ,OAAO,KAAK,SAAS,EAAE,GAE/B,aAAkB,CAAA;AACxB,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI;AACtC,QAAI,MAAM,UAAU,MAAM,WAAW,MAAM,WAAW,MAAM,QAAQ,MAAM,SACtE,KAAM,SAAyB,WAAW,CAAC,IAAI;AAErD,aAAO,OAAO,YAAY,WAAW,KAAK,KAAK,CAAC,GAC5C,OAAO,KAAK,MAAO,aAAU,WAAW,SAAS,KAAK,KACtD,OAAO,KAAK,OAAQ,aAAU,WAAW,UAAU,KAAK;AAE5D,UAAI,OAAO;AACX,YAAM,QAAQ,UAAU,UAAU,cAAc,MAAM,WAAW,IAAI;AAErE,MAAI,UAAO,OAAO,MAAM,CAAC,IACzB,WAAW,OAAO,MAElB,MAAM,KAAK,EAAE,MAAM,gBAAgB,eAAe,OAAO,WAAW,MAAM,WAAU,CAAE;IACxF;EACF;AACA,SAAO,EAAE,OAAO,GAAG,SAAS,GAAG,EAAC;AAClC;AAGM,SAAU,oBAAoB,KAAY;AAC9C,MAAI,CAAC,MAAM,GAAG;AACZ,UAAM,IAAI,SAAS,qCAAqC,cAAc,QAAQ;MAC5E;KACD;AAEH,MAAI,MAAM,QAAQ,IAAI,OAAO,KAAK,MAAM,QAAQ,IAAI,IAAI;AACtD,WAAO,EAAE,SAAS,IAAI,SAAqB,MAAM,IAAI,MAAqB,GAAG,SAAS,GAAG,EAAC;AAE5F,MAAI,MAAM,QAAQ,IAAI,OAAO,GAAG;AAC9B,UAAM,UAAU,IAAI,SACd,UAAU,MAAM,KAAK,IAAI,IAAI,QAAQ,QAAQ,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;AAC1E,WAAO,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,SAAS,GAAK;EACtF;AACA,QAAM,IAAI,SAAS,sDAAsD,cAAc,QAAQ;IAC7F,aAAa,OAAO,KAAK,GAAG,EAAE,KAAK,IAAI,KAAK,QAAQ;GACrD;AACH;AAGM,SAAU,aAAa,QAAwB;AACnD,SAAO,OAAO,KAAK,IAAI,CAAC,QAAO;AAC7B,UAAM,MAA+B,CAAA;AACrC,kBAAO,QAAQ,QAAQ,CAAC,GAAG,MAAO,IAAI,CAAC,IAAI,IAAI,CAAC,CAAE,GAC3C;EACT,CAAC;AACH;AAGM,SAAU,WAAW,GAAU;AACnC,QAAM,IAAI,OAAO,KAAM,WAAW,IAAI,OAAO,KAAM,YAAY,EAAE,KAAI,MAAO,KAAK,OAAO,CAAC,IAAI;AAC7F,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAGM,SAAU,WAAW,GAAU;AACnC,SAAO,OAAO,KAAM,YAAY,MAAM,KAAK,IAAI;AACjD;AAGM,SAAU,UAAU,GAAU;AAClC,MAAI,MAAM,QAAQ,CAAC;AAAG,WAAO,OAAO,EAAE,CAAC,KAAM,WAAW,EAAE,CAAC,IAAI;AAC/D,MAAI,SAAO,KAAM,YAAY,MAAM,KACnC;QAAI,EAAE,WAAW,GAAG;AAClB,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,CAAC;AAC3B,eAAO,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,CAAC,KAAM,WAAW,OAAO,CAAC,IAAI;MAC9E,QAAQ;AACN;MACF;AAEF,WAAO;;AACT;AAMM,SAAU,YAAY,GAAS;AACnC,SACE,GAAG,CAAC,kBAAkB,CAAC,sCAAsC,CAAC,4BAC3D,CAAC,8BAA8B,CAAC,iCAAiC,CAAC;AAEzE;AAOM,SAAU,eAAe,QAAwB;AACrD,SAAO,aAAa,MAAM,EAAE,QAAQ,CAAC,MAAK;AACxC,QAAI,OAAO,QAAQ,WAAW,KAAK,MAAM,EAAE,OAAO,QAAQ,CAAC,CAAC,CAAC;AAC3D,aAAO,CAAC,EAAE,OAAO,QAAQ,CAAC,CAAC,CAAiB;AAE9C,UAAM,OAAO,WAAW,EAAE,IAAI;AAC9B,QAAI,CAAC;AAAM,aAAO,CAAA;AAClB,UAAM,aAAkB,EAAE,KAAI,GACxB,QAAQ,WAAW,EAAE,UAAU,GAC/B,MAAM,WAAW,EAAE,QAAQ;AACjC,WAAI,UAAU,WAAW,WAAW,aAAa,QAC7C,QAAQ,WAAW,WAAW,WAAW,MACtC;MACL;QACE;QACA,gBAAgB,WAAW,EAAE,cAAc;QAC3C,OAAO,UAAU,EAAE,KAAK,KAAK;QAC7B,WAAW,WAAW,EAAE,SAAS;QACjC;;;EAGN,CAAC;AACH;AAQM,SAAU,mBACd,KACA,WAAiC;AAEjC,MAAI,CAAC,MAAM,GAAG;AACZ,UAAM,IAAI,SAAS,oCAAoC,cAAc,QAAQ;MAC3E;KACD;AAEH,MAAI,MAAM,QAAQ,IAAI,KAAK;AACzB,WAAO;MACL,OAAO,IAAI;MACX,OAAO,MAAM,QAAQ,IAAI,KAAK,IAAK,IAAI,QAAwB,CAAA;MAC/D,OAAO;MACP,SAAS;;AAIb,QAAM,UAAU,IAAI,cAAc,YAAY,YAAY,SAAS;AACnE,MAAI,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,QAAQ,QAAQ,MAAM;AAAG,WAAO;AAC9D,QAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI,IAAK,QAAQ,OAAoB,CAAC,QAAQ,KAAK,GAChF,UAAU,KAAK,QAAQ,MAAM,GAC7B,SAAS,KAAK,QAAQ,KAAK,GAC3B,QAAqB,CAAA;AAC3B,aAAW,SAAS,QAAQ;AAC1B,eAAW,OAAQ,MAAM,QAAQ,CAAA,GAAoB;AACnD,YAAM,OAAO,OAAO,IAAI,WAAW,IAAI,UAAU,CAAC,KAAK,EAAE,GACnD,MAAM,UAAU,IAAI,WAAW,IAAI,MAAM,CAAC,IAAI,QAC9C,gBAAgB,qBAAqB,MAAM,WAAW,IAAI;AAChE,YAAM,KAAK;QACT,IAAI;QACJ,gBAAgB;QAChB;QACA,eAAe;QACf,OAAO,OAAO;OACf;IACH;AAEF,QAAM,cAAc,SAAS,OAAO,GAC9B,UAAU,SAAS,GAAG;AAC5B,SAAO;IACL;IACA,OAAO,CAAA;IACP,OAAO;IACP,SAAS,QAAQ,WAAW,YAAY;IACxC,YAAY,QAAQ,cAAc,YAAY;;AAElD;AAGM,SAAU,sBAAsB,KAAY;AAChD,MAAI,CAAC,MAAM,GAAG;AACZ,UAAM,IAAI,SAAS,uCAAuC,cAAc,QAAQ;MAC9E;KACD;AAIH,SAAO,EAAE,UADQ,MAAM,QAAQ,IAAI,QAAQ,IAAK,IAAI,WAA4B,CAAA,GAC7D,GAAG,SAAS,GAAG,EAAC;AACrC;AAYM,SAAU,oBAAoB,OAAuB,UAAwB;AACjF,QAAM,UAAU,oBAAI,IAAG;AACvB,aAAW,KAAK,UAAU;AACxB,UAAM,OAAO,QAAQ,IAAI,EAAE,oBAAoB,KAAK,CAAA;AACpD,IAAK,KAAK,KAAK,CAAC,MAAM,EAAE,mBAAmB,EAAE,cAAc,KAAG,KAAK,KAAK,CAAC,GACzE,QAAQ,IAAI,EAAE,sBAAsB,IAAI;EAC1C;AACA,SAAO,MAAM,QAAQ,CAAC,SAAQ;AAC5B,UAAM,OAAO,QAAQ,IAAI,OAAO,KAAK,kBAAkB,EAAE,CAAC,KAAK,CAAA;AAC/D,QAAI,KAAK,WAAW;AAAG,aAAO,CAAC,IAAI;AACnC,UAAM,SAAS,KAAK,SAAS;AAC7B,WAAO,KAAK,IAAI,CAAC,MAAK;AACpB,YAAM,aAAkB,EAAE,GAAI,KAAK,cAAc,CAAA,EAAG;AACpD,iBAAW,iBAAiB,EAAE,gBAC9B,WAAW,WAAW,EAAE,MACpB,EAAE,eAAe,WAAW,WAAW,eAAe,EAAE,aACxD,EAAE,aAAa,WAAW,WAAW,aAAa,EAAE;AACxD,YAAM,OAAO,SAAS,EAAE,aAAa,KAAK,YAAY,KAAK,aAAa,EAAE;AAC1E,aAAO,EAAE,GAAG,MAAM,WAAW,MAAM,WAAU;IAC/C,CAAC;EACH,CAAC;AACH;AAGO,MAAM,uBACX;AAII,SAAU,uBAAuB,QAAwB;AAC7D,SAAO,aAAa,MAAM,EAAE,QAAQ,CAAC,MAAK;AACxC,UAAM,QAAQ,WAAW,EAAE,oBAAoB,GACzC,KAAK,WAAW,EAAE,cAAc;AACtC,WAAI,CAAC,SAAS,CAAC,KAAW,CAAA,IACnB;MACL;QACE,sBAAsB;QACtB,gBAAgB;QAChB,MAAM,WAAW,EAAE,IAAI,KAAK,GAAG,MAAM,GAAG,YAAY,GAAG,IAAI,CAAC;QAC5D,WAAW,WAAW,EAAE,SAAS;QACjC,YAAY,WAAW,EAAE,UAAU;QACnC,UAAU,WAAW,EAAE,QAAQ;;;EAGrC,CAAC;AACH;AAOM,MAAO,SAAQ;
|
|
4
|
+
"sourcesContent": ["/**\n * codebase-memory-mcp (CBM) response contract\n *\n * Every CBM tool call made by the analyzer goes through {@link CbmTools}. It asks for JSON\n * (`format: \"json\"`; CBM 0.11+ answers in text otherwise), follows the result pages, turns\n * error payloads into errors, and normalises each tool's JSON into the shapes the analyzer\n * works with. Two response generations are accepted:\n *\n * - CBM 0.11 / 0.12 (grouped, paged):\n * - search_graph: `{ cols, groups: [{ qn_prefix, file, rows }], has_more, next_offset }`\n * - query_graph: `{ columns, rows, has_more, next_cursor, next_offset }`\n * - trace_path: `{ callers|callees: { cols: [name, hop], groups: [{ qn_prefix, rows }] }, next_cursor }`\n * - list_projects: `{ projects: [{ name, root_path }], has_more }`\n * - CBM 0.10 and earlier (flat): search_graph `{ results | nodes: [...] }`, trace_path\n * `{ nodes, edges }`, query_graph `{ columns, rows }` (or `{ records }`), list_projects\n * `{ projects }`. These pass through unchanged and are never paged.\n *\n * A grouped row's qualified name is `qn_prefix ? qn_prefix + \".\" + name : name` (the\n * response's own `qn_rule`); the row's `name` is that qualified-name suffix, which is the\n * symbol name for code nodes but only a fragment for Route nodes, whose path lives in the\n * qualified name (`__route__GET__/vets.html`).\n */\n\nimport * as path from \"path\";\nimport { CLIError, ErrorCategory } from \"../utils/errors.js\";\n\n/**\n * A CBM file path relative to the codebase root. CBM stores repository-relative paths\n * (0.10 and 0.12 alike); absolute ones are relativised, relative ones kept as they are.\n * `path.relative` alone would resolve a relative path against the process cwd.\n */\nexport function relativeToRoot(codebaseRoot: string | undefined, filePath: string): string {\n if (!filePath || !codebaseRoot || !path.isAbsolute(filePath)) return filePath;\n return path.relative(codebaseRoot, filePath);\n}\n\n/** Graph node in the shape the analyzer consumes (same as CBM 0.10's flat search results). */\nexport interface CbmGraphNode {\n id?: string;\n name?: string;\n qualified_name?: string;\n label: string;\n file_path?: string;\n properties?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface CbmProject {\n name: string;\n root_path: string;\n [key: string]: unknown;\n}\n\nexport interface QueryGraphResult {\n columns: string[];\n rows: unknown[][];\n}\n\n/** Call-graph node from trace_path; `depth` is the hop count from the traced symbol. */\nexport interface TraceNode {\n id?: string;\n qualified_name?: string;\n name?: string;\n file_path?: string;\n source_symbol?: string;\n depth?: number;\n [key: string]: unknown;\n}\n\nexport interface TraceEdge {\n from_node: string;\n to_node: string;\n type?: string;\n [key: string]: unknown;\n}\n\nexport interface TraceResult {\n nodes: TraceNode[];\n edges: TraceEdge[];\n /** \"grouped\" for CBM 0.11+ (no per-edge types, no file paths), \"flat\" for 0.10. */\n shape: \"grouped\" | \"flat\";\n}\n\n/** Handler of a Route, from a `(f)-[:HANDLES]->(r:Route)` query. */\nexport interface RouteHandler {\n route_qualified_name: string;\n qualified_name: string;\n name: string;\n file_path?: string;\n start_line?: number;\n end_line?: number;\n}\n\n/** Minimal client surface; StdioClient satisfies it. */\nexport interface CbmToolInvoker {\n invokeTool(name: string, args?: unknown, options?: { timeout?: number }): Promise<unknown>;\n}\n\n/** CBM answered a tool call with an error (bad Cypher, unknown symbol, failed index, ...). */\nexport class CbmToolError extends CLIError {\n constructor(\n public readonly tool: string,\n public readonly detail: string,\n public readonly hint?: string\n ) {\n super(\n `codebase-memory-mcp ${tool} failed: ${detail}`,\n ErrorCategory.SYSTEM,\n hint ? [hint] : undefined\n );\n this.name = \"CbmToolError\";\n }\n}\n\ntype Obj = Record<string, unknown>;\n\nconst isObj = (v: unknown): v is Obj => typeof v === \"object\" && v !== null && !Array.isArray(v);\n\n/** Page sizes: the largest each tool accepts, so most results arrive in one page. */\nconst SEARCH_PAGE = 500;\nconst QUERY_PAGE = 5000;\nconst TRACE_PAGE = 5000;\nconst PROJECTS_PAGE = 500;\n/** Lifts CBM's default 3,200-token output budget, which otherwise truncates a page early. */\nconst MAX_OUTPUT_TOKENS = 1_000_000;\n/** Guard against a server that keeps reporting has_more without advancing. */\nconst MAX_PAGES = 1000;\n\n/**\n * Throw if a tool result is an error payload. Since we always ask for JSON, a bare string is\n * an error message (CBM 0.12 sends Cypher errors as text); objects carry `error` or\n * `status: \"error\"` with an optional `hint`.\n */\nexport function assertToolResult(tool: string, raw: unknown): void {\n if (typeof raw === \"string\") {\n throw new CbmToolError(tool, raw.trim() || \"empty response\");\n }\n if (!isObj(raw)) return;\n const hint = typeof raw.hint === \"string\" ? raw.hint : undefined;\n if (typeof raw.error === \"string\") {\n throw new CbmToolError(tool, raw.error, hint);\n }\n if (raw.status === \"error\") {\n throw new CbmToolError(tool, typeof raw.message === \"string\" ? raw.message : \"status: error\", hint);\n }\n}\n\n/** Qualified name from a grouped row (the response's qn_rule). */\nexport function groupedQualifiedName(prefix: unknown, name: string): string {\n return typeof prefix === \"string\" && prefix !== \"\" ? `${prefix}.${name}` : name;\n}\n\nconst ROUTE_QN_RE = /^__route__([A-Z]+)__(.*)$/;\n\n/** Parse \"12-30\" into start/end lines. */\nfunction parseLines(lines: unknown): { start_line?: number; end_line?: number } {\n if (typeof lines !== \"string\") return {};\n const m = lines.match(/^(\\d+)-(\\d+)$/);\n return m ? { start_line: Number(m[1]), end_line: Number(m[2]) } : {};\n}\n\n/** Paging cursor fields shared by the grouped tools. */\ninterface PageInfo {\n hasMore: boolean;\n nextOffset?: number;\n nextCursor?: string;\n}\n\nfunction pageInfo(raw: Obj): PageInfo {\n return {\n hasMore: raw.has_more === true,\n nextOffset: typeof raw.next_offset === \"number\" ? raw.next_offset : undefined,\n nextCursor:\n typeof raw.next_cursor === \"string\"\n ? raw.next_cursor\n : typeof raw.next === \"string\"\n ? raw.next\n : undefined,\n };\n}\n\n/**\n * Normalise one search_graph page into analyzer nodes.\n *\n * Grouped rows become `{ name, qualified_name, label, file_path, properties }` where\n * properties carry `name`, `start_line`/`end_line` (from `lines`), `fan_in`/`fan_out`\n * (from `in`/`out`) and any extra `fields` columns. Route nodes get the path from their\n * qualified name as `name` and the method as `properties.method` when absent, matching\n * how CBM stores them (name `/vets`, qualified name `__route__GET__/vets`).\n */\nexport function normalizeSearchGraph(raw: unknown): { nodes: CbmGraphNode[] } & PageInfo {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from search_graph\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n\n // CBM 0.10 and earlier: flat node arrays.\n const flat = raw.results ?? raw.nodes;\n if (Array.isArray(flat)) {\n return { nodes: flat as CbmGraphNode[], hasMore: false };\n }\n\n if (!Array.isArray(raw.groups) || !Array.isArray(raw.cols)) {\n throw new CLIError(\"Invalid response from search_graph: no results\", ErrorCategory.SYSTEM, [\n `Expected 'groups' (CBM 0.11+) or 'results'/'nodes' (CBM 0.10), got keys: ${Object.keys(raw).join(\", \") || \"(none)\"}`,\n \"Ensure codebase-memory-mcp is properly installed and returning valid results\",\n ]);\n }\n\n const cols = raw.cols as string[];\n const nodes: CbmGraphNode[] = [];\n for (const group of raw.groups as Obj[]) {\n const file = typeof group.file === \"string\" && group.file !== \"\" ? group.file : undefined;\n for (const row of (group.rows ?? []) as unknown[][]) {\n const cell: Obj = {};\n cols.forEach((c, i) => (cell[c] = row[i]));\n const rowName = String(cell.name ?? \"\");\n const qualifiedName = groupedQualifiedName(group.qn_prefix, rowName);\n const label = String(cell.label ?? \"\");\n\n const properties: Obj = {};\n for (const [k, v] of Object.entries(cell)) {\n if (k === \"name\" || k === \"label\" || k === \"lines\" || k === \"in\" || k === \"out\") continue;\n if (v !== null && v !== undefined) properties[k] = v;\n }\n Object.assign(properties, parseLines(cell.lines));\n if (typeof cell.in === \"number\") properties.fan_in = cell.in;\n if (typeof cell.out === \"number\") properties.fan_out = cell.out;\n\n let name = rowName;\n const route = label === \"Route\" ? qualifiedName.match(ROUTE_QN_RE) : null;\n // The method stays in the qualified name, where the analyzer reads it\n if (route) name = route[2];\n properties.name = name;\n\n nodes.push({ name, qualified_name: qualifiedName, label, file_path: file, properties });\n }\n }\n return { nodes, ...pageInfo(raw) };\n}\n\n/** Normalise one query_graph page. Accepts `{ columns, rows }` and the legacy `{ records }`. */\nexport function normalizeQueryGraph(raw: unknown): QueryGraphResult & PageInfo {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from query_graph\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n if (Array.isArray(raw.columns) && Array.isArray(raw.rows)) {\n return { columns: raw.columns as string[], rows: raw.rows as unknown[][], ...pageInfo(raw) };\n }\n if (Array.isArray(raw.records)) {\n const records = raw.records as Obj[];\n const columns = Array.from(new Set(records.flatMap((r) => Object.keys(r))));\n return { columns, rows: records.map((r) => columns.map((c) => r[c])), hasMore: false };\n }\n throw new CLIError(\"Invalid response from query_graph: no columns/rows\", ErrorCategory.SYSTEM, [\n `Got keys: ${Object.keys(raw).join(\", \") || \"(none)\"}`,\n ]);\n}\n\n/** query_graph rows as objects keyed by column name (use `AS` aliases for clean keys). */\nexport function queryRecords(result: QueryGraphResult): Array<Record<string, unknown>> {\n return result.rows.map((row) => {\n const rec: Record<string, unknown> = {};\n result.columns.forEach((c, i) => (rec[c] = row[i]));\n return rec;\n });\n}\n\n/** Number from a query_graph cell (CBM 0.12 returns integers as strings). */\nexport function cellNumber(v: unknown): number | undefined {\n const n = typeof v === \"number\" ? v : typeof v === \"string\" && v.trim() !== \"\" ? Number(v) : NaN;\n return Number.isFinite(n) ? n : undefined;\n}\n\n/** String from a query_graph cell; empty strings and nulls become undefined. */\nexport function cellString(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\n/** First label from a `labels(n)` cell (CBM 0.12 returns it as a JSON string `[\"Route\"]`). */\nexport function cellLabel(v: unknown): string | undefined {\n if (Array.isArray(v)) return typeof v[0] === \"string\" ? v[0] : undefined;\n if (typeof v !== \"string\" || v === \"\") return undefined;\n if (v.startsWith(\"[\")) {\n try {\n const parsed = JSON.parse(v) as unknown;\n return Array.isArray(parsed) && typeof parsed[0] === \"string\" ? parsed[0] : undefined;\n } catch {\n return undefined;\n }\n }\n return v;\n}\n\n/**\n * RETURN clause yielding the columns {@link nodesFromQuery} reads, for node variable `v`.\n * CBM 0.12 returns only the name for a bare `RETURN n`, so node queries spell the columns out.\n */\nexport function nodeColumns(v: string): string {\n return (\n `${v}.name AS name, ${v}.qualified_name AS qualified_name, ${v}.file_path AS file_path, ` +\n `${v}.start_line AS start_line, ${v}.end_line AS end_line, labels(${v}) AS label`\n );\n}\n\n/**\n * Graph nodes from a query_graph result whose columns come from {@link nodeColumns}.\n * A legacy `{ records: [{ n: {...node} }] }` result (one node object per record) is\n * passed through.\n */\nexport function nodesFromQuery(result: QueryGraphResult): CbmGraphNode[] {\n return queryRecords(result).flatMap((r) => {\n if (result.columns.length === 1 && isObj(r[result.columns[0]])) {\n return [r[result.columns[0]] as CbmGraphNode];\n }\n const name = cellString(r.name);\n if (!name) return [];\n const properties: Obj = { name };\n const start = cellNumber(r.start_line);\n const end = cellNumber(r.end_line);\n if (start !== undefined) properties.start_line = start;\n if (end !== undefined) properties.end_line = end;\n return [\n {\n name,\n qualified_name: cellString(r.qualified_name),\n label: cellLabel(r.label) ?? \"\",\n file_path: cellString(r.file_path),\n properties,\n },\n ];\n });\n}\n\n/**\n * Normalise one trace_path page into call-graph nodes.\n *\n * Returns null when the response holds no call graph in either shape, which is how a\n * backend without trace_path support answers; callers fall back to a Cypher CALLS query.\n */\nexport function normalizeTracePath(\n raw: unknown,\n direction: \"inbound\" | \"outbound\"\n): (TraceResult & PageInfo) | null {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from trace_path\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n if (Array.isArray(raw.nodes)) {\n return {\n nodes: raw.nodes as TraceNode[],\n edges: Array.isArray(raw.edges) ? (raw.edges as TraceEdge[]) : [],\n shape: \"flat\",\n hasMore: false,\n };\n }\n\n const section = raw[direction === \"inbound\" ? \"callers\" : \"callees\"];\n if (!isObj(section) || !Array.isArray(section.groups)) return null;\n const cols = Array.isArray(section.cols) ? (section.cols as string[]) : [\"name\", \"hop\"];\n const nameIdx = cols.indexOf(\"name\");\n const hopIdx = cols.indexOf(\"hop\");\n const nodes: TraceNode[] = [];\n for (const group of section.groups as Obj[]) {\n for (const row of (group.rows ?? []) as unknown[][]) {\n const name = String(row[nameIdx >= 0 ? nameIdx : 0] ?? \"\");\n const hop = hopIdx >= 0 ? cellNumber(row[hopIdx]) : undefined;\n const qualifiedName = groupedQualifiedName(group.qn_prefix, name);\n nodes.push({\n id: qualifiedName,\n qualified_name: qualifiedName,\n name,\n source_symbol: name,\n depth: hop ?? 1,\n });\n }\n }\n const sectionPage = pageInfo(section);\n const topPage = pageInfo(raw);\n return {\n nodes,\n edges: [],\n shape: \"grouped\",\n hasMore: topPage.hasMore || sectionPage.hasMore,\n nextCursor: topPage.nextCursor ?? sectionPage.nextCursor,\n };\n}\n\n/** Normalise one list_projects page. */\nexport function normalizeListProjects(raw: unknown): { projects: CbmProject[] } & PageInfo {\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from list_projects\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n // No projects array means nothing is indexed yet.\n const projects = Array.isArray(raw.projects) ? (raw.projects as CbmProject[]) : [];\n return { projects, ...pageInfo(raw) };\n}\n\n/**\n * Attach HANDLES-edge handlers to Route nodes.\n *\n * CBM Route nodes carry only `{ method, source }`; the handler, its symbol, its lines and\n * often the file come from the function that HANDLES the route. CBM keys a Route by method\n * and path, so routers mounted under different prefixes can share one Route node with\n * several handlers (fastapi's `GET /{}` is handled by both `users.read_user_by_id` and\n * `items.read_item`): such a route becomes one node per handler, each with the handler's\n * file. A route with a single handler keeps its own values where it has them.\n */\nexport function attachRouteHandlers(nodes: CbmGraphNode[], handlers: RouteHandler[]): CbmGraphNode[] {\n const byRoute = new Map<string, RouteHandler[]>();\n for (const h of handlers) {\n const list = byRoute.get(h.route_qualified_name) ?? [];\n if (!list.some((x) => x.qualified_name === h.qualified_name)) list.push(h);\n byRoute.set(h.route_qualified_name, list);\n }\n return nodes.flatMap((node) => {\n const list = byRoute.get(String(node.qualified_name ?? \"\")) ?? [];\n if (list.length === 0) return [node];\n const shared = list.length > 1;\n return list.map((h) => {\n const properties: Obj = { ...(node.properties ?? {}) };\n properties.handler_name ??= h.qualified_name;\n properties.symbol ??= h.name;\n if (h.start_line !== undefined) properties.start_line ??= h.start_line;\n if (h.end_line !== undefined) properties.end_line ??= h.end_line;\n const file = shared ? h.file_path || node.file_path : node.file_path || h.file_path;\n return { ...node, file_path: file, properties };\n });\n });\n}\n\n/** Cypher returning every Route handler as {@link RouteHandler} columns. */\nexport const ROUTE_HANDLERS_QUERY =\n \"MATCH (f)-[:HANDLES]->(r:Route) RETURN r.qualified_name AS route_qualified_name, \" +\n \"f.qualified_name AS qualified_name, f.name AS name, f.file_path AS file_path, \" +\n \"f.start_line AS start_line, f.end_line AS end_line\";\n\nexport function routeHandlersFromQuery(result: QueryGraphResult): RouteHandler[] {\n return queryRecords(result).flatMap((r) => {\n const route = cellString(r.route_qualified_name);\n const qn = cellString(r.qualified_name);\n if (!route || !qn) return [];\n return [\n {\n route_qualified_name: route,\n qualified_name: qn,\n name: cellString(r.name) ?? qn.slice(qn.lastIndexOf(\".\") + 1),\n file_path: cellString(r.file_path),\n start_line: cellNumber(r.start_line),\n end_line: cellNumber(r.end_line),\n },\n ];\n });\n}\n\n/**\n * Paged, normalised access to the CBM tools.\n *\n * All calls add `format: \"json\"`; CBM 0.10 ignores the argument.\n */\nexport class CbmTools {\n /**\n * @param defaults.timeout Milliseconds each call waits when the call names none (the client's\n * default is 30s): long reads over a large graph pass more.\n */\n constructor(\n private readonly client: CbmToolInvoker,\n private readonly defaults: { timeout?: number } = {}\n ) {}\n\n /** One JSON tool call, with error payloads raised as {@link CbmToolError}. */\n async call(\n tool: string,\n args: Record<string, unknown> = {},\n options?: { timeout?: number }\n ): Promise<unknown> {\n const timeout = options?.timeout ?? this.defaults.timeout;\n const raw = await this.client.invokeTool(tool, { ...args, format: \"json\" }, timeout !== undefined ? { ...options, timeout } : options);\n assertToolResult(tool, raw);\n return raw;\n }\n\n async listProjects(): Promise<CbmProject[]> {\n const projects: CbmProject[] = [];\n let offset = 0;\n for (let page = 0; page < MAX_PAGES; page++) {\n const res = normalizeListProjects(\n await this.call(\"list_projects\", { limit: PROJECTS_PAGE, offset })\n );\n projects.push(...res.projects);\n const next = res.nextOffset ?? offset + res.projects.length;\n if (!res.hasMore || next <= offset) break;\n offset = next;\n }\n return projects;\n }\n\n /** search_graph across every page. */\n async searchGraph(args: Record<string, unknown>): Promise<CbmGraphNode[]> {\n const nodes: CbmGraphNode[] = [];\n let offset = 0;\n for (let page = 0; page < MAX_PAGES; page++) {\n const res = normalizeSearchGraph(\n await this.call(\"search_graph\", {\n ...args,\n limit: SEARCH_PAGE,\n offset,\n max_output_tokens: MAX_OUTPUT_TOKENS,\n })\n );\n nodes.push(...res.nodes);\n if (!res.hasMore || res.nextOffset === undefined || res.nextOffset <= offset) break;\n offset = res.nextOffset;\n }\n return nodes;\n }\n\n /** query_graph across every page (cursor continuation, offset as fallback). */\n async queryGraph(query: string, project: string): Promise<QueryGraphResult> {\n const base = { query, project, max_rows: QUERY_PAGE, max_output_tokens: MAX_OUTPUT_TOKENS };\n let res = normalizeQueryGraph(await this.call(\"query_graph\", base));\n const columns = res.columns;\n const rows = [...res.rows];\n let offset = 0;\n for (let page = 1; page < MAX_PAGES && res.hasMore; page++) {\n if (res.nextCursor) {\n res = normalizeQueryGraph(await this.call(\"query_graph\", { ...base, cursor: res.nextCursor }));\n } else if (res.nextOffset !== undefined && res.nextOffset > offset) {\n offset = res.nextOffset;\n res = normalizeQueryGraph(await this.call(\"query_graph\", { ...base, offset }));\n } else {\n break;\n }\n rows.push(...res.rows);\n }\n return { columns, rows };\n }\n\n /**\n * trace_path across every page. Depth is raised to CBM's minimum of 1. Returns null when\n * the backend's response holds no call graph (see {@link normalizeTracePath}).\n */\n async tracePath(args: {\n function_name: string;\n direction: \"inbound\" | \"outbound\";\n depth: number;\n project: string;\n }): Promise<TraceResult | null> {\n const base = {\n ...args,\n depth: Math.max(1, args.depth),\n limit: TRACE_PAGE,\n max_output_tokens: MAX_OUTPUT_TOKENS,\n };\n let res = normalizeTracePath(await this.call(\"trace_path\", base), args.direction);\n if (!res) return null;\n const { shape } = res;\n const nodes = [...res.nodes];\n const edges = [...res.edges];\n for (let page = 1; page < MAX_PAGES && res.hasMore && res.nextCursor; page++) {\n res = normalizeTracePath(\n await this.call(\"trace_path\", { ...base, cursor: res.nextCursor }),\n args.direction\n );\n if (!res) break;\n nodes.push(...res.nodes);\n edges.push(...res.edges);\n }\n return { nodes, edges, shape };\n }\n\n /**\n * File path of each qualified name, for trace_path results (which carry none since 0.11).\n * Names containing quotes or backslashes are skipped rather than escaped.\n */\n async filePathsOf(qualifiedNames: string[], project: string): Promise<Map<string, string>> {\n const files = new Map<string, string>();\n const safe = Array.from(new Set(qualifiedNames)).filter((q) => q && !/[\"'\\\\]/.test(q));\n for (let i = 0; i < safe.length; i += 200) {\n const list = safe.slice(i, i + 200).map((q) => `\"${q}\"`).join(\", \");\n const result = await this.queryGraph(\n `MATCH (n) WHERE n.qualified_name IN [${list}] RETURN n.qualified_name AS qualified_name, n.file_path AS file_path`,\n project\n );\n for (const r of queryRecords(result)) {\n const qn = cellString(r.qualified_name);\n const file = cellString(r.file_path);\n if (qn && file && !files.has(qn)) files.set(qn, file);\n }\n }\n return files;\n }\n\n /** index_repository; returns the node/edge counts when CBM reports them. */\n /** @param options.timeout Milliseconds to wait for the index (the client's default is 30s). */\n async indexRepository(\n repoPath: string,\n options?: { timeout?: number }\n ): Promise<{ nodes?: number; edges?: number; [k: string]: unknown }> {\n const raw = await this.call(\"index_repository\", { repo_path: repoPath }, options);\n if (!isObj(raw)) {\n throw new CLIError(\"Invalid response from index_repository\", ErrorCategory.SYSTEM, [\n \"Ensure codebase-memory-mcp is properly installed and functional\",\n ]);\n }\n return raw;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { statSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { CLIError, ErrorCategory } from "../utils/errors.js";
|
|
5
|
+
import { SOURCE_EXTENSIONS } from "./cards/build.js";
|
|
6
|
+
const DR_OWN_DIRS = Object.freeze(["documentation-robotics", ".dr"]);
|
|
7
|
+
function inDirs(file, dirs) {
|
|
8
|
+
return dirs.some((d) => !!d && (file === d || file.startsWith(d.replace(/\/+$/, "") + "/")));
|
|
9
|
+
}
|
|
10
|
+
const MAX_BUFFER = 64 * 1024 * 1024;
|
|
11
|
+
function git(args, cwd) {
|
|
12
|
+
const r = spawnSync("git", ["-c", "core.quotepath=false", ...args], { cwd, encoding: "utf-8", stdio: "pipe", timeout: 3e4, maxBuffer: MAX_BUFFER });
|
|
13
|
+
return r.error ? { ok: !1, error: r.error.message } : r.status !== 0 ? { ok: !1, error: (r.stderr || r.stdout || `git exited ${r.status}`).trim() } : { ok: !0, out: r.stdout };
|
|
14
|
+
}
|
|
15
|
+
function toPosix(p) {
|
|
16
|
+
return p.split(path.sep).join("/");
|
|
17
|
+
}
|
|
18
|
+
function parseNameStatus(out, into) {
|
|
19
|
+
const parts = out.split("\0");
|
|
20
|
+
for (let i = 0; i < parts.length; ) {
|
|
21
|
+
const status = parts[i++];
|
|
22
|
+
if (!status)
|
|
23
|
+
continue;
|
|
24
|
+
const code = status[0];
|
|
25
|
+
if (code === "R" || code === "C") {
|
|
26
|
+
const from = parts[i++], to = parts[i++];
|
|
27
|
+
code === "R" && from && (into.deleted.set(from, to ?? null), into.changed.delete(from)), to && (into.changed.add(to), into.deleted.delete(to));
|
|
28
|
+
} else {
|
|
29
|
+
const file = parts[i++];
|
|
30
|
+
if (!file)
|
|
31
|
+
continue;
|
|
32
|
+
code === "D" ? (into.deleted.set(file, null), into.changed.delete(file)) : (into.changed.add(file), into.deleted.delete(file));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function parseHunks(out) {
|
|
37
|
+
const hunks = /* @__PURE__ */ new Map();
|
|
38
|
+
let file = null, created = !1;
|
|
39
|
+
for (const line of out.split(`
|
|
40
|
+
`))
|
|
41
|
+
if (line.startsWith("diff --git "))
|
|
42
|
+
file = null, created = !1;
|
|
43
|
+
else if (line.startsWith("--- "))
|
|
44
|
+
created = line === "--- /dev/null";
|
|
45
|
+
else if (line.startsWith("+++ "))
|
|
46
|
+
file = line === "+++ /dev/null" ? null : line.slice(4).replace(/\t$/, "").replace(/^b\//, ""), file && created && hunks.set(file, "all");
|
|
47
|
+
else if (file && line.startsWith("@@")) {
|
|
48
|
+
const m = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line), ranges = hunks.get(file) ?? [];
|
|
49
|
+
if (!m || ranges === "all")
|
|
50
|
+
continue;
|
|
51
|
+
const start = Math.max(Number(m[1]), 1), count = m[2] === void 0 ? 1 : Number(m[2]);
|
|
52
|
+
ranges.push(count === 0 ? [start, start, !0] : [start, start + count - 1]), hunks.set(file, ranges);
|
|
53
|
+
}
|
|
54
|
+
return hunks;
|
|
55
|
+
}
|
|
56
|
+
function changedInRange(ranges, start, end) {
|
|
57
|
+
if (!ranges)
|
|
58
|
+
return !1;
|
|
59
|
+
if (ranges === "all")
|
|
60
|
+
return !0;
|
|
61
|
+
if (start === null)
|
|
62
|
+
return !1;
|
|
63
|
+
const last = end ?? start;
|
|
64
|
+
return ranges.some(([a, b, deletion]) => deletion ? a >= start && a < last : a <= last && b >= start);
|
|
65
|
+
}
|
|
66
|
+
async function codeChangesSince(codebaseRoot, since) {
|
|
67
|
+
const top = git(["rev-parse", "--show-toplevel"], codebaseRoot);
|
|
68
|
+
if (!top.ok)
|
|
69
|
+
throw new CLIError(`The code is not in a git repository: ${codebaseRoot}`, ErrorCategory.USER, ["Call codebase_extract without since"]);
|
|
70
|
+
if (since.startsWith("-"))
|
|
71
|
+
throw new CLIError(`since is not a git ref: ${since}`, ErrorCategory.USER);
|
|
72
|
+
const commit = git(["rev-parse", "--verify", "--quiet", `${since}^{commit}`], codebaseRoot);
|
|
73
|
+
if (!commit.ok || !commit.out.trim())
|
|
74
|
+
throw new CLIError(`since is not a commit in this repository: ${since}`, ErrorCategory.USER, [
|
|
75
|
+
"Pass a branch, tag or commit, e.g. the branch the work started from (main)"
|
|
76
|
+
]);
|
|
77
|
+
const mergeBase = git(["merge-base", commit.out.trim(), "HEAD"], codebaseRoot), base = mergeBase.ok && mergeBase.out.trim() ? mergeBase.out.trim() : commit.out.trim(), repo = { changed: /* @__PURE__ */ new Set(), deleted: /* @__PURE__ */ new Map() };
|
|
78
|
+
for (const args of [
|
|
79
|
+
["diff", "--name-status", "-z", "-M", base, "HEAD"],
|
|
80
|
+
["diff", "--name-status", "-z", "-M", "HEAD"]
|
|
81
|
+
]) {
|
|
82
|
+
const r = git(args, codebaseRoot);
|
|
83
|
+
if (!r.ok)
|
|
84
|
+
throw new CLIError(`git ${args.join(" ")} failed: ${r.error}`, ErrorCategory.SYSTEM);
|
|
85
|
+
parseNameStatus(r.out, repo);
|
|
86
|
+
}
|
|
87
|
+
const untracked = git(["ls-files", "--others", "--exclude-standard", "-z", "--full-name"], top.out.trim()), untrackedFiles = untracked.ok ? untracked.out.split("\0").filter(Boolean) : [];
|
|
88
|
+
for (const f of untrackedFiles)
|
|
89
|
+
repo.changed.add(f), repo.deleted.delete(f);
|
|
90
|
+
const pathspecs = [
|
|
91
|
+
...SOURCE_EXTENSIONS.map((ext) => `:(glob,icase)**/*.${ext}`),
|
|
92
|
+
...DR_OWN_DIRS.map((d) => `:(exclude)${d}`)
|
|
93
|
+
], diff = git(["diff", "-U0", "-M", "--no-color", "--no-ext-diff", base, "--", ...pathspecs], codebaseRoot), repoHunks = diff.ok ? parseHunks(diff.out) : /* @__PURE__ */ new Map(), hunksError = diff.ok ? void 0 : diff.error.split(`
|
|
94
|
+
`)[0];
|
|
95
|
+
for (const f of untrackedFiles)
|
|
96
|
+
repoHunks.set(f, "all");
|
|
97
|
+
const prefix = toPosix(path.relative(top.out.trim(), codebaseRoot)), local = (f) => {
|
|
98
|
+
const l = prefix ? f.startsWith(prefix + "/") ? f.slice(prefix.length + 1) : null : f;
|
|
99
|
+
return l && !inDirs(l, DR_OWN_DIRS) ? l : null;
|
|
100
|
+
}, changed = /* @__PURE__ */ new Set();
|
|
101
|
+
for (const f of repo.changed) {
|
|
102
|
+
const l = local(f);
|
|
103
|
+
l && changed.add(l);
|
|
104
|
+
}
|
|
105
|
+
const deleted = /* @__PURE__ */ new Map();
|
|
106
|
+
for (const [from, to] of repo.deleted) {
|
|
107
|
+
const l = local(from);
|
|
108
|
+
l && deleted.set(l, to ? local(to) : null);
|
|
109
|
+
}
|
|
110
|
+
const hunks = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const [f, ranges] of repoHunks) {
|
|
112
|
+
const l = local(f);
|
|
113
|
+
l && changed.has(l) && hunks.set(l, ranges);
|
|
114
|
+
}
|
|
115
|
+
const topDir = top.out.trim();
|
|
116
|
+
return { base, changed, deleted, hunks, ...hunksError ? { hunksError } : {}, fileAtBase: (file) => {
|
|
117
|
+
const r = git(["show", `${base}:${prefix ? `${prefix}/${file}` : file}`], topDir);
|
|
118
|
+
return r.ok ? r.out : /does not exist in|exists on disk, but not in/.test(r.error) ? null : void 0;
|
|
119
|
+
} };
|
|
120
|
+
}
|
|
121
|
+
function scopeChanges(changes, keep) {
|
|
122
|
+
const changed = new Set([...changes.changed].filter(keep)), deleted = new Map([...changes.deleted].filter(([from, to]) => keep(from) || to !== null && keep(to))), hunks = changes.hunks ? new Map([...changes.hunks].filter(([f]) => changed.has(f))) : void 0;
|
|
123
|
+
return { ...changes, changed, deleted, ...hunks ? { hunks } : {} };
|
|
124
|
+
}
|
|
125
|
+
async function sourceEditsSince(codebaseRoot, sinceMs, isSource) {
|
|
126
|
+
const top = git(["rev-parse", "--show-toplevel"], codebaseRoot);
|
|
127
|
+
if (!top.ok)
|
|
128
|
+
return [];
|
|
129
|
+
const status = git(["status", "--porcelain", "-z", "--untracked-files=all", "--", "."], codebaseRoot);
|
|
130
|
+
if (!status.ok)
|
|
131
|
+
return [];
|
|
132
|
+
const prefix = toPosix(path.relative(top.out.trim(), codebaseRoot)), out = [], parts = status.out.split("\0");
|
|
133
|
+
for (let i = 0; i < parts.length; i++) {
|
|
134
|
+
const entry = parts[i];
|
|
135
|
+
if (entry.length < 4)
|
|
136
|
+
continue;
|
|
137
|
+
const code = entry.slice(0, 2), file = entry.slice(3);
|
|
138
|
+
if ((code[0] === "R" || code[0] === "C") && i++, code.includes("D"))
|
|
139
|
+
continue;
|
|
140
|
+
const local = prefix ? file.startsWith(prefix + "/") ? file.slice(prefix.length + 1) : null : file;
|
|
141
|
+
if (!(!local || !isSource(local)))
|
|
142
|
+
try {
|
|
143
|
+
statSync(path.join(codebaseRoot, local)).mtimeMs > sinceMs && out.push(local);
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out.sort();
|
|
148
|
+
}
|
|
149
|
+
export {
|
|
150
|
+
DR_OWN_DIRS,
|
|
151
|
+
changedInRange,
|
|
152
|
+
codeChangesSince,
|
|
153
|
+
inDirs,
|
|
154
|
+
parseHunks,
|
|
155
|
+
parseNameStatus,
|
|
156
|
+
scopeChanges,
|
|
157
|
+
sourceEditsSince
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=code-changes.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/analyzers/code-changes.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The files a git range changed, for `codebase_extract` with `since`: what the branch (or the\n * commits since a ref) added, modified, renamed and deleted, plus uncommitted and untracked files,\n * relative to the codebase root, with the lines each changed file changed. DR's own folders\n * (`documentation-robotics/`, `.dr/`) are never part of it. Read-only git, no shell.\n */\nimport { spawnSync } from \"node:child_process\";\nimport { statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { CLIError, ErrorCategory } from \"../utils/errors.js\";\nimport { SOURCE_EXTENSIONS } from \"./cards/build.js\";\nimport type { CodeChanges, LineRanges } from \"./extraction-plan.js\";\n\nexport interface CodeChangesResult extends CodeChanges {\n /** The commit the range starts from: the merge base of `since` and HEAD. */\n base: string;\n}\n\nexport type { LineRanges };\n\n/** Folders, relative to the codebase root, that hold DR's own files: never changed code. */\nexport const DR_OWN_DIRS: readonly string[] = Object.freeze([\"documentation-robotics\", \".dr\"]);\n\n/** Whether a codebase-relative path is inside one of these folders. */\nexport function inDirs(file: string, dirs: readonly string[]): boolean {\n return dirs.some((d) => !!d && (file === d || file.startsWith(d.replace(/\\/+$/, \"\") + \"/\")));\n}\n\n/** Output git may return: a whole range's diff, a large file at the base. */\nconst MAX_BUFFER = 64 * 1024 * 1024;\n\nfunction git(args: string[], cwd: string): { ok: true; out: string } | { ok: false; error: string } {\n const r = spawnSync(\"git\", [\"-c\", \"core.quotepath=false\", ...args], { cwd, encoding: \"utf-8\", stdio: \"pipe\", timeout: 30_000, maxBuffer: MAX_BUFFER });\n if (r.error) return { ok: false, error: r.error.message };\n if (r.status !== 0) return { ok: false, error: (r.stderr || r.stdout || `git exited ${r.status}`).trim() };\n return { ok: true, out: r.stdout };\n}\n\nfunction toPosix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n\n/** Parse `git diff --name-status -z` output into changed and deleted files (repo-relative). */\nexport function parseNameStatus(out: string, into: { changed: Set<string>; deleted: Map<string, string | null> }): void {\n const parts = out.split(\"\\0\");\n for (let i = 0; i < parts.length; ) {\n const status = parts[i++];\n if (!status) continue;\n const code = status[0];\n if (code === \"R\" || code === \"C\") {\n const from = parts[i++];\n const to = parts[i++];\n if (code === \"R\" && from) {\n into.deleted.set(from, to ?? null);\n into.changed.delete(from);\n }\n if (to) {\n into.changed.add(to);\n into.deleted.delete(to);\n }\n } else {\n const file = parts[i++];\n if (!file) continue;\n if (code === \"D\") {\n into.deleted.set(file, null);\n into.changed.delete(file);\n } else {\n into.changed.add(file);\n into.deleted.delete(file);\n }\n }\n }\n}\n\n/**\n * Parse `git diff -U0` output into the new-side line ranges of each file (repo-relative). A file\n * the diff creates (`--- /dev/null`) is \"all\".\n */\nexport function parseHunks(out: string): Map<string, LineRanges> {\n const hunks = new Map<string, LineRanges>();\n let file: string | null = null;\n let created = false;\n for (const line of out.split(\"\\n\")) {\n if (line.startsWith(\"diff --git \")) {\n file = null;\n created = false;\n } else if (line.startsWith(\"--- \")) {\n created = line === \"--- /dev/null\";\n } else if (line.startsWith(\"+++ \")) {\n file = line === \"+++ /dev/null\" ? null : line.slice(4).replace(/\\t$/, \"\").replace(/^b\\//, \"\");\n if (file && created) hunks.set(file, \"all\");\n } else if (file && line.startsWith(\"@@\")) {\n const m = /^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,(\\d+))? @@/.exec(line);\n const ranges = hunks.get(file) ?? [];\n if (!m || ranges === \"all\") continue;\n const start = Math.max(Number(m[1]), 1);\n const count = m[2] === undefined ? 1 : Number(m[2]);\n // A pure deletion (count 0) changes the code around the line it follows; it is marked, so\n // the symbol that line belongs to isn't credited with code removed after it.\n ranges.push(count === 0 ? [start, start, true] : [start, start + count - 1]);\n hunks.set(file, ranges);\n }\n }\n return hunks;\n}\n\n/** Whether a symbol's lines touch a file's changed lines (a symbol with no lines counts only in a new file). */\nexport function changedInRange(ranges: LineRanges | undefined, start: number | null, end: number | null): boolean {\n if (!ranges) return false;\n if (ranges === \"all\") return true;\n if (start === null) return false;\n const last = end ?? start;\n // A deletion after line N is inside the symbol only when N is before its last line: code\n // removed after a method's closing line is its container's change, not the method's.\n return ranges.some(([a, b, deletion]) => (deletion ? a >= start && a < last : a <= last && b >= start));\n}\n\n/**\n * Files changed between the merge base of `since` and HEAD, and in the working tree, relative to\n * `codebaseRoot`. Files outside the codebase root, and DR's own folders ({@link DR_OWN_DIRS}),\n * are left out.\n *\n * @throws CLIError when the codebase is not in git or `since` is not a commit\n */\nexport async function codeChangesSince(codebaseRoot: string, since: string): Promise<CodeChangesResult> {\n const top = git([\"rev-parse\", \"--show-toplevel\"], codebaseRoot);\n if (!top.ok) throw new CLIError(`The code is not in a git repository: ${codebaseRoot}`, ErrorCategory.USER, [\"Call codebase_extract without since\"]);\n if (since.startsWith(\"-\")) throw new CLIError(`since is not a git ref: ${since}`, ErrorCategory.USER);\n const commit = git([\"rev-parse\", \"--verify\", \"--quiet\", `${since}^{commit}`], codebaseRoot);\n if (!commit.ok || !commit.out.trim()) {\n throw new CLIError(`since is not a commit in this repository: ${since}`, ErrorCategory.USER, [\n \"Pass a branch, tag or commit, e.g. the branch the work started from (main)\",\n ]);\n }\n const mergeBase = git([\"merge-base\", commit.out.trim(), \"HEAD\"], codebaseRoot);\n const base = mergeBase.ok && mergeBase.out.trim() ? mergeBase.out.trim() : commit.out.trim();\n\n const repo = { changed: new Set<string>(), deleted: new Map<string, string | null>() };\n for (const args of [\n [\"diff\", \"--name-status\", \"-z\", \"-M\", base, \"HEAD\"],\n [\"diff\", \"--name-status\", \"-z\", \"-M\", \"HEAD\"],\n ]) {\n const r = git(args, codebaseRoot);\n if (!r.ok) throw new CLIError(`git ${args.join(\" \")} failed: ${r.error}`, ErrorCategory.SYSTEM);\n parseNameStatus(r.out, repo);\n }\n const untracked = git([\"ls-files\", \"--others\", \"--exclude-standard\", \"-z\", \"--full-name\"], top.out.trim());\n const untrackedFiles = untracked.ok ? untracked.out.split(\"\\0\").filter(Boolean) : [];\n for (const f of untrackedFiles) {\n // A file deleted or renamed away in the range and then recreated is there again.\n repo.changed.add(f);\n repo.deleted.delete(f);\n }\n // The changed lines, from the base to the working tree; an untracked file is all new.\n // Only source files count, and never DR's own folders.\n const pathspecs = [\n ...SOURCE_EXTENSIONS.map((ext) => `:(glob,icase)**/*.${ext}`),\n ...DR_OWN_DIRS.map((d) => `:(exclude)${d}`),\n ];\n const diff = git([\"diff\", \"-U0\", \"-M\", \"--no-color\", \"--no-ext-diff\", base, \"--\", ...pathspecs], codebaseRoot);\n const repoHunks = diff.ok ? parseHunks(diff.out) : new Map<string, LineRanges>();\n const hunksError = diff.ok ? undefined : diff.error.split(\"\\n\")[0];\n for (const f of untrackedFiles) repoHunks.set(f, \"all\");\n\n // git names files from the top of the work tree; source references from the codebase root.\n const prefix = toPosix(path.relative(top.out.trim(), codebaseRoot));\n const local = (f: string): string | null => {\n const l = !prefix ? f : f.startsWith(prefix + \"/\") ? f.slice(prefix.length + 1) : null;\n return l && !inDirs(l, DR_OWN_DIRS) ? l : null;\n };\n const changed = new Set<string>();\n for (const f of repo.changed) {\n const l = local(f);\n if (l) changed.add(l);\n }\n const deleted = new Map<string, string | null>();\n for (const [from, to] of repo.deleted) {\n const l = local(from);\n if (l) deleted.set(l, to ? local(to) : null);\n }\n const hunks = new Map<string, LineRanges>();\n for (const [f, ranges] of repoHunks) {\n const l = local(f);\n if (l && changed.has(l)) hunks.set(l, ranges);\n }\n const topDir = top.out.trim();\n const fileAtBase = (file: string): string | null | undefined => {\n const r = git([\"show\", `${base}:${prefix ? `${prefix}/${file}` : file}`], topDir);\n if (r.ok) return r.out;\n // Absent at the base, or a failure git can't explain (too big, git broken): can't tell.\n return /does not exist in|exists on disk, but not in/.test(r.error) ? null : undefined;\n };\n return { base, changed, deleted, hunks, ...(hunksError ? { hunksError } : {}), fileAtBase };\n}\n\n/**\n * The part of a range that is code: changed and deleted files that `keep` accepts (a rename when\n * either path is kept). DR's own folders are left out already.\n */\nexport function scopeChanges(changes: CodeChangesResult, keep: (file: string) => boolean): CodeChangesResult {\n const changed = new Set([...changes.changed].filter(keep));\n const deleted = new Map([...changes.deleted].filter(([from, to]) => keep(from) || (to !== null && keep(to))));\n const hunks = changes.hunks ? new Map([...changes.hunks].filter(([f]) => changed.has(f))) : undefined;\n return { ...changes, changed, deleted, ...(hunks ? { hunks } : {}) };\n}\n\n/**\n * Source files with uncommitted changes (modified, added or untracked) written after `sinceMs`,\n * relative to the codebase root. An index counts as fresh while HEAD hasn't moved, so these are\n * edits the index may not have seen. Empty when git can't say.\n */\nexport async function sourceEditsSince(codebaseRoot: string, sinceMs: number, isSource: (file: string) => boolean): Promise<string[]> {\n const top = git([\"rev-parse\", \"--show-toplevel\"], codebaseRoot);\n if (!top.ok) return [];\n const status = git([\"status\", \"--porcelain\", \"-z\", \"--untracked-files=all\", \"--\", \".\"], codebaseRoot);\n if (!status.ok) return [];\n const prefix = toPosix(path.relative(top.out.trim(), codebaseRoot));\n const out: string[] = [];\n const parts = status.out.split(\"\\0\");\n for (let i = 0; i < parts.length; i++) {\n const entry = parts[i];\n if (entry.length < 4) continue;\n const code = entry.slice(0, 2);\n const file = entry.slice(3);\n if (code[0] === \"R\" || code[0] === \"C\") i++; // the next field is the old path\n if (code.includes(\"D\")) continue;\n const local = prefix ? (file.startsWith(prefix + \"/\") ? file.slice(prefix.length + 1) : null) : file;\n if (!local || !isSource(local)) continue;\n try {\n if (statSync(path.join(codebaseRoot, local)).mtimeMs > sinceMs) out.push(local);\n } catch {\n // gone since git listed it\n }\n }\n return out.sort();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -5,8 +5,10 @@ function edgePreferences(m) {
|
|
|
5
5
|
return Array.isArray(listed) && listed.length ? listed.map((p, i) => {
|
|
6
6
|
if (typeof p == "string")
|
|
7
7
|
return { predicate: p, invert: !1 };
|
|
8
|
-
if (p && typeof p == "object" && typeof p.predicate == "string")
|
|
9
|
-
|
|
8
|
+
if (p && typeof p == "object" && typeof p.predicate == "string") {
|
|
9
|
+
const o = p, types = (v) => Array.isArray(v) ? v.filter((t) => typeof t == "string") : void 0, from = types(o.from_types), to = types(o.to_types);
|
|
10
|
+
return { predicate: o.predicate, invert: o.invert === !0, ...from ? { from_types: from } : {}, ...to ? { to_types: to } : {} };
|
|
11
|
+
}
|
|
10
12
|
throw new Error(`Edge mapping ${m.cbm_edge}: conditions.predicates[${i}] must be a predicate name or {predicate, invert}`);
|
|
11
13
|
}) : m.dr_relationship ? [{ predicate: m.dr_relationship, invert: m.directionality_transform === "invert" }] : [];
|
|
12
14
|
}
|
|
@@ -21,6 +23,8 @@ function relationshipFor(m, fromType, toType, hasTriple) {
|
|
|
21
23
|
if (!edgeAllowed(m, fromType, toType))
|
|
22
24
|
return null;
|
|
23
25
|
for (const pref of edgePreferences(m)) {
|
|
26
|
+
if (!typeAllowed(pref.from_types, fromType) || !typeAllowed(pref.to_types, toType))
|
|
27
|
+
continue;
|
|
24
28
|
const [source, destination] = pref.invert ? [toType, fromType] : [fromType, toType], id = `${source}.${pref.predicate}.${destination}`;
|
|
25
29
|
if (hasTriple(id))
|
|
26
30
|
return { id, source, predicate: pref.predicate, destination, inverted: pref.invert };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/analyzers/edge-relationships.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Turn a CBM edge between two classified symbols into a DR relationship.\n *\n * An edge mapping (spec/analyzers/<name>/edge-mapping.json) that creates relationships\n * (`usage: \"verification_and_mapping\"`) lists, in `conditions.predicates`, the predicates that can\n * express it in order of preference, each optionally inverted (the DR relationship runs against\n * the CBM edge). The relationship is the first catalog triple\n * `{source type}.{predicate}.{destination type}` that exists for the two element types the ends\n * were classified as; the relationship catalog stays the authority on which triples are valid.\n * Without `conditions.predicates` the mapping's own `dr_relationship` and\n * `directionality_transform` are the only preference.\n *\n * `conditions.from_types` / `conditions.to_types`, when present, restrict the element types the\n * CBM edge's source and target may have (\"layer.type\", or \"layer.*\" for any type of a layer); an\n * edge between other types creates no relationship, whatever the catalog holds.\n *\n * Example: CALLS prefers depends-on, then uses, then accesses. A call from an\n * application.applicationfunction to another becomes\n * application.applicationfunction.depends-on.application.applicationfunction; a call from an\n * application.applicationservice to an application.dataobject has no depends-on or uses triple and\n * becomes application.applicationservice.accesses.application.dataobject.\n *\n * Callers pass only edges between two different accepted elements, and should collapse duplicates\n * with dedupeRelationships: CALLS, CALL_REFERENCE and USAGE can all link the same pair.\n */\nimport type { AnalyzerEdgeMapping } from \"./types.js\";\n\nexport interface EdgePreference {\n predicate: string;\n /** true when the DR relationship runs from the CBM edge's target to its source */\n invert: boolean;\n}\n\nexport interface EdgeRelationship {\n id: string;\n source: string;\n predicate: string;\n destination: string;\n inverted: boolean;\n}\n\n/** The predicates an edge mapping may use, in order of preference. */\nexport function edgePreferences(m: AnalyzerEdgeMapping): EdgePreference[] {\n if (m.usage !== \"verification_and_mapping\") return [];\n const listed = (m.conditions as { predicates?: unknown } | undefined)?.predicates;\n if (Array.isArray(listed) && listed.length) {\n return listed.map((p, i) => {\n if (typeof p === \"string\") return { predicate: p, invert: false };\n if (p && typeof p === \"object\" && typeof (p as { predicate?: unknown }).predicate === \"string\") {\n
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * Turn a CBM edge between two classified symbols into a DR relationship.\n *\n * An edge mapping (spec/analyzers/<name>/edge-mapping.json) that creates relationships\n * (`usage: \"verification_and_mapping\"`) lists, in `conditions.predicates`, the predicates that can\n * express it in order of preference, each optionally inverted (the DR relationship runs against\n * the CBM edge). The relationship is the first catalog triple\n * `{source type}.{predicate}.{destination type}` that exists for the two element types the ends\n * were classified as; the relationship catalog stays the authority on which triples are valid.\n * Without `conditions.predicates` the mapping's own `dr_relationship` and\n * `directionality_transform` are the only preference. A preference may carry its own `from_types`\n * / `to_types`: it applies only between those types (CALLS from a test case is `tests`).\n *\n * `conditions.from_types` / `conditions.to_types`, when present, restrict the element types the\n * CBM edge's source and target may have (\"layer.type\", or \"layer.*\" for any type of a layer); an\n * edge between other types creates no relationship, whatever the catalog holds.\n *\n * Example: CALLS prefers depends-on, then uses, then accesses. A call from an\n * application.applicationfunction to another becomes\n * application.applicationfunction.depends-on.application.applicationfunction; a call from an\n * application.applicationservice to an application.dataobject has no depends-on or uses triple and\n * becomes application.applicationservice.accesses.application.dataobject.\n *\n * Callers pass only edges between two different accepted elements, and should collapse duplicates\n * with dedupeRelationships: CALLS, CALL_REFERENCE and USAGE can all link the same pair.\n */\nimport type { AnalyzerEdgeMapping } from \"./types.js\";\n\nexport interface EdgePreference {\n predicate: string;\n /** true when the DR relationship runs from the CBM edge's target to its source */\n invert: boolean;\n /** Only when the CBM edge's source is one of these types (\"layer.type\" or \"layer.*\"). */\n from_types?: string[];\n /** Only when the CBM edge's target is one of these types. */\n to_types?: string[];\n}\n\nexport interface EdgeRelationship {\n id: string;\n source: string;\n predicate: string;\n destination: string;\n inverted: boolean;\n}\n\n/** The predicates an edge mapping may use, in order of preference. */\nexport function edgePreferences(m: AnalyzerEdgeMapping): EdgePreference[] {\n if (m.usage !== \"verification_and_mapping\") return [];\n const listed = (m.conditions as { predicates?: unknown } | undefined)?.predicates;\n if (Array.isArray(listed) && listed.length) {\n return listed.map((p, i) => {\n if (typeof p === \"string\") return { predicate: p, invert: false };\n if (p && typeof p === \"object\" && typeof (p as { predicate?: unknown }).predicate === \"string\") {\n const o = p as { predicate: string; invert?: unknown; from_types?: unknown; to_types?: unknown };\n const types = (v: unknown) => (Array.isArray(v) ? v.filter((t): t is string => typeof t === \"string\") : undefined);\n const from = types(o.from_types);\n const to = types(o.to_types);\n return { predicate: o.predicate, invert: o.invert === true, ...(from ? { from_types: from } : {}), ...(to ? { to_types: to } : {}) };\n }\n throw new Error(`Edge mapping ${m.cbm_edge}: conditions.predicates[${i}] must be a predicate name or {predicate, invert}`);\n });\n }\n return m.dr_relationship ? [{ predicate: m.dr_relationship, invert: m.directionality_transform === \"invert\" }] : [];\n}\n\nfunction typeAllowed(allowed: unknown, type: string): boolean {\n if (!Array.isArray(allowed) || !allowed.length) return true;\n return allowed.some((a) => typeof a === \"string\" && (a === type || (a.endsWith(\".*\") && type.startsWith(a.slice(0, -1)))));\n}\n\n/** Whether an edge mapping allows an edge between these two element types (CBM direction). */\nexport function edgeAllowed(m: AnalyzerEdgeMapping, fromType: string, toType: string): boolean {\n const c = (m.conditions ?? {}) as { from_types?: unknown; to_types?: unknown };\n return typeAllowed(c.from_types, fromType) && typeAllowed(c.to_types, toType);\n}\n\n/**\n * The relationship a CBM edge `from \u2192 to` becomes, given the DR element types its ends were\n * classified as, or null when no preferred predicate has a catalog triple for that pair.\n */\nexport function relationshipFor(\n m: AnalyzerEdgeMapping,\n fromType: string,\n toType: string,\n hasTriple: (id: string) => boolean\n): EdgeRelationship | null {\n if (!edgeAllowed(m, fromType, toType)) return null;\n for (const pref of edgePreferences(m)) {\n if (!typeAllowed(pref.from_types, fromType) || !typeAllowed(pref.to_types, toType)) continue;\n const [source, destination] = pref.invert ? [toType, fromType] : [fromType, toType];\n const id = `${source}.${pref.predicate}.${destination}`;\n if (hasTriple(id)) return { id, source, predicate: pref.predicate, destination, inverted: pref.invert };\n }\n return null;\n}\n\n/**\n * One relationship per (source element, relationship id, destination element), keeping the first.\n * `items` carry the element ids of the two ends alongside the relationship.\n */\nexport function dedupeRelationships<T extends { sourceId: string; destinationId: string; relationship: EdgeRelationship }>(items: T[]): T[] {\n const seen = new Set<string>();\n return items.filter((x) => {\n if (x.sourceId === x.destinationId) return false;\n const key = `${x.sourceId}|${x.relationship.id}|${x.destinationId}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n"],
|
|
5
|
+
"mappings": "AA+CM,SAAU,gBAAgB,GAAsB;AACpD,MAAI,EAAE,UAAU;AAA4B,WAAO,CAAA;AACnD,QAAM,SAAU,EAAE,YAAqD;AACvE,SAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,SAC3B,OAAO,IAAI,CAAC,GAAG,MAAK;AACzB,QAAI,OAAO,KAAM;AAAU,aAAO,EAAE,WAAW,GAAG,QAAQ,GAAK;AAC/D,QAAI,KAAK,OAAO,KAAM,YAAY,OAAQ,EAA8B,aAAc,UAAU;AAC9F,YAAM,IAAI,GACJ,QAAQ,CAAC,MAAgB,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,MAAmB,OAAO,KAAM,QAAQ,IAAI,QAClG,OAAO,MAAM,EAAE,UAAU,GACzB,KAAK,MAAM,EAAE,QAAQ;AAC3B,aAAO,EAAE,WAAW,EAAE,WAAW,QAAQ,EAAE,WAAW,IAAM,GAAI,OAAO,EAAE,YAAY,KAAI,IAAK,CAAA,GAAK,GAAI,KAAK,EAAE,UAAU,GAAE,IAAK,CAAA,EAAG;IACpI;AACA,UAAM,IAAI,MAAM,gBAAgB,EAAE,QAAQ,2BAA2B,CAAC,mDAAmD;EAC3H,CAAC,IAEI,EAAE,kBAAkB,CAAC,EAAE,WAAW,EAAE,iBAAiB,QAAQ,EAAE,6BAA6B,SAAQ,CAAE,IAAI,CAAA;AACnH;AAEA,SAAS,YAAY,SAAkB,MAAY;AACjD,SAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,CAAC,QAAQ,SAAe,KAChD,QAAQ,KAAK,CAAC,MAAM,OAAO,KAAM,aAAa,MAAM,QAAS,EAAE,SAAS,IAAI,KAAK,KAAK,WAAW,EAAE,MAAM,GAAG,EAAE,CAAC,EAAG;AAC3H;AAGM,SAAU,YAAY,GAAwB,UAAkB,QAAc;AAClF,QAAM,IAAK,EAAE,cAAc,CAAA;AAC3B,SAAO,YAAY,EAAE,YAAY,QAAQ,KAAK,YAAY,EAAE,UAAU,MAAM;AAC9E;AAMM,SAAU,gBACd,GACA,UACA,QACA,WAAkC;AAElC,MAAI,CAAC,YAAY,GAAG,UAAU,MAAM;AAAG,WAAO;AAC9C,aAAW,QAAQ,gBAAgB,CAAC,GAAG;AACrC,QAAI,CAAC,YAAY,KAAK,YAAY,QAAQ,KAAK,CAAC,YAAY,KAAK,UAAU,MAAM;AAAG;AACpF,UAAM,CAAC,QAAQ,WAAW,IAAI,KAAK,SAAS,CAAC,QAAQ,QAAQ,IAAI,CAAC,UAAU,MAAM,GAC5E,KAAK,GAAG,MAAM,IAAI,KAAK,SAAS,IAAI,WAAW;AACrD,QAAI,UAAU,EAAE;AAAG,aAAO,EAAE,IAAI,QAAQ,WAAW,KAAK,WAAW,aAAa,UAAU,KAAK,OAAM;EACvG;AACA,SAAO;AACT;AAMM,SAAU,oBAA2G,OAAU;AACnI,QAAM,OAAO,oBAAI,IAAG;AACpB,SAAO,MAAM,OAAO,CAAC,MAAK;AACxB,QAAI,EAAE,aAAa,EAAE;AAAe,aAAO;AAC3C,UAAM,MAAM,GAAG,EAAE,QAAQ,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa;AACjE,WAAI,KAAK,IAAI,GAAG,IAAU,MAC1B,KAAK,IAAI,GAAG,GACL;EACT,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|