phasegate 0.229.0 → 0.254.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 (106) hide show
  1. package/CHANGELOG.md +51 -1
  2. package/README.ja.md +28 -1
  3. package/README.md +27 -0
  4. package/docs/ADR/031-world-model-ownership-and-corpus-lifecycle.md +155 -0
  5. package/docs/ADR/032-world-node-identity.md +198 -0
  6. package/docs/ADR/033-world-snapshot-canonicalization.md +246 -0
  7. package/docs/ADR/034-world-constraint-semantics.md +218 -0
  8. package/docs/ADR/035-world-adoption-baseline-and-waiver.md +341 -0
  9. package/docs/ADR/036-world-model-and-doc-freshness.md +169 -0
  10. package/docs/ADR/037-world-cli-and-output-contract.md +398 -0
  11. package/docs/contracts/requirement-test-matrix.schema.json +15 -0
  12. package/docs/contracts/world-baseline.schema.json +35 -0
  13. package/docs/contracts/world-constraints.schema.json +56 -0
  14. package/docs/contracts/world-debts.schema.json +32 -0
  15. package/docs/contracts/world-obligation-report.schema.json +259 -0
  16. package/docs/contracts/world-waivers.schema.json +32 -0
  17. package/docs/guide/cli-reference.md +34 -0
  18. package/package.json +1 -1
  19. package/scripts/harness/attestation/application/ports/sha256-capability.ts +25 -0
  20. package/scripts/harness/attestation/composition-root.ts +14 -4
  21. package/scripts/harness/attestation/index.ts +6 -1
  22. package/scripts/harness/attestation/infrastructure/adapters/node-crypto-content-hasher-adapter.ts +6 -6
  23. package/scripts/harness/attestation/infrastructure/adapters/node-crypto-sha256-capability.ts +19 -0
  24. package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +4 -1
  25. package/scripts/harness/main.ts +107 -1
  26. package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +11 -0
  27. package/scripts/harness/nyquist-validation/application/usecases/check-ac-coverage-gate-usecase.ts +6 -0
  28. package/scripts/harness/nyquist-validation/application/usecases/generate-requirement-test-matrix-usecase.ts +8 -4
  29. package/scripts/harness/nyquist-validation/domain/entities/story-mapping.ts +29 -2
  30. package/scripts/harness/nyquist-validation/domain/services/ac-coverage-gate-policy.ts +28 -0
  31. package/scripts/harness/nyquist-validation/infrastructure/adapters/markdown-requirement-source-adapter.ts +71 -4
  32. package/scripts/harness/nyquist-validation/infrastructure/schema/matrix-schema-loader.ts +4 -0
  33. package/scripts/harness/traceability-model/application/dto/traceability-world-read-dto.ts +67 -0
  34. package/scripts/harness/traceability-model/application/facades/traceability-world-read-facade.ts +372 -0
  35. package/scripts/harness/traceability-model/application/ports/traceability-world-read-source-port.ts +61 -0
  36. package/scripts/harness/traceability-model/composition-root.ts +21 -5
  37. package/scripts/harness/traceability-model/index.ts +17 -6
  38. package/scripts/harness/traceability-model/infrastructure/adapters/file-system-traceability-world-read-adapter.ts +223 -0
  39. package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +86 -8
  40. package/scripts/harness/world-model/application/dto/world-inspection-dto.ts +43 -0
  41. package/scripts/harness/world-model/application/dto/world-obligation-report-dto.ts +54 -0
  42. package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +62 -0
  43. package/scripts/harness/world-model/application/ports/obligation-report-writer-port.ts +7 -0
  44. package/scripts/harness/world-model/application/ports/world-control-declaration-repository-port.ts +57 -0
  45. package/scripts/harness/world-model/application/ports/world-fact-source-port.ts +17 -0
  46. package/scripts/harness/world-model/application/usecases/build-snapshot-use-case.ts +111 -0
  47. package/scripts/harness/world-model/application/usecases/derive-obligations-use-case.ts +155 -0
  48. package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +144 -0
  49. package/scripts/harness/world-model/application/usecases/inspect-world-use-case.ts +74 -0
  50. package/scripts/harness/world-model/application/usecases/pin-constraint-endpoint-use-case.ts +139 -0
  51. package/scripts/harness/world-model/composition-root.ts +218 -0
  52. package/scripts/harness/world-model/domain/entities/constraint-record.ts +148 -0
  53. package/scripts/harness/world-model/domain/entities/control-declarations.ts +274 -0
  54. package/scripts/harness/world-model/domain/entities/edge.ts +41 -0
  55. package/scripts/harness/world-model/domain/entities/extraction-diagnostic.ts +60 -0
  56. package/scripts/harness/world-model/domain/entities/snapshot.ts +75 -0
  57. package/scripts/harness/world-model/domain/entities/world-node.ts +210 -0
  58. package/scripts/harness/world-model/domain/ports/world-hashing-port.ts +8 -0
  59. package/scripts/harness/world-model/domain/services/canonical-json-serializer.ts +113 -0
  60. package/scripts/harness/world-model/domain/services/constraint-evaluator.ts +486 -0
  61. package/scripts/harness/world-model/domain/services/obligation-derivation-service.ts +199 -0
  62. package/scripts/harness/world-model/domain/services/policy-inputs-digest-deriver.ts +66 -0
  63. package/scripts/harness/world-model/domain/services/snapshot-root-deriver.ts +218 -0
  64. package/scripts/harness/world-model/domain/services/text-content-normalizer.ts +30 -0
  65. package/scripts/harness/world-model/domain/services/violation-fingerprint-deriver.ts +183 -0
  66. package/scripts/harness/world-model/domain/value-objects/artifact-kind.ts +58 -0
  67. package/scripts/harness/world-model/domain/value-objects/change-provenance.ts +137 -0
  68. package/scripts/harness/world-model/domain/value-objects/corpus-role.ts +57 -0
  69. package/scripts/harness/world-model/domain/value-objects/declared-key.ts +35 -0
  70. package/scripts/harness/world-model/domain/value-objects/evaluation-id.ts +41 -0
  71. package/scripts/harness/world-model/domain/value-objects/explicit-constraint-relation.ts +53 -0
  72. package/scripts/harness/world-model/domain/value-objects/explicit-node-alias.ts +30 -0
  73. package/scripts/harness/world-model/domain/value-objects/node-pin.ts +34 -0
  74. package/scripts/harness/world-model/domain/value-objects/path-key.ts +103 -0
  75. package/scripts/harness/world-model/domain/value-objects/sha256-digest.ts +39 -0
  76. package/scripts/harness/world-model/domain/value-objects/violation-fingerprint.ts +36 -0
  77. package/scripts/harness/world-model/domain/value-objects/wcr-rule-id.ts +59 -0
  78. package/scripts/harness/world-model/domain/value-objects/world-node-id.ts +222 -0
  79. package/scripts/harness/world-model/index.ts +22 -0
  80. package/scripts/harness/world-model/infrastructure/adapters/adr-fact-extractor.ts +27 -0
  81. package/scripts/harness/world-model/infrastructure/adapters/assembled-world-fact-source.ts +17 -0
  82. package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +288 -0
  83. package/scripts/harness/world-model/infrastructure/adapters/attestation-sha256-world-hashing-adapter.ts +15 -0
  84. package/scripts/harness/world-model/infrastructure/adapters/composite-design-fact-source.ts +23 -0
  85. package/scripts/harness/world-model/infrastructure/adapters/design-corpus-fact-extractor.ts +285 -0
  86. package/scripts/harness/world-model/infrastructure/adapters/design-fact-extraction.ts +63 -0
  87. package/scripts/harness/world-model/infrastructure/adapters/file-system-obligation-report-writer-adapter.ts +37 -0
  88. package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +330 -0
  89. package/scripts/harness/world-model/infrastructure/adapters/integrity-manifest-fact-extractor.ts +101 -0
  90. package/scripts/harness/world-model/infrastructure/adapters/json-fact-extractor-support.ts +151 -0
  91. package/scripts/harness/world-model/infrastructure/adapters/markdown-design-fact-extractor.ts +493 -0
  92. package/scripts/harness/world-model/infrastructure/adapters/matrix-fact-extractor.ts +283 -0
  93. package/scripts/harness/world-model/infrastructure/adapters/product-fact-extractor.ts +29 -0
  94. package/scripts/harness/world-model/infrastructure/adapters/proposal-fact-extractor.ts +29 -0
  95. package/scripts/harness/world-model/infrastructure/adapters/runtime-fact-extraction.ts +19 -0
  96. package/scripts/harness/world-model/infrastructure/adapters/source-metadata-fact-extractor.ts +22 -0
  97. package/scripts/harness/world-model/infrastructure/adapters/test-reference-source-fact-extractor.ts +22 -0
  98. package/scripts/harness/world-model/infrastructure/adapters/traceability-design-fact-adapter.ts +112 -0
  99. package/scripts/harness/world-model/infrastructure/adapters/traceability-world-read-facade-merger.ts +38 -0
  100. package/scripts/harness/world-model/infrastructure/adapters/type-script-source-fact-extractor.ts +236 -0
  101. package/scripts/harness/world-model/infrastructure/adapters/unit-fact-extractor.ts +71 -0
  102. package/scripts/harness/world-model/infrastructure/adapters/world-control-declaration-mapper.ts +383 -0
  103. package/scripts/harness/world-model/presentation/cli/world-command-support.ts +60 -0
  104. package/scripts/harness/world-model/presentation/cli/world-derive-command-handler.ts +105 -0
  105. package/scripts/harness/world-model/presentation/cli/world-inspect-command-handler.ts +194 -0
  106. package/scripts/harness/world-model/presentation/cli/world-pin-command-handler.ts +100 -0
