[verified] feat: add semantic diagram lifecycle

This commit is contained in:
2026-09-03 19:11:50 +00:00
parent 5146b14d43
commit db4129168b
37 changed files with 1626 additions and 58 deletions
+23 -1
View File
@@ -25,7 +25,15 @@ See [rules-layout.md](./rules-layout.md) for mandatory connector and layout rule
---
## Capability 3 — Diagram analysis (drawio-tools CLI)
## Capability 3 — Semantic lifecycle
The native TypeScript core supports loss-aware Draw.io import and deterministic round trips; transactional stable-ID edit batches; linked audience views; semantic query, architecture policy, and failure what-if analysis; three-way synchronization; accessible self-contained story HTML; and a non-launching doctor report.
See [semantic-lifecycle.md](./semantic-lifecycle.md) for command contracts, safety behavior, built-in policy identifiers, and examples.
---
## Capability 4 — Diagram analysis (drawio-tools CLI)
A **TypeScript / Node.js** CLI tool for programmatic analysis of `.drawio` files.
Entry point: `node dist/cli/commands.js` (run from the skill's `scripts/` directory), or `task run -- --file=… --action=…`.
@@ -49,6 +57,20 @@ Always prints YAML to stdout. Exit code `0` on success, `1` on error.
|---|---|---|
| `build` | YAML Diagram IR | Validate and convert a YAML specification to native `.drawio`; requires `--output` |
#### Semantic lifecycle
| Action | Scope | Description |
|---|---|---|
| `import` | Bare or wrapped Draw.io | Preserve semantic and unknown XML information in validated IR v2; requires YAML/JSON `--output` |
| `edit` | Diagram IR v2 | Apply one atomic stable-ID batch from `--spec`; supports `--dry-run` and Draw.io/YAML/JSON output |
| `views` | Diagram IR v2 | Project linked `executive`, `system`, `deployment`, `dataflow`, and `security` pages |
| `query` | Diagram IR v2 | Filter by `--kind`/`--property`, or find a deterministic path with `--from` and `--to` |
| `test` | Diagram IR v2 | Execute built-in architecture policy rules; `--strict` treats warnings as failures |
| `what-if` | Diagram IR v2 | Calculate outgoing impact from `--fail`, honoring failure-isolating edges |
| `sync` | Base/manual/incoming IR | Three-way synchronization with explicit `--prune` and `--dry-run` |
| `story` | Diagram IR v2 | Write self-contained accessible offline HTML; optional `--fail` overlay |
| `doctor` | Local environment | Report optional backend availability without launching processes or requiring a model file |
#### Inventory
| Action | Scope | Description |
+25 -5
View File
@@ -89,13 +89,20 @@ task build # tsc → dist/
src/
├── cli/
│ └── commands.ts # parseArgs dispatcher → dynamic action imports
├── authoring/
│ └── ir-to-drawio.ts # deterministic IR v1/v2 serializer + preservation envelopes
├── model/
│ └── diagram-ir.ts # canonical types, normalization, runtime validation
├── services/
│ ├── drawio-parser/
│ │ ├── parser.ts # parseAllPages() / parseDiagram() — Shape, Edge, ParsedPage
│ │ └── page-summary.ts # buildPageSummary() — shared per-page serialisation helper
│ └── hierarchy-builder/
│ └── hierarchy-builder.ts # buildHierarchy() — shared BFS depth map + containment tree
│ ├── hierarchy-builder/
│ │ └── hierarchy-builder.ts # buildHierarchy() — shared BFS depth map + containment tree
│ └── semantic-lifecycle/ # import, edit, views/query/policy/what-if, sync, story, atomic I/O
└── actions/
├── build|import|edit|views|query|test|what-if|sync|story|doctor/
│ # authoring and semantic lifecycle actions
├── summary/ # all pages inventory
├── page-summary/ # single page inventory (uses --page)
├── page-hierarchy/ # containment tree from parentId
@@ -109,7 +116,7 @@ src/
└── validate/ # MANDATORY final gate — XML well-formedness + maxGraph compile + sanity check
```
Each action exports `run(filePath: string, pageIndex?: number): Record<string, unknown>`.
Each action exports `run(filePath, pageIndex?, outputPath?, options?): Record<string, unknown>`. Analysis actions may ignore lifecycle-only parameters. File-producing actions must require `--output` and use the shared atomic writer.
### Key modules
@@ -129,7 +136,9 @@ Each action exports `run(filePath: string, pageIndex?: number): Record<string, u
**`src/cli/commands.ts`**
- `parseArgs` dispatcher → dynamic imports of action modules
- Supports `--file` / `-f`, `--action` / `-a`, `--page` / `-p` (0-based, default 0), `--help` / `-h`
- Common options: `--file` / `-f`, `--action` / `-a`, `--page` / `-p`, `--output` / `-o`, `--help` / `-h`
- Lifecycle options: `--spec`, `--base`, `--strict`, `--prune`, `--dry-run`, `--fail`, `--views`, `--kind`, repeatable `--property`, `--from`, and `--to`
- `doctor` is the only action that does not require `--file`
- All output serialised to YAML on stdout; exit `0` success, `1` error
---
@@ -162,6 +171,7 @@ Actions follow `{object}-{action}` naming:
```typescript
const ACTIONS: Record<string, () => Promise<ActionModule>> = {
"build": () => import("../actions/build/action.js"),
"summary": () => import("../actions/summary/action.js"),
"page-summary": () => import("../actions/page-summary/action.js"),
"page-hierarchy": () => import("../actions/page-hierarchy/action.js"),
@@ -173,6 +183,16 @@ const ACTIONS: Record<string, () => Promise<ActionModule>> = {
"page-recommendations": () => import("../actions/page-recommendations/action.js"),
"page-hierarchy-full": () => import("../actions/page-hierarchy-full/action.js"),
"page-negative-space-summary":() => import("../actions/page-negative-space-summary/action.js"),
"validate": () => import("../actions/validate/action.js"),
"validate": () => import("../actions/validate/action.js"),
"quality": () => import("../actions/quality/action.js"),
"import": () => import("../actions/import/action.js"),
"edit": () => import("../actions/edit/action.js"),
"views": () => import("../actions/views/action.js"),
"query": () => import("../actions/query/action.js"),
"test": () => import("../actions/test/action.js"),
"what-if": () => import("../actions/what-if/action.js"),
"sync": () => import("../actions/sync/action.js"),
"story": () => import("../actions/story/action.js"),
"doctor": () => import("../actions/doctor/action.js"),
};
```
+118
View File
@@ -0,0 +1,118 @@
# Semantic lifecycle workflows
`drawio-tools` keeps editable Draw.io XML and semantic Diagram IR v2 synchronized without requiring Draw.io Desktop, a browser, network access, Python, Graphviz, or ELK.
All examples run from `scripts/` after `pnpm install --frozen-lockfile` and `task build`. Results are YAML on standard output. Commands that write files require an explicit `--output` and use an atomic temporary-file rename. Input files cannot be used as output aliases.
## Import and round trip
```bash
node dist/cli/commands.js --action import \
--file architecture.drawio \
--output architecture.yaml
node dist/cli/commands.js --action build \
--file architecture.yaml \
--output rebuilt.drawio
```
Import accepts bare `mxGraphModel` documents and multi-page `mxfile` wrappers. It preserves stable cell IDs, labels (including deliberately empty labels), parent relationships, styles, geometry, waypoints, semantic metadata, wrapper/page/model attributes, and unknown XML children in `$drawio` preservation envelopes. Dangling endpoints and ambiguous identities fail closed. Import output must use `.yaml`, `.yml`, or `.json`.
## Transactional editing
```bash
node dist/cli/commands.js --action edit \
--file architecture.yaml \
--spec ../examples/edit-batch.yaml \
--dry-run
node dist/cli/commands.js --action edit \
--file architecture.yaml \
--spec ../examples/edit-batch.yaml \
--output edited.drawio
```
A batch targets one page and supports typed `add`, `update`, `move`, `delete`, and `connect` operations. Supported preconditions are `exists`, `not-exists`, and `property-equals`, each expressed with a `type` and `id`. Unknown precondition or operation types, update payloads that attempt to change `id`, duplicate identifiers, invalid parents/endpoints, and non-cascading deletion of referenced elements fail closed and abort the entire batch. Dry runs return a preview and write nothing.
## Linked audience views
```bash
node dist/cli/commands.js --action views \
--file architecture.yaml \
--views executive,system,deployment,dataflow,security \
--output linked-views.drawio
```
Views preserve source model identifiers and provenance. A sparse view records an explicit fallback reason and modeling hint rather than pretending complete coverage. Unknown view names fail closed.
## Query and paths
```bash
node dist/cli/commands.js --action query --file architecture.yaml --kind service
node dist/cli/commands.js --action query --file architecture.yaml \
--property owner=platform --property 'production=true'
node dist/cli/commands.js --action query --file architecture.yaml \
--from client --to database
```
Queries filter by semantic kind and exact properties. `--from` plus `--to` returns a deterministic shortest directed path. Semantic operations require globally unambiguous node IDs across pages.
## Architecture policy tests
```bash
node dist/cli/commands.js --action test --file architecture.yaml
node dist/cli/commands.js --action test --file architecture.yaml \
--spec ../examples/policy-rules.yaml --strict
```
Built-in policy identifiers:
- `no-direct-internet-to-database`
- `no-cycles`
- `no-orphans`
- `every-service-has-owner`
- `production-has-observability`
- `external-dependencies-have-timeouts`
- `trust-boundaries-use-protocol`
Errors always fail. With `--strict`, warnings also fail. Unknown policy identifiers fail closed.
## Failure what-if analysis
```bash
node dist/cli/commands.js --action what-if \
--file architecture.yaml --fail api
```
The simulation follows outgoing dependencies and stops propagation at an edge with `properties.isolates_failure: true`. It never mutates the source model.
## Three-way synchronization
```bash
node dist/cli/commands.js --action sync \
--base generated-before.yaml \
--file manually-edited.yaml \
--spec generated-after.yaml \
--output synchronized.drawio
```
The base is the previous generated model, `--file` is the manually edited model, and `--spec` is the newly generated model. Synchronization preserves manual geometry/style, adds incoming identities, and reports semantic conflicts. Removed elements and pages are retained with lifecycle metadata by default; use `--prune` to remove them explicitly. Add `--dry-run` to return a preview without writing.
## Offline story
```bash
node dist/cli/commands.js --action story \
--file architecture.yaml \
--fail api \
--output architecture-story.html
```
The output is one self-contained HTML file with a restrictive Content Security Policy, escaped labels, keyboard navigation, a complete text alternative, provenance, and an optional what-if overlay. It makes no external requests and uses square corners.
## Doctor
```bash
node dist/cli/commands.js --action doctor
```
Doctor reports availability of optional local backends without launching them and confirms that the native core does not require network access.