Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
51741f8
docs(sysprom): record monorepo and web viewer decision (DEC52)
Mearman Jun 24, 2026
c6474b3
build(workspace): add pnpm workspace and @sysprom/core package
Mearman Jun 24, 2026
432b813
refactor(core): extract browser-safe pure library into @sysprom/core
Mearman Jun 24, 2026
1a400b1
test(core): relocate pure-logic tests to @sysprom/core
Mearman Jun 24, 2026
49ffc30
build(sysprom): bundle @sysprom/core into the published package via t…
Mearman Jun 24, 2026
6ad4cb9
feat(web): scaffold Vite + React + vanilla-extract viewer
Mearman Jun 24, 2026
c884b81
feat(web): add vanilla-extract theme and global styles
Mearman Jun 24, 2026
dc9c162
feat(web): load and validate SysProM documents
Mearman Jun 24, 2026
c5a770a
feat(web): render stats, nodes, relationships panels
Mearman Jun 24, 2026
bd2c30b
feat(web): render Mermaid graphs and trace
Mearman Jun 24, 2026
299a9de
ci(pages): deploy the web viewer at root with TypeDoc docs at /docs
Mearman Jun 24, 2026
213fa45
fix(core): declare SysProMDocument and Node as named recursive types …
Mearman Jun 24, 2026
4873857
test(core): add regression test for recursive schema declaration elision
Mearman Jun 24, 2026
c30b418
feat(web): interactive Cytoscape graph with ELK, fcose, and trace lay…
Mearman Jun 24, 2026
d555a5c
fix(web): use literal theme tokens in Cytoscape canvas stylesheet
Mearman Jun 24, 2026
4f0be18
perf(web): lazy-load mermaid and elkjs to shrink the initial bundle
Mearman Jun 24, 2026
3edb724
fix(web): declare @vanilla-extract/css as a direct dependency
Mearman Jun 24, 2026
abea68c
feat(web): add refinement, emergent, and subsystem graph layouts
Mearman Jun 24, 2026
43b5c8a
fix(web): include governance edges in emergent topology so nodes clus…
Mearman Jun 24, 2026
126faf7
fix(web): stop node selection re-running non-trace graph layouts
Mearman Jun 24, 2026
6a84e9e
fix(web): place orphan nodes around the periphery instead of a grid s…
Mearman Jun 24, 2026
f3ec57d
test(web): cover orphan-placement pure functions
Mearman Jun 24, 2026
f056546
fix(web): group orphan nodes by type around the periphery with labels
Mearman Jun 24, 2026
a50e18c
test(web): cover type-grouped orphan placement and clearance invariant
Mearman Jun 24, 2026
9dfad38
feat(web): add ELK layered layout with orthogonal edge routing
Mearman Jun 25, 2026
2e9b1f1
fix(web): resolve ELK bundled UMD module under Vite ESM interop
Mearman Jun 25, 2026
f83a437
fix(web): apply ELK node positions by id, not via the preset callback
Mearman Jun 25, 2026
b638e1b
feat(web): add countEdgeCrossings topology metric
Mearman Jun 25, 2026
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
55 changes: 55 additions & 0 deletions .SysProM.json
Original file line number Diff line number Diff line change
Expand Up @@ -4094,6 +4094,51 @@
"src/operations/graph-shared.ts,src/operations/graph.ts,src/json-to-md.ts,src/cli/commands/graph.ts,src/cli/commands/json2md.ts"
],
"type": "change"
},
{
"context": "We want a GitHub Pages-hosted web app for visualising SysProM documents. The single sysprom package cannot be imported by a browser app because the library modules call node:fs, node:path, and node:crypto directly (io.ts, sync.ts, the multi-doc conversions, three speckit ops), and the operations barrel re-exports syncDocumentsOp, dragging node:fs into every barrel import.",
"id": "DEC52",
"name": "Adopt pnpm workspace monorepo with a browser-safe core and a Vite viewer",
"options": [
{
"description": "Convert to a pnpm workspace; extract a browser-safe @sysprom/core; keep filesystem access in @sysprom/node; preserve the sysprom package via a cli re-export; add an unpublished Vite viewer",
"id": "MONO"
},
{
"description": "Keep sysprom as-is; build the viewer in a second repo that imports sysprom from npm",
"id": "SEPARATE"
},
{
"description": "Add the viewer as a subpath of the current single package with node:fs polyfills for the browser",
"id": "SINGLE"
}
],
"rationale": "A workspace lets the viewer consume core source directly via workspace links with no npm publish lag, and forces the filesystem isolation the library already needs so domain logic is isomorphic behind a storage boundary. A separate repo adds a publish lag for every core change the UI needs. A single-package browser entry needs node:fs and node:path polyfills rather than fixing the boundary. pnpm and Turbo are already in use, so the workspace conversion cost is low; all packages ship together, so unified versioning applies.",
"selected": "MONO",
"type": "decision"
},
{
"id": "CHG50",
"lifecycle": {
"introduced": true
},
"name": "Convert to monorepo; extract browser-safe core; add Vite viewer",
"scope": [
"packages/core",
"packages/node",
"packages/cli",
"packages/mcp",
"packages/web",
"build",
"ci"
],
"type": "change"
},
{
"description": "The published sysprom package contract is preserved across the monorepo split: the sysprom, spm, and sysprom-mcp CLI binaries continue to install and run, and every name currently importable from sysprom remains exported with its existing signature. Existing Node consumers see no change.",
"id": "INV33",
"name": "Published Package Contract Stability",
"type": "invariant"
}
],
"relationships": [
Expand Down Expand Up @@ -5266,6 +5311,16 @@
"from": "CHG49",
"to": "DEC51",
"type": "implements"
},
{
"from": "CHG50",
"to": "DEC52",
"type": "implements"
},
{
"from": "DEC52",
"to": "INV33",
"type": "must_preserve"
}
]
}
17 changes: 17 additions & 0 deletions .SysProM/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -826,3 +826,20 @@ Scope:
Scope:
- src/operations/graph-shared.ts,src/operations/graph.ts,src/json-to-md.ts,src/cli/commands/graph.ts,src/cli/commands/json2md.ts