@@ -0,0 +1,372 @@
1
+ // @unit traceability-model
2
+ // @layer application
3
+ // @work-item-id WI-288
4
+
5
+ import {
6
+ TRACEABILITY_WORLD_READ_SCHEMA_VERSION,
7
+ type TraceabilityAcceptanceCriterionDto,
8
+ type TraceabilityReadDiagnosticDto,
9
+ type TraceabilityStoryDto,
10
+ type TraceabilityTestReferenceDto,
11
+ type TraceabilityUnitDto,
12
+ type TraceabilityWorkItemDto,
13
+ type TraceabilityWorldReadDto,
14
+ } from "../dto/traceability-world-read-dto.js";
15
+ import type {
16
+ RawTraceabilityStory,
17
+ RawTraceabilityTestAnnotation,
18
+ TraceabilityWorldReadSourcePort,
19
+ } from "../ports/traceability-world-read-source-port.js";
20
+
21
+ const STORY_ID_PATTERN = /^H(?:F\d+|\d{2})-\d{2}$/;
22
+ const WORK_ITEM_ID_PATTERN = /^WI-\d+$/;
23
+ const AC_ID_PATTERN = /^AC-(\d+)$/;
24
+
25
+ const compareStrings = (left: string, right: string): number => (left < right ? -1 : left > right ? 1 : 0);
26
+
27
+ const compareAcIds = (left: string, right: string): number => {
28
+ const leftMatch = AC_ID_PATTERN.exec(left);
29
+ const rightMatch = AC_ID_PATTERN.exec(right);
30
+ if (leftMatch && rightMatch) {
31
+ const difference = Number(leftMatch[1]) - Number(rightMatch[1]);
32
+ if (difference !== 0) return difference;
33
+ }
34
+ return compareStrings(left, right);
35
+ };
36
+
37
+ const compareTuples = (
38
+ left: readonly (string | number | null)[],
39
+ right: readonly (string | number | null)[],
40
+ ): number => {
41
+ const length = Math.max(left.length, right.length);
42
+ for (let index = 0; index < length; index += 1) {
43
+ const leftValue = left[index] ?? "";
44
+ const rightValue = right[index] ?? "";
45
+ if (typeof leftValue === "number" && typeof rightValue === "number") {
46
+ if (leftValue !== rightValue) return leftValue - rightValue;
47
+ continue;
48
+ }
49
+ const difference = compareStrings(String(leftValue), String(rightValue));
50
+ if (difference !== 0) return difference;
51
+ }
52
+ return 0;
53
+ };
54
+
55
+ const groupBy = <T>(values: readonly T[], keyOf: (value: T) => string): ReadonlyMap<string, readonly T[]> => {
56
+ const groups = new Map<string, T[]>();
57
+ for (const value of values) {
58
+ const key = keyOf(value);
59
+ const current = groups.get(key) ?? [];
60
+ current.push(value);
61
+ groups.set(key, current);
62
+ }
63
+ return groups;
64
+ };
65
+
66
+ const diagnostic = (
67
+ code: string,
68
+ subjectId: string | null,
69
+ sourcePaths: readonly string[],
70
+ message: string,
71
+ ): TraceabilityReadDiagnosticDto => ({
72
+ code,
73
+ subjectId,
74
+ sourcePaths: [...sourcePaths].sort(compareStrings),
75
+ message,
76
+ });
77
+
78
+ export interface TraceabilityWorldReadFacadeDeps {
79
+ readonly sourcePort: TraceabilityWorldReadSourcePort;
80
+ }
81
+
82
+ export class TraceabilityWorldReadFacade {
83
+ private readonly sourcePort: TraceabilityWorldReadSourcePort;
84
+
85
+ constructor(deps: TraceabilityWorldReadFacadeDeps) {
86
+ this.sourcePort = deps.sourcePort;
87
+ }
88
+
89
+ async read(): Promise<TraceabilityWorldReadDto> {
90
+ const source = await this.sourcePort.read();
91
+ const diagnostics: TraceabilityReadDiagnosticDto[] = source.diagnostics.map((entry) =>
92
+ diagnostic(entry.code, entry.subjectId, entry.sourcePaths, entry.message),
93
+ );
94
+
95
+ const units = this.admitUnits(source.units, diagnostics);
96
+ const admittedStories = this.admitStories(source.stories, diagnostics);
97
+ const { stories, acceptanceCriteria } = this.projectStories(admittedStories, diagnostics);
98
+ const workItems = this.admitWorkItems(source.workItems, diagnostics);
99
+ const testReferences = this.projectTestReferences(
100
+ source.testAnnotations,
101
+ admittedStories,
102
+ acceptanceCriteria,
103
+ diagnostics,
104
+ );
105
+
106
+ return {
107
+ schemaVersion: TRACEABILITY_WORLD_READ_SCHEMA_VERSION,
108
+ units: units.sort((left, right) =>
109
+ compareTuples([left.unitId, left.definitionPath], [right.unitId, right.definitionPath]),
110
+ ),
111
+ stories: stories.sort((left, right) =>
112
+ compareTuples([left.storyId, left.sourcePath], [right.storyId, right.sourcePath]),
113
+ ),
114
+ acceptanceCriteria: acceptanceCriteria.sort((left, right) => {
115
+ const storyDifference = compareStrings(left.storyId, right.storyId);
116
+ if (storyDifference !== 0) return storyDifference;
117
+ const acDifference = compareAcIds(left.acId, right.acId);
118
+ if (acDifference !== 0) return acDifference;
119
+ return compareStrings(left.sourcePath, right.sourcePath);
120
+ }),
121
+ workItems: workItems.sort((left, right) =>
122
+ compareTuples([left.workItemId, left.descriptionPath], [right.workItemId, right.descriptionPath]),
123
+ ),
124
+ testReferences: testReferences.sort((left, right) => {
125
+ const storyDifference = compareStrings(left.storyId, right.storyId);
126
+ if (storyDifference !== 0) return storyDifference;
127
+ const acDifference = compareAcIds(left.acId, right.acId);
128
+ if (acDifference !== 0) return acDifference;
129
+ return compareTuples(
130
+ [left.binding, left.testType, left.filePath, left.testName],
131
+ [right.binding, right.testType, right.filePath, right.testName],
132
+ );
133
+ }),
134
+ diagnostics: diagnostics.sort((left, right) =>
135
+ compareTuples(
136
+ [left.code, left.subjectId, left.sourcePaths.join("\u0000")],
137
+ [right.code, right.subjectId, right.sourcePaths.join("\u0000")],
138
+ ),
139
+ ),
140
+ };
141
+ }
142
+
143
+ private admitUnits(
144
+ rawUnits: Awaited<ReturnType<TraceabilityWorldReadSourcePort["read"]>>["units"],
145
+ diagnostics: TraceabilityReadDiagnosticDto[],
146
+ ): TraceabilityUnitDto[] {
147
+ const result: TraceabilityUnitDto[] = [];
148
+ for (const [unitId, candidates] of groupBy(rawUnits, (entry) => entry.unitId)) {
149
+ if (candidates.length !== 1) {
150
+ diagnostics.push(
151
+ diagnostic(
152
+ "TM-WORLD-READ-DUPLICATE-UNIT",
153
+ unitId,
154
+ candidates.map((entry) => entry.definitionPath),
155
+ `Unit ID ${unitId} has ${candidates.length} owners`,
156
+ ),
157
+ );
158
+ continue;
159
+ }
160
+ const candidate = candidates[0];
161
+ result.push({
162
+ unitId: candidate.unitId,
163
+ definitionPath: candidate.definitionPath,
164
+ constructionRoot: candidate.constructionRoot,
165
+ });
166
+ }
167
+ return result;
168
+ }
169
+
170
+ private admitStories(
171
+ rawStories: readonly RawTraceabilityStory[],
172
+ diagnostics: TraceabilityReadDiagnosticDto[],
173
+ ): readonly RawTraceabilityStory[] {
174
+ const result: RawTraceabilityStory[] = [];
175
+ for (const [storyId, candidates] of groupBy(rawStories, (entry) => entry.storyId)) {
176
+ if (!STORY_ID_PATTERN.test(storyId)) {
177
+ diagnostics.push(
178
+ diagnostic(
179
+ "TM-WORLD-READ-MALFORMED-STORY-ID",
180
+ storyId,
181
+ candidates.map((entry) => entry.sourcePath),
182
+ `Story ID is not canonical: ${storyId}`,
183
+ ),
184
+ );
185
+ continue;
186
+ }
187
+ if (candidates.length !== 1) {
188
+ diagnostics.push(
189
+ diagnostic(
190
+ "TM-WORLD-READ-DUPLICATE-STORY",
191
+ storyId,
192
+ candidates.map((entry) => entry.sourcePath),
193
+ `Story ID ${storyId} has ${candidates.length} owners`,
194
+ ),
195
+ );
196
+ continue;
197
+ }
198
+ result.push(candidates[0]);
199
+ }
200
+ return result;
201
+ }
202
+
203
+ private projectStories(
204
+ rawStories: readonly RawTraceabilityStory[],
205
+ diagnostics: TraceabilityReadDiagnosticDto[],
206
+ ): {
207
+ readonly stories: TraceabilityStoryDto[];
208
+ readonly acceptanceCriteria: TraceabilityAcceptanceCriterionDto[];
209
+ } {
210
+ const stories: TraceabilityStoryDto[] = [];
211
+ const acceptanceCriteria: TraceabilityAcceptanceCriterionDto[] = [];
212
+ for (const story of rawStories) {
213
+ stories.push({
214
+ storyId: story.storyId,
215
+ legacyIds: [...new Set(story.legacyIds)].sort(compareStrings),
216
+ sourcePath: story.sourcePath,
217
+ line: story.line,
218
+ });
219
+ for (const [acId, candidates] of groupBy(story.acceptanceCriteria, (entry) => entry.acId)) {
220
+ if (!AC_ID_PATTERN.test(acId)) {
221
+ diagnostics.push(
222
+ diagnostic(
223
+ "TM-WORLD-READ-MALFORMED-AC-ID",
224
+ `${story.storyId}:${acId}`,
225
+ [story.sourcePath],
226
+ `Acceptance Criterion ID is not canonical: ${acId}`,
227
+ ),
228
+ );
229
+ continue;
230
+ }
231
+ if (candidates.length !== 1) {
232
+ diagnostics.push(
233
+ diagnostic(
234
+ "TM-WORLD-READ-DUPLICATE-AC",
235
+ `${story.storyId}:${acId}`,
236
+ [story.sourcePath],
237
+ `Acceptance Criterion ${story.storyId}:${acId} has ${candidates.length} owners`,
238
+ ),
239
+ );
240
+ continue;
241
+ }
242
+ acceptanceCriteria.push({
243
+ storyId: story.storyId,
244
+ acId,
245
+ sourcePath: story.sourcePath,
246
+ line: candidates[0].line,
247
+ });
248
+ }
249
+ }
250
+ return { stories, acceptanceCriteria };
251
+ }
252
+
253
+ private admitWorkItems(
254
+ rawWorkItems: Awaited<ReturnType<TraceabilityWorldReadSourcePort["read"]>>["workItems"],
255
+ diagnostics: TraceabilityReadDiagnosticDto[],
256
+ ): TraceabilityWorkItemDto[] {
257
+ const candidatesById = groupBy(rawWorkItems, (entry) => entry.workItemId);
258
+ const result: TraceabilityWorkItemDto[] = [];
259
+ for (const [workItemId, candidates] of candidatesById) {
260
+ if (!WORK_ITEM_ID_PATTERN.test(workItemId)) {
261
+ diagnostics.push(
262
+ diagnostic(
263
+ "TM-WORLD-READ-MALFORMED-WORK-ITEM-ID",
264
+ workItemId,
265
+ candidates.map((entry) => entry.descriptionPath),
266
+ `WorkItem ID is not canonical: ${workItemId}`,
267
+ ),
268
+ );
269
+ continue;
270
+ }
271
+ const matchingCandidates = candidates.filter((entry) => entry.directoryId === entry.workItemId);
272
+ for (const candidate of candidates) {
273
+ if (candidate.directoryId !== candidate.workItemId) {
274
+ diagnostics.push(
275
+ diagnostic(
276
+ "TM-WORLD-READ-WORK-ITEM-ID-MISMATCH",
277
+ workItemId,
278
+ [candidate.descriptionPath],
279
+ `WorkItem directory ${candidate.directoryId} does not match frontmatter ${candidate.workItemId}`,
280
+ ),
281
+ );
282
+ }
283
+ }
284
+ if (matchingCandidates.length === 0) continue;
285
+ if (candidates.length !== 1) {
286
+ diagnostics.push(
287
+ diagnostic(
288
+ "TM-WORLD-READ-DUPLICATE-WORK-ITEM",
289
+ workItemId,
290
+ candidates.map((entry) => entry.descriptionPath),
291
+ `WorkItem ID ${workItemId} has ${candidates.length} owners`,
292
+ ),
293
+ );
294
+ continue;
295
+ }
296
+ const candidate = matchingCandidates[0];
297
+ result.push({
298
+ workItemId,
299
+ legacyIds: candidate.legacyId ? [candidate.legacyId] : [],
300
+ type: candidate.type,
301
+ severity: candidate.severity,
302
+ status: candidate.status,
303
+ affects: [...new Set(candidate.affects)].sort(compareStrings),
304
+ descriptionPath: candidate.descriptionPath,
305
+ });
306
+ }
307
+ return result;
308
+ }
309
+
310
+ private projectTestReferences(
311
+ annotations: readonly RawTraceabilityTestAnnotation[],
312
+ stories: readonly RawTraceabilityStory[],
313
+ acceptanceCriteria: readonly TraceabilityAcceptanceCriterionDto[],
314
+ diagnostics: TraceabilityReadDiagnosticDto[],
315
+ ): TraceabilityTestReferenceDto[] {
316
+ const knownStories = new Map(stories.map((story) => [story.storyId, story]));
317
+ const criteriaByStory = groupBy(acceptanceCriteria, (entry) => entry.storyId);
318
+ const candidates: TraceabilityTestReferenceDto[] = [];
319
+
320
+ for (const annotation of annotations) {
321
+ if (!knownStories.has(annotation.storyId)) {
322
+ diagnostics.push(
323
+ diagnostic(
324
+ "TM-WORLD-READ-UNKNOWN-TEST-STORY",
325
+ annotation.storyId,
326
+ [annotation.filePath],
327
+ `Test annotation references unknown Story ${annotation.storyId}`,
328
+ ),
329
+ );
330
+ continue;
331
+ }
332
+ for (const criterion of criteriaByStory.get(annotation.storyId) ?? []) {
333
+ candidates.push({
334
+ storyId: annotation.storyId,
335
+ acId: criterion.acId,
336
+ binding: "file",
337
+ testType: annotation.testType,
338
+ filePath: annotation.filePath,
339
+ testName: null,
340
+ provenance: [
341
+ {
342
+ sourcePath: annotation.filePath,
343
+ line: annotation.line,
344
+ },
345
+ ],
346
+ });
347
+ }
348
+ }
349
+
350
+ const result: TraceabilityTestReferenceDto[] = [];
351
+ for (const [identity, references] of groupBy(candidates, (entry) =>
352
+ [entry.storyId, entry.acId, entry.binding, entry.testType, entry.filePath, entry.testName ?? "none"].join(
353
+ "\u0000",
354
+ ),
355
+ )) {
356
+ if (references.length !== 1) {
357
+ const first = references[0];
358
+ diagnostics.push(
359
+ diagnostic(
360
+ "TM-WORLD-READ-DUPLICATE-TEST-REFERENCE",
361
+ `${first.storyId}:${first.acId}`,
362
+ references.map((entry) => entry.filePath),
363
+ `TestReference identity ${identity} has ${references.length} owners`,
364
+ ),
365
+ );
366
+ continue;
367
+ }
368
+ result.push(references[0]);
369
+ }
370
+ return result;
371
+ }
372
+ }
@@ -0,0 +1,61 @@
1
+ // @unit traceability-model
2
+ // @layer application
3
+ // @work-item-id WI-288
4
+
5
+ import type { TraceabilityTestType } from "../dto/traceability-world-read-dto.js";
6
+
7
+ export interface RawTraceabilityUnit {
8
+ readonly unitId: string;
9
+ readonly definitionPath: string;
10
+ readonly constructionRoot: string | null;
11
+ }
12
+
13
+ export interface RawTraceabilityAcceptanceCriterion {
14
+ readonly acId: string;
15
+ readonly line: number;
16
+ }
17
+
18
+ export interface RawTraceabilityStory {
19
+ readonly storyId: string;
20
+ readonly legacyIds: readonly string[];
21
+ readonly sourcePath: string;
22
+ readonly line: number;
23
+ readonly acceptanceCriteria: readonly RawTraceabilityAcceptanceCriterion[];
24
+ }
25
+
26
+ export interface RawTraceabilityWorkItem {
27
+ readonly directoryId: string;
28
+ readonly workItemId: string;
29
+ readonly legacyId: string | null;
30
+ readonly type: string;
31
+ readonly severity: string | null;
32
+ readonly status: string | null;
33
+ readonly affects: readonly string[];
34
+ readonly descriptionPath: string;
35
+ }
36
+
37
+ export interface RawTraceabilityTestAnnotation {
38
+ readonly storyId: string;
39
+ readonly filePath: string;
40
+ readonly line: number;
41
+ readonly testType: TraceabilityTestType;
42
+ }
43
+
44
+ export interface RawTraceabilityDiagnostic {
45
+ readonly code: string;
46
+ readonly subjectId: string | null;
47
+ readonly sourcePaths: readonly string[];
48
+ readonly message: string;
49
+ }
50
+
51
+ export interface TraceabilityWorldReadSourceDto {
52
+ readonly units: readonly RawTraceabilityUnit[];
53
+ readonly stories: readonly RawTraceabilityStory[];
54
+ readonly workItems: readonly RawTraceabilityWorkItem[];
55
+ readonly testAnnotations: readonly RawTraceabilityTestAnnotation[];
56
+ readonly diagnostics: readonly RawTraceabilityDiagnostic[];
57
+ }
58
+
59
+ export interface TraceabilityWorldReadSourcePort {
60
+ read(): Promise<TraceabilityWorldReadSourceDto>;
61
+ }
@@ -7,6 +7,7 @@
7
7
  * 全コンポーネントを生成・配線し、外部に公開するハンドラー群を返す。
