@capagents/forgeqe 0.0.0-stage → 1.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 +1639 -0
- package/README.md +338 -2
- package/bin/forgeqe.d.ts +3 -0
- package/bin/forgeqe.js +188 -0
- package/docs/ARCHITECTURE.md +408 -0
- package/docs/security-data-flow.html +1870 -0
- package/docs/user-guide.html +2094 -0
- package/evals/domain/access-role-change.yaml +37 -0
- package/evals/domain/banking-payment-transfer.yaml +41 -0
- package/evals/domain/gdpr-account-erasure.yaml +30 -0
- package/evals/domain/insurance-claim-fnol.yaml +30 -0
- package/evals/domain/insurance-policy-renewal.yaml +37 -0
- package/evals/domain/lending-loan-application.yaml +36 -0
- package/forgeqe-architecture.html +718 -0
- package/forgeqe.example.yaml +203 -0
- package/knowledge/README.md +18 -0
- package/knowledge/business-rules.md +10 -0
- package/knowledge/compliance.md +16 -0
- package/knowledge/environments.md +5 -0
- package/knowledge/glossary.md +5 -0
- package/knowledge/product.md +16 -0
- package/knowledge/roles.md +9 -0
- package/knowledge/test-data.md +7 -0
- package/llms.example.json +51 -0
- package/package.json +63 -4
- package/prompts/README.md +34 -0
- package/prompts/api-scenarios.md +21 -0
- package/prompts/api-tests.md +39 -0
- package/prompts/brain.md +166 -0
- package/prompts/browser-capture.md +7 -0
- package/prompts/browser-case-report.md +7 -0
- package/prompts/browser-checks.md +14 -0
- package/prompts/browser-efficiency.md +7 -0
- package/prompts/browser-error-recovery.md +15 -0
- package/prompts/browser-extra.md +5 -0
- package/prompts/browser-goal-verification.md +13 -0
- package/prompts/browser-skill-select.md +6 -0
- package/prompts/domain-rule.md +13 -0
- package/prompts/eval-judge.md +19 -0
- package/prompts/explore-charters.md +27 -0
- package/prompts/find-tests.md +28 -0
- package/prompts/heal.md +40 -0
- package/prompts/pr-review.md +29 -0
- package/prompts/skill-rule.md +6 -0
- package/prompts/test-agent.md +34 -0
- package/prompts/test-reviewer.md +30 -0
- package/prompts/test-rules.md +28 -0
- package/py/forgeqe_engine/__init__.py +3 -0
- package/py/forgeqe_engine/__main__.py +3 -0
- package/py/forgeqe_engine/brand.py +453 -0
- package/py/forgeqe_engine/brand_assets.py +6 -0
- package/py/forgeqe_engine/browser.py +196 -0
- package/py/forgeqe_engine/capture.py +538 -0
- package/py/forgeqe_engine/checks.py +98 -0
- package/py/forgeqe_engine/cli.py +188 -0
- package/py/forgeqe_engine/diagnostics.py +609 -0
- package/py/forgeqe_engine/llm.py +195 -0
- package/py/forgeqe_engine/logins.py +251 -0
- package/py/forgeqe_engine/loops.py +224 -0
- package/py/forgeqe_engine/memory.py +1004 -0
- package/py/forgeqe_engine/network.py +244 -0
- package/py/forgeqe_engine/prompts.py +20 -0
- package/py/forgeqe_engine/replay.py +240 -0
- package/py/forgeqe_engine/report.py +173 -0
- package/py/forgeqe_engine/service.py +1335 -0
- package/py/forgeqe_engine/settings.py +93 -0
- package/py/forgeqe_engine/skills.py +88 -0
- package/py/forgeqe_engine/summary.py +239 -0
- package/py/forgeqe_engine/urls.py +81 -0
- package/py/forgeqe_engine/workflow_yaml.py +689 -0
- package/skills/dotnet-nunit-playwright/SKILL.md +34 -0
- package/skills/dynamics-365/SKILL.md +21 -0
- package/skills/playwright-typescript/SKILL.md +35 -0
- package/skills/writing-skills/SKILL.md +30 -0
- package/src/api/plan.ts +246 -0
- package/src/api/run.ts +204 -0
- package/src/api/scenario.ts +502 -0
- package/src/api/schema.ts +170 -0
- package/src/api/spec.ts +624 -0
- package/src/api/store.ts +232 -0
- package/src/api/traffic.ts +281 -0
- package/src/api/types.ts +163 -0
- package/src/api/write.ts +272 -0
- package/src/auth/signin.ts +494 -0
- package/src/auth/store.ts +170 -0
- package/src/brain/index.ts +189 -0
- package/src/brain/prompt.ts +19 -0
- package/src/capabilities/docs.ts +101 -0
- package/src/capabilities/guide.ts +455 -0
- package/src/capabilities/registry.ts +343 -0
- package/src/capabilities/render.ts +106 -0
- package/src/capabilities/status.ts +137 -0
- package/src/card.ts +11 -0
- package/src/cases/ado.ts +325 -0
- package/src/cases/api.ts +117 -0
- package/src/cases/bugs.ts +223 -0
- package/src/cases/confluence.ts +111 -0
- package/src/cases/context.ts +223 -0
- package/src/cases/dashboards.ts +137 -0
- package/src/cases/failure.ts +97 -0
- package/src/cases/files.ts +295 -0
- package/src/cases/goal.ts +53 -0
- package/src/cases/http.ts +111 -0
- package/src/cases/index.ts +131 -0
- package/src/cases/jira.ts +122 -0
- package/src/cases/qtest.ts +108 -0
- package/src/cases/quality.ts +190 -0
- package/src/cases/rag.ts +100 -0
- package/src/cases/readiness.ts +284 -0
- package/src/cases/save.ts +149 -0
- package/src/cases/sprints.ts +148 -0
- package/src/cases/testrail.ts +176 -0
- package/src/cases/types.ts +107 -0
- package/src/cases/wiki.ts +105 -0
- package/src/cases/xray.ts +206 -0
- package/src/cases/zephyr.ts +153 -0
- package/src/childEnv.ts +86 -0
- package/src/cli/args.ts +114 -0
- package/src/cli/help.ts +167 -0
- package/src/cli/index.ts +1142 -0
- package/src/cli/terminal.ts +187 -0
- package/src/code/areas.ts +337 -0
- package/src/code/attention.ts +214 -0
- package/src/code/find.ts +540 -0
- package/src/code/handoff.ts +541 -0
- package/src/code/history.ts +170 -0
- package/src/code/impact.ts +231 -0
- package/src/code/index.ts +34 -0
- package/src/code/livebrowser.ts +67 -0
- package/src/code/loop.ts +603 -0
- package/src/code/notes.ts +75 -0
- package/src/code/profile.ts +226 -0
- package/src/code/prompt.ts +303 -0
- package/src/code/prreview.ts +649 -0
- package/src/code/prtests.ts +103 -0
- package/src/code/replay.ts +772 -0
- package/src/code/review.ts +202 -0
- package/src/code/runtime.ts +781 -0
- package/src/code/slices.ts +159 -0
- package/src/code/testrun.ts +187 -0
- package/src/code/traceability.ts +100 -0
- package/src/code/worktree.ts +153 -0
- package/src/commands.ts +101 -0
- package/src/config.ts +970 -0
- package/src/detail.ts +65 -0
- package/src/domain.ts +19 -0
- package/src/engine/bridge.ts +359 -0
- package/src/engine/mock.ts +102 -0
- package/src/engine/setup.ts +298 -0
- package/src/entra.ts +154 -0
- package/src/env.ts +24 -0
- package/src/evals/domain.ts +338 -0
- package/src/explore/charters.ts +151 -0
- package/src/explore/run.ts +280 -0
- package/src/explore/story.ts +106 -0
- package/src/guard.ts +51 -0
- package/src/heal/loop.ts +357 -0
- package/src/heal/prompt.ts +143 -0
- package/src/heal/report.ts +95 -0
- package/src/index.ts +12 -0
- package/src/init.ts +102 -0
- package/src/knowledge.ts +114 -0
- package/src/legacyEnv.ts +9 -0
- package/src/llm.ts +61 -0
- package/src/locale.ts +25 -0
- package/src/logins.ts +148 -0
- package/src/managed.ts +101 -0
- package/src/mcp/apitools.ts +335 -0
- package/src/mcp/captools.ts +25 -0
- package/src/mcp/casetools.ts +223 -0
- package/src/mcp/commands.ts +421 -0
- package/src/mcp/exploretools.ts +129 -0
- package/src/mcp/external.ts +172 -0
- package/src/mcp/git.ts +78 -0
- package/src/mcp/notifytools.ts +49 -0
- package/src/mcp/prtools.ts +261 -0
- package/src/mcp/qualitytools.ts +154 -0
- package/src/mcp/repotests.ts +286 -0
- package/src/mcp/server.ts +234 -0
- package/src/mcp/tools.ts +810 -0
- package/src/memory.ts +250 -0
- package/src/money.ts +39 -0
- package/src/monitor/alerts.ts +94 -0
- package/src/monitor/background.ts +68 -0
- package/src/monitor/run.ts +263 -0
- package/src/monitor/state.ts +105 -0
- package/src/notify/events.ts +144 -0
- package/src/notify/notice.ts +177 -0
- package/src/prices.ts +175 -0
- package/src/print.ts +54 -0
- package/src/privacy.ts +35 -0
- package/src/prompts.ts +165 -0
- package/src/quality/budgets.ts +249 -0
- package/src/quality/history.ts +149 -0
- package/src/quality/visual.ts +201 -0
- package/src/report/data.ts +475 -0
- package/src/report/html.ts +106 -0
- package/src/report/template.html +84 -0
- package/src/run.ts +243 -0
- package/src/sessionLog.ts +216 -0
- package/src/sessionLogins.ts +94 -0
- package/src/skills/registry.ts +217 -0
- package/src/skills/scaffold.ts +65 -0
- package/src/skills/select.ts +37 -0
- package/src/skills/view.ts +44 -0
- package/src/suite/report.ts +135 -0
- package/src/suite/run.ts +303 -0
- package/src/summary.ts +188 -0
- package/src/tools.ts +130 -0
- package/src/tui/App.tsx +983 -0
- package/src/tui/clipboard.ts +36 -0
- package/src/tui/components.tsx +263 -0
- package/src/tui/dialogs.ts +65 -0
- package/src/tui/main.tsx +20 -0
- package/src/tui/text.ts +28 -0
- package/src/tui/theme.ts +104 -0
- package/src/tui/transcript.tsx +344 -0
- package/src/types.ts +146 -0
- package/src/update.ts +311 -0
- package/src/usage.ts +158 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { SyntaxStyle } from "@opentui/core"
|
|
3
|
+
|
|
4
|
+
import type { CodeResult, RunStatus } from "../types.ts"
|
|
5
|
+
import { CARD_MARK, type Card } from "../card.ts"
|
|
6
|
+
import { counts } from "../suite/report.ts"
|
|
7
|
+
import { suiteExitCode, type SuiteResult } from "../suite/run.ts"
|
|
8
|
+
import { STATUS_MARK, shortPath, type RunSummary } from "../summary.ts"
|
|
9
|
+
import { BAR } from "./components.tsx"
|
|
10
|
+
import { formatDuration, formatTokens, theme } from "./theme.ts"
|
|
11
|
+
import { clip } from "./text.ts"
|
|
12
|
+
|
|
13
|
+
/** The transcript: its lines, and the cards for a run, a suite, a tool result, and what a request produced. */
|
|
14
|
+
|
|
15
|
+
export type Line = {
|
|
16
|
+
id: number
|
|
17
|
+
kind: "sys" | "user" | "thought" | "timing" | "memory" | "tool" | "file" | "code" | "ok" | "err" | "summary" | "suitesummary" | "reply" | "card"
|
|
18
|
+
text: string
|
|
19
|
+
summary?: RunSummary
|
|
20
|
+
suite?: SuiteResult
|
|
21
|
+
card?: Card
|
|
22
|
+
outcome?: "done" | "failed" | "needs_input"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** What one request produced: browser runs with their files, suites, and the tests the coding agent wrote. */
|
|
26
|
+
export type Produced = {
|
|
27
|
+
runs: { runDir: string; status: RunStatus; files: string[] }[]
|
|
28
|
+
suites: { html: string; junit: string }[]
|
|
29
|
+
tests: { runDir: string; result?: CodeResult; error?: string }[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const emptyProduced = (): Produced => ({ runs: [], suites: [], tests: [] })
|
|
33
|
+
|
|
34
|
+
/** Whether passed runs become tests this session, and why not when they don't. */
|
|
35
|
+
export type TestsSetting = { on: boolean; why: string }
|
|
36
|
+
|
|
37
|
+
export const TEST_VERDICT: Record<CodeResult["status"], [string, string]> = {
|
|
38
|
+
passed: ["✓", "passed when ForgeQE ran it"],
|
|
39
|
+
failed: ["✗", "still fails when ForgeQE runs it"],
|
|
40
|
+
stopped: ["■", "stopped before it passed"],
|
|
41
|
+
blocked: ["✗", "blocked: the app stopped the test browser"],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The closing card of a request: the report of each run, and the test written for it, or why none was. */
|
|
45
|
+
export function producedCard(p: Produced, tests: TestsSetting): Card | null {
|
|
46
|
+
if (!p.runs.length && !p.suites.length && !p.tests.length) return null
|
|
47
|
+
const show = shortPath
|
|
48
|
+
const row = (mark: string, label: string, value: string) => `${mark.padEnd(2)}${label.padEnd(10)}${value}`
|
|
49
|
+
const body: string[] = []
|
|
50
|
+
const testRows = (t: Produced["tests"][number]) => {
|
|
51
|
+
if (!t.result) return [row("✗", "Test", `not written: ${t.error || "the coding agent failed"}`)]
|
|
52
|
+
const r = t.result
|
|
53
|
+
const [mark, verdict] = TEST_VERDICT[r.status]
|
|
54
|
+
return [
|
|
55
|
+
row(mark, "Test", r.testFile ? `${show(path.resolve(r.repo, r.testFile))} ${verdict}` : "no test file reported by the coding agent"),
|
|
56
|
+
...(r.command ? [row("", "Command", r.command)] : []),
|
|
57
|
+
row("", "Handoff", show(r.handoff)),
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
for (const run of p.runs) {
|
|
61
|
+
const find = (name: string) => run.files.find((f) => path.basename(f) === name)
|
|
62
|
+
const report = find("report.html")
|
|
63
|
+
if (body.length) body.push("")
|
|
64
|
+
if (p.runs.length > 1) body.push(row("", "Run", `${show(run.runDir)}/ ${run.status === "complete" ? "passed" : run.status === "stopped" ? "stopped" : "failed"}`))
|
|
65
|
+
body.push(row("", "Report", report ? show(report) : "not written"))
|
|
66
|
+
const written = p.tests.filter((t) => t.runDir === run.runDir)
|
|
67
|
+
if (written.length) body.push(...written.flatMap(testRows))
|
|
68
|
+
else if (run.status !== "complete") body.push(row("", "Test", "not written: the browser run did not pass"))
|
|
69
|
+
else if (!tests.on) body.push(row("", "Test", `not written: ${tests.why}`))
|
|
70
|
+
else body.push(row("?", "Test", "not written: the run was not handed to the coding agent"))
|
|
71
|
+
}
|
|
72
|
+
const other = p.tests.filter((t) => !p.runs.some((r) => r.runDir === t.runDir))
|
|
73
|
+
if (other.length) {
|
|
74
|
+
if (body.length) body.push("")
|
|
75
|
+
body.push(...other.flatMap(testRows))
|
|
76
|
+
}
|
|
77
|
+
for (const s of p.suites) {
|
|
78
|
+
if (body.length) body.push("")
|
|
79
|
+
body.push(row("", "Report", show(s.html)), row("", "JUnit", show(s.junit)))
|
|
80
|
+
}
|
|
81
|
+
const results = p.tests.map((t) => t.result?.status)
|
|
82
|
+
const status = results.includes("failed") || p.tests.some((t) => t.error) ? "fail" : results.includes("passed") ? "ok" : "info"
|
|
83
|
+
return { status, title: "What this produced", body }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function SummaryCard({ summary }: { summary: RunSummary }) {
|
|
87
|
+
const color = summary.status === "complete" ? theme.ok : summary.status === "stopped" ? theme.warn : theme.err
|
|
88
|
+
return (
|
|
89
|
+
<box
|
|
90
|
+
border={["left"]}
|
|
91
|
+
borderColor={color}
|
|
92
|
+
customBorderChars={BAR}
|
|
93
|
+
style={{
|
|
94
|
+
flexDirection: "column",
|
|
95
|
+
backgroundColor: theme.panel,
|
|
96
|
+
paddingLeft: 2,
|
|
97
|
+
paddingRight: 2,
|
|
98
|
+
paddingTop: 1,
|
|
99
|
+
paddingBottom: 1,
|
|
100
|
+
marginTop: 1,
|
|
101
|
+
marginBottom: 1,
|
|
102
|
+
flexShrink: 0,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<box style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
|
106
|
+
<text>
|
|
107
|
+
<span fg={color}>{`${STATUS_MARK[summary.status]} `}</span>
|
|
108
|
+
<span fg={theme.text}>{summary.title}</span>
|
|
109
|
+
</text>
|
|
110
|
+
<text fg={theme.muted}>{summary.stats}</text>
|
|
111
|
+
</box>
|
|
112
|
+
{summary.failed.map((f, i) => (
|
|
113
|
+
<text key={i} fg={theme.err} style={i === 0 ? { marginTop: 1 } : undefined}>
|
|
114
|
+
{`✗ ${f}`}
|
|
115
|
+
</text>
|
|
116
|
+
))}
|
|
117
|
+
<text fg={theme.text} style={{ marginTop: 1 }}>
|
|
118
|
+
{summary.text}
|
|
119
|
+
</text>
|
|
120
|
+
{summary.finalUrl || summary.savedDir ? <box style={{ height: 1 }} /> : null}
|
|
121
|
+
{summary.finalUrl ? (
|
|
122
|
+
<text>
|
|
123
|
+
<span fg={theme.faint}>{"Ended on "}</span>
|
|
124
|
+
<span fg={theme.muted}>{summary.finalUrl}</span>
|
|
125
|
+
</text>
|
|
126
|
+
) : null}
|
|
127
|
+
{summary.memory ? (
|
|
128
|
+
<text>
|
|
129
|
+
<span fg={theme.faint}>{"Memory "}</span>
|
|
130
|
+
<span fg={theme.memory}>{summary.memory}</span>
|
|
131
|
+
</text>
|
|
132
|
+
) : null}
|
|
133
|
+
{summary.quality ? (
|
|
134
|
+
<text>
|
|
135
|
+
<span fg={theme.faint}>{"Quality "}</span>
|
|
136
|
+
<span fg={theme.muted}>{summary.quality}</span>
|
|
137
|
+
</text>
|
|
138
|
+
) : null}
|
|
139
|
+
{summary.usage ? (
|
|
140
|
+
<text>
|
|
141
|
+
<span fg={theme.faint}>{"Usage "}</span>
|
|
142
|
+
<span fg={theme.muted}>{summary.usage}</span>
|
|
143
|
+
</text>
|
|
144
|
+
) : null}
|
|
145
|
+
{summary.savedDir ? (
|
|
146
|
+
<text>
|
|
147
|
+
<span fg={theme.faint}>{"Saved "}</span>
|
|
148
|
+
<span fg={theme.muted}>{`${summary.savedDir}/ `}</span>
|
|
149
|
+
<span fg={theme.faint}>{summary.savedFiles.join(" · ")}</span>
|
|
150
|
+
</text>
|
|
151
|
+
) : null}
|
|
152
|
+
</box>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function Row({ label, value, color = theme.muted }: { label: string; value: string; color?: string }) {
|
|
157
|
+
if (!value) return null
|
|
158
|
+
return (
|
|
159
|
+
<text>
|
|
160
|
+
<span fg={theme.faint}>{label.padEnd(10)}</span>
|
|
161
|
+
<span fg={color}>{value}</span>
|
|
162
|
+
</text>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const CASE_MARK = { passed: "✓", failed: "✗", error: "!", stopped: "■" } as const
|
|
167
|
+
|
|
168
|
+
export function SuiteCard({ suite }: { suite: SuiteResult }) {
|
|
169
|
+
const c = counts(suite.results)
|
|
170
|
+
const ok = !suite.stopped && suiteExitCode(suite) === 0
|
|
171
|
+
const color = suite.stopped ? theme.warn : ok ? theme.ok : theme.err
|
|
172
|
+
const tokens = suite.results.reduce((n, r) => n + r.tokens, 0)
|
|
173
|
+
const show = (f: string) => path.relative(process.cwd(), f) || f
|
|
174
|
+
const caseColor = (s: keyof typeof CASE_MARK) => (s === "passed" ? theme.ok : s === "stopped" ? theme.warn : theme.err)
|
|
175
|
+
return (
|
|
176
|
+
<box
|
|
177
|
+
border={["left"]}
|
|
178
|
+
borderColor={color}
|
|
179
|
+
customBorderChars={BAR}
|
|
180
|
+
style={{
|
|
181
|
+
flexDirection: "column",
|
|
182
|
+
backgroundColor: theme.panel,
|
|
183
|
+
paddingLeft: 2,
|
|
184
|
+
paddingRight: 2,
|
|
185
|
+
paddingTop: 1,
|
|
186
|
+
paddingBottom: 1,
|
|
187
|
+
marginTop: 1,
|
|
188
|
+
marginBottom: 1,
|
|
189
|
+
flexShrink: 0,
|
|
190
|
+
}}
|
|
191
|
+
>
|
|
192
|
+
<box style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
|
193
|
+
<text>
|
|
194
|
+
<span fg={color}>{suite.stopped ? "■ " : ok ? "✓ " : "✗ "}</span>
|
|
195
|
+
<span fg={theme.text}>{suite.stopped ? "Suite stopped" : ok ? "Suite passed" : "Suite failed"}</span>
|
|
196
|
+
</text>
|
|
197
|
+
<text fg={theme.muted}>{`${c.passed}/${c.total} passed · ${formatDuration(suite.ms)} · ${formatTokens(tokens)} tokens`}</text>
|
|
198
|
+
</box>
|
|
199
|
+
<box style={{ height: 1 }} />
|
|
200
|
+
{suite.results.map((r) => (
|
|
201
|
+
<box key={r.case.id} style={{ flexDirection: "column", flexShrink: 0 }}>
|
|
202
|
+
<text>
|
|
203
|
+
<span fg={caseColor(r.status)}>{`${CASE_MARK[r.status]} `}</span>
|
|
204
|
+
<span fg={theme.text}>{`${r.case.id} ${clip(r.case.title, 60)}`}</span>
|
|
205
|
+
<span fg={theme.faint}>{` ${formatDuration(r.ms)}`}</span>
|
|
206
|
+
{r.code ? (
|
|
207
|
+
<span fg={r.code.status === "passed" ? theme.ok : theme.err}>{` test ${r.code.status}${r.code.testFile ? `: ${r.code.testFile}` : ""}`}</span>
|
|
208
|
+
) : null}
|
|
209
|
+
</text>
|
|
210
|
+
{r.status !== "passed" && r.summary ? <text fg={theme.muted}>{` ${clip(r.summary.split("\n")[0], 110)}`}</text> : null}
|
|
211
|
+
</box>
|
|
212
|
+
))}
|
|
213
|
+
{suite.writeBack.length ? <box style={{ height: 1 }} /> : null}
|
|
214
|
+
{suite.writeBack.map((w, i) => (
|
|
215
|
+
<text key={i}>
|
|
216
|
+
<span fg={w.error ? theme.err : theme.ok}>{w.error ? "✗ " : "↑ "}</span>
|
|
217
|
+
<span fg={theme.muted}>{`${w.text}${w.url ? ` ${w.url}` : ""}${w.error ? `: ${w.error}` : ""}`}</span>
|
|
218
|
+
</text>
|
|
219
|
+
))}
|
|
220
|
+
<box style={{ height: 1 }} />
|
|
221
|
+
<Row label="Report" value={show(suite.files.html)} color={theme.text} />
|
|
222
|
+
<Row label="JUnit" value={show(suite.files.junit)} />
|
|
223
|
+
</box>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const CARD_COLOR: Record<Card["status"], string> = { ok: theme.ok, fail: theme.err, stopped: theme.warn, info: theme.accent }
|
|
228
|
+
|
|
229
|
+
export function ResultCard({ card }: { card: Card }) {
|
|
230
|
+
const color = CARD_COLOR[card.status]
|
|
231
|
+
const lineColor = (l: string) => (/^\s*✓/.test(l) ? theme.ok : /^\s*[✗!]/.test(l) ? theme.err : /^\s*\?/.test(l) ? theme.warn : theme.muted)
|
|
232
|
+
return (
|
|
233
|
+
<box
|
|
234
|
+
border={["left"]}
|
|
235
|
+
borderColor={color}
|
|
236
|
+
customBorderChars={BAR}
|
|
237
|
+
style={{
|
|
238
|
+
flexDirection: "column",
|
|
239
|
+
backgroundColor: theme.panel,
|
|
240
|
+
paddingLeft: 2,
|
|
241
|
+
paddingRight: 2,
|
|
242
|
+
paddingTop: 1,
|
|
243
|
+
paddingBottom: 1,
|
|
244
|
+
marginTop: 1,
|
|
245
|
+
marginBottom: 1,
|
|
246
|
+
flexShrink: 0,
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
<box style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
|
250
|
+
<text>
|
|
251
|
+
<span fg={color}>{`${CARD_MARK[card.status]} `}</span>
|
|
252
|
+
<span fg={theme.text}>{card.title}</span>
|
|
253
|
+
</text>
|
|
254
|
+
{card.stats ? <text fg={theme.muted}>{card.stats}</text> : null}
|
|
255
|
+
</box>
|
|
256
|
+
{card.body.length ? <box style={{ height: 1 }} /> : null}
|
|
257
|
+
{card.body.map((l, i) => (
|
|
258
|
+
<text key={i} fg={lineColor(l)}>
|
|
259
|
+
{l || " "}
|
|
260
|
+
</text>
|
|
261
|
+
))}
|
|
262
|
+
</box>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let style: SyntaxStyle | undefined
|
|
267
|
+
/** How the agent's markdown replies look: headings, bold, code, and links in the shell's colours, markers hidden. */
|
|
268
|
+
function replyStyle(): SyntaxStyle {
|
|
269
|
+
style ||= SyntaxStyle.fromStyles({
|
|
270
|
+
default: { fg: theme.text },
|
|
271
|
+
"markup.heading": { fg: theme.accent, bold: true },
|
|
272
|
+
"markup.strong": { bold: true },
|
|
273
|
+
"markup.italic": { italic: true },
|
|
274
|
+
"markup.raw": { fg: theme.memory },
|
|
275
|
+
"markup.quote": { fg: theme.muted, italic: true },
|
|
276
|
+
"markup.list": { fg: theme.accent },
|
|
277
|
+
"markup.link": { fg: theme.accent, underline: true },
|
|
278
|
+
"markup.link.label": { fg: theme.accent, underline: true },
|
|
279
|
+
"markup.link.url": { fg: theme.muted },
|
|
280
|
+
})
|
|
281
|
+
return style
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function TranscriptLine({ line }: { line: Line }) {
|
|
285
|
+
if (line.kind === "card" && line.card) return <ResultCard card={line.card} />
|
|
286
|
+
if (line.kind === "summary" && line.summary) return <SummaryCard summary={line.summary} />
|
|
287
|
+
if (line.kind === "suitesummary" && line.suite) return <SuiteCard suite={line.suite} />
|
|
288
|
+
if (line.kind === "reply") {
|
|
289
|
+
const color = line.outcome === "failed" ? theme.err : line.outcome === "needs_input" ? theme.warn : theme.ok
|
|
290
|
+
return (
|
|
291
|
+
<box
|
|
292
|
+
border={["left"]}
|
|
293
|
+
borderColor={color}
|
|
294
|
+
customBorderChars={BAR}
|
|
295
|
+
style={{ backgroundColor: theme.panel, paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, marginTop: 1, marginBottom: 1, flexShrink: 0 }}
|
|
296
|
+
>
|
|
297
|
+
<markdown content={line.text} syntaxStyle={replyStyle()} fg={theme.text} conceal tableOptions={{ widthMode: "content", wrapMode: "word" }} />
|
|
298
|
+
</box>
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
if (line.kind === "tool") return <text fg={theme.faint}>{` ⚙ ${line.text}`}</text>
|
|
302
|
+
if (line.kind === "file") return <text fg={theme.accent}>{` ✎ ${line.text}`}</text>
|
|
303
|
+
if (line.kind === "code") return <text fg={theme.memory}>{` ▸ ${line.text}`}</text>
|
|
304
|
+
if (line.kind === "user") {
|
|
305
|
+
return (
|
|
306
|
+
<box
|
|
307
|
+
border={["left"]}
|
|
308
|
+
borderColor={theme.accent}
|
|
309
|
+
customBorderChars={BAR}
|
|
310
|
+
style={{
|
|
311
|
+
backgroundColor: theme.panel,
|
|
312
|
+
paddingLeft: 2,
|
|
313
|
+
paddingTop: 1,
|
|
314
|
+
paddingBottom: 1,
|
|
315
|
+
marginTop: 1,
|
|
316
|
+
marginBottom: 1,
|
|
317
|
+
flexShrink: 0,
|
|
318
|
+
}}
|
|
319
|
+
>
|
|
320
|
+
<text fg={theme.text}>{line.text}</text>
|
|
321
|
+
</box>
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
if (line.kind === "timing") return <text fg={theme.warn}>{` + ${line.text}`}</text>
|
|
325
|
+
if (line.kind === "memory") return <text fg={theme.memory}>{` ↻ ${line.text}`}</text>
|
|
326
|
+
if (line.kind === "thought") return <text fg={theme.thought}>{` ${line.text}`}</text>
|
|
327
|
+
if (line.kind === "ok") {
|
|
328
|
+
return (
|
|
329
|
+
<text>
|
|
330
|
+
<span fg={theme.ok}>{" ✓ "}</span>
|
|
331
|
+
<span fg={theme.text}>{line.text}</span>
|
|
332
|
+
</text>
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
if (line.kind === "err") {
|
|
336
|
+
return (
|
|
337
|
+
<text>
|
|
338
|
+
<span fg={theme.err}>{" ✗ "}</span>
|
|
339
|
+
<span fg={theme.text}>{line.text}</span>
|
|
340
|
+
</text>
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
return <text fg={theme.muted}>{` ${line.text || " "}`}</text>
|
|
344
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { QualityReport } from "./quality/budgets.ts"
|
|
2
|
+
import type { Cost } from "./usage.ts"
|
|
3
|
+
|
|
4
|
+
export type Usage = {
|
|
5
|
+
/** Every prompt token, cached ones included. */
|
|
6
|
+
input: number
|
|
7
|
+
output: number
|
|
8
|
+
/** The part of `input` the provider served from its prompt cache (billed at a lower rate). */
|
|
9
|
+
cached?: number
|
|
10
|
+
/** Prompt tokens written to the provider's cache (Anthropic bills these above the input price). */
|
|
11
|
+
cacheWrite?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type RunStatus = "complete" | "fail" | "stopped"
|
|
15
|
+
|
|
16
|
+
export type ReplayMode = "verify" | "trust" | "off"
|
|
17
|
+
|
|
18
|
+
/** What page memory did during a live run. */
|
|
19
|
+
export type MemoryInfo = {
|
|
20
|
+
dir: string
|
|
21
|
+
mode: ReplayMode
|
|
22
|
+
/** Steps replayed from memory without the LLM, out of the recorded flow's replayable steps. */
|
|
23
|
+
replayed: number
|
|
24
|
+
replayTotal: number
|
|
25
|
+
replayStopped?: string
|
|
26
|
+
llmSteps: number
|
|
27
|
+
/** Pages and elements observed this run; newElements were not in memory before. */
|
|
28
|
+
pages: number
|
|
29
|
+
elements: number
|
|
30
|
+
newElements: number
|
|
31
|
+
flow: "saved" | "updated" | null
|
|
32
|
+
/** After a failed run: the steps that worked before it went wrong, saved in resume.json for a retry to replay. */
|
|
33
|
+
resume?: { steps: number; failedAt: number; reason: string; lines: string[]; file?: string }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type RunResult = {
|
|
37
|
+
status: RunStatus
|
|
38
|
+
goal: string
|
|
39
|
+
startUrl: string
|
|
40
|
+
finalUrl: string
|
|
41
|
+
summary: string
|
|
42
|
+
usage: Usage
|
|
43
|
+
/** What `usage` cost at the model's price in llms.json; unset without a price. */
|
|
44
|
+
cost?: Cost
|
|
45
|
+
driver: "synthetic" | "engine"
|
|
46
|
+
memory?: MemoryInfo
|
|
47
|
+
/**
|
|
48
|
+
* passed; assertion_failed: the steps were done and an expected result does not hold (the test failed, which is a
|
|
49
|
+
* valid result); error: the agent could not do the steps. Unset for runs that did not say.
|
|
50
|
+
*/
|
|
51
|
+
outcome?: "passed" | "assertion_failed" | "error"
|
|
52
|
+
/** The expected results that did not hold, and what the page showed instead. */
|
|
53
|
+
failedChecks?: { expected: string; actual: string }[]
|
|
54
|
+
/** For a test case: the agent's verdict on each numbered test step. */
|
|
55
|
+
caseReport?: CaseReport
|
|
56
|
+
/** Skills added to the browser agent's prompt. */
|
|
57
|
+
skills?: string[]
|
|
58
|
+
/** Accessibility (WCAG rules) and Core Web Vitals of the pages visited, judged against the budgets. */
|
|
59
|
+
quality?: QualityReport
|
|
60
|
+
/** Budgets this run exceeded: the run counts as failed, though its steps passed. */
|
|
61
|
+
budgetExceeded?: string[]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Why a failed step failed, in the browser agent's judgement: see src/cases/failure.ts. */
|
|
65
|
+
export type StepCause = "app" | "test_case" | "environment" | "automation"
|
|
66
|
+
|
|
67
|
+
export type StepVerdict = { step: number; status: "passed" | "failed" | "not_run"; actual?: string; cause?: StepCause }
|
|
68
|
+
|
|
69
|
+
export type CaseReport = { summary: string; steps: StepVerdict[] }
|
|
70
|
+
|
|
71
|
+
/** blocked: the app could not be reached or blocked automation, so no honest test could pass. */
|
|
72
|
+
export type CodeStatus = "passed" | "failed" | "stopped" | "blocked"
|
|
73
|
+
|
|
74
|
+
/** A read-only reviewer's verdict on a passing test: does it test the goal, the way the test rules say. */
|
|
75
|
+
export type CodeReview = {
|
|
76
|
+
approved: boolean
|
|
77
|
+
summary: string
|
|
78
|
+
/** correctness: the test does not test what the goal asks, or not reliably. standards: it works, but not in the repository's way (data in a page object, naming, leftovers). */
|
|
79
|
+
issues: { file?: string; problem: string; fix?: string; kind?: "correctness" | "standards" }[]
|
|
80
|
+
/** The coding-standard issues the tidy-up round could not fix without breaking the test: the test was kept as it passed. */
|
|
81
|
+
standardsLeft?: boolean
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The coding agent's job: a test in the target repo that ForgeQE itself ran until it passed. */
|
|
85
|
+
export type CodeResult = {
|
|
86
|
+
status: CodeStatus
|
|
87
|
+
repo: string
|
|
88
|
+
attempts: number
|
|
89
|
+
maxAttempts: number
|
|
90
|
+
testFile?: string
|
|
91
|
+
command?: string
|
|
92
|
+
/** Existing helpers, page objects, and fixtures the agent reused; files it created or changed. */
|
|
93
|
+
reused: string[]
|
|
94
|
+
changed: string[]
|
|
95
|
+
notes: string
|
|
96
|
+
/** Tail of the last test run's output. */
|
|
97
|
+
output: string
|
|
98
|
+
/** Browsers ForgeQE saw the last test run launch. */
|
|
99
|
+
browsers?: string[]
|
|
100
|
+
review?: CodeReview
|
|
101
|
+
/** The replay of the run's steps in Playwright before Scribe started: the steps that failed in each launch setup. */
|
|
102
|
+
replay?: { dir: string; error?: string; passes: { setup: string; failed: number[]; reached: boolean }[] }
|
|
103
|
+
usage: Usage
|
|
104
|
+
/** What `usage` cost at the model's price in llms.json; unset without a price. */
|
|
105
|
+
cost?: Cost
|
|
106
|
+
/** Skills Scribe loaded during the job. */
|
|
107
|
+
skills?: string[]
|
|
108
|
+
sessionId?: string
|
|
109
|
+
/** Scribe reopened the session of an earlier job on this run that stopped before it finished. */
|
|
110
|
+
resumed?: boolean
|
|
111
|
+
model: string
|
|
112
|
+
handoff: string
|
|
113
|
+
/** The test's full name as the framework reports it, for mapping it to its test case. */
|
|
114
|
+
testName?: string
|
|
115
|
+
/** The test asserts the correct behaviour of a known defect and is marked as an expected failure. */
|
|
116
|
+
knownDefect?: boolean
|
|
117
|
+
/** The job did not finish: what it wrote was moved to the needs-attention folder (see src/code/attention.ts). */
|
|
118
|
+
attention?: { dir: string; files: string[]; patch?: string; readme: string }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** What an agent did to a file, for the shell's file list. */
|
|
122
|
+
export type FileAction = "read" | "write" | "edit"
|
|
123
|
+
|
|
124
|
+
/** One item of an agent's own to-do list (the agent runtime's todowrite). */
|
|
125
|
+
export type PlanItem = { text: string; status: "pending" | "in_progress" | "completed" | "cancelled" }
|
|
126
|
+
|
|
127
|
+
export type CodeEvent =
|
|
128
|
+
| { type: "code"; kind: "info"; text: string }
|
|
129
|
+
/** A finished tool call: `tool` is its name, and `file` the file it read or changed, relative to the repository. */
|
|
130
|
+
| { type: "code"; kind: "tool"; text: string; ok: boolean; tool?: string; file?: { path: string; action: FileAction } }
|
|
131
|
+
| { type: "code"; kind: "say"; text: string }
|
|
132
|
+
| { type: "code"; kind: "test"; text: string; ok: boolean; attempt: number }
|
|
133
|
+
/** The session's usage so far; `context` is the last model call's input, how full its context window is. */
|
|
134
|
+
| { type: "code"; kind: "usage"; usage: Usage; context?: number }
|
|
135
|
+
/** The agent's to-do list, each time it changes. */
|
|
136
|
+
| { type: "code"; kind: "plan"; items: PlanItem[] }
|
|
137
|
+
|
|
138
|
+
export type AgentEvent =
|
|
139
|
+
| { type: "log"; level: "info" | "warn" | "error"; text: string }
|
|
140
|
+
| CodeEvent
|
|
141
|
+
| { type: "step"; step: number; url: string; title: string; category: string; source?: "memory" }
|
|
142
|
+
| { type: "memory"; text: string }
|
|
143
|
+
| { type: "thought"; step: number; text: string }
|
|
144
|
+
| { type: "outcome"; step: number; ok: boolean; text: string }
|
|
145
|
+
| { type: "usage"; usage: Usage }
|
|
146
|
+
| { type: "done"; result: RunResult }
|