@taodigital/app-brain 0.0.0-stage → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +13 -2
  2. package/dist/archetype-classification.d.ts +54 -0
  3. package/dist/archetype-classification.js +95 -0
  4. package/dist/cloud-storage.d.ts +106 -0
  5. package/dist/cloud-storage.js +539 -0
  6. package/dist/email.d.ts +21 -0
  7. package/dist/email.js +234 -0
  8. package/dist/grounding.d.ts +30 -0
  9. package/dist/grounding.js +138 -0
  10. package/dist/index.d.ts +13 -0
  11. package/dist/index.js +37 -0
  12. package/dist/nav-graph.d.ts +42 -0
  13. package/dist/nav-graph.js +248 -0
  14. package/dist/notifications.d.ts +26 -0
  15. package/dist/notifications.js +105 -0
  16. package/dist/orphan-classification.d.ts +32 -0
  17. package/dist/orphan-classification.js +40 -0
  18. package/dist/repository.d.ts +3185 -0
  19. package/dist/repository.js +5927 -0
  20. package/dist/run-notifications.d.ts +16 -0
  21. package/dist/run-notifications.js +244 -0
  22. package/dist/skill-file-kinds.d.ts +5 -0
  23. package/dist/skill-file-kinds.js +50 -0
  24. package/dist/token-ledger.d.ts +23 -0
  25. package/dist/token-ledger.js +178 -0
  26. package/dist/traceability.d.ts +213 -0
  27. package/dist/traceability.js +339 -0
  28. package/package.json +24 -4
  29. package/src/archetype-classification.test.ts +91 -0
  30. package/src/archetype-classification.ts +132 -0
  31. package/src/cloud-storage.test.ts +83 -0
  32. package/src/cloud-storage.ts +627 -0
  33. package/src/email.ts +268 -0
  34. package/src/grounding.ts +172 -0
  35. package/src/index.ts +18 -0
  36. package/src/nav-graph.ts +295 -0
  37. package/src/notifications.ts +156 -0
  38. package/src/orphan-classification.test.ts +61 -0
  39. package/src/orphan-classification.ts +65 -0
  40. package/src/orphan-screens.test.ts +88 -0
  41. package/src/repository.ts +7397 -0
  42. package/src/run-notifications.ts +281 -0
  43. package/src/skill-file-kinds.ts +51 -0
  44. package/src/token-ledger.ts +209 -0
  45. package/src/traceability.test.ts +75 -0
  46. package/src/traceability.ts +477 -0
  47. package/tsconfig.json +9 -0
