@adminiumjs/llm 0.1.0-rc.1

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 (126) hide show
  1. package/LICENSE +661 -0
  2. package/dist/apply/diff.d.ts +79 -0
  3. package/dist/apply/diff.d.ts.map +1 -0
  4. package/dist/apply/diff.js +459 -0
  5. package/dist/apply/diff.js.map +1 -0
  6. package/dist/apply/index.d.ts +6 -0
  7. package/dist/apply/index.d.ts.map +1 -0
  8. package/dist/apply/index.js +6 -0
  9. package/dist/apply/index.js.map +1 -0
  10. package/dist/apply/normalize.d.ts +55 -0
  11. package/dist/apply/normalize.d.ts.map +1 -0
  12. package/dist/apply/normalize.js +232 -0
  13. package/dist/apply/normalize.js.map +1 -0
  14. package/dist/apply/plan.d.ts +206 -0
  15. package/dist/apply/plan.d.ts.map +1 -0
  16. package/dist/apply/plan.js +422 -0
  17. package/dist/apply/plan.js.map +1 -0
  18. package/dist/apply/suggestion-id.d.ts +103 -0
  19. package/dist/apply/suggestion-id.d.ts.map +1 -0
  20. package/dist/apply/suggestion-id.js +121 -0
  21. package/dist/apply/suggestion-id.js.map +1 -0
  22. package/dist/crypto.d.ts +48 -0
  23. package/dist/crypto.d.ts.map +1 -0
  24. package/dist/crypto.js +35 -0
  25. package/dist/crypto.js.map +1 -0
  26. package/dist/index.d.ts +17 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +22 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/prompt/builder.d.ts +27 -0
  31. package/dist/prompt/builder.d.ts.map +1 -0
  32. package/dist/prompt/builder.js +263 -0
  33. package/dist/prompt/builder.js.map +1 -0
  34. package/dist/prompt/chunker.d.ts +89 -0
  35. package/dist/prompt/chunker.d.ts.map +1 -0
  36. package/dist/prompt/chunker.js +383 -0
  37. package/dist/prompt/chunker.js.map +1 -0
  38. package/dist/prompt/index.d.ts +14 -0
  39. package/dist/prompt/index.d.ts.map +1 -0
  40. package/dist/prompt/index.js +14 -0
  41. package/dist/prompt/index.js.map +1 -0
  42. package/dist/prompt/serializer.d.ts +138 -0
  43. package/dist/prompt/serializer.d.ts.map +1 -0
  44. package/dist/prompt/serializer.js +178 -0
  45. package/dist/prompt/serializer.js.map +1 -0
  46. package/dist/prompt/templates/v1.d.ts +41 -0
  47. package/dist/prompt/templates/v1.d.ts.map +1 -0
  48. package/dist/prompt/templates/v1.js +312 -0
  49. package/dist/prompt/templates/v1.js.map +1 -0
  50. package/dist/prompt/token-estimate.d.ts +16 -0
  51. package/dist/prompt/token-estimate.d.ts.map +1 -0
  52. package/dist/prompt/token-estimate.js +20 -0
  53. package/dist/prompt/token-estimate.js.map +1 -0
  54. package/dist/prompt/types.d.ts +134 -0
  55. package/dist/prompt/types.d.ts.map +1 -0
  56. package/dist/prompt/types.js +44 -0
  57. package/dist/prompt/types.js.map +1 -0
  58. package/dist/providers/anthropic.d.ts +3 -0
  59. package/dist/providers/anthropic.d.ts.map +1 -0
  60. package/dist/providers/anthropic.js +65 -0
  61. package/dist/providers/anthropic.js.map +1 -0
  62. package/dist/providers/factory.d.ts +3 -0
  63. package/dist/providers/factory.d.ts.map +1 -0
  64. package/dist/providers/factory.js +37 -0
  65. package/dist/providers/factory.js.map +1 -0
  66. package/dist/providers/http.d.ts +56 -0
  67. package/dist/providers/http.d.ts.map +1 -0
  68. package/dist/providers/http.js +148 -0
  69. package/dist/providers/http.js.map +1 -0
  70. package/dist/providers/index.d.ts +15 -0
  71. package/dist/providers/index.d.ts.map +1 -0
  72. package/dist/providers/index.js +15 -0
  73. package/dist/providers/index.js.map +1 -0
  74. package/dist/providers/model-catalog.d.ts +21 -0
  75. package/dist/providers/model-catalog.d.ts.map +1 -0
  76. package/dist/providers/model-catalog.js +121 -0
  77. package/dist/providers/model-catalog.js.map +1 -0
  78. package/dist/providers/ollama.d.ts +3 -0
  79. package/dist/providers/ollama.d.ts.map +1 -0
  80. package/dist/providers/ollama.js +55 -0
  81. package/dist/providers/ollama.js.map +1 -0
  82. package/dist/providers/openai-compatible.d.ts +3 -0
  83. package/dist/providers/openai-compatible.d.ts.map +1 -0
  84. package/dist/providers/openai-compatible.js +50 -0
  85. package/dist/providers/openai-compatible.js.map +1 -0
  86. package/dist/providers/openai.d.ts +18 -0
  87. package/dist/providers/openai.d.ts.map +1 -0
  88. package/dist/providers/openai.js +68 -0
  89. package/dist/providers/openai.js.map +1 -0
  90. package/dist/providers/types.d.ts +112 -0
  91. package/dist/providers/types.d.ts.map +1 -0
  92. package/dist/providers/types.js +55 -0
  93. package/dist/providers/types.js.map +1 -0
  94. package/dist/response/errors.d.ts +60 -0
  95. package/dist/response/errors.d.ts.map +1 -0
  96. package/dist/response/errors.js +101 -0
  97. package/dist/response/errors.js.map +1 -0
  98. package/dist/response/extract.d.ts +38 -0
  99. package/dist/response/extract.d.ts.map +1 -0
  100. package/dist/response/extract.js +77 -0
  101. package/dist/response/extract.js.map +1 -0
  102. package/dist/response/index.d.ts +8 -0
  103. package/dist/response/index.d.ts.map +1 -0
  104. package/dist/response/index.js +8 -0
  105. package/dist/response/index.js.map +1 -0
  106. package/dist/response/merge.d.ts +53 -0
  107. package/dist/response/merge.d.ts.map +1 -0
  108. package/dist/response/merge.js +348 -0
  109. package/dist/response/merge.js.map +1 -0
  110. package/dist/response/referential.d.ts +113 -0
  111. package/dist/response/referential.d.ts.map +1 -0
  112. package/dist/response/referential.js +556 -0
  113. package/dist/response/referential.js.map +1 -0
  114. package/dist/response/schema.d.ts +506 -0
  115. package/dist/response/schema.d.ts.map +1 -0
  116. package/dist/response/schema.js +191 -0
  117. package/dist/response/schema.js.map +1 -0
  118. package/dist/response/validate.d.ts +29 -0
  119. package/dist/response/validate.d.ts.map +1 -0
  120. package/dist/response/validate.js +167 -0
  121. package/dist/response/validate.js.map +1 -0
  122. package/dist/types.d.ts +95 -0
  123. package/dist/types.d.ts.map +1 -0
  124. package/dist/types.js +49 -0
  125. package/dist/types.js.map +1 -0
  126. package/package.json +30 -0
