generate the capability exemplar table so the answer stops being remembered
"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.
This commit is contained in:
parent
88f01b9a95
commit
466493ed80
6 changed files with 249 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
68
docs/reference-capability-exemplars.md
Normal file
68
docs/reference-capability-exemplars.md
Normal file
|
|
@ -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` |
|
||||
|
||||
|
|
@ -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: `<profile>.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 `<profile>.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,
|
||||
|
|
|
|||
64
internal/check-capability-exemplars.mjs
Normal file
64
internal/check-capability-exemplars.mjs
Normal file
|
|
@ -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.`)
|
||||
101
internal/generate-capability-exemplars.mjs
Normal file
101
internal/generate-capability-exemplars.mjs
Normal file
|
|
@ -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)`)
|
||||
|
|
@ -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.",
|
||||
|
|
|
|||
Loading…
Reference in a new issue