### CHG50 — Convert to monorepo; extract browser-safe core; add Vite viewer

- Implements: [DEC52](./DECISIONS.md#dec52--adopt-pnpm-workspace-monorepo-with-a-browser-safe-core-and-a-vite-viewer)

Scope:
- packages/core
- packages/node
- packages/cli
- packages/mcp
- packages/web
- build
- ci

#### Lifecycle

- [x] introduced

15 changes: 15 additions & 0 deletions .SysProM/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1006,3 +1006,18 @@ Chosen: OPT-A

Rationale: Anchor-based links provide the best UX for embedded diagrams since clicking navigates directly to the node definition. External reference links serve standalone use cases.

### DEC52 — Adopt pnpm workspace monorepo with a browser-safe core and a Vite viewer

- Must preserve: [INV33](./INVARIANTS.md#inv33--published-package-contract-stability)

Context: We want a GitHub Pages-hosted web app for visualising SysProM documents. The single sysprom package cannot be imported by a browser app because the library modules call node:fs, node:path, and node:crypto directly (io.ts, sync.ts, the multi-doc conversions, three speckit ops), and the operations barrel re-exports syncDocumentsOp, dragging node:fs into every barrel import.

Options:
- MONO: Convert to a pnpm workspace; extract a browser-safe @sysprom/core; keep filesystem access in @sysprom/node; preserve the sysprom package via a cli re-export; add an unpublished Vite viewer
- SEPARATE: Keep sysprom as-is; build the viewer in a second repo that imports sysprom from npm
- SINGLE: Add the viewer as a subpath of the current single package with node:fs polyfills for the browser

Chosen: MONO

Rationale: A workspace lets the viewer consume core source directly via workspace links with no npm publish lag, and forces the filesystem isolation the library already needs so domain logic is isomorphic behind a storage boundary. A separate repo adds a publish lag for every core change the UI needs. A single-package browser entry needs node:fs and node:path polyfills rather than fixing the boundary. pnpm and Turbo are already in use, so the workspace conversion cost is low; all packages ship together, so unified versioning applies.

4 changes: 4 additions & 0 deletions .SysProM/INVARIANTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ Setting a node to status: retired via updateNode must report all active nodes th

When both JSON and Markdown representations of a SysProM document exist, mutations via the CLI must automatically keep them in sync. Users should not need to manually run json2md or md2json after every change.

### INV33 — Published Package Contract Stability

The published sysprom package contract is preserved across the monorepo split: the sysprom, spm, and sysprom-mcp CLI binaries continue to install and run, and every name currently importable from sysprom remains exported with its existing signature. Existing Node consumers see no change.

## Principles

### PRIN1 — Separate What From Why From How
Expand Down
20 changes: 13 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ jobs:
turbo-quality-${{ runner.os }}-
- run: npx turbo run _typecheck _test

docs:
name: Docs
site:
name: Build site
runs-on: ubuntu-latest
needs: quality
steps:
Expand All @@ -47,19 +47,25 @@ jobs:
- uses: actions/cache@v5
with:
path: .turbo
key: turbo-docs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }}
key: turbo-site-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }}
restore-keys: |
turbo-docs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-
turbo-docs-${{ runner.os }}-
turbo-site-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-
turbo-site-${{ runner.os }}-
# Web app at the site root (builds @sysprom/core first via the workspace dep)
- run: npx turbo run build --filter=web
# TypeDoc HTML API docs -> site/
- run: npx turbo run _docs:cli _docs:api:html
# Serve docs under /docs alongside the viewer at /
- name: Place docs under the web build
run: cp -r site packages/web/dist/docs
- uses: actions/upload-pages-artifact@v4
with:
path: site/
path: packages/web/dist

