@adia-ai/a2ui-retrieval 0.6.4 → 0.6.6

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.
@@ -1,198 +1,117 @@
1
- /**
2
- * FeedbackCollector Structured feedback for the evolution engine.
3
- *
4
- * Captures per-generation feedback across multiple dimensions:
5
- * - Overall rating (1-5)
6
- * - Intent alignment, visual quality, component choice (1-5 each)
7
- * - Whether the user edited the output
8
- * - Pattern promotion signals ("this should become a pattern")
9
- *
10
- * Exports as a structured JSON log for the training cycle.
11
- */
12
-
13
- /**
14
- * @typedef {object} FeedbackEntry
15
- * @property {string} executionId
16
- * @property {string} intent
17
- * @property {string} domain
18
- * @property {string} mode
19
- * @property {number} timestamp
20
- * @property {object} generation
21
- * @property {number} generation.componentCount
22
- * @property {string[]} generation.componentTypes
23
- * @property {number} generation.score
24
- * @property {{ name: string, passed: boolean }[]} generation.validationChecks
25
- * @property {{ structural: number, completeness: number, idiomatic: number, minimal: number }} generation.qualityDimensions
26
- * @property {object} feedback
27
- * @property {number} [feedback.rating]
28
- * @property {number} [feedback.intentAlignment]
29
- * @property {number} [feedback.visualQuality]
30
- * @property {number} [feedback.componentChoice]
31
- * @property {boolean} [feedback.userEdited]
32
- * @property {string} [feedback.editSummary]
33
- * @property {string} [feedback.notes]
34
- * @property {object} patterns
35
- * @property {string} [patterns.patternUsed]
36
- * @property {boolean} [patterns.shouldBePattern]
37
- * @property {string} [patterns.suggestedName]
38
- */
39
-
40
- export class FeedbackCollector {
41
- /** @type {Map<string, FeedbackEntry>} */
42
- #entries = new Map();
43
-
1
+ class FeedbackCollector {
2
+ #entries = /* @__PURE__ */ new Map();
44
3
  /**
45
4
  * Initialize a feedback entry from generation results.
46
5
  * Called automatically after each generation completes.
47
- *
48
- * @param {string} executionId
49
- * @param {object} data
50
- * @param {string} data.intent
51
- * @param {string} data.domain
52
- * @param {string} data.mode
53
- * @param {object[]} data.messages
54
- * @param {object} data.validation
55
6
  */
56
7
  initFromGeneration(executionId, { intent, domain, mode, messages, validation }) {
57
- const components = messages?.[0]?.components || [];
58
- const checks = validation?.checks || [];
59
-
60
- // Compute quality dimensions (same logic as score_quality in mcp-tools.js)
61
- const failedChecks = checks.filter(c => !c.passed);
62
- const structural = failedChecks.some(c =>
63
- ['hasRootComponent', 'noOrphanedChildren', 'flatAdjacency'].includes(c.name)
8
+ const components = messages?.[0]?.components ?? [];
9
+ const checks = validation?.checks ?? [];
10
+ const failedChecks = checks.filter((c) => !c.passed);
11
+ const structural = failedChecks.some(
12
+ (c) => ["hasRootComponent", "noOrphanedChildren", "flatAdjacency"].includes(c.name)
64
13
  ) ? 0.5 : 1;
65
- const completeness = Math.max(0, 1 - (
66
- failedChecks.filter(c => ['textContentSet', 'allTypesRegistered'].includes(c.name)).length * 0.1
67
- ));
68
- const idiomatic = failedChecks.some(c =>
69
- ['noBareDivs', 'noBareInputs', 'cardStructure'].includes(c.name)
14
+ const completeness = Math.max(0, 1 - failedChecks.filter((c) => ["textContentSet", "allTypesRegistered"].includes(c.name)).length * 0.1);
15
+ const idiomatic = failedChecks.some(
16
+ (c) => ["noBareDivs", "noBareInputs", "cardStructure"].includes(c.name)
70
17
  ) ? 0.5 : 1;
71
- const minimal = failedChecks.some(c =>
72
- ['noHardcodedColors', 'noInlineLayout'].includes(c.name)
18
+ const minimal = failedChecks.some(
19
+ (c) => ["noHardcodedColors", "noInlineLayout"].includes(c.name)
73
20
  ) ? 0.5 : 1;
74
-
75
21
  this.#entries.set(executionId, {
76
22
  executionId,
77
- intent: intent || '',
78
- domain: domain || '',
79
- mode: mode || 'instant',
23
+ intent: intent ?? "",
24
+ domain: domain ?? "",
25
+ mode: mode ?? "instant",
80
26
  timestamp: Date.now(),
81
27
  generation: {
82
28
  componentCount: components.length,
83
- componentTypes: [...new Set(components.map(c => c.component).filter(Boolean))],
29
+ componentTypes: [...new Set(components.map((c) => c.component).filter((x) => Boolean(x)))],
84
30
  score: validation?.score ?? 0,
85
- validationChecks: checks.map(c => ({ name: c.name, passed: c.passed })),
86
- qualityDimensions: { structural, completeness, idiomatic, minimal },
31
+ validationChecks: checks.map((c) => ({ name: c.name, passed: c.passed })),
32
+ qualityDimensions: { structural, completeness, idiomatic, minimal }
87
33
  },
88
34
  feedback: {},
89
- patterns: {},
35
+ patterns: {}
90
36
  });
91
37
  }
92
-
93
38
  /**
94
39
  * Collect user feedback for an execution.
95
- *
96
- * @param {string} executionId
97
- * @param {object} feedback
98
- * @param {number} [feedback.rating] — 1-5
99
- * @param {number} [feedback.intentAlignment] — 1-5
100
- * @param {number} [feedback.visualQuality] — 1-5
101
- * @param {number} [feedback.componentChoice] — 1-5
102
- * @param {boolean} [feedback.userEdited]
103
- * @param {string} [feedback.editSummary]
104
- * @param {string} [feedback.notes]
105
40
  */
106
41
  collectFeedback(executionId, feedback) {
107
42
  const entry = this.#entries.get(executionId);
108
43
  if (!entry) {
109
- // Create a minimal entry if init wasn't called
110
44
  this.#entries.set(executionId, {
111
45
  executionId,
112
- intent: '', domain: '', mode: '', timestamp: Date.now(),
113
- generation: { componentCount: 0, componentTypes: [], score: 0, validationChecks: [], qualityDimensions: {} },
46
+ intent: "",
47
+ domain: "",
48
+ mode: "",
49
+ timestamp: Date.now(),
50
+ generation: { componentCount: 0, componentTypes: [], score: 0, validationChecks: [], qualityDimensions: { structural: 0, completeness: 0, idiomatic: 0, minimal: 0 } },
114
51
  feedback: {},
115
- patterns: {},
52
+ patterns: {}
116
53
  });
117
54
  }
118
55
  const e = this.#entries.get(executionId);
119
56
  e.feedback = { ...e.feedback, ...feedback };
120
57
  }
121
-
122
58
  /**
123
59
  * Collect pattern-related feedback.
124
- *
125
- * @param {string} executionId
126
- * @param {object} patternFeedback
127
- * @param {string} [patternFeedback.patternUsed]
128
- * @param {boolean} [patternFeedback.shouldBePattern]
129
- * @param {string} [patternFeedback.suggestedName]
130
60
  */
131
61
  collectPatternFeedback(executionId, patternFeedback) {
132
62
  const entry = this.#entries.get(executionId);
133
63
  if (!entry) return;
134
64
  entry.patterns = { ...entry.patterns, ...patternFeedback };
135
65
  }
136
-
137
66
  /**
138
67
  * Get a single feedback entry.
139
- * @param {string} executionId
140
- * @returns {FeedbackEntry|null}
141
68
  */
142
69
  get(executionId) {
143
70
  return this.#entries.get(executionId) ?? null;
144
71
  }
145
-
146
72
  /**
147
73
  * Get all feedback entries.
148
- * @returns {FeedbackEntry[]}
149
74
  */
150
75
  getAll() {
151
76
  return [...this.#entries.values()];
152
77
  }
153
-
154
78
  /** Number of entries. */
155
79
  get size() {
156
80
  return this.#entries.size;
157
81
  }
158
-
159
82
  /**
160
83
  * Export all feedback as structured JSON.
161
84
  * In browser: triggers a file download.
162
85
  * In Node: returns the JSON string.
163
- *
164
- * @returns {string} — JSON string of all entries
165
86
  */
166
87
  exportFeedback() {
167
88
  const data = {
168
- exportedAt: new Date().toISOString(),
89
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
169
90
  entryCount: this.#entries.size,
170
- entries: this.getAll(),
91
+ entries: this.getAll()
171
92
  };
172
93
  const json = JSON.stringify(data, null, 2);
173
-
174
- // Browser download
175
- if (typeof document !== 'undefined') {
176
- const date = new Date().toISOString().slice(0, 10);
177
- const blob = new Blob([json], { type: 'application/json' });
94
+ if (typeof document !== "undefined") {
95
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
96
+ const blob = new Blob([json], { type: "application/json" });
178
97
  const url = URL.createObjectURL(blob);
179
- const a = document.createElement('a');
98
+ const a = document.createElement("a");
180
99
  a.href = url;
181
100
  a.download = `gen-ui-feedback-${date}.json`;
182
- a.style.display = 'none';
183
- // Prevent SPA router from intercepting the blob URL click
184
- a.addEventListener('click', (e) => e.stopPropagation());
101
+ a.style.display = "none";
102
+ a.addEventListener("click", (e) => e.stopPropagation());
185
103
  document.body.appendChild(a);
186
104
  a.click();
187
105
  document.body.removeChild(a);
188
106
  URL.revokeObjectURL(url);
189
107
  }
190
-
191
108
  return json;
192
109
  }
193
-
194
110
  /** Clear all entries. */
195
111
  clear() {
196
112
  this.#entries.clear();
197
113
  }
198
114
  }
115
+ export {
116
+ FeedbackCollector
117
+ };
@@ -1,122 +1,80 @@
1
- /**
2
- * Gap Registry
3
- *
4
- * Manages a persistent JSON file tracking identified pattern/training gaps.
5
- * Used by the feedback pipeline to record and track resolution of weak areas.
6
- *
7
- * Registry file: packages/a2ui/corpus/gaps/registry.json
8
- *
9
- * Usage:
10
- * import { loadGaps, addGap, updateGapStatus } from './gap-registry.js';
11
- * const gaps = await loadGaps();
12
- * await addGap({ intentCategory: 'form/checkout', ... });
13
- * await updateGapStatus('form/checkout', 'resolved', 'Added checkout pattern');
14
- */
15
-
16
- let fs, path;
17
- const IS_NODE = typeof process !== 'undefined' && process.versions?.node;
1
+ let fs = null;
2
+ let path = null;
3
+ const IS_NODE = typeof process !== "undefined" && process.versions?.node;
18
4
  if (IS_NODE) {
19
5
  try {
20
- fs = await import(/* @vite-ignore */ 'node:fs/promises');
21
- path = await import(/* @vite-ignore */ 'node:path');
6
+ fs = await import(
7
+ /* @vite-ignore */
8
+ "node:fs/promises"
9
+ );
10
+ path = await import(
11
+ /* @vite-ignore */
12
+ "node:path"
13
+ );
22
14
  } catch {
23
- // Node builtins unavailable
24
15
  }
25
16
  }
26
-
27
- // packages/a2ui/retrieval/feedback up 3 → packages/a2ui → corpus/gaps/registry.json
28
- const REGISTRY_PATH = path
29
- ? path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..', 'a2ui/corpus', 'gaps', 'registry.json')
30
- : null;
31
-
32
- /**
33
- * Load all gaps from the registry file.
34
- * @returns {Promise<object[]>}
35
- */
36
- export async function loadGaps() {
17
+ const REGISTRY_PATH = path ? path.join(path.dirname(new URL(import.meta.url).pathname), "..", "..", "..", "a2ui/corpus", "gaps", "registry.json") : null;
18
+ async function loadGaps() {
37
19
  if (!fs || !REGISTRY_PATH) return [];
38
20
  try {
39
- const content = await fs.readFile(REGISTRY_PATH, 'utf8');
21
+ const content = await fs.readFile(REGISTRY_PATH, "utf8");
40
22
  return JSON.parse(content);
41
23
  } catch {
42
24
  return [];
43
25
  }
44
26
  }
45
-
46
- /**
47
- * Save the full gaps array to disk.
48
- * @param {object[]} gaps
49
- */
50
- export async function saveGaps(gaps) {
27
+ async function saveGaps(gaps) {
51
28
  if (!fs || !REGISTRY_PATH) return;
52
29
  const dir = path.dirname(REGISTRY_PATH);
53
30
  await fs.mkdir(dir, { recursive: true });
54
- await fs.writeFile(REGISTRY_PATH, JSON.stringify(gaps, null, 2) + '\n');
31
+ await fs.writeFile(REGISTRY_PATH, JSON.stringify(gaps, null, 2) + "\n");
55
32
  }
56
-
57
- /**
58
- * Add a new gap to the registry. Merges with existing if same intentCategory.
59
- *
60
- * @param {object} gap
61
- * @param {string} gap.intentCategory
62
- * @param {number} gap.sampleCount
63
- * @param {number} gap.avgScore
64
- * @param {number} gap.avgRating
65
- * @param {string[]} gap.sampleIntents
66
- */
67
- export async function addGap(gap) {
33
+ async function addGap(gap) {
68
34
  const gaps = await loadGaps();
69
- const existing = gaps.find(g => g.intentCategory === gap.intentCategory && g.status !== 'resolved');
70
-
35
+ const existing = gaps.find((g) => g.intentCategory === gap.intentCategory && g.status !== "resolved");
71
36
  if (existing) {
72
- // Merge: update stats, add new sample intents
73
- existing.sampleCount = gap.sampleCount;
74
- existing.avgScore = gap.avgScore;
75
- existing.avgRating = gap.avgRating;
76
- existing.lastSeen = new Date().toISOString();
77
- const intentSet = new Set([...existing.sampleIntents, ...(gap.sampleIntents || [])]);
37
+ existing.sampleCount = gap.sampleCount ?? existing.sampleCount;
38
+ existing.avgScore = gap.avgScore ?? existing.avgScore;
39
+ existing.avgRating = gap.avgRating ?? existing.avgRating;
40
+ existing.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
41
+ const intentSet = /* @__PURE__ */ new Set([...existing.sampleIntents, ...gap.sampleIntents ?? []]);
78
42
  existing.sampleIntents = [...intentSet].slice(0, 10);
79
43
  } else {
80
44
  gaps.push({
81
45
  intentCategory: gap.intentCategory,
82
- detectedAt: new Date().toISOString(),
83
- lastSeen: new Date().toISOString(),
84
- sampleCount: gap.sampleCount || 0,
85
- avgScore: gap.avgScore || 0,
86
- avgRating: gap.avgRating || 0,
87
- status: 'open',
46
+ detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
47
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
48
+ sampleCount: gap.sampleCount ?? 0,
49
+ avgScore: gap.avgScore ?? 0,
50
+ avgRating: gap.avgRating ?? 0,
51
+ status: "open",
88
52
  resolution: null,
89
- sampleIntents: (gap.sampleIntents || []).slice(0, 10),
53
+ sampleIntents: (gap.sampleIntents ?? []).slice(0, 10)
90
54
  });
91
55
  }
92
-
93
56
  await saveGaps(gaps);
94
57
  return gaps;
95
58
  }
96
-
97
- /**
98
- * Update the status of a gap by intent category.
99
- *
100
- * @param {string} category — Intent category to update
101
- * @param {'open'|'in-progress'|'resolved'} status
102
- * @param {string} [resolution] — Description of how the gap was resolved
103
- */
104
- export async function updateGapStatus(category, status, resolution) {
59
+ async function updateGapStatus(category, status, resolution) {
105
60
  const gaps = await loadGaps();
106
- const gap = gaps.find(g => g.intentCategory === category && g.status !== 'resolved');
107
-
61
+ const gap = gaps.find((g) => g.intentCategory === category && g.status !== "resolved");
108
62
  if (!gap) {
109
63
  throw new Error(`No open gap found for category: ${category}`);
110
64
  }
111
-
112
65
  gap.status = status;
113
66
  if (resolution) {
114
67
  gap.resolution = resolution;
115
68
  }
116
- if (status === 'resolved') {
117
- gap.resolvedAt = new Date().toISOString();
69
+ if (status === "resolved") {
70
+ gap.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
118
71
  }
119
-
120
72
  await saveGaps(gaps);
121
73
  return gap;
122
74
  }
75
+ export {
76
+ addGap,
77
+ loadGaps,
78
+ saveGaps,
79
+ updateGapStatus
80
+ };
package/feedback/index.js CHANGED
@@ -1,12 +1,14 @@
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';
1
+ import { feedbackStore } from "./feedback-store.js";
2
+ import { FeedbackAnalyzer } from "./feedback-analyzer.js";
3
+ export * from "./feedback.js";
4
+ import { recordTurn, isRecording } from "./dialog-recorder.js";
5
+ import { loadGaps, addGap, updateGapStatus } from "./gap-registry.js";
6
+ export {
7
+ FeedbackAnalyzer,
8
+ addGap,
9
+ feedbackStore,
10
+ isRecording,
11
+ loadGaps,
12
+ recordTurn,
13
+ updateGapStatus
14
+ };
package/index.js CHANGED
@@ -1,16 +1,53 @@
1
- /**
2
- * A2UI Intelligence System — Public API
3
- */
4
-
5
- export { getCatalog, getComponent, getComponentsByCategory, getTraits, getTraitsByCategory, getFullCatalog } from './catalog.js';
6
- export { serializeEntry } from './component-entry.js';
7
- export { getAntiPatterns, checkAntiPattern, checkAllAntiPatterns } from './anti-patterns.js';
8
- export { classifyIntent, getDomain, getAllDomains } from './domain-router.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
- export { assembleContext } from './context-assembler.js';
13
- export { getComponentData, getAllComponentData } from './component-catalog.js';
14
- export { extractExpectations, verifyAlignment, checkIntentAlignment } from './intent/intent-alignment.js';
15
- export { decomposeIntent, composeSubtasks } from './intent/decomposer.js';
16
- export { getWiringCatalog, getControllerInfo, getHandlerInfo } from './wiring-catalog.js';
1
+ import {
2
+ getCatalog,
3
+ getComponent,
4
+ getComponentsByCategory,
5
+ getTraits,
6
+ getTraitsByCategory,
7
+ getFullCatalog
8
+ } from "./catalog.js";
9
+ import { serializeEntry } from "./component-entry.js";
10
+ import { getAntiPatterns, checkAntiPattern, checkAllAntiPatterns } from "./anti-patterns.js";
11
+ import { classifyIntent, getDomain, getAllDomains } from "./domain-router.js";
12
+ import { isConversational } from "./intent/intent-gate.js";
13
+ import { assessClarity } from "./intent/clarity.js";
14
+ import { detectReferences, researchIntent } from "./authoring/web-research.js";
15
+ import { assembleContext } from "./context-assembler.js";
16
+ import { getComponentData, getAllComponentData } from "./component-catalog.js";
17
+ import {
18
+ extractExpectations,
19
+ verifyAlignment,
20
+ checkIntentAlignment
21
+ } from "./intent/intent-alignment.js";
22
+ import { decomposeIntent, composeSubtasks } from "./intent/decomposer.js";
23
+ import { getWiringCatalog, getControllerInfo, getHandlerInfo } from "./wiring-catalog.js";
24
+ export {
25
+ assembleContext,
26
+ assessClarity,
27
+ checkAllAntiPatterns,
28
+ checkAntiPattern,
29
+ checkIntentAlignment,
30
+ classifyIntent,
31
+ composeSubtasks,
32
+ decomposeIntent,
33
+ detectReferences,
34
+ extractExpectations,
35
+ getAllComponentData,
36
+ getAllDomains,
37
+ getAntiPatterns,
38
+ getCatalog,
39
+ getComponent,
40
+ getComponentData,
41
+ getComponentsByCategory,
42
+ getControllerInfo,
43
+ getDomain,
44
+ getFullCatalog,
45
+ getHandlerInfo,
46
+ getTraits,
47
+ getTraitsByCategory,
48
+ getWiringCatalog,
49
+ isConversational,
50
+ researchIntent,
51
+ serializeEntry,
52
+ verifyAlignment
53
+ };