@uwmd/core 1.1.0 → 1.3.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 (153) hide show
  1. package/README.md +3 -3
  2. package/dist/agents/bancroft.d.ts +22 -1
  3. package/dist/agents/bancroft.d.ts.map +1 -1
  4. package/dist/agents/bancroft.js +86 -60
  5. package/dist/agents/bancroft.js.map +1 -1
  6. package/dist/agents/provider.d.ts +62 -0
  7. package/dist/agents/provider.d.ts.map +1 -0
  8. package/dist/agents/provider.js +28 -0
  9. package/dist/agents/provider.js.map +1 -0
  10. package/dist/agents/providers/anthropic.d.ts +20 -0
  11. package/dist/agents/providers/anthropic.d.ts.map +1 -0
  12. package/dist/agents/providers/anthropic.js +104 -0
  13. package/dist/agents/providers/anthropic.js.map +1 -0
  14. package/dist/agents/providers/replay.d.ts +46 -0
  15. package/dist/agents/providers/replay.d.ts.map +1 -0
  16. package/dist/agents/providers/replay.js +162 -0
  17. package/dist/agents/providers/replay.js.map +1 -0
  18. package/dist/agents/schemas.d.ts +3 -3
  19. package/dist/agents/schemas.d.ts.map +1 -1
  20. package/dist/agents/schemas.js.map +1 -1
  21. package/dist/browser.d.ts +26 -3
  22. package/dist/browser.d.ts.map +1 -1
  23. package/dist/browser.js +23 -3
  24. package/dist/browser.js.map +1 -1
  25. package/dist/calc/builtins.d.ts +10 -0
  26. package/dist/calc/builtins.d.ts.map +1 -1
  27. package/dist/calc/builtins.js +64 -42
  28. package/dist/calc/builtins.js.map +1 -1
  29. package/dist/calc/errors.js +1 -1
  30. package/dist/calc/evaluator.js +6 -2
  31. package/dist/calc/evaluator.js.map +1 -1
  32. package/dist/calc/index.d.ts +7 -0
  33. package/dist/calc/index.d.ts.map +1 -1
  34. package/dist/calc/index.js +13 -1
  35. package/dist/calc/index.js.map +1 -1
  36. package/dist/calc/quantize.d.ts +56 -0
  37. package/dist/calc/quantize.d.ts.map +1 -0
  38. package/dist/calc/quantize.js +116 -0
  39. package/dist/calc/quantize.js.map +1 -0
  40. package/dist/cli-packages.d.ts +11 -0
  41. package/dist/cli-packages.d.ts.map +1 -0
  42. package/dist/cli-packages.js +177 -0
  43. package/dist/cli-packages.js.map +1 -0
  44. package/dist/cli.js +424 -28
  45. package/dist/cli.js.map +1 -1
  46. package/dist/context.js +23 -23
  47. package/dist/deal-package-context.d.ts +52 -0
  48. package/dist/deal-package-context.d.ts.map +1 -0
  49. package/dist/deal-package-context.js +132 -0
  50. package/dist/deal-package-context.js.map +1 -0
  51. package/dist/deal-package-zip.d.ts +36 -0
  52. package/dist/deal-package-zip.d.ts.map +1 -0
  53. package/dist/deal-package-zip.js +132 -0
  54. package/dist/deal-package-zip.js.map +1 -0
  55. package/dist/deal-package.d.ts +82 -0
  56. package/dist/deal-package.d.ts.map +1 -0
  57. package/dist/deal-package.js +189 -0
  58. package/dist/deal-package.js.map +1 -0
  59. package/dist/defaults.d.ts +48 -0
  60. package/dist/defaults.d.ts.map +1 -1
  61. package/dist/defaults.js +512 -0
  62. package/dist/defaults.js.map +1 -1
  63. package/dist/index.d.ts +32 -3
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +27 -3
  66. package/dist/index.js.map +1 -1
  67. package/dist/init.js +1 -1
  68. package/dist/init.js.map +1 -1
  69. package/dist/integrity.d.ts +9 -0
  70. package/dist/integrity.d.ts.map +1 -1
  71. package/dist/integrity.js +12 -2
  72. package/dist/integrity.js.map +1 -1
  73. package/dist/lease-abstract.d.ts +82 -0
  74. package/dist/lease-abstract.d.ts.map +1 -0
  75. package/dist/lease-abstract.js +183 -0
  76. package/dist/lease-abstract.js.map +1 -0
  77. package/dist/lite-bridge.d.ts +58 -0
  78. package/dist/lite-bridge.d.ts.map +1 -0
  79. package/dist/lite-bridge.js +525 -0
  80. package/dist/lite-bridge.js.map +1 -0
  81. package/dist/lite.d.ts +63 -0
  82. package/dist/lite.d.ts.map +1 -0
  83. package/dist/lite.js +362 -0
  84. package/dist/lite.js.map +1 -0
  85. package/dist/modules.d.ts.map +1 -1
  86. package/dist/modules.js +278 -13
  87. package/dist/modules.js.map +1 -1
  88. package/dist/packs/excel-emit.d.ts +18 -1
  89. package/dist/packs/excel-emit.d.ts.map +1 -1
  90. package/dist/packs/excel-emit.js +18 -1
  91. package/dist/packs/excel-emit.js.map +1 -1
  92. package/dist/packs/hospitality.d.ts +3 -0
  93. package/dist/packs/hospitality.d.ts.map +1 -0
  94. package/dist/packs/hospitality.js +122 -0
  95. package/dist/packs/hospitality.js.map +1 -0
  96. package/dist/packs/index.d.ts +5 -1
  97. package/dist/packs/index.d.ts.map +1 -1
  98. package/dist/packs/index.js +13 -1
  99. package/dist/packs/index.js.map +1 -1
  100. package/dist/packs/land.d.ts +3 -0
  101. package/dist/packs/land.d.ts.map +1 -0
  102. package/dist/packs/land.js +124 -0
  103. package/dist/packs/land.js.map +1 -0
  104. package/dist/packs/senior-housing.d.ts +3 -0
  105. package/dist/packs/senior-housing.d.ts.map +1 -0
  106. package/dist/packs/senior-housing.js +123 -0
  107. package/dist/packs/senior-housing.js.map +1 -0
  108. package/dist/packs/student-housing.d.ts +3 -0
  109. package/dist/packs/student-housing.d.ts.map +1 -0
  110. package/dist/packs/student-housing.js +124 -0
  111. package/dist/packs/student-housing.js.map +1 -0
  112. package/dist/parser.d.ts +9 -0
  113. package/dist/parser.d.ts.map +1 -1
  114. package/dist/parser.js +40 -6
  115. package/dist/parser.js.map +1 -1
  116. package/dist/protocol.d.ts +69 -2
  117. package/dist/protocol.d.ts.map +1 -1
  118. package/dist/protocol.js +109 -1
  119. package/dist/protocol.js.map +1 -1
  120. package/dist/receipts.d.ts +216 -0
  121. package/dist/receipts.d.ts.map +1 -0
  122. package/dist/receipts.js +554 -0
  123. package/dist/receipts.js.map +1 -0
  124. package/dist/report.js +165 -165
  125. package/dist/runner.d.ts +13 -0
  126. package/dist/runner.d.ts.map +1 -1
  127. package/dist/runner.js +5 -5
  128. package/dist/runner.js.map +1 -1
  129. package/dist/source-representation.d.ts +40 -0
  130. package/dist/source-representation.d.ts.map +1 -0
  131. package/dist/source-representation.js +114 -0
  132. package/dist/source-representation.js.map +1 -0
  133. package/dist/types.d.ts +9 -0
  134. package/dist/types.d.ts.map +1 -1
  135. package/dist/types.js +34 -0
  136. package/dist/types.js.map +1 -1
  137. package/dist/uwcsv.d.ts.map +1 -1
  138. package/dist/uwcsv.js +10 -78
  139. package/dist/uwcsv.js.map +1 -1
  140. package/dist/uwxml.d.ts.map +1 -1
  141. package/dist/uwxml.js +10 -2
  142. package/dist/uwxml.js.map +1 -1
  143. package/dist/validator.js +10 -10
  144. package/dist/validator.js.map +1 -1
  145. package/dist/version.d.ts +3 -0
  146. package/dist/version.d.ts.map +1 -0
  147. package/dist/version.js +8 -0
  148. package/dist/version.js.map +1 -0
  149. package/dist/zip-safety.d.ts +15 -0
  150. package/dist/zip-safety.d.ts.map +1 -0
  151. package/dist/zip-safety.js +109 -0
  152. package/dist/zip-safety.js.map +1 -0
  153. package/package.json +12 -3
