@markdstage/markdstage 3.1.0 → 3.3.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/package.json +1 -1
- package/shared/README.md +61 -9
- package/shared/architecture-editor/editor.js +1 -1
- package/shared/architecture-reference.mjs +150 -0
- package/shared/architecture-validation.mjs +309 -0
- package/shared/markdstage-guide.mjs +62 -142
- package/shared/renderer/architecture-contract.mjs +13707 -0
- package/shared/renderer/architecture-diagnostics.mjs +426 -0
- package/shared/renderer/architecture-scene.mjs +312 -0
- package/shared/renderer/architecture.mjs +221 -217
- package/shared/renderer/index.html +9 -1
- package/shared/renderer/mermaid-scene.mjs +1248 -0
- package/shared/renderer/renderer.js +429 -232
- package/shared/renderer/scene-graph.mjs +739 -0
- package/shared/renderer/scene-pptx.mjs +265 -0
- package/shared/renderer/scene-svg.mjs +321 -0
- package/shared/renderer/slides.css +23 -0
- package/shared/renderer/theme.mjs +54 -0
- package/shared/runtime/architecture-editor-server.mjs +5 -0
- package/shared/runtime/architecture-source.mjs +9 -1
- package/shared/runtime/pptx-package.mjs +4 -3
- package/shared/schema/README.md +60 -5
- package/shared/schema/architecture-contract.mjs +355 -0
- package/shared/scripts/generate-architecture-contract.mjs +41 -0
- package/src/commands/validate.mjs +39 -3
- package/src/runtime.mjs +4 -0
- package/src/skills.mjs +6 -2
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import {
|
|
4
|
+
import { architectureSchemaReference } from "./architecture-reference.mjs";
|
|
5
|
+
import {
|
|
6
|
+
UNCLOSED_ARCHITECTURE_MESSAGE,
|
|
7
|
+
validateArchitectureInput,
|
|
8
|
+
} from "./architecture-validation.mjs";
|
|
5
9
|
|
|
6
10
|
const EXT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
7
11
|
const README_PATH = join(EXT_DIR, "README.md");
|
|
8
|
-
const SCHEMA_PATH = join(EXT_DIR, "schema", "architecture-v1.schema.json");
|
|
9
12
|
const THEME_GUIDE_PATH = join(EXT_DIR, "docs", "custom-theme-authoring.md");
|
|
10
13
|
const THEME_SCHEMA_PATH = join(EXT_DIR, "schema", "theme-v1.json");
|
|
11
|
-
const GUIDE_POINTER =
|
|
12
|
-
"Use the markdstage_guide tool to review the format and schemas before using the MarkdStage canvas.";
|
|
13
|
-
const PRESENTATION_PROMPT =
|
|
14
|
-
/\bpresent(?:ation|er|ing)?\b|\bslides?\b|slides?\.md|\bdeck\b/i;
|
|
15
14
|
|
|
16
15
|
function section(markdown, heading) {
|
|
17
16
|
const lines = markdown.split(/\r?\n/);
|
|
@@ -38,44 +37,6 @@ function section(markdown, heading) {
|
|
|
38
37
|
return lines.slice(start, end).join("\n").trim();
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
function architectureSchemaSummary(schema) {
|
|
42
|
-
const defs = schema.$defs;
|
|
43
|
-
const summary = {
|
|
44
|
-
root: {
|
|
45
|
-
required: schema.required,
|
|
46
|
-
properties: Object.keys(schema.properties),
|
|
47
|
-
elementTypes: [
|
|
48
|
-
defs.nodeBase.properties.type.const,
|
|
49
|
-
defs.groupBase.properties.type.const,
|
|
50
|
-
defs.connector.properties.type.const,
|
|
51
|
-
],
|
|
52
|
-
},
|
|
53
|
-
shape: defs.nodeBase.properties.shape.enum,
|
|
54
|
-
icon: {
|
|
55
|
-
builtIn: defs.iconName.enum,
|
|
56
|
-
assetPath: defs.iconAsset.description,
|
|
57
|
-
},
|
|
58
|
-
connector: {
|
|
59
|
-
labelLayer: defs.connector.properties.labelLayer,
|
|
60
|
-
},
|
|
61
|
-
style: {
|
|
62
|
-
keys: Object.keys(defs.style.properties),
|
|
63
|
-
colors: defs.color.description,
|
|
64
|
-
themeTokens: defs.themeToken.enum,
|
|
65
|
-
literalColors: defs.literalColor.description,
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
return [
|
|
69
|
-
"# Architecture DSL v1 schema summary",
|
|
70
|
-
"",
|
|
71
|
-
"Key details extracted at runtime from the bundled `schema/architecture-v1.schema.json`.",
|
|
72
|
-
"",
|
|
73
|
-
"```json",
|
|
74
|
-
JSON.stringify(summary, null, 2),
|
|
75
|
-
"```",
|
|
76
|
-
].join("\n");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
40
|
function themeSchemaSummary(schema) {
|
|
80
41
|
return [
|
|
81
42
|
"# Custom presentation theme v1 schema",
|
|
@@ -103,6 +64,7 @@ function themeSchemaSummary(schema) {
|
|
|
103
64
|
}
|
|
104
65
|
|
|
105
66
|
export async function readGuide(topic = "overview") {
|
|
67
|
+
if (topic === "architecture-schema") return architectureSchemaReference();
|
|
106
68
|
const readme = await readFile(README_PATH, "utf8");
|
|
107
69
|
switch (topic) {
|
|
108
70
|
case "overview":
|
|
@@ -111,6 +73,7 @@ export async function readGuide(topic = "overview") {
|
|
|
111
73
|
"",
|
|
112
74
|
"Users can load workspace Markdown directly with **More controls > Open Markdown** (deterministic splitting without AI; natural-language summarization remains the AI's responsibility). The workspace root is the Git repository root when available, otherwise the folder opened for the current session.",
|
|
113
75
|
"Use **More controls > Shape editing** to adjust the placement of an existing Architecture diagram. In the CLI, run `markdstage preview slides.md --watch`; it starts in viewing mode and enables the same placement editor plus the detailed Architecture designer. CLI `preview` without `--watch` is read-only. Comprehensive edits affect the source Markdown only when explicitly saved.",
|
|
76
|
+
"Before drafting Architecture DSL, request `architecture-schema` for the generated authoring contract. Before displaying a diagram, call `markdstage_validate` with explicit `format: \"dsl\"` and `source`, or `format: \"slides\"` and one-slide `slides` fragments. This read-only preflight needs no open canvas and does not read or change files.",
|
|
114
77
|
"",
|
|
115
78
|
"For details, request `slide-format`, `themes`, `custom-themes`, `theme-schema`, `architecture-dsl`, or `architecture-schema`.",
|
|
116
79
|
].join("\n");
|
|
@@ -133,71 +96,17 @@ export async function readGuide(topic = "overview") {
|
|
|
133
96
|
}
|
|
134
97
|
case "architecture-dsl":
|
|
135
98
|
return section(readme, "## Architecture DSL v1");
|
|
136
|
-
case "architecture-schema": {
|
|
137
|
-
const schema = JSON.parse(await readFile(SCHEMA_PATH, "utf8"));
|
|
138
|
-
return architectureSchemaSummary(schema);
|
|
139
|
-
}
|
|
140
99
|
default:
|
|
141
100
|
throw new Error(`unknown MarkdStage guide topic: ${topic}`);
|
|
142
101
|
}
|
|
143
102
|
}
|
|
144
103
|
|
|
145
|
-
export function createMarkdStageHooks() {
|
|
146
|
-
const primed = new Set();
|
|
147
|
-
return {
|
|
148
|
-
onSessionStart: ({ sessionId }) => {
|
|
149
|
-
primed.delete(sessionId);
|
|
150
|
-
},
|
|
151
|
-
onSessionEnd: ({ sessionId }) => {
|
|
152
|
-
primed.delete(sessionId);
|
|
153
|
-
},
|
|
154
|
-
onUserPromptSubmitted: ({ sessionId, prompt }) => {
|
|
155
|
-
if (primed.has(sessionId) || !PRESENTATION_PROMPT.test(prompt ?? "")) return;
|
|
156
|
-
primed.add(sessionId);
|
|
157
|
-
return { additionalContext: GUIDE_POINTER };
|
|
158
|
-
},
|
|
159
|
-
onPostToolUse: ({ sessionId, toolName }) => {
|
|
160
|
-
if (toolName === "markdstage_guide") primed.add(sessionId);
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
104
|
export function hasFrontMatter(markdown) {
|
|
166
105
|
const normalized = markdown.replace(/\r\n?/g, "\n").replace(/^[\n \t\uFEFF]+/, "");
|
|
167
106
|
if (!normalized.startsWith("---\n")) return false;
|
|
168
107
|
return normalized.split("\n").slice(1).some((line) => line.trim() === "---");
|
|
169
108
|
}
|
|
170
109
|
|
|
171
|
-
function architectureSources(markdown) {
|
|
172
|
-
const sources = [];
|
|
173
|
-
let fence = "";
|
|
174
|
-
let lines = [];
|
|
175
|
-
for (const line of markdown.split(/\r?\n/)) {
|
|
176
|
-
if (!fence) {
|
|
177
|
-
const opening = line.match(/^\s*(`{3,}|~{3,})architecture\s*$/i);
|
|
178
|
-
if (opening) {
|
|
179
|
-
fence = opening[1];
|
|
180
|
-
lines = [];
|
|
181
|
-
}
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
const closing = new RegExp(`^${fence[0]}{${fence.length},}\\s*$`);
|
|
185
|
-
if (closing.test(line.trim())) {
|
|
186
|
-
sources.push(lines.join("\n"));
|
|
187
|
-
fence = "";
|
|
188
|
-
lines = [];
|
|
189
|
-
} else {
|
|
190
|
-
lines.push(line);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return {
|
|
194
|
-
sources,
|
|
195
|
-
unclosed: Boolean(fence),
|
|
196
|
-
unclosedBlockIndex: sources.length,
|
|
197
|
-
unclosedSource: fence ? lines.join("\n") : "",
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
110
|
function architectureError(slideIndex, blockIndex, code, message) {
|
|
202
111
|
return {
|
|
203
112
|
slideIndex,
|
|
@@ -209,56 +118,59 @@ function architectureError(slideIndex, blockIndex, code, message) {
|
|
|
209
118
|
};
|
|
210
119
|
}
|
|
211
120
|
|
|
212
|
-
export function
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
121
|
+
export function architectureValidationReport(slides, { index, maxDiagnostics } = {}) {
|
|
122
|
+
if (index !== undefined &&
|
|
123
|
+
(!Number.isInteger(index) || index < 0 || index >= slides.length)) {
|
|
124
|
+
throw new RangeError("index must identify a slide in the provided array.");
|
|
125
|
+
}
|
|
126
|
+
const report = validateArchitectureInput({
|
|
127
|
+
format: "slides",
|
|
128
|
+
slides: index === undefined ? slides : [slides[index]],
|
|
129
|
+
...(maxDiagnostics === undefined ? {} : { maxDiagnostics }),
|
|
130
|
+
});
|
|
131
|
+
if (index === undefined) return report;
|
|
132
|
+
const rebase = (item) => typeof item.slideIndex === "number"
|
|
133
|
+
? { ...item, slideIndex: item.slideIndex + index, page: item.page + index }
|
|
134
|
+
: item;
|
|
135
|
+
return {
|
|
136
|
+
...report,
|
|
137
|
+
scope: "slide",
|
|
138
|
+
index,
|
|
139
|
+
page: index + 1,
|
|
140
|
+
total: slides.length,
|
|
141
|
+
diagnostics: report.diagnostics.map(rebase),
|
|
142
|
+
blocks: report.blocks.map(rebase),
|
|
143
|
+
skipped: report.skipped.map(rebase),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
217
146
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
);
|
|
147
|
+
export function architectureValidationErrors(slides, { index, validation } = {}) {
|
|
148
|
+
if (Array.isArray(slides) && slides.length === 0 && index === undefined && !validation) return [];
|
|
149
|
+
const report = validation ?? architectureValidationReport(slides, { index });
|
|
150
|
+
const errors = [];
|
|
151
|
+
for (const block of report.blocks) {
|
|
152
|
+
if (!block.dslValid) {
|
|
153
|
+
const primary = report.diagnostics
|
|
154
|
+
.slice(block.diagnosticStart, block.diagnosticStart + block.diagnosticCount)
|
|
155
|
+
.find((diagnostic) => diagnostic.severity === "error");
|
|
156
|
+
if (primary) {
|
|
157
|
+
errors.push(architectureError(
|
|
158
|
+
block.slideIndex, block.blockIndex, "invalid_architecture", primary.message,
|
|
159
|
+
));
|
|
232
160
|
}
|
|
233
161
|
}
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
architectureError(
|
|
240
|
-
slideIndex,
|
|
241
|
-
architecture.unclosedBlockIndex,
|
|
242
|
-
"invalid_architecture",
|
|
243
|
-
error?.message || String(error),
|
|
244
|
-
),
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
errors.push(
|
|
248
|
-
architectureError(
|
|
249
|
-
slideIndex,
|
|
250
|
-
architecture.unclosedBlockIndex,
|
|
251
|
-
"unclosed_architecture_fence",
|
|
252
|
-
"The architecture code fence is not closed. Add ``` at the end.",
|
|
253
|
-
),
|
|
254
|
-
);
|
|
162
|
+
if (block.closed === false) {
|
|
163
|
+
errors.push(architectureError(
|
|
164
|
+
block.slideIndex, block.blockIndex, "unclosed_architecture_fence",
|
|
165
|
+
UNCLOSED_ARCHITECTURE_MESSAGE,
|
|
166
|
+
));
|
|
255
167
|
}
|
|
256
168
|
}
|
|
257
|
-
|
|
258
169
|
return errors;
|
|
259
170
|
}
|
|
260
171
|
|
|
261
|
-
export function deckValidationFeedback(slides) {
|
|
172
|
+
export function deckValidationFeedback(slides, { validation } = {}) {
|
|
173
|
+
if (Array.isArray(slides) && slides.length === 0 && !validation) return undefined;
|
|
262
174
|
const warnings = [];
|
|
263
175
|
slides.forEach((slide, slideIndex) => {
|
|
264
176
|
if (!hasFrontMatter(slide)) {
|
|
@@ -267,7 +179,8 @@ export function deckValidationFeedback(slides) {
|
|
|
267
179
|
);
|
|
268
180
|
}
|
|
269
181
|
});
|
|
270
|
-
|
|
182
|
+
const report = validation ?? architectureValidationReport(slides);
|
|
183
|
+
for (const error of report.diagnostics) {
|
|
271
184
|
if (error.code === "unclosed_architecture_fence") {
|
|
272
185
|
warnings.push(
|
|
273
186
|
`slide ${error.page}: ${error.message}`,
|
|
@@ -275,7 +188,14 @@ export function deckValidationFeedback(slides) {
|
|
|
275
188
|
continue;
|
|
276
189
|
}
|
|
277
190
|
warnings.push(
|
|
278
|
-
`slide ${error.page}
|
|
191
|
+
`${error.page ? `slide ${error.page}` : "Architecture validation"}${error.architecture ? `, architecture ${error.architecture}` : ""}: ${error.message} [${error.code}${error.pointer ? ` at ${error.pointer}` : ""}]. Review architecture-dsl and architecture-schema in markdstage_guide.`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (!report.complete) {
|
|
195
|
+
warnings.push(
|
|
196
|
+
report.truncated
|
|
197
|
+
? `Architecture validation is incomplete: inspection limits were reached (${report.budget.limitsReached.join(", ")}). Validate smaller inputs; unchecked blocks are not valid.`
|
|
198
|
+
: "Architecture validation is incomplete: later stages were skipped after earlier errors. Fix the reported issues and validate again.",
|
|
279
199
|
);
|
|
280
200
|
}
|
|
281
201
|
return warnings.length ? `Slide validation feedback:\n- ${warnings.join("\n- ")}` : undefined;
|