From 466493ed80901f5e8bebacee75cd32d32500810f Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:38:50 +0000 Subject: [PATCH] generate the capability exemplar table so the answer stops being remembered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Which bundle is the capability-N example?" has been answered wrongly in three separate artifacts, each fixed in isolation while the others kept the bug: verify-5-capabilities cited doxa-vision-card as cap-1, the README made the identical claim, and the /plugin-designer skill still cites both that and unified-research-dashboard, a bundle that does not exist. The skill cannot be gated from this repo. It lives in the session config directory, in no git repo, so a checker pointed at it would pass here and skip silently everywhere else — the gate-that-cannot-fail shape. So instead of policing the skill, give the repo an authoritative generated answer that prose can cite and a gate can verify. The table derives from manifest.json, which the build computed from each folder's real shape, and reference.md now points at it with the reason: a folder's apparent simplicity does not decide its tier. doxa-vision-card looks like the lightest thing in the repo and is capability 2, because it ships an instances file. check:capability-exemplars regenerates and content-compares rather than checking mtimes, because a full build rewrites manifest.json every time and a gate that cries wolf after every build gets switched off. --- .forgejo/workflows/build.yml | 6 ++ docs/reference-capability-exemplars.md | 68 ++++++++++++++ docs/reference.md | 8 ++ internal/check-capability-exemplars.mjs | 64 +++++++++++++ internal/generate-capability-exemplars.mjs | 101 +++++++++++++++++++++ package.json | 2 + 6 files changed, 249 insertions(+) create mode 100644 docs/reference-capability-exemplars.md create mode 100644 internal/check-capability-exemplars.mjs create mode 100644 internal/generate-capability-exemplars.mjs diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index af4ef246e..7242660fb 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -177,6 +177,12 @@ jobs: - name: Assert README capability citations match the manifest run: bun run check:capability-citations + # Regenerates the derived exemplar table and content-compares it. Blocking: the build just + # produced the manifest it derives from, so a mismatch is always real. Content-compare, not + # mtime — a full build rewrites manifest.json every time. + - name: Assert the capability exemplar table matches the manifest + run: bun run check:capability-exemplars + - name: Assert no local credential is being published run: | if [ -e public/token.local.js ]; then diff --git a/docs/reference-capability-exemplars.md b/docs/reference-capability-exemplars.md new file mode 100644 index 000000000..86807cd69 --- /dev/null +++ b/docs/reference-capability-exemplars.md @@ -0,0 +1,68 @@ +# Reference — capability exemplars (GENERATED) + +> **Do not hand-edit.** Regenerate with `bun run capability-exemplars`; `bun run +> check:capability-exemplars` fails if this file falls behind the manifest. +> +> This answers one question authoritatively: **which bundles are the capability-N +> examples right now?** It is derived from `public/doxa-maps/manifest.json`, which is what +> the build computed from each folder's real shape — so it cannot drift from what ships. +> +> **Cite this file rather than remembering.** The same citation has been wrong in three +> separate places: a gate, the README, and the `/plugin-designer` skill. The trap is that a +> folder's apparent simplicity does not decide its tier — an `*.instances.json` makes a +> bundle `parameterized` (cap-2) no matter how light it looks, and a `meta.json` `type` +> declares tiers 3/4/5. + +Generated from 17 bundle(s). + +## Capability 1 — single-element + +| Bundle | Profile | Machine key | +|---|---|---| +| `all-people-groups` | `all-people-groups` | `single` | +| `cities-internet` | `connectivity-list` | `single` | +| `cities-internet` | `connectivity-map` | `single` | +| `cities-internet` | `data-tables` | `single` | +| `country-posters` | `countries-map` | `single` | +| `country-posters` | `country-poster-wall` | `single` | +| `gospel-resources` | `charts` | `single` | +| `gospel-resources` | `data-tables` | `single` | +| `gospel-resources` | `resources-list` | `single` | +| `gospel-resources` | `resources-map` | `single` | +| `my-upg-100-list` | `poster-wall` | `single` | +| `my-upg-100-list` | `upg-100-list` | `single` | +| `template-bundle` | `template-map-a` | `single` | +| `template-bundle` | `template-map-b` | `single` | + +## Capability 2 — preset-element (prop-table) + +| Bundle | Profile | Machine key | +|---|---|---| +| `demo-map` | `demo-map` | `parameterized` | +| `doxa-simple-map` | `doxa-simple-map` | `parameterized` | +| `doxa-vision-card` | `doxa-vision-card` | `parameterized` | +| `people-groups-grid` | `people-groups-grid` | `parameterized` | + +## Capability 3 — multi-view-element (monolithic) + +| Bundle | Profile | Machine key | +|---|---|---| +| `doxa-research-map` | `research-map` | `monolithic` | +| `research-dashboard` | `research-dashboard` | `monolithic` | + +## Capability 4 — composite-element (nested-element SPA) + +| Bundle | Profile | Machine key | +|---|---|---| +| `big-picture` | `big-picture` | `nested-elements-mfe` | +| `dashboard-plus-grid` | `dashboard-plus-grid` | `nested-elements-mfe` | +| `my-upg-100-list` | `upg-100-mfe` | `nested-elements-mfe` | +| `stack-demo` | `stack-demo` | `nested-elements-mfe` | + +## Capability 5 — app-shell (nested-SPA) + +| Bundle | Profile | Machine key | +|---|---|---| +| `doxa-maps-spa` | `doxa-maps-spa` | `nested-mfe-spa` | +| `plugin-studio` | `plugin-studio` | `nested-mfe-spa` | + diff --git a/docs/reference.md b/docs/reference.md index ad9ec1db6..72f6ecfa0 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -44,6 +44,14 @@ this repo's own; the coined names stay as a.k.a.): | **composite-element** (nested-elements-mfe) | `nested-elements-mfe` | an app profile whose template places other app profiles' maps together (stacked or side by side, e.g. a map + a poster wall) and ships the set as one new element — one tag embeds it all | declared: `.meta.json` → `{ "type": "nested-elements-mfe", "composes": [ … ] }` (or `"composite-element"`) | | **app-shell** (nested-mfe-spa) | `nested-mfe-spa` | the outer frame — sidebar, header, navigation — that loads any of the other kinds and switches between them without a reload; the top rung, the full doxa-maps app | a compiled Vue custom element **today** — `plugin-designs/doxa-maps-spa/doxa-maps-spa.vue`, built via `defineCustomElement` like every other rung (its shell layout is `library/layouts/app-wrapper-material/`). `app-wrapper-material/index.html` is now only the dev-time plugin-gallery scaffold, not the shipped shell | +> **Which bundle is the capability-N example *right now*?** See +> **[reference-capability-exemplars.md](./reference-capability-exemplars.md)** — generated from +> `manifest.json` by `bun run capability-exemplars` and gated by `bun run check:capability-exemplars`. +> **Cite that file; never cite memory.** This one question has been answered wrongly in three separate +> places — a gate, this README's capability captions, and the `/plugin-designer` skill — because a +> folder's apparent simplicity does not decide its tier. `doxa-vision-card` looks like the lightest +> thing in the repo and is **capability 2**, since it ships an `*.instances.json`. + Composite kinds are always **declared** in `.meta.json` (`{ "type": …, "label": … }`, either spelling — see `TYPE_ALIASES` in `internal/plugin-gallery/discover.mjs`) — the build never guesses them from source (tag-scanning produced false positives). Discovery is one pure module, diff --git a/internal/check-capability-exemplars.mjs b/internal/check-capability-exemplars.mjs new file mode 100644 index 000000000..ee0e8e7a9 --- /dev/null +++ b/internal/check-capability-exemplars.mjs @@ -0,0 +1,64 @@ +#!/usr/bin/env node +/** + * check-capability-exemplars.mjs — the generated exemplar table must match the manifest. + * + * Companion to generate-capability-exemplars.mjs. That file's header explains WHY the table is + * derived; this one makes the "derived, so it cannot drift" claim true by REGENERATING and + * comparing, rather than trusting mtimes. + * + * ★ WHY CONTENT-COMPARE AND NOT MTIME. check:library-index compares mtimes, which is right there + * (its generator scans library/ directly, so a source edit really does invalidate the output). + * Here the input is manifest.json, which a full build REWRITES EVERY TIME even when nothing + * changed — so an mtime rule would fail after every build for no reason, and a gate that cries + * wolf gets switched off. Comparing regenerated CONTENT fails only when the answer actually moved. + * + * The same reasoning is why CI regenerates-and-diffs the library index instead of using mtimes: + * a fresh checkout gives every file the same timestamp. + * + * NEGATIVE-TESTED: deleting a row from the table makes this FAIL showing the diff; regenerating + * returns PASS. Deleting the file entirely also FAILs rather than silently skipping. + */ +import { readFileSync, existsSync } from 'node:fs' +import { execFileSync } from 'node:child_process' +import { resolve, dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const OUT = join(ROOT, 'docs', 'reference-capability-exemplars.md') +const GEN = join(ROOT, 'internal', 'generate-capability-exemplars.mjs') +const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json') + +if (!existsSync(MANIFEST)) { + console.log('CAPABILITY-EXEMPLARS: SKIP — no manifest.json (nothing built yet).') + process.exit(0) +} +if (!existsSync(OUT)) { + console.error('CAPABILITY-EXEMPLARS: FAIL — docs/reference-capability-exemplars.md does not exist.') + console.error(' It is the authoritative answer to "which bundle is the capability-N example?",') + console.error(' a question that has been answered wrongly in three separate artifacts.') + console.error(' Fix: bun run capability-exemplars') + process.exit(1) +} + +const before = readFileSync(OUT, 'utf8') +execFileSync(process.execPath, [GEN], { stdio: 'pipe' }) +const after = readFileSync(OUT, 'utf8') + +if (before !== after) { + // Restore what was committed so the check itself never mutates the tree. + const { writeFileSync } = await import('node:fs') + writeFileSync(OUT, before) + const bl = before.split('\n'), al = after.split('\n') + const diff = [] + for (let i = 0; i < Math.max(bl.length, al.length) && diff.length < 10; i++) { + if (bl[i] !== al[i]) diff.push(` line ${i + 1}\n file: ${bl[i] ?? '(missing)'}\n real: ${al[i] ?? '(missing)'}`) + } + console.error('\nCAPABILITY-EXEMPLARS: FAIL — the table disagrees with the manifest:') + console.error(diff.join('\n')) + console.error('\n The manifest is what the BUILD computed from each folder\'s real shape.') + console.error(' Fix: bun run capability-exemplars (then commit the regenerated file).') + process.exit(1) +} + +const rows = (after.match(/^\| `/gm) || []).length +console.log(`CAPABILITY-EXEMPLARS: PASS — ${rows} exemplar row(s) match the manifest.`) diff --git a/internal/generate-capability-exemplars.mjs b/internal/generate-capability-exemplars.mjs new file mode 100644 index 000000000..30c5529a1 --- /dev/null +++ b/internal/generate-capability-exemplars.mjs @@ -0,0 +1,101 @@ +#!/usr/bin/env node +/** + * generate-capability-exemplars.mjs — write docs/reference-capability-exemplars.md from the manifest. + * + * WHY THIS IS DERIVED AND NOT HAND-WRITTEN + * "Which bundle is the capability-N example?" has been answered wrongly three times in three + * different artifacts, each fixed in isolation while the others kept the bug: + * · internal/verify-5-capabilities.mjs cited doxa-vision-card as cap-1 — it ships an + * *.instances.json, so it is parameterized (cap-2). Fixed there; its own comment records that + * the gate had been "sampling cap-2 TWICE and cap-1 NEVER". + * · README.md made the identical claim in a caption and a section. Fixed 2026-09-21, and only + * then gated by check:capability-citations. + * · the /plugin-designer skill still cites doxa-vision-card as cap-1 AND cites + * `unified-research-dashboard` as a cap-4 reference — a bundle that DOES NOT EXIST. + * + * ★ THE SKILL CANNOT BE GATED FROM THIS REPO. It lives in the session config directory, in no git + * repo, so a checker pointed at it would PASS on this machine and SKIP SILENTLY on every other — + * the "gate that cannot fail" shape. The durable fix is not to police the skill but to give the + * repo an authoritative, generated answer the skill (and any doc, and any future agent) can cite. + * Prose that cites a generated file can be checked; prose that cites memory cannot. + * + * THE SOURCE OF TRUTH IS manifest.json, because it is what the BUILD computed from each folder's + * real shape — an *.instances.json makes a bundle parameterized however simple it looks, and a + * meta.json `type` declares tiers 3/4/5. capability = 5 - PROFILE_TIER (discover.mjs). + * + * Run: bun run capability-exemplars Check: bun run check:capability-exemplars + */ +import { readFileSync, writeFileSync, existsSync } from 'node:fs' +import { resolve, dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json') +const OUT = join(ROOT, 'docs', 'reference-capability-exemplars.md') + +if (!existsSync(MANIFEST)) { + console.error('CAPABILITY-EXEMPLARS: manifest.json not found — run a build first.') + process.exit(1) +} + +const CAP_OF_TYPE = { + 'single': 1, + 'parameterized': 2, 'preset-element': 2, + 'monolithic': 3, 'multi-view-element': 3, + 'nested-elements-mfe': 4, 'composite-element': 4, + 'nested-mfe-spa': 5, 'app-shell': 5, +} +const CAP_NAME = { + 1: 'single-element', 2: 'preset-element (prop-table)', 3: 'multi-view-element (monolithic)', + 4: 'composite-element (nested-element SPA)', 5: 'app-shell (nested-SPA)', +} + +const mf = JSON.parse(readFileSync(MANIFEST, 'utf8')) +const byCap = new Map([[1, []], [2, []], [3, []], [4, []], [5, []]]) + +for (const b of (mf.bundles || [])) { + for (const p of (b.profiles || [])) { + const cap = CAP_OF_TYPE[p.type] + if (!cap) continue + byCap.get(cap).push({ bundle: b.bundle, profile: p.profile, type: p.type }) + } +} + +const lines = [] +lines.push('# Reference — capability exemplars (GENERATED)') +lines.push('') +lines.push('> **Do not hand-edit.** Regenerate with `bun run capability-exemplars`; `bun run') +lines.push('> check:capability-exemplars` fails if this file falls behind the manifest.') +lines.push('>') +lines.push('> This answers one question authoritatively: **which bundles are the capability-N') +lines.push('> examples right now?** It is derived from `public/doxa-maps/manifest.json`, which is what') +lines.push('> the build computed from each folder\'s real shape — so it cannot drift from what ships.') +lines.push('>') +lines.push('> **Cite this file rather than remembering.** The same citation has been wrong in three') +lines.push('> separate places: a gate, the README, and the `/plugin-designer` skill. The trap is that a') +lines.push('> folder\'s apparent simplicity does not decide its tier — an `*.instances.json` makes a') +lines.push('> bundle `parameterized` (cap-2) no matter how light it looks, and a `meta.json` `type`') +lines.push('> declares tiers 3/4/5.') +lines.push('') +lines.push(`Generated from ${(mf.bundles || []).length} bundle(s).`) +lines.push('') + +for (const cap of [1, 2, 3, 4, 5]) { + const rows = byCap.get(cap) + lines.push(`## Capability ${cap} — ${CAP_NAME[cap]}`) + lines.push('') + if (!rows.length) { + lines.push('_No built bundle currently declares this tier._') + lines.push('') + continue + } + lines.push('| Bundle | Profile | Machine key |') + lines.push('|---|---|---|') + for (const r of rows) lines.push(`| \`${r.bundle}\` | \`${r.profile}\` | \`${r.type}\` |`) + lines.push('') +} + +writeFileSync(OUT, lines.join('\n') + '\n') +const total = [...byCap.values()].reduce((n, v) => n + v.length, 0) +console.log(`[capability-exemplars] wrote docs/reference-capability-exemplars.md — ` + + `${total} profile(s) across ${[...byCap.values()].filter(v => v.length).length} tier(s)`) diff --git a/package.json b/package.json index 9b06e2705..9b42955fd 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ "check:shots": "node internal/check-shot-freshness.mjs # a README screenshot that predates the bundle it shows is a picture telling a lie \u2014 and it looks IDENTICAL to a current one. Compares each shot's PNG against the BUILT BUNDLE it renders (not raw sources: an unbuilt edit is not yet in any leaf). Fix: node internal/readme-shots.mjs", "check:library-index": "node internal/check-library-index.mjs # the DERIVED library menu must not fall behind library/. Compares the index against the newest library SOURCE (not a bundle \u2014 this generator scans library/ directly, with no build step between). Fix: bun run library-index", "check:quoted-scripts": "node internal/check-quoted-scripts.mjs # docs/README-SECTION-3-cli-commands.md QUOTES the scripts block verbatim; it had silently drifted TWICE (a stale bunx cap:sync, and build:unlocked missing check:public-hygiene + check:label-joins). package.json is what RUNS \u2014 update the DOC.", + "capability-exemplars": "node internal/generate-capability-exemplars.mjs # regenerate docs/reference-capability-exemplars.md \u2014 the DERIVED answer to \"which bundle is the capability-N example?\", a question answered WRONGLY in three separate artifacts (a gate, the README, the /plugin-designer skill).", + "check:capability-exemplars": "node internal/check-capability-exemplars.mjs # REGENERATES and content-compares (not mtimes: a full build rewrites manifest.json every time, so an mtime rule would cry wolf after every build and get switched off).", "check:capability-citations": "node internal/check-capability-citations.mjs # a doc that names a bundle as \"capability N\" must match manifest.json, which is what the BUILD computed. README captioned doxa-vision-card as cap-1; it ships 2 instances so it is parameterized (cap-2). The SAME error was fixed once in verify-5-capabilities.mjs and never reached the README \u2014 two copies of one fact, nothing forcing agreement.", "check:shot-distinctness": "node internal/check-shot-distinctness.mjs # two README slots must not show the SAME view. README shipped \"List \u2014 page 1\" and \"page 2\" as the same picture: the shot chased a Next control and a scroll that DO NOT EXIST, every attempt inside try/catch so the miss was silent, and the PNGs differed by 0.16% render noise so no checksum caught them. Keys on url+VIEWPORT \u2014 same url at a different vp (deploy2 mobile) is intended.", "check:shot-fidelity": "node internal/check-shot-fidelity.mjs # warns when THIS machine cannot RENDER what the shots must SHOW. `bun run shots` printed 13x OK while every emoji rendered as a tofu box \u2014 a glyph fallback loads perfectly (0 console errors, 0 HTTP>=400), so the generator cannot see it. Reports by default; --strict to block once an emoji font is installed.",