@documentation-robotics/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +10 -14
  2. package/dist/analyzers/active-analyzer.js +62 -0
  3. package/dist/analyzers/active-analyzer.js.map +7 -0
  4. package/dist/analyzers/cards/build.js +21 -1
  5. package/dist/analyzers/cards/build.js.map +2 -2
  6. package/dist/analyzers/cards/extract.js +138 -0
  7. package/dist/analyzers/cards/extract.js.map +7 -0
  8. package/dist/analyzers/cards/routes.js +96 -0
  9. package/dist/analyzers/cards/routes.js.map +7 -0
  10. package/dist/analyzers/cbm-analyzer.js +76 -4
  11. package/dist/analyzers/cbm-analyzer.js.map +2 -2
  12. package/dist/analyzers/cbm-responses.js +12 -6
  13. package/dist/analyzers/cbm-responses.js.map +2 -2
  14. package/dist/analyzers/code-changes.js +159 -0
  15. package/dist/analyzers/code-changes.js.map +7 -0
  16. package/dist/analyzers/edge-relationships.js +6 -2
  17. package/dist/analyzers/edge-relationships.js.map +2 -2
  18. package/dist/analyzers/extraction-plan.js +541 -0
  19. package/dist/analyzers/extraction-plan.js.map +7 -0
  20. package/dist/analyzers/index-job.js +229 -0
  21. package/dist/analyzers/index-job.js.map +7 -0
  22. package/dist/cli.js +12 -10
  23. package/dist/cli.js.map +2 -2
  24. package/dist/coding-agents/drbot-mcp.js +120 -18
  25. package/dist/coding-agents/drbot-mcp.js.map +2 -2
  26. package/dist/commands/add.js +4 -2
  27. package/dist/commands/add.js.map +2 -2
  28. package/dist/commands/analyzer.js +35 -35
  29. package/dist/commands/analyzer.js.map +2 -2
  30. package/dist/commands/changeset-verify.js +16 -8
  31. package/dist/commands/changeset-verify.js.map +2 -2
  32. package/dist/commands/changeset.js +41 -11
  33. package/dist/commands/changeset.js.map +2 -2
  34. package/dist/commands/init.js +9 -26
  35. package/dist/commands/init.js.map +2 -2
  36. package/dist/commands/mcp.js +35 -31
  37. package/dist/commands/mcp.js.map +3 -3
  38. package/dist/commands/relationship.js +2 -12
  39. package/dist/commands/relationship.js.map +2 -2
  40. package/dist/commands/reports.js +3 -3
  41. package/dist/commands/reports.js.map +2 -2
  42. package/dist/commands/update.js +14 -13
  43. package/dist/commands/update.js.map +2 -2
  44. package/dist/commands/upgrade.js +28 -120
  45. package/dist/commands/upgrade.js.map +2 -2
  46. package/dist/commands/validate.js +19 -3
  47. package/dist/commands/validate.js.map +2 -2
  48. package/dist/commands/version.js +1 -1
  49. package/dist/commands/visualize.js +30 -12
  50. package/dist/commands/visualize.js.map +2 -2
  51. package/dist/core/annotation-store.js +15 -5
  52. package/dist/core/annotation-store.js.map +2 -2
  53. package/dist/core/catalog-migration.js +2 -1
  54. package/dist/core/catalog-migration.js.map +2 -2
  55. package/dist/core/changeset-base-index.js +7 -0
  56. package/dist/core/changeset-base-index.js.map +2 -2
  57. package/dist/core/changeset-brief-compact.js +256 -0
  58. package/dist/core/changeset-brief-compact.js.map +7 -0
  59. package/dist/core/changeset-verify.js +356 -26
  60. package/dist/core/changeset-verify.js.map +3 -3
  61. package/dist/core/extraction-observability.js +161 -0
  62. package/dist/core/extraction-observability.js.map +7 -0
  63. package/dist/core/migration-registry.js +13 -1
  64. package/dist/core/migration-registry.js.map +2 -2
  65. package/dist/core/project-init.js +53 -0
  66. package/dist/core/project-init.js.map +7 -0
  67. package/dist/core/relationship-staging.js +94 -1
  68. package/dist/core/relationship-staging.js.map +2 -2
  69. package/dist/core/staged-changeset-storage.js +83 -3
  70. package/dist/core/staged-changeset-storage.js.map +2 -2
  71. package/dist/core/staging-area.js +85 -8
  72. package/dist/core/staging-area.js.map +2 -2
  73. package/dist/core/upgrade-plan.js +117 -0
  74. package/dist/core/upgrade-plan.js.map +7 -0
  75. package/dist/generated/relationship-index.js +124 -15
  76. package/dist/generated/relationship-index.js.map +2 -2
  77. package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
  78. package/dist/integrations/claude_code/agents/dr-architect.md +19 -5
  79. package/dist/integrations/claude_code/agents/dr-extractor.md +7 -1
  80. package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
  81. package/dist/integrations/claude_code/commands/dr-map.md +7 -1
  82. package/dist/integrations/claude_code/commands/dr-plan.md +42 -16
  83. package/dist/integrations/claude_code/docs/MCP_SERVER.md +28 -31
  84. package/dist/integrations/claude_code/docs/README.md +20 -1
  85. package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
  86. package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
  87. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +3 -0
  88. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +90 -31
  89. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +5 -0
  90. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +5 -0
  91. package/dist/integrations/github_copilot/agents/dr-architect.md +2 -0
  92. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +3 -0
  93. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +88 -30
  94. package/dist/mcp/changeset-only.js +14 -3
  95. package/dist/mcp/changeset-only.js.map +2 -2
  96. package/dist/mcp/project-root.js +85 -0
  97. package/dist/mcp/project-root.js.map +7 -0
  98. package/dist/mcp/server-instructions.js +27 -0
  99. package/dist/mcp/server-instructions.js.map +7 -0
  100. package/dist/mcp/strict-schema.js +52 -0
  101. package/dist/mcp/strict-schema.js.map +7 -0
  102. package/dist/mcp/tool-registry.js +39 -5
  103. package/dist/mcp/tool-registry.js.map +2 -2
  104. package/dist/mcp/tools/changeset-analysis-tools.js +25 -5
  105. package/dist/mcp/tools/changeset-analysis-tools.js.map +3 -3
  106. package/dist/mcp/tools/changeset-commit.js +224 -0
  107. package/dist/mcp/tools/changeset-commit.js.map +7 -0
  108. package/dist/mcp/tools/changeset-editing.js +133 -57
  109. package/dist/mcp/tools/changeset-editing.js.map +3 -3
  110. package/dist/mcp/tools/changeset-lifecycle.js +1 -1
  111. package/dist/mcp/tools/changeset-lifecycle.js.map +2 -2
  112. package/dist/mcp/tools/changeset-verify.js +12 -6
  113. package/dist/mcp/tools/changeset-verify.js.map +2 -2
  114. package/dist/mcp/tools/codebase-extract.js +836 -0
  115. package/dist/mcp/tools/codebase-extract.js.map +7 -0
  116. package/dist/mcp/tools/codebase-index.js +125 -0
  117. package/dist/mcp/tools/codebase-index.js.map +7 -0
  118. package/dist/mcp/tools/codebase-shared.js +265 -0
  119. package/dist/mcp/tools/codebase-shared.js.map +7 -0
  120. package/dist/mcp/tools/codebase-verify.js +48 -0
  121. package/dist/mcp/tools/codebase-verify.js.map +7 -0
  122. package/dist/mcp/tools/index.js +15 -0
  123. package/dist/mcp/tools/index.js.map +2 -2
  124. package/dist/mcp/tools/model-add.js +189 -73
  125. package/dist/mcp/tools/model-add.js.map +2 -2
  126. package/dist/mcp/tools/model-update.js +16 -12
  127. package/dist/mcp/tools/model-update.js.map +2 -2
  128. package/dist/mcp/tools/model-validate.js +46 -4
  129. package/dist/mcp/tools/model-validate.js.map +2 -2
  130. package/dist/mcp/tools/project-status-collectors.js +381 -0
  131. package/dist/mcp/tools/project-status-collectors.js.map +7 -0
  132. package/dist/mcp/tools/project-status.js +237 -0
  133. package/dist/mcp/tools/project-status.js.map +7 -0
  134. package/dist/mcp/tools/project-tools.js +505 -0
  135. package/dist/mcp/tools/project-tools.js.map +7 -0
  136. package/dist/mcp/tools/relationship-tools.js +89 -20
  137. package/dist/mcp/tools/relationship-tools.js.map +2 -2
  138. package/dist/mcp/tools/review-open.js +210 -0
  139. package/dist/mcp/tools/review-open.js.map +7 -0
  140. package/dist/mcp/tools/shared.js +10 -2
  141. package/dist/mcp/tools/shared.js.map +2 -2
  142. package/dist/reports/model-report-orchestrator.js +31 -2
  143. package/dist/reports/model-report-orchestrator.js.map +2 -2
  144. package/dist/schemas/bundled/analyzers/cbm.json +22 -10
  145. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  146. package/dist/schemas/bundled/api.json +1 -1
  147. package/dist/schemas/bundled/apm.json +1 -1
  148. package/dist/schemas/bundled/application.json +1 -1
  149. package/dist/schemas/bundled/base.json +1 -1
  150. package/dist/schemas/bundled/business.json +1 -1
  151. package/dist/schemas/bundled/data-model.json +1 -1
  152. package/dist/schemas/bundled/data-store.json +1 -1
  153. package/dist/schemas/bundled/manifest.json +3 -3
  154. package/dist/schemas/bundled/motivation.json +1 -1
  155. package/dist/schemas/bundled/navigation.json +1 -1
  156. package/dist/schemas/bundled/product.json +1 -1
  157. package/dist/schemas/bundled/security.json +1 -1
  158. package/dist/schemas/bundled/technology.json +1 -1
  159. package/dist/schemas/bundled/testing.json +145 -1
  160. package/dist/schemas/bundled/ux.json +1 -1
  161. package/dist/server/server.js +508 -237
  162. package/dist/server/server.js.map +3 -3
  163. package/dist/server/viewer-registry.js +264 -0
  164. package/dist/server/viewer-registry.js.map +7 -0
  165. package/dist/telemetry/index.js +1 -1
  166. package/dist/utils/id-generator.js +5 -2
  167. package/dist/utils/id-generator.js.map +2 -2
  168. package/dist/utils/source-reference.js +58 -27
  169. package/dist/utils/source-reference.js.map +2 -2
  170. package/dist/utils/spec-version.js +2 -2
  171. package/dist/utils/spec-version.js.map +1 -1
  172. package/dist/validators/model-warnings.js +28 -0
  173. package/dist/validators/model-warnings.js.map +7 -0
  174. package/dist/viewer/README.md +6 -6
  175. package/dist/viewer/assets/main-CUOQGGRA.css +1 -0
  176. package/dist/viewer/assets/main-DvH3gYrI.js +84 -0
  177. package/dist/viewer/index.html +2 -2
  178. package/dist/viewer/manifest.json +10 -10
  179. package/dist/viewer/version.json +1 -1
  180. package/package.json +1 -1
  181. package/dist/viewer/assets/main-CxzcuU_o.js +0 -84
  182. package/dist/viewer/assets/main-j-7ULYpm.css +0 -1
