diff --git a/skills/lark-slides/references/svg-slides/deck-design-reference-catalog.md b/skills/lark-slides/references/svg-slides/deck-design-reference-catalog.md index da9ecd7f..9c0c6da6 100644 --- a/skills/lark-slides/references/svg-slides/deck-design-reference-catalog.md +++ b/skills/lark-slides/references/svg-slides/deck-design-reference-catalog.md @@ -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. diff --git a/skills/lark-slides/references/svg-slides/validation/README.md b/skills/lark-slides/references/svg-slides/validation/README.md index c61c1420..2438ef5b 100644 --- a/skills/lark-slides/references/svg-slides/validation/README.md +++ b/skills/lark-slides/references/svg-slides/validation/README.md @@ -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 diff --git a/skills/lark-slides/scripts/svg_slides_bundle.mjs b/skills/lark-slides/scripts/svg_slides_bundle.mjs index 1469bbad..49b9fa9e 100755 --- a/skills/lark-slides/scripts/svg_slides_bundle.mjs +++ b/skills/lark-slides/scripts/svg_slides_bundle.mjs @@ -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"), diff --git a/skills/lark-slides/scripts/svg_slides_bundle_test.mjs b/skills/lark-slides/scripts/svg_slides_bundle_test.mjs index c0506f60..0f7a3788 100644 --- a/skills/lark-slides/scripts/svg_slides_bundle_test.mjs +++ b/skills/lark-slides/scripts/svg_slides_bundle_test.mjs @@ -25,6 +25,11 @@ const validSlide = ` + + +`; + 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"); +}); diff --git a/skills/lark-slides/scripts/svg_slides_extract_references.mjs b/skills/lark-slides/scripts/svg_slides_extract_references.mjs index 2ee291de..cec2c6ed 100755 --- a/skills/lark-slides/scripts/svg_slides_extract_references.mjs +++ b/skills/lark-slides/scripts/svg_slides_extract_references.mjs @@ -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", "", diff --git a/skills/lark-slides/scripts/svg_slides_extract_references_test.mjs b/skills/lark-slides/scripts/svg_slides_extract_references_test.mjs index df195a45..88cfdf2f 100644 --- a/skills/lark-slides/scripts/svg_slides_extract_references_test.mjs +++ b/skills/lark-slides/scripts/svg_slides_extract_references_test.mjs @@ -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); }); diff --git a/skills/lark-slides/scripts/svg_slides_prompt_trace.mjs b/skills/lark-slides/scripts/svg_slides_prompt_trace.mjs index 4dc1b199..aa4e7425 100755 --- a/skills/lark-slides/scripts/svg_slides_prompt_trace.mjs +++ b/skills/lark-slides/scripts/svg_slides_prompt_trace.mjs @@ -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: [] }; } diff --git a/skills/lark-slides/scripts/svg_slides_prompt_trace_test.mjs b/skills/lark-slides/scripts/svg_slides_prompt_trace_test.mjs index cf3998fe..8278e9c0 100644 --- a/skills/lark-slides/scripts/svg_slides_prompt_trace_test.mjs +++ b/skills/lark-slides/scripts/svg_slides_prompt_trace_test.mjs @@ -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();