@adia-ai/a2ui-retrieval 0.0.1 → 0.2.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 (30) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +43 -12
  3. package/anti-patterns.js +1 -1
  4. package/authoring/index.js +13 -0
  5. package/{pattern-promotion.js → authoring/pattern-promotion.js} +1 -1
  6. package/{synthetic-data.js → authoring/synthetic-data.js} +6 -6
  7. package/catalog.js +52 -47
  8. package/concept-mapper.js +1 -1
  9. package/embedding/chunk-embedding-retriever.js +123 -0
  10. package/{embedding-retriever.js → embedding/embedding-retriever.js} +15 -3
  11. package/embedding/index.js +7 -0
  12. package/{dialog-recorder.js → feedback/dialog-recorder.js} +2 -2
  13. package/{feedback-analyzer.js → feedback/feedback-analyzer.js} +3 -2
  14. package/{feedback-store.js → feedback/feedback-store.js} +2 -1
  15. package/{gap-registry.js → feedback/gap-registry.js} +2 -1
  16. package/feedback/index.js +12 -0
  17. package/index.js +5 -5
  18. package/{clarity.js → intent/clarity.js} +2 -2
  19. package/{decomposer.js → intent/decomposer.js} +1 -1
  20. package/intent/index.js +14 -0
  21. package/{prompt-analyzer.js → intent/prompt-analyzer.js} +3 -2
  22. package/package.json +19 -4
  23. package/pattern-library.js +1 -1
  24. package/wiring-catalog.js +6 -6
  25. /package/{web-research.js → authoring/web-research.js} +0 -0
  26. /package/{embedding-provider.js → embedding/embedding-provider.js} +0 -0
  27. /package/{feedback.js → feedback/feedback.js} +0 -0
  28. /package/{intent-alignment.js → intent/intent-alignment.js} +0 -0
  29. /package/{intent-categorizer.js → intent/intent-categorizer.js} +0 -0
  30. /package/{intent-gate.js → intent/intent-gate.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -9,6 +9,85 @@ _Nothing yet._
9
9
 
10
10
  ---
11
11
 
12
+ ## [0.2.1] - 2026-05-02
13
+
14
+ **Lockstep cut.** All 8 published `@adia-ai/*` packages bump 0.2.0 → 0.2.1 per [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
15
+
16
+ ### Changed
17
+
18
+ - `version`: `0.2.0` → `0.2.1`.
19
+ - `dependencies["@adia-ai/a2ui-corpus"]`: `^0.2.0` (covers `0.2.1`).
20
+ - `dependencies["@adia-ai/a2ui-utils"]`: `^0.2.0` (covers `0.2.1`).
21
+ - `embedding/chunk-embedding-retriever.js` + `embedding/embedding-retriever.js` — defensive resolution path: when the `chunk-embeddings.json` / `pattern-embeddings.json` indexes aren't reachable (e.g. inside a `node_modules/@adia-ai/a2ui-corpus/` install layout where the `files:` array now excludes them), the retriever falls back to keyword-only retrieval gracefully instead of throwing on `ENOENT`. Aligns with the `@adia-ai/a2ui-corpus` 0.2.0 tarball-trim decision.
22
+
23
+ ### No source-behavior changes
24
+
25
+ Public API of `a2ui-retrieval` is byte-identical to `0.2.0`. The above embedding-retriever updates only affect the failure path (`ENOENT` graceful fallback) and don't change the contract for callers passing valid index files. The cut bumps version + tracks dep ranges + softens the embedding-index-missing failure mode.
26
+
27
+ ---
28
+
29
+ ## [0.2.0] - 2026-05-02
30
+
31
+ **Lockstep cut + boundary reorganization.** All 8 published
32
+ `@adia-ai/*` packages now share one version, governed by
33
+ [`docs/specs/package-architecture.md` § 15 (Versioning Policy)](../../../docs/specs/package-architecture.md#15-versioning-policy).
34
+ This release also lands the boundary cleanup from T4 of the
35
+ [`docs/plans/packages-architecture-fixes-2026-05-02.md`](../../../.brain/archive/2026-Q2/PLAN-packages-architecture-fixes-2026-05-02.md)
36
+ plan.
37
+
38
+ ### Changed
39
+
40
+ - `version`: `0.0.1` → `0.2.0`.
41
+ - `dependencies["@adia-ai/a2ui-utils"]`: `^0.0.2` → `^0.2.0`.
42
+
43
+ - **Subdir reorganization** — 26 .js files at the top level
44
+ reorganized into 4 named concerns. Lookup-shaped files stay at the
45
+ top level; the rest move into subdirs. Top-level external imports
46
+ (`@adia-ai/a2ui-retrieval`) continue to work unchanged via the
47
+ re-exporting top-level barrel.
48
+
49
+ | New path | Members |
50
+ |---|---|
51
+ | `intent/` | `intent-alignment`, `intent-categorizer`, `intent-gate`, `prompt-analyzer`, `decomposer`, `clarity` |
52
+ | `embedding/` | `embedding-provider`, `embedding-retriever`, `chunk-embedding-retriever` |
53
+ | `feedback/` | `feedback`, `feedback-store`, `feedback-analyzer`, `dialog-recorder`, `gap-registry` |
54
+ | `authoring/` | `synthetic-data`, `web-research`, `pattern-promotion` |
55
+ | top level (kept) | `catalog`, `pattern-library`, `concept-mapper`, `domain-router`, `wiring-catalog`, `anti-patterns`, `context-assembler`, `component-entry`, `index` |
56
+
57
+ - New subpath exports: `./intent`, `./embedding`, `./feedback`,
58
+ `./authoring` each expose a barrel `index.js`. Wildcard subpaths
59
+ (`./intent/*`, `./embedding/*`, etc.) expose individual files for
60
+ fine-grained imports. The old wildcard `./*` is retained for
61
+ backward compatibility — direct imports of moved files still work
62
+ (e.g. `import { feedbackStore } from '@adia-ai/a2ui-retrieval/feedback/feedback-store'`)
63
+ but consumers should prefer the barrel form.
64
+
65
+ ### Fixed
66
+
67
+ - 6 `__dirname`-based path computations (in `dialog-recorder`,
68
+ `feedback-store`, `feedback-analyzer`, `gap-registry`, the two
69
+ embedding retrievers, and `prompt-analyzer`) gained one more `..`
70
+ segment to climb out of the new subdir to the corpus directory.
71
+ Without this, the moved files would write to a non-existent path
72
+ (`packages/a2ui/a2ui/corpus/...`). Caught during the post-move
73
+ smoke run; same class as the
74
+ `feedback_git_mv_audit_imports` memory entry.
75
+
76
+ ### Migration
77
+
78
+ ```diff
79
+ - import { feedbackStore } from '@adia-ai/a2ui-retrieval'; // still works (top-level barrel)
80
+ - import { feedbackStore } from '@adia-ai/a2ui-retrieval/feedback-store'; // BREAKS — file moved
81
+ + import { feedbackStore } from '@adia-ai/a2ui-retrieval/feedback'; // new barrel
82
+ + import { feedbackStore } from '@adia-ai/a2ui-retrieval/feedback/feedback-store'; // explicit
83
+ ```
84
+
85
+ The top-level `index.js` still re-exports the previously-public surface
86
+ (`assessClarity`, `isConversational`, `decomposeIntent`,
87
+ `checkIntentAlignment`, etc.) — those imports do not need updating.
88
+
89
+ ---
90
+
12
91
  ## [0.0.1] - 2026-04-24
13
92
 
14
93
  First public release. Extracted from
package/README.md CHANGED
@@ -14,18 +14,49 @@ npm install @adia-ai/a2ui-retrieval
14
14
 
15
15
  ## What's here
16
16
 
17
- - **Pattern library** indexed search over the catalog's patterns and
18
- fragments; domain-aware ranking.
19
- - **Intent gate** classifies a user intent as UI-generating vs
20
- conversational based on domain-keyword signal.
21
- - **Domain router** — maps free-form text to catalog domains
22
- (`forms`, `data`, `agent`, `navigation`, etc.).
23
- - **Anti-pattern detector** — flags compositions that violate invariants
24
- (invented components, bad attribute combos).
25
- - **Clarity analyzer** scores how specific an intent is; drives
26
- clarification prompts when the signal is too weak to generate.
27
- - **Context assembly** stitches retrieval results into the prompt
28
- context consumed by the LLM adapters.
17
+ The package groups its 26 source files into 4 sub-concerns plus a
18
+ top-level lookup surface. Importers can pull the top-level barrel
19
+ (`@adia-ai/a2ui-retrieval`) for the previously-public surface, the
20
+ per-subdir barrels (`@adia-ai/a2ui-retrieval/intent` etc.), or
21
+ individual files for fine-grained imports.
22
+
23
+ ```
24
+ a2ui-retrieval/
25
+ ├── catalog.js top-levelcatalog lookup (loaded from corpus)
26
+ ├── pattern-library.js top-level keyword + semantic pattern search
27
+ ├── concept-mapper.js top-levelconcept catalog-entry mapping
28
+ ├── domain-router.js top-level intent catalog-domain classifier
29
+ ├── wiring-catalog.js top-level — controller / handler reference
30
+ ├── anti-patterns.js top-level — known-bad pattern detector
31
+ ├── context-assembler.js top-level — retrieval results → LLM context
32
+ ├── component-entry.js top-level — catalog row serializer
33
+ ├── index.js top-level — barrel
34
+
35
+ ├── intent/ intent classification + decomposition
36
+ │ ├── intent-alignment.js
37
+ │ ├── intent-categorizer.js
38
+ │ ├── intent-gate.js UI-generating vs conversational
39
+ │ ├── prompt-analyzer.js
40
+ │ ├── decomposer.js compound intents → subtasks
41
+ │ └── clarity.js clarity scoring
42
+
43
+ ├── embedding/ dense-vector retrieval
44
+ │ ├── embedding-provider.js adapter (Voyage / OpenAI / fallback)
45
+ │ ├── embedding-retriever.js top-N pattern retrieval
46
+ │ └── chunk-embedding-retriever.js chunk-level retrieval
47
+
48
+ ├── feedback/ user-feedback ingestion loop
49
+ │ ├── feedback.js
50
+ │ ├── feedback-store.js JSONL store under .brain/feedback/
51
+ │ ├── feedback-analyzer.js
52
+ │ ├── dialog-recorder.js LLM round-trip capture (gated by env)
53
+ │ └── gap-registry.js tracked gaps in catalog coverage
54
+
55
+ └── authoring/ corpus-authoring helpers (upstream of retrieval)
56
+ ├── synthetic-data.js
57
+ ├── web-research.js
58
+ └── pattern-promotion.js
59
+ ```
29
60
 
30
61
  ## Runtime
31
62
 
package/anti-patterns.js CHANGED
@@ -76,7 +76,7 @@ const antiPatterns = [
76
76
  'table-ui', 'chart-ui', 'embed-ui', 'stack-ui', 'block-ui', 'code-ui',
77
77
  'textarea-ui', 'radio-ui', 'radio-ui', 'tag-ui', 'accordion-ui',
78
78
  'modal-ui', 'alert-ui', 'tooltip-ui', 'menu-ui', 'breadcrumb-ui',
79
- 'nav-n', 'pagination-ui', 'avatar-group-ui', 'segmented-ui',
79
+ 'nav-ui', 'pagination-ui', 'avatar-group-ui', 'segmented-ui',
80
80
  'segment-ui', 'command-ui', 'command-item-ui', 'command-group-ui',
81
81
  'calendar-picker-ui', 'color-picker-ui', 'kbd-ui', 'toolbar-ui',
82
82
  'otp-input-ui',
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @adia-ai/a2ui-retrieval/authoring — corpus authoring helpers.
3
+ *
4
+ * Scripts and helpers that produce or extend retrieval inputs:
5
+ * synthetic-data generation, web research, pattern promotion. These are
6
+ * upstream of retrieval (they create what retrieval reads) — kept inside
7
+ * the retrieval package because they share the catalog + pattern-library
8
+ * surfaces, but logically a separate concern from lookup.
9
+ */
10
+
11
+ export * from './synthetic-data.js';
12
+ export * from './web-research.js';
13
+ export * from './pattern-promotion.js';
@@ -14,7 +14,7 @@
14
14
  * the pattern library.
15
15
  */
16
16
 
17
- import { getAllPatterns } from './pattern-library.js';
17
+ import { getAllPatterns } from '../pattern-library.js';
18
18
 
19
19
  /**
20
20
  * Evaluate whether a generation should be promoted to a named pattern.
@@ -9,10 +9,10 @@
9
9
  * Spec: A003 section 6 — Synthetic Data Generation
10
10
  */
11
11
 
12
- import { getCatalog } from './catalog.js';
13
- import { getAntiPatterns } from './anti-patterns.js';
14
- import { getAllPatterns } from './pattern-library.js';
15
- import { serializeEntry } from './component-entry.js';
12
+ import { getCatalog } from '../catalog.js';
13
+ import { getAntiPatterns } from '../anti-patterns.js';
14
+ import { getAllPatterns } from '../pattern-library.js';
15
+ import { serializeEntry } from '../component-entry.js';
16
16
 
17
17
  // ── Coverage targets (spec section 6.2) ──
18
18
 
@@ -409,7 +409,7 @@ export class SyntheticDataGenerator {
409
409
 
410
410
  // Map A2UI types to their AdiaUI tag names for anti-pattern checking
411
411
  const tag = type.toLowerCase().replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
412
- const tagName = tag.endsWith('-n') ? tag : `${tag}-n`;
412
+ const tagName = tag.endsWith('-ui') ? tag : `${tag}-ui`;
413
413
 
414
414
  // Build a minimal HTML representation
415
415
  const attrs = [];
@@ -425,7 +425,7 @@ export class SyntheticDataGenerator {
425
425
  .filter(Boolean)
426
426
  .map(c => {
427
427
  const ct = c.component?.toLowerCase().replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() || '';
428
- return ct.endsWith('-n') ? ct : ct;
428
+ return ct.endsWith('-ui') ? ct : ct;
429
429
  });
430
430
  lines.push(`<${tagName}${attrStr}>${childTypes.map(ct => `<${ct}>`).join('')}</${tagName}>`);
431
431
  } else {
package/catalog.js CHANGED
@@ -83,58 +83,63 @@ const categoryMap = {
83
83
  'section': 'card-child', 'header': 'card-child', 'footer': 'card-child',
84
84
  'stream-ui': 'agent', 'table-ui': 'agent', 'chart-ui': 'agent',
85
85
  'embed-ui': 'agent', 'noodles-ui': 'agent',
86
- 'breadcrumb-ui': 'navigation', 'nav-n': 'navigation', 'pagination-ui': 'navigation',
86
+ 'breadcrumb-ui': 'navigation', 'nav-ui': 'navigation', 'pagination-ui': 'navigation',
87
87
  'segmented-ui': 'navigation', 'segment-ui': 'navigation', 'router-ui': 'navigation',
88
88
  'toggle-group-ui': 'navigation', 'toggle-option-ui': 'navigation',
89
89
  };
90
90
 
91
91
  // ── Trait registry ──
92
- const traitRegistry = [
93
- { name: 'pressable', category: 'input-interaction', description: 'Normalizes click/tap/keyboard into a single "press" event' },
94
- { name: 'focusable', category: 'input-interaction', description: 'Keyboard-only focus ring, ignores pointer focus' },
95
- { name: 'hoverable', category: 'input-interaction', description: 'Pointer hover enter/leave state tracking' },
96
- { name: 'active-state', category: 'input-interaction', description: 'Tracks pointer down / active interaction state' },
97
- { name: 'disabled-state', category: 'input-interaction', description: 'Enforces disabled behavior and interaction blocking' },
98
- { name: 'keyboard-nav', category: 'keyboard-navigation', description: 'Arrow keys, Enter, Escape — semantic navigation events' },
99
- { name: 'roving-tabindex', category: 'keyboard-navigation', description: 'Focus management within composite widgets' },
100
- { name: 'typeahead', category: 'keyboard-navigation', description: 'Incremental search within a collection' },
101
- { name: 'hotkey', category: 'keyboard-navigation', description: 'Global or scoped keyboard shortcuts' },
102
- { name: 'focus-trap', category: 'keyboard-navigation', description: 'Traps Tab/Shift+Tab within a container' },
103
- { name: 'form-associated', category: 'forms-data', description: 'ElementInternals integration for form participation' },
104
- { name: 'validation', category: 'forms-data', description: 'Validation rules: required, minlength, pattern, email' },
105
- { name: 'value-sync', category: 'forms-data', description: 'Syncs value between attribute/property/controller' },
106
- { name: 'dirty-state', category: 'forms-data', description: 'Tracks modified vs initial value' },
107
- { name: 'resettable', category: 'forms-data', description: 'Responds to form reset events' },
108
- { name: 'resize-observer-trait', category: 'layout-measurement', description: 'Reacts to element size changes' },
109
- { name: 'intersection-observer-trait', category: 'layout-measurement', description: 'Visibility detection (viewport)' },
110
- { name: 'anchor-positioning', category: 'layout-measurement', description: 'Positions relative to an anchor element' },
111
- { name: 'portal', category: 'layout-measurement', description: 'Renders content in a different DOM root' },
112
- { name: 'scroll-lock', category: 'layout-measurement', description: 'Disables background scrolling' },
113
- { name: 'draggable', category: 'motion-positioning', description: 'Pointer drag to reposition' },
114
- { name: 'tossable', category: 'motion-positioning', description: 'Flick with momentum + viewport bounce' },
115
- { name: 'resizable', category: 'motion-positioning', description: 'Drag edges/corners to resize' },
116
- { name: 'inertia-drag', category: 'motion-positioning', description: 'Momentum-based dragging, smooth deceleration' },
117
- { name: 'snap-to-grid', category: 'motion-positioning', description: 'Snaps position to configurable grid' },
118
- { name: 'drag-ghost', category: 'motion-positioning', description: 'Ghost clone at origin during drag' },
119
- { name: 'ripple', category: 'animation-feedback', description: 'Material-style press ripple effect' },
120
- { name: 'spring-animate', category: 'animation-feedback', description: 'Spring-based motion transitions' },
121
- { name: 'fade-presence', category: 'animation-feedback', description: 'Enter/exit fade with lifecycle' },
122
- { name: 'scale-press', category: 'animation-feedback', description: 'Subtle scale transform on press' },
123
- { name: 'tilt-hover', category: 'animation-feedback', description: 'Tilt based on pointer position' },
124
- { name: 'glow-focus', category: 'visual-dynamics', description: 'Animated pulsing glow on focus' },
125
- { name: 'gradient-shift', category: 'visual-dynamics', description: 'Animated rainbow gradient backgrounds' },
126
- { name: 'parallax', category: 'visual-dynamics', description: 'Layered motion relative to pointer' },
127
- { name: 'shimmer-loading', category: 'visual-dynamics', description: 'Skeleton shimmer effect' },
128
- { name: 'noise-texture', category: 'visual-dynamics', description: 'Procedural grain overlay' },
129
- { name: 'magnetic-hover', category: 'interaction-delight', description: 'Element subtly follows cursor' },
130
- { name: 'confetti', category: 'interaction-delight', description: 'Radial particle burst on press' },
131
- { name: 'confetti-burst', category: 'interaction-delight', description: 'Upward fountain particle burst' },
132
- { name: 'sound-feedback', category: 'audio-haptics-sensory', description: 'Synthesized tones via Web Audio API' },
133
- { name: 'haptic-feedback', category: 'audio-haptics-sensory', description: 'Vibration API feedback' },
134
- { name: 'typewriter', category: 'audio-haptics-sensory', description: 'Animated text reveal character by character' },
135
- { name: 'count-up', category: 'audio-haptics-sensory', description: 'Animated numeric transitions' },
136
- { name: 'attention-pulse', category: 'audio-haptics-sensory', description: 'Periodic pulse to draw attention' },
137
- ];
92
+ //
93
+ // Loaded from packages/web-components/traits/_catalog.json generated
94
+ // by `node scripts/build/traits-catalog.mjs` from the live defineTrait()
95
+ // metadata. Hand-edits here will be overwritten. Treat the trait file
96
+ // itself as the single source of truth.
97
+ let traitRegistry = [];
98
+
99
+ async function loadTraitRegistry() {
100
+ if (typeof process !== 'undefined' && process.versions?.node) {
101
+ try {
102
+ const { readFile } = await import(/* @vite-ignore */ 'node:fs/promises');
103
+ const { fileURLToPath } = await import(/* @vite-ignore */ 'node:url');
104
+ const { dirname, join } = await import(/* @vite-ignore */ 'node:path');
105
+ const path = join(dirname(fileURLToPath(import.meta.url)), '../../web-components/traits/_catalog.json');
106
+ const raw = await readFile(path, 'utf8');
107
+ const data = JSON.parse(raw);
108
+ traitRegistry = data.traits.map(t => ({
109
+ name: t.name,
110
+ category: t.category,
111
+ description: t.description,
112
+ attributes: [...t.attributes],
113
+ events: [...t.events],
114
+ config: [...t.config],
115
+ }));
116
+ } catch (err) {
117
+ console.warn('[a2ui-corpus] could not load trait catalog:', err.message);
118
+ }
119
+ } else {
120
+ // Browser branch — fetch via HTTP, not `import('… .json', { with: { type: 'json' } })`.
121
+ // Vite's dev server transforms JSON imports into JS modules and serves them
122
+ // with `Content-Type: text/javascript`; Chrome's strict MIME check rejects
123
+ // that when the spec-correct `with: { type: 'json' }` attribute is present.
124
+ // (Site pages already use fetch() for the same file — see
125
+ // site/pages/traits/_api-table.js + site/pages/traits/catalog/index.setup.js.)
126
+ try {
127
+ const res = await fetch('/packages/web-components/traits/_catalog.json');
128
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
129
+ const data = await res.json();
130
+ traitRegistry = data.traits.map(t => ({
131
+ name: t.name,
132
+ category: t.category,
133
+ description: t.description,
134
+ attributes: [...t.attributes],
135
+ events: [...t.events],
136
+ config: [...t.config],
137
+ }));
138
+ } catch { /* trait catalog not present in this build context */ }
139
+ }
140
+ }
141
+
142
+ await loadTraitRegistry();
138
143
 
139
144
  // ── Alias tracking ──
140
145
  const aliases = new Set([
package/concept-mapper.js CHANGED
@@ -20,7 +20,7 @@
20
20
  */
21
21
 
22
22
  import { searchBlocks } from '../engine/reference.js';
23
- import { scoreAll as embeddingScoreAll, available as embeddingAvailable } from './embedding-retriever.js';
23
+ import { scoreAll as embeddingScoreAll, available as embeddingAvailable } from './embedding/embedding-retriever.js';
24
24
 
25
25
  /** Weights for the combined score. Tuned to keep lexical authoritative
26
26
  * but let strong conceptual+structural+semantic signals override marginal
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Chunk embedding retriever — loads the build-time chunk embedding index
3
+ * and scores a query against every chunk via cosine similarity.
4
+ *
5
+ * Sibling to `embedding-retriever.js` (pattern embeddings); same provider
6
+ * conventions, same graceful-degradation contract.
7
+ *
8
+ * Index: packages/a2ui/corpus/chunk-embeddings.json (built by
9
+ * scripts/build/embeddings-chunks.mjs). When missing or empty, the
10
+ * retriever is effectively a no-op — callers see an empty score map and
11
+ * should fall back to keyword-only ranking.
12
+ *
13
+ * Used by chunk-library.searchChunks() to blend semantic + keyword scores.
14
+ */
15
+
16
+ import { detectProvider, cosine, voyage, openai } from './embedding-provider.js';
17
+
18
+ const IS_NODE = typeof process !== 'undefined' && !!process.versions?.node;
19
+
20
+ let _index = null;
21
+ let _indexByName = null; // Map<chunk-name, Float32Array>
22
+ let _loadPromise = null;
23
+ let _embedFn = null;
24
+ let _available = null;
25
+
26
+ async function _loadIndex() {
27
+ if (_index !== null) return _index;
28
+ if (_loadPromise) return _loadPromise;
29
+ _loadPromise = (async () => {
30
+ try {
31
+ if (IS_NODE) {
32
+ const fs = await import(/* @vite-ignore */ 'node:fs/promises');
33
+ const path = await import(/* @vite-ignore */ 'node:path');
34
+ const url = await import(/* @vite-ignore */ 'node:url');
35
+ // Try the package-import path first (works under node_modules
36
+ // install layout — `@adia-ai/a2ui-corpus` exports
37
+ // `./chunk-embeddings`). Fall back to the relative source-tree
38
+ // path so a source-checkout monorepo without symlinked deps
39
+ // still resolves.
40
+ let p = null;
41
+ try {
42
+ const { createRequire } = await import(/* @vite-ignore */ 'node:module');
43
+ const require = createRequire(import.meta.url);
44
+ p = require.resolve('@adia-ai/a2ui-corpus/chunk-embeddings');
45
+ } catch {
46
+ const here = path.dirname(url.fileURLToPath(import.meta.url));
47
+ p = path.resolve(here, '../../corpus/chunk-embeddings.json');
48
+ }
49
+ const raw = await fs.readFile(p, 'utf8');
50
+ _index = JSON.parse(raw);
51
+ } else {
52
+ const url = new URL('../../corpus/chunk-embeddings.json', import.meta.url);
53
+ const res = await fetch(url).catch(() => null);
54
+ _index = res?.ok ? await res.json().catch(() => null) : null;
55
+ }
56
+ } catch {
57
+ _index = null;
58
+ }
59
+ if (_index?.chunks?.length) {
60
+ _indexByName = new Map();
61
+ for (const c of _index.chunks) {
62
+ if (c?.name && Array.isArray(c.vector)) {
63
+ _indexByName.set(c.name, Float32Array.from(c.vector));
64
+ }
65
+ }
66
+ }
67
+ return _index;
68
+ })();
69
+ return _loadPromise;
70
+ }
71
+
72
+ function _resolveEmbed(providerName, model) {
73
+ if (providerName === 'voyage') return voyage({ model });
74
+ if (providerName === 'openai') return openai({ model });
75
+ const auto = detectProvider();
76
+ return auto?.embed || null;
77
+ }
78
+
79
+ export async function available() {
80
+ if (_available !== null) return _available;
81
+ const idx = await _loadIndex();
82
+ if (!idx || !idx.chunks?.length) {
83
+ _available = false;
84
+ return false;
85
+ }
86
+ _embedFn = _resolveEmbed(idx.provider, idx.model);
87
+ _available = !!_embedFn;
88
+ return _available;
89
+ }
90
+
91
+ /**
92
+ * Embed a query and return a Map<chunk-name → cosine-score>.
93
+ * Returns an empty Map when unavailable (no index or no API key).
94
+ */
95
+ export async function scoreAll(query) {
96
+ if (!query || typeof query !== 'string') return new Map();
97
+ if (!(await available())) return new Map();
98
+
99
+ let qVec;
100
+ try {
101
+ const [v] = await _embedFn([query]);
102
+ qVec = v;
103
+ } catch (e) {
104
+ if (typeof console !== 'undefined') console.warn('[chunk-embedding-retriever]', e.message);
105
+ return new Map();
106
+ }
107
+
108
+ const out = new Map();
109
+ for (const [name, vec] of _indexByName) {
110
+ out.set(name, cosine(qVec, vec));
111
+ }
112
+ return out;
113
+ }
114
+
115
+ export async function size() {
116
+ const idx = await _loadIndex();
117
+ return idx?.chunks?.length || 0;
118
+ }
119
+
120
+ export async function providerInfo() {
121
+ const idx = await _loadIndex();
122
+ return idx ? { provider: idx.provider, model: idx.model, dims: idx.dims } : null;
123
+ }
@@ -32,12 +32,24 @@ async function _loadIndex() {
32
32
  const fs = await import(/* @vite-ignore */ 'node:fs/promises');
33
33
  const path = await import(/* @vite-ignore */ 'node:path');
34
34
  const url = await import(/* @vite-ignore */ 'node:url');
35
- const here = path.dirname(url.fileURLToPath(import.meta.url));
36
- const p = path.resolve(here, '../corpus/pattern-embeddings.json');
35
+ // Try the package-import path first (works under node_modules
36
+ // install layout `@adia-ai/a2ui-corpus` exports
37
+ // `./pattern-embeddings`). Fall back to the relative source-tree
38
+ // path so a source-checkout monorepo without symlinked deps
39
+ // still resolves.
40
+ let p = null;
41
+ try {
42
+ const { createRequire } = await import(/* @vite-ignore */ 'node:module');
43
+ const require = createRequire(import.meta.url);
44
+ p = require.resolve('@adia-ai/a2ui-corpus/pattern-embeddings');
45
+ } catch {
46
+ const here = path.dirname(url.fileURLToPath(import.meta.url));
47
+ p = path.resolve(here, '../../corpus/pattern-embeddings.json');
48
+ }
37
49
  const raw = await fs.readFile(p, 'utf8');
38
50
  _index = JSON.parse(raw);
39
51
  } else {
40
- const url = new URL('../corpus/pattern-embeddings.json', import.meta.url);
52
+ const url = new URL('../../corpus/pattern-embeddings.json', import.meta.url);
41
53
  const res = await fetch(url).catch(() => null);
42
54
  _index = res?.ok ? await res.json().catch(() => null) : null;
43
55
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @adia-ai/a2ui-retrieval/embedding — embedding-provider + retrievers surface.
3
+ */
4
+
5
+ export * from './embedding-provider.js';
6
+ export * from './embedding-retriever.js';
7
+ export * from './chunk-embedding-retriever.js';
@@ -36,9 +36,9 @@ async function _ensureModules() {
36
36
  _fs = await import(/* @vite-ignore */ 'node:fs/promises');
37
37
  _path = await import(/* @vite-ignore */ 'node:path');
38
38
  _url = await import(/* @vite-ignore */ 'node:url');
39
- // logs/ lives at the repo root: packages/a2ui/retrieval → up 3 → repo root
39
+ // logs/ lives at the repo root: packages/a2ui/retrieval/feedback → up 4 → repo root
40
40
  const __dirname = _path.dirname(_url.fileURLToPath(import.meta.url));
41
- _logsRoot = _path.resolve(__dirname, '..', '..', '..', 'logs', 'dialogs');
41
+ _logsRoot = _path.resolve(__dirname, '..', '..', '..', '..', 'logs', 'dialogs');
42
42
  }
43
43
 
44
44
  // In-memory turn counter per session — keeps the on-disk turn ordering correct
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import { feedbackStore } from './feedback-store.js';
16
- import { categorizeIntent } from './intent-categorizer.js';
16
+ import { categorizeIntent } from '../intent/intent-categorizer.js';
17
17
 
18
18
  let fs, path;
19
19
  const IS_NODE = typeof process !== 'undefined' && process.versions?.node;
@@ -26,8 +26,9 @@ if (IS_NODE) {
26
26
  }
27
27
  }
28
28
 
29
+ // packages/a2ui/retrieval/feedback → up 3 → packages/a2ui → corpus/feedback
29
30
  const FEEDBACK_DIR = path
30
- ? path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', 'a2ui/corpus', 'feedback')
31
+ ? path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..', 'a2ui/corpus', 'feedback')
31
32
  : null;
32
33
 
33
34
  export class FeedbackAnalyzer {
@@ -23,8 +23,9 @@ if (IS_NODE) {
23
23
  }
24
24
  }
25
25
 
26
+ // packages/a2ui/retrieval/feedback → up 3 → packages/a2ui → corpus/feedback
26
27
  const FEEDBACK_DIR = path
27
- ? path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', 'a2ui/corpus', 'feedback')
28
+ ? path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..', 'a2ui/corpus', 'feedback')
28
29
  : null;
29
30
 
30
31
  function todayFile() {
@@ -24,8 +24,9 @@ if (IS_NODE) {
24
24
  }
25
25
  }
26
26
 
27
+ // packages/a2ui/retrieval/feedback → up 3 → packages/a2ui → corpus/gaps/registry.json
27
28
  const REGISTRY_PATH = path
28
- ? path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', 'a2ui/corpus', 'gaps', 'registry.json')
29
+ ? path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..', 'a2ui/corpus', 'gaps', 'registry.json')
29
30
  : null;
30
31
 
31
32
  /**
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @adia-ai/a2ui-retrieval/feedback — feedback-loop surface.
3
+ *
4
+ * Pairs the feedback store, analyzer, dialog recorder, and gap registry
5
+ * — all the artifacts that compose the user-feedback ingestion loop.
6
+ */
7
+
8
+ export { feedbackStore } from './feedback-store.js';
9
+ export { FeedbackAnalyzer } from './feedback-analyzer.js';
10
+ export * from './feedback.js';
11
+ export { recordTurn, isRecording } from './dialog-recorder.js';
12
+ export { loadGaps, addGap, updateGapStatus } from './gap-registry.js';
package/index.js CHANGED
@@ -6,11 +6,11 @@ export { getCatalog, getComponent, getComponentsByCategory, getTraits, getTraits
6
6
  export { serializeEntry } from './component-entry.js';
7
7
  export { getAntiPatterns, checkAntiPattern, checkAllAntiPatterns } from './anti-patterns.js';
8
8
  export { classifyIntent, getDomain, getAllDomains } from './domain-router.js';
9
- export { isConversational } from './intent-gate.js';
10
- export { assessClarity } from './clarity.js';
11
- export { detectReferences, researchIntent } from './web-research.js';
9
+ export { isConversational } from './intent/intent-gate.js';
10
+ export { assessClarity } from './intent/clarity.js';
11
+ export { detectReferences, researchIntent } from './authoring/web-research.js';
12
12
  export { assembleContext } from './context-assembler.js';
13
13
  export { getPattern, searchPatterns, semanticSearchPatterns, getAllPatterns, registerPattern } from './pattern-library.js';
14
- export { extractExpectations, verifyAlignment, checkIntentAlignment } from './intent-alignment.js';
15
- export { decomposeIntent, composeSubtasks } from './decomposer.js';
14
+ export { extractExpectations, verifyAlignment, checkIntentAlignment } from './intent/intent-alignment.js';
15
+ export { decomposeIntent, composeSubtasks } from './intent/decomposer.js';
16
16
  export { getWiringCatalog, getControllerInfo, getHandlerInfo } from './wiring-catalog.js';
@@ -12,8 +12,8 @@
12
12
  * Returns a clarity score (0-1) and targeted questions for what's missing.
13
13
  */
14
14
 
15
- import { classifyIntent } from './domain-router.js';
16
- import { searchPatterns } from './pattern-library.js';
15
+ import { classifyIntent } from '../domain-router.js';
16
+ import { searchPatterns } from '../pattern-library.js';
17
17
 
18
18
  // ── Dimension detectors ──────────────────────────────────────────────────
19
19
 
@@ -14,7 +14,7 @@
14
14
  * is almost trivial."
15
15
  */
16
16
 
17
- import { classifyIntent } from './domain-router.js';
17
+ import { classifyIntent } from '../domain-router.js';
18
18
 
19
19
  // ── Section Detection ────────────────────────────────────────────────────
20
20
 
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @adia-ai/a2ui-retrieval/intent — intent classification + decomposition surface.
3
+ *
4
+ * Re-exports the intent-shaped retrieval primitives. Importers can reach
5
+ * individual files directly (e.g. `@adia-ai/a2ui-retrieval/intent/clarity`)
6
+ * or pull the bundle via this barrel.
7
+ */
8
+
9
+ export { extractExpectations, verifyAlignment, checkIntentAlignment } from './intent-alignment.js';
10
+ export { categorizeIntent } from './intent-categorizer.js';
11
+ export { isConversational } from './intent-gate.js';
12
+ export { analyzePrompt } from './prompt-analyzer.js';
13
+ export { decomposeIntent, composeSubtasks } from './decomposer.js';
14
+ export { assessClarity } from './clarity.js';
@@ -45,11 +45,12 @@ async function getVocab() {
45
45
  const fs = await import(/* @vite-ignore */ 'node:fs/promises');
46
46
  const path = await import(/* @vite-ignore */ 'node:path');
47
47
  const url = await import(/* @vite-ignore */ 'node:url');
48
+ // packages/a2ui/retrieval/intent → up 2 → packages/a2ui → corpus/patterns/_components.json
48
49
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
49
- const raw = await fs.readFile(path.join(__dirname, '../corpus/patterns/_components.json'), 'utf8');
50
+ const raw = await fs.readFile(path.join(__dirname, '../../corpus/patterns/_components.json'), 'utf8');
50
51
  catalog = JSON.parse(raw);
51
52
  } else {
52
- const resp = await fetch(new URL('../corpus/patterns/_components.json', import.meta.url));
53
+ const resp = await fetch(new URL('../../corpus/patterns/_components.json', import.meta.url));
53
54
  if (resp.ok) catalog = await resp.json();
54
55
  }
55
56
  } catch { /* empty vocab — analyzer will still work, just unconstrained */ }
package/package.json CHANGED
@@ -1,15 +1,27 @@
1
1
  {
2
2
  "name": "@adia-ai/a2ui-retrieval",
3
- "version": "0.0.1",
3
+ "version": "0.2.1",
4
4
  "description": "AdiaUI A2UI retrieval layer — 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",
7
7
  "exports": {
8
- ".": "./index.js",
9
- "./*": "./*.js"
8
+ ".": "./index.js",
9
+ "./intent": "./intent/index.js",
10
+ "./intent/*": "./intent/*.js",
11
+ "./embedding": "./embedding/index.js",
12
+ "./embedding/*": "./embedding/*.js",
13
+ "./feedback": "./feedback/index.js",
14
+ "./feedback/*": "./feedback/*.js",
15
+ "./authoring": "./authoring/index.js",
16
+ "./authoring/*": "./authoring/*.js",
17
+ "./*": "./*.js"
10
18
  },
11
19
  "files": [
12
20
  "*.js",
21
+ "intent/",
22
+ "embedding/",
23
+ "feedback/",
24
+ "authoring/",
13
25
  "README.md",
14
26
  "CHANGELOG.md"
15
27
  ],
@@ -24,6 +36,9 @@
24
36
  "directory": "packages/a2ui/retrieval"
25
37
  },
26
38
  "dependencies": {
27
- "@adia-ai/a2ui-utils": "^0.0.2"
39
+ "@adia-ai/a2ui-utils": "^0.2.0"
40
+ },
41
+ "optionalDependencies": {
42
+ "@adia-ai/a2ui-corpus": "^0.2.0"
28
43
  }
29
44
  }
@@ -535,7 +535,7 @@ export async function semanticSearchPatterns(query, options = {}) {
535
535
  // where keyword collisions pick the wrong pattern. Graceful degradation:
536
536
  // empty map → no effect, pipeline behaves exactly as pre-embedding.
537
537
  try {
538
- const { scoreAll, available } = await import('./embedding-retriever.js');
538
+ const { scoreAll, available } = await import('./embedding/embedding-retriever.js');
539
539
  if (await available()) {
540
540
  const semanticMap = await scoreAll(query);
541
541
  if (semanticMap.size > 0) {
package/wiring-catalog.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * The schema has 5 docking points:
8
8
  * DATA — sources, params (how the surface gets its data)
9
9
  * STATE — controllers, model (how components manage behavior)
10
- * ACTIONS — AdiaEvent → handler chains (what happens on events)
10
+ * ACTIONS — UIEvent → handler chains (what happens on events)
11
11
  * PROVIDES — context injection into subtrees
12
12
  * LIFECYCLE — onMount, onUnmount, onModelChange hooks
13
13
  *
@@ -17,7 +17,7 @@
17
17
  * A full app needs all five.
18
18
  */
19
19
 
20
- // ── AdiaEvent ──────────────────────────────────────────────────
20
+ // ── UIEvent ──────────────────────────────────────────────────
21
21
  // A typed event object that the wiring system understands natively.
22
22
  // Maps 1:1 to DOM events but with AdiaUI semantics and payload contracts.
23
23
  //
@@ -26,7 +26,7 @@
26
26
  // { "event": "submit", "target": "form-col" }
27
27
  // { "event": "mount" }
28
28
  //
29
- // "event" — AdiaEvent type name (see adiaEvents below)
29
+ // "event" — UIEvent type name (see adiaEvents below)
30
30
  // "target" — component id that emits. Omit for surface-level events.
31
31
  // "debounce" — ms, coalesce rapid-fire (input, resize)
32
32
  // "throttle" — ms, limit frequency (scroll, drag)
@@ -129,9 +129,9 @@ export function getWiringCatalog() {
129
129
  { name: 'notify', description: 'Show a toast/alert notification.', config: ['message', 'variant'] },
130
130
  ],
131
131
 
132
- // ── ACTION SHAPE (AdiaEvent → handler) ──
132
+ // ── ACTION SHAPE (UIEvent → handler) ──
133
133
  actionShape: {
134
- event: 'AdiaEvent object: { event, target?, debounce?, throttle?, condition? }',
134
+ event: 'UIEvent object: { event, target?, debounce?, throttle?, condition? }',
135
135
  handler: 'Handler name from the handlers list.',
136
136
  config: 'Handler-specific configuration object.',
137
137
  onSuccess: 'Follow-up actions array (each is { handler, config }).',
@@ -174,7 +174,7 @@ export function getWiringCatalog() {
174
174
  }
175
175
 
176
176
  /**
177
- * Get AdiaEvent type info.
177
+ * Get UIEvent type info.
178
178
  */
179
179
  export function getAdiaEvent(type) {
180
180
  return adiaEvents.find(e => e.event === type) || null;
File without changes
File without changes
File without changes