@moreih29/nexus-core 0.12.0 → 0.13.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 (210) hide show
  1. package/README.md +48 -63
  2. package/assets/agents/architect/body.ko.md +177 -0
  3. package/{agents → assets/agents}/architect/body.md +16 -0
  4. package/assets/agents/designer/body.ko.md +125 -0
  5. package/{agents → assets/agents}/designer/body.md +16 -0
  6. package/assets/agents/engineer/body.ko.md +106 -0
  7. package/{agents → assets/agents}/engineer/body.md +14 -0
  8. package/assets/agents/lead/body.ko.md +70 -0
  9. package/assets/agents/lead/body.md +70 -0
  10. package/assets/agents/postdoc/body.ko.md +122 -0
  11. package/{agents → assets/agents}/postdoc/body.md +16 -0
  12. package/assets/agents/researcher/body.ko.md +137 -0
  13. package/{agents → assets/agents}/researcher/body.md +15 -0
  14. package/assets/agents/reviewer/body.ko.md +138 -0
  15. package/{agents → assets/agents}/reviewer/body.md +15 -0
  16. package/assets/agents/strategist/body.ko.md +116 -0
  17. package/{agents → assets/agents}/strategist/body.md +16 -0
  18. package/assets/agents/tester/body.ko.md +195 -0
  19. package/{agents → assets/agents}/tester/body.md +15 -0
  20. package/assets/agents/writer/body.ko.md +122 -0
  21. package/{agents → assets/agents}/writer/body.md +14 -0
  22. package/assets/capability-matrix.yml +198 -0
  23. package/assets/hooks/agent-bootstrap/handler.test.ts +368 -0
  24. package/assets/hooks/agent-bootstrap/handler.ts +119 -0
  25. package/assets/hooks/agent-bootstrap/meta.yml +10 -0
  26. package/assets/hooks/agent-finalize/handler.test.ts +368 -0
  27. package/assets/hooks/agent-finalize/handler.ts +76 -0
  28. package/assets/hooks/agent-finalize/meta.yml +10 -0
  29. package/assets/hooks/capability-matrix.yml +313 -0
  30. package/assets/hooks/post-tool-telemetry/handler.test.ts +302 -0
  31. package/assets/hooks/post-tool-telemetry/handler.ts +49 -0
  32. package/assets/hooks/post-tool-telemetry/meta.yml +11 -0
  33. package/assets/hooks/prompt-router/handler.test.ts +801 -0
  34. package/assets/hooks/prompt-router/handler.ts +261 -0
  35. package/assets/hooks/prompt-router/meta.yml +11 -0
  36. package/assets/hooks/session-init/handler.test.ts +274 -0
  37. package/assets/hooks/session-init/handler.ts +30 -0
  38. package/assets/hooks/session-init/meta.yml +9 -0
  39. package/assets/lsp-servers.json +55 -0
  40. package/assets/schema/lsp-servers.schema.json +67 -0
  41. package/assets/skills/nx-init/body.ko.md +197 -0
  42. package/{skills → assets/skills}/nx-init/body.md +11 -0
  43. package/assets/skills/nx-plan/body.ko.md +361 -0
  44. package/{skills → assets/skills}/nx-plan/body.md +13 -0
  45. package/assets/skills/nx-run/body.ko.md +161 -0
  46. package/{skills → assets/skills}/nx-run/body.md +11 -0
  47. package/assets/skills/nx-sync/body.ko.md +92 -0
  48. package/{skills → assets/skills}/nx-sync/body.md +10 -0
  49. package/assets/tools/tool-name-map.yml +353 -0
  50. package/dist/hooks/opencode-mount.d.ts +35 -0
  51. package/dist/hooks/opencode-mount.d.ts.map +1 -0
  52. package/dist/hooks/opencode-mount.js +332 -0
  53. package/dist/hooks/opencode-mount.js.map +1 -0
  54. package/dist/hooks/runtime.d.ts +37 -0
  55. package/dist/hooks/runtime.d.ts.map +1 -0
  56. package/dist/hooks/runtime.js +274 -0
  57. package/dist/hooks/runtime.js.map +1 -0
  58. package/dist/hooks/types.d.ts +196 -0
  59. package/dist/hooks/types.d.ts.map +1 -0
  60. package/dist/hooks/types.js +85 -0
  61. package/dist/hooks/types.js.map +1 -0
  62. package/dist/lsp/cache.d.ts +9 -0
  63. package/dist/lsp/cache.d.ts.map +1 -0
  64. package/dist/lsp/cache.js +216 -0
  65. package/dist/lsp/cache.js.map +1 -0
  66. package/dist/lsp/client.d.ts +24 -0
  67. package/dist/lsp/client.d.ts.map +1 -0
  68. package/dist/lsp/client.js +166 -0
  69. package/dist/lsp/client.js.map +1 -0
  70. package/dist/lsp/detect.d.ts +77 -0
  71. package/dist/lsp/detect.d.ts.map +1 -0
  72. package/dist/lsp/detect.js +116 -0
  73. package/dist/lsp/detect.js.map +1 -0
  74. package/dist/mcp/server.d.ts +5 -0
  75. package/dist/mcp/server.d.ts.map +1 -0
  76. package/dist/mcp/server.js +34 -0
  77. package/dist/mcp/server.js.map +1 -0
  78. package/dist/mcp/tools/artifact.d.ts +4 -0
  79. package/dist/mcp/tools/artifact.d.ts.map +1 -0
  80. package/dist/mcp/tools/artifact.js +36 -0
  81. package/dist/mcp/tools/artifact.js.map +1 -0
  82. package/dist/mcp/tools/history.d.ts +3 -0
  83. package/dist/mcp/tools/history.d.ts.map +1 -0
  84. package/dist/mcp/tools/history.js +29 -0
  85. package/dist/mcp/tools/history.js.map +1 -0
  86. package/dist/mcp/tools/lsp.d.ts +13 -0
  87. package/dist/mcp/tools/lsp.d.ts.map +1 -0
  88. package/dist/mcp/tools/lsp.js +225 -0
  89. package/dist/mcp/tools/lsp.js.map +1 -0
  90. package/dist/mcp/tools/plan.d.ts +3 -0
  91. package/dist/mcp/tools/plan.d.ts.map +1 -0
  92. package/dist/mcp/tools/plan.js +317 -0
  93. package/dist/mcp/tools/plan.js.map +1 -0
  94. package/dist/mcp/tools/task.d.ts +3 -0
  95. package/dist/mcp/tools/task.d.ts.map +1 -0
  96. package/dist/mcp/tools/task.js +252 -0
  97. package/dist/mcp/tools/task.js.map +1 -0
  98. package/dist/shared/invocations.d.ts +74 -0
  99. package/dist/shared/invocations.d.ts.map +1 -0
  100. package/dist/shared/invocations.js +247 -0
  101. package/dist/shared/invocations.js.map +1 -0
  102. package/dist/shared/json-store.d.ts +37 -0
  103. package/dist/shared/json-store.d.ts.map +1 -0
  104. package/dist/shared/json-store.js +163 -0
  105. package/dist/shared/json-store.js.map +1 -0
  106. package/dist/shared/mcp-utils.d.ts +3 -0
  107. package/dist/shared/mcp-utils.d.ts.map +1 -0
  108. package/dist/shared/mcp-utils.js +6 -0
  109. package/dist/shared/mcp-utils.js.map +1 -0
  110. package/dist/shared/paths.d.ts +21 -0
  111. package/dist/shared/paths.d.ts.map +1 -0
  112. package/dist/shared/paths.js +81 -0
  113. package/dist/shared/paths.js.map +1 -0
  114. package/dist/shared/tool-log.d.ts +8 -0
  115. package/dist/shared/tool-log.d.ts.map +1 -0
  116. package/dist/shared/tool-log.js +22 -0
  117. package/dist/shared/tool-log.js.map +1 -0
  118. package/dist/types/state.d.ts +862 -0
  119. package/dist/types/state.d.ts.map +1 -0
  120. package/dist/types/state.js +66 -0
  121. package/dist/types/state.js.map +1 -0
  122. package/docs/consuming/codex-lead-merge.md +106 -0
  123. package/docs/plugin-guide.md +360 -0
  124. package/docs/plugin-template/claude/.github/workflows/build.yml +60 -0
  125. package/docs/plugin-template/claude/README.md +110 -0
  126. package/docs/plugin-template/claude/package.json +16 -0
  127. package/docs/plugin-template/codex/.github/workflows/build.yml +51 -0
  128. package/docs/plugin-template/codex/README.md +147 -0
  129. package/docs/plugin-template/codex/package.json +17 -0
  130. package/docs/plugin-template/opencode/.github/workflows/build.yml +61 -0
  131. package/docs/plugin-template/opencode/README.md +121 -0
  132. package/docs/plugin-template/opencode/package.json +25 -0
  133. package/package.json +21 -21
  134. package/scripts/build-agents.test.ts +1279 -0
  135. package/scripts/build-agents.ts +978 -0
  136. package/scripts/build-hooks.test.ts +1385 -0
  137. package/scripts/build-hooks.ts +584 -0
  138. package/scripts/cli.test.ts +367 -0
  139. package/scripts/cli.ts +547 -0
  140. package/agents/architect/meta.yml +0 -13
  141. package/agents/designer/meta.yml +0 -13
  142. package/agents/engineer/meta.yml +0 -11
  143. package/agents/postdoc/meta.yml +0 -13
  144. package/agents/researcher/meta.yml +0 -12
  145. package/agents/reviewer/meta.yml +0 -12
  146. package/agents/strategist/meta.yml +0 -13
  147. package/agents/tester/meta.yml +0 -12
  148. package/agents/writer/meta.yml +0 -11
  149. package/conformance/README.md +0 -311
  150. package/conformance/examples/plan.extension.schema.example.json +0 -25
  151. package/conformance/lifecycle/README.md +0 -48
  152. package/conformance/lifecycle/agent-complete.json +0 -44
  153. package/conformance/lifecycle/agent-resume.json +0 -43
  154. package/conformance/lifecycle/agent-spawn.json +0 -36
  155. package/conformance/lifecycle/memory-access-record.json +0 -27
  156. package/conformance/lifecycle/session-end.json +0 -48
  157. package/conformance/scenarios/full-plan-cycle.json +0 -147
  158. package/conformance/scenarios/task-deps-ordering.json +0 -95
  159. package/conformance/schema/fixture.schema.json +0 -354
  160. package/conformance/state-schemas/agent-tracker.schema.json +0 -63
  161. package/conformance/state-schemas/history.schema.json +0 -134
  162. package/conformance/state-schemas/memory-access.schema.json +0 -36
  163. package/conformance/state-schemas/plan.schema.json +0 -77
  164. package/conformance/state-schemas/tasks.schema.json +0 -98
  165. package/conformance/tools/artifact-write.json +0 -97
  166. package/conformance/tools/context.json +0 -172
  167. package/conformance/tools/history-search.json +0 -219
  168. package/conformance/tools/plan-decide.json +0 -139
  169. package/conformance/tools/plan-start.json +0 -81
  170. package/conformance/tools/plan-status.json +0 -127
  171. package/conformance/tools/plan-update.json +0 -341
  172. package/conformance/tools/task-add.json +0 -156
  173. package/conformance/tools/task-close.json +0 -161
  174. package/conformance/tools/task-list.json +0 -177
  175. package/conformance/tools/task-update.json +0 -167
  176. package/docs/behavioral-contracts.md +0 -145
  177. package/docs/consumer-implementation-guide.md +0 -840
  178. package/docs/memory-lifecycle-contract.md +0 -119
  179. package/docs/nexus-layout.md +0 -224
  180. package/docs/nexus-outputs-contract.md +0 -344
  181. package/docs/nexus-state-overview.md +0 -170
  182. package/docs/nexus-tools-contract.md +0 -438
  183. package/manifest.json +0 -448
  184. package/schema/README.md +0 -69
  185. package/schema/agent.schema.json +0 -23
  186. package/schema/common.schema.json +0 -17
  187. package/schema/manifest.schema.json +0 -78
  188. package/schema/memory-policy.schema.json +0 -98
  189. package/schema/skill.schema.json +0 -54
  190. package/schema/task-exceptions.schema.json +0 -40
  191. package/schema/vocabulary.schema.json +0 -167
  192. package/scripts/.gitkeep +0 -0
  193. package/scripts/conformance-coverage.ts +0 -466
  194. package/scripts/import-from-claude-nexus.ts +0 -403
  195. package/scripts/lib/frontmatter.ts +0 -71
  196. package/scripts/lib/lint.ts +0 -348
  197. package/scripts/lib/structure.ts +0 -159
  198. package/scripts/lib/validate.ts +0 -796
  199. package/scripts/validate.ts +0 -90
  200. package/skills/nx-init/meta.yml +0 -8
  201. package/skills/nx-plan/meta.yml +0 -10
  202. package/skills/nx-run/meta.yml +0 -8
  203. package/skills/nx-sync/meta.yml +0 -7
  204. package/vocabulary/capabilities.yml +0 -65
  205. package/vocabulary/categories.yml +0 -11
  206. package/vocabulary/invocations.yml +0 -147
  207. package/vocabulary/memory_policy.yml +0 -88
  208. package/vocabulary/resume-tiers.yml +0 -11
  209. package/vocabulary/tags.yml +0 -60
  210. package/vocabulary/task-exceptions.yml +0 -29
