@adia-ai/a2ui-retrieval 0.6.4 → 0.6.7

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 (38) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/domain-router.js +362 -117
  3. package/embedding/chunk-embedding-retriever.js +47 -79
  4. package/embedding/embedding-provider.js +35 -71
  5. package/embedding/index.js +2 -10
  6. package/feedback/dialog-recorder.js +61 -145
  7. package/feedback/feedback-analyzer.js +46 -102
  8. package/feedback/feedback-store.js +91 -107
  9. package/feedback/feedback.js +36 -117
  10. package/feedback/gap-registry.js +40 -82
  11. package/feedback/index.js +14 -12
  12. package/index.d.ts +4 -0
  13. package/index.js +53 -16
  14. package/intent/clarity.js +61 -129
  15. package/intent/decomposer.js +51 -143
  16. package/intent/index.js +18 -14
  17. package/intent/intent-alignment.js +79 -150
  18. package/intent/intent-categorizer.js +34 -62
  19. package/intent/intent-gate.js +43 -102
  20. package/intent/prompt-analyzer.js +68 -126
  21. package/package.json +4 -2
  22. package/wiring-catalog.js +95 -146
  23. package/embedding/chunk-embedding-retriever.ts +0 -156
  24. package/embedding/embedding-provider.ts +0 -111
  25. package/embedding/index.ts +0 -10
  26. package/feedback/dialog-recorder.ts +0 -172
  27. package/feedback/feedback-analyzer.ts +0 -250
  28. package/feedback/feedback-store.ts +0 -229
  29. package/feedback/feedback.ts +0 -201
  30. package/feedback/gap-registry.ts +0 -137
  31. package/feedback/index.ts +0 -14
  32. package/intent/clarity.ts +0 -224
  33. package/intent/decomposer.ts +0 -229
  34. package/intent/index.ts +0 -20
  35. package/intent/intent-alignment.ts +0 -267
  36. package/intent/intent-categorizer.ts +0 -104
  37. package/intent/intent-gate.ts +0 -151
  38. package/intent/prompt-analyzer.ts +0 -231
@@ -1,18 +1,5 @@
1
- /**
2
- * Intent Gate — Detect whether a user message is conversational (question,
3
- * greeting, meta-question) vs. a UI generation request.
4
- *
5
- * Used by the chat setup to route non-generation messages to a text reply
6
- * instead of the full 6-stage pipeline.
7
- */
8
-
9
- /** Question starters that signal conversational intent */
10
1
  const QUESTION_STARTERS = /^\s*(what|how|why|can you|could you|tell me|explain|describe|help|who|where|when|is there|are there|do you|does|did|will|should|would|which|list|show me how)\b/i;
11
-
12
- /** Greetings and social signals */
13
2
  const GREETINGS = /^\s*(hi|hey|hello|thanks|thank you|bye|goodbye|ok|okay|sure|great|nice|cool|awesome|good|perfect)\b/i;
