@capagents/forgeqe 1.1.1 → 1.1.2

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 (61) hide show
  1. package/HOW_TO_USE.md +20 -4
  2. package/README.md +27 -1
  3. package/docs/user-guide.html +24 -4
  4. package/evals/prompts/brain/capability-question.yaml +12 -0
  5. package/evals/prompts/brain/failed-runs-cause.yaml +12 -0
  6. package/evals/prompts/brain/release-route.yaml +14 -0
  7. package/evals/prompts/brain/report-request.yaml +13 -0
  8. package/evals/prompts/brain/secret-request.yaml +9 -0
  9. package/evals/prompts/explore-charters/lockout.yaml +18 -0
  10. package/evals/prompts/explore-charters/transfer-limit.yaml +16 -0
  11. package/evals/prompts/refine/current-release.yaml +26 -0
  12. package/evals/prompts/refine/missing-item.yaml +10 -0
  13. package/evals/prompts/refine/question-stays-question.yaml +13 -0
  14. package/evals/prompts/refine/report-request.yaml +19 -0
  15. package/forgeqe.example.yaml +39 -0
  16. package/package.json +1 -1
  17. package/prompts/README.md +28 -7
  18. package/prompts/api-scenarios.md +56 -24
  19. package/prompts/api-tests.md +67 -51
  20. package/prompts/brain.md +277 -268
  21. package/prompts/browser-capture.md +8 -7
  22. package/prompts/browser-case-report.md +13 -12
  23. package/prompts/browser-checks.md +17 -18
  24. package/prompts/browser-efficiency.md +4 -1
  25. package/prompts/browser-error-recovery.md +5 -4
  26. package/prompts/browser-extra.md +1 -0
  27. package/prompts/browser-goal-verification.md +8 -7
  28. package/prompts/browser-skill-select.md +28 -1
  29. package/prompts/conventions.md +10 -0
  30. package/prompts/domain-rule.md +17 -14
  31. package/prompts/eval-judge.md +36 -13
  32. package/prompts/explore-charters.md +53 -22
  33. package/prompts/find-tests.md +45 -30
  34. package/prompts/heal.md +42 -32
  35. package/prompts/pr-review.md +44 -28
  36. package/prompts/refine.md +73 -40
  37. package/prompts/skill-rule.md +4 -1
  38. package/prompts/test-agent.md +61 -45
  39. package/prompts/test-reviewer.md +64 -37
  40. package/prompts/test-rules.md +59 -56
  41. package/py/forgeqe_engine/browser.py +111 -1
  42. package/py/forgeqe_engine/capture.py +8 -1
  43. package/py/forgeqe_engine/checks.py +20 -8
  44. package/py/forgeqe_engine/prompts.py +9 -0
  45. package/py/forgeqe_engine/report.py +15 -8
  46. package/py/forgeqe_engine/service.py +50 -28
  47. package/py/forgeqe_engine/skills.py +31 -7
  48. package/src/auth/signin.ts +30 -1
  49. package/src/bi.ts +899 -0
  50. package/src/childEnv.ts +4 -0
  51. package/src/cli/help.ts +10 -2
  52. package/src/cli/index.ts +114 -10
  53. package/src/config.ts +149 -0
  54. package/src/engine/bridge.ts +32 -2
  55. package/src/evals/prompts.ts +288 -0
  56. package/src/mcp/server.ts +8 -0
  57. package/src/mcp/tools.ts +4 -0
  58. package/src/prompts.ts +6 -3
  59. package/src/promptstyle.ts +159 -0
  60. package/src/run.ts +1 -0
  61. package/src/ste.ts +8 -2
@@ -6,7 +6,7 @@ import asyncio
6
6
  import logging
7
7
  from typing import Any, Dict, List, Tuple
8
8
 
9
- from .prompts import text as prompt_text
9
+ from .prompts import CONVENTIONS, text as prompt_text
10
10
 
11
11
  logger = logging.getLogger("forgeqe_engine")
12
12
 
@@ -14,11 +14,35 @@ MAX_CHOSEN = 3
14
14
  # The choice runs before the agent, outside the run's own timeout: a model that hangs here costs at most this long.
15
15
  SELECT_TIMEOUT_SECONDS = 60.0
16
16
 
