@capagents/forgeqe 1.3.2 → 2.0.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/HOW_TO_USE.md +373 -9
- package/README.md +55 -4
- package/docs/ARCHITECTURE.md +62 -1
- package/docs/CONTAINERS.md +227 -0
- package/docs/COST_ARCHITECTURE.md +32 -0
- package/docs/DESKTOP-CI.md +87 -0
- package/docs/DESKTOP.md +387 -0
- package/docs/MOBILE.md +228 -0
- package/docs/feature-test-prompts.md +25 -0
- package/docs/security-data-flow.html +440 -3
- package/docs/user-guide.html +797 -415
- package/env.example.yaml +67 -0
- package/forgeqe.example.yaml +56 -1
- package/llms.example.json +7 -0
- package/package.json +4 -1
- package/prompts/brain.md +31 -0
- package/prompts/find-tests.md +1 -0
- package/py/forgeqe_desktop/__init__.py +1 -0
- package/py/forgeqe_desktop/__main__.py +3 -0
- package/py/forgeqe_desktop/apps.py +80 -0
- package/py/forgeqe_desktop/checks.py +168 -0
- package/py/forgeqe_desktop/cli.py +90 -0
- package/py/forgeqe_desktop/handhooks.py +293 -0
- package/py/forgeqe_desktop/handrecord.py +239 -0
- package/py/forgeqe_desktop/heal.py +107 -0
- package/py/forgeqe_desktop/models.py +208 -0
- package/py/forgeqe_desktop/pywinauto_driver.py +217 -0
- package/py/forgeqe_desktop/record.py +150 -0
- package/py/forgeqe_desktop/replay.py +345 -0
- package/py/forgeqe_desktop/service.py +356 -0
- package/py/forgeqe_desktop/session.py +60 -0
- package/py/forgeqe_desktop/ufo_bridge.py +86 -0
- package/py/forgeqe_desktop/ufo_runner.py +305 -0
- package/py/forgeqe_desktop/visual.py +112 -0
- package/py/forgeqe_desktop/workflow.py +338 -0
- package/py/forgeqe_engine/llm.py +54 -4
- package/py/forgeqe_engine/model_params.py +169 -0
- package/py/forgeqe_mobile/__init__.py +1 -0
- package/py/forgeqe_mobile/__main__.py +3 -0
- package/py/forgeqe_mobile/appium_driver.py +342 -0
- package/py/forgeqe_mobile/apps.py +84 -0
- package/py/forgeqe_mobile/cli.py +70 -0
- package/py/forgeqe_mobile/explore.py +203 -0
- package/py/forgeqe_mobile/hyperexecute.py +261 -0
- package/py/forgeqe_mobile/maestro.py +321 -0
- package/py/forgeqe_mobile/service.py +332 -0
- package/skills/testcontainers/SKILL.md +42 -0
- package/src/agents.ts +11 -2
- package/src/api/plan.ts +2 -0
- package/src/api/write.ts +2 -0
- package/src/auth/copilot.ts +130 -0
- package/src/auth/signin.ts +54 -1
- package/src/brain/index.ts +26 -4
- package/src/capabilities/guide.ts +2 -0
- package/src/capabilities/registry.ts +110 -2
- package/src/capabilities/status.ts +15 -0
- package/src/cases/rca.ts +2 -0
- package/src/childEnv.ts +4 -1
- package/src/cli/args.ts +3 -2
- package/src/cli/help.ts +24 -3
- package/src/cli/index.ts +47 -3
- package/src/code/find.ts +26 -0
- package/src/code/loop.ts +4 -1
- package/src/code/prompt.ts +2 -1
- package/src/code/prreview.ts +2 -0
- package/src/code/review.ts +6 -2
- package/src/code/runtime.ts +34 -3
- package/src/commands.ts +6 -1
- package/src/config.ts +159 -3
- package/src/containers/command.ts +68 -0
- package/src/containers/compose.ts +173 -0
- package/src/containers/config.ts +283 -0
- package/src/containers/mock.ts +156 -0
- package/src/containers/run.ts +362 -0
- package/src/containers/tests.ts +80 -0
- package/src/context.ts +33 -6
- package/src/copilot.ts +155 -0
- package/src/desktop/bridge.ts +229 -0
- package/src/desktop/command.ts +350 -0
- package/src/desktop/edit.ts +217 -0
- package/src/desktop/junit.ts +35 -0
- package/src/desktop/report.ts +155 -0
- package/src/desktop/run.ts +252 -0
- package/src/desktop/setup.ts +134 -0
- package/src/engine/bridge.ts +12 -5
- package/src/engine/setup.ts +3 -3
- package/src/environments.ts +566 -0
- package/src/evals/domain.ts +1 -0
- package/src/explore/charters.ts +2 -0
- package/src/guardrails.ts +145 -0
- package/src/heal/loop.ts +4 -1
- package/src/heal/prompt.ts +2 -1
- package/src/init.ts +5 -2
- package/src/llm.ts +23 -2
- package/src/mcp/attachtools.ts +16 -4
- package/src/mcp/captools.ts +27 -0
- package/src/mcp/containertools.ts +212 -0
- package/src/mcp/desktoptools.ts +334 -0
- package/src/mcp/mobiletools.ts +132 -0
- package/src/mcp/reporttools.ts +17 -2
- package/src/mcp/repotests.ts +22 -3
- package/src/mcp/routing.ts +11 -5
- package/src/mcp/scope.ts +210 -0
- package/src/mcp/server.ts +80 -5
- package/src/mcp/tools.ts +38 -1
- package/src/mobile/command.ts +245 -0
- package/src/mobile/run.ts +226 -0
- package/src/pack/pipeline.ts +44 -2
- package/src/refine.ts +3 -0
- package/src/report/html.ts +3 -0
- package/src/report/template.html +48 -48
- package/src/run.ts +7 -3
- package/src/tokenomics/ledger.ts +3 -1
- package/src/tui/App.tsx +23 -0
- package/src/usage.ts +1 -1
|
@@ -30,13 +30,17 @@ export type Requirement =
|
|
|
30
30
|
| { kind: "command"; command: "gh" | "az" | "git" | "npx"; optional?: boolean; why?: string }
|
|
31
31
|
/** Something only the request can supply (a repository, a pull request); stated, never checked. */
|
|
32
32
|
| { kind: "input"; what: string }
|
|
33
|
+
/** Apps of this kind in the active environment (env.yaml) or forgeqe.yaml (desktop: or mobile:). */
|
|
34
|
+
| { kind: "apps"; app: "desktop" | "mobile"; optional?: boolean }
|
|
35
|
+
/** Docker or Podman on PATH (containers.runtime picks one). */
|
|
36
|
+
| { kind: "containers"; optional?: boolean }
|
|
33
37
|
/** Alternatives: met when any one of them is (GitHub through gh, or Azure DevOps). */
|
|
34
38
|
| { kind: "either"; of: Requirement[]; optional?: boolean }
|
|
35
39
|
|
|
36
40
|
/** A heading in README.md or HOW_TO_USE.md, by its exact text (### headings without their parent). */
|
|
37
41
|
export type DocRef = { doc: "README.md" | "HOW_TO_USE.md"; heading: string }
|
|
38
42
|
|
|
39
|
-
export type Area = "Browser" | "Test cases" | "Automated tests" | "Pull requests" | "API" | "Quality" | "Release" | "Operations" | "Systems" | "Setup"
|
|
43
|
+
export type Area = "Browser" | "Desktop" | "Mobile" | "Test cases" | "Automated tests" | "Pull requests" | "API" | "Quality" | "Release" | "Operations" | "Systems" | "Setup"
|
|
40
44
|
|
|
41
45
|
export type Capability = {
|
|
42
46
|
/** Stable id the agent asks for details by. */
|
|
@@ -47,6 +51,11 @@ export type Capability = {
|
|
|
47
51
|
summary: string
|
|
48
52
|
/** The tools that carry it out (forgeqe_ prefix left off). */
|
|
49
53
|
tools: string[]
|
|
54
|
+
/**
|
|
55
|
+
* Words and phrases in a request that mean this capability (lower case). ForgeQE's agent gets a capability's tools
|
|
56
|
+
* for a turn when the request matches them (src/mcp/scope.ts); its examples must match, a test checks it.
|
|
57
|
+
*/
|
|
58
|
+
keywords: string[]
|
|
50
59
|
/** How a user might ask for it, in their words. */
|
|
51
60
|
examples: string[]
|
|
52
61
|
requires: Requirement[]
|
|
@@ -70,6 +79,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
70
79
|
summary:
|
|
71
80
|
"Carries out a plain-language goal in a real browser one step at a time and verifies the outcome, with screenshots, a report, and the steps it took. The browser comes from forgeqe.yaml: local Chrome or Chromium by default, or BrowserStack or TestMu AI/LambdaTest when configured. Page memory replays steps that already worked on a site, so repeat runs are faster and cheaper.",
|
|
72
81
|
tools: ["browser_run", "run_details", "page_memory", "browser_devices"],
|
|
82
|
+
keywords: ["browser", "website", "web site", "web app", "page", "url", "go to", "open the", "click", "search", "chrome", "edge", "firefox", "device", "testmu", "browserstack", "sign up", "checkout", "site", "form", "cart"],
|
|
73
83
|
examples: [
|
|
74
84
|
"Go to booking.com and search hotels in Mumbai for next weekend",
|
|
75
85
|
"What does ForgeQE know about the checkout page?",
|
|
@@ -88,6 +98,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
88
98
|
title: "Saved logins for the application under test",
|
|
89
99
|
summary: "Signs the browser agent in with saved credentials (including TOTP) and reuses the session; a user can also sign in by hand once in a browser.",
|
|
90
100
|
tools: ["logins"],
|
|
101
|
+
keywords: ["login", "log in", "sign in", "signed in", "session", "credential", "password", "saved login", "forget the saved"],
|
|
91
102
|
examples: ["Sign in to the admin site and keep the session", "Forget the saved session for shop.example.com"],
|
|
92
103
|
requires: [],
|
|
93
104
|
docs: [{ doc: "HOW_TO_USE.md", heading: "Signing in" }],
|
|
@@ -99,6 +110,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
99
110
|
summary:
|
|
100
111
|
"Reads test cases from Azure DevOps, Jira, Xray, TestRail, Zephyr Scale, qTest, a file, or pasted text, runs each in a real browser with a verdict per step, writes report.html and junit.xml, posts results back, and files or updates bugs for failures.",
|
|
101
112
|
tools: ["find_test_cases", "run_test_cases"],
|
|
113
|
+
keywords: ["test case", "cases", "test plan", "suite", "manual test", "run case", "execute case", "run the case"],
|
|
102
114
|
examples: ["Run SHOP-12 and SHOP-14 from Jira, two browsers at once", "Run test plan 12 suite 34 from Azure DevOps and don't post results"],
|
|
103
115
|
requires: [
|
|
104
116
|
{ kind: "live_model" },
|
|
@@ -120,6 +132,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
120
132
|
summary:
|
|
121
133
|
"Checks test cases before they run (vague or missing expected results, missing test data, placeholders, duplicates) against the story, feature, and epic they test, then rewrites Azure DevOps or Jira cases in place once the user agrees.",
|
|
122
134
|
tools: ["check_test_cases", "update_test_case"],
|
|
135
|
+
keywords: ["good enough", "quality", "improve", "fix the expected", "expected result", "check the cases", "check cases", "review the cases", "rewrite"],
|
|
123
136
|
examples: ["Are the cases in test plan 12 good enough to run?", "Fix the expected results of case 4512"],
|
|
124
137
|
requires: [{ kind: "system", anyOf: ["azure_devops", "jira"], optional: true, why: "to rewrite cases in place" }],
|
|
125
138
|
docs: [{ doc: "HOW_TO_USE.md", heading: "Checking test cases before they run" }],
|
|
@@ -131,6 +144,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
131
144
|
summary:
|
|
132
145
|
"Writes test cases from a story's acceptance criteria and saves them in Azure DevOps or Jira, linked to the story. Reads the cases already linked first, so asking again adds only what is missing and updates cases in place instead of duplicating them; plans end-to-end journeys from the existing cases.",
|
|
133
146
|
tools: ["story_test_cases", "save_test_cases", "e2e_journey"],
|
|
147
|
+
keywords: ["write test cases", "test cases for", "story", "acceptance criteria", "journey", "end-to-end", "generate cases", "save them"],
|
|
134
148
|
examples: ["Write test cases for story 1234 and save them", "Build an end-to-end journey from story 1234's cases"],
|
|
135
149
|
requires: [{ kind: "system", anyOf: ["azure_devops", "jira"] }],
|
|
136
150
|
docs: [{ doc: "HOW_TO_USE.md", heading: "Where cases come from" }],
|
|
@@ -146,6 +160,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
146
160
|
summary:
|
|
147
161
|
"Every agent works as an expert in the product's industry would, automatically: the risks the industry is known for and the obligations of its regulations and standards (for example DORA, PSD2, GDPR, ISO 27001), declared in forgeqe.yaml or worked out from the story and the application. It applies to analysing requirements, writing and checking test cases, exploration, writing, reviewing, and healing test code, API tests, finding tests, and pull request reviews, labelled so coverage can be traced and citing only the control ids in the team's compliance file. forgeqe eval domain measures how well the agent does it.",
|
|
148
162
|
tools: [],
|
|
163
|
+
keywords: ["gdpr", "dora", "psd2", "hipaa", "pci", "sox", "iso 27001", "compliance", "regulation", "obligation"],
|
|
149
164
|
examples: ["Write test cases for story 1234, covering our DORA and PSD2 obligations", "Which GDPR obligations do these cases miss?"],
|
|
150
165
|
requires: [{ kind: "domain", optional: true }],
|
|
151
166
|
limits: ["Designs and checks tests for regulatory obligations; it does not certify compliance or replace an audit.", "Cites control or article ids only from the team's compliance file, never from memory."],
|
|
@@ -158,11 +173,86 @@ export const CAPABILITIES: Capability[] = [
|
|
|
158
173
|
summary:
|
|
159
174
|
"Explores a story in real browsers beyond its scripted cases (edge cases, invalid input, boundaries, roles, state), confirms each defect by reproducing it, proposes test cases, and files bugs once the user agrees.",
|
|
160
175
|
tools: ["explore_story"],
|
|
176
|
+
keywords: ["explore", "exploratory", "find bugs", "beyond the test cases", "charter"],
|
|
161
177
|
examples: ["Explore story 1234 and find bugs", "Test this feature beyond the test cases"],
|
|
162
178
|
requires: [{ kind: "live_model" }, { kind: "system", anyOf: ["azure_devops", "jira"], optional: true, why: "to read the story and file bugs" }],
|
|
163
179
|
docs: [{ doc: "HOW_TO_USE.md", heading: "Exploratory testing from a story" }],
|
|
164
180
|
playbook: [{ heading: "Exploratory testing", when: "explore a story or feature past its scripted cases, or find bugs in it" }],
|
|
165
181
|
},
|
|
182
|
+
{
|
|
183
|
+
id: "test-apps",
|
|
184
|
+
area: "Setup",
|
|
185
|
+
title: "Test environments and their apps",
|
|
186
|
+
summary:
|
|
187
|
+
"Knows the test environments in env.yaml (QA, UAT, …) and each one's apps: web apps and APIs by URL, Windows desktop apps, and mobile apps, with what each is for. Every agent works from the active one; switches environment on request, picks the apps a scenario needs, and carries values such as an order number from one app to the next.",
|
|
188
|
+
tools: ["test_apps", "use_environment"],
|
|
189
|
+
keywords: ["environment", "env", "qa", "uat", "staging", "apps", "which apps"],
|
|
190
|
+
examples: ["Place an order in the shop, then check its invoice in Billing, in UAT", "Which apps can you test in QA?", "From now on, use the UAT environment"],
|
|
191
|
+
requires: [{ kind: "input", what: "env.yaml with the environments and apps (forgeqe init writes one)" }],
|
|
192
|
+
limits: [],
|
|
193
|
+
docs: [{ doc: "HOW_TO_USE.md", heading: "Environments and apps" }],
|
|
194
|
+
playbook: [{ heading: "Apps and environments", when: "a request names an app or environment, or a scenario spans several apps" }],
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "desktop-apps",
|
|
198
|
+
area: "Desktop",
|
|
199
|
+
title: "Automate Windows desktop apps",
|
|
200
|
+
summary:
|
|
201
|
+
"Carries out a plain-language goal in a Windows thick-client app with UFO² and saves it as a workflow with checks of the outcome, or records the steps a tester does by hand, with no model; replays saved workflows with no model, healing a step whose control moved, and fixes a workflow from chat (a timeout, a wait, a dialog to dismiss) after showing the change. Apps and the secrets they may type come from env.yaml or forgeqe.yaml desktop:.",
|
|
202
|
+
tools: ["desktop_explore", "desktop_record", "desktop_replay", "desktop_edit_workflow"],
|
|
203
|
+
keywords: ["desktop", "windows app", "thick client", "thick-client", ".exe", "record", "replay", "workflow", "ufo", "notepad", "win32", "wpf", "winforms"],
|
|
204
|
+
examples: [
|
|
205
|
+
"Create an invoice for Contoso in the Billing app and save it as a test",
|
|
206
|
+
"I'll show you the steps in Billing: record them",
|
|
207
|
+
"Replay the billing workflow with customer Fabrikam",
|
|
208
|
+
"Step 4 timed out while the list loaded: fix the workflow",
|
|
209
|
+
],
|
|
210
|
+
requires: [
|
|
211
|
+
{ kind: "live_model", optional: true },
|
|
212
|
+
{ kind: "apps", app: "desktop" },
|
|
213
|
+
{ kind: "input", what: "a Windows machine, and the app in forgeqe.yaml desktop.apps" },
|
|
214
|
+
],
|
|
215
|
+
limits: ["Windows only, one run at a time per desktop", "Apps whose controls UI Automation cannot see (Citrix, some Java and custom-drawn apps) are not supported yet"],
|
|
216
|
+
docs: [{ doc: "HOW_TO_USE.md", heading: "Desktop apps (Windows)" }],
|
|
217
|
+
playbook: [{ heading: "Desktop apps", when: "automate or test a Windows desktop (thick-client) app" }],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "mobile-apps",
|
|
221
|
+
area: "Mobile",
|
|
222
|
+
title: "Automate Android and iOS apps",
|
|
223
|
+
summary:
|
|
224
|
+
"Carries out a plain-language goal in an Android or iOS app through Appium and saves it as a mobile workflow with checks of the outcome; replays workflows with no model on Appium (healing a moved control) or Maestro; runs the team's own Maestro flows after checking them against an allowlist. Apps come from env.yaml (kind: mobile) or forgeqe.yaml mobile:.",
|
|
225
|
+
tools: ["mobile_explore", "mobile_replay", "mobile_maestro"],
|
|
226
|
+
keywords: ["mobile", "android", "ios", "iphone", "ipad", "phone", "tablet", "apk", "ipa", "maestro", "appium", "emulator", "simulator", "app store", "play store"],
|
|
227
|
+
examples: ["Sign in to the courier app and accept the first delivery, and save it as a test", "Replay the courier login on Maestro", "Run our Maestro flows in .maestro/"],
|
|
228
|
+
requires: [
|
|
229
|
+
{ kind: "live_model", optional: true },
|
|
230
|
+
{ kind: "apps", app: "mobile" },
|
|
231
|
+
{ kind: "input", what: "an Appium server with a device or emulator (or a device cloud), or Maestro installed; the app in env.yaml or forgeqe.yaml mobile.apps" },
|
|
232
|
+
],
|
|
233
|
+
limits: ["Explore runs on Appium only", "Maestro replays do not heal moved controls or compare images (expect_image)", "On TestMu AI, Maestro (through HyperExecute) refuses workflows that type secrets; replay those there with --driver appium"],
|
|
234
|
+
docs: [{ doc: "HOW_TO_USE.md", heading: "Mobile apps (Android, iOS)" }],
|
|
235
|
+
playbook: [{ heading: "Mobile apps", when: "automate or test an Android or iOS app" }],
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: "containers",
|
|
239
|
+
area: "Operations",
|
|
240
|
+
title: "Services and mocks in Docker or Podman",
|
|
241
|
+
summary:
|
|
242
|
+
"Starts the services a test run needs (a database, a queue, the API under test) in Docker or Podman, as forgeqe.yaml declares them, and waits until each is ready; stops them, and shows their status and logs. Starts mocks of an API on WireMock, from the calls a browser run recorded or from an OpenAPI description, and can point an API app of the environment at the mock for the session.",
|
|
243
|
+
tools: ["services", "mock_service"],
|
|
244
|
+
keywords: ["docker", "podman", "container", "containers", "compose", "mock", "mocks", "mock service", "mock server", "mock api", "stub", "stubs", "wiremock", "testcontainers", "service up", "services up"],
|
|
245
|
+
examples: ["Start the database and the queue in Docker", "Start the services from our docker-compose file", "Mock the payments API from the last run", "Create a mock service from openapi.yaml and point order-api at it", "Show the logs of the db container"],
|
|
246
|
+
requires: [{ kind: "containers" }],
|
|
247
|
+
limits: [
|
|
248
|
+
"Starts only services declared in forgeqe.yaml containers.services; it never runs an image, flag or mount from a request",
|
|
249
|
+
"No privileged containers, host network, or access to the container runtime's socket; ports on 127.0.0.1 only; mounts read-only from inside the project",
|
|
250
|
+
"Mocks answer REST calls (WireMock); GraphQL, SOAP and gRPC are not mocked",
|
|
251
|
+
"Reads docker-compose files only for their services' image, ports, environment, bind mounts, command and depends_on; refuses build, privileged, network_mode, cap_add, devices and env_file; leaves named volumes out",
|
|
252
|
+
],
|
|
253
|
+
docs: [{ doc: "HOW_TO_USE.md", heading: "Containers and mock services" }],
|
|
254
|
+
playbook: [{ heading: "Containers and mocks", when: "start, stop or inspect the project's services in Docker or Podman, or mock an API" }],
|
|
255
|
+
},
|
|
166
256
|
{
|
|
167
257
|
id: "write-tests",
|
|
168
258
|
area: "Automated tests",
|
|
@@ -170,6 +260,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
170
260
|
summary:
|
|
171
261
|
"Scribe, the coding agent, writes a test for a passed browser run in the repository's own framework, page objects, and helpers; ForgeQE runs it until it passes and a reviewer approves it.",
|
|
172
262
|
tools: ["write_test", "run_test"],
|
|
263
|
+
keywords: ["automated test", "automation", "automate", "playwright", "cypress", "selenium", "webdriverio", "write a test", "test code", "spec", "turn it into a test", "turn that run into", "scribe"],
|
|
173
264
|
examples: ["Write a Playwright test for that run in ../app"],
|
|
174
265
|
requires: [{ kind: "coding_agent" }, { kind: "input", what: "a repository to write the test in (default: the working directory)" }],
|
|
175
266
|
docs: [{ doc: "HOW_TO_USE.md", heading: "6. Tests" }],
|
|
@@ -182,6 +273,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
182
273
|
title: "Heal failing tests",
|
|
183
274
|
summary: "Runs a repository's tests and updates the ones the application has outgrown against the live app, or reports a real application bug instead.",
|
|
184
275
|
tools: ["heal_tests"],
|
|
276
|
+
keywords: ["heal", "failing test", "broken test", "fix the tests", "fix tests", "repair"],
|
|
185
277
|
examples: ["Heal the failing tests in ../app"],
|
|
186
278
|
requires: [{ kind: "coding_agent" }, { kind: "input", what: "a repository with tests" }],
|
|
187
279
|
docs: [{ doc: "HOW_TO_USE.md", heading: "Fixing broken tests" }],
|
|
@@ -193,6 +285,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
193
285
|
title: "Find and run existing tests",
|
|
194
286
|
summary: "Finds the automated tests that match a feature, tags, ids, a folder, or a sprint, confirms the selection, and runs it once the user agrees, with a verdict per test.",
|
|
195
287
|
tools: ["find_tests", "run_tests", "list_sprints"],
|
|
288
|
+
keywords: ["run the tests", "run tests", "run the", "existing tests", "tests in", "test suite", "npm test", "pytest", "added in sprint", "checkout tests", "tests added"],
|
|
196
289
|
examples: ["Run the checkout tests", "Run the tests added in sprint 12"],
|
|
197
290
|
requires: [
|
|
198
291
|
{ kind: "coding_agent" },
|
|
@@ -208,6 +301,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
208
301
|
title: "Regression packs for a release",
|
|
209
302
|
summary: "Builds a regression pack from what goes into a release (an Azure DevOps sprint or work items, a commit range, pull requests, or changes read from any connected tool): the tests those changes can break, each with why, plus risk-area and smoke tests, and the changes no test covers. Runs it locally, on TestMu AI, or in a pipeline, and compares each run with the last.",
|
|
210
303
|
tools: ["build_regression_pack", "run_regression_pack", "list_regression_packs"],
|
|
304
|
+
keywords: ["regression", "pack", "which tests should", "release candidate", "merged this week"],
|
|
211
305
|
examples: ["Build a regression pack for sprint 12 and run it on TestMu", "Which tests should we run for the PRs merged this week?"],
|
|
212
306
|
requires: [
|
|
213
307
|
{ kind: "input", what: "a repository with tests" },
|
|
@@ -222,6 +316,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
222
316
|
title: "Flaky test detection and quarantine",
|
|
223
317
|
summary: "Finds tests that flip between passing and failing in the repository's run history, with their likely cause, and quarantines them once the user agrees.",
|
|
224
318
|
tools: ["flaky_tests", "quarantine_tests"],
|
|
319
|
+
keywords: ["flaky", "quarantine", "intermittent", "unstable", "flakes"],
|
|
225
320
|
examples: ["Which tests are flaky?", "Quarantine the flaky login test"],
|
|
226
321
|
requires: [{ kind: "input", what: "a repository whose tests ForgeQE has run" }],
|
|
227
322
|
docs: [{ doc: "HOW_TO_USE.md", heading: "Flaky tests" }],
|
|
@@ -232,6 +327,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
232
327
|
title: "Review pull requests",
|
|
233
328
|
summary: "Reviews an Azure DevOps or GitHub pull request from the local clone for bugs, security, requirements, and tests, and posts the review once the user agrees.",
|
|
234
329
|
tools: ["review_pull_request", "post_pr_review"],
|
|
330
|
+
keywords: ["review pr", "review the pr", "review pull request", "pull request", "code review", "pr review", "review the change"],
|
|
235
331
|
examples: ["Review PR 42 and focus on error handling"],
|
|
236
332
|
requires: [
|
|
237
333
|
{ kind: "coding_agent" },
|
|
@@ -249,6 +345,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
249
345
|
summary:
|
|
250
346
|
"Works out which product areas, pages, requirements, and tests a pull request touches, picks the tests it can break, runs them, and posts the impact or results on the pull request once the user agrees.",
|
|
251
347
|
tools: ["analyze_pr_impact", "select_pr_tests", "post_pr_test_results", "comment_on_pr"],
|
|
348
|
+
keywords: ["impact", "pr ", "pull request", "affected", "can break", "what does pr", "changed files"],
|
|
252
349
|
examples: ["What does PR 42 impact?", "Run the tests PR 42 can break"],
|
|
253
350
|
requires: [
|
|
254
351
|
{ kind: "input", what: "a local clone of the repository" },
|
|
@@ -266,6 +363,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
266
363
|
summary:
|
|
267
364
|
"Builds API contracts (status, schema, key fields) from the calls a browser run saw or from an API description (OpenAPI, Swagger, WSDL, GraphQL, .proto, gRPC reflection), and end-to-end API scenarios from runs, descriptions, test cases, or plain English. Runs them straight against the API without a browser, or writes them as tests in the repository. REST, GraphQL, SOAP, and gRPC.",
|
|
268
365
|
tools: ["api_contracts", "run_api_contracts", "api_scenarios", "run_api_scenarios", "write_api_tests"],
|
|
366
|
+
keywords: ["api", "endpoint", "openapi", "swagger", "contract", "rest", "graphql", "postman", "request", "response"],
|
|
269
367
|
examples: ["Turn the last run's API calls into tests", "Test the API in openapi.yaml", "Write API scenario tests for the order lifecycle"],
|
|
270
368
|
requires: [{ kind: "coding_agent", optional: true }],
|
|
271
369
|
limits: ["Functional API testing, not load or stress testing."],
|
|
@@ -281,6 +379,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
281
379
|
title: "Accessibility and page performance",
|
|
282
380
|
summary: "Checks every page a run visits against WCAG rules (axe-core) and measures its Core Web Vitals, against budgets, with trends over time per page.",
|
|
283
381
|
tools: ["quality_report", "quality_trends"],
|
|
382
|
+
keywords: ["accessible", "accessibility", "a11y", "wcag", "performance", "lighthouse", "slow", "page speed", "web vitals", "load time"],
|
|
284
383
|
examples: ["Is the checkout page accessible?", "Did page performance get worse this week?"],
|
|
285
384
|
requires: [{ kind: "input", what: "a browser run (from browser_run or run_test_cases); new runs need a live model" }],
|
|
286
385
|
limits: ["Per-page Core Web Vitals, not load testing under many users."],
|
|
@@ -294,6 +393,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
294
393
|
title: "Visual comparison with baselines",
|
|
295
394
|
summary: "Compares each page a run captures with its approved baseline and shows what changed; intended changes become the new baseline once the user agrees.",
|
|
296
395
|
tools: ["visual_compare", "visual_approve"],
|
|
396
|
+
keywords: ["visual", "visually", "baseline", "looks different", "approve the new", "pixel", "screenshot diff", "layout"],
|
|
297
397
|
examples: ["What changed visually since the last run?", "Approve the new header"],
|
|
298
398
|
requires: [{ kind: "input", what: "a browser run (from browser_run or run_test_cases); new runs need a live model" }],
|
|
299
399
|
docs: [{ doc: "HOW_TO_USE.md", heading: "Visual comparison" }],
|
|
@@ -305,6 +405,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
305
405
|
summary:
|
|
306
406
|
"Groups failures into defects, keeps their history across suites (new, known, gone, back again), and gives defect trends and where a change is likely to cause defects, with the cases to run first.",
|
|
307
407
|
tools: ["defect_trends", "predict_defects"],
|
|
408
|
+
keywords: ["defect", "bugs keep", "trend", "predict", "coming back", "root cause", "rca", "hotspot", "since main", "changes since", "can the changes"],
|
|
308
409
|
examples: ["Which defects keep coming back?", "What can the changes since main break?"],
|
|
309
410
|
requires: [
|
|
310
411
|
{ kind: "input", what: "suites run into one folder (suite.out_dir)" },
|
|
@@ -322,6 +423,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
322
423
|
title: "Requirement coverage and release readiness",
|
|
323
424
|
summary: "Gives requirement coverage and a GO / GO WITH RISKS / NO-GO verdict for a sprint, area, epic, or stories, from test cases, results, bugs, and flaky tests; can publish it to the wiki.",
|
|
324
425
|
tools: ["release_readiness"],
|
|
426
|
+
keywords: ["release", "ready to release", "coverage", "go/no-go", "no-go", "readiness", "requirement coverage", "ship"],
|
|
325
427
|
examples: ["Are we ready to release?", "Coverage of sprint 12"],
|
|
326
428
|
requires: [{ kind: "system", anyOf: ["azure_devops", "jira"] }],
|
|
327
429
|
docs: [{ doc: "HOW_TO_USE.md", heading: "Requirement coverage and release readiness" }],
|
|
@@ -332,6 +434,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
332
434
|
title: "Scheduled monitoring with alerts",
|
|
333
435
|
summary: "Runs checks on a schedule and alerts Teams or email when one keeps failing, and again when it recovers.",
|
|
334
436
|
tools: ["monitor"],
|
|
437
|
+
keywords: ["monitor", "every minute", "every hour", "every day", "every night", "every 5 minutes", "every 15 minutes", "every 30 minutes", "schedule", "uptime", "alert", "cron", "minutes", "hourly", "nightly"],
|
|
335
438
|
examples: ["Check the login flow every 15 minutes and alert Teams"],
|
|
336
439
|
requires: [{ kind: "live_model" }, { kind: "monitor_alerts", optional: true }],
|
|
337
440
|
docs: [{ doc: "HOW_TO_USE.md", heading: "9. Monitoring" }],
|
|
@@ -344,6 +447,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
344
447
|
summary:
|
|
345
448
|
"Tells the team in Microsoft Teams channels or by email when a suite fails or passes, when a release readiness verdict is in, and when the domain evaluation falls below its gate, with the failing cases, their bugs, and links to the report and the CI build. Each channel picks its events; the agent also posts a summary to a channel when asked.",
|
|
346
449
|
tools: ["notify"],
|
|
450
|
+
keywords: ["teams", "email", "notify", "post the results", "channel", "tell the", "send the"],
|
|
347
451
|
examples: ["Post the results to the QA channel", "Tell the release channel the verdict is NO-GO"],
|
|
348
452
|
requires: [{ kind: "notify_channels" }],
|
|
349
453
|
limits: ["Automatic notifications come from runs nobody is watching (forgeqe suite, forgeqe run, pipelines, the MCP server), not from the interactive shell, where the agent posts only when asked."],
|
|
@@ -356,6 +460,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
356
460
|
summary:
|
|
357
461
|
"Writes up what ForgeQE's agent found, such as open defects across projects, release status, or pipeline health, as a page with filters (project, state, priority, assignee, …), charts, and a sortable table, plus Markdown and CSV. Every table is built from evidence: the results of the tool calls the agent made, or a read-only query ForgeQE runs itself, listed on the page's Sources with each raw result kept beside the report.",
|
|
358
462
|
tools: ["write_report"],
|
|
463
|
+
keywords: ["report", "write up", "write-up", "summary", "share", "pdf", "html report"],
|
|
359
464
|
examples: ["Create a release report for all projects", "Write up the open defects as a report I can share"],
|
|
360
465
|
requires: [{ kind: "live_model" }],
|
|
361
466
|
limits: [
|
|
@@ -371,6 +476,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
371
476
|
title: "CI pipelines",
|
|
372
477
|
summary: "Writes a GitHub Actions or Azure Pipelines workflow that runs ForgeQE, stores the secrets it needs, pushes a branch, and makes the pipeline green.",
|
|
373
478
|
tools: ["push_branch", "set_ci_secret"],
|
|
479
|
+
keywords: ["pipeline", "github actions", "azure pipelines", "ci", "gitlab", "jenkins", "make it green", "ci secret", "workflow file", "on every pr"],
|
|
374
480
|
examples: ["Add a GitHub Actions workflow that runs tests/cases.yaml on every PR, and make it green"],
|
|
375
481
|
requires: [
|
|
376
482
|
{ kind: "command", command: "git" },
|
|
@@ -385,6 +491,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
385
491
|
title: "Work with Azure DevOps, Jira, GitHub, and Confluence",
|
|
386
492
|
summary: "Reads anything in the configured systems (work items, issues, queries, boards, pages), makes changes the user asks for, puts run evidence (screenshots, reports) on work items and issues as attachments or comments, and builds Azure DevOps dashboards and wiki pages.",
|
|
387
493
|
tools: ["query_api", "read_confluence", "create_ado_dashboard", "write_ado_wiki_page", "attach_to_work_item"],
|
|
494
|
+
keywords: ["jira", "azure devops", "ado", "confluence", "system", "github issue", "work item", "bug", "sprint", "dashboard", "wiki", "comment", "backlog", "issue", "ticket", "requirement"],
|
|
388
495
|
examples: [
|
|
389
496
|
"Show the open bugs in this sprint",
|
|
390
497
|
"Read the checkout requirements from Confluence",
|
|
@@ -404,7 +511,8 @@ export const CAPABILITIES: Capability[] = [
|
|
|
404
511
|
area: "Setup",
|
|
405
512
|
title: "Setup, skills, and documentation",
|
|
406
513
|
summary: "Shows the model profiles and MCP servers, installs or repairs the browser engine, creates a forgeqe.yaml, manages skills, and answers questions about ForgeQE from its documentation and this list.",
|
|
407
|
-
tools: ["forgeqe_setup", "capabilities", "playbook"],
|
|
514
|
+
tools: ["forgeqe_setup", "capabilities", "playbook", "use_capabilities"],
|
|
515
|
+
keywords: ["help", "what can you", "how do i", "set up", "setup", "configure", "model", "profile", "skill", "install"],
|
|
408
516
|
examples: ["What can you do?", "Which model am I using?", "How do I set up email alerts?"],
|
|
409
517
|
requires: [],
|
|
410
518
|
docs: [
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AppConfig } from "../config.ts"
|
|
2
2
|
import { findOpencode } from "../code/runtime.ts"
|
|
3
3
|
import { noAgentModel } from "../llm.ts"
|
|
4
|
+
import { findRuntime } from "../containers/run.ts"
|
|
4
5
|
import { CAPABILITIES, type Capability, type Requirement, type System } from "./registry.ts"
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -87,6 +88,16 @@ export function requirementGap(r: Requirement, cfg: AppConfig, probes: Probes =
|
|
|
87
88
|
return cfg.domain ? null : { missing: "the product's industry and regulations", fix: "set domain: in forgeqe.yaml, and list your applicable controls in .forgeqe/knowledge/compliance.md" }
|
|
88
89
|
case "command":
|
|
89
90
|
return probes.which(r.command) ? null : { missing: `${r.command} on PATH${r.why ? ` (${r.why})` : ""}`, fix: `install ${r.command}` }
|
|
91
|
+
case "apps": {
|
|
92
|
+
const apps = r.app === "desktop" ? cfg.desktop?.apps : cfg.mobile?.apps
|
|
93
|
+
return Object.keys(apps || {}).length
|
|
94
|
+
? null
|
|
95
|
+
: { missing: `a ${r.app} app to test`, fix: `add it to env.yaml (kind: ${r.app}) or to ${r.app}.apps in forgeqe.yaml` }
|
|
96
|
+
}
|
|
97
|
+
case "containers": {
|
|
98
|
+
const which = (c: string) => (probes.which(c) ? c : null)
|
|
99
|
+
return findRuntime(cfg, which) ? null : { missing: `${cfg.containers?.runtime || "Docker or Podman"} on PATH`, fix: "install Docker Desktop, Docker Engine or Podman (containers.runtime in forgeqe.yaml picks one)" }
|
|
100
|
+
}
|
|
90
101
|
case "input":
|
|
91
102
|
return null
|
|
92
103
|
case "either": {
|
|
@@ -130,6 +141,10 @@ export function describeRequirement(r: Requirement): string {
|
|
|
130
141
|
return "The product's industry and regulations: domain: in forgeqe.yaml, with your applicable controls in .forgeqe/knowledge/compliance.md"
|
|
131
142
|
case "command":
|
|
132
143
|
return `${r.command} on PATH${r.why ? `, ${r.why}` : ""}`
|
|
144
|
+
case "apps":
|
|
145
|
+
return `${r.app === "desktop" ? "Windows desktop" : "Mobile"} apps: env.yaml (kind: ${r.app}) or ${r.app}.apps in forgeqe.yaml`
|
|
146
|
+
case "containers":
|
|
147
|
+
return "Docker or Podman on PATH; the services to start in containers.services in forgeqe.yaml (mocks need none)"
|
|
133
148
|
case "input":
|
|
134
149
|
return `From your request: ${r.what}`
|
|
135
150
|
case "either":
|
package/src/cases/rca.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { lastPassed } from "./defectHistory.ts"
|
|
|
13
13
|
import { routeOf, type DefectCluster } from "./defects.ts"
|
|
14
14
|
import { reproText } from "./repro.ts"
|
|
15
15
|
import type { CaseResult } from "./types.ts"
|
|
16
|
+
import { guardrailsNotes } from "../guardrails.ts"
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Defect intelligence, part three (b): why a defect may happen. Always a hypothesis, never a verdict.
|
|
@@ -219,6 +220,7 @@ export async function analyzeCause(
|
|
|
219
220
|
repo,
|
|
220
221
|
profile: config.llm,
|
|
221
222
|
domain: domainNotes(config),
|
|
223
|
+
guardrails: guardrailsNotes(config),
|
|
222
224
|
model: config.code.model,
|
|
223
225
|
bin: config.code.opencode,
|
|
224
226
|
readDirs: [repo],
|
package/src/childEnv.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { savedSecrets } from "./auth/store.ts"
|
|
2
2
|
import type { AppConfig } from "./config.ts"
|
|
3
|
+
import { COPILOT_AUTH_ENV, MODELS_TOKEN_ENV, RUNTIME_AUTH_ENV } from "./copilot.ts"
|
|
3
4
|
import { NO_TELEMETRY_ENV, withoutTelemetry } from "./privacy.ts"
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -72,7 +73,9 @@ function collectEnvNames(value: unknown, out: Set<string>, inLogins: boolean): v
|
|
|
72
73
|
* (`*Env` settings), and variables `mcp:` servers reference. Saved-login variables are left out: tests read them.
|
|
73
74
|
*/
|
|
74
75
|
export function privateEnvNames(cfg: AppConfig, extra: Iterable<string> = []): string[] {
|
|
75
|
-
const out = new Set<string>(extra)
|
|
76
|
+
const out = new Set<string>([...extra, COPILOT_AUTH_ENV, RUNTIME_AUTH_ENV, MODELS_TOKEN_ENV])
|
|
77
|
+
// A github_copilot profile without api_key_env reaches GitHub Models with GITHUB_TOKEN: a model key then.
|
|
78
|
+
if (cfg.llm?.provider === "github_copilot" && !cfg.llm.apiKeyEnv) out.add("GITHUB_TOKEN")
|
|
76
79
|
collectEnvNames({ ...cfg, logins: undefined }, out, false)
|
|
77
80
|
for (const name of envRefs(cfg.mcp)) out.add(name)
|
|
78
81
|
// Secrets a saved sign-in put into the environment, including those saved by name (forgeqe auth login NAME).
|
package/src/cli/args.ts
CHANGED
|
@@ -8,8 +8,8 @@ export type Flags = Map<string, string | boolean>
|
|
|
8
8
|
|
|
9
9
|
/** Flags that never take the next word as their value, so `run --direct "<goal>"` keeps the goal. `--headed=false` still works. */
|
|
10
10
|
export const SWITCHES = new Set([
|
|
11
|
-
"all", "check", "debug", "direct", "force", "global", "guide", "headed", "help", "locators", "no-code", "no-memory", "no-write-back",
|
|
12
|
-
"once", "open", "plain", "shots", "steps", "test-alerts", "user", "verbose", "yes",
|
|
11
|
+
"accept-heals", "all", "all-tools", "check", "debug", "direct", "force", "global", "guide", "headed", "help", "locators", "no-code", "no-heal", "no-memory", "no-write-back",
|
|
12
|
+
"once", "open", "plain", "shots", "steps", "test-alerts", "ufo", "update-baselines", "user", "verbose", "virtual", "yes",
|
|
13
13
|
])
|
|
14
14
|
|
|
15
15
|
export function parseArgs(argv: string[]): { positionals: string[]; flags: Flags } {
|
|
@@ -89,6 +89,7 @@ export function configOpts(flags: Flags) {
|
|
|
89
89
|
return {
|
|
90
90
|
configPath: strFlag(flags, "config"),
|
|
91
91
|
profile: strFlag(flags, "profile"),
|
|
92
|
+
env: strFlag(flags, "env"),
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
package/src/cli/help.ts
CHANGED
|
@@ -13,6 +13,7 @@ Usage:
|
|
|
13
13
|
forgeqe models [--config forgeqe.yaml]
|
|
14
14
|
forgeqe features [--config forgeqe.yaml] what ForgeQE can do, and what each capability still needs here
|
|
15
15
|
forgeqe context what each agent loads on every model call, against its budget (exits 1 over budget)
|
|
16
|
+
forgeqe guardrails [add "rule"] the team's rules every agent must follow (.forgeqe/guardrails), or add one
|
|
16
17
|
forgeqe docs [--guide|--open] [--copy DIR] the guides and the architecture page (--guide: the user guide in the browser; --open: the architecture page)
|
|
17
18
|
forgeqe start [--profile name] [--url URL] [--goal TEXT] [--headed] [--replay MODE] [--no-memory]
|
|
18
19
|
[--repo DIR] [--no-code] [--browser-provider local|browserstack|testmu] [--devices a,b]
|
|
@@ -26,6 +27,26 @@ Usage:
|
|
|
26
27
|
[--profile name] [--url URL] [--headed] [--plain] [--attempts N] [--skill NAME]
|
|
27
28
|
[--browser-provider local|browserstack|testmu] [--devices a,b]
|
|
28
29
|
run test cases the same way every time (what pipelines call)
|
|
30
|
+
forgeqe apps [--env NAME] the test environments in env.yaml and the apps (web, desktop, mobile) of one;
|
|
31
|
+
every command takes --env NAME (or FORGEQE_ENV) to pick the environment
|
|
32
|
+
forgeqe desktop explore "<goal>" --app NAME [--name TEXT] [--out DIR] [--yes]
|
|
33
|
+
Windows thick clients: UFO² does the goal on the live app and the run is
|
|
34
|
+
saved as a YAML workflow (docs/DESKTOP.md)
|
|
35
|
+
forgeqe desktop record --app NAME [--name TEXT] do the steps in a Windows app yourself; they are saved as a workflow
|
|
36
|
+
forgeqe desktop replay <workflow.yaml|folder> ... [name=value ...] [--no-heal] [--accept-heals] [--junit FILE]
|
|
37
|
+
replay desktop workflows with no model; moved controls are healed for review
|
|
38
|
+
forgeqe desktop setup [--ufo] | apps | doctor | validate <workflow.yaml>
|
|
39
|
+
forgeqe mobile explore "<goal>" --app NAME Android and iOS apps through Appium: the model does the goal and the run
|
|
40
|
+
is saved as a mobile workflow (docs/MOBILE.md)
|
|
41
|
+
forgeqe mobile replay <workflow.yaml|folder> [--driver appium|maestro] [--junit FILE]
|
|
42
|
+
replay mobile workflows with no model, on Appium or Maestro
|
|
43
|
+
forgeqe mobile maestro <flow.yaml|folder> run your team's Maestro flows (checked against an allowlist first)
|
|
44
|
+
forgeqe mobile upload <app> [--virtual] upload a build to TestMu AI; prints the lt:// id for the app's app:
|
|
45
|
+
forgeqe mobile apps | doctor | flow <workflow.yaml> | validate <workflow.yaml>
|
|
46
|
+
forgeqe services [status|up|down|logs] [name ...]
|
|
47
|
+
the services forgeqe.yaml (containers.services) or its compose files
|
|
48
|
+
declare, in Docker or Podman: start in order and wait until ready,
|
|
49
|
+
stop, logs (docs/CONTAINERS.md)
|
|
29
50
|
forgeqe cases <source...> [--goal] list the test cases a source gives (--goal: the goal each runs)
|
|
30
51
|
forgeqe code [run-dir] [--repo DIR] [--attempts N] [--code-model provider/model]
|
|
31
52
|
write a test for a run (default: the latest) with Scribe
|
|
@@ -36,7 +57,7 @@ Usage:
|
|
|
36
57
|
[--browser-provider local|browserstack|testmu]
|
|
37
58
|
check flows on a schedule and alert Teams or email when one breaks
|
|
38
59
|
forgeqe monitor --test-alerts send a sample alert to check the Teams and email settings
|
|
39
|
-
forgeqe mcp [--repo DIR] [--profile name] [--url URL] [--headed] [--browser-provider local|browserstack|testmu]
|
|
60
|
+
forgeqe mcp [--all-tools] [--repo DIR] [--profile name] [--url URL] [--headed] [--browser-provider local|browserstack|testmu]
|
|
40
61
|
ForgeQE's tools as an MCP server on stdio, for other agents
|
|
41
62
|
forgeqe mcp list the Azure DevOps, Jira and other MCP servers ForgeQE's agent gets
|
|
42
63
|
forgeqe bi [build] [--evidence-dir DIR] [--data-dir DIR] [--build-dir DIR] [--static | --evidence]
|
|
@@ -90,8 +111,8 @@ Usage:
|
|
|
90
111
|
forgeqe api tests [SET] --scenarios[=a,b] --repo DIR
|
|
91
112
|
scenario tests on the repository's existing API helpers
|
|
92
113
|
forgeqe api list saved contract sets
|
|
93
|
-
forgeqe auth login [system] [--no-browser] sign in to azure_devops, github, jira, confluence, testrail, zephyr,
|
|
94
|
-
xray, browserstack, or testmu in the browser, and save the token in the system's credential store
|
|
114
|
+
forgeqe auth login [system] [--no-browser] sign in to azure_devops, github, copilot, jira, confluence, testrail, zephyr,
|
|
115
|
+
qtest, xray, browserstack, or testmu in the browser, and save the token in the system's credential store
|
|
95
116
|
forgeqe auth login <ENV_NAME> save one secret (an LLM API key, a webhook URL) in the credential store
|
|
96
117
|
forgeqe auth [status] which systems are signed in, and how
|
|
97
118
|
forgeqe auth logout <system|ENV_NAME> forget a saved sign-in
|
package/src/cli/index.ts
CHANGED
|
@@ -56,9 +56,15 @@ import { DOMAIN_CASES, gate, loadDomainCases, reportMarkdown, runDomainEval, sav
|
|
|
56
56
|
import { evalNotice, notifiedLine, notify, notifySuite } from "../notify/events.ts"
|
|
57
57
|
import { sendNotice, type Notice } from "../notify/notice.ts"
|
|
58
58
|
import { buildBi, initBiProject, syncBiData } from "../bi.ts"
|
|
59
|
+
import { cmdDesktop } from "../desktop/command.ts"
|
|
60
|
+
import { cmdMobile } from "../mobile/command.ts"
|
|
61
|
+
import { cmdServices } from "../containers/command.ts"
|
|
62
|
+
import { startedContainers, stopStartedContainers } from "../containers/run.ts"
|
|
63
|
+
import { CapabilityScope } from "../mcp/scope.ts"
|
|
64
|
+
import { appsText } from "../environments.ts"
|
|
59
65
|
|
|
60
66
|
const AGENT_COMMANDS = new Set(["start", "run", "code", "suite", "heal", "monitor", "explore"])
|
|
61
|
-
const LOGGED_COMMANDS = new Set([...AGENT_COMMANDS, "mcp", "login", "setup", "bi"])
|
|
67
|
+
const LOGGED_COMMANDS = new Set([...AGENT_COMMANDS, "mcp", "login", "setup", "bi", "desktop", "mobile", "services"])
|
|
62
68
|
|
|
63
69
|
/** How much of the agents' work to print: brief unless --verbose. */
|
|
64
70
|
const detailOf = (flags: Flags) => detailFrom(boolFlag(flags, "verbose"))
|
|
@@ -151,6 +157,9 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
151
157
|
return 0
|
|
152
158
|
}
|
|
153
159
|
|
|
160
|
+
// --env picks the test environment (env.yaml) for everything this process loads: commands, the shell, MCP, suites.
|
|
161
|
+
const envFlag = strFlag(flags, "env")
|
|
162
|
+
if (envFlag) process.env.FORGEQE_ENV = envFlag
|
|
154
163
|
// Sign-ins saved with forgeqe auth login, under the names the config reads; the terminal's own variables win.
|
|
155
164
|
await applySavedCredentials().catch(() => [])
|
|
156
165
|
// Models' list prices for costs: waited for only the first time, refreshed daily in the background after that.
|
|
@@ -176,8 +185,26 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
176
185
|
const { agentContexts, contextReport } = await import("../context.ts")
|
|
177
186
|
const rows = agentContexts()
|
|
178
187
|
console.log(contextReport(rows).join("\n"))
|
|
188
|
+
// The team's guardrails ride on top of every agent's fixed context.
|
|
189
|
+
const { guardrailsNotes, readGuardrails } = await import("../guardrails.ts")
|
|
190
|
+
const gcfg = loadConfig(configOpts(flags))
|
|
191
|
+
const block = guardrailsNotes(gcfg)
|
|
192
|
+
if (block) console.log(`\nGuardrails: ${readGuardrails(gcfg).length} rules add ~${Math.ceil(block.length / 4)} tokens to every agent above (forgeqe guardrails lists them).`)
|
|
179
193
|
return rows.some((r) => r.over) ? 1 : 0
|
|
180
194
|
}
|
|
195
|
+
case "guardrails": {
|
|
196
|
+
// The team's rules for every agent: list them, or add one (src/guardrails.ts).
|
|
197
|
+
const { addGuardrail, guardrailsText } = await import("../guardrails.ts")
|
|
198
|
+
const cfg = loadConfig(configOpts(flags))
|
|
199
|
+
if (positionals[1] === "add") {
|
|
200
|
+
const { added, file } = addGuardrail(cfg, positionals.slice(2).join(" "))
|
|
201
|
+
console.log(added ? `Added to ${file}. Every agent started from now on follows it (restart a running ForgeQE shell for its own agent).` : `That guardrail is already in force (${file}).`)
|
|
202
|
+
return 0
|
|
203
|
+
}
|
|
204
|
+
if (positionals[1]) throw new Error(`Unknown guardrails action "${positionals[1]}". Use: forgeqe guardrails [add "rule"]`)
|
|
205
|
+
console.log(guardrailsText(cfg))
|
|
206
|
+
return 0
|
|
207
|
+
}
|
|
181
208
|
case "features":
|
|
182
209
|
// What ForgeQE can do, and whether each capability works with this config and machine.
|
|
183
210
|
console.log(featuresText(loadConfig(configOpts(flags))))
|
|
@@ -212,6 +239,15 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
212
239
|
return await withUsageLine(() => cmdExplore(positionals.slice(1), flags))
|
|
213
240
|
case "bi":
|
|
214
241
|
return await cmdBi(positionals.slice(1), flags)
|
|
242
|
+
case "desktop":
|
|
243
|
+
return await withUsageLine(() => cmdDesktop(positionals.slice(1), flags))
|
|
244
|
+
case "mobile":
|
|
245
|
+
return await withUsageLine(() => cmdMobile(positionals.slice(1), flags))
|
|
246
|
+
case "services":
|
|
247
|
+
return await withUsageLine(() => cmdServices(positionals.slice(1), flags))
|
|
248
|
+
case "apps":
|
|
249
|
+
console.log(appsText(loadConfig(configOpts(flags))))
|
|
250
|
+
return 0
|
|
215
251
|
case "prompts":
|
|
216
252
|
return positionals[1] === "eval" ? await withUsageLine(() => cmdPromptEval(positionals.slice(2), flags)) : cmdPrompts(positionals.slice(1), flags)
|
|
217
253
|
case "eval":
|
|
@@ -897,6 +933,11 @@ async function cmdStart(flags: Flags): Promise<number> {
|
|
|
897
933
|
|
|
898
934
|
/** Background monitors started by the agent live as long as this process; wind their rounds down before exiting. */
|
|
899
935
|
async function stopMonitorsOnExit(log: (line: string) => void) {
|
|
936
|
+
// Services and mocks the agent started in this session end with it (forgeqe services up keeps its own).
|
|
937
|
+
if (startedContainers().length) {
|
|
938
|
+
log(`Stopping the containers this session started (${startedContainers().join(", ")})…`)
|
|
939
|
+
await stopStartedContainers()
|
|
940
|
+
}
|
|
900
941
|
const running = backgroundMonitors()
|
|
901
942
|
if (!running.length) return
|
|
902
943
|
log(`Stopping ${running.length === 1 ? "the monitor" : `${running.length} monitors`}…`)
|
|
@@ -1062,6 +1103,7 @@ async function runAgent(cfg: AppConfig, flags: Flags, text: string, maxSteps: nu
|
|
|
1062
1103
|
} finally {
|
|
1063
1104
|
process.off("SIGINT", onSigint)
|
|
1064
1105
|
await brain?.close()
|
|
1106
|
+
if (startedContainers().length) await stopStartedContainers()
|
|
1065
1107
|
}
|
|
1066
1108
|
}
|
|
1067
1109
|
|
|
@@ -1105,8 +1147,10 @@ async function cmdMcp(args: string[], flags: Flags): Promise<number> {
|
|
|
1105
1147
|
emit: log,
|
|
1106
1148
|
session: "server",
|
|
1107
1149
|
})
|
|
1108
|
-
|
|
1109
|
-
|
|
1150
|
+
// The client lists the core and the capabilities this project can use; use_capabilities adds more (--all-tools: every tool).
|
|
1151
|
+
const scope = boolFlag(flags, "all-tools") ? undefined : new CapabilityScope(cfg, TOOLS.map((t) => t.name))
|
|
1152
|
+
const server = createMcpServer({ tools: TOOLS, context, instructions: () => environmentNotes(context()), version: packageVersion(), ...(scope ? { scope } : {}) })
|
|
1153
|
+
process.stderr.write(`ForgeQE MCP server on stdio (${scope ? `${scope.listedTools().length} of ${TOOLS.length}` : TOOLS.length} tools, working in ${cwd})\n`)
|
|
1110
1154
|
await serveStdio(server)
|
|
1111
1155
|
await stopMonitorsOnExit((line) => process.stderr.write(`${line}\n`))
|
|
1112
1156
|
return 0
|
package/src/code/find.ts
CHANGED
|
@@ -13,11 +13,32 @@ import { CAUSE_TEXT, flakyReport, quarantined, recordResults, RELEASE_AFTER, set
|
|
|
13
13
|
import { loadSkills } from "../skills/registry.ts"
|
|
14
14
|
import { prompt } from "../prompts.ts"
|
|
15
15
|
import { domainNotes } from "../domain.ts"
|
|
16
|
+
import { guardrailsNotes } from "../guardrails.ts"
|
|
16
17
|
|
|
17
18
|
const LIST_ROUNDS = 3
|
|
18
19
|
const LIST_TIMEOUT_SECONDS = 180
|
|
19
20
|
const OUTPUT_CHARS = 6_000
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* ForgeQE runs Scribe's list command without asking, so it takes only a test framework's own listing: Scribe reads the
|
|
24
|
+
* repository, and a file in it could talk Scribe into any command. The run command is shown to the user first instead.
|
|
25
|
+
*/
|
|
26
|
+
const LIST_RUNNER =
|
|
27
|
+
/^(?:(?:(?:npx(?:\s+--no-install)?|pnpm(?:\s+exec)?|yarn|bunx|bun\s+x)\s+)?(?:playwright\s+test|jest|vitest|mocha)|(?:(?:python3?|py)\s+-m\s+|(?:uv|poetry)\s+run\s+)?pytest|dotnet\s+test)(?=\s|$)/i
|
|
28
|
+
const LIST_FLAG = /(?:^|\s)(?:--list|--listTests|--list-tests|--collect-only|--co|--dry-run)(?=[\s=]|$)/
|
|
29
|
+
/** Characters that chain, substitute, or redirect in sh or cmd.exe. */
|
|
30
|
+
const SHELL_SYNTAX = /[;&|`$<>()\r\n%]/
|
|
31
|
+
|
|
32
|
+
/** Whether ForgeQE may run `command` unasked as a list command: one test runner, a listing flag, no shell syntax. */
|
|
33
|
+
export function listCommandAllowed(command: string): boolean {
|
|
34
|
+
const c = command.trim()
|
|
35
|
+
if (!c || SHELL_SYNTAX.test(c)) return false
|
|
36
|
+
const runner = LIST_RUNNER.exec(c)
|
|
37
|
+
if (!runner) return false
|
|
38
|
+
const rest = c.slice(runner[0].length)
|
|
39
|
+
return LIST_FLAG.test(rest) || (/vitest$/i.test(runner[0]) && /^\s+list(?=\s|$)/.test(rest))
|
|
40
|
+
}
|
|
41
|
+
|
|
21
42
|
export const findAgentPrompt = () => prompt("find-tests")
|
|
22
43
|
|
|
23
44
|
export const FIND_AGENT: AgentSpec = {
|
|
@@ -176,6 +197,7 @@ export async function findTests(opts: {
|
|
|
176
197
|
repo,
|
|
177
198
|
profile: opts.config.llm,
|
|
178
199
|
domain: domainNotes(opts.config),
|
|
200
|
+
guardrails: guardrailsNotes(opts.config),
|
|
179
201
|
model: opts.config.code.model,
|
|
180
202
|
bin: opts.config.code.opencode,
|
|
181
203
|
readDirs: [repo],
|
|
@@ -195,6 +217,10 @@ export async function findTests(opts: {
|
|
|
195
217
|
let report = reply.report
|
|
196
218
|
let listed = false
|
|
197
219
|
for (let round = 1; round <= LIST_ROUNDS && clean(report.tests).length && report.list_command?.trim() && !opts.signal?.aborted; round++) {
|
|
220
|
+
if (!listCommandAllowed(report.list_command!)) {
|
|
221
|
+
emit({ type: "code", kind: "info", text: `ForgeQE does not run this list command, which is not a test framework's own listing: ${report.list_command}` })
|
|
222
|
+
break
|
|
223
|
+
}
|
|
198
224
|
emit({ type: "code", kind: "info", text: `ForgeQE lists the selected tests: ${report.list_command}` })
|
|
199
225
|
const run = await runTest({ command: report.list_command!, cwd: repo, timeoutSeconds: LIST_TIMEOUT_SECONDS, signal: opts.signal, hide: privateEnvNames(opts.config) })
|
|
200
226
|
if (opts.signal?.aborted) break
|