Fix review findings: importer DoS, sync data loss, views/profile rendering, docs

Source importers: lstat before open so FIFOs no longer hang the directory
walk; replace the quadratic Rust macro regex with a linear scan.
Three-way sync: report page add/add and delete-vs-modify as conflicts
instead of silently overwriting or resurrecting; canonical comparison so
key order is not a change; the sync action withholds output and fails on
conflicts unless --force. Story publishing escapes <, >, & and U+2028/9
inside the embedded JSON.
Views: drop parentId of unselected containers, re-layout instead of
manual geometry, validate before serialising; flatten() merges identical
nodes repeated across pages so C4 output works with every analysis
action. Dark theme edge labels get a background; tube-map corridors sit
above the stations; C4 containers keep the swimlane style and orphan
relationships land on the matching page; router keeps container header
bands as obstacles; sequence self-messages loop on one side.
Docs: SKILL.md lists all 23 CLI actions and how each capability family
is invoked, task wrappers for query/test/what-if/doctor, capability
tables and --page scope corrected, maintenance snippet uses the real
synchronous action signature, duplicated rule bullets moved to the rule
references, coverage matrix wording made verifiable.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-09-06 17:01:50 +03:00
co-authored by Claude Code
parent 7610235781
commit 6584df0666
32 changed files with 801 additions and 209 deletions
+23 -8
View File
@@ -36,7 +36,7 @@ The git repository is the source of truth. Install dependencies and build inside
```bash
cd <skill>/scripts
pnpm install
pnpm install --frozen-lockfile
task build # or: pnpm run build
```
@@ -99,7 +99,11 @@ src/
│ │ └── page-summary.ts # buildPageSummary() — shared per-page serialisation helper
│ ├── 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
│ ├── semantic-lifecycle/ # import, edit, views/query/policy/what-if, sync, story, atomic I/O
│ ├── layout/, authoring-router/ # deterministic layout + obstacle-aware routing used by build (ir-to-drawio)
│ ├── connector-router/ # edge path reconstruction used by page-connectors-summary/-validation
│ ├── maxgraph-loader/ # jsdom polyfill + maxGraph state loader (used by validate and negative-space)
│ └── source-importers/, transforms/, themes/, shape-catalog/, profiles/ # library-only services (no CLI action)
└── actions/
├── build|import|edit|views|query|test|what-if|sync|story|doctor/
│ # authoring and semantic lifecycle actions
@@ -113,6 +117,8 @@ src/
├── page-orphans/ # isolated shapes + dangling connectors
├── page-recommendations/ # page size recommendation
├── page-hierarchy-full/ # nesting levels with full shape geometry (x, y, width, height)
├── page-negative-space-summary/ # free horizontal corridors per nesting level (bbox + text-aware)
├── quality/ # all-pages clipping/overflow/placeholder/palette/density checks with severity
└── validate/ # MANDATORY final gate — XML well-formedness + maxGraph compile + sanity check
```
@@ -145,25 +151,34 @@ Each action exports `run(filePath, pageIndex?, outputPath?, options?): Record<st
## Adding a new action
1. Create `src/actions/<name>/action.ts` exporting:
1. Create `src/actions/<name>/action.ts` exporting a **synchronous** `run` (the dispatcher in `commands.ts` reads `result.summary` / `result.failed` without `await`, so an `async` function or a returned `Promise` would break exit-code handling). Copy the signature from an existing action, e.g. `src/actions/page-hierarchy/action.ts`:
```ts
export async function run(filePath: string, pageIndex?: number): Promise<Record<string, unknown>>
export function run(filePath: string, pageIndex: number = 0): Record<string, unknown> {
const pages = parseAllPages(filePath);
const page = pages[pageIndex];
if (!page) {
return { error: true, message: `Page index ${pageIndex} not found` };
}
// ...
return { action: "<name>", /* ... */ };
}
```
The full `ActionModule` contract is `run(filePath: string, pageIndex?: number, outputPath?: string, options?: LifecycleActionOptions): Record<string, unknown>`; declare only the parameters you use. Set `failed: true` in the result to make the CLI exit with code `1`.
2. Register it in `src/cli/commands.ts` under `ACTIONS`:
```ts
"my-action": () => import("../actions/my-action/action.js"),
```
3. Use `parseDiagram(filePath)` (first page) or `parseAllPages(filePath)` (all pages) from the parser
3. Use `parseAllPages(filePath)[pageIndex]` to honour `--page`, or `parseDiagram(filePath)` when the action is deliberately page-0-only; `parseAllPages` for all-page actions. Document the choice in the Scope column of `capabilities.md`
4. Optionally import `buildPageSummary(page)` from `page-summary.ts` for standard shape/edge serialisation
5. Return a plain object — the CLI serialises it to YAML automatically
6. Run `pnpm run build` to compile and verify no TypeScript errors
6. Run `task build` (or `pnpm run build`) to compile and verify no TypeScript errors, then add the action to `capabilities.md`, the `Actions:` list in `commands.ts --help`, and the Taskfile descriptions
### Naming convention
Actions follow `{object}-{action}` naming:
- `page-*` — operates on a single diagram page (uses `--page`, default 0)
- `summary` — operates on all pages
- `page-*` — operates on a single diagram page (`page-summary`, `page-hierarchy`, `page-hierarchy-full`, `page-negative-space-summary` honour `--page`; the remaining `page-*` actions currently analyse page 0 only)
- `summary`, `validate`, `quality` — operate on all pages
---