@@ -1,466 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * conformance-coverage.ts
5
- *
6
- * Build-time validator for conformance fixture coverage.
7
- * Checks:
8
- * 1. Every field in every state-schema is covered by at least one fixture
9
- * 2. Params in action fixtures are traceable to postcondition assertions (anti-pattern check)
10
- *
11
- * rule:no-runtime exception — this is a build-time utility under scripts/.
12
- */
13
-
14
- import { readFile, readdir } from 'node:fs/promises';
15
- import path from 'node:path';
16
-
17
- /**
18
- * List *.json files (non-recursive) in a directory.
19
- * Returns absolute or repo-relative paths matching the input dir style.
20
- * Returns [] when the directory does not exist.
21
- */
22
- async function listJsonFiles(dir: string): Promise<string[]> {
23
- let entries: string[];
24
- try {
25
- entries = await readdir(dir);
26
- } catch {
27
- return [];
28
- }
29
- return entries
30
- .filter((name) => name.endsWith('.json'))
31
- .map((name) => path.join(dir, name));
32
- }
33
-
34
- // ─── Types ───────────────────────────────────────────────────────────────────
35
-
36
- interface CoversBlock {
37
- state_schemas?: Record<string, string[]>;
38
- return_value?: Record<string, string[]>;
39
- description?: string;
40
- }
41
-
42
- interface ActionBlock {
43
- tool: string;
44
- params: Record<string, unknown>;
45
- }
46
-
47
- interface EventBlock {
48
- type: string;
49
- params?: Record<string, unknown>;
50
- }
51
-
52
- interface PostconditionBlock {
53
- state_files?: Record<string, Record<string, unknown> | null>;
54
- return_value?: Record<string, unknown>;
55
- error?: boolean;
56
- error_contains?: string;
57
- }
58
-
59
- interface StepBlock {
60
- description?: string;
61
- action?: ActionBlock;
62
- event?: EventBlock;
63
- assert_return?: Record<string, unknown>;
64
- assert_state?: Record<string, Record<string, unknown> | null>;
65
- }
66
-
67
- interface Fixture {
68
- test_id: string;
69
- description: string;
70
- covers?: CoversBlock;
71
- uncovered_params?: string[];
72
- precondition?: { state_files?: Record<string, unknown> };
73
- action?: ActionBlock;
74
- event?: EventBlock;
75
- steps?: StepBlock[];
76
- postcondition?: PostconditionBlock;
77
- _source_file: string;
78
- }
79
-
80
- interface MissingReport {
81
- schema: string;
82
- missingFields: string[];
83
- }
84
-
85
- interface AntiPatternReport {
86
- fixture: string;
87
- sourceFile: string;
88
- uncoveredParams: string[];
89
- }
90
-
91
- // ─── extractSchemaFields ──────────────────────────────────────────────────────
92
-
93
- /**
94
- * Recursively traverse a JSON Schema object and return all field paths.
95
- * - Object properties => "key"
96
- * - Array items => "key[]"
97
- * - $defs/$ref resolution for same-file refs (e.g. "#/$defs/task")
98
- * - Top-level array: items are emitted without a leading "[]" wrapper;
99
- * fields are emitted as-is (e.g. "agent_type", "agent_id")
100
- */
101
- function extractSchemaFields(
102
- schemaObj: Record<string, unknown>,
103
- basePath = '',
104
- defs?: Record<string, unknown>,
105
- topLevelIsArray = false,
106
- ): string[] {
107
- // Resolve $ref if present
108
- const resolved = resolveRef(schemaObj, defs ?? (schemaObj['$defs'] as Record<string, unknown> | undefined));
109
- if (resolved !== schemaObj) {
110
- return extractSchemaFields(resolved, basePath, defs, topLevelIsArray);
111
- }
112
-
113
- const localDefs = (defs ?? (schemaObj['$defs'] as Record<string, unknown> | undefined)) as
114
- | Record<string, unknown>
115
- | undefined;
116
-
117
- const type = schemaObj['type'] as string | undefined;
118
-
119
- // Top-level array: recurse into items without prefixing with "[]"
120
- if (type === 'array' && basePath === '') {
121
- const items = schemaObj['items'] as Record<string, unknown> | undefined;
122
- if (items) {
123
- return extractSchemaFields(items, basePath, localDefs, true);
124
- }
125
- return [];
126
- }
127
-
128
- // Array field inside an object
129
- if (type === 'array' && basePath !== '') {
130
- const items = schemaObj['items'] as Record<string, unknown> | undefined;
131
- if (!items) return [basePath + '[]'];
132
- const itemType = (items as Record<string, unknown>)['type'] as string | undefined;
133
- // Scalar array items — no sub-fields to extract
134
- if (itemType === 'string' || itemType === 'number' || itemType === 'boolean') {
135
- return [basePath + '[]'];
136
- }
137
- // Object array items — recurse with "[]" appended to base path
138
- return extractSchemaFields(items as Record<string, unknown>, basePath + '[]', localDefs);
139
- }
140
-
141
- // Object with properties
142
- if (type === 'object' || schemaObj['properties']) {
143
- const properties = schemaObj['properties'] as Record<string, unknown> | undefined;
144
- if (!properties) return basePath ? [basePath] : [];
145
-
146
- const fields: string[] = [];
147
- if (basePath) fields.push(basePath);
148
-
149
- for (const [key, propSchema] of Object.entries(properties)) {
150
- const childPath = basePath ? `${basePath}.${key}` : key;
151
- const childFields = extractSchemaFields(
152
- propSchema as Record<string, unknown>,
153
- childPath,
154
- localDefs,
155
- );
156
- fields.push(...childFields);
157
- }
158
- return fields;
159
- }
160
-
161
- // Leaf scalar
162
- if (basePath) return [basePath];
163
- return [];
164
- }
165
-
166
- function resolveRef(
167
- schemaObj: Record<string, unknown>,
168
- defs: Record<string, unknown> | undefined,
169
- ): Record<string, unknown> {
170
- const ref = schemaObj['$ref'] as string | undefined;
171
- if (!ref || !defs) return schemaObj;
172
-
173
- // Handle "#/$defs/<name>"
174
- const match = ref.match(/^#\/\$defs\/(.+)$/);
175
- if (!match) return schemaObj;
176
-
177
- const defName = match[1];
178
- const def = defs[defName] as Record<string, unknown> | undefined;
179
- if (!def) return schemaObj;
180
-
181
- // Merge any non-$ref fields from schemaObj on top of resolved def
182
- // (allOf/oneOf merging is not needed for our strict schemas)
183
- return def;
184
- }
185
-
186
- // ─── Schema loading helpers ───────────────────────────────────────────────────
187
-
188
- /**
189
- * Flatten schema field paths, removing parent object paths when they have
190
- * children (only keep leaf paths and array sentinel paths).
191
- */
192
- function leafFields(schemaObj: Record<string, unknown>): string[] {
193
- const all = extractSchemaFields(schemaObj, '', undefined, false);
194
- // Remove intermediate paths that are parents of other paths
195
- const result: string[] = [];
196
- for (const field of all) {
197
- const hasChild = all.some((f) => f !== field && f.startsWith(field + '.'));
198
- if (!hasChild) result.push(field);
199
- }
200
- return result;
201
- }
202
-
203
- // ─── loadFixtures ─────────────────────────────────────────────────────────────
204
-
205
- async function loadFixtures(root: string): Promise<Fixture[]> {
206
- const dirs = [
207
- path.join(root, 'conformance/tools'),
208
- path.join(root, 'conformance/scenarios'),
209
- path.join(root, 'conformance/lifecycle'),
210
- ];
211
-
212
- const fileLists = await Promise.all(dirs.map((d) => listJsonFiles(d)));
213
- const files = fileLists.flat();
214
-
215
- const fixtures: Fixture[] = [];
216
-
217
- for (const file of files) {
218
- const raw = await readFile(file, 'utf-8');
219
- let parsed: unknown;
220
- try {
221
- parsed = JSON.parse(raw);
222
- } catch {
223
- console.error(`Failed to parse JSON: ${file}`);
224
- continue;
225
- }
226
-
227
- const sourceFile = path.relative(root, file);
228
-
229
- if (Array.isArray(parsed)) {
230
- for (const item of parsed as Record<string, unknown>[]) {
231
- fixtures.push({ ...(item as Omit<Fixture, '_source_file'>), _source_file: sourceFile });
232
- }
233
- } else {
234
- fixtures.push({
235
- ...(parsed as Omit<Fixture, '_source_file'>),
236
- _source_file: sourceFile,
237
- });
238
- }
239
- }
240
-
241
- return fixtures;
242
- }
243
-
244
- // ─── unionCovers ─────────────────────────────────────────────────────────────
245
-
246
- interface CoverageUnion {
247
- state_schemas: Map<string, Set<string>>;
248
- return_value: Map<string, Set<string>>;
249
- }
250
-
251
- function unionCovers(fixtures: Fixture[]): CoverageUnion {
252
- const state_schemas = new Map<string, Set<string>>();
253
- const return_value = new Map<string, Set<string>>();
254
-
255
- for (const fixture of fixtures) {
256
- if (!fixture.covers) continue;
257
-
258
- if (fixture.covers.state_schemas) {
259
- for (const [schemaName, fields] of Object.entries(fixture.covers.state_schemas)) {
260
- if (!state_schemas.has(schemaName)) {
261
- state_schemas.set(schemaName, new Set());
262
- }
263
- for (const f of fields) {
264
- state_schemas.get(schemaName)!.add(f);
265
- }
266
- }
267
- }
268
-
269
- if (fixture.covers.return_value) {
270
- for (const [toolName, fields] of Object.entries(fixture.covers.return_value)) {
271
- if (!return_value.has(toolName)) {
272
- return_value.set(toolName, new Set());
273
- }
274
- for (const f of fields) {
275
- return_value.get(toolName)!.add(f);
276
- }
277
- }
278
- }
279
- }
280
-
281
- return { state_schemas, return_value };
282
- }
283
-
284
- // ─── checkSchemaFieldCoverage ─────────────────────────────────────────────────
285
-
286
- function checkSchemaFieldCoverage(
287
- schemaFields: Map<string, string[]>,
288
- covered: CoverageUnion,
289
- ): MissingReport[] {
290
- const reports: MissingReport[] = [];
291
-
292
- for (const [schemaName, fields] of schemaFields.entries()) {
293
- const coveredFields = covered.state_schemas.get(schemaName) ?? new Set<string>();
294
- const missing = fields.filter((f) => !coveredFields.has(f));
295
- if (missing.length > 0) {
296
- reports.push({ schema: schemaName, missingFields: missing });
297
- }
298
- }
299
-
300
- return reports;
301
- }
302
-
303
- // ─── checkParamsAntiPattern ───────────────────────────────────────────────────
304
-
305
- /**
306
- * For a single-action fixture, check whether every param key appears in
307
- * some postcondition assertion key (substring match).
308
- * Keys listed in uncovered_params are exempt.
309
- */
310
- function getAssertionKeys(postcondition: PostconditionBlock | undefined): string[] {
311
- const keys: string[] = [];
312
- if (!postcondition) return keys;
313
-
314
- if (postcondition.return_value) {
315
- keys.push(...Object.keys(postcondition.return_value));
316
- }
317
- if (postcondition.state_files) {
318
- for (const assertions of Object.values(postcondition.state_files)) {
319
- if (assertions && typeof assertions === 'object') {
320
- keys.push(...Object.keys(assertions));
321
- }
322
- }
323
- }
324
- return keys;
325
- }
326
-
327
- function getStepAssertionKeys(step: StepBlock): string[] {
328
- const keys: string[] = [];
329
- if (step.assert_return) keys.push(...Object.keys(step.assert_return));
330
- if (step.assert_state) {
331
- for (const assertions of Object.values(step.assert_state)) {
332
- if (assertions && typeof assertions === 'object') {
333
- keys.push(...Object.keys(assertions));
334
- }
335
- }
336
- }
337
- return keys;
338
- }
339
-
340
- function paramIsCovered(paramKey: string, assertionKeys: string[]): boolean {
341
- return assertionKeys.some((ak) => ak.includes(paramKey));
342
- }
343
-
344
- function checkParamsAntiPattern(fixtures: Fixture[]): AntiPatternReport[] {
345
- const reports: AntiPatternReport[] = [];
346
-
347
- for (const fixture of fixtures) {
348
- const exempt = new Set(fixture.uncovered_params ?? []);
349
-
350
- if (fixture.action) {
351
- // Single-action fixture
352
- const paramKeys = Object.keys(fixture.action.params ?? {});
353
- const assertionKeys = getAssertionKeys(fixture.postcondition);
354
- const uncovered = paramKeys.filter(
355
- (k) => !exempt.has(k) && !paramIsCovered(k, assertionKeys),
356
- );
357
- if (uncovered.length > 0) {
358
- reports.push({
359
- fixture: fixture.test_id,
360
- sourceFile: fixture._source_file,
361
- uncoveredParams: uncovered,
362
- });
363
- }
364
- } else if (fixture.steps) {
365
- // Multi-step fixture: check each step's action params against that step's assertions
366
- const perStepUncovered: string[] = [];
367
-
368
- for (const step of fixture.steps) {
369
- if (!step.action) continue;
370
- const paramKeys = Object.keys(step.action.params ?? {});
371
- const assertionKeys = getStepAssertionKeys(step);
372
- const uncovered = paramKeys.filter(
373
- (k) => !exempt.has(k) && !paramIsCovered(k, assertionKeys),
374
- );
375
- perStepUncovered.push(...uncovered.filter((k) => !perStepUncovered.includes(k)));
376
- }
377
-
378
- if (perStepUncovered.length > 0) {
379
- reports.push({
380
- fixture: fixture.test_id,
381
- sourceFile: fixture._source_file,
382
- uncoveredParams: perStepUncovered,
383
- });
384
- }
385
- }
386
- // event-only fixtures have no action params to check
387
- }
388
-
389
- return reports;
390
- }
391
-
392
- // ─── main ─────────────────────────────────────────────────────────────────────
393
-
394
- async function main(): Promise<void> {
395
- const root = process.cwd();
396
-
397
- // 1. Load state schemas
398
- const schemaDir = path.join(root, 'conformance/state-schemas');
399
- const schemaFiles = await listJsonFiles(schemaDir);
400
-
401
- const schemaFields = new Map<string, string[]>();
402
- for (const schemaFile of schemaFiles) {
403
- const raw = await readFile(schemaFile, 'utf-8');
404
- const schemaObj = JSON.parse(raw) as Record<string, unknown>;
405
- const schemaName = path.basename(schemaFile);
406
- const fields = leafFields(schemaObj);
407
- schemaFields.set(schemaName, fields);
408
- }
409
-
410
- // 2. Load fixtures
411
- const fixtures = await loadFixtures(root);
412
-
413
- // 3. Check for missing covers blocks
414
- const missingCovers = fixtures.filter((f) => !f.covers);
415
- if (missingCovers.length > 0) {
416
- console.error('✗ Fixtures missing required "covers" block:');
417
- for (const f of missingCovers) {
418
- console.error(` ${f._source_file}: test_id="${f.test_id}"`);
419
- }
420
- process.exit(1);
421
- }
422
-
423
- // 4. Compute union of all covers
424
- const covered = unionCovers(fixtures);
425
-
426
- // 5. Check schema field coverage
427
- const coverageReports = checkSchemaFieldCoverage(schemaFields, covered);
428
-
429
- // 6. Check params anti-pattern
430
- const antiPatternReports = checkParamsAntiPattern(fixtures);
431
-
432
- // 7. Report
433
- let hasFailure = false;
434
-
435
- if (coverageReports.length > 0) {
436
- hasFailure = true;
437
- console.error('✗ Schema field coverage incomplete:');
438
- for (const r of coverageReports) {
439
- console.error(` ${r.schema}: missing fields [${r.missingFields.join(', ')}]`);
440
- }
441
- }
442
-
443
- if (antiPatternReports.length > 0) {
444
- hasFailure = true;
445
- console.error('✗ Params anti-pattern detected (params not verified in postcondition):');
446
- for (const r of antiPatternReports) {
447
- console.error(` ${r.sourceFile} (${r.fixture}): uncovered params [${r.uncoveredParams.join(', ')}]`);
448
- }
449
- }
450
-
451
- if (hasFailure) {
452
- process.exit(1);
453
- }
454
-
455
- // Compute totals for success message
456
- const totalFields = Array.from(schemaFields.values()).reduce((n, f) => n + f.length, 0);
457
- const totalFixtures = fixtures.length;
458
- console.log(
459
- `✓ All state-schema fields covered: ${schemaFields.size} schemas, ${totalFields} fields across ${totalFixtures} fixtures`,
460
- );
461
- }
462
-
463
- main().catch((err) => {
464
- console.error('Fatal error in conformance-coverage.ts:', err);
465
- process.exit(2);
466
- });