@@ -23,6 +23,7 @@ class ModelReportOrchestrator {
23
23
  * Invalid layer names in the affected set are skipped with a warning.
24
24
  * Also regenerates the README since it contains model-wide stats that may have changed.
25
25
  * README is generated first so it exists even if a layer report throws.
26
+ * A report whose content is unchanged apart from its timestamp is not rewritten.
26
27
  */
27
28
  async regenerate(affectedLayers) {
28
29
  if (!await this.isInitialized())
@@ -60,6 +61,21 @@ class ModelReportOrchestrator {
60
61
  rel.layer === primaryLayer && rel.targetLayer && isValidLayerName(rel.targetLayer) && affected.add(rel.targetLayer), rel.targetLayer === primaryLayer && isValidLayerName(rel.layer) && affected.add(rel.layer);
61
62
  return affected;
62
63
  }
64
+ /**
65
+ * The layers whose reports a change to these elements can alter: the elements' own layer, and
66
+ * the layer at the other end of each of their relationships (that report lists them).
67
+ * Unlike {@link computeAffectedLayers}, a relationship elsewhere in the layer does not count.
68
+ */
69
+ computeElementAffectedLayers(layerName, elementIds) {
70
+ const affected = /* @__PURE__ */ new Set();
71
+ isValidLayerName(layerName) && affected.add(layerName);
72
+ const ids = new Set(elementIds);
73
+ if (ids.size === 0)
74
+ return affected;
75
+ for (const rel of this.model.relationships.getAll())
76
+ ids.has(rel.source) && rel.targetLayer && isValidLayerName(rel.targetLayer) && affected.add(rel.targetLayer), ids.has(rel.target) && isValidLayerName(rel.layer) && affected.add(rel.layer);
77
+ return affected;
78
+ }
63
79
  /**
64
80
  * Check if all 13 report files exist and are initialized.
65
81
  * Returns false only if files don't exist (ENOENT); propagates other errors like EACCES, EMFILE, EIO.
@@ -101,7 +117,7 @@ class ModelReportOrchestrator {
101
117
  throw new Error(`Failed to generate report content for layer: ${layerName} (programming error or corrupted model) - ${getErrorMessage(error)}`, { cause: error });
102
118
  }
103
119
  const filePath = this.getReportFilePath(layerName);
104
- await fs.writeFile(filePath, markdown, "utf-8");
120
+ await writeIfChanged(filePath, markdown);
105
121
  }
106
122
  /**
107
123
  * Generate and write the model README.
@@ -117,8 +133,21 @@ class ModelReportOrchestrator {
117
133
  throw new Error(`Failed to generate README content (programming error or corrupted model) - ${getErrorMessage(error)}`, { cause: error });
118
134
  }
119
135
  const filePath = path.join(this.rootPath, "documentation-robotics", "README.md");
120
- await fs.writeFile(filePath, markdown, "utf-8");
136
+ await writeIfChanged(filePath, markdown);
137
+ }
138
+ }
139
+ const GENERATED_AT = /^Generated: [^|\n]*\|/m, LAST_UPDATED_ROW = /^\|\s*Last Updated\s*\|[^\n]*$/m;
140
+ function withoutTimestamps(markdown) {
141
+ return markdown.replace(GENERATED_AT, "Generated: |").replace(LAST_UPDATED_ROW, "| Last Updated |");
142
+ }
143
+ async function writeIfChanged(filePath, markdown) {
144
+ let current;
145
+ try {
146
+ current = await fs.readFile(filePath, "utf-8");
147
+ } catch {
148
+ current = void 0;
121
149
  }
150
+ current !== void 0 && withoutTimestamps(current) === withoutTimestamps(markdown) || await fs.writeFile(filePath, markdown, "utf-8");
122
151
  }
123
152
  export {
124
153
  ModelReportOrchestrator
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/reports/model-report-orchestrator.ts"],
4
- "sourcesContent": ["/**\n * Model Report Orchestrator\n *\n * Coordinates report generation across all 13 architecture layers.\n * Determines which reports need regeneration and manages the report directory.\n */\n\nimport type { Model } from '../core/model.js';\nimport { ModelReportDataCollector } from './model-report-data.js';\nimport { ModelLayerReportGenerator } from './model-layer-report-generator.js';\nimport { ModelReadmeGenerator } from './model-readme-generator.js';\nimport { CANONICAL_LAYER_NAMES, type CanonicalLayerName, isValidLayerName } from '../core/layers.js';\nimport { getErrorMessage } from '../utils/errors.js';\nimport { getLayerReportFileName } from './model-report-utils.js';\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nexport class ModelReportOrchestrator {\n private collector: ModelReportDataCollector;\n private generator: ModelLayerReportGenerator;\n private readmeGenerator: ModelReadmeGenerator;\n\n constructor(private model: Model, private rootPath: string) {\n this.collector = new ModelReportDataCollector();\n const modelVersion = model.manifest.version || 'unknown';\n const generatedAt = new Date().toISOString();\n this.generator = new ModelLayerReportGenerator(modelVersion, generatedAt);\n this.readmeGenerator = new ModelReadmeGenerator(generatedAt);\n }\n\n /**\n * Regenerate reports for a specific set of affected layers.\n * On first invocation or when reports don't exist, regenerates all 13 reports.\n * Invalid layer names in the affected set are skipped with a warning.\n * Also regenerates the README since it contains model-wide stats that may have changed.\n * README is generated first so it exists even if a layer report throws.\n */\n async regenerate(affectedLayers: Set<string>): Promise<void> {\n if (!(await this.isInitialized())) {\n return this.regenerateAll();\n }\n\n // Generate README first so it exists even if a layer report throws\n await this.generateReadme();\n\n for (const layerName of affectedLayers) {\n // Type predicate narrows layerName to CanonicalLayerName automatically\n if (!isValidLayerName(layerName)) {\n console.warn(`Skipping invalid layer name: ${layerName}`);\n continue;\n }\n await this.generateLayerReport(layerName);\n }\n }\n\n /**\n * Regenerate all 13 layer reports and the README.\n * Lets mkdir errors propagate so callers can handle them with proper telemetry.\n * README is generated first so it exists even if a layer report throws.\n */\n async regenerateAll(): Promise<void> {\n // Ensure report directory exists\n const reportDir = this.getReportDir();\n await fs.mkdir(reportDir, { recursive: true });\n\n // Generate README first so it exists even if a layer report throws\n await this.generateReadme();\n\n // Generate all 13 layer reports\n for (const layerName of CANONICAL_LAYER_NAMES) {\n await this.generateLayerReport(layerName);\n }\n }\n\n /**\n * Compute the set of layers affected by a change to a primary layer.\n * Returns the primary layer plus any layers that have cross-layer relationships with it.\n * Throws if the primary layer is not a recognized canonical layer name.\n */\n computeAffectedLayers(primaryLayer: CanonicalLayerName): Set<CanonicalLayerName> {\n // Defense-in-depth: validate despite type signature (guards against as casts from callers)\n if (!isValidLayerName(primaryLayer)) {\n throw new Error(\n `Invalid primary layer name: '${primaryLayer}' is not a recognized canonical layer. ` +\n `Valid layers are: ${CANONICAL_LAYER_NAMES.join(', ')}`\n );\n }\n\n const affected = new Set<CanonicalLayerName>([primaryLayer]);\n\n for (const rel of this.model.relationships.getAll()) {\n // Outbound: this layer references another layer\n if (rel.layer === primaryLayer && rel.targetLayer) {\n // Validate layer name before casting (defensive: guards against user-editable YAML with invalid layer names)\n if (isValidLayerName(rel.targetLayer)) {\n affected.add(rel.targetLayer);\n }\n }\n // Inbound: another layer references this layer\n if (rel.targetLayer === primaryLayer) {\n // Validate layer name before casting (defensive: guards against user-editable YAML with invalid layer names)\n if (isValidLayerName(rel.layer)) {\n affected.add(rel.layer);\n }\n }\n }\n\n return affected;\n }\n\n /**\n * Check if all 13 report files exist and are initialized.\n * Returns false only if files don't exist (ENOENT); propagates other errors like EACCES, EMFILE, EIO.\n */\n private async isInitialized(): Promise<boolean> {\n for (const layerName of CANONICAL_LAYER_NAMES) {\n try {\n await fs.access(this.getReportFilePath(layerName));\n } catch (error: unknown) {\n const err = error as NodeJS.ErrnoException;\n // Only ENOENT (file not found) should return false; other errors propagate\n if (err.code === 'ENOENT') {\n return false;\n }\n // Re-throw permission errors, I/O errors, etc.\n throw error;\n }\n }\n return true;\n }\n\n /**\n * Get the reports directory path.\n */\n private getReportDir(): string {\n return path.join(this.rootPath, 'documentation-robotics', 'reports');\n }\n\n /**\n * Get the full file path for a layer report.\n */\n private getReportFilePath(layerName: CanonicalLayerName): string {\n const filename = getLayerReportFileName(layerName);\n return path.join(this.getReportDir(), filename);\n }\n\n /**\n * Generate and write a single layer report.\n * Separates concerns to distinguish programming bugs from I/O errors.\n * Lets generation and write errors propagate so callers can handle them with proper telemetry.\n */\n private async generateLayerReport(layerName: CanonicalLayerName): Promise<void> {\n let data;\n let markdown;\n\n try {\n // Collect layer data (may throw if programming bug or model is corrupted)\n data = this.collector.collectLayerData(this.model, layerName);\n\n // Generate markdown report (may throw if programming bug)\n markdown = this.generator.generate(data);\n } catch (error) {\n throw new Error(\n `Failed to generate report content for layer: ${layerName} (programming error or corrupted model) - ${getErrorMessage(error)}`,\n { cause: error }\n );\n }\n\n // Write to file (may throw for I/O errors like ENOSPC, EACCES, EIO)\n // Let write errors propagate so callers can handle them with proper telemetry\n const filePath = this.getReportFilePath(layerName);\n await fs.writeFile(filePath, markdown, 'utf-8');\n }\n\n /**\n * Generate and write the model README.\n * Separates concerns to distinguish programming bugs from I/O errors.\n * Lets generation and write errors propagate so callers can handle them with proper telemetry.\n * README is written to documentation-robotics/README.md (outside reports/ folder).\n */\n private async generateReadme(): Promise<void> {\n let data;\n let markdown;\n\n try {\n // Collect model-wide data (may throw if programming bug or model is corrupted)\n data = this.collector.collectModelData(this.model);\n\n // Generate markdown README (may throw if programming bug)\n markdown = this.readmeGenerator.generate(data);\n } catch (error) {\n throw new Error(\n `Failed to generate README content (programming error or corrupted model) - ${getErrorMessage(error)}`,\n { cause: error }\n );\n }\n\n // Write to file (may throw for I/O errors like ENOSPC, EACCES, EIO)\n // Let write errors propagate so callers can handle them with proper telemetry\n const filePath = path.join(this.rootPath, 'documentation-robotics', 'README.md');\n await fs.writeFile(filePath, markdown, 'utf-8');\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Model Report Orchestrator\n *\n * Coordinates report generation across all 13 architecture layers.\n * Determines which reports need regeneration and manages the report directory.\n */\n\nimport type { Model } from '../core/model.js';\nimport { ModelReportDataCollector } from './model-report-data.js';\nimport { ModelLayerReportGenerator } from './model-layer-report-generator.js';\nimport { ModelReadmeGenerator } from './model-readme-generator.js';\nimport { CANONICAL_LAYER_NAMES, type CanonicalLayerName, isValidLayerName } from '../core/layers.js';\nimport { getErrorMessage } from '../utils/errors.js';\nimport { getLayerReportFileName } from './model-report-utils.js';\nimport * as fs from 'fs/promises';\nimport * as path from 'path';\n\nexport class ModelReportOrchestrator {\n private collector: ModelReportDataCollector;\n private generator: ModelLayerReportGenerator;\n private readmeGenerator: ModelReadmeGenerator;\n\n constructor(private model: Model, private rootPath: string) {\n this.collector = new ModelReportDataCollector();\n const modelVersion = model.manifest.version || 'unknown';\n const generatedAt = new Date().toISOString();\n this.generator = new ModelLayerReportGenerator(modelVersion, generatedAt);\n this.readmeGenerator = new ModelReadmeGenerator(generatedAt);\n }\n\n /**\n * Regenerate reports for a specific set of affected layers.\n * On first invocation or when reports don't exist, regenerates all 13 reports.\n * Invalid layer names in the affected set are skipped with a warning.\n * Also regenerates the README since it contains model-wide stats that may have changed.\n * README is generated first so it exists even if a layer report throws.\n * A report whose content is unchanged apart from its timestamp is not rewritten.\n */\n async regenerate(affectedLayers: Set<string>): Promise<void> {\n if (!(await this.isInitialized())) {\n return this.regenerateAll();\n }\n\n // Generate README first so it exists even if a layer report throws\n await this.generateReadme();\n\n for (const layerName of affectedLayers) {\n // Type predicate narrows layerName to CanonicalLayerName automatically\n if (!isValidLayerName(layerName)) {\n console.warn(`Skipping invalid layer name: ${layerName}`);\n continue;\n }\n await this.generateLayerReport(layerName);\n }\n }\n\n /**\n * Regenerate all 13 layer reports and the README.\n * Lets mkdir errors propagate so callers can handle them with proper telemetry.\n * README is generated first so it exists even if a layer report throws.\n */\n async regenerateAll(): Promise<void> {\n // Ensure report directory exists\n const reportDir = this.getReportDir();\n await fs.mkdir(reportDir, { recursive: true });\n\n // Generate README first so it exists even if a layer report throws\n await this.generateReadme();\n\n // Generate all 13 layer reports\n for (const layerName of CANONICAL_LAYER_NAMES) {\n await this.generateLayerReport(layerName);\n }\n }\n\n /**\n * Compute the set of layers affected by a change to a primary layer.\n * Returns the primary layer plus any layers that have cross-layer relationships with it.\n * Throws if the primary layer is not a recognized canonical layer name.\n */\n computeAffectedLayers(primaryLayer: CanonicalLayerName): Set<CanonicalLayerName> {\n // Defense-in-depth: validate despite type signature (guards against as casts from callers)\n if (!isValidLayerName(primaryLayer)) {\n throw new Error(\n `Invalid primary layer name: '${primaryLayer}' is not a recognized canonical layer. ` +\n `Valid layers are: ${CANONICAL_LAYER_NAMES.join(', ')}`\n );\n }\n\n const affected = new Set<CanonicalLayerName>([primaryLayer]);\n\n for (const rel of this.model.relationships.getAll()) {\n // Outbound: this layer references another layer\n if (rel.layer === primaryLayer && rel.targetLayer) {\n // Validate layer name before casting (defensive: guards against user-editable YAML with invalid layer names)\n if (isValidLayerName(rel.targetLayer)) {\n affected.add(rel.targetLayer);\n }\n }\n // Inbound: another layer references this layer\n if (rel.targetLayer === primaryLayer) {\n // Validate layer name before casting (defensive: guards against user-editable YAML with invalid layer names)\n if (isValidLayerName(rel.layer)) {\n affected.add(rel.layer);\n }\n }\n }\n\n return affected;\n }\n\n /**\n * The layers whose reports a change to these elements can alter: the elements' own layer, and\n * the layer at the other end of each of their relationships (that report lists them).\n * Unlike {@link computeAffectedLayers}, a relationship elsewhere in the layer does not count.\n */\n computeElementAffectedLayers(layerName: string, elementIds: Iterable<string>): Set<CanonicalLayerName> {\n const affected = new Set<CanonicalLayerName>();\n if (isValidLayerName(layerName)) affected.add(layerName);\n const ids = new Set(elementIds);\n if (ids.size === 0) return affected;\n\n for (const rel of this.model.relationships.getAll()) {\n if (ids.has(rel.source) && rel.targetLayer && isValidLayerName(rel.targetLayer)) {\n affected.add(rel.targetLayer);\n }\n if (ids.has(rel.target) && isValidLayerName(rel.layer)) {\n affected.add(rel.layer);\n }\n }\n return affected;\n }\n\n /**\n * Check if all 13 report files exist and are initialized.\n * Returns false only if files don't exist (ENOENT); propagates other errors like EACCES, EMFILE, EIO.\n */\n private async isInitialized(): Promise<boolean> {\n for (const layerName of CANONICAL_LAYER_NAMES) {\n try {\n await fs.access(this.getReportFilePath(layerName));\n } catch (error: unknown) {\n const err = error as NodeJS.ErrnoException;\n // Only ENOENT (file not found) should return false; other errors propagate\n if (err.code === 'ENOENT') {\n return false;\n }\n // Re-throw permission errors, I/O errors, etc.\n throw error;\n }\n }\n return true;\n }\n\n /**\n * Get the reports directory path.\n */\n private getReportDir(): string {\n return path.join(this.rootPath, 'documentation-robotics', 'reports');\n }\n\n /**\n * Get the full file path for a layer report.\n */\n private getReportFilePath(layerName: CanonicalLayerName): string {\n const filename = getLayerReportFileName(layerName);\n return path.join(this.getReportDir(), filename);\n }\n\n /**\n * Generate and write a single layer report.\n * Separates concerns to distinguish programming bugs from I/O errors.\n * Lets generation and write errors propagate so callers can handle them with proper telemetry.\n */\n private async generateLayerReport(layerName: CanonicalLayerName): Promise<void> {\n let data;\n let markdown;\n\n try {\n // Collect layer data (may throw if programming bug or model is corrupted)\n data = this.collector.collectLayerData(this.model, layerName);\n\n // Generate markdown report (may throw if programming bug)\n markdown = this.generator.generate(data);\n } catch (error) {\n throw new Error(\n `Failed to generate report content for layer: ${layerName} (programming error or corrupted model) - ${getErrorMessage(error)}`,\n { cause: error }\n );\n }\n\n // Write to file (may throw for I/O errors like ENOSPC, EACCES, EIO)\n // Let write errors propagate so callers can handle them with proper telemetry\n const filePath = this.getReportFilePath(layerName);\n await writeIfChanged(filePath, markdown);\n }\n\n /**\n * Generate and write the model README.\n * Separates concerns to distinguish programming bugs from I/O errors.\n * Lets generation and write errors propagate so callers can handle them with proper telemetry.\n * README is written to documentation-robotics/README.md (outside reports/ folder).\n */\n private async generateReadme(): Promise<void> {\n let data;\n let markdown;\n\n try {\n // Collect model-wide data (may throw if programming bug or model is corrupted)\n data = this.collector.collectModelData(this.model);\n\n // Generate markdown README (may throw if programming bug)\n markdown = this.readmeGenerator.generate(data);\n } catch (error) {\n throw new Error(\n `Failed to generate README content (programming error or corrupted model) - ${getErrorMessage(error)}`,\n { cause: error }\n );\n }\n\n // Write to file (may throw for I/O errors like ENOSPC, EACCES, EIO)\n // Let write errors propagate so callers can handle them with proper telemetry\n const filePath = path.join(this.rootPath, 'documentation-robotics', 'README.md');\n await writeIfChanged(filePath, markdown);\n }\n}\n\n/**\n * The footer's generation time changes on every generation, and the README's \"Last Updated\" row\n * (the manifest's modified time) on every save: neither is a change to the report's content.\n */\nconst GENERATED_AT = /^Generated: [^|\\n]*\\|/m;\nconst LAST_UPDATED_ROW = /^\\|\\s*Last Updated\\s*\\|[^\\n]*$/m;\n\nfunction withoutTimestamps(markdown: string): string {\n return markdown.replace(GENERATED_AT, 'Generated: |').replace(LAST_UPDATED_ROW, '| Last Updated |');\n}\n\n/**\n * Write a generated report unless the file already holds the same report apart from its\n * timestamps, so regenerating a report nothing changed leaves no diff. A file that cannot be read\n * is written.\n */\nasync function writeIfChanged(filePath: string, markdown: string): Promise<void> {\n let current: string | undefined;\n try {\n current = await fs.readFile(filePath, 'utf-8');\n } catch {\n current = undefined;\n }\n if (current !== undefined && withoutTimestamps(current) === withoutTimestamps(markdown)) {\n return;\n }\n await fs.writeFile(filePath, markdown, 'utf-8');\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cbm",
3
- "version": "0.11.0",
4
- "source_version": "0.11.0",
3
+ "version": "0.12.0",
4
+ "source_version": "0.12.0",
5
5
  "analyzer": {
6
6
  "name": "cbm",
7
7
  "display_name": "codebase-memory-mcp",
@@ -386,9 +386,14 @@
386
386
  "dr_scope": "intra-layer",
387
387
  "direction": "forward",
388
388
  "dr_pattern": "application.applicationfunction → application.applicationfunction",
389
- "implementation_note": "Listed first: it is the call-graph default edge type for dr analyzer callers and callees. Creates a relationship only between functions that both survive classification.",
389
+ "implementation_note": "Listed first: it is the call-graph default edge type for dr analyzer callers and callees. Creates a relationship only between functions that both survive classification. A call from a test case to the code it exercises (a component, function, process, interface, service, operation, object schema or access pattern) is the test testing it (testing.testcasesketch.tests.*); a call to a member with no element of its own counts as a call to its class, and from a test only when the test's file refers to that class (a USAGE or IMPORTS edge), because the index resolves a call by the member's name.",
390
390
  "conditions": {
391
391
  "predicates": [
392
+ {
393
+ "predicate": "tests",
394
+ "invert": false,
395
+ "from_types": ["testing.testcasesketch"]
396
+ },
392
397
  {
393
398
  "predicate": "depends-on",
394
399
  "invert": false
@@ -660,7 +665,7 @@
660
665
  "cbm_edge": "TESTS",
661
666
  "dr_relationship": "tests",
662
667
  "confidence": "high",
663
- "description": "A test function exercises a symbol: the test case sketch tests that function, interface, service or operation",
668
+ "description": "A test function exercises a symbol: the test case sketch tests that component, function, interface, service, operation, object schema or access pattern",
664
669
  "usage": "verification_and_mapping",
665
670
  "dr_scope": "cross-layer",
666
671
  "direction": "forward",
@@ -838,7 +843,7 @@
838
843
  "dr_scope": "cross-layer",
839
844
  "direction": "caller_to_callee",
840
845
  "confidence_from_property": "confidence",
841
- "implementation_note": "Creates a relationship only from a test case (a symbol Tier 0 classified as testing.testcasesketch) to an operation: on CBM 0.12.1 almost every observed caller is test code (53 of 55 on fastapi, 81 of 86 on dr-cli). A production caller (application code consuming an operation) still creates none: that triple waits on catalog addition 7 (docs/analyzers/CBM_MAPPING_INVENTORY.md), queued in Context Studio.",
846
+ "implementation_note": "Creates a relationship only from a test case (a symbol Tier 0 classified as testing.testcasesketch) to an operation: on CBM 0.12.1 almost every observed caller is test code (53 of 55 on fastapi, 81 of 86 on dr-cli). A production caller (application code consuming an operation) still creates none: that triple waits on catalog addition 7 (docs/analyzers/CBM_MAPPING_INVENTORY.md), queued in Context Studio. Extraction also reads this edge from test code that calls a served route by URL (MockMvc get(\"/owners/{ownerId}\"), RestTemplate, supertest), which CBM has no edge for.",
842
847
  "dr_pattern": "testing.testcasesketch → api.operation",
843
848
  "conditions": {
844
849
  "from_types": ["testing.testcasesketch"],
@@ -883,15 +888,22 @@
883
888
  },
884
889
  "INHERITS": {
885
890
  "cbm_edge": "INHERITS",
886
- "dr_relationship": null,
891
+ "dr_relationship": "specializes",
887
892
  "confidence": "high",
888
- "description": "Class or interface inheritance",
889
- "usage": "traversal_only",
893
+ "description": "Class or interface inheritance: the subclass specializes its parent",
894
+ "usage": "verification_and_mapping",
890
895
  "dr_scope": "intra-layer",
891
896
  "direction": "forward",
892
- "implementation_note": "specializes between components and between data objects; proposed to Context Studio's review queue as a catalog addition (docs/analyzers/CBM_MAPPING_INVENTORY.md, addition 1); map once accepted.",
897
+ "dr_pattern": "data-model.objectschema → data-model.objectschema",
898
+ "implementation_note": "Only between two elements of the same type that the catalog lets specialize (object schemas, data objects, components, functions, ...): an entity class extending a mapped superclass. Inheriting from a class of another type, or from one that is not an element, creates nothing.",
893
899
  "conditions": {
894
- "usage_detail": "intra-layer structural"
900
+ "usage_detail": "intra-layer structural",
901
+ "predicates": [
902
+ {
903
+ "predicate": "specializes",
904
+ "invert": false
905
+ }
906
+ ]
895
907
  }
896
908
  },
897
909
  "EXTENDS": {
@@ -1,9 +1,9 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "analyzers": [
4
4
  {
5
5
  "name": "cbm",
6
- "version": "0.11.0"
6
+ "version": "0.12.0"
7
7
  }
8
8
  ]
9
9
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "api",
5
5
  "number": 7,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "apm",
5
5
  "number": 12,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "application",
5
5
  "number": 5,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "schemas": {
4
4
  "spec-node": {
5
5
  "$schema": "http://json-schema.org/draft-07/schema#",
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "business",
5
5
  "number": 2,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "data-model",
5
5
  "number": 8,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "data-store",
5
5
  "number": 9,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layers": [
4
4
  {
5
5
  "id": "motivation",
@@ -90,11 +90,11 @@
90
90
  "number": 13,
91
91
  "name": "Testing Layer",
92
92
  "nodeTypeCount": 17,
93
- "relationshipCount": 89
93
+ "relationshipCount": 101
94
94
  }
95
95
  ],
96
96
  "totals": {
97
97
  "nodeTypes": 193,
98
- "relationships": 975
98
+ "relationships": 987
99
99
  }
100
100
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "motivation",
5
5
  "number": 1,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "navigation",
5
5
  "number": 11,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "product",
5
5
  "number": 3,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "security",
5
5
  "number": 4,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "technology",
5
5
  "number": 6,
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "testing",
5
5
  "number": 13,
@@ -1543,6 +1543,18 @@
1543
1543
  "required": false,
1544
1544
  "description": "The test case verifies the requirement, so passing it is evidence that the requirement is met."
1545
1545
  },
1546
+ "testing.testcasesketch.tests.data-store.accesspattern": {
1547
+ "id": "testing.testcasesketch.tests.data-store.accesspattern",
1548
+ "source_spec_node_id": "testing.testcasesketch",
1549
+ "source_layer": "testing",
1550
+ "destination_spec_node_id": "data-store.accesspattern",
1551
+ "destination_layer": "data-store",
1552
+ "predicate": "tests",
1553
+ "cardinality": "many-to-many",
1554
+ "strength": "medium",
1555
+ "required": false,
1556
+ "description": "The test case exercises the access pattern."
1557
+ },
1546
1558
  "testing.testcasesketch.tests.ux.actioncomponent": {
1547
1559
  "id": "testing.testcasesketch.tests.ux.actioncomponent",
1548
1560
  "source_spec_node_id": "testing.testcasesketch",
@@ -1555,6 +1567,18 @@
1555
1567
  "required": false,
1556
1568
  "description": "The test case exercises the action component, such as clicking a button."
1557
1569
  },
1570
+ "testing.testcasesketch.tests.application.applicationcomponent": {
1571
+ "id": "testing.testcasesketch.tests.application.applicationcomponent",
1572
+ "source_spec_node_id": "testing.testcasesketch",
1573
+ "source_layer": "testing",
1574
+ "destination_spec_node_id": "application.applicationcomponent",
1575
+ "destination_layer": "application",
1576
+ "predicate": "tests",
1577
+ "cardinality": "many-to-many",
1578
+ "strength": "medium",
1579
+ "required": false,
1580
+ "description": "The test case exercises the application component."
1581
+ },
1558
1582
  "testing.testcasesketch.tests.application.applicationfunction": {
1559
1583
  "id": "testing.testcasesketch.tests.application.applicationfunction",
1560
1584
  "source_spec_node_id": "testing.testcasesketch",
@@ -1579,6 +1603,18 @@
1579
1603
  "required": false,
1580
1604
  "description": "The test case exercises the application interface, such as a contract test."
1581
1605
  },
1606
+ "testing.testcasesketch.tests.application.applicationprocess": {
1607
+ "id": "testing.testcasesketch.tests.application.applicationprocess",
1608
+ "source_spec_node_id": "testing.testcasesketch",
1609
+ "source_layer": "testing",
1610
+ "destination_spec_node_id": "application.applicationprocess",
1611
+ "destination_layer": "application",
1612
+ "predicate": "tests",
1613
+ "cardinality": "many-to-many",
1614
+ "strength": "medium",
1615
+ "required": false,
1616
+ "description": "The test case exercises the application process."
1617
+ },
1582
1618
  "testing.testcasesketch.tests.application.applicationservice": {
1583
1619
  "id": "testing.testcasesketch.tests.application.applicationservice",
1584
1620
  "source_spec_node_id": "testing.testcasesketch",
@@ -1591,6 +1627,18 @@
1591
1627
  "required": false,
1592
1628
  "description": "The test case exercises the application service."
1593
1629
  },
1630
+ "testing.testcasesketch.tests.security.authenticationconfig": {
1631
+ "id": "testing.testcasesketch.tests.security.authenticationconfig",
1632
+ "source_spec_node_id": "testing.testcasesketch",
1633
+ "source_layer": "testing",
1634
+ "destination_spec_node_id": "security.authenticationconfig",
1635
+ "destination_layer": "security",
1636
+ "predicate": "tests",
1637
+ "cardinality": "many-to-many",
1638
+ "strength": "medium",
1639
+ "required": false,
1640
+ "description": "The test case exercises the authentication configuration."
1641
+ },
1594
1642
  "testing.testcasesketch.tests.business.businessprocess": {
1595
1643
  "id": "testing.testcasesketch.tests.business.businessprocess",
1596
1644
  "source_spec_node_id": "testing.testcasesketch",
@@ -1687,6 +1735,30 @@
1687
1735
  "required": false,
1688
1736
  "description": "The test case checks that the guard allows or blocks navigation."
1689
1737
  },
1738
+ "testing.testcasesketch.tests.navigation.navigationtransition": {
1739
+ "id": "testing.testcasesketch.tests.navigation.navigationtransition",
1740
+ "source_spec_node_id": "testing.testcasesketch",
1741
+ "source_layer": "testing",
1742
+ "destination_spec_node_id": "navigation.navigationtransition",
1743
+ "destination_layer": "navigation",
1744
+ "predicate": "tests",
1745
+ "cardinality": "many-to-many",
1746
+ "strength": "medium",
1747
+ "required": false,
1748
+ "description": "The test case exercises the navigation transition."
1749
+ },
1750
+ "testing.testcasesketch.tests.data-model.objectschema": {
1751
+ "id": "testing.testcasesketch.tests.data-model.objectschema",
1752
+ "source_spec_node_id": "testing.testcasesketch",
1753
+ "source_layer": "testing",
1754
+ "destination_spec_node_id": "data-model.objectschema",
1755
+ "destination_layer": "data-model",
1756
+ "predicate": "tests",
1757
+ "cardinality": "many-to-many",
1758
+ "strength": "medium",
1759
+ "required": false,
1760
+ "description": "The test case exercises the object schema."
1761
+ },
1690
1762
  "testing.testcasesketch.tests.api.operation": {
1691
1763
  "id": "testing.testcasesketch.tests.api.operation",
1692
1764
  "source_spec_node_id": "testing.testcasesketch",
@@ -1735,6 +1807,18 @@
1735
1807
  "required": false,
1736
1808
  "description": "The test case checks values against the schema definition."
1737
1809
  },
1810
+ "testing.testcasesketch.tests.security.secureresource": {
1811
+ "id": "testing.testcasesketch.tests.security.secureresource",
1812
+ "source_spec_node_id": "testing.testcasesketch",
1813
+ "source_layer": "testing",
1814
+ "destination_spec_node_id": "security.secureresource",
1815
+ "destination_layer": "security",
1816
+ "predicate": "tests",
1817
+ "cardinality": "many-to-many",
1818
+ "strength": "medium",
1819
+ "required": false,
1820
+ "description": "The test case exercises access control on the secure resource."
1821
+ },
1738
1822
  "testing.testcasesketch.tests.security.securitypolicy": {
1739
1823
  "id": "testing.testcasesketch.tests.security.securitypolicy",
1740
1824
  "source_spec_node_id": "testing.testcasesketch",
@@ -1747,6 +1831,42 @@
1747
1831
  "required": false,
1748
1832
  "description": "The test case checks that the security policy is enforced."
1749
1833
  },
1834
+ "testing.testcasesketch.tests.apm.span": {
1835
+ "id": "testing.testcasesketch.tests.apm.span",
1836
+ "source_spec_node_id": "testing.testcasesketch",
1837
+ "source_layer": "testing",
1838
+ "destination_spec_node_id": "apm.span",
1839
+ "destination_layer": "apm",
1840
+ "predicate": "tests",
1841
+ "cardinality": "many-to-many",
1842
+ "strength": "medium",
1843
+ "required": false,
1844
+ "description": "The test case exercises the span."
1845
+ },
1846
+ "testing.testcasesketch.tests.data-store.storedlogic": {
1847
+ "id": "testing.testcasesketch.tests.data-store.storedlogic",
1848
+ "source_spec_node_id": "testing.testcasesketch",
1849
+ "source_layer": "testing",
1850
+ "destination_spec_node_id": "data-store.storedlogic",
1851
+ "destination_layer": "data-store",
1852
+ "predicate": "tests",
1853
+ "cardinality": "many-to-many",
1854
+ "strength": "medium",
1855
+ "required": false,
1856
+ "description": "The test case exercises the stored logic."
1857
+ },
1858
+ "testing.testcasesketch.tests.ux.subview": {
1859
+ "id": "testing.testcasesketch.tests.ux.subview",
1860
+ "source_spec_node_id": "testing.testcasesketch",
1861
+ "source_layer": "testing",
1862
+ "destination_spec_node_id": "ux.subview",
1863
+ "destination_layer": "ux",
1864
+ "predicate": "tests",
1865
+ "cardinality": "many-to-many",
1866
+ "strength": "medium",
1867
+ "required": false,
1868
+ "description": "The test case exercises the subview."
1869
+ },
1750
1870
  "testing.testcasesketch.tests.security.threat": {
1751
1871
  "id": "testing.testcasesketch.tests.security.threat",
1752
1872
  "source_spec_node_id": "testing.testcasesketch",
@@ -1759,6 +1879,18 @@
1759
1879
  "required": false,
1760
1880
  "description": "The test case attempts the threat, such as a penetration test."
1761
1881
  },
1882
+ "testing.testcasesketch.tests.product.userworkflow": {
1883
+ "id": "testing.testcasesketch.tests.product.userworkflow",
1884
+ "source_spec_node_id": "testing.testcasesketch",
1885
+ "source_layer": "testing",
1886
+ "destination_spec_node_id": "product.userworkflow",
1887
+ "destination_layer": "product",
1888
+ "predicate": "tests",
1889
+ "cardinality": "many-to-many",
1890
+ "strength": "medium",
1891
+ "required": false,
1892
+ "description": "The test case exercises the user workflow."
1893
+ },
1762
1894
  "testing.testcasesketch.tests.data-store.validationrule": {
1763
1895
  "id": "testing.testcasesketch.tests.data-store.validationrule",
1764
1896
  "source_spec_node_id": "testing.testcasesketch",
@@ -2071,6 +2203,18 @@
2071
2203
  "required": false,
2072
2204
  "description": "The target puts the feature in test scope."
2073
2205
  },
2206
+ "testing.testcoveragetarget.covers.ux.librarycomponent": {
2207
+ "id": "testing.testcoveragetarget.covers.ux.librarycomponent",
2208
+ "source_spec_node_id": "testing.testcoveragetarget",
2209
+ "source_layer": "testing",
2210
+ "destination_spec_node_id": "ux.librarycomponent",
2211
+ "destination_layer": "ux",
2212
+ "predicate": "covers",
2213
+ "cardinality": "many-to-many",
2214
+ "strength": "medium",
2215
+ "required": false,
2216
+ "description": "The target puts the library component in test scope."
2217
+ },
2074
2218
  "testing.testcoveragetarget.covers.data-model.objectschema": {
2075
2219
  "id": "testing.testcoveragetarget.covers.data-model.objectschema",
2076
2220
  "source_spec_node_id": "testing.testcoveragetarget",
@@ -1,5 +1,5 @@
1
1
  {
2
- "specVersion": "0.11.0",
2
+ "specVersion": "0.12.0",
3
3
  "layer": {
4
4
  "id": "ux",
5
5
  "number": 10,