8
8
  */
9
9
 
10
+ import { TraceabilityWorldReadFacade } from "./application/facades/traceability-world-read-facade.js";
10
11
  import { ApplyWorkItemMigrationUseCase } from "./application/usecases/apply-work-item-migration-usecase.js";
11
12
  import { ApplyWorkItemStatusUseCase } from "./application/usecases/apply-work-item-status-usecase.js";
12
13
  import { DeriveWorkItemStatusUseCase } from "./application/usecases/derive-work-item-status-usecase.js";
@@ -19,11 +20,12 @@ import { StoryIdAliasResolver } from "./domain/services/story-id-alias-resolver.
19
20
  import { TraceabilityChainBuilder } from "./domain/services/traceability-chain-builder.js";
20
21
  import { WorkItemStatusDerivationService } from "./domain/services/work-item-status-derivation-service.js";
21
22
  import { ProjectRelativePath } from "./domain/value-objects/project-relative-path.js";
23
+ import { FileSystemTraceabilityWorldReadAdapter } from "./infrastructure/adapters/file-system-traceability-world-read-adapter.js";
22
24
  import { FileSystemInceptionPlanGateway } from "./infrastructure/gateways/file-system-inception-plan-gateway.js";
23
25
  import { FileSystemMetadataReader } from "./infrastructure/gateways/file-system-metadata-reader.js";