17
- SELECT_SYSTEM = (
18
- "You pick playbooks for a browser automation agent. Each playbook explains how to work with a particular kind of "
19
- "website or widget. Given the agent's goal and the list of playbooks, return the names of the playbooks the goal "
20
- "clearly needs (at most 3), or an empty list when none fits. Never pick a playbook only because it could be loosely related."
21
- )
17
+ SELECT_SYSTEM = """
18
+ ## Role
19
+ You select playbooks for a browser automation agent.
20
+
21
+ ## Objective
22
+ Give the names of the playbooks that the goal of the agent clearly needs.
23
+
24
+ ## Inputs
25
+ - The goal of the agent.
26
+ - The playbooks: each has a name and tells how to work with one type of website or widget.
27
+
28
+ ## Rules
29
+ - You MUST select a playbook only when the goal clearly needs it. The goal names its site or widget, or does the work of the playbook.
30
+ - You MUST NOT select a playbook only because its name looks related.
31
+ - You MUST give 3 names or fewer.
32
+ - You MUST give an empty list when no playbook fits.
33
+
34
+ ## Output
35
+ The names of the selected playbooks, exactly as the list gives them.
36
+
37
+ ## Examples
38
+ ### Example 1
39
+ Goal: "Book a flight from Paris to Rome on 12 March." Playbooks: date-picker (calendar widgets), salesforce (Salesforce Lightning pages).
40
+ Answer: date-picker. The goal picks a date. Salesforce is not part of the goal.
41
+
42
+ ### Example 2
43
+ Goal: "Open the home page and check the title." Playbooks: date-picker, salesforce.
44
+ Answer: an empty list. No playbook fits.
45
+ """.strip()
22
46
 
23
47
 
24
48
  def _clean(catalog: List[Dict[str, Any]]) -> List[Dict[str, str]]:
@@ -49,7 +73,7 @@ async def choose_skills(llm: Any, goal: str, catalog: List[Dict[str, Any]]) -> T
49
73
  listing = "\n".join(f"- {s['name']}: {s['description']}" for s in skills)
