fix: close svg slides reference audit gaps

This commit is contained in:
songtianyi.theo
2026-07-08 18:56:34 +08:00
parent f26042d000
commit eb460a23fa
8 changed files with 117 additions and 32 deletions

View File

@@ -2,7 +2,7 @@
## Context
- Role: reference
- Purpose: Narrative archetypes and depth rubric consumed by the design brief prompt.
- Source lines: 1082-1233
- Source lines: 1082-1234
## Content
# Deck design reference catalog
@@ -156,9 +156,10 @@ THREE QUICK DISAMBIGUATIONS (where decks wrongly converge to MID)
1. Same metric, different altitude: a board QBR shows ONE variance walk + the ask (high altitude / low density); the operating MOR behind it shows every driver four ways (low altitude / high density). Don't ship the MOR to the board or the QBR to ops.
2. Expert vs lay on the SAME topic (ai-hardtech-pitch vs ai-101): both about AI — one is eval tables + failure-mode rates for technical partners, the other bans benchmark charts entirely. Audience, not topic, sets altitude.
3. Pitch is NOT low-detail everywhere: stage demo-day is C1 (one sentence/slide), but the Series B/C IC pre-read is A-altitude self-read (cohort triangles, Rule-of-40, 20-page-memo-equivalent density). Same category, opposite depth — delivery_mode + audience decide.
```
## Source Coverage
- Covers manifest sections: deck_design_reference_catalog
- Source lines: 1082-1233
- Source lines: 1082-1234
- Coverage mode: extracted runtime-unit content, preserving executable prompt or contract boundaries.
- Omitted source-only artifacts: copied implementation filenames only.

View File

@@ -5,10 +5,10 @@ Use this directory for local validation and provenance audit routing.
## Read Routes
- Source coverage audit: `source-coverage.md`
- Protocol validation script: `../../scripts/validate_svg_deck.mjs`
- Bundle manifest script: `../../scripts/svg_slides_bundle.mjs`
- Browser text bounds script: `../../scripts/svg_slides_browser_text_bounds.mjs`
- Prompt read trace script: `../../scripts/svg_slides_prompt_trace.mjs`
- Protocol validation script: `../../../scripts/validate_svg_deck.mjs`
- Bundle manifest script: `../../../scripts/svg_slides_bundle.mjs`
- Browser text bounds script: `../../../scripts/svg_slides_browser_text_bounds.mjs`
- Prompt read trace script: `../../../scripts/svg_slides_prompt_trace.mjs`
## Source Slice

View File

@@ -14,6 +14,27 @@ function argValue(argv, name, fallback = "") {
return index >= 0 ? argv[index + 1] : fallback;
}
function deckUsesCharts(slideFiles) {
return slideFiles.some((file) => fs.readFileSync(file, "utf8").includes('slide:role="chart"'));
}
function promptTraceHasChartEvidence(promptTracePath) {
const receipt = JSON.parse(fs.readFileSync(promptTracePath, "utf8"));
const paths = new Set((receipt.read_order || []).map((entry) => entry.path));
const hasBaseChartReads = [
"generate-svg-chart/README.md",
"generate-svg-chart/input-schema.md",
"generate-svg-chart/chart-agent-contract.md",
"generate-svg-chart/design-envelope.md",
"generate-svg-chart/snippets/README.md"
].every((relPath) => paths.has(relPath));
const hasFallback = Array.isArray(receipt.fallbacks)
&& receipt.fallbacks.includes("generate-svg-chart/full.md")
&& paths.has("generate-svg-chart/full.md");
const hasSnippet = [...paths].some((relPath) => relPath.startsWith("generate-svg-chart/snippets/") && relPath !== "generate-svg-chart/snippets/README.md");
return hasBaseChartReads && (hasFallback || hasSnippet);
}
const args = process.argv.slice(2);
const deckArg = args.find((arg) => !arg.startsWith("--"));
const title = argValue(args, "--title");
@@ -43,6 +64,11 @@ if (receipt.totalErrors !== 0) {
fail(`SVG deck is not publish-ready: ${receipt.totalErrors} validation error(s)`, 1);
}
const slideFiles = fs.readdirSync(slidesDir)
.filter((file) => file.endsWith(".svg"))
.sort()
.map((file) => path.join(slidesDir, file));
const promptTracePath = path.join(root, "receipts", "prompt_read_trace.json");
if (!fs.existsSync(promptTracePath)) {
fail(`Missing prompt read trace receipt: ${promptTracePath}`, 1);
@@ -57,13 +83,12 @@ if (validateTrace.status !== 0) {
process.stderr.write(validateTrace.stderr || validateTrace.stdout);
process.exit(validateTrace.status || 1);
}
const slideFiles = fs.readdirSync(slidesDir)
.filter((file) => file.endsWith(".svg"))
.sort();
if (deckUsesCharts(slideFiles) && !promptTraceHasChartEvidence(promptTracePath)) {
fail("Prompt read trace is missing generate_svg_chart reads required by slide:role=\"chart\"", 1);
}
const pages = slideFiles.map((file, index) => {
const abs = path.join(slidesDir, file);
const abs = file;
const raw = fs.readFileSync(abs);
return {
id: path.basename(file, ".svg"),

View File

@@ -25,6 +25,11 @@ const validSlide = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:slide="https:/
</foreignObject>
</svg>`;
const chartSlide = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:slide="https://slides.bytedance.com/ns" slide:role="slide" id="chart_slide" viewBox="0 0 960 540">
<rect slide:role="background" x="0" y="0" width="960" height="540" fill="rgba(255,255,255,1)"/>
<rect slide:role="chart" href="resources/charts/example_bar.svg" x="80" y="120" width="640" height="360"/>
</svg>`;
function writeDoc(root, relPath, role, sourceLines = "1-1", body = "content") {
const filePath = path.join(root, relPath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
@@ -54,8 +59,8 @@ function writeChartReferenceSet(root) {
writeDoc(root, "generate-svg-chart/snippets/README.md", "reference", "1590-2354");
}
function createPromptTrace(root, refs) {
const result = spawnSync("node", [traceScript, root, "--task", "generate_deck", "--references", refs], { encoding: "utf8" });
function createPromptTrace(root, refs, extraArgs = []) {
const result = spawnSync("node", [traceScript, root, "--task", "generate_deck", "--references", refs, ...extraArgs], { encoding: "utf8" });
assert.equal(result.status, 0, result.stderr || result.stdout);
}
@@ -156,3 +161,29 @@ test("bundle builder rejects chart-only prompt trace for deck bundle", () => {
assert.match(result.stderr, /task mismatch/);
assert.equal(fs.existsSync(path.join(root, "manifest.json")), false);
});
test("bundle builder rejects chart deck without chart prompt reads", () => {
const root = tempDeck();
const refs = tempRoot("svg-slides-bundle-refs-");
writeDeckReferenceSet(refs);
fs.writeFileSync(path.join(root, "slides", "slide_01.svg"), chartSlide);
createPromptTrace(root, refs);
const result = spawnSync("node", [script, root, "--title", "Chart Without Reads", "--references", refs], { encoding: "utf8" });
assert.equal(result.status, 1);
assert.match(result.stderr, /missing generate_svg_chart reads/);
assert.equal(fs.existsSync(path.join(root, "manifest.json")), false);
});
test("bundle builder accepts chart deck with deck and chart prompt reads", () => {
const root = tempDeck();
const refs = tempRoot("svg-slides-bundle-refs-");
writeDeckReferenceSet(refs);
writeChartReferenceSet(refs);
fs.writeFileSync(path.join(root, "slides", "slide_01.svg"), chartSlide);
createPromptTrace(root, refs, ["--use-full-fallback"]);
const result = spawnSync("node", [script, root, "--title", "Chart With Reads", "--references", refs], { encoding: "utf8" });
assert.equal(result.status, 0, result.stderr || result.stdout);
const manifest = JSON.parse(fs.readFileSync(path.join(root, "manifest.json"), "utf8"));
assert.equal(manifest.publish_ready, true);
assert.equal(manifest.receipts.prompt_read_trace, "receipts/prompt_read_trace.json");
});

View File

@@ -175,9 +175,9 @@ function writeStandaloneDocs() {
title: "Deck Design Reference Catalog",
role: "reference",
purpose: "Narrative archetypes and depth rubric consumed by the design brief prompt.",
source: "1082-1233",
source: "1082-1234",
coverageIds: ["deck_design_reference_catalog"],
content: lineRange(1082, 1233)
content: lineRange(1082, 1234)
}));
}
@@ -410,10 +410,10 @@ function writeValidationDocs() {
"## Read Routes",
"",
"- Source coverage audit: `source-coverage.md`",
"- Protocol validation script: `../../scripts/validate_svg_deck.mjs`",
"- Bundle manifest script: `../../scripts/svg_slides_bundle.mjs`",
"- Browser text bounds script: `../../scripts/svg_slides_browser_text_bounds.mjs`",
"- Prompt read trace script: `../../scripts/svg_slides_prompt_trace.mjs`",
"- Protocol validation script: `../../../scripts/validate_svg_deck.mjs`",
"- Bundle manifest script: `../../../scripts/svg_slides_bundle.mjs`",
"- Browser text bounds script: `../../../scripts/svg_slides_browser_text_bounds.mjs`",
"- Prompt read trace script: `../../../scripts/svg_slides_prompt_trace.mjs`",
"",
"## Source Slice",
"",

View File

@@ -31,6 +31,8 @@ test("extracts runtime-unit documents and removes obsolete summaries", () => {
assert.ok(fs.existsSync(path.join(root, "resolve-design-brief", "full.md")));
assert.ok(fs.existsSync(path.join(root, "generate-svg-chart", "full.md")));
assert.match(fs.readFileSync(path.join(root, "tools", "slides-edit.md"), "utf8"), /- Role: tool_contract/);
assert.match(fs.readFileSync(path.join(root, "deck-design-reference-catalog.md"), "utf8"), /- Source lines: 1082-1234/);
assert.match(fs.readFileSync(path.join(root, "validation", "README.md"), "utf8"), /\.\.\/\.\.\/\.\.\/scripts\/validate_svg_deck\.mjs/);
assert.equal(fs.existsSync(path.join(root, "workflow.md")), false);
assert.equal(fs.existsSync(path.join(root, "protocol.md")), false);
});

View File

@@ -78,20 +78,28 @@ function requiredReads(referenceRoot, task, chartType, useFullFallback) {
}
if (task === "generate_deck") {
const entries = [
["README.md", "root routing entrypoint"],
["system-prompt.md", "main workflow"],
["svg-reference.md", "SVG schema and examples"],
["deck-design-reference-catalog.md", "narrative archetypes and depth rubric"],
["resolve-design-brief/README.md", "design brief read path"],
["resolve-design-brief/full.md", "design brief prompt bundle"],
["tools/slide-outline.md", "outline tool contract"],
["tools/activate-slides-edit.md", "activate edit mode contract"],
["tools/slides-edit.md", "SVG authoring tool contract"],
["tools/finish-slides-edit.md", "finish edit mode contract"],
["validation/prompt-read-trace.md", "prompt read trace receipt contract"]
];
if (useFullFallback || chartType) {
const chartReads = requiredReads(referenceRoot, "generate_chart", chartType, useFullFallback);
return {
entries: [...entries, ...chartReads.entries],
fallbacks: chartReads.fallbacks
};
}
return {
entries: [
["README.md", "root routing entrypoint"],
["system-prompt.md", "main workflow"],
["svg-reference.md", "SVG schema and examples"],
["deck-design-reference-catalog.md", "narrative archetypes and depth rubric"],
["resolve-design-brief/README.md", "design brief read path"],
["resolve-design-brief/full.md", "design brief prompt bundle"],
["tools/slide-outline.md", "outline tool contract"],
["tools/activate-slides-edit.md", "activate edit mode contract"],
["tools/slides-edit.md", "SVG authoring tool contract"],
["tools/finish-slides-edit.md", "finish edit mode contract"],
["validation/prompt-read-trace.md", "prompt read trace receipt contract"]
],
entries,
fallbacks: []
};
}

View File

@@ -88,6 +88,24 @@ test("uses full fallback when snippet routing is uncertain", () => {
assert.deepEqual(receipt.fallbacks, ["generate-svg-chart/full.md"]);
});
test("generate_deck can include chart fallback reads when deck uses charts", () => {
const refs = tempRoot();
const deck = tempRoot();
writeDeckReferenceSet(refs);
writeChartReferenceSet(refs);
const result = run([deck, "--task", "generate_deck", "--references", refs, "--use-full-fallback"]);
assert.equal(result.status, 0, result.stderr || result.stdout);
const receipt = readReceipt(deck);
const paths = receipt.read_order.map((entry) => entry.path);
assert.equal(receipt.task, "generate_deck");
assert.ok(paths.includes("system-prompt.md"));
assert.ok(paths.includes("generate-svg-chart/input-schema.md"));
assert.ok(paths.includes("generate-svg-chart/full.md"));
assert.deepEqual(receipt.fallbacks, ["generate-svg-chart/full.md"]);
const validate = run([deck, "--validate", "--task", "generate_deck", "--references", refs]);
assert.equal(validate.status, 0, validate.stderr || validate.stdout);
});
test("fails when a required prompt file is missing", () => {
const refs = tempRoot();
const deck = tempRoot();