@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/README.md CHANGED
@@ -1,3 +1,14 @@
1
- # Temporary Holding Version
1
+ # @detiq/app-brain — The Moat
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Zeta Core is the project's permanent, structured, exportable knowledge.
4
+
5
+ ## Grounding (the important bit)
6
+ `grounding.ts` defines how the AI gets real facts before generating tests.
7
+ - **Default: `DirectLoadGrounding`** — fetch the exact facts for a screen and
8
+ load them directly. Simple, precise, cheap. Best for a closed workspace.
9
+ - **Later: `VectorSearchGrounding`** (RAG) — added ONLY for `oversized`
10
+ projects whose knowledge is too big to load directly. Same Zeta Core,
11
+ smarter finder. Agents don't change — only the strategy swaps.
12
+
13
+ > Rule: agents call `ground(...)` and never invent elements. If a fact isn't
14
+ > in the returned context, the agent asks instead of guessing.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Screen-archetype classification + KAG journey-pair suggestion — pure logic,
3
+ * no I/O, unit-testable in isolation (same shape as orphan-classification.ts).
4
+ *
5
+ * Rule-based rather than LLM-based on purpose: this only needs to look at
6
+ * data already captured during crawl (Element.role/expectedData, Screen.url/
7
+ * name), so it works with zero LLM provider configured and costs nothing to
8
+ * run on every project. It is deliberately coarse — good enough to rank
9
+ * "which screen pairs are worth generating a KAG journey test for" so a user
10
+ * doesn't have to already know both screen ids, not a general-purpose UI
11
+ * classifier.
12
+ */
13
+ export type ScreenArchetype = 'LOGIN' | 'SIGNUP' | 'SEARCH' | 'CHECKOUT' | 'FORM' | 'GENERIC';
14
+ export interface ClassifiableElement {
15
+ role: string | null;
16
+ expectedData: string | null;
17
+ }
18
+ export interface ClassifiableScreen {
19
+ id: string;
20
+ name: string;
21
+ url: string | null;
22
+ }
23
+ export interface ArchetypeResult {
24
+ archetype: ScreenArchetype;
25
+ reason: string;
26
+ }
27
+ /** Classifies a single screen from its URL/name and the elements crawled on it.
28
+ * Checked in priority order — the first matching, most specific signal wins,
29
+ * since e.g. a checkout page often also has a form field or two. */
30
+ export declare function classifyScreenArchetype(screen: ClassifiableScreen, elements: ClassifiableElement[]): ArchetypeResult;
31
+ export interface ClassifiedScreen {
32
+ id: string;
33
+ name: string;
34
+ archetype: ScreenArchetype;
35
+ }
36
+ export interface JourneyCandidate {
37
+ startScreenId: string;
38
+ startScreenName: string;
39
+ startArchetype: ScreenArchetype;
40
+ endScreenId: string;
41
+ endScreenName: string;
42
+ endArchetype: ScreenArchetype;
43
+ label: string;
44
+ }
45
+ /**
46
+ * Finds every (start, end) screen pair whose archetypes match one of the
47
+ * interesting-journey rules. Pure and graph-agnostic on purpose — this does
48
+ * NOT check whether a KAG path actually connects the two screens (that
49
+ * requires a DB-backed BFS, see AppBrain.suggestJourneys in repository.ts,
50
+ * which calls this first to get a small, ranked candidate list and only then
51
+ * pays for a graph traversal per candidate instead of checking every
52
+ * archetype pair against every other screen).
53
+ */
54
+ export declare function matchInterestingJourneyPairs(screens: ClassifiedScreen[]): JourneyCandidate[];
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Screen-archetype classification + KAG journey-pair suggestion — pure logic,
4
+ * no I/O, unit-testable in isolation (same shape as orphan-classification.ts).
5
+ *
6
+ * Rule-based rather than LLM-based on purpose: this only needs to look at
7
+ * data already captured during crawl (Element.role/expectedData, Screen.url/
8
+ * name), so it works with zero LLM provider configured and costs nothing to
9
+ * run on every project. It is deliberately coarse — good enough to rank
10
+ * "which screen pairs are worth generating a KAG journey test for" so a user
11
+ * doesn't have to already know both screen ids, not a general-purpose UI
12
+ * classifier.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.classifyScreenArchetype = classifyScreenArchetype;
16
+ exports.matchInterestingJourneyPairs = matchInterestingJourneyPairs;
17
+ const LOGIN_URL_RE = /login|signin|sign-in/i;
18
+ const SIGNUP_URL_RE = /signup|sign-up|register|onboard/i;
19
+ const CHECKOUT_URL_RE = /checkout|cart|basket/i;
20
+ // Element.role values observed at crawl time (crawler.ts) are lowercase HTML
21
+ // input types or tag/aria names — see that file for exactly how they're set.
22
+ const FORM_FIELD_ROLES = new Set(['text', 'email', 'tel', 'number', 'date', 'textarea', 'select']);
23
+ /** Classifies a single screen from its URL/name and the elements crawled on it.
24
+ * Checked in priority order — the first matching, most specific signal wins,
25
+ * since e.g. a checkout page often also has a form field or two. */
26
+ function classifyScreenArchetype(screen, elements) {
27
+ const hasPassword = elements.some((e) => e.role === 'password' || e.expectedData === 'password');
28
+ const hasEmail = elements.some((e) => e.role === 'email' || e.expectedData === 'valid_email');
29
+ const hasSearch = elements.some((e) => e.role === 'search' || e.expectedData === 'search_term');
30
+ const urlOrName = `${screen.url ?? ''} ${screen.name ?? ''}`;
31
+ if (CHECKOUT_URL_RE.test(urlOrName)) {
32
+ return { archetype: 'CHECKOUT', reason: "URL/name matches checkout/cart/basket" };
33
+ }
34
+ if (SIGNUP_URL_RE.test(urlOrName) && hasEmail && hasPassword) {
35
+ return { archetype: 'SIGNUP', reason: 'URL/name matches signup/register, with email + password fields present' };
36
+ }
37
+ if (hasPassword) {
38
+ return { archetype: 'LOGIN', reason: 'Has a password-type input element' };
39
+ }
40
+ if (LOGIN_URL_RE.test(urlOrName)) {
41
+ return { archetype: 'LOGIN', reason: 'URL/name matches login/signin' };
42
+ }
43
+ if (hasSearch) {
44
+ return { archetype: 'SEARCH', reason: 'Has a search-type input element' };
45
+ }
46
+ if (elements.some((e) => e.role && FORM_FIELD_ROLES.has(e.role))) {
47
+ return { archetype: 'FORM', reason: 'Has one or more data-entry form fields' };
48
+ }
49
+ return { archetype: 'GENERIC', reason: 'No distinguishing signals found' };
50
+ }
51
+ /** Archetype pairs worth generating a cross-screen journey test for, ranked
52
+ * by how commonly they represent a real user flow. Order matters — earlier
53
+ * entries are suggested before later ones when a project has many candidate
54
+ * pairs. GENERIC is deliberately excluded from both sides: with no
55
+ * distinguishing signal, pairing it with anything is a guess, not a
56
+ * suggestion. */
57
+ const INTERESTING_JOURNEYS = [
58
+ { from: 'LOGIN', to: 'CHECKOUT', label: 'Login → Checkout' },
59
+ { from: 'SIGNUP', to: 'LOGIN', label: 'Signup → Login' },
60
+ { from: 'SEARCH', to: 'CHECKOUT', label: 'Search → Checkout' },
61
+ { from: 'LOGIN', to: 'FORM', label: 'Login → Form submission' },
62
+ { from: 'SEARCH', to: 'FORM', label: 'Search → Form submission' },
63
+ ];
64
+ /**
65
+ * Finds every (start, end) screen pair whose archetypes match one of the
66
+ * interesting-journey rules. Pure and graph-agnostic on purpose — this does
67
+ * NOT check whether a KAG path actually connects the two screens (that
68
+ * requires a DB-backed BFS, see AppBrain.suggestJourneys in repository.ts,
69
+ * which calls this first to get a small, ranked candidate list and only then
70
+ * pays for a graph traversal per candidate instead of checking every
71
+ * archetype pair against every other screen).
72
+ */
73
+ function matchInterestingJourneyPairs(screens) {
74
+ const candidates = [];
75
+ for (const rule of INTERESTING_JOURNEYS) {
76
+ const starts = screens.filter((s) => s.archetype === rule.from);
77
+ const ends = screens.filter((s) => s.archetype === rule.to);
78
+ for (const start of starts) {
79
+ for (const end of ends) {
80
+ if (start.id === end.id)
81
+ continue;
82
+ candidates.push({
83
+ startScreenId: start.id,
84
+ startScreenName: start.name,
85
+ startArchetype: start.archetype,
86
+ endScreenId: end.id,
87
+ endScreenName: end.name,
88
+ endArchetype: end.archetype,
89
+ label: rule.label,
90
+ });
91
+ }
92
+ }
93
+ }
94
+ return candidates;
95
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Cloud Storage integration for ZeTa-AI artifacts.
3
+ *
4
+ * Folder convention:
5
+ * {prefix}/{tenantId}/{projectId}/screenshots/{filename}
6
+ * {prefix}/{tenantId}/{projectId}/test-plans/{filename}
7
+ * {prefix}/{tenantId}/{projectId}/test-data/{filename}
8
+ * {prefix}/{tenantId}/{projectId}/reports/{runId}/{filename}
9
+ *
10
+ * Industry reference: Playwright Cloud, BrowserStack, Cypress Cloud all use
11
+ * tenant→project→type→artifact path hierarchies for multi-tenant isolation.
12
+ */
13
+ type StorageKind = 'S3' | 'R2' | 'GDRIVE' | 'DROPBOX';
14
+ interface StorageConfig {
15
+ kind: StorageKind;
16
+ creds: Record<string, string>;
17
+ prefix: string;
18
+ }
19
+ export interface TenantStorageConfig {
20
+ provider: 'S3' | 'R2';
21
+ bucket: string;
22
+ region?: string;
23
+ accountId?: string;
24
+ endpointUrl?: string;
25
+ accessKeyId: string;
26
+ secretAccessKey: string;
27
+ publicUrl?: string;
28
+ prefix?: string;
29
+ }
30
+ /**
31
+ * Resolves the storage config to use for a tenant's screenshots/artifacts:
32
+ * the tenant's own connector if they've configured one, otherwise the
33
+ * platform-managed default bucket (if configured via env vars), otherwise
34
+ * null — meaning the caller should leave the artifact on local disk.
35
+ */
36
+ export declare function getTenantStorageConfig(tenantId: string): Promise<TenantStorageConfig | null>;
37
+ /**
38
+ * Upload a screenshot to tenant-level S3/R2 storage.
39
+ * Returns public URL if publicUrl configured, otherwise returns the storage key
40
+ * (callers should use resolveCloudUrl() to generate presigned URLs on demand).
41
+ */
42
+ export declare function uploadScreenshotToTenantStorage(cfg: TenantStorageConfig, localPath: string, screenshotKey: string): Promise<string>;
43
+ /**
44
+ * Upload any file to tenant-level S3/R2 storage.
45
+ * Returns public URL if publicUrl configured, otherwise returns the storage key.
46
+ */
47
+ export declare function uploadFileToTenantStorage(cfg: TenantStorageConfig, localPath: string, storageKey: string, contentType: string): Promise<string>;
48
+ /**
49
+ * Resolve a stored cloudUrl to a usable URL for the browser.
50
+ * - If it starts with https:// → return as-is (public URL, no expiry).
51
+ * - Otherwise → treat as storage key and generate a presigned GET URL.
52
+ */
53
+ export declare function resolveCloudUrl(cfg: TenantStorageConfig, cloudUrl: string, expiresIn?: number): Promise<string | null>;
54
+ export declare function getProjectStorageConfig(tenantId: string, projectId: string): Promise<StorageConfig | null>;
55
+ /** Build the cloud storage key for an artifact. */
56
+ export declare function buildArtifactKey(tenantId: string, projectId: string, type: string, filename: string, prefix?: string): string;
57
+ /** Upload a single file to the configured cloud storage provider. Returns storage key/path. */
58
+ export declare function uploadArtifact(cfg: StorageConfig, localPath: string, storageKey: string, contentType?: string): Promise<string>;
59
+ /** Upload a Buffer (for generated content like test plans, scripts). */
60
+ export declare function uploadArtifactBuffer(cfg: StorageConfig, buffer: Buffer, storageKey: string, contentType?: string): Promise<string>;
61
+ /**
62
+ * Generate a presigned GET URL for S3/R2 (time-limited, auth-gated).
63
+ * GDrive/Dropbox: returns null (caller must use downloadBufferFromTenantStorage + proxy).
64
+ */
65
+ export declare function generatePresignedGetUrl(cfg: TenantStorageConfig, storageKey: string, expiresIn?: number): Promise<string | null>;
66
+ /**
67
+ * Download raw bytes from tenant storage (for GDrive/Dropbox proxy, or fallback).
68
+ */
69
+ export declare function downloadBufferFromTenantStorage(cfg: TenantStorageConfig, storageKey: string): Promise<Buffer>;
70
+ /** Upload a buffer to tenant storage. Returns the storage key. */
71
+ export declare function uploadBufferToTenantStorage(cfg: TenantStorageConfig, buffer: Buffer, storageKey: string, contentType?: string): Promise<string>;
72
+ /** Build the canonical storage key for a knowledge artifact. */
73
+ export declare function buildKnowledgeKey(tenantId: string, projectId: string, artifactId: string, filename: string, prefix?: string): string;
74
+ export interface StorageObject {
75
+ key: string;
76
+ size: number;
77
+ lastModified: string | null;
78
+ }
79
+ /** List all S3/R2 objects under a project prefix. Paginates automatically. */
80
+ export declare function listProjectStorageObjects(cfg: TenantStorageConfig, tenantId: string, projectId: string): Promise<StorageObject[]>;
81
+ /**
82
+ * Delete all S3/R2 objects under a project prefix (GDPR cleanup on project delete).
83
+ * Batch-deletes in groups of 1 000 (S3 DeleteObjects limit).
84
+ * No-ops silently when cfg is null.
85
+ */
86
+ export declare function deleteProjectStorageObjects(tenantId: string, projectId: string): Promise<void>;
87
+ /**
88
+ * Delete all S3/R2 objects for an entire tenant (GDPR cleanup on tenant delete).
89
+ * Lists by tenant prefix and batch-deletes everything found.
90
+ * No-ops silently when cfg is null.
91
+ */
92
+ export declare function deleteTenantStorageObjects(tenantId: string): Promise<void>;
93
+ /** Build a content object key for a specific entity type. */
94
+ export declare function buildContentKey(tenantId: string, projectId: string, type: 'scripts' | 'artifacts' | 'testcases', entityId: string, ext: string, prefix?: string): string;
95
+ /**
96
+ * Persist string content to tenant S3 if configured.
97
+ * Returns the storage key on success, or null if no S3 is configured
98
+ * (caller should keep content in the DB column in that case).
99
+ */
100
+ export declare function persistContent(tenantId: string, projectId: string, type: 'scripts' | 'artifacts' | 'testcases', entityId: string, ext: string, content: string, mimeType?: string): Promise<string | null>;
101
+ /**
102
+ * Resolve content: if a storage key is provided, download from S3.
103
+ * Otherwise (or on error) return the fallback DB content.
104
+ */
105
+ export declare function resolveContent(tenantId: string, storageKey: string | null | undefined, fallback: string): Promise<string>;
106
+ export {};