@documentation-robotics/cli 0.2.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 +46 -16
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- package/dist/analyzers/active-analyzer.js +62 -0
- package/dist/analyzers/active-analyzer.js.map +7 -0
- package/dist/analyzers/call-graph-utils.js +2 -2
- package/dist/analyzers/call-graph-utils.js.map +2 -2
- package/dist/analyzers/cards/build.js +191 -0
- package/dist/analyzers/cards/build.js.map +7 -0
- package/dist/analyzers/cards/classify.js +42 -0
- package/dist/analyzers/cards/classify.js.map +7 -0
- package/dist/analyzers/cards/edge-index.js +44 -0
- package/dist/analyzers/cards/edge-index.js.map +7 -0
- package/dist/analyzers/cards/extract.js +138 -0
- package/dist/analyzers/cards/extract.js.map +7 -0
- package/dist/analyzers/cards/facts.js +46 -0
- package/dist/analyzers/cards/facts.js.map +7 -0
- package/dist/analyzers/cards/graph.js +42 -0
- package/dist/analyzers/cards/graph.js.map +7 -0
- package/dist/analyzers/cards/index.js +8 -0
- package/dist/analyzers/cards/index.js.map +7 -0
- package/dist/analyzers/cards/routes.js +96 -0
- package/dist/analyzers/cards/routes.js.map +7 -0
- package/dist/analyzers/cards/tier0.js +18 -0
- package/dist/analyzers/cards/tier0.js.map +7 -0
- package/dist/analyzers/cards/types.js +1 -0
- package/dist/analyzers/cards/types.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +270 -174
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +342 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/code-changes.js +159 -0
- package/dist/analyzers/code-changes.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +49 -0
- package/dist/analyzers/edge-relationships.js.map +7 -0
- 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/analyzers/mapping-loader.js +9 -0
- package/dist/analyzers/mapping-loader.js.map +2 -2
- package/dist/analyzers/tier0-rules.js +153 -0
- package/dist/analyzers/tier0-rules.js.map +7 -0
- package/dist/analyzers/types.js.map +1 -1
- package/dist/audit/relationships/ai/prompts.js +10 -10
- package/dist/audit/relationships/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/analysis/layer-templates.js +52 -52
- package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
- package/dist/cli.js +23 -11
- package/dist/cli.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +31 -1
- package/dist/coding-agents/default-permissions.js.map +2 -2
- package/dist/coding-agents/drbot-mcp.js +269 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +7 -4
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +143 -40
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/changeset-verify.js +63 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +80 -29
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +10 -4
- package/dist/commands/delete.js.map +2 -2
- package/dist/commands/farm.js +176 -60
- package/dist/commands/farm.js.map +2 -2
- package/dist/commands/init.js +9 -26
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +40 -21
- package/dist/commands/mcp.js.map +3 -3
- package/dist/commands/relationship.js +208 -37
- 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 +52 -32
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -103
- package/dist/commands/upgrade.js.map +3 -3
- 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 +34 -15
- 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 +34 -0
- package/dist/core/catalog-migration.js.map +7 -0
- package/dist/core/changeset-analysis.js +421 -0
- package/dist/core/changeset-analysis.js.map +7 -0
- package/dist/core/changeset-base-index.js +160 -0
- package/dist/core/changeset-base-index.js.map +7 -0
- package/dist/core/changeset-brief-compact.js +256 -0
- package/dist/core/changeset-brief-compact.js.map +7 -0
- package/dist/core/changeset-brief-delta.js +102 -0
- package/dist/core/changeset-brief-delta.js.map +7 -0
- package/dist/core/changeset-graph.js +281 -0
- package/dist/core/changeset-graph.js.map +7 -0
- package/dist/core/changeset-only-guard.js +32 -0
- package/dist/core/changeset-only-guard.js.map +7 -0
- package/dist/core/changeset-summary.js +68 -0
- package/dist/core/changeset-summary.js.map +7 -0
- package/dist/core/changeset-validator.js +66 -20
- package/dist/core/changeset-validator.js.map +2 -2
- package/dist/core/changeset-verify.js +848 -0
- package/dist/core/changeset-verify.js.map +7 -0
- package/dist/core/changeset.js +56 -9
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/element-type-migration.js +145 -0
- package/dist/core/element-type-migration.js.map +7 -0
- package/dist/core/extraction-observability.js +161 -0
- package/dist/core/extraction-observability.js.map +7 -0
- package/dist/core/farm-manifest.js +12 -6
- package/dist/core/farm-manifest.js.map +2 -2
- package/dist/core/farm-sync-engine.js +1 -1
- package/dist/core/farm-sync-engine.js.map +2 -2
- package/dist/core/farm-sync-state.js +10 -2
- package/dist/core/farm-sync-state.js.map +2 -2
- package/dist/core/migration-registry.js +88 -47
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model-upgrade.js +102 -0
- package/dist/core/model-upgrade.js.map +7 -0
- package/dist/core/model.js +82 -30
- package/dist/core/model.js.map +2 -2
- package/dist/core/mutation-handler.js +71 -6
- package/dist/core/mutation-handler.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-migration-table.js +6639 -0
- package/dist/core/relationship-migration-table.js.map +7 -0
- package/dist/core/relationship-migration.js +272 -0
- package/dist/core/relationship-migration.js.map +7 -0
- package/dist/core/relationship-prune.js +49 -0
- package/dist/core/relationship-prune.js.map +7 -0
- package/dist/core/relationship-staging.js +161 -0
- package/dist/core/relationship-staging.js.map +7 -0
- package/dist/core/relationships.js +26 -4
- package/dist/core/relationships.js.map +2 -2
- package/dist/core/report-data-model.js +7 -13
- package/dist/core/report-data-model.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +145 -18
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staged-source-reference.js +28 -0
- package/dist/core/staged-source-reference.js.map +7 -0
- package/dist/core/staging-area.js +166 -24
- 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/core/virtual-projection.js +13 -5
- package/dist/core/virtual-projection.js.map +2 -2
- package/dist/export/archimate-exporter.js +1 -0
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/generated/compiled-validators.js +2 -2
- package/dist/generated/compiled-validators.js.map +2 -2
- package/dist/generated/layer-registry.js +16 -16
- package/dist/generated/layer-registry.js.map +2 -2
- package/dist/generated/node-types.js +186 -168
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +2468 -7913
- 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 +30 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +8 -2
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
- package/dist/integrations/claude_code/commands/dr-map.md +20 -1
- package/dist/integrations/claude_code/commands/dr-plan.md +66 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
- package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +47 -26
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +68 -2
- 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_01_motivation_layer/SKILL.md +93 -72
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
- package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
- package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
- package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
- package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
- package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
- package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
- package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
- package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
- package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
- package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +95 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +178 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +8 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +6 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +3 -1
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
- package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
- package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
- package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
- package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
- package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
- package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
- package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
- package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
- package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
- package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +225 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +177 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/hash-utils.js.map +2 -2
- package/dist/mcp/api-key-manager.js +14 -1
- package/dist/mcp/api-key-manager.js.map +2 -2
- package/dist/mcp/changeset-only.js +45 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- 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 +55 -5
- package/dist/mcp/tool-registry.js.map +2 -2
- package/dist/mcp/tools/changeset-analysis-tools.js +98 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- 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 +260 -0
- package/dist/mcp/tools/changeset-editing.js.map +7 -0
- package/dist/mcp/tools/changeset-lifecycle.js +83 -0
- package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
- package/dist/mcp/tools/changeset-shared.js +31 -0
- package/dist/mcp/tools/changeset-shared.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +17 -1
- package/dist/mcp/tools/changeset-show.js.map +2 -2
- package/dist/mcp/tools/changeset-verify.js +49 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- 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 +34 -1
- package/dist/mcp/tools/index.js.map +2 -2
- package/dist/mcp/tools/model-add.js +191 -74
- package/dist/mcp/tools/model-add.js.map +2 -2
- package/dist/mcp/tools/model-delete.js +2 -2
- package/dist/mcp/tools/model-delete.js.map +2 -2
- package/dist/mcp/tools/model-search.js +14 -0
- package/dist/mcp/tools/model-search.js.map +2 -2
- package/dist/mcp/tools/model-show.js +29 -2
- package/dist/mcp/tools/model-show.js.map +2 -2
- package/dist/mcp/tools/model-trace.js +93 -30
- package/dist/mcp/tools/model-trace.js.map +2 -2
- package/dist/mcp/tools/model-update.js +46 -26
- 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 +167 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- 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 +35 -4
- package/dist/mcp/tools/shared.js.map +2 -2
- package/dist/mcp/tools/spec-type.js +90 -0
- package/dist/mcp/tools/spec-type.js.map +7 -0
- 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 +1200 -70
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1029 -1658
- package/dist/schemas/bundled/apm.json +499 -734
- package/dist/schemas/bundled/application.json +735 -371
- package/dist/schemas/bundled/base.json +3 -220
- package/dist/schemas/bundled/business.json +452 -366
- package/dist/schemas/bundled/data-model.json +395 -411
- package/dist/schemas/bundled/data-store.json +334 -558
- package/dist/schemas/bundled/manifest.json +18 -18
- package/dist/schemas/bundled/motivation.json +349 -368
- package/dist/schemas/bundled/navigation.json +415 -731
- package/dist/schemas/bundled/product.json +325 -159
- package/dist/schemas/bundled/security.json +864 -1232
- package/dist/schemas/bundled/technology.json +574 -703
- package/dist/schemas/bundled/testing.json +865 -1646
- package/dist/schemas/bundled/ux.json +739 -1352
- package/dist/server/schemas.js +8 -0
- package/dist/server/schemas.js.map +2 -2
- package/dist/server/server.js +686 -231
- 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/server-entry.js +2 -1
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/ansi-suppressor.js +6 -8
- package/dist/utils/ansi-suppressor.js.map +2 -2
- package/dist/utils/attribute-patch.js +41 -0
- package/dist/utils/attribute-patch.js.map +7 -0
- package/dist/utils/error-hints.js +29 -0
- package/dist/utils/error-hints.js.map +7 -0
- package/dist/utils/errors.js +9 -3
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/id-generator.js +5 -2
- package/dist/utils/id-generator.js.map +2 -2
- package/dist/utils/project-paths.js +26 -1
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/source-reference.js +64 -31
- 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/composed-reference-validator.js +5 -27
- package/dist/validators/composed-reference-validator.js.map +2 -2
- package/dist/validators/model-warnings.js +28 -0
- package/dist/validators/model-warnings.js.map +7 -0
- package/dist/validators/reference-validator.js +0 -7
- package/dist/validators/reference-validator.js.map +2 -2
- package/dist/validators/relationship-schema-validator.js +3 -3
- package/dist/validators/relationship-schema-validator.js.map +2 -2
- package/dist/validators/schema-validator.js +27 -9
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/types.js.map +1 -1
- 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 +2 -1
- package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
- package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { spawnSync } from "child_process";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { readFile } from "fs/promises";
|
|
3
|
+
import { readFile, writeFile } from "fs/promises";
|
|
4
4
|
import { CLIError, ErrorCategory, handleWarning } from "../utils/errors.js";
|
|
5
5
|
import { VALID_HTTP_METHODS } from "./types.js";
|
|
6
6
|
import { StdioClient } from "./stdio-client.js";
|
|
7
7
|
import { readIndexMeta, writeIndexMeta } from "./session-state.js";
|
|
8
8
|
import { getCliVersion } from "./version.js";
|
|
9
9
|
import { StagingAreaManager } from "../core/staging-area.js";
|
|
10
|
+
import { DR_OWN_DIRS } from "./code-changes.js";
|
|
10
11
|
import { VerifyEngine } from "./verify-engine.js";
|
|
11
12
|
import { clampDepth, shapeCallGraphNode, determineEdgeType } from "./call-graph-utils.js";
|
|
12
13
|
import { evaluateHeuristic, capConfidence, matchPattern, getKnownHeuristicNames } from "./heuristic-utils.js";
|
|
13
14
|
import { toKebabCase } from "../utils/id-generator.js";
|
|
14
|
-
|
|
15
|
+
import { CbmTools, CbmToolError, ROUTE_HANDLERS_QUERY, attachRouteHandlers, nodeColumns, nodesFromQuery, relativeToRoot, routeHandlersFromQuery } from "./cbm-responses.js";
|
|
16
|
+
import { cbmCardGraph } from "./cards/graph.js";
|
|
17
|
+
import { classifyTier0, defaultLabels } from "./cards/classify.js";
|
|
18
|
+
import { CARD_FIELDS } from "./cards/build.js";
|
|
19
|
+
import { tier0RulesFrom } from "./tier0-rules.js";
|
|
20
|
+
import { collectExtraction, extractionConfigFrom } from "./cards/extract.js";
|
|
21
|
+
const EXTRACT_CALL_TIMEOUT_MS = 12e4, SOURCE_CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs|vue|svelte|py|go|rb|java|rs|php|cs|cpp|c|h|kt|swift|sh|bash)$/i;
|
|
15
22
|
function isLikelyHttpRoute(qualifiedName) {
|
|
16
23
|
const httpPath = qualifiedName.replace(/^__route__[A-Z]+__/, "");
|
|
17
24
|
return !SOURCE_CODE_EXT_RE.test(httpPath) && !httpPath.startsWith("/.dr/") && !httpPath.startsWith("/schemas/");
|
|
@@ -24,21 +31,17 @@ function inferHttpPathFromFunctionName(name) {
|
|
|
24
31
|
const stripped = name.replace(/^(fetch|get|list|create|add|update|put|delete|remove|patch|merge|load|save|replace|post|submit|send|destroy)/i, ""), segment = toKebabCase(stripped || name);
|
|
25
32
|
return segment ? `/${segment}` : "/";
|
|
26
33
|
}
|
|
34
|
+
const SERVICE_SOURCE_TYPES = /* @__PURE__ */ new Set(["applicationfunction", "applicationcomponent", "applicationservice"]);
|
|
27
35
|
class CbmAnalyzer {
|
|
28
36
|
mapper;
|
|
29
37
|
testCodePatternWarned = !1;
|
|
30
38
|
warnedHeuristics = /* @__PURE__ */ new Set();
|
|
31
39
|
/**
|
|
32
|
-
*
|
|
40
|
+
* Test code detection when the mapping has no test_code_exclusion rule: the same test-file
|
|
41
|
+
* pattern as the mapping's rule and its tier0_test_file rule.
|
|
33
42
|
*/
|
|
34
43
|
static DEFAULT_TEST_PATTERNS = [
|
|
35
|
-
|
|
36
|
-
/\.spec\./,
|
|
37
|
-
/\/tests?\//,
|
|
38
|
-
/\/__tests__\//,
|
|
39
|
-
/\/test-/,
|
|
40
|
-
/\/fixtures?\//,
|
|
41
|
-
/\/__fixtures__\//
|
|
44
|
+
/((^|\/)(tests?|__tests__|__fixtures__|fixtures?)\/|(^|\/)test_[^/]*\.py$|(^|\/)conftest\.py$|_test\.(go|py)$|\.(test|spec)\.[cm]?[jt]sx?$|Tests?\.(java|kt)$)/
|
|
42
45
|
];
|
|
43
46
|
constructor(mapper) {
|
|
44
47
|
this.mapper = mapper;
|
|
@@ -171,10 +174,11 @@ class CbmAnalyzer {
|
|
|
171
174
|
* Checks if the project is indexed and whether the index is fresh relative to git HEAD.
|
|
172
175
|
*
|
|
173
176
|
* @param projectRoot Absolute path to the project root
|
|
177
|
+
* @param options Optional codebase configuration
|
|
174
178
|
* @returns Current status including detection, index state, and freshness
|
|
175
179
|
*/
|
|
176
|
-
async status(projectRoot) {
|
|
177
|
-
const detected = await this.detect(projectRoot), analyzerName = this.mapper.getAnalyzerName(), indexMeta = await readIndexMeta(projectRoot, analyzerName);
|
|
180
|
+
async status(projectRoot, options) {
|
|
181
|
+
const detected = await this.detect(projectRoot), effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, analyzerName = this.mapper.getAnalyzerName(), indexMeta = await readIndexMeta(projectRoot, analyzerName);
|
|
178
182
|
if (!(indexMeta !== null))
|
|
179
183
|
return {
|
|
180
184
|
detected,
|
|
@@ -184,7 +188,7 @@ class CbmAnalyzer {
|
|
|
184
188
|
let fresh = !1;
|
|
185
189
|
try {
|
|
186
190
|
const currentHeadResult = spawnSync("git", ["rev-parse", "HEAD"], {
|
|
187
|
-
cwd:
|
|
191
|
+
cwd: effectiveCodebaseRoot,
|
|
188
192
|
stdio: "pipe",
|
|
189
193
|
encoding: "utf-8"
|
|
190
194
|
});
|
|
@@ -192,7 +196,11 @@ class CbmAnalyzer {
|
|
|
192
196
|
fresh = currentHeadResult.stdout.trim() === indexMeta.git_head;
|
|
193
197
|
else {
|
|
194
198
|
const gitError = currentHeadResult.stderr?.trim() || "git returned non-zero status";
|
|
195
|
-
handleWarning("Failed to check git freshness", [
|
|
199
|
+
options?.codebaseRoot ? handleWarning("Failed to check git freshness in codebase root", [
|
|
200
|
+
`Codebase root: ${effectiveCodebaseRoot}`,
|
|
201
|
+
`Git error: ${gitError}`,
|
|
202
|
+
"Index freshness cannot be determined - treating as stale"
|
|
203
|
+
]) : handleWarning("Failed to check git freshness", [
|
|
196
204
|
`Git error: ${gitError}`,
|
|
197
205
|
"Index freshness cannot be determined - treating as stale"
|
|
198
206
|
]), fresh = !1;
|
|
@@ -221,11 +229,11 @@ class CbmAnalyzer {
|
|
|
221
229
|
* Skips indexing if the index is fresh (git HEAD matches) unless --force is set.
|
|
222
230
|
*
|
|
223
231
|
* @param projectRoot Absolute path to the project root
|
|
224
|
-
* @param options Optional configuration
|
|
232
|
+
* @param options Optional configuration including force flag and codebase root
|
|
225
233
|
* @returns Index result with counts and metadata
|
|
226
234
|
*/
|
|
227
235
|
async index(projectRoot, options) {
|
|
228
|
-
const status = await this.status(projectRoot);
|
|
236
|
+
const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
|
|
229
237
|
if (status.indexed && status.fresh && !options?.force) {
|
|
230
238
|
handleWarning("Index is fresh. Use --force to reindex.", [
|
|
231
239
|
`Last indexed: ${status.last_indexed}`
|
|
@@ -252,10 +260,8 @@ class CbmAnalyzer {
|
|
|
252
260
|
name: "dr-cli",
|
|
253
261
|
version
|
|
254
262
|
});
|
|
255
|
-
const
|
|
256
|
-
if (
|
|
257
|
-
throw new CLIError("Invalid response from list_projects", ErrorCategory.SYSTEM, ["Ensure codebase-memory-mcp is properly installed and functional"]);
|
|
258
|
-
if ((Array.isArray(listProjectsResponse.projects) ? listProjectsResponse.projects : []).some((p) => p.root_path === projectRoot) && status.fresh && !options?.force) {
|
|
263
|
+
const tools = new CbmTools(client);
|
|
264
|
+
if ((await tools.listProjects()).some((p) => p.root_path === effectiveCodebaseRoot) && status.fresh && !options?.force) {
|
|
259
265
|
if (handleWarning("Project already indexed in CBM backend with fresh index.", ["Use --force to re-index the project"]), !status.index_meta)
|
|
260
266
|
throw new CLIError("Project exists in CBM backend but local metadata is missing", ErrorCategory.VALIDATION, [
|
|
261
267
|
"Use --force to re-index and regenerate metadata",
|
|
@@ -270,13 +276,14 @@ class CbmAnalyzer {
|
|
|
270
276
|
};
|
|
271
277
|
}
|
|
272
278
|
const headResult = spawnSync("git", ["rev-parse", "HEAD"], {
|
|
273
|
-
cwd:
|
|
279
|
+
cwd: effectiveCodebaseRoot,
|
|
274
280
|
stdio: "pipe",
|
|
275
281
|
encoding: "utf-8"
|
|
276
282
|
});
|
|
277
283
|
if (headResult.status !== 0)
|
|
278
284
|
throw new CLIError("Failed to get git HEAD", ErrorCategory.SYSTEM, [
|
|
279
|
-
"Ensure the
|
|
285
|
+
"Ensure the codebase root is a git repository",
|
|
286
|
+
`Codebase root: ${effectiveCodebaseRoot}`,
|
|
280
287
|
"Verify git is installed and functional",
|
|
281
288
|
`Error: ${headResult.stderr?.trim() || "unknown error"}`
|
|
282
289
|
]);
|
|
@@ -288,12 +295,14 @@ class CbmAnalyzer {
|
|
|
288
295
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
289
296
|
handleWarning("Failed to read active changeset", [`Error: ${errorMsg}`, "Continuing with indexing"]);
|
|
290
297
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
298
|
+
try {
|
|
299
|
+
const ignore = await ensureCbmIgnore(effectiveCodebaseRoot, projectRoot);
|
|
300
|
+
ignore.added.length > 0 && console.log(`Added ${ignore.added.join(", ")} to ${ignore.path} so the index leaves DR's own files out`);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
303
|
+
handleWarning("Could not update .cbmignore; DR's own files may be indexed", [`Error: ${errorMsg}`]);
|
|
304
|
+
}
|
|
305
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString(), indexResponse = await tools.indexRepository(effectiveCodebaseRoot, options?.timeoutMs ? { timeout: options.timeoutMs } : void 0), nodeCount = typeof indexResponse.nodes == "number" ? indexResponse.nodes : 0, edgeCount = typeof indexResponse.edges == "number" ? indexResponse.edges : 0;
|
|
297
306
|
typeof indexResponse.nodes != "number" && handleWarning("Invalid node count from index_repository", [
|
|
298
307
|
`Expected number, got ${typeof indexResponse.nodes}: ${String(indexResponse.nodes)}`,
|
|
299
308
|
"Defaulting to 0 - index may be incomplete or corrupted"
|
|
@@ -318,26 +327,25 @@ class CbmAnalyzer {
|
|
|
318
327
|
}
|
|
319
328
|
}
|
|
320
329
|
/**
|
|
321
|
-
*
|
|
330
|
+
* Fetch Route nodes with their HANDLES-edge handlers attached.
|
|
322
331
|
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
332
|
+
* CBM Route nodes carry only `{ method, source }` and often no file (every fastapi route,
|
|
333
|
+
* for example); the handler function, its symbol, lines and file come from HANDLES edges.
|
|
334
|
+
* If the handler query fails the routes are returned as they are.
|
|
325
335
|
*
|
|
326
336
|
* @private
|
|
327
|
-
* @param response Raw response from search_graph MCP tool
|
|
328
|
-
* @returns Typed nodes array (may be empty if no results found)
|
|
329
|
-
* @throws CLIError if response structure is invalid
|
|
330
337
|
*/
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
338
|
+
async fetchRoutes(tools, projectName, label) {
|
|
339
|
+
const routes = await tools.searchGraph({ label, project: projectName });
|
|
340
|
+
try {
|
|
341
|
+
const handlers = routeHandlersFromQuery(await tools.queryGraph(ROUTE_HANDLERS_QUERY, projectName));
|
|
342
|
+
return attachRouteHandlers(routes, handlers);
|
|
343
|
+
} catch (error) {
|
|
344
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
345
|
+
return handleWarning("Could not read route handlers (HANDLES edges); routes lack handler details", [
|
|
346
|
+
`Error: ${errorMsg}`
|
|
347
|
+
]), routes;
|
|
348
|
+
}
|
|
341
349
|
}
|
|
342
350
|
/**
|
|
343
351
|
* Resolve the internal project name used by codebase-memory-mcp for a given root path.
|
|
@@ -346,12 +354,12 @@ class CbmAnalyzer {
|
|
|
346
354
|
* query tools require the internal `name`, not the absolute `root_path`.
|
|
347
355
|
* Falls back to the absolute path with a warning if the project is not found.
|
|
348
356
|
*/
|
|
349
|
-
async resolveProjectName(client,
|
|
350
|
-
const
|
|
357
|
+
async resolveProjectName(client, codebaseRoot) {
|
|
358
|
+
const match = (await (client instanceof CbmTools ? client : new CbmTools(client)).listProjects()).find((p) => p.root_path === codebaseRoot);
|
|
351
359
|
return match ? match.name : (handleWarning("Project not found in CBM backend; using absolute path as project identifier", [
|
|
352
|
-
`
|
|
360
|
+
`Codebase root: ${codebaseRoot}`,
|
|
353
361
|
"Run `dr analyzer index` to ensure the project is indexed"
|
|
354
|
-
]),
|
|
362
|
+
]), codebaseRoot);
|
|
355
363
|
}
|
|
356
364
|
/**
|
|
357
365
|
* Query for API endpoints/routes in the indexed project
|
|
@@ -360,11 +368,12 @@ class CbmAnalyzer {
|
|
|
360
368
|
* the analyzer's mapping, and filters based on test code exclusion rules.
|
|
361
369
|
*
|
|
362
370
|
* @param projectRoot Absolute path to the project root
|
|
371
|
+
* @param options Optional codebase configuration
|
|
363
372
|
* @returns Array of endpoint candidates with confidence and source info
|
|
364
373
|
* @throws CLIError if the project is not indexed
|
|
365
374
|
*/
|
|
366
|
-
async endpoints(projectRoot) {
|
|
367
|
-
const status = await this.status(projectRoot);
|
|
375
|
+
async endpoints(projectRoot, options) {
|
|
376
|
+
const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
|
|
368
377
|
if (!status.indexed)
|
|
369
378
|
throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
|
|
370
379
|
"Run `dr analyzer index` to index the project",
|
|
@@ -384,18 +393,14 @@ class CbmAnalyzer {
|
|
|
384
393
|
name: "dr-cli",
|
|
385
394
|
version
|
|
386
395
|
});
|
|
387
|
-
const projectName = await this.resolveProjectName(
|
|
388
|
-
label: routeMapping.cbm_label,
|
|
389
|
-
project: projectName
|
|
390
|
-
}), nodes = this.validateSearchResponse(searchResponse).filter((n) => /^__route__[A-Z]+__\//.test(String(n.qualified_name ?? ""))).filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? ""))), candidates = [];
|
|
396
|
+
const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot), nodes = (await this.fetchRoutes(tools, projectName, routeMapping.cbm_label)).filter((n) => /^__route__[A-Z]+__\//.test(String(n.qualified_name ?? ""))).filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? ""))), candidates = [];
|
|
391
397
|
for (const node of nodes) {
|
|
392
|
-
const candidate = await this.transformNodeToEndpoint(node, routeMapping,
|
|
398
|
+
const candidate = await this.transformNodeToEndpoint(node, routeMapping, effectiveCodebaseRoot);
|
|
393
399
|
candidate.source_file && (this.isTestCode(candidate) || candidates.push(candidate));
|
|
394
400
|
}
|
|
395
401
|
if (candidates.length === 0)
|
|
396
402
|
try {
|
|
397
|
-
const
|
|
398
|
-
query: `
|
|
403
|
+
const clientQuery = `
|
|
399
404
|
MATCH (n:Function)
|
|
400
405
|
WHERE (n.file_path CONTAINS 'client' OR n.file_path CONTAINS 'Client'
|
|
401
406
|
OR n.file_path CONTAINS 'Api' OR n.file_path CONTAINS 'Service'
|
|
@@ -406,21 +411,17 @@ class CbmAnalyzer {
|
|
|
406
411
|
OR n.name STARTS WITH 'delete' OR n.name STARTS WITH 'patch'
|
|
407
412
|
OR n.name STARTS WITH 'create' OR n.name STARTS WITH 'update'
|
|
408
413
|
OR n.name STARTS WITH 'list')
|
|
409
|
-
RETURN n
|
|
410
|
-
`,
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
const node = record.n;
|
|
417
|
-
if (!node || !node.file_path)
|
|
418
|
-
continue;
|
|
419
|
-
const candidate = this.shapeClientSideEndpoint(node, projectRoot);
|
|
420
|
-
candidate && !this.isTestCode(candidate) && candidates.push(candidate);
|
|
421
|
-
}
|
|
414
|
+
RETURN ${nodeColumns("n")}
|
|
415
|
+
`, clientNodes = nodesFromQuery(await tools.queryGraph(clientQuery, projectName));
|
|
416
|
+
for (const node of clientNodes) {
|
|
417
|
+
if (!node.file_path)
|
|
418
|
+
continue;
|
|
419
|
+
const candidate = this.shapeClientSideEndpoint(node, effectiveCodebaseRoot);
|
|
420
|
+
candidate && !this.isTestCode(candidate) && candidates.push(candidate);
|
|
422
421
|
}
|
|
423
|
-
} catch {
|
|
422
|
+
} catch (error) {
|
|
423
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
424
|
+
handleWarning("Client-side endpoint fallback skipped: query_graph failed", [`Error: ${errorMsg}`]);
|
|
424
425
|
}
|
|
425
426
|
return this.warnOnDuplicateIdFragments(candidates, (c) => c.suggested_id_fragment, (c) => c.source_symbol || c.source_file || "(unknown)"), candidates;
|
|
426
427
|
} finally {
|
|
@@ -436,11 +437,12 @@ class CbmAnalyzer {
|
|
|
436
437
|
*
|
|
437
438
|
* @param projectRoot Absolute path to the project root
|
|
438
439
|
* @param rawQuery Raw Cypher query string
|
|
440
|
+
* @param options Optional codebase configuration
|
|
439
441
|
* @returns Raw result from query_graph tool
|
|
440
442
|
* @throws CLIError if project not indexed or query_graph tool unavailable
|
|
441
443
|
*/
|
|
442
|
-
async query(projectRoot, rawQuery) {
|
|
443
|
-
const status = await this.status(projectRoot);
|
|
444
|
+
async query(projectRoot, rawQuery, options) {
|
|
445
|
+
const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
|
|
444
446
|
if (!status.indexed)
|
|
445
447
|
throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
|
|
446
448
|
"Run `dr analyzer index` to index the project",
|
|
@@ -457,13 +459,13 @@ class CbmAnalyzer {
|
|
|
457
459
|
name: "dr-cli",
|
|
458
460
|
version
|
|
459
461
|
});
|
|
460
|
-
const projectName = await this.resolveProjectName(
|
|
462
|
+
const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
|
|
461
463
|
try {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
project: projectName
|
|
465
|
-
});
|
|
464
|
+
const result = await tools.queryGraph(rawQuery, projectName);
|
|
465
|
+
return { ...result, total: result.rows.length };
|
|
466
466
|
} catch (error) {
|
|
467
|
+
if (error instanceof CbmToolError)
|
|
468
|
+
throw new CLIError(`Query failed: ${error.detail}`, ErrorCategory.USER, error.suggestions);
|
|
467
469
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
468
470
|
throw errorMessage.includes("query_graph") || errorMessage.includes("Unknown method") ? new CLIError("query_graph not supported by this analyzer", ErrorCategory.USER, [
|
|
469
471
|
"The query_graph tool is optional in the CBM analyzer contract",
|
|
@@ -507,7 +509,7 @@ class CbmAnalyzer {
|
|
|
507
509
|
*
|
|
508
510
|
* @private
|
|
509
511
|
*/
|
|
510
|
-
async transformNodeToEndpoint(node, mapping,
|
|
512
|
+
async transformNodeToEndpoint(node, mapping, codebaseRoot) {
|
|
511
513
|
const properties = node.properties ?? {}, nodeId = String(node.name ?? node.id ?? "(unknown)");
|
|
512
514
|
let confidence = mapping.confidence;
|
|
513
515
|
const suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? "")), suggestedIdFragment = suggestedName;
|
|
@@ -527,9 +529,9 @@ class CbmAnalyzer {
|
|
|
527
529
|
const handlerQualifiedName = String(properties.handler_name ?? properties.handler_qualified_name ?? ""), sourceSymbol = String(properties.symbol ?? properties.source_symbol ?? ""), sourceStartLine = Number(properties.start_line ?? 0), sourceEndLine = Number(properties.end_line ?? 0);
|
|
528
530
|
(!handlerQualifiedName || !sourceSymbol) && (confidence = capConfidence(confidence));
|
|
529
531
|
let sourceFile = String(node.file_path ?? properties.file_path ?? properties.source_file ?? "");
|
|
530
|
-
if (sourceFile &&
|
|
532
|
+
if (sourceFile && codebaseRoot)
|
|
531
533
|
try {
|
|
532
|
-
sourceFile =
|
|
534
|
+
sourceFile = relativeToRoot(codebaseRoot, sourceFile);
|
|
533
535
|
} catch (error) {
|
|
534
536
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
535
537
|
handleWarning(`Failed to compute relative path for ${nodeId}`, [
|
|
@@ -577,14 +579,14 @@ class CbmAnalyzer {
|
|
|
577
579
|
* Build a low-confidence EndpointCandidate from a client-side API function node.
|
|
578
580
|
* Used as a fallback when no server-side Route nodes are found (ISSUE-003).
|
|
579
581
|
*/
|
|
580
|
-
shapeClientSideEndpoint(node,
|
|
582
|
+
shapeClientSideEndpoint(node, codebaseRoot) {
|
|
581
583
|
const name = String(node.name ?? node.id ?? "");
|
|
582
584
|
if (!name)
|
|
583
585
|
return null;
|
|
584
586
|
let sourceFile = String(node.file_path ?? "");
|
|
585
|
-
if (sourceFile &&
|
|
587
|
+
if (sourceFile && codebaseRoot)
|
|
586
588
|
try {
|
|
587
|
-
sourceFile =
|
|
589
|
+
sourceFile = relativeToRoot(codebaseRoot, sourceFile);
|
|
588
590
|
} catch {
|
|
589
591
|
}
|
|
590
592
|
if (!sourceFile)
|
|
@@ -637,11 +639,12 @@ class CbmAnalyzer {
|
|
|
637
639
|
* at "medium" and excludes test files.
|
|
638
640
|
*
|
|
639
641
|
* @param projectRoot Absolute path to the project root
|
|
642
|
+
* @param options Optional codebase configuration
|
|
640
643
|
* @returns Array of service candidates with qualifying heuristics populated
|
|
641
644
|
* @throws CLIError if the project is not indexed
|
|
642
645
|
*/
|
|
643
|
-
async services(projectRoot) {
|
|
644
|
-
const status = await this.status(projectRoot);
|
|
646
|
+
async services(projectRoot, options) {
|
|
647
|
+
const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
|
|
645
648
|
if (!status.indexed)
|
|
646
649
|
throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
|
|
647
650
|
"Run `dr analyzer index` to index the project",
|
|
@@ -661,7 +664,7 @@ class CbmAnalyzer {
|
|
|
661
664
|
const nodeLabels = this.mapper.getNodeLabels(), applicationLabels = [];
|
|
662
665
|
for (const label of nodeLabels) {
|
|
663
666
|
const mapping = this.mapper.getNodeMapping(label);
|
|
664
|
-
mapping && mapping.dr_layer === "application" && applicationLabels.push(label);
|
|
667
|
+
mapping && mapping.dr_layer === "application" && SERVICE_SOURCE_TYPES.has(mapping.dr_element_type) && applicationLabels.push(label);
|
|
665
668
|
}
|
|
666
669
|
if (applicationLabels.length === 0)
|
|
667
670
|
return handleWarning("No application-layer labels found in analyzer mapping", [
|
|
@@ -669,18 +672,15 @@ class CbmAnalyzer {
|
|
|
669
672
|
"Services detection requires at least one mapped application-layer node type",
|
|
670
673
|
"Returning empty services list"
|
|
671
674
|
]), [];
|
|
672
|
-
const candidates = [], projectName = await this.resolveProjectName(
|
|
675
|
+
const candidates = [], tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot), labelPriority = { Class: 0, Module: 1 };
|
|
673
676
|
applicationLabels.sort((a, b) => (labelPriority[a] ?? 99) - (labelPriority[b] ?? 99));
|
|
674
677
|
for (const label of applicationLabels) {
|
|
675
|
-
const
|
|
676
|
-
label,
|
|
677
|
-
project: projectName
|
|
678
|
-
}), nodes = this.validateSearchResponse(searchResponse), nodeMapping = this.mapper.getNodeMapping(label);
|
|
678
|
+
const nodes = await tools.searchGraph({ label, project: projectName }), nodeMapping = this.mapper.getNodeMapping(label);
|
|
679
679
|
if (!nodeMapping)
|
|
680
680
|
continue;
|
|
681
681
|
const promotionHeuristicNames = nodeMapping.promotion_heuristics || [];
|
|
682
682
|
for (const node of nodes) {
|
|
683
|
-
const candidate = await this.transformNodeToService(node, nodeMapping,
|
|
683
|
+
const candidate = await this.transformNodeToService(node, nodeMapping, effectiveCodebaseRoot, promotionHeuristicNames);
|
|
684
684
|
candidate.qualifying_heuristics.length > 0 && !this.isTestCode(candidate) && candidates.push(candidate);
|
|
685
685
|
}
|
|
686
686
|
}
|
|
@@ -700,12 +700,12 @@ class CbmAnalyzer {
|
|
|
700
700
|
*
|
|
701
701
|
* @private
|
|
702
702
|
*/
|
|
703
|
-
async transformNodeToService(node, mapping,
|
|
703
|
+
async transformNodeToService(node, mapping, codebaseRoot, promotionHeuristicNames) {
|
|
704
704
|
const properties = node.properties ?? {}, suggestedName = toKebabCase(String(properties.name ?? node.name ?? node.id ?? "")), suggestedIdFragment = suggestedName;
|
|
705
705
|
let sourceFile = node.file_path ?? "";
|
|
706
|
-
if (sourceFile &&
|
|
706
|
+
if (sourceFile && codebaseRoot)
|
|
707
707
|
try {
|
|
708
|
-
sourceFile =
|
|
708
|
+
sourceFile = relativeToRoot(codebaseRoot, sourceFile);
|
|
709
709
|
} catch (error) {
|
|
710
710
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
711
711
|
handleWarning(`Failed to compute relative path for ${String(node.name ?? node.id ?? "(unknown)")}`, [
|
|
@@ -795,11 +795,12 @@ class CbmAnalyzer {
|
|
|
795
795
|
* naming_indicators from the heuristic parameters. All candidates have confidence "low".
|
|
796
796
|
*
|
|
797
797
|
* @param projectRoot Absolute path to the project root
|
|
798
|
+
* @param options Optional codebase configuration
|
|
798
799
|
* @returns Array of datastore candidates aggregated by inferred datastore
|
|
799
800
|
* @throws CLIError if the project is not indexed
|
|
800
801
|
*/
|
|
801
|
-
async datastores(projectRoot) {
|
|
802
|
-
const status = await this.status(projectRoot);
|
|
802
|
+
async datastores(projectRoot, options) {
|
|
803
|
+
const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
|
|
803
804
|
if (!status.indexed)
|
|
804
805
|
throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
|
|
805
806
|
"Run `dr analyzer index` to index the project",
|
|
@@ -822,20 +823,17 @@ class CbmAnalyzer {
|
|
|
822
823
|
"The analyzer configuration is missing the datastore_detection heuristic",
|
|
823
824
|
"Run `npm run build:spec` to recompile the analyzer artifacts"
|
|
824
825
|
]);
|
|
825
|
-
const importPatterns = datastoreHeuristic.parameters?.patterns ?? [], namingIndicators = datastoreHeuristic.parameters?.naming_indicators ?? [], datastoreSignals = /* @__PURE__ */ new Map(), datastoreFirstSource = /* @__PURE__ */ new Map(), projectName = await this.resolveProjectName(
|
|
826
|
+
const importPatterns = datastoreHeuristic.parameters?.patterns ?? [], namingIndicators = datastoreHeuristic.parameters?.naming_indicators ?? [], datastoreSignals = /* @__PURE__ */ new Map(), datastoreFirstSource = /* @__PURE__ */ new Map(), tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
|
|
826
827
|
let allNodes = [];
|
|
827
828
|
try {
|
|
828
|
-
const
|
|
829
|
-
query: `
|
|
829
|
+
const importsQuery = `
|
|
830
830
|
MATCH (n1)-[r:IMPORTS]->(n2)
|
|
831
|
-
RETURN DISTINCT n1
|
|
831
|
+
RETURN DISTINCT ${nodeColumns("n1")}
|
|
832
832
|
UNION
|
|
833
833
|
MATCH (n1)-[r:IMPORTS]->(n2)
|
|
834
|
-
RETURN DISTINCT n2
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
});
|
|
838
|
-
queryResponse && typeof queryResponse == "object" && Array.isArray(queryResponse.records) && (allNodes = (queryResponse.records || []).map((r) => r.node).filter((n) => n && typeof n == "object"));
|
|
834
|
+
RETURN DISTINCT ${nodeColumns("n2")}
|
|
835
|
+
`;
|
|
836
|
+
allNodes = nodesFromQuery(await tools.queryGraph(importsQuery, projectName));
|
|
839
837
|
} catch (error) {
|
|
840
838
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
841
839
|
handleWarning("query_graph tool failed; falling back to search_graph for datastore detection", [
|
|
@@ -845,13 +843,8 @@ class CbmAnalyzer {
|
|
|
845
843
|
"Ensure query_graph is supported by your analyzer version for best results"
|
|
846
844
|
]);
|
|
847
845
|
const nodeLabels = this.mapper.getNodeLabels();
|
|
848
|
-
for (const label of nodeLabels)
|
|
849
|
-
|
|
850
|
-
label,
|
|
851
|
-
project: projectName
|
|
852
|
-
}), nodes = this.validateSearchResponse(searchResponse);
|
|
853
|
-
allNodes.push(...nodes);
|
|
854
|
-
}
|
|
846
|
+
for (const label of nodeLabels)
|
|
847
|
+
allNodes.push(...await tools.searchGraph({ label, project: projectName }));
|
|
855
848
|
}
|
|
856
849
|
for (const node of allNodes) {
|
|
857
850
|
const filePath = node.file_path ?? "", importTargetName = String(node.label ?? node.properties?.name ?? node.id ?? "").toLowerCase();
|
|
@@ -874,9 +867,9 @@ class CbmAnalyzer {
|
|
|
874
867
|
]) : (datastoreSignals.set(datastoreName, []), datastoreFirstSource.set(datastoreName, importTargetName));
|
|
875
868
|
const signals = datastoreSignals.get(datastoreName);
|
|
876
869
|
let relativeFile = filePath;
|
|
877
|
-
if (filePath &&
|
|
870
|
+
if (filePath && effectiveCodebaseRoot)
|
|
878
871
|
try {
|
|
879
|
-
relativeFile =
|
|
872
|
+
relativeFile = relativeToRoot(effectiveCodebaseRoot, filePath);
|
|
880
873
|
} catch (error) {
|
|
881
874
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
882
875
|
handleWarning("Failed to compute relative path in datastore detection", [
|
|
@@ -925,8 +918,7 @@ class CbmAnalyzer {
|
|
|
925
918
|
}
|
|
926
919
|
const browserStorageTypes = ["local-storage", "session-storage"], browserStorageSignals = /* @__PURE__ */ new Map();
|
|
927
920
|
try {
|
|
928
|
-
const
|
|
929
|
-
query: `
|
|
921
|
+
const storageQuery = `
|
|
930
922
|
MATCH (n:Function)
|
|
931
923
|
WHERE n.file_path CONTAINS 'Store' OR n.file_path CONTAINS 'store'
|
|
932
924
|
OR n.file_path CONTAINS 'storage' OR n.file_path CONTAINS 'Storage'
|
|
@@ -934,24 +926,13 @@ class CbmAnalyzer {
|
|
|
934
926
|
OR n.file_path CONTAINS 'preference' OR n.file_path CONTAINS 'Preference'
|
|
935
927
|
OR n.file_path CONTAINS 'layout' OR n.file_path CONTAINS 'Layout'
|
|
936
928
|
OR n.file_path CONTAINS 'settings' OR n.file_path CONTAINS 'Settings'
|
|
937
|
-
RETURN n
|
|
938
|
-
`,
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
const node = record.n;
|
|
945
|
-
if (!node?.file_path)
|
|
946
|
-
continue;
|
|
947
|
-
let relFile = node.file_path;
|
|
948
|
-
try {
|
|
949
|
-
relFile = path.relative(projectRoot, node.file_path);
|
|
950
|
-
} catch {
|
|
951
|
-
}
|
|
952
|
-
const storageKey = /session/i.test(relFile) ? "session-storage" : "local-storage";
|
|
953
|
-
browserStorageSignals.has(storageKey) || browserStorageSignals.set(storageKey, /* @__PURE__ */ new Set()), browserStorageSignals.get(storageKey).add(relFile);
|
|
954
|
-
}
|
|
929
|
+
RETURN ${nodeColumns("n")}
|
|
930
|
+
`, storageNodes = nodesFromQuery(await tools.queryGraph(storageQuery, projectName));
|
|
931
|
+
for (const node of storageNodes) {
|
|
932
|
+
if (!node.file_path)
|
|
933
|
+
continue;
|
|
934
|
+
const relFile = relativeToRoot(effectiveCodebaseRoot, node.file_path), storageKey = /session/i.test(relFile) ? "session-storage" : "local-storage";
|
|
935
|
+
browserStorageSignals.has(storageKey) || browserStorageSignals.set(storageKey, /* @__PURE__ */ new Set()), browserStorageSignals.get(storageKey).add(relFile);
|
|
955
936
|
}
|
|
956
937
|
} catch {
|
|
957
938
|
}
|
|
@@ -1014,11 +995,12 @@ class CbmAnalyzer {
|
|
|
1014
995
|
* @param projectRoot Absolute path to the project root
|
|
1015
996
|
* @param qualifiedName Fully qualified symbol name (e.g., "com.example.UserService.getUser")
|
|
1016
997
|
* @param depth Maximum traversal depth (default 3, clamped to max 10)
|
|
998
|
+
* @param options Optional codebase configuration
|
|
1017
999
|
* @returns Array of callers in the call graph
|
|
1018
1000
|
* @throws CLIError if project not indexed or analyzer not installed
|
|
1019
1001
|
*/
|
|
1020
|
-
async callers(projectRoot, qualifiedName, depth) {
|
|
1021
|
-
return this.traceCallPath(projectRoot, qualifiedName, depth, "inbound");
|
|
1002
|
+
async callers(projectRoot, qualifiedName, depth, options) {
|
|
1003
|
+
return this.traceCallPath(projectRoot, qualifiedName, depth, "inbound", options);
|
|
1022
1004
|
}
|
|
1023
1005
|
/**
|
|
1024
1006
|
* Query for callees of a specific function or symbol
|
|
@@ -1031,11 +1013,12 @@ class CbmAnalyzer {
|
|
|
1031
1013
|
* @param projectRoot Absolute path to the project root
|
|
1032
1014
|
* @param qualifiedName Fully qualified symbol name (e.g., "com.example.UserService.getUser")
|
|
1033
1015
|
* @param depth Maximum traversal depth (default 3, clamped to max 10)
|
|
1016
|
+
* @param options Optional codebase configuration
|
|
1034
1017
|
* @returns Array of callees in the call graph
|
|
1035
1018
|
* @throws CLIError if project not indexed or analyzer not installed
|
|
1036
1019
|
*/
|
|
1037
|
-
async callees(projectRoot, qualifiedName, depth) {
|
|
1038
|
-
return this.traceCallPath(projectRoot, qualifiedName, depth, "outbound");
|
|
1020
|
+
async callees(projectRoot, qualifiedName, depth, options) {
|
|
1021
|
+
return this.traceCallPath(projectRoot, qualifiedName, depth, "outbound", options);
|
|
1039
1022
|
}
|
|
1040
1023
|
/**
|
|
1041
1024
|
* Cypher fallback for call graph traversal when trace_path is unavailable.
|
|
@@ -1045,7 +1028,7 @@ class CbmAnalyzer {
|
|
|
1045
1028
|
*
|
|
1046
1029
|
* @private
|
|
1047
1030
|
*/
|
|
1048
|
-
async callGraphViaCypher(binaryPath, projectName, qualifiedName, direction, depth,
|
|
1031
|
+
async callGraphViaCypher(binaryPath, projectName, qualifiedName, direction, depth, codebaseRoot) {
|
|
1049
1032
|
const safeQN = qualifiedName.replace(/'/g, "''"), limit = Math.min(depth * 20, 100), cypher = direction === "inbound" ? `MATCH (caller)-[:CALLS]->(target) WHERE target.qualified_name = '${safeQN}' OR target.name = '${safeQN}' RETURN caller.id AS id, caller.qualified_name AS qualified_name, caller.file_path AS file_path LIMIT ${limit}` : `MATCH (source)-[:CALLS]->(callee) WHERE source.qualified_name = '${safeQN}' OR source.name = '${safeQN}' RETURN callee.id AS id, callee.qualified_name AS qualified_name, callee.file_path AS file_path LIMIT ${limit}`;
|
|
1050
1033
|
depth > 1 && handleWarning(`Cypher CALLS fallback: depth ${depth} requested but fallback only traverses 1 hop`, ["Multi-hop traversal requires trace_path support in the analyzer binary"]);
|
|
1051
1034
|
const client = new StdioClient();
|
|
@@ -1053,16 +1036,13 @@ class CbmAnalyzer {
|
|
|
1053
1036
|
client.spawn(binaryPath);
|
|
1054
1037
|
const version = await getCliVersion();
|
|
1055
1038
|
await client.initialize({ name: "dr-cli", version });
|
|
1056
|
-
const queryResponse = await client.
|
|
1057
|
-
|
|
1058
|
-
project: projectName
|
|
1059
|
-
}), cols = queryResponse?.columns ?? [];
|
|
1060
|
-
return (queryResponse?.rows ?? []).map((row) => {
|
|
1039
|
+
const queryResponse = await new CbmTools(client).queryGraph(cypher, projectName), cols = queryResponse.columns;
|
|
1040
|
+
return queryResponse.rows.map((row) => {
|
|
1061
1041
|
const obj = {};
|
|
1062
1042
|
return cols.forEach((col, i) => {
|
|
1063
1043
|
const key = col.includes(".") ? col.slice(col.lastIndexOf(".") + 1) : col;
|
|
1064
1044
|
obj[key] = row[i];
|
|
1065
|
-
}), shapeCallGraphNode(obj,
|
|
1045
|
+
}), shapeCallGraphNode(obj, codebaseRoot);
|
|
1066
1046
|
});
|
|
1067
1047
|
} catch (error) {
|
|
1068
1048
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -1082,8 +1062,8 @@ class CbmAnalyzer {
|
|
|
1082
1062
|
*
|
|
1083
1063
|
* @private
|
|
1084
1064
|
*/
|
|
1085
|
-
async traceCallPath(projectRoot, qualifiedName, depth, direction) {
|
|
1086
|
-
const status = await this.status(projectRoot);
|
|
1065
|
+
async traceCallPath(projectRoot, qualifiedName, depth, direction, options) {
|
|
1066
|
+
const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
|
|
1087
1067
|
if (!status.indexed)
|
|
1088
1068
|
throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
|
|
1089
1069
|
"Run `dr analyzer index` to index the project",
|
|
@@ -1100,25 +1080,37 @@ class CbmAnalyzer {
|
|
|
1100
1080
|
name: "dr-cli",
|
|
1101
1081
|
version
|
|
1102
1082
|
});
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
`
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1083
|
+
const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
|
|
1084
|
+
let trace;
|
|
1085
|
+
try {
|
|
1086
|
+
trace = await tools.tracePath({
|
|
1087
|
+
function_name: qualifiedName,
|
|
1088
|
+
direction,
|
|
1089
|
+
depth: clampedDepth,
|
|
1090
|
+
project: projectName
|
|
1091
|
+
});
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
throw error instanceof CbmToolError && /not found/i.test(error.detail) ? new CLIError(`Symbol not found in the code graph: ${qualifiedName}`, ErrorCategory.NOT_FOUND, [
|
|
1094
|
+
...error.suggestions ?? [],
|
|
1095
|
+
"Pass the fully qualified name as CBM records it (see `dr analyzer services --json`)"
|
|
1096
|
+
]) : error;
|
|
1097
|
+
}
|
|
1098
|
+
if (!trace)
|
|
1099
|
+
return this.callGraphViaCypher(detection.binary_path, projectName, qualifiedName, direction, clampedDepth, effectiveCodebaseRoot);
|
|
1100
|
+
const nodes = trace.nodes, edges = trace.edges;
|
|
1101
|
+
if (trace.shape === "grouped" && nodes.length > 0)
|
|
1102
|
+
try {
|
|
1103
|
+
const files = await tools.filePathsOf(nodes.map((n) => String(n.qualified_name ?? "")), projectName);
|
|
1104
|
+
for (const node of nodes)
|
|
1105
|
+
node.file_path ??= files.get(String(node.qualified_name ?? ""));
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1108
|
+
handleWarning("Could not look up source files for the call graph", [`Error: ${errorMsg}`]);
|
|
1109
|
+
}
|
|
1110
|
+
const validEdgeTypes = ["CALLS", ...this.mapper.getEdgeTypes().filter((t) => t !== "CALLS")], callGraphNodes = [];
|
|
1119
1111
|
for (const node of nodes) {
|
|
1120
|
-
const nodeQualifiedName = node.qualified_name || node.id;
|
|
1121
|
-
let callGraphNode = shapeCallGraphNode(node,
|
|
1112
|
+
const nodeQualifiedName = String(node.qualified_name || node.id || "");
|
|
1113
|
+
let callGraphNode = shapeCallGraphNode(node, effectiveCodebaseRoot);
|
|
1122
1114
|
const nodeDepth = typeof node.depth == "number" ? node.depth : 0, edgeType = determineEdgeType(nodeDepth, edges, nodeQualifiedName, validEdgeTypes);
|
|
1123
1115
|
callGraphNode.edge_type = edgeType, callGraphNodes.push(callGraphNode);
|
|
1124
1116
|
}
|
|
@@ -1156,6 +1148,85 @@ class CbmAnalyzer {
|
|
|
1156
1148
|
source_symbol: sourceSymbol
|
|
1157
1149
|
};
|
|
1158
1150
|
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Tier-0 classification of the indexed project (`dr analyzer classify --tier0-only`).
|
|
1153
|
+
*
|
|
1154
|
+
* Builds a card for every candidate symbol (src/analyzers/cards) and applies the Tier-0 rules
|
|
1155
|
+
* from this analyzer's extraction heuristics. Reads the graph only: no model calls, no writes.
|
|
1156
|
+
*
|
|
1157
|
+
* @throws CLIError if the project is not indexed or the mapping has no Tier-0 rules
|
|
1158
|
+
*/
|
|
1159
|
+
async classify(projectRoot, options = {}) {
|
|
1160
|
+
const effectiveCodebaseRoot = options.codebaseRoot ?? projectRoot, rules = tier0RulesFrom(this.mapper.getHeuristics());
|
|
1161
|
+
if (rules.length === 0)
|
|
1162
|
+
throw new CLIError(`The ${this.name} analyzer mapping has no Tier-0 rules`, ErrorCategory.USER, [
|
|
1163
|
+
"Tier-0 rules are heuristics with a parameters.tier0 block in the analyzer's extraction-heuristics.json"
|
|
1164
|
+
]);
|
|
1165
|
+
if (options.labels?.length) {
|
|
1166
|
+
const known = /* @__PURE__ */ new Set([...this.mapper.getNodeLabels(), ...defaultLabels(rules), ...Object.keys(CARD_FIELDS)]), unknown = options.labels.filter((l) => !known.has(l));
|
|
1167
|
+
if (unknown.length)
|
|
1168
|
+
throw new CLIError(`Unknown label${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}`, ErrorCategory.USER, [
|
|
1169
|
+
`Labels are case-sensitive; known labels: ${[...known].sort().join(", ")}`
|
|
1170
|
+
]);
|
|
1171
|
+
}
|
|
1172
|
+
const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
|
|
1173
|
+
if (!status.indexed)
|
|
1174
|
+
throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
|
|
1175
|
+
"Run `dr analyzer index` to index the project",
|
|
1176
|
+
"Ensure codebase-memory-mcp is installed and working"
|
|
1177
|
+
]);
|
|
1178
|
+
const detection = status.detected;
|
|
1179
|
+
if (!detection.installed)
|
|
1180
|
+
throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, [
|
|
1181
|
+
"Install codebase-memory-mcp: npm install -g codebase-memory-mcp"
|
|
1182
|
+
]);
|
|
1183
|
+
const client = new StdioClient();
|
|
1184
|
+
try {
|
|
1185
|
+
client.spawn(detection.binary_path), await client.initialize({ name: "dr-cli", version: await getCliVersion() });
|
|
1186
|
+
const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
|
|
1187
|
+
return await classifyTier0(cbmCardGraph(tools, projectName), rules, this.name, {
|
|
1188
|
+
sourceRoot: effectiveCodebaseRoot,
|
|
1189
|
+
...options
|
|
1190
|
+
});
|
|
1191
|
+
} finally {
|
|
1192
|
+
client.close();
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
/** The Tier-0 rules, edge mappings and node-mapping decisions extraction runs with. */
|
|
1196
|
+
extractionConfig() {
|
|
1197
|
+
const nodeMappings = this.mapper.getNodeLabels().map((label) => this.mapper.getNodeMapping(label)).filter((m) => !!m), edgeMappings = this.mapper.getEdgeTypes().map((type) => this.mapper.getEdgeMapping(type)).filter((m) => !!m);
|
|
1198
|
+
return extractionConfigFrom(tier0RulesFrom(this.mapper.getHeuristics()), edgeMappings, nodeMappings);
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* What `codebase_extract` reads from the index (src/analyzers/cards/extract.ts): cards with
|
|
1202
|
+
* their Tier-0 outcomes, the mapped edges and the symbols of the files asked about. Read-only.
|
|
1203
|
+
* Graph reads get two minutes each, past the client's 30s default, for large graphs.
|
|
1204
|
+
*
|
|
1205
|
+
* @throws CLIError if the project is not indexed or the mapping has no Tier-0 rules
|
|
1206
|
+
*/
|
|
1207
|
+
async extract(projectRoot, options = {}) {
|
|
1208
|
+
const effectiveCodebaseRoot = options.codebaseRoot ?? projectRoot, config = this.extractionConfig();
|
|
1209
|
+
if (config.rules.length === 0)
|
|
1210
|
+
throw new CLIError(`The ${this.name} analyzer mapping has no Tier-0 rules`, ErrorCategory.USER);
|
|
1211
|
+
const status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
|
|
1212
|
+
if (!status.indexed)
|
|
1213
|
+
throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, ["Run `dr analyzer index` to index the project"]);
|
|
1214
|
+
if (!status.detected.installed)
|
|
1215
|
+
throw new CLIError("CBM analyzer not installed", ErrorCategory.NOT_FOUND, [
|
|
1216
|
+
"Install codebase-memory-mcp: npm install -g codebase-memory-mcp"
|
|
1217
|
+
]);
|
|
1218
|
+
const client = new StdioClient();
|
|
1219
|
+
try {
|
|
1220
|
+
client.spawn(status.detected.binary_path), await client.initialize({ name: "dr-cli", version: await getCliVersion() });
|
|
1221
|
+
const tools = new CbmTools(client, { timeout: EXTRACT_CALL_TIMEOUT_MS }), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot);
|
|
1222
|
+
return await collectExtraction(cbmCardGraph(tools, projectName), config, this.name, {
|
|
1223
|
+
sourceRoot: effectiveCodebaseRoot,
|
|
1224
|
+
...options
|
|
1225
|
+
});
|
|
1226
|
+
} finally {
|
|
1227
|
+
client.close();
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1159
1230
|
/**
|
|
1160
1231
|
* Verify that graph-discovered routes align with model endpoints
|
|
1161
1232
|
*
|
|
@@ -1163,12 +1234,12 @@ class CbmAnalyzer {
|
|
|
1163
1234
|
* and delegates comparison to VerifyEngine. Entirely read-only.
|
|
1164
1235
|
*
|
|
1165
1236
|
* @param projectRoot Absolute path to the project root
|
|
1166
|
-
* @param options Verification options
|
|
1237
|
+
* @param options Verification options including codebase root
|
|
1167
1238
|
* @returns Comprehensive verification report
|
|
1168
1239
|
* @throws CLIError if project not indexed or analyzer not installed
|
|
1169
1240
|
*/
|
|
1170
1241
|
async verify(projectRoot, options) {
|
|
1171
|
-
const status = await this.status(projectRoot);
|
|
1242
|
+
const effectiveCodebaseRoot = options?.codebaseRoot ?? projectRoot, status = await this.status(projectRoot, { codebaseRoot: effectiveCodebaseRoot });
|
|
1172
1243
|
if (!status.indexed)
|
|
1173
1244
|
throw new CLIError(`Project not indexed: ${projectRoot}`, ErrorCategory.NOT_FOUND, [
|
|
1174
1245
|
"Run `dr analyzer index` to index the project",
|
|
@@ -1188,18 +1259,43 @@ class CbmAnalyzer {
|
|
|
1188
1259
|
name: "dr-cli",
|
|
1189
1260
|
version
|
|
1190
1261
|
});
|
|
1191
|
-
const projectName = await this.resolveProjectName(
|
|
1192
|
-
label: routeMapping.cbm_label,
|
|
1193
|
-
project: projectName
|
|
1194
|
-
}), routes = this.validateSearchResponse(searchResponse).filter((n) => /^__route__[A-Z]+__\//.test(String(n.qualified_name ?? ""))).filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? ""))).map((node) => this.shapeRoute(node)).filter((r) => r.source_file), engine = new VerifyEngine(), indexMeta = status.index_meta, analyzerName = this.name;
|
|
1262
|
+
const tools = new CbmTools(client), projectName = await this.resolveProjectName(tools, effectiveCodebaseRoot), routes = (await this.fetchRoutes(tools, projectName, routeMapping.cbm_label)).filter((n) => /^__route__[A-Z]+__\//.test(String(n.qualified_name ?? ""))).filter((n) => isLikelyHttpRoute(String(n.qualified_name ?? ""))).map((node) => this.shapeRoute(node)).filter((r) => r.source_file), engine = new VerifyEngine(), indexMeta = status.index_meta, analyzerName = this.name;
|
|
1195
1263
|
return await engine.computeReport(projectRoot, routes, options, analyzerName, indexMeta);
|
|
1196
1264
|
} finally {
|
|
1197
1265
|
client.close();
|
|
1198
1266
|
}
|
|
1199
1267
|
}
|
|
1200
1268
|
}
|
|
1269
|
+
const CBM_IGNORE_HEADING = "# Added by Documentation Robotics: its model, changesets and analyzer state are not code.";
|
|
1270
|
+
async function ensureCbmIgnore(codebaseRoot, projectRoot) {
|
|
1271
|
+
const file = path.join(codebaseRoot, ".cbmignore");
|
|
1272
|
+
let text = "", created = !0;
|
|
1273
|
+
try {
|
|
1274
|
+
text = await readFile(file, "utf-8"), created = !1;
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
if (error.code !== "ENOENT")
|
|
1277
|
+
throw error;
|
|
1278
|
+
}
|
|
1279
|
+
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")), folder = (line) => line.replace(/^\/+/, "").replace(/\/+$/, ""), listed = new Set(lines.filter((line) => !line.startsWith("!")).map(folder)), negated = new Set(lines.filter((line) => line.startsWith("!")).map((line) => folder(line.slice(1)))), added = [];
|
|
1280
|
+
for (const dir of DR_OWN_DIRS) {
|
|
1281
|
+
const relative = path.relative(codebaseRoot, path.join(projectRoot, dir)).split(path.sep).join("/");
|
|
1282
|
+
!relative || relative.startsWith("..") || path.isAbsolute(relative) || !listed.has(relative) && !negated.has(relative) && added.push(`/${relative}/`);
|
|
1283
|
+
}
|
|
1284
|
+
if (added.length === 0)
|
|
1285
|
+
return { path: file, added, created: !1 };
|
|
1286
|
+
const separator = text.length === 0 ? "" : text.endsWith(`
|
|
1287
|
+
`) ? `
|
|
1288
|
+
` : `
|
|
1289
|
+
|
|
1290
|
+
`, heading = text.includes(CBM_IGNORE_HEADING) ? "" : `${CBM_IGNORE_HEADING}
|
|
1291
|
+
`;
|
|
1292
|
+
return await writeFile(file, `${text}${separator}${heading}${added.join(`
|
|
1293
|
+
`)}
|
|
1294
|
+
`), { path: file, added, created };
|
|
1295
|
+
}
|
|
1201
1296
|
export {
|
|
1202
1297
|
CbmAnalyzer,
|
|
1203
|
-
SOURCE_CODE_EXT_RE
|
|
1298
|
+
SOURCE_CODE_EXT_RE,
|
|
1299
|
+
ensureCbmIgnore
|
|
1204
1300
|
};
|
|
1205
1301
|
//# sourceMappingURL=cbm-analyzer.js.map
|