pages:
name: Deploy Pages
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: docs
needs: site
runs-on: ubuntu-latest
environment:
name: github-pages
Expand Down
12 changes: 8 additions & 4 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,11 @@ export default defineConfig(
languageOptions: {
parserOptions: {
projectService: {
allowDefaultProject: ["eslint.config.ts", "scripts/*.ts"],
allowDefaultProject: [
"eslint.config.ts",
"tsdown.config.ts",
"scripts/*.ts",
],
},
tsconfigRootDir: import.meta.dirname,
},
Expand All @@ -256,7 +260,7 @@ export default defineConfig(
},
},
{
files: ["src/**/*.ts"],
files: ["src/**/*.ts", "packages/core/src/**/*.ts"],
rules: {
"@typescript-eslint/no-unnecessary-condition": "off",
"sonarjs/cognitive-complexity": "off",
Expand All @@ -271,7 +275,7 @@ export default defineConfig(
},
},
{
files: ["tests/**/*.ts"],
files: ["tests/**/*.ts", "packages/core/tests/**/*.ts"],
rules: {
"@typescript-eslint/consistent-type-assertions": [
"error",
Expand Down Expand Up @@ -310,7 +314,7 @@ export default defineConfig(
{
ignores: [
".claude/worktrees/**",
"dist/",
"**/dist/",
"node_modules/",
"docs/",
"commitlint.config.ts",
Expand Down
24 changes: 13 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@
],
"packageManager": "pnpm@10.32.1",
"type": "module",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"exports": {
".": {
"types": "./dist/src/index.d.ts",
"import": "./dist/src/index.js"
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs"
}
},
"files": [
"dist/src/**/*.js",
"dist/src/**/*.d.ts",
"dist/**/*.mjs",
"dist/**/*.d.mts",
"schema.json"
],
"bin": {
"sysprom": "dist/src/cli/index.js",
"spm": "dist/src/cli/index.js",
"sysprom-mcp": "dist/src/mcp/index.js"
"sysprom": "dist/cli/index.mjs",
"spm": "dist/cli/index.mjs",
"sysprom-mcp": "dist/mcp/index.mjs"
},
"scripts": {
"build": "turbo run _build",
Expand All @@ -51,7 +51,7 @@
"spm": "tsx src/cli/index.ts",
"_lint": "eslint --cache .",
"_typecheck": "tsc --noEmit",
"_compile": "tsc",
"_compile": "tsdown",
"_schema": "tsx src/generate-schema.ts",
"_test": "tsx --test tests/*.test.ts",
"_test:coverage": "c8 --src src --exclude 'src/cli/**' --exclude 'src/generate-schema.ts' tsx --test tests/*.test.ts",
Expand Down Expand Up @@ -87,6 +87,7 @@
"@semantic-release/changelog": "6.0.3",
"@semantic-release/exec": "7.1.0",
"@semantic-release/git": "10.0.1",
"@sysprom/core": "workspace:*",
"@types/node": "25.5.0",
"c8": "11.0.0",
"conventional-changelog-conventionalcommits": "9.3.0",
Expand All @@ -100,12 +101,13 @@
"lint-staged": "16.4.0",
"prettier": "3.8.1",
"semantic-release": "25.0.3",
"tsdown": "0.22.3",
"tsx": "4.21.0",
"turbo": "2.8.20",
"typedoc": "0.28.18",
"typedoc-plugin-markdown": "4.11.0",
"typedoc-plugin-zod": "1.4.3",
"typescript": "6.0.2",
"typescript": "6.0.3",
"typescript-eslint": "8.58.0"
}
}
42 changes: 42 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@sysprom/core",
"version": "0.0.0",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./operations/index.js": {
"types": "./dist/operations/index.d.ts",
"import": "./dist/operations/index.js"
},
"./operations/graph-shared.js": {
"types": "./dist/operations/graph-shared.d.ts",
"import": "./dist/operations/graph-shared.js"
},
"./speckit/plan.js": {
"types": "./dist/speckit/plan.d.ts",
"import": "./dist/speckit/plan.js"
}
},
"files": [
"dist/**"
],
"scripts": {
"build": "tsc -p tsconfig.build.json",
"typecheck": "tsc --noEmit",
"test": "tsx --test tests/*.test.ts"
},
"dependencies": {
"zod": "4.3.6"
},
"devDependencies": {
"@types/node": "25.5.0",
"tsx": "4.21.0",
"typescript": "6.0.3"
}
}
File renamed without changes.
File renamed without changes.
Loading
Loading