@texuli/tools 0.0.0-stage → 0.1.0-next.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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -2
  3. package/dist/a2ui/from-a2ui.d.ts +35 -0
  4. package/dist/a2ui/from-a2ui.js +180 -0
  5. package/dist/a2ui/index.d.ts +11 -0
  6. package/dist/a2ui/index.js +11 -0
  7. package/dist/a2ui/props.d.ts +10 -0
  8. package/dist/a2ui/props.js +94 -0
  9. package/dist/a2ui/schema.d.ts +26 -0
  10. package/dist/a2ui/schema.js +193 -0
  11. package/dist/a2ui/to-a2ui.d.ts +30 -0
  12. package/dist/a2ui/to-a2ui.js +119 -0
  13. package/dist/a2ui/types.d.ts +71 -0
  14. package/dist/a2ui/types.js +11 -0
  15. package/dist/cli-add/add.d.ts +21 -0
  16. package/dist/cli-add/add.js +241 -0
  17. package/dist/cli-add/cli.d.ts +25 -0
  18. package/dist/cli-add/cli.js +157 -0
  19. package/dist/cli-add/index.d.ts +12 -0
  20. package/dist/cli-add/index.js +11 -0
  21. package/dist/cli-add/paths.d.ts +4 -0
  22. package/dist/cli-add/paths.js +4 -0
  23. package/dist/cli-add/provenance.d.ts +32 -0
  24. package/dist/cli-add/provenance.js +78 -0
  25. package/dist/cli-add/registry.d.ts +19 -0
  26. package/dist/cli-add/registry.js +50 -0
  27. package/dist/cli-add/types.d.ts +71 -0
  28. package/dist/cli-add/types.js +4 -0
  29. package/dist/cli-init/cli.d.ts +25 -0
  30. package/dist/cli-init/cli.js +212 -0
  31. package/dist/cli-init/config.d.ts +10 -0
  32. package/dist/cli-init/config.js +62 -0
  33. package/dist/cli-init/detect.d.ts +13 -0
  34. package/dist/cli-init/detect.js +42 -0
  35. package/dist/cli-init/files.d.ts +18 -0
  36. package/dist/cli-init/files.js +59 -0
  37. package/dist/cli-init/index.d.ts +14 -0
  38. package/dist/cli-init/index.js +13 -0
  39. package/dist/cli-init/init.d.ts +14 -0
  40. package/dist/cli-init/init.js +157 -0
  41. package/dist/cli-init/safe-path.d.ts +2 -0
  42. package/dist/cli-init/safe-path.js +26 -0
  43. package/dist/cli-init/theme-apply.d.ts +2 -0
  44. package/dist/cli-init/theme-apply.js +69 -0
  45. package/dist/cli-init/themes.d.ts +37 -0
  46. package/dist/cli-init/themes.js +83 -0
  47. package/dist/cli-init/types.d.ts +50 -0
  48. package/dist/cli-init/types.js +1 -0
  49. package/dist/cli-init/wire.d.ts +20 -0
  50. package/dist/cli-init/wire.js +90 -0
  51. package/dist/cli-update/cli.d.ts +26 -0
  52. package/dist/cli-update/cli.js +161 -0
  53. package/dist/cli-update/index.d.ts +11 -0
  54. package/dist/cli-update/index.js +10 -0
  55. package/dist/cli-update/lines.d.ts +38 -0
  56. package/dist/cli-update/lines.js +189 -0
  57. package/dist/cli-update/provenance.d.ts +14 -0
  58. package/dist/cli-update/provenance.js +51 -0
  59. package/dist/cli-update/types.d.ts +83 -0
  60. package/dist/cli-update/types.js +1 -0
  61. package/dist/cli-update/update.d.ts +12 -0
  62. package/dist/cli-update/update.js +261 -0
  63. package/dist/elements/content.d.ts +15 -0
  64. package/dist/elements/content.js +53 -0
  65. package/dist/elements/define.d.ts +49 -0
  66. package/dist/elements/define.js +281 -0
  67. package/dist/elements/index.d.ts +6 -0
  68. package/dist/elements/index.js +6 -0
  69. package/dist/golden/axe.d.ts +32 -0
  70. package/dist/golden/axe.js +22 -0
  71. package/dist/golden/catalog.d.ts +24 -0
  72. package/dist/golden/catalog.js +85 -0
  73. package/dist/golden/cli.d.ts +9 -0
  74. package/dist/golden/cli.js +45 -0
  75. package/dist/golden/guidance.d.ts +12 -0
  76. package/dist/golden/guidance.js +171 -0
  77. package/dist/golden/index.d.ts +17 -0
  78. package/dist/golden/index.js +17 -0
  79. package/dist/golden/outline.d.ts +21 -0
  80. package/dist/golden/outline.js +68 -0
  81. package/dist/golden/paint.d.ts +21 -0
  82. package/dist/golden/paint.js +103 -0
  83. package/dist/golden/report.d.ts +28 -0
  84. package/dist/golden/report.js +64 -0
  85. package/dist/golden/score.d.ts +16 -0
  86. package/dist/golden/score.js +116 -0
  87. package/dist/golden/sinks.d.ts +8 -0
  88. package/dist/golden/sinks.js +11 -0
  89. package/dist/golden/tasks.d.ts +4 -0
  90. package/dist/golden/tasks.js +686 -0
  91. package/dist/golden/types.d.ts +54 -0
  92. package/dist/golden/types.js +9 -0
  93. package/dist/index.d.ts +8 -0
  94. package/dist/index.js +8 -0
  95. package/dist/internal/paths.d.ts +56 -0
  96. package/dist/internal/paths.js +186 -0
  97. package/dist/llms/bundle.d.ts +6 -0
  98. package/dist/llms/bundle.js +47 -0
  99. package/dist/llms/cli.d.ts +24 -0
  100. package/dist/llms/cli.js +104 -0
  101. package/dist/llms/format.d.ts +35 -0
  102. package/dist/llms/format.js +166 -0
  103. package/dist/llms/index.d.ts +9 -0
  104. package/dist/llms/index.js +8 -0
  105. package/dist/llms/types.d.ts +51 -0
  106. package/dist/llms/types.js +1 -0
  107. package/dist/llms/write.d.ts +14 -0
  108. package/dist/llms/write.js +49 -0
  109. package/dist/mcp/index.d.ts +2 -0
  110. package/dist/mcp/index.js +2 -0
  111. package/dist/mcp/server.d.ts +20 -0
  112. package/dist/mcp/server.js +326 -0
  113. package/dist/mcp/stdio.d.ts +3 -0
  114. package/dist/mcp/stdio.js +34 -0
  115. package/package.json +63 -4