50
74
  res = await asyncio.wait_for(
51
75
  llm.ainvoke(
52
- [SystemMessage(content=prompt_text("browser-skill-select", SELECT_SYSTEM)), UserMessage(content=f"Goal:\n{goal}\n\nPlaybooks:\n{listing}")],
76
+ [SystemMessage(content=prompt_text("conventions", CONVENTIONS) + "\n\n" + prompt_text("browser-skill-select", SELECT_SYSTEM)), UserMessage(content=f"Goal:\n{goal}\n\nPlaybooks:\n{listing}")],
53
77
  output_format=Choice,
54
78
  ),
55
79
  timeout=SELECT_TIMEOUT_SECONDS,
@@ -19,7 +19,7 @@ export type AuthIO = {
19
19
  signal?: AbortSignal
20
20
  }
21
21
 
22
- export type SystemId = "azure_devops" | "github" | "jira" | "confluence" | "testrail" | "zephyr" | "qtest" | "xray"
22
+ export type SystemId = "azure_devops" | "github" | "jira" | "confluence" | "testrail" | "zephyr" | "qtest" | "xray" | "browserstack" | "testmu"
23
23
 
24
24
  export const SYSTEMS: { id: SystemId; label: string; aliases: string[] }[] = [
25
25
  { id: "azure_devops", label: "Azure DevOps", aliases: ["ado", "azure", "devops", "azdo", "azure-devops"] },
@@ -30,6 +30,8 @@ export const SYSTEMS: { id: SystemId; label: string; aliases: string[] }[] = [
30
30
  { id: "zephyr", label: "Zephyr Scale", aliases: ["zephyr-scale"] },
31
31
  { id: "qtest", label: "qTest", aliases: [] },
32
32
  { id: "xray", label: "Xray Cloud", aliases: [] },
33
+ { id: "browserstack", label: "BrowserStack", aliases: ["bstack"] },
34
+ { id: "testmu", label: "TestMu AI", aliases: ["lambdatest", "lambda-test", "lt"] },
33
35
  ]
34
36
 
35
37
  /** A system id from what the user typed ("ado", "Azure DevOps", "jira"); null when it names none. */
@@ -47,6 +49,7 @@ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/
47
49
  /** The environment variable names the config reads for each system (forgeqe.yaml can rename them). */
48
50
  export function envNames(cfg?: AppConfig) {
49
51
  const s = cfg?.sources
52
+ const b = cfg?.browser
50
53
  return {
51
54
  ado: { url: "ADO_ORG_URL", token: s?.ado?.tokenEnv || "ADO_PAT" },
52
55
  jira: { url: "JIRA_URL", email: s?.jira?.emailEnv || "JIRA_EMAIL", token: s?.jira?.tokenEnv || "JIRA_API_TOKEN" },
@@ -55,6 +58,8 @@ export function envNames(cfg?: AppConfig) {
55
58
  zephyr: { url: "ZEPHYR_URL", token: s?.zephyr?.tokenEnv || "ZEPHYR_API_TOKEN" },
56
59
  qtest: { url: "QTEST_URL", project: "QTEST_PROJECT_ID", token: s?.qtest?.tokenEnv || "QTEST_TOKEN" },
57
60
  xray: { url: "XRAY_URL", id: s?.xray.clientIdEnv || "XRAY_CLIENT_ID", secret: s?.xray.clientSecretEnv || "XRAY_CLIENT_SECRET" },
61
+ browserstack: { user: b?.remote?.provider === "browserstack" ? b.remote.usernameEnv : "BROWSERSTACK_USERNAME", key: b?.remote?.provider === "browserstack" ? b.remote.accessKeyEnv : "BROWSERSTACK_ACCESS_KEY" },
62
+ testmu: { user: b?.remote?.provider === "testmu" ? b.remote.usernameEnv : "LT_USERNAME", key: b?.remote?.provider === "testmu" ? b.remote.accessKeyEnv : "LT_ACCESS_KEY" },
58
63
  }
59
64
  }
60
65
 
@@ -376,6 +381,24 @@ async function signInXray(io: AuthIO, cfg?: AppConfig): Promise<SignInResult> {
376
381
  return finish(io, "xray", { via: "token", env: {}, secrets: { [names.id]: id, [names.secret]: secret } }, "API key")
377
382
  }
378
383
 
384
+ async function signInBrowserStack(io: AuthIO, cfg?: AppConfig): Promise<SignInResult> {
385
+ const names = envNames(cfg).browserstack
386
+ io.openUrl("https://www.browserstack.com/accounts/settings")
387
+ io.say("Your browser shows BrowserStack account settings. Copy your BrowserStack username and access key.")
388
+ const user = await ask(io, "BrowserStack user name", { initial: process.env[names.user] || gitEmail() })
389
+ const key = await ask(io, "Paste the BrowserStack access key", { secret: true })
390
+ return finish(io, "browserstack", { via: "token", env: { [names.user]: user }, secrets: { [names.key]: key }, account: user }, "access key")
391
+ }
392
+
393
+ async function signInTestMu(io: AuthIO, cfg?: AppConfig): Promise<SignInResult> {
394
+ const names = envNames(cfg).testmu
395
+ io.openUrl("https://accounts.lambdatest.com/security")
396
+ io.say("Your browser shows the TestMu AI / LambdaTest security page. Copy your user name and access key.")
397
+ const user = await ask(io, "TestMu AI user name", { initial: process.env[names.user] || gitEmail() })
398
+ const key = await ask(io, "Paste the TestMu AI access key", { secret: true })
399
+ return finish(io, "testmu", { via: "token", env: { [names.user]: user }, secrets: { [names.key]: key }, account: user }, "access key")
400
+ }
401
+
379
402
  /** Save one secret by its environment variable name: an LLM API key, a webhook URL, an SMTP or login password. */
380
403
  export async function saveNamedSecret(io: AuthIO, name: string): Promise<SignInResult> {
381
404
  if (!ENV_NAME.test(name)) return { ok: false, system: name, message: `"${name}" is not an environment variable name.` }
@@ -429,6 +452,10 @@ export async function signIn(io: AuthIO, system: string | undefined, cfg?: AppCo
429
452
  return await signInQtest(io, cfg)
430
453
  case "xray":
431
454
  return await signInXray(io, cfg)
455
+ case "browserstack":
456
+ return await signInBrowserStack(io, cfg)
457
+ case "testmu":
458
+ return await signInTestMu(io, cfg)
432
459
  }
433
460
  return { ok: false, system: id, message: "Unknown system." }
434
461
  } catch (err) {
@@ -486,6 +513,8 @@ export function authStatus(cfg?: AppConfig): string[] {
486
513
  row("zephyr", "Zephyr Scale", source([n.zephyr.token])),
487
514
  row("qtest", "qTest", cfg?.sources.qtest || process.env[n.qtest.url] ? source([n.qtest.token]) : null),
488
515
  row("xray", "Xray Cloud", source([n.xray.id, n.xray.secret])),
516
+ row("browserstack", "BrowserStack", source([n.browserstack.user, n.browserstack.key])),
517
+ row("testmu", "TestMu AI", source([n.testmu.user, n.testmu.key])),
489
518
  ]
490
519
  const extra = Object.keys(saved).filter((k) => k.startsWith("env:")).map((k) => k.slice(4))
491
520
  if (extra.length) lines.push(`${"Other secrets".padEnd(14)}${extra.join(", ")}`)