@transitrix/diagrams 1.8.13 → 1.8.18

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 (39) hide show
  1. package/dist/activities/validate.js +9 -1
  2. package/dist/blocks/index.d.ts +5 -4
  3. package/dist/blocks/index.js +3 -2
  4. package/dist/blocks/layout.d.ts +8 -1
  5. package/dist/blocks/layout.js +57 -0
  6. package/dist/blocks/templates/index.d.ts +9 -0
  7. package/dist/blocks/templates/index.js +11 -0
  8. package/dist/blocks/templates/raci.d.ts +9 -0
  9. package/dist/blocks/templates/raci.js +24 -0
  10. package/dist/blocks/types.d.ts +58 -0
  11. package/dist/blocks/validate.d.ts +31 -1
  12. package/dist/blocks/validate.js +127 -0
  13. package/dist/capability-map/validate.js +41 -0
  14. package/dist/fgca/parse-canonical.js +8 -0
  15. package/dist/repo-validate/check-element-hygiene.d.ts +8 -0
  16. package/dist/repo-validate/check-element-hygiene.js +211 -0
  17. package/dist/repo-validate/check-strategy-chain.d.ts +8 -0
  18. package/dist/repo-validate/check-strategy-chain.js +491 -0
  19. package/dist/repo-validate/types.d.ts +14 -1
  20. package/dist/repo-validate/types.js +22 -3
  21. package/dist/repo-validate/validate-repo.d.ts +3 -2
  22. package/dist/repo-validate/validate-repo.js +16 -4
  23. package/dist/webview/render-blocks.d.ts +23 -1
  24. package/dist/webview/render-blocks.js +100 -2
  25. package/package.json +1 -1
  26. package/src/activities/validate.ts +9 -1
  27. package/src/blocks/index.ts +16 -2
  28. package/src/blocks/layout.ts +70 -0
  29. package/src/blocks/templates/index.ts +13 -0
  30. package/src/blocks/templates/raci.ts +28 -0
  31. package/src/blocks/types.ts +67 -0
  32. package/src/blocks/validate.ts +157 -1
  33. package/src/capability-map/validate.ts +40 -0
  34. package/src/fgca/parse-canonical.ts +8 -0
  35. package/src/repo-validate/check-element-hygiene.ts +236 -0
  36. package/src/repo-validate/check-strategy-chain.ts +528 -0
  37. package/src/repo-validate/types.ts +36 -4
  38. package/src/repo-validate/validate-repo.ts +16 -4
  39. package/src/webview/render-blocks.ts +159 -2
