phasegate 0.222.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 (108) 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/001-l1-biome-editor-time-enforcement.md +2 -0
  5. package/docs/ADR/015-architecture-preset.md +2 -0
  6. package/docs/ADR/031-world-model-ownership-and-corpus-lifecycle.md +155 -0
  7. package/docs/ADR/032-world-node-identity.md +198 -0
  8. package/docs/ADR/033-world-snapshot-canonicalization.md +246 -0
  9. package/docs/ADR/034-world-constraint-semantics.md +218 -0
  10. package/docs/ADR/035-world-adoption-baseline-and-waiver.md +341 -0
  11. package/docs/ADR/036-world-model-and-doc-freshness.md +169 -0
  12. package/docs/ADR/037-world-cli-and-output-contract.md +398 -0
  13. package/docs/contracts/requirement-test-matrix.schema.json +15 -0
  14. package/docs/contracts/world-baseline.schema.json +35 -0
  15. package/docs/contracts/world-constraints.schema.json +56 -0
  16. package/docs/contracts/world-debts.schema.json +32 -0
  17. package/docs/contracts/world-obligation-report.schema.json +259 -0
  18. package/docs/contracts/world-waivers.schema.json +32 -0
  19. package/docs/guide/cli-reference.md +34 -0
  20. package/package.json +1 -1
  21. package/scripts/harness/attestation/application/ports/sha256-capability.ts +25 -0
  22. package/scripts/harness/attestation/composition-root.ts +14 -4
  23. package/scripts/harness/attestation/index.ts +6 -1
  24. package/scripts/harness/attestation/infrastructure/adapters/node-crypto-content-hasher-adapter.ts +6 -6
  25. package/scripts/harness/attestation/infrastructure/adapters/node-crypto-sha256-capability.ts +19 -0
  26. package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +4 -1
  27. package/scripts/harness/main.ts +107 -1
  28. package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +11 -0
  29. package/scripts/harness/nyquist-validation/application/usecases/check-ac-coverage-gate-usecase.ts +6 -0
  30. package/scripts/harness/nyquist-validation/application/usecases/generate-requirement-test-matrix-usecase.ts +8 -4
  31. package/scripts/harness/nyquist-validation/domain/entities/story-mapping.ts +29 -2
  32. package/scripts/harness/nyquist-validation/domain/services/ac-coverage-gate-policy.ts +28 -0
  33. package/scripts/harness/nyquist-validation/infrastructure/adapters/markdown-requirement-source-adapter.ts +71 -4
  34. package/scripts/harness/nyquist-validation/infrastructure/schema/matrix-schema-loader.ts +4 -0
  35. package/scripts/harness/traceability-model/application/dto/traceability-world-read-dto.ts +67 -0
  36. package/scripts/harness/traceability-model/application/facades/traceability-world-read-facade.ts +372 -0
  37. package/scripts/harness/traceability-model/application/ports/traceability-world-read-source-port.ts +61 -0
  38. package/scripts/harness/traceability-model/composition-root.ts +21 -5
  39. package/scripts/harness/traceability-model/index.ts +17 -6
  40. package/scripts/harness/traceability-model/infrastructure/adapters/file-system-traceability-world-read-adapter.ts +223 -0
  41. package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +86 -8
  42. package/scripts/harness/world-model/application/dto/world-inspection-dto.ts +43 -0
  43. package/scripts/harness/world-model/application/dto/world-obligation-report-dto.ts +54 -0
  44. package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +62 -0
  45. package/scripts/harness/world-model/application/ports/obligation-report-writer-port.ts +7 -0
  46. package/scripts/harness/world-model/application/ports/world-control-declaration-repository-port.ts +57 -0
  47. package/scripts/harness/world-model/application/ports/world-fact-source-port.ts +17 -0
  48. package/scripts/harness/world-model/application/usecases/build-snapshot-use-case.ts +111 -0
  49. package/scripts/harness/world-model/application/usecases/derive-obligations-use-case.ts +155 -0
  50. package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +144 -0
  51. package/scripts/harness/world-model/application/usecases/inspect-world-use-case.ts +74 -0
  52. package/scripts/harness/world-model/application/usecases/pin-constraint-endpoint-use-case.ts +139 -0
  53. package/scripts/harness/world-model/composition-root.ts +218 -0
  54. package/scripts/harness/world-model/domain/entities/constraint-record.ts +148 -0
  55. package/scripts/harness/world-model/domain/entities/control-declarations.ts +274 -0
  56. package/scripts/harness/world-model/domain/entities/edge.ts +41 -0
  57. package/scripts/harness/world-model/domain/entities/extraction-diagnostic.ts +60 -0
  58. package/scripts/harness/world-model/domain/entities/snapshot.ts +75 -0
  59. package/scripts/harness/world-model/domain/entities/world-node.ts +210 -0
  60. package/scripts/harness/world-model/domain/ports/world-hashing-port.ts +8 -0
  61. package/scripts/harness/world-model/domain/services/canonical-json-serializer.ts +113 -0
  62. package/scripts/harness/world-model/domain/services/constraint-evaluator.ts +486 -0
  63. package/scripts/harness/world-model/domain/services/obligation-derivation-service.ts +199 -0
  64. package/scripts/harness/world-model/domain/services/policy-inputs-digest-deriver.ts +66 -0
  65. package/scripts/harness/world-model/domain/services/snapshot-root-deriver.ts +218 -0
  66. package/scripts/harness/world-model/domain/services/text-content-normalizer.ts +30 -0
  67. package/scripts/harness/world-model/domain/services/violation-fingerprint-deriver.ts +183 -0
  68. package/scripts/harness/world-model/domain/value-objects/artifact-kind.ts +58 -0
  69. package/scripts/harness/world-model/domain/value-objects/change-provenance.ts +137 -0
  70. package/scripts/harness/world-model/domain/value-objects/corpus-role.ts +57 -0
  71. package/scripts/harness/world-model/domain/value-objects/declared-key.ts +35 -0
  72. package/scripts/harness/world-model/domain/value-objects/evaluation-id.ts +41 -0
  73. package/scripts/harness/world-model/domain/value-objects/explicit-constraint-relation.ts +53 -0
  74. package/scripts/harness/world-model/domain/value-objects/explicit-node-alias.ts +30 -0
  75. package/scripts/harness/world-model/domain/value-objects/node-pin.ts +34 -0
  76. package/scripts/harness/world-model/domain/value-objects/path-key.ts +103 -0
  77. package/scripts/harness/world-model/domain/value-objects/sha256-digest.ts +39 -0
  78. package/scripts/harness/world-model/domain/value-objects/violation-fingerprint.ts +36 -0
  79. package/scripts/harness/world-model/domain/value-objects/wcr-rule-id.ts +59 -0
  80. package/scripts/harness/world-model/domain/value-objects/world-node-id.ts +222 -0
  81. package/scripts/harness/world-model/index.ts +22 -0
  82. package/scripts/harness/world-model/infrastructure/adapters/adr-fact-extractor.ts +27 -0
  83. package/scripts/harness/world-model/infrastructure/adapters/assembled-world-fact-source.ts +17 -0
  84. package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +288 -0
  85. package/scripts/harness/world-model/infrastructure/adapters/attestation-sha256-world-hashing-adapter.ts +15 -0
  86. package/scripts/harness/world-model/infrastructure/adapters/composite-design-fact-source.ts +23 -0
  87. package/scripts/harness/world-model/infrastructure/adapters/design-corpus-fact-extractor.ts +285 -0
  88. package/scripts/harness/world-model/infrastructure/adapters/design-fact-extraction.ts +63 -0
  89. package/scripts/harness/world-model/infrastructure/adapters/file-system-obligation-report-writer-adapter.ts +37 -0
  90. package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +330 -0
  91. package/scripts/harness/world-model/infrastructure/adapters/integrity-manifest-fact-extractor.ts +101 -0
  92. package/scripts/harness/world-model/infrastructure/adapters/json-fact-extractor-support.ts +151 -0
  93. package/scripts/harness/world-model/infrastructure/adapters/markdown-design-fact-extractor.ts +493 -0
  94. package/scripts/harness/world-model/infrastructure/adapters/matrix-fact-extractor.ts +283 -0
  95. package/scripts/harness/world-model/infrastructure/adapters/product-fact-extractor.ts +29 -0
  96. package/scripts/harness/world-model/infrastructure/adapters/proposal-fact-extractor.ts +29 -0
  97. package/scripts/harness/world-model/infrastructure/adapters/runtime-fact-extraction.ts +19 -0
  98. package/scripts/harness/world-model/infrastructure/adapters/source-metadata-fact-extractor.ts +22 -0
  99. package/scripts/harness/world-model/infrastructure/adapters/test-reference-source-fact-extractor.ts +22 -0
  100. package/scripts/harness/world-model/infrastructure/adapters/traceability-design-fact-adapter.ts +112 -0
  101. package/scripts/harness/world-model/infrastructure/adapters/traceability-world-read-facade-merger.ts +38 -0
  102. package/scripts/harness/world-model/infrastructure/adapters/type-script-source-fact-extractor.ts +236 -0
  103. package/scripts/harness/world-model/infrastructure/adapters/unit-fact-extractor.ts +71 -0
  104. package/scripts/harness/world-model/infrastructure/adapters/world-control-declaration-mapper.ts +383 -0
  105. package/scripts/harness/world-model/presentation/cli/world-command-support.ts +60 -0
  106. package/scripts/harness/world-model/presentation/cli/world-derive-command-handler.ts +105 -0
  107. package/scripts/harness/world-model/presentation/cli/world-inspect-command-handler.ts +194 -0
  108. package/scripts/harness/world-model/presentation/cli/world-pin-command-handler.ts +100 -0
