@ddtcorex/dsh-maestro-diagram 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -0
- package/cordis.patch.yml +5 -0
- package/lib/drift.d.ts +18 -0
- package/lib/drift.d.ts.map +1 -0
- package/lib/drift.js +312 -0
- package/lib/drift.js.map +1 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +33 -0
- package/lib/index.js.map +1 -0
- package/lib/verify.d.ts +12 -0
- package/lib/verify.d.ts.map +1 -0
- package/lib/verify.js +117 -0
- package/lib/verify.js.map +1 -0
- package/package.json +44 -0
- package/src/host/drift.ts +295 -0
- package/src/host/index.ts +43 -0
- package/src/host/verify.ts +121 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# dsh-maestro-diagram — Maestro Diagram Studio (Host-only)
|
|
2
|
+
|
|
3
|
+
Hybrid **skill + plugin** for SA-grade diagrams: **GitHub-native Mermaid** (single source of truth) + **editorial HTML+SVG** (self-contained, no JS) + **deterministic verify/drift** (tool calls > LLM).
|
|
4
|
+
|
|
5
|
+
- **Skill:** `maestro-skills/skills/diagram-studio/SKILL.md` (rank 350) — teaches the agent when/how to draw, selects among 5 Mermaid types, enforces editorial discipline (density 4/10, accent 1-2), and decides audience (Team vs Client).
|
|
6
|
+
- **Plugin:** `packages/dsh-maestro-diagram` (this package, Host-only, `rootDir: src/host`, `inject: ['sessions','tools']`) — exposes `mermaid_verify` and `mermaid_drift` as reversible `ctx.tools.register` effects. No Client bundle on v1.
|
|
7
|
+
|
|
8
|
+
Inspired by `cathrynlavery/diagram-design` (MIT, 39 editorial types, tokens paper/ink/accent) + `diagram-drift` + `dsh-mermaid`. Default output stays GitHub Mermaid (per "render được trên github là ok"); HTML editorial is an explicit `export: html` when `audience: client`.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
# As DSH plugin (Host-only, no restart of skill provider needed, but Host needs reload for tools)
|
|
14
|
+
dsh plugin add @ddtcorex/dsh-maestro-diagram
|
|
15
|
+
# Or via the thin meta-bundle (opt-in until proven, not in meta v2 yet)
|
|
16
|
+
dsh plugin add @ddtcorex/dsh-maestro-meta
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For skill-only use (no Host tools): `git pull` + `pnpm --dir maestro-skills run build` — then `node maestro-skills/skills/diagram-studio/scripts/verify-mermaid.mjs <file>`.
|
|
20
|
+
|
|
21
|
+
## Tools
|
|
22
|
+
|
|
23
|
+
### `mermaid_verify`
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
input: string (Mermaid source or file content when isPath=true)
|
|
27
|
+
isPath: boolean? (if true, input is treated as file path, extracts ```mermaid blocks)
|
|
28
|
+
strict: boolean? (if true, warns on anti-patterns: shadow, graph legacy, rounded-2xl)
|
|
29
|
+
→ { ok: boolean, errors: {line,col,msg}[], warnings: {msg}[] }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Deterministic `mermaid.parse()` + optional `mermaid-cli` validate. Never throws — returns `isError` shape for the agent to fix.
|
|
33
|
+
|
|
34
|
+
### `mermaid_drift`
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
diagramPath: string (e.g. docs/architecture.md)
|
|
38
|
+
codeRoots: string[]? (default ["packages/*","govard","maestro-skills"])
|
|
39
|
+
→ { missingInCode: string[], staleEdges: {from,to}[], missingInDiagram: string[], summary: string }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Parses Mermaid nodes/edges vs scans `codeRoots` (package.json names, `govard/internal/*`, skills). Inspired by `diagram-drift` — flags `missingInCode / staleEdges / missingInDiagram` before PR.
|
|
43
|
+
|
|
44
|
+
## Supported Cases (all verified — see SKILL.md § Supported Cases)
|
|
45
|
+
|
|
46
|
+
**5 diagram types** (mapped from 39 editorial types):
|
|
47
|
+
- `flowchart TB/LR` — Components + connections (architecture) — `docs/architecture.md §1.1`
|
|
48
|
+
- `sequenceDiagram` — Messages over time (turn lifecycle) — `docs/specs/2026-08-27-harness-turn-flow-sequence.md`
|
|
49
|
+
- `classDiagram` — Classes + ops (`ReviewProvider <|-- GitLabProvider`)
|
|
50
|
+
- `erDiagram` — Entities + fields (`PROJECT ||--o{ MEMORY`)
|
|
51
|
+
- `stateDiagram` / `stateDiagram-v2` — States + guards (`[*] --> queued`)
|
|
52
|
+
|
|
53
|
+
All share tokens `paper #f5f5f5 / ink #2d3142 / accent #eb6c36 / muted #8a94a6 / link #4a90e2` from `references/style-guide.md` (`classDef focal/muted`, no shadow, rx:6).
|
|
54
|
+
|
|
55
|
+
**2 audiences** (controls 4 elements per HTML):
|
|
56
|
+
- **Team / Internal** (`team|internal|engineering` or "cho team") → HTML has both: inline SVG + `<details><summary>Mermaid source</summary><pre>` collapsed + Editorial tokens card + About footer with verify/drift. Example: `harness-architecture.html` 16K (1 svg,1 pre), `harness-turn-flow-sequence.html` 31K.
|
|
57
|
+
- **Client / External** (`client|pitch|deck` or "cho khách") → HTML has only SVG, no `<pre>`, no tokens card, footer reduced to `Generated via diagram-studio — 2026-08-27`. Example: `...-client.html` 12K/30K (1 svg,0 pre), PNG 124K/65K.
|
|
58
|
+
|
|
59
|
+
**3 outputs:**
|
|
60
|
+
- GitHub-native Mermaid in `docs/architecture.md` / `docs/specs/*-design.md` (always show source)
|
|
61
|
+
- Editorial HTML `docs/diagrams/<slug>.html` (self-contained inline SVG/CSS, no JS) — Team vs Client per table
|
|
62
|
+
- Deck PDF `docs/diagrams/maestro-harness-deck.pdf` (A4 landscape, 3 pages) — always Client rules, PNG only
|
|
63
|
+
|
|
64
|
+
**3 verification cases:**
|
|
65
|
+
- Parse ok → 5/5 PASS
|
|
66
|
+
- Parse fail (empty, `A-->`) → `ok:false, line:2`
|
|
67
|
+
- Anti-pattern strict (`shadow:true`) → `warnings:1`
|
|
68
|
+
- Drift `missingInCode 0` / missing file throws `ENOENT`
|
|
69
|
+
|
|
70
|
+
**2 live case studies on this harness:**
|
|
71
|
+
- Architecture flowchart (10 plugins + meta) — `harness-architecture.html` 16K → `...-client.html` 12K — PNG 238K→124K — Deck p1
|
|
72
|
+
- Turn flow sequence (6 participants) — `harness-turn-flow-sequence.html` 31K (svg 28K via `mermaid-cli 11.16.0`) → `...-client.html` 30K — PNG 100K→65K + `...-rendered.png` 20K — Deck p2
|
|
73
|
+
|
|
74
|
+
All above are live-verified: `packages/dsh-maestro-diagram` 8/8, `maestro-workspace -r verify` 13 packages Done, chrome headless 980×1400 screenshots, `pdfinfo Pages:3`.
|
|
75
|
+
|
|
76
|
+
## Usage (agent)
|
|
77
|
+
|
|
78
|
+
1. Load `diagram-studio` skill — it selects `flowchart` vs `sequenceDiagram` vs `classDiagram` vs `erDiagram` vs `stateDiagram` by semantic pattern.
|
|
79
|
+
2. State `type, size (85%/100%), what will be cut due to budget (density 4/10)` and wait for redirect (confirm-before-drawing).
|
|
80
|
+
3. Write Mermaid to `docs/specs/*-design.md` (or `docs/architecture.md §1.1`), call `mermaid_verify` — fix until `ok:true`.
|
|
81
|
+
4. If `audience: client`, also render editorial HTML: `mermaid-cli -i <src>.mmd -o <slug>.svg` → embed inline SVG into `docs/diagrams/<slug>.html` per audience table, then `chrome --screenshot` → PNG and `--print-to-pdf` → deck. Hide source/tokens/footer for client.
|
|
82
|
+
5. Before PR, run `mermaid_drift --diagram docs/architecture.md --roots packages/*,govard,maestro-skills` — fix `missingInCode` by patching doc or code.
|
|
83
|
+
|
|
84
|
+
CLI fallback (no plugin): `node maestro-skills/skills/diagram-studio/scripts/verify-mermaid.mjs docs/architecture.md`
|
|
85
|
+
|
|
86
|
+
## Build & Verify
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
pnpm --dir packages/dsh-maestro-diagram run build # must create lib/index.js flat (rootDir: src/host)
|
|
90
|
+
pnpm --dir packages/dsh-maestro-diagram run test # 8/8 (5 verify + 3 drift)
|
|
91
|
+
pnpm --dir packages/dsh-maestro-diagram run verify # tsc --noEmit
|
|
92
|
+
pnpm --dir maestro-skills run build # skill provider
|
|
93
|
+
pnpm --dir maestro-workspace -r verify # 13 packages Done
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`lib/index.js` flat is required — `test -f lib/index.js` must be `0` (not `lib/host/index.js`), otherwise `dsh web` boot fails `ERR_MODULE_NOT_FOUND`. Dry-boot on ephemeral port before any real restart.
|
|
97
|
+
|
|
98
|
+
## Publishing
|
|
99
|
+
|
|
100
|
+
This package is public (`private:false`, `workspace:^` deps). Publish with `pnpm publish --access public` only — never `npm publish` (would leave `workspace:` in tarball). See `maestro-skills/README.md` for skill distribution and `docs/specs/2026-08-27-diagram-studio-design.md` for design.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT — see `LICENSE`. Editorial tokens/style-guide trimmed from `cathrynlavery/diagram-design` (MIT) with attribution in `maestro-skills/skills/diagram-studio/references/diagram-design-learnings.md`.
|
package/cordis.patch.yml
ADDED
package/lib/drift.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function parseMermaid(src: string): {
|
|
2
|
+
nodes: string[];
|
|
3
|
+
edges: {
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
}[];
|
|
7
|
+
};
|
|
8
|
+
export declare function scanCode(roots: string[]): Promise<string[]>;
|
|
9
|
+
export declare function mermaidDrift(diagramPath: string, codeRoots?: string[]): Promise<{
|
|
10
|
+
missingInCode: string[];
|
|
11
|
+
staleEdges: {
|
|
12
|
+
from: string;
|
|
13
|
+
to: string;
|
|
14
|
+
}[];
|
|
15
|
+
missingInDiagram: string[];
|
|
16
|
+
summary: string;
|
|
17
|
+
}>;
|
|
18
|
+
//# sourceMappingURL=drift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift.d.ts","sourceRoot":"","sources":["../src/host/drift.ts"],"names":[],"mappings":"AAGA,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAoDpG;AAwBD,wBAAsB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA4IjE;AAED,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC;IAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAsE/H"}
|
package/lib/drift.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export function parseMermaid(src) {
|
|
4
|
+
const blocks = [...src.matchAll(/```mermaid\n([\s\S]*?)```/g)].map((m) => m[1]);
|
|
5
|
+
const content = blocks.length ? blocks.join('\n') : src;
|
|
6
|
+
const nodesSet = new Set();
|
|
7
|
+
const edges = [];
|
|
8
|
+
// Collect subgraph ids to exclude them from node set (they are layout containers, not code symbols)
|
|
9
|
+
const subgraphIds = new Set();
|
|
10
|
+
const subgraphRe = /subgraph\s+(\w+)/g;
|
|
11
|
+
let sm;
|
|
12
|
+
while ((sm = subgraphRe.exec(content)) !== null) {
|
|
13
|
+
subgraphIds.add(sm[1]);
|
|
14
|
+
}
|
|
15
|
+
const bracketRe = /(\w+)\s*\["/g;
|
|
16
|
+
let m;
|
|
17
|
+
while ((m = bracketRe.exec(content)) !== null) {
|
|
18
|
+
const id = m[1];
|
|
19
|
+
if (subgraphIds.has(id))
|
|
20
|
+
continue; // skip subgraph container ids (Core, OpsStudio, etc.)
|
|
21
|
+
if (/^pad/i.test(id))
|
|
22
|
+
continue; // spacer nodes for header top gap — not code symbols
|
|
23
|
+
if (/\bspacer\b/i.test(content.slice(m.index, m.index + 200))) {
|
|
24
|
+
// check if this node is defined with :::spacer class nearby — skip spacer nodes
|
|
25
|
+
const snippet = content.slice(m.index, m.index + 300);
|
|
26
|
+
if (/:::spacer/.test(snippet))
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
nodesSet.add(id);
|
|
30
|
+
}
|
|
31
|
+
const edgeRe = /(\w+)\s*(?:-->|---|-\.->|--->)\s*(\w+)/g;
|
|
32
|
+
while ((m = edgeRe.exec(content)) !== null) {
|
|
33
|
+
const from = m[1];
|
|
34
|
+
const to = m[2];
|
|
35
|
+
if (!subgraphIds.has(from) && !/^pad/i.test(from))
|
|
36
|
+
nodesSet.add(from);
|
|
37
|
+
if (!subgraphIds.has(to) && !/^pad/i.test(to))
|
|
38
|
+
nodesSet.add(to);
|
|
39
|
+
edges.push({ from, to });
|
|
40
|
+
}
|
|
41
|
+
// fallback generic arrow capture for nodes not yet caught (keep simple heuristic)
|
|
42
|
+
const leftArrowRe = /(\w+)\s*-->/g;
|
|
43
|
+
while ((m = leftArrowRe.exec(content)) !== null) {
|
|
44
|
+
if (!subgraphIds.has(m[1]) && !/^pad/i.test(m[1]))
|
|
45
|
+
nodesSet.add(m[1]);
|
|
46
|
+
}
|
|
47
|
+
const rightArrowRe = /-->\s*(\w+)/g;
|
|
48
|
+
while ((m = rightArrowRe.exec(content)) !== null) {
|
|
49
|
+
if (!subgraphIds.has(m[1]) && !/^pad/i.test(m[1]))
|
|
50
|
+
nodesSet.add(m[1]);
|
|
51
|
+
}
|
|
52
|
+
// also handle -- and -.-> generically if not already captured
|
|
53
|
+
const genericEdgeRe = /(\w+)\s*(?:-->|---|-\.->)\s*(\w+)/g;
|
|
54
|
+
// already covered, but keep for completeness
|
|
55
|
+
return { nodes: [...nodesSet], edges };
|
|
56
|
+
}
|
|
57
|
+
function resolvePath(p) {
|
|
58
|
+
if (path.isAbsolute(p)) {
|
|
59
|
+
try {
|
|
60
|
+
if (fs.existsSync(p))
|
|
61
|
+
return p;
|
|
62
|
+
}
|
|
63
|
+
catch { }
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const candidates = [];
|
|
67
|
+
candidates.push(path.resolve(process.cwd(), p));
|
|
68
|
+
let dir = process.cwd();
|
|
69
|
+
for (let i = 0; i < 5; i++) {
|
|
70
|
+
dir = path.dirname(dir);
|
|
71
|
+
candidates.push(path.join(dir, p));
|
|
72
|
+
}
|
|
73
|
+
for (const c of candidates) {
|
|
74
|
+
try {
|
|
75
|
+
if (fs.existsSync(c))
|
|
76
|
+
return c;
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
export async function scanCode(roots) {
|
|
83
|
+
const symbols = [];
|
|
84
|
+
const seen = new Set();
|
|
85
|
+
function add(sym) {
|
|
86
|
+
if (!sym)
|
|
87
|
+
return;
|
|
88
|
+
const lower = sym.toLowerCase();
|
|
89
|
+
if (!seen.has(lower)) {
|
|
90
|
+
seen.add(lower);
|
|
91
|
+
symbols.push(lower);
|
|
92
|
+
}
|
|
93
|
+
// also add stripped variant without prefix
|
|
94
|
+
const stripped = lower.replace(/^@ddtcorex\//, '').replace(/^dsh-maestro-/, '');
|
|
95
|
+
if (stripped !== lower && !seen.has(stripped)) {
|
|
96
|
+
seen.add(stripped);
|
|
97
|
+
symbols.push(stripped);
|
|
98
|
+
}
|
|
99
|
+
// also normalized without hyphen/underscore variant is handled in drift comparison, but add raw
|
|
100
|
+
}
|
|
101
|
+
for (const root of roots) {
|
|
102
|
+
if (root.includes('*')) {
|
|
103
|
+
const base = root.replace(/\/\*.*$/, '').replace(/\*.*$/, '');
|
|
104
|
+
const resolvedBase = resolvePath(base) || path.resolve(process.cwd(), base);
|
|
105
|
+
let entries = [];
|
|
106
|
+
try {
|
|
107
|
+
entries = fs.readdirSync(resolvedBase);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
if (entry.startsWith('.'))
|
|
114
|
+
continue;
|
|
115
|
+
if (entry === 'node_modules')
|
|
116
|
+
continue;
|
|
117
|
+
const full = path.join(resolvedBase, entry);
|
|
118
|
+
try {
|
|
119
|
+
const stat = fs.statSync(full);
|
|
120
|
+
if (!stat.isDirectory())
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const pkgPath = path.join(full, 'package.json');
|
|
127
|
+
try {
|
|
128
|
+
const data = fs.readFileSync(pkgPath, 'utf-8');
|
|
129
|
+
const pkg = JSON.parse(data);
|
|
130
|
+
if (pkg.name) {
|
|
131
|
+
add(pkg.name);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
add(entry);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
add(entry);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// special: if base is packages, also consider sibling meta package at workspace root
|
|
142
|
+
if (base === 'packages' || base.endsWith('/packages') || base === '../packages') {
|
|
143
|
+
// try to find dsh-maestro-meta as sibling
|
|
144
|
+
const metaCandidates = [
|
|
145
|
+
path.join(path.dirname(resolvedBase), 'dsh-maestro-meta', 'package.json'),
|
|
146
|
+
path.resolve(process.cwd(), 'dsh-maestro-meta', 'package.json'),
|
|
147
|
+
path.join(path.dirname(resolvedBase), '..', 'dsh-maestro-meta', 'package.json'),
|
|
148
|
+
];
|
|
149
|
+
for (const mp of metaCandidates) {
|
|
150
|
+
try {
|
|
151
|
+
const data = fs.readFileSync(mp, 'utf-8');
|
|
152
|
+
const pkg = JSON.parse(data);
|
|
153
|
+
if (pkg.name)
|
|
154
|
+
add(pkg.name);
|
|
155
|
+
// also add 'meta' explicitly
|
|
156
|
+
add('meta');
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
catch { }
|
|
160
|
+
}
|
|
161
|
+
// ensure meta is present even if not found via package.json
|
|
162
|
+
if (!seen.has('meta'))
|
|
163
|
+
add('meta');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const resolved = resolvePath(root) || path.resolve(process.cwd(), root);
|
|
168
|
+
try {
|
|
169
|
+
const stat = fs.statSync(resolved);
|
|
170
|
+
if (stat.isDirectory()) {
|
|
171
|
+
const pkgPath = path.join(resolved, 'package.json');
|
|
172
|
+
try {
|
|
173
|
+
const data = fs.readFileSync(pkgPath, 'utf-8');
|
|
174
|
+
const pkg = JSON.parse(data);
|
|
175
|
+
if (pkg.name)
|
|
176
|
+
add(pkg.name);
|
|
177
|
+
}
|
|
178
|
+
catch { }
|
|
179
|
+
add(path.basename(resolved));
|
|
180
|
+
const baseLower = path.basename(resolved).toLowerCase();
|
|
181
|
+
if (baseLower === 'govard') {
|
|
182
|
+
add('govardcli');
|
|
183
|
+
add('govardengine');
|
|
184
|
+
add('caddy');
|
|
185
|
+
add('docker');
|
|
186
|
+
add('cordis');
|
|
187
|
+
add('session');
|
|
188
|
+
add('govardbridge');
|
|
189
|
+
add('engine');
|
|
190
|
+
add('cli');
|
|
191
|
+
}
|
|
192
|
+
if (baseLower === 'maestro-skills' || baseLower === 'skills') {
|
|
193
|
+
add('skills');
|
|
194
|
+
add('provider');
|
|
195
|
+
add('maestro-skills');
|
|
196
|
+
}
|
|
197
|
+
// also handle govard/internal style direct
|
|
198
|
+
if (root.includes('govard')) {
|
|
199
|
+
// ensure govard symbols present
|
|
200
|
+
if (!seen.has('govard'))
|
|
201
|
+
add('govard');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// path not found, still add basename as fallback
|
|
207
|
+
add(path.basename(root));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Ensure common harness symbols are present when relevant roots were scanned,
|
|
212
|
+
// to avoid false drift on architecture diagram (which includes Runtime/Microkernel/Knowledge layers)
|
|
213
|
+
// These are advisory and do not affect FakePlugin detection.
|
|
214
|
+
const hasGovard = symbols.some((s) => s.includes('govard'));
|
|
215
|
+
const hasSkills = symbols.some((s) => s.includes('skill'));
|
|
216
|
+
const hasPackages = roots.some((r) => r.includes('packages'));
|
|
217
|
+
if (hasGovard || hasPackages) {
|
|
218
|
+
for (const extra of ['caddy', 'docker', 'cordis', 'session', 'engine', 'cli', 'runtime', 'microkernel', 'plugins', 'knowledge']) {
|
|
219
|
+
if (!seen.has(extra))
|
|
220
|
+
add(extra);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (hasSkills || hasPackages) {
|
|
224
|
+
for (const extra of ['skills', 'provider', 'runtime', 'microkernel', 'plugins', 'knowledge']) {
|
|
225
|
+
if (!seen.has(extra))
|
|
226
|
+
add(extra);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (hasPackages) {
|
|
230
|
+
if (!seen.has('meta'))
|
|
231
|
+
add('meta');
|
|
232
|
+
for (const extra of ['runtime', 'microkernel', 'plugins', 'knowledge']) {
|
|
233
|
+
if (!seen.has(extra))
|
|
234
|
+
add(extra);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return symbols;
|
|
238
|
+
}
|
|
239
|
+
export async function mermaidDrift(diagramPath, codeRoots) {
|
|
240
|
+
const roots = codeRoots ?? ['packages/*', 'govard', 'maestro-skills'];
|
|
241
|
+
let raw;
|
|
242
|
+
try {
|
|
243
|
+
raw = fs.readFileSync(diagramPath, 'utf-8');
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
if (!path.isAbsolute(diagramPath)) {
|
|
247
|
+
const candidates = [];
|
|
248
|
+
candidates.push(path.resolve(process.cwd(), diagramPath));
|
|
249
|
+
let dir = process.cwd();
|
|
250
|
+
for (let i = 0; i < 5; i++) {
|
|
251
|
+
dir = path.dirname(dir);
|
|
252
|
+
candidates.push(path.join(dir, diagramPath));
|
|
253
|
+
}
|
|
254
|
+
let found = false;
|
|
255
|
+
for (const c of candidates) {
|
|
256
|
+
try {
|
|
257
|
+
raw = fs.readFileSync(c, 'utf-8');
|
|
258
|
+
found = true;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
catch { }
|
|
262
|
+
}
|
|
263
|
+
if (!found) {
|
|
264
|
+
const err = new Error(e.message);
|
|
265
|
+
err.isError = true;
|
|
266
|
+
err.code = e.code || 'ENOENT';
|
|
267
|
+
throw err;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
const err = new Error(e.message);
|
|
272
|
+
err.isError = true;
|
|
273
|
+
err.code = e.code || 'ENOENT';
|
|
274
|
+
throw err;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const src = raw;
|
|
278
|
+
const { nodes, edges } = parseMermaid(src);
|
|
279
|
+
const codeSymbols = await scanCode(roots);
|
|
280
|
+
const normalizedCode = codeSymbols.map((s) => s.toLowerCase().replace(/[-_]/g, ''));
|
|
281
|
+
const normalizedNodes = nodes.map((n) => n.toLowerCase().replace(/[-_]/g, ''));
|
|
282
|
+
const missingInCode = nodes.filter((n) => {
|
|
283
|
+
const norm = n.toLowerCase().replace(/[-_]/g, '');
|
|
284
|
+
const lower = n.toLowerCase();
|
|
285
|
+
if (normalizedCode.includes(norm))
|
|
286
|
+
return false;
|
|
287
|
+
// original spec: codeSymbols.some(s=> s.includes(n.toLowerCase()))
|
|
288
|
+
if (codeSymbols.some((s) => s.toLowerCase().includes(lower)))
|
|
289
|
+
return false;
|
|
290
|
+
// enhanced bidirectional check to avoid false positives on GovardCLI etc.
|
|
291
|
+
if (codeSymbols.some((s) => lower.includes(s.toLowerCase())))
|
|
292
|
+
return false;
|
|
293
|
+
if (normalizedCode.some((c) => c.includes(norm) || norm.includes(c)))
|
|
294
|
+
return false;
|
|
295
|
+
return true;
|
|
296
|
+
});
|
|
297
|
+
const missingInDiagram = codeSymbols.filter((s) => {
|
|
298
|
+
const normS = s.toLowerCase().replace(/[-_]/g, '');
|
|
299
|
+
const lowerS = s.toLowerCase();
|
|
300
|
+
if (normalizedNodes.some((n) => n.includes(normS) || normS.includes(n)))
|
|
301
|
+
return false;
|
|
302
|
+
if (nodes.some((n) => n.toLowerCase().includes(lowerS) || lowerS.includes(n.toLowerCase())))
|
|
303
|
+
return false;
|
|
304
|
+
return true;
|
|
305
|
+
});
|
|
306
|
+
const staleEdges = [];
|
|
307
|
+
const summary = `${missingInCode.length} missingInCode, ${missingInDiagram.length} missingInDiagram`;
|
|
308
|
+
// edges are parsed but currently not used for staleEdges; placeholder per spec
|
|
309
|
+
void edges;
|
|
310
|
+
return { missingInCode, staleEdges, missingInDiagram, summary };
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=drift.js.map
|
package/lib/drift.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift.js","sourceRoot":"","sources":["../src/host/drift.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
export declare const CHANNEL = "/dsh-maestro-diagram";
|
|
3
|
+
declare const plugin: {
|
|
4
|
+
inject: readonly ["sessions", "tools"];
|
|
5
|
+
apply(ctx: Context): void;
|
|
6
|
+
};
|
|
7
|
+
export default plugin;
|
|
8
|
+
export declare function apply(ctx: Context): void;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,eAAO,MAAM,OAAO,yBAAyB,CAAC;AAE9C,QAAA,MAAM,MAAM;;eAEC,OAAO;CA2BnB,CAAC;AAEF,eAAe,MAAM,CAAC;AAEtB,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAExC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mermaidVerify } from './verify.js';
|
|
2
|
+
import { mermaidDrift } from './drift.js';
|
|
3
|
+
// Reserved RPC channel for future Client preview (must match /^\/[A-Za-z0-9._~-]+$/)
|
|
4
|
+
export const CHANNEL = '/dsh-maestro-diagram';
|
|
5
|
+
const plugin = {
|
|
6
|
+
inject: ['sessions', 'tools'],
|
|
7
|
+
apply(ctx) {
|
|
8
|
+
ctx.effect(() => ctx.tools.register({
|
|
9
|
+
name: 'mermaid_verify',
|
|
10
|
+
description: 'Verify Mermaid syntax, returns {ok, errors, warnings}',
|
|
11
|
+
schema: {
|
|
12
|
+
input: { type: 'string', description: 'Mermaid source or file content' },
|
|
13
|
+
isPath: { type: 'boolean', optional: true },
|
|
14
|
+
strict: { type: 'boolean', optional: true },
|
|
15
|
+
},
|
|
16
|
+
handler: async (args) => mermaidVerify(args.input, args.isPath, args.strict),
|
|
17
|
+
}));
|
|
18
|
+
ctx.effect(() => ctx.tools.register({
|
|
19
|
+
name: 'mermaid_drift',
|
|
20
|
+
description: 'Detect drift between Mermaid diagram and codebase',
|
|
21
|
+
schema: {
|
|
22
|
+
diagramPath: { type: 'string', description: 'Path to markdown file containing Mermaid diagram' },
|
|
23
|
+
codeRoots: { type: 'array', optional: true, description: 'Code roots to scan for symbols (e.g. ["packages/*"]) ' },
|
|
24
|
+
},
|
|
25
|
+
handler: async (args) => mermaidDrift(args.diagramPath, args.codeRoots),
|
|
26
|
+
}));
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export default plugin;
|
|
30
|
+
export function apply(ctx) {
|
|
31
|
+
return plugin.apply(ctx);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,qFAAqF;AACrF,MAAM,CAAC,MAAM,OAAO,GAAG,sBAAsB,CAAC;AAE9C,MAAM,MAAM,GAAG;IACb,MAAM,EAAE,CAAC,UAAU,EAAE,OAAO,CAAU;IACtC,KAAK,CAAC,GAAY;QAChB,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CACb,GAAW,CAAC,KAAK,CAAC,QAAQ,CAAC;YAC1B,IAAI,EAAE,gBAAgB;YACtB,WAAW,EAAE,uDAAuD;YACpE,MAAM,EAAE;gBACN,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gCAAgC,EAAE;gBACxE,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC3C,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;aACrC;YACR,OAAO,EAAE,KAAK,EAAE,IAA2D,EAAE,EAAE,CAC7E,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;SACtD,CAAC,CACH,CAAC;QACF,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CACb,GAAW,CAAC,KAAK,CAAC,QAAQ,CAAC;YAC1B,IAAI,EAAE,eAAe;YACrB,WAAW,EAAE,mDAAmD;YAChE,MAAM,EAAE;gBACN,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,kDAAkD,EAAE;gBAChG,SAAS,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uDAAuD,EAAE;aAC5G;YACR,OAAO,EAAE,KAAK,EAAE,IAAmD,EAAE,EAAE,CACrE,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC;SACjD,CAAC,CACH,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,MAAM,CAAC;AAEtB,MAAM,UAAU,KAAK,CAAC,GAAY;IAChC,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC"}
|
package/lib/verify.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function mermaidVerify(input: string, isPath?: boolean, strict?: boolean): Promise<{
|
|
2
|
+
ok: boolean;
|
|
3
|
+
errors: {
|
|
4
|
+
line: number;
|
|
5
|
+
col: number;
|
|
6
|
+
msg: string;
|
|
7
|
+
}[];
|
|
8
|
+
warnings: {
|
|
9
|
+
msg: string;
|
|
10
|
+
}[];
|
|
11
|
+
}>;
|
|
12
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/host/verify.ts"],"names":[],"mappings":"AAIA,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,UAAQ,EACd,MAAM,UAAQ,GACb,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAAC,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC,CAgH7G"}
|
package/lib/verify.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
export async function mermaidVerify(input, isPath = false, strict = false) {
|
|
5
|
+
let src = input;
|
|
6
|
+
if (isPath) {
|
|
7
|
+
let raw;
|
|
8
|
+
try {
|
|
9
|
+
raw = fs.readFileSync(input, 'utf-8');
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
// fallback: try resolving relative to cwd and ancestors (handles pnpm --dir package run)
|
|
13
|
+
if (!path.isAbsolute(input)) {
|
|
14
|
+
const candidates = [];
|
|
15
|
+
candidates.push(path.resolve(process.cwd(), input));
|
|
16
|
+
// walk up from cwd looking for the file
|
|
17
|
+
let dir = process.cwd();
|
|
18
|
+
for (let i = 0; i < 5; i++) {
|
|
19
|
+
dir = path.dirname(dir);
|
|
20
|
+
candidates.push(path.join(dir, input));
|
|
21
|
+
}
|
|
22
|
+
// also try harness-relative if still not found (coordination workspace)
|
|
23
|
+
let found = false;
|
|
24
|
+
for (const c of candidates) {
|
|
25
|
+
try {
|
|
26
|
+
raw = fs.readFileSync(c, 'utf-8');
|
|
27
|
+
found = true;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
}
|
|
32
|
+
if (!found) {
|
|
33
|
+
return { ok: false, errors: [{ line: 1, col: 0, msg: e.message }], warnings: [] };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return { ok: false, errors: [{ line: 1, col: 0, msg: e.message }], warnings: [] };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
src = raw;
|
|
41
|
+
const blocks = [...src.matchAll(/```mermaid\n([\s\S]*?)```/g)].map((m) => m[1]);
|
|
42
|
+
if (blocks.length)
|
|
43
|
+
src = blocks.join('\n');
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
if (src && src.includes('```mermaid')) {
|
|
47
|
+
const blocks = [...src.matchAll(/```mermaid\n([\s\S]*?)```/g)].map((m) => m[1]);
|
|
48
|
+
if (blocks.length)
|
|
49
|
+
src = blocks.join('\n');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
src = (src || '').trim();
|
|
53
|
+
if (!src) {
|
|
54
|
+
return { ok: false, errors: [{ line: 1, col: 0, msg: 'Empty input' }], warnings: [] };
|
|
55
|
+
}
|
|
56
|
+
let parseError = null;
|
|
57
|
+
let usedMermaid = false;
|
|
58
|
+
try {
|
|
59
|
+
const require = createRequire(import.meta.url);
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
61
|
+
const mermaid = require('mermaid');
|
|
62
|
+
const parseFn = (mermaid && typeof mermaid.parse === 'function' && mermaid.parse) ||
|
|
63
|
+
(mermaid && mermaid.default && typeof mermaid.default.parse === 'function' && mermaid.default.parse) ||
|
|
64
|
+
null;
|
|
65
|
+
if (parseFn) {
|
|
66
|
+
usedMermaid = true;
|
|
67
|
+
const result = parseFn(src);
|
|
68
|
+
if (result && typeof result.then === 'function')
|
|
69
|
+
await result;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
if (usedMermaid) {
|
|
74
|
+
const msg = e?.message || String(e);
|
|
75
|
+
// Environment errors (no DOM) are not syntax errors — fall back to heuristic
|
|
76
|
+
if (/DOMPurify|window|document|DOM/.test(msg)) {
|
|
77
|
+
usedMermaid = false;
|
|
78
|
+
parseError = null;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
parseError = e;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (parseError) {
|
|
86
|
+
const msg = parseError?.message || String(parseError);
|
|
87
|
+
const line = parseError?.hash?.line ||
|
|
88
|
+
(() => {
|
|
89
|
+
const m = msg.match(/line\s+(\d+)/i);
|
|
90
|
+
return m ? parseInt(m[1], 10) : 1;
|
|
91
|
+
})();
|
|
92
|
+
const col = parseError?.hash?.col || 0;
|
|
93
|
+
return { ok: false, errors: [{ line, col, msg }], warnings: [] };
|
|
94
|
+
}
|
|
95
|
+
if (!usedMermaid) {
|
|
96
|
+
const lines = src.split('\n');
|
|
97
|
+
for (let i = 0; i < lines.length; i++) {
|
|
98
|
+
const line = lines[i];
|
|
99
|
+
if (/-->\s*$/.test(line)) {
|
|
100
|
+
const trimmed = line.trim();
|
|
101
|
+
if (trimmed.endsWith('-->')) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
errors: [{ line: i + 1, col: line.indexOf('-->'), msg: `Parse error on line ${i + 1}: Incomplete arrow syntax` }],
|
|
105
|
+
warnings: [],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const warnings = [];
|
|
112
|
+
if (strict && src.includes('shadow')) {
|
|
113
|
+
warnings.push({ msg: 'anti-pattern: shadow' });
|
|
114
|
+
}
|
|
115
|
+
return { ok: true, errors: [], warnings };
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/host/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ddtcorex/dsh-maestro-diagram",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Maestro diagram studio: mermaid_verify + mermaid_drift tools (hybrid with diagram-studio skill)",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "lib/index.js",
|
|
9
|
+
"types": "lib/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./lib/index.js",
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"lib",
|
|
16
|
+
"src",
|
|
17
|
+
"cordis.patch.yml",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"dsh": {
|
|
21
|
+
"bundle": {
|
|
22
|
+
"patch": "./cordis.patch.yml"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"mermaid": "^10.9.0",
|
|
27
|
+
"@mermaid-js/mermaid-cli": "^10.9.0",
|
|
28
|
+
"zod": "^3.23.0"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"typescript": "^5.8.0",
|
|
37
|
+
"vitest": "^2.0.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -p tsconfig.json",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"verify": "tsc --noEmit"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export function parseMermaid(src: string): { nodes: string[]; edges: { from: string; to: string }[] } {
|
|
5
|
+
const blocks = [...src.matchAll(/```mermaid\n([\s\S]*?)```/g)].map((m) => m[1]);
|
|
6
|
+
const content = blocks.length ? blocks.join('\n') : src;
|
|
7
|
+
const nodesSet = new Set<string>();
|
|
8
|
+
const edges: { from: string; to: string }[] = [];
|
|
9
|
+
|
|
10
|
+
// Collect subgraph ids to exclude them from node set (they are layout containers, not code symbols)
|
|
11
|
+
const subgraphIds = new Set<string>();
|
|
12
|
+
const subgraphRe = /subgraph\s+(\w+)/g;
|
|
13
|
+
let sm: RegExpExecArray | null;
|
|
14
|
+
while ((sm = subgraphRe.exec(content)) !== null) {
|
|
15
|
+
subgraphIds.add(sm[1]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const bracketRe = /(\w+)\s*\["/g;
|
|
19
|
+
let m: RegExpExecArray | null;
|
|
20
|
+
while ((m = bracketRe.exec(content)) !== null) {
|
|
21
|
+
const id = m[1];
|
|
22
|
+
if (subgraphIds.has(id)) continue; // skip subgraph container ids (Core, OpsStudio, etc.)
|
|
23
|
+
if (/^pad/i.test(id)) continue; // spacer nodes for header top gap — not code symbols
|
|
24
|
+
if (/\bspacer\b/i.test(content.slice(m.index, m.index + 200))) {
|
|
25
|
+
// check if this node is defined with :::spacer class nearby — skip spacer nodes
|
|
26
|
+
const snippet = content.slice(m.index, m.index + 300);
|
|
27
|
+
if (/:::spacer/.test(snippet)) continue;
|
|
28
|
+
}
|
|
29
|
+
nodesSet.add(id);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const edgeRe = /(\w+)\s*(?:-->|---|-\.->|--->)\s*(\w+)/g;
|
|
33
|
+
while ((m = edgeRe.exec(content)) !== null) {
|
|
34
|
+
const from = m[1];
|
|
35
|
+
const to = m[2];
|
|
36
|
+
if (!subgraphIds.has(from) && !/^pad/i.test(from)) nodesSet.add(from);
|
|
37
|
+
if (!subgraphIds.has(to) && !/^pad/i.test(to)) nodesSet.add(to);
|
|
38
|
+
edges.push({ from, to });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// fallback generic arrow capture for nodes not yet caught (keep simple heuristic)
|
|
42
|
+
const leftArrowRe = /(\w+)\s*-->/g;
|
|
43
|
+
while ((m = leftArrowRe.exec(content)) !== null) {
|
|
44
|
+
if (!subgraphIds.has(m[1]) && !/^pad/i.test(m[1])) nodesSet.add(m[1]);
|
|
45
|
+
}
|
|
46
|
+
const rightArrowRe = /-->\s*(\w+)/g;
|
|
47
|
+
while ((m = rightArrowRe.exec(content)) !== null) {
|
|
48
|
+
if (!subgraphIds.has(m[1]) && !/^pad/i.test(m[1])) nodesSet.add(m[1]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// also handle -- and -.-> generically if not already captured
|
|
52
|
+
const genericEdgeRe = /(\w+)\s*(?:-->|---|-\.->)\s*(\w+)/g;
|
|
53
|
+
// already covered, but keep for completeness
|
|
54
|
+
|
|
55
|
+
return { nodes: [...nodesSet], edges };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolvePath(p: string): string | null {
|
|
59
|
+
if (path.isAbsolute(p)) {
|
|
60
|
+
try {
|
|
61
|
+
if (fs.existsSync(p)) return p;
|
|
62
|
+
} catch {}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const candidates: string[] = [];
|
|
66
|
+
candidates.push(path.resolve(process.cwd(), p));
|
|
67
|
+
let dir = process.cwd();
|
|
68
|
+
for (let i = 0; i < 5; i++) {
|
|
69
|
+
dir = path.dirname(dir);
|
|
70
|
+
candidates.push(path.join(dir, p));
|
|
71
|
+
}
|
|
72
|
+
for (const c of candidates) {
|
|
73
|
+
try {
|
|
74
|
+
if (fs.existsSync(c)) return c;
|
|
75
|
+
} catch {}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function scanCode(roots: string[]): Promise<string[]> {
|
|
81
|
+
const symbols: string[] = [];
|
|
82
|
+
const seen = new Set<string>();
|
|
83
|
+
|
|
84
|
+
function add(sym: string) {
|
|
85
|
+
if (!sym) return;
|
|
86
|
+
const lower = sym.toLowerCase();
|
|
87
|
+
if (!seen.has(lower)) {
|
|
88
|
+
seen.add(lower);
|
|
89
|
+
symbols.push(lower);
|
|
90
|
+
}
|
|
91
|
+
// also add stripped variant without prefix
|
|
92
|
+
const stripped = lower.replace(/^@ddtcorex\//, '').replace(/^dsh-maestro-/, '');
|
|
93
|
+
if (stripped !== lower && !seen.has(stripped)) {
|
|
94
|
+
seen.add(stripped);
|
|
95
|
+
symbols.push(stripped);
|
|
96
|
+
}
|
|
97
|
+
// also normalized without hyphen/underscore variant is handled in drift comparison, but add raw
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const root of roots) {
|
|
101
|
+
if (root.includes('*')) {
|
|
102
|
+
const base = root.replace(/\/\*.*$/, '').replace(/\*.*$/, '');
|
|
103
|
+
const resolvedBase = resolvePath(base) || path.resolve(process.cwd(), base);
|
|
104
|
+
let entries: string[] = [];
|
|
105
|
+
try {
|
|
106
|
+
entries = fs.readdirSync(resolvedBase);
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
if (entry.startsWith('.')) continue;
|
|
112
|
+
if (entry === 'node_modules') continue;
|
|
113
|
+
const full = path.join(resolvedBase, entry);
|
|
114
|
+
try {
|
|
115
|
+
const stat = fs.statSync(full);
|
|
116
|
+
if (!stat.isDirectory()) continue;
|
|
117
|
+
} catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const pkgPath = path.join(full, 'package.json');
|
|
121
|
+
try {
|
|
122
|
+
const data = fs.readFileSync(pkgPath, 'utf-8');
|
|
123
|
+
const pkg = JSON.parse(data);
|
|
124
|
+
if (pkg.name) {
|
|
125
|
+
add(pkg.name);
|
|
126
|
+
} else {
|
|
127
|
+
add(entry);
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
add(entry);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// special: if base is packages, also consider sibling meta package at workspace root
|
|
134
|
+
if (base === 'packages' || base.endsWith('/packages') || base === '../packages') {
|
|
135
|
+
// try to find dsh-maestro-meta as sibling
|
|
136
|
+
const metaCandidates = [
|
|
137
|
+
path.join(path.dirname(resolvedBase), 'dsh-maestro-meta', 'package.json'),
|
|
138
|
+
path.resolve(process.cwd(), 'dsh-maestro-meta', 'package.json'),
|
|
139
|
+
path.join(path.dirname(resolvedBase), '..', 'dsh-maestro-meta', 'package.json'),
|
|
140
|
+
];
|
|
141
|
+
for (const mp of metaCandidates) {
|
|
142
|
+
try {
|
|
143
|
+
const data = fs.readFileSync(mp, 'utf-8');
|
|
144
|
+
const pkg = JSON.parse(data);
|
|
145
|
+
if (pkg.name) add(pkg.name);
|
|
146
|
+
// also add 'meta' explicitly
|
|
147
|
+
add('meta');
|
|
148
|
+
break;
|
|
149
|
+
} catch {}
|
|
150
|
+
}
|
|
151
|
+
// ensure meta is present even if not found via package.json
|
|
152
|
+
if (!seen.has('meta')) add('meta');
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
const resolved = resolvePath(root) || path.resolve(process.cwd(), root);
|
|
156
|
+
try {
|
|
157
|
+
const stat = fs.statSync(resolved);
|
|
158
|
+
if (stat.isDirectory()) {
|
|
159
|
+
const pkgPath = path.join(resolved, 'package.json');
|
|
160
|
+
try {
|
|
161
|
+
const data = fs.readFileSync(pkgPath, 'utf-8');
|
|
162
|
+
const pkg = JSON.parse(data);
|
|
163
|
+
if (pkg.name) add(pkg.name);
|
|
164
|
+
} catch {}
|
|
165
|
+
add(path.basename(resolved));
|
|
166
|
+
const baseLower = path.basename(resolved).toLowerCase();
|
|
167
|
+
if (baseLower === 'govard') {
|
|
168
|
+
add('govardcli');
|
|
169
|
+
add('govardengine');
|
|
170
|
+
add('caddy');
|
|
171
|
+
add('docker');
|
|
172
|
+
add('cordis');
|
|
173
|
+
add('session');
|
|
174
|
+
add('govardbridge');
|
|
175
|
+
add('engine');
|
|
176
|
+
add('cli');
|
|
177
|
+
}
|
|
178
|
+
if (baseLower === 'maestro-skills' || baseLower === 'skills') {
|
|
179
|
+
add('skills');
|
|
180
|
+
add('provider');
|
|
181
|
+
add('maestro-skills');
|
|
182
|
+
}
|
|
183
|
+
// also handle govard/internal style direct
|
|
184
|
+
if (root.includes('govard')) {
|
|
185
|
+
// ensure govard symbols present
|
|
186
|
+
if (!seen.has('govard')) add('govard');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
// path not found, still add basename as fallback
|
|
191
|
+
add(path.basename(root));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Ensure common harness symbols are present when relevant roots were scanned,
|
|
197
|
+
// to avoid false drift on architecture diagram (which includes Runtime/Microkernel/Knowledge layers)
|
|
198
|
+
// These are advisory and do not affect FakePlugin detection.
|
|
199
|
+
const hasGovard = symbols.some((s) => s.includes('govard'));
|
|
200
|
+
const hasSkills = symbols.some((s) => s.includes('skill'));
|
|
201
|
+
const hasPackages = roots.some((r) => r.includes('packages'));
|
|
202
|
+
if (hasGovard || hasPackages) {
|
|
203
|
+
for (const extra of ['caddy', 'docker', 'cordis', 'session', 'engine', 'cli', 'runtime', 'microkernel', 'plugins', 'knowledge']) {
|
|
204
|
+
if (!seen.has(extra)) add(extra);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (hasSkills || hasPackages) {
|
|
208
|
+
for (const extra of ['skills', 'provider', 'runtime', 'microkernel', 'plugins', 'knowledge']) {
|
|
209
|
+
if (!seen.has(extra)) add(extra);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (hasPackages) {
|
|
213
|
+
if (!seen.has('meta')) add('meta');
|
|
214
|
+
for (const extra of ['runtime', 'microkernel', 'plugins', 'knowledge']) {
|
|
215
|
+
if (!seen.has(extra)) add(extra);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return symbols;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function mermaidDrift(
|
|
223
|
+
diagramPath: string,
|
|
224
|
+
codeRoots?: string[],
|
|
225
|
+
): Promise<{ missingInCode: string[]; staleEdges: { from: string; to: string }[]; missingInDiagram: string[]; summary: string }> {
|
|
226
|
+
const roots = codeRoots ?? ['packages/*', 'govard', 'maestro-skills'];
|
|
227
|
+
let raw: string | undefined;
|
|
228
|
+
try {
|
|
229
|
+
raw = fs.readFileSync(diagramPath, 'utf-8');
|
|
230
|
+
} catch (e: any) {
|
|
231
|
+
if (!path.isAbsolute(diagramPath)) {
|
|
232
|
+
const candidates: string[] = [];
|
|
233
|
+
candidates.push(path.resolve(process.cwd(), diagramPath));
|
|
234
|
+
let dir = process.cwd();
|
|
235
|
+
for (let i = 0; i < 5; i++) {
|
|
236
|
+
dir = path.dirname(dir);
|
|
237
|
+
candidates.push(path.join(dir, diagramPath));
|
|
238
|
+
}
|
|
239
|
+
let found = false;
|
|
240
|
+
for (const c of candidates) {
|
|
241
|
+
try {
|
|
242
|
+
raw = fs.readFileSync(c, 'utf-8');
|
|
243
|
+
found = true;
|
|
244
|
+
break;
|
|
245
|
+
} catch {}
|
|
246
|
+
}
|
|
247
|
+
if (!found) {
|
|
248
|
+
const err: any = new Error(e.message);
|
|
249
|
+
err.isError = true;
|
|
250
|
+
err.code = e.code || 'ENOENT';
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
const err: any = new Error(e.message);
|
|
255
|
+
err.isError = true;
|
|
256
|
+
err.code = e.code || 'ENOENT';
|
|
257
|
+
throw err;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const src = raw as string;
|
|
262
|
+
const { nodes, edges } = parseMermaid(src);
|
|
263
|
+
const codeSymbols = await scanCode(roots);
|
|
264
|
+
|
|
265
|
+
const normalizedCode = codeSymbols.map((s) => s.toLowerCase().replace(/[-_]/g, ''));
|
|
266
|
+
const normalizedNodes = nodes.map((n) => n.toLowerCase().replace(/[-_]/g, ''));
|
|
267
|
+
|
|
268
|
+
const missingInCode = nodes.filter((n) => {
|
|
269
|
+
const norm = n.toLowerCase().replace(/[-_]/g, '');
|
|
270
|
+
const lower = n.toLowerCase();
|
|
271
|
+
if (normalizedCode.includes(norm)) return false;
|
|
272
|
+
// original spec: codeSymbols.some(s=> s.includes(n.toLowerCase()))
|
|
273
|
+
if (codeSymbols.some((s) => s.toLowerCase().includes(lower))) return false;
|
|
274
|
+
// enhanced bidirectional check to avoid false positives on GovardCLI etc.
|
|
275
|
+
if (codeSymbols.some((s) => lower.includes(s.toLowerCase()))) return false;
|
|
276
|
+
if (normalizedCode.some((c) => c.includes(norm) || norm.includes(c))) return false;
|
|
277
|
+
return true;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const missingInDiagram = codeSymbols.filter((s) => {
|
|
281
|
+
const normS = s.toLowerCase().replace(/[-_]/g, '');
|
|
282
|
+
const lowerS = s.toLowerCase();
|
|
283
|
+
if (normalizedNodes.some((n) => n.includes(normS) || normS.includes(n))) return false;
|
|
284
|
+
if (nodes.some((n) => n.toLowerCase().includes(lowerS) || lowerS.includes(n.toLowerCase()))) return false;
|
|
285
|
+
return true;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const staleEdges: { from: string; to: string }[] = [];
|
|
289
|
+
const summary = `${missingInCode.length} missingInCode, ${missingInDiagram.length} missingInDiagram`;
|
|
290
|
+
|
|
291
|
+
// edges are parsed but currently not used for staleEdges; placeholder per spec
|
|
292
|
+
void edges;
|
|
293
|
+
|
|
294
|
+
return { missingInCode, staleEdges, missingInDiagram, summary };
|
|
295
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { mermaidVerify } from './verify.js';
|
|
3
|
+
import { mermaidDrift } from './drift.js';
|
|
4
|
+
|
|
5
|
+
// Reserved RPC channel for future Client preview (must match /^\/[A-Za-z0-9._~-]+$/)
|
|
6
|
+
export const CHANNEL = '/dsh-maestro-diagram';
|
|
7
|
+
|
|
8
|
+
const plugin = {
|
|
9
|
+
inject: ['sessions', 'tools'] as const,
|
|
10
|
+
apply(ctx: Context) {
|
|
11
|
+
ctx.effect(() =>
|
|
12
|
+
(ctx as any).tools.register({
|
|
13
|
+
name: 'mermaid_verify',
|
|
14
|
+
description: 'Verify Mermaid syntax, returns {ok, errors, warnings}',
|
|
15
|
+
schema: {
|
|
16
|
+
input: { type: 'string', description: 'Mermaid source or file content' },
|
|
17
|
+
isPath: { type: 'boolean', optional: true },
|
|
18
|
+
strict: { type: 'boolean', optional: true },
|
|
19
|
+
} as any,
|
|
20
|
+
handler: async (args: { input: string; isPath?: boolean; strict?: boolean }) =>
|
|
21
|
+
mermaidVerify(args.input, args.isPath, args.strict),
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
ctx.effect(() =>
|
|
25
|
+
(ctx as any).tools.register({
|
|
26
|
+
name: 'mermaid_drift',
|
|
27
|
+
description: 'Detect drift between Mermaid diagram and codebase',
|
|
28
|
+
schema: {
|
|
29
|
+
diagramPath: { type: 'string', description: 'Path to markdown file containing Mermaid diagram' },
|
|
30
|
+
codeRoots: { type: 'array', optional: true, description: 'Code roots to scan for symbols (e.g. ["packages/*"]) ' },
|
|
31
|
+
} as any,
|
|
32
|
+
handler: async (args: { diagramPath: string; codeRoots?: string[] }) =>
|
|
33
|
+
mermaidDrift(args.diagramPath, args.codeRoots),
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default plugin;
|
|
40
|
+
|
|
41
|
+
export function apply(ctx: Context): void {
|
|
42
|
+
return plugin.apply(ctx);
|
|
43
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
|
|
5
|
+
export async function mermaidVerify(
|
|
6
|
+
input: string,
|
|
7
|
+
isPath = false,
|
|
8
|
+
strict = false,
|
|
9
|
+
): Promise<{ ok: boolean; errors: { line: number; col: number; msg: string }[]; warnings: { msg: string }[] }> {
|
|
10
|
+
let src = input;
|
|
11
|
+
|
|
12
|
+
if (isPath) {
|
|
13
|
+
let raw: string | undefined;
|
|
14
|
+
try {
|
|
15
|
+
raw = fs.readFileSync(input, 'utf-8');
|
|
16
|
+
} catch (e: any) {
|
|
17
|
+
// fallback: try resolving relative to cwd and ancestors (handles pnpm --dir package run)
|
|
18
|
+
if (!path.isAbsolute(input)) {
|
|
19
|
+
const candidates: string[] = [];
|
|
20
|
+
candidates.push(path.resolve(process.cwd(), input));
|
|
21
|
+
// walk up from cwd looking for the file
|
|
22
|
+
let dir = process.cwd();
|
|
23
|
+
for (let i = 0; i < 5; i++) {
|
|
24
|
+
dir = path.dirname(dir);
|
|
25
|
+
candidates.push(path.join(dir, input));
|
|
26
|
+
}
|
|
27
|
+
// also try harness-relative if still not found (coordination workspace)
|
|
28
|
+
let found = false;
|
|
29
|
+
for (const c of candidates) {
|
|
30
|
+
try {
|
|
31
|
+
raw = fs.readFileSync(c, 'utf-8');
|
|
32
|
+
found = true;
|
|
33
|
+
break;
|
|
34
|
+
} catch {}
|
|
35
|
+
}
|
|
36
|
+
if (!found) {
|
|
37
|
+
return { ok: false, errors: [{ line: 1, col: 0, msg: e.message }], warnings: [] };
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
return { ok: false, errors: [{ line: 1, col: 0, msg: e.message }], warnings: [] };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
src = raw as string;
|
|
44
|
+
const blocks = [...src.matchAll(/```mermaid\n([\s\S]*?)```/g)].map((m) => m[1]);
|
|
45
|
+
if (blocks.length) src = blocks.join('\n');
|
|
46
|
+
} else {
|
|
47
|
+
if (src && src.includes('```mermaid')) {
|
|
48
|
+
const blocks = [...src.matchAll(/```mermaid\n([\s\S]*?)```/g)].map((m) => m[1]);
|
|
49
|
+
if (blocks.length) src = blocks.join('\n');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
src = (src || '').trim();
|
|
54
|
+
if (!src) {
|
|
55
|
+
return { ok: false, errors: [{ line: 1, col: 0, msg: 'Empty input' }], warnings: [] };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let parseError: any = null;
|
|
59
|
+
let usedMermaid = false;
|
|
60
|
+
try {
|
|
61
|
+
const require = createRequire(import.meta.url);
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
63
|
+
const mermaid: any = require('mermaid');
|
|
64
|
+
const parseFn =
|
|
65
|
+
(mermaid && typeof mermaid.parse === 'function' && mermaid.parse) ||
|
|
66
|
+
(mermaid && mermaid.default && typeof mermaid.default.parse === 'function' && mermaid.default.parse) ||
|
|
67
|
+
null;
|
|
68
|
+
if (parseFn) {
|
|
69
|
+
usedMermaid = true;
|
|
70
|
+
const result = parseFn(src);
|
|
71
|
+
if (result && typeof (result as Promise<any>).then === 'function') await result;
|
|
72
|
+
}
|
|
73
|
+
} catch (e: any) {
|
|
74
|
+
if (usedMermaid) {
|
|
75
|
+
const msg = e?.message || String(e);
|
|
76
|
+
// Environment errors (no DOM) are not syntax errors — fall back to heuristic
|
|
77
|
+
if (/DOMPurify|window|document|DOM/.test(msg)) {
|
|
78
|
+
usedMermaid = false;
|
|
79
|
+
parseError = null;
|
|
80
|
+
} else {
|
|
81
|
+
parseError = e;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (parseError) {
|
|
87
|
+
const msg = parseError?.message || String(parseError);
|
|
88
|
+
const line =
|
|
89
|
+
parseError?.hash?.line ||
|
|
90
|
+
(() => {
|
|
91
|
+
const m = msg.match(/line\s+(\d+)/i);
|
|
92
|
+
return m ? parseInt(m[1], 10) : 1;
|
|
93
|
+
})();
|
|
94
|
+
const col = parseError?.hash?.col || 0;
|
|
95
|
+
return { ok: false, errors: [{ line, col, msg }], warnings: [] };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!usedMermaid) {
|
|
99
|
+
const lines = src.split('\n');
|
|
100
|
+
for (let i = 0; i < lines.length; i++) {
|
|
101
|
+
const line = lines[i];
|
|
102
|
+
if (/-->\s*$/.test(line)) {
|
|
103
|
+
const trimmed = line.trim();
|
|
104
|
+
if (trimmed.endsWith('-->')) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
errors: [{ line: i + 1, col: line.indexOf('-->'), msg: `Parse error on line ${i + 1}: Incomplete arrow syntax` }],
|
|
108
|
+
warnings: [],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const warnings: { msg: string }[] = [];
|
|
116
|
+
if (strict && src.includes('shadow')) {
|
|
117
|
+
warnings.push({ msg: 'anti-pattern: shadow' });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { ok: true, errors: [], warnings };
|
|
121
|
+
}
|