@documentation-robotics/cli 0.1.13 → 0.3.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 +74 -2
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- 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 +171 -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/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/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 +195 -171
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +336 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +45 -0
- package/dist/analyzers/edge-relationships.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/analyzers/verify-engine.js +22 -19
- package/dist/analyzers/verify-engine.js.map +2 -2
- 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 +112 -28
- package/dist/cli.js.map +3 -3
- package/dist/coding-agents/agents/claude-code.js +3 -1
- package/dist/coding-agents/agents/claude-code.js.map +2 -2
- package/dist/coding-agents/claude-code-client.js +2 -1
- package/dist/coding-agents/claude-code-client.js.map +2 -2
- package/dist/coding-agents/copilot-client.js +17 -12
- package/dist/coding-agents/copilot-client.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +152 -0
- package/dist/coding-agents/default-permissions.js.map +7 -0
- package/dist/coding-agents/drbot-mcp.js +167 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +12 -7
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +134 -23
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/audit.js +6 -6
- package/dist/commands/audit.js.map +2 -2
- package/dist/commands/changeset-verify.js +55 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +261 -147
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +52 -19
- package/dist/commands/delete.js.map +3 -3
- package/dist/commands/element.js +3 -2
- package/dist/commands/element.js.map +2 -2
- package/dist/commands/farm.js +773 -0
- package/dist/commands/farm.js.map +7 -0
- package/dist/commands/init.js +11 -1
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +122 -0
- package/dist/commands/mcp.js.map +7 -0
- package/dist/commands/relationship.js +251 -47
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +1 -1
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +56 -24
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -11
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/validate.js +41 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +2 -2
- package/dist/commands/version.js.map +2 -2
- package/dist/commands/visualize.js +4 -3
- package/dist/commands/visualize.js.map +2 -2
- package/dist/config.js +83 -0
- package/dist/config.js.map +7 -0
- package/dist/core/annotation-store.js +138 -0
- package/dist/core/annotation-store.js.map +7 -0
- package/dist/core/catalog-migration.js +33 -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 +153 -0
- package/dist/core/changeset-base-index.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 +518 -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/farm-manifest.js +140 -0
- package/dist/core/farm-manifest.js.map +7 -0
- package/dist/core/farm-sync-engine.js +346 -0
- package/dist/core/farm-sync-engine.js.map +7 -0
- package/dist/core/farm-sync-state.js +167 -0
- package/dist/core/farm-sync-state.js.map +7 -0
- package/dist/core/manifest.js +6 -3
- package/dist/core/manifest.js.map +2 -2
- package/dist/core/migration-registry.js +84 -1
- 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 +257 -44
- package/dist/core/model.js.map +3 -3
- package/dist/core/mutation-handler.js +71 -6
- package/dist/core/mutation-handler.js.map +2 -2
- 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 +68 -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 +64 -17
- 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 +83 -18
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/virtual-projection.js +14 -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 +2209 -7727
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-map.md +13 -0
- package/dist/integrations/claude_code/commands/dr-plan.md +40 -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 +79 -0
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +52 -1
- 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 +92 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +1 -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 +222 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -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 +111 -0
- package/dist/mcp/api-key-manager.js.map +7 -0
- package/dist/mcp/changeset-only.js +34 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- package/dist/mcp/http-transport.js +176 -0
- package/dist/mcp/http-transport.js.map +7 -0
- package/dist/mcp/resource-registry.js +97 -0
- package/dist/mcp/resource-registry.js.map +7 -0
- package/dist/mcp/tool-registry.js +60 -0
- package/dist/mcp/tool-registry.js.map +7 -0
- package/dist/mcp/tools/annotation-create.js +39 -0
- package/dist/mcp/tools/annotation-create.js.map +7 -0
- package/dist/mcp/tools/annotation-delete.js +29 -0
- package/dist/mcp/tools/annotation-delete.js.map +7 -0
- package/dist/mcp/tools/annotation-get.js +29 -0
- package/dist/mcp/tools/annotation-get.js.map +7 -0
- package/dist/mcp/tools/annotation-list.js +24 -0
- package/dist/mcp/tools/annotation-list.js.map +7 -0
- package/dist/mcp/tools/annotation-reply.js +34 -0
- package/dist/mcp/tools/annotation-reply.js.map +7 -0
- package/dist/mcp/tools/annotation-shared.js +10 -0
- package/dist/mcp/tools/annotation-shared.js.map +7 -0
- package/dist/mcp/tools/annotation-update.js +39 -0
- package/dist/mcp/tools/annotation-update.js.map +7 -0
- package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +184 -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-list.js +33 -0
- package/dist/mcp/tools/changeset-list.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 +55 -0
- package/dist/mcp/tools/changeset-show.js.map +7 -0
- package/dist/mcp/tools/changeset-verify.js +43 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- package/dist/mcp/tools/chat-launch.js +50 -0
- package/dist/mcp/tools/chat-launch.js.map +7 -0
- package/dist/mcp/tools/chat-status.js +26 -0
- package/dist/mcp/tools/chat-status.js.map +7 -0
- package/dist/mcp/tools/index.js +65 -0
- package/dist/mcp/tools/index.js.map +7 -0
- package/dist/mcp/tools/model-add.js +107 -0
- package/dist/mcp/tools/model-add.js.map +7 -0
- package/dist/mcp/tools/model-delete.js +99 -0
- package/dist/mcp/tools/model-delete.js.map +7 -0
- package/dist/mcp/tools/model-export.js +72 -0
- package/dist/mcp/tools/model-export.js.map +7 -0
- package/dist/mcp/tools/model-info.js +51 -0
- package/dist/mcp/tools/model-info.js.map +7 -0
- package/dist/mcp/tools/model-list.js +53 -0
- package/dist/mcp/tools/model-list.js.map +7 -0
- package/dist/mcp/tools/model-reload.js +34 -0
- package/dist/mcp/tools/model-reload.js.map +7 -0
- package/dist/mcp/tools/model-search.js +102 -0
- package/dist/mcp/tools/model-search.js.map +7 -0
- package/dist/mcp/tools/model-show.js +68 -0
- package/dist/mcp/tools/model-show.js.map +7 -0
- package/dist/mcp/tools/model-stats.js +22 -0
- package/dist/mcp/tools/model-stats.js.map +7 -0
- package/dist/mcp/tools/model-trace.js +118 -0
- package/dist/mcp/tools/model-trace.js.map +7 -0
- package/dist/mcp/tools/model-update.js +121 -0
- package/dist/mcp/tools/model-update.js.map +7 -0
- package/dist/mcp/tools/model-validate.js +78 -0
- package/dist/mcp/tools/model-validate.js.map +7 -0
- package/dist/mcp/tools/relationship-tools.js +98 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- package/dist/mcp/tools/shared.js +126 -0
- package/dist/mcp/tools/shared.js.map +7 -0
- package/dist/mcp/tools/spec-type.js +90 -0
- package/dist/mcp/tools/spec-type.js.map +7 -0
- package/dist/reports/model-layer-report-generator.js +5 -3
- package/dist/reports/model-layer-report-generator.js.map +2 -2
- package/dist/reports/model-readme-generator.js +116 -0
- package/dist/reports/model-readme-generator.js.map +7 -0
- package/dist/reports/model-report-data.js +37 -1
- package/dist/reports/model-report-data.js.map +2 -2
- package/dist/reports/model-report-orchestrator.js +29 -6
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/reports/model-report-utils.js +9 -0
- package/dist/reports/model-report-utils.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
- 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 +745 -370
- package/dist/schemas/bundled/base.json +3 -220
- package/dist/schemas/bundled/business.json +452 -366
- package/dist/schemas/bundled/data-model.json +403 -383
- 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 +718 -1643
- 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 +234 -31
- package/dist/server/server.js.map +2 -2
- package/dist/server-entry.js +2 -1
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/config.js +2 -40
- package/dist/telemetry/config.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/types/index.js.map +2 -2
- package/dist/utils/ansi-suppressor.js +23 -0
- package/dist/utils/ansi-suppressor.js.map +7 -0
- package/dist/utils/attribute-patch.js +41 -0
- package/dist/utils/attribute-patch.js.map +7 -0
- package/dist/utils/color-utils.js +34 -0
- package/dist/utils/color-utils.js.map +7 -0
- package/dist/utils/element-reference-shape.js +15 -1
- package/dist/utils/element-reference-shape.js.map +3 -3
- package/dist/utils/error-hints.js +29 -0
- package/dist/utils/error-hints.js.map +7 -0
- package/dist/utils/errors.js +75 -10
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/globals.js +17 -1
- package/dist/utils/globals.js.map +2 -2
- package/dist/utils/project-paths.js +50 -3
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/reference-path-parser.js +49 -0
- package/dist/utils/reference-path-parser.js.map +7 -0
- package/dist/utils/source-reference.js +17 -10
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +10 -2
- package/dist/utils/spec-version.js.map +2 -2
- package/dist/validators/composed-reference-validator.js +240 -0
- package/dist/validators/composed-reference-validator.js.map +7 -0
- package/dist/validators/reference-validator.js +57 -13
- package/dist/validators/reference-validator.js.map +3 -3
- package/dist/validators/relationship-schema-validator.js +33 -5
- package/dist/validators/relationship-schema-validator.js.map +3 -3
- 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-CxzcuU_o.js +84 -0
- package/dist/viewer/assets/main-j-7ULYpm.css +1 -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 +7 -3
- package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
- package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const TIER0_KEYS = ["labels", "when", "decide", "narrow", "confidence"], WHEN_KEYS = ["decorator", "declaration", "file", "name"];
|
|
2
|
+
function parseTarget(ruleName, s, allowWildcard) {
|
|
3
|
+
if (typeof s != "string")
|
|
4
|
+
throw new Error(`Tier-0 rule ${ruleName}: target must be a string, got ${JSON.stringify(s)}`);
|
|
5
|
+
const m = /^([a-z][a-z-]*)\.([a-z][a-z0-9-]*|\*)$/.exec(s);
|
|
6
|
+
if (!m)
|
|
7
|
+
throw new Error(`Tier-0 rule ${ruleName}: '${s}' is not 'layer.type'${allowWildcard ? " or 'layer.*'" : ""}`);
|
|
8
|
+
if (m[2] === "*" && !allowWildcard)
|
|
9
|
+
throw new Error(`Tier-0 rule ${ruleName}: decide needs a type, not '${s}'`);
|
|
10
|
+
return { layer: m[1], type: m[2] === "*" ? null : m[2] };
|
|
11
|
+
}
|
|
12
|
+
function tier0RulesFrom(heuristics) {
|
|
13
|
+
const rules = [];
|
|
14
|
+
for (const h of heuristics) {
|
|
15
|
+
const t = h.parameters?.tier0;
|
|
16
|
+
if (!t)
|
|
17
|
+
continue;
|
|
18
|
+
const unknown = Object.keys(t).filter((k) => !TIER0_KEYS.includes(k));
|
|
19
|
+
if (unknown.length)
|
|
20
|
+
throw new Error(`Tier-0 rule ${h.name}: unknown key ${unknown.map((k) => `'${k}'`).join(", ")} (known: ${TIER0_KEYS.join(", ")})`);
|
|
21
|
+
const labels = t.labels;
|
|
22
|
+
if (!Array.isArray(labels) || !labels.length || !labels.every((l) => typeof l == "string"))
|
|
23
|
+
throw new Error(`Tier-0 rule ${h.name}: labels must be a non-empty list of CBM labels`);
|
|
24
|
+
const when = {};
|
|
25
|
+
for (const [k, v] of Object.entries(t.when ?? {})) {
|
|
26
|
+
if (!WHEN_KEYS.includes(k))
|
|
27
|
+
throw new Error(`Tier-0 rule ${h.name}: unknown condition '${k}' (known: ${WHEN_KEYS.join(", ")})`);
|
|
28
|
+
if (typeof v != "string")
|
|
29
|
+
throw new Error(`Tier-0 rule ${h.name}: condition '${k}' must be a regular expression string`);
|
|
30
|
+
when[k] = new RegExp(v);
|
|
31
|
+
}
|
|
32
|
+
if (t.decide === void 0 == (t.narrow === void 0))
|
|
33
|
+
throw new Error(`Tier-0 rule ${h.name}: give exactly one of decide or narrow`);
|
|
34
|
+
const confidence = t.confidence ?? "medium";
|
|
35
|
+
if (!["high", "medium", "low"].includes(confidence))
|
|
36
|
+
throw new Error(`Tier-0 rule ${h.name}: confidence must be high, medium or low`);
|
|
37
|
+
let action;
|
|
38
|
+
if (t.decide === "none")
|
|
39
|
+
action = { kind: "reject" };
|
|
40
|
+
else if (t.decide !== void 0) {
|
|
41
|
+
const target = parseTarget(h.name, t.decide, !1);
|
|
42
|
+
action = { kind: "decide", layer: target.layer, type: target.type };
|
|
43
|
+
} else {
|
|
44
|
+
if (!Array.isArray(t.narrow) || !t.narrow.length)
|
|
45
|
+
throw new Error(`Tier-0 rule ${h.name}: narrow must be a non-empty list`);
|
|
46
|
+
action = { kind: "narrow", candidates: t.narrow.map((n) => parseTarget(h.name, n, !0)) };
|
|
47
|
+
}
|
|
48
|
+
rules.push({ name: h.name, labels, when, action, confidence });
|
|
49
|
+
}
|
|
50
|
+
return rules;
|
|
51
|
+
}
|
|
52
|
+
const MAX_DECLARATION_LINES = 8;
|
|
53
|
+
function depthDelta(line) {
|
|
54
|
+
let depth = 0, quote = null;
|
|
55
|
+
for (let i = 0; i < line.length; i++) {
|
|
56
|
+
const c = line[i];
|
|
57
|
+
if (quote) {
|
|
58
|
+
c === "\\" ? i++ : c === quote && (quote = null);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
c === '"' || c === "'" || c === "`" ? quote = c : c === "(" || c === "[" ? depth++ : (c === ")" || c === "]") && depth--;
|
|
62
|
+
}
|
|
63
|
+
return depth;
|
|
64
|
+
}
|
|
65
|
+
function stripLeadingDecorators(line) {
|
|
66
|
+
let s = line;
|
|
67
|
+
for (; ; ) {
|
|
68
|
+
const m = /^@[\w.$]+/.exec(s);
|
|
69
|
+
if (!m)
|
|
70
|
+
return { rest: s.trim(), open: 0 };
|
|
71
|
+
if (s = s.slice(m[0].length), s.startsWith("(")) {
|
|
72
|
+
let depth = 0, i = 0, quote = null;
|
|
73
|
+
for (; i < s.length; i++) {
|
|
74
|
+
const c = s[i];
|
|
75
|
+
if (quote) {
|
|
76
|
+
c === "\\" ? i++ : c === quote && (quote = null);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (c === '"' || c === "'" || c === "`")
|
|
80
|
+
quote = c;
|
|
81
|
+
else if (c === "(")
|
|
82
|
+
depth++;
|
|
83
|
+
else if (c === ")" && (depth--, depth === 0))
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
if (depth > 0)
|
|
87
|
+
return { rest: "", open: depth };
|
|
88
|
+
s = s.slice(i + 1);
|
|
89
|
+
}
|
|
90
|
+
s = s.trimStart();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function declarationOf(source, signature) {
|
|
94
|
+
const lines = (source ?? "").split(`
|
|
95
|
+
`);
|
|
96
|
+
let i = 0, decoratorDepth = 0, docstring = null;
|
|
97
|
+
for (; i < lines.length; i++) {
|
|
98
|
+
let line = lines[i].trim();
|
|
99
|
+
if (decoratorDepth > 0) {
|
|
100
|
+
if (decoratorDepth += depthDelta(line), decoratorDepth > 0)
|
|
101
|
+
continue;
|
|
102
|
+
decoratorDepth = 0;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (docstring) {
|
|
106
|
+
line.includes(docstring) && (docstring = null);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!line || line.startsWith("//") || line.startsWith("#") || line.startsWith("/*") || line.startsWith("*"))
|
|
110
|
+
continue;
|
|
111
|
+
const doc = /^[rRbBuU]?("""|''')/.exec(line);
|
|
112
|
+
if (doc) {
|
|
113
|
+
line.slice(doc[0].length).includes(doc[1]) || (docstring = doc[1]);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (line.startsWith("@")) {
|
|
117
|
+
const { rest, open } = stripLeadingDecorators(line);
|
|
118
|
+
if (open > 0) {
|
|
119
|
+
decoratorDepth = open;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!rest)
|
|
123
|
+
continue;
|
|
124
|
+
line = rest;
|
|
125
|
+
}
|
|
126
|
+
const parts = [line];
|
|
127
|
+
let depth = depthDelta(line);
|
|
128
|
+
for (let j = i + 1; depth > 0 && j < lines.length && parts.length < MAX_DECLARATION_LINES; j++) {
|
|
129
|
+
const next = lines[j].trim();
|
|
130
|
+
parts.push(next), depth += depthDelta(next);
|
|
131
|
+
}
|
|
132
|
+
return parts.join(" ").replace(/\(\s+/g, "(").replace(/\s+\)/g, ")").replace(/\s+/g, " ");
|
|
133
|
+
}
|
|
134
|
+
return signature?.trim() || null;
|
|
135
|
+
}
|
|
136
|
+
function matches(rule, s) {
|
|
137
|
+
if (!rule.labels.includes(s.label))
|
|
138
|
+
return !1;
|
|
139
|
+
const { decorator, declaration, file, name } = rule.when;
|
|
140
|
+
return !(decorator && !(s.decorator_names ?? []).some((d) => decorator.test(d)) || declaration && !(s.declaration && declaration.test(s.declaration)) || file && !(s.file_path && file.test(s.file_path)) || name && !name.test(s.name));
|
|
141
|
+
}
|
|
142
|
+
function evaluateTier0(symbol, rules) {
|
|
143
|
+
for (const rule of rules)
|
|
144
|
+
if (matches(rule, symbol))
|
|
145
|
+
return { ...rule.action, rule: rule.name, confidence: rule.confidence };
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
declarationOf,
|
|
150
|
+
evaluateTier0,
|
|
151
|
+
tier0RulesFrom
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=tier0-rules.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/analyzers/tier0-rules.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Tier-0 classification rules: deterministic decisions about a code symbol's DR element type,\n * made before any model is asked (docs/analyzers/CASCADE_DESIGN.md).\n *\n * Rules are data. They live in an analyzer's extraction-heuristics.json as heuristics whose\n * `parameters` carry a `tier0` block, with exactly these keys:\n *\n * labels CBM labels the rule applies to (\"Class\", \"Method\", ...)\n * when conditions, all of which must match; each is a regular expression:\n * decorator matches any of the symbol's decorator names (\"GetMapping\", \"router.get\")\n * declaration matches the symbol's declaration (see declarationOf)\n * file matches the symbol's file path\n * name matches the symbol's name\n * an empty or missing `when` matches every symbol with one of the labels\n * decide \"layer.type\": the answer, no model asked; or \"none\": the symbol is not an element\n * narrow [\"layer.type\" | \"layer.*\", ...]: the only answers the classifier may choose from\n * (besides \"none\")\n * confidence \"high\" | \"medium\" | \"low\" (default \"medium\")\n *\n * Exactly one of decide and narrow. Unknown keys are rejected, so a misspelt `when` cannot turn a\n * rule into one that matches every symbol. Rules are evaluated in file order and the first rule\n * that matches wins, so specific rules come before the fallbacks for a label.\n */\nimport type { AnalyzerHeuristic } from \"./types.js\";\n\nexport type Tier0Confidence = \"high\" | \"medium\" | \"low\";\n\n/** A layer and, for narrowing, an optional type (null = any type of that layer). */\nexport interface Tier0Target {\n layer: string;\n type: string | null;\n}\n\nexport type Tier0Action =\n | { kind: \"decide\"; layer: string; type: string }\n | { kind: \"reject\" }\n | { kind: \"narrow\"; candidates: Tier0Target[] };\n\nexport interface Tier0Rule {\n name: string;\n labels: string[];\n when: { decorator?: RegExp; declaration?: RegExp; file?: RegExp; name?: RegExp };\n action: Tier0Action;\n confidence: Tier0Confidence;\n}\n\n/** What a Tier-0 rule needs to know about a symbol. */\nexport interface Tier0Symbol {\n label: string;\n name: string;\n file_path?: string | null;\n declaration?: string | null;\n decorator_names?: string[];\n}\n\n/** The first matching rule's action, with the rule's name and confidence. */\nexport type Tier0Outcome = Tier0Action & { rule: string; confidence: Tier0Confidence };\n\nconst TIER0_KEYS = [\"labels\", \"when\", \"decide\", \"narrow\", \"confidence\"] as const;\nconst WHEN_KEYS = [\"decorator\", \"declaration\", \"file\", \"name\"] as const;\n\nfunction parseTarget(ruleName: string, s: unknown, allowWildcard: boolean): Tier0Target {\n if (typeof s !== \"string\") throw new Error(`Tier-0 rule ${ruleName}: target must be a string, got ${JSON.stringify(s)}`);\n const m = /^([a-z][a-z-]*)\\.([a-z][a-z0-9-]*|\\*)$/.exec(s);\n if (!m) throw new Error(`Tier-0 rule ${ruleName}: '${s}' is not 'layer.type'${allowWildcard ? \" or 'layer.*'\" : \"\"}`);\n if (m[2] === \"*\" && !allowWildcard) throw new Error(`Tier-0 rule ${ruleName}: decide needs a type, not '${s}'`);\n return { layer: m[1], type: m[2] === \"*\" ? null : m[2] };\n}\n\n/** Compile the Tier-0 rules out of an analyzer's heuristics, in order. Throws on a malformed rule. */\nexport function tier0RulesFrom(heuristics: AnalyzerHeuristic[]): Tier0Rule[] {\n const rules: Tier0Rule[] = [];\n for (const h of heuristics) {\n const t = (h.parameters as Record<string, unknown> | undefined)?.tier0 as Record<string, unknown> | undefined;\n if (!t) continue;\n const unknown = Object.keys(t).filter((k) => !(TIER0_KEYS as readonly string[]).includes(k));\n if (unknown.length) throw new Error(`Tier-0 rule ${h.name}: unknown key ${unknown.map((k) => `'${k}'`).join(\", \")} (known: ${TIER0_KEYS.join(\", \")})`);\n const labels = t.labels;\n if (!Array.isArray(labels) || !labels.length || !labels.every((l) => typeof l === \"string\")) {\n throw new Error(`Tier-0 rule ${h.name}: labels must be a non-empty list of CBM labels`);\n }\n const when: Tier0Rule[\"when\"] = {};\n for (const [k, v] of Object.entries((t.when ?? {}) as Record<string, unknown>)) {\n if (!(WHEN_KEYS as readonly string[]).includes(k)) throw new Error(`Tier-0 rule ${h.name}: unknown condition '${k}' (known: ${WHEN_KEYS.join(\", \")})`);\n if (typeof v !== \"string\") throw new Error(`Tier-0 rule ${h.name}: condition '${k}' must be a regular expression string`);\n when[k as (typeof WHEN_KEYS)[number]] = new RegExp(v);\n }\n if ((t.decide === undefined) === (t.narrow === undefined)) throw new Error(`Tier-0 rule ${h.name}: give exactly one of decide or narrow`);\n const confidence = (t.confidence ?? \"medium\") as Tier0Confidence;\n if (![\"high\", \"medium\", \"low\"].includes(confidence)) throw new Error(`Tier-0 rule ${h.name}: confidence must be high, medium or low`);\n let action: Tier0Action;\n if (t.decide === \"none\") action = { kind: \"reject\" };\n else if (t.decide !== undefined) {\n const target = parseTarget(h.name, t.decide, false);\n action = { kind: \"decide\", layer: target.layer, type: target.type! };\n } else {\n if (!Array.isArray(t.narrow) || !t.narrow.length) throw new Error(`Tier-0 rule ${h.name}: narrow must be a non-empty list`);\n action = { kind: \"narrow\", candidates: t.narrow.map((n) => parseTarget(h.name, n, true)) };\n }\n rules.push({ name: h.name, labels: labels as string[], when, action, confidence });\n }\n return rules;\n}\n\nconst MAX_DECLARATION_LINES = 8;\n\n/**\n * Net change in open parentheses and square brackets on a line, ignoring string literals. Braces\n * are not counted: an opening brace starts a body, not a continuation of the declaration.\n */\nfunction depthDelta(line: string): number {\n let depth = 0;\n let quote: string | null = null;\n for (let i = 0; i < line.length; i++) {\n const c = line[i];\n if (quote) {\n if (c === \"\\\\\") i++;\n else if (c === quote) quote = null;\n continue;\n }\n if (c === '\"' || c === \"'\" || c === \"`\") quote = c;\n else if (c === \"(\" || c === \"[\") depth++;\n else if (c === \")\" || c === \"]\") depth--;\n }\n return depth;\n}\n\n/** Remove leading decorators/annotations (\"@Entity\", \"@Table(name = \\\"x\\\")\") from a line; returns the rest. */\nfunction stripLeadingDecorators(line: string): { rest: string; open: number } {\n let s = line;\n for (;;) {\n const m = /^@[\\w.$]+/.exec(s);\n if (!m) return { rest: s.trim(), open: 0 };\n s = s.slice(m[0].length);\n if (s.startsWith(\"(\")) {\n // consume the argument list, which may continue on later lines\n let depth = 0;\n let i = 0;\n let quote: string | null = null;\n for (; i < s.length; i++) {\n const c = s[i];\n if (quote) {\n if (c === \"\\\\\") i++;\n else if (c === quote) quote = null;\n continue;\n }\n if (c === '\"' || c === \"'\" || c === \"`\") quote = c;\n else if (c === \"(\") depth++;\n else if (c === \")\") {\n depth--;\n if (depth === 0) break;\n }\n }\n if (depth > 0) return { rest: \"\", open: depth };\n s = s.slice(i + 1);\n }\n s = s.trimStart();\n }\n}\n\n/**\n * The declaration of a symbol: the first statement of its source that is not a blank line, a\n * comment, a docstring or a decorator/annotation, joined across lines while brackets are open\n * (\"class Item(ItemBase, table=True):\", \"public interface OwnerRepository extends\n * JpaRepository<Owner, Integer> {\"). Decorators with multi-line arguments are skipped whole, and\n * an annotation on the same line as the declaration is stripped from it. Falls back to the\n * signature.\n */\nexport function declarationOf(source: string | null | undefined, signature?: string | null): string | null {\n const lines = (source ?? \"\").split(\"\\n\");\n let i = 0;\n let decoratorDepth = 0;\n let docstring: string | null = null;\n for (; i < lines.length; i++) {\n let line = lines[i].trim();\n if (decoratorDepth > 0) {\n decoratorDepth += depthDelta(line);\n if (decoratorDepth > 0) continue;\n decoratorDepth = 0;\n continue;\n }\n if (docstring) {\n if (line.includes(docstring)) docstring = null;\n continue;\n }\n if (!line || line.startsWith(\"//\") || line.startsWith(\"#\") || line.startsWith(\"/*\") || line.startsWith(\"*\")) continue;\n const doc = /^[rRbBuU]?(\"\"\"|''')/.exec(line);\n if (doc) {\n const rest = line.slice(doc[0].length);\n if (!rest.includes(doc[1])) docstring = doc[1];\n continue;\n }\n if (line.startsWith(\"@\")) {\n const { rest, open } = stripLeadingDecorators(line);\n if (open > 0) {\n decoratorDepth = open;\n continue;\n }\n if (!rest) continue;\n line = rest;\n }\n // the declaration starts here; join continuation lines while brackets stay open\n const parts = [line];\n let depth = depthDelta(line);\n for (let j = i + 1; depth > 0 && j < lines.length && parts.length < MAX_DECLARATION_LINES; j++) {\n const next = lines[j].trim();\n parts.push(next);\n depth += depthDelta(next);\n }\n return parts.join(\" \").replace(/\\(\\s+/g, \"(\").replace(/\\s+\\)/g, \")\").replace(/\\s+/g, \" \");\n }\n return signature?.trim() || null;\n}\n\nfunction matches(rule: Tier0Rule, s: Tier0Symbol): boolean {\n if (!rule.labels.includes(s.label)) return false;\n const { decorator, declaration, file, name } = rule.when;\n if (decorator && !(s.decorator_names ?? []).some((d) => decorator.test(d))) return false;\n if (declaration && !(s.declaration && declaration.test(s.declaration))) return false;\n if (file && !(s.file_path && file.test(s.file_path))) return false;\n if (name && !name.test(s.name)) return false;\n return true;\n}\n\n/** Apply the rules to one symbol: the first matching rule's action, or null when no rule matches. */\nexport function evaluateTier0(symbol: Tier0Symbol, rules: Tier0Rule[]): Tier0Outcome | null {\n for (const rule of rules) {\n if (matches(rule, symbol)) return { ...rule.action, rule: rule.name, confidence: rule.confidence };\n }\n return null;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/analyzers/types.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Shared types for analyzer adapters\n *\n * These types define the contract between:\n * - The analyzer backend implementations\n * - The registry and commands\n * - The mapping loader\n */\n\nimport type { SourceReference } from \"../types/source-reference.js\";\n\n/**\n * Binary detection result - whether an analyzer is installed and functional\n * Discriminated union on `installed` field to make binary_path required when installed\n */\nexport type DetectionResult =\n | {\n installed: true;\n binary_path: string;\n version?: string;\n mcp_registered?: boolean;\n contract_ok?: boolean;\n }\n | {\n installed: false;\n binary_path?: never;\n version?: never;\n mcp_registered?: never;\n contract_ok?: never;\n };\n\n/**\n * Information about an available analyzer\n * Used in discovery results to report analyzer availability and installation status\n */\nexport interface AvailableAnalyzer {\n name: string;\n display_name: string;\n description: string;\n homepage: string;\n installed: boolean;\n}\n\n/**\n * Result of analyzer discovery\n * Lists all available analyzers and their installation status\n */\nexport interface DiscoveryResult {\n found: AvailableAnalyzer[];\n installed_count: number;\n selected?: string | null;\n}\n\n/**\n * Confidence level for mappings and detections\n */\nexport type ConfidenceLevel = \"high\" | \"medium\" | \"low\";\n\n/**\n * Valid HTTP methods - single source of truth for both type and runtime validation\n */\nexport const VALID_HTTP_METHODS = [\n \"GET\",\n \"POST\",\n \"PUT\",\n \"DELETE\",\n \"PATCH\",\n \"HEAD\",\n \"OPTIONS\",\n \"TRACE\",\n \"CONNECT\",\n] as const;\n\n/**\n * Heuristic parameters - shape depends on heuristic type\n * Known parameter types:\n * - min_fan_in: { threshold: number }\n * - directory_match: { patterns: string[] }\n * - naming_patterns: { service_suffixes: string[] }\n * - class_is_service: { threshold: number }\n * - service_class_naming: { service_method_prefixes: string[] }\n * - is_entry_point: { entry_point_patterns: string[] }\n * - datastore_detection: { patterns: string[], naming_indicators: string[] }\n * - handles_route: (no parameters required)\n */\nexport interface HeuristicParameters {\n threshold?: number;\n patterns?: string[];\n service_suffixes?: string[];\n service_method_prefixes?: string[];\n entry_point_patterns?: string[];\n naming_indicators?: string[];\n [key: string]: unknown;\n}\n\n// TODO: MappingConditions currently remain untyped as Record<string, unknown> because the\n// mapping schema defines conditions as a flexible object without known fields. If specific\n// condition properties are identified in future mapping iterations, define them here.\n\n/**\n * HTTP method type derived from VALID_HTTP_METHODS constant\n */\nexport type HttpMethod = (typeof VALID_HTTP_METHODS)[number];\n\n/**\n * An API endpoint candidate discovered from code analysis\n * Maps to `dr add api operation` command arguments\n *\n * These 13 fields directly support the downstream extraction workflow:\n * - Layer: api\n * - Element type: operation\n * - Name: constructed from suggested_name\n * - ID fragment: suggested_id_fragment (kebab-case for element ID)\n * - HTTP method/path: required for OpenAPI compliance\n * - Source information: for traceability and provenance\n */\nexport interface EndpointCandidate {\n /** Relative path from project root */\n source_file: string;\n /** Confidence in the detection (high, medium, low) */\n confidence: ConfidenceLevel;\n /** Suggested DR layer for this endpoint (must be \"api\") */\n suggested_layer: \"api\";\n /** Suggested element type for this endpoint (must be \"operation\") */\n suggested_element_type: \"operation\";\n /** Suggested kebab-case name for the endpoint */\n suggested_name: string;\n /** Suggested kebab-case ID fragment for constructing element ID */\n suggested_id_fragment: string;\n /** HTTP method (GET, POST, PUT, DELETE, etc.) - REQUIRED */\n http_method: HttpMethod;\n /** HTTP path or resource identifier - REQUIRED */\n http_path: string;\n /** Qualified name of the handler/implementation */\n handler_qualified_name: string;\n /** Source symbol or identifier where handler is defined */\n source_symbol: string;\n /** Starting line number of handler in source file */\n source_start_line: number;\n /** Ending line number of handler in source file */\n source_end_line: number;\n /** Source reference linking this endpoint to its implementation */\n source_reference: SourceReference;\n}\n\n/**\n * Index metadata - tracks when a project was indexed and freshness\n */\nexport interface IndexMeta {\n git_head: string;\n timestamp: string;\n node_count: number;\n edge_count: number;\n}\n\n/**\n * Result of an index operation\n * Contains counts and metadata from indexing.\n * Failures are signaled by throwing CLIError, not by success: false.\n */\nexport interface IndexResult {\n git_head: string;\n timestamp: string;\n node_count: number;\n edge_count: number;\n}\n\n/**\n * Current status of an analyzer for a project\n * Discriminated union on `indexed` field to enforce index_meta presence when indexed: true\n */\nexport type AnalyzerStatus =\n | {\n detected: DetectionResult;\n indexed: true;\n index_meta: IndexMeta;\n fresh: boolean;\n last_indexed?: string;\n }\n | {\n detected: DetectionResult;\n indexed: false;\n index_meta?: never;\n fresh: boolean;\n last_indexed?: string;\n };\n\n/**\n * Session state - tracks which analyzer is currently active\n */\nexport interface SessionState {\n active_analyzer: string;\n selected_at: string;\n}\n\n/**\n * Mapping for a single CBM node label to DR element type\n * Matches the structure in cbm.json nodes_by_label\n */\nexport interface AnalyzerNodeMapping {\n cbm_label: string;\n dr_layer: string;\n dr_element_type: string;\n confidence: ConfidenceLevel;\n description: string;\n source_reference: {\n provenance: string;\n file: {\n from: string;\n };\n symbol?: {\n from: string;\n };\n };\n dr_element_fields?: Record<\n string,\n {\n name_template?: string;\n template?: string;\n id_source?: string;\n id_transform?: string;\n }\n >;\n verify_role?: string;\n attribute_mappings?: Array<{\n from: string;\n dr_attribute: string;\n }>;\n dr_element_type_promoted?: string;\n promotion_heuristics?: string[];\n evidence_fields?: Record<string, { from: string }>;\n conditions?: Record<string, unknown>;\n}\n\n/**\n * Mapping for a single CBM edge type to DR relationship\n * Matches the structure in cbm.json edges_by_type\n */\nexport interface AnalyzerEdgeMapping {\n cbm_edge: string;\n dr_relationship: string | null;\n confidence: ConfidenceLevel;\n description: string;\n usage: string;\n dr_scope?: string;\n direction?: string;\n confidence_from_property?: string;\n dr_pattern?: string;\n implementation_note?: string;\n conditions?: Record<string, unknown>;\n}\n\n/**\n * Single heuristic rule from the mapping\n */\nexport interface AnalyzerHeuristic {\n name: string;\n description: string;\n applies_to?: string[];\n parameters?: HeuristicParameters;\n rule?: string;\n confidence_adjustment?: number;\n conditions?: Record<string, unknown>;\n}\n\n/**\n * Confidence rubric - threshold ranges for confidence levels\n */\nexport interface ConfidenceRubric {\n high: {\n min: number;\n max: number;\n description: string;\n };\n medium: {\n min: number;\n max: number;\n description: string;\n };\n low: {\n min: number;\n max: number;\n description: string;\n };\n}\n\n/**\n * Filtering rule to exclude mappings\n */\nexport interface FilteringRule {\n name: string;\n description: string;\n filter_type: string;\n threshold?: number;\n pattern?: string;\n enabled: boolean;\n}\n\n/**\n * A service candidate discovered from code analysis\n * Maps to `dr add application service` or similar command arguments\n */\nexport interface ServiceCandidate {\n /** Suggested DR layer for this service (always \"application\") */\n suggested_layer: \"application\";\n /** Suggested element type for this service */\n suggested_element_type: \"applicationservice\" | \"applicationcomponent\";\n /** Suggested kebab-case ID fragment for constructing element ID */\n suggested_id_fragment: string;\n /** Suggested name for the service */\n suggested_name: string;\n /** Relative path from project root */\n source_file: string;\n /** Source symbol or identifier where service is defined */\n source_symbol: string;\n /** Fully qualified name of the service */\n qualified_name: string;\n /** List of heuristics that qualified this as a service candidate */\n qualifying_heuristics: string[];\n /** Confidence in the detection (medium or low only) */\n confidence: \"medium\" | \"low\";\n /** Number of incoming calls/references */\n fan_in: number;\n /** Number of outgoing calls/references */\n fan_out: number;\n}\n\n/**\n * A datastore candidate inferred from code analysis\n */\nexport interface DatastoreCandidate {\n /** Suggested DR layer for this datastore (always \"data-store\") */\n suggested_layer: \"data-store\";\n /** Suggested name for the datastore */\n suggested_name: string;\n /** Array of evidence sources for this datastore inference */\n inferred_from: Array<{\n /** Relative path from project root */\n source_file: string;\n /** Pattern used to match imports (e.g., \"mongodb\", \"pg\") */\n import_pattern: string;\n /** Array of function/method call patterns that suggest datastore usage */\n function_patterns: string[];\n }>;\n /** Confidence in the detection (always \"low\") */\n confidence: \"low\";\n /** Additional notes about the inference */\n notes: string;\n}\n\n/**\n * A node in the call graph\n */\nexport interface CallGraphNode {\n /** Fully qualified name of the node */\n qualified_name: string;\n /** Relative path from project root */\n source_file: string;\n /** Source symbol or identifier */\n source_symbol: string;\n /** Depth in the call graph (0 = root node) */\n depth: number;\n /** Type of edge (CALLS, HTTP_CALLS, HANDLES) */\n edge_type: \"CALLS\" | \"HTTP_CALLS\" | \"HANDLES\";\n}\n\n/**\n * Options for the verify operation\n */\nexport interface VerifyOptions {\n /** Array of layer names to verify (empty = verify all layers) */\n layers?: string[];\n /** Whether to use changeset view if active (default: true) */\n changesetAware?: boolean;\n /** Optional path to custom ignore file */\n ignoreFilePath?: string;\n}\n\n/**\n * Entry for a matched route/element in verify report\n */\nexport interface MatchedEntry {\n /** Identifier for the matched entry */\n id: string;\n /** Type of the entry (e.g., \"operation\") */\n type: string;\n /** Source file where this entry is located */\n source_file: string;\n /** Source symbol */\n source_symbol: string;\n}\n\n/**\n * Entry for routes found in graph but not in model\n */\nexport interface GraphOnlyEntry {\n /** Identifier for the entry */\n id: string;\n /** HTTP method (GET, POST, etc.) */\n http_method?: string;\n /** HTTP path */\n http_path?: string;\n /** Source file where this route is located */\n source_file: string;\n /** Source symbol */\n source_symbol: string;\n}\n\n/**\n * Entry for elements in model but not found in graph\n */\nexport interface ModelOnlyEntry {\n /** Identifier for the model element */\n id: string;\n /** Type of the element (e.g., \"operation\") */\n type: string;\n /** Source file referenced in the model */\n source_file: string;\n /** Source symbol referenced in the model */\n source_symbol: string;\n}\n\n/**\n * Entry for matches that were ignored by rules\n */\nexport interface IgnoredEntry {\n /** Identifier for the ignored entry */\n id: string;\n /** Type of the entry (route or element) */\n entry_type: \"route\" | \"element\";\n /** Reason for ignoring */\n reason: string;\n}\n\n/**\n * Summary statistics for verify report\n */\nexport interface VerifySummary {\n /** Number of routes matched to model elements */\n matched_count: number;\n /** Number of routes found in graph only (gaps in model) */\n gap_count: number;\n /** Number of elements found in model only (drift from model) */\n drift_count: number;\n /** Number of entries ignored by rules */\n ignored_count: number;\n /** Total graph entries analyzed */\n total_graph_entries: number;\n /** Total elements in the API layer (all elements, regardless of source attribution) */\n total_model_entries: number;\n /** Elements excluded from drift detection because they have no source_reference.\n * These are documentation-only elements with no code linkage. */\n elements_without_source_ref: number;\n}\n\n/**\n * Changeset context information in verify report\n * Discriminated union to prevent impossible states like active_changeset: \"cs-1\" with verified_against: \"base_model\"\n */\nexport type VerifyChangesetContext =\n | {\n /** ID of the active changeset */\n active_changeset: string;\n /** Report was verified against changeset view */\n verified_against: \"changeset_view\";\n }\n | {\n /** No active changeset */\n active_changeset: null;\n /** Report was verified against base model */\n verified_against: \"base_model\";\n };\n\n/**\n * Buckets of categorized entries from verify operation\n */\nexport interface VerifyBuckets {\n /** Routes matched to model elements */\n matched: MatchedEntry[];\n /** Routes found in graph only */\n in_graph_only: GraphOnlyEntry[];\n /** Elements in model only */\n in_model_only: ModelOnlyEntry[];\n /** Entries ignored by rules */\n ignored: IgnoredEntry[];\n}\n\n/**\n * Detailed verification report for a project\n *\n * This is a public API contract consumed by /dr-verify Claude Code command.\n * Treat as stable from issue #654 forward.\n */\nexport interface VerifyReport {\n /** ISO 8601 timestamp when report was generated */\n generated_at: string;\n /** Absolute path to the project root */\n project_root: string;\n /** Name of the analyzer used */\n analyzer: string;\n /** ISO 8601 timestamp when analyzer was indexed */\n analyzer_indexed_at: string;\n /** Changeset context information */\n changeset_context: VerifyChangesetContext;\n /** Array of layer names that were verified */\n layers_verified: string[];\n /** Buckets of categorized entries */\n buckets: VerifyBuckets;\n /** Summary statistics */\n summary: VerifySummary;\n /** Actionable hints for resolving gaps or drift, present when issues are detected */\n hints?: string[];\n}\n"],
|
|
4
|
+
"sourcesContent": ["/**\n * Shared types for analyzer adapters\n *\n * These types define the contract between:\n * - The analyzer backend implementations\n * - The registry and commands\n * - The mapping loader\n */\n\nimport type { SourceReference } from \"../types/source-reference.js\";\n\n/**\n * Binary detection result - whether an analyzer is installed and functional\n * Discriminated union on `installed` field to make binary_path required when installed\n */\nexport type DetectionResult =\n | {\n installed: true;\n binary_path: string;\n version?: string;\n mcp_registered?: boolean;\n contract_ok?: boolean;\n }\n | {\n installed: false;\n binary_path?: never;\n version?: never;\n mcp_registered?: never;\n contract_ok?: never;\n };\n\n/**\n * Information about an available analyzer\n * Used in discovery results to report analyzer availability and installation status\n */\nexport interface AvailableAnalyzer {\n name: string;\n display_name: string;\n description: string;\n homepage: string;\n installed: boolean;\n}\n\n/**\n * Result of analyzer discovery\n * Lists all available analyzers and their installation status\n */\nexport interface DiscoveryResult {\n found: AvailableAnalyzer[];\n installed_count: number;\n selected?: string | null;\n}\n\n/**\n * Confidence level for mappings and detections\n */\nexport type ConfidenceLevel = \"high\" | \"medium\" | \"low\";\n\n/**\n * Valid HTTP methods - single source of truth for both type and runtime validation\n */\nexport const VALID_HTTP_METHODS = [\n \"GET\",\n \"POST\",\n \"PUT\",\n \"DELETE\",\n \"PATCH\",\n \"HEAD\",\n \"OPTIONS\",\n \"TRACE\",\n \"CONNECT\",\n] as const;\n\n/**\n * Heuristic parameters - shape depends on heuristic type\n * Known parameter types:\n * - min_fan_in: { threshold: number }\n * - directory_match: { patterns: string[] }\n * - naming_patterns: { service_suffixes: string[] }\n * - class_is_service: { threshold: number }\n * - service_class_naming: { service_method_prefixes: string[] }\n * - is_entry_point: { entry_point_patterns: string[] }\n * - datastore_detection: { patterns: string[], naming_indicators: string[] }\n * - handles_route: (no parameters required)\n */\nexport interface HeuristicParameters {\n threshold?: number;\n patterns?: string[];\n service_suffixes?: string[];\n service_method_prefixes?: string[];\n entry_point_patterns?: string[];\n naming_indicators?: string[];\n [key: string]: unknown;\n}\n\n// TODO: MappingConditions currently remain untyped as Record<string, unknown> because the\n// mapping schema defines conditions as a flexible object without known fields. If specific\n// condition properties are identified in future mapping iterations, define them here.\n\n/**\n * HTTP method type derived from VALID_HTTP_METHODS constant\n */\nexport type HttpMethod = (typeof VALID_HTTP_METHODS)[number];\n\n/**\n * An API endpoint candidate discovered from code analysis\n * Maps to `dr add api operation` command arguments\n *\n * These 13 fields directly support the downstream extraction workflow:\n * - Layer: api\n * - Element type: operation\n * - Name: constructed from suggested_name\n * - ID fragment: suggested_id_fragment (kebab-case for element ID)\n * - HTTP method/path: required for OpenAPI compliance\n * - Source information: for traceability and provenance\n */\nexport interface EndpointCandidate {\n /** Relative path from project root */\n source_file: string;\n /** Confidence in the detection (high, medium, low) */\n confidence: ConfidenceLevel;\n /** Suggested DR layer for this endpoint (must be \"api\") */\n suggested_layer: \"api\";\n /** Suggested element type for this endpoint (must be \"operation\") */\n suggested_element_type: \"operation\";\n /** Suggested kebab-case name for the endpoint */\n suggested_name: string;\n /** Suggested kebab-case ID fragment for constructing element ID */\n suggested_id_fragment: string;\n /** HTTP method (GET, POST, PUT, DELETE, etc.) - REQUIRED */\n http_method: HttpMethod;\n /** HTTP path or resource identifier - REQUIRED */\n http_path: string;\n /** Qualified name of the handler/implementation */\n handler_qualified_name: string;\n /** Source symbol or identifier where handler is defined */\n source_symbol: string;\n /** Starting line number of handler in source file */\n source_start_line: number;\n /** Ending line number of handler in source file */\n source_end_line: number;\n /** Source reference linking this endpoint to its implementation */\n source_reference: SourceReference;\n}\n\n/**\n * Index metadata - tracks when a project was indexed and freshness\n */\nexport interface IndexMeta {\n git_head: string;\n timestamp: string;\n node_count: number;\n edge_count: number;\n}\n\n/**\n * Result of an index operation\n * Contains counts and metadata from indexing.\n * Failures are signaled by throwing CLIError, not by success: false.\n */\nexport interface IndexResult {\n git_head: string;\n timestamp: string;\n node_count: number;\n edge_count: number;\n}\n\n/**\n * Current status of an analyzer for a project\n * Discriminated union on `indexed` field to enforce index_meta presence when indexed: true\n */\nexport type AnalyzerStatus =\n | {\n detected: DetectionResult;\n indexed: true;\n index_meta: IndexMeta;\n fresh: boolean;\n last_indexed?: string;\n }\n | {\n detected: DetectionResult;\n indexed: false;\n index_meta?: never;\n fresh: boolean;\n last_indexed?: string;\n };\n\n/**\n * Session state - tracks which analyzer is currently active\n */\nexport interface SessionState {\n active_analyzer: string;\n selected_at: string;\n}\n\n/**\n * Mapping for a single CBM node label to DR element type\n * Matches the structure in cbm.json nodes_by_label\n */\nexport interface AnalyzerNodeMapping {\n cbm_label: string;\n dr_layer: string;\n dr_element_type: string;\n confidence: ConfidenceLevel;\n description: string;\n source_reference: {\n provenance: string;\n file: {\n from: string;\n };\n symbol?: {\n from: string;\n };\n };\n dr_element_fields?: Record<\n string,\n {\n name_template?: string;\n template?: string;\n id_source?: string;\n id_transform?: string;\n }\n >;\n verify_role?: string;\n attribute_mappings?: Array<{\n from: string;\n dr_attribute: string;\n }>;\n dr_element_type_promoted?: string;\n promotion_heuristics?: string[];\n evidence_fields?: Record<string, { from: string }>;\n conditions?: Record<string, unknown>;\n}\n\n/**\n * Mapping for a single CBM edge type to DR relationship\n * Matches the structure in cbm.json edges_by_type\n */\nexport interface AnalyzerEdgeMapping {\n cbm_edge: string;\n dr_relationship: string | null;\n confidence: ConfidenceLevel;\n description: string;\n usage: string;\n dr_scope?: string;\n direction?: string;\n /** \"invert\" when the DR relationship runs opposite to the CBM edge (HANDLES) */\n directionality_transform?: \"invert\" | \"bidirectional\" | \"symmetric\" | null;\n confidence_from_property?: string;\n dr_pattern?: string;\n implementation_note?: string;\n conditions?: Record<string, unknown>;\n}\n\n/**\n * Single heuristic rule from the mapping\n */\nexport interface AnalyzerHeuristic {\n name: string;\n description: string;\n applies_to?: string[];\n parameters?: HeuristicParameters;\n rule?: string;\n confidence_adjustment?: number;\n conditions?: Record<string, unknown>;\n}\n\n/**\n * Confidence rubric - threshold ranges for confidence levels\n */\nexport interface ConfidenceRubric {\n high: {\n min: number;\n max: number;\n description: string;\n };\n medium: {\n min: number;\n max: number;\n description: string;\n };\n low: {\n min: number;\n max: number;\n description: string;\n };\n}\n\n/**\n * Filtering rule to exclude mappings\n */\nexport interface FilteringRule {\n name: string;\n description: string;\n filter_type: string;\n threshold?: number;\n pattern?: string;\n enabled: boolean;\n}\n\n/**\n * A service candidate discovered from code analysis\n * Maps to `dr add application service` or similar command arguments\n */\nexport interface ServiceCandidate {\n /** Suggested DR layer for this service (always \"application\") */\n suggested_layer: \"application\";\n /** Suggested element type for this service */\n suggested_element_type: \"applicationservice\" | \"applicationcomponent\";\n /** Suggested kebab-case ID fragment for constructing element ID */\n suggested_id_fragment: string;\n /** Suggested name for the service */\n suggested_name: string;\n /** Relative path from project root */\n source_file: string;\n /** Source symbol or identifier where service is defined */\n source_symbol: string;\n /** Fully qualified name of the service */\n qualified_name: string;\n /** List of heuristics that qualified this as a service candidate */\n qualifying_heuristics: string[];\n /** Confidence in the detection (medium or low only) */\n confidence: \"medium\" | \"low\";\n /** Number of incoming calls/references */\n fan_in: number;\n /** Number of outgoing calls/references */\n fan_out: number;\n}\n\n/**\n * A datastore candidate inferred from code analysis\n */\nexport interface DatastoreCandidate {\n /** Suggested DR layer for this datastore (always \"data-store\") */\n suggested_layer: \"data-store\";\n /** Suggested name for the datastore */\n suggested_name: string;\n /** Array of evidence sources for this datastore inference */\n inferred_from: Array<{\n /** Relative path from project root */\n source_file: string;\n /** Pattern used to match imports (e.g., \"mongodb\", \"pg\") */\n import_pattern: string;\n /** Array of function/method call patterns that suggest datastore usage */\n function_patterns: string[];\n }>;\n /** Confidence in the detection (always \"low\") */\n confidence: \"low\";\n /** Additional notes about the inference */\n notes: string;\n}\n\n/**\n * A node in the call graph\n */\nexport interface CallGraphNode {\n /** Fully qualified name of the node */\n qualified_name: string;\n /** Relative path from project root */\n source_file: string;\n /** Source symbol or identifier */\n source_symbol: string;\n /** Depth in the call graph (0 = root node) */\n depth: number;\n /** Type of edge (CALLS, HTTP_CALLS, HANDLES) */\n edge_type: \"CALLS\" | \"HTTP_CALLS\" | \"HANDLES\";\n}\n\n/**\n * Options for the verify operation\n */\nexport interface VerifyOptions {\n /** Array of layer names to verify (empty = verify all layers) */\n layers?: string[];\n /** Whether to use changeset view if active (default: true) */\n changesetAware?: boolean;\n /** Optional path to custom ignore file */\n ignoreFilePath?: string;\n}\n\n/**\n * Entry for a matched route/element in verify report\n */\nexport interface MatchedEntry {\n /** Identifier for the matched entry */\n id: string;\n /** Type of the entry (e.g., \"operation\") */\n type: string;\n /** Source file where this entry is located */\n source_file: string;\n /** Source symbol */\n source_symbol: string;\n}\n\n/**\n * Entry for routes found in graph but not in model\n */\nexport interface GraphOnlyEntry {\n /** Identifier for the entry */\n id: string;\n /** HTTP method (GET, POST, etc.) */\n http_method?: string;\n /** HTTP path */\n http_path?: string;\n /** Source file where this route is located */\n source_file: string;\n /** Source symbol */\n source_symbol: string;\n}\n\n/**\n * Entry for elements in model but not found in graph\n */\nexport interface ModelOnlyEntry {\n /** Identifier for the model element */\n id: string;\n /** Type of the element (e.g., \"operation\") */\n type: string;\n /** Source file referenced in the model */\n source_file: string;\n /** Source symbol referenced in the model */\n source_symbol: string;\n}\n\n/**\n * Entry for matches that were ignored by rules\n */\nexport interface IgnoredEntry {\n /** Identifier for the ignored entry */\n id: string;\n /** Type of the entry (route or element) */\n entry_type: \"route\" | \"element\";\n /** Reason for ignoring */\n reason: string;\n}\n\n/**\n * Summary statistics for verify report\n */\nexport interface VerifySummary {\n /** Number of routes matched to model elements */\n matched_count: number;\n /** Number of routes found in graph only (gaps in model) */\n gap_count: number;\n /** Number of elements found in model only (drift from model) */\n drift_count: number;\n /** Number of entries ignored by rules */\n ignored_count: number;\n /** Total graph entries analyzed */\n total_graph_entries: number;\n /** Total elements in the API layer (all elements, regardless of source attribution) */\n total_model_entries: number;\n /** Elements excluded from drift detection because they have no source_reference.\n * These are documentation-only elements with no code linkage. */\n elements_without_source_ref: number;\n}\n\n/**\n * Changeset context information in verify report\n * Discriminated union to prevent impossible states like active_changeset: \"cs-1\" with verified_against: \"base_model\"\n */\nexport type VerifyChangesetContext =\n | {\n /** ID of the active changeset */\n active_changeset: string;\n /** Report was verified against changeset view */\n verified_against: \"changeset_view\";\n }\n | {\n /** No active changeset */\n active_changeset: null;\n /** Report was verified against base model */\n verified_against: \"base_model\";\n };\n\n/**\n * Buckets of categorized entries from verify operation\n */\nexport interface VerifyBuckets {\n /** Routes matched to model elements */\n matched: MatchedEntry[];\n /** Routes found in graph only */\n in_graph_only: GraphOnlyEntry[];\n /** Elements in model only */\n in_model_only: ModelOnlyEntry[];\n /** Entries ignored by rules */\n ignored: IgnoredEntry[];\n}\n\n/**\n * Detailed verification report for a project\n *\n * This is a public API contract consumed by /dr-verify Claude Code command.\n * Treat as stable from issue #654 forward.\n */\nexport interface VerifyReport {\n /** ISO 8601 timestamp when report was generated */\n generated_at: string;\n /** Absolute path to the project root */\n project_root: string;\n /** Name of the analyzer used */\n analyzer: string;\n /** ISO 8601 timestamp when analyzer was indexed */\n analyzer_indexed_at: string;\n /** Changeset context information */\n changeset_context: VerifyChangesetContext;\n /** Array of layer names that were verified */\n layers_verified: string[];\n /** Buckets of categorized entries */\n buckets: VerifyBuckets;\n /** Summary statistics */\n summary: VerifySummary;\n /** Actionable hints for resolving gaps or drift, present when issues are detected */\n hints?: string[];\n}\n"],
|
|
5
5
|
"mappings": "AA6DO,MAAM,qBAAqB;EAChC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import { access } from "fs/promises";
|
|
3
3
|
import { Model } from "../core/model.js";
|
|
4
|
+
import { getErrorMessage } from "../utils/errors.js";
|
|
4
5
|
import { IgnoreFileLoader } from "./verify-ignore.js";
|
|
5
6
|
import { SOURCE_CODE_EXT_RE } from "./cbm-analyzer.js";
|
|
6
7
|
class VerifyEngine {
|
|
@@ -25,8 +26,8 @@ class VerifyEngine {
|
|
|
25
26
|
try {
|
|
26
27
|
model = await Model.load(projectRoot);
|
|
27
28
|
} catch (error) {
|
|
28
|
-
const
|
|
29
|
-
throw
|
|
29
|
+
const message = getErrorMessage(error);
|
|
30
|
+
throw message.includes("No DR project") || message.includes("Model not found") || message.includes("no model found") || message.includes("Could not find documentation_robotics") ? new Error(`Failed to load DR model at ${projectRoot}: Model not found. Please run 'dr init' to initialize the documentation robotics model.`) : error;
|
|
30
31
|
}
|
|
31
32
|
const activeChangesetId = model.getActiveChangesetId(), apiLayer = options.changesetAware === !1 ? await model.getBaseLayer("api") : await model.getLayer("api");
|
|
32
33
|
if (!apiLayer)
|
|
@@ -129,7 +130,7 @@ Warning: ${emptySymbolCount}/${routes.length} graph route(s) have empty source_s
|
|
|
129
130
|
file: firstLocation.file,
|
|
130
131
|
symbol: firstLocation.symbol || ""
|
|
131
132
|
});
|
|
132
|
-
const filePath = path.join(
|
|
133
|
+
const filePath = path.join(model.codebaseRoot, firstLocation.file);
|
|
133
134
|
fileChecks.push(access(filePath).then(() => !0).catch((err) => err.code !== "ENOENT"));
|
|
134
135
|
}
|
|
135
136
|
elementsWithoutSourceRef > 0 && console.warn(`
|
|
@@ -143,22 +144,24 @@ Warning: ${elementsWithNonIndexableSource} API model element(s) excluded from dr
|
|
|
143
144
|
or use dr analyzer endpoints to discover routes automatically.
|
|
144
145
|
`);
|
|
145
146
|
const fileExistsResults = await Promise.all(fileChecks);
|
|
146
|
-
for (let i = 0; i < elementsToCheck.length; i++)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
147
|
+
for (let i = 0; i < elementsToCheck.length; i++) {
|
|
148
|
+
const fileExists = fileExistsResults[i], elem = elementsToCheck[i];
|
|
149
|
+
if (fileExists === !1)
|
|
150
|
+
continue;
|
|
151
|
+
const ignoreReason = IgnoreFileLoader.matches({
|
|
152
|
+
element_id: elem.path
|
|
153
|
+
}, "element", ignoreRules);
|
|
154
|
+
ignoreReason ? ignored.push({
|
|
155
|
+
id: elem.id,
|
|
156
|
+
entry_type: "element",
|
|
157
|
+
reason: ignoreReason
|
|
158
|
+
}) : inModelOnly.push({
|
|
159
|
+
id: elem.id,
|
|
160
|
+
type: "operation",
|
|
161
|
+
source_file: elem.file,
|
|
162
|
+
source_symbol: elem.symbol
|
|
163
|
+
});
|
|
164
|
+
}
|
|
162
165
|
const buckets = {
|
|
163
166
|
matched,
|
|
164
167
|
in_graph_only: inGraphOnly,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/analyzers/verify-engine.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Verify Engine - computes verification reports for API endpoints\n *\n * Implements the core algorithm for comparing graph-discovered routes against\n * model-defined operations, supporting dual-index matching and changeset awareness.\n */\n\nimport * as path from \"path\";\nimport { access } from \"fs/promises\";\nimport { Model } from \"../core/model.js\";\nimport type {\n VerifyReport,\n VerifyOptions,\n MatchedEntry,\n GraphOnlyEntry,\n ModelOnlyEntry,\n IgnoredEntry,\n VerifyBuckets,\n VerifySummary,\n IndexMeta,\n VerifyChangesetContext,\n} from \"./types.js\";\nimport { IgnoreFileLoader } from \"./verify-ignore.js\";\nimport { SOURCE_CODE_EXT_RE } from \"./cbm-analyzer.js\";\n\n/**\n * A discovered route from the graph\n */\nexport interface DiscoveredRoute {\n id: string;\n http_method?: string;\n http_path?: string;\n handler?: string;\n source_file?: string;\n source_symbol?: string;\n}\n\n/**\n * Model element reference extracted from the API layer\n * Contains the minimal set of fields needed for dual-index matching\n */\ninterface ModelElementRef {\n /** Element ID from the model */\n id: string;\n /** Source file path for primary index matching */\n source_file?: string;\n /** Source symbol for primary index matching */\n source_symbol?: string;\n /** HTTP method for secondary index matching */\n http_method?: string;\n /** HTTP path for secondary index matching */\n http_path?: string;\n}\n\n/**\n * Primary or secondary index entry (holds model element data, not discovered route data)\n */\ninterface IndexEntry {\n modelEntry: ModelElementRef;\n key: string;\n}\n\n/**\n * VerifyEngine - computes verification reports for API endpoints\n */\nexport class VerifyEngine {\n /**\n * Compute a comprehensive verification report\n *\n * Implements the five-step algorithm:\n * 1. Resolve model view via Model.load() + getActiveChangesetId() + getLayer(\"api\")\n * 2. Build primary index on source_reference.locations[0].file + \":\" + symbol\n * and optional secondary index on http_method + \":\" + http_path\n * 3. Load ignore rules\n * 4. Compute four buckets (matched, in_graph_only, in_model_only, ignored)\n * 5. Assemble VerifyReport\n *\n * @param projectRoot Absolute path to the project root\n * @param routes Array of discovered routes from the graph\n * @param options Verification options\n * @returns Comprehensive verification report\n */\n async computeReport(\n projectRoot: string,\n routes: DiscoveredRoute[],\n options: VerifyOptions,\n analyzerName: string = \"codebase-memory-mcp\",\n indexMeta?: IndexMeta\n ): Promise<VerifyReport> {\n // Step 1: Resolve model view\n let model: Model;\n try {\n model = await Model.load(projectRoot);\n } catch (error) {\n const err = error as NodeJS.ErrnoException;\n // Provide helpful error message if model directory is missing or corrupted\n if (err.code === \"ENOENT\" || err.code === \"ENOTDIR\") {\n throw new Error(\n `Failed to load DR model at ${projectRoot}: Model directory not found or inaccessible. ` +\n `Please run 'dr init' to initialize the documentation robotics model.`\n );\n }\n throw new Error(\n `Failed to load DR model at ${projectRoot}: ${err.message}. ` +\n `Please check that the model directory exists and is accessible.`\n );\n }\n\n const activeChangesetId = model.getActiveChangesetId();\n\n // Use base layer if changesetAware is explicitly false\n const apiLayer = options.changesetAware === false\n ? await model.getBaseLayer(\"api\")\n : await model.getLayer(\"api\");\n\n if (!apiLayer) {\n // No API layer found - all routes are in_graph_only\n return this.assembleReport(\n projectRoot,\n model,\n {\n matched: [],\n in_graph_only: routes.map((r) => ({\n id: r.id,\n http_method: r.http_method,\n http_path: r.http_path,\n source_file: r.source_file || \"\",\n source_symbol: r.source_symbol || \"\",\n })),\n in_model_only: [],\n ignored: [],\n },\n activeChangesetId,\n options,\n analyzerName,\n indexMeta\n );\n }\n\n // Step 2: Build dual-index\n const primaryIndex = new Map<string, IndexEntry>();\n const secondaryIndex = new Map<string, IndexEntry>();\n\n for (const element of apiLayer.elements.values()) {\n const firstLocation = element.source_reference?.locations?.[0];\n\n // Primary index: file:symbol\n if (firstLocation && firstLocation.file) {\n const symbol = firstLocation.symbol || \"\";\n const key = `${firstLocation.file}:${symbol}`;\n primaryIndex.set(key, {\n modelEntry: {\n id: element.id,\n source_file: firstLocation.file,\n source_symbol: symbol,\n },\n key,\n });\n }\n\n // Secondary index: http_method:http_path\n const attrs = element.attributes as any;\n if (attrs?.http_method && attrs?.http_path) {\n const key = `${attrs.http_method}:${attrs.http_path}`;\n secondaryIndex.set(key, {\n modelEntry: {\n id: element.id,\n http_method: attrs.http_method,\n http_path: attrs.http_path,\n },\n key,\n });\n }\n }\n\n // Warn if any graph routes have no source_symbol \u2014 they will be skipped by primary-index matching\n const emptySymbolCount = routes.filter((r) => !r.source_symbol).length;\n if (emptySymbolCount > 0) {\n console.warn(\n `\\nWarning: ${emptySymbolCount}/${routes.length} graph route(s) have empty source_symbol.` +\n ` Primary-index matching by (source_file, source_symbol) will skip them.\\n` +\n ` Likely cause: the code graph analyzer did not extract handler function names for this language.\\n` +\n ` Secondary-index matching (http_method + http_path) requires those attributes on model operations.\\n` +\n ` To fix: re-index after the analyzer is updated, or manually set http_method/http_path on operations.\\n`\n );\n }\n\n // Step 3: Load ignore rules\n const ignoreFilePath =\n options.ignoreFilePath || path.join(projectRoot, \".dr-verify-ignore.yaml\");\n const ignoreRules = await IgnoreFileLoader.load(ignoreFilePath);\n\n // Step 4: Compute buckets\n const matched: MatchedEntry[] = [];\n const inGraphOnly: GraphOnlyEntry[] = [];\n const ignored: IgnoredEntry[] = [];\n const matchedRouteIds = new Set<string>();\n\n for (const route of routes) {\n let matchedElement: { id: string } | undefined;\n\n // Try primary index match\n if (route.source_file && route.source_symbol) {\n const key = `${route.source_file}:${route.source_symbol}`;\n const primaryMatch = primaryIndex.get(key);\n if (primaryMatch) {\n matchedElement = primaryMatch.modelEntry;\n }\n }\n\n // Fallback to secondary index match\n if (!matchedElement && route.http_method && route.http_path) {\n const key = `${route.http_method}:${route.http_path}`;\n const secondaryMatch = secondaryIndex.get(key);\n if (secondaryMatch) {\n matchedElement = secondaryMatch.modelEntry;\n }\n }\n\n // Check if ignored\n const ignoreReason = IgnoreFileLoader.matches(\n {\n handler: route.handler,\n path: route.http_path,\n element_id: route.id,\n },\n \"route\",\n ignoreRules\n );\n\n if (ignoreReason) {\n ignored.push({\n id: route.id,\n entry_type: \"route\",\n reason: ignoreReason,\n });\n // Update matchedRouteIds even when ignored to prevent false drift\n if (matchedElement) {\n matchedRouteIds.add(matchedElement.id);\n }\n } else if (matchedElement) {\n matched.push({\n id: matchedElement.id,\n type: \"operation\",\n source_file: route.source_file || \"\",\n source_symbol: route.source_symbol || \"\",\n });\n matchedRouteIds.add(matchedElement.id);\n } else {\n inGraphOnly.push({\n id: route.id,\n http_method: route.http_method,\n http_path: route.http_path,\n source_file: route.source_file || \"\",\n source_symbol: route.source_symbol || \"\",\n });\n }\n }\n\n // Compute in_model_only: model elements not matched in graph\n const inModelOnly: ModelOnlyEntry[] = [];\n\n // Batch file-existence checks\n const fileChecks: Promise<boolean>[] = [];\n const elementsToCheck: Array<{ id: string; path: string; file: string; symbol: string }> = [];\n let elementsWithoutSourceRef = 0;\n let elementsWithNonIndexableSource = 0;\n\n for (const element of apiLayer.elements.values()) {\n if (matchedRouteIds.has(element.id)) {\n continue; // Already matched\n }\n\n const firstLocation = element.source_reference?.locations?.[0];\n\n if (!firstLocation || !firstLocation.file) {\n // Skip elements with no source_reference file - they cannot drift since they have no code linkage.\n // This includes manually-created elements that are documentation-only.\n elementsWithoutSourceRef++;\n continue;\n }\n\n // CBM only indexes source-code files \u2014 non-code source files (e.g. openapi.yaml)\n // will never appear in the graph, so including them in drift detection produces false positives.\n if (!SOURCE_CODE_EXT_RE.test(firstLocation.file)) {\n elementsWithNonIndexableSource++;\n continue;\n }\n\n elementsToCheck.push({\n id: element.id,\n path: element.path,\n file: firstLocation.file,\n symbol: firstLocation.symbol || \"\",\n });\n\n const filePath = path.join(projectRoot, firstLocation.file);\n fileChecks.push(\n access(filePath)\n .then(() => true)\n .catch((err: NodeJS.ErrnoException) => {\n // Only treat ENOENT (file not found) as \"file doesn't exist\"\n // Other errors (EACCES, EMFILE, etc.) are treated as \"file exists\" to avoid false negatives\n if (err.code === \"ENOENT\") {\n return false;\n }\n // For permission errors and other issues, assume file exists to be conservative\n return true;\n })\n );\n }\n\n if (elementsWithoutSourceRef > 0) {\n console.warn(\n `\\nWarning: ${elementsWithoutSourceRef} API model element(s) excluded from drift detection` +\n ` \u2014 they have no source_reference with a file location.\\n` +\n ` Drift detection requires a linked source file to compare against the code graph.\\n` +\n ` Fix: dr update <element-id> --source-file <relative-path> --source-symbol <symbol>\\n` +\n ` Elements ingested via /dr-map or dr analyzer endpoints are automatically attributed.\\n`\n );\n }\n\n if (elementsWithNonIndexableSource > 0) {\n console.warn(\n `\\nWarning: ${elementsWithNonIndexableSource} API model element(s) excluded from drift detection` +\n ` \u2014 their source files are not indexable source-code files.\\n` +\n ` Point source_reference to a source-code file (.ts, .js, .py, etc.) for drift detection,\\n` +\n ` or use dr analyzer endpoints to discover routes automatically.\\n`\n );\n }\n\n // Wait for all file checks. All promises have .catch() handlers that return booleans,\n // so they will never reject - Promise.all() is safe and simpler than allSettled.\n const fileExistsResults = await Promise.all(fileChecks);\n\n // Add to in_model_only only if file exists, then check against ignore rules\n for (let i = 0; i < elementsToCheck.length; i++) {\n const fileExists = fileExistsResults[i];\n\n if (fileExists) {\n const elem = elementsToCheck[i];\n\n // Check if element matches any ignore rule\n // Use element.path (semantic path) instead of elem.id (UUID)\n const ignoreReason = IgnoreFileLoader.matches(\n {\n element_id: elem.path,\n },\n \"element\",\n ignoreRules\n );\n\n if (ignoreReason) {\n ignored.push({\n id: elem.id,\n entry_type: \"element\",\n reason: ignoreReason,\n });\n } else {\n inModelOnly.push({\n id: elem.id,\n type: \"operation\",\n source_file: elem.file,\n source_symbol: elem.symbol,\n });\n }\n }\n }\n\n // Step 5: Assemble report\n const buckets: VerifyBuckets = {\n matched,\n in_graph_only: inGraphOnly,\n in_model_only: inModelOnly,\n ignored,\n };\n\n return this.assembleReport(\n projectRoot,\n model,\n buckets,\n activeChangesetId,\n options,\n analyzerName,\n indexMeta,\n elementsWithoutSourceRef + elementsWithNonIndexableSource\n );\n }\n\n /**\n * Assemble the final verification report\n *\n * @private\n */\n private assembleReport(\n projectRoot: string,\n model: Model,\n buckets: VerifyBuckets,\n activeChangesetId: string | null,\n options: VerifyOptions,\n analyzerName: string = \"codebase-memory-mcp\",\n indexMeta?: IndexMeta,\n elementsWithoutSourceRef: number = 0\n ): VerifyReport {\n const matchedElements = buckets.matched.length;\n const inModelOnly = buckets.in_model_only.length;\n const ignoredElements = buckets.ignored.filter((e) => e.entry_type === \"element\").length;\n const summary: VerifySummary = {\n matched_count: matchedElements,\n gap_count: buckets.in_graph_only.length,\n drift_count: inModelOnly,\n ignored_count: buckets.ignored.length,\n total_graph_entries: matchedElements + buckets.in_graph_only.length + buckets.ignored.filter((e) => e.entry_type === \"route\").length,\n // Count all API layer elements: those that participated in matching plus those\n // excluded because they lack source_reference (documentation-only elements).\n total_model_entries: matchedElements + inModelOnly + ignoredElements + elementsWithoutSourceRef,\n elements_without_source_ref: elementsWithoutSourceRef,\n };\n\n // Determine changeset context\n const changesetAware = options.changesetAware ?? true;\n const hasActiveChangeset = changesetAware && activeChangesetId !== null;\n\n // Use analyzer index timestamp if provided, otherwise fall back to model manifest modified time\n const analyzerIndexedAt = indexMeta?.timestamp || model.manifest.modified;\n\n // Construct changeset context as discriminated union\n const changeset_context: VerifyChangesetContext = hasActiveChangeset\n ? {\n active_changeset: activeChangesetId,\n verified_against: \"changeset_view\",\n }\n : {\n active_changeset: null,\n verified_against: \"base_model\",\n };\n\n const hints: string[] = [];\n if (buckets.in_graph_only.length > 0) {\n hints.push(\n \"To close gaps with source attribution, use /dr-map or `dr analyzer endpoints` ingestion \u2014 \"\n + \"manual `dr add` operations cannot be matched by the verifier.\"\n );\n }\n\n return {\n generated_at: new Date().toISOString(),\n project_root: projectRoot,\n analyzer: analyzerName,\n analyzer_indexed_at: analyzerIndexedAt,\n changeset_context,\n layers_verified: [\"api\"],\n buckets,\n summary,\n ...(hints.length > 0 ? { hints } : {}),\n };\n }\n}\n"],
|
|
5
|
-
"mappings": "AAOA,YAAY,UAAU;AACtB,SAAS,cAAc;AACvB,SAAS,aAAa;
|
|
4
|
+
"sourcesContent": ["/**\n * Verify Engine - computes verification reports for API endpoints\n *\n * Implements the core algorithm for comparing graph-discovered routes against\n * model-defined operations, supporting dual-index matching and changeset awareness.\n */\n\nimport * as path from \"path\";\nimport { access } from \"fs/promises\";\nimport { Model } from \"../core/model.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport type {\n VerifyReport,\n VerifyOptions,\n MatchedEntry,\n GraphOnlyEntry,\n ModelOnlyEntry,\n IgnoredEntry,\n VerifyBuckets,\n VerifySummary,\n IndexMeta,\n VerifyChangesetContext,\n} from \"./types.js\";\nimport { IgnoreFileLoader } from \"./verify-ignore.js\";\nimport { SOURCE_CODE_EXT_RE } from \"./cbm-analyzer.js\";\n\n/**\n * A discovered route from the graph\n */\nexport interface DiscoveredRoute {\n id: string;\n http_method?: string;\n http_path?: string;\n handler?: string;\n source_file?: string;\n source_symbol?: string;\n}\n\n/**\n * Model element reference extracted from the API layer\n * Contains the minimal set of fields needed for dual-index matching\n */\ninterface ModelElementRef {\n /** Element ID from the model */\n id: string;\n /** Source file path for primary index matching */\n source_file?: string;\n /** Source symbol for primary index matching */\n source_symbol?: string;\n /** HTTP method for secondary index matching */\n http_method?: string;\n /** HTTP path for secondary index matching */\n http_path?: string;\n}\n\n/**\n * Primary or secondary index entry (holds model element data, not discovered route data)\n */\ninterface IndexEntry {\n modelEntry: ModelElementRef;\n key: string;\n}\n\n/**\n * VerifyEngine - computes verification reports for API endpoints\n */\nexport class VerifyEngine {\n /**\n * Compute a comprehensive verification report\n *\n * Implements the five-step algorithm:\n * 1. Resolve model view via Model.load() + getActiveChangesetId() + getLayer(\"api\")\n * 2. Build primary index on source_reference.locations[0].file + \":\" + symbol\n * and optional secondary index on http_method + \":\" + http_path\n * 3. Load ignore rules\n * 4. Compute four buckets (matched, in_graph_only, in_model_only, ignored)\n * 5. Assemble VerifyReport\n *\n * @param projectRoot Absolute path to the project root\n * @param routes Array of discovered routes from the graph\n * @param options Verification options\n * @returns Comprehensive verification report\n */\n async computeReport(\n projectRoot: string,\n routes: DiscoveredRoute[],\n options: VerifyOptions,\n analyzerName: string = \"codebase-memory-mcp\",\n indexMeta?: IndexMeta\n ): Promise<VerifyReport> {\n // Step 1: Resolve model view\n let model: Model;\n try {\n model = await Model.load(projectRoot);\n } catch (error) {\n const message = getErrorMessage(error);\n // Check for model-not-found errors (matches all 4 error messages from Model.resolveModelPaths)\n if (\n message.includes(\"No DR project\") ||\n message.includes(\"Model not found\") ||\n message.includes(\"no model found\") ||\n message.includes(\"Could not find documentation_robotics\")\n ) {\n throw new Error(\n `Failed to load DR model at ${projectRoot}: Model not found. Please run 'dr init' to initialize the documentation robotics model.`\n );\n }\n // Re-throw original error unchanged to preserve stack trace and error type (e.g., YAML parse errors)\n throw error;\n }\n\n const activeChangesetId = model.getActiveChangesetId();\n\n // Use base layer if changesetAware is explicitly false\n const apiLayer = options.changesetAware === false\n ? await model.getBaseLayer(\"api\")\n : await model.getLayer(\"api\");\n\n if (!apiLayer) {\n // No API layer found - all routes are in_graph_only\n return this.assembleReport(\n projectRoot,\n model,\n {\n matched: [],\n in_graph_only: routes.map((r) => ({\n id: r.id,\n http_method: r.http_method,\n http_path: r.http_path,\n source_file: r.source_file || \"\",\n source_symbol: r.source_symbol || \"\",\n })),\n in_model_only: [],\n ignored: [],\n },\n activeChangesetId,\n options,\n analyzerName,\n indexMeta\n );\n }\n\n // Step 2: Build dual-index\n const primaryIndex = new Map<string, IndexEntry>();\n const secondaryIndex = new Map<string, IndexEntry>();\n\n for (const element of apiLayer.elements.values()) {\n const firstLocation = element.source_reference?.locations?.[0];\n\n // Primary index: file:symbol\n if (firstLocation && firstLocation.file) {\n const symbol = firstLocation.symbol || \"\";\n const key = `${firstLocation.file}:${symbol}`;\n primaryIndex.set(key, {\n modelEntry: {\n id: element.id,\n source_file: firstLocation.file,\n source_symbol: symbol,\n },\n key,\n });\n }\n\n // Secondary index: http_method:http_path\n const attrs = element.attributes as any;\n if (attrs?.http_method && attrs?.http_path) {\n const key = `${attrs.http_method}:${attrs.http_path}`;\n secondaryIndex.set(key, {\n modelEntry: {\n id: element.id,\n http_method: attrs.http_method,\n http_path: attrs.http_path,\n },\n key,\n });\n }\n }\n\n // Warn if any graph routes have no source_symbol \u2014 they will be skipped by primary-index matching\n const emptySymbolCount = routes.filter((r) => !r.source_symbol).length;\n if (emptySymbolCount > 0) {\n console.warn(\n `\\nWarning: ${emptySymbolCount}/${routes.length} graph route(s) have empty source_symbol.` +\n ` Primary-index matching by (source_file, source_symbol) will skip them.\\n` +\n ` Likely cause: the code graph analyzer did not extract handler function names for this language.\\n` +\n ` Secondary-index matching (http_method + http_path) requires those attributes on model operations.\\n` +\n ` To fix: re-index after the analyzer is updated, or manually set http_method/http_path on operations.\\n`\n );\n }\n\n // Step 3: Load ignore rules\n const ignoreFilePath =\n options.ignoreFilePath || path.join(projectRoot, \".dr-verify-ignore.yaml\");\n const ignoreRules = await IgnoreFileLoader.load(ignoreFilePath);\n\n // Step 4: Compute buckets\n const matched: MatchedEntry[] = [];\n const inGraphOnly: GraphOnlyEntry[] = [];\n const ignored: IgnoredEntry[] = [];\n const matchedRouteIds = new Set<string>();\n\n for (const route of routes) {\n let matchedElement: { id: string } | undefined;\n\n // Try primary index match\n if (route.source_file && route.source_symbol) {\n const key = `${route.source_file}:${route.source_symbol}`;\n const primaryMatch = primaryIndex.get(key);\n if (primaryMatch) {\n matchedElement = primaryMatch.modelEntry;\n }\n }\n\n // Fallback to secondary index match\n if (!matchedElement && route.http_method && route.http_path) {\n const key = `${route.http_method}:${route.http_path}`;\n const secondaryMatch = secondaryIndex.get(key);\n if (secondaryMatch) {\n matchedElement = secondaryMatch.modelEntry;\n }\n }\n\n // Check if ignored\n const ignoreReason = IgnoreFileLoader.matches(\n {\n handler: route.handler,\n path: route.http_path,\n element_id: route.id,\n },\n \"route\",\n ignoreRules\n );\n\n if (ignoreReason) {\n ignored.push({\n id: route.id,\n entry_type: \"route\",\n reason: ignoreReason,\n });\n // Update matchedRouteIds even when ignored to prevent false drift\n if (matchedElement) {\n matchedRouteIds.add(matchedElement.id);\n }\n } else if (matchedElement) {\n matched.push({\n id: matchedElement.id,\n type: \"operation\",\n source_file: route.source_file || \"\",\n source_symbol: route.source_symbol || \"\",\n });\n matchedRouteIds.add(matchedElement.id);\n } else {\n inGraphOnly.push({\n id: route.id,\n http_method: route.http_method,\n http_path: route.http_path,\n source_file: route.source_file || \"\",\n source_symbol: route.source_symbol || \"\",\n });\n }\n }\n\n // Compute in_model_only: model elements not matched in graph\n const inModelOnly: ModelOnlyEntry[] = [];\n\n // Batch file-existence checks\n const fileChecks: Promise<boolean>[] = [];\n const elementsToCheck: Array<{ id: string; path: string; file: string; symbol: string }> = [];\n let elementsWithoutSourceRef = 0;\n let elementsWithNonIndexableSource = 0;\n\n for (const element of apiLayer.elements.values()) {\n if (matchedRouteIds.has(element.id)) {\n continue; // Already matched\n }\n\n const firstLocation = element.source_reference?.locations?.[0];\n\n if (!firstLocation || !firstLocation.file) {\n // Skip elements with no source_reference file - they cannot drift since they have no code linkage.\n // This includes manually-created elements that are documentation-only.\n elementsWithoutSourceRef++;\n continue;\n }\n\n // CBM only indexes source-code files \u2014 non-code source files (e.g. openapi.yaml)\n // will never appear in the graph, so including them in drift detection produces false positives.\n if (!SOURCE_CODE_EXT_RE.test(firstLocation.file)) {\n elementsWithNonIndexableSource++;\n continue;\n }\n\n elementsToCheck.push({\n id: element.id,\n path: element.path,\n file: firstLocation.file,\n symbol: firstLocation.symbol || \"\",\n });\n\n const filePath = path.join(model.codebaseRoot, firstLocation.file);\n fileChecks.push(\n access(filePath)\n .then(() => true)\n .catch((err: NodeJS.ErrnoException) => {\n // Only treat ENOENT (file not found) as \"file doesn't exist\"\n // Other errors (EACCES, EMFILE, etc.) are treated as \"file exists\" to avoid false negatives\n if (err.code === \"ENOENT\") {\n return false;\n }\n // For permission errors and other issues, assume file exists to be conservative\n return true;\n })\n );\n }\n\n if (elementsWithoutSourceRef > 0) {\n console.warn(\n `\\nWarning: ${elementsWithoutSourceRef} API model element(s) excluded from drift detection` +\n ` \u2014 they have no source_reference with a file location.\\n` +\n ` Drift detection requires a linked source file to compare against the code graph.\\n` +\n ` Fix: dr update <element-id> --source-file <relative-path> --source-symbol <symbol>\\n` +\n ` Elements ingested via /dr-map or dr analyzer endpoints are automatically attributed.\\n`\n );\n }\n\n if (elementsWithNonIndexableSource > 0) {\n console.warn(\n `\\nWarning: ${elementsWithNonIndexableSource} API model element(s) excluded from drift detection` +\n ` \u2014 their source files are not indexable source-code files.\\n` +\n ` Point source_reference to a source-code file (.ts, .js, .py, etc.) for drift detection,\\n` +\n ` or use dr analyzer endpoints to discover routes automatically.\\n`\n );\n }\n\n // Wait for all file checks. All promises have .catch() handlers that return booleans,\n // so they will never reject - Promise.all() is safe and simpler than allSettled.\n const fileExistsResults = await Promise.all(fileChecks);\n\n // Add to in_model_only only if file exists, then check against ignore rules\n for (let i = 0; i < elementsToCheck.length; i++) {\n const fileExists = fileExistsResults[i];\n const elem = elementsToCheck[i];\n\n // Skip only if file definitively doesn't exist (false).\n // Process if file exists (true) or if check is unavailable (undefined).\n if (fileExists === false) {\n continue;\n }\n\n // Check if element matches any ignore rule\n // Use element.path (semantic path) instead of elem.id (UUID)\n const ignoreReason = IgnoreFileLoader.matches(\n {\n element_id: elem.path,\n },\n \"element\",\n ignoreRules\n );\n\n if (ignoreReason) {\n ignored.push({\n id: elem.id,\n entry_type: \"element\",\n reason: ignoreReason,\n });\n } else {\n inModelOnly.push({\n id: elem.id,\n type: \"operation\",\n source_file: elem.file,\n source_symbol: elem.symbol,\n });\n }\n }\n\n // Step 5: Assemble report\n const buckets: VerifyBuckets = {\n matched,\n in_graph_only: inGraphOnly,\n in_model_only: inModelOnly,\n ignored,\n };\n\n return this.assembleReport(\n projectRoot,\n model,\n buckets,\n activeChangesetId,\n options,\n analyzerName,\n indexMeta,\n elementsWithoutSourceRef + elementsWithNonIndexableSource\n );\n }\n\n /**\n * Assemble the final verification report\n *\n * @private\n */\n private assembleReport(\n projectRoot: string,\n model: Model,\n buckets: VerifyBuckets,\n activeChangesetId: string | null,\n options: VerifyOptions,\n analyzerName: string = \"codebase-memory-mcp\",\n indexMeta?: IndexMeta,\n elementsWithoutSourceRef: number = 0\n ): VerifyReport {\n const matchedElements = buckets.matched.length;\n const inModelOnly = buckets.in_model_only.length;\n const ignoredElements = buckets.ignored.filter((e) => e.entry_type === \"element\").length;\n const summary: VerifySummary = {\n matched_count: matchedElements,\n gap_count: buckets.in_graph_only.length,\n drift_count: inModelOnly,\n ignored_count: buckets.ignored.length,\n total_graph_entries: matchedElements + buckets.in_graph_only.length + buckets.ignored.filter((e) => e.entry_type === \"route\").length,\n // Count all API layer elements: those that participated in matching plus those\n // excluded because they lack source_reference (documentation-only elements).\n total_model_entries: matchedElements + inModelOnly + ignoredElements + elementsWithoutSourceRef,\n elements_without_source_ref: elementsWithoutSourceRef,\n };\n\n // Determine changeset context\n const changesetAware = options.changesetAware ?? true;\n const hasActiveChangeset = changesetAware && activeChangesetId !== null;\n\n // Use analyzer index timestamp if provided, otherwise fall back to model manifest modified time\n const analyzerIndexedAt = indexMeta?.timestamp || model.manifest.modified;\n\n // Construct changeset context as discriminated union\n const changeset_context: VerifyChangesetContext = hasActiveChangeset\n ? {\n active_changeset: activeChangesetId,\n verified_against: \"changeset_view\",\n }\n : {\n active_changeset: null,\n verified_against: \"base_model\",\n };\n\n const hints: string[] = [];\n if (buckets.in_graph_only.length > 0) {\n hints.push(\n \"To close gaps with source attribution, use /dr-map or `dr analyzer endpoints` ingestion \u2014 \"\n + \"manual `dr add` operations cannot be matched by the verifier.\"\n );\n }\n\n return {\n generated_at: new Date().toISOString(),\n project_root: projectRoot,\n analyzer: analyzerName,\n analyzer_indexed_at: analyzerIndexedAt,\n changeset_context,\n layers_verified: [\"api\"],\n buckets,\n summary,\n ...(hints.length > 0 ? { hints } : {}),\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "AAOA,YAAY,UAAU;AACtB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,uBAAuB;AAahC,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AA0C7B,MAAO,aAAY;;;;;;;;;;;;;;;;;EAiBvB,MAAM,cACJ,aACA,QACA,SACA,eAAuB,uBACvB,WAAqB;AAGrB,QAAI;AACJ,QAAI;AACF,cAAQ,MAAM,MAAM,KAAK,WAAW;IACtC,SAAS,OAAO;AACd,YAAM,UAAU,gBAAgB,KAAK;AAErC,YACE,QAAQ,SAAS,eAAe,KAChC,QAAQ,SAAS,iBAAiB,KAClC,QAAQ,SAAS,gBAAgB,KACjC,QAAQ,SAAS,uCAAuC,IAElD,IAAI,MACR,8BAA8B,WAAW,yFAAyF,IAIhI;IACR;AAEA,UAAM,oBAAoB,MAAM,qBAAoB,GAG9C,WAAW,QAAQ,mBAAmB,KACxC,MAAM,MAAM,aAAa,KAAK,IAC9B,MAAM,MAAM,SAAS,KAAK;AAE9B,QAAI,CAAC;AAEH,aAAO,KAAK,eACV,aACA,OACA;QACE,SAAS,CAAA;QACT,eAAe,OAAO,IAAI,CAAC,OAAO;UAChC,IAAI,EAAE;UACN,aAAa,EAAE;UACf,WAAW,EAAE;UACb,aAAa,EAAE,eAAe;UAC9B,eAAe,EAAE,iBAAiB;UAClC;QACF,eAAe,CAAA;QACf,SAAS,CAAA;SAEX,mBACA,SACA,cACA,SAAS;AAKb,UAAM,eAAe,oBAAI,IAAG,GACtB,iBAAiB,oBAAI,IAAG;AAE9B,eAAW,WAAW,SAAS,SAAS,OAAM,GAAI;AAChD,YAAM,gBAAgB,QAAQ,kBAAkB,YAAY,CAAC;AAG7D,UAAI,iBAAiB,cAAc,MAAM;AACvC,cAAM,SAAS,cAAc,UAAU,IACjC,MAAM,GAAG,cAAc,IAAI,IAAI,MAAM;AAC3C,qBAAa,IAAI,KAAK;UACpB,YAAY;YACV,IAAI,QAAQ;YACZ,aAAa,cAAc;YAC3B,eAAe;;UAEjB;SACD;MACH;AAGA,YAAM,QAAQ,QAAQ;AACtB,UAAI,OAAO,eAAe,OAAO,WAAW;AAC1C,cAAM,MAAM,GAAG,MAAM,WAAW,IAAI,MAAM,SAAS;AACnD,uBAAe,IAAI,KAAK;UACtB,YAAY;YACV,IAAI,QAAQ;YACZ,aAAa,MAAM;YACnB,WAAW,MAAM;;UAEnB;SACD;MACH;IACF;AAGA,UAAM,mBAAmB,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE;AAChE,IAAI,mBAAmB,KACrB,QAAQ,KACN;WAAc,gBAAgB,IAAI,OAAO,MAAM;;;;CAI2D;AAK9G,UAAM,iBACJ,QAAQ,kBAAkB,KAAK,KAAK,aAAa,wBAAwB,GACrE,cAAc,MAAM,iBAAiB,KAAK,cAAc,GAGxD,UAA0B,CAAA,GAC1B,cAAgC,CAAA,GAChC,UAA0B,CAAA,GAC1B,kBAAkB,oBAAI,IAAG;AAE/B,eAAW,SAAS,QAAQ;AAC1B,UAAI;AAGJ,UAAI,MAAM,eAAe,MAAM,eAAe;AAC5C,cAAM,MAAM,GAAG,MAAM,WAAW,IAAI,MAAM,aAAa,IACjD,eAAe,aAAa,IAAI,GAAG;AACzC,QAAI,iBACF,iBAAiB,aAAa;MAElC;AAGA,UAAI,CAAC,kBAAkB,MAAM,eAAe,MAAM,WAAW;AAC3D,cAAM,MAAM,GAAG,MAAM,WAAW,IAAI,MAAM,SAAS,IAC7C,iBAAiB,eAAe,IAAI,GAAG;AAC7C,QAAI,mBACF,iBAAiB,eAAe;MAEpC;AAGA,YAAM,eAAe,iBAAiB,QACpC;QACE,SAAS,MAAM;QACf,MAAM,MAAM;QACZ,YAAY,MAAM;SAEpB,SACA,WAAW;AAGb,MAAI,gBACF,QAAQ,KAAK;QACX,IAAI,MAAM;QACV,YAAY;QACZ,QAAQ;OACT,GAEG,kBACF,gBAAgB,IAAI,eAAe,EAAE,KAE9B,kBACT,QAAQ,KAAK;QACX,IAAI,eAAe;QACnB,MAAM;QACN,aAAa,MAAM,eAAe;QAClC,eAAe,MAAM,iBAAiB;OACvC,GACD,gBAAgB,IAAI,eAAe,EAAE,KAErC,YAAY,KAAK;QACf,IAAI,MAAM;QACV,aAAa,MAAM;QACnB,WAAW,MAAM;QACjB,aAAa,MAAM,eAAe;QAClC,eAAe,MAAM,iBAAiB;OACvC;IAEL;AAGA,UAAM,cAAgC,CAAA,GAGhC,aAAiC,CAAA,GACjC,kBAAqF,CAAA;AAC3F,QAAI,2BAA2B,GAC3B,iCAAiC;AAErC,eAAW,WAAW,SAAS,SAAS,OAAM,GAAI;AAChD,UAAI,gBAAgB,IAAI,QAAQ,EAAE;AAChC;AAGF,YAAM,gBAAgB,QAAQ,kBAAkB,YAAY,CAAC;AAE7D,UAAI,CAAC,iBAAiB,CAAC,cAAc,MAAM;AAGzC;AACA;MACF;AAIA,UAAI,CAAC,mBAAmB,KAAK,cAAc,IAAI,GAAG;AAChD;AACA;MACF;AAEA,sBAAgB,KAAK;QACnB,IAAI,QAAQ;QACZ,MAAM,QAAQ;QACd,MAAM,cAAc;QACpB,QAAQ,cAAc,UAAU;OACjC;AAED,YAAM,WAAW,KAAK,KAAK,MAAM,cAAc,cAAc,IAAI;AACjE,iBAAW,KACT,OAAO,QAAQ,EACZ,KAAK,MAAM,EAAI,EACf,MAAM,CAAC,QAGF,IAAI,SAAS,QAKlB,CAAC;IAER;AAEA,IAAI,2BAA2B,KAC7B,QAAQ,KACN;WAAc,wBAAwB;;;;CAIoD,GAI1F,iCAAiC,KACnC,QAAQ,KACN;WAAc,8BAA8B;;;CAGwB;AAMxE,UAAM,oBAAoB,MAAM,QAAQ,IAAI,UAAU;AAGtD,aAAS,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;AAC/C,YAAM,aAAa,kBAAkB,CAAC,GAChC,OAAO,gBAAgB,CAAC;AAI9B,UAAI,eAAe;AACjB;AAKF,YAAM,eAAe,iBAAiB,QACpC;QACE,YAAY,KAAK;SAEnB,WACA,WAAW;AAGb,MAAI,eACF,QAAQ,KAAK;QACX,IAAI,KAAK;QACT,YAAY;QACZ,QAAQ;OACT,IAED,YAAY,KAAK;QACf,IAAI,KAAK;QACT,MAAM;QACN,aAAa,KAAK;QAClB,eAAe,KAAK;OACrB;IAEL;AAGA,UAAM,UAAyB;MAC7B;MACA,eAAe;MACf,eAAe;MACf;;AAGF,WAAO,KAAK,eACV,aACA,OACA,SACA,mBACA,SACA,cACA,WACA,2BAA2B,8BAA8B;EAE7D;;;;;;EAOQ,eACN,aACA,OACA,SACA,mBACA,SACA,eAAuB,uBACvB,WACA,2BAAmC,GAAC;AAEpC,UAAM,kBAAkB,QAAQ,QAAQ,QAClC,cAAc,QAAQ,cAAc,QACpC,kBAAkB,QAAQ,QAAQ,OAAO,CAAC,MAAM,EAAE,eAAe,SAAS,EAAE,QAC5E,UAAyB;MAC7B,eAAe;MACf,WAAW,QAAQ,cAAc;MACjC,aAAa;MACb,eAAe,QAAQ,QAAQ;MAC/B,qBAAqB,kBAAkB,QAAQ,cAAc,SAAS,QAAQ,QAAQ,OAAO,CAAC,MAAM,EAAE,eAAe,OAAO,EAAE;;;MAG9H,qBAAqB,kBAAkB,cAAc,kBAAkB;MACvE,6BAA6B;OAKzB,sBADiB,QAAQ,kBAAkB,OACJ,sBAAsB,MAG7D,oBAAoB,WAAW,aAAa,MAAM,SAAS,UAG3D,oBAA4C,qBAC9C;MACE,kBAAkB;MAClB,kBAAkB;QAEpB;MACE,kBAAkB;MAClB,kBAAkB;OAGlB,QAAkB,CAAA;AACxB,WAAI,QAAQ,cAAc,SAAS,KACjC,MAAM,KACJ,8JACiE,GAI9D;MACL,eAAc,oBAAI,KAAI,GAAG,YAAW;MACpC,cAAc;MACd,UAAU;MACV,qBAAqB;MACrB;MACA,iBAAiB,CAAC,KAAK;MACvB;MACA;MACA,GAAI,MAAM,SAAS,IAAI,EAAE,MAAK,IAAK,CAAA;;EAEvC;;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -104,24 +104,24 @@ A "missing schema" is a relationship type that:
|
|
|
104
104
|
- Would be meaningfully instantiated in real codebases regularly \u2014 not just theoretically possible
|
|
105
105
|
- Does NOT yet exist in the spec for this layer pair
|
|
106
106
|
- Enables traceability, governance, or cross-cutting concern validation across this boundary
|
|
107
|
-
-
|
|
107
|
+
- Runs in the direction its predicate defines (spec/DESIGN_CHARTER.md); most cross-layer predicates (realizes, serves, satisfies, supports) run ${concreteLayer} \u2192 ${abstractLayer}, while maps-to and constrained-by run the other way
|
|
108
108
|
|
|
109
109
|
## The 13-Layer Model (concrete \u2192 abstract)
|
|
110
110
|
|
|
111
111
|
| # | Layer | Standard | Representative Node Types |
|
|
112
112
|
|---|-------|----------|--------------------------|
|
|
113
|
-
| 13 | testing | IEEE 829 |
|
|
113
|
+
| 13 | testing | IEEE 829 | testcoveragemodel, testcoveragetarget, coveragerequirement, testcasesketch, inputspacepartition |
|
|
114
114
|
| 12 | apm | OpenTelemetry | metric, trace, span, alert, dashboard, traceconfiguration |
|
|
115
115
|
| 11 | navigation | Router patterns | route, menu, menuitem, breadcrumb, flow, tab |
|
|
116
|
-
| 10 | ux | Component patterns | view, subview, librarycomponent,
|
|
116
|
+
| 10 | ux | Component patterns | view, subview, librarycomponent, componentinstance, actioncomponent |
|
|
117
117
|
| 9 | data-store | DB patterns | database, table, collection, index, view |
|
|
118
118
|
| 8 | data-model | JSON Schema | entity, attribute, relationship, enum, type |
|
|
119
119
|
| 7 | api | OpenAPI 3.0 | operation, pathitem, requestbody, response, schema, securityscheme |
|
|
120
120
|
| 6 | technology | ArchiMate | node, device, systemsoftware, technologyservice, infrastructureservice |
|
|
121
121
|
| 5 | application | ArchiMate | applicationcomponent, applicationservice, applicationfunction, applicationinterface |
|
|
122
|
-
| 4 | security | NIST SP 800-53 | secureresource, securitypolicy, threat,
|
|
123
|
-
| 3 | product | Product Management | persona,
|
|
124
|
-
| 2 | business | ArchiMate | businessprocess, businessservice, businessfunction, businessrole, businessobject |
|
|
122
|
+
| 4 | security | NIST SP 800-53 | secureresource, securitypolicy, threat, countermeasure, role, permission |
|
|
123
|
+
| 3 | product | Product Management | persona, softwarecapability, feature, userworkflow, milestone |
|
|
124
|
+
| 2 | business | ArchiMate | businessprocess, businessservice, businessfunction, businesscapability, businessrole, businessobject |
|
|
125
125
|
| 1 | motivation | ArchiMate | goal, driver, requirement, principle, constraint, outcome, stakeholder |
|
|
126
126
|
|
|
127
127
|
## Reference Direction Rule
|
|
@@ -190,17 +190,17 @@ Rules:
|
|
|
190
190
|
return {
|
|
191
191
|
motivation: "goal, driver, requirement, principle, constraint, outcome, stakeholder",
|
|
192
192
|
business: "businessprocess, businessservice, businessfunction, businessrole, businessobject, businessactor, product",
|
|
193
|
-
product: "persona,
|
|
194
|
-
security: "secureresource, securitypolicy, threat,
|
|
193
|
+
product: "persona, softwarecapability, feature, userworkflow, milestone",
|
|
194
|
+
security: "secureresource, securitypolicy, threat, countermeasure, role, permission, actor",
|
|
195
195
|
application: "applicationcomponent, applicationservice, applicationfunction, applicationinterface, applicationevent",
|
|
196
196
|
technology: "node, device, systemsoftware, technologyservice, technologyfunction, infrastructureservice",
|
|
197
197
|
api: "openapidocument, pathitem, operation, requestbody, response, schema, securityscheme, server, ratelimit",
|
|
198
198
|
"data-model": "entity, attribute, relationship, enum, type",
|
|
199
199
|
"data-store": "database, table, collection, index, view",
|
|
200
|
-
ux: "view, subview, librarycomponent,
|
|
200
|
+
ux: "view, subview, librarycomponent, componentinstance, actioncomponent, dataconfig, experiencestate, statetransition",
|
|
201
201
|
navigation: "route, menu, menuitem, breadcrumb, flow, tab",
|
|
202
202
|
apm: "metric, trace, span, alert, dashboard, traceconfiguration, logsource, sdkconfig",
|
|
203
|
-
testing: "
|
|
203
|
+
testing: "testcoveragemodel, testcoveragetarget, coveragerequirement, testcasesketch, inputspacepartition"
|
|
204
204
|
}[layer] ?? "see layer spec for node types";
|
|
205
205
|
}
|
|
206
206
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/audit/relationships/ai/prompts.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CoverageMetrics } from \"../../types.js\";\nimport { getLayerOrder } from \"../../../core/layers.js\";\n\n/**\n * Optional spec context passed to interLayerValidation() to ground the prompt\n * in real node types and existing schemas rather than relying on static hints.\n */\nexport interface InterLayerContext {\n /** 1-based layer number of the more-abstract (lower-numbered) layer */\n sourceLayerNumber: number;\n /** 1-based layer number of the more-concrete (higher-numbered) layer */\n targetLayerNumber: number;\n /** Node types available in the more-concrete layer (concrete \u2192 abstract references) */\n concreteNodeTypes?: string[];\n /** Node types available in the more-abstract layer */\n abstractNodeTypes?: string[];\n /** Cross-layer schemas already defined for this pair \u2014 excluded from suggestions */\n existingSchemas?: string[];\n /** Predicates valid for cross-layer use in this spec version */\n availablePredicates?: string[];\n}\n\nexport class PromptTemplates {\n /**\n * Generate element evaluation prompt for low-coverage node types\n */\n elementEvaluation(\n nodeType: string,\n coverage: CoverageMetrics,\n predicates: string[]\n ): string {\n const [layer, type] = nodeType.split(\".\");\n const standard = this.getLayerStandard(layer);\n const targetRange = this.getTargetRange();\n\n return `Evaluate the node type '${nodeType}' for missing intra-layer relationships.\n\nLayer: ${layer}\nNode Type: ${type}\nCurrent Relationships: ${coverage.relationshipsPerNodeType} (target: ${targetRange})\nLayer Standard: ${standard}\n\nAvailable Predicates:\n${predicates.map((p) => `- ${p}: ${this.getPredicateDefinition(p)}`).join(\"\\n\")}\n\nSuggest meaningful intra-layer relationships based on ${standard} patterns.\nFor each suggestion, provide:\n1. Source node type (this type: ${nodeType})\n2. Predicate (from available list)\n3. Destination node type (must exist in ${layer} layer)\n4. Architectural justification (reference ${standard} specification)\n5. Priority (high/medium/low)\n\nFormat as JSON array of relationship recommendations.`;\n }\n\n /**\n * Generate layer review prompt for overall coherence evaluation\n */\n layerReview(layer: string, coverage: CoverageMetrics[]): string {\n const layerMetrics = coverage.find((c) => c.layer === layer);\n if (!layerMetrics) {\n throw new Error(`No coverage metrics found for layer: ${layer}`);\n }\n\n const standard = this.getLayerStandard(layer);\n\n return `Review the ${layer} layer relationship coverage.\n\nNode Types: ${layerMetrics.nodeTypeCount}\nCurrent Relationships: ${layerMetrics.relationshipCount}\nIsolation: ${layerMetrics.isolationPercentage}%\nDensity: ${layerMetrics.relationshipsPerNodeType} per node type\nStandard: ${standard}\n\nEvaluate:\n1. Semantic coherence of existing relationships\n2. Missing critical relationship patterns from ${standard}\n3. Prioritized recommendations for relationship additions\n4. Balance assessment (are we over/under-specifying?)\n\nRespond with ONLY the following JSON, wrapped in a json code block (no other text before or after):\n\n\\`\\`\\`json\n{\n \"coherenceIssues\": [\"description of coherence issue\"],\n \"missingPatterns\": [\"description of missing pattern from ${standard}\"],\n \"recommendations\": [\n {\n \"sourceNodeType\": \"${layer}.nodeType\",\n \"predicate\": \"predicate-name\",\n \"destinationNodeType\": \"${layer}.otherNodeType\",\n \"justification\": \"architectural justification\",\n \"priority\": \"high\"\n }\n ],\n \"balanceAssessment\": \"brief assessment of relationship density and coverage\"\n}\n\\`\\`\\``;\n }\n\n /**\n * Generate inter-layer gap identification prompt.\n *\n * Identifies cross-layer relationship schemas that are architecturally missing\n * from the spec \u2014 schemas that SHOULD exist but DON'T \u2014 rather than validating\n * existing ones. Output `violations` entries represent missing schemas;\n * `recommendations` list the concrete spec files to create.\n *\n * @param sourceLayer - The more-abstract (lower-numbered) layer in the pair\n * @param targetLayer - The more-concrete (higher-numbered) layer in the pair\n * @param context - Optional spec context to ground suggestions in real node types\n */\n interLayerValidation(\n sourceLayer: string,\n targetLayer: string,\n context?: InterLayerContext\n ): string {\n // Determine which layer is more concrete (higher-numbered = closer to implementation).\n // The pipeline always passes source=lower-index, target=higher-index, so target\n // is typically the concrete layer. Use context numbers when available.\n const sourceNum = context?.sourceLayerNumber ?? this.getLayerNumber(sourceLayer);\n const targetNum = context?.targetLayerNumber ?? this.getLayerNumber(targetLayer);\n\n const [concreteLayer, concreteNum, abstractLayer, abstractNum] =\n sourceNum < targetNum\n ? [targetLayer, targetNum, sourceLayer, sourceNum]\n : [sourceLayer, sourceNum, targetLayer, targetNum];\n\n // Node type sections \u2014 use context when available, otherwise fall back to static list\n const concreteTypes =\n context?.concreteNodeTypes?.join(\", \") ??\n this.getLayerNodeTypeHint(concreteLayer);\n const abstractTypes =\n context?.abstractNodeTypes?.join(\", \") ??\n this.getLayerNodeTypeHint(abstractLayer);\n\n // Existing-schema section (skip boilerplate when the pair has no prior schemas)\n const existingSection =\n context?.existingSchemas && context.existingSchemas.length > 0\n ? `\\n## Schemas Already Defined for This Pair\\nDo NOT re-suggest these \u2014 they exist:\\n${context.existingSchemas.map((s) => `- ${s}`).join(\"\\n\")}\\n`\n : \"\";\n\n // Available predicates section\n const predicateSection =\n context?.availablePredicates && context.availablePredicates.length > 0\n ? `\\n## Valid Cross-Layer Predicates\\nOnly use predicates from this list:\\n${context.availablePredicates.map((p) => `- ${p}`).join(\"\\n\")}\\n`\n : `\\n## Commonly Valid Cross-Layer Predicates\\nUse one of: realizes, serves, accesses, uses, references, triggers, monitors, tests, covers, satisfies, implements, depends-on, maps-to, requires\\n`;\n\n return `You are an architecture spec analyst for the Documentation Robotics 13-layer model.\n\n## Task\nIdentify cross-layer relationship schemas that are MISSING from the spec for the layer pair:\n **${concreteLayer}** (layer ${concreteNum}, more concrete) \u2192 **${abstractLayer}** (layer ${abstractNum}, more abstract)\n\nA \"missing schema\" is a relationship type that:\n- Would be meaningfully instantiated in real codebases regularly \u2014 not just theoretically possible\n- Does NOT yet exist in the spec for this layer pair\n- Enables traceability, governance, or cross-cutting concern validation across this boundary\n- Follows the correct reference direction: ${concreteLayer} \u2192 ${abstractLayer}\n\n## The 13-Layer Model (concrete \u2192 abstract)\n\n| # | Layer | Standard | Representative Node Types |\n|---|-------|----------|--------------------------|\n| 13 | testing | IEEE 829 | testsuite, testcase, testdata, teststrategy, testcoverage |\n| 12 | apm | OpenTelemetry | metric, trace, span, alert, dashboard, traceconfiguration |\n| 11 | navigation | Router patterns | route, menu, menuitem, breadcrumb, flow, tab |\n| 10 | ux | Component patterns | view, subview, librarycomponent, actioncomponent, formcomponent |\n| 9 | data-store | DB patterns | database, table, collection, index, view |\n| 8 | data-model | JSON Schema | entity, attribute, relationship, enum, type |\n| 7 | api | OpenAPI 3.0 | operation, pathitem, requestbody, response, schema, securityscheme |\n| 6 | technology | ArchiMate | node, device, systemsoftware, technologyservice, infrastructureservice |\n| 5 | application | ArchiMate | applicationcomponent, applicationservice, applicationfunction, applicationinterface |\n| 4 | security | NIST SP 800-53 | secureresource, securitypolicy, threat, control, accesscontrol |\n| 3 | product | Product Management | persona, capability, feature, userworkflow, milestone |\n| 2 | business | ArchiMate | businessprocess, businessservice, businessfunction, businessrole, businessobject |\n| 1 | motivation | ArchiMate | goal, driver, requirement, principle, constraint, outcome, stakeholder |\n\n## Reference Direction Rule\nCONCRETE layers (higher numbers, closer to implementation) reference ABSTRACT layers (lower numbers, closer to goals). References flow downward through the stack:\n testing(13) \u2192 apm(12) \u2192 navigation(11) \u2192 ux(10) \u2192 data-store(9) \u2192 data-model(8) \u2192 api(7) \u2192 technology(6) \u2192 application(5) \u2192 security(4) \u2192 product(3) \u2192 business(2) \u2192 motivation(1)\n\nFor this pair, valid schemas define relationships **from ${concreteLayer} node types to ${abstractLayer} node types**.\n${existingSection}${predicateSection}\n## Node Types in This Pair\n\n**${concreteLayer}** (source of relationships): ${concreteTypes}\n**${abstractLayer}** (target of relationships): ${abstractTypes}\n\n## Anti-Noise Criteria\nONLY suggest a schema if ALL of the following hold:\n1. A real development team would routinely create instances of this relationship\n2. The schema enables meaningful traceability or cross-cutting validation at this layer boundary\n3. The source and target node types are semantically compatible (the relationship \"makes sense\" to a developer)\n4. The gap would affect \u226510% of real models containing both layers\n5. The relationship is NOT already covered by an existing schema for this pair\n\nDo NOT suggest schemas that are:\n- Speculative, aspirational, or \"might be useful someday\"\n- Already covered by existing schemas (listed above)\n- In the wrong direction (abstract \u2192 concrete references are violations of the model)\n- Between node types with no natural semantic connection\n\n## Output Format\nRespond with ONLY the following JSON, wrapped in a json code block (no other text before or after):\n\n\\`\\`\\`json\n{\n \"violations\": [\n {\n \"sourceLayer\": \"${concreteLayer}\",\n \"targetLayer\": \"${abstractLayer}\",\n \"issue\": \"Missing schema: {sourceNodeType}.{predicate}.{targetNodeType} \u2014 {one-sentence architectural justification}\"\n }\n ],\n \"recommendations\": [\n \"Add spec/schemas/relationships/${concreteLayer}/{sourceNodeType}.{predicate}.{targetNodeType}.relationship.schema.json\"\n ]\n}\n\\`\\`\\`\n\nRules:\n- Each violation entry corresponds to exactly one missing schema\n- Each recommendation is the concrete spec file path to create for that schema\n- Limit to at most 8 gap suggestions \u2014 prefer highest-confidence, highest-impact gaps\n- If no schemas are architecturally missing, return: { \"violations\": [], \"recommendations\": [] }`;\n }\n\n /**\n * Get the canonical layer number (1\u201313) for a layer name.\n * Returns 0 if the layer name is unrecognized.\n */\n private getLayerNumber(layer: string): number {\n const layerNum = getLayerOrder(layer);\n return layerNum >= 0 ? layerNum : 0;\n }\n\n /**\n * Get a representative hint string for the node types in a layer.\n * Used as a fallback when context.concreteNodeTypes / context.abstractNodeTypes\n * are not provided by the caller.\n */\n private getLayerNodeTypeHint(layer: string): string {\n const hints: Record<string, string> = {\n motivation: \"goal, driver, requirement, principle, constraint, outcome, stakeholder\",\n business:\n \"businessprocess, businessservice, businessfunction, businessrole, businessobject, businessactor, product\",\n product: \"persona, capability, feature, userworkflow, milestone\",\n security: \"secureresource, securitypolicy, threat, control, accesscontrol, identitysubject\",\n application:\n \"applicationcomponent, applicationservice, applicationfunction, applicationinterface, applicationevent\",\n technology:\n \"node, device, systemsoftware, technologyservice, technologyfunction, infrastructureservice\",\n api: \"openapidocument, pathitem, operation, requestbody, response, schema, securityscheme, server, ratelimit\",\n \"data-model\": \"entity, attribute, relationship, enum, type\",\n \"data-store\": \"database, table, collection, index, view\",\n ux: \"view, subview, librarycomponent, actioncomponent, formcomponent, contentcomponent, loadingstate, modal\",\n navigation: \"route, menu, menuitem, breadcrumb, flow, tab\",\n apm: \"metric, trace, span, alert, dashboard, traceconfiguration, logsource, sdkconfig\",\n testing: \"testsuite, testcase, testdata, teststrategy, testcoverage\",\n };\n return hints[layer] ?? \"see layer spec for node types\";\n }\n\n /**\n * Get the standard/framework for a layer\n */\n getLayerStandard(layer: string): string {\n const standards: Record<string, string> = {\n motivation: \"ArchiMate 3.2\",\n business: \"ArchiMate 3.2\",\n product: \"Product Management Practice\",\n security: \"NIST SP 800-53\",\n application: \"ArchiMate 3.2\",\n technology: \"ArchiMate 3.2\",\n api: \"OpenAPI 3.0\",\n \"data-model\": \"JSON Schema Draft 7\",\n \"data-store\": \"Database patterns\",\n ux: \"React/Component patterns\",\n navigation: \"Router patterns\",\n apm: \"OpenTelemetry\",\n testing: \"Test patterns\",\n };\n return standards[layer] || \"Architecture best practices\";\n }\n\n /**\n * Get target relationship range for a node type\n *\n * Returns a static range \"2-5\" for all types. This is intentionally\n * simplified for AI prompts to avoid over-constraining suggestions.\n * Type-specific refinement could be added in the future if needed,\n * but the current approach works well for generating diverse recommendations.\n */\n private getTargetRange(): string {\n // Default target based on common patterns across all types\n return \"2-5\";\n }\n\n /**\n * Get predicate definition/description\n *\n * NOTE: These predicate definitions are intentionally a curated subset\n * rather than being loaded from the full relationship catalog. This is\n * by design to keep AI prompts focused on the most common and important\n * relationships. The full catalog has 252 relationship types which would\n * overwhelm the AI context window and reduce prompt clarity.\n */\n private getPredicateDefinition(predicate: string): string {\n // Curated predicate definitions (subset of relationship catalog)\n const definitions: Record<string, string> = {\n realizes: \"Realization relationship - implements or fulfills\",\n serves: \"Serving relationship - provides service to\",\n accesses: \"Access relationship - reads/writes data\",\n triggers: \"Triggering relationship - initiates or causes\",\n influences: \"Influence relationship - affects or impacts\",\n uses: \"Usage relationship - employs or utilizes\",\n composes: \"Composition relationship - structural part-of\",\n aggregates: \"Aggregation relationship - logical grouping\",\n specializes: \"Specialization relationship - is-a subtype of\",\n assigns: \"Assignment relationship - allocates responsibility\",\n flows: \"Flow relationship - information or control transfer\",\n };\n\n return definitions[predicate] || \"Relationship between elements\";\n }\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,qBAAqB;AAqBxB,MAAO,gBAAe;;;;EAI1B,kBACE,UACA,UACA,YAAoB;AAEpB,UAAM,CAAC,OAAO,IAAI,IAAI,SAAS,MAAM,GAAG,GAClC,WAAW,KAAK,iBAAiB,KAAK,GACtC,cAAc,KAAK,eAAc;AAEvC,WAAO,2BAA2B,QAAQ;;SAErC,KAAK;aACD,IAAI;yBACQ,SAAS,wBAAwB,aAAa,WAAW;kBAChE,QAAQ;;;EAGxB,WAAW,IAAI,CAAC,MAAM,KAAK,CAAC,KAAK,KAAK,uBAAuB,CAAC,CAAC,EAAE,EAAE,KAAK;CAAI,CAAC;;wDAEvB,QAAQ;;kCAE9B,QAAQ;;0CAEA,KAAK;4CACH,QAAQ;;;;EAIlD;;;;EAKA,YAAY,OAAe,UAA2B;AACpD,UAAM,eAAe,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK;AAC3D,QAAI,CAAC;AACH,YAAM,IAAI,MAAM,wCAAwC,KAAK,EAAE;AAGjE,UAAM,WAAW,KAAK,iBAAiB,KAAK;AAE5C,WAAO,cAAc,KAAK;;cAEhB,aAAa,aAAa;yBACf,aAAa,iBAAiB;aAC1C,aAAa,mBAAmB;WAClC,aAAa,wBAAwB;YACpC,QAAQ;;;;iDAI6B,QAAQ;;;;;;;;;6DASI,QAAQ;;;2BAG1C,KAAK;;gCAEA,KAAK;;;;;;;;EAQnC;;;;;;;;;;;;;EAcA,qBACE,aACA,aACA,SAA2B;AAK3B,UAAM,YAAY,SAAS,qBAAqB,KAAK,eAAe,WAAW,GACzE,YAAY,SAAS,qBAAqB,KAAK,eAAe,WAAW,GAEzE,CAAC,eAAe,aAAa,eAAe,WAAW,IAC3D,YAAY,YACR,CAAC,aAAa,WAAW,aAAa,SAAS,IAC/C,CAAC,aAAa,WAAW,aAAa,SAAS,GAG/C,gBACJ,SAAS,mBAAmB,KAAK,IAAI,KACrC,KAAK,qBAAqB,aAAa,GACnC,gBACJ,SAAS,mBAAmB,KAAK,IAAI,KACrC,KAAK,qBAAqB,aAAa,GAGnC,kBACJ,SAAS,mBAAmB,QAAQ,gBAAgB,SAAS,IACzD;;;EAAsF,QAAQ,gBAAgB,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK;CAAI,CAAC;IAC7I,IAGA,mBACJ,SAAS,uBAAuB,QAAQ,oBAAoB,SAAS,IACjE;;;EAA2E,QAAQ,oBAAoB,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK;CAAI,CAAC;IACtI;;;;AAEN,WAAO;;;;MAIL,aAAa,aAAa,WAAW,6BAAwB,aAAa,aAAa,WAAW;;;;;;
|
|
4
|
+
"sourcesContent": ["import type { CoverageMetrics } from \"../../types.js\";\nimport { getLayerOrder } from \"../../../core/layers.js\";\n\n/**\n * Optional spec context passed to interLayerValidation() to ground the prompt\n * in real node types and existing schemas rather than relying on static hints.\n */\nexport interface InterLayerContext {\n /** 1-based layer number of the more-abstract (lower-numbered) layer */\n sourceLayerNumber: number;\n /** 1-based layer number of the more-concrete (higher-numbered) layer */\n targetLayerNumber: number;\n /** Node types available in the more-concrete layer (concrete \u2192 abstract references) */\n concreteNodeTypes?: string[];\n /** Node types available in the more-abstract layer */\n abstractNodeTypes?: string[];\n /** Cross-layer schemas already defined for this pair \u2014 excluded from suggestions */\n existingSchemas?: string[];\n /** Predicates valid for cross-layer use in this spec version */\n availablePredicates?: string[];\n}\n\nexport class PromptTemplates {\n /**\n * Generate element evaluation prompt for low-coverage node types\n */\n elementEvaluation(\n nodeType: string,\n coverage: CoverageMetrics,\n predicates: string[]\n ): string {\n const [layer, type] = nodeType.split(\".\");\n const standard = this.getLayerStandard(layer);\n const targetRange = this.getTargetRange();\n\n return `Evaluate the node type '${nodeType}' for missing intra-layer relationships.\n\nLayer: ${layer}\nNode Type: ${type}\nCurrent Relationships: ${coverage.relationshipsPerNodeType} (target: ${targetRange})\nLayer Standard: ${standard}\n\nAvailable Predicates:\n${predicates.map((p) => `- ${p}: ${this.getPredicateDefinition(p)}`).join(\"\\n\")}\n\nSuggest meaningful intra-layer relationships based on ${standard} patterns.\nFor each suggestion, provide:\n1. Source node type (this type: ${nodeType})\n2. Predicate (from available list)\n3. Destination node type (must exist in ${layer} layer)\n4. Architectural justification (reference ${standard} specification)\n5. Priority (high/medium/low)\n\nFormat as JSON array of relationship recommendations.`;\n }\n\n /**\n * Generate layer review prompt for overall coherence evaluation\n */\n layerReview(layer: string, coverage: CoverageMetrics[]): string {\n const layerMetrics = coverage.find((c) => c.layer === layer);\n if (!layerMetrics) {\n throw new Error(`No coverage metrics found for layer: ${layer}`);\n }\n\n const standard = this.getLayerStandard(layer);\n\n return `Review the ${layer} layer relationship coverage.\n\nNode Types: ${layerMetrics.nodeTypeCount}\nCurrent Relationships: ${layerMetrics.relationshipCount}\nIsolation: ${layerMetrics.isolationPercentage}%\nDensity: ${layerMetrics.relationshipsPerNodeType} per node type\nStandard: ${standard}\n\nEvaluate:\n1. Semantic coherence of existing relationships\n2. Missing critical relationship patterns from ${standard}\n3. Prioritized recommendations for relationship additions\n4. Balance assessment (are we over/under-specifying?)\n\nRespond with ONLY the following JSON, wrapped in a json code block (no other text before or after):\n\n\\`\\`\\`json\n{\n \"coherenceIssues\": [\"description of coherence issue\"],\n \"missingPatterns\": [\"description of missing pattern from ${standard}\"],\n \"recommendations\": [\n {\n \"sourceNodeType\": \"${layer}.nodeType\",\n \"predicate\": \"predicate-name\",\n \"destinationNodeType\": \"${layer}.otherNodeType\",\n \"justification\": \"architectural justification\",\n \"priority\": \"high\"\n }\n ],\n \"balanceAssessment\": \"brief assessment of relationship density and coverage\"\n}\n\\`\\`\\``;\n }\n\n /**\n * Generate inter-layer gap identification prompt.\n *\n * Identifies cross-layer relationship schemas that are architecturally missing\n * from the spec \u2014 schemas that SHOULD exist but DON'T \u2014 rather than validating\n * existing ones. Output `violations` entries represent missing schemas;\n * `recommendations` list the concrete spec files to create.\n *\n * @param sourceLayer - The more-abstract (lower-numbered) layer in the pair\n * @param targetLayer - The more-concrete (higher-numbered) layer in the pair\n * @param context - Optional spec context to ground suggestions in real node types\n */\n interLayerValidation(\n sourceLayer: string,\n targetLayer: string,\n context?: InterLayerContext\n ): string {\n // Determine which layer is more concrete (higher-numbered = closer to implementation).\n // The pipeline always passes source=lower-index, target=higher-index, so target\n // is typically the concrete layer. Use context numbers when available.\n const sourceNum = context?.sourceLayerNumber ?? this.getLayerNumber(sourceLayer);\n const targetNum = context?.targetLayerNumber ?? this.getLayerNumber(targetLayer);\n\n const [concreteLayer, concreteNum, abstractLayer, abstractNum] =\n sourceNum < targetNum\n ? [targetLayer, targetNum, sourceLayer, sourceNum]\n : [sourceLayer, sourceNum, targetLayer, targetNum];\n\n // Node type sections \u2014 use context when available, otherwise fall back to static list\n const concreteTypes =\n context?.concreteNodeTypes?.join(\", \") ??\n this.getLayerNodeTypeHint(concreteLayer);\n const abstractTypes =\n context?.abstractNodeTypes?.join(\", \") ??\n this.getLayerNodeTypeHint(abstractLayer);\n\n // Existing-schema section (skip boilerplate when the pair has no prior schemas)\n const existingSection =\n context?.existingSchemas && context.existingSchemas.length > 0\n ? `\\n## Schemas Already Defined for This Pair\\nDo NOT re-suggest these \u2014 they exist:\\n${context.existingSchemas.map((s) => `- ${s}`).join(\"\\n\")}\\n`\n : \"\";\n\n // Available predicates section\n const predicateSection =\n context?.availablePredicates && context.availablePredicates.length > 0\n ? `\\n## Valid Cross-Layer Predicates\\nOnly use predicates from this list:\\n${context.availablePredicates.map((p) => `- ${p}`).join(\"\\n\")}\\n`\n : `\\n## Commonly Valid Cross-Layer Predicates\\nUse one of: realizes, serves, accesses, uses, references, triggers, monitors, tests, covers, satisfies, implements, depends-on, maps-to, requires\\n`;\n\n return `You are an architecture spec analyst for the Documentation Robotics 13-layer model.\n\n## Task\nIdentify cross-layer relationship schemas that are MISSING from the spec for the layer pair:\n **${concreteLayer}** (layer ${concreteNum}, more concrete) \u2192 **${abstractLayer}** (layer ${abstractNum}, more abstract)\n\nA \"missing schema\" is a relationship type that:\n- Would be meaningfully instantiated in real codebases regularly \u2014 not just theoretically possible\n- Does NOT yet exist in the spec for this layer pair\n- Enables traceability, governance, or cross-cutting concern validation across this boundary\n- Runs in the direction its predicate defines (spec/DESIGN_CHARTER.md); most cross-layer predicates (realizes, serves, satisfies, supports) run ${concreteLayer} \u2192 ${abstractLayer}, while maps-to and constrained-by run the other way\n\n## The 13-Layer Model (concrete \u2192 abstract)\n\n| # | Layer | Standard | Representative Node Types |\n|---|-------|----------|--------------------------|\n| 13 | testing | IEEE 829 | testcoveragemodel, testcoveragetarget, coveragerequirement, testcasesketch, inputspacepartition |\n| 12 | apm | OpenTelemetry | metric, trace, span, alert, dashboard, traceconfiguration |\n| 11 | navigation | Router patterns | route, menu, menuitem, breadcrumb, flow, tab |\n| 10 | ux | Component patterns | view, subview, librarycomponent, componentinstance, actioncomponent |\n| 9 | data-store | DB patterns | database, table, collection, index, view |\n| 8 | data-model | JSON Schema | entity, attribute, relationship, enum, type |\n| 7 | api | OpenAPI 3.0 | operation, pathitem, requestbody, response, schema, securityscheme |\n| 6 | technology | ArchiMate | node, device, systemsoftware, technologyservice, infrastructureservice |\n| 5 | application | ArchiMate | applicationcomponent, applicationservice, applicationfunction, applicationinterface |\n| 4 | security | NIST SP 800-53 | secureresource, securitypolicy, threat, countermeasure, role, permission |\n| 3 | product | Product Management | persona, softwarecapability, feature, userworkflow, milestone |\n| 2 | business | ArchiMate | businessprocess, businessservice, businessfunction, businesscapability, businessrole, businessobject |\n| 1 | motivation | ArchiMate | goal, driver, requirement, principle, constraint, outcome, stakeholder |\n\n## Reference Direction Rule\nCONCRETE layers (higher numbers, closer to implementation) reference ABSTRACT layers (lower numbers, closer to goals). References flow downward through the stack:\n testing(13) \u2192 apm(12) \u2192 navigation(11) \u2192 ux(10) \u2192 data-store(9) \u2192 data-model(8) \u2192 api(7) \u2192 technology(6) \u2192 application(5) \u2192 security(4) \u2192 product(3) \u2192 business(2) \u2192 motivation(1)\n\nFor this pair, valid schemas define relationships **from ${concreteLayer} node types to ${abstractLayer} node types**.\n${existingSection}${predicateSection}\n## Node Types in This Pair\n\n**${concreteLayer}** (source of relationships): ${concreteTypes}\n**${abstractLayer}** (target of relationships): ${abstractTypes}\n\n## Anti-Noise Criteria\nONLY suggest a schema if ALL of the following hold:\n1. A real development team would routinely create instances of this relationship\n2. The schema enables meaningful traceability or cross-cutting validation at this layer boundary\n3. The source and target node types are semantically compatible (the relationship \"makes sense\" to a developer)\n4. The gap would affect \u226510% of real models containing both layers\n5. The relationship is NOT already covered by an existing schema for this pair\n\nDo NOT suggest schemas that are:\n- Speculative, aspirational, or \"might be useful someday\"\n- Already covered by existing schemas (listed above)\n- In the wrong direction (abstract \u2192 concrete references are violations of the model)\n- Between node types with no natural semantic connection\n\n## Output Format\nRespond with ONLY the following JSON, wrapped in a json code block (no other text before or after):\n\n\\`\\`\\`json\n{\n \"violations\": [\n {\n \"sourceLayer\": \"${concreteLayer}\",\n \"targetLayer\": \"${abstractLayer}\",\n \"issue\": \"Missing schema: {sourceNodeType}.{predicate}.{targetNodeType} \u2014 {one-sentence architectural justification}\"\n }\n ],\n \"recommendations\": [\n \"Add spec/schemas/relationships/${concreteLayer}/{sourceNodeType}.{predicate}.{targetNodeType}.relationship.schema.json\"\n ]\n}\n\\`\\`\\`\n\nRules:\n- Each violation entry corresponds to exactly one missing schema\n- Each recommendation is the concrete spec file path to create for that schema\n- Limit to at most 8 gap suggestions \u2014 prefer highest-confidence, highest-impact gaps\n- If no schemas are architecturally missing, return: { \"violations\": [], \"recommendations\": [] }`;\n }\n\n /**\n * Get the canonical layer number (1\u201313) for a layer name.\n * Returns 0 if the layer name is unrecognized.\n */\n private getLayerNumber(layer: string): number {\n const layerNum = getLayerOrder(layer);\n return layerNum >= 0 ? layerNum : 0;\n }\n\n /**\n * Get a representative hint string for the node types in a layer.\n * Used as a fallback when context.concreteNodeTypes / context.abstractNodeTypes\n * are not provided by the caller.\n */\n private getLayerNodeTypeHint(layer: string): string {\n const hints: Record<string, string> = {\n motivation: \"goal, driver, requirement, principle, constraint, outcome, stakeholder\",\n business:\n \"businessprocess, businessservice, businessfunction, businessrole, businessobject, businessactor, product\",\n product: \"persona, softwarecapability, feature, userworkflow, milestone\",\n security: \"secureresource, securitypolicy, threat, countermeasure, role, permission, actor\",\n application:\n \"applicationcomponent, applicationservice, applicationfunction, applicationinterface, applicationevent\",\n technology:\n \"node, device, systemsoftware, technologyservice, technologyfunction, infrastructureservice\",\n api: \"openapidocument, pathitem, operation, requestbody, response, schema, securityscheme, server, ratelimit\",\n \"data-model\": \"entity, attribute, relationship, enum, type\",\n \"data-store\": \"database, table, collection, index, view\",\n ux: \"view, subview, librarycomponent, componentinstance, actioncomponent, dataconfig, experiencestate, statetransition\",\n navigation: \"route, menu, menuitem, breadcrumb, flow, tab\",\n apm: \"metric, trace, span, alert, dashboard, traceconfiguration, logsource, sdkconfig\",\n testing: \"testcoveragemodel, testcoveragetarget, coveragerequirement, testcasesketch, inputspacepartition\",\n };\n return hints[layer] ?? \"see layer spec for node types\";\n }\n\n /**\n * Get the standard/framework for a layer\n */\n getLayerStandard(layer: string): string {\n const standards: Record<string, string> = {\n motivation: \"ArchiMate 3.2\",\n business: \"ArchiMate 3.2\",\n product: \"Product Management Practice\",\n security: \"NIST SP 800-53\",\n application: \"ArchiMate 3.2\",\n technology: \"ArchiMate 3.2\",\n api: \"OpenAPI 3.0\",\n \"data-model\": \"JSON Schema Draft 7\",\n \"data-store\": \"Database patterns\",\n ux: \"React/Component patterns\",\n navigation: \"Router patterns\",\n apm: \"OpenTelemetry\",\n testing: \"Test patterns\",\n };\n return standards[layer] || \"Architecture best practices\";\n }\n\n /**\n * Get target relationship range for a node type\n *\n * Returns a static range \"2-5\" for all types. This is intentionally\n * simplified for AI prompts to avoid over-constraining suggestions.\n * Type-specific refinement could be added in the future if needed,\n * but the current approach works well for generating diverse recommendations.\n */\n private getTargetRange(): string {\n // Default target based on common patterns across all types\n return \"2-5\";\n }\n\n /**\n * Get predicate definition/description\n *\n * NOTE: These predicate definitions are intentionally a curated subset\n * rather than being loaded from the full relationship catalog. This is\n * by design to keep AI prompts focused on the most common and important\n * relationships. The full catalog has 252 relationship types which would\n * overwhelm the AI context window and reduce prompt clarity.\n */\n private getPredicateDefinition(predicate: string): string {\n // Curated predicate definitions (subset of relationship catalog)\n const definitions: Record<string, string> = {\n realizes: \"Realization relationship - implements or fulfills\",\n serves: \"Serving relationship - provides service to\",\n accesses: \"Access relationship - reads/writes data\",\n triggers: \"Triggering relationship - initiates or causes\",\n influences: \"Influence relationship - affects or impacts\",\n uses: \"Usage relationship - employs or utilizes\",\n composes: \"Composition relationship - structural part-of\",\n aggregates: \"Aggregation relationship - logical grouping\",\n specializes: \"Specialization relationship - is-a subtype of\",\n assigns: \"Assignment relationship - allocates responsibility\",\n flows: \"Flow relationship - information or control transfer\",\n };\n\n return definitions[predicate] || \"Relationship between elements\";\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|