ultra-builder-pro-cli 5.0.1 → 6.0.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 (159) hide show
  1. package/CHANGELOG.md +103 -3
  2. package/README.md +67 -478
  3. package/README.zh-CN.md +49 -422
  4. package/adapters/_shared/frontmatter.cjs +1 -18
  5. package/adapters/_shared/plugin-core.cjs +17 -83
  6. package/adapters/_shared/runtime-assets.cjs +6 -120
  7. package/adapters/claude.js +8 -24
  8. package/adapters/codex.js +13 -32
  9. package/bin/install.js +35 -57
  10. package/docs/PLUGIN-ISOLATION-CONTRACT.md +13 -101
  11. package/docs/examples/drive/claude-reviewer.sh +53 -0
  12. package/docs/examples/drive/claude-writer.sh +43 -0
  13. package/engine/FORMATS.md +1051 -0
  14. package/engine/checks.cjs +139 -0
  15. package/engine/cli.cjs +148 -0
  16. package/engine/completion.cjs +48 -0
  17. package/engine/context.cjs +94 -0
  18. package/engine/drive.cjs +220 -0
  19. package/engine/expect-worker.cjs +5 -0
  20. package/engine/expect.cjs +33 -0
  21. package/engine/formats.cjs +189 -0
  22. package/engine/git-state.cjs +112 -0
  23. package/engine/ledger.cjs +205 -0
  24. package/engine/loading.cjs +28 -0
  25. package/engine/merge.cjs +273 -0
  26. package/engine/milestone.cjs +89 -0
  27. package/engine/plan.cjs +232 -0
  28. package/engine/project.cjs +5 -0
  29. package/engine/signals.cjs +3 -0
  30. package/engine/states.cjs +175 -0
  31. package/hooks/session-start.cjs +54 -0
  32. package/package.json +17 -52
  33. package/skills/ultra/SKILL.md +395 -0
  34. package/.ultra-template/changes/abandoned/.gitkeep +0 -0
  35. package/.ultra-template/changes/active/.gitkeep +0 -1
  36. package/.ultra-template/changes/archive/.gitkeep +0 -1
  37. package/.ultra-template/contexts/TEMPLATE.md +0 -135
  38. package/.ultra-template/decisions/.gitkeep +0 -1
  39. package/.ultra-template/evidence/.gitkeep +0 -1
  40. package/.ultra-template/north-star.md +0 -52
  41. package/.ultra-template/project-brief.md +0 -28
  42. package/.ultra-template/research/.gitkeep +0 -1
  43. package/.ultra-template/specs/architecture.md +0 -81
  44. package/.ultra-template/specs/discovery.md +0 -74
  45. package/.ultra-template/specs/product.md +0 -80
  46. package/.ultra-template/specs/research-distillate.md +0 -57
  47. package/.ultra-template/tasks.json +0 -4
  48. package/.ultra-template/test-report.json +0 -44
  49. package/adapters/_shared/host-profile.cjs +0 -5
  50. package/adapters/grok.js +0 -242
  51. package/adapters/kimi.js +0 -232
  52. package/adapters/opencode.js +0 -408
  53. package/adapters/zcode.js +0 -375
  54. package/bin/delegate-worker.cjs +0 -5
  55. package/bin/delegate.cjs +0 -5
  56. package/docs/ARCHITECTURE.md +0 -274
  57. package/docs/ARTIFACT-AUTHORITY.md +0 -305
  58. package/docs/DECISIONS.md +0 -229
  59. package/docs/PHILOSOPHY.md +0 -261
  60. package/docs/RUNTIME-COMPAT-MATRIX.md +0 -316
  61. package/docs/SKILL-AUTHORING.md +0 -113
  62. package/docs/WORKFLOW-LIFECYCLE.md +0 -350
  63. package/docs/evals/adversarial-review-2026-08-14.md +0 -84
  64. package/docs/evals/ultra-5-0-journey-results.md +0 -62
  65. package/docs/evals/ultra-5-0-live-acp-conformance.md +0 -93
  66. package/docs/evals/ultra-5-0-route-journeys.md +0 -145
  67. package/docs/evals/zcode-automation-2026-08-14.md +0 -103
  68. package/hooks/README.md +0 -52
  69. package/hooks/_common.py +0 -1779
  70. package/hooks/adapters/codex.py +0 -295
  71. package/hooks/adapters/grok.py +0 -134
  72. package/hooks/adapters/kimi.py +0 -156
  73. package/hooks/adapters/zcode.py +0 -227
  74. package/hooks/block_dangerous_commands.py +0 -201
  75. package/hooks/compact_context.py +0 -224
  76. package/hooks/mid_workflow_recall.py +0 -47
  77. package/hooks/post_edit_guard.py +0 -215
  78. package/hooks/session_context.py +0 -741
  79. package/skills/ultra-change/SKILL.md +0 -165
  80. package/skills/ultra-change/references/change-contract.md +0 -199
  81. package/skills/ultra-change/references/execution-grant.md +0 -109
  82. package/skills/ultra-change/references/primary-transfer.md +0 -273
  83. package/skills/ultra-change/scripts/validate_primary_transfer.cjs +0 -1928
  84. package/skills/ultra-delegate/SKILL.md +0 -195
  85. package/skills/ultra-delegate/patches/zcode-acp-0.1.0-zcode-0.16.5.patch +0 -184
  86. package/skills/ultra-delegate/references/delegation-contract.md +0 -138
  87. package/skills/ultra-delegate/scripts/acp-conformance.cjs +0 -287
  88. package/skills/ultra-delegate/scripts/acp-driver.cjs +0 -762
  89. package/skills/ultra-delegate/scripts/agent-execution-driver.cjs +0 -320
  90. package/skills/ultra-delegate/scripts/cli-driver.cjs +0 -61
  91. package/skills/ultra-delegate/scripts/delegate-profiles/kimi-read-only.md +0 -15
  92. package/skills/ultra-delegate/scripts/delegate-profiles/kimi-write.md +0 -17
  93. package/skills/ultra-delegate/scripts/delegate-worker.cjs +0 -328
  94. package/skills/ultra-delegate/scripts/delegate.cjs +0 -690
  95. package/skills/ultra-delegate/scripts/delegate_wait.py +0 -86
  96. package/skills/ultra-delegate/scripts/host-profile.cjs +0 -267
  97. package/skills/ultra-deliver/SKILL.md +0 -194
  98. package/skills/ultra-deliver/references/baseline-reconciliation.md +0 -41
  99. package/skills/ultra-dev/SKILL.md +0 -265
  100. package/skills/ultra-dev/references/debugging.md +0 -22
  101. package/skills/ultra-domain-modeling/SKILL.md +0 -79
  102. package/skills/ultra-grilling/SKILL.md +0 -121
  103. package/skills/ultra-grilling/references/reframing.md +0 -76
  104. package/skills/ultra-init/SKILL.md +0 -143
  105. package/skills/ultra-init/references/brownfield-adoption.md +0 -32
  106. package/skills/ultra-init/scripts/init_project.cjs +0 -1031
  107. package/skills/ultra-plan/SKILL.md +0 -160
  108. package/skills/ultra-plan/references/task-evidence-v2.md +0 -300
  109. package/skills/ultra-plan/scripts/validate_task_evidence.cjs +0 -859
  110. package/skills/ultra-research/SKILL.md +0 -167
  111. package/skills/ultra-research/references/00-problem-validation.md +0 -36
  112. package/skills/ultra-research/references/01-opportunity-discovery.md +0 -33
  113. package/skills/ultra-research/references/02-market-assessment.md +0 -31
  114. package/skills/ultra-research/references/03-alternatives.md +0 -31
  115. package/skills/ultra-research/references/04-product-strategy.md +0 -41
  116. package/skills/ultra-research/references/05-assumptions-validation.md +0 -31
  117. package/skills/ultra-research/references/10-user-personas.md +0 -30
  118. package/skills/ultra-research/references/11-user-scenarios.md +0 -28
  119. package/skills/ultra-research/references/20-user-stories.md +0 -31
  120. package/skills/ultra-research/references/21-features-scope.md +0 -38
  121. package/skills/ultra-research/references/22-success-metrics.md +0 -33
  122. package/skills/ultra-research/references/30-architecture-context.md +0 -30
  123. package/skills/ultra-research/references/31-solution-strategy.md +0 -28
  124. package/skills/ultra-research/references/32-building-blocks.md +0 -28
  125. package/skills/ultra-research/references/40-deployment.md +0 -29
  126. package/skills/ultra-research/references/41-quality-risks.md +0 -28
  127. package/skills/ultra-research/references/99-synthesis.md +0 -59
  128. package/skills/ultra-research/references/north-star-v2.md +0 -168
  129. package/skills/ultra-research/references/wayfinding.md +0 -50
  130. package/skills/ultra-research/scripts/validate_north_star.cjs +0 -1174
  131. package/skills/ultra-review/SKILL.md +0 -275
  132. package/skills/ultra-review/references/adversarial-evaluation.md +0 -31
  133. package/skills/ultra-review/references/code.md +0 -17
  134. package/skills/ultra-review/references/comments.md +0 -12
  135. package/skills/ultra-review/references/design.md +0 -12
  136. package/skills/ultra-review/references/errors.md +0 -13
  137. package/skills/ultra-review/references/review-modes.md +0 -29
  138. package/skills/ultra-review/references/spec.md +0 -18
  139. package/skills/ultra-review/references/tests.md +0 -12
  140. package/skills/ultra-review/references/unified-schema.md +0 -284
  141. package/skills/ultra-review/references/worker-packet.md +0 -183
  142. package/skills/ultra-review/scripts/review_wait.py +0 -2943
  143. package/skills/ultra-status/SKILL.md +0 -209
  144. package/skills/ultra-status/references/route-contract.md +0 -100
  145. package/skills/ultra-tdd/SKILL.md +0 -91
  146. package/skills/ultra-tdd/references/templates/README.md +0 -20
  147. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +0 -79
  148. package/skills/ultra-tdd/references/templates/persistence-real.ts +0 -78
  149. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +0 -78
  150. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +0 -73
  151. package/skills/ultra-tdd/references/templates/vertical-slice.ts +0 -66
  152. package/skills/ultra-tdd/references/test-execution.md +0 -23
  153. package/skills/ultra-test/SKILL.md +0 -244
  154. package/skills/ultra-test/references/export-syntax.md +0 -18
  155. package/skills/ultra-test/scripts/validate_review_transport.cjs +0 -549
  156. package/skills/ultra-test/scripts/worktree_digest.cjs +0 -576
  157. package/skills/ultra-think/SKILL.md +0 -56
  158. package/skills/ultra-think/references/autonomy-boundary.md +0 -48
  159. package/skills/ultra-think/references/interaction-boundary.md +0 -42
