wand-decks-kit 0.8.6 → 0.8.7

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/SKILL.md CHANGED
@@ -4,7 +4,7 @@ description: Rebrand, merge, edit or author a PowerPoint deck on the Wand design
4
4
  compatibility: Requires Node.js 18+ and code execution enabled in claude.ai (Settings > Features). No network access needed - dependencies are bundled. Python 3 is used to read a source .pptx and to embed fonts; without it the kit can still author and check a deck, but the output will render in Arial on machines without Geist.
5
5
  license: Proprietary - Wand AI
6
6
  metadata:
7
- version: "0.7.0"
7
+ version: "0.8.7"
8
8
  ---
9
9
 
10
10
 
@@ -61,7 +61,16 @@ node wand.js doctor
61
61
 
62
62
  If `node_modules` is somehow missing, `npm install` in this directory.
63
63
 
64
- ## The four jobs
64
+ ## Before any build: show the plan, then wait
65
+
66
+ Write the slide-by-slide mapping in the chat **before** writing a line of JSON:
67
+ `S07 (timeline, 6 phases) → phaseCards`, one line per slide, with the reason
68
+ where it is not obvious. Then build. A field run skipped this, built the wrong
69
+ thing, and paid a full second pass. This is not optional for a rebuild, a merge
70
+ or an adaptation; only "author from a brief" with fewer than eight slides may
71
+ go straight to the spec.
72
+
73
+ ## The five jobs
65
74
 
66
75
  ### Rebrand a deck they attached
67
76
 
@@ -89,6 +98,24 @@ to cover — and it says so.
89
98
  spec. Ask which deck leads if they have not said. Flag duplicates rather than
90
99
  silently dropping them, and list every merge in the review note.
91
100
 
101
+ ### Adapt a deck and keep its graphics
102
+
103
+ The ask sounds like a rebrand but is not: *"take our standard deck, drop in an
104
+ agenda, a team slide and a demo placeholder, keep everything else as is."* They
105
+ want the source's own slides and art preserved, with a few slides retexted or
106
+ added. **This kit re-authors from a spec; it cannot copy a source slide's
107
+ shapes.** Say that in one line, then edit the source `.pptx` directly with the
108
+ pptx tools — and still run this kit's gates on the result:
109
+
110
+ ```bash
111
+ node wand.js audit <edited-deck>.pptx # file, pdf text, renders + contact sheet
112
+ node wand.js audit <edited-deck>.pptx --dump work/visible_dump.txt # + coverage
113
+ ```
114
+
115
+ `audit` runs every gate that does not need a spec. A deck delivered without it
116
+ has been checked by nobody. Say which slides were lifted unchanged, which were
117
+ retexted, and that the source's fonts (not Geist) carry through.
118
+
92
119
  ### Edit a deck this kit built
93
120
 
