@capagents/forgeqe 1.1.1 → 1.1.3
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 +25 -9
- package/README.md +30 -1
- package/browserstack.example.yaml +14 -0
- package/docs/security-data-flow.html +154 -86
- package/docs/user-guide.html +329 -135
- package/evals/prompts/brain/capability-question.yaml +12 -0
- package/evals/prompts/brain/failed-runs-cause.yaml +12 -0
- package/evals/prompts/brain/release-route.yaml +14 -0
- package/evals/prompts/brain/report-request.yaml +13 -0
- package/evals/prompts/brain/secret-request.yaml +9 -0
- package/evals/prompts/explore-charters/lockout.yaml +18 -0
- package/evals/prompts/explore-charters/transfer-limit.yaml +16 -0
- package/evals/prompts/refine/current-release.yaml +26 -0
- package/evals/prompts/refine/missing-item.yaml +10 -0
- package/evals/prompts/refine/question-stays-question.yaml +13 -0
- package/evals/prompts/refine/report-request.yaml +19 -0
- package/forgeqe.example.yaml +51 -0
- package/package.json +5 -2
- package/prompts/README.md +28 -7
- package/prompts/api-scenarios.md +56 -24
- package/prompts/api-tests.md +67 -51
- package/prompts/brain.md +277 -268
- package/prompts/browser-capture.md +8 -7
- package/prompts/browser-case-report.md +13 -12
- package/prompts/browser-checks.md +17 -18
- package/prompts/browser-efficiency.md +4 -1
- package/prompts/browser-error-recovery.md +5 -4
- package/prompts/browser-extra.md +1 -0
- package/prompts/browser-goal-verification.md +8 -7
- package/prompts/browser-skill-select.md +28 -1
- package/prompts/conventions.md +10 -0
- package/prompts/domain-rule.md +17 -14
- package/prompts/eval-judge.md +36 -13
- package/prompts/explore-charters.md +53 -22
- package/prompts/find-tests.md +45 -30
- package/prompts/heal.md +42 -32
- package/prompts/pr-review.md +44 -28
- package/prompts/refine.md +73 -40
- package/prompts/skill-rule.md +4 -1
- package/prompts/test-agent.md +61 -45
- package/prompts/test-reviewer.md +64 -37
- package/prompts/test-rules.md +59 -56
- package/py/forgeqe_engine/browser.py +111 -1
- package/py/forgeqe_engine/capture.py +8 -1
- package/py/forgeqe_engine/checks.py +20 -8
- package/py/forgeqe_engine/prompts.py +9 -0
- package/py/forgeqe_engine/report.py +15 -8
- package/py/forgeqe_engine/service.py +50 -27
- package/py/forgeqe_engine/skills.py +31 -7
- package/src/auth/signin.ts +30 -1
- package/src/bi.ts +914 -0
- package/src/cache.ts +92 -0
- package/src/capabilities/guide.ts +322 -140
- package/src/capabilities/registry.ts +3 -3
- package/src/childEnv.ts +4 -0
- package/src/cli/help.ts +18 -4
- package/src/cli/index.ts +149 -15
- package/src/config.ts +272 -0
- package/src/engine/bridge.ts +32 -2
- package/src/evals/prompts.ts +288 -0
- package/src/heal/loop.ts +3 -1
- package/src/init.ts +18 -0
- package/src/mcp/server.ts +8 -0
- package/src/mcp/tools.ts +30 -4
- package/src/monitor/run.ts +3 -1
- package/src/prompts.ts +6 -3
- package/src/promptstyle.ts +159 -0
- package/src/run.ts +4 -1
- package/src/ste.ts +8 -2
- package/src/suite/run.ts +3 -1
- package/testmu.example.yaml +14 -0
|
@@ -61,11 +61,11 @@ export const CAPABILITIES: Capability[] = [
|
|
|
61
61
|
area: "Browser",
|
|
62
62
|
title: "Run any goal in a real browser",
|
|
63
63
|
summary:
|
|
64
|
-
"Carries out a plain-language goal in
|
|
64
|
+
"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.",
|
|
65
65
|
tools: ["browser_run", "run_details", "page_memory"],
|
|
66
66
|
examples: ["Go to booking.com and search hotels in Mumbai for next weekend", "What does ForgeQE know about the checkout page?"],
|
|
67
67
|
requires: [{ kind: "live_model" }],
|
|
68
|
-
limits: ["Web applications
|
|
68
|
+
limits: ["Web applications only: local Chrome or Chromium, or configured remote web grids such as BrowserStack and TestMu AI/LambdaTest; not native mobile or desktop applications."],
|
|
69
69
|
docs: [
|
|
70
70
|
{ doc: "HOW_TO_USE.md", heading: "4. Without the shell" },
|
|
71
71
|
{ doc: "HOW_TO_USE.md", heading: "5. Page memory" },
|
|
@@ -345,7 +345,7 @@ export const CAPABILITIES: Capability[] = [
|
|
|
345
345
|
* Only add what is certain.
|
|
346
346
|
*/
|
|
347
347
|
export const OUT_OF_SCOPE: { what: string; instead?: string }[] = [
|
|
348
|
-
{ what: "Native mobile apps (iOS, Android) and desktop applications", instead: "web applications in Chrome or Chromium" },
|
|
348
|
+
{ what: "Native mobile apps (iOS, Android) and desktop applications", instead: "web applications in local Chrome or Chromium, or configured remote web grids such as BrowserStack and TestMu AI/LambdaTest" },
|
|
349
349
|
{ what: "Load, stress, or soak testing with many simultaneous users", instead: "per-page Core Web Vitals with budgets" },
|
|
350
350
|
{ what: "Penetration testing or dynamic security scanning", instead: "security findings in pull request reviews" },
|
|
351
351
|
]
|
package/src/childEnv.ts
CHANGED
|
@@ -84,3 +84,7 @@ export function privateEnvNames(cfg: AppConfig, extra: Iterable<string> = []): s
|
|
|
84
84
|
export function loginEnvNames(logins: { usernameEnv?: string; passwordEnv?: string; totpEnv?: string }[]): string[] {
|
|
85
85
|
return logins.flatMap((l) => [l.usernameEnv, l.passwordEnv, l.totpEnv]).filter((n): n is string => Boolean(n))
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
export function browserProviderEnvNames(browser: Pick<AppConfig["browser"], "provider" | "remote">): string[] {
|
|
89
|
+
return browser.provider === "local" || !browser.remote ? [] : [browser.remote.usernameEnv, browser.remote.accessKeyEnv].filter(Boolean)
|
|
90
|
+
}
|
package/src/cli/help.ts
CHANGED
|
@@ -17,23 +17,31 @@ Usage:
|
|
|
17
17
|
[--repo DIR] [--no-code]
|
|
18
18
|
forgeqe run "<request>" [--profile name] [--url URL] [--headed] [--plain] [--max-steps N]
|
|
19
19
|
[--repo DIR] [--no-code] [--replay MODE] [--no-memory] [--attempts N] [--code-model provider/model]
|
|
20
|
+
[--browser-provider local|browserstack|testmu]
|
|
20
21
|
ForgeQE's agent works out what the request needs: a browser goal,
|
|
21
22
|
test cases from Jira, Azure DevOps, Xray or files, a test, a pipeline
|
|
22
|
-
forgeqe run --direct "<goal>" [--out DIR] [--skill NAME] ... one browser goal, no agent
|
|
23
|
+
forgeqe run --direct "<goal>" [--out DIR] [--skill NAME] [--browser-provider local|browserstack|testmu] ... one browser goal, no agent
|
|
23
24
|
forgeqe suite <source...> [--parallel N] [--repo DIR] [--no-code] [--no-write-back] [--out DIR]
|
|
24
25
|
[--profile name] [--url URL] [--headed] [--plain] [--attempts N] [--skill NAME]
|
|
26
|
+
[--browser-provider local|browserstack|testmu]
|
|
25
27
|
run test cases the same way every time (what pipelines call)
|
|
26
28
|
forgeqe cases <source...> [--goal] list the test cases a source gives (--goal: the goal each runs)
|
|
27
29
|
forgeqe code [run-dir] [--repo DIR] [--attempts N] [--code-model provider/model]
|
|
28
30
|
write a test for a run (default: the latest) with Scribe
|
|
29
31
|
forgeqe heal ["<test command>"] [--repo DIR] [--attempts N] [--code-model provider/model] [--headed] [--plain]
|
|
32
|
+
[--browser-provider local|browserstack|testmu]
|
|
30
33
|
run the repo's tests and heal the ones the app has outgrown
|
|
31
34
|
forgeqe monitor [source...] [--every 15m] [--once] [--replay MODE] [--parallel N] [--out DIR] [--verbose] [--skill NAME]
|
|
35
|
+
[--browser-provider local|browserstack|testmu]
|
|
32
36
|
check flows on a schedule and alert Teams or email when one breaks
|
|
33
37
|
forgeqe monitor --test-alerts send a sample alert to check the Teams and email settings
|
|
34
|
-
forgeqe mcp [--repo DIR] [--profile name] [--url URL] [--headed]
|
|
38
|
+
forgeqe mcp [--repo DIR] [--profile name] [--url URL] [--headed] [--browser-provider local|browserstack|testmu]
|
|
35
39
|
ForgeQE's tools as an MCP server on stdio, for other agents
|
|
36
40
|
forgeqe mcp list the Azure DevOps, Jira and other MCP servers ForgeQE's agent gets
|
|
41
|
+
forgeqe bi [build] [--evidence-dir DIR] [--data-dir DIR] [--build-dir DIR]
|
|
42
|
+
export ForgeQE + ADO/Jira datasets into an Evidence project and build static BI HTML
|
|
43
|
+
forgeqe bi init scaffold or refresh the managed Evidence project files
|
|
44
|
+
forgeqe bi sync refresh the CSV datasets and generated Evidence pages, without building
|
|
37
45
|
forgeqe pr-tests [pr] [--base BRANCH] [--agent] [--run] [--post]
|
|
38
46
|
the tests a pull request (or the commits since BRANCH) can break; --run runs
|
|
39
47
|
them and exits 1 when one fails (a CI gate), --post puts the results on the PR
|
|
@@ -48,6 +56,7 @@ Usage:
|
|
|
48
56
|
forgeqe visual approve [run-dir] [--page a,b] [--yes]
|
|
49
57
|
make the run's screenshots the new baselines (an intended change)
|
|
50
58
|
forgeqe explore <story> [--url URL] [--charters N] [--roles a,b] [--focus TEXT] [--yes] [--file-bugs]
|
|
59
|
+
[--browser-provider local|browserstack|testmu]
|
|
51
60
|
exploratory testing of a story (ado:123, SHOP-12, a link, or its text):
|
|
52
61
|
charters beyond the scripted cases, defects confirmed by a second run,
|
|
53
62
|
proposed test cases, bugs for confirmed defects; exits 1 on a defect
|
|
@@ -67,8 +76,8 @@ Usage:
|
|
|
67
76
|
forgeqe api tests [SET] --scenarios[=a,b] --repo DIR
|
|
68
77
|
scenario tests on the repository's existing API helpers
|
|
69
78
|
forgeqe api list saved contract sets
|
|
70
|
-
forgeqe auth login [system] [--no-browser] sign in to azure_devops, github, jira, confluence, testrail, zephyr, qtest
|
|
71
|
-
or
|
|
79
|
+
forgeqe auth login [system] [--no-browser] sign in to azure_devops, github, jira, confluence, testrail, zephyr, qtest,
|
|
80
|
+
xray, browserstack, or testmu in the browser, and save the token in the system's credential store
|
|
72
81
|
forgeqe auth login <ENV_NAME> save one secret (an LLM API key, a webhook URL) in the credential store
|
|
73
82
|
forgeqe auth [status] which systems are signed in, and how
|
|
74
83
|
forgeqe auth logout <system|ENV_NAME> forget a saved sign-in
|
|
@@ -100,6 +109,7 @@ page with ranked locators and a confidence %, plus the steps of each goal's last
|
|
|
100
109
|
--replay verify replay a known goal without the LLM, then one LLM step checks the result (default)
|
|
101
110
|
--replay trust replay and finish without the LLM when every step works
|
|
102
111
|
--replay off never replay; still record pages
|
|
112
|
+
--browser-provider pick which configured browser provider runs this command's browser work
|
|
103
113
|
|
|
104
114
|
ForgeQE's agent (forgeqe start, forgeqe run) runs on the agent runtime with the active profile's model (or code.model) and
|
|
105
115
|
calls ForgeQE's tools: browser runs, reading and running test cases, writing tests and running them, page memory, pushing
|
|
@@ -127,6 +137,10 @@ With sources.bugs, each failed case files a bug in Jira or Azure DevOps, or comm
|
|
|
127
137
|
The report is report.html (step verdicts, timeline with screenshots, timings, environment) + junit.xml in
|
|
128
138
|
out/suites/<time>/; every run folder also has its own report.html.
|
|
129
139
|
|
|
140
|
+
Static BI (bi: in forgeqe.yaml): \`forgeqe bi\` writes curated CSV datasets from ForgeQE's own suite and readiness
|
|
141
|
+
reports plus optional Azure DevOps WIQL and Jira JQL datasets, scaffolds an Evidence project, and runs
|
|
142
|
+
\`evidence build\` to produce static HTML in the build directory. No server stays up between runs.
|
|
143
|
+
|
|
130
144
|
Healing (forgeqe heal): ForgeQE runs the tests (the command given, code.test_command, or one the agent finds).
|
|
131
145
|
For each failing test, Scribe re-runs its flow in the real browser: when the flow still works, the test is out
|
|
132
146
|
of date and it updates the steps and locators from page memory; when the browser fails at the same step, it is an
|
package/src/cli/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { goalFor, loadCases } from "../cases/index.ts"
|
|
|
10
10
|
import { codeSkipReason, latestRunDir, runCodeAgent } from "../code/index.ts"
|
|
11
11
|
import { detailFrom } from "../detail.ts"
|
|
12
12
|
import { ensureOpencode, INSTALL_HINT } from "../code/runtime.ts"
|
|
13
|
-
import { loadConfig, parseDuration, replayMode, selectProfile, userConfigDir, type AppConfig } from "../config.ts"
|
|
13
|
+
import { browserProvider, loadConfig, parseDuration, replayMode, selectProfile, userConfigDir, type AppConfig, type BrowserProvider } from "../config.ts"
|
|
14
14
|
import { runHeal } from "../heal/loop.ts"
|
|
15
15
|
import { formatHeal } from "../heal/report.ts"
|
|
16
16
|
import { backgroundMonitors, stopBackgroundMonitors } from "../monitor/background.ts"
|
|
@@ -44,6 +44,8 @@ import { resultsCard, runSelection, selectionCard } from "../code/find.ts"
|
|
|
44
44
|
import { listSets as listApiSets } from "../api/store.ts"
|
|
45
45
|
import { builtinPrompt, lineDiff, listPrompts, prompt, promptDirs, promptFile, promptWarnings } from "../prompts.ts"
|
|
46
46
|
import { steScore, STE_TARGET } from "../ste.ts"
|
|
47
|
+
import { checkStyle, promptKind, PROMPT_KINDS, RFC_TARGET } from "../promptstyle.ts"
|
|
48
|
+
import { BUILTIN_CASES, loadPromptCases, previousRates, runCase, saveResults, type CaseResult } from "../evals/prompts.ts"
|
|
47
49
|
import { resultsMarkdown, selectPrTests } from "../code/prtests.ts"
|
|
48
50
|
import { postPrComment, resolvePr } from "../code/prreview.ts"
|
|
49
51
|
import { privateEnvNames } from "../childEnv.ts"
|
|
@@ -52,13 +54,19 @@ import { featuresText } from "../capabilities/render.ts"
|
|
|
52
54
|
import { DOMAIN_CASES, gate, loadDomainCases, reportMarkdown, runDomainEval, saveReport, VARIANTS, type Variant } from "../evals/domain.ts"
|
|
53
55
|
import { evalNotice, notifiedLine, notify, notifySuite } from "../notify/events.ts"
|
|
54
56
|
import { sendNotice, type Notice } from "../notify/notice.ts"
|
|
57
|
+
import { buildBi, initBiProject, syncBiData } from "../bi.ts"
|
|
55
58
|
|
|
56
59
|
const AGENT_COMMANDS = new Set(["start", "run", "code", "suite", "heal", "monitor", "explore"])
|
|
57
|
-
const LOGGED_COMMANDS = new Set([...AGENT_COMMANDS, "mcp", "login", "setup"])
|
|
60
|
+
const LOGGED_COMMANDS = new Set([...AGENT_COMMANDS, "mcp", "login", "setup", "bi"])
|
|
58
61
|
|
|
59
62
|
/** How much of the agents' work to print: brief unless --verbose. */
|
|
60
63
|
const detailOf = (flags: Flags) => detailFrom(boolFlag(flags, "verbose"))
|
|
61
64
|
|
|
65
|
+
function browserProviderFlag(flags: Flags): BrowserProvider | undefined {
|
|
66
|
+
const value = strFlag(flags, "browser-provider")
|
|
67
|
+
return value ? browserProvider(value) : undefined
|
|
68
|
+
}
|
|
69
|
+
|
|
62
70
|
function debugRequested(flags: Flags): boolean {
|
|
63
71
|
return boolFlag(flags, "debug") || /^(1|true|yes|on)$/i.test(process.env.FORGEQE_DEBUG || "")
|
|
64
72
|
}
|
|
@@ -184,8 +192,10 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
184
192
|
return await withUsageLine(() => cmdApi(positionals.slice(1), flags))
|
|
185
193
|
case "explore":
|
|
186
194
|
return await withUsageLine(() => cmdExplore(positionals.slice(1), flags))
|
|
195
|
+
case "bi":
|
|
196
|
+
return await cmdBi(positionals.slice(1), flags)
|
|
187
197
|
case "prompts":
|
|
188
|
-
return cmdPrompts(positionals.slice(1), flags)
|
|
198
|
+
return positionals[1] === "eval" ? await withUsageLine(() => cmdPromptEval(positionals.slice(2), flags)) : cmdPrompts(positionals.slice(1), flags)
|
|
189
199
|
case "eval":
|
|
190
200
|
return await withUsageLine(() => cmdEval(positionals.slice(1), flags))
|
|
191
201
|
case "notify":
|
|
@@ -207,7 +217,7 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
207
217
|
const cfg = loadConfig(configOpts(flags))
|
|
208
218
|
const trends = positionals[1] === "trends"
|
|
209
219
|
const color = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR
|
|
210
|
-
|
|
220
|
+
const ctx: ToolContext = { config: cfg, cwd: process.cwd(), session: "once", emit: toolPrinter(color, (line) => console.log(line), detailOf(flags)) }
|
|
211
221
|
const out = (await TOOLS.find((t) => t.name === (trends ? "quality_trends" : "quality_report"))!.run(trends ? { page: positionals[2] } : { run_dir: positionals[1] }, ctx)) as { budget_exceeded?: unknown[]; pages?: { slower: unknown[]; newly_failing_rules: unknown[] }[] }
|
|
212
222
|
return out.budget_exceeded?.length || out.pages?.some((p) => p.slower.length || p.newly_failing_rules.length) ? 1 : 0
|
|
213
223
|
}
|
|
@@ -372,7 +382,7 @@ async function cmdApi(args: string[], flags: Flags): Promise<number> {
|
|
|
372
382
|
const cfg = loadConfig(configOpts(flags))
|
|
373
383
|
const action = args[0] || "list"
|
|
374
384
|
const color = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR
|
|
375
|
-
|
|
385
|
+
const ctx: ToolContext = { config: cfg, cwd: process.cwd(), session: "once", emit: toolPrinter(color, (line) => console.log(line), detailOf(flags)) }
|
|
376
386
|
const tool = (name: string) => TOOLS.find((t) => t.name === name)!
|
|
377
387
|
const csv = (name: string) => (strFlag(flags, name) || "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
378
388
|
switch (action) {
|
|
@@ -449,7 +459,17 @@ async function cmdExplore(args: string[], flags: Flags): Promise<number> {
|
|
|
449
459
|
process.once("SIGINT", onSigint)
|
|
450
460
|
try {
|
|
451
461
|
const ask = process.stdin.isTTY && !boolFlag(flags, "yes") ? terminalAsk(controller.signal) : undefined
|
|
452
|
-
const ctx: ToolContext = {
|
|
462
|
+
const ctx: ToolContext = {
|
|
463
|
+
config: cfg,
|
|
464
|
+
cwd: process.cwd(),
|
|
465
|
+
session: "once",
|
|
466
|
+
signal: controller.signal,
|
|
467
|
+
headed: boolFlag(flags, "headed"),
|
|
468
|
+
browserProvider: browserProviderFlag(flags),
|
|
469
|
+
maxSteps: maxStepsFlag(flags),
|
|
470
|
+
emit: toolPrinter(color, (line) => console.log(line), detailOf(flags)),
|
|
471
|
+
...(ask ? { ask } : {}),
|
|
472
|
+
}
|
|
453
473
|
const roles = (strFlag(flags, "roles") || "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
454
474
|
const out = (await TOOLS.find((t) => t.name === "explore_story")!.run(
|
|
455
475
|
{ story, url: strFlag(flags, "url"), charters: Number(strFlag(flags, "charters")) || undefined, ...(roles.length ? { roles } : {}), focus: strFlag(flags, "focus"), file_bugs: boolFlag(flags, "file-bugs") },
|
|
@@ -461,6 +481,50 @@ async function cmdExplore(args: string[], flags: Flags): Promise<number> {
|
|
|
461
481
|
}
|
|
462
482
|
}
|
|
463
483
|
|
|
484
|
+
async function cmdBi(args: string[], flags: Flags): Promise<number> {
|
|
485
|
+
const cfg = loadConfig(configOpts(flags))
|
|
486
|
+
const action = args[0] || "build"
|
|
487
|
+
const overrides = {
|
|
488
|
+
evidenceDir: strFlag(flags, "evidence-dir") || strFlag(flags, "dir"),
|
|
489
|
+
dataDir: strFlag(flags, "data-dir"),
|
|
490
|
+
buildDir: strFlag(flags, "build-dir") || strFlag(flags, "out"),
|
|
491
|
+
}
|
|
492
|
+
switch (action) {
|
|
493
|
+
case "init": {
|
|
494
|
+
const out = await initBiProject(cfg, overrides)
|
|
495
|
+
console.log(`Evidence project ${out.evidenceDir}`)
|
|
496
|
+
for (const file of out.files) console.log(`generated ${path.relative(process.cwd(), file) || file}`)
|
|
497
|
+
return 0
|
|
498
|
+
}
|
|
499
|
+
case "sync": {
|
|
500
|
+
const out = await syncBiData(cfg, overrides)
|
|
501
|
+
console.log(`Evidence project ${out.evidenceDir}`)
|
|
502
|
+
console.log(`Data export ${out.dataDir}`)
|
|
503
|
+
console.log(`Build output ${out.buildDir}`)
|
|
504
|
+
console.log(`Suite runs ${out.suites}`)
|
|
505
|
+
console.log(`Case results ${out.caseResults}`)
|
|
506
|
+
console.log(`Readiness reports ${out.readinessReports}`)
|
|
507
|
+
console.log(`External datasets ${out.externalDatasets}`)
|
|
508
|
+
console.log(`Summary ${path.relative(process.cwd(), out.summaryFile) || out.summaryFile}`)
|
|
509
|
+
return 0
|
|
510
|
+
}
|
|
511
|
+
case "build": {
|
|
512
|
+
const out = await buildBi(cfg, overrides)
|
|
513
|
+
console.log(`Evidence project ${out.evidenceDir}`)
|
|
514
|
+
console.log(`Static site ${out.buildDir}`)
|
|
515
|
+
console.log(`Index ${path.relative(process.cwd(), out.indexFile) || out.indexFile}`)
|
|
516
|
+
console.log(`Summary ${path.relative(process.cwd(), out.summaryFile) || out.summaryFile}`)
|
|
517
|
+
console.log(`Suite runs ${out.suites}`)
|
|
518
|
+
console.log(`Case results ${out.caseResults}`)
|
|
519
|
+
console.log(`Readiness reports ${out.readinessReports}`)
|
|
520
|
+
console.log(`External datasets ${out.externalDatasets}`)
|
|
521
|
+
return 0
|
|
522
|
+
}
|
|
523
|
+
default:
|
|
524
|
+
throw new Error("Usage: forgeqe bi [init|sync|build] [--evidence-dir DIR] [--data-dir DIR] [--build-dir DIR]")
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
464
528
|
/** forgeqe notify test [channel]: a test card to every notification channel (or one), to check each arrives. */
|
|
465
529
|
async function cmdNotify(args: string[], flags: Flags): Promise<number> {
|
|
466
530
|
if (args[0] !== "test") throw new Error("Usage: forgeqe notify test [channel]")
|
|
@@ -535,6 +599,51 @@ async function cmdEval(args: string[], flags: Flags): Promise<number> {
|
|
|
535
599
|
* from (ForgeQE's, or an edited copy in .forgeqe/prompts), how an edited one differs from ForgeQE's, and how closely each
|
|
536
600
|
* follows ASD-STE100.
|
|
537
601
|
*/
|
|
602
|
+
/**
|
|
603
|
+
* forgeqe prompts eval [prompt] [--case <id>] [--runs <n>]: runs the evaluation cases of the prompts (ForgeQE's, and
|
|
604
|
+
* the project's in .forgeqe/evals/prompts), with checks decided by code, and compares each pass rate with the last run.
|
|
605
|
+
*/
|
|
606
|
+
async function cmdPromptEval(args: string[], flags: Flags): Promise<number> {
|
|
607
|
+
const config = loadConfig(configOpts(flags))
|
|
608
|
+
const runs = Math.max(1, Number(strFlag(flags, "runs")) || 1)
|
|
609
|
+
const dirs = [BUILTIN_CASES, ...(config.configPath ? [path.join(path.dirname(config.configPath), ".forgeqe", "evals", "prompts")] : [])]
|
|
610
|
+
const cases = loadPromptCases(dirs, { prompt: args[0], case: strFlag(flags, "case") })
|
|
611
|
+
if (!cases.length) throw new Error(`No evaluation cases${args[0] ? ` for ${args[0]}` : ""}. They are in ${dirs.join(" and ")}, as <prompt>/<case>.yaml.`)
|
|
612
|
+
const before = previousRates(config)
|
|
613
|
+
const results: CaseResult[] = []
|
|
614
|
+
let failed = 0
|
|
615
|
+
for (const c of cases) {
|
|
616
|
+
process.stdout.write(`${c.prompt}/${c.id} … `)
|
|
617
|
+
const r = await runCase(c, config, process.cwd(), runs)
|
|
618
|
+
results.push(r)
|
|
619
|
+
if (r.skipped) {
|
|
620
|
+
console.log(`skipped (${r.skipped})`)
|
|
621
|
+
continue
|
|
622
|
+
}
|
|
623
|
+
const passed = r.runs.filter((x) => x.pass).length
|
|
624
|
+
if (passed < r.runs.length) failed++
|
|
625
|
+
const was = before.get(`${c.prompt}/${c.id}`)
|
|
626
|
+
const tokens = Math.round(r.runs.reduce((a, x) => a + x.tokens, 0) / r.runs.length / 100) / 10
|
|
627
|
+
console.log(`${passed === r.runs.length ? "✓" : "✗"} ${passed}/${r.runs.length}${was ? ` (was ${was[0]}/${was[1]})` : ""} ${tokens}K tokens a run ${c.title}`)
|
|
628
|
+
// Each check that failed in any run, once, with what was seen.
|
|
629
|
+
const seen = new Set<string>()
|
|
630
|
+
for (const run of r.runs) {
|
|
631
|
+
if (run.error) console.log(` error: ${run.error}`)
|
|
632
|
+
for (const ch of run.checks.filter((x) => !x.pass && !seen.has(x.id))) {
|
|
633
|
+
seen.add(ch.id)
|
|
634
|
+
console.log(` ✗ ${ch.id}: ${ch.detail}`)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const seenText = r.runs.find((x) => x.seen)?.seen
|
|
638
|
+
if (seenText) console.log(` output: ${seenText}`)
|
|
639
|
+
}
|
|
640
|
+
const file = saveResults(config, results)
|
|
641
|
+
const ran = results.filter((r) => !r.skipped)
|
|
642
|
+
const runsAll = ran.flatMap((r) => r.runs)
|
|
643
|
+
console.log(`\n${runsAll.filter((x) => x.pass).length} of ${runsAll.length} runs passed in ${ran.length} cases${results.length > ran.length ? `; ${results.length - ran.length} skipped` : ""}. Results: ${path.relative(process.cwd(), file) || file}`)
|
|
644
|
+
return failed ? 1 : 0
|
|
645
|
+
}
|
|
646
|
+
|
|
538
647
|
function cmdPrompts(args: string[], flags: Flags): number {
|
|
539
648
|
loadConfig(configOpts(flags))
|
|
540
649
|
const action = args[0] || "list"
|
|
@@ -547,20 +656,30 @@ function cmdPrompts(args: string[], flags: Flags): number {
|
|
|
547
656
|
return 0
|
|
548
657
|
}
|
|
549
658
|
if (action === "check") {
|
|
550
|
-
//
|
|
659
|
+
// The prompting standard (src/promptstyle.ts) for each prompt in use, ForgeQE's or the edited copy; with a name,
|
|
660
|
+
// every issue to fix.
|
|
551
661
|
const all = listPrompts().filter((p) => !name || p.name === name)
|
|
552
662
|
if (name && !all.length) throw new Error(`No prompt named "${name}". forgeqe prompts lists them.`)
|
|
553
663
|
const width = Math.max(...all.map((p) => p.name.length))
|
|
554
|
-
let
|
|
664
|
+
let failing = 0
|
|
555
665
|
for (const p of all) {
|
|
556
666
|
const text = readFileSync(p.file, "utf8").replace(/^---\n[\s\S]*?\n---\n/, "")
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (
|
|
667
|
+
const kind = promptKind(p.kind)
|
|
668
|
+
const r = kind ? checkStyle(text, kind) : undefined
|
|
669
|
+
const issues = r ? r.issues : [{ rule: "kind", detail: `no kind in the header; use one of ${PROMPT_KINDS.join(", ")}` }]
|
|
670
|
+
if (issues.length) failing++
|
|
671
|
+
const pct = (n: number) => `${String(Math.round(n * 100)).padStart(3)}%`
|
|
672
|
+
const facts = r && r.kind !== "user" ? `STE ${pct(r.ste)} RFC 2119 ${r.ruleLines ? pct(r.rfc) : " –"}${r.kind === "structured" ? ` examples ${r.examples}` : ""}` : ""
|
|
673
|
+
console.log(`${issues.length ? "✗" : "✓"} ${p.name.padEnd(width)} ${(kind || "?").padEnd(10)} ${facts}${issues.length ? ` ${issues.length} issue${issues.length === 1 ? "" : "s"}` : ""}${p.source === "built-in" ? "" : ` (${p.source})`}`)
|
|
674
|
+
if (name) {
|
|
675
|
+
for (const i of issues) console.log(` - ${i.rule}: ${i.detail}`)
|
|
676
|
+
for (const i of steScore(text).issues) console.log(` - ste: ${i.issues.map((x) => x.detail).join("; ")}\n ${i.text}`)
|
|
677
|
+
}
|
|
561
678
|
}
|
|
562
|
-
console.log(
|
|
563
|
-
|
|
679
|
+
console.log(
|
|
680
|
+
`\nThe standard: ASD-STE100 (${Math.round(STE_TARGET * 100)}% of sentences), RFC 2119 key words for rules (${Math.round(RFC_TARGET * 100)}% of rule lines, no lower-case must/should), the sections of each kind, worked examples for structured prompts, and an execution contract for agents.${name ? "" : " forgeqe prompts check <name> lists what to fix."}`,
|
|
681
|
+
)
|
|
682
|
+
return failing ? 1 : 0
|
|
564
683
|
}
|
|
565
684
|
if (!name) throw new Error(`Usage: forgeqe prompts ${action} <name> (forgeqe prompts lists the names)`)
|
|
566
685
|
const where = promptFile(name)
|
|
@@ -809,6 +928,7 @@ async function runAgent(cfg: AppConfig, flags: Flags, text: string, maxSteps: nu
|
|
|
809
928
|
signal: controller.signal,
|
|
810
929
|
context: () => ({
|
|
811
930
|
headed: boolFlag(flags, "headed"),
|
|
931
|
+
browserProvider: browserProviderFlag(flags),
|
|
812
932
|
maxSteps,
|
|
813
933
|
url: strFlag(flags, "url"),
|
|
814
934
|
...memoryFlags(flags),
|
|
@@ -869,7 +989,17 @@ async function cmdMcp(args: string[], flags: Flags): Promise<number> {
|
|
|
869
989
|
const repo = repoFlag(flags)
|
|
870
990
|
const cwd = repo || process.cwd()
|
|
871
991
|
const log = toolPrinter(false, (line) => process.stderr.write(`${line}\n`), detailOf(flags))
|
|
872
|
-
const context = (): ToolContext => ({
|
|
992
|
+
const context = (): ToolContext => ({
|
|
993
|
+
config: cfg,
|
|
994
|
+
cwd,
|
|
995
|
+
headed: boolFlag(flags, "headed"),
|
|
996
|
+
browserProvider: browserProviderFlag(flags),
|
|
997
|
+
maxSteps: maxStepsFlag(flags),
|
|
998
|
+
url: strFlag(flags, "url"),
|
|
999
|
+
...memoryFlags(flags),
|
|
1000
|
+
emit: log,
|
|
1001
|
+
session: "server",
|
|
1002
|
+
})
|
|
873
1003
|
const server = createMcpServer({ tools: TOOLS, context, instructions: () => environmentNotes(context()), version: packageVersion() })
|
|
874
1004
|
process.stderr.write(`ForgeQE MCP server on stdio (${TOOLS.length} tools, working in ${cwd})\n`)
|
|
875
1005
|
await serveStdio(server)
|
|
@@ -884,6 +1014,7 @@ async function runGoal(cfg: AppConfig, flags: Flags, goal: string, maxSteps: num
|
|
|
884
1014
|
const { result, files } = await executeRun({
|
|
885
1015
|
config: cfg,
|
|
886
1016
|
goal,
|
|
1017
|
+
browserProvider: browserProviderFlag(flags),
|
|
887
1018
|
url: strFlag(flags, "url"),
|
|
888
1019
|
headed: boolFlag(flags, "headed"),
|
|
889
1020
|
maxSteps,
|
|
@@ -1025,6 +1156,7 @@ async function cmdHeal(args: string[], flags: Flags): Promise<number> {
|
|
|
1025
1156
|
repo,
|
|
1026
1157
|
command,
|
|
1027
1158
|
outDir: strFlag(flags, "out"),
|
|
1159
|
+
browserProvider: browserProviderFlag(flags),
|
|
1028
1160
|
headed: boolFlag(flags, "headed"),
|
|
1029
1161
|
maxSteps: maxStepsFlag(flags),
|
|
1030
1162
|
url: strFlag(flags, "url"),
|
|
@@ -1096,6 +1228,7 @@ async function cmdMonitor(refs: string[], flags: Flags): Promise<number> {
|
|
|
1096
1228
|
config: cfg,
|
|
1097
1229
|
checks,
|
|
1098
1230
|
once,
|
|
1231
|
+
browserProvider: browserProviderFlag(flags),
|
|
1099
1232
|
headed: boolFlag(flags, "headed"),
|
|
1100
1233
|
url: strFlag(flags, "url"),
|
|
1101
1234
|
skills: skillFlags(flags),
|
|
@@ -1134,6 +1267,7 @@ async function cmdSuite(refs: string[], flags: Flags): Promise<number> {
|
|
|
1134
1267
|
config: cfg,
|
|
1135
1268
|
cases,
|
|
1136
1269
|
repo,
|
|
1270
|
+
browserProvider: browserProviderFlag(flags),
|
|
1137
1271
|
parallel,
|
|
1138
1272
|
outDir: strFlag(flags, "out"),
|
|
1139
1273
|
writeBack: boolFlag(flags, "no-write-back") ? false : undefined,
|