@@ -0,0 +1,223 @@
1
+ // @unit traceability-model
2
+ // @layer infrastructure
3
+ // @work-item-id WI-288
4
+
5
+ import { readdir, readFile } from "node:fs/promises";
6
+ import path from "node:path";
7
+ import type {
8
+ RawTraceabilityDiagnostic,
9
+ RawTraceabilityStory,
10
+ RawTraceabilityTestAnnotation,
11
+ RawTraceabilityUnit,
12
+ RawTraceabilityWorkItem,
13
+ TraceabilityWorldReadSourceDto,
14
+ TraceabilityWorldReadSourcePort,
15
+ } from "../../application/ports/traceability-world-read-source-port.js";
16
+ import type { MarkdownUnitDefinitionGateway } from "../gateways/markdown-unit-definition-gateway.js";
17
+ import { parseTestTags } from "../parsers/source-metadata-parser.js";
18
+ import { parseStoryCatalogEntries } from "../parsers/story-catalog-parser.js";
19
+ import { parseWorkItemFrontmatter } from "../parsers/work-item-frontmatter-parser.js";
20
+
21
+ const compareStrings = (left: string, right: string): number => (left < right ? -1 : left > right ? 1 : 0);
22
+
23
+ const toProjectRelativePath = (rootDir: string, absolutePath: string): string =>
24
+ path.relative(rootDir, absolutePath).split(path.sep).join("/");
25
+
26
+ const collectFiles = async (rootDir: string, accept: (fileName: string) => boolean): Promise<readonly string[]> => {
27
+ const result: string[] = [];
28
+ const visit = async (directory: string): Promise<void> => {
29
+ const entries = await readdir(directory, { withFileTypes: true });
30
+ entries.sort((left, right) => compareStrings(left.name, right.name));
31
+ for (const entry of entries) {
32
+ const absolutePath = path.join(directory, entry.name);
33
+ if (entry.isDirectory()) {
34
+ await visit(absolutePath);
35
+ } else if (entry.isFile() && accept(entry.name)) {
36
+ result.push(absolutePath);
37
+ }
38
+ }
39
+ };
40
+ await visit(rootDir);
41
+ return result;
42
+ };
43
+
44
+ const sourceDiagnostic = (
45
+ code: string,
46
+ subjectId: string | null,
47
+ sourcePaths: readonly string[],
48
+ error: unknown,
49
+ ): RawTraceabilityDiagnostic => ({
50
+ code,
51
+ subjectId,
52
+ sourcePaths,
53
+ message: error instanceof Error ? error.message : String(error),
54
+ });
55
+
56
+ const classifyTestType = (filePath: string): RawTraceabilityTestAnnotation["testType"] => {
57
+ if (filePath.includes("/e2e/") || filePath.includes("/scenario/")) {
58
+ return "scenario";
59
+ }
60
+ if (filePath.includes("/integration/") || /\.it\.test\.[cm]?[jt]sx?$/.test(filePath)) {
61
+ return "it";
62
+ }
63
+ return "unit";
64
+ };
65
+
66
+ export interface FileSystemTraceabilityWorldReadAdapterDeps {
67
+ readonly rootDir: string;
68
+ readonly productDocsRoot: string;
69
+ readonly designDocsRoot: string;
70
+ readonly storyCatalogPath: string;
71
+ readonly inceptionRoot: string;
72
+ readonly testRoots: readonly string[];
73
+ readonly unitDefinitionGateway: MarkdownUnitDefinitionGateway;
74
+ }
75
+
76
+ export class FileSystemTraceabilityWorldReadAdapter implements TraceabilityWorldReadSourcePort {
77
+ private readonly deps: FileSystemTraceabilityWorldReadAdapterDeps;
78
+
79
+ constructor(deps: FileSystemTraceabilityWorldReadAdapterDeps) {
80
+ this.deps = deps;
81
+ }
82
+
83
+ async read(): Promise<TraceabilityWorldReadSourceDto> {
84
+ const diagnostics: RawTraceabilityDiagnostic[] = [];
85
+ const [units, stories, workItems, testAnnotations] = await Promise.all([
86
+ this.readUnits(diagnostics),
87
+ this.readStories(diagnostics),
88
+ this.readWorkItems(diagnostics),
89
+ this.readTestAnnotations(diagnostics),
90
+ ]);
91
+ return {
92
+ units,
93
+ stories,
94
+ workItems,
95
+ testAnnotations,
96
+ diagnostics,
97
+ };
98
+ }
99
+
100
+ private async readUnits(diagnostics: RawTraceabilityDiagnostic[]): Promise<readonly RawTraceabilityUnit[]> {
101
+ try {
102
+ const unitIds = await this.deps.unitDefinitionGateway.getAllUnitNames();
103
+ const result: RawTraceabilityUnit[] = [];
104
+ for (const unitId of unitIds) {
105
+ const constructionRoot = await this.deps.unitDefinitionGateway.findConstructionRoot(unitId);
106
+ result.push({
107
+ unitId,
108
+ definitionPath: path.posix.join(this.deps.productDocsRoot, "units", `${unitId}_unit.md`),
109
+ constructionRoot: constructionRoot?.value ?? null,
110
+ });
111
+ }
112
+ return result;
113
+ } catch (error) {
114
+ diagnostics.push(
115
+ sourceDiagnostic(
116
+ "TM-WORLD-READ-UNIT-SOURCE-ERROR",
117
+ null,
118
+ [path.posix.join(this.deps.productDocsRoot, "units")],
119
+ error,
120
+ ),
121
+ );
122
+ return [];
123
+ }
124
+ }
125
+
126
+ private async readStories(diagnostics: RawTraceabilityDiagnostic[]): Promise<readonly RawTraceabilityStory[]> {
127
+ try {
128
+ const absolutePath = path.join(this.deps.rootDir, this.deps.storyCatalogPath);
129
+ const content = await readFile(absolutePath, "utf8");
130
+ return parseStoryCatalogEntries(content).map((entry) => ({
131
+ storyId: entry.storyId,
132
+ legacyIds: [...entry.legacyIds],
133
+ sourcePath: this.deps.storyCatalogPath,
134
+ line: entry.lineNumber,
135
+ acceptanceCriteria: entry.acceptanceCriteria.map((criterion) => ({
136
+ acId: criterion.acId,
137
+ line: criterion.lineNumber,
138
+ })),
139
+ }));
140
+ } catch (error) {
141
+ diagnostics.push(sourceDiagnostic("TM-WORLD-READ-STORY-SOURCE-ERROR", null, [this.deps.storyCatalogPath], error));
142
+ return [];
143
+ }
144
+ }
145
+
146
+ private async readWorkItems(diagnostics: RawTraceabilityDiagnostic[]): Promise<readonly RawTraceabilityWorkItem[]> {
147
+ const absoluteRoot = path.join(this.deps.rootDir, this.deps.inceptionRoot);
148
+ let files: readonly string[];
149
+ try {
150
+ files = await collectFiles(absoluteRoot, (fileName) => fileName === "description.md");
151
+ } catch (error) {
152
+ diagnostics.push(
153
+ sourceDiagnostic("TM-WORLD-READ-WORK-ITEM-SOURCE-ERROR", null, [this.deps.inceptionRoot], error),
154
+ );
155
+ return [];
156
+ }
157
+
158
+ const result: RawTraceabilityWorkItem[] = [];
159
+ for (const absolutePath of files) {
160
+ const directoryId = path.basename(path.dirname(absolutePath));
161
+ if (!/^WI-/.test(directoryId)) continue;
162
+ const relativePath = toProjectRelativePath(this.deps.rootDir, absolutePath);
163
+ try {
164
+ const frontmatter = parseWorkItemFrontmatter(await readFile(absolutePath, "utf8"));
165
+ if (!frontmatter) {
166
+ diagnostics.push({
167
+ code: "TM-WORLD-READ-MISSING-WORK-ITEM-FRONTMATTER",
168
+ subjectId: directoryId,
169
+ sourcePaths: [relativePath],
170
+ message: `WorkItem ${directoryId} has no readable frontmatter`,
171
+ });
172
+ continue;
173
+ }
174
+ result.push({
175
+ directoryId,
176
+ workItemId: frontmatter.id,
177
+ legacyId: frontmatter.legacyId ?? null,
178
+ type: frontmatter.type,
179
+ severity: frontmatter.severity ?? null,
180
+ status: frontmatter.status ?? null,
181
+ affects: [...(frontmatter.affects ?? [])],
182
+ descriptionPath: relativePath,
183
+ });
184
+ } catch (error) {
185
+ diagnostics.push(sourceDiagnostic("TM-WORLD-READ-WORK-ITEM-PARSE-ERROR", directoryId, [relativePath], error));
186
+ }
187
+ }
188
+ return result;
189
+ }
190
+
191
+ private async readTestAnnotations(
192
+ diagnostics: RawTraceabilityDiagnostic[],
193
+ ): Promise<readonly RawTraceabilityTestAnnotation[]> {
194
+ const result: RawTraceabilityTestAnnotation[] = [];
195
+ for (const testRoot of this.deps.testRoots) {
196
+ const absoluteRoot = path.join(this.deps.rootDir, testRoot);
197
+ let files: readonly string[];
198
+ try {
199
+ files = await collectFiles(absoluteRoot, (fileName) => /\.(?:test|spec)\.[cm]?[jt]sx?$/.test(fileName));
200
+ } catch (error) {
201
+ diagnostics.push(sourceDiagnostic("TM-WORLD-READ-TEST-SOURCE-ERROR", null, [testRoot], error));
202
+ continue;
203
+ }
204
+ for (const absolutePath of files) {
205
+ const relativePath = toProjectRelativePath(this.deps.rootDir, absolutePath);
206
+ try {
207
+ const tags = parseTestTags(await readFile(absolutePath, "utf8"));
208
+ for (const tag of tags) {
209
+ result.push({
210
+ storyId: tag.value,
211
+ filePath: relativePath,
212
+ line: tag.lineNumber,
213
+ testType: classifyTestType(relativePath),
214
+ });
215
+ }
216
+ } catch (error) {
217
+ diagnostics.push(sourceDiagnostic("TM-WORLD-READ-TEST-PARSE-ERROR", null, [relativePath], error));
218
+ }
219
+ }
220
+ }
221
+ return result;
222
+ }
223
+ }
@@ -10,20 +10,29 @@ export interface ParsedStoryCatalog {
10
10
  readonly aliasMap: ReadonlyMap<string, string>;
11
11
  }
