@transitrix/diagrams 1.0.0 → 1.0.1

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 (267) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +47 -47
  3. package/dist/__tests__/edge-path.test.js +8 -0
  4. package/dist/activity-card/__tests__/example.test.js +20 -1
  5. package/dist/activity-card/__tests__/layout.test.js +102 -11
  6. package/dist/activity-card/__tests__/resolver.test.js +139 -6
  7. package/dist/activity-card/index.d.ts +1 -1
  8. package/dist/activity-card/layout.d.ts +1 -1
  9. package/dist/activity-card/layout.js +220 -103
  10. package/dist/activity-card/resolver.js +107 -26
  11. package/dist/activity-card/types.d.ts +150 -27
  12. package/dist/activity-card/validate.js +3 -0
  13. package/dist/actor/__tests__/validate.test.js +63 -31
  14. package/dist/actor/index.d.ts +1 -1
  15. package/dist/actor/index.js +1 -1
  16. package/dist/actor/types.d.ts +7 -3
  17. package/dist/actor/types.js +17 -3
  18. package/dist/actor/validate.js +55 -26
  19. package/dist/assertion/types.d.ts +3 -1
  20. package/dist/assertion/types.js +1 -1
  21. package/dist/change/__tests__/validate.test.js +40 -34
  22. package/dist/change/index.d.ts +1 -0
  23. package/dist/change/types.d.ts +1 -1
  24. package/dist/change/types.js +2 -2
  25. package/dist/change/validate.d.ts +6 -1
  26. package/dist/change/validate.js +68 -41
  27. package/dist/compliance/__tests__/classify.test.js +34 -0
  28. package/dist/compliance/__tests__/coverage-metric.test.js +146 -46
  29. package/dist/compliance/__tests__/html.test.js +2 -2
  30. package/dist/compliance/__tests__/impact.test.js +311 -6
  31. package/dist/compliance/classify.d.ts +6 -0
  32. package/dist/compliance/classify.js +9 -1
  33. package/dist/compliance/coverage-metric.d.ts +31 -11
  34. package/dist/compliance/coverage-metric.js +149 -39
  35. package/dist/compliance/html.js +77 -74
  36. package/dist/compliance/impact.d.ts +77 -16
  37. package/dist/compliance/impact.js +193 -25
  38. package/dist/compliance/index.d.ts +2 -2
  39. package/dist/compliance/index.js +1 -1
  40. package/dist/compliance/markdown.js +1 -0
  41. package/dist/compliance/types.d.ts +25 -6
  42. package/dist/edge-path.d.ts +8 -4
  43. package/dist/edge-path.js +11 -6
  44. package/dist/factor/__tests__/validate.test.js +74 -45
  45. package/dist/factor/index.d.ts +3 -2
  46. package/dist/factor/index.js +1 -1
  47. package/dist/factor/types.d.ts +4 -7
  48. package/dist/factor/types.js +10 -5
  49. package/dist/factor/validate.d.ts +6 -1
  50. package/dist/factor/validate.js +68 -50
  51. package/dist/fgca/__tests__/resolver.test.js +185 -0
  52. package/dist/fgca/index.d.ts +2 -0
  53. package/dist/fgca/index.js +1 -0
  54. package/dist/fgca/resolver.d.ts +49 -0
  55. package/dist/fgca/resolver.js +124 -0
  56. package/dist/goals/index.d.ts +1 -1
  57. package/dist/index.d.ts +5 -0
  58. package/dist/index.js +5 -0
  59. package/dist/process-blueprint/__tests__/layout.test.js +37 -0
  60. package/dist/process-blueprint/layout.js +5 -1
  61. package/dist/process-blueprint/types.d.ts +22 -1
  62. package/dist/stakeholder/__tests__/validate.test.js +49 -47
  63. package/dist/stakeholder/index.d.ts +1 -0
  64. package/dist/stakeholder/types.d.ts +3 -4
  65. package/dist/stakeholder/types.js +2 -3
  66. package/dist/stakeholder/validate.d.ts +6 -1
  67. package/dist/stakeholder/validate.js +71 -50
  68. package/dist/target-state/__tests__/validate.test.js +43 -27
  69. package/dist/target-state/index.d.ts +2 -0
  70. package/dist/target-state/index.js +1 -0
  71. package/dist/target-state/types.d.ts +6 -1
  72. package/dist/target-state/types.js +8 -7
  73. package/dist/target-state/validate.d.ts +6 -1
  74. package/dist/target-state/validate.js +69 -45
  75. package/dist/theme/themes.js +69 -69
  76. package/dist/webview/__tests__/entry.test.js +11 -11
  77. package/dist/webview/render-activities.d.ts +3 -1
  78. package/dist/webview/render-activities.js +2 -2
  79. package/dist/webview/render-activity-card.js +104 -77
  80. package/dist/webview/render-fgca.d.ts +4 -0
  81. package/dist/webview/render-fgca.js +2 -5
  82. package/dist/webview/render-goals.d.ts +1 -0
  83. package/dist/webview/render-goals.js +15 -15
  84. package/dist/webview/render-process-blueprint.js +10 -10
  85. package/package.json +40 -40
  86. package/src/__tests__/edge-path.test.ts +58 -46
  87. package/src/__tests__/scope.test.ts +27 -27
  88. package/src/__tests__/yaml-normalize.test.ts +158 -158
  89. package/src/activities/__tests__/cpm.test.ts +164 -164
  90. package/src/activities/__tests__/gantt.test.ts +245 -245
  91. package/src/activities/__tests__/layout.test.ts +112 -112
  92. package/src/activities/__tests__/validate.test.ts +409 -409
  93. package/src/activities/cpm.ts +95 -95
  94. package/src/activities/gantt.ts +276 -276
  95. package/src/activities/index.ts +28 -28
  96. package/src/activities/layout.ts +112 -112
  97. package/src/activities/types.ts +148 -148
  98. package/src/activities/validate.ts +357 -357
  99. package/src/activity-card/__tests__/example.test.ts +85 -66
  100. package/src/activity-card/__tests__/layout.test.ts +169 -63
  101. package/src/activity-card/__tests__/resolver.test.ts +307 -161
  102. package/src/activity-card/__tests__/validate.test.ts +131 -131
  103. package/src/activity-card/index.ts +38 -32
  104. package/src/activity-card/layout.ts +324 -194
  105. package/src/activity-card/resolver.ts +415 -337
  106. package/src/activity-card/types.ts +376 -237
  107. package/src/activity-card/validate.ts +162 -158
  108. package/src/actor/__tests__/validate.test.ts +70 -31
  109. package/src/actor/index.ts +1 -1
  110. package/src/actor/types.ts +21 -7
  111. package/src/actor/validate.ts +59 -27
  112. package/src/applications/__tests__/validate.test.ts +256 -256
  113. package/src/applications/index.ts +11 -11
  114. package/src/applications/types.ts +43 -43
  115. package/src/applications/validate.ts +131 -131
  116. package/src/assertion/__tests__/example.test.ts +37 -37
  117. package/src/assertion/__tests__/validate.test.ts +142 -142
  118. package/src/assertion/index.ts +12 -12
  119. package/src/assertion/types.ts +60 -58
  120. package/src/assertion/validate.ts +145 -145
  121. package/src/blocks/__tests__/layout.test.ts +169 -169
  122. package/src/blocks/__tests__/validate.test.ts +260 -260
  123. package/src/blocks/index.ts +17 -17
  124. package/src/blocks/layout.ts +186 -186
  125. package/src/blocks/types.ts +62 -62
  126. package/src/blocks/validate.ts +206 -206
  127. package/src/capability-map/__tests__/validate.test.ts +238 -238
  128. package/src/capability-map/index.ts +8 -8
  129. package/src/capability-map/types.ts +29 -29
  130. package/src/capability-map/validate.ts +127 -127
  131. package/src/change/__tests__/validate.test.ts +43 -34
  132. package/src/change/index.ts +1 -0
  133. package/src/change/types.ts +5 -3
  134. package/src/change/validate.ts +75 -43
  135. package/src/compliance/__tests__/classify.test.ts +64 -27
  136. package/src/compliance/__tests__/confidence.test.ts +98 -98
  137. package/src/compliance/__tests__/coverage-metric.test.ts +300 -188
  138. package/src/compliance/__tests__/demo-scenario.test.ts +273 -273
  139. package/src/compliance/__tests__/example.test.ts +57 -57
  140. package/src/compliance/__tests__/gap-report.test.ts +128 -128
  141. package/src/compliance/__tests__/html.test.ts +141 -141
  142. package/src/compliance/__tests__/impact.test.ts +840 -481
  143. package/src/compliance/__tests__/markdown.test.ts +94 -94
  144. package/src/compliance/__tests__/views.test.ts +64 -64
  145. package/src/compliance/classify.ts +100 -84
  146. package/src/compliance/confidence.ts +69 -69
  147. package/src/compliance/coverage-metric.ts +404 -263
  148. package/src/compliance/gap-report.ts +94 -94
  149. package/src/compliance/html.ts +298 -295
  150. package/src/compliance/impact.ts +861 -677
  151. package/src/compliance/index.ts +57 -56
  152. package/src/compliance/markdown.ts +136 -135
  153. package/src/compliance/reverse-index.ts +33 -33
  154. package/src/compliance/types.ts +136 -120
  155. package/src/compliance/views.ts +46 -46
  156. package/src/compliance-matrix/__tests__/build.test.ts +200 -200
  157. package/src/compliance-matrix/__tests__/example.test.ts +57 -57
  158. package/src/compliance-matrix/index.ts +11 -11
  159. package/src/compliance-matrix/types.ts +102 -102
  160. package/src/edge-path.ts +50 -44
  161. package/src/factor/__tests__/validate.test.ts +80 -45
  162. package/src/factor/index.ts +3 -7
  163. package/src/factor/types.ts +14 -18
  164. package/src/factor/validate.ts +77 -51
  165. package/src/fgca/__tests__/chain-table.test.ts +172 -172
  166. package/src/fgca/__tests__/layout.test.ts +179 -179
  167. package/src/fgca/__tests__/parse-canonical.test.ts +248 -248
  168. package/src/fgca/__tests__/preview-layout.test.ts +124 -124
  169. package/src/fgca/__tests__/resolver.test.ts +209 -0
  170. package/src/fgca/__tests__/validate.test.ts +142 -142
  171. package/src/fgca/chain-table.ts +225 -225
  172. package/src/fgca/index.ts +47 -45
  173. package/src/fgca/layout.ts +204 -204
  174. package/src/fgca/nodeTypes.ts +11 -11
  175. package/src/fgca/nodes/FGCAActivityNode.tsx +65 -65
  176. package/src/fgca/nodes/FGCAChangeNode.tsx +64 -64
  177. package/src/fgca/nodes/FGCAFactorNode.tsx +64 -64
  178. package/src/fgca/nodes/FGCAGoalNode.tsx +72 -72
  179. package/src/fgca/parse-canonical.ts +400 -400
  180. package/src/fgca/preview-layout.ts +217 -217
  181. package/src/fgca/resolver.ts +157 -0
  182. package/src/fgca/types.ts +50 -50
  183. package/src/fgca/validate.ts +126 -126
  184. package/src/geometry.ts +9 -9
  185. package/src/goals/__tests__/layout.test.ts +175 -175
  186. package/src/goals/__tests__/mutations.test.ts +111 -111
  187. package/src/goals/__tests__/parse-canonical.test.ts +145 -145
  188. package/src/goals/__tests__/validate.test.ts +202 -202
  189. package/src/goals/index.ts +20 -21
  190. package/src/goals/layout.ts +172 -172
  191. package/src/goals/mutations.ts +123 -123
  192. package/src/goals/parse-canonical.ts +286 -286
  193. package/src/goals/types.ts +85 -85
  194. package/src/goals/validate.ts +136 -136
  195. package/src/index.ts +28 -23
  196. package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -320
  197. package/src/process-blueprint/__tests__/layout.test.ts +240 -195
  198. package/src/process-blueprint/__tests__/validate.test.ts +310 -310
  199. package/src/process-blueprint/index.ts +31 -31
  200. package/src/process-blueprint/layout.ts +535 -531
  201. package/src/process-blueprint/types.ts +263 -242
  202. package/src/process-blueprint/validate.ts +181 -181
  203. package/src/process-map/__tests__/validate.test.ts +223 -223
  204. package/src/process-map/index.ts +10 -10
  205. package/src/process-map/types.ts +36 -36
  206. package/src/process-map/validate.ts +135 -135
  207. package/src/products/__tests__/validate.test.ts +239 -239
  208. package/src/products/index.ts +9 -9
  209. package/src/products/types.ts +31 -31
  210. package/src/products/validate.ts +112 -112
  211. package/src/repo-validate/__tests__/validate-repo.test.ts +149 -149
  212. package/src/repo-validate/index.ts +2 -2
  213. package/src/repo-validate/types.ts +50 -50
  214. package/src/repo-validate/validate-repo.ts +196 -196
  215. package/src/requirement/__tests__/example.test.ts +24 -24
  216. package/src/requirement/__tests__/validate.test.ts +96 -96
  217. package/src/requirement/index.ts +4 -4
  218. package/src/requirement/types.ts +35 -35
  219. package/src/requirement/validate.ts +92 -92
  220. package/src/scenarios/__tests__/validate.test.ts +209 -209
  221. package/src/scenarios/index.ts +15 -15
  222. package/src/scenarios/types.ts +37 -37
  223. package/src/scenarios/validate.ts +122 -122
  224. package/src/schema-version.ts +16 -16
  225. package/src/scope.ts +46 -46
  226. package/src/stakeholder/__tests__/validate.test.ts +51 -47
  227. package/src/stakeholder/index.ts +1 -0
  228. package/src/stakeholder/types.ts +7 -7
  229. package/src/stakeholder/validate.ts +79 -52
  230. package/src/target-state/__tests__/validate.test.ts +48 -27
  231. package/src/target-state/index.ts +2 -0
  232. package/src/target-state/types.ts +11 -7
  233. package/src/target-state/validate.ts +76 -46
  234. package/src/theme/index.ts +4 -4
  235. package/src/theme/themes.ts +237 -237
  236. package/src/theme/tokens.ts +133 -133
  237. package/src/typed-id.ts +56 -56
  238. package/src/validation-types.ts +36 -36
  239. package/src/webview/__tests__/entry-notations.test.ts +77 -77
  240. package/src/webview/__tests__/entry.test.ts +64 -64
  241. package/src/webview/__tests__/render-goals.test.ts +72 -72
  242. package/src/webview/entry.ts +292 -292
  243. package/src/webview/render-activities.ts +5 -3
  244. package/src/webview/render-activity-card.ts +266 -234
  245. package/src/webview/render-fgca.ts +6 -5
  246. package/src/webview/render-goals.ts +111 -110
  247. package/src/webview/render-process-blueprint.ts +207 -207
  248. package/src/webview/styles.css +322 -323
  249. package/src/yaml-normalize.ts +56 -56
  250. package/dist/element-envelope.d.ts +0 -15
  251. package/dist/element-envelope.js +0 -55
  252. package/dist/issues/__tests__/layout.test.js +0 -62
  253. package/dist/issues/__tests__/validate.test.d.ts +0 -1
  254. package/dist/issues/__tests__/validate.test.js +0 -82
  255. package/dist/issues/index.d.ts +0 -4
  256. package/dist/issues/index.js +0 -2
  257. package/dist/issues/layout.d.ts +0 -10
  258. package/dist/issues/layout.js +0 -82
  259. package/dist/issues/types.d.ts +0 -61
  260. package/dist/issues/types.js +0 -6
  261. package/dist/issues/validate.d.ts +0 -3
  262. package/dist/issues/validate.js +0 -143
  263. package/dist/webview/transitrix-render.css +0 -323
  264. package/dist/webview/transitrix-render.js +0 -6452
  265. package/dist/webview/transitrix-render.js.map +0 -7
  266. package/src/element-envelope.ts +0 -76
  267. /package/dist/{issues/__tests__/layout.test.d.ts → fgca/__tests__/resolver.test.d.ts} +0 -0
