feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
/**
|
|
|
|
|
|
* docs-check — guard for copyable doc invariants (docs reconciliation, Phase 3).
|
|
|
|
|
|
*
|
|
|
|
|
|
* Every invariant below was born from a REAL drift found by the 2026-07-01/02
|
|
|
|
|
|
* audits (fable_audit D1–D5, fable-eidos-audit §3.11). The corpus law is
|
|
|
|
|
|
* "link the canon, never copy it" (docs/authoring.md §1); this script catches
|
|
|
|
|
|
* the copies that survive anyway.
|
|
|
|
|
|
*
|
|
|
|
|
|
* 1. Vocabulary counts — no doc may assert a count of families / intents /
|
|
|
|
|
|
* archetypes different from the source consts (SEMA_FAMILIES, INTENTS,
|
|
|
|
|
|
* ARCHETYPE_VOCABULARY). ("7 familias" survived in three docs for weeks.)
|
|
|
|
|
|
* 2. Phantom fields — `translations:` (the pre-`texts:` morfo field) must not
|
|
|
|
|
|
* appear in morfo/soma docs; `defaultSemantic` (a rejected API shape) must
|
|
|
|
|
|
* not appear outside LIBRO_VARIACIONES / chronicle docs.
|
|
|
|
|
|
* 3. Dependency claims — the dependency lists in soma/README §3 and
|
|
|
|
|
|
* SOMA_ARCHITECTURE §12 may only cite packages that are real runtime
|
|
|
|
|
|
* dependencies (catches the stale @floating-ui / clsx claims).
|
|
|
|
|
|
* 4. Variant vocab mirror — SHARED_VARIANT_VOCAB (scripts/component-audit.ts)
|
|
|
|
|
|
* must equal EIDOS_VARIANTS (src/uix/eidos/lib/types.ts) entry by entry.
|
|
|
|
|
|
* 5. Checklist ↔ audit script — every COMPONENT_COMPLETION_CHECKLIST rule
|
|
|
|
|
|
* with `Enforcement: audit` must exist in component-audit.ts, and every
|
|
|
|
|
|
* rule ID the script emits must be declared in the checklist.
|
|
|
|
|
|
* 6. Relative links — markdown links in docs/** and the layer docs must
|
fix(cleanroom): F2+F3+F4-C+SEM-4s1 — lote mecánico, censos con guard, corpus documental y el close polimórfico de los pickers VIVO
F2 — lote mecánico (13 ítems):
- DEP-2 ogl eliminado (0 imports) · DEP-1 clsx inlineado como toClassString
propio + suite de contrato (props.test.ts; soma.md §12 cerrado).
- THM-7: los 5 selectores manuales de sema.md reescritos con semaSelector
(los ejemplos [data-toast-root] apuntaban a un part INEXISTENTE — la
deriva que el builder previene, demostrada en el propio doc).
- MOR-1 escape isomorfo + validación de attr-names en semaSelector + 9
tests (selectors.test.ts, matches() real con comillas/corchetes) ·
MOR-2 partMarkerAttr = única fuente compilador↔builder + test de paridad ·
MOR-3 _resetCompileCache borrado (0 usos).
- SOM-2 keydown continue en match sin handler + keyboardFixtureMorfo ·
SOM-1 no-await de handlers (censo async = 0; contrato V1 cumplido) + pin.
- SEM-2 trigger pre-attacha catch con logger (void trigger sin unhandled
rejection; throw intacto para awaiters) + pin · SEM-3 fallback muerto de
applyDominance → skip defensivo + timer tope de awaitExpression cancelado ·
SEC-1 adjudicado YA implementado (assertCssVariableValue desde 2026-05-11)
+ pin del path de VALOR.
- accordion → outline (§32; su outline:none dejaba CERO anillo en HCM) —
verificado en vivo · THM-6 radius-full 9999px · EID-4 recuentos 33.
F3 — censos con guard:
- SOM-3 cerrado: announcer + image-provider migrados a scheduler-preferred
(consumidores cableados: date/time-field vía soma.uix.timers; avatar/image
vía eidos.timers — verificado en vivo); guard de timers ENSANCHADO de
soma/components a TODO soma y pasado a EVIDENCIA (setTimeout exige
.schedule( en el fichero — layers/ y datetime/ escapaban del ámbito viejo).
- THM-5: R-4.7 nueva (válvula same-line /* important: <razón> */, escaneo
comment-blanked) + las 15 declaraciones anotadas con su razón + canon
recipe-contract §3/§4.
- SOM-4 adjudicado: el censo/guard YA existían (49 pins); knob/mask-field/
timeline pinneados (overrides documentados en call-site); media-player
Batch-4 (35 hits, cero renderProps) = único batch restante, registrado.
- THM-4 doctrinado en eidos.md §unused (comportamiento/composición =
legítimo; deuda = eje visual sin consumidor; hotspots por lotes).
F4-C — corpus documental (decisiones de usuario aplicadas):
- DOC-3: los 15 enlaces muertos resueltos (repoint a la edición FINAL
trackeada / des-link históricos) · docs:check I6-links WARN→ERROR.
- DOC-1: tabla «Build contract» MIGRADA a component-guide con estados
modernizados (A3–A5 → LIVE + guards de hoy); banners reapuntados; citas
de CANON/sema.md historificadas; lápida-redirect en el §13 del fósil.
- DOC-4: hold chain → holds.ts · FAQ event:* SUPERSEDED por signatures ·
gradient añadido a los DOS capstones (sextet real) · nota de paleta de
demo-authoring corregida (universalPaletteDecls + decisión THM-2 =
mecanismo universal como sucesor del tracker borrado).
- DOC-5/6: recuentos anti-frágiles datados · §4.11 dup → §4.12 · Known gaps
historificado · N-6/N-7 recuperadas de git (d68d2c45^) y canonizadas en
eidos.md §pickers · authoring E2 → canon/tsc.md · air-old des-linkado ·
EID-3 (placement) en la fila RTL · AUX-2 disabledDom documentado.
SEM-4 sesión 1 — el close polimórfico de los pickers, VIVO (D.11):
- Reconciliación: los morfos ya no declaran close (delegated al Popover,
de-dialoged 06-27); el agujero real era el cierre programático bypaseando
dismissWith → save/cancel/select eran perceptualmente SILENCIOSOS.
- Fix: PickerShellHandle.setPopoverDismiss + closeWith(cause) en los 5
providers (14 sitios; select/commit → 'save' = commit.save+fulfill,
cancel → 'cancel' = emerge; fallback raw para headless) + UN inyector en
el eidos PickerShell root (norma N-8). Picker genérico fuera a propósito
(ya suena commit-set/cancel por diseño S9).
- Verificado en vivo (date-picker): Done → close·commit·fulfill·active ·
Cancel → close·emerge · cierre real.
Gates: matriz 141/141 (los 6 morfos nuevos de la pista de texto paralela
también PASS) · contracts 38/38 · eidos 314 · sema 178 · morfo 94 ·
docs:check 0/0 con I6 en error · baseline propio 57.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
* resolve on disk. ERROR severity since 2026-07-11 (DOC-3, clean-room):
|
|
|
|
|
|
* the 15 tolerated dangling targets were repointed or de-linked; a dead
|
|
|
|
|
|
* link can never again sit at WARN indefinitely.
|
feat(docs-check): el guard deja de ser ciego — I8/I9/I10 + corpus completo
El barrido exhaustivo del eje de opts encontro rutas fantasma, simbolos
inexistentes y props inventadas que docs:check no podia ver: sus 7 invariantes
no miraban dentro de los bloques de codigo, y su corpus no caminaba
src/arts, src/libs, src/svrs ni la raiz (567 de 673 .md). Tres invariantes
nuevos, escritos en la gramatica del propio guard:
- I8 imports: cabezas `$`/`@/` de los bloques ts/js/svelte contra la tabla de
alias EXTRAIDA de vite.config.ts (nunca copiada) + resolucion en disco.
`$lib` esta retirado: error en docs del framework (docs/**, src/uix/**),
tolerado en ejemplos de app consumidora. Builtins de SvelteKit en whitelist;
placeholders `{name}` y anti-ejemplos marcados, exentos.
- I9 simbolos: todo `XxxProvider`/`XxxOpts` en bloque de codigo debe existir
en el indice de simbolos de src. Exentos: placeholders, menciones negadas
("not TerraDialogProvider"), imports externos declarados por el propio
bloque (LoggerProvider de @opentelemetry) y el pragma `docs-check-allow:`.
- I10 tablas de props: cada prop documentada en un README de componente debe
declararse en su dir O en sus imports a un salto (convencion types.ts) —
el salto existe porque el rest-forwarding de eidos expone props que nunca
declara (los 4 falsos positivos del extractor lo demostraron).
Corpus 567 -> 614: entran las capas arts/libs/svrs/packs y la raiz;
crónica case-insensitive (continuar-*.md); docs aspiracionales
(RFC/IMPROVEMENTS/...) exentos de los invariantes nuevos.
Triage del fallout inicial (11): los 11 eran defectos del guard, no de los
docs — ethereal compara con @floating-ui por oficio (exento de I3),
`{Name}TriggerProvider` es placeholder pegado, LoggerProvider viene de un
paquete externo que el bloque importa. Verificado en ROJO (5 plantas, 5
disparos con linea exacta) y en verde (0/614).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
* 7. Generated vocabularies appendix — docs/canon/vocabularies.md must match
|
|
|
|
|
|
* an in-memory regeneration from the code consts.
|
|
|
|
|
|
* 8. Code-block imports — `$`/`@/` import heads in fenced ts/js/svelte blocks
|
|
|
|
|
|
* must be real repo aliases (extracted from vite.config.ts, never copied)
|
|
|
|
|
|
* and resolve on disk. `$lib` is retired: forbidden in framework-owned
|
|
|
|
|
|
* docs (docs/**, src/uix/**); tolerated in consumer-app examples
|
|
|
|
|
|
* elsewhere. (Born 2026-08-05: the exhaustive opts audit found phantom
|
|
|
|
|
|
* '../../reactive' paths and a `$lib` debounce recipe in live docs.)
|
|
|
|
|
|
* 9. Provider/Opts symbols — `XxxProvider` / `XxxOpts` names inside fenced
|
|
|
|
|
|
* code blocks must exist in the src symbol index. Placeholders (Xxx…),
|
|
|
|
|
|
* negated mentions ("not TerraDialogProvider") and a
|
|
|
|
|
|
* `docs-check-allow:` pragma are exempt. (SplitterTriggerProvider
|
|
|
|
|
|
* survived in GESTURES.md — the class is SplitterResizeTriggerProvider.)
|
|
|
|
|
|
* 10. Prop tables ↔ code — every `prop` documented in a component README's
|
|
|
|
|
|
* prop table must be declared somewhere in the component dir OR in the
|
|
|
|
|
|
* files its sources import (one hop, types.ts convention included).
|
|
|
|
|
|
* The one-hop resolution exists because rest-forwarding (`...rest` to a
|
|
|
|
|
|
* soma provider) is how eidos wrappers legitimately expose props they
|
|
|
|
|
|
* never declare.
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
*
|
feat(docs-check): el guard deja de ser ciego — I8/I9/I10 + corpus completo
El barrido exhaustivo del eje de opts encontro rutas fantasma, simbolos
inexistentes y props inventadas que docs:check no podia ver: sus 7 invariantes
no miraban dentro de los bloques de codigo, y su corpus no caminaba
src/arts, src/libs, src/svrs ni la raiz (567 de 673 .md). Tres invariantes
nuevos, escritos en la gramatica del propio guard:
- I8 imports: cabezas `$`/`@/` de los bloques ts/js/svelte contra la tabla de
alias EXTRAIDA de vite.config.ts (nunca copiada) + resolucion en disco.
`$lib` esta retirado: error en docs del framework (docs/**, src/uix/**),
tolerado en ejemplos de app consumidora. Builtins de SvelteKit en whitelist;
placeholders `{name}` y anti-ejemplos marcados, exentos.
- I9 simbolos: todo `XxxProvider`/`XxxOpts` en bloque de codigo debe existir
en el indice de simbolos de src. Exentos: placeholders, menciones negadas
("not TerraDialogProvider"), imports externos declarados por el propio
bloque (LoggerProvider de @opentelemetry) y el pragma `docs-check-allow:`.
- I10 tablas de props: cada prop documentada en un README de componente debe
declararse en su dir O en sus imports a un salto (convencion types.ts) —
el salto existe porque el rest-forwarding de eidos expone props que nunca
declara (los 4 falsos positivos del extractor lo demostraron).
Corpus 567 -> 614: entran las capas arts/libs/svrs/packs y la raiz;
crónica case-insensitive (continuar-*.md); docs aspiracionales
(RFC/IMPROVEMENTS/...) exentos de los invariantes nuevos.
Triage del fallout inicial (11): los 11 eran defectos del guard, no de los
docs — ethereal compara con @floating-ui por oficio (exento de I3),
`{Name}TriggerProvider` es placeholder pegado, LoggerProvider viene de un
paquete externo que el bloque importa. Verificado en ROJO (5 plantas, 5
disparos con linea exacta) y en verde (0/614).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
* Severities: all invariants are errors. Exit code 1 when any error-level
|
|
|
|
|
|
* finding exists.
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
*
|
|
|
|
|
|
* Chronicle docs (docs/process/**, THEMING_CHANGELOG, audits, CONTINUE
|
|
|
|
|
|
* hand-offs, the book) record what WAS true — they are excluded from the
|
|
|
|
|
|
* truth-tracking invariants (1–3) and from link checking where noted.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
|
|
|
|
import { join, dirname, resolve, relative, sep } from 'node:path';
|
|
|
|
|
|
|
feat(docs): S1 — generated canonical-vocabulary appendix (STUMBLES #1)
An agent building a component from the docs-book alone could not assign
archetypes / holds / haptic kinds: the closed sets live only in code consts
and the corpus (correctly) forbids copying them into prose, so they were
invisible from the docs. STUMBLES #1, the top stumble.
Fix: docs/canon/vocabularies.md is GENERATED from the consts by
'npm run docs:vocabularies' — the ONE sanctioned place the lists are spelled
out (generated = no drift objection), which every other doc links to. It
covers part archetypes (with a one-line role each), sema families with their
default hold/persistence + per-intent overrides, verbs by family, intents,
the perceptual-duration scale, haptic kinds, the 33 palette scales, sizes,
variant archetypes, and the shared common.* strings.
- scripts/docs-vocabularies.ts imports the consts (robust — no fragile
JSDoc/regex parsing) and formats them; the generation is an exported
function so docs-check can compare.
- Added ARCHETYPE_DESCRIPTIONS to morfo/types.ts (co-located with
ARCHETYPE_VOCABULARY, satisfies Record<MorfoArchetype,string>) as the
authoritative one-liners the appendix reads — descriptions become
first-class data instead of inline union JSDoc a tool would have to parse.
- docs-check gains I7: it regenerates in-memory and fails if the committed
file drifts from the consts (negative-tested: corrupt -> error, regen ->
green). The appendix's own counts pass I1, so I1 doubles as a second guard.
- Linked from README (E2 canon row + two 'I want to' shortcuts), CANON.md
(doctrine here, enumerated lists there), architecture/morfo.md (pick a
part's archetype), and the component-audit minimum-package list (STUMBLES
#8).
docs:check 0 errors; npm run check unchanged in my files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
import { generateVocabulariesDoc } from './docs-vocabularies';
|
|
|
|
|
|
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
const REPO = resolve(import.meta.dirname, '..');
|
|
|
|
|
|
|
|
|
|
|
|
interface Finding {
|
|
|
|
|
|
severity: 'error' | 'warn';
|
|
|
|
|
|
invariant: string;
|
|
|
|
|
|
file: string;
|
|
|
|
|
|
line: number;
|
|
|
|
|
|
message: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const findings: Finding[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
function report(
|
|
|
|
|
|
severity: 'error' | 'warn',
|
|
|
|
|
|
invariant: string,
|
|
|
|
|
|
file: string,
|
|
|
|
|
|
line: number,
|
|
|
|
|
|
message: string
|
|
|
|
|
|
): void {
|
|
|
|
|
|
findings.push({
|
|
|
|
|
|
severity,
|
|
|
|
|
|
invariant,
|
|
|
|
|
|
file: relative(REPO, file).split(sep).join('/'),
|
|
|
|
|
|
line,
|
|
|
|
|
|
message
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Corpus walking ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/** Dirs never walked: WIP tracks, third parties, build output. */
|
|
|
|
|
|
const SKIP_DIRS = new Set([
|
|
|
|
|
|
'node_modules',
|
|
|
|
|
|
'.git',
|
|
|
|
|
|
'.svelte-kit',
|
|
|
|
|
|
'build',
|
|
|
|
|
|
'dist',
|
|
|
|
|
|
'words',
|
|
|
|
|
|
'palabras',
|
|
|
|
|
|
'chronos',
|
|
|
|
|
|
'.claude',
|
|
|
|
|
|
'tmp'
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
function walkMd(dir: string, out: string[] = []): string[] {
|
|
|
|
|
|
for (const entry of readdirSync(dir)) {
|
|
|
|
|
|
const full = join(dir, entry);
|
|
|
|
|
|
const st = statSync(full);
|
|
|
|
|
|
if (st.isDirectory()) {
|
|
|
|
|
|
if (!SKIP_DIRS.has(entry)) walkMd(full, out);
|
|
|
|
|
|
} else if (entry.endsWith('.md')) {
|
|
|
|
|
|
out.push(full);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Chronicle docs: record history, exempt from truth-tracking invariants. */
|
|
|
|
|
|
function isChronicle(file: string): boolean {
|
|
|
|
|
|
const rel = relative(REPO, file).split(sep).join('/');
|
|
|
|
|
|
if (
|
|
|
|
|
|
rel.startsWith('docs/process/') ||
|
docs+fix: old docs quarantined in docs/old-deprecated; STUMBLES doc-class fixes
Two user findings from the Knob build exercise (an agent building a new
component from the docs alone — STUMBLES.md).
Quarantine: the superseded fossils no longer share shelf space with the
live corpus. docs/old-deprecated/ (with an index README explaining what
lands there and pointing readers at docs/README.md) now holds the
executed audits and fix plans: fable_audit, fable-eidos-audit,
inherit_audit + inherit_fix_plan, ARCHETYPE_COHERENCE_AUDIT_2026-06-19
(still citable — the component-guide banner and the eidos components
README repoint to it), COMPONENT_COHERENCE_AUDIT. pendiente.md (a live
pending list, not a fossil) moved to docs/process/. docs-check treats
the folder as sealed chronicle (I1/I2 exempt; I6 skips its internal
links, as its README promises). Root-level *.md is now: README, CLAUDE,
AGENTS + the user's own working files.
STUMBLES fixes applied on the spot (the doc-class ones):
- #2 kind drift: the REAL enum is 'public' | 'private' | 'virtual'
(MorfoPartKind, 671/9/14 uses) — morfo.md omitted 'private', the
checklist invented 'internal' (0 uses). Both fixed; I2 gains the
phantom-'internal' guard. A-2.1's row now says what the audit script
actually checks (kebab only — the archetype may vary, the Toggle
provider-IS-trigger doctrine).
- #6: the langs catalog SHAPE (flat keys, per-language leaves, named
export, index registration) is now shown in morfo.md instead of only
its location.
- #8: component-audit s0 defines the minimum brief package as an
explicit 8-file list.
The engineering-class stumbles are registered as plan batches S1-S6
(generated vocabularies appendix, continuous-gesture trigger doctrine,
part-absent condition, Gesture.rotate, the soma->eidos CSS-var
contract, minor frictions). docs:check 0 errors, 11-warn baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
rel.startsWith('docs/old-deprecated/') || // superseded fossils — chronicle
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
rel.includes('CHANGELOG') ||
|
feat(docs-check): el guard deja de ser ciego — I8/I9/I10 + corpus completo
El barrido exhaustivo del eje de opts encontro rutas fantasma, simbolos
inexistentes y props inventadas que docs:check no podia ver: sus 7 invariantes
no miraban dentro de los bloques de codigo, y su corpus no caminaba
src/arts, src/libs, src/svrs ni la raiz (567 de 673 .md). Tres invariantes
nuevos, escritos en la gramatica del propio guard:
- I8 imports: cabezas `$`/`@/` de los bloques ts/js/svelte contra la tabla de
alias EXTRAIDA de vite.config.ts (nunca copiada) + resolucion en disco.
`$lib` esta retirado: error en docs del framework (docs/**, src/uix/**),
tolerado en ejemplos de app consumidora. Builtins de SvelteKit en whitelist;
placeholders `{name}` y anti-ejemplos marcados, exentos.
- I9 simbolos: todo `XxxProvider`/`XxxOpts` en bloque de codigo debe existir
en el indice de simbolos de src. Exentos: placeholders, menciones negadas
("not TerraDialogProvider"), imports externos declarados por el propio
bloque (LoggerProvider de @opentelemetry) y el pragma `docs-check-allow:`.
- I10 tablas de props: cada prop documentada en un README de componente debe
declararse en su dir O en sus imports a un salto (convencion types.ts) —
el salto existe porque el rest-forwarding de eidos expone props que nunca
declara (los 4 falsos positivos del extractor lo demostraron).
Corpus 567 -> 614: entran las capas arts/libs/svrs/packs y la raiz;
crónica case-insensitive (continuar-*.md); docs aspiracionales
(RFC/IMPROVEMENTS/...) exentos de los invariantes nuevos.
Triage del fallout inicial (11): los 11 eran defectos del guard, no de los
docs — ethereal compara con @floating-ui por oficio (exento de I3),
`{Name}TriggerProvider` es placeholder pegado, LoggerProvider viene de un
paquete externo que el bloque importa. Verificado en ROJO (5 plantas, 5
disparos con linea exacta) y en verde (0/614).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
/CONTINUE|continuar/i.test(rel) || // hand-offs, any casing (root `continuar-*.md`)
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
/AUDIT/i.test(rel) ||
|
|
|
|
|
|
/fable.*audit/i.test(rel) ||
|
|
|
|
|
|
rel.includes('Disenando_lo_que_ocurre') || // the book — never linted
|
|
|
|
|
|
rel.includes('handoffs-') ||
|
|
|
|
|
|
rel === 'CLAUDE.md' || // own deferred pass
|
|
|
|
|
|
rel === 'AGENTS.md'
|
|
|
|
|
|
) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Frontmatter `status: historical` (e.g. the degraded GUIA) — the doc
|
|
|
|
|
|
// declares itself a seed, not current truth.
|
|
|
|
|
|
const head = readFileSync(file, 'utf-8').slice(0, 400);
|
|
|
|
|
|
return /^status:\s*(historical|chronicle)\s*$/m.test(head);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const corpus = [
|
|
|
|
|
|
...walkMd(join(REPO, 'docs')),
|
|
|
|
|
|
...walkMd(join(REPO, 'src', 'uix')),
|
|
|
|
|
|
...walkMd(join(REPO, 'src', 'docs')),
|
feat(docs-check): el guard deja de ser ciego — I8/I9/I10 + corpus completo
El barrido exhaustivo del eje de opts encontro rutas fantasma, simbolos
inexistentes y props inventadas que docs:check no podia ver: sus 7 invariantes
no miraban dentro de los bloques de codigo, y su corpus no caminaba
src/arts, src/libs, src/svrs ni la raiz (567 de 673 .md). Tres invariantes
nuevos, escritos en la gramatica del propio guard:
- I8 imports: cabezas `$`/`@/` de los bloques ts/js/svelte contra la tabla de
alias EXTRAIDA de vite.config.ts (nunca copiada) + resolucion en disco.
`$lib` esta retirado: error en docs del framework (docs/**, src/uix/**),
tolerado en ejemplos de app consumidora. Builtins de SvelteKit en whitelist;
placeholders `{name}` y anti-ejemplos marcados, exentos.
- I9 simbolos: todo `XxxProvider`/`XxxOpts` en bloque de codigo debe existir
en el indice de simbolos de src. Exentos: placeholders, menciones negadas
("not TerraDialogProvider"), imports externos declarados por el propio
bloque (LoggerProvider de @opentelemetry) y el pragma `docs-check-allow:`.
- I10 tablas de props: cada prop documentada en un README de componente debe
declararse en su dir O en sus imports a un salto (convencion types.ts) —
el salto existe porque el rest-forwarding de eidos expone props que nunca
declara (los 4 falsos positivos del extractor lo demostraron).
Corpus 567 -> 614: entran las capas arts/libs/svrs/packs y la raiz;
crónica case-insensitive (continuar-*.md); docs aspiracionales
(RFC/IMPROVEMENTS/...) exentos de los invariantes nuevos.
Triage del fallout inicial (11): los 11 eran defectos del guard, no de los
docs — ethereal compara con @floating-ui por oficio (exento de I3),
`{Name}TriggerProvider` es placeholder pegado, LoggerProvider viene de un
paquete externo que el bloque importa. Verificado en ROJO (5 plantas, 5
disparos con linea exacta) y en verde (0/614).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
// The other framework layers — arts/libs/svrs/packs READMEs are doctrine
|
|
|
|
|
|
// too (widened 2026-08-05: the opts audit found $lib recipes living there
|
|
|
|
|
|
// precisely because no guard walked them).
|
|
|
|
|
|
...['arts', 'libs', 'svrs', 'packs']
|
|
|
|
|
|
.map((d) => join(REPO, 'src', d))
|
|
|
|
|
|
.filter((d) => existsSync(d))
|
|
|
|
|
|
.flatMap((d) => walkMd(d)),
|
|
|
|
|
|
// Repo-root docs (top level only — no recursion, the trees above own the rest).
|
|
|
|
|
|
...readdirSync(REPO)
|
|
|
|
|
|
.filter((f) => f.endsWith('.md'))
|
|
|
|
|
|
.map((f) => join(REPO, f)),
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
...(existsSync(join(REPO, 'web', 'routes', 'uix', 'lib'))
|
|
|
|
|
|
? walkMd(join(REPO, 'web', 'routes', 'uix', 'lib'))
|
|
|
|
|
|
: [])
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const liveCorpus = corpus.filter((f) => !isChronicle(f));
|
|
|
|
|
|
|
feat(docs-check): el guard deja de ser ciego — I8/I9/I10 + corpus completo
El barrido exhaustivo del eje de opts encontro rutas fantasma, simbolos
inexistentes y props inventadas que docs:check no podia ver: sus 7 invariantes
no miraban dentro de los bloques de codigo, y su corpus no caminaba
src/arts, src/libs, src/svrs ni la raiz (567 de 673 .md). Tres invariantes
nuevos, escritos en la gramatica del propio guard:
- I8 imports: cabezas `$`/`@/` de los bloques ts/js/svelte contra la tabla de
alias EXTRAIDA de vite.config.ts (nunca copiada) + resolucion en disco.
`$lib` esta retirado: error en docs del framework (docs/**, src/uix/**),
tolerado en ejemplos de app consumidora. Builtins de SvelteKit en whitelist;
placeholders `{name}` y anti-ejemplos marcados, exentos.
- I9 simbolos: todo `XxxProvider`/`XxxOpts` en bloque de codigo debe existir
en el indice de simbolos de src. Exentos: placeholders, menciones negadas
("not TerraDialogProvider"), imports externos declarados por el propio
bloque (LoggerProvider de @opentelemetry) y el pragma `docs-check-allow:`.
- I10 tablas de props: cada prop documentada en un README de componente debe
declararse en su dir O en sus imports a un salto (convencion types.ts) —
el salto existe porque el rest-forwarding de eidos expone props que nunca
declara (los 4 falsos positivos del extractor lo demostraron).
Corpus 567 -> 614: entran las capas arts/libs/svrs/packs y la raiz;
crónica case-insensitive (continuar-*.md); docs aspiracionales
(RFC/IMPROVEMENTS/...) exentos de los invariantes nuevos.
Triage del fallout inicial (11): los 11 eran defectos del guard, no de los
docs — ethereal compara con @floating-ui por oficio (exento de I3),
`{Name}TriggerProvider` es placeholder pegado, LoggerProvider viene de un
paquete externo que el bloque importa. Verificado en ROJO (5 plantas, 5
disparos con linea exacta) y en verde (0/614).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
/** Aspirational docs — proposals/backlogs that name APIs which do not exist YET. */
|
|
|
|
|
|
function isAspirational(file: string): boolean {
|
|
|
|
|
|
return /IMPROVEMENTS|PROPOSAL|RFC|ROADMAP|IDEAS|WISHLIST/i.test(
|
|
|
|
|
|
relative(REPO, file).split(sep).join('/')
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// ── Source-of-truth extraction (textual, no imports — scripts must not pull
|
|
|
|
|
|
// the Svelte graph) ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
function read(file: string): string {
|
|
|
|
|
|
return readFileSync(file, 'utf-8');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Extract the string literals of an `as const` array by const name. */
|
|
|
|
|
|
function extractConstArray(source: string, constName: string): string[] {
|
|
|
|
|
|
const m = source.match(new RegExp(`${constName}\\s*=\\s*\\[([\\s\\S]*?)\\]`));
|
|
|
|
|
|
if (!m) throw new Error(`Cannot find const ${constName}`);
|
fix(docs): counts sweep round 2 — 12 slots (not 13), 9 roles (not 8) + guards
Extending the I1 count guard to color roles and role slots surfaced
four more stale counts the palette fix predicted: the RFCs still said
'13 slots' and enumerated borderHover8 (the slot was retired 2026-07-02,
zero consumers — COLOR_ROLE_SLOTS is 12), and two component READMEs
(css-field, float-panel) still said '8 roles' from before tertiary made
it 9. The guard itself had the same disease it polices: extractConstArray
counted the quoted 'borderHover' inside the comment documenting its own
retirement, so expected=13 and the stale mentions passed — comments are
now stripped before extraction. css-field's README is fixed on disk but
left unstaged (it carries unrelated local modifications).
docs-check now verifies families=8, intents=6, archetypes=26,
palette=33, roles=9, slots=12 against their consts on every run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// Strip comments first — a quoted name inside a comment (e.g. the note
|
|
|
|
|
|
// documenting the retired 'borderHover' slot) must not count as an entry.
|
|
|
|
|
|
const body = m[1].replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
|
|
|
|
return [...body.matchAll(/'([\w-]+)'/g)].map((x) => x[1]);
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SEMA_FAMILIES (event.ts) is a spread of the two partitions — extract both.
|
|
|
|
|
|
const semaEventSrc = read(join(REPO, 'src/uix/sema/event.ts'));
|
|
|
|
|
|
const SEMA_FAMILIES = [
|
|
|
|
|
|
...extractConstArray(semaEventSrc, 'SEMA_VALENCED_FAMILIES'),
|
|
|
|
|
|
...extractConstArray(semaEventSrc, 'SEMA_TRANSITIONAL_FAMILIES')
|
|
|
|
|
|
];
|
|
|
|
|
|
const INTENTS = extractConstArray(read(join(REPO, 'src/uix/intent.ts')), 'INTENTS');
|
|
|
|
|
|
const ARCHETYPES = extractConstArray(
|
|
|
|
|
|
read(join(REPO, 'src/uix/morfo/types.ts')),
|
|
|
|
|
|
'ARCHETYPE_VOCABULARY'
|
|
|
|
|
|
);
|
fix(eidos-docs): the palette is 33 scales, not 31 — UI bug + docs + count guard
User-caught drift, and it was live in the UI, not just prose: the demo
harness's hand-copied PALETTE_GROUPS enumerated 31 scales — fuchsia and
steel (the two non-Radix scales) never appeared in any demo's
PalettePicker even though PALETTE_SCALES (the const, 33) and the recipe
cascade (paletteScaleDecls maps the const) resolve them fine. Fixed the
two missing entries, typed the groups as PaletteScale[], and added a
dev-time completeness assertion so growing the const without grouping
throws at module load.
Prose swept (the numbers were inherited verbatim from the pre-book
sources, which contradicted their own '33-scale library' headers):
decisions.md color-model row, demo-authoring x4 (incl. the '31-scale
palette' lede), rfc-color-engine x4 (ground-truth + templates
open-question). Where the count was incidental the text now points at
PALETTE_SCALES instead of restating a number.
Guard: docs-check I1 gains the palette count (EN + ES patterns,
~-approximation and type/space-scale exclusions; s-mark added to the
section-number skip). The check line now prints palette=33. This was
the exact class I1 already covered for families/intents/archetypes and
had not been extended to the palette.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
const PALETTE = extractConstArray(
|
|
|
|
|
|
read(join(REPO, 'src/uix/eidos/lib/types.ts')),
|
|
|
|
|
|
'PALETTE_SCALES'
|
|
|
|
|
|
);
|
fix(docs): counts sweep round 2 — 12 slots (not 13), 9 roles (not 8) + guards
Extending the I1 count guard to color roles and role slots surfaced
four more stale counts the palette fix predicted: the RFCs still said
'13 slots' and enumerated borderHover8 (the slot was retired 2026-07-02,
zero consumers — COLOR_ROLE_SLOTS is 12), and two component READMEs
(css-field, float-panel) still said '8 roles' from before tertiary made
it 9. The guard itself had the same disease it polices: extractConstArray
counted the quoted 'borderHover' inside the comment documenting its own
retirement, so expected=13 and the stale mentions passed — comments are
now stripped before extraction. css-field's README is fixed on disk but
left unstaged (it carries unrelated local modifications).
docs-check now verifies families=8, intents=6, archetypes=26,
palette=33, roles=9, slots=12 against their consts on every run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
const eidosConfigTypesSrc = read(join(REPO, 'src/uix/eidos/lib/config-types.ts'));
|
|
|
|
|
|
// COLOR_ROLES is a spread (hierarchy + intents) — count the two parts.
|
|
|
|
|
|
const COLOR_ROLES_COUNT =
|
|
|
|
|
|
extractConstArray(eidosConfigTypesSrc, 'HIERARCHY_COLOR_ROLES').length + INTENTS.length;
|
|
|
|
|
|
const COLOR_ROLE_SLOTS = extractConstArray(eidosConfigTypesSrc, 'COLOR_ROLE_SLOTS');
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
|
|
|
|
|
|
/** Files in a directory that ARE the catalog (no tests, no barrel). */
|
|
|
|
|
|
function catalogEntries(dir: string, opts: { skip?: RegExp } = {}): string[] {
|
|
|
|
|
|
if (!existsSync(dir)) return [];
|
|
|
|
|
|
return readdirSync(dir)
|
|
|
|
|
|
.filter((f) => f.endsWith('.ts') && !f.endsWith('.test.ts') && f !== 'index.ts')
|
|
|
|
|
|
.filter((f) => !opts.skip?.test(f))
|
|
|
|
|
|
.map((f) => f.replace(/\.ts$/, ''))
|
|
|
|
|
|
.sort();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const MORFO_COMPONENTS = catalogEntries(join(REPO, 'src/uix/morfo/components'));
|
|
|
|
|
|
const SERVICE_FACTORIES = catalogEntries(join(REPO, 'src/arts/active-app/service-factories'));
|
|
|
|
|
|
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// ── Invariant 1 — vocabulary counts ────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
interface CountPattern {
|
|
|
|
|
|
re: RegExp;
|
|
|
|
|
|
expected: number;
|
|
|
|
|
|
what: string;
|
|
|
|
|
|
/** The match only counts when the line (or its neighbour) is about THIS vocabulary. */
|
|
|
|
|
|
context: RegExp;
|
|
|
|
|
|
/** Lines about a DIFFERENT vocabulary that shares the word (font families, variant archetypes…). */
|
|
|
|
|
|
exclude?: RegExp;
|
|
|
|
|
|
/** Ignore totals below this (subset phrasing like "5 intent roles" / "3 arquetipos de size"). */
|
|
|
|
|
|
minToFlag: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const COUNT_PATTERNS: CountPattern[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
re: /\b(\d+)\s+(?:sema\s+)?famil(?:ies|ias)\b/gi,
|
|
|
|
|
|
expected: SEMA_FAMILIES.length,
|
|
|
|
|
|
what: 'SEMA_FAMILIES',
|
|
|
|
|
|
context:
|
|
|
|
|
|
/sema|semánt|semant|famil.{0,30}(evento|event)|SEMA_FAMILIES|contact|commit|emerge|delegate/i,
|
|
|
|
|
|
exclude: /tipográf|typograph|font|shape|forma|escala|scale|superel/i,
|
|
|
|
|
|
minToFlag: 6
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
re: /\b(\d+)\s+intents?\b/gi,
|
|
|
|
|
|
expected: INTENTS.length,
|
|
|
|
|
|
what: 'INTENTS',
|
|
|
|
|
|
context: /intent/i,
|
|
|
|
|
|
exclude: /evaluativ|valenc|roles?\b/i,
|
|
|
|
|
|
minToFlag: 6
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
re: /\b(\d+)\s+archetypes?\b/gi,
|
|
|
|
|
|
expected: ARCHETYPES.length,
|
|
|
|
|
|
what: 'ARCHETYPE_VOCABULARY',
|
|
|
|
|
|
context: /morfo|part|ARCHETYPE_VOCABULARY|archetype/i,
|
|
|
|
|
|
exclude: /variant|EIDOS_VARIANTS|size|tamañ/i,
|
|
|
|
|
|
minToFlag: 10
|
fix(eidos-docs): the palette is 33 scales, not 31 — UI bug + docs + count guard
User-caught drift, and it was live in the UI, not just prose: the demo
harness's hand-copied PALETTE_GROUPS enumerated 31 scales — fuchsia and
steel (the two non-Radix scales) never appeared in any demo's
PalettePicker even though PALETTE_SCALES (the const, 33) and the recipe
cascade (paletteScaleDecls maps the const) resolve them fine. Fixed the
two missing entries, typed the groups as PaletteScale[], and added a
dev-time completeness assertion so growing the const without grouping
throws at module load.
Prose swept (the numbers were inherited verbatim from the pre-book
sources, which contradicted their own '33-scale library' headers):
decisions.md color-model row, demo-authoring x4 (incl. the '31-scale
palette' lede), rfc-color-engine x4 (ground-truth + templates
open-question). Where the count was incidental the text now points at
PALETTE_SCALES instead of restating a number.
Guard: docs-check I1 gains the palette count (EN + ES patterns,
~-approximation and type/space-scale exclusions; s-mark added to the
section-number skip). The check line now prints palette=33. This was
the exact class I1 already covered for families/intents/archetypes and
had not been extended to the palette.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
},
|
|
|
|
|
|
// Palette scale count — "the 31 palette scales" survived in four docs +
|
|
|
|
|
|
// the demo harness after fuchsia/steel grew the library to 33 (2026-07-02).
|
|
|
|
|
|
{
|
|
|
|
|
|
re: /\b(\d+)[\s-]+(?:palette\s+|donor\s+|color\s+|Radix\s+)?scales?\b/gi,
|
|
|
|
|
|
expected: PALETTE.length,
|
|
|
|
|
|
what: 'PALETTE_SCALES',
|
|
|
|
|
|
context: /palette|paleta|scale|color/i,
|
|
|
|
|
|
// ~N = approximations about OTHER libraries (Radix "~30 scales"); type/space
|
|
|
|
|
|
// scales and token names (--scale-*, "12-step scale") are different vocab.
|
|
|
|
|
|
exclude: /~\s*\d|type\s+scale|font|typo|space|spacing|--scale|scale-\d|step|gray|grey/i,
|
|
|
|
|
|
minToFlag: 20
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
re: /\b(\d+)\s+escalas\b/gi,
|
|
|
|
|
|
expected: PALETTE.length,
|
|
|
|
|
|
what: 'PALETTE_SCALES',
|
|
|
|
|
|
context: /palet|escala|color/i,
|
|
|
|
|
|
exclude: /~\s*\d|tipogr|espaci|paso|step|gris/i,
|
|
|
|
|
|
minToFlag: 20
|
fix(docs): counts sweep round 2 — 12 slots (not 13), 9 roles (not 8) + guards
Extending the I1 count guard to color roles and role slots surfaced
four more stale counts the palette fix predicted: the RFCs still said
'13 slots' and enumerated borderHover8 (the slot was retired 2026-07-02,
zero consumers — COLOR_ROLE_SLOTS is 12), and two component READMEs
(css-field, float-panel) still said '8 roles' from before tertiary made
it 9. The guard itself had the same disease it polices: extractConstArray
counted the quoted 'borderHover' inside the comment documenting its own
retirement, so expected=13 and the stale mentions passed — comments are
now stripped before extraction. css-field's README is fixed on disk but
left unstaged (it carries unrelated local modifications).
docs-check now verifies families=8, intents=6, archetypes=26,
palette=33, roles=9, slots=12 against their consts on every run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
},
|
|
|
|
|
|
// Color roles (9 = 3 hierarchy + 6 intents) and role slots (12 since
|
|
|
|
|
|
// border-hover was retired, 2026-07-02) — "13 slots" survived in the RFCs.
|
|
|
|
|
|
{
|
|
|
|
|
|
// `colou?r` — the British spelling slipped a "the 8 colour roles" past
|
|
|
|
|
|
// this rule while its American sibling on the next line was caught.
|
|
|
|
|
|
re: /\b(\d+)\s+(?:colou?r\s+)?roles\b/gi,
|
fix(docs): counts sweep round 2 — 12 slots (not 13), 9 roles (not 8) + guards
Extending the I1 count guard to color roles and role slots surfaced
four more stale counts the palette fix predicted: the RFCs still said
'13 slots' and enumerated borderHover8 (the slot was retired 2026-07-02,
zero consumers — COLOR_ROLE_SLOTS is 12), and two component READMEs
(css-field, float-panel) still said '8 roles' from before tertiary made
it 9. The guard itself had the same disease it polices: extractConstArray
counted the quoted 'borderHover' inside the comment documenting its own
retirement, so expected=13 and the stale mentions passed — comments are
now stripped before extraction. css-field's README is fixed on disk but
left unstaged (it carries unrelated local modifications).
docs-check now verifies families=8, intents=6, archetypes=26,
palette=33, roles=9, slots=12 against their consts on every run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
expected: COLOR_ROLES_COUNT,
|
|
|
|
|
|
what: 'COLOR_ROLES',
|
|
|
|
|
|
context: /colou?r|role|palet/i,
|
fix(docs): counts sweep round 2 — 12 slots (not 13), 9 roles (not 8) + guards
Extending the I1 count guard to color roles and role slots surfaced
four more stale counts the palette fix predicted: the RFCs still said
'13 slots' and enumerated borderHover8 (the slot was retired 2026-07-02,
zero consumers — COLOR_ROLE_SLOTS is 12), and two component READMEs
(css-field, float-panel) still said '8 roles' from before tertiary made
it 9. The guard itself had the same disease it polices: extractConstArray
counted the quoted 'borderHover' inside the comment documenting its own
retirement, so expected=13 and the stale mentions passed — comments are
now stripped before extraction. css-field's README is fixed on disk but
left unstaged (it carries unrelated local modifications).
docs-check now verifies families=8, intents=6, archetypes=26,
palette=33, roles=9, slots=12 against their consts on every run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
exclude: /accent|hierarch|jerarqu|intent\s+roles|semantic/i,
|
|
|
|
|
|
minToFlag: 7
|
|
|
|
|
|
},
|
|
|
|
|
|
// The morfo catalog: "≈140 morfos today" while the tree held 165. An
|
|
|
|
|
|
// approximation of OUR OWN catalog is the disease, not an escape hatch —
|
|
|
|
|
|
// so no `~`/`≈` exclusion here (unlike the palette rule, whose `~N` refers
|
|
|
|
|
|
// to other libraries).
|
|
|
|
|
|
{
|
|
|
|
|
|
re: /\b(\d+)\s+morfos?\b/gi,
|
|
|
|
|
|
expected: MORFO_COMPONENTS.length,
|
|
|
|
|
|
what: 'the morfo catalog',
|
|
|
|
|
|
context: /morfo|component|catálogo|catalog|matri|cat[aá]logo/i,
|
|
|
|
|
|
minToFlag: 20
|
|
|
|
|
|
},
|
fix(docs): counts sweep round 2 — 12 slots (not 13), 9 roles (not 8) + guards
Extending the I1 count guard to color roles and role slots surfaced
four more stale counts the palette fix predicted: the RFCs still said
'13 slots' and enumerated borderHover8 (the slot was retired 2026-07-02,
zero consumers — COLOR_ROLE_SLOTS is 12), and two component READMEs
(css-field, float-panel) still said '8 roles' from before tertiary made
it 9. The guard itself had the same disease it polices: extractConstArray
counted the quoted 'borderHover' inside the comment documenting its own
retirement, so expected=13 and the stale mentions passed — comments are
now stripped before extraction. css-field's README is fixed on disk but
left unstaged (it carries unrelated local modifications).
docs-check now verifies families=8, intents=6, archetypes=26,
palette=33, roles=9, slots=12 against their consts on every run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
{
|
|
|
|
|
|
re: /\b(\d+)\s+slots\b/gi,
|
|
|
|
|
|
expected: COLOR_ROLE_SLOTS.length,
|
|
|
|
|
|
what: 'COLOR_ROLE_SLOTS',
|
|
|
|
|
|
context: /color|slot|role|token/i,
|
|
|
|
|
|
exclude: /~\s*\d/,
|
|
|
|
|
|
minToFlag: 10
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of liveCorpus) {
|
|
|
|
|
|
const lines = read(file).split('\n');
|
|
|
|
|
|
lines.forEach((line, i) => {
|
|
|
|
|
|
for (const { re, expected, what, context, exclude, minToFlag } of COUNT_PATTERNS) {
|
|
|
|
|
|
re.lastIndex = 0;
|
|
|
|
|
|
for (const m of line.matchAll(re)) {
|
|
|
|
|
|
const n = Number(m[1]);
|
|
|
|
|
|
if (n === expected || n < minToFlag) continue;
|
fix(eidos-docs): the palette is 33 scales, not 31 — UI bug + docs + count guard
User-caught drift, and it was live in the UI, not just prose: the demo
harness's hand-copied PALETTE_GROUPS enumerated 31 scales — fuchsia and
steel (the two non-Radix scales) never appeared in any demo's
PalettePicker even though PALETTE_SCALES (the const, 33) and the recipe
cascade (paletteScaleDecls maps the const) resolve them fine. Fixed the
two missing entries, typed the groups as PaletteScale[], and added a
dev-time completeness assertion so growing the const without grouping
throws at module load.
Prose swept (the numbers were inherited verbatim from the pre-book
sources, which contradicted their own '33-scale library' headers):
decisions.md color-model row, demo-authoring x4 (incl. the '31-scale
palette' lede), rfc-color-engine x4 (ground-truth + templates
open-question). Where the count was incidental the text now points at
PALETTE_SCALES instead of restating a number.
Guard: docs-check I1 gains the palette count (EN + ES patterns,
~-approximation and type/space-scale exclusions; s-mark added to the
section-number skip). The check line now prints palette=33. This was
the exact class I1 already covered for families/intents/archetypes and
had not been extended to the palette.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// Section numbering ("B.7 Intent", "§35 scale canon") — number glued
|
|
|
|
|
|
// to a dot / section mark.
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
const before = line.slice(0, m.index ?? 0);
|
fix(eidos-docs): the palette is 33 scales, not 31 — UI bug + docs + count guard
User-caught drift, and it was live in the UI, not just prose: the demo
harness's hand-copied PALETTE_GROUPS enumerated 31 scales — fuchsia and
steel (the two non-Radix scales) never appeared in any demo's
PalettePicker even though PALETTE_SCALES (the const, 33) and the recipe
cascade (paletteScaleDecls maps the const) resolve them fine. Fixed the
two missing entries, typed the groups as PaletteScale[], and added a
dev-time completeness assertion so growing the const without grouping
throws at module load.
Prose swept (the numbers were inherited verbatim from the pre-book
sources, which contradicted their own '33-scale library' headers):
decisions.md color-model row, demo-authoring x4 (incl. the '31-scale
palette' lede), rfc-color-engine x4 (ground-truth + templates
open-question). Where the count was incidental the text now points at
PALETTE_SCALES instead of restating a number.
Guard: docs-check I1 gains the palette count (EN + ES patterns,
~-approximation and type/space-scale exclusions; s-mark added to the
section-number skip). The check line now prints palette=33. This was
the exact class I1 already covered for families/intents/archetypes and
had not been extended to the palette.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
if (/[.\d\-§#]$/.test(before)) continue;
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// Quoted historical mentions ('"7 families" survived…').
|
|
|
|
|
|
if (/["'“”]$/.test(before)) continue;
|
|
|
|
|
|
const windowText = `${lines[i - 1] ?? ''} ${line} ${lines[i + 1] ?? ''}`;
|
|
|
|
|
|
if (!context.test(windowText)) continue;
|
|
|
|
|
|
if (exclude && exclude.test(line)) continue;
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I1-count',
|
|
|
|
|
|
file,
|
|
|
|
|
|
i + 1,
|
|
|
|
|
|
`says "${m[0].trim()}" but ${what}.length is ${expected} — link the const, don't copy the count`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Invariant 1.bis — enumerated catalogs vs their source directory ────────
|
|
|
|
|
|
//
|
|
|
|
|
|
// A copied NUMBER is one disease ("8 roles"); a hand-enumerated LIST that fell
|
|
|
|
|
|
// behind is the other, and no word-counter can see it: `active-app.md` listed
|
|
|
|
|
|
// 12 of the 16 service factories without ever writing "12". Same law
|
|
|
|
|
|
// (`authoring.md`: a catalog becomes a pointer), so same check — compare the
|
|
|
|
|
|
// doc's mentions against the directory that IS the catalog.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Deliberately conservative: it only asks whether the entry is mentioned AT
|
|
|
|
|
|
// ALL, so a doc that names it in prose passes. That under-reports rather than
|
|
|
|
|
|
// crying wolf, and it still catches the real failure (an entry absent
|
|
|
|
|
|
// entirely, which is how `agent` / `motion` / `scene` / `sound` went missing).
|
|
|
|
|
|
|
|
|
|
|
|
interface CatalogCheck {
|
|
|
|
|
|
/** Repo-relative doc that enumerates the catalog. */
|
|
|
|
|
|
doc: string;
|
|
|
|
|
|
/** The entries that ARE the catalog, read from the tree. */
|
|
|
|
|
|
entries: string[];
|
|
|
|
|
|
/** Where the truth lives, for the message. */
|
|
|
|
|
|
source: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const CATALOG_CHECKS: CatalogCheck[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
doc: 'docs/architecture/active-app.md',
|
|
|
|
|
|
entries: SERVICE_FACTORIES,
|
|
|
|
|
|
source: 'src/arts/active-app/service-factories/'
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (const { doc, entries, source } of CATALOG_CHECKS) {
|
|
|
|
|
|
const full = join(REPO, doc);
|
|
|
|
|
|
if (!existsSync(full) || entries.length === 0) continue;
|
|
|
|
|
|
const text = read(full);
|
|
|
|
|
|
const missing = entries.filter((entry) => !new RegExp(`\\b${entry}\\b`, 'i').test(text));
|
|
|
|
|
|
if (missing.length > 0) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I1-catalog',
|
|
|
|
|
|
full,
|
|
|
|
|
|
1,
|
|
|
|
|
|
`enumerates ${source} (${entries.length} entries) but never mentions ${missing.join(', ')} — point at the directory, don't enumerate it`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// ── Invariant 2 — phantom fields ───────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
const MORFO_SOMA_DOCS = liveCorpus.filter((f) => {
|
|
|
|
|
|
const rel = relative(REPO, f).split(sep).join('/');
|
|
|
|
|
|
return (
|
|
|
|
|
|
rel.startsWith('src/uix/morfo/') ||
|
|
|
|
|
|
rel.startsWith('src/uix/soma/') ||
|
|
|
|
|
|
/^src\/uix\/[^/]+\.md$/.test(rel)
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/** Lines that legitimately DISCUSS the phantom (renames, rejections). */
|
|
|
|
|
|
const PHANTOM_MENTION_OK =
|
|
|
|
|
|
/renombr|renamed|legacy|migrat|descart|reject|rechaz|superseded|was removed|se retir/i;
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of MORFO_SOMA_DOCS) {
|
|
|
|
|
|
const lines = read(file).split('\n');
|
|
|
|
|
|
lines.forEach((line, i) => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
(/\btranslations:\s*\{/.test(line) || /\bmorfo\.translations\b/.test(line)) &&
|
|
|
|
|
|
!PHANTOM_MENTION_OK.test(line)
|
|
|
|
|
|
) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I2-phantom',
|
|
|
|
|
|
file,
|
|
|
|
|
|
i + 1,
|
|
|
|
|
|
'legacy `translations:` field — the Morfo field is `texts:` (idlangrefs)'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
docs+fix: old docs quarantined in docs/old-deprecated; STUMBLES doc-class fixes
Two user findings from the Knob build exercise (an agent building a new
component from the docs alone — STUMBLES.md).
Quarantine: the superseded fossils no longer share shelf space with the
live corpus. docs/old-deprecated/ (with an index README explaining what
lands there and pointing readers at docs/README.md) now holds the
executed audits and fix plans: fable_audit, fable-eidos-audit,
inherit_audit + inherit_fix_plan, ARCHETYPE_COHERENCE_AUDIT_2026-06-19
(still citable — the component-guide banner and the eidos components
README repoint to it), COMPONENT_COHERENCE_AUDIT. pendiente.md (a live
pending list, not a fossil) moved to docs/process/. docs-check treats
the folder as sealed chronicle (I1/I2 exempt; I6 skips its internal
links, as its README promises). Root-level *.md is now: README, CLAUDE,
AGENTS + the user's own working files.
STUMBLES fixes applied on the spot (the doc-class ones):
- #2 kind drift: the REAL enum is 'public' | 'private' | 'virtual'
(MorfoPartKind, 671/9/14 uses) — morfo.md omitted 'private', the
checklist invented 'internal' (0 uses). Both fixed; I2 gains the
phantom-'internal' guard. A-2.1's row now says what the audit script
actually checks (kebab only — the archetype may vary, the Toggle
provider-IS-trigger doctrine).
- #6: the langs catalog SHAPE (flat keys, per-language leaves, named
export, index registration) is now shown in morfo.md instead of only
its location.
- #8: component-audit s0 defines the minimum brief package as an
explicit 8-file list.
The engineering-class stumbles are registered as plan batches S1-S6
(generated vocabularies appendix, continuous-gesture trigger doctrine,
part-absent condition, Gesture.rotate, the soma->eidos CSS-var
contract, minor frictions). docs:check 0 errors, 11-warn baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// `kind: 'internal'` never existed — the MorfoPartKind enum is
|
|
|
|
|
|
// 'public' | 'private' | 'virtual' (morfo/types.ts). The phantom value
|
|
|
|
|
|
// survived in the completion checklist until the Knob build exercise
|
|
|
|
|
|
// tripped over it (STUMBLES.md #2, 2026-07-03).
|
|
|
|
|
|
if (/\bkind:\s*'public'\s*(?:\\?\|)\s*'internal'|\bkind:\s*'internal'/.test(line) && !PHANTOM_MENTION_OK.test(line)) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I2-phantom',
|
|
|
|
|
|
file,
|
|
|
|
|
|
i + 1,
|
|
|
|
|
|
"phantom part kind 'internal' — MorfoPartKind is 'public' | 'private' | 'virtual' (morfo/types.ts)"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of liveCorpus) {
|
|
|
|
|
|
const rel = relative(REPO, file).split(sep).join('/');
|
docs(book): F7.6 (1/2) — decision logs moved to docs/decisions/ (verbatim Spanish)
LIBRO_VARIACIONES_Y_EXTENSIONES -> decisions/book-deviations.md and
GUIA_IMPLEMENTACION_SEMAUIX -> decisions/guia-semantica-historica.md,
both moved AS-IS in Spanish: the deviations registry is a logbook of the
author's literal decisions ('transcrita literal') and carries proposed
doctrinal text destined for the Spanish book — translating it would
destroy that function (s G calls itself bitacora); the guia was already
status: historical (Fase 6) and the plan exempts it explicitly. English
frontmatter added to both; internal cross-links repointed (CANON,
theming/reference, book-deviations D.11). docs-check's I2 phantom-field
exemption follows the moved file (it matched by the LIBRO_VARIACIONES
filename; now also matches decisions/book-deviations.md). Corpus swept:
CANON x3, README (E2/E3 strata + tables — also fixed the pre-Fase-6
leftover row still calling the guia 'authoritative for any new wrapper'
and the unswept eidos/TSC.md stratum mention), building-a-component D.4,
completion-checklist G-1.1 + header, theming/reference, architecture
x5 (active-architecture, eidos, morfo, overview, sema x2).
docs:check 0 errors, 11-warn baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
if (rel.includes('LIBRO_VARIACIONES') || rel.endsWith('decisions/book-deviations.md')) continue; // the decision log that records the rejection
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
const lines = read(file).split('\n');
|
|
|
|
|
|
lines.forEach((line, i) => {
|
|
|
|
|
|
if (/\bdefaultSemantic\b/.test(line) && !PHANTOM_MENTION_OK.test(line)) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I2-phantom',
|
|
|
|
|
|
file,
|
|
|
|
|
|
i + 1,
|
|
|
|
|
|
'`defaultSemantic` was rejected (D.11) — the implemented shape is additive `allowedFamilies`'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Invariant 3 — dependency claims ⊆ package.json ─────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
const pkg = JSON.parse(read(join(REPO, 'package.json')));
|
|
|
|
|
|
const runtimeDeps = new Set(Object.keys(pkg.dependencies ?? {}));
|
|
|
|
|
|
// svelte is the framework (devDep by design); clsx is a KNOWN phantom import
|
|
|
|
|
|
// pending decision — documented as such, so a mention is legal only when the
|
|
|
|
|
|
// same line admits it is not declared.
|
|
|
|
|
|
const DEP_DOCS = [
|
|
|
|
|
|
join(REPO, 'src/uix/soma/README.md'),
|
|
|
|
|
|
join(REPO, 'src/uix/soma/SOMA_ARCHITECTURE.md')
|
|
|
|
|
|
];
|
|
|
|
|
|
const DEP_CLAIM = /^\s*[-*]\s+`?(@?[a-z][\w./-]*[\w])`?\s+\(/;
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of DEP_DOCS) {
|
|
|
|
|
|
if (!existsSync(file)) continue;
|
|
|
|
|
|
const lines = read(file).split('\n');
|
|
|
|
|
|
let inDepList = false;
|
|
|
|
|
|
lines.forEach((line, i) => {
|
|
|
|
|
|
if (/depende de:|dependencias npm declaradas|solo depende/.test(line)) inDepList = true;
|
|
|
|
|
|
else if (inDepList && /^\s*$/.test(line) && !DEP_CLAIM.test(lines[i + 1] ?? ''))
|
|
|
|
|
|
inDepList = false;
|
|
|
|
|
|
if (!inDepList) return;
|
|
|
|
|
|
const m = line.match(DEP_CLAIM);
|
|
|
|
|
|
if (!m) return;
|
|
|
|
|
|
const name = m[1];
|
|
|
|
|
|
if (name === 'svelte') return;
|
|
|
|
|
|
if (name.startsWith('$') || name.startsWith('src/')) return; // repo-internal
|
|
|
|
|
|
if (!runtimeDeps.has(name)) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I3-deps',
|
|
|
|
|
|
file,
|
|
|
|
|
|
i + 1,
|
|
|
|
|
|
`cites \`${name}\` as a dependency but package.json > dependencies does not declare it`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Corpus-wide: no live doc may present @floating-ui as a runtime dep. A
|
|
|
|
|
|
// mention is fine when the surrounding lines state its devDep-only /
|
|
|
|
|
|
// historical status (±1 line window — markdown wraps sentences).
|
|
|
|
|
|
for (const file of liveCorpus) {
|
feat(docs-check): el guard deja de ser ciego — I8/I9/I10 + corpus completo
El barrido exhaustivo del eje de opts encontro rutas fantasma, simbolos
inexistentes y props inventadas que docs:check no podia ver: sus 7 invariantes
no miraban dentro de los bloques de codigo, y su corpus no caminaba
src/arts, src/libs, src/svrs ni la raiz (567 de 673 .md). Tres invariantes
nuevos, escritos en la gramatica del propio guard:
- I8 imports: cabezas `$`/`@/` de los bloques ts/js/svelte contra la tabla de
alias EXTRAIDA de vite.config.ts (nunca copiada) + resolucion en disco.
`$lib` esta retirado: error en docs del framework (docs/**, src/uix/**),
tolerado en ejemplos de app consumidora. Builtins de SvelteKit en whitelist;
placeholders `{name}` y anti-ejemplos marcados, exentos.
- I9 simbolos: todo `XxxProvider`/`XxxOpts` en bloque de codigo debe existir
en el indice de simbolos de src. Exentos: placeholders, menciones negadas
("not TerraDialogProvider"), imports externos declarados por el propio
bloque (LoggerProvider de @opentelemetry) y el pragma `docs-check-allow:`.
- I10 tablas de props: cada prop documentada en un README de componente debe
declararse en su dir O en sus imports a un salto (convencion types.ts) —
el salto existe porque el rest-forwarding de eidos expone props que nunca
declara (los 4 falsos positivos del extractor lo demostraron).
Corpus 567 -> 614: entran las capas arts/libs/svrs/packs y la raiz;
crónica case-insensitive (continuar-*.md); docs aspiracionales
(RFC/IMPROVEMENTS/...) exentos de los invariantes nuevos.
Triage del fallout inicial (11): los 11 eran defectos del guard, no de los
docs — ethereal compara con @floating-ui por oficio (exento de I3),
`{Name}TriggerProvider` es placeholder pegado, LoggerProvider viene de un
paquete externo que el bloque importa. Verificado en ROJO (5 plantas, 5
disparos con linea exacta) y en verde (0/614).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
// The in-house engine's own docs (src/arts/ethereal) benchmark AGAINST
|
|
|
|
|
|
// @floating-ui — comparison is their raison d'être, not a dependency claim.
|
|
|
|
|
|
if (relative(REPO, file).split(sep).join('/').startsWith('src/arts/ethereal/')) continue;
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
const lines = read(file).split('\n');
|
|
|
|
|
|
lines.forEach((line, i) => {
|
|
|
|
|
|
if (!/@floating-ui/.test(line)) return;
|
|
|
|
|
|
const windowText = `${lines[i - 1] ?? ''} ${line} ${lines[i + 1] ?? ''}`;
|
|
|
|
|
|
if (
|
|
|
|
|
|
/devDep|dev-only|parity|paridad|histor|superseded|CONTINUE|removal|spec|motor propio|in-house|ya no|dejó de/i.test(
|
|
|
|
|
|
windowText
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
return;
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I3-deps',
|
|
|
|
|
|
file,
|
|
|
|
|
|
i + 1,
|
|
|
|
|
|
'`@floating-ui` presented without its devDep-only status — positioning is the in-house engine (`layers/floating` + `$ethereal`)'
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Invariant 4 — variant vocab mirror ─────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
const eidosTypes = read(join(REPO, 'src/uix/eidos/lib/types.ts'));
|
|
|
|
|
|
const auditSrc = read(join(REPO, 'scripts/component-audit.ts'));
|
|
|
|
|
|
|
|
|
|
|
|
const evm = eidosTypes.match(/EIDOS_VARIANTS\s*=\s*\{([\s\S]*?)\}\s*as const/);
|
|
|
|
|
|
if (!evm) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I4-mirror',
|
|
|
|
|
|
join(REPO, 'src/uix/eidos/lib/types.ts'),
|
|
|
|
|
|
1,
|
|
|
|
|
|
'cannot locate EIDOS_VARIANTS'
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const canonical = new Map<string, string[]>();
|
|
|
|
|
|
for (const m of evm[1].matchAll(/(\w+):\s*\[([^\]]*)\]/g)) {
|
|
|
|
|
|
canonical.set(
|
|
|
|
|
|
m[1],
|
|
|
|
|
|
[...m[2].matchAll(/'([\w-]+)'/g)].map((x) => x[1])
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
const aliasFor: Record<string, string> = {
|
|
|
|
|
|
control: 'ControlVariant',
|
|
|
|
|
|
selection: 'SelectionVariant',
|
|
|
|
|
|
chip: 'ChipVariant',
|
|
|
|
|
|
marker: 'MarkerVariant',
|
|
|
|
|
|
tabs: 'TabsVariant'
|
|
|
|
|
|
};
|
|
|
|
|
|
const mirrorBlock = auditSrc.match(/SHARED_VARIANT_VOCAB\s*=\s*new Map[\s\S]*?\]\);/);
|
|
|
|
|
|
if (!mirrorBlock) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I4-mirror',
|
|
|
|
|
|
join(REPO, 'scripts/component-audit.ts'),
|
|
|
|
|
|
1,
|
|
|
|
|
|
'cannot locate SHARED_VARIANT_VOCAB'
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const mirror = new Map<string, string[]>();
|
|
|
|
|
|
for (const m of mirrorBlock[0].matchAll(/\['(\w+)',\s*\[([\s\S]*?)\]\s*\]/g)) {
|
|
|
|
|
|
mirror.set(
|
|
|
|
|
|
m[1],
|
|
|
|
|
|
[...m[2].matchAll(/'([\w-]+)'/g)].map((x) => x[1])
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const [archetype, values] of canonical) {
|
|
|
|
|
|
const alias = aliasFor[archetype];
|
|
|
|
|
|
if (!alias) continue;
|
|
|
|
|
|
const mirrored = mirror.get(alias);
|
|
|
|
|
|
if (!mirrored) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I4-mirror',
|
|
|
|
|
|
join(REPO, 'scripts/component-audit.ts'),
|
|
|
|
|
|
1,
|
|
|
|
|
|
`SHARED_VARIANT_VOCAB is missing '${alias}' (EIDOS_VARIANTS.${archetype})`
|
|
|
|
|
|
);
|
|
|
|
|
|
} else if (mirrored.join('|') !== values.join('|')) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I4-mirror',
|
|
|
|
|
|
join(REPO, 'scripts/component-audit.ts'),
|
|
|
|
|
|
1,
|
|
|
|
|
|
`SHARED_VARIANT_VOCAB['${alias}'] = [${mirrored.join(', ')}] != EIDOS_VARIANTS.${archetype} = [${values.join(', ')}]`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Invariant 5 — checklist ↔ component-audit ──────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
{
|
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
const checklistPath = join(REPO, 'docs/guides/completion-checklist.md');
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
const checklist = read(checklistPath);
|
|
|
|
|
|
const auditSrc = read(join(REPO, 'scripts/component-audit.ts'));
|
|
|
|
|
|
|
|
|
|
|
|
// Checklist rows: | ID | Rule... | severity | applicability | enforcement |
|
|
|
|
|
|
const rowRe =
|
|
|
|
|
|
/^\|\s*([A-Z]+-\d+\.\d+[a-z]?)\s*\|.*\|\s*(audit(?:\s*\(via [A-Z]+-\d+\.\d+[a-z]?\))?|tool:[\w:-]+|manual)\s*\|\s*$/;
|
|
|
|
|
|
const declared = new Map<string, string>();
|
|
|
|
|
|
checklist.split('\n').forEach((line, i) => {
|
|
|
|
|
|
const m = line.match(rowRe);
|
|
|
|
|
|
if (m) declared.set(m[1], m[2]);
|
|
|
|
|
|
else {
|
|
|
|
|
|
// Row without a recognizable enforcement value?
|
|
|
|
|
|
const idOnly = line.match(/^\|\s*([A-Z]+-\d+\.\d+[a-z]?)\s*\|/);
|
|
|
|
|
|
if (idOnly) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I5-checklist',
|
|
|
|
|
|
checklistPath,
|
|
|
|
|
|
i + 1,
|
|
|
|
|
|
`rule ${idOnly[1]} has no recognizable Enforcement value (audit | tool:X | manual)`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const implemented = new Set(
|
|
|
|
|
|
[...auditSrc.matchAll(/(?:pass|fail)\(\s*'([A-Z]+-\d+\.\d+[a-z]?)'/g)].map((m) => m[1])
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
for (const [id, enforcement] of declared) {
|
|
|
|
|
|
if (enforcement === 'audit' && !implemented.has(id)) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I5-checklist',
|
|
|
|
|
|
checklistPath,
|
|
|
|
|
|
1,
|
|
|
|
|
|
`rule ${id} declares Enforcement: audit but component-audit.ts never emits it`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
const via = enforcement.match(/audit\s*\(via ([A-Z]+-\d+\.\d+[a-z]?)\)/);
|
|
|
|
|
|
if (via && !implemented.has(via[1])) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I5-checklist',
|
|
|
|
|
|
checklistPath,
|
|
|
|
|
|
1,
|
|
|
|
|
|
`rule ${id} claims coverage via ${via[1]} but component-audit.ts never emits ${via[1]}`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const id of implemented) {
|
|
|
|
|
|
if (!declared.has(id)) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I5-checklist',
|
|
|
|
|
|
checklistPath,
|
|
|
|
|
|
1,
|
|
|
|
|
|
`component-audit.ts emits ${id} but the checklist does not declare it`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(cleanroom): F2+F3+F4-C+SEM-4s1 — lote mecánico, censos con guard, corpus documental y el close polimórfico de los pickers VIVO
F2 — lote mecánico (13 ítems):
- DEP-2 ogl eliminado (0 imports) · DEP-1 clsx inlineado como toClassString
propio + suite de contrato (props.test.ts; soma.md §12 cerrado).
- THM-7: los 5 selectores manuales de sema.md reescritos con semaSelector
(los ejemplos [data-toast-root] apuntaban a un part INEXISTENTE — la
deriva que el builder previene, demostrada en el propio doc).
- MOR-1 escape isomorfo + validación de attr-names en semaSelector + 9
tests (selectors.test.ts, matches() real con comillas/corchetes) ·
MOR-2 partMarkerAttr = única fuente compilador↔builder + test de paridad ·
MOR-3 _resetCompileCache borrado (0 usos).
- SOM-2 keydown continue en match sin handler + keyboardFixtureMorfo ·
SOM-1 no-await de handlers (censo async = 0; contrato V1 cumplido) + pin.
- SEM-2 trigger pre-attacha catch con logger (void trigger sin unhandled
rejection; throw intacto para awaiters) + pin · SEM-3 fallback muerto de
applyDominance → skip defensivo + timer tope de awaitExpression cancelado ·
SEC-1 adjudicado YA implementado (assertCssVariableValue desde 2026-05-11)
+ pin del path de VALOR.
- accordion → outline (§32; su outline:none dejaba CERO anillo en HCM) —
verificado en vivo · THM-6 radius-full 9999px · EID-4 recuentos 33.
F3 — censos con guard:
- SOM-3 cerrado: announcer + image-provider migrados a scheduler-preferred
(consumidores cableados: date/time-field vía soma.uix.timers; avatar/image
vía eidos.timers — verificado en vivo); guard de timers ENSANCHADO de
soma/components a TODO soma y pasado a EVIDENCIA (setTimeout exige
.schedule( en el fichero — layers/ y datetime/ escapaban del ámbito viejo).
- THM-5: R-4.7 nueva (válvula same-line /* important: <razón> */, escaneo
comment-blanked) + las 15 declaraciones anotadas con su razón + canon
recipe-contract §3/§4.
- SOM-4 adjudicado: el censo/guard YA existían (49 pins); knob/mask-field/
timeline pinneados (overrides documentados en call-site); media-player
Batch-4 (35 hits, cero renderProps) = único batch restante, registrado.
- THM-4 doctrinado en eidos.md §unused (comportamiento/composición =
legítimo; deuda = eje visual sin consumidor; hotspots por lotes).
F4-C — corpus documental (decisiones de usuario aplicadas):
- DOC-3: los 15 enlaces muertos resueltos (repoint a la edición FINAL
trackeada / des-link históricos) · docs:check I6-links WARN→ERROR.
- DOC-1: tabla «Build contract» MIGRADA a component-guide con estados
modernizados (A3–A5 → LIVE + guards de hoy); banners reapuntados; citas
de CANON/sema.md historificadas; lápida-redirect en el §13 del fósil.
- DOC-4: hold chain → holds.ts · FAQ event:* SUPERSEDED por signatures ·
gradient añadido a los DOS capstones (sextet real) · nota de paleta de
demo-authoring corregida (universalPaletteDecls + decisión THM-2 =
mecanismo universal como sucesor del tracker borrado).
- DOC-5/6: recuentos anti-frágiles datados · §4.11 dup → §4.12 · Known gaps
historificado · N-6/N-7 recuperadas de git (d68d2c45^) y canonizadas en
eidos.md §pickers · authoring E2 → canon/tsc.md · air-old des-linkado ·
EID-3 (placement) en la fila RTL · AUX-2 disabledDom documentado.
SEM-4 sesión 1 — el close polimórfico de los pickers, VIVO (D.11):
- Reconciliación: los morfos ya no declaran close (delegated al Popover,
de-dialoged 06-27); el agujero real era el cierre programático bypaseando
dismissWith → save/cancel/select eran perceptualmente SILENCIOSOS.
- Fix: PickerShellHandle.setPopoverDismiss + closeWith(cause) en los 5
providers (14 sitios; select/commit → 'save' = commit.save+fulfill,
cancel → 'cancel' = emerge; fallback raw para headless) + UN inyector en
el eidos PickerShell root (norma N-8). Picker genérico fuera a propósito
(ya suena commit-set/cancel por diseño S9).
- Verificado en vivo (date-picker): Done → close·commit·fulfill·active ·
Cancel → close·emerge · cierre real.
Gates: matriz 141/141 (los 6 morfos nuevos de la pista de texto paralela
también PASS) · contracts 38/38 · eidos 314 · sema 178 · morfo 94 ·
docs:check 0/0 con I6 en error · baseline propio 57.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// ── Invariant 6 — relative links resolve (ERROR since 2026-07-11) ──────────
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
|
|
|
|
|
|
const LINK_RE = /\[[^\]]*\]\(([^)\s]+)\)/g;
|
|
|
|
|
|
|
|
|
|
|
|
const linkScope = corpus.filter((f) => {
|
|
|
|
|
|
const rel = relative(REPO, f).split(sep).join('/');
|
|
|
|
|
|
if (rel.includes('Disenando_lo_que_ocurre')) return false;
|
|
|
|
|
|
// docs/** + uix layer-level docs (not per-component READMEs — too many
|
|
|
|
|
|
// historical relative paths; promote later if useful).
|
|
|
|
|
|
return (
|
|
|
|
|
|
rel.startsWith('docs/') ||
|
|
|
|
|
|
/^src\/uix\/[^/]+\.md$/.test(rel) ||
|
|
|
|
|
|
/^src\/uix\/(morfo|soma|sema|eidos|active-uix)\/[^/]+\.md$/.test(rel) ||
|
|
|
|
|
|
/^src\/docs\/[^/]+\.md$/.test(rel)
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of linkScope) {
|
docs+fix: old docs quarantined in docs/old-deprecated; STUMBLES doc-class fixes
Two user findings from the Knob build exercise (an agent building a new
component from the docs alone — STUMBLES.md).
Quarantine: the superseded fossils no longer share shelf space with the
live corpus. docs/old-deprecated/ (with an index README explaining what
lands there and pointing readers at docs/README.md) now holds the
executed audits and fix plans: fable_audit, fable-eidos-audit,
inherit_audit + inherit_fix_plan, ARCHETYPE_COHERENCE_AUDIT_2026-06-19
(still citable — the component-guide banner and the eidos components
README repoint to it), COMPONENT_COHERENCE_AUDIT. pendiente.md (a live
pending list, not a fossil) moved to docs/process/. docs-check treats
the folder as sealed chronicle (I1/I2 exempt; I6 skips its internal
links, as its README promises). Root-level *.md is now: README, CLAUDE,
AGENTS + the user's own working files.
STUMBLES fixes applied on the spot (the doc-class ones):
- #2 kind drift: the REAL enum is 'public' | 'private' | 'virtual'
(MorfoPartKind, 671/9/14 uses) — morfo.md omitted 'private', the
checklist invented 'internal' (0 uses). Both fixed; I2 gains the
phantom-'internal' guard. A-2.1's row now says what the audit script
actually checks (kebab only — the archetype may vary, the Toggle
provider-IS-trigger doctrine).
- #6: the langs catalog SHAPE (flat keys, per-language leaves, named
export, index registration) is now shown in morfo.md instead of only
its location.
- #8: component-audit s0 defines the minimum brief package as an
explicit 8-file list.
The engineering-class stumbles are registered as plan batches S1-S6
(generated vocabularies appendix, continuous-gesture trigger doctrine,
part-absent condition, Gesture.rotate, the soma->eidos CSS-var
contract, minor frictions). docs:check 0 errors, 11-warn baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// Sealed fossils: their internal links reflect the tree at their moment in
|
|
|
|
|
|
// time and are allowed to dangle (docs/old-deprecated/README.md).
|
|
|
|
|
|
if (relative(REPO, file).split(sep).join('/').startsWith('docs/old-deprecated/')) continue;
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
const lines = read(file).split('\n');
|
|
|
|
|
|
let inFence = false;
|
|
|
|
|
|
lines.forEach((line, i) => {
|
|
|
|
|
|
if (/^\s*```/.test(line)) inFence = !inFence;
|
|
|
|
|
|
if (inFence) return;
|
|
|
|
|
|
for (const m of line.matchAll(LINK_RE)) {
|
|
|
|
|
|
const target = m[1];
|
|
|
|
|
|
if (/^(https?:|mailto:|#)/.test(target)) continue;
|
|
|
|
|
|
const clean = decodeURI(target.split('#')[0]);
|
|
|
|
|
|
if (!clean) continue;
|
|
|
|
|
|
const abs = resolve(dirname(file), clean);
|
|
|
|
|
|
if (!existsSync(abs)) {
|
fix(cleanroom): F2+F3+F4-C+SEM-4s1 — lote mecánico, censos con guard, corpus documental y el close polimórfico de los pickers VIVO
F2 — lote mecánico (13 ítems):
- DEP-2 ogl eliminado (0 imports) · DEP-1 clsx inlineado como toClassString
propio + suite de contrato (props.test.ts; soma.md §12 cerrado).
- THM-7: los 5 selectores manuales de sema.md reescritos con semaSelector
(los ejemplos [data-toast-root] apuntaban a un part INEXISTENTE — la
deriva que el builder previene, demostrada en el propio doc).
- MOR-1 escape isomorfo + validación de attr-names en semaSelector + 9
tests (selectors.test.ts, matches() real con comillas/corchetes) ·
MOR-2 partMarkerAttr = única fuente compilador↔builder + test de paridad ·
MOR-3 _resetCompileCache borrado (0 usos).
- SOM-2 keydown continue en match sin handler + keyboardFixtureMorfo ·
SOM-1 no-await de handlers (censo async = 0; contrato V1 cumplido) + pin.
- SEM-2 trigger pre-attacha catch con logger (void trigger sin unhandled
rejection; throw intacto para awaiters) + pin · SEM-3 fallback muerto de
applyDominance → skip defensivo + timer tope de awaitExpression cancelado ·
SEC-1 adjudicado YA implementado (assertCssVariableValue desde 2026-05-11)
+ pin del path de VALOR.
- accordion → outline (§32; su outline:none dejaba CERO anillo en HCM) —
verificado en vivo · THM-6 radius-full 9999px · EID-4 recuentos 33.
F3 — censos con guard:
- SOM-3 cerrado: announcer + image-provider migrados a scheduler-preferred
(consumidores cableados: date/time-field vía soma.uix.timers; avatar/image
vía eidos.timers — verificado en vivo); guard de timers ENSANCHADO de
soma/components a TODO soma y pasado a EVIDENCIA (setTimeout exige
.schedule( en el fichero — layers/ y datetime/ escapaban del ámbito viejo).
- THM-5: R-4.7 nueva (válvula same-line /* important: <razón> */, escaneo
comment-blanked) + las 15 declaraciones anotadas con su razón + canon
recipe-contract §3/§4.
- SOM-4 adjudicado: el censo/guard YA existían (49 pins); knob/mask-field/
timeline pinneados (overrides documentados en call-site); media-player
Batch-4 (35 hits, cero renderProps) = único batch restante, registrado.
- THM-4 doctrinado en eidos.md §unused (comportamiento/composición =
legítimo; deuda = eje visual sin consumidor; hotspots por lotes).
F4-C — corpus documental (decisiones de usuario aplicadas):
- DOC-3: los 15 enlaces muertos resueltos (repoint a la edición FINAL
trackeada / des-link históricos) · docs:check I6-links WARN→ERROR.
- DOC-1: tabla «Build contract» MIGRADA a component-guide con estados
modernizados (A3–A5 → LIVE + guards de hoy); banners reapuntados; citas
de CANON/sema.md historificadas; lápida-redirect en el §13 del fósil.
- DOC-4: hold chain → holds.ts · FAQ event:* SUPERSEDED por signatures ·
gradient añadido a los DOS capstones (sextet real) · nota de paleta de
demo-authoring corregida (universalPaletteDecls + decisión THM-2 =
mecanismo universal como sucesor del tracker borrado).
- DOC-5/6: recuentos anti-frágiles datados · §4.11 dup → §4.12 · Known gaps
historificado · N-6/N-7 recuperadas de git (d68d2c45^) y canonizadas en
eidos.md §pickers · authoring E2 → canon/tsc.md · air-old des-linkado ·
EID-3 (placement) en la fila RTL · AUX-2 disabledDom documentado.
SEM-4 sesión 1 — el close polimórfico de los pickers, VIVO (D.11):
- Reconciliación: los morfos ya no declaran close (delegated al Popover,
de-dialoged 06-27); el agujero real era el cierre programático bypaseando
dismissWith → save/cancel/select eran perceptualmente SILENCIOSOS.
- Fix: PickerShellHandle.setPopoverDismiss + closeWith(cause) en los 5
providers (14 sitios; select/commit → 'save' = commit.save+fulfill,
cancel → 'cancel' = emerge; fallback raw para headless) + UN inyector en
el eidos PickerShell root (norma N-8). Picker genérico fuera a propósito
(ya suena commit-set/cancel por diseño S9).
- Verificado en vivo (date-picker): Done → close·commit·fulfill·active ·
Cancel → close·emerge · cierre real.
Gates: matriz 141/141 (los 6 morfos nuevos de la pista de texto paralela
también PASS) · contracts 38/38 · eidos 314 · sema 178 · morfo 94 ·
docs:check 0/0 con I6 en error · baseline propio 57.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
report('error', 'I6-links', file, i + 1, `relative link does not resolve: ${target}`);
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(docs): S1 — generated canonical-vocabulary appendix (STUMBLES #1)
An agent building a component from the docs-book alone could not assign
archetypes / holds / haptic kinds: the closed sets live only in code consts
and the corpus (correctly) forbids copying them into prose, so they were
invisible from the docs. STUMBLES #1, the top stumble.
Fix: docs/canon/vocabularies.md is GENERATED from the consts by
'npm run docs:vocabularies' — the ONE sanctioned place the lists are spelled
out (generated = no drift objection), which every other doc links to. It
covers part archetypes (with a one-line role each), sema families with their
default hold/persistence + per-intent overrides, verbs by family, intents,
the perceptual-duration scale, haptic kinds, the 33 palette scales, sizes,
variant archetypes, and the shared common.* strings.
- scripts/docs-vocabularies.ts imports the consts (robust — no fragile
JSDoc/regex parsing) and formats them; the generation is an exported
function so docs-check can compare.
- Added ARCHETYPE_DESCRIPTIONS to morfo/types.ts (co-located with
ARCHETYPE_VOCABULARY, satisfies Record<MorfoArchetype,string>) as the
authoritative one-liners the appendix reads — descriptions become
first-class data instead of inline union JSDoc a tool would have to parse.
- docs-check gains I7: it regenerates in-memory and fails if the committed
file drifts from the consts (negative-tested: corrupt -> error, regen ->
green). The appendix's own counts pass I1, so I1 doubles as a second guard.
- Linked from README (E2 canon row + two 'I want to' shortcuts), CANON.md
(doctrine here, enumerated lists there), architecture/morfo.md (pick a
part's archetype), and the component-audit minimum-package list (STUMBLES
#8).
docs:check 0 errors; npm run check unchanged in my files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// ── Invariant 7 — generated vocabularies appendix is fresh ─────────────────
|
|
|
|
|
|
// docs/canon/vocabularies.md is generated from the code consts. If it drifts
|
|
|
|
|
|
// (a const changed but `npm run docs:vocabularies` wasn't re-run), the appendix
|
|
|
|
|
|
// that agents build from is stale. Regenerate in-memory and compare.
|
|
|
|
|
|
{
|
|
|
|
|
|
const vocPath = join(REPO, 'docs/canon/vocabularies.md');
|
|
|
|
|
|
const expected = generateVocabulariesDoc();
|
|
|
|
|
|
const actual = existsSync(vocPath) ? read(vocPath).replace(/\r\n/g, '\n') : '';
|
|
|
|
|
|
if (actual !== expected) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I7-vocab',
|
|
|
|
|
|
vocPath,
|
|
|
|
|
|
1,
|
|
|
|
|
|
'docs/canon/vocabularies.md is stale — run `npm run docs:vocabularies` (it is generated from the code consts)'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(docs-check): el guard deja de ser ciego — I8/I9/I10 + corpus completo
El barrido exhaustivo del eje de opts encontro rutas fantasma, simbolos
inexistentes y props inventadas que docs:check no podia ver: sus 7 invariantes
no miraban dentro de los bloques de codigo, y su corpus no caminaba
src/arts, src/libs, src/svrs ni la raiz (567 de 673 .md). Tres invariantes
nuevos, escritos en la gramatica del propio guard:
- I8 imports: cabezas `$`/`@/` de los bloques ts/js/svelte contra la tabla de
alias EXTRAIDA de vite.config.ts (nunca copiada) + resolucion en disco.
`$lib` esta retirado: error en docs del framework (docs/**, src/uix/**),
tolerado en ejemplos de app consumidora. Builtins de SvelteKit en whitelist;
placeholders `{name}` y anti-ejemplos marcados, exentos.
- I9 simbolos: todo `XxxProvider`/`XxxOpts` en bloque de codigo debe existir
en el indice de simbolos de src. Exentos: placeholders, menciones negadas
("not TerraDialogProvider"), imports externos declarados por el propio
bloque (LoggerProvider de @opentelemetry) y el pragma `docs-check-allow:`.
- I10 tablas de props: cada prop documentada en un README de componente debe
declararse en su dir O en sus imports a un salto (convencion types.ts) —
el salto existe porque el rest-forwarding de eidos expone props que nunca
declara (los 4 falsos positivos del extractor lo demostraron).
Corpus 567 -> 614: entran las capas arts/libs/svrs/packs y la raiz;
crónica case-insensitive (continuar-*.md); docs aspiracionales
(RFC/IMPROVEMENTS/...) exentos de los invariantes nuevos.
Triage del fallout inicial (11): los 11 eran defectos del guard, no de los
docs — ethereal compara con @floating-ui por oficio (exento de I3),
`{Name}TriggerProvider` es placeholder pegado, LoggerProvider viene de un
paquete externo que el bloque importa. Verificado en ROJO (5 plantas, 5
disparos con linea exacta) y en verde (0/614).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
// ── Shared machinery for invariants 8–10 ───────────────────────────────────
|
|
|
|
|
|
//
|
|
|
|
|
|
// All three were born 2026-08-05 from the exhaustive opts-axis audit: the
|
|
|
|
|
|
// mechanical sweep that found them ran OUTSIDE this guard, which is exactly
|
|
|
|
|
|
// the blindness these invariants close. They scan fenced code blocks only —
|
|
|
|
|
|
// prose and comparison tables legitimately name foreign-library APIs.
|
|
|
|
|
|
|
|
|
|
|
|
interface Fence {
|
|
|
|
|
|
lang: string;
|
|
|
|
|
|
/** 1-based line of the opening ``` — content lines carry their own number. */
|
|
|
|
|
|
lines: { text: string; line: number }[];
|
|
|
|
|
|
/** The two lines above the opener — anti-example fences announce themselves there. */
|
|
|
|
|
|
intro: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function codeFences(rawLines: string[]): Fence[] {
|
|
|
|
|
|
const out: Fence[] = [];
|
|
|
|
|
|
let cur: Fence | null = null;
|
|
|
|
|
|
rawLines.forEach((text, i) => {
|
|
|
|
|
|
const open = text.match(/^\s*```([\w-]*)\s*$/);
|
|
|
|
|
|
if (open) {
|
|
|
|
|
|
if (cur) {
|
|
|
|
|
|
out.push(cur);
|
|
|
|
|
|
cur = null;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
cur = {
|
|
|
|
|
|
lang: open[1].toLowerCase(),
|
|
|
|
|
|
lines: [],
|
|
|
|
|
|
intro: `${rawLines[i - 2] ?? ''} ${rawLines[i - 1] ?? ''}`
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (cur) cur.lines.push({ text, line: i + 1 });
|
|
|
|
|
|
});
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const CODE_LANGS = new Set(['ts', 'typescript', 'js', 'javascript', 'svelte']);
|
|
|
|
|
|
|
|
|
|
|
|
/** Deliberate anti-examples — the doc is SHOWING the mistake. */
|
|
|
|
|
|
const ANTI_EXAMPLE = /❌|✗|\bwrong\b|\bbad\b|\bnever\b|\bnunca\b|\bantes\b|\bbefore\b|\bmal\b/i;
|
|
|
|
|
|
|
|
|
|
|
|
/** `<!-- docs-check-allow: SymbolA, SymbolB -->` pragma, per doc. */
|
|
|
|
|
|
function allowedSymbols(text: string): Set<string> {
|
|
|
|
|
|
const out = new Set<string>();
|
|
|
|
|
|
for (const m of text.matchAll(/docs-check-allow:\s*([^\n>]+)/g)) {
|
|
|
|
|
|
for (const s of m[1].split(/[,\s]+/)) if (s) out.add(s);
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const codeCorpus = liveCorpus.filter((f) => !isAspirational(f));
|
|
|
|
|
|
|
|
|
|
|
|
// ── Invariant 8 — code-block imports resolve ───────────────────────────────
|
|
|
|
|
|
//
|
|
|
|
|
|
// The alias table is EXTRACTED from vite.config.ts (the declared source of
|
|
|
|
|
|
// truth for aliases), never copied — copying it here would recreate the
|
|
|
|
|
|
// disease this script exists to catch.
|
|
|
|
|
|
|
|
|
|
|
|
const ALIASES = new Map<string, string>();
|
|
|
|
|
|
{
|
|
|
|
|
|
const viteSrc = read(join(REPO, 'vite.config.ts'));
|
|
|
|
|
|
for (const m of viteSrc.matchAll(
|
|
|
|
|
|
/['"]([$@][\w./-]*)['"]\s*:\s*resolve\(__dirname,\s*['"]([^'"]+)['"]\)/g
|
|
|
|
|
|
)) {
|
|
|
|
|
|
ALIASES.set(m[1], m[2]);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (ALIASES.size < 10) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I8-imports',
|
|
|
|
|
|
join(REPO, 'vite.config.ts'),
|
|
|
|
|
|
1,
|
|
|
|
|
|
`alias extraction found only ${ALIASES.size} entries — the aliases const changed shape`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** SvelteKit's own virtual modules — legal without being repo aliases. */
|
|
|
|
|
|
const SVELTEKIT_HEADS = new Set(['$app', '$env', '$service-worker']);
|
|
|
|
|
|
|
|
|
|
|
|
const IMPORT_RE = /(?:from\s+|import\s*\(\s*|import\s+)['"]([^'"\n]+)['"]/g;
|
|
|
|
|
|
|
|
|
|
|
|
/** A doc that speaks WITH the framework's voice — consumer-app idioms are drift there. */
|
|
|
|
|
|
function isFrameworkOwned(file: string): boolean {
|
|
|
|
|
|
const rel = relative(REPO, file).split(sep).join('/');
|
|
|
|
|
|
return rel.startsWith('docs/') || rel.startsWith('src/uix/');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function importResolves(head: string, rest: string): boolean {
|
|
|
|
|
|
const base = join(REPO, ALIASES.get(head)!, rest);
|
|
|
|
|
|
if (existsSync(base)) return true; // file or directory
|
|
|
|
|
|
return ['.ts', '.svelte', '.svelte.ts', '.js', `${sep}index.ts`].some((suffix) =>
|
|
|
|
|
|
existsSync(base + suffix)
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of codeCorpus) {
|
|
|
|
|
|
const rawLines = read(file).split('\n');
|
|
|
|
|
|
for (const fence of codeFences(rawLines)) {
|
|
|
|
|
|
if (!CODE_LANGS.has(fence.lang)) continue;
|
|
|
|
|
|
if (ANTI_EXAMPLE.test(fence.intro)) continue;
|
|
|
|
|
|
fence.lines.forEach(({ text, line }, idx) => {
|
|
|
|
|
|
if (ANTI_EXAMPLE.test(text) || ANTI_EXAMPLE.test(fence.lines[idx - 1]?.text ?? '')) return;
|
|
|
|
|
|
IMPORT_RE.lastIndex = 0;
|
|
|
|
|
|
for (const m of text.matchAll(IMPORT_RE)) {
|
|
|
|
|
|
const spec = m[1];
|
|
|
|
|
|
if (/[{}<>]|\.\.\./.test(spec)) continue; // `{name}` placeholders / elisions
|
|
|
|
|
|
if (!spec.startsWith('$') && !spec.startsWith('@/') && spec !== '@') continue;
|
|
|
|
|
|
const slash = spec.indexOf('/');
|
|
|
|
|
|
const head = slash === -1 ? spec : spec.slice(0, slash);
|
|
|
|
|
|
const rest = slash === -1 ? '' : spec.slice(slash + 1);
|
|
|
|
|
|
if (SVELTEKIT_HEADS.has(head)) continue;
|
|
|
|
|
|
if (head === '$lib') {
|
|
|
|
|
|
if (isFrameworkOwned(file)) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I8-imports',
|
|
|
|
|
|
file,
|
|
|
|
|
|
line,
|
|
|
|
|
|
`\`${spec}\` — the \`$lib\` alias was retired; framework docs must use the real repo alias`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
continue; // consumer-app examples elsewhere may keep their own $lib
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!ALIASES.has(head)) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I8-imports',
|
|
|
|
|
|
file,
|
|
|
|
|
|
line,
|
|
|
|
|
|
`\`${spec}\` — \`${head}\` is not a repo alias (vite.config.ts)`
|
|
|
|
|
|
);
|
|
|
|
|
|
} else if (!importResolves(head, rest)) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I8-imports',
|
|
|
|
|
|
file,
|
|
|
|
|
|
line,
|
|
|
|
|
|
`\`${spec}\` does not resolve on disk (alias \`${head}\` → ${ALIASES.get(head)})`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Invariant 9 — Provider/Opts symbols exist in src ───────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
const symbolIndex = new Set<string>();
|
|
|
|
|
|
{
|
|
|
|
|
|
const stack = [join(REPO, 'src')];
|
|
|
|
|
|
while (stack.length > 0) {
|
|
|
|
|
|
const dir = stack.pop()!;
|
|
|
|
|
|
for (const entry of readdirSync(dir)) {
|
|
|
|
|
|
const full = join(dir, entry);
|
|
|
|
|
|
if (statSync(full).isDirectory()) {
|
|
|
|
|
|
if (!SKIP_DIRS.has(entry)) stack.push(full);
|
|
|
|
|
|
} else if (/\.(ts|svelte)$/.test(entry) && !/\.(test|spec)\.ts$/.test(entry)) {
|
|
|
|
|
|
for (const m of read(full).matchAll(
|
|
|
|
|
|
/\b(?:class|interface|type|function|const|let|enum)\s+([A-Z][A-Za-z0-9_]*)/g
|
|
|
|
|
|
)) {
|
|
|
|
|
|
symbolIndex.add(m[1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Placeholder stems used by guides for "your component here". */
|
|
|
|
|
|
const PLACEHOLDER_STEM =
|
|
|
|
|
|
/^(X{1,3}[a-z]*|[A-Z]|Foo|Bar|Baz|Example|Sample|Demo|My\w*|Some\w*|Your\w*|Name|Abc|Component)$/;
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of codeCorpus) {
|
|
|
|
|
|
const text = read(file);
|
|
|
|
|
|
const allowed = allowedSymbols(text);
|
|
|
|
|
|
for (const fence of codeFences(text.split('\n'))) {
|
|
|
|
|
|
if (!CODE_LANGS.has(fence.lang)) continue;
|
|
|
|
|
|
if (ANTI_EXAMPLE.test(fence.intro)) continue;
|
|
|
|
|
|
// Symbols the block itself imports from an external package (e.g.
|
|
|
|
|
|
// `import { LoggerProvider } from '@opentelemetry/sdk-logs'`) carry
|
|
|
|
|
|
// their own provenance — they are not ours to index.
|
|
|
|
|
|
const externals = new Set<string>();
|
|
|
|
|
|
for (const { text: lineText } of fence.lines) {
|
|
|
|
|
|
const im = lineText.match(
|
|
|
|
|
|
/import\s+(?:([A-Za-z_$][\w$]*)\s*,?\s*)?(?:\{([^}]*)\})?\s*from\s*['"]([^'"]+)['"]/
|
|
|
|
|
|
);
|
|
|
|
|
|
if (!im) continue;
|
|
|
|
|
|
const spec = im[3];
|
|
|
|
|
|
if (spec.startsWith('$') || spec.startsWith('.') || spec.startsWith('@/')) continue;
|
|
|
|
|
|
if (im[1]) externals.add(im[1]);
|
|
|
|
|
|
for (const part of (im[2] ?? '').split(',')) {
|
|
|
|
|
|
const named = part
|
|
|
|
|
|
.trim()
|
|
|
|
|
|
.split(/\s+as\s+/)
|
|
|
|
|
|
.pop()
|
|
|
|
|
|
?.trim();
|
|
|
|
|
|
if (named) externals.add(named);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const { text: lineText, line } of fence.lines) {
|
|
|
|
|
|
if (/\bnot\b|\bno\b|\bnever\b|❌|✗/i.test(lineText)) continue; // negated mentions
|
|
|
|
|
|
for (const m of lineText.matchAll(/\b([A-Z][A-Za-z0-9_]*?)(Provider|Opts)\b/g)) {
|
|
|
|
|
|
const name = m[1] + m[2];
|
|
|
|
|
|
if (m[1] === '' || PLACEHOLDER_STEM.test(m[1])) continue;
|
|
|
|
|
|
// `{Name}TriggerProvider` — a placeholder prefix glued to the
|
|
|
|
|
|
// symbol; the `}` before the match gives it away.
|
|
|
|
|
|
if (lineText[(m.index ?? 0) - 1] === '}') continue;
|
|
|
|
|
|
if (allowed.has(name) || externals.has(name)) continue;
|
|
|
|
|
|
if (!symbolIndex.has(name)) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I9-symbols',
|
|
|
|
|
|
file,
|
|
|
|
|
|
line,
|
|
|
|
|
|
`\`${name}\` does not exist in src — phantom symbol (or add a \`docs-check-allow: ${name}\` pragma if it is deliberate)`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Invariant 10 — README prop tables ↔ component code ─────────────────────
|
|
|
|
|
|
//
|
|
|
|
|
|
// The truth set is deliberately over-inclusive (any identifier declared as a
|
|
|
|
|
|
// property / binding in the dir or its one-hop imports counts): it
|
|
|
|
|
|
// under-reports rather than crying wolf, and still catches the real failure —
|
|
|
|
|
|
// a documented prop NO source file ever declares (date-range-field's
|
|
|
|
|
|
// symmetric `readonlySegments` never existed; the API is per-endpoint).
|
|
|
|
|
|
|
|
|
|
|
|
const identifierCache = new Map<string, Set<string>>();
|
|
|
|
|
|
|
|
|
|
|
|
function identifiersOf(file: string): Set<string> {
|
|
|
|
|
|
const cached = identifierCache.get(file);
|
|
|
|
|
|
if (cached) return cached;
|
|
|
|
|
|
const out = new Set<string>();
|
|
|
|
|
|
if (existsSync(file) && statSync(file).isFile()) {
|
|
|
|
|
|
for (const line of read(file).split('\n')) {
|
|
|
|
|
|
const m = line.match(
|
|
|
|
|
|
/^\s*(?:export\s+)?(?:public\s+|private\s+|protected\s+|readonly\s+)*'?([A-Za-z_$][\w$]*)'?\s*\??\s*[:=,)]/
|
|
|
|
|
|
);
|
|
|
|
|
|
if (m) out.add(m[1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
identifierCache.set(file, out);
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Resolve a one-hop import target to concrete files worth harvesting. */
|
|
|
|
|
|
function hopTargets(fromFile: string, spec: string): string[] {
|
|
|
|
|
|
let base: string | null = null;
|
|
|
|
|
|
if (spec.startsWith('.')) {
|
|
|
|
|
|
base = resolve(dirname(fromFile), spec);
|
|
|
|
|
|
} else if (spec.startsWith('$') || spec.startsWith('@/') || spec === '@') {
|
|
|
|
|
|
const slash = spec.indexOf('/');
|
|
|
|
|
|
const head = slash === -1 ? spec : spec.slice(0, slash);
|
|
|
|
|
|
const target = ALIASES.get(head);
|
|
|
|
|
|
if (target) base = join(REPO, target, slash === -1 ? '' : spec.slice(slash + 1));
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!base || !base.startsWith(join(REPO, 'src'))) return [];
|
|
|
|
|
|
if (existsSync(base) && statSync(base).isDirectory()) {
|
|
|
|
|
|
// A directory / barrel — the framework convention keeps the prop
|
|
|
|
|
|
// surface in types.ts next to it.
|
|
|
|
|
|
return [join(base, 'types.ts'), join(base, 'index.ts')].filter((f) => existsSync(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const suffix of ['', '.ts', '.svelte', '.svelte.ts']) {
|
|
|
|
|
|
if (existsSync(base + suffix) && statSync(base + suffix).isFile()) {
|
|
|
|
|
|
const hits = [base + suffix];
|
|
|
|
|
|
const sibling = join(dirname(base + suffix), 'types.ts');
|
|
|
|
|
|
if (existsSync(sibling)) hits.push(sibling);
|
|
|
|
|
|
return hits;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const PROP_TABLE_HEADER = /^\|\s*`?(Props?|Property|Nombre)\b/i;
|
|
|
|
|
|
const TABLE_SEPARATOR = /^\|[\s:|-]+\|?\s*$/;
|
|
|
|
|
|
|
|
|
|
|
|
const componentReadmes = codeCorpus.filter((f) =>
|
|
|
|
|
|
/^src\/uix\/(soma|eidos)\/components\/[^/]+\/README\.md$/.test(
|
|
|
|
|
|
relative(REPO, f).split(sep).join('/')
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of componentReadmes) {
|
|
|
|
|
|
const dir = dirname(file);
|
|
|
|
|
|
// Truth set: every file in the component dir + one hop through its imports.
|
|
|
|
|
|
const truth = new Set<string>();
|
|
|
|
|
|
const dirFiles = readdirSync(dir)
|
|
|
|
|
|
.filter((f) => /\.(ts|svelte)$/.test(f))
|
|
|
|
|
|
.map((f) => join(dir, f));
|
|
|
|
|
|
for (const src of dirFiles) {
|
|
|
|
|
|
for (const id of identifiersOf(src)) truth.add(id);
|
|
|
|
|
|
for (const m of read(src).matchAll(IMPORT_RE)) {
|
|
|
|
|
|
for (const hop of hopTargets(src, m[1])) {
|
|
|
|
|
|
for (const id of identifiersOf(hop)) truth.add(id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (truth.size === 0) continue; // markdown-only dir — nothing to check against
|
|
|
|
|
|
|
|
|
|
|
|
const lines = read(file).split('\n');
|
|
|
|
|
|
let inPropTable = false;
|
|
|
|
|
|
lines.forEach((line, i) => {
|
|
|
|
|
|
if (PROP_TABLE_HEADER.test(line)) {
|
|
|
|
|
|
inPropTable = true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!line.startsWith('|')) {
|
|
|
|
|
|
inPropTable = false;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!inPropTable || TABLE_SEPARATOR.test(line)) return;
|
|
|
|
|
|
const firstCell = line.split('|')[1] ?? '';
|
|
|
|
|
|
for (const m of firstCell.matchAll(/`([^`]+)`/g)) {
|
|
|
|
|
|
const name = m[1].replace(/\?$/, '');
|
|
|
|
|
|
// Only bare camelCase prop names — methods, data-attrs, css vars,
|
|
|
|
|
|
// compound cells and event names live outside this invariant.
|
|
|
|
|
|
if (!/^[a-z][A-Za-z0-9]*$/.test(name)) continue;
|
|
|
|
|
|
if (!truth.has(name)) {
|
|
|
|
|
|
report(
|
|
|
|
|
|
'error',
|
|
|
|
|
|
'I10-props',
|
|
|
|
|
|
file,
|
|
|
|
|
|
i + 1,
|
|
|
|
|
|
`prop table documents \`${name}\` but no file in ${relative(REPO, dir).split(sep).join('/')} (or its one-hop imports) declares it`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
// ── Report ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
findings.sort(
|
|
|
|
|
|
(a, b) =>
|
|
|
|
|
|
a.invariant.localeCompare(b.invariant) || a.file.localeCompare(b.file) || a.line - b.line
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const errors = findings.filter((f) => f.severity === 'error');
|
|
|
|
|
|
const warns = findings.filter((f) => f.severity === 'warn');
|
|
|
|
|
|
|
|
|
|
|
|
for (const f of findings) {
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`${f.severity.toUpperCase().padEnd(5)} [${f.invariant}] ${f.file}:${f.line} — ${f.message}`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`\ndocs-check: ${errors.length} error(s), ${warns.length} warning(s) across ${corpus.length} docs ` +
|
fix(eidos-docs): the palette is 33 scales, not 31 — UI bug + docs + count guard
User-caught drift, and it was live in the UI, not just prose: the demo
harness's hand-copied PALETTE_GROUPS enumerated 31 scales — fuchsia and
steel (the two non-Radix scales) never appeared in any demo's
PalettePicker even though PALETTE_SCALES (the const, 33) and the recipe
cascade (paletteScaleDecls maps the const) resolve them fine. Fixed the
two missing entries, typed the groups as PaletteScale[], and added a
dev-time completeness assertion so growing the const without grouping
throws at module load.
Prose swept (the numbers were inherited verbatim from the pre-book
sources, which contradicted their own '33-scale library' headers):
decisions.md color-model row, demo-authoring x4 (incl. the '31-scale
palette' lede), rfc-color-engine x4 (ground-truth + templates
open-question). Where the count was incidental the text now points at
PALETTE_SCALES instead of restating a number.
Guard: docs-check I1 gains the palette count (EN + ES patterns,
~-approximation and type/space-scale exclusions; s-mark added to the
section-number skip). The check line now prints palette=33. This was
the exact class I1 already covered for families/intents/archetypes and
had not been extended to the palette.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
`(families=${SEMA_FAMILIES.length}, intents=${INTENTS.length}, archetypes=${ARCHETYPES.length}, palette=${PALETTE.length})`
|
feat(docs): docs:check guard — copyable-invariant lint for the doc corpus
Phase 3 of PLAN-docs-reconciliation (fable_audit D5: the 'link, don't
copy' law had no mechanical guard). New scripts/docs-check.ts + npm run
docs:check with six invariants, each born from a verified drift:
- I1 vocabulary counts vs SEMA_FAMILIES/INTENTS/ARCHETYPE_VOCABULARY
(context-gated: font/shape families, variant archetypes and evaluative
subsets don't false-positive; section numbers and quoted historical
mentions skipped).
- I2 phantom fields: translations: in morfo/soma docs, defaultSemantic
outside LIBRO_VARIACIONES (rename/rejection mentions allowed).
- I3 dependency claims: soma dep lists must cite real package.json
dependencies; corpus-wide @floating-ui mentions need their devDep-only
context.
- I4 SHARED_VARIANT_VOCAB (component-audit) == EIDOS_VARIANTS (textual
mirror comparison).
- I5 checklist<->audit: every Enforcement:audit rule exists in the
script, every script rule ID is declared.
- I6 relative links resolve (WARN until the known dangling targets get
their user decision: THEMING_AUDIT deleted in worktree, PENDIENTES.md
deleted 2026-06-07, MOTION_SERVICE_RFC demo routes).
Chronicle docs (process/, changelogs, audits, CONTINUEs, status:
historical frontmatter, the book) are exempt from truth-tracking.
Also: THEMING s2 per-layer table realigned to the real entrypoint (the
fragile rule-count column dropped); stale adom link in SOMA_ARCHITECTURE
fixed; validator documented in docs/testing-and-tooling.md.
Current output: 1 error (building-a-component known-traps row — cleared
in the closing pass) + 13 warns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
process.exit(errors.length > 0 ? 1 : 0);
|