@taodigital/app-brain 0.0.0-stage → 1.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.
- package/README.md +13 -2
- package/dist/archetype-classification.d.ts +54 -0
- package/dist/archetype-classification.js +95 -0
- package/dist/cloud-storage.d.ts +106 -0
- package/dist/cloud-storage.js +539 -0
- package/dist/email.d.ts +21 -0
- package/dist/email.js +234 -0
- package/dist/grounding.d.ts +30 -0
- package/dist/grounding.js +138 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +37 -0
- package/dist/nav-graph.d.ts +42 -0
- package/dist/nav-graph.js +248 -0
- package/dist/notifications.d.ts +26 -0
- package/dist/notifications.js +105 -0
- package/dist/orphan-classification.d.ts +32 -0
- package/dist/orphan-classification.js +40 -0
- package/dist/repository.d.ts +3185 -0
- package/dist/repository.js +5927 -0
- package/dist/run-notifications.d.ts +16 -0
- package/dist/run-notifications.js +244 -0
- package/dist/skill-file-kinds.d.ts +5 -0
- package/dist/skill-file-kinds.js +50 -0
- package/dist/token-ledger.d.ts +23 -0
- package/dist/token-ledger.js +178 -0
- package/dist/traceability.d.ts +213 -0
- package/dist/traceability.js +339 -0
- package/package.json +24 -4
- package/src/archetype-classification.test.ts +91 -0
- package/src/archetype-classification.ts +132 -0
- package/src/cloud-storage.test.ts +83 -0
- package/src/cloud-storage.ts +627 -0
- package/src/email.ts +268 -0
- package/src/grounding.ts +172 -0
- package/src/index.ts +18 -0
- package/src/nav-graph.ts +295 -0
- package/src/notifications.ts +156 -0
- package/src/orphan-classification.test.ts +61 -0
- package/src/orphan-classification.ts +65 -0
- package/src/orphan-screens.test.ts +88 -0
- package/src/repository.ts +7397 -0
- package/src/run-notifications.ts +281 -0
- package/src/skill-file-kinds.ts +51 -0
- package/src/token-ledger.ts +209 -0
- package/src/traceability.test.ts +75 -0
- package/src/traceability.ts +477 -0
- package/tsconfig.json +9 -0
package/README.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @detiq/app-brain — The Moat
|
|
2
2
|
|
|
3
|
-
|
|
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 {};
|