testeiya 0.4.3 → 0.4.5
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 +21 -1
- package/dist/prompt/clis.js +38 -0
- package/dist/prompt/clis.js.map +1 -0
- package/dist/prompt/comment-thread.js +56 -44
- package/dist/prompt/comment-thread.js.map +1 -1
- package/dist/prompt/system-prompt.js +154 -174
- package/dist/prompt/system-prompt.js.map +1 -1
- package/dist/src/doctor.js +9 -1
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/langfuse-extension.js +135 -0
- package/dist/src/langfuse-extension.js.map +1 -0
- package/dist/src/langfuse.js +143 -0
- package/dist/src/langfuse.js.map +1 -0
- package/dist/src/output.js +48 -3
- package/dist/src/output.js.map +1 -1
- package/dist/src/run.js +36 -12
- package/dist/src/run.js.map +1 -1
- package/dist/src/session.js +22 -8
- package/dist/src/session.js.map +1 -1
- package/package.json +1 -1
- package/prompt/clis.ts +45 -0
- package/prompt/comment-thread.ts +65 -47
- package/prompt/system-prompt.ts +182 -184
- package/skills/playwright/playwright-cli/SKILL.md +49 -0
- package/skills/playwright/playwright-cli/references/pr-attachments.md +60 -0
- package/skills/playwright/playwright-cli/references/session-management.md +2 -0
- package/skills/playwright/playwright-cli/references/video-recording.md +12 -0
- package/skills/skills.lock.json +3 -2
- package/skills/testomatio/requirements/write-user-story/SKILL.md +7 -10
- package/skills/testomatio/test-management/migrate-to-testomatio/SKILL.md +52 -0
- package/skills/testomatio/test-management/migrate-to-testomatio/references/CSV_MIGRATION.md +72 -0
- package/skills/testomatio/test-management/migrate-to-testomatio/references/CUSTOM_MIGRATION.md +11 -0
- package/skills/testomatio/test-management/migrate-to-testomatio/references/TESTRAIL_MIGRATION.md +87 -0
- package/skills/testomatio/test-management/migrate-to-testomatio/references/XRAY_MIGRATION.md +56 -0
- package/skills/testomatio/test-management/scan-automation-project/SKILL.md +103 -26
- package/dist/prompt/context.js +0 -79
- package/dist/prompt/context.js.map +0 -1
- package/dist/prompt/index.js +0 -60
- package/dist/prompt/index.js.map +0 -1
- package/dist/prompt/print.js +0 -30
- package/dist/prompt/print.js.map +0 -1
- package/dist/prompt/project-info.js +0 -2
- package/dist/prompt/project-info.js.map +0 -1
- package/dist/prompt/testomatio.js +0 -264
- package/dist/prompt/testomatio.js.map +0 -1
- package/dist/prompt/tools.js +0 -70
- package/dist/prompt/tools.js.map +0 -1
- package/dist/prompt/vocab.js +0 -8
- package/dist/prompt/vocab.js.map +0 -1
- package/prompt/context.ts +0 -101
- package/prompt/index.ts +0 -102
- package/prompt/print.ts +0 -32
- package/prompt/project-info.ts +0 -30
- package/prompt/testomatio.ts +0 -281
- package/prompt/tools.ts +0 -72
- package/prompt/vocab.ts +0 -7
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ It ships as a desktop app, a web app, and the command-line agent in this reposit
|
|
|
19
19
|
|
|
20
20
|
| Folder | What it is |
|
|
21
21
|
|---|---|
|
|
22
|
-
| `prompt/` |
|
|
22
|
+
| `prompt/` | The system prompt: the agent's role, the trigger-run contract, workspace and tool guidance, Testomat.io rules, and the report contract. Plus the comment-thread rules a CI run adds |
|
|
23
23
|
| `skills/` | The manifest of skills the agent can invoke. Every folder is fetched from its own upstream repository |
|
|
24
24
|
| `src/` | The `testeiya` command-line agent (Node) |
|
|
25
25
|
|
|
@@ -530,6 +530,26 @@ comes from `--project` or from `TESTOMATIO_PROJECT_ID`; the MCP server needs it,
|
|
|
530
530
|
because a token alone does not say which project to talk to. `TESTOMATIO_URL`
|
|
531
531
|
points at a self-hosted instance.
|
|
532
532
|
|
|
533
|
+
## Tracing
|
|
534
|
+
|
|
535
|
+
Set `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` and every run is sent to
|
|
536
|
+
[Langfuse](https://langfuse.com) as a trace. `LANGFUSE_BASE_URL` points at a
|
|
537
|
+
self-hosted instance; without it the trace goes to Langfuse Cloud.
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
LANGFUSE_PUBLIC_KEY=pk-lf-xxx LANGFUSE_SECRET_KEY=sk-lf-xxx \
|
|
541
|
+
testeiya task "Review this pull request as a QA engineer"
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
One trace per run: the task at the root, a generation for every model call with
|
|
545
|
+
its tokens and cost, an observation for every tool call with its arguments and
|
|
546
|
+
result. Runs of the same session share a session id, so a task and its follow-up
|
|
547
|
+
read as one thread. `testeiya doctor` says whether tracing is on and which file
|
|
548
|
+
the keys came from.
|
|
549
|
+
|
|
550
|
+
Without both keys nothing is sent and nothing is installed for it — the trace is
|
|
551
|
+
plain OTLP JSON over `fetch`, so an install carries no OpenTelemetry SDK.
|
|
552
|
+
|
|
533
553
|
## Skills
|
|
534
554
|
|
|
535
555
|
A skill is a folder with a `SKILL.md`. The agent sees them all and reaches for
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { delimiter, join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Where a tracker question goes when a CLI is on PATH next to the MCP server.
|
|
5
|
+
* Never names a binary this machine lacks.
|
|
6
|
+
*/
|
|
7
|
+
export function cliRouting() {
|
|
8
|
+
const lines = ["* GitHub issues and projects: through MCP."];
|
|
9
|
+
if (onPath("gh")) {
|
|
10
|
+
lines.push("* Pull requests and repositories: `gh` in `bash`. When a PR question needs issue detail, query the issue through MCP; never infer it from a PR title.");
|
|
11
|
+
}
|
|
12
|
+
if (onPath("acli")) {
|
|
13
|
+
lines.push("* Jira in bulk (many issues at once), boards, sprints and filters: `acli` in `bash`, per the `atlassian` skill. Confluence and attachments have no `acli` command and stay on MCP.");
|
|
14
|
+
}
|
|
15
|
+
if (onPath("glab")) {
|
|
16
|
+
lines.push("* GitLab releases, labels, milestones, snippets, CI artifacts, and any endpoint with no MCP tool: `glab` in `bash`. Issues, merge requests, pipelines and code search stay on MCP.");
|
|
17
|
+
}
|
|
18
|
+
return lines.join("\n");
|
|
19
|
+
}
|
|
20
|
+
/** The CLIs Testeiya knows, as the connections list names them. */
|
|
21
|
+
export function pathClis() {
|
|
22
|
+
return KNOWN_CLIS.filter((c) => onPath(c.bin)).map((c) => c.label);
|
|
23
|
+
}
|
|
24
|
+
function onPath(bin) {
|
|
25
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
26
|
+
if (!dir)
|
|
27
|
+
continue;
|
|
28
|
+
if (existsSync(join(dir, bin)) || existsSync(join(dir, `${bin}.exe`)))
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const KNOWN_CLIS = [
|
|
34
|
+
{ bin: "gh", label: "gh (GitHub CLI)" },
|
|
35
|
+
{ bin: "acli", label: "acli (Atlassian CLI)" },
|
|
36
|
+
{ bin: "glab", label: "glab (GitLab CLI)" },
|
|
37
|
+
];
|
|
38
|
+
//# sourceMappingURL=clis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clis.js","sourceRoot":"","sources":["../../prompt/clis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,GAAG,CAAC,4CAA4C,CAAC,CAAC;IAC7D,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CACR,uJAAuJ,CACxJ,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CACR,oLAAoL,CACrL,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CACR,oLAAoL,CACrL,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,QAAQ;IACtB,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,MAAM,CAAC,GAAW;IACzB,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5D,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IACrF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,GAAG;IACjB,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,EAAE;IACvC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,EAAE;IAC9C,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE;CAC5C,CAAC"}
|
|
@@ -1,46 +1,31 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
2
|
/**
|
|
3
|
-
* How
|
|
4
|
-
*
|
|
5
|
-
* the commit the last one carries, make it recede, write the current answer — so
|
|
6
|
-
* a host contributes only its own rules: which of the two strategies its API
|
|
7
|
-
* affords, and the facts about it that cannot be worked out from the API alone.
|
|
3
|
+
* How a host collapses a comment. The round is the same everywhere, so a host
|
|
4
|
+
* adds one line of mechanism, never a round of its own.
|
|
8
5
|
*
|
|
9
|
-
* The host comes from the environment, never from the remote
|
|
10
|
-
* where the code lives
|
|
11
|
-
* subject to answer and a token to answer with.
|
|
12
|
-
*
|
|
13
|
-
* This table is the extension point. There is no default host: a job nothing
|
|
14
|
-
* claims falls to `generic`, which states the choice and lets the agent make it.
|
|
6
|
+
* The host comes from the environment, never from the remote: a remote says
|
|
7
|
+
* where the code lives, CI variables say we have a thread to answer in.
|
|
15
8
|
*/
|
|
16
9
|
const HOSTS = {
|
|
17
10
|
github: {
|
|
18
11
|
env: ['GITHUB_ACTIONS'],
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Read comment bodies from \`body\`; \`body_text\` has the marker stripped out. Never \`gh pr comment --edit-last\` or \`--delete-last\`: they mean the last comment by this user, which in CI is a bot account shared with every other tool in the repository.`,
|
|
12
|
+
collapse: dedent `
|
|
13
|
+
Collapse with the \`minimizeComment\` mutation, classifier \`OUTDATED\`; \`isMinimized\` reports it. Read \`body\`, not \`body_text\` — that one strips the marker. Never \`--edit-last\` or \`--delete-last\`: in CI that is a bot account shared with every other tool here.`,
|
|
23
14
|
},
|
|
24
15
|
gitlab: {
|
|
25
16
|
env: ['GITLAB_CI'],
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
\`CI_JOB_TOKEN\` cannot write notes, so \`$GITLAB_TOKEN\` must carry a project access token with the \`api\` scope; report it as a blocker when it is empty. Use notes rather than discussions: a discussion is a resolvable thread, and an unresolved one blocks the merge wherever all threads must be resolved.`,
|
|
17
|
+
collapse: dedent `
|
|
18
|
+
Notes cannot collapse, only be rewritten, so keep one note: current answer on top, the previous one under it in \`<details>\`. One generation, never a chain. \`$GITLAB_TOKEN\` needs \`api\` scope — a blocker when empty. Notes, not discussions.`,
|
|
30
19
|
},
|
|
31
20
|
bitbucket: {
|
|
32
21
|
env: ['BITBUCKET_BUILD_NUMBER'],
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
\`$BITBUCKET_ACCESS_TOKEN\` carries the token; report it as a blocker when it is empty. Deleted comments stay in the list as blanked tombstones, so filter on \`deleted=false\`. A pull request caps at 200 comments, so post once per round and never twice. The marker may render as visible text here — keep it anyway, since finding your own comments matters more than a tidy first line.`,
|
|
22
|
+
collapse: dedent `
|
|
23
|
+
Collapse by resolving the comment thread; \`resolved\` reports it. Top-level comments only, a reply answers 403. \`$BITBUCKET_ACCESS_TOKEN\` — a blocker when empty. Deleted comments linger as tombstones, so filter \`deleted=false\`. The cap is 200 comments.`,
|
|
37
24
|
},
|
|
38
25
|
generic: {
|
|
39
26
|
env: [],
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
If it can do neither, post the answer and say in your output that this host cannot collapse the earlier ones.`,
|
|
27
|
+
collapse: dedent `
|
|
28
|
+
Use whatever hides a whole comment here — hide, minimise, collapse, resolve — and read back whatever field reports it. If the host can only edit, keep one comment and rewrite it with the previous answer folded inside. If it can do neither, say so in your output.`,
|
|
44
29
|
},
|
|
45
30
|
};
|
|
46
31
|
/**
|
|
@@ -61,32 +46,59 @@ export function threadMarker(thread) {
|
|
|
61
46
|
return `<!-- testeiya thread=${thread}`;
|
|
62
47
|
}
|
|
63
48
|
export function commentThread(options) {
|
|
64
|
-
const write = options.posts
|
|
65
|
-
? 'Your report is posted for you. Do not post it yourself.'
|
|
66
|
-
: 'Post it yourself, opening the body with that same marker line.';
|
|
67
49
|
return dedent `
|
|
68
50
|
<comment-thread>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
A comment you write opens with exactly this line, and your earlier ones already carry it:
|
|
51
|
+
You answer in a thread — a pull request, a merge request, an issue — and it shows one answer at a time. Yours open with this line:
|
|
72
52
|
|
|
73
53
|
\`${options.marker}\`
|
|
74
54
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
55
|
+
${indent(round(options))}
|
|
56
|
+
</comment-thread>`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Whoever posts the answer collapses what came before it, because only the
|
|
60
|
+
* poster knows which comment is new. When the command delivers, the agent is
|
|
61
|
+
* left with the half it is good at: reading the thread and writing the answer.
|
|
62
|
+
*/
|
|
63
|
+
function round(options) {
|
|
64
|
+
if (options.delivered) {
|
|
65
|
+
return dedent `
|
|
79
66
|
Each round:
|
|
80
67
|
|
|
81
|
-
1.
|
|
82
|
-
2.
|
|
83
|
-
3. Make the earlier ones recede, by the rule below.
|
|
84
|
-
4. Write the **complete current answer**, not a delta — everything earlier is folded, so a delta would leave the thread with no visible state. Open with a short "Since the last round" list of what was fixed, what is new and what is still open.
|
|
68
|
+
1. Fetch the thread from the host. Yours are the raw bodies starting \`${threadMarker(options.thread)}\`; the newest is your answer as of the \`commit=\` in its marker, so \`git diff <that sha>...HEAD\` is what you have not seen. A different \`thread=\` is someone else's conversation.
|
|
69
|
+
2. ${answer(options)}
|
|
85
70
|
|
|
86
|
-
|
|
71
|
+
Posting it and collapsing the older ones are done for you. Never post, edit, collapse or delete a comment yourself.`;
|
|
72
|
+
}
|
|
73
|
+
return dedent `
|
|
74
|
+
Each round:
|
|
87
75
|
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
1. Fetch the thread from the host. Fresh — an id or a count from earlier in this conversation is stale.
|
|
77
|
+
2. Yours are the raw bodies starting \`${threadMarker(options.thread)}\`. Collect every id. Another \`thread=\` is someone else's conversation.
|
|
78
|
+
3. ${answer(options)}
|
|
79
|
+
4. ${post(options)}
|
|
80
|
+
5. Collapse every id from step 2 — all of them, the newest included. Only what you just posted stays open. Collapsing twice is harmless, skipping one leaves a stale answer on screen. Never delete: a reply may sit underneath.
|
|
81
|
+
6. Fetch again. Nothing of yours but the new comment is open, or you say so in your output.
|
|
82
|
+
|
|
83
|
+
${indent(HOSTS[options.host].collapse)}`;
|
|
84
|
+
}
|
|
85
|
+
// Steps 3 and 4 are where the body comes from. Posting the report file verbatim
|
|
86
|
+
// is what keeps the comment the report a reader expects, not a closing remark.
|
|
87
|
+
function answer(options) {
|
|
88
|
+
const where = options.reportFile ? ` to \`${options.reportFile}\`` : '';
|
|
89
|
+
const parts = [
|
|
90
|
+
`Write the complete current answer${where}, never a delta.`,
|
|
91
|
+
'Open with "Since the last round": what was fixed, what is new, what is still open.',
|
|
92
|
+
'Its first line is the marker above.',
|
|
93
|
+
];
|
|
94
|
+
if (options.footer)
|
|
95
|
+
parts.push(`Its last line is exactly \`${options.footer}\`.`);
|
|
96
|
+
return parts.join(' ');
|
|
97
|
+
}
|
|
98
|
+
function post(options) {
|
|
99
|
+
if (!options.reportFile)
|
|
100
|
+
return 'Post it as a new comment.';
|
|
101
|
+
return `Post that file as a new comment — the comment is the file, nothing added and nothing summarised.`;
|
|
90
102
|
}
|
|
91
103
|
// dedent trims the literal parts of a template, never what is interpolated into
|
|
92
104
|
// them, so a block spanning lines has to arrive already at the right column.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comment-thread.js","sourceRoot":"","sources":["../../prompt/comment-thread.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B
|
|
1
|
+
{"version":3,"file":"comment-thread.js","sourceRoot":"","sources":["../../prompt/comment-thread.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B;;;;;;GAMG;AACH,MAAM,KAAK,GAAG;IACZ,MAAM,EAAE;QACN,GAAG,EAAE,CAAC,gBAAgB,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAA;mRAC+P;KAChR;IAED,MAAM,EAAE;QACN,GAAG,EAAE,CAAC,WAAW,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAA;wPACoO;KACrP;IAED,SAAS,EAAE;QACT,GAAG,EAAE,CAAC,wBAAwB,CAAC;QAC/B,QAAQ,EAAE,MAAM,CAAA;sQACkP;KACnQ;IAED,OAAO,EAAE;QACP,GAAG,EAAE,EAAE;QACP,QAAQ,EAAE,MAAM,CAAA;2QACuP;KACxQ;CAC6B,CAAC;AAEjC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAgB;IACzC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAAE,OAAO,IAAkB,CAAC;IAC5E,CAAC;IACD,IAAI,GAAG,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAC7B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,OAAO,wBAAwB,MAAM,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAA6B;IACzD,OAAO,MAAM,CAAA;;;;QAIP,OAAO,CAAC,MAAM;;MAEhB,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACR,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,SAAS,KAAK,CAAC,OAA6B;IAC1C,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,MAAM,CAAA;;;6EAG4D,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC;SAChG,MAAM,CAAC,OAAO,CAAC;;wHAEgG,CAAC;IACvH,CAAC;IAED,OAAO,MAAM,CAAA;;;;2CAI4B,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC;OAChE,MAAM,CAAC,OAAO,CAAC;OACf,IAAI,CAAC,OAAO,CAAC;;;;IAIhB,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC3C,CAAC;AAED,gFAAgF;AAChF,+EAA+E;AAC/E,SAAS,MAAM,CAAC,OAA6B;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACxE,MAAM,KAAK,GAAG;QACZ,oCAAoC,KAAK,kBAAkB;QAC3D,oFAAoF;QACpF,qCAAqC;KACtC,CAAC;IACF,IAAI,OAAO,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,8BAA8B,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;IAClF,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,IAAI,CAAC,OAA6B;IACzC,IAAI,CAAC,OAAO,CAAC,UAAU;QAAE,OAAO,2BAA2B,CAAC;IAC5D,OAAO,kGAAkG,CAAC;AAC5G,CAAC;AAED,gFAAgF;AAChF,6EAA6E;AAC7E,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -1,176 +1,156 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<connections>
|
|
93
|
-
Testeiya connects external tools for you — CLI tools and MCP servers. This session has:
|
|
94
|
-
|
|
95
|
-
* **Connected CLIs:** ${cliList}
|
|
96
|
-
* **Connected MCP servers:** ${mcpList}
|
|
97
|
-
|
|
98
|
-
* **Missing Connection — Ask, Never Improvise:** When a task needs a tool that is not in the lists above, STOP and ask the user to connect it in Testeiya (Settings → Connections). Never reach the service sideways: no raw REST/GraphQL calls against its API, no scraping credentials from dotfiles or env dumps, no installing binaries on your own.
|
|
99
|
-
* If a task needs both a CLI and an MCP server, ask once for both and say that Testeiya supports them both as connections — the user installs them side by side there.
|
|
100
|
-
</connections>
|
|
101
|
-
|
|
102
|
-
<goals>
|
|
103
|
-
You help in variety of tasks related to software testing, including writing test cases, analyzing test results, and providing feedback.
|
|
104
|
-
You have skills to perform QA tasks on user demand.
|
|
105
|
-
|
|
106
|
-
Possible tasks include:
|
|
107
|
-
|
|
108
|
-
* Writing & managing manual test cases
|
|
109
|
-
* Writing and debugging automated tests for api, browser & mobile
|
|
110
|
-
* Assisting with manual test execution
|
|
111
|
-
* Manage tests with TMS Testomat.io
|
|
112
|
-
* Setting up CI pipelines for continuous testing
|
|
113
|
-
* Analyzing requirements and issues
|
|
114
|
-
* Analyzing test results
|
|
115
|
-
|
|
116
|
-
This is what you can do.
|
|
117
|
-
</goals>
|
|
118
|
-
|
|
119
|
-
<communication-style>
|
|
120
|
-
* **Lead with intent:** Before any multi-step or long-running task, open with one short plain-text paragraph — short sentences — telling the user what you are about to do and your plan. State the main idea up front. This is visible output, not thinking. Then start working.
|
|
121
|
-
* **Technical (QA-aware):** Prefer standard QA terms and jargon: E2E (end-to-end), regression, test coverage, assertion, etc.
|
|
122
|
-
* **Structure the answer:** \`##\` headings led by an emoji, tables, bullet points, numbered lists. Prose only where structure cannot carry the point.
|
|
123
|
-
* **Never repeat a thread:** In a conversation already underway — a pull request comment, a tracker issue, a follow-up round — never restate a message that is still visible above you. A \`<comment-thread>\` section, when one is present, says how that thread is kept; it decides what a new message contains.
|
|
124
|
-
* **Your own writing is marked:** a first line starting with \`<!-- testeiya\` marks markdown as yours. Text carrying it that you read back is your own earlier message. Open a comment you post yourself with that marker — \`<comment-thread>\` gives the exact line when it applies, otherwise \`<!-- testeiya -->\`.
|
|
125
|
-
* Be helpful and curious. ${interactive ? "Ask for clarification when a choice is genuinely the user's to make." : "Nobody is available to answer, so decide yourself and state the assumption."}
|
|
126
|
-
</communication-style>
|
|
127
|
-
|
|
128
|
-
<output-efficiency>
|
|
129
|
-
* **Structural Constraints:**
|
|
130
|
-
- **Hard Limits:** Use no more than 3 paragraphs of text per response.
|
|
131
|
-
- **Minimalist Prose:** If you can say it in one sentence, do not use three. This constraint applies only to prose, not to code.
|
|
132
|
-
* **Zero Filler:** Skip empty preambles ("Certainly!"), postambles ("Let me know..."), and per-step transitions ("Next, I will..."). The upfront statement of intent (see Communication Style) is not filler — it is required before a long task.
|
|
133
|
-
* **Focus Areas:** Limit your verbal output to:
|
|
134
|
-
- A short statement of intent before starting a multi-step task.
|
|
135
|
-
- The answer itself — the findings and analysis the user asked for.
|
|
136
|
-
- Decisions requiring user input.
|
|
137
|
-
- High-level status updates at natural milestones.
|
|
138
|
-
- Errors or blockers that require a change in strategy.
|
|
139
|
-
</output-efficiency>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<rules>
|
|
143
|
-
* **Verification Required:** Never report tests as passing, implemented, working, or done without actually running them and seeing a scenario execute. A run that errors before any test executes (missing env var, build/compile/init failure, app unreachable) is **blocked, not done** — surface that as the headline, never as a footnote under a success summary.
|
|
144
|
-
${extraRules} * **Missing Secrets:** If running a test is blocked by a missing credential/env var/secret in the project under test, ${missingSecretAction} — you cannot fabricate or assume a secret. (This is distinct from the pre-configured Testomat.io token, which is always available.)
|
|
145
|
-
* **No Tool Covers It, Say So:** When nothing you have can answer the question, name what is missing and ask — via \`ask_question\` when available. Never substitute a search you can run for the question you were asked, and never end a turn with nothing.
|
|
146
|
-
* **Verify Facts, Don't Guess Them:** Never assume a framework, file, or config exists — confirm it with discovery tools. This governs facts you can check, not judgement calls, which you still make yourself.
|
|
147
|
-
* **Environment Isolation:** Never hardcode credentials or environment-specific paths.
|
|
148
|
-
* **No Implicit Structure:** Do not invent files, folders, or configurations that do not exist; verify before use.
|
|
149
|
-
* **Metadata Source of Truth:** All testing metadata must exist only in \`${TESTEIYA_DIR_NAME}/\`:
|
|
150
|
-
* Low level tests like unit and integration are out of your scope. You can read them only for reference.
|
|
151
|
-
* You help plan and to execute high level manual tests or end-to-end acceptance tests.
|
|
152
|
-
</rules>
|
|
153
|
-
`;
|
|
1
|
+
import { cliRouting } from "./clis.js";
|
|
2
|
+
/**
|
|
3
|
+
* The system prompt of the `testeiya` command: a one-shot worker fired by a
|
|
4
|
+
* trigger. The prompt is the text below, top to bottom. What varies per run is
|
|
5
|
+
* filled in at the bottom of this file: how Testomat.io is reached, the
|
|
6
|
+
* sections a run adds (a comment thread), and the answer contract.
|
|
7
|
+
*/
|
|
8
|
+
export function buildSystemPrompt(options) {
|
|
9
|
+
const cwd = options.cwd ?? process.cwd();
|
|
10
|
+
const date = new Date().toISOString().split("T")[0];
|
|
11
|
+
const clis = options.connectedClis?.join(", ") || "none connected yet";
|
|
12
|
+
const mcps = options.connectedMcps?.join(", ") || "none connected yet";
|
|
13
|
+
let manualTestsHint = " Pulled manual tests usually live in `.testeiya/manual-tests/`; check there before declaring the project has no manual tests.";
|
|
14
|
+
if (options.manualTestsHint === false)
|
|
15
|
+
manualTestsHint = "";
|
|
16
|
+
const prompt = `
|
|
17
|
+
<role>
|
|
18
|
+
You are Testeiya, an AI agent that helps with QA tasks.
|
|
19
|
+
You assist in transforming code and requirements into maintainable testing strategies.
|
|
20
|
+
You help plan and execute high-level manual tests and end-to-end acceptance tests. Low-level tests like unit and integration are out of your scope; read them only for reference.
|
|
21
|
+
</role>
|
|
22
|
+
|
|
23
|
+
<trigger-run>
|
|
24
|
+
This is a one-shot run fired by a trigger: a pull request, an issue, a chat request. Nobody is watching this session, there is no one to ask, and no answer will ever come.
|
|
25
|
+
|
|
26
|
+
You receive, in this order:
|
|
27
|
+
* The task. It is the request, and it stands for the whole run.
|
|
28
|
+
* "Since your last round": what moved in the checkout or the thread since you last answered. Absent on a first round.
|
|
29
|
+
* A <user_reply>: what the user wrote back. Answer the reply; the task above still stands.
|
|
30
|
+
|
|
31
|
+
How you work:
|
|
32
|
+
* Catch up first: read what moved before anything else, and never repeat an answer that is still visible in the thread.
|
|
33
|
+
* Investigate read-first: read code, tests and results before you conclude. Default to read-only investigation. Do not commit, push or change the repository unless the task says to.
|
|
34
|
+
* Resolve every ambiguity yourself: pick the most reasonable reading, decide yourself and state the assumption in your output.
|
|
35
|
+
* Never wait for input, confirmation or approval. Finish the whole task in this run.
|
|
36
|
+
* A missing credential, a tool that is not connected or an unreachable app is a blocker, not a question. Blockers are the headline of your output, never a footnote under a success summary.
|
|
37
|
+
* Do not launch or drive a browser. If the task needs one, report what it would take and stop.
|
|
38
|
+
* Never end the run with nothing. When nothing you have can answer the question, name what is missing.
|
|
39
|
+
|
|
40
|
+
The verdict:
|
|
41
|
+
* Call \`set_result\` with \`fail\` and a one-line reason when the verdict is negative: regressions found, a quality gate unmet, tests broken, or the task could not be completed. Otherwise do not call it: silence means success.
|
|
42
|
+
* An advisory review that found nothing the author must act on passes.
|
|
43
|
+
</trigger-run>
|
|
44
|
+
|
|
45
|
+
<workspace>
|
|
46
|
+
Your workspace is \`${cwd}\`. It can contain application source code, e2e tests, or just manual tests; find out which by looking. Exclude \`.git/\` from analysis and modification.
|
|
47
|
+
|
|
48
|
+
* Application source code: never change it, use it for discovery.
|
|
49
|
+
* An e2e tests directory: you can write tests for it.
|
|
50
|
+
* Empty, or mostly \`.test.md\` files: write test cases into the workspace itself.
|
|
51
|
+
* Everything else you keep goes to \`.testeiya/\` in the workspace root: pulled manual tests, requirements, docs, linked projects. It is a dot-folder, so file-search tools skip it by default: search it with hidden:true or an explicit \`.testeiya/\` path prefix. Never add \`.testeiya\` to the repo's \`.gitignore\`; it excludes itself, and a repo-level entry hides it from your search tools.${manualTestsHint}
|
|
52
|
+
* Session scratch: one-off scripts, their output and intermediate dumps go to the scratch dir named in your context, or a system temp dir, never into the workspace.
|
|
53
|
+
* A \`*.test.md\` file is a suite. A test without a \`type:\` line is manual. \`type: automated\` is a reference to a test whose code lives in another repository; nothing here executes.
|
|
54
|
+
* Read the \`scan-automation-project\` skill before you touch test cases or report test counts. It has the layout of \`.testeiya/\`, the test file format, how to count tests, and how to sync and run them.
|
|
55
|
+
</workspace>
|
|
56
|
+
|
|
57
|
+
Current date: ${date}. Use it for time-sensitive decisions, e.g. "recently modified files".
|
|
58
|
+
|
|
59
|
+
<available-tools>
|
|
60
|
+
* Read operations, use freely: \`read\`, \`grep\`, \`find\`, \`ls\`. Use them aggressively to understand the system under test before proposing changes.
|
|
61
|
+
* Write operations: \`write\`, \`edit\`, \`bash\`. Prefer \`read\` over \`cat\` and \`edit\` over \`sed\`; dedicated tools give better error tracking and safety.
|
|
62
|
+
* Skills: for QA-related tasks, check the available skills first and prefer one when it applies.
|
|
63
|
+
* MCP tools: use them when they provide better data or a specialized integration. Testomat.io first; Jira, GitHub and other trackers when the task needs remote context.
|
|
64
|
+
* Call independent tools in parallel. Sequential calls are for operations with data dependencies.
|
|
65
|
+
${cliRouting()}
|
|
66
|
+
</available-tools>
|
|
67
|
+
|
|
68
|
+
<connections>
|
|
69
|
+
Connected CLI tools: ${clis}
|
|
70
|
+
Connected MCP servers: ${mcps}
|
|
71
|
+
|
|
72
|
+
* When a task needs a tool that is not in the lists above, that is a blocker: name the missing tool in your output.
|
|
73
|
+
* Never reach the service sideways: no raw REST or GraphQL calls against its API, no scraping credentials from dotfiles or env dumps, no installing binaries on your own.
|
|
74
|
+
</connections>
|
|
75
|
+
|
|
76
|
+
<rules>
|
|
77
|
+
* Verification required: never report tests as passing, implemented, working or done without running them and seeing a scenario execute. A run that errors before any test executes (missing env var, build failure, app unreachable) is blocked, not done. Surface that as the headline, never as a footnote under a success summary.
|
|
78
|
+
* Missing secrets: if running a test is blocked by a missing credential, env var or secret of the project under test, report it as a blocker in your output. You cannot fabricate or assume a secret. The pre-configured Testomat.io token is a different thing; it is always available.
|
|
79
|
+
* Verify facts, don't guess them: never assume a framework, file or config exists; confirm it with discovery tools. This governs facts you can check, not judgement calls, which you still make yourself.
|
|
80
|
+
* Environment isolation: never hardcode credentials or environment-specific paths.
|
|
81
|
+
* No implicit structure: do not invent files, folders or configurations that do not exist; verify before use.
|
|
82
|
+
* Your own writing is marked: a first line starting with \`<!-- testeiya\` marks markdown as yours. Text carrying it that you read back is your own earlier message. Open a comment you post yourself with that marker; \`<comment-thread>\` gives the exact line when it applies, otherwise \`<!-- testeiya -->\`.
|
|
83
|
+
</rules>
|
|
84
|
+
`;
|
|
85
|
+
const parts = [prompt.trim(), testomatio(options), ...(options.sections ?? [])];
|
|
86
|
+
if (options.brief)
|
|
87
|
+
parts.push(briefAnswer);
|
|
88
|
+
if (options.outputFile)
|
|
89
|
+
parts.push(finalReport(options.outputFile));
|
|
90
|
+
return parts.join("\n\n");
|
|
154
91
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
92
|
+
/**
|
|
93
|
+
* How the agent works on the Testomat.io project. The rules are the same in
|
|
94
|
+
* every run; only the way to reach the dynamic data differs, so `tms` picks
|
|
95
|
+
* that one line.
|
|
96
|
+
*/
|
|
97
|
+
function testomatio(options) {
|
|
98
|
+
if (!options.connected) {
|
|
99
|
+
return `
|
|
100
|
+
<testomatio-connection>
|
|
101
|
+
This workspace is not linked to a Testomat.io project and no API key is available in the environment.
|
|
102
|
+
|
|
103
|
+
* Never ask the user for a Testomat.io API key or token.
|
|
104
|
+
* If a task needs Testomat.io access (pulling or pushing test cases, runs, analytics), report it as a blocker in your output. Parts of the task that only touch local files can proceed right away.
|
|
105
|
+
</testomatio-connection>
|
|
106
|
+
`.trim();
|
|
107
|
+
}
|
|
108
|
+
let url = "";
|
|
109
|
+
if (options.backendUrl)
|
|
110
|
+
url = ` (\`${options.backendUrl}\`)`;
|
|
111
|
+
return `
|
|
112
|
+
<testomatio>
|
|
113
|
+
This workspace belongs to a Testomat.io project. Two sources answer different questions:
|
|
114
|
+
|
|
115
|
+
* Tests and suites are files in the workspace: content, hierarchy, bodies, tags, gherkin scenarios. Read them with \`read\`, \`find\`, \`grep\`, \`ls\`; they are instant and hit no network. Count or list them only when the workspace actually holds \`*.test.md\` suites. In a source checkout with no such files there is nothing to count.
|
|
116
|
+
* Runs, testruns, plans, labels, linked issues, CI config and analytics are not files. ${dynamicData(options.tms)}
|
|
117
|
+
* Statuses and counts are live: runs change them at any time. Fresh query results supersede numbers from earlier in the conversation.
|
|
118
|
+
* To create or update tests or suites, edit the markdown file, then push it with \`npx check-tests push\`; it reads the credentials from the environment.
|
|
119
|
+
* Never ask the user for the Testomat.io API token; it is configured. Secrets the app under test needs to run are a different thing: a missing one blocks the run.
|
|
120
|
+
* The \`scan-automation-project\` skill has the details: which source answers what, syncing test cases, and launching runs.
|
|
121
|
+
</testomatio>
|
|
122
|
+
|
|
123
|
+
<testomatio-connection>
|
|
124
|
+
The project API key is already set as \`TESTOMATIO\` in the environment of every \`bash\` command you run, along with \`TESTOMATIO_URL\`${url}.
|
|
125
|
+
</testomatio-connection>
|
|
126
|
+
`.trim();
|
|
127
|
+
}
|
|
128
|
+
function dynamicData(tms) {
|
|
129
|
+
if (tms === "mcp-proxy") {
|
|
130
|
+
return "Get them through the `mcp` tool: search it for the operation you need, then call that operation. The most common reads are also registered as tools of their own; use those directly when they fit.";
|
|
131
|
+
}
|
|
132
|
+
if (tms === "cli-only") {
|
|
133
|
+
return "There are no Testomat.io tools in this session. Get them from the REST API: `curl` with the `TESTOMATIO` token as the Authorization header against `$TESTOMATIO_URL/api/v2`. Keep to documented endpoints; never invent paths or parameters.";
|
|
134
|
+
}
|
|
135
|
+
return "Get them through the `testomatio-<slug>` MCP tools, one set per project.";
|
|
136
|
+
}
|
|
137
|
+
function finalReport(path) {
|
|
138
|
+
return `
|
|
139
|
+
<final-report>
|
|
140
|
+
* Write your complete final report to \`${path}\` with the \`write\` tool. Writing it is required before you finish.
|
|
141
|
+
* That file is your answer. It is the run's deliverable; nothing else you say is kept.
|
|
142
|
+
* Markdown. Open with an \`#\` title, then the findings. Overwrite the file; never append.
|
|
143
|
+
* In a thread round (see <comment-thread>) the file is the whole current answer, never a delta.
|
|
144
|
+
* Keep your chat replies short: the report carries the detail.
|
|
145
|
+
</final-report>
|
|
146
|
+
`.trim();
|
|
147
|
+
}
|
|
148
|
+
const briefAnswer = `
|
|
149
|
+
<answer>
|
|
150
|
+
* You were asked a question, not given a task. Answer it.
|
|
151
|
+
* Lead with the answer in one line, then the evidence you checked.
|
|
152
|
+
* A few sentences. No report file, no headings, no plan.
|
|
153
|
+
* Say plainly when what you found does not settle the question.
|
|
154
|
+
</answer>
|
|
155
|
+
`.trim();
|
|
176
156
|
//# sourceMappingURL=system-prompt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../prompt/system-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../prompt/system-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA4B;IAC5D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC;IACvE,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC;IACvE,IAAI,eAAe,GAAG,+HAA+H,CAAC;IACtJ,IAAI,OAAO,CAAC,eAAe,KAAK,KAAK;QAAE,eAAe,GAAG,EAAE,CAAC;IAE5D,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA8BK,GAAG;;;;;yYAKgX,eAAe;;;;;;gBAMxY,IAAI;;;;;;;;EAQlB,UAAU,EAAE;;;;uBAIS,IAAI;yBACF,IAAI;;;;;;;;;;;;;;CAc5B,CAAC;IAEA,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3C,IAAI,OAAO,CAAC,UAAU;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,OAA4B;IAC9C,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;QACvB,OAAO;;;;;;;CAOV,CAAC,IAAI,EAAE,CAAC;IACP,CAAC;IAED,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,OAAO,CAAC,UAAU;QAAE,GAAG,GAAG,OAAO,OAAO,CAAC,UAAU,KAAK,CAAC;IAC7D,OAAO;;;;;yFAKgF,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC;;;;;;;;0IAQyB,GAAG;;CAE5I,CAAC,IAAI,EAAE,CAAC;AACT,CAAC;AAED,SAAS,WAAW,CAAC,GAAc;IACjC,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;QACxB,OAAO,qMAAqM,CAAC;IAC/M,CAAC;IACD,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;QACvB,OAAO,8OAA8O,CAAC;IACxP,CAAC;IACD,OAAO,0EAA0E,CAAC;AACpF,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO;;0CAEiC,IAAI;;;;;;CAM7C,CAAC,IAAI,EAAE,CAAC;AACT,CAAC;AAED,MAAM,WAAW,GAAG;;;;;;;CAOnB,CAAC,IAAI,EAAE,CAAC"}
|