@@ -0,0 +1,178 @@
1
+ /** Text columns with `distinctCount` at or below this are pseudo-enum candidates (§4.2). */
2
+ export const PSEUDO_ENUM_MAX_DISTINCT = 24;
3
+ /** Header line the opt-in sampling block leads with (§5.2 builder note). */
4
+ export const SAMPLING_BLOCK_HEADER = '=== INPUT: SAMPLE VALUES (user opted in; PII columns excluded) ===';
5
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
6
+ /** Compact, deterministic JSON for injection into the prompt. */
7
+ export function toPromptJson(value) {
8
+ return JSON.stringify(value);
9
+ }
10
+ /** `TableRef` → the model's qualified `"schema.table"` id (mysql/sqlite have a null schema). */
11
+ export function qualifyTableRef(ref, defaultSchema) {
12
+ return `${ref.schema ?? defaultSchema}.${ref.name}`;
13
+ }
14
+ function statsKey(tableId, column) {
15
+ return `${tableId}\x00${column}`;
16
+ }
17
+ /** `(tableId, column) → distinctCount | null`, built from the stats. */
18
+ function distinctCountLookup(stats, defaultSchema) {
19
+ const map = new Map();
20
+ for (const result of stats) {
21
+ const tableId = qualifyTableRef(result.table, defaultSchema);
22
+ for (const column of result.columns) {
23
+ map.set(statsKey(tableId, column.column), column.distinctCount);
24
+ }
25
+ }
26
+ return (tableId, column) => map.get(statsKey(tableId, column)) ?? null;
27
+ }
28
+ /** `(tableId, column) → true` for PII-suspected or secret columns (never sampled). */
29
+ function sensitiveColumnLookup(model) {
30
+ const set = new Set();
31
+ for (const table of model.tables) {
32
+ for (const column of table.columns) {
33
+ if (isPiiSuspected(column) || isSecret(column)) {
34
+ set.add(statsKey(table.id, column.name));
35
+ }
36
+ }
37
+ }
38
+ return (tableId, column) => set.has(statsKey(tableId, column));
39
+ }
40
+ function isPiiSuspected(column) {
41
+ return (column.semantics?.flags.pii ?? null) !== null;
42
+ }
43
+ function isSecret(column) {
44
+ return column.semantics?.flags.secret ?? false;
45
+ }
46
+ export function serializeSchemaIr(model, options = {}) {
47
+ const stubs = options.stubTables ?? new Set();
48
+ const distinctCount = distinctCountLookup(options.stats ?? [], model.defaultSchema);
49
+ const enumValues = new Map(model.enums.map((def) => [def.id, def.values]));
50
+ const tables = model.tables.map((table) => {
51
+ if (stubs.has(table.id)) {
52
+ return { table: table.id, stub: true, columns: table.columns.map((c) => c.name) };
53
+ }
54
+ const role = table.semantics?.role;
55
+ return {
56
+ table: table.id,
57
+ ...(role !== undefined ? { role } : {}),
58
+ columns: table.columns.map((column) => serializeColumn(column, distinctCount(table.id, column.name))),
59
+ };
60
+ });
61
+ const enumColumns = [];
62
+ for (const table of model.tables) {
63
+ if (stubs.has(table.id))
64
+ continue;
65
+ for (const column of table.columns) {
66
+ if (column.enumRef === null)
67
+ continue;
68
+ const values = enumValues.get(column.enumRef);
69
+ if (values === undefined)
70
+ continue;
71
+ enumColumns.push({ table: table.id, column: column.name, values: [...values] });
72
+ }
73
+ }
74
+ const declaredRelations = model.relations
75
+ .filter((relation) => relation.kind === 'declared-fk')
76
+ .map((relation) => ({
77
+ fromTable: relation.from.tableId,
78
+ fromColumns: [...relation.from.columns],
79
+ toTable: relation.to.tableId,
80
+ toColumns: [...relation.to.columns],
81
+ cardinality: relation.cardinality,
82
+ }));
83
+ return {
84
+ database: { name: model.name, dialect: model.dialect, defaultSchema: model.defaultSchema },
85
+ tables,
86
+ enumColumns,
87
+ declaredRelations,
88
+ };
89
+ }
90
+ function serializeColumn(column, distinctCount) {
91
+ const pii = isPiiSuspected(column);
92
+ const secret = isSecret(column);
93
+ const isText = column.logicalType === 'text' || column.logicalType === 'varchar';
94
+ const pseudoEnum = isText &&
95
+ !pii &&
96
+ !secret &&
97
+ column.enumRef === null &&
98
+ distinctCount !== null &&
99
+ distinctCount <= PSEUDO_ENUM_MAX_DISTINCT;
100
+ return {
101
+ column: column.name,
102
+ type: column.logicalType,
103
+ nullable: column.nullable,
104
+ ...(column.isPrimaryKey ? { primaryKey: true } : {}),
105
+ ...(column.isUnique ? { unique: true } : {}),
106
+ ...(column.enumRef !== null ? { enum: column.enumRef } : {}),
107
+ ...(column.references !== null
108
+ ? { references: `${column.references.tableId}.${column.references.column}` }
109
+ : {}),
110
+ ...(pii ? { piiSuspected: true } : {}),
111
+ ...(secret ? { secret: true } : {}),
112
+ ...(pseudoEnum ? { pseudoEnumCandidate: true } : {}),
113
+ };
114
+ }
115
+ /**
116
+ * Format `StatsResult[]` into the aggregates-only stats block. Deliberately
117
+ * copies ONLY row counts, null fractions and distinct counts — never `min`,
118
+ * `max` or `sampleValues`, which are cell values and belong to the opt-in
119
+ * sampling block. This is what guarantees the sample-free default at the stats
120
+ * layer regardless of what the adapter happened to collect.
121
+ */
122
+ export function serializeStats(stats, options = {}) {
123
+ const defaultSchema = options.defaultSchema ?? 'public';
124
+ return {
125
+ tables: stats.map((result) => ({
126
+ table: qualifyTableRef(result.table, defaultSchema),
127
+ rowCountEstimate: result.rowCountEstimate,
128
+ rowCountExact: result.rowCountExact,
129
+ columns: result.columns.map((column) => ({
130
+ column: column.column,
131
+ nullFraction: column.nullFraction,
132
+ distinctCount: column.distinctCount,
133
+ })),
134
+ })),
135
+ };
136
+ }
137
+ // ─── Sampling block (opt-in; the ONLY place cell values appear) ──────────────
138
+ /**
139
+ * Build the `{{SAMPLING_BLOCK}}` text. Returns `''` when `sampling` is null
140
+ * (sample-free default). When opted in, renders the header plus a JSON object of
141
+ * per-column `min`/`max`/`sampleValues` — EXCLUDING every PII-suspected or
142
+ * secret column (looked up from the model), and truncating each column's value
143
+ * list to `sampling.maxValuesPerColumn`.
144
+ */
145
+ export function serializeSampling(model, stats, sampling) {
146
+ if (sampling === null)
147
+ return '';
148
+ const payload = serializeSamplingPayload(model, stats, sampling.maxValuesPerColumn);
149
+ return `${SAMPLING_BLOCK_HEADER}\n${toPromptJson(payload)}`;
150
+ }
151
+ export function serializeSamplingPayload(model, stats, maxValuesPerColumn) {
152
+ const isSensitive = sensitiveColumnLookup(model);
153
+ const tables = [];
154
+ for (const result of stats) {
155
+ const tableId = qualifyTableRef(result.table, model.defaultSchema);
156
+ const columns = [];
157
+ for (const column of result.columns) {
158
+ if (isSensitive(tableId, column.column))
159
+ continue;
160
+ const hasMin = column.min !== undefined;
161
+ const hasMax = column.max !== undefined;
162
+ const values = column.sampleValues;
163
+ const hasValues = values !== undefined && values.length > 0;
164
+ if (!hasMin && !hasMax && !hasValues)
165
+ continue;
166
+ columns.push({
167
+ column: column.column,
168
+ ...(hasMin ? { min: column.min } : {}),
169
+ ...(hasMax ? { max: column.max } : {}),
170
+ ...(hasValues ? { sampleValues: values.slice(0, maxValuesPerColumn) } : {}),
171
+ });
172
+ }
173
+ if (columns.length > 0)
174
+ tables.push({ table: tableId, columns });
175
+ }
176
+ return { tables };
177
+ }
178
+ //# sourceMappingURL=serializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../src/prompt/serializer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * §5.1 — the system section. Fixed policy text (no tokens); it frames the task,
3
+ * the quality bar and the STRICT output rules. Reused verbatim by
4
+ * {@link PROMPT_MERGE_V1} (§4.5).
5
+ */
6
+ export declare const PROMPT_V1_SYSTEM = "You are a senior data architect and admin-interface designer. You work for\nAdminium, an engine that introspects a relational database schema and\nauto-generates a complete admin dashboard from it. A heuristic pipeline has\nalready produced a baseline; your judgment refines it. Your output is machine-\napplied: it will be schema-validated, cross-checked against the real database\nschema, and reviewed by a human as a diff before anything is applied.\n\nTASK\nAnalyze the database schema and statistics provided in the user message and\nreturn enrichment decisions as a single JSON object, following the response\nschema and the decision instructions exactly.\n\nQUALITY BAR\n- Domain understanding first: infer what the business behind this schema does\n from table/column names, types, relations and statistics, and make every\n label, grouping and recommendation read like it was written by an expert in\n that domain \u2014 not like it was mechanically derived from identifiers.\n- Labels are for non-technical staff: \"Purchase orders\", never \"tbl_po_hdr\".\n Descriptions are one sentence, concrete, and state what a row represents.\n- Translations must be native-quality, using the domain's standard terminology\n in each language, and consistent across the whole response (the same concept\n gets the same term everywhere). Do not transliterate; translate.\n- Be decisive but honest: every suggestion carries a \"confidence\" between 0.0\n and 1.0. Prefer a lower confidence over inventing detail. If the input gives\n no basis for a decision, omit that suggestion entirely.\n- Ground every recommendation in evidence from the input (name patterns, types,\n cardinalities, null ratios, relation shapes). \"reason\" fields must cite that\n evidence in one short sentence.\n\nOUTPUT RULES \u2014 STRICT\n1. Respond with EXACTLY ONE JSON object and nothing else. No prose, no markdown,\n no code fences, no comments, no trailing commas. The first character of your\n response must be \"{\" and the last must be \"}\".\n2. The first key must be \"schema_version\" with the exact value\n \"adminium.llm/v1\".\n3. Reference ONLY tables and columns that appear in the input schema. Never\n invent, rename, pluralize or \"correct\" an identifier. Tables marked\n \"stub\": true are context only \u2014 emit no suggestions for them.\n4. Use only the allowed page-template ids, widget ids, tone values and masking\n values listed in the user message. Icon names must be kebab-case lucide\n icon names (e.g. \"shopping-cart\", \"users\", \"package\").\n5. Localized text objects must contain exactly the requested locale keys \u2014\n no more, no fewer. \"en_US\" is always required.\n6. Numbers for \"confidence\" use at most 2 decimal places. Keep all \"reason\"\n fields under 200 characters.\n7. If you cannot comply with any rule, return\n {\"schema_version\":\"adminium.llm/v1\",\"error\":\"<one-sentence reason>\"} and\n nothing else.";
7
+ /**
8
+ * §5.2 — the user section template. Carries the `{{DOUBLE_BRACE}}` tokens the
9
+ * builder fills: run id, chunk info, requested-sections list, the allowed
10
+ * vocabularies, target locales, and the serialized schema IR / statistics /
11
+ * (opt-in) sample-values blocks. The ten numbered decision blocks map 1:1 to
12
+ * {@link RequestedSection} ids; the builder deletes deselected blocks (§4.4).
13
+ */
14
+ export declare const PROMPT_V1_USER = "=== CONTEXT ===\nAdminium generated an admin dashboard for the database described below. Your\ndecisions refine: naming, navigation, enum semantics, relations, key columns,\npage layouts, dashboard widgets, privacy flags, icons and micro-copy.\nrun_id: {{RUN_ID}} (echo it back as \"run_id\" if convenient; optional)\n{{CHUNK_INFO}}\n\n=== YOUR DECISIONS ===\nProduce the following, per the response schema at the end of this message.\n{{REQUESTED_SECTIONS_LIST}}\n\n1. LABELS & DESCRIPTIONS (response: tables[].label, tables[].description,\n tables[].columns[].label, tables[].columns[].description)\n For every non-stub table and every column: a short human label (Title Case\n for tables, Sentence case for columns; \u2264 28 chars for tables, \u2264 24 for\n columns) and a one-sentence description. Localize both into every requested\n locale. Column descriptions are optional for self-explanatory columns\n (id, created_at) \u2014 omit rather than pad.\n\n2. DOMAIN GROUPING (response: navGroups)\n Group all tables into 3\u20137 navigation groups that reflect business domains\n (e.g. \"Sales\", \"Catalog\", \"People\"), not technical layers. Every non-stub\n table appears in exactly one group. Order groups by operational importance;\n order tables within a group the same way. Give each group a localized label\n and a lucide icon.\n\n3. ENUM SEMANTICS (response: enums) \u2014 for every column listed under\n \"enumColumns\" in the input:\n - kind: \"workflow\" if values are states a record moves through over time\n (e.g. draft \u2192 active \u2192 closed); \"category\" if values are unordered\n classifications (e.g. region, plan tier).\n - order: for workflow enums, the full value list in lifecycle order\n (must be a permutation of the given values). null for category enums.\n - tones: map EVERY value to exactly one of:\n \"pos\" (successfully completed / healthy), \"warn\" (needs attention /\n pending / at risk), \"danger\" (failed / blocked / cancelled),\n \"accent\" (actively in progress), \"muted\" (inactive / draft / neutral).\n - terminal: for workflow enums, the values that end the lifecycle.\n\n4. RELATIONS (response: relations)\n - confirmed: for each relation under \"declaredRelations\" in the input,\n state whether it is semantically what it looks like, and name the\n relationship (e.g. \"orders belong to customers\").\n - inferred: relations that SHOULD exist but are not declared as foreign\n keys. Use naming conventions (customer_id \u2192 customers.id), matching\n types, and statistics (a column whose distinctCount \u2248 another table's\n rowCountEstimate is a strong signal). Give kind\n (\"many-to-one\" | \"one-to-one\" | \"many-to-many-via\"), evidence, and\n confidence. Never re-state a declared relation as inferred.\n\n5. KEY COLUMNS (response: tables[].displayColumn, tables[].naturalKey)\n - displayColumn: the single column whose value best represents a row to a\n human (a name/title/number \u2014 never a UUID/serial id). null only if truly\n nothing qualifies.\n - naturalKey: minimal column set a human would use to uniquely identify a\n row (e.g. [\"sku\"], [\"email\"], [\"order_number\"]). null if none.\n\n6. PAGE TEMPLATES (response: tables[].pageTemplates)\n Recommend 1\u20133 page templates per table from ALLOWED PAGE TEMPLATES below,\n ranked. \"page-crud\" is always generated and must not be recommended.\n For each: cite which trigger(s) from the taxonomy fired (\"triggers\"), and\n give a one-sentence reason grounded in this schema. Do not recommend a\n template whose trigger conditions this table does not meet.\n\n7. DASHBOARD WIDGETS (response: dashboards)\n Propose up to 6 dashboards, one per major domain (aligned with your nav\n groups). For each, rank up to 8 widgets from ALLOWED WIDGETS below. Every\n widget binds to real columns: metric column (numeric), dimension/group-by\n column, time column where applicable, and an aggregation\n (\"count\" | \"sum\" | \"avg\" | \"min\" | \"max\"). Spans on a 12-column grid:\n KPI = 3, donut/funnel = 4, bar/table = 6, line/area = 8.\n\n8. PII & MASKING (response: tables[].columns[].pii)\n Flag every column plausibly containing personal or sensitive data.\n kind: one of \"email\" | \"phone\" | \"name\" | \"address\" | \"gov-id\" | \"dob\" |\n \"financial\" | \"credentials\" | \"health\" | \"ip\" | \"location\" | \"other\".\n masking: one of \"mask-email\" (j***@d***.com) | \"mask-partial\" (first/last\n char visible) | \"last4\" | \"redact\" (fully hidden) | \"hash\" | \"none\"\n (flag only). Columns already marked \"piiSuspected\": true in the input were\n flagged by heuristics \u2014 confirm or reject them explicitly (pii: null means\n explicitly not PII).\n\n9. ICONS (response: tables[].icon, navGroups[].icon)\n One lucide icon per table and per nav group, kebab-case (e.g. \"users\",\n \"shopping-cart\", \"package\", \"receipt\", \"calendar\", \"building-2\",\n \"credit-card\", \"truck\", \"message-square\", \"file-text\"). Choose the most\n domain-specific icon; avoid reusing one icon for multiple tables in the\n same group.\n\n10. MICRO-COPY (response: tables[].microcopy)\n Localized, per table:\n - emptyState.headline (\u2264 40 chars) and emptyState.guidance (one sentence,\n ends with what to do first) \u2014 shown when the table has no rows.\n - pageSubtitle: \u2264 60 chars shown under the page title, describing what\n the page manages (e.g. \"Track and fulfil customer orders\").\n Tone: calm, concrete, no exclamation marks, no marketing language.\n\n=== TRIGGER TAXONOMY (for decision 6) ===\n| template id | trigger conditions |\n|--------------------|--------------------|\n| page-dashboard | numeric measure columns + a timestamp column exist in the domain |\n| page-master-detail | enum-heavy table with rich per-record detail (many columns / child conversation table) |\n| page-queue-inbox | workflow enum with pending/approved-style states, or read/unread boolean, + assignee/requester FK |\n| page-board | status enum classified \"workflow\"; optional second categorical column \u2192 swimlanes; period column \u2192 roadmap |\n| page-calendar | date/datetime column + a title-like text column |\n| page-scheduler | person FK \u00D7 date \u00D7 shift/type columns, or hours-per-project assignment shape |\n| page-directory | people-shaped table: name + email (+ role/department enum, avatar) |\n| page-log-viewer | append-only shape: timestamp + actor + action/level, high row count, few updates |\n| page-files | file/attachment shape: filename/mime/size/url columns or parent-folder self-FK |\n| page-chat | conversation + message table pair (thread FK, sender FK, body, timestamp) |\n(Templates not listed here \u2014 settings, wizard, builder, auth, billing, api,\nmarketing, system pages \u2014 are platform-owned; never recommend them for user\ntables.)\n\n=== ALLOWED PAGE TEMPLATES ===\n{{ALLOWED_PAGE_TEMPLATE_IDS_JSON}}\n\n=== ALLOWED WIDGETS ===\n{{ALLOWED_WIDGET_IDS_JSON}}\n\n=== TARGET LOCALES ===\n{{LOCALES_JSON}}\nEvery localized text object uses exactly these keys.\n\n=== RESPONSE SCHEMA (adminium.llm/v1) ===\nReturn one JSON object of this shape. TypeScript-style notation; \"?\" = may be\nomitted; L10n = { \"<locale>\": string } with exactly the target locales.\n\n{\n schema_version: \"adminium.llm/v1\", // REQUIRED, first key\n run_id?: string,\n tables: [{\n table: string, // \"schema.table\", exactly as in input\n confidence: number, // 0..1, overall for this table's suggestions\n label: L10n, description: L10n,\n icon: string, // lucide kebab-case\n displayColumn: string | null,\n naturalKey: string[] | null,\n pageTemplates: [{ template: string, rank: number, triggers: string[],\n reason: string, confidence: number }],\n microcopy: { emptyState: { headline: L10n, guidance: L10n },\n pageSubtitle: L10n },\n columns: [{\n column: string,\n label: L10n, description?: L10n,\n pii: null | { kind: string, masking: string, reason: string,\n confidence: number }\n }]\n }],\n enums: [{ table: string, column: string, kind: \"workflow\" | \"category\",\n order: string[] | null, terminal?: string[],\n tones: { [value: string]: \"pos\"|\"warn\"|\"danger\"|\"accent\"|\"muted\" },\n reason: string, confidence: number }],\n relations: {\n confirmed: [{ fromTable: string, fromColumns: string[], toTable: string,\n toColumns: string[], semantics: string, correct: boolean,\n confidence: number }],\n inferred: [{ fromTable: string, fromColumns: string[], toTable: string,\n toColumns: string[],\n kind: \"many-to-one\"|\"one-to-one\"|\"many-to-many-via\",\n viaTable?: string, evidence: string, confidence: number }]\n },\n navGroups: [{ id: string, // kebab-case slug\n label: L10n, icon: string, order: number,\n tables: string[], confidence: number }],\n dashboards: [{ id: string, domain: string, label: L10n, order: number,\n tables: string[],\n widgets: [{ widget: string, rank: number, span: number,\n table: string, metricColumn?: string,\n dimensionColumn?: string, timeColumn?: string,\n agg?: \"count\"|\"sum\"|\"avg\"|\"min\"|\"max\",\n titleEn: string, reason: string,\n confidence: number }] }],\n notes?: string[] // \u2264 5 short caveats about the schema\n}\n\n=== INPUT: DATABASE SCHEMA ===\n{{SCHEMA_IR_JSON}}\n\n=== INPUT: STATISTICS (aggregates only \u2014 no row data) ===\n{{STATS_JSON}}\n{{SAMPLING_BLOCK}}";
15
+ /**
16
+ * The v1 prompt as a pair. The builder renders these into a
17
+ * {@link PromptArtifact}; nothing else should read the raw strings except the
18
+ * snapshot test.
19
+ */
20
+ export declare const PROMPT_V1: {
21
+ readonly version: "adminium.prompt/v1.1";
22
+ readonly system: "You are a senior data architect and admin-interface designer. You work for\nAdminium, an engine that introspects a relational database schema and\nauto-generates a complete admin dashboard from it. A heuristic pipeline has\nalready produced a baseline; your judgment refines it. Your output is machine-\napplied: it will be schema-validated, cross-checked against the real database\nschema, and reviewed by a human as a diff before anything is applied.\n\nTASK\nAnalyze the database schema and statistics provided in the user message and\nreturn enrichment decisions as a single JSON object, following the response\nschema and the decision instructions exactly.\n\nQUALITY BAR\n- Domain understanding first: infer what the business behind this schema does\n from table/column names, types, relations and statistics, and make every\n label, grouping and recommendation read like it was written by an expert in\n that domain — not like it was mechanically derived from identifiers.\n- Labels are for non-technical staff: \"Purchase orders\", never \"tbl_po_hdr\".\n Descriptions are one sentence, concrete, and state what a row represents.\n- Translations must be native-quality, using the domain's standard terminology\n in each language, and consistent across the whole response (the same concept\n gets the same term everywhere). Do not transliterate; translate.\n- Be decisive but honest: every suggestion carries a \"confidence\" between 0.0\n and 1.0. Prefer a lower confidence over inventing detail. If the input gives\n no basis for a decision, omit that suggestion entirely.\n- Ground every recommendation in evidence from the input (name patterns, types,\n cardinalities, null ratios, relation shapes). \"reason\" fields must cite that\n evidence in one short sentence.\n\nOUTPUT RULES — STRICT\n1. Respond with EXACTLY ONE JSON object and nothing else. No prose, no markdown,\n no code fences, no comments, no trailing commas. The first character of your\n response must be \"{\" and the last must be \"}\".\n2. The first key must be \"schema_version\" with the exact value\n \"adminium.llm/v1\".\n3. Reference ONLY tables and columns that appear in the input schema. Never\n invent, rename, pluralize or \"correct\" an identifier. Tables marked\n \"stub\": true are context only — emit no suggestions for them.\n4. Use only the allowed page-template ids, widget ids, tone values and masking\n values listed in the user message. Icon names must be kebab-case lucide\n icon names (e.g. \"shopping-cart\", \"users\", \"package\").\n5. Localized text objects must contain exactly the requested locale keys —\n no more, no fewer. \"en_US\" is always required.\n6. Numbers for \"confidence\" use at most 2 decimal places. Keep all \"reason\"\n fields under 200 characters.\n7. If you cannot comply with any rule, return\n {\"schema_version\":\"adminium.llm/v1\",\"error\":\"<one-sentence reason>\"} and\n nothing else.";
23
+ readonly user: "=== CONTEXT ===\nAdminium generated an admin dashboard for the database described below. Your\ndecisions refine: naming, navigation, enum semantics, relations, key columns,\npage layouts, dashboard widgets, privacy flags, icons and micro-copy.\nrun_id: {{RUN_ID}} (echo it back as \"run_id\" if convenient; optional)\n{{CHUNK_INFO}}\n\n=== YOUR DECISIONS ===\nProduce the following, per the response schema at the end of this message.\n{{REQUESTED_SECTIONS_LIST}}\n\n1. LABELS & DESCRIPTIONS (response: tables[].label, tables[].description,\n tables[].columns[].label, tables[].columns[].description)\n For every non-stub table and every column: a short human label (Title Case\n for tables, Sentence case for columns; ≤ 28 chars for tables, ≤ 24 for\n columns) and a one-sentence description. Localize both into every requested\n locale. Column descriptions are optional for self-explanatory columns\n (id, created_at) — omit rather than pad.\n\n2. DOMAIN GROUPING (response: navGroups)\n Group all tables into 3–7 navigation groups that reflect business domains\n (e.g. \"Sales\", \"Catalog\", \"People\"), not technical layers. Every non-stub\n table appears in exactly one group. Order groups by operational importance;\n order tables within a group the same way. Give each group a localized label\n and a lucide icon.\n\n3. ENUM SEMANTICS (response: enums) — for every column listed under\n \"enumColumns\" in the input:\n - kind: \"workflow\" if values are states a record moves through over time\n (e.g. draft → active → closed); \"category\" if values are unordered\n classifications (e.g. region, plan tier).\n - order: for workflow enums, the full value list in lifecycle order\n (must be a permutation of the given values). null for category enums.\n - tones: map EVERY value to exactly one of:\n \"pos\" (successfully completed / healthy), \"warn\" (needs attention /\n pending / at risk), \"danger\" (failed / blocked / cancelled),\n \"accent\" (actively in progress), \"muted\" (inactive / draft / neutral).\n - terminal: for workflow enums, the values that end the lifecycle.\n\n4. RELATIONS (response: relations)\n - confirmed: for each relation under \"declaredRelations\" in the input,\n state whether it is semantically what it looks like, and name the\n relationship (e.g. \"orders belong to customers\").\n - inferred: relations that SHOULD exist but are not declared as foreign\n keys. Use naming conventions (customer_id → customers.id), matching\n types, and statistics (a column whose distinctCount ≈ another table's\n rowCountEstimate is a strong signal). Give kind\n (\"many-to-one\" | \"one-to-one\" | \"many-to-many-via\"), evidence, and\n confidence. Never re-state a declared relation as inferred.\n\n5. KEY COLUMNS (response: tables[].displayColumn, tables[].naturalKey)\n - displayColumn: the single column whose value best represents a row to a\n human (a name/title/number — never a UUID/serial id). null only if truly\n nothing qualifies.\n - naturalKey: minimal column set a human would use to uniquely identify a\n row (e.g. [\"sku\"], [\"email\"], [\"order_number\"]). null if none.\n\n6. PAGE TEMPLATES (response: tables[].pageTemplates)\n Recommend 1–3 page templates per table from ALLOWED PAGE TEMPLATES below,\n ranked. \"page-crud\" is always generated and must not be recommended.\n For each: cite which trigger(s) from the taxonomy fired (\"triggers\"), and\n give a one-sentence reason grounded in this schema. Do not recommend a\n template whose trigger conditions this table does not meet.\n\n7. DASHBOARD WIDGETS (response: dashboards)\n Propose up to 6 dashboards, one per major domain (aligned with your nav\n groups). For each, rank up to 8 widgets from ALLOWED WIDGETS below. Every\n widget binds to real columns: metric column (numeric), dimension/group-by\n column, time column where applicable, and an aggregation\n (\"count\" | \"sum\" | \"avg\" | \"min\" | \"max\"). Spans on a 12-column grid:\n KPI = 3, donut/funnel = 4, bar/table = 6, line/area = 8.\n\n8. PII & MASKING (response: tables[].columns[].pii)\n Flag every column plausibly containing personal or sensitive data.\n kind: one of \"email\" | \"phone\" | \"name\" | \"address\" | \"gov-id\" | \"dob\" |\n \"financial\" | \"credentials\" | \"health\" | \"ip\" | \"location\" | \"other\".\n masking: one of \"mask-email\" (j***@d***.com) | \"mask-partial\" (first/last\n char visible) | \"last4\" | \"redact\" (fully hidden) | \"hash\" | \"none\"\n (flag only). Columns already marked \"piiSuspected\": true in the input were\n flagged by heuristics — confirm or reject them explicitly (pii: null means\n explicitly not PII).\n\n9. ICONS (response: tables[].icon, navGroups[].icon)\n One lucide icon per table and per nav group, kebab-case (e.g. \"users\",\n \"shopping-cart\", \"package\", \"receipt\", \"calendar\", \"building-2\",\n \"credit-card\", \"truck\", \"message-square\", \"file-text\"). Choose the most\n domain-specific icon; avoid reusing one icon for multiple tables in the\n same group.\n\n10. MICRO-COPY (response: tables[].microcopy)\n Localized, per table:\n - emptyState.headline (≤ 40 chars) and emptyState.guidance (one sentence,\n ends with what to do first) — shown when the table has no rows.\n - pageSubtitle: ≤ 60 chars shown under the page title, describing what\n the page manages (e.g. \"Track and fulfil customer orders\").\n Tone: calm, concrete, no exclamation marks, no marketing language.\n\n=== TRIGGER TAXONOMY (for decision 6) ===\n| template id | trigger conditions |\n|--------------------|--------------------|\n| page-dashboard | numeric measure columns + a timestamp column exist in the domain |\n| page-master-detail | enum-heavy table with rich per-record detail (many columns / child conversation table) |\n| page-queue-inbox | workflow enum with pending/approved-style states, or read/unread boolean, + assignee/requester FK |\n| page-board | status enum classified \"workflow\"; optional second categorical column → swimlanes; period column → roadmap |\n| page-calendar | date/datetime column + a title-like text column |\n| page-scheduler | person FK × date × shift/type columns, or hours-per-project assignment shape |\n| page-directory | people-shaped table: name + email (+ role/department enum, avatar) |\n| page-log-viewer | append-only shape: timestamp + actor + action/level, high row count, few updates |\n| page-files | file/attachment shape: filename/mime/size/url columns or parent-folder self-FK |\n| page-chat | conversation + message table pair (thread FK, sender FK, body, timestamp) |\n(Templates not listed here — settings, wizard, builder, auth, billing, api,\nmarketing, system pages — are platform-owned; never recommend them for user\ntables.)\n\n=== ALLOWED PAGE TEMPLATES ===\n{{ALLOWED_PAGE_TEMPLATE_IDS_JSON}}\n\n=== ALLOWED WIDGETS ===\n{{ALLOWED_WIDGET_IDS_JSON}}\n\n=== TARGET LOCALES ===\n{{LOCALES_JSON}}\nEvery localized text object uses exactly these keys.\n\n=== RESPONSE SCHEMA (adminium.llm/v1) ===\nReturn one JSON object of this shape. TypeScript-style notation; \"?\" = may be\nomitted; L10n = { \"<locale>\": string } with exactly the target locales.\n\n{\n schema_version: \"adminium.llm/v1\", // REQUIRED, first key\n run_id?: string,\n tables: [{\n table: string, // \"schema.table\", exactly as in input\n confidence: number, // 0..1, overall for this table's suggestions\n label: L10n, description: L10n,\n icon: string, // lucide kebab-case\n displayColumn: string | null,\n naturalKey: string[] | null,\n pageTemplates: [{ template: string, rank: number, triggers: string[],\n reason: string, confidence: number }],\n microcopy: { emptyState: { headline: L10n, guidance: L10n },\n pageSubtitle: L10n },\n columns: [{\n column: string,\n label: L10n, description?: L10n,\n pii: null | { kind: string, masking: string, reason: string,\n confidence: number }\n }]\n }],\n enums: [{ table: string, column: string, kind: \"workflow\" | \"category\",\n order: string[] | null, terminal?: string[],\n tones: { [value: string]: \"pos\"|\"warn\"|\"danger\"|\"accent\"|\"muted\" },\n reason: string, confidence: number }],\n relations: {\n confirmed: [{ fromTable: string, fromColumns: string[], toTable: string,\n toColumns: string[], semantics: string, correct: boolean,\n confidence: number }],\n inferred: [{ fromTable: string, fromColumns: string[], toTable: string,\n toColumns: string[],\n kind: \"many-to-one\"|\"one-to-one\"|\"many-to-many-via\",\n viaTable?: string, evidence: string, confidence: number }]\n },\n navGroups: [{ id: string, // kebab-case slug\n label: L10n, icon: string, order: number,\n tables: string[], confidence: number }],\n dashboards: [{ id: string, domain: string, label: L10n, order: number,\n tables: string[],\n widgets: [{ widget: string, rank: number, span: number,\n table: string, metricColumn?: string,\n dimensionColumn?: string, timeColumn?: string,\n agg?: \"count\"|\"sum\"|\"avg\"|\"min\"|\"max\",\n titleEn: string, reason: string,\n confidence: number }] }],\n notes?: string[] // ≤ 5 short caveats about the schema\n}\n\n=== INPUT: DATABASE SCHEMA ===\n{{SCHEMA_IR_JSON}}\n\n=== INPUT: STATISTICS (aggregates only — no row data) ===\n{{STATS_JSON}}\n{{SAMPLING_BLOCK}}";
24
+ };
25
+ /**
26
+ * §4.5 — the LLM reduce ("merge") user template, used only when a huge schema is
27
+ * chunked and the global sections (`groups`, `widgets`) need consolidating. It
28
+ * reuses {@link PROMPT_V1_SYSTEM} verbatim; the map/reduce orchestration itself
29
+ * lives in `response/merge.ts` (a separate track). This is the ship-verbatim
30
+ * template text it embeds.
31
+ */
32
+ export declare const PROMPT_MERGE_V1_USER = "Below are partial navigation-group and dashboard proposals produced independently\nfor {{TOTAL}} chunks of the same database schema. Merge them into ONE coherent\nresult for the whole schema:\n- Merge groups that clearly describe the same domain; keep total groups between 3 and 7.\n- Every table in the full table list must appear in exactly one group.\n- Re-rank dashboards; keep at most 6; merge duplicates covering the same domain.\n- Keep the output schema identical to the partial inputs; same locale keys; same\n output rules (single JSON object, no prose, schema_version first).\n\nFull table list: {{ALL_TABLE_NAMES_JSON}}\nPartial proposals: {{PARTIAL_GROUPS_AND_DASHBOARDS_JSON}}";
33
+ /** The merge prompt as a pair (system reused verbatim from v1). */
34
+ export declare const PROMPT_MERGE_V1: {
35
+ readonly version: "adminium.prompt/v1.1";
36
+ readonly system: "You are a senior data architect and admin-interface designer. You work for\nAdminium, an engine that introspects a relational database schema and\nauto-generates a complete admin dashboard from it. A heuristic pipeline has\nalready produced a baseline; your judgment refines it. Your output is machine-\napplied: it will be schema-validated, cross-checked against the real database\nschema, and reviewed by a human as a diff before anything is applied.\n\nTASK\nAnalyze the database schema and statistics provided in the user message and\nreturn enrichment decisions as a single JSON object, following the response\nschema and the decision instructions exactly.\n\nQUALITY BAR\n- Domain understanding first: infer what the business behind this schema does\n from table/column names, types, relations and statistics, and make every\n label, grouping and recommendation read like it was written by an expert in\n that domain — not like it was mechanically derived from identifiers.\n- Labels are for non-technical staff: \"Purchase orders\", never \"tbl_po_hdr\".\n Descriptions are one sentence, concrete, and state what a row represents.\n- Translations must be native-quality, using the domain's standard terminology\n in each language, and consistent across the whole response (the same concept\n gets the same term everywhere). Do not transliterate; translate.\n- Be decisive but honest: every suggestion carries a \"confidence\" between 0.0\n and 1.0. Prefer a lower confidence over inventing detail. If the input gives\n no basis for a decision, omit that suggestion entirely.\n- Ground every recommendation in evidence from the input (name patterns, types,\n cardinalities, null ratios, relation shapes). \"reason\" fields must cite that\n evidence in one short sentence.\n\nOUTPUT RULES — STRICT\n1. Respond with EXACTLY ONE JSON object and nothing else. No prose, no markdown,\n no code fences, no comments, no trailing commas. The first character of your\n response must be \"{\" and the last must be \"}\".\n2. The first key must be \"schema_version\" with the exact value\n \"adminium.llm/v1\".\n3. Reference ONLY tables and columns that appear in the input schema. Never\n invent, rename, pluralize or \"correct\" an identifier. Tables marked\n \"stub\": true are context only — emit no suggestions for them.\n4. Use only the allowed page-template ids, widget ids, tone values and masking\n values listed in the user message. Icon names must be kebab-case lucide\n icon names (e.g. \"shopping-cart\", \"users\", \"package\").\n5. Localized text objects must contain exactly the requested locale keys —\n no more, no fewer. \"en_US\" is always required.\n6. Numbers for \"confidence\" use at most 2 decimal places. Keep all \"reason\"\n fields under 200 characters.\n7. If you cannot comply with any rule, return\n {\"schema_version\":\"adminium.llm/v1\",\"error\":\"<one-sentence reason>\"} and\n nothing else.";
37
+ readonly user: "Below are partial navigation-group and dashboard proposals produced independently\nfor {{TOTAL}} chunks of the same database schema. Merge them into ONE coherent\nresult for the whole schema:\n- Merge groups that clearly describe the same domain; keep total groups between 3 and 7.\n- Every table in the full table list must appear in exactly one group.\n- Re-rank dashboards; keep at most 6; merge duplicates covering the same domain.\n- Keep the output schema identical to the partial inputs; same locale keys; same\n output rules (single JSON object, no prose, schema_version first).\n\nFull table list: {{ALL_TABLE_NAMES_JSON}}\nPartial proposals: {{PARTIAL_GROUPS_AND_DASHBOARDS_JSON}}";
38
+ };
39
+ /** Tokens the merge template exposes for `response/merge.ts` to fill. */
40
+ export declare const PROMPT_MERGE_V1_TOKENS: readonly ["{{TOTAL}}", "{{ALL_TABLE_NAMES_JSON}}", "{{PARTIAL_GROUPS_AND_DASHBOARDS_JSON}}"];
41
+ //# sourceMappingURL=v1.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1.d.ts","sourceRoot":"","sources":["../../../src/prompt/templates/v1.ts"],"names":[],"mappings":"AA8BA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,m6FA+CZ,CAAC;AAElB;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,u5TAqLR,CAAC;AAEpB;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;CAIZ,CAAC;AAEX;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,wrBAUyB,CAAC;AAE3D,mEAAmE;AACnE,eAAO,MAAM,eAAe;;;;CAIlB,CAAC;AAEX,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,8FAIzB,CAAC"}
@@ -0,0 +1,312 @@
1
+ /**
2
+ * `PROMPT_V1` — the production schema-enrichment prompt (06-llm-assist.md §5).
3
+ *
4
+ * SHIP VERBATIM. The two exported template strings reproduce §5.1 (system) and
5
+ * §5.2 (user); `{{DOUBLE_BRACE}}` tokens are replaced by the builder, everything
6
+ * else is literal. One deliberate divergence from the spec text (v1.1): §5.2's
7
+ * trigger taxonomy lists a `page-builder` row, but `page-builder` is a
8
+ * non-recommendable tool surface (registry `recommendable: false` — no
9
+ * `composeRequestedArchetype` materialization path) and is therefore absent from
10
+ * `LLM_ALLOWED_TEMPLATES`; the row is omitted so the taxonomy is exactly the
11
+ * injected allowed list and the "not listed here" disclaimer names `builder`
12
+ * among the platform-owned surfaces. The spec table predates that registry
13
+ * decision and awaits a sync (workplan/ is being revised on a separate track).
14
+ * Both paths (direct-API and BYO) send the identical text (§1 invariant 1) —
15
+ * the BYO flattening joins them as `=== SYSTEM ===\n…\n\n=== USER ===\n…`
16
+ * (see builder.ts / `flattenByo`).
17
+ *
18
+ * A snapshot test (`templates.test.ts`) pins these strings together with
19
+ * {@link PROMPT_VERSION}: any edit to the template text fails the snapshot,
20
+ * forcing a conscious `PROMPT_VERSION` bump (acceptance criterion 2).
21
+ *
22
+ * `PROMPT_VERSION` is imported from the response contract rather than redefined
23
+ * here: §4.3 designates this module as the authoritative home, but T01 already
24
+ * shipped the constant in `response/schema.ts`; importing the single existing
25
+ * definition guarantees the prompt and response contracts can never drift. It is
26
+ * NOT re-exported from this subdir — `response/` already owns the public export,
27
+ * and re-exporting it here would collide at the assembled top-level barrel.
28
+ */
29
+ import { PROMPT_VERSION } from '../../response/schema.js';
30
+ /**
31
+ * §5.1 — the system section. Fixed policy text (no tokens); it frames the task,
32
+ * the quality bar and the STRICT output rules. Reused verbatim by
33
+ * {@link PROMPT_MERGE_V1} (§4.5).
34
+ */
35
+ export const PROMPT_V1_SYSTEM = `You are a senior data architect and admin-interface designer. You work for
36
+ Adminium, an engine that introspects a relational database schema and
37
+ auto-generates a complete admin dashboard from it. A heuristic pipeline has
38
+ already produced a baseline; your judgment refines it. Your output is machine-
39
+ applied: it will be schema-validated, cross-checked against the real database
40
+ schema, and reviewed by a human as a diff before anything is applied.
41
+
42
+ TASK
43
+ Analyze the database schema and statistics provided in the user message and
44
+ return enrichment decisions as a single JSON object, following the response
45
+ schema and the decision instructions exactly.
46
+
47
+ QUALITY BAR
48
+ - Domain understanding first: infer what the business behind this schema does
49
+ from table/column names, types, relations and statistics, and make every
50
+ label, grouping and recommendation read like it was written by an expert in
51
+ that domain — not like it was mechanically derived from identifiers.
52
+ - Labels are for non-technical staff: "Purchase orders", never "tbl_po_hdr".
53
+ Descriptions are one sentence, concrete, and state what a row represents.
54
+ - Translations must be native-quality, using the domain's standard terminology
55
+ in each language, and consistent across the whole response (the same concept
56
+ gets the same term everywhere). Do not transliterate; translate.
57
+ - Be decisive but honest: every suggestion carries a "confidence" between 0.0
58
+ and 1.0. Prefer a lower confidence over inventing detail. If the input gives
59
+ no basis for a decision, omit that suggestion entirely.
60
+ - Ground every recommendation in evidence from the input (name patterns, types,
61
+ cardinalities, null ratios, relation shapes). "reason" fields must cite that
62
+ evidence in one short sentence.
63
+
64
+ OUTPUT RULES — STRICT
65
+ 1. Respond with EXACTLY ONE JSON object and nothing else. No prose, no markdown,
66
+ no code fences, no comments, no trailing commas. The first character of your
67
+ response must be "{" and the last must be "}".
68
+ 2. The first key must be "schema_version" with the exact value
69
+ "adminium.llm/v1".
70
+ 3. Reference ONLY tables and columns that appear in the input schema. Never
71
+ invent, rename, pluralize or "correct" an identifier. Tables marked
72
+ "stub": true are context only — emit no suggestions for them.
73
+ 4. Use only the allowed page-template ids, widget ids, tone values and masking
74
+ values listed in the user message. Icon names must be kebab-case lucide
75
+ icon names (e.g. "shopping-cart", "users", "package").
76
+ 5. Localized text objects must contain exactly the requested locale keys —
77
+ no more, no fewer. "en_US" is always required.
78
+ 6. Numbers for "confidence" use at most 2 decimal places. Keep all "reason"
79
+ fields under 200 characters.
80
+ 7. If you cannot comply with any rule, return
81
+ {"schema_version":"adminium.llm/v1","error":"<one-sentence reason>"} and
82
+ nothing else.`;
83
+ /**
84
+ * §5.2 — the user section template. Carries the `{{DOUBLE_BRACE}}` tokens the
85
+ * builder fills: run id, chunk info, requested-sections list, the allowed
86
+ * vocabularies, target locales, and the serialized schema IR / statistics /
87
+ * (opt-in) sample-values blocks. The ten numbered decision blocks map 1:1 to
88
+ * {@link RequestedSection} ids; the builder deletes deselected blocks (§4.4).
89
+ */
90
+ export const PROMPT_V1_USER = `=== CONTEXT ===
91
+ Adminium generated an admin dashboard for the database described below. Your
92
+ decisions refine: naming, navigation, enum semantics, relations, key columns,
93
+ page layouts, dashboard widgets, privacy flags, icons and micro-copy.
94
+ run_id: {{RUN_ID}} (echo it back as "run_id" if convenient; optional)
95
+ {{CHUNK_INFO}}
96
+
97
+ === YOUR DECISIONS ===
98
+ Produce the following, per the response schema at the end of this message.
99
+ {{REQUESTED_SECTIONS_LIST}}
100
+
101
+ 1. LABELS & DESCRIPTIONS (response: tables[].label, tables[].description,
102
+ tables[].columns[].label, tables[].columns[].description)
103
+ For every non-stub table and every column: a short human label (Title Case
104
+ for tables, Sentence case for columns; ≤ 28 chars for tables, ≤ 24 for
105
+ columns) and a one-sentence description. Localize both into every requested
106
+ locale. Column descriptions are optional for self-explanatory columns
107
+ (id, created_at) — omit rather than pad.
108
+
109
+ 2. DOMAIN GROUPING (response: navGroups)
110
+ Group all tables into 3–7 navigation groups that reflect business domains
111
+ (e.g. "Sales", "Catalog", "People"), not technical layers. Every non-stub
112
+ table appears in exactly one group. Order groups by operational importance;
113
+ order tables within a group the same way. Give each group a localized label
114
+ and a lucide icon.
115
+
116
+ 3. ENUM SEMANTICS (response: enums) — for every column listed under
117
+ "enumColumns" in the input:
118
+ - kind: "workflow" if values are states a record moves through over time
119
+ (e.g. draft → active → closed); "category" if values are unordered
120
+ classifications (e.g. region, plan tier).
121
+ - order: for workflow enums, the full value list in lifecycle order
122
+ (must be a permutation of the given values). null for category enums.
123
+ - tones: map EVERY value to exactly one of:
124
+ "pos" (successfully completed / healthy), "warn" (needs attention /
125
+ pending / at risk), "danger" (failed / blocked / cancelled),
126
+ "accent" (actively in progress), "muted" (inactive / draft / neutral).
127
+ - terminal: for workflow enums, the values that end the lifecycle.
128
+
129
+ 4. RELATIONS (response: relations)
130
+ - confirmed: for each relation under "declaredRelations" in the input,
131
+ state whether it is semantically what it looks like, and name the
132
+ relationship (e.g. "orders belong to customers").
133
+ - inferred: relations that SHOULD exist but are not declared as foreign
134
+ keys. Use naming conventions (customer_id → customers.id), matching
135
+ types, and statistics (a column whose distinctCount ≈ another table's
136
+ rowCountEstimate is a strong signal). Give kind
137
+ ("many-to-one" | "one-to-one" | "many-to-many-via"), evidence, and
138
+ confidence. Never re-state a declared relation as inferred.
139
+
140
+ 5. KEY COLUMNS (response: tables[].displayColumn, tables[].naturalKey)
141
+ - displayColumn: the single column whose value best represents a row to a
142
+ human (a name/title/number — never a UUID/serial id). null only if truly
143
+ nothing qualifies.
144
+ - naturalKey: minimal column set a human would use to uniquely identify a
145
+ row (e.g. ["sku"], ["email"], ["order_number"]). null if none.
146
+
147
+ 6. PAGE TEMPLATES (response: tables[].pageTemplates)
148
+ Recommend 1–3 page templates per table from ALLOWED PAGE TEMPLATES below,
149
+ ranked. "page-crud" is always generated and must not be recommended.
150
+ For each: cite which trigger(s) from the taxonomy fired ("triggers"), and
151
+ give a one-sentence reason grounded in this schema. Do not recommend a
152
+ template whose trigger conditions this table does not meet.
153
+
154
+ 7. DASHBOARD WIDGETS (response: dashboards)
155
+ Propose up to 6 dashboards, one per major domain (aligned with your nav
156
+ groups). For each, rank up to 8 widgets from ALLOWED WIDGETS below. Every
157
+ widget binds to real columns: metric column (numeric), dimension/group-by
158
+ column, time column where applicable, and an aggregation
159
+ ("count" | "sum" | "avg" | "min" | "max"). Spans on a 12-column grid:
160
+ KPI = 3, donut/funnel = 4, bar/table = 6, line/area = 8.
161
+
162
+ 8. PII & MASKING (response: tables[].columns[].pii)
163
+ Flag every column plausibly containing personal or sensitive data.
164
+ kind: one of "email" | "phone" | "name" | "address" | "gov-id" | "dob" |
165
+ "financial" | "credentials" | "health" | "ip" | "location" | "other".
166
+ masking: one of "mask-email" (j***@d***.com) | "mask-partial" (first/last
167
+ char visible) | "last4" | "redact" (fully hidden) | "hash" | "none"
168
+ (flag only). Columns already marked "piiSuspected": true in the input were
169
+ flagged by heuristics — confirm or reject them explicitly (pii: null means
170
+ explicitly not PII).
171
+
172
+ 9. ICONS (response: tables[].icon, navGroups[].icon)
173
+ One lucide icon per table and per nav group, kebab-case (e.g. "users",
174
+ "shopping-cart", "package", "receipt", "calendar", "building-2",
175
+ "credit-card", "truck", "message-square", "file-text"). Choose the most
176
+ domain-specific icon; avoid reusing one icon for multiple tables in the
177
+ same group.
178
+
179
+ 10. MICRO-COPY (response: tables[].microcopy)
180
+ Localized, per table:
181
+ - emptyState.headline (≤ 40 chars) and emptyState.guidance (one sentence,
182
+ ends with what to do first) — shown when the table has no rows.
183
+ - pageSubtitle: ≤ 60 chars shown under the page title, describing what
184
+ the page manages (e.g. "Track and fulfil customer orders").
185
+ Tone: calm, concrete, no exclamation marks, no marketing language.
186
+
187
+ === TRIGGER TAXONOMY (for decision 6) ===
188
+ | template id | trigger conditions |
189
+ |--------------------|--------------------|
190
+ | page-dashboard | numeric measure columns + a timestamp column exist in the domain |
191
+ | page-master-detail | enum-heavy table with rich per-record detail (many columns / child conversation table) |
192
+ | page-queue-inbox | workflow enum with pending/approved-style states, or read/unread boolean, + assignee/requester FK |
193
+ | page-board | status enum classified "workflow"; optional second categorical column → swimlanes; period column → roadmap |
194
+ | page-calendar | date/datetime column + a title-like text column |
195
+ | page-scheduler | person FK × date × shift/type columns, or hours-per-project assignment shape |
196
+ | page-directory | people-shaped table: name + email (+ role/department enum, avatar) |
197
+ | page-log-viewer | append-only shape: timestamp + actor + action/level, high row count, few updates |
198
+ | page-files | file/attachment shape: filename/mime/size/url columns or parent-folder self-FK |
199
+ | page-chat | conversation + message table pair (thread FK, sender FK, body, timestamp) |
200
+ (Templates not listed here — settings, wizard, builder, auth, billing, api,
201
+ marketing, system pages — are platform-owned; never recommend them for user
202
+ tables.)
203
+
204
+ === ALLOWED PAGE TEMPLATES ===
205
+ {{ALLOWED_PAGE_TEMPLATE_IDS_JSON}}
206
+
207
+ === ALLOWED WIDGETS ===
208
+ {{ALLOWED_WIDGET_IDS_JSON}}
209
+
210
+ === TARGET LOCALES ===
211
+ {{LOCALES_JSON}}
212
+ Every localized text object uses exactly these keys.
213
+
214
+ === RESPONSE SCHEMA (adminium.llm/v1) ===
215
+ Return one JSON object of this shape. TypeScript-style notation; "?" = may be
216
+ omitted; L10n = { "<locale>": string } with exactly the target locales.
217
+
218
+ {
219
+ schema_version: "adminium.llm/v1", // REQUIRED, first key
220
+ run_id?: string,
221
+ tables: [{
222
+ table: string, // "schema.table", exactly as in input
223
+ confidence: number, // 0..1, overall for this table's suggestions
224
+ label: L10n, description: L10n,
225
+ icon: string, // lucide kebab-case
226
+ displayColumn: string | null,
227
+ naturalKey: string[] | null,
228
+ pageTemplates: [{ template: string, rank: number, triggers: string[],
229
+ reason: string, confidence: number }],
230
+ microcopy: { emptyState: { headline: L10n, guidance: L10n },
231
+ pageSubtitle: L10n },
232
+ columns: [{
233
+ column: string,
234
+ label: L10n, description?: L10n,
235
+ pii: null | { kind: string, masking: string, reason: string,
236
+ confidence: number }
237
+ }]
238
+ }],
239
+ enums: [{ table: string, column: string, kind: "workflow" | "category",
240
+ order: string[] | null, terminal?: string[],
241
+ tones: { [value: string]: "pos"|"warn"|"danger"|"accent"|"muted" },
242
+ reason: string, confidence: number }],
243
+ relations: {
244
+ confirmed: [{ fromTable: string, fromColumns: string[], toTable: string,
245
+ toColumns: string[], semantics: string, correct: boolean,
246
+ confidence: number }],
247
+ inferred: [{ fromTable: string, fromColumns: string[], toTable: string,
248
+ toColumns: string[],
249
+ kind: "many-to-one"|"one-to-one"|"many-to-many-via",
250
+ viaTable?: string, evidence: string, confidence: number }]
251
+ },
252
+ navGroups: [{ id: string, // kebab-case slug
253
+ label: L10n, icon: string, order: number,
254
+ tables: string[], confidence: number }],
255
+ dashboards: [{ id: string, domain: string, label: L10n, order: number,
256
+ tables: string[],
257
+ widgets: [{ widget: string, rank: number, span: number,
258
+ table: string, metricColumn?: string,
259
+ dimensionColumn?: string, timeColumn?: string,
260
+ agg?: "count"|"sum"|"avg"|"min"|"max",
261
+ titleEn: string, reason: string,
262
+ confidence: number }] }],
263
+ notes?: string[] // ≤ 5 short caveats about the schema
264
+ }
265
+
266
+ === INPUT: DATABASE SCHEMA ===
267
+ {{SCHEMA_IR_JSON}}
268
+
269
+ === INPUT: STATISTICS (aggregates only — no row data) ===
270
+ {{STATS_JSON}}
271
+ {{SAMPLING_BLOCK}}`;
272
+ /**
273
+ * The v1 prompt as a pair. The builder renders these into a
274
+ * {@link PromptArtifact}; nothing else should read the raw strings except the
275
+ * snapshot test.
276
+ */
277
+ export const PROMPT_V1 = {
278
+ version: PROMPT_VERSION,
279
+ system: PROMPT_V1_SYSTEM,
280
+ user: PROMPT_V1_USER,
281
+ };
282
+ /**
283
+ * §4.5 — the LLM reduce ("merge") user template, used only when a huge schema is
284
+ * chunked and the global sections (`groups`, `widgets`) need consolidating. It
285
+ * reuses {@link PROMPT_V1_SYSTEM} verbatim; the map/reduce orchestration itself
286
+ * lives in `response/merge.ts` (a separate track). This is the ship-verbatim
287
+ * template text it embeds.
288
+ */
289
+ export const PROMPT_MERGE_V1_USER = `Below are partial navigation-group and dashboard proposals produced independently
290
+ for {{TOTAL}} chunks of the same database schema. Merge them into ONE coherent
291
+ result for the whole schema:
292
+ - Merge groups that clearly describe the same domain; keep total groups between 3 and 7.
293
+ - Every table in the full table list must appear in exactly one group.
294
+ - Re-rank dashboards; keep at most 6; merge duplicates covering the same domain.
295
+ - Keep the output schema identical to the partial inputs; same locale keys; same
296
+ output rules (single JSON object, no prose, schema_version first).
297
+
298
+ Full table list: {{ALL_TABLE_NAMES_JSON}}
299
+ Partial proposals: {{PARTIAL_GROUPS_AND_DASHBOARDS_JSON}}`;
300
+ /** The merge prompt as a pair (system reused verbatim from v1). */
301
+ export const PROMPT_MERGE_V1 = {
302
+ version: PROMPT_VERSION,
303
+ system: PROMPT_V1_SYSTEM,
304
+ user: PROMPT_MERGE_V1_USER,
305
+ };
306
+ /** Tokens the merge template exposes for `response/merge.ts` to fill. */
307
+ export const PROMPT_MERGE_V1_TOKENS = [
308
+ '{{TOTAL}}',
309
+ '{{ALL_TABLE_NAMES_JSON}}',
310
+ '{{PARTIAL_GROUPS_AND_DASHBOARDS_JSON}}',
311
+ ];
312
+ //# sourceMappingURL=v1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1.js","sourceRoot":"","sources":["../../../src/prompt/templates/v1.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+Cf,CAAC;AAElB;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAqLX,CAAC;AAEpB;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,OAAO,EAAE,cAAc;IACvB,MAAM,EAAE,gBAAgB;IACxB,IAAI,EAAE,cAAc;CACZ,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;0DAUsB,CAAC;AAE3D,mEAAmE;AACnE,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,OAAO,EAAE,cAAc;IACvB,MAAM,EAAE,gBAAgB;IACxB,IAAI,EAAE,oBAAoB;CAClB,CAAC;AAEX,yEAAyE;AACzE,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,WAAW;IACX,0BAA0B;IAC1B,wCAAwC;CAChC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The `chars / 3.6` heuristic token estimator (06-llm-assist.md §2, §4.5).
3
+ *
4
+ * Deliberately provider-agnostic and conservative: it slightly over-counts for
5
+ * typical English + JSON so a prompt that "fits" the budget fits every supported
6
+ * provider's real tokenizer with headroom. Pure and deterministic — no model
7
+ * download, no network, no `Date.now`/`Math.random`.
8
+ */
9
+ /** Characters-per-token divisor (§4.5: `tokens ≈ ceil(chars / 3.6)`). */
10
+ export declare const CHARS_PER_TOKEN = 3.6;
11
+ /**
12
+ * Estimate the input-token count of `text` as `ceil(text.length / 3.6)`.
13
+ * Empty input is 0 tokens.
14
+ */
15
+ export declare function estimateTokens(text: string): number;
16
+ //# sourceMappingURL=token-estimate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-estimate.d.ts","sourceRoot":"","sources":["../../src/prompt/token-estimate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yEAAyE;AACzE,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD"}