@@ -0,0 +1,236 @@
1
+ // Standalone-element envelope checks — the error-severity subset of DSM's Go
2
+ // `Validate*Element` functions (`api02/internal/importer/{goal_element,
3
+ // action_element}.go` in transitrix-dsm), ported onto the standalone-element
4
+ // repo shape (`canon/elements/**`) that `validate --scope=repo` already
5
+ // loads. Sibling to `check-strategy-chain.ts` (which covers cross-element
6
+ // reference/cycle rules); this file covers the per-element envelope rules —
7
+ // id presence/grammar, name presence, and the ACTION type vocabulary +
8
+ // deprecated-alias warnings.
9
+ //
10
+ // DSM identifies "this file is a GOAL/ACTION element" by its folder location
11
+ // during the import walk (`canon/elements/01_motivation/goals/`,
12
+ // `canon/elements/05_implementation/actions/`) — the validator functions
13
+ // receive an already-typed struct, so a wrong `notation` value on that
14
+ // struct is still checkable (`GOAL-ELEM-001`/the notation branch of
15
+ // `ACTION-001`). This repo-scope model has no such pre-typing
16
+ // (`RepoModelInput.elements` is a flat, untyped list from `canon/elements/**`)
17
+ // — candidate selection here is content-based (the element's own `notation`
18
+ // field), same convention as `check-strategy-chain.ts`'s `collectByNotation`.
19
+ // That means a candidate is by construction already notation-correct, so
20
+ // GOAL-ELEM-001's and ACTION-001's "wrong notation" case can never fire here
21
+ // — tried an id-prefix-based candidate signal instead (matching on a `GOAL-`/
22
+ // `ACTION-` id TYPE prefix regardless of notation) to cover that case, but it
23
+ // produced false positives against this repo's own test fixtures, which
24
+ // reuse a `GOAL-`-prefixed id under a `goals/` path for an unrelated
25
+ // notation as a referential-integrity test double
26
+ // (`validate-repo.test.ts`'s `GOAL-OPS-1`/notation:'assessment' fixture) —
27
+ // there is no reliable, adopter-folder-layout-independent way to tell
28
+ // "misfiled element" from "id reused for something else" without DSM's own
29
+ // folder-based typing. Flagged for a decision, same as `GOALS-008`: skip
30
+ // permanently, or scope a follow-up once the repo-scope model gains
31
+ // path-aware typing.
32
+ //
33
+ // Rule codes are DSM's own — not invented here — so DSM can map a CLI
34
+ // finding straight back onto its import-log taxonomy (`RepoFinding.ruleId`).
35
+ //
36
+ // Ported (error-severity, blocking):
37
+ // GOAL-ELEM-002 — GOAL element `id` is missing.
38
+ // GOAL-ELEM-003 — GOAL element `name` is missing.
39
+ // ACTION-001 — ACTION element `id` or `name` is missing.
40
+ // ACTION-002 — ACTION element `type` is set but not one of
41
+ // Initiative | Strategic Initiative | Programme |
42
+ // Project | Task (Strategic Initiative is an accepted
43
+ // alias for Initiative). DSM treats this as fatal — the
44
+ // type vocabulary has real behavioural teeth downstream.
45
+ //
46
+ // Ported (warning-severity, advisory):
47
+ // GOAL-ELEM-002 — GOAL element `id` does not match the canonical
48
+ // `GOAL-[<middle>-]<INTEGER>` grammar (non-fatal —
49
+ // DSM still imports it).
50
+ // ACTION-001 — ACTION element `id` does not match the canonical
51
+ // `ACTION-[<middle>-]<INTEGER>` grammar.
52
+ // ACTION-005 — ACTION element uses a deprecated alias: `notation:
53
+ // activity` (pre-2026-06-25), an `ACTIVITY-` id prefix,
54
+ // or the `activity_type` field (rename to `type`).
55
+ //
56
+ // Not ported: GOALS-007 (duplicate id) / ACT-004 (duplicate id) — already
57
+ // covered generically by `validate-repo.ts`'s `checkIdUniqueness`, which
58
+ // spans every canon element/relation id, not just GOAL/ACTION. Porting a
59
+ // second, notation-scoped duplicate check here would just double-report the
60
+ // same defect under two rule codes.
61
+
62
+ import { docId } from './validate-repo.js';
63
+ import type { RepoDoc, RepoFinding, RepoModelInput } from './types.js';
64
+
65
+ const PScope: RepoFinding['scope'] = 'repo';
66
+
67
+ const GOAL_ELEM_ID_RE = /^GOAL-([A-Z0-9]+-)*[0-9]+$/;
68
+ const ACTION_ELEM_ID_RE = /^ACTION-([A-Z0-9]+-)*[0-9]+$/;
69
+
70
+ /** ACTION scale-level vocabulary (elements/24-action.md §1). "Strategic
71
+ * Initiative" is an accepted alias for "Initiative", matching DSM's
72
+ * `actionTypeVocabulary` (action_element.go). */
73
+ const ACTION_TYPE_VOCABULARY = new Set([
74
+ 'Initiative',
75
+ 'Strategic Initiative',
76
+ 'Programme',
77
+ 'Project',
78
+ 'Task',
79
+ ]);
80
+
81
+ function readString(data: Record<string, unknown>, key: string): string | undefined {
82
+ const v = data[key];
83
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
84
+ }
85
+
86
+ interface ElementCandidate {
87
+ id: string | undefined;
88
+ doc: RepoDoc;
89
+ data: Record<string, unknown>;
90
+ }
91
+
92
+ function collectCandidates(
93
+ elements: RepoDoc[],
94
+ matches: (data: Record<string, unknown>) => boolean,
95
+ ): ElementCandidate[] {
96
+ const out: ElementCandidate[] = [];
97
+ for (const doc of elements) {
98
+ if (!doc.data) continue;
99
+ if (!matches(doc.data)) continue;
100
+ out.push({ id: docId(doc) ?? undefined, doc, data: doc.data });
101
+ }
102
+ return out;
103
+ }
104
+
105
+ /** GOAL-ELEM-002/003 — the GOAL element envelope: id presence + grammar,
106
+ * name presence. Candidate selection is content-based (`notation === 'goal'`)
107
+ * — see the module header for why `GOAL-ELEM-001` (wrong notation) isn't
108
+ * ported here. */
109
+ function checkGoalElements(elements: RepoDoc[], findings: RepoFinding[]): void {
110
+ const candidates = collectCandidates(elements, (data) => data['notation'] === 'goal');
111
+
112
+ for (const { id, data } of candidates) {
113
+ const where = id ?? '';
114
+
115
+ if (!id) {
116
+ findings.push({
117
+ scope: PScope,
118
+ id: '',
119
+ ruleId: 'GOAL-ELEM-002',
120
+ message: 'GOAL-ELEM-002: GOAL element id is missing.',
121
+ });
122
+ } else if (!GOAL_ELEM_ID_RE.test(id)) {
123
+ findings.push({
124
+ scope: PScope,
125
+ id,
126
+ ruleId: 'GOAL-ELEM-002',
127
+ severity: 'warning',
128
+ message: `GOAL-ELEM-002: goal id '${id}' does not match GOAL-[<middle>-]<INTEGER>.`,
129
+ });
130
+ }
131
+
132
+ if (!readString(data, 'name')) {
133
+ findings.push({
134
+ scope: PScope,
135
+ id: where,
136
+ ruleId: 'GOAL-ELEM-003',
137
+ message: `GOAL-ELEM-003: GOAL element '${where || '(no id)'}' name is missing.`,
138
+ });
139
+ }
140
+ }
141
+ }
142
+
143
+ /** ACTION-001/002/005 — the ACTION element envelope: id presence + grammar,
144
+ * name presence, type vocabulary, and the pre-2026-06-25 deprecated aliases
145
+ * (`notation: activity`, `ACTIVITY-` id prefix, `activity_type` field).
146
+ * Candidate selection is content-based (`notation === 'action'` or the
147
+ * deprecated `'activity'` alias) — see the module header for why
148
+ * `ACTION-001`'s "wrong notation" case isn't ported here. */
149
+ function checkActionElements(elements: RepoDoc[], findings: RepoFinding[]): void {
150
+ const candidates = collectCandidates(
151
+ elements,
152
+ (data) => data['notation'] === 'action' || data['notation'] === 'activity',
153
+ );
154
+
155
+ for (const { id, data } of candidates) {
156
+ const where = id ?? '';
157
+
158
+ if (data['notation'] === 'activity') {
159
+ findings.push({
160
+ scope: PScope,
161
+ id: where,
162
+ ruleId: 'ACTION-005',
163
+ severity: 'warning',
164
+ message: `ACTION-005: element '${where || '(no id)'}' notation 'activity' is deprecated; migrate to 'action'.`,
165
+ });
166
+ }
167
+
168
+ if (!id) {
169
+ findings.push({
170
+ scope: PScope,
171
+ id: '',
172
+ ruleId: 'ACTION-001',
173
+ message: 'ACTION-001: ACTION element id is missing.',
174
+ });
175
+ } else if (id.startsWith('ACTIVITY-')) {
176
+ findings.push({
177
+ scope: PScope,
178
+ id,
179
+ ruleId: 'ACTION-005',
180
+ severity: 'warning',
181
+ message: `ACTION-005: id '${id}' uses the deprecated ACTIVITY- prefix; migrate to ACTION-.`,
182
+ });
183
+ } else if (!ACTION_ELEM_ID_RE.test(id)) {
184
+ findings.push({
185
+ scope: PScope,
186
+ id,
187
+ ruleId: 'ACTION-001',
188
+ severity: 'warning',
189
+ message: `ACTION-001: action id '${id}' does not match ACTION-[<middle>-]<INTEGER>.`,
190
+ });
191
+ }
192
+
193
+ if (!readString(data, 'name')) {
194
+ findings.push({
195
+ scope: PScope,
196
+ id: where,
197
+ ruleId: 'ACTION-001',
198
+ message: `ACTION-001: ACTION element '${where || '(no id)'}' name is missing.`,
199
+ });
200
+ }
201
+
202
+ let actionType = readString(data, 'type');
203
+ if (!actionType) {
204
+ const legacy = readString(data, 'activity_type');
205
+ if (legacy) {
206
+ findings.push({
207
+ scope: PScope,
208
+ id: where,
209
+ ruleId: 'ACTION-005',
210
+ severity: 'warning',
211
+ message: `ACTION-005: element '${where || '(no id)'}' field 'activity_type' is deprecated; migrate to 'type'.`,
212
+ });
213
+ actionType = legacy;
214
+ }
215
+ }
216
+ if (actionType && !ACTION_TYPE_VOCABULARY.has(actionType)) {
217
+ findings.push({
218
+ scope: PScope,
219
+ id: where,
220
+ ruleId: 'ACTION-002',
221
+ message: `ACTION-002: element '${where || '(no id)'}' type '${actionType}' is not one of Initiative, Programme, Project, Task.`,
222
+ });
223
+ }
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Run the standalone-element envelope checks (GOAL-ELEM-001..003,
229
+ * ACTION-001/002/005) over the loaded element set and append findings.
230
+ * Called from `validateRepoModel` alongside `checkStrategyChainSemantics`.
231
+ * Pure, deterministic order.
232
+ */
233
+ export function checkElementHygiene(input: RepoModelInput, findings: RepoFinding[]): void {
234
+ checkGoalElements(input.elements, findings);
235
+ checkActionElements(input.elements, findings);
236
+ }