@@ -1,56 +1,57 @@
1
- export { buildComplianceIndex } from './reverse-index.js';
2
- export { buildLawTree, buildProductView } from './views.js';
3
- export { buildGapReport } from './gap-report.js';
4
- export type { GapReport, GapReportOptions } from './gap-report.js';
5
- export { emptyCanon, ingestComplianceDoc } from './classify.js';
6
- export type { ComplianceCanon, ComplianceProduct, ComplianceCodexDoc } from './classify.js';
7
- export { renderComplianceMarkdown } from './markdown.js';
8
- export type { ReportScope, MarkdownOptions } from './markdown.js';
9
- export { renderComplianceHtml, renderImpactMatrixHtml } from './html.js';
10
- export type { HtmlOptions, ImpactMatrixHtmlOptions } from './html.js';
11
- export { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, computeDeadlineStatus } from './impact.js';
12
- export type {
13
- ImpactCell,
14
- ImpactColumn,
15
- ImpactEmptyCellLabels,
16
- ImpactGrouping,
17
- ImpactMatrix,
18
- ImpactObligationFilter,
19
- ImpactStatusDisplay,
20
- ImpactSubjects,
21
- ImpactViewConfig,
22
- ParseImpactViewConfigResult,
23
- } from './impact.js';
24
- export {
25
- assertionToScoringElement,
26
- complianceConfidenceHeader,
27
- requirementToScoringElement,
28
- scoreComplianceView,
29
- } from './confidence.js';
30
- export {
31
- parseCoverageMetricConfig,
32
- buildCoverageMatrix,
33
- } from './coverage-metric.js';
34
- export type {
35
- CoverageMetricConfig,
36
- CoverageMetricScope,
37
- CoverageMetricSubjects,
38
- CoverageMetricThresholds,
39
- CoverageMatrix,
40
- CoverageRow,
41
- ParseCoverageMetricResult,
42
- RagStatus,
43
- } from './coverage-metric.js';
44
- export type {
45
- ComplianceIndex,
46
- ComplianceIndexInput,
47
- DeadlineStatus,
48
- IndexRequirement,
49
- IndexAssertion,
50
- LawTree,
51
- LawTreeRequirement,
52
- ProductView,
53
- ProductRequirementStatus,
54
- ObjectDetailDef,
55
- ObjectDetailInput,
56
- } from './types.js';
1
+ export { buildComplianceIndex } from './reverse-index.js';
2
+ export { buildLawTree, buildProductView } from './views.js';
3
+ export { buildGapReport } from './gap-report.js';
4
+ export type { GapReport, GapReportOptions } from './gap-report.js';
5
+ export { emptyCanon, ingestComplianceDoc } from './classify.js';
6
+ export type { ComplianceCanon, ComplianceProduct, ComplianceCodexDoc } from './classify.js';
7
+ export { renderComplianceMarkdown } from './markdown.js';
8
+ export type { ReportScope, MarkdownOptions } from './markdown.js';
9
+ export { renderComplianceHtml, renderImpactMatrixHtml } from './html.js';
10
+ export type { HtmlOptions, ImpactMatrixHtmlOptions } from './html.js';
11
+ export { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, extractProcessFlowTasks, mergeStageTaskDetails, computeDeadlineStatus } from './impact.js';
12
+ export type {
13
+ ImpactCell,
14
+ ImpactColumn,
15
+ ImpactEmptyCellLabels,
16
+ ImpactGrouping,
17
+ ImpactMatrix,
18
+ ImpactObligationFilter,
19
+ ImpactStatusDisplay,
20
+ ImpactSubjects,
21
+ ImpactViewConfig,
22
+ ParseImpactViewConfigResult,
23
+ } from './impact.js';
24
+ export {
25
+ assertionToScoringElement,
26
+ complianceConfidenceHeader,
27
+ requirementToScoringElement,
28
+ scoreComplianceView,
29
+ } from './confidence.js';
30
+ export {
31
+ parseCoverageMetricConfig,
32
+ buildCoverageMatrix,
33
+ } from './coverage-metric.js';
34
+ export type {
35
+ CoverageMetricConfig,
36
+ CoverageMetricRegimes,
37
+ CoverageMetricRegimesFilter,
38
+ CoverageMetricSubjects,
39
+ CoverageMetricThresholds,
40
+ CoverageMatrix,
41
+ CoverageRow,
42
+ ParseCoverageMetricResult,
43
+ RagStatus,
44
+ } from './coverage-metric.js';
45
+ export type {
46
+ ComplianceIndex,
47
+ ComplianceIndexInput,
48
+ DeadlineStatus,
49
+ IndexRequirement,
50
+ IndexAssertion,
51
+ LawTree,
52
+ LawTreeRequirement,
53
+ ProductView,
54
+ ProductRequirementStatus,
55
+ ObjectDetailDef,
56
+ ObjectDetailInput,
57
+ } from './types.js';
@@ -1,135 +1,136 @@
1
- // Markdown renderers for the compliance views (vkgeorgia/strategy#84 Phase 5).
2
- // Pure string builders consumed by the `transitrix export-compliance` CLI.
3
- // PDF rendering lives in `html.ts` (HTML doc fed to WeasyPrint by the CLI).
4
-
5
- import { buildComplianceMatrix } from '../compliance-matrix/index.js';
6
- import { buildComplianceIndex } from './reverse-index.js';
7
- import { buildLawTree, buildProductView } from './views.js';
8
- import { buildGapReport } from './gap-report.js';
9
- import type { ComplianceCanon } from './classify.js';
10
- import type { AssertionStatus } from '../assertion/types.js';
11
-
12
- const STATUS_LABELS: Record<AssertionStatus, string> = {
13
- compliant: 'Compliant',
14
- partial: 'Partial',
15
- non_compliant: 'Non-compliant',
16
- under_review: 'Under review',
17
- n_a: 'N/A',
18
- };
19
-
20
- /** Escapes a value for a Markdown table cell (pipes + newlines). */
21
- function cell(s: string): string {
22
- return s.replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
23
- }
24
-
25
- export type ReportScope =
26
- | { mode: 'matrix' }
27
- | { mode: 'law'; id: string }
28
- | { mode: 'product'; id: string }
29
- | { mode: 'gap' };
30
-
31
- export interface MarkdownOptions {
32
- /** Today as ISO YYYY-MM-DD — enables the gap dashboard's stale-assertion list. */
33
- today?: string;
34
- }
35
-
36
- /** Renders the requested compliance view from a scanned canon to Markdown. */
37
- export function renderComplianceMarkdown(canon: ComplianceCanon, scope: ReportScope, options: MarkdownOptions = {}): string {
38
- switch (scope.mode) {
39
- case 'matrix': return matrixMarkdown(canon);
40
- case 'law': return lawMarkdown(canon, scope.id);
41
- case 'product': return productMarkdown(canon, scope.id);
42
- case 'gap': return gapMarkdown(canon, options.today);
43
- }
44
- }
45
-
46
- function matrixMarkdown(canon: ComplianceCanon): string {
47
- const m = buildComplianceMatrix({ products: canon.products, requirements: canon.requirements, assertions: canon.assertions });
48
- const out: string[] = ['# Compliance Matrix', ''];
49
- out.push(`_${m.summary.products} products × ${m.summary.requirements} requirements · ${m.summary.gaps} gaps · ${m.summary.assertions} assertions_`, '');
50
- if (m.products.length === 0 || m.requirements.length === 0) {
51
- out.push('_No products or requirements found in the scanned canon._', '');
52
- return out.join('\n');
53
- }
54
- out.push(`| Product \\ Requirement | ${m.requirements.map(r => cell(r.name)).join(' | ')} |`);
55
- out.push(`|---|${m.requirements.map(() => '---').join('|')}|`);
56
- m.products.forEach((p, ri) => {
57
- const cells = m.requirements.map((_r, ci) => {
58
- const c = m.cells[ri][ci];
59
- return c.status ? STATUS_LABELS[c.status] : '—';
60
- });
61
- const name = cell(p.name) + (p.unresolved ? ' ⚠' : '');
62
- out.push(`| ${name} | ${cells.join(' | ')} |`);
63
- });
64
- out.push('');
65
- return out.join('\n');
66
- }
67
-
68
- function lawMarkdown(canon: ComplianceCanon, lawId: string): string {
69
- const index = buildComplianceIndex({ requirements: canon.requirements, assertions: canon.assertions });
70
- const tree = buildLawTree(lawId, index);
71
- const law = canon.codex.find(c => c.id === lawId);
72
- const out: string[] = [`# Compliance — ${law ? law.name : lawId}`, ''];
73
- out.push(`\`${lawId}\` · ${tree.requirements.length} requirement(s)`, '');
74
- if (tree.requirements.length === 0) {
75
- out.push(`_No requirements derive from \`${lawId}\`._`, '');
76
- return out.join('\n');
77
- }
78
- for (const node of tree.requirements) {
79
- const sev = node.requirement.severity ? ` — severity: ${node.requirement.severity}` : '';
80
- out.push(`## ${node.requirement.name} (\`${node.requirement.id}\`)${sev}`, '');
81
- if (node.assertions.length === 0) {
82
- out.push('_No assertion — compliance gap._', '');
83
- continue;
84
- }
85
- for (const a of node.assertions) {
86
- const meta = [a.assessed_at ? `assessed ${a.assessed_at}` : null, a.next_review_at ? `review by ${a.next_review_at}` : null].filter(Boolean).join(', ');
87
- out.push(`- **${STATUS_LABELS[a.status]}** — \`${a.id}\` (subject \`${a.subject}\`${meta ? `; ${meta}` : ''})`);
88
- }
89
- out.push('');
90
- }
91
- return out.join('\n');
92
- }
93
-
94
- function productMarkdown(canon: ComplianceCanon, productId: string): string {
95
- const index = buildComplianceIndex({ requirements: canon.requirements, assertions: canon.assertions });
96
- const view = buildProductView(productId, index);
97
- const product = canon.products.find(p => p.id === productId);
98
- const out: string[] = [`# Compliance — ${product ? product.name : productId}`, ''];
99
- out.push(`\`${productId}\` · ${view.requirements.length} requirement(s) asserted`, '');
100
- if (view.requirements.length === 0) {
101
- out.push('_No assertion names this product as its subject._', '');
102
- return out.join('\n');
103
- }
104
- out.push('| Requirement | Status | Assertion | Next review |', '|---|---|---|---|');
105
- for (const { requirement, assertion } of view.requirements) {
106
- out.push(`| ${cell(requirement.name)} (\`${requirement.id}\`) | ${STATUS_LABELS[assertion.status]} | \`${assertion.id}\` | ${assertion.next_review_at ?? '—'} |`);
107
- }
108
- out.push('');
109
- return out.join('\n');
110
- }
111
-
112
- function gapMarkdown(canon: ComplianceCanon, today?: string): string {
113
- const index = buildComplianceIndex({ requirements: canon.requirements, assertions: canon.assertions });
114
- const report = buildGapReport(index, { today });
115
- const total = report.requirementsWithoutAssertions.length + report.assertionsWithoutEvidence.length + report.staleAssertions.length + report.pastDeadlineRequirements.length;
116
- const out: string[] = ['# Compliance Gap Dashboard', '', `_${total} gap(s) across 4 checks_`, ''];
117
-
118
- out.push(`## Requirements without assertions (${report.requirementsWithoutAssertions.length})`, '');
119
- if (report.requirementsWithoutAssertions.length === 0) out.push('_✓ none_', '');
120
- else { for (const r of report.requirementsWithoutAssertions) out.push(`- [ ] \`${r.id}\` ${r.name}${r.severity ? ` — severity: ${r.severity}` : ''}${r.deadline ? ` — deadline: ${r.deadline}` : ''}`); out.push(''); }
121
-
122
- out.push(`## Assertions without evidence — ASSERT-007 (${report.assertionsWithoutEvidence.length})`, '');
123
- if (report.assertionsWithoutEvidence.length === 0) out.push('_✓ none_', '');
124
- else { for (const a of report.assertionsWithoutEvidence) out.push(`- [ ] \`${a.id}\` — about \`${a.about}\`, subject \`${a.subject}\`, status ${a.status}`); out.push(''); }
125
-
126
- out.push(`## Stale assertions — ASSERT-008 (${report.staleAssertions.length})`, '');
127
- if (report.staleAssertions.length === 0) out.push('_✓ none_', '');
128
- else { for (const a of report.staleAssertions) out.push(`- [ ] \`${a.id}\` — review due ${a.next_review_at ?? '—'}, subject \`${a.subject}\``); out.push(''); }
129
-
130
- out.push(`## Past-deadline requirements — CV-5 (${report.pastDeadlineRequirements.length})`, '');
131
- if (report.pastDeadlineRequirements.length === 0) out.push('_✓ none_', '');
132
- else { for (const r of report.pastDeadlineRequirements) out.push(`- [ ] \`${r.id}\` ${r.name} — deadline: ${r.deadline ?? '—'}${r.severity ? `, severity: ${r.severity}` : ''}`); out.push(''); }
133
-
134
- return out.join('\n');
135
- }
1
+ // Markdown renderers for the compliance views (vkgeorgia/strategy#84 Phase 5).
2
+ // Pure string builders consumed by the `transitrix export-compliance` CLI.
3
+ // PDF rendering lives in `html.ts` (HTML doc fed to WeasyPrint by the CLI).
4
+
5
+ import { buildComplianceMatrix } from '../compliance-matrix/index.js';
6
+ import { buildComplianceIndex } from './reverse-index.js';
7
+ import { buildLawTree, buildProductView } from './views.js';
8
+ import { buildGapReport } from './gap-report.js';
9
+ import type { ComplianceCanon } from './classify.js';
10
+ import type { AssertionStatus } from '../assertion/types.js';
11
+
12
+ const STATUS_LABELS: Record<AssertionStatus, string> = {
13
+ compliant: 'Compliant',
14
+ partial: 'Partial',
15
+ non_compliant: 'Non-compliant',
16
+ under_review: 'Under review',
17
+ pending_owner: 'Pending owner',
18
+ n_a: 'N/A',
19
+ };
20
+
21
+ /** Escapes a value for a Markdown table cell (pipes + newlines). */
22
+ function cell(s: string): string {
23
+ return s.replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
24
+ }
25
+
26
+ export type ReportScope =
27
+ | { mode: 'matrix' }
28
+ | { mode: 'law'; id: string }
29
+ | { mode: 'product'; id: string }
30
+ | { mode: 'gap' };
31
+
32
+ export interface MarkdownOptions {
33
+ /** Today as ISO YYYY-MM-DD — enables the gap dashboard's stale-assertion list. */
34
+ today?: string;
35
+ }
36
+
37
+ /** Renders the requested compliance view from a scanned canon to Markdown. */
38
+ export function renderComplianceMarkdown(canon: ComplianceCanon, scope: ReportScope, options: MarkdownOptions = {}): string {
39
+ switch (scope.mode) {
40
+ case 'matrix': return matrixMarkdown(canon);
41
+ case 'law': return lawMarkdown(canon, scope.id);
42
+ case 'product': return productMarkdown(canon, scope.id);
43
+ case 'gap': return gapMarkdown(canon, options.today);
44
+ }
45
+ }
46
+
47
+ function matrixMarkdown(canon: ComplianceCanon): string {
48
+ const m = buildComplianceMatrix({ products: canon.products, requirements: canon.requirements, assertions: canon.assertions });
49
+ const out: string[] = ['# Compliance Matrix', ''];
50
+ out.push(`_${m.summary.products} products × ${m.summary.requirements} requirements · ${m.summary.gaps} gaps · ${m.summary.assertions} assertions_`, '');
51
+ if (m.products.length === 0 || m.requirements.length === 0) {
52
+ out.push('_No products or requirements found in the scanned canon._', '');
53
+ return out.join('\n');
54
+ }
55
+ out.push(`| Product \\ Requirement | ${m.requirements.map(r => cell(r.name)).join(' | ')} |`);
56
+ out.push(`|---|${m.requirements.map(() => '---').join('|')}|`);
57
+ m.products.forEach((p, ri) => {
58
+ const cells = m.requirements.map((_r, ci) => {
59
+ const c = m.cells[ri][ci];
60
+ return c.status ? STATUS_LABELS[c.status] : '—';
61
+ });
62
+ const name = cell(p.name) + (p.unresolved ? ' ⚠' : '');
63
+ out.push(`| ${name} | ${cells.join(' | ')} |`);
64
+ });
65
+ out.push('');
66
+ return out.join('\n');
67
+ }
68
+
69
+ function lawMarkdown(canon: ComplianceCanon, lawId: string): string {
70
+ const index = buildComplianceIndex({ requirements: canon.requirements, assertions: canon.assertions });
71
+ const tree = buildLawTree(lawId, index);
72
+ const law = canon.codex.find(c => c.id === lawId);
73
+ const out: string[] = [`# Compliance — ${law ? law.name : lawId}`, ''];
74
+ out.push(`\`${lawId}\` · ${tree.requirements.length} requirement(s)`, '');
75
+ if (tree.requirements.length === 0) {
76
+ out.push(`_No requirements derive from \`${lawId}\`._`, '');
77
+ return out.join('\n');
78
+ }
79
+ for (const node of tree.requirements) {
80
+ const sev = node.requirement.severity ? ` — severity: ${node.requirement.severity}` : '';
81
+ out.push(`## ${node.requirement.name} (\`${node.requirement.id}\`)${sev}`, '');
82
+ if (node.assertions.length === 0) {
83
+ out.push('_No assertion — compliance gap._', '');
84
+ continue;
85
+ }
86
+ for (const a of node.assertions) {
87
+ const meta = [a.assessed_at ? `assessed ${a.assessed_at}` : null, a.next_review_at ? `review by ${a.next_review_at}` : null].filter(Boolean).join(', ');
88
+ out.push(`- **${STATUS_LABELS[a.status]}** — \`${a.id}\` (subject \`${a.subject}\`${meta ? `; ${meta}` : ''})`);
89
+ }
90
+ out.push('');
91
+ }
92
+ return out.join('\n');
93
+ }
94
+
95
+ function productMarkdown(canon: ComplianceCanon, productId: string): string {
96
+ const index = buildComplianceIndex({ requirements: canon.requirements, assertions: canon.assertions });
97
+ const view = buildProductView(productId, index);
98
+ const product = canon.products.find(p => p.id === productId);
99
+ const out: string[] = [`# Compliance — ${product ? product.name : productId}`, ''];
100
+ out.push(`\`${productId}\` · ${view.requirements.length} requirement(s) asserted`, '');
101
+ if (view.requirements.length === 0) {
102
+ out.push('_No assertion names this product as its subject._', '');
103
+ return out.join('\n');
104
+ }
105
+ out.push('| Requirement | Status | Assertion | Next review |', '|---|---|---|---|');
106
+ for (const { requirement, assertion } of view.requirements) {
107
+ out.push(`| ${cell(requirement.name)} (\`${requirement.id}\`) | ${STATUS_LABELS[assertion.status]} | \`${assertion.id}\` | ${assertion.next_review_at ?? '—'} |`);
108
+ }
109
+ out.push('');
110
+ return out.join('\n');
111
+ }
112
+
113
+ function gapMarkdown(canon: ComplianceCanon, today?: string): string {
114
+ const index = buildComplianceIndex({ requirements: canon.requirements, assertions: canon.assertions });
115
+ const report = buildGapReport(index, { today });
116
+ const total = report.requirementsWithoutAssertions.length + report.assertionsWithoutEvidence.length + report.staleAssertions.length + report.pastDeadlineRequirements.length;
117
+ const out: string[] = ['# Compliance Gap Dashboard', '', `_${total} gap(s) across 4 checks_`, ''];
118
+
119
+ out.push(`## Requirements without assertions (${report.requirementsWithoutAssertions.length})`, '');
120
+ if (report.requirementsWithoutAssertions.length === 0) out.push('_✓ none_', '');
121
+ else { for (const r of report.requirementsWithoutAssertions) out.push(`- [ ] \`${r.id}\` ${r.name}${r.severity ? ` — severity: ${r.severity}` : ''}${r.deadline ? ` — deadline: ${r.deadline}` : ''}`); out.push(''); }
122
+
123
+ out.push(`## Assertions without evidence — ASSERT-007 (${report.assertionsWithoutEvidence.length})`, '');
124
+ if (report.assertionsWithoutEvidence.length === 0) out.push('_✓ none_', '');
125
+ else { for (const a of report.assertionsWithoutEvidence) out.push(`- [ ] \`${a.id}\` — about \`${a.about}\`, subject \`${a.subject}\`, status ${a.status}`); out.push(''); }
126
+
127
+ out.push(`## Stale assertions — ASSERT-008 (${report.staleAssertions.length})`, '');
128
+ if (report.staleAssertions.length === 0) out.push('_✓ none_', '');
129
+ else { for (const a of report.staleAssertions) out.push(`- [ ] \`${a.id}\` — review due ${a.next_review_at ?? '—'}, subject \`${a.subject}\``); out.push(''); }
130
+
131
+ out.push(`## Past-deadline requirements — CV-5 (${report.pastDeadlineRequirements.length})`, '');
132
+ if (report.pastDeadlineRequirements.length === 0) out.push('_✓ none_', '');
133
+ else { for (const r of report.pastDeadlineRequirements) out.push(`- [ ] \`${r.id}\` ${r.name} — deadline: ${r.deadline ?? '—'}${r.severity ? `, severity: ${r.severity}` : ''}`); out.push(''); }
134
+
135
+ return out.join('\n');
136
+ }
@@ -1,33 +1,33 @@
1
- // Reverse-index builder (vkgeorgia/strategy#84 Phase 3).
2
-
3
- import type { ComplianceIndex, ComplianceIndexInput, IndexAssertion, IndexRequirement } from './types.js';
4
-
5
- function push<K, V>(map: Map<K, V[]>, key: K, value: V): void {
6
- const list = map.get(key);
7
- if (list) list.push(value);
8
- else map.set(key, [value]);
9
- }
10
-
11
- /**
12
- * Builds the reverse indexes used by every compliance view. Pure; the input
13
- * arrays are read, not mutated. Requirements with no `derived_from` simply do
14
- * not appear under any law; assertions are indexed by both their requirement
15
- * (`about`) and their subject.
16
- */
17
- export function buildComplianceIndex(input: ComplianceIndexInput): ComplianceIndex {
18
- const requirementById = new Map<string, IndexRequirement>();
19
- const requirementsByLaw = new Map<string, IndexRequirement[]>();
20
- const assertionsByRequirement = new Map<string, IndexAssertion[]>();
21
- const assertionsBySubject = new Map<string, IndexAssertion[]>();
22
-
23
- for (const r of input.requirements) {
24
- requirementById.set(r.id, r);
25
- for (const law of r.derived_from ?? []) push(requirementsByLaw, law, r);
26
- }
27
- for (const a of input.assertions) {
28
- push(assertionsByRequirement, a.about, a);
29
- push(assertionsBySubject, a.subject, a);
30
- }
31
-
32
- return { requirementById, requirementsByLaw, assertionsByRequirement, assertionsBySubject };
33
- }
1
+ // Reverse-index builder (vkgeorgia/strategy#84 Phase 3).
2
+
3
+ import type { ComplianceIndex, ComplianceIndexInput, IndexAssertion, IndexRequirement } from './types.js';
4
+
5
+ function push<K, V>(map: Map<K, V[]>, key: K, value: V): void {
6
+ const list = map.get(key);
7
+ if (list) list.push(value);
8
+ else map.set(key, [value]);
9
+ }
10
+
11
+ /**
12
+ * Builds the reverse indexes used by every compliance view. Pure; the input
13
+ * arrays are read, not mutated. Requirements with no `derived_from` simply do
14
+ * not appear under any law; assertions are indexed by both their requirement
15
+ * (`about`) and their subject.
16
+ */
17
+ export function buildComplianceIndex(input: ComplianceIndexInput): ComplianceIndex {
18
+ const requirementById = new Map<string, IndexRequirement>();
19
+ const requirementsByLaw = new Map<string, IndexRequirement[]>();
20
+ const assertionsByRequirement = new Map<string, IndexAssertion[]>();
21
+ const assertionsBySubject = new Map<string, IndexAssertion[]>();
22
+
23
+ for (const r of input.requirements) {
24
+ requirementById.set(r.id, r);
25
+ for (const law of r.derived_from ?? []) push(requirementsByLaw, law, r);
26
+ }
27
+ for (const a of input.assertions) {
28
+ push(assertionsByRequirement, a.about, a);
29
+ push(assertionsBySubject, a.subject, a);
30
+ }
31
+
32
+ return { requirementById, requirementsByLaw, assertionsByRequirement, assertionsBySubject };
33
+ }