26
+ import { FileSystemWorkItemIdentityGateway } from "./infrastructure/gateways/file-system-work-item-identity-gateway.js";
24
27
  import { FileSystemWorkItemMigrationApplyGateway } from "./infrastructure/gateways/file-system-work-item-migration-apply-gateway.js";
25
28
  import { FileSystemWorkItemMigrationSourceGateway } from "./infrastructure/gateways/file-system-work-item-migration-source-gateway.js";
26
- import { FileSystemWorkItemIdentityGateway } from "./infrastructure/gateways/file-system-work-item-identity-gateway.js";
27
29
  import { FileSystemWorkItemStatusGateway } from "./infrastructure/gateways/file-system-work-item-status-gateway.js";
28
30
  import { MarkdownDesignDocumentGateway } from "./infrastructure/gateways/markdown-design-document-gateway.js";
29
31
  import { MarkdownStoryCatalogGateway } from "./infrastructure/gateways/markdown-story-catalog-gateway.js";
@@ -34,6 +36,8 @@ import { WorkItemStatusCommandHandler } from "./presentation/cli/work-item-statu
34
36
 
35
37
  export interface TraceabilityModelPathRoots {
36
38
  readonly designDocsRoot: string;
39
+ readonly inceptionRoot?: string;
40
+ readonly testRoots?: readonly string[];
37
41
  }