14
-
15
- /** Explicit meta-questions about the system itself */
16
3
  const META_PATTERNS = [
17
4
  /what\s+(components?|types?|elements?)\s+(are|do|can)/i,
18
5
  /available\s+(components?|types?|elements?)/i,
@@ -25,131 +12,85 @@ const META_PATTERNS = [
25
12
  /explain\s+(the|how|what|a)/i,
26
13
  /how\s+(to|do\s+I)\s+(use|create|build|make)/i,
27
14
  /what\s+would\s+.+\s+look\s+like/i,
28
- /show\s+me\s+how/i,
15
+ /show\s+me\s+how/i
29
16
  ];
30
-
31
- /** Content that is clearly not a UI intent (injection, noise, gibberish) */
32
17
  const NOISE_PATTERNS = [
33
- /^<[^>]+>/, // Starts with HTML tag
34
- /^\s*[^\w\s]{3,}/, // Starts with 3+ non-word chars
18
+ /^<[^>]+>/,
19
+ // Starts with HTML tag
20
+ /^\s*[^\w\s]{3,}/
21
+ // Starts with 3+ non-word chars
35
22
  ];
36
-
37
- /** Vague, motivational, or abstract phrases with no UI-describing language.
38
- * These are common conversational inputs that the LLM hallucinates UIs from. */
39
23
  const VAGUE_PATTERNS = [
40
- /^(do|just|go|let'?s|get|keep|stay|stop|try|move|make it|finish|start|begin|end|run)\b.{0,30}$/i, // short imperative without UI nouns
24
+ /^(do|just|go|let'?s|get|keep|stay|stop|try|move|make it|finish|start|begin|end|run)\b.{0,30}$/i,
25
+ // short imperative without UI nouns
41
26
  /\b(you gotta|gotta do|no excuses|believe|motivat|inspir|goal|dream|life|love|hate|feel|think about)\b/i,
42
- /\b(what is preventing|what stops|why can't|why won't)\b/i, // philosophical/motivational questions
43
- /\b(do it|get it done|let'?s go|make it happen|just do it|never give up)\b/i, // motivational slogans
27
+ /\b(what is preventing|what stops|why can't|why won't)\b/i,
28
+ // philosophical/motivational questions
29
+ /\b(do it|get it done|let'?s go|make it happen|just do it|never give up)\b/i
30
+ // motivational slogans
44
31
  ];
45
-
46
- /** Tool-use and action-oriented intents that should be handled conversationally,
47
- * not as UI generation requests. These are commands directed at the system. */
48
32
  const TOOL_USE_PATTERNS = [
49
33
  /\b(create|file|write|open|submit|log)\s+(a\s+)?(ticket|bug|issue|report|feature request)/i,
50
34
  /\b(report|flag)\s+(a\s+)?(bug|issue|problem|error|regression)/i,
51
- /\b(why|how come)\s+(does|did|is|was|are)\b/i, // "why does X do Y" — asking for rationale
35
+ /\b(why|how come)\s+(does|did|is|was|are)\b/i,
36
+ // "why does X do Y" — asking for rationale
52
37
  /\bthis\s+(is|looks?)\s+(broken|wrong|buggy|off|weird|strange)/i,
53
38
  /\b(fix|repair|debug|investigate)\s+(this|the|that)/i,
54
39
  /\bsomething\s+(is|seems|looks?)\s+(wrong|broken|off)/i,
55
40
  /\bmark\s+(this|it)\s+as\b/i,
56
- /\b(save|bookmark|pin)\s+(this|the)\s+(pattern|output|result)/i,
41
+ /\b(save|bookmark|pin)\s+(this|the)\s+(pattern|output|result)/i
57
42
  ];
58
-
59
- /**
60
- * Determine if a user message is conversational (should get a text reply)
61
- * or a UI generation request (should go through the pipeline).
62
- *
63
- * @param {string} text — Raw user input
64
- * @param {{ confidence: number, matchedSignals: string[] }} classifyResult — From classifyIntent()
65
- * @returns {{ conversational: boolean, reason: string }}
66
- */
67
- export function isConversational(text, classifyResult) {
68
- const trimmed = (text ?? '').trim();
69
- if (!classifyResult || typeof classifyResult !== 'object') {
70
- classifyResult = { confidence: 0, matchedSignals: [] };
43
+ function isConversational(text, classifyResult) {
44
+ const trimmed = (text ?? "").trim();
45
+ let cls = classifyResult && typeof classifyResult === "object" ? classifyResult : { confidence: 0, matchedSignals: [] };
46
+ if (trimmed.length < 5 && cls.confidence === 0) {
47
+ return { conversational: true, reason: "short-input" };
71
48
  }
72
-
73
- // Very short messages without generation keywords are likely conversational
74
- if (trimmed.length < 5 && classifyResult.confidence === 0) {
75
- return { conversational: true, reason: 'short-input' };
76
- }
77
-
78
- // Noise/injection detection — not a real intent
79
49
  for (const pattern of NOISE_PATTERNS) {
80
- if (pattern.test(trimmed) && classifyResult.confidence === 0) {
81
- return { conversational: true, reason: 'noise-input' };
50
+ if (pattern.test(trimmed) && cls.confidence === 0) {
51
+ return { conversational: true, reason: "noise-input" };
82
52
  }
83
53
  }
84
-
85
- // Explicit meta-questions about the system → always conversational
86
54
  for (const pattern of META_PATTERNS) {
87
55
  if (pattern.test(trimmed)) {
88
- return { conversational: true, reason: 'meta-question' };
56
+ return { conversational: true, reason: "meta-question" };
89
57
  }
90
58
  }
91
-
92
- // Tool-use commands (create ticket, report bug, etc.) → conversational
93
59
  for (const pattern of TOOL_USE_PATTERNS) {
94
60
  if (pattern.test(trimmed)) {
95
- return { conversational: true, reason: 'tool-use' };
61
+ return { conversational: true, reason: "tool-use" };
96
62
  }
97
63
  }
98
-
99
- // Vague / motivational / abstract input with no or weak domain signals
100
- // Catches "do what you gotta do", "just do it", "what is preventing you?", etc.
101
- // These have zero genuine UI intent but the LLM hallucinates full UIs from them.
102
- if (classifyResult.confidence < 0.2) {
64
+ if (cls.confidence < 0.2) {
103
65
  for (const pattern of VAGUE_PATTERNS) {
104
66
  if (pattern.test(trimmed)) {
105
- return { conversational: true, reason: 'vague-or-abstract' };
67
+ return { conversational: true, reason: "vague-or-abstract" };
106
68
  }
107
69
  }
108
70
  }
109
-
110
- // Greetings with no generation signals
111
- if (GREETINGS.test(trimmed) && classifyResult.confidence === 0) {
112
- return { conversational: true, reason: 'greeting' };
71
+ if (GREETINGS.test(trimmed) && cls.confidence === 0) {
72
+ return { conversational: true, reason: "greeting" };
113
73
  }
114
-
115
- // High confidence in a domain → generation request
116
- if (classifyResult.confidence >= 0.3) {
117
- return { conversational: false, reason: 'high-domain-confidence' };
74
+ if (cls.confidence >= 0.3) {
75
+ return { conversational: false, reason: "high-domain-confidence" };
118
76
  }
119
-
120
- // Has matched at least 2 domain signals → generation
121
- if (classifyResult.matchedSignals?.length >= 2) {
122
- return { conversational: false, reason: 'multiple-domain-signals' };
77
+ if ((cls.matchedSignals?.length ?? 0) >= 2) {
78
+ return { conversational: false, reason: "multiple-domain-signals" };
123
79
  }
124
-
125
- // Question pattern with single low-confidence signal → conversational
126
- // Key insight: "explain the card content model" has 1 signal ("card") but
127
- // the leading "explain" means the user wants information, not generation.
128
- if (QUESTION_STARTERS.test(trimmed) && classifyResult.matchedSignals?.length <= 1) {
129
- return { conversational: true, reason: 'question-single-signal' };
80
+ if (QUESTION_STARTERS.test(trimmed) && (cls.matchedSignals?.length ?? 0) <= 1) {
81
+ return { conversational: true, reason: "question-single-signal" };
130
82
  }
131
-
132
- // Greetings even with some confidence → still conversational
133
- if (GREETINGS.test(trimmed) && classifyResult.confidence < 0.2) {
134
- return { conversational: true, reason: 'greeting-low-confidence' };
83
+ if (GREETINGS.test(trimmed) && cls.confidence < 0.2) {
84
+ return { conversational: true, reason: "greeting-low-confidence" };
135
85
  }
136
-
137
- // Default: if there's any domain signal at all, treat as generation
138
- if (classifyResult.confidence > 0) {
139
- return { conversational: false, reason: 'has-domain-signal' };
86
+ if (cls.confidence > 0) {
87
+ return { conversational: false, reason: "has-domain-signal" };
140
88
  }
141
-
142
- // No signals check for question marks or question patterns
143
- if (trimmed.endsWith('?')) {
144
- return { conversational: true, reason: 'ends-with-question-mark' };
89
+ if (trimmed.endsWith("?")) {
90
+ return { conversational: true, reason: "ends-with-question-mark" };
145
91
  }
146
-
147
- // No domain signals at all — this input doesn't describe UI.
148
- // Phrases like "do what you gotta do", "what is preventing you?", or
149
- // motivational text have zero keyword overlap with any UI domain.
150
- // Route to conversational so the system can ask for clarification
151
- // instead of hallucinating a UI from abstract text.
152
- // Legitimate UI intents like "sidebar with navigation" will have
153
- // domain signals (sidebar → layout/navigation) and be caught above.
154
- return { conversational: true, reason: 'no-ui-signals' };
92
+ return { conversational: true, reason: "no-ui-signals" };
155
93
  }
94
+ export {
95
+ isConversational
96
+ };
@@ -1,91 +1,47 @@
1
- /**
2
- * Prompt Analyzer — baseline ingestion stage for the gen-ui pipeline.
3
- *
4
- * Every user intent enters through here, regardless of engine (monolithic,
5
- * zettel) or mode (instant, pro, thinking). The analyzer extracts structured
6
- * signals from the raw prompt — what concepts the user is gesturing at, which
7
- * AdiaUI components are implied, what implicit requirements a good designer
8
- * would add — and produces a "steelmanned" enriched brief that downstream
9
- * stages reason against.
10
- *
11
- * Output schema:
12
- * {
13
- * raw: string // user's original intent, verbatim
14
- * concepts: string[] // 2-5 short concept tags ("commerce", "auth", "data-display")
15
- * entities: string[] // explicit nouns from the prompt ("image", "title", "price")
16
- * impliedComponents: string[] // 4-12 AdiaUI component names ("Card", "Image", "Button")
17
- * styleHints: string[] // visual/style hints ("3D", "minimal", "dense"), [] if none
18
- * steelman: string // 1-2 sentence enriched brief
19
- * analyzed: boolean // true if LLM call succeeded; false if fell back to passthrough
20
- * }
21
- *
22
- * Failure mode: if the LLM call throws or returns unparseable JSON, we
23
- * gracefully degrade to a passthrough analysis (steelman = raw intent, no
24
- * extracted signals). Generation continues; quality may suffer but nothing
25
- * breaks. See diagnosis report 2026-04-19 for why silent failure is the right
26
- * default here (vs. raising) — the analyzer is enrichment, not a gate.
27
- *
28
- * Latency budget: one Haiku call, typically 800-1500ms. This is the new floor
29
- * for every generation regardless of mode.
30
- */
31
-
32
- // Component vocabulary derived from the catalog. Two artifacts:
33
- // - displayList: human-readable list shown to the LLM, with aliases inlined
34
- // ("Swiper (also: Carousel, Slideshow)") so the LLM knows it can use any.
35
- // - allowedSet: canonical AND alias names accepted in impliedComponents,
36
- // because the runtime registry already maps aliases to the right tag.
37
- // Loaded once at module init from the catalog. Changes require server restart.
38
1
  let _vocab = null;
39
2
  async function getVocab() {
40
3
  if (_vocab) return _vocab;
41
4
  let catalogJson = {};
42
5
  try {
43
- const IS_NODE = typeof process !== 'undefined' && process.versions?.node;
6
+ const IS_NODE = typeof process !== "undefined" && process.versions?.node;
44
7
  if (IS_NODE) {
45
- const fs = await import(/* @vite-ignore */ 'node:fs/promises');
46
- const path = await import(/* @vite-ignore */ 'node:path');
47
- const url = await import(/* @vite-ignore */ 'node:url');
48
- // Since §65 (v0.4.7), reads from canonical v0.9 catalog at
49
- // corpus/catalog-a2ui_0_9.json (assembled from yamls). Previously read
50
- // the hand-maintained corpus/patterns/_components.json retired in §65.
51
- // packages/a2ui/retrieval/intent → up 2 → packages/a2ui → corpus/catalog-a2ui_0_9.json
8
+ const fs = await import(
9
+ /* @vite-ignore */
10
+ "node:fs/promises"
11
+ );
12
+ const path = await import(
13
+ /* @vite-ignore */
14
+ "node:path"
15
+ );
16
+ const url = await import(
17
+ /* @vite-ignore */
18
+ "node:url"
19
+ );
52
20
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
53
- const raw = await fs.readFile(path.join(__dirname, '../../corpus/catalog-a2ui_0_9.json'), 'utf8');
21
+ const raw = await fs.readFile(path.join(__dirname, "../../corpus/catalog-a2ui_0_9.json"), "utf8");
54
22
  catalogJson = JSON.parse(raw);
55
23
  } else {
56
- const resp = await fetch(new URL('../../corpus/catalog-a2ui_0_9.json', import.meta.url));
24
+ const resp = await fetch(new URL("../../corpus/catalog-a2ui_0_9.json", import.meta.url));
57
25
  if (resp.ok) catalogJson = await resp.json();
58
26
  }
59
- } catch { /* empty vocab — analyzer will still work, just unconstrained */ }
60
-
61
- // Adapt v0.9 catalog shape: `components: {Name: {x-adiaui: {synonyms: {tags: [...]}}}}`
62
- // → legacy `{Name: {aliases}}` shape this vocab builder expects.
63
- const comps = catalogJson?.components || {};
27
+ } catch {
28
+ }
29
+ const comps = catalogJson?.["components"] ?? {};
64
30
  const displayList = [];
65
- const allowedSet = new Set();
31
+ const allowedSet = /* @__PURE__ */ new Set();
66
32
  for (const [name, def] of Object.entries(comps)) {
67
33
  allowedSet.add(name);
68
- const ext = def?.['x-adiaui'] || {};
69
- const syns = (ext.synonyms && typeof ext.synonyms === 'object') ? ext.synonyms : null;
70
- const aliases = Array.isArray(syns?.tags) ? syns.tags : [];
34
+ const ext = def?.["x-adiaui"] ?? {};
35
+ const syns = ext && typeof ext === "object" ? ext["synonyms"] : null;
36
+ const tags = syns && typeof syns === "object" ? syns["tags"] : null;
37
+ const aliases = Array.isArray(tags) ? tags.filter((t) => typeof t === "string") : [];
71
38
  for (const a of aliases) allowedSet.add(a);
72
- displayList.push(aliases.length ? `${name} (also: ${aliases.join(', ')})` : name);
39
+ displayList.push(aliases.length ? `${name} (also: ${aliases.join(", ")})` : name);
73
40
  }
74
41
  _vocab = { displayList, allowedSet };
75
42
  return _vocab;
76
43
  }
77
-
78
- /**
79
- * Analyze a user prompt — extract concepts, entities, implied components,
80
- * style hints, and produce a steelmanned brief.
81
- *
82
- * @param {object} opts
83
- * @param {string} opts.intent — Raw user intent
84
- * @param {object} [opts.llmAdapter] — LLM adapter (must implement .complete())
85
- * @param {object} [opts.domain] — Domain classification from domain-router (optional context)
86
- * @returns {Promise<object>} Analysis object (see module docstring for schema)
87
- */
88
- export async function analyzePrompt({ intent, llmAdapter, domain }) {
44
+ async function analyzePrompt({ intent, llmAdapter, domain }) {
89
45
  const passthrough = {
90
46
  raw: intent,
91
47
  concepts: [],
@@ -93,23 +49,16 @@ export async function analyzePrompt({ intent, llmAdapter, domain }) {
93
49
  impliedComponents: [],
94
50
  styleHints: [],
95
51
  steelman: intent,
96
- analyzed: false,
52
+ analyzed: false
97
53
  };
98
-
99
54
  if (!intent || !llmAdapter) return passthrough;
100
-
101
55
  const { displayList, allowedSet } = await getVocab();
102
- const componentList = displayList.length
103
- ? displayList.join(', ')
104
- : 'Card, Section, Header, Footer, Column, Row, Grid, Button, Input, Text, Image, Badge, Icon, Avatar, Alert, Modal, Tabs, Accordion';
105
-
106
- const domainHint = domain?.domain && domain.confidence > 0
107
- ? `\nDomain classifier suggests: ${domain.domain} (confidence ${domain.confidence})`
108
- : '';
109
-
56
+ const componentList = displayList.length ? displayList.join(", ") : "Card, Section, Header, Footer, Column, Row, Grid, Button, Input, Text, Image, Badge, Icon, Avatar, Alert, Modal, Tabs, Accordion";
57
+ const domainHint = domain?.["domain"] && (domain?.["confidence"] ?? 0) > 0 ? `
58
+ Domain classifier suggests: ${domain["domain"]} (confidence ${domain["confidence"]})` : "";
110
59
  const systemPrompt = `You are a UI brief enricher for the AdiaUI design system. Given a terse or under-specified user prompt, you extract structured signals and produce an enriched brief that a downstream UI generator can use to produce a polished, complete UI.
111
60
 
112
- Your output is JSON with this exact shape no markdown, no explanation:
61
+ Your output is JSON with this exact shape \u2014 no markdown, no explanation:
113
62
  {
114
63
  "concepts": [string], // 2-5 short concept tags (e.g. "commerce", "authentication", "data-display", "navigation")
115
64
  "entities": [string], // explicit nouns from the prompt (lowercase, singular)
@@ -121,7 +70,7 @@ Your output is JSON with this exact shape — no markdown, no explanation:
121
70
  AdiaUI components you may name in implied_components (use these names exactly): ${componentList}
122
71
 
123
72
  Rules:
124
- - Extract entities verbatim from the prompt do not invent.
73
+ - Extract entities verbatim from the prompt \u2014 do not invent.
125
74
  - implied_components should include both the obvious (Card, Button) AND the ones a thoughtful designer would add (Badge for sale tag on a product, Avatar for a user row, EmptyState for empty data).
126
75
  - The steelman expands the prompt into what the user MEANT, not what they SAID. Add the implicit requirements: typical sub-elements, sensible defaults, a header, a footer, common variants. Do NOT add scope the user wouldn't want (e.g. don't turn "login form" into "login form with social auth" unless that's a near-universal expectation).
127
76
  - Keep steelman under 250 characters.
@@ -135,86 +84,79 @@ Input: "settings page with toggles for notifications, dark mode, and privacy"
135
84
  Output: {"concepts":["settings","preferences"],"entities":["notifications","dark mode","privacy","toggles"],"implied_components":["Card","Header","Section","Toggle","Text","Divider"],"style_hints":[],"steelman":"Settings page with three toggle rows for notifications, dark mode, and privacy. Each row has a label, description, and toggle. Grouped under a header card."}
136
85
 
137
86
  Input: "make it 3D"
138
- Output: {"concepts":["style-modifier"],"entities":[],"implied_components":[],"style_hints":["3D","depth","shadow"],"steelman":"Modify the existing canvas to feel three-dimensional add elevation, layered shadows, and depth-suggesting transforms. Apply to existing components without changing structure."}`;
139
-
87
+ Output: {"concepts":["style-modifier"],"entities":[],"implied_components":[],"style_hints":["3D","depth","shadow"],"steelman":"Modify the existing canvas to feel three-dimensional \u2014 add elevation, layered shadows, and depth-suggesting transforms. Apply to existing components without changing structure."}`;
140
88
  const userMsg = `Prompt: "${intent}"${domainHint}`;
141
-
142
89
  let response;
143
90
  try {
144
91
  response = await llmAdapter.complete({
145
- messages: [{ role: 'user', content: userMsg }],
146
- systemPrompt,
92
+ messages: [{ role: "user", content: userMsg }],
93
+ systemPrompt
147
94
  });
148
95
  } catch {
149
96
  return passthrough;
150
97
  }
151
-
152
98
  const text = response?.content;
153
99
  if (!text) return passthrough;
154
-
155
- // Truncation: if the analyzer LLM hit max_tokens we'd rather use the raw
156
- // prompt than a half-parsed analysis that misleads downstream stages.
157
- if (response.stopReason && response.stopReason !== 'end' && response.stopReason !== 'end_turn' && response.stopReason !== 'stop') {
100
+ if (response.stopReason && response.stopReason !== "end" && response.stopReason !== "end_turn" && response.stopReason !== "stop") {
158
101
  return passthrough;
159
102
  }
160
-
161
- // Try direct parse, then code-fence extraction, then outermost JSON.
162
103
  let parsed = null;
163
- try { parsed = JSON.parse(text.trim()); } catch { /* fall through */ }
104
+ try {
105
+ parsed = JSON.parse(text.trim());
106
+ } catch {
107
+ }
164
108
  if (!parsed) {
165
109
  const fence = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
166
- if (fence) try { parsed = JSON.parse(fence[1].trim()); } catch { /* fall through */ }
110
+ if (fence) try {
111
+ parsed = JSON.parse((fence[1] ?? "").trim());
112
+ } catch {
113
+ }
167
114
  }
168
115
  if (!parsed) {
169
- const start = text.indexOf('{');
170
- const end = text.lastIndexOf('}');
116
+ const start = text.indexOf("{");
117
+ const end = text.lastIndexOf("}");
171
118
  if (start >= 0 && end > start) {
172
- try { parsed = JSON.parse(text.slice(start, end + 1)); } catch { /* fall through */ }
119
+ try {
120
+ parsed = JSON.parse(text.slice(start, end + 1));
121
+ } catch {
122
+ }
173
123
  }
174
124
  }
175
- if (!parsed || typeof parsed !== 'object') return passthrough;
176
-
177
- // Constrain impliedComponents to the allowed vocab (canonical names AND
178
- // their aliases — runtime registry maps aliases to the right tag, and the
179
- // LLM was told it can use either, so both are accepted here).
180
- const impliedComponents = Array.isArray(parsed.implied_components)
181
- ? parsed.implied_components.filter(c => typeof c === 'string' && (allowedSet.size === 0 || allowedSet.has(c)))
182
- : [];
183
-
125
+ if (!parsed || typeof parsed !== "object") return passthrough;
126
+ const p = parsed;
127
+ const impliedComponents = Array.isArray(p["implied_components"]) ? p["implied_components"].filter((c) => typeof c === "string" && (allowedSet.size === 0 || allowedSet.has(c))) : [];
184
128
  return {
185
129
  raw: intent,
186
- concepts: asStringArray(parsed.concepts).slice(0, 8),
187
- entities: asStringArray(parsed.entities).slice(0, 16),
130
+ concepts: asStringArray(p["concepts"]).slice(0, 8),
131
+ entities: asStringArray(p["entities"]).slice(0, 16),
188
132
  impliedComponents: impliedComponents.slice(0, 16),
189
- styleHints: asStringArray(parsed.style_hints).slice(0, 8),
190
- steelman: (typeof parsed.steelman === 'string' && parsed.steelman.trim()) || intent,
191
- analyzed: true,
133
+ styleHints: asStringArray(p["style_hints"]).slice(0, 8),
134
+ steelman: typeof p["steelman"] === "string" && p["steelman"].trim() || intent,
135
+ analyzed: true
192
136
  };
193
137
  }
194
-
195
138
  function asStringArray(v) {
196
139
  if (!Array.isArray(v)) return [];
197
- return v.filter(x => typeof x === 'string' && x.trim().length > 0).map(x => x.trim());
140
+ return v.filter((x) => typeof x === "string" && x.trim().length > 0).map((x) => x.trim());
198
141
  }
199
-
200
- /**
201
- * Format an analysis object for inclusion in a downstream system prompt.
202
- * Keeps the shape compact so it doesn't blow the context budget.
203
- */
204
- export function formatAnalysisForPrompt(analysis) {
205
- if (!analysis || !analysis.analyzed) return '';
142
+ function formatAnalysisForPrompt(analysis) {
143
+ if (!analysis || !analysis.analyzed) return "";
206
144
  const parts = [];
207
145
  if (analysis.steelman && analysis.steelman !== analysis.raw) {
208
146
  parts.push(`ENRICHED BRIEF: ${analysis.steelman}`);
209
147
  }
210
148
  if (analysis.impliedComponents?.length) {
211
- parts.push(`EXPECTED COMPONENTS: ${analysis.impliedComponents.join(', ')}`);
149
+ parts.push(`EXPECTED COMPONENTS: ${analysis.impliedComponents.join(", ")}`);
212
150
  }
213
151
  if (analysis.concepts?.length) {
214
- parts.push(`CONCEPTS: ${analysis.concepts.join(', ')}`);
152
+ parts.push(`CONCEPTS: ${analysis.concepts.join(", ")}`);
215
153
  }
216
154
  if (analysis.styleHints?.length) {
217
- parts.push(`STYLE HINTS: ${analysis.styleHints.join(', ')}`);
155
+ parts.push(`STYLE HINTS: ${analysis.styleHints.join(", ")}`);
218
156
  }
219
- return parts.join('\n');
157
+ return parts.join("\n");
220
158
  }
159
+ export {
160
+ analyzePrompt,
161
+ formatAnalysisForPrompt
162
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/a2ui-retrieval",
3
- "version": "0.6.4",
3
+ "version": "0.6.7",
4
4
  "description": "AdiaUI A2UI retrieval layer \u2014 catalog lookup, intent classification, domain routing, pattern + anti-pattern matching, clarity + context assembly. Consumed by the compose engine and any A2UI-protocol tooling that needs to reason about user intent against the catalog.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -63,7 +63,9 @@
63
63
  "feedback/",
64
64
  "authoring/",
65
65
  "README.md",
66
- "CHANGELOG.md"
66
+ "CHANGELOG.md",
67
+ "!**/*.ts",
68
+ "**/*.d.ts"
67
69
  ],
68
70
  "license": "MIT",
69
71
  "publishConfig": {