12
12
 
13
+ export interface ParsedStoryCatalogEntry {
14
+ readonly storyId: string;
15
+ readonly legacyIds: readonly string[];
16
+ readonly lineNumber: number;
17
+ readonly acceptanceCriteria: readonly {
18
+ readonly acId: string;
19
+ readonly lineNumber: number;
20
+ }[];
21
+ }
22
+
13
23
  const STORY_ID_LINE_PATTERN = /\bH(?:F\d+|[0-9]{2})-[0-9]{2}\b/g;
14
- const TABLE_ALIAS_PATTERN =
15
- /\|\s*(H(?:F\d+|[0-9]{2})-[0-9]{2})\s*\|.*?\|\s*(US-[0-9]{3})\s*\|/g;
24
+ const TABLE_ALIAS_PATTERN = /\|\s*(H(?:F\d+|[0-9]{2})-[0-9]{2})\s*\|.*?\|\s*(US-[0-9]{3})\s*\|/g;
16
25
  // 旧US ラベルは Markdown 強調(**旧US**:)で囲まれることがあるため `\**` を許容する。
17
- const INLINE_ALIAS_PATTERN =
18
- /(H(?:F\d+|[0-9]{2})-[0-9]{2}).*?旧US\**\s*[::]\s*(US-[0-9]{3})/g;
26
+ const INLINE_ALIAS_PATTERN = /(H(?:F\d+|[0-9]{2})-[0-9]{2}).*?旧US\**\s*[::]\s*(US-[0-9]{3})/g;
19
27
 
