@radicool/throughline 0.12.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +2 -2
  2. package/adapters/codex/AGENTS.md +2 -1
  3. package/adapters/codex/prompts/component-builder.md +80 -2
  4. package/adapters/codex/prompts/design-system-audit.md +20 -0
  5. package/adapters/codex/prompts/document-component.md +26 -0
  6. package/adapters/codex/prompts/icon-system-builder.md +10 -2
  7. package/adapters/codex/prompts/repository-builder.md +14 -0
  8. package/adapters/codex/prompts/retrofit-planner.md +21 -1
  9. package/adapters/codex/prompts/storybook-chromatic-builder.md +52 -7
  10. package/adapters/codex/prompts/token-builder.md +16 -0
  11. package/adapters/codex/prompts/token-sheet-builder.md +15 -1
  12. package/adapters/codex/prompts/token-sync-layer.md +14 -7
  13. package/adapters/cursor/.cursor/commands/document-component.md +26 -0
  14. package/adapters/cursor/.cursor/rules/component-builder.mdc +80 -2
  15. package/adapters/cursor/.cursor/rules/design-system-audit.mdc +20 -0
  16. package/adapters/cursor/.cursor/rules/icon-system-builder.mdc +10 -2
  17. package/adapters/cursor/.cursor/rules/repository-builder.mdc +14 -0
  18. package/adapters/cursor/.cursor/rules/retrofit-planner.mdc +22 -2
  19. package/adapters/cursor/.cursor/rules/storybook-chromatic-builder.mdc +52 -7
  20. package/adapters/cursor/.cursor/rules/token-builder.mdc +16 -0
  21. package/adapters/cursor/.cursor/rules/token-sheet-builder.mdc +15 -1
  22. package/adapters/cursor/.cursor/rules/token-sync-layer.mdc +14 -7
  23. package/adapters/generic/AGENTS.md +2 -1
  24. package/adapters/generic/commands/document-component.md +26 -0
  25. package/adapters/generic/skills/component-builder/SKILL.md +80 -2
  26. package/adapters/generic/skills/design-system-audit/SKILL.md +20 -0
  27. package/adapters/generic/skills/icon-system-builder/SKILL.md +10 -2
  28. package/adapters/generic/skills/repository-builder/SKILL.md +14 -0
  29. package/adapters/generic/skills/retrofit-planner/SKILL.md +21 -1
  30. package/adapters/generic/skills/storybook-chromatic-builder/SKILL.md +52 -7
  31. package/adapters/generic/skills/token-builder/SKILL.md +16 -0
  32. package/adapters/generic/skills/token-sheet-builder/SKILL.md +15 -1
  33. package/adapters/generic/skills/token-sync-layer/SKILL.md +14 -7
  34. package/package.json +1 -1
  35. package/references/agent-routing.md +70 -0
  36. package/references/component-doc-archetypes.md +86 -0
  37. package/references/component-doc-schema.md +136 -0
  38. package/references/figma-component-standards.md +24 -0
  39. package/references/figma-scripting.md +62 -0
  40. package/references/manifest-schema.md +27 -5
  41. package/references/sync-adapters.md +5 -0
  42. package/scripts/README.md +11 -0
  43. package/scripts/build-docs-digest.mjs +74 -0
  44. package/scripts/docs-check.mjs +103 -0
  45. package/scripts/lib/doc-record.mjs +54 -0