package/dist/cli.js CHANGED
@@ -10,12 +10,14 @@ import { compact, diff } from './compactor.js';
10
10
  import { generateBlankUWFile } from './init.js';
11
11
  import { render } from './renderer.js';
12
12
  import { renderReportHtml } from './report.js';
13
- import { stringifyUWJsonWithDigest } from './uwjson.js';
14
- import { toUWEnvelope } from './envelope.js';
13
+ import { stringifyUWEnvelope } from './uwjson.js';
14
+ import { stampEnvelopeDigest, toUWEnvelope, } from './envelope.js';
15
15
  import { CORE_CODEC_REGISTRY } from './codecs.js';
16
16
  import { writeAgentBlock } from './runner.js';
17
17
  import { applyEdit } from './editor.js';
18
18
  import { verifyChain, verifyProvenance } from './integrity.js';
19
+ import { loadModuleManifest, createModuleRegistry, ModuleRegistryError } from './modules.js';
20
+ import { cmdLeaseValidate, cmdLeaseProject, cmdPackageCreate, cmdPackageVerify, cmdPackageList, cmdPackageToContext, cmdPackageContextValidate, cmdPackageEdges, } from './cli-packages.js';
19
21
  import { evaluateCalc } from './calc/index.js';
20
22
  import { buildAgentContext, buildAgentPrompt, isContextReady, BANCROFT_LAYERS } from './context.js';
