Changelog
All notable changes to UW Markdown — the format spec, the protocol spec, the reference library @uwmd/core, the conformance corpus, and starter tools — are documented here. The format is based on Keep a Changelog, and the project follows semantic versioning per surface (the format, the protocol, and each package each carry an independent semver).
Unreleased
Added
- HTTP and MCP Binding 1.0 (RFC 0014 Phase E) — publishes stable
https://uwmd.org/deals/{deal_id}resource identities, an OpenAPI 3.1 contract, negotiated HTTP responses with semantic ETags and preconditions, MCP text/blob resources, compact dual structured/text tool results, resource links, all five reference tool handlers, and a runnable SDK-neutral adapter example. Core binding tests cover JSON/XML/CSV transport,304/406/412/415/428, resource variants, validation results, and Tier-2 source edits. - UW CSV Bundle 1.0 (RFC 0014 Phase D) — adds the normalized model-fidelity directory and deterministic ZIP codecs, manifest inventory and file hashes, semantic-digest verification, bounded extraction defenses, and all six named spreadsheet-safe view profiles. The shared registry and
uwmd convertnow round-trip Markdown, JSON, XML, and.uw.csv.ziprepresentations. - UW XML 1.0 and cross-format conversion (RFC 0014 Phase C) — adds the deterministic
uw-xmlcodec, namespace and normative mapping specification, structural XSD, semantic-digest verification, bounded secure parsing, and shared JSON/XML registry APIs.uwmd convertnow converts.uw.md, verified.uw.json, and verified.uw.xml; round-trip, hostile-input, registry, and CLI integration tests cover the implementation. - UW Document Envelope 1.0 and UW JSON 1.0 (RFC 0014 Phase A) — adds the format-neutral
UWDocumentEnvelope, a normative JSON Schema, one authoritative_metaand prose location per block, semantic canonicalization/digests, equivalence checks,CodecRegistry, the registereduw-jsoncodec, verified parsing, and digesteduwmd exportoutput. Core and CLI round-trip, tampering, registry, and schema tests cover the new contract. XML, CSV, discovery, and HTTP/MCP companion profiles now ship in the same 1.1 release train. - Protocol 1.2 representation discovery —
ImplementationManifestnow advertises typed representation descriptors; its normative schema mirrors the addition.negotiateRepresentationimplements Accept quality/specificity and fidelity filtering,resolveInputRepresentationresolves Content-Type, anduwmd formatsexposes the live registry for API/MCP hosts. - Owner-led governance mode — the owner may accept RFCs and merge owner-authored work immediately while the project is solo. External pull requests require owner review; collaborative 14-day normative comment periods activate automatically after the first outside contribution merges.
- Cross-platform release gates - CI now runs the complete build, workspace tests, and tiers 1-3 conformance suite on Windows/Node 20 in addition to the existing Ubuntu matrix. Core coverage is now a blocking gate at 70% lines, 70% statements, 70% branches, and 90% functions, based on a measured baseline of 72.45% / 72.45% / 74.25% / 91.92% respectively.
- Repository line-ending policy -
.gitattributespins text files to LF while retaining CRLF for Windows command scripts. - CI now builds and tests the web editor (
.github/workflows/ci.yml) — a newweb-editorjob builds@uwmd/core, then runs the web editor's ownnpm run build(tsc + vite) andnpm test(33 vitest cases). The rootnpm testonly covers workspace packages, andtools/*are intentionally not workspace members, so the editor's calc-integrity suite was never gated in CI. - Web editor: accessibility & keyboard pass (
tools/web-editor/) — a global:focus-visiblering (light on the navy toolbar) so keyboard users can see focus without cluttering mouse use; the New Deal dialog gainsaria-modal,aria-labelledby, real<label htmlFor>associations on every field, and initial focus (it already closed on Esc); the editor-views tabs are a labelled<nav>witharia-currenton the active tab; the footed-cell override inputs getaria-labels; (the report iframe already had atitle). A newa11y.test.tsxruns axe-core against the dialog and a flagged section view and fails on any serious/critical violation (color-contrast excluded — jsdom can't compute rendered colors). Web-editor suite is now 33 tests across 5 files. - Web editor: inline validator remediations (
tools/web-editor/src/components/SectionView.tsx) — the validator's issues for the active section now render in context at the top of that section (severity, code, field badge, message, and theBUILTIN_REMEDIATIONScopy), and the offending flat numeric field is flagged with a red border,aria-invalid, an associated<label>, and the remediation shown beneath it viaaria-describedby— all read off theValidationMessage(never re-authored, so the footer and the inline copy can't drift). The globalValidationPanelfooter is unchanged.SectionView.test.tsx(jsdom) pins that the remediation shows in-context and the field is marked invalid, and that a clean section shows nothing. Web-editor suite is now 31 tests across 4 files. - Web-editor component tests for the footed-model surfaces (jsdom) (
tools/web-editor/src/components/footed-model.test.tsx) — adds jsdom +@testing-library/react(dev-only; bundle unchanged) and pins the contract every footed surface shares: editing one input re-foots the dependent totals and dispatches exactly onesection_replace. CoversValuationModel(NOI change re-foots indicated value) and the newDcfModel(a year's NOI re-foots its levered cash flow; the gross exit value re-foots the whole disposition→net→proceeds waterfall; "+ Add year" appends a projection year in one dispatch). Component tests are*.test.tsxand opt into jsdom via a// @vitest-environment jsdomdocblock, so the pure node suite stays fast. Web-editor suite is now 29 tests across 3 files. - DCF footing in
@uwmd/core+ a DCF footed-model surface in the web editor (dcf.ts,tools/web-editor/src/components/DcfModel.tsx) — thedcfsection was the last calc-bearing section still edited as a flat numeric grid. NewderiveDCF(content)foots the relationships that follow unambiguously from a DCF block's own stored inputs — per projection yearnet_cash_flow_levered = NOI − annual_debt_serviceandcash_on_cash_return = levered / cumulative_equity_invested, and the exit waterfalldisposition_costs = exit_value_gross × disposition_costs_pct,exit_value_net = gross − disposition, andnet_proceeds_to_equity = net − loan_balance_at_exit. It deliberately leavesexit_value_gross(capitalizes a forward NOI the block doesn't store) andreturns.*(IRR / NPV / equity multiple — cash-flow-timing convention) as inputs, in the same narrow, self-contained spirit asderiveValuation. Pure, exported fromindex.ts+browser.ts, and pinned to the Parkview worked example indcf.test.ts(8 tests). The web editor's newDcfModelsurface edits the assumptions, the per-year cash-flow rows (add/remove years), and the exit gross + loan balance; the derived totals render as locked ƒ derived cells and IRR/NPV/equity multiple show read-only as engine-provided. Removes the five flatdcfentries from the editor's numeric catalog. Web-editor tests grew to 25 (a dcf array round-trip throughapplyEdit+ reparse, plus array-indexeddeepGet/deepSet). - Web editor test harness (
tools/web-editor/) — the calc-aware editor previously shipped with zero tests despite being the Tier-2/3 chokepoint. Adds Vitest (dev-only; not in the production bundle — bundle hash unchanged) with atestscript and a Node test environment, plus two suites:src/edits.test.ts(7 tests) pins therunEdit()contract — afrontmatter_setapplies and re-parses so in-memory state can't drift from the canonical bytes, a rejected op returns a failed outcome instead of throwing,EditSettingsthread confidence +human_review_requiredonto the written block,supersedemode archives the prior block (two fences) wherereplaceedits in place (one), andcarryForwardOverrides()keeps a pinned_meta.field_overridesalive across a later edit that doesn't restate it; andsrc/catalog.test.ts(16 tests) covers the path helpers and the wrapper-awaregetNumeric/setNumericthat preserve the{ value, … }provenance wrapper. 23 tests, all green;npm --prefix tools/web-editor run testandrun buildboth pass. - Deterministic section-footing in
@uwmd/core(derived.ts,rentroll.ts,opstatement.ts) —deriveRentRoll(content)rolls a unit-level (multifamily) or tenant-level (commercial) schedule up into the section's totals (GPR, in-place rent, physical occupancy, loss-to-lease, concessions, net effective rent, leased/vacant SF, rent PSF, WALT, plus a recomputedunit_mix_summary/tenant_concentration), tolerant of both the spec naming (nra_sqft/base_rent_annual) and the worked-example naming (leased_sf/annual_base_rent) and writing back to whichever total keys the block already uses.deriveOperatingStatement(content)foots EGI / total OpEx (excluding below-the-line capex + replacement reserves) / NOI / expense ratio / NOI margin from the income and expense line items, withother_incomeandutilitiessub-rollups. Both are pure (no I/O, never mutate), returnDerivedField[]via the sharedderived.tscollector (which drops any non-finite result so partial data never writesNaN), and honor invariant #1 (not AI math — plain deterministic arithmetic, the same spirit as the calc packs) and #4 (one source so the editor, CLI, and Excel converter never disagree). Exported from bothindex.tsandbrowser.ts; covered byrentroll.test.ts(13) andopstatement.test.ts(8). Extended to three more sections (debt.ts,sourcesuses.ts,valuation.ts):deriveDebt(content)foots monthly + annual debt service from the loan terms — fully-amortizing via apmt()byte-identical to the calc engine'spmtbuiltin,loan × ratewhen interest-only (detected fromamortization: "interest_only"or a missing/zero term), rate-key tolerant;deriveSourcesUses(content)foots the per-bucketsources.total/uses.total, the nestedclosing_costs.total, and the top-level project-cost mirrors from the line items, and reports the sources-vs-usesgap/balancedso an imbalance surfaces without being silently written;deriveValuation(content)foots the income-approachindicated_value = noi_used / cap_rate_applied(and its delta to purchase price) entirely from the block's own inputs. Covered bydebt.test.ts(6),sourcesuses.test.ts(6),valuation.test.ts(5). - Lender Package / Credit Memo report renderer in
@uwmd/core(report.ts) —renderReportHtml(parsed, opts)implements the spec's rendering targets §7.1 (Tier 1 Lender Package: cover page, executive summary with metric grid, property overview, proforma, rent-roll summary, debt structure, sources & uses, borrower summary, exit analysis, assumptions table with source badges + standard disclaimer) and §7.2 (Tier 2 Credit Memo: adds market analysis with comps, financial analysis with annual cash flows + stress matrix + break-even, due diligence, risk assessment, compliance, covenants, pipeline-log appendix). Output is a self-contained, print-aware HTML document (embeddedREPORT_CSSwith@page/@media printrules) or an<article>fragment for embedding. Zero dependencies, browser-safe, exported fromindex.tsandbrowser.ts; every number is read from the file (calc engine/pack output), never recomputed. Handles both multifamilyunit_mix_summaryand commercialtenantsrent rolls, single-variant maps, and missing sections (skipped + reported insectionsSkipped). Newuwmd reportCLI subcommand (--tier,--prepared-by,--output,--stdout). - PDF report pipeline (
packages/uwmd-report/, preview0.1.0) — new@uwmd/reportpackage providinguwmd-report <file.uw.md> [-o out.pdf] [--tier] [--format pdf|html]and a programmaticgenerateReport(parsed, opts)API. Prints core's report HTML to PDF via playwright-core with no bundled-browser download: resolves--browser/UWMD_REPORT_BROWSER, then system Chrome, then system Edge, then a Playwright-managed Chromium; typedBrowserNotFoundErrorwith remediation otherwise. Page layout is owned entirely by core'sREPORT_CSS(preferCSSPageSize, zero engine margins), so browser print of the HTML and the CLI PDF are identical.
Fixed
- Windows CRLF parsing -
parseUWFile()now accepts CRLF documents without silently dropping every fenced UW section. The parser retainsrawinput byte-for-byte while normalizing line endings only for structural scanning. Regression coverage exercises a complete CRLF section. - Unterminated section fences fail closed - a recognized
uw:sectionfence without a closing fence now raises typedUNCLOSED_SECTION_FENCEeven outside strict mode, preventing the remainder of a deal from being swallowed as one malformed block. - Unsupported document targets fail explicitly - the core
pdfanddocxrender targets now throw typedUnsupportedRenderFormatErrorinstead of returning successful-looking empty output. PDF callers are directed to@uwmd/report; DOCX remains explicitly unimplemented. - Publishable core package contents -
@uwmd/corenow explicitly ships its compileddistAPI and package README while excluding source tests. CI and the release workflow runverify-packagesso a package whose exports point at missing artifacts cannot be published again. - Web editor lint errors that were failing the CI
lintjob —biome lintflags (recommended-default errors):noAutofocuson the New Deal dialog and the footed-cell override input (replaced theautoFocusattribute with a ref +useEffect().focus()), andnoArrayIndexKeyon the DCF per-year rows (now keyed by the row's year). These had accumulated across the unpushed web-editor train; the lint job is green again.
Changed
- Dependency hardening - the workspace Vitest and coverage-provider floor is
- Canonical project identity - the repository is renamed to
jaredmaxey/uw-markdown, package repository links follow the new URL, and public package/homepage metadata now points tohttps://uwmd.org. raised to 3.2.6, clearing the critical development-server advisory. Safe transitive lockfile updates also patch Vite, esbuild, PostCSS, fast-uri, tmp, and brace-expansion versions where upstream ranges permit. Remaining ExcelJS archive/UUID advisories require an upstream release or a separately validated converter migration before the separately versioned Excel add-on can publish. - Web editor 0.5.0 — rent-roll & operating-statement become underwriting models, not forms (
tools/web-editor/). NewRentRollModelandOperatingStatementModelsurfaces (sharedFooted.tsx) treat the line items as the only inputs and render the section totals as locked "ƒ derived" cells footed live by core'sderiveRentRoll/deriveOperatingStatement. Every mutation (cell edit, add/remove row, override, revert) clones the block, mutates one array, re-foots, writes back every non-pinned total, and dispatches a singlesection_replacethrough theapplyEdit()chokepoint, so the file can never be left internally inconsistent. A footed total can be pinned by hand via an inline override editor; the pin is recorded in the format's own_meta.field_overrides(reason: "overridden") and badged "manual" until reverted to formula. The edit dispatcher (edits.ts) now carriesfield_overridesforward across unrelated edits (the protocol'sbuildMeta()doesn't), and the generic scalar editor acceptslockedPathsso each footed total has exactly one editing path. Replaces the oldRentRollTable(deleted) and the hand-entered rent-roll/operating-statement numeric fields. The same pattern now extends to three more sections —DebtModel(loan terms in, monthly/annual debt service footed),SourcesUsesModel(every source/use line in, bucket + closing-cost + project-cost totals footed, with a live sources-vs-uses balance check), andValuationModel(NOI + cap rate in, income-approach indicated value footed) — via a sharedmodel-kit.tsx(theuseFootingwrite-path hook +InputRow/FootedRowrow primitives). Those three sections drop out of the flat numeric allow-list (catalog.ts); their footed totals are locked out of the generic scalar editor so each has exactly one editing path. @uwmd/core/browsernow exports the intelligence + calc-introspection surfaces —resolveValue/readInFile(cascade),rankGaps(value-of- information),inferGaps/summarizeGaps/readGapsContent, the asset-class default tables (getAssetClassDefaults/getDefaultRange/listDefaultedFields- the five
*_DEFAULTS), andgetExprDependencies/extractDependencyGraph, with their types. All confirmed browser-safe (no node, no SDK); previously only on the nodeindex.tsentry. Enables the web editor's intelligence and calc-transparency panels.
- the five
- Web editor 0.4.0 — extensive build-out (
tools/web-editor/): five tabs (Editor, Intelligence, Report, Diff, Source). New Intelligence tab surfaces Scope (every required input resolved through the fallback cascade with its source step + range) and Refine (VOI gap ranking with affected-output ranges and suggested questions). New edit-provenance bar threading actor/source/confidence/notes/human-review into_metaand toggling replace vs. append (supersede) mode (promotessection_replace→section_supersede). New assumptions editor capturing override rationale (is_overridden/original_value/override_rationale), generic field editor for every scalar leaf (narrative strings → textareas), add/remove rent-roll rows, clickable calc cards opening a formula + resolved-inputs- result detail (
CalcDetail), and a Diff tab (section + frontmatter changes since load/save via corediff()). Edit chokepoint unchanged (edits.ts→applyEdit()→ re-parse); modal Escape handling made global.
- result detail (
- Web editor 0.3.0 — richer editing surfaces (
tools/web-editor/): editable rent-roll tables (unit-mix rows for multifamily-style rolls, tenant rows for commercial — cell edits replace the row throughapplyEdit()); NOI line-item editor (income/expense entries; wrapped{value, …}fields are updated via a wrapper-awaresetNumericthat preserves rationale/source provenance, stored totals shown read-only with validator-flagged drift); snapshot-based undo/redo (restores prior canonical source verbatim; Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z + toolbar); New Deal dialog scaffolding a blank file viagenerateBlankUWFile; per-section superseded-version history view; Source tab (read-only canonical bytes + copy); Ctrl+S download; numeric allow-list expanded from 8 to ~30 fields across eight sections (incl. DCF assumptions and operating statement). - Web editor rebuilt on React 18 + Tailwind CSS 4 (
tools/web-editor/, preview0.2.0) — replaces the vanilla-TS stage-3 editor. Adds: a pinned calc strip that evaluates the asset class's full pack viagetPackForAssetClass(all five shipped classes, was multifamily-only), block_metaprovenance chips (version/source/confidence/review-required), collapsible raw-JSON block view, per-section prose display, pipeline-log table, and a live Report Preview tab renderingrenderReportHtmlin a sandboxed iframe on every edit with Lender Package / Credit Memo toggle, Download HTML, and Print/PDF. The edit chokepoint is unchanged: every mutation flows throughsrc/edits.ts→applyEdit()→ re-parse, so in-memory state can never drift from canonical bytes (React state only holds the result). - Initial
.uw.jsonprototype in@uwmd/coreestablished the model-level round-trip and CLI export path later stabilized by RFC 0014 Phase A above.- Renderer unit tests in@uwmd/corecovering JSON projection, superseded history opt-in, CSV numeric/percent output, summary/chat rendering, chat truncation, and the explicit PDF/DOCX stub targets. - Self-storage calc pack + defaults + worked example + Excel layout —
SELF_STORAGE_PACKadds twelve deterministic metrics keyed off NRSF, rentable units, physical occupancy, and economic occupancy.SELF_STORAGE_DEFAULTSregisters cascade/refinement ranges,Sonoran-Self-Storage-Peoria-AZ.uw.mdprovides a footing worked example, and@uwmd/excelnow emits a self-storage workbook layout with Excel↔evaluator parity to 6 decimals. - Declarative module loader/registry in
@uwmd/core—modules.tsvalidates and registers in-processModuleManifestobjects, including shape checks, formula/rule parsing, dependency load order, and tier/protocol/format compatibility. Dynamic imports, signing, and custom asset-class identifiers remain v2/RFC work. - Repo restructured into OSS-ready monorepo (
spec/,packages/,examples/,conformance/,tools/). @uwmd/corepackage (renamed fromuwmd).- UW Protocol v1 specification (
spec/UW_PROTOCOL_v1.md). - TypeScript protocol surface (
packages/uwmd-core/src/protocol.ts) —ViewerCapability,SectionViewModel,ModuleManifest,ProtocolError,BUILTIN_VIEW_MODELS, etc. - Module manifest JSON Schema (
spec/schemas/module-manifest.schema.json). - Canonical formatting helpers (
packages/uwmd-core/src/format.ts) —formatCurrency,formatPercent,formatRatio, etc. - Conformance test corpus (
conformance/tier-{1..4}/). - Single-file Tier-1 reference viewer (
tools/web-viewer/index.html). - Top-level OSS scaffolding (LICENSE, CONTRIBUTING, CODE_OF_CONDUCT, CI workflow, issue templates).
- Tier-2 Editor —
applyEdit()dispatcher in@uwmd/corecoveringfrontmatter_set,section_replace,section_supersede, andpipeline_log_append, withBUILTIN_EDIT_POLICIESenforcement and round-trip preservation. Newuwmd editCLI subcommand. - Tier-3 Calc Host — safe-expression parser + evaluator + built-ins (
sum,avg,min,max,coalesce,if,round,pmt,npv,irr) per protocol §VIII.1 EBNF, with fullCalcErrortaxonomy. Newuwmd calcCLI subcommand. - Validator wired to
BUILTIN_REMEDIATIONSregistry (no inline strings; per protocol §III.6). - Conformance runner (
scripts/run-conformance.mjs) executing tiers 1–4 with CI gate on tiers 1–3. Filled missing tier-2/3/4 fixtures (frontmatter-set-recommendationbefore/after pair,revpar-basic,dscr-from-section,l6-risk-ratingshape assertion). - JSON Schemas for all six boundary-crossing protocol types:
uwmd-block,edit-operation,protocol-error,implementation-manifest,calc-result,issue-remediation. Programmatic validator (scripts/validate-schemas.mjs) using ajv 2020 + ajv-formats with cross-file$refpre-registration. CI gate. - Governance scaffolding — SECURITY.md, GOVERNANCE.md (owner-led mode with contributor-activated collaborative safeguards and an RFC process), MAINTAINERS.md,
.github/CODEOWNERS, ROADMAP.md,docs/rfcs/directory with template and process README. - npm publish workflow (
.github/workflows/release.yml) — onv*tag, runs full test + conformance + schema-validation gate, then publishes@uwmd/coreto npm with provenance.prepublishOnlyscript in the package mirrors the same gate locally. - VS Code extension (
tools/vscode-uwmd/, preview0.1.0) — syntax highlighting for.uw.md(YAML frontmatter + Markdown + embedded JSON inuwmd jsonblocks), folding for frontmatter / fenced blocks / heading sections, document outline, and on-save validation surfacing every@uwmd/coreissue with itscode,title,remediation, andspec_ref. Bundled via esbuild; ships as.vsix. - Documentation site (
tools/docs-site/, preview0.1.0) — VitePress build covering the format spec, protocol spec, JSON Schemas, conformance corpus per tier, and the full set of project documents (roadmap, governance, contributing, security, RFC process). Repo-root markdown remains the single source of truth;scripts/prebuild.mjscopies content into the site tree at build time and rewrites relative links to site URLs. Ships as a 2.9 MB static bundle deployable to any static host. - Standalone CLI installer (
packages/uwmd-cli/, preview1.0.0) — newuwmdnpm package providingnpx uwmd <command>for non-developers who don't want to clone the repo. Thin wrapper that re-exposes the@uwmd/coreCLI module via a new./clisubpath export. Covers all eleven subcommands:init,parse,validate,compact,diff,render,edit,calc,run,summary,layers. Same Tier-1/2/3/4 conformance behavior as the in-repo CLI; no separate code path, so no calc-drift risk. @uwmd/core/browsersubpath export — new browser-safe entry point that excludes the agent runner and@anthropic-ai/sdkso the library can be bundled directly into web apps. Re-exports parser, validator, compactor, renderer, editor (applyEdit), calc engine (evaluateCalc), formatting helpers, and the full type / protocol surface. Source atpackages/uwmd-core/src/browser.ts.- v2 RFC drafts (
docs/rfcs/) — six initial drafts opened to start the v2 design conversation, one per item in ROADMAP §"v2 spec exploration": RFC 0001 (locale negotiation), RFC 0002 (Sigstore-style module signing), RFC 0003 (custom asset-class declarations from modules), RFC 0004 (language-agnostic conformance runner with TAP14 + JSON manifest output), RFC 0005 (stochastic calc declarations with seeded PCG-XSL-RR-64 PRNG), and RFC 0006 (hospitality reference module). All instatus: draft; none required for v1 conformance. - Excel converter (
packages/uwmd-excel/, preview0.1.0) — new@uwmd/excelpackage providinguwmd-excel <file.uw.md> [-o out.xlsx]and a programmatictoWorkbook(parsed)API. Generates a three-sheet multifamily workbook: an Underwriting sheet (header + named-range inputs block + derived-metric formulas block), an Operating Statement sheet (five income lines +EGI=SUM(income)+ eleven expense lines +total_opex=SUM(expenses)+NOI=EGI−total_opex), and a Pipeline Log audit sheet. The eight derived-metric formulas (cap rate, LTV, DSCR, debt yield, price/unit, loan/unit, loan/sqft, cash-on-cash) ship as Excel formulas referencing the workbook-scope named ranges (purchase_price,loan_amount,annual_debt_service,total_units,total_nra_sqft,equity_sponsor,noi) — these mirrorMULTIFAMILY_STARTER_PACKin@uwmd/coreexactly, so opening the workbook in Excel and runninguwmd calcagainst the same.uw.mdproduce identical numbers by construction. Editing any named-input cell or any line item ripples through to every dependent metric. Multifamily-only and.uw.md→.xlsxonly in 0.1.0; the reverse direction is deferred and the calc-aware web editor remains the canonical Tier-2 chokepoint for editing. - Calc-aware web editor (
tools/web-editor/, preview0.1.0) — Vite + plain TS bundle on top of@uwmd/core/browser. Drag-drop file load, sidebar with per-section validation badges, frontmatter editor (16 typed inputs spanning text / enum / list fields), and section views with typed numeric inputs on five calc-bearing sections (property,valuation,noi_model,debt_structure,sources_uses). Every edit dispatches throughapplyEdit()and reparses the file, so in-memory state can never drift from canonical source. Multifamily calc starter pack (cap rate, LTV, DSCR, debt yield, $/unit, $/sqft, price/unit, cash-on-cash) re-evaluates on every render viaevaluateCalc. Validation footer surfaces everyValidationMessagewith severity, code, section, andBUILTIN_REMEDIATIONScopy. Bundle size: 19 kB app + 11 kB core, gzipped. Replaces the originally-planned narrative-only Tier-2 web editor — that design was rejected because separating safe narrative edits from unsafe numeric edits creates two paths into the same file. - Lint toolchain — Biome
1.9.4configured at the repo root (biome.json) withnpm run lint/npm run formatscripts. Linter-only (no formatter enforcement) so the existing code style is preserved. Newlintjob in CI (.github/workflows/ci.yml). - CLI smoke tests —
uwmdCLI installer now ships a vitest suite (packages/uwmd-cli/test/cli.smoke.test.ts) covering--help,parse,validate, and missing-file error path against the bundled Parkview fixture. CLI was previously untested. .editorconfigat the repo root for cross-editor consistency (LF, UTF-8, 2-space, trim trailing whitespace except in.md).- Canonical asset-class calc packs in
@uwmd/core(packages/uwmd-core/src/packs/) —MULTIFAMILY_PACKis now the single source of truth for the eight multifamily derived metrics, exported from both@uwmd/coreand@uwmd/core/browseras aModuleManifest. Previously, the same eight calcs were defined twice — once in calc-engine syntax insidetools/web-editor/src/calc-pack.tsand again in Excel formula syntax insidepackages/uwmd-excel/src/multifamily.ts. Adding a metric required editing both. Both consumers now import from the canonical pack. - Office calc pack + defaults in
@uwmd/core—OFFICE_PACK(packages/uwmd-core/src/packs/office.ts) is the second built-in asset-class pack: eleven office derived metrics (cap rate, LTV, LTC, DSCR, debt yield, price/sqft, loan/sqft, NOI/sqft, operating expense ratio, cash-on-cash, occupancy) as aModuleManifest, exported from both@uwmd/coreand@uwmd/core/browser. Office field paths differ from multifamily — size isproperty.rentable_square_feet, sponsor equity issources_uses.sources.sponsor_equity, and the NOI model splits into nestedincome/expenses.OFFICE_DEFAULTS(defaults.ts) adds a 13-field office triage table (wider ranges than multifamily, plus office-specific TI/LC fields) registered for the cascade. NewgetPackForAssetClass(asset_class)registry helper selects the right pack by asset class;uwmd refineanduwmd scopenow resolve office deals automatically off the deal'sfrontmatter.asset_class. Every office metric is verified through bothevaluateCalc()and the emitted Excel formula to 6 decimals against the Riverside office worked example (packages/uwmd-core/src/packs/office.test.ts). - Retail calc pack + defaults + worked example in
@uwmd/core—RETAIL_PACK(packages/uwmd-core/src/packs/retail.ts) is the third built-in asset-class pack: twelve retail derived metrics, keyed off gross leasable area (property.gross_leasable_area) for the per-SF metrics and GLA for occupancy, plus a retail-distinctiveexpense_recovery_ratio(income.expense_reimbursements / expenses.total_operating_expenses) for NNN recovery.RETAIL_DEFAULTS(defaults.ts) adds a 13-field retail triage table including the NNNexpense_recovery_rate. New third worked exampleexamples/Cactus-Crossing-Retail-Mesa-AZ.uw.md— a 95k SF grocery-anchored neighborhood center with a tenant-level rent roll, NNN reimbursements, and percentage rent; validates with zero issues. Both registries (getPackForAssetClass,getAssetClassDefaults) now resolveretail, souwmd refine/uwmd scopework for retail deals automatically. Every metric is verified throughevaluateCalc()and the emitted Excel formula to 6 decimals (packages/uwmd-core/src/packs/retail.test.ts). - Industrial calc pack + defaults + worked example in
@uwmd/core—INDUSTRIAL_PACK(packages/uwmd-core/src/packs/industrial.ts) is the fourth built-in asset-class pack, completing the four core commercial types: twelve metrics keyed off rentable building area (property.rentable_square_feet) and SF occupancy, including the NNNexpense_recovery_ratio.INDUSTRIAL_DEFAULTS(defaults.ts) adds a 13-field industrial triage table (lowest expense ratio + highest recovery + tightest caps of the four classes). New fourth worked exampleexamples/Ironwood-Logistics-Industrial-Tolleson-AZ.uw.md— a 220k SF Class A bulk-distribution warehouse, multi-tenant NNN; validates with zero issues. Both registries now resolveindustrial, souwmd refine/uwmd scopework for industrial deals automatically. Every metric verified throughevaluateCalc()and the emitted Excel formula to 6 decimals (packages/uwmd-core/src/packs/industrial.test.ts). - Excel converter: office/retail/industrial layouts + multifamily NOI parity fix (
packages/uwmd-excel/) —@uwmd/excelis no longer multifamily-only. The converter engine (toWorkbook.ts) is now generic over aWorkbookLayout(layout.ts) selected byfrontmatter.asset_classvia a registry (layouts.ts,getLayoutForAssetClass); layouts ship for multifamily, office, retail, and industrial. A layout is just{ assetClass, pack, incomeLines, expenseLines, namedInputs }— the engine derives the calc-path→named-range map and the derived-metrics block from the layout (buildNamedRangeMap/buildDerivedMetrics) so formulas and named ranges can't drift. Bug fix: the operating statement previously computedEGI = SUM(income lines)while vacancy/credit-loss were stored as positive magnitudes, so the SUM added vacancy instead of subtracting it — overstating EGI and NOI, and (since thenoinamed range feeds cap-rate/DSCR/etc.) silently breaking Excel↔evaluator parity. Income deduction lines now carrysign: -1, soEGI = SUMandNOI = EGI − total opexfoot to the storedeffective_gross_income/net_operating_income. The round-trip test (toWorkbook.test.ts) was rewritten to compute parity for all four example deals (assert the statement foots, then evaluate every derived-metric formula against the workbook's named-range values and compare toevaluateCalcto 6 decimals) — the old test only checked formula text and missed the bug. The Riverside office example's operating statement was reconciled (+$2,550 utilities → $382,550 total opex) so EGI − opex foots to its stated $300,000 NOI; cap rate, DSCR, and all other narrative figures are unchanged. - AST → Excel formula emitter (
packages/uwmd-core/src/packs/excel-emit.ts) —emitExcelFormula(formula, { namedRanges })translates a Tier-3 safe expression into Excel syntax via a caller-supplied identifier-to-named-range map. Maps arithmetic 1:1, ternary toIF(), modulo toMOD(), logical&&/||toAND()/OR(), and the 17 supported builtins (sum/min/max/if/round/pmt/npv/irr/abs/floor/ceil/sqrt/pow/log/exp/fv/pv/nper). ThrowsExcelEmitErrorwith codesEXCEL-EMIT-PATH(unmapped identifier),EXCEL-EMIT-FN(no Excel equivalent),EXCEL-EMIT-OP(unhandled operator). New parity test inpacks.test.tsevaluates each multifamily metric both throughevaluateCalc()and through the emitted Excel formula (with named-range substitution) and asserts they agree to 6 decimals. - Pre-publication review follow-ups (REVIEW-2026-04-26.md) — first pass at the audit's punch list:
- Documentation on-ramps: ARCHITECTURE.md (system map: spec → reference library → tools → conformance corpus, with dependency rules and "where to start contributing" guide), VERSIONS.md (compatibility matrix across format / protocol /
@uwmd/core/ CLI / tools, plus pinning recommendations), docs/GLOSSARY.md (alphabetical entries for both format-specific and CRE domain terms with normative spec links), docs/TOOLS.md (decision tree for picking among the seven shipped tools), and tools/docs-site/tutorials/your-first-uwmd-file.md (5-step walkthrough from blank file to validated minimal.uw.md). All five are wired into the docs-site sidebar viaprebuild.mjsso the repo-root markdown remains the single source of truth. - Second worked example: examples/Riverside-Office-Phoenix-AZ.uw.md — suburban office with a bridge-loan capital stack and tenant-level rent roll, demonstrating that the format is not multifamily-only. Validates with zero errors and a single intentional
FV_DSCR_BELOW_THRESHOLDwarning (bridge-loan DSCR is thin at close by design). - Coverage in CI: new
coveragejob in.github/workflows/ci.ymlrunsvitest --coverageon@uwmd/core, publishes a coverage table to the GitHub job summary, and uploads the fulllcovreport as a build artifact. Soft floor (continue-on-error: true) at the current baseline; will be tightened once the number is ratcheted up. Vitest config atpackages/uwmd-core/vitest.config.ts. - Malformed conformance fixtures: new
conformance/tier-1-reader/malformed/category with three fixtures (META_MISSING,META_LOW_CONFIDENCE_NO_REVIEW_FLAG,CC-04) and matchingexpected.jsondeclarations. The conformance runner (scripts/run-conformance.mjs) gained arunTier1Malformedpass that dispatches each fixture throughvalidateUWFileand asserts the actual issue codes are a superset of the expected codes. - Property-based tests on the calc engine: new
packages/uwmd-core/src/calc/calc.property.test.tsusingfast-checkto assert (1) parser totality on arbitrary ASCII input ≤ 256 chars (always returns AST or throws typedCalcError), (2) evaluator null-safety against the empty context (always returnsCalcValuescalar or throws typedCalcError), (3) Excel-emit grammar parity (every AST the constrained generator can produce emits to a non-empty string). Runs 500 + 300 + 300 cases per property in CI. - YAML parser hardening: new Appendix D in the format spec defines the supported YAML subset (scalars, mappings, sequences, comments) and the rejected features (anchors, aliases, explicit tags, block scalars, complex keys, directives). The parser at
packages/uwmd-core/src/parser.tsnow hard-rejects unsupported features at frontmatter parse time with a typedUWMDParseErrorcarrying the new validator codeUNSUPPORTED_YAML_FEATURE(registered inBUILTIN_REMEDIATIONS). Eight new unit tests inparser.test.tscover the rejection paths plus the all-supported subset. - Validator code taxonomy: new §III.6a in the protocol spec documents the three validator code prefixes (
CC-NNcross-section consistency,FV_*financial validity,META_*meta integrity) and their severity defaults. Pointer toBUILTIN_REMEDIATIONSand the validator implementation as the authoritative registry — the review's claim that codes were unregistered turned out to be wrong; the gap was that the categorization wasn't documented. - Pipeline state L3 documented: the L3 layer was missing from the Parkview example's
pipeline_statebecause it is intentionally reserved. The format spec now states this explicitly inside thepipeline_stateexample block — conforming files MUST NOT defineL3_*keys, and any layer key absent frompipeline_stateis treated aspendingby default. - README cleanup: dropped the stale "(in progress)" labels next to the protocol spec and the conformance corpus links. Both shipped per ROADMAP.md.
- Documentation on-ramps: ARCHITECTURE.md (system map: spec → reference library → tools → conformance corpus, with dependency rules and "where to start contributing" guide), VERSIONS.md (compatibility matrix across format / protocol /
- Two new v2 RFC drafts — RFC 0007 (sensitivity tables as a first-class calc primitive —
sensitivity_table()builtin returning a structured grid result) and RFC 0008 (alease_up_schedulestandard section for value-add and ground-up deals). Both instatus: draft; not implemented. - Architectural review v1.1 train (six-phase landing of
ARCHITECTURAL-REVIEW-2026-04-27.md) — full surface expansion across format, protocol, library, conformance, and docs in response to the three architectural concerns raised in the review (wrong-actor / wrong-tool writes, incomplete-data first-class support, AI-efficient representation). Implemented as six independently-shippable phases.- Phase 1 — Foundation (
_metaextensions + validator taxonomy +scopestage)._metagained five optional fields documented in Format Spec Part III §3:partial,provisional,field_overrides[],content_hash,parent_hash._meta.sourceenum widened by four additive values (user_input,asset_class_default,global_default,system_default); existingscenario_defaultretained. New normative §IX "Fallback cascade" in the protocol spec defines the seven-step resolution order producers MUST walk. NewDealStagevaluescopeadded belowscreeningfor back-of-napkin triage;STAGE_REQUIREMENTSwidened to support field-level + either-or readiness checks (required_field_paths,required_one_of). Validator code taxonomy finalized:CC-NN(existing),FV-NN(renamed from legacyFV_*strings, both emitted vialegacy_codefor one release),DQ-NN(new),INT-NN(new),POL-NN(new),META_*(existing).BUILTIN_REMEDIATIONSextended with one entry per new code. Confidence vshuman_review_requireddisambiguated as orthogonal in spec. - Phase 2 — Gaps section + cascade resolver + asset-class defaults +
INCOMPLETE_DATA_POLICIES. New optional standard sectiongapswithspec/schemas/sections/gaps.schema.json—items[]with(section, field_path, reason ∈ {missing, illegible, out_of_scope, deferred, blocked_by_dependency, awaiting_external}, blocks_stage, first_seen, last_checked, owner, note)plussummaryrollup. Newpackages/uwmd-core/src/defaults.tsexportsMULTIFAMILY_DEFAULTScovering 11 fields with low/central/high ranges (expense ratio, vacancy, rent growth, management fee, replacement reserve, debt rate, amortization, IO months, LTV, exit cap, closing costs); each entry version-pinned at1.0.0with the contract that range revisions bump the version. Newpackages/uwmd-core/src/cascade.tsexportsresolveValue(field_path, parsed, ctx?)walking the protocol §IX cascade;MarketDataLookupandInvestorProfileare interface-only (adopters bring their own). Newpackages/uwmd-core/src/gaps.tswithinferGaps,applyGapPolicy,summarizeGaps. NewINCOMPLETE_DATA_POLICIESregistry inprotocol.tswithGapAction = halt | degrade | substitute | deferandlookupIncompleteDataPolicyresolution; seeded with conservative(section, stage)defaults.applyEditgained an opt-inmaintain_gapsoption that recomputes the gaps section after every successful write under actorsystem/gaps-maintainer. - Phase 3 — Integrity (hashes + chains +
uwmd verify). Newpackages/uwmd-core/src/integrity-canonical.tsimplements RFC 8785 (JCS) JSON canonicalization — vendored ~40 LOC, zero dependencies — with the documented exclusion of_meta.content_hashand_meta.signaturefrom the hash input. Newpackages/uwmd-core/src/integrity.tsexportscomputeBlockHash,verifyChain,verifyProvenance, returning typedIntegrityIssue[]with codesINT-01(parent_hash mismatch),INT-03(partial chain),INT-04(content_hash recompute mismatch),POL-01(unauthorized actor perBUILTIN_EDIT_POLICIES),POL-02(section_replacewheresection_supersederequired). NewapplyEditAsyncrecomputescontent_hashon every write whenoptions.integrityis enabled and rejects edits whosectx.parentHashis stale withINT-02. Crypto split: Nodecrypto.createHashand Web Cryptosubtle.digestproduce byte-identical hashes verified inintegrity-canonical.test.ts. Newuwmd verify <file> [--integrity] [--policy] [--validate] [--json]subcommand combines all three checks;uwmd validate --integrityalso opts in inline. - Phase 4 — Context profiles for AI consumption. New
packages/uwmd-core/src/context-profiles.tsexportsbuildContext(parsed, profile, opts?)returning aContextResultwith{content, tokenEstimate, profile, sectionsIncluded, truncated}. Five normative profiles defined in new protocol §X "Context profiles":summary(frontmatter + quick_metrics + pipeline_state + gaps, ≤600 tokens),live(all non-superseded, prose included),compact(all non-superseded, JSON-only, minified, stable→volatile section order, ≤55% oflivetokens),full(every byte),relevant(filtered toopts.sections). Stable→volatile compact ordering targets prompt-cache-friendly prefixes for adopters using LLMs at scale.buildAgentContextrewritten as a thin shim overbuildContext(parsed, layer.consumed_profile, {sections: layer.reads})— externalAgentContextshape preserved.BANCROFT_LAYERSentries gained aconsumed_profilefield. Newuwmd render --profile=summary|live|compact|full|relevant [--sections=…] [--max-tokens=N] [--no-meta]flag. - Phase 5 — Refinement engine (dependency graph + perturbation VOI + L0a / L0b agents). New
packages/uwmd-core/src/calc/dependencies.tsexportsgetExprDependencies(ast)andextractDependencyGraph(parsed, {packs?})which walks everyMULTIFAMILY_PACKcalc plus everycustom_calculations[]entry into aDependencyGraphof(outputs: calc → inputs, inputs: input → calcs, formulas). Newpackages/uwmd-core/src/refinement.tsexportsrankGaps(parsed, opts?)returningRankGapsResultwithby_voi[]ranked by value-of-information andby_stage_blocking[]for non-numeric completeness gaps. The perturbation method evaluates each provisional input at its low / central / high values using interval arithmetic across the dependency graph, computes today's output range, then computes the collapsed range if the gap were known; VOI = today − collapsed, summed across affected outputs. Question templates ship indefaults.tsco-located with the ranges. Diagnostics surface non-monotonic outputs as warnings. Two new pipeline layers:L0a Scope(prerequisites: none; readsproperty; writesnoi_model,debt_structure,valuation,market_analysis,gaps;consumed_profile: summary) andL0b Scope Refinement(prerequisites:L0a; reads*; writesproperty,noi_model,debt_structure,gaps;consumed_profile: compact). Newuwmd scope <file>deterministically resolves every required input via the cascade and stamps results provisional atdeal_stage: scope. Newuwmd refine <file> [--targets=...] [--top=N] [--json]runsrankGapsand prints / emits the ranking. L0b prompt template atpackages/uwmd-core/src/agents/L0b-prompt.md. - Phase 6 — Conformance corpus + RFC drafts. New conformance fixtures: tier-1
04-scope-only.uw.md(back-of-napkin scope file), and six new malformed fixtures (04-broken-chain→INT-01,06-wrong-actor→POL-01with siblingpolicies.json,07-replace-where-supersede-required→POL-02,08-provisional-without-gap→DQ-01,09-partial-without-overrides→DQ-03). New tier-2 fixtures:gaps-section-update(post-write maintainer hook),parent-hash-stamp(integrity-aware edit),stale-parent-rejected(negative-pathINT-02). New tier-3 mode:dependency-graph-multifamilyexercisesextractDependencyGraph. New tier-4 fixtures:l0a-scope-deterministic(shape contract for the scope agent's writes) andconsumer-profile-contract(verifies every Bancroft layer declares the rightconsumed_profile). The conformance runner (scripts/run-conformance.mjs) gained async dispatch, integrity / policy code multiplexing on tier-1 malformed viaverifyChain+verifyProvenance, optional<id>.policies.jsonand<id>.options.jsonsiblings on tier-1/tier-2 fixtures,expected-error.jsonfor negative-path tier-2 fixtures,runTier3Refinement/runTier4Profilemodes, and a<volatile>mask oncontent_hashstrings (which canonicalize over a per-run timestamp). Final tally: 29 conformance fixtures pass, 0 fail. - Deferred / discussed — five items intentionally captured as RFC drafts rather than implementation in this train: RFC 0009 (
_metav2 sub-object reorganization, the only proposed breaking change; deferred to v2.0 with back-compat parser shim), RFC 0010 (_meta.integrity.signatureas a separate@uwmd/signingpackage; explicit non-goal as everyday open-standard usage), RFC 0011 (JWT-style scoped capability tokens for write authorization in orchestrator-bound deployments), RFC 0013 (per-section embedding sidecar for portfolio-scale "find similar deals", v3 territory), and a new "Range types and napkin mode" subsection added to RFC 0005 framing the v1.1 perturbation engine as the v1.1 approximation of a stochastic v2. Other items explicitly NOT pursued and documented in the architectural review's out-of-scope catalog: per-field provenance everywhere (rejected —field_overridesis the chosen mid-point), calc-engine confidence propagation (deferred indefinitely — math is murky), pure-halt mode for required fields (rejected — partial data is the norm), compiled binary.uwcformat (rejected with extended explanation), AST cache files (deferred until measured parsing speed becomes a problem), per-block telemetry (no current adopter need), asset-class default tables for non-multifamily (one pack per RFC; office / retail / industrial follow), andMarketDataLookupreference implementation (interface only — adopters bring CoStar / Yardi / internal).
- Phase 1 — Foundation (
- Calc engine v1.1 (Phase B) — protocol version bumped to
1.1.0with two additive Tier-3 changes. (1) Grammar gains short-circuiting logical operators&&and||between conditional and comparison precedence levels, with full null propagation (any null operand → null) andCALC-TYPE-001on non-boolean operands; the right side is only evaluated when the left does not determine the result. (2) Built-in function set expands by 10: math (abs,floor,ceil,sqrt,pow,log,exp) and financial (fv,pv,nper). All new financials use the same unsigned convention as the existingpmt(positivepv= loan balance, positivepmt= periodic payment) and admit zero-rate edge cases by closed-form fallback. Spec updated atspec/UW_PROTOCOL_v1.md§VIII.1 (EBNF) and §VIII.3 (builtin table). Backwards-compatible: every v1.0.0 expression continues to parse and evaluate identically.
Changed
@uwmd/coreno longer declares abinentry — the standaloneuwmdpackage owns the binary so the two packages don't conflict when both installed. Library consumers continue to import from@uwmd/core; CLI consumers should installuwmd(ornpx uwmd). The CLI module is now exposed as the@uwmd/core/clisubpath export.- Release workflow now publishes both
@uwmd/coreanduwmdtogether on eachv*tag, with a version-match gate that also verifiesuwmd's pinned dependency on the corresponding@uwmd/coreversion. - Quickstart in
README.mdand fixture-regen examples inCONTRIBUTING.mdupdated to usenpx uwmd …(ornpm run cli -- …from a clone) instead of the rawnode packages/uwmd-core/dist/cli.jspath. - Format spec — added RFC 2119 preamble; restored §4.18 (Pipeline Log) to canonical numeric position; clarified section count (21 standard + 1 meta).
- Protocol spec — fixed
.uw.institution.jsoncross-reference (now points to Appendix C.6); added "Normative schema:" links from §I.4, §III.1, §V, §VIII, §XI to the new JSON Schemas; added §XIII "Future work" consolidating v2 deferrals. - Root
npm testandnpm run buildnow run across all workspaces (--workspaces --if-present), not just@uwmd/core. The existing@uwmd/exceltest suite and the newuwmdCLI smoke tests are now covered. vitestbumped from^1.6.0to^3.0.0in@uwmd/core,@uwmd/excel, anduwmd(clears four moderatenpm auditadvisories in the vite/esbuild dev-server chain).esbuildbumped from^0.24.0to^0.25.0intools/vscode-uwmd(clears the dev-server CSRF advisory).vitebumped from^5.4.0to^7.0.0intools/web-editor; the web-editor lockfile now reports0 vulnerabilities.- Calc engine — internal AST node for ternary expressions renamed
cond.then→cond.consequentto avoid the false thenable signal the previous shape produced. No external API change (the AST is internal topackages/uwmd-core/src/calc/parser.tsandevaluator.ts). - Docs site prebuild (
tools/docs-site/scripts/prebuild.mjs) — now copies the six numbered RFC drafts into the site tree and rewritesdocs/rfcs/NNNN-…and bareNNNN-…link forms to/about/rfcs/NNNN-…. Fixes 18 dead links that were breakingnpm run buildfor the docs site. - Workspace lockfile policy —
packages/uwmd-core/package-lock.jsonremoved (workspace member, drifts from the root lockfile). Tools undertools/*are intentionally not workspace members and continue to commit their own lockfiles. Documented in CONTRIBUTING.md.
Removed
packages/uwmd-core/ANTHROPIC_API_KEY.env— local-dev convenience file containing a real API key. Deleted; the CLI already loadsANTHROPIC_API_KEYfromprocess.env. Future: set it via your shell or a gitignored~/.config/uwmd/.envinstead of inside the repo tree.
[1.0.0-pre] — pre-public
Pre-public development of the format spec (UW_FORMAT_SPEC_v1.md) and reference parser/validator/renderer/runner/Claude agent host inside uwmd/.