@@ -0,0 +1,74 @@
1
+ // docs:digest — aggregates every component doc record into two AI-facing
2
+ // artifacts: index.json (machine map) and llms.txt (narrative index).
3
+ // Zero dependencies.
4
+ //
5
+ // Usage: node build-docs-digest.mjs [--root <dir>]
6
+ import { readdirSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { parseArgs } from 'node:util';
9
+ import { pathToFileURL } from 'node:url';
10
+ import { loadRecord } from './lib/doc-record.mjs';
11
+
12
+ const DOCS_DIR = join('design-system', 'docs');
13
+ const COMPONENTS_DIR = join(DOCS_DIR, 'components');
14
+
15
+ export function buildIndex(records) {
16
+ return {
17
+ generatedFrom: 'design-system/docs/components/*.doc.json',
18
+ components: records.map((r) => ({
19
+ name: r.name,
20
+ summary: r.summary ?? '',
21
+ description: r.description ?? '',
22
+ whenToUse: r.whenToUse ?? [],
23
+ whenNotToUse: r.whenNotToUse ?? [],
24
+ variants: r.variants ?? {},
25
+ states: r.states ?? {},
26
+ dos: r.dos ?? [],
27
+ donts: r.donts ?? [],
28
+ accessibility: r.accessibility ?? {},
29
+ tokensUsed: r.tokensUsed ?? [],
30
+ status: r.status ?? 'draft',
31
+ })),
32
+ };
33
+ }
34
+
35
+ export function buildLlmsTxt(records) {
36
+ const lines = ['# Design system — component usage guide', ''];
37
+ lines.push('Generated documentation for AI and human consumers. One section per component.', '');
38
+ for (const r of records) {
39
+ lines.push(`## ${r.name}`, '');
40
+ if (r.summary) lines.push(r.summary, '');
41
+ if (r.description) lines.push(r.description, '');
42
+ if ((r.whenToUse ?? []).length) lines.push('**When to use:** ' + r.whenToUse.join('; '));
43
+ if ((r.whenNotToUse ?? []).length) lines.push('**When not to use:** ' + r.whenNotToUse.join('; '));
44
+ if ((r.dos ?? []).length) lines.push('**Do:** ' + r.dos.join('; '));
45
+ if ((r.donts ?? []).length) lines.push("**Don't:** " + r.donts.join('; '));
46
+ if ((r.tokensUsed ?? []).length) lines.push('**Tokens:** ' + r.tokensUsed.join(', '));
47
+ lines.push('');
48
+ }
49
+ return lines.join('\n');
50
+ }
51
+
52
+ export function loadAllRecords(root) {
53
+ const dir = join(root, COMPONENTS_DIR);
54
+ if (!existsSync(dir)) return [];
55
+ return readdirSync(dir)
56
+ .filter((f) => f.endsWith('.doc.json'))
57
+ .sort()
58
+ .map((f) => loadRecord(join(dir, f)));
59
+ }
60
+
61
+ function main() {
62
+ const { values } = parseArgs({ options: { root: { type: 'string', default: '.' } } });
63
+ const root = values.root;
64
+ const records = loadAllRecords(root);
65
+ const outDir = join(root, DOCS_DIR);
66
+ if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
67
+ writeFileSync(join(outDir, 'index.json'), JSON.stringify(buildIndex(records), null, 2) + '\n');
68
+ writeFileSync(join(outDir, 'llms.txt'), buildLlmsTxt(records));
69
+ console.log(`✓ docs:digest — ${records.length} component(s) → design-system/docs/{index.json,llms.txt}`);
70
+ }
71
+
72
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
73
+ main();
74
+ }
@@ -0,0 +1,103 @@
1
+ // docs:check — the documentation drift gate. Compares each component's canonical
2
+ // record and its rendered surfaces against the fingerprints recorded in
3
+ // design-system.json, and reports drift. Zero dependencies.
4
+ //
5
+ // Drift classes: canonical-changed | stale | edited | missing-surface | edit-unverified
6
+ // (edit-unverified = a surface the CLI cannot read, e.g. Figma — informational;
7
+ // it is checked live by the Figma-connected skill instead.
8
+ // missing-surface = a repo surface that declares a file which is now gone — failing;
9
+ // distinct from edit-unverified, which has no file to read in the first place.)
10
+ //
11
+ // Usage: node docs-check.mjs [--root <dir>] (default root: cwd)
12
+ import { readFileSync, existsSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { parseArgs } from 'node:util';
15
+ import { pathToFileURL } from 'node:url';
16
+ import { loadRecord, canonicalFingerprint, fingerprint } from './lib/doc-record.mjs';
17
+
18
+ // Surfaces whose rendered content the CLI can re-read from the repo.
19
+ const REPO_SURFACES = new Set(['storybookMdx']);
20
+
21
+ export function classifySurface({ currentCanonical, surface, currentRenderHash, fileMissing = false }) {
22
+ const flags = [];
23
+ if (surface.src !== currentCanonical) flags.push('stale');
24
+ if (fileMissing) {
25
+ flags.push('missing-surface');
26
+ } else if (currentRenderHash === null) {
27
+ flags.push('edit-unverified');
28
+ } else if (surface.render !== currentRenderHash) {
29
+ flags.push('edited');
30
+ }
31
+ return flags;
32
+ }
33
+
34
+ export function checkComponent({ name, meta, root }) {
35
+ const out = [];
36
+ const doc = meta && meta.doc;
37
+ if (!doc) return out;
38
+
39
+ const recordPath = join(root, doc.path);
40
+ if (!existsSync(recordPath)) {
41
+ out.push({ name, surface: 'canonical', flags: ['missing-record'] });
42
+ return out;
43
+ }
44
+ const currentCanonical = canonicalFingerprint(loadRecord(recordPath));
45
+ if (currentCanonical !== doc.fingerprint) {
46
+ out.push({ name, surface: 'canonical', flags: ['canonical-changed'] });
47
+ }
48
+
49
+ for (const [surfaceName, surface] of Object.entries(doc.surfaces || {})) {
50
+ let currentRenderHash = null;
51
+ let fileMissing = false;
52
+ if (REPO_SURFACES.has(surfaceName) && surface.file) {
53
+ const filePath = join(root, surface.file);
54
+ if (existsSync(filePath)) {
55
+ currentRenderHash = fingerprint(readFileSync(filePath, 'utf8'));
56
+ } else {
57
+ fileMissing = true;
58
+ }
59
+ }
60
+ const flags = classifySurface({ currentCanonical, surface, currentRenderHash, fileMissing });
61
+ if (flags.length) out.push({ name, surface: surfaceName, flags });
62
+ }
63
+ return out;
64
+ }
65
+
66
+ export function checkAll(manifest, root) {
67
+ const out = [];
68
+ const meta = (manifest && manifest.components && manifest.components.meta) || {};
69
+ for (const [name, m] of Object.entries(meta)) {
70
+ out.push(...checkComponent({ name, meta: m, root }));
71
+ }
72
+ return out;
73
+ }
74
+
75
+ const FAILING = new Set(['canonical-changed', 'stale', 'edited', 'missing-record', 'missing-surface']);
76
+
77
+ function main() {
78
+ const { values } = parseArgs({ options: { root: { type: 'string', default: '.' } } });
79
+ const root = values.root;
80
+ const manifestPath = join(root, 'design-system.json');
81
+ if (!existsSync(manifestPath)) {
82
+ console.error(`docs:check — no design-system.json at ${root}`);
83
+ process.exit(1);
84
+ }
85
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
86
+ const results = checkAll(manifest, root);
87
+
88
+ const drift = results.filter((r) => r.flags.some((f) => FAILING.has(f)));
89
+ const info = results.filter((r) => !r.flags.some((f) => FAILING.has(f)));
90
+
91
+ for (const r of drift) console.error(` ✗ ${r.name} · ${r.surface}: ${r.flags.join(', ')}`);
92
+ for (const r of info) console.log(` ~ ${r.name} · ${r.surface}: ${r.flags.join(', ')} (check in a Figma session)`);
93
+
94
+ if (drift.length) {
95
+ console.error(`✗ docs:check — ${drift.length} drifted surface(s); reconcile with /document-component`);
96
+ process.exit(1);
97
+ }
98
+ console.log(`✓ docs:check — no drift${info.length ? ` (${info.length} Figma surface(s) unverified)` : ''}`);
99
+ }
100
+
101
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
102
+ main();
103
+ }
@@ -0,0 +1,54 @@
1
+ // Loads, validates, and fingerprints a component documentation record
2
+ // (design-system/docs/components/<Name>.doc.json). Zero dependencies.
3
+ import { readFileSync } from 'node:fs';
4
+ import { createHash } from 'node:crypto';
5
+
6
+ // Blocks that get PROJECTED to surfaces. `provenance` is authoring metadata and
7
+ // is intentionally excluded from the fingerprint.
8
+ // MAINTENANCE: keep this in sync with the record schema — any NEW projected field
9
+ // (see references/component-doc-schema.md) must be added here, or it will be
10
+ // silently excluded from the fingerprint and its drift will go undetected.
11
+ const PROJECTED_KEYS = [
12
+ 'name', 'summary', 'description', 'whenToUse', 'whenNotToUse',
13
+ 'variants', 'states', 'dos', 'donts', 'accessibility', 'tokensUsed',
14
+ 'status', 'updatedAt',
15
+ ];
16
+
17
+ const REQUIRED_KEYS = ['name', 'summary', 'description'];
18
+
19
+ export function stableStringify(value) {
20
+ if (Array.isArray(value)) {
21
+ return `[${value.map(stableStringify).join(',')}]`;
22
+ }
23
+ if (value && typeof value === 'object') {
24
+ const keys = Object.keys(value).sort();
25
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(',')}}`;
26
+ }
27
+ return JSON.stringify(value ?? null);
28
+ }
29
+
30
+ export function fingerprint(text) {
31
+ return createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 16);
32
+ }
33
+
34
+ export function canonicalFingerprint(record) {
35
+ const projected = {};
36
+ for (const k of PROJECTED_KEYS) {
37
+ if (record[k] !== undefined) projected[k] = record[k];
38
+ }
39
+ return fingerprint(stableStringify(projected));
40
+ }
41
+
42
+ export function validateRecord(record) {
43
+ const problems = [];
44
+ for (const k of REQUIRED_KEYS) {
45
+ if (typeof record[k] !== 'string' || record[k].trim() === '') {
46
+ problems.push(`missing or empty required field "${k}"`);
47
+ }
48
+ }
49
+ return problems;
50
+ }
51
+
52
+ export function loadRecord(path) {
53
+ return JSON.parse(readFileSync(path, 'utf8'));
54
+ }