20
28
  function execAll(pattern: RegExp, text: string): RegExpExecArray[] {
21
29
  const results: RegExpExecArray[] = [];
22
- let match: RegExpExecArray | null;
23
30
  // Reset lastIndex to ensure clean iteration
24
31
  pattern.lastIndex = 0;
25
- while ((match = pattern.exec(text)) !== null) {
32
+ let match = pattern.exec(text);
33
+ while (match !== null) {
26
34
  results.push(match);
35
+ match = pattern.exec(text);
27
36
  }
28
37
  return results;
29
38
  }
@@ -37,7 +46,7 @@ export function parseStoryCatalog(content: string): ParsedStoryCatalog {
37
46
  const aliasMap = new Map<string, string>();
38
47
 
39
48
  // 正規StoryIdを収集
40
- const lines = content.split('\n');
49
+ const lines = content.split("\n");
41
50
  for (const line of lines) {
42
51
  const matches = execAll(STORY_ID_LINE_PATTERN, line);
43
52
  for (let j = 0; j < matches.length; j++) {
@@ -88,10 +97,79 @@ export function parseStoryCatalog(content: string): ParsedStoryCatalog {
88
97
  }
89
98
 
90
99
  const storyIds: string[] = [];
91
- storyIdSet.forEach((id) => storyIds.push(id));
100
+ storyIdSet.forEach((id) => {
101
+ storyIds.push(id);
102
+ });
92
103
 
93
104
  return {
94
105
  storyIds: Object.freeze(storyIds),
95
106
  aliasMap,
96
107
  };
97
108
  }
109
+
110
+ /**
111
+ * Story headingをowner locatorとして、heading scope内のlegacy IDとACを構造化する。
112
+ * `####`以下はStory内の補足見出しとして扱い、次のlevel 1-3見出しでscopeを閉じる。
113
+ */
114
+ export function parseStoryCatalogEntries(content: string): readonly ParsedStoryCatalogEntry[] {
115
+ const lines = content.split(/\r?\n/);
116
+ const STORY_HEADING_PATTERN = /^###\s+(H(?:F\d+|[0-9]{2})-[0-9]{2})\b/;
117
+ const CLOSING_HEADING_PATTERN = /^#{1,3}\s+/;
118
+ const LEGACY_PATTERN = /旧US\**\s*[::]\s*(US-[0-9]{3})/;
119
+ const AC_PATTERN = /^\s*-\s*\[[ xX]\]\s*(AC-[0-9]+)\s*[::]/;
120
+ const entries: {
121
+ storyId: string;
122
+ legacyIds: string[];
123
+ lineNumber: number;
124
+ acceptanceCriteria: { acId: string; lineNumber: number }[];
125
+ }[] = [];
126
+ let current:
127
+ | {
128
+ storyId: string;
129
+ legacyIds: string[];
130
+ lineNumber: number;
131
+ acceptanceCriteria: { acId: string; lineNumber: number }[];
132
+ }
133
+ | undefined;
134
+
135
+ for (let index = 0; index < lines.length; index += 1) {
136
+ const line = lines[index];
137
+ const storyHeading = STORY_HEADING_PATTERN.exec(line);
138
+ if (storyHeading) {
139
+ current = {
140
+ storyId: storyHeading[1],
141
+ legacyIds: [],
142
+ lineNumber: index + 1,
143
+ acceptanceCriteria: [],
144
+ };
145
+ entries.push(current);
146
+ continue;
147
+ }
148
+ if (CLOSING_HEADING_PATTERN.test(line)) {
149
+ current = undefined;
150
+ continue;
151
+ }
152
+ if (!current) continue;
153
+
154
+ const legacy = LEGACY_PATTERN.exec(line);
155
+ if (legacy && !current.legacyIds.includes(legacy[1])) {
156
+ current.legacyIds.push(legacy[1]);
157
+ }
158
+ const acceptanceCriterion = AC_PATTERN.exec(line);
159
+ if (acceptanceCriterion) {
160
+ current.acceptanceCriteria.push({
161
+ acId: acceptanceCriterion[1],
162
+ lineNumber: index + 1,
163
+ });
164
+ }
165
+ }
166
+
167
+ return entries.map((entry) => ({
168
+ storyId: entry.storyId,
169
+ legacyIds: [...entry.legacyIds],
170
+ lineNumber: entry.lineNumber,
171
+ acceptanceCriteria: entry.acceptanceCriteria.map((criterion) => ({
172
+ ...criterion,
173
+ })),
174
+ }));
175
+ }
@@ -0,0 +1,43 @@
1
+ // @unit world-model
2
+ // @layer application
3
+ // @work-item-id WI-291
4
+
5
+ export type WorldJsonValue = null | boolean | number | string | readonly WorldJsonValue[] | WorldJsonObject;
6
+
7
+ export interface WorldJsonObject {
8
+ readonly [key: string]: WorldJsonValue;
9
+ }
10
+
11
+ export interface WorldInventoryCountDto {
12
+ readonly value: string;
13
+ readonly count: number;
14
+ }
15
+
16
+ export interface WorldExtractionDiagnosticDto extends WorldJsonObject {
17
+ readonly code: string;
18
+ readonly line: number | null;
19
+ readonly nodeId: string | null;
20
+ readonly pathKey: string | null;
21
+ readonly payload: WorldJsonObject;
22
+ }
23
+
24
+ export interface WorldInspectionDto {
25
+ readonly snapshotId: string;
26
+ readonly schemaVersion: string;
27
+ readonly extractorVersion: string;
28
+ readonly corpusRoot: string;
29
+ readonly summary: {
30
+ readonly nodeCount: number;
31
+ readonly edgeCount: number;
32
+ readonly diagnosticCount: number;
33
+ readonly hardDiagnosticCount: number;
34
+ };
35
+ readonly inventory: {
36
+ readonly nodeTypes: readonly WorldInventoryCountDto[];
37
+ readonly corpusRoles: readonly WorldInventoryCountDto[];
38
+ readonly artifactKinds: readonly WorldInventoryCountDto[];
39
+ };
40
+ readonly nodes: readonly WorldJsonObject[];
41
+ readonly edges: readonly WorldJsonObject[];
42
+ readonly diagnostics: readonly WorldExtractionDiagnosticDto[];
43
+ }
@@ -0,0 +1,54 @@
1
+ // @unit world-model
2
+ // @layer application
3
+ // @work-item-id WI-295
4
+
5
+ import type { CanonicalJsonObject } from "../../domain/services/canonical-json-serializer.js";
6
+
7
+ export type StructuralObligationClassification = "adopted-legacy" | "new-structural" | "invalid-declaration" | "waived";
8
+
9
+ export interface StructuralObligationDto {
10
+ readonly violationFingerprint: string;
11
+ readonly ruleId: string;
12
+ readonly constraintId: string | null;
13
+ readonly factType: string | null;
14
+ readonly subject: CanonicalJsonObject;
15
+ readonly claimantPin: CanonicalJsonObject | null;
16
+ readonly premisePin: CanonicalJsonObject | null;
17
+ readonly expected: CanonicalJsonObject;
18
+ readonly observed: CanonicalJsonObject;
19
+ readonly classification: StructuralObligationClassification;
20
+ readonly waiver: CanonicalJsonObject | null;
21
+ }
22
+
23
+ export interface RepaidBaselineEntryDto {
24
+ readonly violationFingerprint: string;
25
+ readonly ruleId: string;
26
+ readonly constraintId: string | null;
27
+ readonly classification: "repaid";
28
+ readonly disposition: "cleanup-required";
29
+ }
30
+
31
+ export interface PolicyDiagnosticDto {
32
+ readonly code: string;
33
+ readonly subjectId: string | null;
34
+ readonly details: CanonicalJsonObject;
35
+ }
36
+
37
+ export interface ObligationReportSummaryDto {
38
+ readonly structuralObligations: number;
39
+ readonly repaidBaselineEntries: number;
40
+ readonly declaredSemanticDebts: number;
41
+ readonly policyDiagnostics: number;
42
+ }
43
+
44
+ export interface WorldObligationReportDto {
45
+ readonly schemaVersion: "phasegate-world-obligation-report/v1";
46
+ readonly evaluationId: string;
47
+ readonly rulesetVersion: string;
48
+ readonly policyInputsDigest: string;
49
+ readonly structuralObligations: readonly StructuralObligationDto[];
50
+ readonly repaidBaselineEntries: readonly RepaidBaselineEntryDto[];
51
+ readonly declaredSemanticDebts: readonly CanonicalJsonObject[];
52
+ readonly policyDiagnostics: readonly PolicyDiagnosticDto[];
53
+ readonly summary: ObligationReportSummaryDto;
54
+ }
@@ -0,0 +1,62 @@
1
+ // @unit world-model
2
+ // @layer application
3
+ // @work-item-id WI-291
4
+
5
+ import { PathKey } from "../../domain/value-objects/path-key.js";
6
+
7
+ export interface WorldResolvedConfigInput {
8
+ readonly designDocsRoot?: string;
9
+ readonly inceptionRoot?: string;
10
+ readonly requirementMatrixPath?: string;
11
+ }
12
+
13
+ export interface WorldCorpusConfig {
14
+ readonly productScopes: readonly {
15
+ readonly productRoot: string;
16
+ readonly designDocsRoot: string;
17
+ readonly unitRoot: string;
18
+ }[];
19
+ readonly inceptionRoot: string;
20
+ readonly adrRoot: string;
21
+ readonly sourceRoot: string;
22
+ readonly include: readonly string[];
23
+ readonly exclude: readonly string[];
24
+ readonly matrixPath: string;
25
+ readonly attestationPath: string;
26
+ readonly integrityManifestPath: string;
27
+ }
28
+
29
+ const DEFAULT_DESIGN_DOCS_ROOT = "docs/product/construction";
30
+
31
+ const validatePath = (value: string): string => PathKey.create(value).toString();
32
+
33
+ const deriveProductRoot = (designDocsRoot: string): string => {
34
+ const normalized = designDocsRoot.replace(/\/+$/, "");
35
+ return normalized.endsWith("/construction") ? normalized.slice(0, -"/construction".length) : normalized;
36
+ };
37
+
38
+ export const resolveWorldCorpusConfig = (input?: WorldResolvedConfigInput): WorldCorpusConfig => {
39
+ const designDocsRoot = validatePath(input?.designDocsRoot ?? DEFAULT_DESIGN_DOCS_ROOT);
40
+ const productRoot = validatePath(deriveProductRoot(designDocsRoot));
41
+ const productScopes = [
42
+ {
43
+ productRoot: "docs/product",
44
+ designDocsRoot: DEFAULT_DESIGN_DOCS_ROOT,
45
+ unitRoot: "docs/product/units",
46
+ },
47
+ ...(productRoot === "docs/product"
48
+ ? []
49
+ : [{ productRoot, designDocsRoot, unitRoot: validatePath(`${productRoot}/units`) }]),
50
+ ];
51
+ return Object.freeze({
52
+ productScopes: Object.freeze(productScopes),
53
+ inceptionRoot: validatePath(input?.inceptionRoot ?? "docs/inception"),
54
+ adrRoot: "docs/ADR",
55
+ sourceRoot: "scripts/harness",
56
+ include: Object.freeze(["**/*"]),
57
+ exclude: Object.freeze([]),
58
+ matrixPath: validatePath(input?.requirementMatrixPath ?? ".harness/requirement-test-matrix.json"),
59
+ attestationPath: ".harness/attestation.json",
60
+ integrityManifestPath: "phasegate.integrity.json",
61
+ });
62
+ };
@@ -0,0 +1,7 @@
1
+ // @unit world-model
2
+ // @layer application
3
+ // @work-item-id WI-295, WI-296
4
+
5
+ export interface ObligationReportWriterPort {
6
+ write(bytes: Uint8Array, reportPath?: string): Promise<void>;
7
+ }
@@ -0,0 +1,57 @@
1
+ // @unit world-model
2
+ // @layer application
3
+ // @work-item-id WI-294
4
+
5
+ import type { ConstraintRecord, MalformedConstraintDeclaration } from "../../domain/entities/constraint-record.js";
6
+ import type {
7
+ AdoptionBaseline,
8
+ SemanticDebtDeclaration,
9
+ WorldWaiver,
10
+ } from "../../domain/entities/control-declarations.js";
11
+ import type { ExplicitConstraintRelation } from "../../domain/value-objects/explicit-constraint-relation.js";
12
+ import type { ExplicitNodeAlias } from "../../domain/value-objects/explicit-node-alias.js";
13
+
14
+ export interface WorldControlDiagnosticDto {
15
+ readonly code: string;
16
+ readonly path: string;
17
+ readonly locator: string | null;
18
+ readonly message: string;
19
+ }
20
+
21
+ export interface ConstraintDeclarationSet {
22
+ readonly schemaVersion: "phasegate-world-constraints/v1";
23
+ readonly records: readonly ConstraintRecord[];
24
+ readonly malformedDeclarations: readonly MalformedConstraintDeclaration[];
25
+ readonly aliases: readonly ExplicitNodeAlias[];
26
+ readonly relations: readonly ExplicitConstraintRelation[];
27
+ readonly diagnostics: readonly WorldControlDiagnosticDto[];
28
+ }
29
+
30
+ export type WorldControlReadResult<T> =
31
+ | { readonly state: "absent"; readonly value: T; readonly diagnostics: readonly WorldControlDiagnosticDto[] }
32
+ | { readonly state: "loaded"; readonly value: T; readonly diagnostics: readonly WorldControlDiagnosticDto[] }
33
+ | { readonly state: "invalid"; readonly diagnostics: readonly WorldControlDiagnosticDto[] };
34
+
35
+ export type WorldControlReplaceResult =
36
+ | { readonly state: "written"; readonly path: string }
37
+ | { readonly state: "invalid"; readonly diagnostics: readonly WorldControlDiagnosticDto[] };
38
+
39
+ export interface ConstraintDeclarationRepositoryPort {
40
+ load(): Promise<WorldControlReadResult<ConstraintDeclarationSet>>;
41
+ replaceAtomically(document: unknown): Promise<WorldControlReplaceResult>;
42
+ }
43
+
44
+ export interface AdoptionBaselineRepositoryPort {
45
+ load(): Promise<WorldControlReadResult<AdoptionBaseline | null>>;
46
+ replaceAtomically(document: unknown): Promise<WorldControlReplaceResult>;
47
+ }
48
+
49
+ export interface WaiverDeclarationRepositoryPort {
50
+ load(): Promise<WorldControlReadResult<readonly WorldWaiver[]>>;
51
+ replaceAtomically(document: unknown): Promise<WorldControlReplaceResult>;
52
+ }
53
+
54
+ export interface SemanticDebtRepositoryPort {
55
+ load(): Promise<WorldControlReadResult<readonly SemanticDebtDeclaration[]>>;
56
+ replaceAtomically(document: unknown): Promise<WorldControlReplaceResult>;
57
+ }
@@ -0,0 +1,17 @@
1
+ // @unit world-model
2
+ // @layer application
3
+ // @work-item-id WI-291
4
+
5
+ import type { Edge } from "../../domain/entities/edge.js";
6
+ import type { ExtractionDiagnostic } from "../../domain/entities/extraction-diagnostic.js";
7
+ import type { WorldNode } from "../../domain/entities/world-node.js";
8
+
9
+ export interface WorldFactBatch {
10
+ readonly nodes: readonly WorldNode[];
11
+ readonly edges: readonly Edge[];
12
+ readonly diagnostics: readonly ExtractionDiagnostic[];
13
+ }
14
+
15
+ export interface WorldFactSourcePort {
16
+ extract(): Promise<readonly WorldFactBatch[]>;
17
+ }