@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,56 @@
1
- /**
2
- * Coerce native `Date` instances inside a parsed YAML document into ISO
3
- * `YYYY-MM-DD` strings.
4
- *
5
- * Background: per the YAML 1.1 timestamp tag, a bare `2026-06-01` is parsed
6
- * by `js-yaml` as a native `Date`, not a string. Every notation validator in
7
- * this package expects ISO date strings (`typeof === 'string'` +
8
- * `YYYY-MM-DD` regex), so a Date reaching validation would falsely fail
9
- * shape checks even though the user followed the spec, minus quotes.
10
- *
11
- * The canonical, methodology-correct form is the quoted ISO string. This
12
- * coercion is a backstop so the unquoted form does not silently bite.
13
- *
14
- * UTC-safe: uses `getUTC*` so the JS process timezone cannot drift the day.
15
- * A bare `2026-06-01` parses to midnight UTC and round-trips to
16
- * `"2026-06-01"` regardless of `process.env.TZ`.
17
- *
18
- * Walks in place and returns the same reference for convenience.
19
- */
20
- export function coerceDatesToIsoStrings<T>(value: T): T {
21
- if (value === null || value === undefined) return value;
22
- if (value instanceof Date) {
23
- return dateToIsoYmd(value) as unknown as T;
24
- }
25
- if (typeof value !== 'object') return value;
26
-
27
- if (Array.isArray(value)) {
28
- for (let i = 0; i < value.length; i++) {
29
- const v = value[i];
30
- if (v instanceof Date) {
31
- value[i] = dateToIsoYmd(v) as unknown as typeof v;
32
- } else if (v !== null && typeof v === 'object') {
33
- coerceDatesToIsoStrings(v);
34
- }
35
- }
36
- return value;
37
- }
38
-
39
- const obj = value as Record<string, unknown>;
40
- for (const key of Object.keys(obj)) {
41
- const v = obj[key];
42
- if (v instanceof Date) {
43
- obj[key] = dateToIsoYmd(v);
44
- } else if (v !== null && typeof v === 'object') {
45
- coerceDatesToIsoStrings(v);
46
- }
47
- }
48
- return value;
49
- }
50
-
51
- function dateToIsoYmd(d: Date): string {
52
- const y = d.getUTCFullYear();
53
- const m = String(d.getUTCMonth() + 1).padStart(2, '0');
54
- const day = String(d.getUTCDate()).padStart(2, '0');
55
- return `${y}-${m}-${day}`;
56
- }
1
+ /**
2
+ * Coerce native `Date` instances inside a parsed YAML document into ISO
3
+ * `YYYY-MM-DD` strings.
4
+ *
5
+ * Background: per the YAML 1.1 timestamp tag, a bare `2026-06-01` is parsed
6
+ * by `js-yaml` as a native `Date`, not a string. Every notation validator in
7
+ * this package expects ISO date strings (`typeof === 'string'` +
8
+ * `YYYY-MM-DD` regex), so a Date reaching validation would falsely fail
9
+ * shape checks even though the user followed the spec, minus quotes.
10
+ *
11
+ * The canonical, methodology-correct form is the quoted ISO string. This
12
+ * coercion is a backstop so the unquoted form does not silently bite.
13
+ *
14
+ * UTC-safe: uses `getUTC*` so the JS process timezone cannot drift the day.
15
+ * A bare `2026-06-01` parses to midnight UTC and round-trips to
16
+ * `"2026-06-01"` regardless of `process.env.TZ`.
17
+ *
18
+ * Walks in place and returns the same reference for convenience.
19
+ */
20
+ export function coerceDatesToIsoStrings<T>(value: T): T {
21
+ if (value === null || value === undefined) return value;
22
+ if (value instanceof Date) {
23
+ return dateToIsoYmd(value) as unknown as T;
24
+ }
25
+ if (typeof value !== 'object') return value;
26
+
27
+ if (Array.isArray(value)) {
28
+ for (let i = 0; i < value.length; i++) {
29
+ const v = value[i];
30
+ if (v instanceof Date) {
31
+ value[i] = dateToIsoYmd(v) as unknown as typeof v;
32
+ } else if (v !== null && typeof v === 'object') {
33
+ coerceDatesToIsoStrings(v);
34
+ }
35
+ }
36
+ return value;
37
+ }
38
+
39
+ const obj = value as Record<string, unknown>;
40
+ for (const key of Object.keys(obj)) {
41
+ const v = obj[key];
42
+ if (v instanceof Date) {
43
+ obj[key] = dateToIsoYmd(v);
44
+ } else if (v !== null && typeof v === 'object') {
45
+ coerceDatesToIsoStrings(v);
46
+ }
47
+ }
48
+ return value;
49
+ }
50
+
51
+ function dateToIsoYmd(d: Date): string {
52
+ const y = d.getUTCFullYear();
53
+ const m = String(d.getUTCMonth() + 1).padStart(2, '0');
54
+ const day = String(d.getUTCDate()).padStart(2, '0');
55
+ return `${y}-${m}-${day}`;
56
+ }
@@ -1,15 +0,0 @@
1
- import type { ValidationError } from './validation-types.js';
2
- export interface EnvelopeOptions {
3
- /** Fixed `notation:` tag — lowercased TYPE short name (e.g. `factor`). */
4
- notation: string;
5
- /** Required `id:` TYPE prefix (e.g. `FACTOR`). */
6
- idType: string;
7
- /** Error code used for every envelope failure (e.g. `FACTOR-001`). */
8
- code: string;
9
- }
10
- export interface EnvelopeResult {
11
- /** The input narrowed to a record on success; `null` when the input is not a mapping. */
12
- record: Record<string, unknown> | null;
13
- errors: ValidationError[];
14
- }
15
- export declare function validateElementEnvelope(input: unknown, opts: EnvelopeOptions): EnvelopeResult;
@@ -1,55 +0,0 @@
1
- // Shared §3 envelope validator for standalone element primitives.
2
- //
3
- // Every standalone element file in the methodology canon carries the same
4
- // envelope: a fixed `notation` tag, a canonical `id` whose TYPE prefix matches
5
- // the element TYPE, `name`, an admission record (`zone`/`admitted_at`/
6
- // `admitted_by`/`gate_checks`), and a primitive lifecycle
7
- // (`valid_from`/`valid_to`). Per-TYPE fields (§7) sit between.
8
- //
9
- // Each per-TYPE validator delegates envelope checks here so the §3 rule lives
10
- // in one place; per-TYPE fields (subtype enums, cross-references) are checked
11
- // in the TYPE's own validator. The error `code` is parameterised so an
12
- // envelope failure surfaces under the TYPE's own code (e.g. `FACTOR-001`,
13
- // `CHANGE-001`), preserving per-TYPE attribution.
14
- import { isCanonicalIdOfType } from './typed-id.js';
15
- export function validateElementEnvelope(input, opts) {
16
- const errors = [];
17
- if (input === null || typeof input !== 'object' || Array.isArray(input)) {
18
- errors.push({ code: opts.code, message: `${opts.idType} must be a YAML mapping.` });
19
- return { record: null, errors };
20
- }
21
- const r = input;
22
- if (r.notation !== opts.notation) {
23
- errors.push({
24
- code: opts.code,
25
- message: `notation must be the fixed value "${opts.notation}".`,
26
- path: 'notation',
27
- });
28
- }
29
- if (!isCanonicalIdOfType(r.id, opts.idType)) {
30
- errors.push({
31
- code: opts.code,
32
- message: `id "${String(r.id)}" must match ${opts.idType}-[<middle>-]<INTEGER>.`,
33
- path: 'id',
34
- });
35
- }
36
- for (const f of ['name', 'admitted_at', 'admitted_by', 'valid_from']) {
37
- if (typeof r[f] !== 'string' || r[f].trim() === '') {
38
- errors.push({ code: opts.code, message: `${f} is required.`, path: f });
39
- }
40
- }
41
- if (r.zone !== 'canon') {
42
- errors.push({ code: opts.code, message: 'zone is required and must be "canon".', path: 'zone' });
43
- }
44
- if (r.gate_checks === null || typeof r.gate_checks !== 'object' || Array.isArray(r.gate_checks)) {
45
- errors.push({ code: opts.code, message: 'gate_checks is required and must be a mapping.', path: 'gate_checks' });
46
- }
47
- if (!('valid_to' in r) || !(typeof r.valid_to === 'string' || r.valid_to === null)) {
48
- errors.push({
49
- code: opts.code,
50
- message: 'valid_to is required (an ISO date string or null).',
51
- path: 'valid_to',
52
- });
53
- }
54
- return { record: r, errors };
55
- }
@@ -1,62 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { layoutIssues } from '../layout.js';
3
- function build(issues) {
4
- return {
5
- notation: 'issues',
6
- issues_catalogue: {
7
- id: 'ISSUES-CAT-1',
8
- name: 'Test',
9
- updated_at: '2026-05-25',
10
- issues,
11
- },
12
- };
13
- }
14
- describe('layoutIssues', () => {
15
- it('lays out one row per issue', () => {
16
- const layout = layoutIssues(build([
17
- { issue_id: 'ISSUE-1', name: 'A', status: 'open' },
18
- { issue_id: 'ISSUE-2', name: 'B', status: 'open' },
19
- ]));
20
- expect(layout.rows).toHaveLength(2);
21
- });
22
- it('indents sub-issues by nesting depth', () => {
23
- const layout = layoutIssues(build([
24
- { issue_id: 'ISSUE-1', name: 'Root', status: 'open' },
25
- { issue_id: 'ISSUE-2', name: 'Child', status: 'open', parent: 'ISSUE-1' },
26
- { issue_id: 'ISSUE-3', name: 'Grandchild', status: 'open', parent: 'ISSUE-2' },
27
- ]));
28
- const byId = new Map(layout.rows.map((r) => [r.issue_id, r]));
29
- expect(byId.get('ISSUE-1').depth).toBe(0);
30
- expect(byId.get('ISSUE-2').depth).toBe(1);
31
- expect(byId.get('ISSUE-3').depth).toBe(2);
32
- expect(byId.get('ISSUE-2').x).toBeGreaterThan(byId.get('ISSUE-1').x);
33
- });
34
- it('renders pre-order: a child row follows its parent', () => {
35
- const layout = layoutIssues(build([
36
- { issue_id: 'ISSUE-1', name: 'Root', status: 'open' },
37
- { issue_id: 'ISSUE-2', name: 'Child', status: 'open', parent: 'ISSUE-1' },
38
- ]));
39
- expect(layout.rows[0].issue_id).toBe('ISSUE-1');
40
- expect(layout.rows[1].issue_id).toBe('ISSUE-2');
41
- expect(layout.rows[0].hasChildren).toBe(true);
42
- expect(layout.rows[1].hasChildren).toBe(false);
43
- });
44
- it('places a broken-parent issue at the root', () => {
45
- const layout = layoutIssues(build([
46
- { issue_id: 'ISSUE-1', name: 'Orphan', status: 'open', parent: 'ISSUE-99' },
47
- ]));
48
- expect(layout.rows[0].depth).toBe(0);
49
- });
50
- it('does not stack-overflow or drop nodes on a parent cycle', () => {
51
- const layout = layoutIssues(build([
52
- { issue_id: 'ISSUE-1', name: 'A', status: 'open', parent: 'ISSUE-2' },
53
- { issue_id: 'ISSUE-2', name: 'B', status: 'open', parent: 'ISSUE-1' },
54
- ]));
55
- expect(layout.rows).toHaveLength(2);
56
- });
57
- it('returns empty bounds for an empty register', () => {
58
- const layout = layoutIssues(build([]));
59
- expect(layout.rows).toHaveLength(0);
60
- expect(layout.bounds).toEqual({ x: 0, y: 0, width: 0, height: 0 });
61
- });
62
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,82 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { validateIssues } from '../validate.js';
3
- const VALID = {
4
- notation: 'issues',
5
- issues_catalogue: {
6
- id: 'ISSUES-CAT-1',
7
- name: 'Test Register',
8
- updated_at: '2026-05-25',
9
- issues: [
10
- { issue_id: 'ISSUE-1', name: 'Root issue', status: 'open' },
11
- { issue_id: 'ISSUE-2', name: 'Sub issue', status: 'in_progress', parent: 'ISSUE-1' },
12
- ],
13
- },
14
- };
15
- function withIssues(issues) {
16
- return { ...VALID, issues_catalogue: { ...VALID.issues_catalogue, issues } };
17
- }
18
- describe('validateIssues', () => {
19
- it('passes on valid input', () => {
20
- const r = validateIssues(VALID);
21
- expect(r.valid).toBe(true);
22
- expect(r.errors).toHaveLength(0);
23
- });
24
- it('ISS-000: rejects a non-object', () => {
25
- expect(validateIssues(null).valid).toBe(false);
26
- expect(validateIssues('x').errors[0].code).toBe('ISS-000');
27
- });
28
- it('ISS-000: rejects a wrong notation header', () => {
29
- const r = validateIssues({ ...VALID, notation: 'goals' });
30
- expect(r.errors.some(e => e.code === 'ISS-000')).toBe(true);
31
- });
32
- it('ISS-000: rejects a missing issues_catalogue', () => {
33
- const r = validateIssues({ notation: 'issues' });
34
- expect(r.errors.some(e => e.code === 'ISS-000')).toBe(true);
35
- });
36
- it('ISS-001: detects a duplicate issue_id', () => {
37
- const r = validateIssues(withIssues([
38
- { issue_id: 'ISSUE-1', name: 'A', status: 'open' },
39
- { issue_id: 'ISSUE-1', name: 'B', status: 'open' },
40
- ]));
41
- expect(r.errors.some(e => e.code === 'ISS-001')).toBe(true);
42
- });
43
- it('ISS-002: detects a status outside the vocabulary', () => {
44
- const r = validateIssues(withIssues([{ issue_id: 'ISSUE-1', name: 'A', status: 'wip' }]));
45
- expect(r.errors.some(e => e.code === 'ISS-002')).toBe(true);
46
- });
47
- it('ISS-003: detects a missing or empty name', () => {
48
- const r = validateIssues(withIssues([{ issue_id: 'ISSUE-1', name: '', status: 'open' }]));
49
- expect(r.errors.some(e => e.code === 'ISS-003')).toBe(true);
50
- });
51
- it('ISS-004: warns on a broken parent reference and stays valid', () => {
52
- const r = validateIssues(withIssues([
53
- { issue_id: 'ISSUE-1', name: 'Orphan', status: 'open', parent: 'ISSUE-99' },
54
- ]));
55
- expect(r.valid).toBe(true);
56
- expect(r.warnings.some(w => w.code === 'ISS-004')).toBe(true);
57
- });
58
- it('ISS-005: detects a parent cycle', () => {
59
- const r = validateIssues(withIssues([
60
- { issue_id: 'ISSUE-1', name: 'A', status: 'open', parent: 'ISSUE-2' },
61
- { issue_id: 'ISSUE-2', name: 'B', status: 'open', parent: 'ISSUE-1' },
62
- ]));
63
- expect(r.errors.some(e => e.code === 'ISS-005')).toBe(true);
64
- });
65
- it('ISS-006: rejects a relates_to entry that is not an ACTIVITY-/GOAL- id', () => {
66
- const r = validateIssues(withIssues([
67
- { issue_id: 'ISSUE-1', name: 'A', status: 'open', relates_to: ['PRODUCT-1'] },
68
- ]));
69
- expect(r.errors.some(e => e.code === 'ISS-006')).toBe(true);
70
- });
71
- it('accepts valid ACTIVITY-/GOAL- relates_to ids', () => {
72
- const r = validateIssues(withIssues([
73
- { issue_id: 'ISSUE-1', name: 'A', status: 'open', relates_to: ['ACTIVITY-5', 'GOAL-CUST-1'] },
74
- ]));
75
- expect(r.valid).toBe(true);
76
- });
77
- it('tolerates a null element in issues[] without throwing', () => {
78
- const r = validateIssues(withIssues([null]));
79
- expect(r.valid).toBe(false);
80
- expect(r.errors.some(e => e.code === 'ISS-003')).toBe(true);
81
- });
82
- });
@@ -1,4 +0,0 @@
1
- export type { IssueStatus, Issue, IssuesCatalogue, IssuesFile, IssuesLayoutOptions, LaidOutIssue, IssuesLayoutBounds, IssuesLayout, } from './types.js';
2
- export { validateIssues } from './validate.js';
3
- export type { ValidationError as IssuesValidationError, ValidationWarning as IssuesValidationWarning, ValidationResult as IssuesValidationResult, } from './validate.js';
4
- export { layoutIssues } from './layout.js';
@@ -1,2 +0,0 @@
1
- export { validateIssues } from './validate.js';
2
- export { layoutIssues } from './layout.js';
@@ -1,10 +0,0 @@
1
- import type { IssuesFile, IssuesLayout, IssuesLayoutOptions } from './types.js';
2
- /**
3
- * Lays an Issues document out as a nested outline: one row per issue, in
4
- * pre-order, with x indented by nesting depth and y stacked top-to-bottom.
5
- *
6
- * Robust to malformed input — a broken `parent` reference is treated as a
7
- * root, and a `parent` cycle cannot stack-overflow or drop nodes (a
8
- * `placed` guard ensures every issue is laid out exactly once).
9
- */
10
- export declare function layoutIssues(file: IssuesFile, options?: IssuesLayoutOptions): IssuesLayout;
@@ -1,82 +0,0 @@
1
- const DEFAULTS = {
2
- rowHeight: 44,
3
- rowGap: 10,
4
- indentWidth: 32,
5
- nodeWidth: 460,
6
- paddingX: 0,
7
- paddingY: 0,
8
- };
9
- /**
10
- * Lays an Issues document out as a nested outline: one row per issue, in
11
- * pre-order, with x indented by nesting depth and y stacked top-to-bottom.
12
- *
13
- * Robust to malformed input — a broken `parent` reference is treated as a
14
- * root, and a `parent` cycle cannot stack-overflow or drop nodes (a
15
- * `placed` guard ensures every issue is laid out exactly once).
16
- */
17
- export function layoutIssues(file, options = {}) {
18
- const o = { ...DEFAULTS, ...options };
19
- const issues = file?.issues_catalogue?.issues ?? [];
20
- const byId = new Map();
21
- for (const it of issues) {
22
- if (it && typeof it.issue_id === 'string')
23
- byId.set(it.issue_id, it);
24
- }
25
- const childrenOf = new Map();
26
- const roots = [];
27
- for (const it of issues) {
28
- if (!it || typeof it.issue_id !== 'string')
29
- continue;
30
- const parent = it.parent;
31
- if (typeof parent === 'string' && byId.has(parent) && parent !== it.issue_id) {
32
- const arr = childrenOf.get(parent) ?? [];
33
- arr.push(it.issue_id);
34
- childrenOf.set(parent, arr);
35
- }
36
- else {
37
- roots.push(it.issue_id);
38
- }
39
- }
40
- const rows = [];
41
- const placed = new Set();
42
- let y = o.paddingY;
43
- function place(id, depth) {
44
- if (placed.has(id))
45
- return; // cycle / duplicate guard
46
- const issue = byId.get(id);
47
- if (!issue)
48
- return;
49
- placed.add(id);
50
- const kids = childrenOf.get(id) ?? [];
51
- rows.push({
52
- issue_id: id,
53
- depth,
54
- x: o.paddingX + depth * o.indentWidth,
55
- y,
56
- width: o.nodeWidth,
57
- height: o.rowHeight,
58
- data: issue,
59
- hasChildren: kids.length > 0,
60
- });
61
- y += o.rowHeight + o.rowGap;
62
- for (const childId of kids)
63
- place(childId, depth + 1);
64
- }
65
- for (const rootId of roots)
66
- place(rootId, 0);
67
- // Any issue not reached above (e.g. trapped inside a parent cycle) is
68
- // still laid out, at the root level, so nothing silently disappears.
69
- for (const it of issues) {
70
- if (it && typeof it.issue_id === 'string' && !placed.has(it.issue_id)) {
71
- place(it.issue_id, 0);
72
- }
73
- }
74
- if (rows.length === 0) {
75
- return { rows: [], bounds: { x: 0, y: 0, width: 0, height: 0 } };
76
- }
77
- const minX = Math.min(...rows.map((r) => r.x));
78
- const minY = Math.min(...rows.map((r) => r.y));
79
- const maxX = Math.max(...rows.map((r) => r.x + r.width));
80
- const maxY = Math.max(...rows.map((r) => r.y + r.height));
81
- return { rows, bounds: { x: minX, y: minY, width: maxX - minX, height: maxY - minY } };
82
- }
@@ -1,61 +0,0 @@
1
- /**
2
- * Issues notation — a text-native register of issues (problems, defects,
3
- * open questions) with parent/child nesting. See methodology spec
4
- * `notations/12-issues.md`.
5
- */
6
- export type IssueStatus = 'open' | 'in_progress' | 'blocked' | 'resolved' | 'closed';
7
- export interface Issue {
8
- issue_id: string;
9
- name: string;
10
- status: IssueStatus;
11
- /** issue_id of the parent issue — the nesting mechanism. */
12
- parent?: string;
13
- description?: string;
14
- /** Typed IDs of ACTIVITY / GOAL elements this issue concerns. */
15
- relates_to?: string[];
16
- /** ROLE element ID of the role accountable for the issue. */
17
- owner_role?: string;
18
- }
19
- export interface IssuesCatalogue {
20
- id: string;
21
- name: string;
22
- description?: string;
23
- version?: string;
24
- updated_at: string;
25
- issues: Issue[];
26
- }
27
- export interface IssuesFile {
28
- notation: string;
29
- spec_version?: string;
30
- issues_catalogue: IssuesCatalogue;
31
- }
32
- export interface IssuesLayoutOptions {
33
- rowHeight?: number;
34
- rowGap?: number;
35
- indentWidth?: number;
36
- nodeWidth?: number;
37
- paddingX?: number;
38
- paddingY?: number;
39
- }
40
- export interface LaidOutIssue {
41
- issue_id: string;
42
- /** 0 for a root issue, +1 per nesting level. */
43
- depth: number;
44
- x: number;
45
- y: number;
46
- width: number;
47
- height: number;
48
- data: Issue;
49
- hasChildren: boolean;
50
- }
51
- export interface IssuesLayoutBounds {
52
- x: number;
53
- y: number;
54
- width: number;
55
- height: number;
56
- }
57
- export interface IssuesLayout {
58
- rows: LaidOutIssue[];
59
- bounds: IssuesLayoutBounds;
60
- }
61
- export type { ValidationError, ValidationWarning, ValidationResult } from '../validation-types.js';
@@ -1,6 +0,0 @@
1
- /**
2
- * Issues notation — a text-native register of issues (problems, defects,
3
- * open questions) with parent/child nesting. See methodology spec
4
- * `notations/12-issues.md`.
5
- */
6
- export {};
@@ -1,3 +0,0 @@
1
- import type { ValidationError, ValidationWarning, ValidationResult } from './types.js';
2
- export type { ValidationError, ValidationWarning, ValidationResult };
3
- export declare function validateIssues(input: unknown): ValidationResult;
@@ -1,143 +0,0 @@
1
- /**
2
- * Validates an Issues document against `notations/12-issues.md`.
3
- *
4
- * Error codes:
5
- * - ISS-000 structural / header problem (not an object, wrong notation,
6
- * missing issues_catalogue, missing catalogue fields)
7
- * - ISS-001 duplicate issue_id
8
- * - ISS-002 status outside the vocabulary
9
- * - ISS-003 issue_id or name missing or empty
10
- * - ISS-004 parent references a missing issue (warning)
11
- * - ISS-005 cycle in the parent chain
12
- * - ISS-006 relates_to entry is not an ACTIVITY- / GOAL- id
13
- */
14
- const STATUS_VALUES = ['open', 'in_progress', 'blocked', 'resolved', 'closed'];
15
- const RELATES_TO_RE = /^(ACTIVITY|GOAL)(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
16
- function isNonEmptyString(v) {
17
- return typeof v === 'string' && v.trim().length > 0;
18
- }
19
- export function validateIssues(input) {
20
- const errors = [];
21
- const warnings = [];
22
- if (!input || typeof input !== 'object') {
23
- return { valid: false, errors: [{ code: 'ISS-000', message: 'Input must be an object' }], warnings };
24
- }
25
- const raw = input;
26
- if ('notation' in raw && raw['notation'] !== 'issues') {
27
- errors.push({ code: 'ISS-000', message: `notation must be "issues", got "${String(raw['notation'])}"` });
28
- }
29
- const catRaw = raw['issues_catalogue'];
30
- if (!catRaw || typeof catRaw !== 'object') {
31
- errors.push({ code: 'ISS-000', message: 'Missing required root key: issues_catalogue' });
32
- return { valid: false, errors, warnings };
33
- }
34
- const cat = catRaw;
35
- if (!isNonEmptyString(cat['id'])) {
36
- errors.push({ code: 'ISS-000', message: 'issues_catalogue.id is required', path: 'issues_catalogue.id' });
37
- }
38
- if (!isNonEmptyString(cat['name'])) {
39
- errors.push({ code: 'ISS-000', message: 'issues_catalogue.name is required', path: 'issues_catalogue.name' });
40
- }
41
- if (!isNonEmptyString(cat['updated_at'])) {
42
- errors.push({ code: 'ISS-000', message: 'issues_catalogue.updated_at is required', path: 'issues_catalogue.updated_at' });
43
- }
44
- const issuesRaw = cat['issues'];
45
- if (!Array.isArray(issuesRaw)) {
46
- errors.push({ code: 'ISS-000', message: 'issues_catalogue.issues must be an array', path: 'issues_catalogue.issues' });
47
- return { valid: false, errors, warnings };
48
- }
49
- const idSet = new Set();
50
- for (let i = 0; i < issuesRaw.length; i++) {
51
- const it = issuesRaw[i];
52
- const path = `issues_catalogue.issues[${i}]`;
53
- if (!it || typeof it !== 'object') {
54
- errors.push({ code: 'ISS-003', message: `${path} must be an object`, path });
55
- continue;
56
- }
57
- const issue = it;
58
- const idOk = isNonEmptyString(issue['issue_id']);
59
- if (!idOk) {
60
- errors.push({ code: 'ISS-003', message: `${path}.issue_id is missing or empty`, path });
61
- }
62
- if (!isNonEmptyString(issue['name'])) {
63
- errors.push({ code: 'ISS-003', message: `${path}.name is missing or empty`, path });
64
- }
65
- if (idOk) {
66
- const id = issue['issue_id'].trim();
67
- if (idSet.has(id)) {
68
- errors.push({ code: 'ISS-001', message: `Duplicate issue_id: "${id}"`, path });
69
- }
70
- else {
71
- idSet.add(id);
72
- }
73
- }
74
- const status = issue['status'];
75
- if (!STATUS_VALUES.includes(status)) {
76
- errors.push({
77
- code: 'ISS-002',
78
- message: `${path}.status "${String(status)}" must be one of: ${STATUS_VALUES.join(', ')}`,
79
- path,
80
- });
81
- }
82
- const relates = issue['relates_to'];
83
- if (relates !== undefined) {
84
- if (!Array.isArray(relates)) {
85
- errors.push({ code: 'ISS-006', message: `${path}.relates_to must be an array`, path });
86
- }
87
- else {
88
- for (let j = 0; j < relates.length; j++) {
89
- const ref = relates[j];
90
- if (typeof ref !== 'string' || !RELATES_TO_RE.test(ref)) {
91
- errors.push({
92
- code: 'ISS-006',
93
- message: `${path}.relates_to[${j}] "${String(ref)}" must be an ACTIVITY- or GOAL- id`,
94
- path,
95
- });
96
- }
97
- }
98
- }
99
- }
100
- }
101
- // parent reference + cycle checks — run once all ids are collected.
102
- const parentOf = new Map();
103
- for (let i = 0; i < issuesRaw.length; i++) {
104
- const it = issuesRaw[i];
105
- if (!it || typeof it !== 'object')
106
- continue;
107
- const issue = it;
108
- const parent = issue['parent'];
109
- const path = `issues_catalogue.issues[${i}]`;
110
- if (parent === undefined)
111
- continue;
112
- if (typeof parent !== 'string' || !idSet.has(parent.trim())) {
113
- warnings.push({
114
- code: 'ISS-004',
115
- message: `${path}.parent "${String(parent)}" does not resolve to a known issue — rendered at the root`,
116
- path,
117
- });
118
- continue;
119
- }
120
- if (isNonEmptyString(issue['issue_id'])) {
121
- parentOf.set(issue['issue_id'].trim(), parent.trim());
122
- }
123
- }
124
- let cycleNode;
125
- for (const start of parentOf.keys()) {
126
- if (cycleNode)
127
- break;
128
- const seen = new Set();
129
- let cur = start;
130
- while (cur !== undefined) {
131
- if (seen.has(cur)) {
132
- cycleNode = cur;
133
- break;
134
- }
135
- seen.add(cur);
136
- cur = parentOf.get(cur);
137
- }
138
- }
139
- if (cycleNode) {
140
- errors.push({ code: 'ISS-005', message: `Cycle detected in the parent chain involving issue "${cycleNode}"` });
141
- }
142
- return { valid: errors.length === 0, errors, warnings };
143
- }