21
23
  import { runBancroftAgent } from './agents/bancroft.js';
@@ -24,6 +26,12 @@ import { rankGaps } from './refinement.js';
24
26
  import { resolveValue } from './cascade.js';
25
27
  import { getAssetClassDefaults } from './defaults.js';
26
28
  import { MULTIFAMILY_PACK, getPackForAssetClass } from './packs/index.js';
29
+ import { issueReceipt, verifyReceipt, assertUWReceipt } from './receipts.js';
30
+ import { CORE_VERSION } from './version.js';
31
+ import { detectUWSourceRepresentation, UWX_REPRESENTATION_ID, UW_LITE_SOURCE_DESCRIPTOR, UWX_SOURCE_DESCRIPTOR, UWX_EXTENSION, } from './source-representation.js';
32
+ import { parseUWLite } from './lite.js';
33
+ import { compileUWLite, projectUWEnvelopeToLite, stringifyUWX, } from './lite-bridge.js';
34
+ import { UW_LITE_REPRESENTATION_ID } from './source-representation.js';
27
35
  const [, , command, ...args] = process.argv;
28
36
  function readFile(path) {
29
37
  const resolved = resolve(path);
@@ -71,6 +79,20 @@ function parseFlags(rawArgs) {
71
79
  // ─── Commands ─────────────────────────────────────────────────────────────────
72
80
  function cmdParse(file, flags) {
73
81
  const content = readFile(file);
82
+ const detection = detectUWSourceRepresentation(content, file);
83
+ if (detection.representation !== UWX_REPRESENTATION_ID) {
84
+ const lite = parseUWLite(content);
85
+ console.log(JSON.stringify({
86
+ representation: lite.representation,
87
+ representation_version: lite.representation_version,
88
+ frontmatter: lite.frontmatter,
89
+ fields: lite.fields,
90
+ issues: lite.issues,
91
+ }, null, flags['compact'] ? 0 : 2));
92
+ return;
93
+ }
94
+ for (const warning of detection.warnings)
95
+ console.warn(`Warning: ${warning}`);
74
96
  const parsed = parseUWFile(content, { strict: flags['strict'] === true });
75
97
  const output = {
76
98
  frontmatter: parsed.frontmatter,
@@ -83,6 +105,38 @@ function cmdParse(file, flags) {
83
105
  }
84
106
  function cmdValidate(file, flags) {
85
107
  const content = readFile(file);
108
+ const detection = detectUWSourceRepresentation(content, file);
109
+ if (detection.representation !== UWX_REPRESENTATION_ID) {
110
+ const lite = parseUWLite(content);
111
+ const errors = lite.issues.filter((issue) => issue.severity === 'error');
112
+ const warnings = lite.issues.filter((issue) => issue.severity === 'warning');
113
+ const result = {
114
+ representation: lite.representation,
115
+ overall_status: errors.length > 0 ? 'errors' : warnings.length > 0 ? 'warnings' : 'clean',
116
+ issues: lite.issues,
117
+ errors,
118
+ warnings,
119
+ };
120
+ if (flags['json']) {
121
+ console.log(JSON.stringify(result, null, 2));
122
+ return;
123
+ }
124
+ console.log(`\n${result.overall_status.toUpperCase()} - ${basename(file)}\n`);
125
+ if (result.issues.length === 0) {
126
+ console.log('No Lite syntax issues found.');
127
+ }
128
+ else {
129
+ for (const issue of result.issues) {
130
+ const location = issue.line ? ` line ${issue.line}` : '';
131
+ const field = issue.field_path ? ` [${issue.field_path}]` : '';
132
+ console.log(` [${issue.severity.toUpperCase()}]${location}${field} ${issue.code}: ${issue.message}`);
133
+ }
134
+ }
135
+ console.log('');
136
+ process.exit(errors.length > 0 ? 1 : 0);
137
+ }
138
+ for (const warning of detection.warnings)
139
+ console.warn(`Warning: ${warning}`);
86
140
  const parsed = parseUWFile(content);
87
141
  const instCfg = loadInstitutionConfig(flags['institution']);
88
142
  const result = validateUWFile(parsed, instCfg?.thresholds);
@@ -229,8 +283,13 @@ function cmdInit(flags) {
229
283
  dealStage: flags['stage'],
230
284
  tier: flags['tier'] ?? 'screener',
231
285
  });
286
+ // generateBlankUWFile() emits structured UWX content, so the default filename
287
+ // must be .uwx.md. Writing it as .uw.md produced a file the format spec
288
+ // forbids and detectUWSourceRepresentation() flags as legacy on the next load.
232
289
  const filename = flags['output']
233
- ?? (flags['name'] ? `${flags['name'].replace(/\s+/g, '-').toLowerCase()}.uw.md` : 'new-deal.uw.md');
290
+ ?? (flags['name']
291
+ ? `${flags['name'].replace(/\s+/g, '-').toLowerCase()}${UWX_EXTENSION}`
292
+ : `new-deal${UWX_EXTENSION}`);
234
293
  const outPath = resolve(filename);
235
294
  writeFileSync(outPath, content, 'utf-8');
236
295
  console.log(`Created: ${outPath}`);
@@ -269,16 +328,14 @@ function cmdSummary(file) {
269
328
  `);
270
329
  }
271
330
  async function cmdExport(file, flags) {
272
- const content = readFile(file);
273
- const parsed = parseUWFile(content);
274
- const text = await stringifyUWJsonWithDigest(parsed, {
275
- // --no-superseded or --compact drops the append-only history.
276
- includeSuperseded: !(flags['no-superseded'] === true || flags['compact'] === true),
277
- });
331
+ const includeSuperseded = !(flags['no-superseded'] === true || flags['compact'] === true);
332
+ const loaded = await loadEnvelope(file);
333
+ const envelope = includeSuperseded ? loaded : { ...loaded, superseded: {} };
334
+ const text = stringifyUWEnvelope(await stampEnvelopeDigest(envelope));
278
335
  // Default output path: swap the .uw.md suffix for .uw.json (fall back to appending).
279
336
  const outPath = flags['output']
280
337
  ? resolve(flags['output'])
281
- : resolve(file.endsWith('.uw.md') ? `${file.slice(0, -'.uw.md'.length)}.uw.json` : `${file}.uw.json`);
338
+ : resolve(replaceUWExtension(file, '.uw.json'));
282
339
  if (flags['stdout']) {
283
340
  process.stdout.write(text);
284
341
  return;
@@ -292,12 +349,33 @@ const FORMAT_ALIASES = {
292
349
  csv: 'uw-csv-bundle',
293
350
  'csv-bundle': 'uw-csv-bundle',
294
351
  'uw-json': 'uw-json',
352
+ lite: 'uw-lite-markdown',
353
+ uw: 'uw-lite-markdown',
354
+ uwx: 'uwx-markdown',
355
+ 'uw-lite': 'uw-lite-markdown',
356
+ 'uw-lite-markdown': 'uw-lite-markdown',
357
+ 'uwx-markdown': 'uwx-markdown',
295
358
  'uw-xml': 'uw-xml',
296
359
  'uw-csv-bundle': 'uw-csv-bundle',
297
360
  };
298
361
  async function loadEnvelope(file) {
299
- if (file.toLowerCase().endsWith('.uw.md')) {
300
- return toUWEnvelope(parseUWFile(readFile(file)));
362
+ const lower = file.toLowerCase();
363
+ if (lower.endsWith('.uw.md') || lower.endsWith(UWX_EXTENSION)) {
364
+ const content = readFile(file);
365
+ const detection = detectUWSourceRepresentation(content, file);
366
+ if (detection.representation !== UWX_REPRESENTATION_ID) {
367
+ const compilation = compileUWLite(parseUWLite(content));
368
+ if (!compilation.ok) {
369
+ const issues = compilation.report.issues
370
+ .map((issue) => `${issue.code}: ${issue.message}`)
371
+ .join('; ');
372
+ throw new Error(`UW Lite compilation failed: ${issues}`);
373
+ }
374
+ return compilation.envelope;
375
+ }
376
+ for (const warning of detection.warnings)
377
+ console.warn(`Warning: ${warning}`);
378
+ return toUWEnvelope(parseUWFile(content));
301
379
  }
302
380
  const sourceCodec = CORE_CODEC_REGISTRY.findByFileName(file);
303
381
  if (!sourceCodec || !sourceCodec.descriptor.directions.includes('read')) {
@@ -308,9 +386,17 @@ async function loadEnvelope(file) {
308
386
  : readFile(file);
309
387
  return sourceCodec.decode(input);
310
388
  }
389
+ /**
390
+ * Swap a known UW source extension for `extension`, appending if none matches.
391
+ *
392
+ * `.uwx.md` must be in this list. Without it a UWX input fell through to the
393
+ * append branch, so `uwmd export deal.uwx.md` wrote `deal.uwx.md.uw.json`
394
+ * rather than `deal.uw.json`. (`.uw.md` does not match a `.uwx.md` filename —
395
+ * the suffixes genuinely differ — so it was silently a no-op, not a mis-match.)
396
+ */
311
397
  function replaceUWExtension(file, extension) {
312
398
  const lower = file.toLowerCase();
313
- for (const suffix of ['.uw.csv.zip', '.uw.md', '.uw.json', '.uw.xml']) {
399
+ for (const suffix of [UWX_EXTENSION, '.uw.csv.zip', '.uw.md', '.uw.json', '.uw.xml']) {
314
400
  if (lower.endsWith(suffix))
315
401
  return `${file.slice(0, -suffix.length)}${extension}`;
316
402
  }
@@ -319,15 +405,39 @@ function replaceUWExtension(file, extension) {
319
405
  async function cmdConvert(file, flags) {
320
406
  const requested = flags['to'];
321
407
  if (typeof requested !== 'string') {
322
- throw new Error('convert requires --to uw-json|uw-xml|uw-csv-bundle.');
408
+ throw new Error('convert requires --to uw-lite-markdown|uwx-markdown|uw-json|uw-xml|uw-csv-bundle.');
323
409
  }
324
410
  const targetId = FORMAT_ALIASES[requested.toLowerCase()] ?? requested;
325
- const target = CORE_CODEC_REGISTRY.get(targetId);
326
- if (!target.descriptor.directions.includes('write')) {
327
- throw new Error(`Representation ${targetId} is not writable.`);
328
- }
329
411
  const envelope = await loadEnvelope(file);
330
- const encoded = await target.encode(envelope);
412
+ let encoded;
413
+ let extension;
414
+ if (targetId === UWX_REPRESENTATION_ID) {
415
+ encoded = stringifyUWX(envelope);
416
+ extension = UWX_EXTENSION;
417
+ }
418
+ else if (targetId === UW_LITE_REPRESENTATION_ID) {
419
+ const projection = projectUWEnvelopeToLite(envelope);
420
+ encoded = projection.content;
421
+ extension = '.uw.md';
422
+ if (projection.report.lossy) {
423
+ console.warn(`Warning: Lite projection omitted ${projection.report.omitted_paths.length} advanced path(s).`);
424
+ }
425
+ if (typeof flags['projection-report'] === 'string') {
426
+ writeFileSync(resolve(flags['projection-report']), JSON.stringify(projection.report, null, 2), 'utf-8');
427
+ }
428
+ }
429
+ else {
430
+ const target = CORE_CODEC_REGISTRY.get(targetId);
431
+ if (!target.descriptor.directions.includes('write')) {
432
+ throw new Error(`Representation ${targetId} is not writable.`);
433
+ }
434
+ const targetEncoded = await target.encode(envelope);
435
+ if (typeof targetEncoded !== 'string' && !(targetEncoded instanceof Uint8Array)) {
436
+ throw new Error(`Representation ${targetId} did not produce file-compatible output.`);
437
+ }
438
+ encoded = targetEncoded;
439
+ extension = target.descriptor.file_extensions[0];
440
+ }
331
441
  if (typeof encoded !== 'string' && !(encoded instanceof Uint8Array)) {
332
442
  throw new Error(`Representation ${targetId} did not produce file-compatible output.`);
333
443
  }
@@ -335,15 +445,174 @@ async function cmdConvert(file, flags) {
335
445
  process.stdout.write(typeof encoded === 'string' ? encoded : Buffer.from(encoded));
336
446
  return;
337
447
  }
338
- const extension = target.descriptor.file_extensions[0];
339
448
  const outPath = flags['output']
340
449
  ? resolve(flags['output'])
341
450
  : resolve(replaceUWExtension(file, extension));
342
451
  writeFileSync(outPath, encoded, 'utf-8');
343
452
  console.log(`Converted ${basename(file)} → ${basename(outPath)} (${targetId})`);
344
453
  }
454
+ // ─── Receipts (RFC 0016 / spec/UW_RECEIPT_v1.md) ─────────────────────────────
455
+ async function cmdReceiptIssue(file, flags) {
456
+ const receipt = await issueReceipt(readFile(file), {
457
+ filename: file,
458
+ issuer: `uwmd-cli@${CORE_VERSION}`,
459
+ ...(typeof flags['issued-at'] === 'string' ? { issued_at: flags['issued-at'] } : {}),
460
+ });
461
+ const serialized = `${JSON.stringify(receipt, null, 2)}\n`;
462
+ if (flags['stdout']) {
463
+ process.stdout.write(serialized);
464
+ return;
465
+ }
466
+ const outPath = flags['output']
467
+ ? resolve(flags['output'])
468
+ : resolve(`${file.replace(/\.(uw|uwx)\.md$/i, '')}.receipt.json`);
469
+ writeFileSync(outPath, serialized, 'utf-8');
470
+ const computed = receipt.computation.results.filter((r) => r.computed).length;
471
+ console.log(`Issued receipt for ${basename(file)} → ${basename(outPath)} ` +
472
+ `(${receipt.computation.pack}@${receipt.computation.pack_version}, ` +
473
+ `${computed}/${receipt.computation.results.length} outputs computed)`);
474
+ console.log('A receipt attests that these outputs follow from this record. It does not attest that the inputs are true.');
475
+ }
476
+ // ─── Modules ──────────────────────────────────────────────────────────────────
477
+ function readManifestFile(path) {
478
+ try {
479
+ return { ok: true, manifest: JSON.parse(readFileSync(resolve(path), 'utf-8')) };
480
+ }
481
+ catch (e) {
482
+ return { ok: false, message: e instanceof Error ? e.message : String(e) };
483
+ }
484
+ }
485
+ function hostTierFlag(flags) {
486
+ const tier = flags['tier'];
487
+ return typeof tier === 'string' ? tier : undefined;
488
+ }
489
+ function cmdModulesValidate(files, flags) {
490
+ const hostTier = hostTierFlag(flags);
491
+ const reports = files.map((file) => {
492
+ const read = readManifestFile(file);
493
+ if (!read.ok) {
494
+ return { file, ok: false, errors: [{ category: 'module', code: 'CLI-MOD-READ', message: read.message }] };
495
+ }
496
+ const result = loadModuleManifest(read.manifest, hostTier ? { hostTier } : {});
497
+ return { file, ok: result.ok, errors: result.errors };
498
+ });
499
+ if (flags['json']) {
500
+ console.log(JSON.stringify({ modules: reports }, null, 2));
501
+ process.exit(reports.every((r) => r.ok) ? 0 : 1);
502
+ }
503
+ for (const report of reports) {
504
+ if (report.ok) {
505
+ console.log(`\nOK - ${basename(report.file)}`);
506
+ continue;
507
+ }
508
+ console.log(`\nERRORS - ${basename(report.file)}`);
509
+ for (const error of report.errors) {
510
+ // The pointer is what makes this actionable: it names the exact
511
+ // declaration that failed, not just the manifest.
512
+ const at = 'pointer' in error && error.pointer ? ` [${error.pointer}]` : '';
513
+ console.log(` ${error.code}${at} ${error.message}`);
514
+ }
515
+ }
516
+ const failed = reports.filter((r) => !r.ok).length;
517
+ console.log(`\n${reports.length - failed}/${reports.length} manifest(s) loaded${hostTier ? ` against ${hostTier}` : ''}.\n`);
518
+ process.exit(failed > 0 ? 1 : 0);
519
+ }
520
+ function cmdModulesList(files, flags) {
521
+ const hostTier = hostTierFlag(flags);
522
+ const manifests = [];
523
+ for (const file of files) {
524
+ const read = readManifestFile(file);
525
+ if (!read.ok) {
526
+ console.error(`Cannot read ${basename(file)}: ${read.message}`);
527
+ process.exit(1);
528
+ }
529
+ manifests.push(read.manifest);
530
+ }
531
+ let registry;
532
+ try {
533
+ registry = createModuleRegistry({ modules: manifests, ...(hostTier ? { hostTier } : {}) });
534
+ }
535
+ catch (e) {
536
+ // Registry construction is all-or-nothing: a bad manifest means there is no
537
+ // registry to list, so report why rather than printing a partial one.
538
+ if (e instanceof ModuleRegistryError) {
539
+ console.error('Registry refused to load:');
540
+ for (const error of e.errors)
541
+ console.error(` ${error.code} ${error.message}`);
542
+ }
543
+ else {
544
+ console.error(e instanceof Error ? e.message : String(e));
545
+ }
546
+ process.exit(1);
547
+ }
548
+ const rows = registry.modules.map((m) => ({
549
+ id: m.id,
550
+ name: m.name,
551
+ version: m.version,
552
+ requires_tier: m.requires_tier,
553
+ asset_classes: m.asset_classes ?? [],
554
+ calculations: (m.calculations ?? []).length,
555
+ validations: (m.validations ?? []).length,
556
+ sections: (m.sections ?? []).length,
557
+ agent_layers: (m.agent_layers ?? []).length,
558
+ }));
559
+ if (flags['json']) {
560
+ console.log(JSON.stringify({ modules: rows }, null, 2));
561
+ return;
562
+ }
563
+ console.log(`\n${rows.length} module(s) loaded\n`);
564
+ for (const row of rows) {
565
+ console.log(` ${row.id}@${row.version} — ${row.name}`);
566
+ console.log(` tier: ${row.requires_tier}`);
567
+ console.log(` asset classes: ${row.asset_classes.length ? row.asset_classes.join(', ') : 'class-agnostic'}`);
568
+ console.log(` contributes: ${row.calculations} calc, ${row.validations} validation, ` +
569
+ `${row.sections} section, ${row.agent_layers} agent layer`);
570
+ }
571
+ console.log('');
572
+ }
573
+ async function cmdReceiptVerify(file, receiptPath, flags) {
574
+ let receipt;
575
+ try {
576
+ const raw = JSON.parse(readFileSync(resolve(receiptPath), 'utf-8'));
577
+ assertUWReceipt(raw);
578
+ receipt = raw;
579
+ }
580
+ catch (e) {
581
+ console.error(`Not a readable receipt: ${e instanceof Error ? e.message : String(e)}`);
582
+ process.exit(2);
583
+ }
584
+ const result = await verifyReceipt(receipt, readFile(file), { filename: file });
585
+ if (flags['json']) {
586
+ console.log(JSON.stringify(result, null, 2));
587
+ }
588
+ else {
589
+ console.log(`Verdict: ${result.verdict.toUpperCase()}`);
590
+ for (const issue of result.issues) {
591
+ const detail = issue.expected !== undefined || issue.actual !== undefined
592
+ ? ` (expected ${issue.expected ?? '—'}, got ${issue.actual ?? '—'})`
593
+ : '';
594
+ console.log(` [${issue.code}] ${issue.message}${detail}`);
595
+ }
596
+ if (result.verdict === 'verified') {
597
+ console.log('\nThis record is unchanged since issuance and its stated outputs follow deterministically\n' +
598
+ 'from its contents. It is NOT a statement that the inputs are true, complete, or approved.');
599
+ }
600
+ else if (result.verdict === 'unverifiable') {
601
+ console.log('\nThis verifier could not decide. That is not a negative result.');
602
+ }
603
+ }
604
+ // 0 verified, 1 failed, 3 unverifiable — so scripts can tell the third state apart.
605
+ if (result.verdict === 'failed')
606
+ process.exit(1);
607
+ if (result.verdict === 'unverifiable')
608
+ process.exit(3);
609
+ }
345
610
  function cmdFormats(flags) {
346
- const descriptors = CORE_CODEC_REGISTRY.list();
611
+ const descriptors = [
612
+ UW_LITE_SOURCE_DESCRIPTOR,
613
+ UWX_SOURCE_DESCRIPTOR,
614
+ ...CORE_CODEC_REGISTRY.list(),
615
+ ];
347
616
  if (flags['json']) {
348
617
  console.log(JSON.stringify(descriptors, null, 2));
349
618
  return;
@@ -360,10 +629,12 @@ function cmdReport(file, flags) {
360
629
  tier: flags['tier'],
361
630
  preparedBy: flags['prepared-by'],
362
631
  });
363
- // Default output path: swap the .uw.md suffix for .report.html.
632
+ // Default output path: swap the source extension for .report.html. Routed
633
+ // through replaceUWExtension so every UW source extension is handled in one
634
+ // place — the inline .uw.md check this replaced missed .uwx.md entirely.
364
635
  const outPath = flags['output']
365
636
  ? resolve(flags['output'])
366
- : resolve(file.endsWith('.uw.md') ? `${file.slice(0, -'.uw.md'.length)}.report.html` : `${file}.report.html`);
637
+ : resolve(replaceUWExtension(file, '.report.html'));
367
638
  if (flags['stdout']) {
368
639
  process.stdout.write(result.html);
369
640
  return;
@@ -759,7 +1030,7 @@ for (let _i = 0; _i < args.length; _i++) {
759
1030
  break;
760
1031
  case 'convert':
761
1032
  if (!positional[0]) {
762
- console.error('Usage: uwmd convert <file.uw.md|file.uw.json|file.uw.xml|file.uw.csv.zip> --to uw-json|uw-xml|uw-csv-bundle [--output <file>] [--stdout]');
1033
+ console.error('Usage: uwmd convert <file.uw.md|file.uwx.md|file.uw.json|file.uw.xml|file.uw.csv.zip> --to lite|uwx|uw-json|uw-xml|uw-csv-bundle [--projection-report <file>] [--output <file>] [--stdout]');
763
1034
  process.exit(1);
764
1035
  }
765
1036
  await cmdConvert(positional[0], flags);
@@ -785,9 +1056,127 @@ for (let _i = 0; _i < args.length; _i++) {
785
1056
  }
786
1057
  cmdRefine(positional[0], flags);
787
1058
  break;
1059
+ case 'lease': {
1060
+ const sub = positional[0];
1061
+ if (sub === 'validate') {
1062
+ if (!positional[1]) {
1063
+ console.error('Usage: uwmd lease validate <abstract.json> [--json]');
1064
+ process.exit(1);
1065
+ }
1066
+ cmdLeaseValidate(positional[1], flags);
1067
+ }
1068
+ else if (sub === 'project') {
1069
+ if (!positional[1]) {
1070
+ console.error('Usage: uwmd lease project <abstract.json> [--compact]');
1071
+ process.exit(1);
1072
+ }
1073
+ cmdLeaseProject(positional[1], flags);
1074
+ }
1075
+ else {
1076
+ console.error('Usage: uwmd lease <validate|project> <abstract.json>');
1077
+ process.exit(1);
1078
+ }
1079
+ break;
1080
+ }
1081
+ case 'package': {
1082
+ const sub = positional[0];
1083
+ const target = positional[1];
1084
+ if (sub === 'create') {
1085
+ if (!target) {
1086
+ console.error('Usage: uwmd package create <manifest.json> [--output <file.uwpkg.zip>]');
1087
+ process.exit(1);
1088
+ }
1089
+ await cmdPackageCreate(target, flags);
1090
+ }
1091
+ else if (sub === 'verify') {
1092
+ if (!target) {
1093
+ console.error('Usage: uwmd package verify <file.uwpkg.zip> [--json]');
1094
+ process.exit(1);
1095
+ }
1096
+ await cmdPackageVerify(target, flags);
1097
+ }
1098
+ else if (sub === 'list') {
1099
+ if (!target) {
1100
+ console.error('Usage: uwmd package list <file.uwpkg.zip> [--json]');
1101
+ process.exit(1);
1102
+ }
1103
+ cmdPackageList(target, flags);
1104
+ }
1105
+ else if (sub === 'to-context') {
1106
+ if (!target) {
1107
+ console.error('Usage: uwmd package to-context <file.uwpkg.zip> [--output <file.json>] [--stdout]');
1108
+ process.exit(1);
1109
+ }
1110
+ cmdPackageToContext(target, flags);
1111
+ }
1112
+ else if (sub === 'validate-context') {
1113
+ if (!target) {
1114
+ console.error('Usage: uwmd package validate-context <file.uwpkg.context.json> [--json]');
1115
+ process.exit(1);
1116
+ }
1117
+ cmdPackageContextValidate(target, flags);
1118
+ }
1119
+ else if (sub === 'edges') {
1120
+ if (!target) {
1121
+ console.error('Usage: uwmd package edges <file.uwpkg.zip>');
1122
+ process.exit(1);
1123
+ }
1124
+ cmdPackageEdges(target);
1125
+ }
1126
+ else {
1127
+ console.error('Usage: uwmd package <create|verify|list|to-context|validate-context|edges> ...');
1128
+ process.exit(1);
1129
+ }
1130
+ break;
1131
+ }
1132
+ case 'modules': {
1133
+ const sub = positional[0];
1134
+ const files = positional.slice(1);
1135
+ if (sub === 'validate') {
1136
+ if (files.length === 0) {
1137
+ console.error('Usage: uwmd modules validate <manifest.json...> [--tier <viewer-tier>] [--json]');
1138
+ process.exit(1);
1139
+ }
1140
+ cmdModulesValidate(files, flags);
1141
+ }
1142
+ else if (sub === 'list') {
1143
+ if (files.length === 0) {
1144
+ console.error('Usage: uwmd modules list <manifest.json...> [--tier <viewer-tier>] [--json]');
1145
+ process.exit(1);
1146
+ }
1147
+ cmdModulesList(files, flags);
1148
+ }
1149
+ else {
1150
+ console.error('Usage: uwmd modules <validate|list> <manifest.json...>');
1151
+ process.exit(1);
1152
+ }
1153
+ break;
1154
+ }
1155
+ case 'receipt': {
1156
+ const sub = positional[0];
1157
+ if (sub === 'issue') {
1158
+ if (!positional[1]) {
1159
+ console.error('Usage: uwmd receipt issue <file> [--output <file.receipt.json>] [--issued-at <iso>] [--stdout]');
1160
+ process.exit(1);
1161
+ }
1162
+ await cmdReceiptIssue(positional[1], flags);
1163
+ }
1164
+ else if (sub === 'verify') {
1165
+ if (!positional[1] || !positional[2]) {
1166
+ console.error('Usage: uwmd receipt verify <file> <receipt.json> [--json]');
1167
+ process.exit(1);
1168
+ }
1169
+ await cmdReceiptVerify(positional[1], positional[2], flags);
1170
+ }
1171
+ else {
1172
+ console.error('Usage: uwmd receipt <issue|verify> ...');
1173
+ process.exit(1);
1174
+ }
1175
+ break;
1176
+ }
788
1177
  default:
789
1178
  console.log(`
790
- uwmd — .uw.md underwriting file toolkit
1179
+ uwmd — UW Markdown underwriting file toolkit
791
1180
 
792
1181
  Commands:
793
1182
  parse <file> Parse file and output JSON
@@ -799,14 +1188,18 @@ Commands:
799
1188
  compact <file> Strip superseded blocks
800
1189
  diff <file-a> <file-b> Compare two files section by section
801
1190
  calc <file> <calc.json> Evaluate a calc declaration or inline formula (Tier-3)
802
- init Generate a blank .uw.md file
1191
+ init Generate a blank .uwx.md file
803
1192
  summary <file> Print quick metrics to terminal
804
1193
  export <file> Export a digested UW JSON 1.0 document
805
1194
  formats List registered machine representations
806
- convert <file> Convert Markdown/JSON/XML/CSV bundle representations
1195
+ convert <file> Convert Lite/UWX/JSON/XML/CSV bundle representations
807
1196
  report <file> Render the lender package / credit memo HTML (§7.1/§7.2)
808
1197
  scope <file> Resolve every required input via the fallback cascade and emit a triage view
809
1198
  refine <file> Rank gaps by value-of-information for stated calc targets
1199
+ receipt issue|verify Issue or verify a detached verification receipt (RFC 0016)
1200
+ modules validate|list Validate module manifest files, or list the registry they form
1201
+ lease validate|project Validate a lease abstract, or project it to a rent-roll row (RFC 0018)
1202
+ package create|verify|... Build, verify, list, or project a UW Deal Package (RFC 0018)
810
1203
  layers List Bancroft agent layers
811
1204
 
812
1205
  Options:
@@ -817,7 +1210,10 @@ Options:
817
1210
  --format <f> Render format: json|csv|chat|summary (render, default: summary)
818
1211
  --no-superseded Drop append-only history from the .uw.json export (export)
819
1212
  --stdout Write export/convert output to stdout
820
- --to <format> Target representation: uw-json|uw-xml|uw-csv-bundle (convert)
1213
+ --to <format> Target: lite|uwx|uw-json|uw-xml|uw-csv-bundle (convert)
1214
+ --projection-report Write the UWX-to-Lite omission report as JSON (convert)
1215
+ --issued-at <iso> Fix the receipt issuance timestamp (receipt issue)
1216
+ --tier <tier> Host viewer tier to load modules against (modules)
821
1217
  --max-tokens <n> Max tokens for chat render (default: 12000)
822
1218
  --institution <f> Path to .uw.institution.json config (validate)
823
1219
  --name <n> Deal name (init)