94
121
  ```bash
@@ -139,6 +166,14 @@ repeatedly.
139
166
  4. **Icons are named.** `node wand.js glyphs` lists all 75. Pick by meaning.
140
167
  They recolour to the theme, so there is no polarity to get wrong.
141
168
  5. **Never invent content.** If the source is ambiguous, ask.
169
+ 6. **A FAIL in the gate table is not shippable.** Not "acceptable two-line
170
+ wraps", not "probably fine" — either fix it and re-run, or list each failing
171
+ item with its measurement in the review note and let the user decide. A
172
+ field run shipped on `FAIL fit + bounds` with a reassurance instead of a
173
+ list; the reassurance was not checked against anything.
174
+ 7. **Verbatim applies to rebuilds, merges and lifted slides.** When the job is
175
+ to author or retext a slide, say which slides were retexted — the review
176
+ note separates *lifted verbatim* from *rewritten*.
142
177
 
143
178
  ## Useful commands
144
179
 
@@ -150,6 +185,7 @@ repeatedly.
150
185
  | `node wand.js check <spec>` | fit in real Geist, bounds, assets |
151
186
  | `node wand.js preview <spec>` | writes `preview/trace.js` for the browser preview |
152
187
  | `node wand.js inspect <deck>` | parts, relationships, embedded fonts |
188
+ | `node wand.js audit <deck> [--dump f]` | every spec-free gate on any .pptx — use on decks edited outside the kit |
153
189
 
154
190
  ## Deliver
155
191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wand-decks-kit",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "description": "Rebuilds or authors a client-ready .pptx on the Wand design system: measured fit, embedded Geist, verbatim content.",
5
5
  "license": "SEE LICENSE IN PROMPT.md",
6
6
  "bin": {
package/wand.js CHANGED
@@ -56,6 +56,7 @@ function runPython(args) {
56
56
  function doctor() {
57
57
  const rows = [];
58
58
  const py = python();
59
+ rows.push(["wand-decks-kit", require("./wand_kit").VERSION, "ok"]);
59
60
  rows.push(["node", process.version, "ok"]);
60
61
  rows.push(["python3", py ? execFileSync(py, ["-V"], { encoding: "utf8" }).trim() : "MISSING",
61
62
  py ? "ok" : "extract and font embedding unavailable"]);
@@ -253,6 +254,70 @@ function ship(specPath, outArg) {
253
254
  return gates.some(([, ok]) => ok === false) ? 1 : 0;
254
255
  }
255
256
 
257
+ /**
258
+ * Every gate that does not need a spec, on any .pptx.
259
+ *
260
+ * A field run adapted a source deck by editing it directly — the right call,
261
+ * this kit cannot copy a source slide's shapes — and then delivered it with
262
+ * no gate but a generic validator, because `ship` wants a spec. Now the
263
+ * spec-free gates run on their own: file structure and fonts, the PDF text
264
+ * layer, a render of every slide with the contact sheet, and coverage when a
265
+ * verbatim dump is given. LibreOffice does the render, so fit is *not* judged
266
+ * here (§9 — DejaVu is wider than Geist); geometry, overlap and contrast are.
267
+ */
268
+ function audit(file, dump) {
269
+ const work = path.join(path.dirname(file), "audit");
270
+ fs.mkdirSync(work, { recursive: true });
271
+ console.log(rule("1 file"));
272
+ const fileErrs = inspect(file);
273
+
274
+ console.log(rule("2 render"));
275
+ let shots = 0, garbled = null, pdf = null;
276
+ // on PATH in a sandbox; an app bundle on a Mac
277
+ const soffice = ["soffice", "libreoffice"].find((c) => has(c))
278
+ || ["/Applications/LibreOffice.app/Contents/MacOS/soffice"].find((p) => fs.existsSync(p)) || null;
279
+ if (soffice) {
280
+ spawnSync(soffice, ["--headless", "--convert-to", "pdf", "--outdir", work, file], { stdio: "ignore" });
281
+ pdf = path.join(work, path.basename(file).replace(/\.pptx$/i, ".pdf"));
282
+ }
283
+ if (pdf && fs.existsSync(pdf) && has("pdftoppm")) {
284
+ const dir = path.join(work, "slides");
285
+ fs.mkdirSync(dir, { recursive: true });
286
+ spawnSync("pdftoppm", ["-r", "96", "-png", pdf, path.join(dir, "slide")], { stdio: "ignore" });
287
+ shots = fs.readdirSync(dir).filter((f) => f.startsWith("slide")).length;
288
+ const small = fs.mkdtempSync(path.join(require("os").tmpdir(), "wand-sheet-"));
289
+ spawnSync("pdftoppm", ["-r", "26", "-png", pdf, path.join(small, "s")], { stdio: "ignore" });
290
+ const sheet = path.join(dir, "sheet.png");
291
+ runPython([path.join(KIT, "tools", "sheet.py"), small, sheet]);
292
+ fs.rmSync(small, { recursive: true, force: true });
293
+ console.log(`rendered : ${shots} slide(s) — open ${rel(sheet)} (LibreOffice: trust geometry,`);
294
+ console.log(` overlap and contrast; do NOT judge fit or weight from it)`);
295
+ if (has("pdftotext")) {
296
+ const r = spawnSync("pdftotext", [pdf, "-"], { encoding: "utf8" });
297
+ if (r.status === 0) {
298
+ garbled = 0;
299
+ for (const c of r.stdout || "") { const cp = c.codePointAt(0); if (cp >= 0x01c9 && cp <= 0x024f) garbled++; }
300
+ console.log(garbled ? `text layer : ${garbled} garbled glyph(s) — fonts are subsetted` : "text layer : digits decode clean");
301
+ }
302
+ }
303
+ } else {
304
+ console.log(soffice ? "pdftoppm absent — no renders" : "no LibreOffice — no renders; open the deck and look at every slide");
305
+ }
306
+
307
+ console.log(rule("3 coverage"));
308
+ let cov = null;
309
+ if (dump && fs.existsSync(dump)) cov = runPython([path.join(KIT, "qa_coverage.py"), dump, file]);
310
+ else console.log("no dump given — skipped. Pass --dump work/visible_dump.txt from `rebuild` to check\n" +
311
+ "every source string survived the edit.");
312
+
313
+ const gates = [["file", fileErrs === 0], ["pdf text", garbled === null ? null : garbled === 0],
314
+ ["renders", shots > 0 ? true : null], ["coverage", cov === null ? null : cov === 0]];
315
+ console.log(rule("gates"));
316
+ for (const [n, ok] of gates) console.log(` ${ok === null ? "skip" : ok ? " ok " : "FAIL"} ${n}`);
317
+ console.log(" (fit is not gated on a deck built outside the kit — say so in the review note)\n");
318
+ return gates.some(([, ok]) => ok === false) ? 1 : 0;
319
+ }
320
+
256
321
  /**
257
322
  * File-level check, in-process. `validate.py` from the pptx skill is not in the
258
323
  * zip and LibreOffice may not be installed, so the kit carries its own: the
@@ -415,6 +480,12 @@ to.
415
480
  if (!positional[0]) die("usage: node wand.js inspect <deck.pptx>");
416
481
  process.exit(inspect(path.resolve(positional[0])) ? 1 : 0);
417
482
 
483
+ case "audit": {
484
+ if (!positional[0]) die("usage: node wand.js audit <deck.pptx> [--dump visible_dump.txt]");
485
+ const di = rest.indexOf("--dump");
486
+ process.exit(audit(path.resolve(positional[0]), di >= 0 ? path.resolve(rest[di + 1]) : null));
487
+ }
488
+
418
489
  case "ship":
419
490
  if (!positional[0]) die("usage: node wand.js ship <spec.json> [out.pptx]");
420
491
  process.exit(ship(positional[0], positional[1]));
package/wand_kit.js CHANGED
@@ -999,7 +999,7 @@ function productMap(pres, { tag, titleText, subtitleText, bandRow, cards, split,
999
999
 
1000
1000
 
1001
1001
  module.exports = {
1002
- VERSION: "0.8.6",
1002
+ VERSION: "0.8.7",
1003
1003
  T, F, COLS, ASSETS, radius, theme, autoSize, measure: M,
1004
1004
  background, chrome, footer, title, subtitle, kicker, contentTop,
1005
1005
  card, iconTile, pill,