Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions notations/views/documents/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Document views

Reserved for the document-view class — specs that render a standard document
(MRD, SRS, SDD, …) from canon. Landing with the document-view engine; see the
2026-07-30 rendered-documents architecture decision.
(MRD, SRS, SDD, …) from canon. See the 2026-07-30 rendered-documents architecture
decision. The document-view engine's skeleton-file parser lives at
[`packages/document-view-engine`](../../../packages/document-view-engine/README.md);
reference resolution and rendering are follow-on work.
64 changes: 64 additions & 0 deletions packages/document-view-engine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# @transitrix/document-view-engine

Parser for **skeleton files** — the Markdown-with-transclusion source format the
document-view engine renders against canon. A skeleton carries structure and
transclusion tags; the engine resolves them and emits a document. No document layout
ships with this package — a layout is authored by whoever needs that document, in
their own repository.

**Scope of this package today: syntax only.** `parseSkeleton()` turns a skeleton
file's text into a header object and a body AST. It does not resolve references
against canon, does not compute reference-resolution state, and does not render.
Those are later layers built on top of this AST.

## Skeleton file shape

```markdown
---
document: design description # free text; the name its readers use
canon: ../canon # root of the model this renders against
profile: neutral # reserved; only `neutral` exists today
---

{{# each REQUIREMENT where level = system and kind = functional order by id }}
### {{ .id }} — {{ .title }}

{{ .text }}
{{/ each }}
```

## Syntax

Delimiters `{{ … }}`; `\{{` escapes a literal.

| Form | AST node |
|---|---|
| `{{ REQ-14 }}` | `{ type: 'inline', id, fields: [] }` |
| `{{ REQ-14.text }}` | `{ type: 'inline', id, fields: ['text'] }` |
| `{{ REQ-14.parent.title }}` | `{ type: 'inline', id, fields: ['parent', 'title'] }` — traversal capped at depth 3 |
| `{{# each TYPE where f = v and f2 != v2 order by f }} … {{/ each }}` | `{ type: 'each', entityType, where, orderBy, children }` |
| `{{ .field }}` (inside an `each` body only) | `{ type: 'field-ref', fields }` |
| `{{ trace from = A to = B via = rel }}` | `{ type: 'trace', from, to, via }` |
| `{{ view <path> }}` | `{ type: 'view', path }` |

`id` is validated against the canonical ID grammar
([`IDS_AND_REFERENCES.md`](../../notations/IDS_AND_REFERENCES.md) §1-2), including the
`CAPABILITY` V/H diagram-address exception. A `where` clause's comparison is `=` / `!=`
against a literal, ANDed only — no other operator is expressible, by design.

## Usage

```js
import { parseSkeleton } from '@transitrix/document-view-engine/src/parse-skeleton.mjs';

const { header, ast, errors } = parseSkeleton(fileText);
if (errors.length > 0) {
// each entry is { message } — surface all of them, not just the first
}
```

## Tests

```
node packages/document-view-engine/tests/test_parse_skeleton.mjs
```
18 changes: 18 additions & 0 deletions packages/document-view-engine/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@transitrix/document-view-engine",
"version": "0.0.1",
"description": "Skeleton-file parser for the document-view engine — turns a Markdown skeleton with {{ ... }} transclusion syntax into a header + body AST. Syntax only: no canon resolution, no rendering (see README for scope).",
"type": "module",
"engines": {
"node": ">=18"
},
"files": [
"src/"
],
"license": "MIT",
"author": {
"name": "Transitrix"
},
"repository": "https://github.com/transitrix/methodology",
"dependencies": {}
}
19 changes: 19 additions & 0 deletions packages/document-view-engine/src/ids.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Canonical ID grammar (notations/IDS_AND_REFERENCES.md §1-2): `<TYPE>-[<middle>-]<INTEGER>`,
// plus the CAPABILITY V/H diagram-address exception. Own copy by design — same posture
// as decisions-cli's src/yaml.mjs: zero cross-package runtime dependency.

const GENERAL_ID = /^[A-Z][A-Z0-9_]*(-[A-Za-z0-9]+)*-[1-9][0-9]*$/;
const CAPABILITY_ID = /^CAPABILITY-[VH][1-9][0-9]*(\.[1-9][0-9]*){0,2}$/;

export function isValidId(id) {
if (typeof id !== 'string' || id === '') return false;
return GENERAL_ID.test(id) || CAPABILITY_ID.test(id);
}

// TYPE registry is open-ended (notations keep adding types) — a selection's entity
// type is validated as an uppercase TYPE-shaped token, not against a closed list.
const TYPE_NAME = /^[A-Z][A-Z0-9_]*$/;

export function isValidTypeName(name) {
return typeof name === 'string' && TYPE_NAME.test(name);
}
Loading
Loading