@@ -0,0 +1,28 @@
1
+ import { type GoldenScoreOptions } from './score.js';
2
+ import { type GoldenCheckId, type GoldenScore, type GoldenTask } from './types.js';
3
+ /**
4
+ * Model runs are report-only: a run is scored and summarized, and nothing
5
+ * here throws or fails a build on a model's output. The reference trees are
6
+ * the gate (`packages/tools/tests/golden.test.ts`).
7
+ */
8
+ export interface GoldenRunReport {
9
+ /** One score per golden task, in task order (a missing output scores as empty). */
10
+ scores: readonly GoldenScore[];
11
+ /** Task ids the run had no output for. */
12
+ missing: readonly string[];
13
+ /** Output keys that name no golden task (ignored). */
14
+ unknown: readonly string[];
15
+ /** Per check: how many tasks passed it, and how many ran it. */
16
+ checks: Readonly<Record<GoldenCheckId, {
17
+ passed: number;
18
+ ran: number;
19
+ }>>;
20
+ /** Tasks that passed every check that ran. */
21
+ passed: number;
22
+ total: number;
23
+ }
24
+ export declare function scoreGoldenRun(outputs: unknown, options?: GoldenScoreOptions & {
25
+ tasks?: readonly GoldenTask[];
26
+ }): Promise<GoldenRunReport>;
27
+ /** A Markdown summary of a run: totals, per-check pass counts, then failures. */
28
+ export declare function formatGoldenReport(report: GoldenRunReport): string;
@@ -0,0 +1,64 @@
1
+ import { scoreGoldenTree } from "./score.js";
2
+ import { GOLDEN_TASKS } from "./tasks.js";
3
+ import { GOLDEN_CHECKS } from "./types.js";
4
+ export async function scoreGoldenRun(outputs, options = {}) {
5
+ const tasks = options.tasks ?? GOLDEN_TASKS;
6
+ const record = outputs && typeof outputs === 'object' && !Array.isArray(outputs)
7
+ ? outputs
8
+ : {};
9
+ const ids = new Set(tasks.map((task) => task.id));
10
+ const scores = [];
11
+ for (const task of tasks)
12
+ scores.push(await scoreGoldenTree(task, record[task.id], options));
13
+ const checks = Object.fromEntries(GOLDEN_CHECKS.map((id) => {
14
+ const rows = scores.map((score) => score.checks.find((check) => check.check === id));
15
+ return [
16
+ id,
17
+ {
18
+ passed: rows.filter((row) => row?.status === 'pass').length,
19
+ ran: rows.filter((row) => row && row.status !== 'skipped').length,
20
+ },
21
+ ];
22
+ }));
23
+ return {
24
+ scores,
25
+ missing: tasks.filter((task) => !(task.id in record)).map((task) => task.id),
26
+ unknown: Object.keys(record).filter((key) => !ids.has(key)),
27
+ checks,
28
+ passed: scores.filter((score) => score.passed).length,
29
+ total: tasks.length,
30
+ };
31
+ }
32
+ /** A Markdown summary of a run: totals, per-check pass counts, then failures. */
33
+ export function formatGoldenReport(report) {
34
+ const lines = [
35
+ '# Golden set run (report only)',
36
+ '',
37
+ `**${report.passed} of ${report.total}** tasks passed every check that ran.`,
38
+ '',
39
+ '| Check | Passed | Ran |',
40
+ '| --- | ---: | ---: |',
41
+ ...GOLDEN_CHECKS.map((id) => `| ${id} | ${report.checks[id].passed} | ${report.checks[id].ran} |`),
42
+ ];
43
+ if (report.missing.length)
44
+ lines.push('', `Missing outputs: ${report.missing.join(', ')}`);
45
+ if (report.unknown.length)
46
+ lines.push('', `Ignored keys: ${report.unknown.join(', ')}`);
47
+ const missing = new Set(report.missing);
48
+ const failed = report.scores.filter((score) => !score.passed && !missing.has(score.taskId));
49
+ if (failed.length) {
50
+ lines.push('', '## Failures');
51
+ for (const score of failed) {
52
+ lines.push('', `### ${score.taskId}`);
53
+ for (const check of score.checks) {
54
+ if (check.status !== 'fail')
55
+ continue;
56
+ for (const issue of check.issues.slice(0, 8))
57
+ lines.push(`- ${check.check}: ${issue}`);
58
+ if (check.issues.length > 8)
59
+ lines.push(`- ${check.check}: … ${check.issues.length - 8} more`);
60
+ }
61
+ }
62
+ }
63
+ return `${lines.join('\n')}\n`;
64
+ }
@@ -0,0 +1,16 @@
1
+ import type { RenderCatalog } from '@texuli/catalog/render/renderer';
2
+ import type { ElementComponentLoader } from '../elements/content.js';
3
+ import { type GoldenAxeRunner, type GoldenScore, type GoldenTask } from './types.js';
4
+ export interface GoldenScoreOptions {
5
+ /** Defaults to the golden render catalog (the agent surface). */
6
+ catalog?: RenderCatalog;
7
+ /**
8
+ * Runs axe on the painted tree. Without it (or without `document`), the axe
9
+ * check is `skipped`: offline scoring needs a DOM and an axe-core instance.
10
+ */
11
+ axe?: GoldenAxeRunner;
12
+ document?: Document;
13
+ loaders?: Readonly<Record<string, ElementComponentLoader>>;
14
+ }
15
+ /** Scores one tree against one golden task. Never throws on a bad tree. */
16
+ export declare function scoreGoldenTree(task: GoldenTask, tree: unknown, options?: GoldenScoreOptions): Promise<GoldenScore>;
@@ -0,0 +1,116 @@
1
+ import { safeParseBlock } from '@texuli/blocks';
2
+ import { validateDraft } from '@texuli/catalog/validate';
3
+ import { GOLDEN_FRAME_IDS, goldenRenderCatalog } from "./catalog.js";
4
+ import { checkGuidance } from "./guidance.js";
5
+ import { paintGoldenTree } from "./paint.js";
6
+ import { checkSinks } from "./sinks.js";
7
+ import { GOLDEN_CHECKS, } from "./types.js";
8
+ function result(check, issues) {
9
+ return { check, status: issues.length === 0 ? 'pass' : 'fail', issues };
10
+ }
11
+ function isTreeInput(value) {
12
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
13
+ }
14
+ function componentIds(roots, out = new Set()) {
15
+ for (const node of roots) {
16
+ if (node.kind !== 'component')
17
+ continue;
18
+ out.add(node.id);
19
+ componentIds(node.children, out);
20
+ }
21
+ return out;
22
+ }
23
+ function line(d) {
24
+ return `${d.path} ${d.id ?? ''}: ${d.code} (${d.message})`;
25
+ }
26
+ /**
27
+ * Schema and grammar verdicts from the shared `validateDraft`: input,
28
+ * structure, limits, the sink-free renderer, deep block schemas and the
29
+ * frame root are `schema`; the composition grammar is `grammar`. The raw
30
+ * sink scan is its own golden check (`checkSinks`, the same scan).
31
+ */
32
+ function checkDraft(tree, task, catalog) {
33
+ if (!isTreeInput(tree)) {
34
+ return { issues: ['the output is not a catalog tree object'], grammar: [], roots: [] };
35
+ }
36
+ const validation = validateDraft(tree, {
37
+ catalog,
38
+ tier: task.tier,
39
+ frameRoots: GOLDEN_FRAME_IDS,
40
+ checkBlock: (block) => safeParseBlock(block),
41
+ });
42
+ const of = (check) => validation.diagnostics.filter((d) => d.check === check).map(line);
43
+ return {
44
+ issues: [
45
+ ...of('input'),
46
+ ...of('structure'),
47
+ ...of('limits'),
48
+ ...of('render'),
49
+ ...of('root'),
50
+ ...of('blocks'),
51
+ ],
52
+ grammar: of('grammar'),
53
+ roots: validation.roots,
54
+ };
55
+ }
56
+ /** Scores one tree against one golden task. Never throws on a bad tree. */
57
+ export async function scoreGoldenTree(task, tree, options = {}) {
58
+ const catalog = options.catalog ?? goldenRenderCatalog();
59
+ const schema = checkDraft(tree, task, catalog);
60
+ const checks = [result('schema', schema.issues)];
61
+ const skipped = (check, why) => ({
62
+ check,
63
+ status: 'skipped',
64
+ issues: [why],
65
+ });
66
+ if (!isTreeInput(tree)) {
67
+ // Nothing to inspect: only the schema (and coverage) verdicts mean anything.
68
+ checks.push(skipped('dontUseWhen', 'not a tree'), skipped('sinks', 'not a tree'));
69
+ }
70
+ else {
71
+ checks.push(result('dontUseWhen', checkGuidance(tree)), result('sinks', checkSinks(tree)));
72
+ }
73
+ if (schema.roots.length === 0) {
74
+ checks.push(skipped('grammar', 'nothing rendered'));
75
+ }
76
+ else {
77
+ checks.push(result('grammar', schema.grammar));
78
+ }
79
+ if (!options.axe || !options.document) {
80
+ checks.push({ check: 'axe', status: 'skipped', issues: ['no DOM axe runner given'] });
81
+ }
82
+ else if (schema.issues.length > 0) {
83
+ checks.push({
84
+ check: 'axe',
85
+ status: 'skipped',
86
+ issues: ['not painted: the schema check failed'],
87
+ });
88
+ }
89
+ else {
90
+ const paint = await paintGoldenTree(schema.roots, options.document, {
91
+ loaders: options.loaders,
92
+ title: task.title,
93
+ });
94
+ try {
95
+ const violations = await options.axe(options.document);
96
+ checks.push(result('axe', [
97
+ ...paint.issues,
98
+ ...violations.map((v) => `${v.id} (${v.impact ?? 'n/a'}, ${v.nodes} node${v.nodes === 1 ? '' : 's'}): ${v.sample ?? ''}`),
99
+ ]));
100
+ }
101
+ finally {
102
+ paint.destroy();
103
+ }
104
+ }
105
+ const present = componentIds(schema.roots);
106
+ checks.push(result('coverage', task.mustInclude.filter((id) => !present.has(id)).map((id) => `missing a ${id} component`)));
107
+ const ordered = GOLDEN_CHECKS.map((id) => checks.find((check) => check.check === id));
108
+ const ran = ordered.filter((check) => check.status !== 'skipped');
109
+ const passedCount = ran.filter((check) => check.status === 'pass').length;
110
+ return {
111
+ taskId: task.id,
112
+ passed: ran.every((check) => check.status === 'pass'),
113
+ score: ran.length === 0 ? 0 : passedCount / ran.length,
114
+ checks: ordered,
115
+ };
116
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Sink checks on the raw model output, before the renderer cleans it: a tree
3
+ * that tries an HTML string, a script URL or a style is penalized even though
4
+ * the sink-free renderer would have made it inert. The scan is the shared
5
+ * `scanSinks` that `validateDraft` runs.
6
+ */
7
+ /** Every sink-like value in an untrusted tree (any shape), with its path. */
8
+ export declare function checkSinks(tree: unknown): string[];
@@ -0,0 +1,11 @@
1
+ import { scanSinks } from '@texuli/catalog/validate';
2
+ /**
3
+ * Sink checks on the raw model output, before the renderer cleans it: a tree
4
+ * that tries an HTML string, a script URL or a style is penalized even though
5
+ * the sink-free renderer would have made it inert. The scan is the shared
6
+ * `scanSinks` that `validateDraft` runs.
7
+ */
8
+ /** Every sink-like value in an untrusted tree (any shape), with its path. */
9
+ export function checkSinks(tree) {
10
+ return scanSinks(tree).map((finding) => `${finding.path}: ${finding.message}`);
11
+ }
@@ -0,0 +1,4 @@
1
+ import type { GoldenTask } from './types.js';
2
+ export declare const GOLDEN_TASKS: readonly GoldenTask[];
3
+ /** The task with `id`, or `undefined`. */
4
+ export declare function goldenTask(id: string): GoldenTask | undefined;