38
42
 
39
43
  export interface TraceabilityModelModuleOptions {
@@ -48,13 +52,12 @@ const deriveProductDocsRoot = (designDocsRoot: string): string => {
48
52
  return normalized;
49
53
  };
50
54
 
51
- export function createTraceabilityModelModule(
52
- rootDir: string,
53
- options: TraceabilityModelModuleOptions = {},
54
- ) {
55
+ export function createTraceabilityModelModule(rootDir: string, options: TraceabilityModelModuleOptions = {}) {
55
56
  const designDocsRoot = options.pathRoots?.designDocsRoot ?? "docs/product/construction";
56
57
  const productDocsRoot = deriveProductDocsRoot(designDocsRoot);
57
58
  const storyCatalogPath = `${productDocsRoot}/user_stories.md`;
59
+ const inceptionRoot = options.pathRoots?.inceptionRoot ?? "docs/inception";
60
+ const testRoots = options.pathRoots?.testRoots ?? ["scripts/harness/__tests__"];
58
61
 
59
62
  // Infrastructure gateways
60
63
  const storyCatalog = new MarkdownStoryCatalogGateway({ rootDir, storyCatalogPath });
@@ -70,6 +73,15 @@ export function createTraceabilityModelModule(
70
73
  const workItemMigrationApply = new FileSystemWorkItemMigrationApplyGateway({ rootDir });
71
74
  const workItemIdentity = new FileSystemWorkItemIdentityGateway({ rootDir });
72
75
  const workItemStatus = new FileSystemWorkItemStatusGateway({ rootDir });
76
+ const worldReadSource = new FileSystemTraceabilityWorldReadAdapter({
77
+ rootDir,
78
+ productDocsRoot,
79
+ designDocsRoot,
80
+ storyCatalogPath,
81
+ inceptionRoot,
82
+ testRoots,
83
+ unitDefinitionGateway: unitDefinition,
84
+ });
73
85
 
74
86
  // Domain services
75
87
  const metadataValidator = new MetadataValidator({
@@ -86,6 +98,9 @@ export function createTraceabilityModelModule(
86
98
  });
87
99
  const storyIdAliasResolver = new StoryIdAliasResolver(storyCatalog);
88
100
  const workItemStatusDerivationService = new WorkItemStatusDerivationService();
101
+ const worldReadFacade = new TraceabilityWorldReadFacade({
102
+ sourcePort: worldReadSource,
103
+ });
89
104
 
90
105
  // Usecases
91
106
  const validateImplementationMetadataUseCase = new ValidateImplementationMetadataUseCase({
@@ -141,5 +156,6 @@ export function createTraceabilityModelModule(
141
156
  storyCatalog,
142
157
  traceabilityChainBuilder,
143
158
  storyIdAliasResolver,
159
+ worldReadFacade,
144
160
  } as const;
145
161
  }
@@ -5,12 +5,23 @@
5
5
  * traceability-model ユニットのバレルエクスポート。
6
6
  */
7
7
 
8
+ export type {
9
+ TraceabilityAcceptanceCriterionDto,
10
+ TraceabilityReadDiagnosticDto,
11
+ TraceabilityStoryDto,
12
+ TraceabilityTestReferenceDto,
13
+ TraceabilityTestType,
14
+ TraceabilityUnitDto,
15
+ TraceabilityWorkItemDto,
16
+ TraceabilityWorldReadDto,
17
+ } from "./application/dto/traceability-world-read-dto.js";
18
+ export { TRACEABILITY_WORLD_READ_SCHEMA_VERSION } from "./application/dto/traceability-world-read-dto.js";
19
+ // Application — public World read facade and plain DTO contract
20
+ export { TraceabilityWorldReadFacade } from "./application/facades/traceability-world-read-facade.js";
8
21
  // Composition Root
9
- export { createTraceabilityModelModule } from './composition-root.js';
10
-
22
+ export { createTraceabilityModelModule } from "./composition-root.js";
11
23
  // Domain — value objects
12
- export { ProjectRelativePath } from './domain/value-objects/project-relative-path.js';
13
- export { StoryId } from './domain/value-objects/story-id.js';
14
-
24
+ export { ProjectRelativePath } from "./domain/value-objects/project-relative-path.js";
25
+ export { StoryId } from "./domain/value-objects/story-id.js";
15
26
  // Presentation — CLI handlers
16
- export { ValidateMetadataCommandHandler } from './presentation/cli/validate-metadata-command-handler.js';
27
+ export { ValidateMetadataCommandHandler } from "./presentation/cli/validate-metadata-command-handler.js";