package/dist/email.js ADDED
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getEmailDiagnostics = getEmailDiagnostics;
7
+ exports.sendEmail = sendEmail;
8
+ const nodemailer_1 = __importDefault(require("nodemailer"));
9
+ const repository_js_1 = require("./repository.js");
10
+ // Branding defaults — overridden by DB-backed admin config (getSystemConfig('branding')) at send time.
11
+ const DEFAULT_BRAND_NAME = 'ZeTa-AI';
12
+ const DEFAULT_BRAND_COLOR = '#0ff';
13
+ async function getBranding() {
14
+ try {
15
+ const { getSystemConfig } = await import('@taodigital/core');
16
+ const cfg = (await getSystemConfig('branding')) ?? {};
17
+ return {
18
+ brandName: String(cfg.brandName ?? DEFAULT_BRAND_NAME),
19
+ brandColor: String(cfg.brandColor ?? DEFAULT_BRAND_COLOR),
20
+ appUrl: String(cfg.appUrl ?? process.env.APP_URL ?? `http://localhost:${process.env.PORT ?? 3000}`),
21
+ };
22
+ }
23
+ catch {
24
+ return {
25
+ brandName: DEFAULT_BRAND_NAME,
26
+ brandColor: DEFAULT_BRAND_COLOR,
27
+ appUrl: process.env.APP_URL ?? `http://localhost:${process.env.PORT ?? 3000}`,
28
+ };
29
+ }
30
+ }
31
+ async function getTenantConfig(tenantId) {
32
+ if (!tenantId)
33
+ return null;
34
+ try {
35
+ return await repository_js_1.AppBrain.getTenantEmailConfig(tenantId);
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ async function getEmailDiagnostics(tenantId) {
42
+ const { getSystemConfig } = await import('@taodigital/core');
43
+ const smtpCfg = await getSystemConfig('smtp').catch(() => ({}));
44
+ const smtpHost = (smtpCfg?.host && smtpCfg.host.trim()) || process.env.SMTP_HOST || '';
45
+ const smtpUser = (smtpCfg?.username && smtpCfg.username.trim()) || process.env.SMTP_USERNAME || process.env.SMTP_USER || '';
46
+ const smtpPass = (smtpCfg?.password && smtpCfg.password.trim()) || process.env.SMTP_PASSWORD || process.env.SMTP_PASS || '';
47
+ if (smtpHost || smtpUser || smtpPass) {
48
+ const missing = [
49
+ !smtpHost ? 'Platform SMTP host' : null,
50
+ !smtpUser ? 'Platform SMTP username' : null,
51
+ !smtpPass ? 'Platform SMTP password' : null,
52
+ ].filter(Boolean);
53
+ return {
54
+ configured: missing.length === 0,
55
+ provider: 'platform-smtp',
56
+ missing,
57
+ message: missing.length === 0
58
+ ? 'Platform SMTP is configured.'
59
+ : `Platform SMTP is incomplete: ${missing.join(', ')}.`,
60
+ };
61
+ }
62
+ const tenantCfg = await getTenantConfig(tenantId);
63
+ if (tenantCfg?.provider === 'resend' || tenantCfg?.resendApiKey) {
64
+ const missing = tenantCfg.resendApiKey ? [] : ['Workspace Resend API key'];
65
+ return {
66
+ configured: missing.length === 0,
67
+ provider: 'tenant-resend',
68
+ missing,
69
+ message: missing.length === 0
70
+ ? 'Workspace Resend email is configured.'
71
+ : `Workspace Resend email is incomplete: ${missing.join(', ')}.`,
72
+ };
73
+ }
74
+ if (tenantCfg?.smtpHost || tenantCfg?.smtpUser || tenantCfg?.smtpPass) {
75
+ const missing = [
76
+ !tenantCfg.smtpHost ? 'Workspace SMTP host' : null,
77
+ !tenantCfg.smtpUser ? 'Workspace SMTP username' : null,
78
+ !tenantCfg.smtpPass ? 'Workspace SMTP password' : null,
79
+ ].filter(Boolean);
80
+ return {
81
+ configured: missing.length === 0,
82
+ provider: 'tenant-smtp',
83
+ missing,
84
+ message: missing.length === 0
85
+ ? 'Workspace SMTP email is configured.'
86
+ : `Workspace SMTP email is incomplete: ${missing.join(', ')}.`,
87
+ };
88
+ }
89
+ if (process.env.RESEND_API_KEY) {
90
+ return {
91
+ configured: true,
92
+ provider: 'env-resend',
93
+ missing: [],
94
+ message: 'Environment Resend email is configured.',
95
+ };
96
+ }
97
+ return {
98
+ configured: false,
99
+ provider: 'none',
100
+ missing: ['Email provider'],
101
+ message: 'No email provider configured. Configure Workspace email, platform SMTP, or RESEND_API_KEY.',
102
+ };
103
+ }
104
+ async function sendEmail(payload) {
105
+ const branding = await getBranding();
106
+ const html = buildHtml(payload, branding);
107
+ // Platform-level SMTP from DB (admin panel) or env fallback — always wins over tenant config.
108
+ const { getSystemConfig } = await import('@taodigital/core');
109
+ const smtpCfg = await getSystemConfig('smtp').catch(() => ({}));
110
+ const smtpHost = (smtpCfg?.host && smtpCfg.host.trim()) || process.env.SMTP_HOST || '';
111
+ const smtpUser = (smtpCfg?.username && smtpCfg.username.trim()) || process.env.SMTP_USERNAME || process.env.SMTP_USER || '';
112
+ const smtpPass = (smtpCfg?.password && smtpCfg.password.trim()) || process.env.SMTP_PASSWORD || process.env.SMTP_PASS || '';
113
+ const smtpPort = Number(smtpCfg?.port || process.env.SMTP_PORT || 587);
114
+ const smtpFrom = (smtpCfg?.from && smtpCfg.from.trim()) || process.env.SMTP_FROM || process.env.EMAIL_FROM || 'noreply@tucdesk.app';
115
+ const smtpFromName = (smtpCfg?.fromName && smtpCfg.fromName.trim()) || process.env.SMTP_FROM_NAME || branding.brandName;
116
+ if (smtpHost && smtpUser && smtpPass) {
117
+ return sendViaSMTP({
118
+ smtpHost,
119
+ smtpPort,
120
+ smtpUser,
121
+ smtpPass,
122
+ fromEmail: smtpFrom,
123
+ fromName: smtpFromName,
124
+ }, payload.to, payload.title, html, branding.brandName);
125
+ }
126
+ // Tenant-level config (Workspace → Integrations → Email).
127
+ const tenantCfg = await getTenantConfig(payload.tenantId);
128
+ if (tenantCfg?.smtpHost && tenantCfg.smtpUser && tenantCfg.smtpPass) {
129
+ return sendViaSMTP(tenantCfg, payload.to, payload.title, html, branding.brandName);
130
+ }
131
+ if (tenantCfg?.resendApiKey) {
132
+ return sendViaResend(tenantCfg.resendApiKey, tenantCfg, payload.to, payload.title, html, branding.brandName);
133
+ }
134
+ // Last resort: env Resend key.
135
+ const resendKey = process.env.RESEND_API_KEY;
136
+ if (resendKey) {
137
+ return sendViaResend(resendKey, null, payload.to, payload.title, html, branding.brandName);
138
+ }
139
+ console.warn(`[email] No email provider configured — "${payload.title}" to ${payload.to} not sent.`);
140
+ return { sent: false, reason: 'No email provider configured. Configure one in Workspace → Integrations → Email, or set RESEND_API_KEY / SMTP_* on the server.' };
141
+ }
142
+ async function sendViaResend(apiKey, cfg, to, subject, html, brandName = DEFAULT_BRAND_NAME) {
143
+ const from = cfg?.fromEmail
144
+ ? `${cfg.fromName ?? brandName} <${cfg.fromEmail}>`
145
+ : (process.env.EMAIL_FROM || `${brandName} <onboarding@resend.dev>`);
146
+ try {
147
+ const resp = await fetch('https://api.resend.com/emails', {
148
+ method: 'POST',
149
+ headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
150
+ body: JSON.stringify({ from, to: [to], subject, html }),
151
+ });
152
+ if (!resp.ok) {
153
+ const detail = await resp.text();
154
+ let hint = '';
155
+ if (resp.status === 403)
156
+ hint = ' — likely cause: sending domain not verified in Resend dashboard, or API key lacks send permission.';
157
+ if (resp.status === 422)
158
+ hint = ' — likely cause: "from" domain not verified in Resend. Add & verify your domain at resend.com/domains.';
159
+ console.error(`[email] Resend ${resp.status}:`, detail);
160
+ return { sent: false, reason: `Resend rejected the email (${resp.status})${hint} Detail: ${detail}` };
161
+ }
162
+ return { sent: true };
163
+ }
164
+ catch (err) {
165
+ console.error('[email] Resend error:', err);
166
+ return { sent: false, reason: `Resend request failed: ${err?.message ?? err}` };
167
+ }
168
+ }
169
+ async function sendViaSMTP(cfg, to, subject, html, brandName = DEFAULT_BRAND_NAME) {
170
+ try {
171
+ const transport = nodemailer_1.default.createTransport({
172
+ host: cfg.smtpHost,
173
+ port: cfg.smtpPort ?? 587,
174
+ secure: (cfg.smtpPort ?? 587) === 465,
175
+ auth: { user: cfg.smtpUser, pass: cfg.smtpPass },
176
+ });
177
+ // Use EMAIL_FROM env var if set; otherwise fall back to smtpUser address (standard practice).
178
+ // Set EMAIL_FROM=Name <user@domain.com> in env for a clean sender display name.
179
+ const from = cfg.fromEmail
180
+ ? `${cfg.fromName ?? brandName} <${cfg.fromEmail}>`
181
+ : (process.env.EMAIL_FROM ?? `${brandName} <${cfg.smtpUser ?? ''}>`);
182
+ await transport.sendMail({ from, to, subject, html });
183
+ return { sent: true };
184
+ }
185
+ catch (err) {
186
+ console.error('[email] SMTP error:', err);
187
+ return { sent: false, reason: `SMTP send failed: ${err?.message ?? err}` };
188
+ }
189
+ }
190
+ /** Internal metadata keys that should never be shown as raw table rows in emails. */
191
+ const HIDDEN_META_KEYS = new Set(['tenantId', 'joinUrl', 'projectId', 'runId']);
192
+ function buildHtml(p, branding) {
193
+ const { brandName, brandColor, appUrl } = branding;
194
+ const joinUrl = p.metadata?.joinUrl;
195
+ // For invite emails, fall back to the workspace dashboard when no explicit joinUrl is set.
196
+ const ctaUrl = (p.type === 'invite.created')
197
+ ? (joinUrl ?? `${appUrl}/dashboard`)
198
+ : joinUrl;
199
+ const metaHtml = p.metadata
200
+ ? Object.entries(p.metadata)
201
+ .filter(([k, v]) => v != null && !HIDDEN_META_KEYS.has(k))
202
+ .map(([k, v]) => `<tr><td style="color:#888;padding:4px 0;width:120px">${k}</td><td>${v}</td></tr>`)
203
+ .join('')
204
+ : '';
205
+ const ctaHtml = ctaUrl
206
+ ? `<div style="margin-top:24px;text-align:center">
207
+ <a href="${ctaUrl}"
208
+ style="display:inline-block;background:${brandColor};color:#000;font-weight:700;font-size:14px;
209
+ text-decoration:none;padding:12px 28px;border-radius:8px;letter-spacing:0.3px">
210
+ Go to Workspace
211
+ </a>
212
+ </div>`
213
+ : '';
214
+ return `<!DOCTYPE html>
215
+ <html>
216
+ <head><meta charset="utf-8"/><meta name="viewport" content="width=device-width"/></head>
217
+ <body style="font-family:system-ui,sans-serif;background:#0d0d0d;color:#e0e0e0;margin:0;padding:32px 16px">
218
+ <div style="max-width:560px;margin:0 auto">
219
+ <div style="margin-bottom:24px">
220
+ <span style="font-size:22px;font-weight:700;letter-spacing:-0.5px;color:${brandColor}">${brandName}</span>
221
+ </div>
222
+ <div style="background:#161616;border:1px solid #2a2a2a;border-radius:12px;padding:28px">
223
+ <h2 style="margin:0 0 12px;font-size:18px;font-weight:600">${p.title}</h2>
224
+ <p style="margin:0 0 20px;color:#bbb;line-height:1.6">${p.body}</p>
225
+ ${metaHtml ? `<table style="font-size:13px;border-collapse:collapse;width:100%">${metaHtml}</table>` : ''}
226
+ ${ctaHtml}
227
+ </div>
228
+ <p style="font-size:11px;color:#555;margin-top:20px;text-align:center">
229
+ ${brandName} Quality Intelligence · <a href="${appUrl}/settings/notifications" style="color:#555">Manage notifications</a>
230
+ </p>
231
+ </div>
232
+ </body>
233
+ </html>`;
234
+ }
@@ -0,0 +1,30 @@
1
+ import type { GroundingContext } from '@taodigital/core';
2
+ /**
3
+ * GROUNDING — how the AI gets real facts before it generates anything.
4
+ *
5
+ * Strategy is swappable. Two tiers exist:
6
+ *
7
+ * DirectLoadGrounding (default):
8
+ * Loads ALL relevant skill files for a screen in one query.
9
+ * Fast and precise for < 300 screens. No vector DB needed.
10
+ * Taxonomy retrieval: PLATFORM+FUNCTION+DESIGN+THEME always,
11
+ * COMPONENT+ELEMENT filtered to matching screenIds.
12
+ *
13
+ * VectorSearchGrounding (Phase 2+):
14
+ * For projects flagged `oversized = true` (300+ screens).
15
+ * Runs a semantic similarity search on skill file summaries to find
16
+ * the top-K most relevant COMPONENT/ELEMENT files.
17
+ * Global tier (PLATFORM/FUNCTION/DESIGN/THEME) still loads directly.
18
+ */
19
+ export interface GroundingStrategy {
20
+ ground(tenantId: string, projectId: string, screenId: string): Promise<GroundingContext>;
21
+ }
22
+ export declare class DirectLoadGrounding implements GroundingStrategy {
23
+ ground(tenantId: string, projectId: string, screenId: string): Promise<GroundingContext>;
24
+ }
25
+ /** BM25-backed grounding for oversized projects (300+ screens). */
26
+ export declare class VectorSearchGrounding implements GroundingStrategy {
27
+ ground(tenantId: string, projectId: string, screenId: string): Promise<GroundingContext>;
28
+ }
29
+ /** Pick grounding strategy. Oversized (300+ screens) uses BM25; others load directly. */
30
+ export declare function groundingFor(oversized: boolean): GroundingStrategy;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VectorSearchGrounding = exports.DirectLoadGrounding = void 0;
4
+ exports.groundingFor = groundingFor;
5
+ const repository_js_1 = require("./repository.js");
6
+ const reranker_1 = require("@taodigital/reranker");
7
+ const skill_file_kinds_js_1 = require("./skill-file-kinds.js");
8
+ const SELECTIVE_KINDS = new Set(skill_file_kinds_js_1.SKILL_FILE_SELECTIVE_KINDS);
9
+ const MAX_SELECTIVE = 5;
10
+ class DirectLoadGrounding {
11
+ async ground(tenantId, projectId, screenId) {
12
+ const [screen, rawSkillFiles, probeObs, outgoingTransitions] = await Promise.all([
13
+ repository_js_1.AppBrain.getScreenWithElements(tenantId, screenId),
14
+ repository_js_1.AppBrain.getRelevantSkillFilesForScreen(tenantId, projectId, screenId),
15
+ repository_js_1.AppBrain.getProbeObservations(tenantId, screenId),
16
+ repository_js_1.AppBrain.getScreenOutgoingTransitions(tenantId, screenId),
17
+ ]);
18
+ if (!screen)
19
+ throw new Error(`Screen ${screenId} not found in Zeta Core`);
20
+ const s = screen;
21
+ const global = rawSkillFiles.filter((sf) => !SELECTIVE_KINDS.has(sf.kind));
22
+ const selective = rawSkillFiles.filter((sf) => SELECTIVE_KINDS.has(sf.kind));
23
+ // Rerank selective skill files by semantic relevance when multiple candidates exist
24
+ let rankedSelective = selective;
25
+ if (selective.length > MAX_SELECTIVE) {
26
+ const screenQuery = [s.name, s.url, ...s.elements.map((e) => e.meaning)].filter(Boolean).join(' ');
27
+ try {
28
+ const reranked = await (0, reranker_1.rerankStrings)(screenQuery, selective.map((sf) => `${sf.kind} ${sf.summary ?? ''} ${sf.content.slice(0, 400)}`), MAX_SELECTIVE, 0);
29
+ rankedSelective = reranked.map(({ index }) => selective[index]);
30
+ }
31
+ catch {
32
+ rankedSelective = selective.slice(0, MAX_SELECTIVE);
33
+ }
34
+ }
35
+ const skillFiles = [...global, ...rankedSelective].map((sf) => ({
36
+ kind: sf.kind,
37
+ summary: sf.summary ?? undefined,
38
+ content: sf.content,
39
+ version: sf.version,
40
+ approvedBy: sf.approvedBy ?? undefined,
41
+ }));
42
+ const skillFile = skillFiles.length > 0
43
+ ? skillFiles.map((sf) => `### ${sf.kind}\n${sf.content}`).join('\n\n')
44
+ : undefined;
45
+ return {
46
+ screenName: s.name,
47
+ url: s.url ?? undefined,
48
+ elements: s.elements.map((e) => ({
49
+ meaning: e.meaning,
50
+ role: e.role,
51
+ expectedData: e.expectedData ?? undefined,
52
+ notes: e.notes ?? undefined,
53
+ boundingRect: e.boundingRect ?? undefined,
54
+ ariaState: e.ariaState ?? undefined,
55
+ parentLandmark: e.parentLandmark ?? undefined,
56
+ })),
57
+ relatedFlows: outgoingTransitions.map((t) => {
58
+ const dest = t.toScreen?.name ?? 'unknown screen';
59
+ const action = typeof t.action === 'object' && t.action?.description
60
+ ? t.action.description
61
+ : JSON.stringify(t.action ?? {});
62
+ return `Navigate to "${dest}" via: ${action}`;
63
+ }),
64
+ skillFiles,
65
+ skillFile,
66
+ recentChanges: [],
67
+ probeObservations: probeObs.length > 0 ? probeObs : undefined,
68
+ viewport: undefined,
69
+ };
70
+ }
71
+ }
72
+ exports.DirectLoadGrounding = DirectLoadGrounding;
73
+ /** BM25-backed grounding for oversized projects (300+ screens). */
74
+ class VectorSearchGrounding {
75
+ async ground(tenantId, projectId, screenId) {
76
+ const [screen, probeObs, outgoingTransitions] = await Promise.all([
77
+ repository_js_1.AppBrain.getScreenWithElements(tenantId, screenId),
78
+ repository_js_1.AppBrain.getProbeObservations(tenantId, screenId),
79
+ repository_js_1.AppBrain.getScreenOutgoingTransitions(tenantId, screenId),
80
+ ]);
81
+ if (!screen)
82
+ throw new Error(`Screen ${screenId} not found`);
83
+ const globalFiles = await repository_js_1.AppBrain.listSkillFiles(tenantId, projectId, skill_file_kinds_js_1.SKILL_FILE_GLOBAL_KINDS);
84
+ const query = [screen.name, ...screen.elements.map((e) => e.meaning)].filter(Boolean).join(' ');
85
+ const searchResults = await repository_js_1.AppBrain.searchKnowledge(tenantId, projectId, query, 5);
86
+ const globalEntries = globalFiles.map((sf) => ({
87
+ kind: sf.kind,
88
+ summary: sf.summary ?? undefined,
89
+ content: sf.content,
90
+ version: sf.version,
91
+ approvedBy: sf.approvedBy ?? undefined,
92
+ }));
93
+ const selectiveEntries = searchResults
94
+ .filter((r) => r.source === 'SKILL_FILE')
95
+ .map((r) => ({
96
+ kind: r.label,
97
+ content: r.content,
98
+ summary: undefined,
99
+ version: 1,
100
+ approvedBy: undefined,
101
+ }));
102
+ const skillFiles = [...globalEntries, ...selectiveEntries];
103
+ const skillFile = skillFiles.length > 0
104
+ ? skillFiles.map((sf) => `### ${sf.kind}\n${sf.content}`).join('\n\n')
105
+ : undefined;
106
+ const sv = screen;
107
+ return {
108
+ screenName: sv.name,
109
+ url: sv.url ?? undefined,
110
+ elements: sv.elements.map((e) => ({
111
+ meaning: e.meaning,
112
+ role: e.role,
113
+ expectedData: e.expectedData ?? undefined,
114
+ notes: e.notes ?? undefined,
115
+ boundingRect: e.boundingRect ?? undefined,
116
+ ariaState: e.ariaState ?? undefined,
117
+ parentLandmark: e.parentLandmark ?? undefined,
118
+ })),
119
+ relatedFlows: outgoingTransitions.map((t) => {
120
+ const dest = t.toScreen?.name ?? 'unknown screen';
121
+ const action = typeof t.action === 'object' && t.action?.description
122
+ ? t.action.description
123
+ : JSON.stringify(t.action ?? {});
124
+ return `Navigate to "${dest}" via: ${action}`;
125
+ }),
126
+ skillFiles,
127
+ skillFile,
128
+ recentChanges: [],
129
+ probeObservations: probeObs.length > 0 ? probeObs : undefined,
130
+ viewport: undefined,
131
+ };
132
+ }
133
+ }
134
+ exports.VectorSearchGrounding = VectorSearchGrounding;
135
+ /** Pick grounding strategy. Oversized (300+ screens) uses BM25; others load directly. */
136
+ function groundingFor(oversized) {
137
+ return oversized ? new VectorSearchGrounding() : new DirectLoadGrounding();
138
+ }
@@ -0,0 +1,13 @@
1
+ export * from './repository.js';
2
+ export { buildNavigationGraph, toMermaid } from './nav-graph.js';
3
+ export type { NavGraph, NavGraphNode, NavGraphEdge } from './nav-graph.js';
4
+ export * from './grounding.js';
5
+ export * from './token-ledger.js';
6
+ export * from './cloud-storage.js';
7
+ export * from './orphan-classification.js';
8
+ export * from './email.js';
9
+ export * from './notifications.js';
10
+ export * from './run-notifications.js';
11
+ export * from './traceability.js';
12
+ export { rerankStrings } from '@taodigital/reranker';
13
+ export declare function rebuildGherkinOutlineForCase(tenantId: string, testCaseId: string): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.rerankStrings = exports.toMermaid = exports.buildNavigationGraph = void 0;
18
+ exports.rebuildGherkinOutlineForCase = rebuildGherkinOutlineForCase;
19
+ __exportStar(require("./repository.js"), exports);
20
+ var nav_graph_js_1 = require("./nav-graph.js");
21
+ Object.defineProperty(exports, "buildNavigationGraph", { enumerable: true, get: function () { return nav_graph_js_1.buildNavigationGraph; } });
22
+ Object.defineProperty(exports, "toMermaid", { enumerable: true, get: function () { return nav_graph_js_1.toMermaid; } });
23
+ __exportStar(require("./grounding.js"), exports);
24
+ __exportStar(require("./token-ledger.js"), exports);
25
+ __exportStar(require("./cloud-storage.js"), exports);
26
+ __exportStar(require("./orphan-classification.js"), exports);
27
+ __exportStar(require("./email.js"), exports);
28
+ __exportStar(require("./notifications.js"), exports);
29
+ __exportStar(require("./run-notifications.js"), exports);
30
+ __exportStar(require("./traceability.js"), exports);
31
+ var reranker_1 = require("@taodigital/reranker");
32
+ Object.defineProperty(exports, "rerankStrings", { enumerable: true, get: function () { return reranker_1.rerankStrings; } });
33
+ // R2/H4: Standalone export so callers can import rebuildGherkinOutlineForCase by name
34
+ const repository_js_1 = require("./repository.js");
35
+ function rebuildGherkinOutlineForCase(tenantId, testCaseId) {
36
+ return repository_js_1.AppBrain.rebuildGherkinOutlineForCase(tenantId, testCaseId);
37
+ }
@@ -0,0 +1,42 @@
1
+ export interface NavGraphNode {
2
+ id: string;
3
+ name: string;
4
+ url: string | null;
5
+ section: string;
6
+ sectionColor?: string | null;
7
+ groupId?: string | null;
8
+ depth: number | null;
9
+ entryPoint: boolean;
10
+ discoveryMethod: string;
11
+ }
12
+ export interface NavGraphEdge {
13
+ id: string;
14
+ fromScreenId: string;
15
+ toScreenId: string | null;
16
+ href: string;
17
+ text: string | null;
18
+ landmark: string | null;
19
+ linkType: string;
20
+ source: string;
21
+ confidence: number;
22
+ isBackEdge: boolean;
23
+ }
24
+ export interface NavGraph {
25
+ nodes: NavGraphNode[];
26
+ edges: NavGraphEdge[];
27
+ entryPoints: string[];
28
+ orphans: string[];
29
+ sections: Record<string, string[]>;
30
+ sectionColors?: Record<string, string>;
31
+ stats: {
32
+ totalNodes: number;
33
+ totalEdges: number;
34
+ maxDepth: number | null;
35
+ sections: string[];
36
+ };
37
+ }
38
+ export declare function buildNavigationGraph(tenantId: string, projectId: string, opts?: {
39
+ jobId?: string;
40
+ maxDepth?: number;
41
+ }): Promise<NavGraph>;
42
+ export declare function toMermaid(graph: NavGraph): string;