@@ -1,549 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const crypto = require('node:crypto');
5
- const { spawnSync } = require('node:child_process');
6
- const fs = require('node:fs');
7
- const path = require('node:path');
8
- const { isDeepStrictEqual } = require('node:util');
9
-
10
- const MAX_REVIEW_JSON_BYTES = 8 * 1024 * 1024;
11
- const READ_CHUNK_BYTES = 64 * 1024;
12
-
13
- function diagnostic(code, message) {
14
- return { code, message };
15
- }
16
-
17
- function parseObject(bytes, diagnostics, label) {
18
- try {
19
- const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
20
- const value = JSON.parse(text);
21
- if (!value || Array.isArray(value) || typeof value !== 'object') {
22
- diagnostics.push(diagnostic(`${label}_not_object`, `${label} must be a JSON object`));
23
- return null;
24
- }
25
- return value;
26
- } catch (error) {
27
- diagnostics.push(diagnostic(`${label}_unreadable`, `${label} is unreadable JSON: ${error.message}`));
28
- return null;
29
- }
30
- }
31
-
32
- function reportLocation(reportFile, diagnostics) {
33
- const reportDirectory = path.dirname(reportFile);
34
- if (path.basename(reportFile) !== 'test-report.json'
35
- || path.basename(reportDirectory) !== '.ultra') {
36
- diagnostics.push(diagnostic(
37
- 'report_canonical_path',
38
- 'report must be the canonical repository .ultra/test-report.json',
39
- ));
40
- return null;
41
- }
42
- try {
43
- const inputProjectRoot = path.dirname(reportDirectory);
44
- const root = fs.realpathSync(inputProjectRoot);
45
- return {
46
- projectRoot: root,
47
- inputProjectRoot,
48
- relative: '.ultra/test-report.json',
49
- path: path.join(root, '.ultra', 'test-report.json'),
50
- };
51
- } catch (error) {
52
- diagnostics.push(diagnostic(
53
- 'report_repository_root',
54
- `report repository root cannot be resolved: ${error.message}`,
55
- ));
56
- return null;
57
- }
58
- }
59
-
60
- function inspectRepositoryPath(projectRoot, relative, label) {
61
- const segments = relative.split('/');
62
- const directories = [projectRoot];
63
- for (const segment of segments.slice(0, -1)) {
64
- directories.push(path.join(directories[directories.length - 1], segment));
65
- }
66
- const componentIdentities = [];
67
- const directoryFlags = fs.constants.O_RDONLY
68
- | (fs.constants.O_DIRECTORY || 0)
69
- | (fs.constants.O_NOFOLLOW || 0);
70
- for (let index = 0; index < directories.length; index += 1) {
71
- const target = directories[index];
72
- const component = index === 0 ? 'repository root' : segments[index - 1];
73
- let entry;
74
- let descriptor;
75
- try {
76
- entry = fs.lstatSync(target, { bigint: true });
77
- if (entry.isSymbolicLink()) {
78
- return {
79
- error: diagnostic(
80
- `${label}_snapshot_symlink_component`,
81
- `${label} canonical path must not contain a symlink component: ${component}`,
82
- ),
83
- };
84
- }
85
- if (!entry.isDirectory()) {
86
- return {
87
- error: diagnostic(
88
- `${label}_snapshot_not_regular`,
89
- `${label} canonical path must end in a regular file beneath real directories; retry after workspace writes settle`,
90
- ),
91
- };
92
- }
93
- descriptor = fs.openSync(target, directoryFlags);
94
- const opened = fs.fstatSync(descriptor, { bigint: true });
95
- if (!opened.isDirectory() || !sameComponentIdentity(entry, opened)) {
96
- return {
97
- error: diagnostic(
98
- `${label}_snapshot_changed`,
99
- `${label} path component changed before its stable raw-byte snapshot could be read; retry after writes settle`,
100
- ),
101
- };
102
- }
103
- componentIdentities.push(componentIdentity(opened));
104
- } catch (error) {
105
- return {
106
- error: diagnostic(
107
- `${label}_snapshot_missing`,
108
- `${label} canonical path is missing or unreadable: ${error.message}`,
109
- ),
110
- };
111
- } finally {
112
- if (descriptor !== undefined) fs.closeSync(descriptor);
113
- }
114
- }
115
-
116
- const target = path.join(projectRoot, ...segments);
117
- let entry;
118
- try {
119
- entry = fs.lstatSync(target, { bigint: true });
120
- } catch (error) {
121
- return {
122
- error: diagnostic(
123
- `${label}_snapshot_missing`,
124
- `${label} canonical path is missing or unreadable: ${error.message}`,
125
- ),
126
- };
127
- }
128
- if (entry.isSymbolicLink()) {
129
- return {
130
- error: diagnostic(
131
- `${label}_snapshot_symlink_component`,
132
- `${label} canonical path must not contain a symlink component: ${segments.at(-1)}`,
133
- ),
134
- };
135
- }
136
- if (!entry.isFile()) {
137
- return {
138
- error: diagnostic(
139
- `${label}_snapshot_not_regular`,
140
- `${label} canonical path must end in a regular file beneath real directories; retry after workspace writes settle`,
141
- ),
142
- };
143
- }
144
- return { target, entry, componentIdentities };
145
- }
146
-
147
- function componentIdentity(value) {
148
- return {
149
- dev: value.dev,
150
- ino: value.ino,
151
- mode: value.mode,
152
- };
153
- }
154
-
155
- function sameComponentIdentity(left, right) {
156
- return left.dev === right.dev
157
- && left.ino === right.ino
158
- && left.mode === right.mode;
159
- }
160
-
161
- function sameComponentIdentities(left, right) {
162
- return left.length === right.length
163
- && left.every((identity, index) => sameComponentIdentity(identity, right[index]));
164
- }
165
-
166
- function sameFileIdentity(left, right) {
167
- return left.dev === right.dev
168
- && left.ino === right.ino
169
- && left.mode === right.mode
170
- && left.size === right.size
171
- && left.mtimeNs === right.mtimeNs
172
- && left.ctimeNs === right.ctimeNs;
173
- }
174
-
175
- function readBoundedStableSnapshot(projectRoot, relative, diagnostics, label) {
176
- const beforePath = inspectRepositoryPath(projectRoot, relative, label);
177
- if (beforePath.error) {
178
- diagnostics.push(beforePath.error);
179
- return null;
180
- }
181
- if (beforePath.entry.size > BigInt(MAX_REVIEW_JSON_BYTES)) {
182
- diagnostics.push(diagnostic(
183
- `${label}_snapshot_oversize`,
184
- `${label} exceeds the ${MAX_REVIEW_JSON_BYTES}-byte snapshot limit`,
185
- ));
186
- return null;
187
- }
188
-
189
- let descriptor;
190
- const chunks = [];
191
- let observed = 0;
192
- let beforeDescriptor;
193
- let afterDescriptor;
194
- try {
195
- descriptor = fs.openSync(
196
- beforePath.target,
197
- fs.constants.O_RDONLY
198
- | fs.constants.O_NONBLOCK
199
- | (fs.constants.O_NOFOLLOW || 0),
200
- );
201
- beforeDescriptor = fs.fstatSync(descriptor, { bigint: true });
202
- if (!beforeDescriptor.isFile()
203
- || !sameFileIdentity(beforePath.entry, beforeDescriptor)) {
204
- diagnostics.push(diagnostic(
205
- `${label}_snapshot_changed`,
206
- `${label} changed before its stable raw-byte snapshot could be read; retry after writes settle`,
207
- ));
208
- return null;
209
- }
210
-
211
- while (true) {
212
- const capacity = Math.min(
213
- READ_CHUNK_BYTES,
214
- MAX_REVIEW_JSON_BYTES + 1 - observed,
215
- );
216
- const chunk = Buffer.allocUnsafe(capacity);
217
- const count = fs.readSync(descriptor, chunk, 0, capacity, null);
218
- if (count === 0) break;
219
- observed += count;
220
- if (observed > MAX_REVIEW_JSON_BYTES) {
221
- diagnostics.push(diagnostic(
222
- `${label}_snapshot_oversize`,
223
- `${label} exceeds the ${MAX_REVIEW_JSON_BYTES}-byte snapshot limit`,
224
- ));
225
- return null;
226
- }
227
- chunks.push(chunk.subarray(0, count));
228
- }
229
- afterDescriptor = fs.fstatSync(descriptor, { bigint: true });
230
- } catch (error) {
231
- diagnostics.push(diagnostic(
232
- `${label}_snapshot_unreadable`,
233
- `${label} raw-byte snapshot could not be captured safely; retry after writes settle: ${error.message}`,
234
- ));
235
- return null;
236
- } finally {
237
- if (descriptor !== undefined) fs.closeSync(descriptor);
238
- }
239
-
240
- const afterPath = inspectRepositoryPath(projectRoot, relative, label);
241
- if (afterPath.error
242
- || !sameComponentIdentities(
243
- beforePath.componentIdentities,
244
- afterPath.componentIdentities,
245
- )
246
- || !sameFileIdentity(beforeDescriptor, afterDescriptor)
247
- || !sameFileIdentity(afterDescriptor, afterPath.entry)
248
- || BigInt(observed) !== afterDescriptor.size) {
249
- diagnostics.push(diagnostic(
250
- `${label}_snapshot_changed`,
251
- `${label} changed while its stable raw-byte snapshot was captured; retry after writes settle`,
252
- ));
253
- return null;
254
- }
255
- return {
256
- path: beforePath.target,
257
- bytes: Buffer.concat(chunks, observed),
258
- };
259
- }
260
-
261
- function canonicalSummaryPath(location, summaryFile, value, diagnostics) {
262
- if (!location || typeof value !== 'string') {
263
- diagnostics.push(diagnostic(
264
- 'review_summary_ref',
265
- 'report review.summary_ref must be a normalized repository-relative summary path',
266
- ));
267
- return null;
268
- }
269
- const segments = value.split('/');
270
- const canonical = segments.length === 4
271
- && segments[0] === '.ultra'
272
- && segments[1] === 'reviews'
273
- && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(segments[2])
274
- && segments[3] === 'SUMMARY.json'
275
- && !value.includes('\\')
276
- && !value.includes('\0')
277
- && path.posix.normalize(value) === value;
278
- if (!canonical) {
279
- diagnostics.push(diagnostic(
280
- 'review_summary_ref',
281
- 'report review.summary_ref must be .ultra/reviews/<session>/SUMMARY.json as a normalized repository-relative POSIX path',
282
- ));
283
- return null;
284
- }
285
-
286
- const candidate = path.join(location.projectRoot, ...segments);
287
- const supplied = path.resolve(summaryFile);
288
- const inputCandidate = path.join(location.inputProjectRoot, ...segments);
289
- if (supplied !== candidate && supplied !== inputCandidate) {
290
- diagnostics.push(diagnostic(
291
- 'review_summary_ref',
292
- 'the CLI summary path must equal the report repository summary_ref',
293
- ));
294
- return null;
295
- }
296
- return {
297
- path: candidate,
298
- relative: value,
299
- };
300
- }
301
-
302
- function validateSummaryWithWaiter(
303
- summaryFile,
304
- summary,
305
- snapshot,
306
- diagnostics,
307
- legacyV4,
308
- ) {
309
- if (!summary || typeof summary.packet_digest !== 'string'
310
- || !/^[0-9a-f]{64}$/.test(summary.packet_digest)) {
311
- diagnostics.push(diagnostic(
312
- 'summary_packet_digest',
313
- 'summary packet_digest must be a lowercase SHA-256 before waiter validation',
314
- ));
315
- return;
316
- }
317
- const waiter = path.resolve(
318
- __dirname,
319
- '..',
320
- '..',
321
- 'ultra-review',
322
- 'scripts',
323
- 'review_wait.py',
324
- );
325
- const waiterArguments = [
326
- waiter,
327
- path.dirname(summaryFile),
328
- 'summary',
329
- '--packet-digest',
330
- summary.packet_digest,
331
- ];
332
- if (legacyV4) waiterArguments.push('--legacy-v4');
333
- waiterArguments.push(
334
- '--summary-snapshot-digest',
335
- snapshot.digest,
336
- );
337
- const result = spawnSync('python3', waiterArguments, {
338
- encoding: 'utf8',
339
- input: snapshot.bytes,
340
- timeout: 10_000,
341
- maxBuffer: 64 * 1024,
342
- env: {
343
- ...process.env,
344
- UBP_REVIEW_WAIT_TIMEOUT: '0.05',
345
- UBP_REVIEW_WAIT_POLL: '0.01',
346
- },
347
- });
348
- if (result.error || result.status !== 0) {
349
- const detail = result.error?.message
350
- || result.stdout?.trim()
351
- || result.stderr?.trim()
352
- || `exit ${result.status}`;
353
- diagnostics.push(diagnostic(
354
- 'summary_waiter_validation',
355
- `summary failed canonical review_wait.py validation: ${detail}`,
356
- ));
357
- return;
358
- }
359
- try {
360
- const receipt = JSON.parse(result.stdout);
361
- if (receipt.status !== 'complete' || receipt.summary_digest !== snapshot.digest) {
362
- diagnostics.push(diagnostic(
363
- 'summary_waiter_snapshot',
364
- 'canonical review_wait.py did not validate the exact supplied SUMMARY.json snapshot',
365
- ));
366
- }
367
- } catch (error) {
368
- diagnostics.push(diagnostic(
369
- 'summary_waiter_snapshot',
370
- `canonical review_wait.py returned an invalid snapshot receipt: ${error.message}`,
371
- ));
372
- }
373
- }
374
-
375
- function validateTransport(summaryFile, reportFile, options = {}) {
376
- const resolvedReport = path.resolve(reportFile);
377
- const diagnostics = [];
378
- const location = reportLocation(resolvedReport, diagnostics);
379
- const reportSnapshot = location
380
- ? readBoundedStableSnapshot(
381
- location.projectRoot,
382
- location.relative,
383
- diagnostics,
384
- 'report',
385
- )
386
- : null;
387
- const report = reportSnapshot
388
- ? parseObject(reportSnapshot.bytes, diagnostics, 'report')
389
- : null;
390
- const review = report && report.review;
391
- const summaryLocation = report
392
- ? canonicalSummaryPath(
393
- location,
394
- summaryFile,
395
- review && typeof review === 'object' && !Array.isArray(review)
396
- ? review.summary_ref
397
- : null,
398
- diagnostics,
399
- )
400
- : null;
401
- const summarySnapshot = summaryLocation
402
- ? readBoundedStableSnapshot(
403
- location.projectRoot,
404
- summaryLocation.relative,
405
- diagnostics,
406
- 'summary',
407
- )
408
- : null;
409
- const summary = summarySnapshot
410
- ? parseObject(summarySnapshot.bytes, diagnostics, 'summary')
411
- : null;
412
- const summaryDigest = summarySnapshot
413
- ? crypto.createHash('sha256').update(summarySnapshot.bytes).digest('hex')
414
- : null;
415
- if (summaryLocation && summarySnapshot) {
416
- validateSummaryWithWaiter(
417
- summaryLocation.path,
418
- summary,
419
- { bytes: summarySnapshot.bytes, digest: summaryDigest },
420
- diagnostics,
421
- options.legacyV4 === true,
422
- );
423
- }
424
-
425
- if (summary && summary.$schema !== 'ultra-review-summary-v4') {
426
- diagnostics.push(diagnostic(
427
- 'summary_schema',
428
- 'summary must use ultra-review-summary-v4',
429
- ));
430
- }
431
- if (report && !['ultra-test-report-v1', 'ultra-test-report-v2'].includes(report.$schema)) {
432
- diagnostics.push(diagnostic(
433
- 'report_schema',
434
- 'report must use ultra-test-report-v1 or ultra-test-report-v2',
435
- ));
436
- }
437
- if (report && report.$schema === 'ultra-test-report-v2' && !Array.isArray(report.task_evidence)) {
438
- diagnostics.push(diagnostic(
439
- 'report_task_evidence',
440
- 'ultra-test-report-v2 task_evidence must be an array',
441
- ));
442
- }
443
- if (summary && !Array.isArray(summary.findings)) {
444
- diagnostics.push(diagnostic('summary_findings', 'summary findings must be an array'));
445
- }
446
- if (report && !Array.isArray(report.findings)) {
447
- diagnostics.push(diagnostic('report_findings', 'report findings must be an array'));
448
- }
449
-
450
- if (report && (!review || Array.isArray(review) || typeof review !== 'object')) {
451
- diagnostics.push(diagnostic('report_review', 'report review must be an object'));
452
- }
453
- if (summary && review && typeof review === 'object' && !Array.isArray(review)) {
454
- const exactMetadata = [
455
- ['session', summary.session],
456
- ['packet_digest', summary.packet_digest],
457
- ['execution_mode', summary.execution_mode],
458
- ['verdict', summary.verdict],
459
- ['context_digest', summary.context_digest],
460
- ['worktree_digest', summary.worktree_digest],
461
- ];
462
- if (options.legacyV4 !== true) {
463
- exactMetadata.push(
464
- ['admission_digest', summary.admission_digest],
465
- ['subject_digest', summary.subject_digest],
466
- );
467
- }
468
- for (const [field, expected] of exactMetadata) {
469
- if (review[field] !== expected) {
470
- diagnostics.push(diagnostic(
471
- `review_${field}`,
472
- `report review.${field} must equal SUMMARY.json ${field}`,
473
- ));
474
- }
475
- }
476
- if (review.finding_schema !== 'ultra-review-findings-v4') {
477
- diagnostics.push(diagnostic(
478
- 'review_finding_schema',
479
- 'report review.finding_schema must be ultra-review-findings-v4',
480
- ));
481
- }
482
- if (review.summary_digest !== summaryDigest) {
483
- diagnostics.push(diagnostic(
484
- 'review_summary_digest',
485
- 'report review.summary_digest must equal the SHA-256 of exact SUMMARY.json bytes',
486
- ));
487
- }
488
- for (const field of ['coverage_refs', 'limitations']) {
489
- if (!isDeepStrictEqual(review[field], summary[field])) {
490
- diagnostics.push(diagnostic(
491
- `review_${field}`,
492
- `report review.${field} must equal SUMMARY.json ${field} unchanged`,
493
- ));
494
- }
495
- }
496
- }
497
- if (summary && report) {
498
- const exactSubjects = [
499
- ['change_id', report.change_id, summary.change_id],
500
- ['task_ids', report.task_ids, summary.task_ids],
501
- ['git_commit', report.git_commit, summary.head],
502
- ];
503
- for (const [field, observed, expected] of exactSubjects) {
504
- if (!isDeepStrictEqual(observed, expected)) {
505
- diagnostics.push(diagnostic(
506
- `review_subject_${field}`,
507
- `report ${field} must equal SUMMARY.json ${field === 'git_commit' ? 'head' : field}`,
508
- ));
509
- }
510
- }
511
- }
512
- if (summary && report && Array.isArray(summary.findings) && Array.isArray(report.findings)
513
- && !isDeepStrictEqual(report.findings, summary.findings)) {
514
- diagnostics.push(diagnostic(
515
- 'finding_transport_mismatch',
516
- 'report findings must equal every SUMMARY.json finding object in order and unchanged',
517
- ));
518
- }
519
-
520
- return {
521
- $schema: 'ultra-review-transport-validation-v1',
522
- summary: summaryLocation?.path || path.resolve(summaryFile),
523
- report: resolvedReport,
524
- summary_digest: summaryDigest,
525
- finding_count: Array.isArray(summary?.findings) ? summary.findings.length : 0,
526
- valid: diagnostics.length === 0,
527
- diagnostics,
528
- };
529
- }
530
-
531
- function main(argv) {
532
- const legacyV4 = argv.length === 5 && argv[4] === '--legacy-v4';
533
- if ((argv.length !== 4 && !legacyV4)
534
- || argv[0] !== '--summary'
535
- || argv[2] !== '--report') {
536
- process.stderr.write(
537
- 'usage: validate_review_transport.cjs --summary <SUMMARY.json> --report <test-report.json> [--legacy-v4]\n',
538
- );
539
- process.exitCode = 2;
540
- return;
541
- }
542
- const result = validateTransport(argv[1], argv[3], { legacyV4 });
543
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
544
- if (!result.valid) process.exitCode = 1;
545
- }
546
-
547
- if (require.main === module) main(process.argv.slice(2));
548
-
549
- module.exports = { validateTransport };