@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
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/analyzers/cards/classify.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Tier-0 classification of a whole project: every candidate symbol gets a card, its facts and the\n * first matching Tier-0 rule. No network and no model calls. `dr analyzer classify --tier0-only`\n * runs this through the cbm analyzer.\n */\nimport type { Tier0Outcome, Tier0Rule } from \"../tier0-rules.js\";\nimport { CARD_FIELDS, CARD_LABELS, CARD_VERSION, CardBuilder, VENDOR_EXCLUDE, isCandidate } from \"./build.js\";\nimport { buildEdgeIndex } from \"./edge-index.js\";\nimport { buildFactsIndex, factsFor } from \"./facts.js\";\nimport { tier0ForCard } from \"./tier0.js\";\nimport type { CardFacts, CardGraph, SymbolCard } from \"./types.js\";\n\nexport interface ClassifyOptions {\n /** CBM labels to classify (default {@link CARD_LABELS} plus every label a rule names). */\n labels?: string[];\n /** Only files matching this glob (search_graph file_pattern). */\n filePattern?: string;\n /** Files to leave out, in addition to {@link VENDOR_EXCLUDE}. */\n exclude?: RegExp;\n /** Stop after this many cards, in label order. */\n limit?: number;\n /** Absolute path of the indexed codebase, so source is read from disk (see CardBuilderOptions). */\n sourceRoot?: string;\n /** Include each full card (source and neighbourhood) in the results. */\n includeCards?: boolean;\n /** Called after each card, for progress output. */\n onProgress?: (done: number, total: number) => void;\n}\n\nexport interface ClassifyResult {\n card_id: string;\n label: string;\n name: string;\n qualified_name: string;\n file_path: string | null;\n start_line: number | null;\n end_line: number | null;\n /** The first matching Tier-0 rule's action, or null when no rule covers the symbol. */\n tier0: Tier0Outcome | null;\n facts: CardFacts;\n card?: SymbolCard;\n}\n\nexport interface ClassifyReport {\n analyzer: string;\n project: string;\n mode: \"tier0\";\n /** {@link CARD_VERSION} of the cards behind these results. */\n card_version: number;\n rules: number;\n summary: {\n cards: number;\n decided: number;\n rejected: number;\n narrowed: number;\n undecided: number;\n /**\n * Non-route cards built without source (no line range or readable file, and no CBM\n * snippet), on which rules that read the declaration cannot fire.\n */\n no_source: number;\n /** Cards per rule that matched. */\n by_rule: Record<string, number>;\n /** Decided cards per `layer.type`. */\n by_target: Record<string, number>;\n };\n results: ClassifyResult[];\n}\n\n/** {@link CARD_LABELS} plus every label a Tier-0 rule names (Type, Enum, Trait, ...). */\nexport function defaultLabels(rules: Tier0Rule[]): string[] {\n return [...new Set([...CARD_LABELS, ...rules.flatMap((r) => r.labels)])];\n}\n\nexport async function classifyTier0(\n graph: CardGraph,\n rules: Tier0Rule[],\n analyzer: string,\n options: ClassifyOptions = {}\n): Promise<ClassifyReport> {\n const labels = options.labels?.length ? options.labels : defaultLabels(rules);\n const exclude = options.exclude\n ? new RegExp(`(?:${VENDOR_EXCLUDE.source})|(?:${options.exclude.source})`, options.exclude.flags.replace(\"g\", \"\"))\n : VENDOR_EXCLUDE;\n\n // One card per qualified name (Java overloads share one), deduplicated before the limit.\n const nodes = [];\n const seen = new Set<string>();\n for (const label of labels) {\n for (const node of await graph.search(label, options.filePattern, CARD_FIELDS[label] ?? [])) {\n if (!isCandidate(node, label, exclude) || seen.has(node.qualified_name)) continue;\n seen.add(node.qualified_name);\n nodes.push(node);\n }\n }\n const selected = options.limit !== undefined ? nodes.slice(0, options.limit) : nodes;\n\n const edges = await buildEdgeIndex(graph);\n const factsIndex = await buildFactsIndex(graph);\n const builder = new CardBuilder(graph, edges, { scope: graph.project, sourceRoot: options.sourceRoot });\n\n const results: ClassifyResult[] = [];\n const summary: ClassifyReport[\"summary\"] = { cards: 0, decided: 0, rejected: 0, narrowed: 0, undecided: 0, no_source: 0, by_rule: {}, by_target: {} };\n for (const node of selected) {\n const card = await builder.build(node);\n const facts = factsFor(factsIndex, node.qualified_name, card.file_path);\n const tier0 = tier0ForCard(card, facts, rules);\n\n summary.cards++;\n if (card.source === null && card.label !== \"Route\") summary.no_source++;\n if (!tier0) summary.undecided++;\n else {\n summary.by_rule[tier0.rule] = (summary.by_rule[tier0.rule] ?? 0) + 1;\n if (tier0.kind === \"decide\") {\n summary.decided++;\n const target = `${tier0.layer}.${tier0.type}`;\n summary.by_target[target] = (summary.by_target[target] ?? 0) + 1;\n } else if (tier0.kind === \"reject\") summary.rejected++;\n else summary.narrowed++;\n }\n results.push({\n card_id: card.id,\n label: card.label,\n name: card.name,\n qualified_name: card.qualified_name,\n file_path: card.file_path,\n start_line: card.start_line,\n end_line: card.end_line,\n tier0,\n facts,\n ...(options.includeCards ? { card } : {}),\n });\n options.onProgress?.(results.length, selected.length);\n }\n return { analyzer, project: graph.project, mode: \"tier0\", card_version: CARD_VERSION, rules: rules.length, summary, results };\n}\n"],
|
|
5
|
+
"mappings": "AAMA,SAAS,aAAa,aAAa,cAAc,aAAa,gBAAgB,mBAAmB;AACjG,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB,gBAAgB;AAC1C,SAAS,oBAAoB;AA6DvB,SAAU,cAAc,OAAkB;AAC9C,SAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,aAAa,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AACzE;AAEA,eAAsB,cACpB,OACA,OACA,UACA,UAA2B,CAAA,GAAE;AAE7B,QAAM,SAAS,QAAQ,QAAQ,SAAS,QAAQ,SAAS,cAAc,KAAK,GACtE,UAAU,QAAQ,UACpB,IAAI,OAAO,MAAM,eAAe,MAAM,QAAQ,QAAQ,QAAQ,MAAM,KAAK,QAAQ,QAAQ,MAAM,QAAQ,KAAK,EAAE,CAAC,IAC/G,gBAGE,QAAQ,CAAA,GACR,OAAO,oBAAI,IAAG;AACpB,aAAW,SAAS;AAClB,eAAW,QAAQ,MAAM,MAAM,OAAO,OAAO,QAAQ,aAAa,YAAY,KAAK,KAAK,CAAA,CAAE;AACxF,MAAI,CAAC,YAAY,MAAM,OAAO,OAAO,KAAK,KAAK,IAAI,KAAK,cAAc,MACtE,KAAK,IAAI,KAAK,cAAc,GAC5B,MAAM,KAAK,IAAI;AAGnB,QAAM,WAAW,QAAQ,UAAU,SAAY,MAAM,MAAM,GAAG,QAAQ,KAAK,IAAI,OAEzE,QAAQ,MAAM,eAAe,KAAK,GAClC,aAAa,MAAM,gBAAgB,KAAK,GACxC,UAAU,IAAI,YAAY,OAAO,OAAO,EAAE,OAAO,MAAM,SAAS,YAAY,QAAQ,WAAU,CAAE,GAEhG,UAA4B,CAAA,GAC5B,UAAqC,EAAE,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAA,GAAI,WAAW,CAAA,EAAE;AACnJ,aAAW,QAAQ,UAAU;AAC3B,UAAM,OAAO,MAAM,QAAQ,MAAM,IAAI,GAC/B,QAAQ,SAAS,YAAY,KAAK,gBAAgB,KAAK,SAAS,GAChE,QAAQ,aAAa,MAAM,OAAO,KAAK;AAI7C,QAFA,QAAQ,SACJ,KAAK,WAAW,QAAQ,KAAK,UAAU,WAAS,QAAQ,aACxD,CAAC;AAAO,cAAQ;aAElB,QAAQ,QAAQ,MAAM,IAAI,KAAK,QAAQ,QAAQ,MAAM,IAAI,KAAK,KAAK,GAC/D,MAAM,SAAS,UAAU;AAC3B,cAAQ;AACR,YAAM,SAAS,GAAG,MAAM,KAAK,IAAI,MAAM,IAAI;AAC3C,cAAQ,UAAU,MAAM,KAAK,QAAQ,UAAU,MAAM,KAAK,KAAK;IACjE,MAAO,CAAI,MAAM,SAAS,WAAU,QAAQ,aACvC,QAAQ;AAEf,YAAQ,KAAK;MACX,SAAS,KAAK;MACd,OAAO,KAAK;MACZ,MAAM,KAAK;MACX,gBAAgB,KAAK;MACrB,WAAW,KAAK;MAChB,YAAY,KAAK;MACjB,UAAU,KAAK;MACf;MACA;MACA,GAAI,QAAQ,eAAe,EAAE,KAAI,IAAK,CAAA;KACvC,GACD,QAAQ,aAAa,QAAQ,QAAQ,SAAS,MAAM;EACtD;AACA,SAAO,EAAE,UAAU,SAAS,MAAM,SAAS,MAAM,SAAS,cAAc,cAAc,OAAO,MAAM,QAAQ,SAAS,QAAO;AAC7H;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function push(m, k, v) {
|
|
2
|
+
const arr = m.get(k);
|
|
3
|
+
arr ? arr.includes(v) || arr.push(v) : m.set(k, [v]);
|
|
4
|
+
}
|
|
5
|
+
async function buildEdgeIndex(graph) {
|
|
6
|
+
const idx = {
|
|
7
|
+
callers: /* @__PURE__ */ new Map(),
|
|
8
|
+
callees: /* @__PURE__ */ new Map(),
|
|
9
|
+
importsByFile: /* @__PURE__ */ new Map(),
|
|
10
|
+
envVars: /* @__PURE__ */ new Map(),
|
|
11
|
+
handles: /* @__PURE__ */ new Map(),
|
|
12
|
+
httpCalls: /* @__PURE__ */ new Map(),
|
|
13
|
+
inherits: /* @__PURE__ */ new Map(),
|
|
14
|
+
implements: /* @__PURE__ */ new Map(),
|
|
15
|
+
routeHandlers: /* @__PURE__ */ new Map(),
|
|
16
|
+
routeCallers: /* @__PURE__ */ new Map(),
|
|
17
|
+
qnFile: /* @__PURE__ */ new Map()
|
|
18
|
+
};
|
|
19
|
+
for (const [a, b] of await graph.rows("MATCH (a)-[r:CALLS]->(b) RETURN a.qualified_name, b.qualified_name"))
|
|
20
|
+
typeof a != "string" || typeof b != "string" || (push(idx.callees, a, b), push(idx.callers, b, a));
|
|
21
|
+
for (const [file, b, bfile] of await graph.rows("MATCH (a)-[r:IMPORTS]->(b) RETURN a.file_path, b.qualified_name, b.file_path")) {
|
|
22
|
+
if (typeof file != "string")
|
|
23
|
+
continue;
|
|
24
|
+
const target = typeof bfile == "string" && bfile ? bfile : typeof b == "string" ? b : null;
|
|
25
|
+
target && push(idx.importsByFile, file, target);
|
|
26
|
+
}
|
|
27
|
+
for (const [a, key] of await graph.rows("MATCH (a)-[r:CONFIGURES]->(e:EnvVar) RETURN a.qualified_name, e.env_key"))
|
|
28
|
+
typeof a == "string" && typeof key == "string" && push(idx.envVars, a, key);
|
|
29
|
+
for (const [a, route] of await graph.rows("MATCH (a)-[r:HANDLES]->(x:Route) RETURN a.qualified_name, x.qualified_name"))
|
|
30
|
+
typeof a == "string" && typeof route == "string" && (push(idx.handles, a, route), push(idx.routeHandlers, route, a), push(idx.routeCallers, route, a));
|
|
31
|
+
for (const [a, file, route] of await graph.rows("MATCH (a)-[r]->(x:Route) RETURN a.qualified_name, a.file_path, x.qualified_name"))
|
|
32
|
+
typeof a != "string" || typeof route != "string" || (push(idx.routeCallers, route, a), typeof file == "string" && file && idx.qnFile.set(a, file));
|
|
33
|
+
for (const [a, file] of await graph.rows("MATCH (a:Function) RETURN a.qualified_name, a.file_path"))
|
|
34
|
+
typeof a == "string" && typeof file == "string" && file && idx.qnFile.set(a, file);
|
|
35
|
+
for (const [a, route, url] of await graph.rows("MATCH (a)-[r:HTTP_CALLS]->(x:Route) RETURN a.qualified_name, x.name, r.url_path"))
|
|
36
|
+
typeof a == "string" && push(idx.httpCalls, a, String(url ?? route ?? ""));
|
|
37
|
+
for (const [a, t, b] of await graph.rows("MATCH (a)-[r:INHERITS|IMPLEMENTS]->(b) RETURN a.qualified_name, type(r), b.qualified_name"))
|
|
38
|
+
typeof a != "string" || typeof b != "string" || push(t === "IMPLEMENTS" ? idx.implements : idx.inherits, a, b);
|
|
39
|
+
return idx;
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
buildEdgeIndex
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=edge-index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/analyzers/cards/edge-index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The graph neighbourhood every card reads, pulled once per project into adjacency maps keyed by\n * full qualified name.\n */\nimport type { CardGraph } from \"./types.js\";\n\nexport interface EdgeIndex {\n callers: Map<string, string[]>;\n callees: Map<string, string[]>;\n /** file path -> files (or symbols, when the target has no file) it imports */\n importsByFile: Map<string, string[]>;\n envVars: Map<string, string[]>;\n /** handler -> routes it handles */\n handles: Map<string, string[]>;\n httpCalls: Map<string, string[]>;\n inherits: Map<string, string[]>;\n implements: Map<string, string[]>;\n /** route qualified name -> handler qualified names */\n routeHandlers: Map<string, string[]>;\n /** route qualified name -> symbols or modules with any edge into it */\n routeCallers: Map<string, string[]>;\n /** qualified name -> file path, for every Function and route caller seen with a file */\n qnFile: Map<string, string>;\n}\n\nfunction push(m: Map<string, string[]>, k: string, v: string): void {\n const arr = m.get(k);\n if (arr) {\n if (!arr.includes(v)) arr.push(v);\n } else m.set(k, [v]);\n}\n\nexport async function buildEdgeIndex(graph: CardGraph): Promise<EdgeIndex> {\n const idx: EdgeIndex = {\n callers: new Map(),\n callees: new Map(),\n importsByFile: new Map(),\n envVars: new Map(),\n handles: new Map(),\n httpCalls: new Map(),\n inherits: new Map(),\n implements: new Map(),\n routeHandlers: new Map(),\n routeCallers: new Map(),\n qnFile: new Map(),\n };\n for (const [a, b] of await graph.rows(\"MATCH (a)-[r:CALLS]->(b) RETURN a.qualified_name, b.qualified_name\")) {\n if (typeof a !== \"string\" || typeof b !== \"string\") continue;\n push(idx.callees, a, b);\n push(idx.callers, b, a);\n }\n for (const [file, b, bfile] of await graph.rows(\"MATCH (a)-[r:IMPORTS]->(b) RETURN a.file_path, b.qualified_name, b.file_path\")) {\n if (typeof file !== \"string\") continue;\n const target = typeof bfile === \"string\" && bfile ? bfile : typeof b === \"string\" ? b : null;\n if (target) push(idx.importsByFile, file, target);\n }\n for (const [a, key] of await graph.rows(\"MATCH (a)-[r:CONFIGURES]->(e:EnvVar) RETURN a.qualified_name, e.env_key\")) {\n if (typeof a === \"string\" && typeof key === \"string\") push(idx.envVars, a, key);\n }\n for (const [a, route] of await graph.rows(\"MATCH (a)-[r:HANDLES]->(x:Route) RETURN a.qualified_name, x.qualified_name\")) {\n if (typeof a === \"string\" && typeof route === \"string\") {\n push(idx.handles, a, route);\n push(idx.routeHandlers, route, a);\n push(idx.routeCallers, route, a);\n }\n }\n for (const [a, file, route] of await graph.rows(\"MATCH (a)-[r]->(x:Route) RETURN a.qualified_name, a.file_path, x.qualified_name\")) {\n if (typeof a !== \"string\" || typeof route !== \"string\") continue;\n push(idx.routeCallers, route, a);\n if (typeof file === \"string\" && file) idx.qnFile.set(a, file);\n }\n for (const [a, file] of await graph.rows(\"MATCH (a:Function) RETURN a.qualified_name, a.file_path\")) {\n if (typeof a === \"string\" && typeof file === \"string\" && file) idx.qnFile.set(a, file);\n }\n for (const [a, route, url] of await graph.rows(\"MATCH (a)-[r:HTTP_CALLS]->(x:Route) RETURN a.qualified_name, x.name, r.url_path\")) {\n if (typeof a === \"string\") push(idx.httpCalls, a, String(url ?? route ?? \"\"));\n }\n for (const [a, t, b] of await graph.rows(\"MATCH (a)-[r:INHERITS|IMPLEMENTS]->(b) RETURN a.qualified_name, type(r), b.qualified_name\")) {\n if (typeof a !== \"string\" || typeof b !== \"string\") continue;\n push(t === \"IMPLEMENTS\" ? idx.implements : idx.inherits, a, b);\n }\n return idx;\n}\n"],
|
|
5
|
+
"mappings": "AAyBA,SAAS,KAAK,GAA0B,GAAW,GAAS;AAC1D,QAAM,MAAM,EAAE,IAAI,CAAC;AACnB,EAAI,MACG,IAAI,SAAS,CAAC,KAAG,IAAI,KAAK,CAAC,IAC3B,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;AACrB;AAEA,eAAsB,eAAe,OAAgB;AACnD,QAAM,MAAiB;IACrB,SAAS,oBAAI,IAAG;IAChB,SAAS,oBAAI,IAAG;IAChB,eAAe,oBAAI,IAAG;IACtB,SAAS,oBAAI,IAAG;IAChB,SAAS,oBAAI,IAAG;IAChB,WAAW,oBAAI,IAAG;IAClB,UAAU,oBAAI,IAAG;IACjB,YAAY,oBAAI,IAAG;IACnB,eAAe,oBAAI,IAAG;IACtB,cAAc,oBAAI,IAAG;IACrB,QAAQ,oBAAI,IAAG;;AAEjB,aAAW,CAAC,GAAG,CAAC,KAAK,MAAM,MAAM,KAAK,oEAAoE;AACxG,IAAI,OAAO,KAAM,YAAY,OAAO,KAAM,aAC1C,KAAK,IAAI,SAAS,GAAG,CAAC,GACtB,KAAK,IAAI,SAAS,GAAG,CAAC;AAExB,aAAW,CAAC,MAAM,GAAG,KAAK,KAAK,MAAM,MAAM,KAAK,8EAA8E,GAAG;AAC/H,QAAI,OAAO,QAAS;AAAU;AAC9B,UAAM,SAAS,OAAO,SAAU,YAAY,QAAQ,QAAQ,OAAO,KAAM,WAAW,IAAI;AACxF,IAAI,UAAQ,KAAK,IAAI,eAAe,MAAM,MAAM;EAClD;AACA,aAAW,CAAC,GAAG,GAAG,KAAK,MAAM,MAAM,KAAK,yEAAyE;AAC/G,IAAI,OAAO,KAAM,YAAY,OAAO,OAAQ,YAAU,KAAK,IAAI,SAAS,GAAG,GAAG;AAEhF,aAAW,CAAC,GAAG,KAAK,KAAK,MAAM,MAAM,KAAK,4EAA4E;AACpH,IAAI,OAAO,KAAM,YAAY,OAAO,SAAU,aAC5C,KAAK,IAAI,SAAS,GAAG,KAAK,GAC1B,KAAK,IAAI,eAAe,OAAO,CAAC,GAChC,KAAK,IAAI,cAAc,OAAO,CAAC;AAGnC,aAAW,CAAC,GAAG,MAAM,KAAK,KAAK,MAAM,MAAM,KAAK,iFAAiF;AAC/H,IAAI,OAAO,KAAM,YAAY,OAAO,SAAU,aAC9C,KAAK,IAAI,cAAc,OAAO,CAAC,GAC3B,OAAO,QAAS,YAAY,QAAM,IAAI,OAAO,IAAI,GAAG,IAAI;AAE9D,aAAW,CAAC,GAAG,IAAI,KAAK,MAAM,MAAM,KAAK,yDAAyD;AAChG,IAAI,OAAO,KAAM,YAAY,OAAO,QAAS,YAAY,QAAM,IAAI,OAAO,IAAI,GAAG,IAAI;AAEvF,aAAW,CAAC,GAAG,OAAO,GAAG,KAAK,MAAM,MAAM,KAAK,iFAAiF;AAC9H,IAAI,OAAO,KAAM,YAAU,KAAK,IAAI,WAAW,GAAG,OAAO,OAAO,SAAS,EAAE,CAAC;AAE9E,aAAW,CAAC,GAAG,GAAG,CAAC,KAAK,MAAM,MAAM,KAAK,2FAA2F;AAClI,IAAI,OAAO,KAAM,YAAY,OAAO,KAAM,YAC1C,KAAK,MAAM,eAAe,IAAI,aAAa,IAAI,UAAU,GAAG,CAAC;AAE/D,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { CARD_FIELDS, CardBuilder, VENDOR_EXCLUDE, isCandidate } from "./build.js";
|
|
2
|
+
import { defaultLabels } from "./classify.js";
|
|
3
|
+
import { buildEdgeIndex } from "./edge-index.js";
|
|
4
|
+
import { buildFactsIndex, factsFor } from "./facts.js";
|
|
5
|
+
import { TEST_PATH, namedRoutePath, routesCalledIn } from "./routes.js";
|
|
6
|
+
import { tier0ForCard } from "./tier0.js";
|
|
7
|
+
const CHECK_FILES_PER_QUERY = 200, FILE_JOINED_EDGES = /* @__PURE__ */ new Set(["IMPORTS", "TESTS_FILE"]);
|
|
8
|
+
function extractionConfigFrom(rules, edgeMappings, nodeMappings) {
|
|
9
|
+
const labelDecisions = {}, labelDefaults = {};
|
|
10
|
+
for (const m of nodeMappings)
|
|
11
|
+
!m.dr_layer || !m.dr_element_type || (labelDefaults[m.cbm_label] = `${m.dr_layer}.${m.dr_element_type}`, m.confidence === "high" && (labelDecisions[m.cbm_label] = { layer: m.dr_layer, type: m.dr_element_type }));
|
|
12
|
+
return {
|
|
13
|
+
rules,
|
|
14
|
+
edgeMappings: edgeMappings.filter((m) => m.usage === "verification_and_mapping"),
|
|
15
|
+
labelDecisions,
|
|
16
|
+
labelDefaults
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
class ExtractionTimeoutError extends Error {
|
|
20
|
+
stage;
|
|
21
|
+
constructor(stage) {
|
|
22
|
+
super(`Extraction ran out of time while ${stage}`), this.stage = stage, this.name = "ExtractionTimeoutError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function parseRouteName(name) {
|
|
26
|
+
const m = /^__route__([A-Z]+)__(\/.*)$/.exec(name);
|
|
27
|
+
return m ? { method: m[1], path: m[2] } : null;
|
|
28
|
+
}
|
|
29
|
+
function shortQualifiedName(qn, project) {
|
|
30
|
+
return qn.startsWith(project + ".") ? qn.slice(project.length + 1) : qn;
|
|
31
|
+
}
|
|
32
|
+
function symbolSpellings(qualifiedName, name, project) {
|
|
33
|
+
const route = parseRouteName(name) ?? parseRouteName(qualifiedName);
|
|
34
|
+
if (route)
|
|
35
|
+
return [`${route.method} ${route.path}`, `__route__${route.method}__${route.path}`];
|
|
36
|
+
const short = shortQualifiedName(qualifiedName, project), parts = short.split("."), out = /* @__PURE__ */ new Set([name]);
|
|
37
|
+
return parts.length >= 2 && parts[parts.length - 1] === name && out.add(`${parts[parts.length - 2]}.${name}`), out.add(short), [...out].filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
function cypherString(s) {
|
|
40
|
+
return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
41
|
+
}
|
|
42
|
+
function str(v) {
|
|
43
|
+
return typeof v == "string" && v ? v : null;
|
|
44
|
+
}
|
|
45
|
+
async function collectExtraction(graph, config, analyzer, options = {}) {
|
|
46
|
+
const checkDeadline = (stage) => {
|
|
47
|
+
if (options.deadline !== void 0 && Date.now() > options.deadline)
|
|
48
|
+
throw new ExtractionTimeoutError(stage);
|
|
49
|
+
}, labels = options.labels?.length ? options.labels : [.../* @__PURE__ */ new Set([...defaultLabels(config.rules), ...Object.keys(config.labelDecisions)])], routes = /* @__PURE__ */ new Set(), routeNodes = [], nodes = [], seen = /* @__PURE__ */ new Set();
|
|
50
|
+
for (const label of labels)
|
|
51
|
+
for (const node of await graph.search(label, void 0, CARD_FIELDS[label] ?? [])) {
|
|
52
|
+
if (label === "Route") {
|
|
53
|
+
for (const s of symbolSpellings(node.qualified_name, node.name, graph.project))
|
|
54
|
+
routes.add(s);
|
|
55
|
+
routeNodes.push(node);
|
|
56
|
+
}
|
|
57
|
+
!isCandidate(node, label, VENDOR_EXCLUDE) || seen.has(node.qualified_name) || label === "Method" && node.qualified_name.split(".").slice(-2, -1)[0] === node.name || options.files && node.file_path && !options.files.has(node.file_path) || (seen.add(node.qualified_name), nodes.push(node));
|
|
58
|
+
}
|
|
59
|
+
const edgeIndex = await buildEdgeIndex(graph), factsIndex = await buildFactsIndex(graph);
|
|
60
|
+
if (checkDeadline("reading the graph"), options.files) {
|
|
61
|
+
const files = options.files, inFiles = (qns) => (qns ?? []).some((qn) => files.has(edgeIndex.qnFile.get(qn) ?? ""));
|
|
62
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
63
|
+
const n = nodes[i];
|
|
64
|
+
n.label === "Route" && !n.file_path && !inFiles(edgeIndex.routeHandlers.get(n.qualified_name)) && !inFiles(edgeIndex.routeCallers.get(n.qualified_name)) && nodes.splice(i, 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const builder = new CardBuilder(graph, edgeIndex, { scope: graph.project, sourceRoot: options.sourceRoot }), handled = (n) => !!n.file_path || (edgeIndex.routeHandlers.get(n.qualified_name) ?? []).length > 0, served = routeNodes.filter(handled).map((n) => ({ name: n.qualified_name, ...parseRouteName(n.qualified_name) })).filter((r) => r.method), routeNames = /* @__PURE__ */ new Map();
|
|
68
|
+
if (routeNodes.length)
|
|
69
|
+
for (const [qn, name] of await graph.rows("MATCH (r:Route) RETURN r.qualified_name, r.name"))
|
|
70
|
+
typeof qn == "string" && typeof name == "string" && routeNames.set(qn, name);
|
|
71
|
+
const symbols = [], summary = { cards: 0, decided: 0, rejected: 0, narrowed: 0, undecided: 0, no_source: 0 };
|
|
72
|
+
let done = 0;
|
|
73
|
+
for (const node of nodes) {
|
|
74
|
+
done % 50 === 0 && checkDeadline("building cards");
|
|
75
|
+
const card = await builder.build(node);
|
|
76
|
+
if (options.onProgress?.(++done, nodes.length), options.files && !(card.file_path && options.files.has(card.file_path)))
|
|
77
|
+
continue;
|
|
78
|
+
const facts = factsFor(factsIndex, node.qualified_name, card.file_path), route = card.label === "Route" ? parseRouteName(card.name) : null, unhandled = !!route && !handled(node);
|
|
79
|
+
let tier0 = unhandled ? null : tier0ForCard(card, facts, config.rules);
|
|
80
|
+
const byLabel = config.labelDecisions[card.label];
|
|
81
|
+
!tier0 && byLabel && !unhandled && (tier0 = { kind: "decide", layer: byLabel.layer, type: byLabel.type, rule: `node-mapping:${card.label}`, confidence: "high" }), summary.cards++, card.source === null && card.label !== "Route" && summary.no_source++, tier0 ? tier0.kind === "decide" ? summary.decided++ : tier0.kind === "reject" ? summary.rejected++ : summary.narrowed++ : summary.undecided++, symbols.push({
|
|
82
|
+
qualifiedName: node.qualified_name,
|
|
83
|
+
card,
|
|
84
|
+
facts,
|
|
85
|
+
tier0,
|
|
86
|
+
...route ? { routePath: namedRoutePath(route.path, routeNames.get(node.qualified_name), card.source) } : {},
|
|
87
|
+
...unhandled ? { unhandled: !0 } : {}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const inScope = new Set(symbols.map((s) => s.qualifiedName)), scopeFiles = new Set(symbols.map((s) => s.card.file_path).filter((f) => !!f)), edges = [], edgeSeen = /* @__PURE__ */ new Set();
|
|
91
|
+
for (const type of [...new Set(config.edgeMappings.map((m) => m.cbm_edge))]) {
|
|
92
|
+
checkDeadline("reading edges");
|
|
93
|
+
const rows = await graph.rows(`MATCH (a)-[r:${type}]->(b) RETURN a.qualified_name, a.file_path, a.name, b.qualified_name, b.file_path, b.name`), fileJoined = FILE_JOINED_EDGES.has(type);
|
|
94
|
+
for (const [aq, af, an, bq, bf, bn] of rows) {
|
|
95
|
+
if (typeof aq != "string" || typeof bq != "string" || aq === bq)
|
|
96
|
+
continue;
|
|
97
|
+
const from = { qualifiedName: aq, name: str(an) ?? aq, file: str(af) }, to = { qualifiedName: bq, name: str(bn) ?? bq, file: str(bf) };
|
|
98
|
+
if (!(!options.files || inScope.has(aq) || inScope.has(bq) || fileJoined && (from.file !== null && scopeFiles.has(from.file) || to.file !== null && scopeFiles.has(to.file))))
|
|
99
|
+
continue;
|
|
100
|
+
const key = `${type}|${aq}|${bq}`;
|
|
101
|
+
edgeSeen.has(key) || (edgeSeen.add(key), edges.push({ type, from, to }));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (config.edgeMappings.some((m) => m.cbm_edge === "HTTP_CALLS")) {
|
|
105
|
+
for (const s of symbols)
|
|
106
|
+
if (!(!(s.tier0?.kind === "decide" && s.tier0.layer === "testing") && !(s.card.file_path && TEST_PATH.test(s.card.file_path))))
|
|
107
|
+
for (const name of routesCalledIn(s.card.source, served)) {
|
|
108
|
+
const key = `HTTP_CALLS|${s.qualifiedName}|${name}`;
|
|
109
|
+
edgeSeen.has(key) || (edgeSeen.add(key), edges.push({
|
|
110
|
+
type: "HTTP_CALLS",
|
|
111
|
+
from: { qualifiedName: s.qualifiedName, name: s.card.name, file: s.card.file_path },
|
|
112
|
+
to: { qualifiedName: name, name, file: null }
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const fileSymbols = {}, checkFiles = [...new Set(options.checkFiles ?? [])];
|
|
117
|
+
for (let i = 0; i < checkFiles.length; i += CHECK_FILES_PER_QUERY) {
|
|
118
|
+
const batch = checkFiles.slice(i, i + CHECK_FILES_PER_QUERY), rows = await graph.rows(`MATCH (n) WHERE n.file_path IN [${batch.map(cypherString).join(", ")}] RETURN n.file_path, n.qualified_name, n.name, n.label`);
|
|
119
|
+
for (const [file, qn, name, label] of rows) {
|
|
120
|
+
if (typeof file != "string" || typeof qn != "string" || label === "File" || label === "Folder")
|
|
121
|
+
continue;
|
|
122
|
+
const found = fileSymbols[file] ??= [];
|
|
123
|
+
for (const sp of symbolSpellings(qn, str(name) ?? qn, graph.project))
|
|
124
|
+
found.includes(sp) || found.push(sp);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { analyzer, project: graph.project, config, symbols, edges, fileSymbols, routes: [...routes], summary };
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
ExtractionTimeoutError,
|
|
131
|
+
FILE_JOINED_EDGES,
|
|
132
|
+
collectExtraction,
|
|
133
|
+
extractionConfigFrom,
|
|
134
|
+
parseRouteName,
|
|
135
|
+
shortQualifiedName,
|
|
136
|
+
symbolSpellings
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/analyzers/cards/extract.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * What the extraction cascade reads from a CBM project: a card, its facts and its Tier-0 outcome\n * for every candidate symbol in scope, and the CBM edges the edge mapping turns into\n * relationships. No network, no model calls, no writes. `codebase_extract` plans and stages a\n * changeset from it (src/analyzers/extraction-plan.ts).\n *\n * Tier 1 (a decision model) is not wired: a symbol Tier 0 narrows or leaves open is returned\n * undecided, for the calling agent. A Tier-1 pass would run between the Tier-0 outcome and the\n * plan, on the cards this returns (docs/analyzers/CASCADE_DESIGN.md, build step 3).\n */\nimport type { Tier0Outcome, Tier0Rule } from \"../tier0-rules.js\";\nimport type { AnalyzerEdgeMapping, AnalyzerNodeMapping } from \"../types.js\";\nimport { CARD_FIELDS, CardBuilder, VENDOR_EXCLUDE, isCandidate } from \"./build.js\";\nimport { defaultLabels } from \"./classify.js\";\nimport { buildEdgeIndex } from \"./edge-index.js\";\nimport { buildFactsIndex, factsFor } from \"./facts.js\";\nimport { TEST_PATH, namedRoutePath, routesCalledIn } from \"./routes.js\";\nimport { tier0ForCard } from \"./tier0.js\";\nimport type { CardFacts, CardGraph, GraphNode, SymbolCard } from \"./types.js\";\n\n/** Files whose symbols one query lists. */\nconst CHECK_FILES_PER_QUERY = 200;\n\n/** CBM edges whose ends are files: an end is joined to the Module card of its file. */\nexport const FILE_JOINED_EDGES: ReadonlySet<string> = new Set([\"IMPORTS\", \"TESTS_FILE\"]);\n\n/** A layer and type a label maps to outright (the node mapping's high-confidence entries). */\nexport interface LabelDecision {\n layer: string;\n type: string;\n}\n\nexport interface ExtractionConfig {\n rules: Tier0Rule[];\n /** Edge mappings that create relationships (`usage: \"verification_and_mapping\"`). */\n edgeMappings: AnalyzerEdgeMapping[];\n /**\n * Labels whose node mapping decides the type when no Tier-0 rule matches: a CBM Route is an\n * `api.operation` (docs/analyzers/CASCADE_DESIGN.md, \"A route handler is an application\n * function, not an operation\").\n */\n labelDecisions: Record<string, LabelDecision>;\n /** The node mapping's default type per label, offered to the agent as a hint for undecided cards. */\n labelDefaults: Record<string, string>;\n}\n\n/** The extraction settings an analyzer mapping gives: its Tier-0 rules, edge and node mappings. */\nexport function extractionConfigFrom(\n rules: Tier0Rule[],\n edgeMappings: AnalyzerEdgeMapping[],\n nodeMappings: AnalyzerNodeMapping[]\n): ExtractionConfig {\n const labelDecisions: Record<string, LabelDecision> = {};\n const labelDefaults: Record<string, string> = {};\n for (const m of nodeMappings) {\n if (!m.dr_layer || !m.dr_element_type) continue;\n labelDefaults[m.cbm_label] = `${m.dr_layer}.${m.dr_element_type}`;\n if (m.confidence === \"high\") labelDecisions[m.cbm_label] = { layer: m.dr_layer, type: m.dr_element_type };\n }\n return {\n rules,\n edgeMappings: edgeMappings.filter((m) => m.usage === \"verification_and_mapping\"),\n labelDecisions,\n labelDefaults,\n };\n}\n\nexport interface ExtractOptions {\n /** Absolute path of the indexed codebase, so source is read from disk. */\n sourceRoot?: string;\n /** Only symbols in these files (paths relative to the codebase root); every file when absent. */\n files?: ReadonlySet<string>;\n /** CBM labels to extract (default: the card labels plus every label a rule names). */\n labels?: string[];\n /**\n * Files whose symbols the caller wants listed (in {@link ExtractionGraph.fileSymbols}), to check\n * that the symbols existing elements name are still there.\n */\n checkFiles?: string[];\n /** Give up past this time (epoch ms), with an {@link ExtractionTimeoutError}. */\n deadline?: number;\n onProgress?: (done: number, total: number) => void;\n}\n\n/** The extraction ran past its deadline. */\nexport class ExtractionTimeoutError extends Error {\n constructor(readonly stage: string) {\n super(`Extraction ran out of time while ${stage}`);\n this.name = \"ExtractionTimeoutError\";\n }\n}\n\nexport interface ExtractedSymbol {\n /** Full qualified name (with the CBM project prefix; none for routes). */\n qualifiedName: string;\n card: SymbolCard;\n facts: CardFacts;\n /** The Tier-0 outcome, or a node-mapping decision (`rule: \"node-mapping:<label>\"`), or null. */\n tier0: Tier0Outcome | null;\n /** For a route: its path with the parameters named where the code names them (`/owners/{ownerId}`). */\n routePath?: string;\n /**\n * For a route: nothing in the code handles it (no file of its own, no HANDLES edge). It is a URL\n * client or test code calls, not one this code serves, so it is never decided.\n */\n unhandled?: true;\n}\n\nexport interface EdgeEnd {\n qualifiedName: string;\n name: string;\n file: string | null;\n}\n\nexport interface ExtractedEdge {\n type: string;\n from: EdgeEnd;\n to: EdgeEnd;\n}\n\nexport interface ExtractionGraph {\n analyzer: string;\n project: string;\n config: ExtractionConfig;\n symbols: ExtractedSymbol[];\n /** Mapped CBM edges with at least one end in scope. */\n edges: ExtractedEdge[];\n /**\n * For each requested file the graph has nodes in: every spelling of their symbols (name,\n * `Parent.name`, qualified name without the project). A file the graph has no nodes in is absent.\n */\n fileSymbols: Record<string, string[]>;\n /** Every route the graph has, as `METHOD /path` and as `__route__METHOD__/path`. */\n routes: string[];\n summary: { cards: number; decided: number; rejected: number; narrowed: number; undecided: number; no_source: number };\n}\n\n/** `__route__GET__/owners/{}` -> { method: \"GET\", path: \"/owners/{}\" }, or null for anything else. */\nexport function parseRouteName(name: string): { method: string; path: string } | null {\n const m = /^__route__([A-Z]+)__(\\/.*)$/.exec(name);\n return m ? { method: m[1], path: m[2] } : null;\n}\n\n/** A qualified name without the project prefix. */\nexport function shortQualifiedName(qn: string, project: string): string {\n return qn.startsWith(project + \".\") ? qn.slice(project.length + 1) : qn;\n}\n\n/**\n * The ways an element's source reference may name a symbol: its name, `Parent.name` and its\n * qualified name without the project; for a route, `METHOD /path` and CBM's route name.\n */\nexport function symbolSpellings(qualifiedName: string, name: string, project: string): string[] {\n const route = parseRouteName(name) ?? parseRouteName(qualifiedName);\n if (route) return [`${route.method} ${route.path}`, `__route__${route.method}__${route.path}`];\n const short = shortQualifiedName(qualifiedName, project);\n const parts = short.split(\".\");\n const out = new Set<string>([name]);\n if (parts.length >= 2 && parts[parts.length - 1] === name) out.add(`${parts[parts.length - 2]}.${name}`);\n out.add(short);\n return [...out].filter(Boolean);\n}\n\nfunction cypherString(s: string): string {\n return `'${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\")}'`;\n}\n\nfunction str(v: unknown): string | null {\n return typeof v === \"string\" && v ? v : null;\n}\n\n/**\n * Read a project for extraction. Cards and Tier 0 are those of `dr analyzer classify\n * --tier0-only`; a label the node mapping decides outright (Route) is decided when no rule matches.\n */\nexport async function collectExtraction(\n graph: CardGraph,\n config: ExtractionConfig,\n analyzer: string,\n options: ExtractOptions = {}\n): Promise<ExtractionGraph> {\n const checkDeadline = (stage: string) => {\n if (options.deadline !== undefined && Date.now() > options.deadline) throw new ExtractionTimeoutError(stage);\n };\n const labels = options.labels?.length\n ? options.labels\n : [...new Set([...defaultLabels(config.rules), ...Object.keys(config.labelDecisions)])];\n\n // Candidates for cards, and every route.\n const routes = new Set<string>();\n const routeNodes: GraphNode[] = [];\n const nodes: GraphNode[] = [];\n const seen = new Set<string>();\n for (const label of labels) {\n for (const node of await graph.search(label, undefined, CARD_FIELDS[label] ?? [])) {\n if (label === \"Route\") {\n for (const s of symbolSpellings(node.qualified_name, node.name, graph.project)) routes.add(s);\n routeNodes.push(node);\n }\n if (!isCandidate(node, label, VENDOR_EXCLUDE) || seen.has(node.qualified_name)) continue;\n // A Java or C# constructor is named after its class: like `constructor`, not an element.\n if (label === \"Method\" && node.qualified_name.split(\".\").slice(-2, -1)[0] === node.name) continue;\n // A route often has no file of its own (it takes its handler's); keep it until its card says.\n if (options.files && node.file_path && !options.files.has(node.file_path)) continue;\n seen.add(node.qualified_name);\n nodes.push(node);\n }\n }\n\n const edgeIndex = await buildEdgeIndex(graph);\n const factsIndex = await buildFactsIndex(graph);\n checkDeadline(\"reading the graph\");\n // With a file scope, a route with no file of its own is built (its handler resolved, its source\n // read) only when its handler, or something that reaches it, is in a scoped file.\n if (options.files) {\n const files = options.files;\n const inFiles = (qns: string[] | undefined) => (qns ?? []).some((qn) => files.has(edgeIndex.qnFile.get(qn) ?? \"\"));\n for (let i = nodes.length - 1; i >= 0; i--) {\n const n = nodes[i];\n if (n.label === \"Route\" && !n.file_path && !inFiles(edgeIndex.routeHandlers.get(n.qualified_name)) && !inFiles(edgeIndex.routeCallers.get(n.qualified_name))) {\n nodes.splice(i, 1);\n }\n }\n }\n const builder = new CardBuilder(graph, edgeIndex, { scope: graph.project, sourceRoot: options.sourceRoot });\n\n // A route is served here when it has a file of its own or a HANDLES edge.\n const handled = (n: GraphNode) => !!n.file_path || (edgeIndex.routeHandlers.get(n.qualified_name) ?? []).length > 0;\n const served = routeNodes\n .filter(handled)\n .map((n) => ({ name: n.qualified_name, ...parseRouteName(n.qualified_name)! }))\n .filter((r) => r.method);\n // CBM's stored route names keep the code's parameter names (`/owners/{ownerId}`); search_graph\n // returns them as `{}`, a query returns them as stored.\n const routeNames = new Map<string, string>();\n if (routeNodes.length) {\n for (const [qn, name] of await graph.rows(\"MATCH (r:Route) RETURN r.qualified_name, r.name\")) {\n if (typeof qn === \"string\" && typeof name === \"string\") routeNames.set(qn, name);\n }\n }\n\n const symbols: ExtractedSymbol[] = [];\n const summary = { cards: 0, decided: 0, rejected: 0, narrowed: 0, undecided: 0, no_source: 0 };\n let done = 0;\n for (const node of nodes) {\n if (done % 50 === 0) checkDeadline(\"building cards\");\n const card = await builder.build(node);\n options.onProgress?.(++done, nodes.length);\n if (options.files && !(card.file_path && options.files.has(card.file_path))) continue;\n const facts = factsFor(factsIndex, node.qualified_name, card.file_path);\n const route = card.label === \"Route\" ? parseRouteName(card.name) : null;\n // A route with no file of its own and no HANDLES edge: a URL that test or client code calls,\n // whoever the callers are.\n const unhandled = !!route && !handled(node);\n let tier0 = unhandled ? null : tier0ForCard(card, facts, config.rules);\n const byLabel = config.labelDecisions[card.label];\n if (!tier0 && byLabel && !unhandled) {\n tier0 = { kind: \"decide\", layer: byLabel.layer, type: byLabel.type, rule: `node-mapping:${card.label}`, confidence: \"high\" };\n }\n summary.cards++;\n if (card.source === null && card.label !== \"Route\") summary.no_source++;\n if (!tier0) summary.undecided++;\n else if (tier0.kind === \"decide\") summary.decided++;\n else if (tier0.kind === \"reject\") summary.rejected++;\n else summary.narrowed++;\n symbols.push({\n qualifiedName: node.qualified_name,\n card,\n facts,\n tier0,\n ...(route ? { routePath: namedRoutePath(route.path, routeNames.get(node.qualified_name), card.source) } : {}),\n ...(unhandled ? { unhandled: true as const } : {}),\n });\n }\n\n // Mapped edges with an end in scope (all of them when the whole project is in scope).\n const inScope = new Set(symbols.map((s) => s.qualifiedName));\n const scopeFiles = new Set(symbols.map((s) => s.card.file_path).filter((f): f is string => !!f));\n const edges: ExtractedEdge[] = [];\n const edgeSeen = new Set<string>();\n for (const type of [...new Set(config.edgeMappings.map((m) => m.cbm_edge))]) {\n checkDeadline(\"reading edges\");\n const rows = await graph.rows(\n `MATCH (a)-[r:${type}]->(b) RETURN a.qualified_name, a.file_path, a.name, b.qualified_name, b.file_path, b.name`\n );\n const fileJoined = FILE_JOINED_EDGES.has(type);\n for (const [aq, af, an, bq, bf, bn] of rows) {\n if (typeof aq !== \"string\" || typeof bq !== \"string\" || aq === bq) continue;\n const from = { qualifiedName: aq, name: str(an) ?? aq, file: str(af) };\n const to = { qualifiedName: bq, name: str(bn) ?? bq, file: str(bf) };\n const touches =\n !options.files ||\n inScope.has(aq) ||\n inScope.has(bq) ||\n (fileJoined && ((from.file !== null && scopeFiles.has(from.file)) || (to.file !== null && scopeFiles.has(to.file))));\n if (!touches) continue;\n const key = `${type}|${aq}|${bq}`;\n if (edgeSeen.has(key)) continue;\n edgeSeen.add(key);\n edges.push({ type, from, to });\n }\n }\n\n // A test that calls a served route by its URL (MockMvc, RestTemplate, supertest) makes the HTTP\n // call CBM has no edge for: one HTTP_CALLS edge from the test to the route.\n if (config.edgeMappings.some((m) => m.cbm_edge === \"HTTP_CALLS\")) {\n for (const s of symbols) {\n const testing = s.tier0?.kind === \"decide\" && s.tier0.layer === \"testing\";\n if (!testing && !(s.card.file_path && TEST_PATH.test(s.card.file_path))) continue;\n for (const name of routesCalledIn(s.card.source, served)) {\n const key = `HTTP_CALLS|${s.qualifiedName}|${name}`;\n if (edgeSeen.has(key)) continue;\n edgeSeen.add(key);\n edges.push({\n type: \"HTTP_CALLS\",\n from: { qualifiedName: s.qualifiedName, name: s.card.name, file: s.card.file_path },\n to: { qualifiedName: name, name, file: null },\n });\n }\n }\n }\n\n // The symbols in each file the caller asked about, of every label (a React component declared\n // as `const X = () => ...` is a Variable), a batch of files per query.\n const fileSymbols: Record<string, string[]> = {};\n const checkFiles = [...new Set(options.checkFiles ?? [])];\n for (let i = 0; i < checkFiles.length; i += CHECK_FILES_PER_QUERY) {\n const batch = checkFiles.slice(i, i + CHECK_FILES_PER_QUERY);\n const rows = await graph.rows(\n `MATCH (n) WHERE n.file_path IN [${batch.map(cypherString).join(\", \")}] RETURN n.file_path, n.qualified_name, n.name, n.label`\n );\n for (const [file, qn, name, label] of rows) {\n // A file the graph holds only as a File node (a document, a config) has no symbols to check.\n if (typeof file !== \"string\" || typeof qn !== \"string\" || label === \"File\" || label === \"Folder\") continue;\n const found = (fileSymbols[file] ??= []);\n for (const sp of symbolSpellings(qn, str(name) ?? qn, graph.project)) if (!found.includes(sp)) found.push(sp);\n }\n }\n\n return { analyzer, project: graph.project, config, symbols, edges, fileSymbols, routes: [...routes], summary };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function decoratorName(text) {
|
|
2
|
+
return text.trim().replace(/^@/, "").replace(/\s*#.*$/, "").replace(/\(.*$/s, "").trim();
|
|
3
|
+
}
|
|
4
|
+
function parseList(v) {
|
|
5
|
+
if (Array.isArray(v))
|
|
6
|
+
return v.map(String);
|
|
7
|
+
if (typeof v != "string" || !v.trim())
|
|
8
|
+
return [];
|
|
9
|
+
try {
|
|
10
|
+
const p = JSON.parse(v);
|
|
11
|
+
return Array.isArray(p) ? p.map(String) : [];
|
|
12
|
+
} catch {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function buildFactsIndex(graph) {
|
|
17
|
+
const decorators = /* @__PURE__ */ new Map();
|
|
18
|
+
for (const [qn, decs] of await graph.rows("MATCH (b) WHERE b.decorators IS NOT NULL RETURN b.qualified_name, b.decorators"))
|
|
19
|
+
typeof qn == "string" && decorators.set(qn, parseList(decs));
|
|
20
|
+
const importers = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const [afile, bfile] of await graph.rows("MATCH (a)-[r:IMPORTS]->(b) RETURN a.file_path, b.file_path"))
|
|
22
|
+
typeof afile != "string" || typeof bfile != "string" || !bfile || afile === bfile || (importers.has(bfile) || importers.set(bfile, /* @__PURE__ */ new Set()), importers.get(bfile).add(afile));
|
|
23
|
+
const methods = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const [a] of await graph.rows("MATCH (a)-[r:DEFINES_METHOD]->(m) RETURN a.qualified_name, m.qualified_name"))
|
|
25
|
+
typeof a == "string" && methods.set(a, (methods.get(a) ?? 0) + 1);
|
|
26
|
+
const usage = /* @__PURE__ */ new Map();
|
|
27
|
+
for (const [afile, b, bfile] of await graph.rows("MATCH (a)-[r:USAGE]->(b) RETURN a.file_path, b.qualified_name, b.file_path"))
|
|
28
|
+
typeof afile != "string" || typeof b != "string" || afile === bfile || (usage.has(b) || usage.set(b, /* @__PURE__ */ new Set()), usage.get(b).add(afile));
|
|
29
|
+
return { decorators, importers, methods, usage };
|
|
30
|
+
}
|
|
31
|
+
function factsFor(idx, fullQualifiedName, filePath) {
|
|
32
|
+
const decorators = idx.decorators.get(fullQualifiedName) ?? [];
|
|
33
|
+
return {
|
|
34
|
+
decorators,
|
|
35
|
+
decorator_names: decorators.map(decoratorName),
|
|
36
|
+
file_importers: filePath ? idx.importers.get(filePath)?.size ?? 0 : 0,
|
|
37
|
+
methods: idx.methods.get(fullQualifiedName) ?? 0,
|
|
38
|
+
usage_files: idx.usage.get(fullQualifiedName)?.size ?? 0
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
buildFactsIndex,
|
|
43
|
+
decoratorName,
|
|
44
|
+
factsFor
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=facts.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/analyzers/cards/facts.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Tier-0 structural facts: decorators and a few reuse counts, from one graph query per fact.\n */\nimport type { CardFacts, CardGraph } from \"./types.js\";\n\n/** `@field_validator(\"X\", mode=\"before\")` -> `field_validator`; `@router.get('/')` -> `router.get`. */\nexport function decoratorName(text: string): string {\n return text.trim().replace(/^@/, \"\").replace(/\\s*#.*$/, \"\").replace(/\\(.*$/s, \"\").trim();\n}\n\n/** CBM stores decorators as a JSON-encoded list (or, in some builds, a real one). */\nfunction parseList(v: unknown): string[] {\n if (Array.isArray(v)) return v.map(String);\n if (typeof v !== \"string\" || !v.trim()) return [];\n try {\n const p = JSON.parse(v);\n return Array.isArray(p) ? p.map(String) : [];\n } catch {\n return [];\n }\n}\n\nexport interface FactsIndex {\n /** full qualified name -> decorator texts */\n decorators: Map<string, string[]>;\n /** file path -> other files importing it */\n importers: Map<string, Set<string>>;\n /** full qualified name -> DEFINES_METHOD count */\n methods: Map<string, number>;\n /** full qualified name -> other files with USAGE edges into it */\n usage: Map<string, Set<string>>;\n}\n\nexport async function buildFactsIndex(graph: CardGraph): Promise<FactsIndex> {\n const decorators = new Map<string, string[]>();\n for (const [qn, decs] of await graph.rows(\"MATCH (b) WHERE b.decorators IS NOT NULL RETURN b.qualified_name, b.decorators\")) {\n if (typeof qn === \"string\") decorators.set(qn, parseList(decs));\n }\n const importers = new Map<string, Set<string>>();\n for (const [afile, bfile] of await graph.rows(\"MATCH (a)-[r:IMPORTS]->(b) RETURN a.file_path, b.file_path\")) {\n if (typeof afile !== \"string\" || typeof bfile !== \"string\" || !bfile || afile === bfile) continue;\n if (!importers.has(bfile)) importers.set(bfile, new Set());\n importers.get(bfile)!.add(afile);\n }\n const methods = new Map<string, number>();\n for (const [a] of await graph.rows(\"MATCH (a)-[r:DEFINES_METHOD]->(m) RETURN a.qualified_name, m.qualified_name\")) {\n if (typeof a === \"string\") methods.set(a, (methods.get(a) ?? 0) + 1);\n }\n const usage = new Map<string, Set<string>>();\n for (const [afile, b, bfile] of await graph.rows(\"MATCH (a)-[r:USAGE]->(b) RETURN a.file_path, b.qualified_name, b.file_path\")) {\n if (typeof afile !== \"string\" || typeof b !== \"string\" || afile === bfile) continue;\n if (!usage.has(b)) usage.set(b, new Set());\n usage.get(b)!.add(afile);\n }\n return { decorators, importers, methods, usage };\n}\n\n/** Facts for one symbol, by its full (project-prefixed) qualified name and file. */\nexport function factsFor(idx: FactsIndex, fullQualifiedName: string, filePath: string | null): CardFacts {\n const decorators = idx.decorators.get(fullQualifiedName) ?? [];\n return {\n decorators,\n decorator_names: decorators.map(decoratorName),\n file_importers: filePath ? (idx.importers.get(filePath)?.size ?? 0) : 0,\n methods: idx.methods.get(fullQualifiedName) ?? 0,\n usage_files: idx.usage.get(fullQualifiedName)?.size ?? 0,\n };\n}\n"],
|
|
5
|
+
"mappings": "AAMM,SAAU,cAAc,MAAY;AACxC,SAAO,KAAK,KAAI,EAAG,QAAQ,MAAM,EAAE,EAAE,QAAQ,WAAW,EAAE,EAAE,QAAQ,UAAU,EAAE,EAAE,KAAI;AACxF;AAGA,SAAS,UAAU,GAAU;AAC3B,MAAI,MAAM,QAAQ,CAAC;AAAG,WAAO,EAAE,IAAI,MAAM;AACzC,MAAI,OAAO,KAAM,YAAY,CAAC,EAAE,KAAI;AAAI,WAAO,CAAA;AAC/C,MAAI;AACF,UAAM,IAAI,KAAK,MAAM,CAAC;AACtB,WAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,MAAM,IAAI,CAAA;EAC5C,QAAQ;AACN,WAAO,CAAA;EACT;AACF;AAaA,eAAsB,gBAAgB,OAAgB;AACpD,QAAM,aAAa,oBAAI,IAAG;AAC1B,aAAW,CAAC,IAAI,IAAI,KAAK,MAAM,MAAM,KAAK,gFAAgF;AACxH,IAAI,OAAO,MAAO,YAAU,WAAW,IAAI,IAAI,UAAU,IAAI,CAAC;AAEhE,QAAM,YAAY,oBAAI,IAAG;AACzB,aAAW,CAAC,OAAO,KAAK,KAAK,MAAM,MAAM,KAAK,4DAA4D;AACxG,IAAI,OAAO,SAAU,YAAY,OAAO,SAAU,YAAY,CAAC,SAAS,UAAU,UAC7E,UAAU,IAAI,KAAK,KAAG,UAAU,IAAI,OAAO,oBAAI,IAAG,CAAE,GACzD,UAAU,IAAI,KAAK,EAAG,IAAI,KAAK;AAEjC,QAAM,UAAU,oBAAI,IAAG;AACvB,aAAW,CAAC,CAAC,KAAK,MAAM,MAAM,KAAK,6EAA6E;AAC9G,IAAI,OAAO,KAAM,YAAU,QAAQ,IAAI,IAAI,QAAQ,IAAI,CAAC,KAAK,KAAK,CAAC;AAErE,QAAM,QAAQ,oBAAI,IAAG;AACrB,aAAW,CAAC,OAAO,GAAG,KAAK,KAAK,MAAM,MAAM,KAAK,4EAA4E;AAC3H,IAAI,OAAO,SAAU,YAAY,OAAO,KAAM,YAAY,UAAU,UAC/D,MAAM,IAAI,CAAC,KAAG,MAAM,IAAI,GAAG,oBAAI,IAAG,CAAE,GACzC,MAAM,IAAI,CAAC,EAAG,IAAI,KAAK;AAEzB,SAAO,EAAE,YAAY,WAAW,SAAS,MAAK;AAChD;AAGM,SAAU,SAAS,KAAiB,mBAA2B,UAAuB;AAC1F,QAAM,aAAa,IAAI,WAAW,IAAI,iBAAiB,KAAK,CAAA;AAC5D,SAAO;IACL;IACA,iBAAiB,WAAW,IAAI,aAAa;IAC7C,gBAAgB,WAAY,IAAI,UAAU,IAAI,QAAQ,GAAG,QAAQ,IAAK;IACtE,SAAS,IAAI,QAAQ,IAAI,iBAAiB,KAAK;IAC/C,aAAa,IAAI,MAAM,IAAI,iBAAiB,GAAG,QAAQ;;AAE3D;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CbmToolError } from "../cbm-responses.js";
|
|
2
|
+
function graphNodeOf(node) {
|
|
3
|
+
const qualifiedName = String(node.qualified_name ?? node.name ?? "");
|
|
4
|
+
return {
|
|
5
|
+
...node.properties ?? {},
|
|
6
|
+
label: node.label,
|
|
7
|
+
name: node.label === "Route" ? qualifiedName : String(node.name ?? ""),
|
|
8
|
+
qualified_name: qualifiedName,
|
|
9
|
+
file_path: typeof node.file_path == "string" ? node.file_path : ""
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function cbmCardGraph(tools, project) {
|
|
13
|
+
return {
|
|
14
|
+
project,
|
|
15
|
+
async search(label, filePattern, fields) {
|
|
16
|
+
return (await tools.searchGraph({
|
|
17
|
+
project,
|
|
18
|
+
label,
|
|
19
|
+
...filePattern ? { file_pattern: filePattern } : {},
|
|
20
|
+
...fields.length ? { fields } : {}
|
|
21
|
+
})).map(graphNodeOf);
|
|
22
|
+
},
|
|
23
|
+
async rows(cypher) {
|
|
24
|
+
return (await tools.queryGraph(cypher, project)).rows;
|
|
25
|
+
},
|
|
26
|
+
async snippet(qualifiedName) {
|
|
27
|
+
try {
|
|
28
|
+
const raw = await tools.call("get_code_snippet", { project, qualified_name: qualifiedName, source_mode: "full" });
|
|
29
|
+
return raw && typeof raw.source == "string" ? { source: raw.source } : null;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error instanceof CbmToolError)
|
|
32
|
+
return null;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
cbmCardGraph,
|
|
40
|
+
graphNodeOf
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/analyzers/cards/graph.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The card builder's view of a CBM project, over the analyzer's {@link CbmTools}.\n */\nimport { CbmToolError, type CbmGraphNode, type CbmTools } from \"../cbm-responses.js\";\nimport type { CardGraph, GraphNode } from \"./types.js\";\n\n/**\n * Flatten a normalised search_graph node: its properties become top-level fields. A Route keeps\n * its full qualified name (`__route__GET__/owners`) as its name, since CBM's own name for it is\n * only the path, or a fragment of it.\n */\nexport function graphNodeOf(node: CbmGraphNode): GraphNode {\n const qualifiedName = String(node.qualified_name ?? node.name ?? \"\");\n const props = node.properties ?? {};\n return {\n ...props,\n label: node.label,\n name: node.label === \"Route\" ? qualifiedName : String(node.name ?? \"\"),\n qualified_name: qualifiedName,\n file_path: typeof node.file_path === \"string\" ? node.file_path : \"\",\n };\n}\n\nexport function cbmCardGraph(tools: CbmTools, project: string): CardGraph {\n return {\n project,\n async search(label, filePattern, fields) {\n const nodes = await tools.searchGraph({\n project,\n label,\n ...(filePattern ? { file_pattern: filePattern } : {}),\n ...(fields.length ? { fields } : {}),\n });\n return nodes.map(graphNodeOf);\n },\n async rows(cypher) {\n return (await tools.queryGraph(cypher, project)).rows;\n },\n async snippet(qualifiedName) {\n try {\n // CBM 0.12's default (\"auto\") outlines containers of 200+ lines and caps output at about\n // 2,500 tokens; \"full\" returns the source (up to 500 lines, uncapped).\n const raw = (await tools.call(\"get_code_snippet\", { project, qualified_name: qualifiedName, source_mode: \"full\" })) as { source?: unknown };\n return raw && typeof raw.source === \"string\" ? { source: raw.source } : null;\n } catch (error) {\n // An unknown or source-less symbol comes back as an error payload: no source.\n if (error instanceof CbmToolError) return null;\n throw error;\n }\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AAGA,SAAS,oBAAsD;AAQzD,SAAU,YAAY,MAAkB;AAC5C,QAAM,gBAAgB,OAAO,KAAK,kBAAkB,KAAK,QAAQ,EAAE;AAEnE,SAAO;IACL,GAFY,KAAK,cAAc,CAAA;IAG/B,OAAO,KAAK;IACZ,MAAM,KAAK,UAAU,UAAU,gBAAgB,OAAO,KAAK,QAAQ,EAAE;IACrE,gBAAgB;IAChB,WAAW,OAAO,KAAK,aAAc,WAAW,KAAK,YAAY;;AAErE;AAEM,SAAU,aAAa,OAAiB,SAAe;AAC3D,SAAO;IACL;IACA,MAAM,OAAO,OAAO,aAAa,QAAM;AAOrC,cANc,MAAM,MAAM,YAAY;QACpC;QACA;QACA,GAAI,cAAc,EAAE,cAAc,YAAW,IAAK,CAAA;QAClD,GAAI,OAAO,SAAS,EAAE,OAAM,IAAK,CAAA;OAClC,GACY,IAAI,WAAW;IAC9B;IACA,MAAM,KAAK,QAAM;AACf,cAAQ,MAAM,MAAM,WAAW,QAAQ,OAAO,GAAG;IACnD;IACA,MAAM,QAAQ,eAAa;AACzB,UAAI;AAGF,cAAM,MAAO,MAAM,MAAM,KAAK,oBAAoB,EAAE,SAAS,gBAAgB,eAAe,aAAa,OAAM,CAAE;AACjH,eAAO,OAAO,OAAO,IAAI,UAAW,WAAW,EAAE,QAAQ,IAAI,OAAM,IAAK;MAC1E,SAAS,OAAO;AAEd,YAAI,iBAAiB;AAAc,iBAAO;AAC1C,cAAM;MACR;IACF;;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/analyzers/cards/index.ts"],
|
|
4
|
+
"sourcesContent": ["export * from \"./types.js\";\nexport * from \"./graph.js\";\nexport * from \"./edge-index.js\";\nexport * from \"./facts.js\";\nexport * from \"./build.js\";\nexport * from \"./tier0.js\";\nexport * from \"./classify.js\";\n"],
|
|
5
|
+
"mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
function normRoutePath(p) {
|
|
2
|
+
return p.replace(/\{[^}]*\}|:[A-Za-z_]\w*/g, "{}").replace(/\/+$/, "") || "/";
|
|
3
|
+
}
|
|
4
|
+
function slots(p) {
|
|
5
|
+
return (p.match(/\{\}/g) ?? []).length;
|
|
6
|
+
}
|
|
7
|
+
function fill(p, names) {
|
|
8
|
+
let i = 0;
|
|
9
|
+
return p.replace(/\{\}/g, () => `{${names[i++]}}`);
|
|
10
|
+
}
|
|
11
|
+
function templateNames(p) {
|
|
12
|
+
return [...p.matchAll(/\{(\w+)[^}]*\}|:([A-Za-z_]\w*)/g)].map((m) => m[1] ?? m[2]);
|
|
13
|
+
}
|
|
14
|
+
const NOT_PATH = /^(req|res|request|response|next|ctx|context|model|modelMap|session|self|cls|body|payload|result|bindingResult|redirectAttributes|principal|page|pageable|locale|headers?|form|dto)$/i, SEGMENT_TYPE = /^(final\s+)?(int|long|short|Integer|Long|Short|String|string|number|str|UUID|uuid\.UUID|bool|boolean|Boolean|Int|i32|i64|u32|u64|&str)\b/, NOT_PATH_TYPE = /\b(Model|ModelMap|BindingResult|HttpServletRequest|HttpServletResponse|RedirectAttributes|Principal|Pageable|Locale|Errors|WebRequest|Request|Response|NextFunction)\b/;
|
|
15
|
+
function parameterList(source) {
|
|
16
|
+
const m = /\b[\w$]+\s*\(([^()]*(?:\([^()]*\)[^()]*)*)\)\s*(?::[^{=]+)?(?:throws [\w., ]+)?\s*(?:\{|=>|->|:)/.exec(source);
|
|
17
|
+
return m ? m[1] : null;
|
|
18
|
+
}
|
|
19
|
+
function splitParams(list) {
|
|
20
|
+
const out = [];
|
|
21
|
+
let depth = 0, cur = "";
|
|
22
|
+
for (const ch of list)
|
|
23
|
+
"(<[{".includes(ch) ? depth++ : ")>]}".includes(ch) && depth--, ch === "," && depth === 0 ? (out.push(cur.trim()), cur = "") : cur += ch;
|
|
24
|
+
return cur.trim() && out.push(cur.trim()), out;
|
|
25
|
+
}
|
|
26
|
+
function paramName(p) {
|
|
27
|
+
const annotated = /@PathVariable\s*\(\s*(?:(?:name|value)\s*=\s*)?["'](\w+)["']/.exec(p)?.[1] ?? null, pathMarked = annotated !== null || /@(PathVariable|PathParam|Param)\b|\bPath\s*\(/.test(p), bare = p.replace(/@\w+(\s*\([^)]*\))?/g, "").replace(/=.*$/, "").trim();
|
|
28
|
+
let name, type = "";
|
|
29
|
+
const colon = /^([A-Za-z_$][\w$]*)\??\s*:\s*(.+)$/.exec(bare);
|
|
30
|
+
if (colon)
|
|
31
|
+
name = colon[1], type = colon[2];
|
|
32
|
+
else {
|
|
33
|
+
const words = bare.split(/\s+/).filter(Boolean);
|
|
34
|
+
name = words[words.length - 1]?.replace(/^[*&.]+/, ""), type = words.slice(0, -1).join(" ");
|
|
35
|
+
}
|
|
36
|
+
return !name || !/^[A-Za-z_$][\w$]*$/.test(name) ? null : { name, annotated: pathMarked ? annotated ?? name : null, type };
|
|
37
|
+
}
|
|
38
|
+
function namedRoutePath(path, cbmName, handlerSource) {
|
|
39
|
+
const n = slots(path);
|
|
40
|
+
if (n === 0)
|
|
41
|
+
return path;
|
|
42
|
+
if (typeof cbmName == "string" && cbmName.startsWith("/") && normRoutePath(cbmName) === normRoutePath(path) && templateNames(cbmName).length === n)
|
|
43
|
+
return fill(path, templateNames(cbmName));
|
|
44
|
+
if (!handlerSource)
|
|
45
|
+
return path;
|
|
46
|
+
const norm = normRoutePath(path);
|
|
47
|
+
for (const m of handlerSource.matchAll(/["'`](\/[^"'`\s]*)["'`]/g)) {
|
|
48
|
+
const literal = m[1], names = templateNames(literal);
|
|
49
|
+
if (names.length === n && norm.endsWith(normRoutePath(literal)))
|
|
50
|
+
return fill(path, names);
|
|
51
|
+
}
|
|
52
|
+
const list = parameterList(handlerSource);
|
|
53
|
+
if (list === null)
|
|
54
|
+
return path;
|
|
55
|
+
const params = splitParams(list).map(paramName).filter((p) => !!p), marked = params.filter((p) => p.annotated).map((p) => p.annotated);
|
|
56
|
+
if (marked.length === n)
|
|
57
|
+
return fill(path, marked);
|
|
58
|
+
const plain = params.filter((p) => !NOT_PATH.test(p.name) && !NOT_PATH_TYPE.test(p.type) && (!p.type || SEGMENT_TYPE.test(p.type.trim()))).map((p) => p.name);
|
|
59
|
+
return plain.length === n ? fill(path, plain) : path;
|
|
60
|
+
}
|
|
61
|
+
const TEST_PATH = /(^|\/)(tests?|__tests__)(\/|$)|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)test_[^/]*\.py$|_test\.(py|go)$|Tests?\.(java|kt|cs)$|_spec\.rb$|Spec\.(kt|groovy|scala)$/, METHODS = "get|post|put|patch|delete", PATH_ARG = "\\(\\s*[\"'`](\\/[^\"'`\\s]*)[\"'`]", CLIENT_CALL = new RegExp(`\\b(?:mockMvc|mvc|webTestClient|testClient|client|restTemplate|testRestTemplate|RequestEntity|MockMvcRequestBuilders|http|httpClient|axios|supertest\\([^)]*\\)|request\\([^)]*\\)|agent|api)\\s*\\.\\s*(${METHODS})(?:ForEntity|ForObject|ForLocation)?\\s*${PATH_ARG}`, "gi"), BARE_CALL = new RegExp(`(?<![\\w$.])(${METHODS})\\s*${PATH_ARG}`, "g"), FETCH_CALL = new RegExp(`\\bfetch\\s*${PATH_ARG}`, "g"), USES_HTTP_CLIENT = /\b(mockMvc|perform\s*\(|webTestClient|TestClient|restTemplate|supertest|request\s*\(\s*app|axios|httpClient)\b/i;
|
|
62
|
+
function routesCalledIn(source, served) {
|
|
63
|
+
if (!source)
|
|
64
|
+
return [];
|
|
65
|
+
const calls = [];
|
|
66
|
+
for (const m of source.matchAll(CLIENT_CALL))
|
|
67
|
+
calls.push({ method: m[1].toUpperCase(), url: m[2] });
|
|
68
|
+
for (const m of source.matchAll(FETCH_CALL))
|
|
69
|
+
calls.push({ method: "GET", url: m[1] });
|
|
70
|
+
if (USES_HTTP_CLIENT.test(source))
|
|
71
|
+
for (const m of source.matchAll(BARE_CALL))
|
|
72
|
+
calls.push({ method: m[1].toUpperCase(), url: m[2] });
|
|
73
|
+
const out = /* @__PURE__ */ new Set();
|
|
74
|
+
for (const call of calls) {
|
|
75
|
+
const method = call.method, url = call.url.replace(/[?#].*$/, ""), segs = normRoutePath(url).split("/");
|
|
76
|
+
let best = null;
|
|
77
|
+
for (const r of served) {
|
|
78
|
+
if (r.method !== method)
|
|
79
|
+
continue;
|
|
80
|
+
const rsegs = normRoutePath(r.path).split("/");
|
|
81
|
+
if (rsegs.length !== segs.length || rsegs.length > 1 && rsegs.slice(1).every((s) => s === "{}") || !rsegs.every((s, i) => s === "{}" ? segs[i] !== "" : s === segs[i]))
|
|
82
|
+
continue;
|
|
83
|
+
const params = slots(normRoutePath(r.path));
|
|
84
|
+
(!best || params < best.params) && (best = { name: r.name, params });
|
|
85
|
+
}
|
|
86
|
+
best && out.add(best.name);
|
|
87
|
+
}
|
|
88
|
+
return [...out];
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
TEST_PATH,
|
|
92
|
+
namedRoutePath,
|
|
93
|
+
normRoutePath,
|
|
94
|
+
routesCalledIn
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/analyzers/cards/routes.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Route helpers for extraction: the parameter names a route's path has in the code, and the\n * routes test code calls by URL.\n *\n * CBM names a route's parameters `{}` (`__route__GET__/owners/{}`). The names come from CBM's own\n * route name when it has them (`/owners/{ownerId}`), else from the handler: a path literal in its\n * source with as many parameters, or its path-annotated parameters, or its parameters.\n */\n\n/** A path with every parameter as `{}` and no trailing slash: `/owners/{ownerId}/` -> `/owners/{}`. */\nexport function normRoutePath(p: string): string {\n return p.replace(/\\{[^}]*\\}|:[A-Za-z_]\\w*/g, \"{}\").replace(/\\/+$/, \"\") || \"/\";\n}\n\nfunction slots(p: string): number {\n return (p.match(/\\{\\}/g) ?? []).length;\n}\n\n/** Fill a `{}` path's parameters with these names, in order. */\nfunction fill(p: string, names: string[]): string {\n let i = 0;\n return p.replace(/\\{\\}/g, () => `{${names[i++]}}`);\n}\n\n/** Parameter names in a path template, `{name}` or `:name`, in order. */\nfunction templateNames(p: string): string[] {\n return [...p.matchAll(/\\{(\\w+)[^}]*\\}|:([A-Za-z_]\\w*)/g)].map((m) => m[1] ?? m[2]);\n}\n\n/** Parameters that never carry a path segment: request objects, models, framework plumbing. */\nconst NOT_PATH = /^(req|res|request|response|next|ctx|context|model|modelMap|session|self|cls|body|payload|result|bindingResult|redirectAttributes|principal|page|pageable|locale|headers?|form|dto)$/i;\nconst SEGMENT_TYPE = /^(final\\s+)?(int|long|short|Integer|Long|Short|String|string|number|str|UUID|uuid\\.UUID|bool|boolean|Boolean|Int|i32|i64|u32|u64|&str)\\b/;\nconst NOT_PATH_TYPE =/\\b(Model|ModelMap|BindingResult|HttpServletRequest|HttpServletResponse|RedirectAttributes|Principal|Pageable|Locale|Errors|WebRequest|Request|Response|NextFunction)\\b/;\n\n/** The parameter list of the first function or method declared in a source text. */\nfunction parameterList(source: string): string | null {\n const m = /\\b[\\w$]+\\s*\\(([^()]*(?:\\([^()]*\\)[^()]*)*)\\)\\s*(?::[^{=]+)?(?:throws [\\w., ]+)?\\s*(?:\\{|=>|->|:)/.exec(source);\n return m ? m[1] : null;\n}\n\nfunction splitParams(list: string): string[] {\n const out: string[] = [];\n let depth = 0;\n let cur = \"\";\n for (const ch of list) {\n if (\"(<[{\".includes(ch)) depth++;\n else if (\")>]}\".includes(ch)) depth--;\n if (ch === \",\" && depth === 0) {\n out.push(cur.trim());\n cur = \"\";\n } else cur += ch;\n }\n if (cur.trim()) out.push(cur.trim());\n return out;\n}\n\n/** A parameter's name: `int ownerId`, `ownerId: int`, `owner_id: int = Path(...)`, `@PathVariable(\"ownerId\") int id`. */\nfunction paramName(p: string): { name: string; annotated: string | null; type: string } | null {\n const annotated = /@PathVariable\\s*\\(\\s*(?:(?:name|value)\\s*=\\s*)?[\"'](\\w+)[\"']/.exec(p)?.[1] ?? null;\n const pathMarked = annotated !== null || /@(PathVariable|PathParam|Param)\\b|\\bPath\\s*\\(/.test(p);\n const bare = p.replace(/@\\w+(\\s*\\([^)]*\\))?/g, \"\").replace(/=.*$/, \"\").trim();\n let name: string | undefined;\n let type = \"\";\n const colon = /^([A-Za-z_$][\\w$]*)\\??\\s*:\\s*(.+)$/.exec(bare);\n if (colon) {\n name = colon[1];\n type = colon[2];\n } else {\n const words = bare.split(/\\s+/).filter(Boolean);\n name = words[words.length - 1]?.replace(/^[*&.]+/, \"\");\n type = words.slice(0, -1).join(\" \");\n }\n if (!name || !/^[A-Za-z_$][\\w$]*$/.test(name)) return null;\n return { name, annotated: pathMarked ? (annotated ?? name) : null, type };\n}\n\n/**\n * The route's path with its parameters named where the code names them. `path` is CBM's\n * (`/owners/{}`); `cbmName` is CBM's own route name; `handlerSource` the handler's source.\n * Unnamed parameters stay `{}`.\n */\nexport function namedRoutePath(path: string, cbmName: unknown, handlerSource: string | null): string {\n const n = slots(path);\n if (n === 0) return path;\n if (typeof cbmName === \"string\" && cbmName.startsWith(\"/\") && normRoutePath(cbmName) === normRoutePath(path) && templateNames(cbmName).length === n) {\n return fill(path, templateNames(cbmName));\n }\n if (!handlerSource) return path;\n // A path literal in the handler (its mapping annotation) whose parameters line up with the route's.\n const norm = normRoutePath(path);\n for (const m of handlerSource.matchAll(/[\"'`](\\/[^\"'`\\s]*)[\"'`]/g)) {\n const literal = m[1];\n const names = templateNames(literal);\n if (names.length === n && norm.endsWith(normRoutePath(literal))) return fill(path, names);\n }\n // The handler's parameters: those marked as path parameters, else the ones that could be.\n const list = parameterList(handlerSource);\n if (list === null) return path;\n const params = splitParams(list).map(paramName).filter((p): p is NonNullable<typeof p> => !!p);\n const marked = params.filter((p) => p.annotated).map((p) => p.annotated!);\n if (marked.length === n) return fill(path, marked);\n // Unmarked, only those of a type a path segment can be (or of no stated type).\n const plain = params\n .filter((p) => !NOT_PATH.test(p.name) && !NOT_PATH_TYPE.test(p.type) && (!p.type || SEGMENT_TYPE.test(p.type.trim())))\n .map((p) => p.name);\n return plain.length === n ? fill(path, plain) : path;\n}\n\n/**\n * A test file, by its path: under a `test`/`tests`/`__tests__` folder, or named as a test\n * (`*.test.ts`, `*.spec.ts`, `test_*.py`, `*_test.go`, `*Tests.java`, `*_spec.rb`, `*Spec.kt`). A\n * `spec/` folder alone is not tests (this repository keeps its specification there).\n */\nexport const TEST_PATH =\n /(^|\\/)(tests?|__tests__)(\\/|$)|\\.(test|spec)\\.[cm]?[jt]sx?$|(^|\\/)test_[^/]*\\.py$|_test\\.(py|go)$|Tests?\\.(java|kt|cs)$|_spec\\.rb$|Spec\\.(kt|groovy|scala)$/;\n\nconst METHODS = \"get|post|put|patch|delete\";\nconst PATH_ARG = `\\\\(\\\\s*[\"'\\`](\\\\/[^\"'\\`\\\\s]*)[\"'\\`]`;\n/** An HTTP or test client's call: `mockMvc.perform(get(...))` aside, `client.get(\"/x\")`, `request(app).post('/x')`. */\nconst CLIENT_CALL = new RegExp(\n `\\\\b(?:mockMvc|mvc|webTestClient|testClient|client|restTemplate|testRestTemplate|RequestEntity|MockMvcRequestBuilders|http|httpClient|axios|supertest\\\\([^)]*\\\\)|request\\\\([^)]*\\\\)|agent|api)\\\\s*\\\\.\\\\s*(${METHODS})(?:ForEntity|ForObject|ForLocation)?\\\\s*${PATH_ARG}`,\n \"gi\"\n);\n/** A bare `get(\"/x\")`: a request builder, but only in a test that uses an HTTP client. */\nconst BARE_CALL = new RegExp(`(?<![\\\\w$.])(${METHODS})\\\\s*${PATH_ARG}`, \"g\");\nconst FETCH_CALL = new RegExp(`\\\\bfetch\\\\s*${PATH_ARG}`, \"g\");\nconst USES_HTTP_CLIENT = /\\b(mockMvc|perform\\s*\\(|webTestClient|TestClient|restTemplate|supertest|request\\s*\\(\\s*app|axios|httpClient)\\b/i;\n\n/**\n * The served routes (`__route__METHOD__/path` names) a test's source calls by URL:\n * `client.get(\"/owners/1\")`, `RequestEntity.get(\"/owners/1\")`, `request(app).post('/users')`,\n * `fetch(\"/users\")`, and in a test that uses an HTTP client, MockMvc's `get(\"/owners/{ownerId}\", 1)`.\n * A URL matches a route whose literal segments it repeats, a parameter matching any segment; the\n * query string is ignored. Of several, the one with fewest parameters wins. A route with no\n * literal segment (`GET /{}`) matches nothing: it would match any URL.\n */\nexport function routesCalledIn(source: string | null, served: ReadonlyArray<{ name: string; method: string; path: string }>): string[] {\n if (!source) return [];\n const calls: Array<{ method: string; url: string }> = [];\n for (const m of source.matchAll(CLIENT_CALL)) calls.push({ method: m[1].toUpperCase(), url: m[2] });\n for (const m of source.matchAll(FETCH_CALL)) calls.push({ method: \"GET\", url: m[1] });\n if (USES_HTTP_CLIENT.test(source)) for (const m of source.matchAll(BARE_CALL)) calls.push({ method: m[1].toUpperCase(), url: m[2] });\n const out = new Set<string>();\n for (const call of calls) {\n const method = call.method;\n const url = call.url.replace(/[?#].*$/, \"\");\n const segs = normRoutePath(url).split(\"/\");\n let best: { name: string; params: number } | null = null;\n for (const r of served) {\n if (r.method !== method) continue;\n const rsegs = normRoutePath(r.path).split(\"/\");\n if (rsegs.length !== segs.length) continue;\n if (rsegs.length > 1 && rsegs.slice(1).every((s) => s === \"{}\")) continue;\n if (!rsegs.every((s, i) => s === \"{}\" ? segs[i] !== \"\" : s === segs[i])) continue;\n const params = slots(normRoutePath(r.path));\n if (!best || params < best.params) best = { name: r.name, params };\n }\n if (best) out.add(best.name);\n }\n return [...out];\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { declarationOf, evaluateTier0 } from "../tier0-rules.js";
|
|
2
|
+
function tier0SymbolOf(card, facts) {
|
|
3
|
+
return {
|
|
4
|
+
label: card.label,
|
|
5
|
+
name: card.name,
|
|
6
|
+
file_path: card.file_path,
|
|
7
|
+
declaration: declarationOf(card.source, card.signature),
|
|
8
|
+
decorator_names: facts?.decorator_names ?? []
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function tier0ForCard(card, facts, rules) {
|
|
12
|
+
return evaluateTier0(tier0SymbolOf(card, facts), rules);
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
tier0ForCard,
|
|
16
|
+
tier0SymbolOf
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=tier0.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/analyzers/cards/tier0.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Tier 0 on cards: the analyzer's rules (src/analyzers/tier0-rules.ts) applied to a card and its\n * facts. Used by `dr analyzer classify` and by the classify-eval harness.\n */\nimport { declarationOf, evaluateTier0, type Tier0Outcome, type Tier0Rule, type Tier0Symbol } from \"../tier0-rules.js\";\nimport type { CardFacts } from \"./types.js\";\n\n/** The card fields Tier 0 reads (a harness card, with its corpus field, also fits). */\nexport type Tier0Card = { label: string; name: string; file_path: string | null; source: string | null; signature: string | null };\n\nexport function tier0SymbolOf(card: Tier0Card, facts: CardFacts | undefined): Tier0Symbol {\n return {\n label: card.label,\n name: card.name,\n file_path: card.file_path,\n declaration: declarationOf(card.source, card.signature),\n decorator_names: facts?.decorator_names ?? [],\n };\n}\n\nexport function tier0ForCard(card: Tier0Card, facts: CardFacts | undefined, rules: Tier0Rule[]): Tier0Outcome | null {\n return evaluateTier0(tier0SymbolOf(card, facts), rules);\n}\n"],
|
|
5
|
+
"mappings": "AAIA,SAAS,eAAe,qBAA0E;AAM5F,SAAU,cAAc,MAAiB,OAA4B;AACzE,SAAO;IACL,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,WAAW,KAAK;IAChB,aAAa,cAAc,KAAK,QAAQ,KAAK,SAAS;IACtD,iBAAiB,OAAO,mBAAmB,CAAA;;AAE/C;AAEM,SAAU,aAAa,MAAiB,OAA8B,OAAkB;AAC5F,SAAO,cAAc,cAAc,MAAM,KAAK,GAAG,KAAK;AACxD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|