@uwmd/core 1.1.2 → 1.5.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.
- package/README.md +3 -3
- package/dist/agents/bancroft.d.ts +22 -1
- package/dist/agents/bancroft.d.ts.map +1 -1
- package/dist/agents/bancroft.js +86 -60
- package/dist/agents/bancroft.js.map +1 -1
- package/dist/agents/provider.d.ts +62 -0
- package/dist/agents/provider.d.ts.map +1 -0
- package/dist/agents/provider.js +28 -0
- package/dist/agents/provider.js.map +1 -0
- package/dist/agents/providers/anthropic.d.ts +20 -0
- package/dist/agents/providers/anthropic.d.ts.map +1 -0
- package/dist/agents/providers/anthropic.js +104 -0
- package/dist/agents/providers/anthropic.js.map +1 -0
- package/dist/agents/providers/replay.d.ts +46 -0
- package/dist/agents/providers/replay.d.ts.map +1 -0
- package/dist/agents/providers/replay.js +162 -0
- package/dist/agents/providers/replay.js.map +1 -0
- package/dist/agents/schemas.d.ts +3 -3
- package/dist/agents/schemas.d.ts.map +1 -1
- package/dist/agents/schemas.js.map +1 -1
- package/dist/browser.d.ts +33 -4
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +26 -3
- package/dist/browser.js.map +1 -1
- package/dist/calc/builtins.d.ts +10 -0
- package/dist/calc/builtins.d.ts.map +1 -1
- package/dist/calc/builtins.js +64 -42
- package/dist/calc/builtins.js.map +1 -1
- package/dist/calc/errors.js +1 -1
- package/dist/calc/evaluator.js +6 -2
- package/dist/calc/evaluator.js.map +1 -1
- package/dist/calc/index.d.ts +7 -0
- package/dist/calc/index.d.ts.map +1 -1
- package/dist/calc/index.js +13 -1
- package/dist/calc/index.js.map +1 -1
- package/dist/calc/quantize.d.ts +56 -0
- package/dist/calc/quantize.d.ts.map +1 -0
- package/dist/calc/quantize.js +116 -0
- package/dist/calc/quantize.js.map +1 -0
- package/dist/cascade.d.ts +60 -6
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +93 -14
- package/dist/cascade.js.map +1 -1
- package/dist/cli-packages.d.ts +11 -0
- package/dist/cli-packages.d.ts.map +1 -0
- package/dist/cli-packages.js +177 -0
- package/dist/cli-packages.js.map +1 -0
- package/dist/cli.js +691 -37
- package/dist/cli.js.map +1 -1
- package/dist/composition.d.ts +182 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +536 -0
- package/dist/composition.js.map +1 -0
- package/dist/deal-package-context.d.ts +52 -0
- package/dist/deal-package-context.d.ts.map +1 -0
- package/dist/deal-package-context.js +132 -0
- package/dist/deal-package-context.js.map +1 -0
- package/dist/deal-package-zip.d.ts +36 -0
- package/dist/deal-package-zip.d.ts.map +1 -0
- package/dist/deal-package-zip.js +132 -0
- package/dist/deal-package-zip.js.map +1 -0
- package/dist/deal-package.d.ts +82 -0
- package/dist/deal-package.d.ts.map +1 -0
- package/dist/deal-package.js +189 -0
- package/dist/deal-package.js.map +1 -0
- package/dist/defaults.d.ts +48 -0
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +512 -0
- package/dist/defaults.js.map +1 -1
- package/dist/index.d.ts +39 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -3
- package/dist/index.js.map +1 -1
- package/dist/init.js +1 -1
- package/dist/init.js.map +1 -1
- package/dist/integrity.d.ts +9 -0
- package/dist/integrity.d.ts.map +1 -1
- package/dist/integrity.js +12 -2
- package/dist/integrity.js.map +1 -1
- package/dist/lease-abstract.d.ts +82 -0
- package/dist/lease-abstract.d.ts.map +1 -0
- package/dist/lease-abstract.js +183 -0
- package/dist/lease-abstract.js.map +1 -0
- package/dist/lite-bridge.d.ts +67 -0
- package/dist/lite-bridge.d.ts.map +1 -0
- package/dist/lite-bridge.js +564 -0
- package/dist/lite-bridge.js.map +1 -0
- package/dist/lite.d.ts +63 -0
- package/dist/lite.d.ts.map +1 -0
- package/dist/lite.js +398 -0
- package/dist/lite.js.map +1 -0
- package/dist/market-data.d.ts +134 -0
- package/dist/market-data.d.ts.map +1 -0
- package/dist/market-data.js +292 -0
- package/dist/market-data.js.map +1 -0
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +278 -13
- package/dist/modules.js.map +1 -1
- package/dist/packs/excel-emit.d.ts +18 -1
- package/dist/packs/excel-emit.d.ts.map +1 -1
- package/dist/packs/excel-emit.js +18 -1
- package/dist/packs/excel-emit.js.map +1 -1
- package/dist/packs/hospitality.d.ts +3 -0
- package/dist/packs/hospitality.d.ts.map +1 -0
- package/dist/packs/hospitality.js +122 -0
- package/dist/packs/hospitality.js.map +1 -0
- package/dist/packs/index.d.ts +5 -1
- package/dist/packs/index.d.ts.map +1 -1
- package/dist/packs/index.js +13 -1
- package/dist/packs/index.js.map +1 -1
- package/dist/packs/land.d.ts +3 -0
- package/dist/packs/land.d.ts.map +1 -0
- package/dist/packs/land.js +124 -0
- package/dist/packs/land.js.map +1 -0
- package/dist/packs/senior-housing.d.ts +3 -0
- package/dist/packs/senior-housing.d.ts.map +1 -0
- package/dist/packs/senior-housing.js +123 -0
- package/dist/packs/senior-housing.js.map +1 -0
- package/dist/packs/student-housing.d.ts +3 -0
- package/dist/packs/student-housing.d.ts.map +1 -0
- package/dist/packs/student-housing.js +124 -0
- package/dist/packs/student-housing.js.map +1 -0
- package/dist/parser.d.ts +9 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +40 -6
- package/dist/parser.js.map +1 -1
- package/dist/protocol.d.ts +125 -9
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +182 -4
- package/dist/protocol.js.map +1 -1
- package/dist/receipts.d.ts +396 -0
- package/dist/receipts.d.ts.map +1 -0
- package/dist/receipts.js +913 -0
- package/dist/receipts.js.map +1 -0
- package/dist/runner.d.ts +13 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +5 -5
- package/dist/runner.js.map +1 -1
- package/dist/source-representation.d.ts +40 -0
- package/dist/source-representation.d.ts.map +1 -0
- package/dist/source-representation.js +114 -0
- package/dist/source-representation.js.map +1 -0
- package/dist/types.d.ts +63 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +34 -0
- package/dist/types.js.map +1 -1
- package/dist/uwcsv.d.ts.map +1 -1
- package/dist/uwcsv.js +10 -78
- package/dist/uwcsv.js.map +1 -1
- package/dist/uwxml.d.ts.map +1 -1
- package/dist/uwxml.js +10 -2
- package/dist/uwxml.js.map +1 -1
- package/dist/validator.d.ts.map +1 -1
- package/dist/validator.js +27 -10
- package/dist/validator.js.map +1 -1
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +8 -0
- package/dist/version.js.map +1 -0
- package/dist/zip-safety.d.ts +15 -0
- package/dist/zip-safety.d.ts.map +1 -0
- package/dist/zip-safety.js +109 -0
- package/dist/zip-safety.js.map +1 -0
- package/package.json +12 -3
package/dist/cli.js
CHANGED
|
@@ -2,28 +2,38 @@
|
|
|
2
2
|
// uwmd CLI — command-line interface for .uw.md files
|
|
3
3
|
// Commands: parse, validate, compact, diff, init, summary, render
|
|
4
4
|
// Usage: uwmd <command> <file> [options]
|
|
5
|
-
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
6
|
-
import { resolve, basename } from 'node:path';
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'node:fs';
|
|
6
|
+
import { resolve, basename, dirname } from 'node:path';
|
|
7
7
|
import { parseUWFile } from './parser.js';
|
|
8
8
|
import { validateUWFile } from './validator.js';
|
|
9
9
|
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 {
|
|
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';
|
|
22
24
|
import { buildContext } from './context-profiles.js';
|
|
23
25
|
import { rankGaps } from './refinement.js';
|
|
24
26
|
import { resolveValue } from './cascade.js';
|
|
27
|
+
import { parseUWPart, resolveComposition, externalizeSection, stringifyUWPart, UWPART_EXTENSION, } from './composition.js';
|
|
28
|
+
import { createDocumentMarketData, parseMarketDataDocument } from './market-data.js';
|
|
25
29
|
import { getAssetClassDefaults } from './defaults.js';
|
|
26
30
|
import { MULTIFAMILY_PACK, getPackForAssetClass } from './packs/index.js';
|
|
31
|
+
import { issueReceipt, verifyReceipt, assertUWReceipt } from './receipts.js';
|
|
32
|
+
import { CORE_VERSION } from './version.js';
|
|
33
|
+
import { detectUWSourceRepresentation, UWX_REPRESENTATION_ID, UW_LITE_SOURCE_DESCRIPTOR, UWX_SOURCE_DESCRIPTOR, UWX_EXTENSION, } from './source-representation.js';
|
|
34
|
+
import { parseUWLite } from './lite.js';
|
|
35
|
+
import { compileUWLite, projectUWEnvelopeToLite, stringifyUWX, } from './lite-bridge.js';
|
|
36
|
+
import { UW_LITE_REPRESENTATION_ID } from './source-representation.js';
|
|
27
37
|
const [, , command, ...args] = process.argv;
|
|
28
38
|
function readFile(path) {
|
|
29
39
|
const resolved = resolve(path);
|
|
@@ -71,6 +81,20 @@ function parseFlags(rawArgs) {
|
|
|
71
81
|
// ─── Commands ─────────────────────────────────────────────────────────────────
|
|
72
82
|
function cmdParse(file, flags) {
|
|
73
83
|
const content = readFile(file);
|
|
84
|
+
const detection = detectUWSourceRepresentation(content, file);
|
|
85
|
+
if (detection.representation !== UWX_REPRESENTATION_ID) {
|
|
86
|
+
const lite = parseUWLite(content);
|
|
87
|
+
console.log(JSON.stringify({
|
|
88
|
+
representation: lite.representation,
|
|
89
|
+
representation_version: lite.representation_version,
|
|
90
|
+
frontmatter: lite.frontmatter,
|
|
91
|
+
fields: lite.fields,
|
|
92
|
+
issues: lite.issues,
|
|
93
|
+
}, null, flags['compact'] ? 0 : 2));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
for (const warning of detection.warnings)
|
|
97
|
+
console.warn(`Warning: ${warning}`);
|
|
74
98
|
const parsed = parseUWFile(content, { strict: flags['strict'] === true });
|
|
75
99
|
const output = {
|
|
76
100
|
frontmatter: parsed.frontmatter,
|
|
@@ -83,6 +107,38 @@ function cmdParse(file, flags) {
|
|
|
83
107
|
}
|
|
84
108
|
function cmdValidate(file, flags) {
|
|
85
109
|
const content = readFile(file);
|
|
110
|
+
const detection = detectUWSourceRepresentation(content, file);
|
|
111
|
+
if (detection.representation !== UWX_REPRESENTATION_ID) {
|
|
112
|
+
const lite = parseUWLite(content);
|
|
113
|
+
const errors = lite.issues.filter((issue) => issue.severity === 'error');
|
|
114
|
+
const warnings = lite.issues.filter((issue) => issue.severity === 'warning');
|
|
115
|
+
const result = {
|
|
116
|
+
representation: lite.representation,
|
|
117
|
+
overall_status: errors.length > 0 ? 'errors' : warnings.length > 0 ? 'warnings' : 'clean',
|
|
118
|
+
issues: lite.issues,
|
|
119
|
+
errors,
|
|
120
|
+
warnings,
|
|
121
|
+
};
|
|
122
|
+
if (flags['json']) {
|
|
123
|
+
console.log(JSON.stringify(result, null, 2));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
console.log(`\n${result.overall_status.toUpperCase()} - ${basename(file)}\n`);
|
|
127
|
+
if (result.issues.length === 0) {
|
|
128
|
+
console.log('No Lite syntax issues found.');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
for (const issue of result.issues) {
|
|
132
|
+
const location = issue.line ? ` line ${issue.line}` : '';
|
|
133
|
+
const field = issue.field_path ? ` [${issue.field_path}]` : '';
|
|
134
|
+
console.log(` [${issue.severity.toUpperCase()}]${location}${field} ${issue.code}: ${issue.message}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
console.log('');
|
|
138
|
+
process.exit(errors.length > 0 ? 1 : 0);
|
|
139
|
+
}
|
|
140
|
+
for (const warning of detection.warnings)
|
|
141
|
+
console.warn(`Warning: ${warning}`);
|
|
86
142
|
const parsed = parseUWFile(content);
|
|
87
143
|
const instCfg = loadInstitutionConfig(flags['institution']);
|
|
88
144
|
const result = validateUWFile(parsed, instCfg?.thresholds);
|
|
@@ -117,7 +173,9 @@ function cmdValidate(file, flags) {
|
|
|
117
173
|
}
|
|
118
174
|
async function cmdVerify(file, flags) {
|
|
119
175
|
const content = readFile(file);
|
|
120
|
-
|
|
176
|
+
// --resolved verifies the assembled record. Without it an externalized
|
|
177
|
+
// section verifies as a directive, which is not the document anyone means.
|
|
178
|
+
const parsed = withResolved(parseUWFile(content), file, flags);
|
|
121
179
|
// No flag set → run all three.
|
|
122
180
|
const onlyValidate = flags['validate'] === true;
|
|
123
181
|
const onlyIntegrity = flags['integrity'] === true;
|
|
@@ -229,8 +287,13 @@ function cmdInit(flags) {
|
|
|
229
287
|
dealStage: flags['stage'],
|
|
230
288
|
tier: flags['tier'] ?? 'screener',
|
|
231
289
|
});
|
|
290
|
+
// generateBlankUWFile() emits structured UWX content, so the default filename
|
|
291
|
+
// must be .uwx.md. Writing it as .uw.md produced a file the format spec
|
|
292
|
+
// forbids and detectUWSourceRepresentation() flags as legacy on the next load.
|
|
232
293
|
const filename = flags['output']
|
|
233
|
-
?? (flags['name']
|
|
294
|
+
?? (flags['name']
|
|
295
|
+
? `${flags['name'].replace(/\s+/g, '-').toLowerCase()}${UWX_EXTENSION}`
|
|
296
|
+
: `new-deal${UWX_EXTENSION}`);
|
|
234
297
|
const outPath = resolve(filename);
|
|
235
298
|
writeFileSync(outPath, content, 'utf-8');
|
|
236
299
|
console.log(`Created: ${outPath}`);
|
|
@@ -269,16 +332,18 @@ function cmdSummary(file) {
|
|
|
269
332
|
`);
|
|
270
333
|
}
|
|
271
334
|
async function cmdExport(file, flags) {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
335
|
+
const includeSuperseded = !(flags['no-superseded'] === true || flags['compact'] === true);
|
|
336
|
+
// --resolved exports the assembled record. Exporting the directive instead
|
|
337
|
+
// would ship a document whose rent roll is a list of filenames.
|
|
338
|
+
const loaded = flags['resolved']
|
|
339
|
+
? toUWEnvelope(withResolved(parseUWFile(readFile(file)), file, flags))
|
|
340
|
+
: await loadEnvelope(file);
|
|
341
|
+
const envelope = includeSuperseded ? loaded : { ...loaded, superseded: {} };
|
|
342
|
+
const text = stringifyUWEnvelope(await stampEnvelopeDigest(envelope));
|
|
278
343
|
// Default output path: swap the .uw.md suffix for .uw.json (fall back to appending).
|
|
279
344
|
const outPath = flags['output']
|
|
280
345
|
? resolve(flags['output'])
|
|
281
|
-
: resolve(
|
|
346
|
+
: resolve(replaceUWExtension(file, '.uw.json'));
|
|
282
347
|
if (flags['stdout']) {
|
|
283
348
|
process.stdout.write(text);
|
|
284
349
|
return;
|
|
@@ -292,12 +357,33 @@ const FORMAT_ALIASES = {
|
|
|
292
357
|
csv: 'uw-csv-bundle',
|
|
293
358
|
'csv-bundle': 'uw-csv-bundle',
|
|
294
359
|
'uw-json': 'uw-json',
|
|
360
|
+
lite: 'uw-lite-markdown',
|
|
361
|
+
uw: 'uw-lite-markdown',
|
|
362
|
+
uwx: 'uwx-markdown',
|
|
363
|
+
'uw-lite': 'uw-lite-markdown',
|
|
364
|
+
'uw-lite-markdown': 'uw-lite-markdown',
|
|
365
|
+
'uwx-markdown': 'uwx-markdown',
|
|
295
366
|
'uw-xml': 'uw-xml',
|
|
296
367
|
'uw-csv-bundle': 'uw-csv-bundle',
|
|
297
368
|
};
|
|
298
369
|
async function loadEnvelope(file) {
|
|
299
|
-
|
|
300
|
-
|
|
370
|
+
const lower = file.toLowerCase();
|
|
371
|
+
if (lower.endsWith('.uw.md') || lower.endsWith(UWX_EXTENSION)) {
|
|
372
|
+
const content = readFile(file);
|
|
373
|
+
const detection = detectUWSourceRepresentation(content, file);
|
|
374
|
+
if (detection.representation !== UWX_REPRESENTATION_ID) {
|
|
375
|
+
const compilation = compileUWLite(parseUWLite(content));
|
|
376
|
+
if (!compilation.ok) {
|
|
377
|
+
const issues = compilation.report.issues
|
|
378
|
+
.map((issue) => `${issue.code}: ${issue.message}`)
|
|
379
|
+
.join('; ');
|
|
380
|
+
throw new Error(`UW Lite compilation failed: ${issues}`);
|
|
381
|
+
}
|
|
382
|
+
return compilation.envelope;
|
|
383
|
+
}
|
|
384
|
+
for (const warning of detection.warnings)
|
|
385
|
+
console.warn(`Warning: ${warning}`);
|
|
386
|
+
return toUWEnvelope(parseUWFile(content));
|
|
301
387
|
}
|
|
302
388
|
const sourceCodec = CORE_CODEC_REGISTRY.findByFileName(file);
|
|
303
389
|
if (!sourceCodec || !sourceCodec.descriptor.directions.includes('read')) {
|
|
@@ -308,9 +394,17 @@ async function loadEnvelope(file) {
|
|
|
308
394
|
: readFile(file);
|
|
309
395
|
return sourceCodec.decode(input);
|
|
310
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Swap a known UW source extension for `extension`, appending if none matches.
|
|
399
|
+
*
|
|
400
|
+
* `.uwx.md` must be in this list. Without it a UWX input fell through to the
|
|
401
|
+
* append branch, so `uwmd export deal.uwx.md` wrote `deal.uwx.md.uw.json`
|
|
402
|
+
* rather than `deal.uw.json`. (`.uw.md` does not match a `.uwx.md` filename —
|
|
403
|
+
* the suffixes genuinely differ — so it was silently a no-op, not a mis-match.)
|
|
404
|
+
*/
|
|
311
405
|
function replaceUWExtension(file, extension) {
|
|
312
406
|
const lower = file.toLowerCase();
|
|
313
|
-
for (const suffix of ['.uw.csv.zip', '.uw.md', '.uw.json', '.uw.xml']) {
|
|
407
|
+
for (const suffix of [UWX_EXTENSION, '.uw.csv.zip', '.uw.md', '.uw.json', '.uw.xml']) {
|
|
314
408
|
if (lower.endsWith(suffix))
|
|
315
409
|
return `${file.slice(0, -suffix.length)}${extension}`;
|
|
316
410
|
}
|
|
@@ -319,15 +413,45 @@ function replaceUWExtension(file, extension) {
|
|
|
319
413
|
async function cmdConvert(file, flags) {
|
|
320
414
|
const requested = flags['to'];
|
|
321
415
|
if (typeof requested !== 'string') {
|
|
322
|
-
throw new Error('convert requires --to uw-json|uw-xml|uw-csv-bundle.');
|
|
416
|
+
throw new Error('convert requires --to uw-lite-markdown|uwx-markdown|uw-json|uw-xml|uw-csv-bundle.');
|
|
323
417
|
}
|
|
324
418
|
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
419
|
const envelope = await loadEnvelope(file);
|
|
330
|
-
|
|
420
|
+
let encoded;
|
|
421
|
+
let extension;
|
|
422
|
+
if (targetId === UWX_REPRESENTATION_ID) {
|
|
423
|
+
encoded = stringifyUWX(envelope);
|
|
424
|
+
extension = UWX_EXTENSION;
|
|
425
|
+
}
|
|
426
|
+
else if (targetId === UW_LITE_REPRESENTATION_ID) {
|
|
427
|
+
const projection = projectUWEnvelopeToLite(envelope);
|
|
428
|
+
encoded = projection.content;
|
|
429
|
+
extension = '.uw.md';
|
|
430
|
+
// Two independent losses, reported separately. A record whose only loss is
|
|
431
|
+
// an externalized section omits zero *paths*, so folding these together
|
|
432
|
+
// would print "omitted 0 advanced path(s)" over a missing rent roll.
|
|
433
|
+
if (projection.report.omitted_paths.length > 0) {
|
|
434
|
+
console.warn(`Warning: Lite projection omitted ${projection.report.omitted_paths.length} advanced path(s).`);
|
|
435
|
+
}
|
|
436
|
+
if (projection.report.externalized_sections.length > 0) {
|
|
437
|
+
console.warn(`Warning: Lite projection omitted ${projection.report.externalized_sections.length} externalized section(s), unresolved here: ${projection.report.externalized_sections.join(', ')}.`);
|
|
438
|
+
}
|
|
439
|
+
if (typeof flags['projection-report'] === 'string') {
|
|
440
|
+
writeFileSync(resolve(flags['projection-report']), JSON.stringify(projection.report, null, 2), 'utf-8');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
const target = CORE_CODEC_REGISTRY.get(targetId);
|
|
445
|
+
if (!target.descriptor.directions.includes('write')) {
|
|
446
|
+
throw new Error(`Representation ${targetId} is not writable.`);
|
|
447
|
+
}
|
|
448
|
+
const targetEncoded = await target.encode(envelope);
|
|
449
|
+
if (typeof targetEncoded !== 'string' && !(targetEncoded instanceof Uint8Array)) {
|
|
450
|
+
throw new Error(`Representation ${targetId} did not produce file-compatible output.`);
|
|
451
|
+
}
|
|
452
|
+
encoded = targetEncoded;
|
|
453
|
+
extension = target.descriptor.file_extensions[0];
|
|
454
|
+
}
|
|
331
455
|
if (typeof encoded !== 'string' && !(encoded instanceof Uint8Array)) {
|
|
332
456
|
throw new Error(`Representation ${targetId} did not produce file-compatible output.`);
|
|
333
457
|
}
|
|
@@ -335,15 +459,174 @@ async function cmdConvert(file, flags) {
|
|
|
335
459
|
process.stdout.write(typeof encoded === 'string' ? encoded : Buffer.from(encoded));
|
|
336
460
|
return;
|
|
337
461
|
}
|
|
338
|
-
const extension = target.descriptor.file_extensions[0];
|
|
339
462
|
const outPath = flags['output']
|
|
340
463
|
? resolve(flags['output'])
|
|
341
464
|
: resolve(replaceUWExtension(file, extension));
|
|
342
465
|
writeFileSync(outPath, encoded, 'utf-8');
|
|
343
466
|
console.log(`Converted ${basename(file)} → ${basename(outPath)} (${targetId})`);
|
|
344
467
|
}
|
|
468
|
+
// ─── Receipts (RFC 0016 / spec/UW_RECEIPT_v1.md) ─────────────────────────────
|
|
469
|
+
async function cmdReceiptIssue(file, flags) {
|
|
470
|
+
const receipt = await issueReceipt(readFile(file), {
|
|
471
|
+
filename: file,
|
|
472
|
+
issuer: `uwmd-cli@${CORE_VERSION}`,
|
|
473
|
+
...(typeof flags['issued-at'] === 'string' ? { issued_at: flags['issued-at'] } : {}),
|
|
474
|
+
});
|
|
475
|
+
const serialized = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
476
|
+
if (flags['stdout']) {
|
|
477
|
+
process.stdout.write(serialized);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const outPath = flags['output']
|
|
481
|
+
? resolve(flags['output'])
|
|
482
|
+
: resolve(`${file.replace(/\.(uw|uwx)\.md$/i, '')}.receipt.json`);
|
|
483
|
+
writeFileSync(outPath, serialized, 'utf-8');
|
|
484
|
+
const computed = receipt.computation.results.filter((r) => r.computed).length;
|
|
485
|
+
console.log(`Issued receipt for ${basename(file)} → ${basename(outPath)} ` +
|
|
486
|
+
`(${receipt.computation.pack}@${receipt.computation.pack_version}, ` +
|
|
487
|
+
`${computed}/${receipt.computation.results.length} outputs computed)`);
|
|
488
|
+
console.log('A receipt attests that these outputs follow from this record. It does not attest that the inputs are true.');
|
|
489
|
+
}
|
|
490
|
+
// ─── Modules ──────────────────────────────────────────────────────────────────
|
|
491
|
+
function readManifestFile(path) {
|
|
492
|
+
try {
|
|
493
|
+
return { ok: true, manifest: JSON.parse(readFileSync(resolve(path), 'utf-8')) };
|
|
494
|
+
}
|
|
495
|
+
catch (e) {
|
|
496
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function hostTierFlag(flags) {
|
|
500
|
+
const tier = flags['tier'];
|
|
501
|
+
return typeof tier === 'string' ? tier : undefined;
|
|
502
|
+
}
|
|
503
|
+
function cmdModulesValidate(files, flags) {
|
|
504
|
+
const hostTier = hostTierFlag(flags);
|
|
505
|
+
const reports = files.map((file) => {
|
|
506
|
+
const read = readManifestFile(file);
|
|
507
|
+
if (!read.ok) {
|
|
508
|
+
return { file, ok: false, errors: [{ category: 'module', code: 'CLI-MOD-READ', message: read.message }] };
|
|
509
|
+
}
|
|
510
|
+
const result = loadModuleManifest(read.manifest, hostTier ? { hostTier } : {});
|
|
511
|
+
return { file, ok: result.ok, errors: result.errors };
|
|
512
|
+
});
|
|
513
|
+
if (flags['json']) {
|
|
514
|
+
console.log(JSON.stringify({ modules: reports }, null, 2));
|
|
515
|
+
process.exit(reports.every((r) => r.ok) ? 0 : 1);
|
|
516
|
+
}
|
|
517
|
+
for (const report of reports) {
|
|
518
|
+
if (report.ok) {
|
|
519
|
+
console.log(`\nOK - ${basename(report.file)}`);
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
console.log(`\nERRORS - ${basename(report.file)}`);
|
|
523
|
+
for (const error of report.errors) {
|
|
524
|
+
// The pointer is what makes this actionable: it names the exact
|
|
525
|
+
// declaration that failed, not just the manifest.
|
|
526
|
+
const at = 'pointer' in error && error.pointer ? ` [${error.pointer}]` : '';
|
|
527
|
+
console.log(` ${error.code}${at} ${error.message}`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const failed = reports.filter((r) => !r.ok).length;
|
|
531
|
+
console.log(`\n${reports.length - failed}/${reports.length} manifest(s) loaded${hostTier ? ` against ${hostTier}` : ''}.\n`);
|
|
532
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
533
|
+
}
|
|
534
|
+
function cmdModulesList(files, flags) {
|
|
535
|
+
const hostTier = hostTierFlag(flags);
|
|
536
|
+
const manifests = [];
|
|
537
|
+
for (const file of files) {
|
|
538
|
+
const read = readManifestFile(file);
|
|
539
|
+
if (!read.ok) {
|
|
540
|
+
console.error(`Cannot read ${basename(file)}: ${read.message}`);
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
manifests.push(read.manifest);
|
|
544
|
+
}
|
|
545
|
+
let registry;
|
|
546
|
+
try {
|
|
547
|
+
registry = createModuleRegistry({ modules: manifests, ...(hostTier ? { hostTier } : {}) });
|
|
548
|
+
}
|
|
549
|
+
catch (e) {
|
|
550
|
+
// Registry construction is all-or-nothing: a bad manifest means there is no
|
|
551
|
+
// registry to list, so report why rather than printing a partial one.
|
|
552
|
+
if (e instanceof ModuleRegistryError) {
|
|
553
|
+
console.error('Registry refused to load:');
|
|
554
|
+
for (const error of e.errors)
|
|
555
|
+
console.error(` ${error.code} ${error.message}`);
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
559
|
+
}
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
const rows = registry.modules.map((m) => ({
|
|
563
|
+
id: m.id,
|
|
564
|
+
name: m.name,
|
|
565
|
+
version: m.version,
|
|
566
|
+
requires_tier: m.requires_tier,
|
|
567
|
+
asset_classes: m.asset_classes ?? [],
|
|
568
|
+
calculations: (m.calculations ?? []).length,
|
|
569
|
+
validations: (m.validations ?? []).length,
|
|
570
|
+
sections: (m.sections ?? []).length,
|
|
571
|
+
agent_layers: (m.agent_layers ?? []).length,
|
|
572
|
+
}));
|
|
573
|
+
if (flags['json']) {
|
|
574
|
+
console.log(JSON.stringify({ modules: rows }, null, 2));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
console.log(`\n${rows.length} module(s) loaded\n`);
|
|
578
|
+
for (const row of rows) {
|
|
579
|
+
console.log(` ${row.id}@${row.version} — ${row.name}`);
|
|
580
|
+
console.log(` tier: ${row.requires_tier}`);
|
|
581
|
+
console.log(` asset classes: ${row.asset_classes.length ? row.asset_classes.join(', ') : 'class-agnostic'}`);
|
|
582
|
+
console.log(` contributes: ${row.calculations} calc, ${row.validations} validation, ` +
|
|
583
|
+
`${row.sections} section, ${row.agent_layers} agent layer`);
|
|
584
|
+
}
|
|
585
|
+
console.log('');
|
|
586
|
+
}
|
|
587
|
+
async function cmdReceiptVerify(file, receiptPath, flags) {
|
|
588
|
+
let receipt;
|
|
589
|
+
try {
|
|
590
|
+
const raw = JSON.parse(readFileSync(resolve(receiptPath), 'utf-8'));
|
|
591
|
+
assertUWReceipt(raw);
|
|
592
|
+
receipt = raw;
|
|
593
|
+
}
|
|
594
|
+
catch (e) {
|
|
595
|
+
console.error(`Not a readable receipt: ${e instanceof Error ? e.message : String(e)}`);
|
|
596
|
+
process.exit(2);
|
|
597
|
+
}
|
|
598
|
+
const result = await verifyReceipt(receipt, readFile(file), { filename: file });
|
|
599
|
+
if (flags['json']) {
|
|
600
|
+
console.log(JSON.stringify(result, null, 2));
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
console.log(`Verdict: ${result.verdict.toUpperCase()}`);
|
|
604
|
+
for (const issue of result.issues) {
|
|
605
|
+
const detail = issue.expected !== undefined || issue.actual !== undefined
|
|
606
|
+
? ` (expected ${issue.expected ?? '—'}, got ${issue.actual ?? '—'})`
|
|
607
|
+
: '';
|
|
608
|
+
console.log(` [${issue.code}] ${issue.message}${detail}`);
|
|
609
|
+
}
|
|
610
|
+
if (result.verdict === 'verified') {
|
|
611
|
+
console.log('\nThis record is unchanged since issuance and its stated outputs follow deterministically\n' +
|
|
612
|
+
'from its contents. It is NOT a statement that the inputs are true, complete, or approved.');
|
|
613
|
+
}
|
|
614
|
+
else if (result.verdict === 'unverifiable') {
|
|
615
|
+
console.log('\nThis verifier could not decide. That is not a negative result.');
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
// 0 verified, 1 failed, 3 unverifiable — so scripts can tell the third state apart.
|
|
619
|
+
if (result.verdict === 'failed')
|
|
620
|
+
process.exit(1);
|
|
621
|
+
if (result.verdict === 'unverifiable')
|
|
622
|
+
process.exit(3);
|
|
623
|
+
}
|
|
345
624
|
function cmdFormats(flags) {
|
|
346
|
-
const descriptors =
|
|
625
|
+
const descriptors = [
|
|
626
|
+
UW_LITE_SOURCE_DESCRIPTOR,
|
|
627
|
+
UWX_SOURCE_DESCRIPTOR,
|
|
628
|
+
...CORE_CODEC_REGISTRY.list(),
|
|
629
|
+
];
|
|
347
630
|
if (flags['json']) {
|
|
348
631
|
console.log(JSON.stringify(descriptors, null, 2));
|
|
349
632
|
return;
|
|
@@ -360,10 +643,12 @@ function cmdReport(file, flags) {
|
|
|
360
643
|
tier: flags['tier'],
|
|
361
644
|
preparedBy: flags['prepared-by'],
|
|
362
645
|
});
|
|
363
|
-
// Default output path: swap the
|
|
646
|
+
// Default output path: swap the source extension for .report.html. Routed
|
|
647
|
+
// through replaceUWExtension so every UW source extension is handled in one
|
|
648
|
+
// place — the inline .uw.md check this replaced missed .uwx.md entirely.
|
|
364
649
|
const outPath = flags['output']
|
|
365
650
|
? resolve(flags['output'])
|
|
366
|
-
: resolve(
|
|
651
|
+
: resolve(replaceUWExtension(file, '.report.html'));
|
|
367
652
|
if (flags['stdout']) {
|
|
368
653
|
process.stdout.write(result.html);
|
|
369
654
|
return;
|
|
@@ -373,6 +658,65 @@ function cmdReport(file, flags) {
|
|
|
373
658
|
console.log(`Rendered ${label} → ${basename(outPath)} (${result.sectionsRendered.length} sections${result.sectionsSkipped.length ? `, skipped: ${result.sectionsSkipped.join(', ')}` : ''})`);
|
|
374
659
|
console.log('For PDF output, use @uwmd/report (uwmd-report) or print this HTML from a browser.');
|
|
375
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Load `--market-data <file>` into a cascade-ready lookup, or return null when
|
|
663
|
+
* the flag is absent. Exits with a message rather than throwing, so a bad
|
|
664
|
+
* observation set is a usage error rather than a stack trace.
|
|
665
|
+
*/
|
|
666
|
+
function loadMarketData(flags) {
|
|
667
|
+
const path = flags['market-data'];
|
|
668
|
+
if (!path)
|
|
669
|
+
return null;
|
|
670
|
+
try {
|
|
671
|
+
const doc = parseMarketDataDocument(parseUWFile(readFile(path)));
|
|
672
|
+
return {
|
|
673
|
+
lookup: createDocumentMarketData(doc),
|
|
674
|
+
identity: {
|
|
675
|
+
document_id: doc.document_id,
|
|
676
|
+
as_of: doc.as_of,
|
|
677
|
+
provider: doc.provider,
|
|
678
|
+
geo: doc.geo,
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
catch (e) {
|
|
683
|
+
console.error(`Market data: ${e instanceof Error ? e.message : String(e)}`);
|
|
684
|
+
process.exit(1);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function cmdMarketDataValidate(file, flags) {
|
|
688
|
+
const parsed = parseUWFile(readFile(file));
|
|
689
|
+
try {
|
|
690
|
+
const doc = parseMarketDataDocument(parsed);
|
|
691
|
+
if (flags['json']) {
|
|
692
|
+
process.stdout.write(`${JSON.stringify({ valid: true, ...doc }, null, 2)}\n`);
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
console.log(`OK ${doc.document_id}`);
|
|
696
|
+
console.log(` as of ${doc.as_of} — ${doc.provider}`);
|
|
697
|
+
console.log(` ${doc.geo}${doc.asset_class ? ` — ${doc.asset_class}` : ''}`);
|
|
698
|
+
console.log(` ${doc.observations.length} observation(s):`);
|
|
699
|
+
for (const o of doc.observations) {
|
|
700
|
+
console.log(` ${o.field_path} = ${String(o.value)} ${o.unit}${o.confidence ? ` (${o.confidence})` : ''}`);
|
|
701
|
+
console.log(` basis: ${o.basis}`);
|
|
702
|
+
}
|
|
703
|
+
// Restated on every run because it is the thing people forget.
|
|
704
|
+
console.log('\n Attributable, not verified: this says the observations');
|
|
705
|
+
console.log(' are traceable, not that they are accurate or current.');
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch (e) {
|
|
709
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
710
|
+
if (flags['json']) {
|
|
711
|
+
process.stdout.write(`${JSON.stringify({ valid: false, error: message }, null, 2)}\n`);
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
console.error(`INVALID ${file}`);
|
|
715
|
+
console.error(` ${message}`);
|
|
716
|
+
}
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
376
720
|
function cmdScope(file, flags) {
|
|
377
721
|
const content = readFile(file);
|
|
378
722
|
const parsed = parseUWFile(content);
|
|
@@ -384,12 +728,19 @@ function cmdScope(file, flags) {
|
|
|
384
728
|
console.error(`No published asset-class default table for '${assetClass}'.`);
|
|
385
729
|
process.exit(1);
|
|
386
730
|
}
|
|
731
|
+
const market = loadMarketData(flags);
|
|
387
732
|
const out = {};
|
|
388
733
|
for (const path of Object.keys(table.fields)) {
|
|
389
|
-
const r = resolveValue(path, parsed
|
|
734
|
+
const r = resolveValue(path, parsed, {
|
|
735
|
+
asset_class: assetClass,
|
|
736
|
+
...(market ? { market: market.lookup } : {}),
|
|
737
|
+
});
|
|
390
738
|
out[path] = {
|
|
391
739
|
value: r.value,
|
|
392
740
|
step: r.step,
|
|
741
|
+
// Surfaced so a promoted value stays distinguishable in the output: it
|
|
742
|
+
// resolves at the `user_input` step but is tagged `market_data_accepted`.
|
|
743
|
+
source: r.source,
|
|
393
744
|
range: r.range,
|
|
394
745
|
resolved_from: r.resolved_from,
|
|
395
746
|
};
|
|
@@ -399,6 +750,7 @@ function cmdScope(file, flags) {
|
|
|
399
750
|
deal_stage_target: 'scope',
|
|
400
751
|
asset_class: assetClass,
|
|
401
752
|
defaults_table: `${assetClass}@${table.version}`,
|
|
753
|
+
...(market ? { market_data: market.identity } : {}),
|
|
402
754
|
resolved: out,
|
|
403
755
|
};
|
|
404
756
|
const text = JSON.stringify(payload, null, 2);
|
|
@@ -421,7 +773,16 @@ function cmdRefine(file, flags) {
|
|
|
421
773
|
parsed.frontmatter.asset_class ??
|
|
422
774
|
'multifamily';
|
|
423
775
|
const pack = getPackForAssetClass(assetClass) ?? MULTIFAMILY_PACK;
|
|
424
|
-
|
|
776
|
+
// With `--market-data`, a field the observation set covers is no longer a
|
|
777
|
+
// gap the cascade cannot fill, so it drops out of the VOI ranking. That is
|
|
778
|
+
// the point: the ranking should tell you what is still worth diligencing.
|
|
779
|
+
const market = loadMarketData(flags);
|
|
780
|
+
const result = rankGaps(parsed, {
|
|
781
|
+
targets,
|
|
782
|
+
top,
|
|
783
|
+
packs: [pack],
|
|
784
|
+
...(market ? { cascadeContext: { asset_class: assetClass, market: market.lookup } } : {}),
|
|
785
|
+
});
|
|
425
786
|
if (flags['json']) {
|
|
426
787
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
427
788
|
return;
|
|
@@ -458,6 +819,137 @@ for (let _i = 0; _i < args.length; _i++) {
|
|
|
458
819
|
} // skip flag + its value
|
|
459
820
|
positional.push(a);
|
|
460
821
|
}
|
|
822
|
+
// ─── Composition (RFC 0021) ──────────────────────────────────────────────────
|
|
823
|
+
/** Load every `.uwpart.md` in a directory, keyed by `part_id`. */
|
|
824
|
+
function loadPartsDir(dir) {
|
|
825
|
+
const parts = new Map();
|
|
826
|
+
if (!existsSync(dir))
|
|
827
|
+
return parts;
|
|
828
|
+
for (const file of readdirSync(dir).filter((f) => f.endsWith(UWPART_EXTENSION)).sort()) {
|
|
829
|
+
const path = resolve(dir, file);
|
|
830
|
+
const part = parseUWPart(parseUWFile(readFileSync(path, 'utf-8')), { filename: file });
|
|
831
|
+
if (parts.has(part.part_id)) {
|
|
832
|
+
console.error(`Duplicate part_id '${part.part_id}' in ${dir}`);
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
parts.set(part.part_id, part);
|
|
836
|
+
}
|
|
837
|
+
return parts;
|
|
838
|
+
}
|
|
839
|
+
/** Where fragments live for a record, unless told otherwise. */
|
|
840
|
+
function defaultPartsDir(file) {
|
|
841
|
+
return resolve(dirname(resolve(file)), 'parts');
|
|
842
|
+
}
|
|
843
|
+
function cmdCompose(file, flags) {
|
|
844
|
+
const section = flags['externalize'];
|
|
845
|
+
if (typeof section !== 'string') {
|
|
846
|
+
console.error('Usage: uwmd compose <file> --externalize <section> [--collection-key <k>] [--collection-path <p>]');
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
849
|
+
const collectionKey = flags['collection-key'] ?? 'unit_id';
|
|
850
|
+
const collectionPath = flags['collection-path'] ?? 'units';
|
|
851
|
+
const parsed = parseUWFile(readFile(file));
|
|
852
|
+
let result;
|
|
853
|
+
try {
|
|
854
|
+
result = externalizeSection(parsed, {
|
|
855
|
+
section,
|
|
856
|
+
collectionKey,
|
|
857
|
+
collectionPath,
|
|
858
|
+
...(flags['part-prefix'] ? { partIdPrefix: flags['part-prefix'] } : {}),
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
catch (e) {
|
|
862
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
863
|
+
process.exit(1);
|
|
864
|
+
}
|
|
865
|
+
const outDir = flags['out-dir'] ? resolve(flags['out-dir']) : defaultPartsDir(file);
|
|
866
|
+
const recordPath = flags['in-place']
|
|
867
|
+
? resolve(file)
|
|
868
|
+
: flags['output']
|
|
869
|
+
? resolve(flags['output'])
|
|
870
|
+
: resolve(replaceUWExtension(file, '.externalized.uwx.md'));
|
|
871
|
+
const recordText = stringifyUWX(toUWEnvelope(result.document));
|
|
872
|
+
if (flags['dry-run']) {
|
|
873
|
+
console.log(`Would write ${result.parts.length} fragment(s) to ${outDir}:`);
|
|
874
|
+
for (const part of result.parts)
|
|
875
|
+
console.log(` ${part.part_id}${UWPART_EXTENSION}`);
|
|
876
|
+
console.log(`Would write the record to ${recordPath}`);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
mkdirSync(outDir, { recursive: true });
|
|
880
|
+
for (const part of result.parts) {
|
|
881
|
+
writeFileSync(resolve(outDir, `${part.part_id}${UWPART_EXTENSION}`), stringifyUWPart(part), 'utf-8');
|
|
882
|
+
}
|
|
883
|
+
writeFileSync(recordPath, recordText, 'utf-8');
|
|
884
|
+
console.log(`Externalized ${section} → ${result.parts.length} fragment(s) in ${basename(outDir)}/`);
|
|
885
|
+
console.log(`Record written to ${basename(recordPath)}`);
|
|
886
|
+
// Externalizing is a packaging decision, not a model change — worth saying,
|
|
887
|
+
// because the file looks dramatically different afterwards.
|
|
888
|
+
console.log('The resolved record has the same semantic digest as the original.');
|
|
889
|
+
}
|
|
890
|
+
function cmdResolve(file, flags) {
|
|
891
|
+
const partsDir = flags['parts'] ? resolve(flags['parts']) : defaultPartsDir(file);
|
|
892
|
+
const parsed = parseUWFile(readFile(file));
|
|
893
|
+
let parts;
|
|
894
|
+
try {
|
|
895
|
+
parts = loadPartsDir(partsDir);
|
|
896
|
+
}
|
|
897
|
+
catch (e) {
|
|
898
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
const resolution = resolveComposition(parsed, { parts });
|
|
902
|
+
if (flags['json']) {
|
|
903
|
+
process.stdout.write(`${JSON.stringify({
|
|
904
|
+
status: resolution.status,
|
|
905
|
+
externalized: resolution.externalized,
|
|
906
|
+
parts_available: parts.size,
|
|
907
|
+
issues: resolution.issues,
|
|
908
|
+
}, null, 2)}\n`);
|
|
909
|
+
}
|
|
910
|
+
else {
|
|
911
|
+
console.log(`${resolution.status === 'resolved' ? 'RESOLVED' : 'UNRESOLVED'} ${basename(file)}`);
|
|
912
|
+
console.log(` ${parts.size} fragment(s) available in ${basename(partsDir)}/`);
|
|
913
|
+
console.log(` externalized section(s): ${resolution.externalized.join(', ') || '(none)'}`);
|
|
914
|
+
for (const issue of resolution.issues) {
|
|
915
|
+
console.log(` [${issue.code}] ${issue.message}`);
|
|
916
|
+
}
|
|
917
|
+
if (resolution.status === 'unresolved') {
|
|
918
|
+
// The rule worth restating: under-resolution is never a smaller answer.
|
|
919
|
+
console.log('\n Sections stay externalized rather than resolving partially —');
|
|
920
|
+
console.log(' a collection missing rows would still total and still validate.');
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (flags['output'] && resolution.status === 'resolved') {
|
|
924
|
+
const outPath = resolve(flags['output']);
|
|
925
|
+
writeFileSync(outPath, stringifyUWX(toUWEnvelope(resolution.document)), 'utf-8');
|
|
926
|
+
if (!flags['json'])
|
|
927
|
+
console.log(` resolved record → ${basename(outPath)}`);
|
|
928
|
+
}
|
|
929
|
+
if (resolution.status !== 'resolved')
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Resolve a record's externalized sections before another command reads it.
|
|
934
|
+
*
|
|
935
|
+
* Returns the record unchanged when `--resolved` was not requested, so callers
|
|
936
|
+
* can apply it unconditionally. Exits when resolution fails: verifying or
|
|
937
|
+
* exporting a partially-resolved record would report on a document that does
|
|
938
|
+
* not exist.
|
|
939
|
+
*/
|
|
940
|
+
function withResolved(parsed, file, flags) {
|
|
941
|
+
if (!flags['resolved'])
|
|
942
|
+
return parsed;
|
|
943
|
+
const partsDir = flags['parts'] ? resolve(flags['parts']) : defaultPartsDir(file);
|
|
944
|
+
const resolution = resolveComposition(parsed, { parts: loadPartsDir(partsDir) });
|
|
945
|
+
if (resolution.status !== 'resolved') {
|
|
946
|
+
console.error(`Cannot resolve ${basename(file)} against ${basename(partsDir)}/:`);
|
|
947
|
+
for (const issue of resolution.issues)
|
|
948
|
+
console.error(` [${issue.code}] ${issue.message}`);
|
|
949
|
+
process.exit(1);
|
|
950
|
+
}
|
|
951
|
+
return resolution.document;
|
|
952
|
+
}
|
|
461
953
|
// Top-level async wrapper so `run --live` can use await
|
|
462
954
|
(async () => {
|
|
463
955
|
switch (command) {
|
|
@@ -477,7 +969,7 @@ for (let _i = 0; _i < args.length; _i++) {
|
|
|
477
969
|
break;
|
|
478
970
|
case 'verify':
|
|
479
971
|
if (!positional[0]) {
|
|
480
|
-
console.error('Usage: uwmd verify <file> [--validate] [--integrity] [--policy] [--json]');
|
|
972
|
+
console.error('Usage: uwmd verify <file> [--validate] [--integrity] [--policy] [--resolved] [--json]');
|
|
481
973
|
process.exit(1);
|
|
482
974
|
}
|
|
483
975
|
await cmdVerify(positional[0], flags);
|
|
@@ -749,7 +1241,7 @@ for (let _i = 0; _i < args.length; _i++) {
|
|
|
749
1241
|
break;
|
|
750
1242
|
case 'export':
|
|
751
1243
|
if (!positional[0]) {
|
|
752
|
-
console.error('Usage: uwmd export <file.uw.md> [--output <file.uw.json>] [--no-superseded] [--stdout]');
|
|
1244
|
+
console.error('Usage: uwmd export <file.uw.md> [--output <file.uw.json>] [--no-superseded] [--resolved] [--stdout]');
|
|
753
1245
|
process.exit(1);
|
|
754
1246
|
}
|
|
755
1247
|
await cmdExport(positional[0], flags);
|
|
@@ -759,7 +1251,7 @@ for (let _i = 0; _i < args.length; _i++) {
|
|
|
759
1251
|
break;
|
|
760
1252
|
case 'convert':
|
|
761
1253
|
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]');
|
|
1254
|
+
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
1255
|
process.exit(1);
|
|
764
1256
|
}
|
|
765
1257
|
await cmdConvert(positional[0], flags);
|
|
@@ -773,21 +1265,168 @@ for (let _i = 0; _i < args.length; _i++) {
|
|
|
773
1265
|
break;
|
|
774
1266
|
case 'scope':
|
|
775
1267
|
if (!positional[0]) {
|
|
776
|
-
console.error('Usage: uwmd scope <file> [--asset-class multifamily] [--output <file>]');
|
|
1268
|
+
console.error('Usage: uwmd scope <file> [--asset-class multifamily] [--market-data <file>] [--output <file>]');
|
|
777
1269
|
process.exit(1);
|
|
778
1270
|
}
|
|
779
1271
|
cmdScope(positional[0], flags);
|
|
780
1272
|
break;
|
|
781
1273
|
case 'refine':
|
|
782
1274
|
if (!positional[0]) {
|
|
783
|
-
console.error('Usage: uwmd refine <file> [--targets dscr,debt_yield] [--top 5] [--json]');
|
|
1275
|
+
console.error('Usage: uwmd refine <file> [--targets dscr,debt_yield] [--top 5] [--market-data <file>] [--json]');
|
|
784
1276
|
process.exit(1);
|
|
785
1277
|
}
|
|
786
1278
|
cmdRefine(positional[0], flags);
|
|
787
1279
|
break;
|
|
1280
|
+
case 'compose':
|
|
1281
|
+
if (!positional[0]) {
|
|
1282
|
+
console.error('Usage: uwmd compose <file> --externalize <section> [--collection-key <k>] [--collection-path <p>] [--out-dir <dir>] [--in-place] [--dry-run]');
|
|
1283
|
+
process.exit(1);
|
|
1284
|
+
}
|
|
1285
|
+
cmdCompose(positional[0], flags);
|
|
1286
|
+
break;
|
|
1287
|
+
case 'resolve':
|
|
1288
|
+
if (!positional[0]) {
|
|
1289
|
+
console.error('Usage: uwmd resolve <file> [--parts <dir>] [--output <file>] [--json]');
|
|
1290
|
+
process.exit(1);
|
|
1291
|
+
}
|
|
1292
|
+
cmdResolve(positional[0], flags);
|
|
1293
|
+
break;
|
|
1294
|
+
case 'market-data': {
|
|
1295
|
+
const sub = positional[0];
|
|
1296
|
+
if (sub === 'validate') {
|
|
1297
|
+
if (!positional[1]) {
|
|
1298
|
+
console.error('Usage: uwmd market-data validate <file> [--json]');
|
|
1299
|
+
process.exit(1);
|
|
1300
|
+
}
|
|
1301
|
+
cmdMarketDataValidate(positional[1], flags);
|
|
1302
|
+
}
|
|
1303
|
+
else {
|
|
1304
|
+
console.error('Usage: uwmd market-data validate <file> [--json]');
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
case 'lease': {
|
|
1310
|
+
const sub = positional[0];
|
|
1311
|
+
if (sub === 'validate') {
|
|
1312
|
+
if (!positional[1]) {
|
|
1313
|
+
console.error('Usage: uwmd lease validate <abstract.json> [--json]');
|
|
1314
|
+
process.exit(1);
|
|
1315
|
+
}
|
|
1316
|
+
cmdLeaseValidate(positional[1], flags);
|
|
1317
|
+
}
|
|
1318
|
+
else if (sub === 'project') {
|
|
1319
|
+
if (!positional[1]) {
|
|
1320
|
+
console.error('Usage: uwmd lease project <abstract.json> [--compact]');
|
|
1321
|
+
process.exit(1);
|
|
1322
|
+
}
|
|
1323
|
+
cmdLeaseProject(positional[1], flags);
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1326
|
+
console.error('Usage: uwmd lease <validate|project> <abstract.json>');
|
|
1327
|
+
process.exit(1);
|
|
1328
|
+
}
|
|
1329
|
+
break;
|
|
1330
|
+
}
|
|
1331
|
+
case 'package': {
|
|
1332
|
+
const sub = positional[0];
|
|
1333
|
+
const target = positional[1];
|
|
1334
|
+
if (sub === 'create') {
|
|
1335
|
+
if (!target) {
|
|
1336
|
+
console.error('Usage: uwmd package create <manifest.json> [--output <file.uwpkg.zip>]');
|
|
1337
|
+
process.exit(1);
|
|
1338
|
+
}
|
|
1339
|
+
await cmdPackageCreate(target, flags);
|
|
1340
|
+
}
|
|
1341
|
+
else if (sub === 'verify') {
|
|
1342
|
+
if (!target) {
|
|
1343
|
+
console.error('Usage: uwmd package verify <file.uwpkg.zip> [--json]');
|
|
1344
|
+
process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
await cmdPackageVerify(target, flags);
|
|
1347
|
+
}
|
|
1348
|
+
else if (sub === 'list') {
|
|
1349
|
+
if (!target) {
|
|
1350
|
+
console.error('Usage: uwmd package list <file.uwpkg.zip> [--json]');
|
|
1351
|
+
process.exit(1);
|
|
1352
|
+
}
|
|
1353
|
+
cmdPackageList(target, flags);
|
|
1354
|
+
}
|
|
1355
|
+
else if (sub === 'to-context') {
|
|
1356
|
+
if (!target) {
|
|
1357
|
+
console.error('Usage: uwmd package to-context <file.uwpkg.zip> [--output <file.json>] [--stdout]');
|
|
1358
|
+
process.exit(1);
|
|
1359
|
+
}
|
|
1360
|
+
cmdPackageToContext(target, flags);
|
|
1361
|
+
}
|
|
1362
|
+
else if (sub === 'validate-context') {
|
|
1363
|
+
if (!target) {
|
|
1364
|
+
console.error('Usage: uwmd package validate-context <file.uwpkg.context.json> [--json]');
|
|
1365
|
+
process.exit(1);
|
|
1366
|
+
}
|
|
1367
|
+
cmdPackageContextValidate(target, flags);
|
|
1368
|
+
}
|
|
1369
|
+
else if (sub === 'edges') {
|
|
1370
|
+
if (!target) {
|
|
1371
|
+
console.error('Usage: uwmd package edges <file.uwpkg.zip>');
|
|
1372
|
+
process.exit(1);
|
|
1373
|
+
}
|
|
1374
|
+
cmdPackageEdges(target);
|
|
1375
|
+
}
|
|
1376
|
+
else {
|
|
1377
|
+
console.error('Usage: uwmd package <create|verify|list|to-context|validate-context|edges> ...');
|
|
1378
|
+
process.exit(1);
|
|
1379
|
+
}
|
|
1380
|
+
break;
|
|
1381
|
+
}
|
|
1382
|
+
case 'modules': {
|
|
1383
|
+
const sub = positional[0];
|
|
1384
|
+
const files = positional.slice(1);
|
|
1385
|
+
if (sub === 'validate') {
|
|
1386
|
+
if (files.length === 0) {
|
|
1387
|
+
console.error('Usage: uwmd modules validate <manifest.json...> [--tier <viewer-tier>] [--json]');
|
|
1388
|
+
process.exit(1);
|
|
1389
|
+
}
|
|
1390
|
+
cmdModulesValidate(files, flags);
|
|
1391
|
+
}
|
|
1392
|
+
else if (sub === 'list') {
|
|
1393
|
+
if (files.length === 0) {
|
|
1394
|
+
console.error('Usage: uwmd modules list <manifest.json...> [--tier <viewer-tier>] [--json]');
|
|
1395
|
+
process.exit(1);
|
|
1396
|
+
}
|
|
1397
|
+
cmdModulesList(files, flags);
|
|
1398
|
+
}
|
|
1399
|
+
else {
|
|
1400
|
+
console.error('Usage: uwmd modules <validate|list> <manifest.json...>');
|
|
1401
|
+
process.exit(1);
|
|
1402
|
+
}
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
case 'receipt': {
|
|
1406
|
+
const sub = positional[0];
|
|
1407
|
+
if (sub === 'issue') {
|
|
1408
|
+
if (!positional[1]) {
|
|
1409
|
+
console.error('Usage: uwmd receipt issue <file> [--output <file.receipt.json>] [--issued-at <iso>] [--stdout]');
|
|
1410
|
+
process.exit(1);
|
|
1411
|
+
}
|
|
1412
|
+
await cmdReceiptIssue(positional[1], flags);
|
|
1413
|
+
}
|
|
1414
|
+
else if (sub === 'verify') {
|
|
1415
|
+
if (!positional[1] || !positional[2]) {
|
|
1416
|
+
console.error('Usage: uwmd receipt verify <file> <receipt.json> [--json]');
|
|
1417
|
+
process.exit(1);
|
|
1418
|
+
}
|
|
1419
|
+
await cmdReceiptVerify(positional[1], positional[2], flags);
|
|
1420
|
+
}
|
|
1421
|
+
else {
|
|
1422
|
+
console.error('Usage: uwmd receipt <issue|verify> ...');
|
|
1423
|
+
process.exit(1);
|
|
1424
|
+
}
|
|
1425
|
+
break;
|
|
1426
|
+
}
|
|
788
1427
|
default:
|
|
789
1428
|
console.log(`
|
|
790
|
-
uwmd —
|
|
1429
|
+
uwmd — UW Markdown underwriting file toolkit
|
|
791
1430
|
|
|
792
1431
|
Commands:
|
|
793
1432
|
parse <file> Parse file and output JSON
|
|
@@ -799,14 +1438,20 @@ Commands:
|
|
|
799
1438
|
compact <file> Strip superseded blocks
|
|
800
1439
|
diff <file-a> <file-b> Compare two files section by section
|
|
801
1440
|
calc <file> <calc.json> Evaluate a calc declaration or inline formula (Tier-3)
|
|
802
|
-
init Generate a blank .
|
|
1441
|
+
init Generate a blank .uwx.md file
|
|
803
1442
|
summary <file> Print quick metrics to terminal
|
|
804
1443
|
export <file> Export a digested UW JSON 1.0 document
|
|
1444
|
+
compose <file> Externalize a section into .uwpart.md fragments (RFC 0021)
|
|
1445
|
+
resolve <file> Resolve externalized sections from a parts directory (RFC 0021)
|
|
805
1446
|
formats List registered machine representations
|
|
806
|
-
convert <file> Convert
|
|
1447
|
+
convert <file> Convert Lite/UWX/JSON/XML/CSV bundle representations
|
|
807
1448
|
report <file> Render the lender package / credit memo HTML (§7.1/§7.2)
|
|
808
1449
|
scope <file> Resolve every required input via the fallback cascade and emit a triage view
|
|
809
1450
|
refine <file> Rank gaps by value-of-information for stated calc targets
|
|
1451
|
+
receipt issue|verify Issue or verify a detached verification receipt (RFC 0016)
|
|
1452
|
+
modules validate|list Validate module manifest files, or list the registry they form
|
|
1453
|
+
lease validate|project Validate a lease abstract, or project it to a rent-roll row (RFC 0018)
|
|
1454
|
+
package create|verify|... Build, verify, list, or project a UW Deal Package (RFC 0018)
|
|
810
1455
|
layers List Bancroft agent layers
|
|
811
1456
|
|
|
812
1457
|
Options:
|
|
@@ -816,8 +1461,17 @@ Options:
|
|
|
816
1461
|
--strict Throw on parse errors instead of collecting
|
|
817
1462
|
--format <f> Render format: json|csv|chat|summary (render, default: summary)
|
|
818
1463
|
--no-superseded Drop append-only history from the .uw.json export (export)
|
|
1464
|
+
--resolved Resolve externalized sections first (verify, export)
|
|
1465
|
+
--parts <dir> Fragment directory (compose, resolve, --resolved; default: ./parts)
|
|
1466
|
+
--externalize <s> Section to split into fragments (compose)
|
|
1467
|
+
--collection-key <k> Row identity field (compose, default: unit_id)
|
|
1468
|
+
--collection-path <p> Field the rows occupy (compose, default: units)
|
|
1469
|
+
--in-place Overwrite the input record (compose)
|
|
819
1470
|
--stdout Write export/convert output to stdout
|
|
820
|
-
--to <format> Target
|
|
1471
|
+
--to <format> Target: lite|uwx|uw-json|uw-xml|uw-csv-bundle (convert)
|
|
1472
|
+
--projection-report Write the UWX-to-Lite omission report as JSON (convert)
|
|
1473
|
+
--issued-at <iso> Fix the receipt issuance timestamp (receipt issue)
|
|
1474
|
+
--tier <tier> Host viewer tier to load modules against (modules)
|
|
821
1475
|
--max-tokens <n> Max tokens for chat render (default: 12000)
|
|
822
1476
|
--institution <f> Path to .uw.institution.json config (validate)
|
|
823
1477
|
--name <n> Deal name (init)
|