@capagents/forgeqe 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/HOW_TO_USE.md +1639 -0
  2. package/README.md +338 -2
  3. package/bin/forgeqe.d.ts +3 -0
  4. package/bin/forgeqe.js +188 -0
  5. package/docs/ARCHITECTURE.md +408 -0
  6. package/docs/security-data-flow.html +1870 -0
  7. package/docs/user-guide.html +2094 -0
  8. package/evals/domain/access-role-change.yaml +37 -0
  9. package/evals/domain/banking-payment-transfer.yaml +41 -0
  10. package/evals/domain/gdpr-account-erasure.yaml +30 -0
  11. package/evals/domain/insurance-claim-fnol.yaml +30 -0
  12. package/evals/domain/insurance-policy-renewal.yaml +37 -0
  13. package/evals/domain/lending-loan-application.yaml +36 -0
  14. package/forgeqe-architecture.html +718 -0
  15. package/forgeqe.example.yaml +203 -0
  16. package/knowledge/README.md +18 -0
  17. package/knowledge/business-rules.md +10 -0
  18. package/knowledge/compliance.md +16 -0
  19. package/knowledge/environments.md +5 -0
  20. package/knowledge/glossary.md +5 -0
  21. package/knowledge/product.md +16 -0
  22. package/knowledge/roles.md +9 -0
  23. package/knowledge/test-data.md +7 -0
  24. package/llms.example.json +51 -0
  25. package/package.json +63 -4
  26. package/prompts/README.md +34 -0
  27. package/prompts/api-scenarios.md +21 -0
  28. package/prompts/api-tests.md +39 -0
  29. package/prompts/brain.md +166 -0
  30. package/prompts/browser-capture.md +7 -0
  31. package/prompts/browser-case-report.md +7 -0
  32. package/prompts/browser-checks.md +14 -0
  33. package/prompts/browser-efficiency.md +7 -0
  34. package/prompts/browser-error-recovery.md +15 -0
  35. package/prompts/browser-extra.md +5 -0
  36. package/prompts/browser-goal-verification.md +13 -0
  37. package/prompts/browser-skill-select.md +6 -0
  38. package/prompts/domain-rule.md +13 -0
  39. package/prompts/eval-judge.md +19 -0
  40. package/prompts/explore-charters.md +27 -0
  41. package/prompts/find-tests.md +28 -0
  42. package/prompts/heal.md +40 -0
  43. package/prompts/pr-review.md +29 -0
  44. package/prompts/skill-rule.md +6 -0
  45. package/prompts/test-agent.md +34 -0
  46. package/prompts/test-reviewer.md +30 -0
  47. package/prompts/test-rules.md +28 -0
  48. package/py/forgeqe_engine/__init__.py +3 -0
  49. package/py/forgeqe_engine/__main__.py +3 -0
  50. package/py/forgeqe_engine/brand.py +453 -0
  51. package/py/forgeqe_engine/brand_assets.py +6 -0
  52. package/py/forgeqe_engine/browser.py +196 -0
  53. package/py/forgeqe_engine/capture.py +538 -0
  54. package/py/forgeqe_engine/checks.py +98 -0
  55. package/py/forgeqe_engine/cli.py +188 -0
  56. package/py/forgeqe_engine/diagnostics.py +609 -0
  57. package/py/forgeqe_engine/llm.py +195 -0
  58. package/py/forgeqe_engine/logins.py +251 -0
  59. package/py/forgeqe_engine/loops.py +224 -0
  60. package/py/forgeqe_engine/memory.py +1004 -0
  61. package/py/forgeqe_engine/network.py +244 -0
  62. package/py/forgeqe_engine/prompts.py +20 -0
  63. package/py/forgeqe_engine/replay.py +240 -0
  64. package/py/forgeqe_engine/report.py +173 -0
  65. package/py/forgeqe_engine/service.py +1335 -0
  66. package/py/forgeqe_engine/settings.py +93 -0
  67. package/py/forgeqe_engine/skills.py +88 -0
  68. package/py/forgeqe_engine/summary.py +239 -0
  69. package/py/forgeqe_engine/urls.py +81 -0
  70. package/py/forgeqe_engine/workflow_yaml.py +689 -0
  71. package/skills/dotnet-nunit-playwright/SKILL.md +34 -0
  72. package/skills/dynamics-365/SKILL.md +21 -0
  73. package/skills/playwright-typescript/SKILL.md +35 -0
  74. package/skills/writing-skills/SKILL.md +30 -0
  75. package/src/api/plan.ts +246 -0
  76. package/src/api/run.ts +204 -0
  77. package/src/api/scenario.ts +502 -0
  78. package/src/api/schema.ts +170 -0
  79. package/src/api/spec.ts +624 -0
  80. package/src/api/store.ts +232 -0
  81. package/src/api/traffic.ts +281 -0
  82. package/src/api/types.ts +163 -0
  83. package/src/api/write.ts +272 -0
  84. package/src/auth/signin.ts +494 -0
  85. package/src/auth/store.ts +170 -0
  86. package/src/brain/index.ts +189 -0
  87. package/src/brain/prompt.ts +19 -0
  88. package/src/capabilities/docs.ts +101 -0
  89. package/src/capabilities/guide.ts +455 -0
  90. package/src/capabilities/registry.ts +343 -0
  91. package/src/capabilities/render.ts +106 -0
  92. package/src/capabilities/status.ts +137 -0
  93. package/src/card.ts +11 -0
  94. package/src/cases/ado.ts +325 -0
  95. package/src/cases/api.ts +117 -0
  96. package/src/cases/bugs.ts +223 -0
  97. package/src/cases/confluence.ts +111 -0
  98. package/src/cases/context.ts +223 -0
  99. package/src/cases/dashboards.ts +137 -0
  100. package/src/cases/failure.ts +97 -0
  101. package/src/cases/files.ts +295 -0
  102. package/src/cases/goal.ts +53 -0
  103. package/src/cases/http.ts +111 -0
  104. package/src/cases/index.ts +131 -0
  105. package/src/cases/jira.ts +122 -0
  106. package/src/cases/qtest.ts +108 -0
  107. package/src/cases/quality.ts +190 -0
  108. package/src/cases/rag.ts +100 -0
  109. package/src/cases/readiness.ts +284 -0
  110. package/src/cases/save.ts +149 -0
  111. package/src/cases/sprints.ts +148 -0
  112. package/src/cases/testrail.ts +176 -0
  113. package/src/cases/types.ts +107 -0
  114. package/src/cases/wiki.ts +105 -0
  115. package/src/cases/xray.ts +206 -0
  116. package/src/cases/zephyr.ts +153 -0
  117. package/src/childEnv.ts +86 -0
  118. package/src/cli/args.ts +114 -0
  119. package/src/cli/help.ts +167 -0
  120. package/src/cli/index.ts +1142 -0
  121. package/src/cli/terminal.ts +187 -0
  122. package/src/code/areas.ts +337 -0
  123. package/src/code/attention.ts +214 -0
  124. package/src/code/find.ts +540 -0
  125. package/src/code/handoff.ts +541 -0
  126. package/src/code/history.ts +170 -0
  127. package/src/code/impact.ts +231 -0
  128. package/src/code/index.ts +34 -0
  129. package/src/code/livebrowser.ts +67 -0
  130. package/src/code/loop.ts +603 -0
  131. package/src/code/notes.ts +75 -0
  132. package/src/code/profile.ts +226 -0
  133. package/src/code/prompt.ts +303 -0
  134. package/src/code/prreview.ts +649 -0
  135. package/src/code/prtests.ts +103 -0
  136. package/src/code/replay.ts +772 -0
  137. package/src/code/review.ts +202 -0
  138. package/src/code/runtime.ts +757 -0
  139. package/src/code/slices.ts +159 -0
  140. package/src/code/testrun.ts +187 -0
  141. package/src/code/traceability.ts +100 -0
  142. package/src/code/worktree.ts +153 -0
  143. package/src/commands.ts +101 -0
  144. package/src/config.ts +970 -0
  145. package/src/detail.ts +65 -0
  146. package/src/domain.ts +19 -0
  147. package/src/engine/bridge.ts +359 -0
  148. package/src/engine/mock.ts +102 -0
  149. package/src/engine/setup.ts +298 -0
  150. package/src/entra.ts +154 -0
  151. package/src/env.ts +24 -0
  152. package/src/evals/domain.ts +338 -0
  153. package/src/explore/charters.ts +151 -0
  154. package/src/explore/run.ts +280 -0
  155. package/src/explore/story.ts +106 -0
  156. package/src/guard.ts +51 -0
  157. package/src/heal/loop.ts +357 -0
  158. package/src/heal/prompt.ts +143 -0
  159. package/src/heal/report.ts +95 -0
  160. package/src/index.ts +12 -0
  161. package/src/init.ts +102 -0
  162. package/src/knowledge.ts +114 -0
  163. package/src/legacyEnv.ts +9 -0
  164. package/src/llm.ts +61 -0
  165. package/src/locale.ts +25 -0
  166. package/src/logins.ts +148 -0
  167. package/src/managed.ts +101 -0
  168. package/src/mcp/apitools.ts +335 -0
  169. package/src/mcp/captools.ts +25 -0
  170. package/src/mcp/casetools.ts +223 -0
  171. package/src/mcp/commands.ts +421 -0
  172. package/src/mcp/exploretools.ts +129 -0
  173. package/src/mcp/external.ts +172 -0
  174. package/src/mcp/git.ts +78 -0
  175. package/src/mcp/notifytools.ts +49 -0
  176. package/src/mcp/prtools.ts +261 -0
  177. package/src/mcp/qualitytools.ts +154 -0
  178. package/src/mcp/repotests.ts +286 -0
  179. package/src/mcp/server.ts +234 -0
  180. package/src/mcp/tools.ts +810 -0
  181. package/src/memory.ts +250 -0
  182. package/src/money.ts +39 -0
  183. package/src/monitor/alerts.ts +94 -0
  184. package/src/monitor/background.ts +68 -0
  185. package/src/monitor/run.ts +263 -0
  186. package/src/monitor/state.ts +105 -0
  187. package/src/notify/events.ts +144 -0
  188. package/src/notify/notice.ts +177 -0
  189. package/src/prices.ts +175 -0
  190. package/src/print.ts +54 -0
  191. package/src/privacy.ts +35 -0
  192. package/src/prompts.ts +165 -0
  193. package/src/quality/budgets.ts +249 -0
  194. package/src/quality/history.ts +149 -0
  195. package/src/quality/visual.ts +201 -0
  196. package/src/report/data.ts +475 -0
  197. package/src/report/html.ts +106 -0
  198. package/src/report/template.html +84 -0
  199. package/src/run.ts +243 -0
  200. package/src/sessionLog.ts +216 -0
  201. package/src/sessionLogins.ts +94 -0
  202. package/src/skills/registry.ts +217 -0
  203. package/src/skills/scaffold.ts +65 -0
  204. package/src/skills/select.ts +37 -0
  205. package/src/skills/view.ts +44 -0
  206. package/src/suite/report.ts +135 -0
  207. package/src/suite/run.ts +303 -0
  208. package/src/summary.ts +188 -0
  209. package/src/tools.ts +130 -0
  210. package/src/tui/App.tsx +983 -0
  211. package/src/tui/clipboard.ts +36 -0
  212. package/src/tui/components.tsx +263 -0
  213. package/src/tui/dialogs.ts +65 -0
  214. package/src/tui/main.tsx +20 -0
  215. package/src/tui/text.ts +28 -0
  216. package/src/tui/theme.ts +104 -0
  217. package/src/tui/transcript.tsx +344 -0
  218. package/src/types.ts +136 -0
  219. package/src/update.ts +311 -0
  220. 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,136 @@
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
+ export type CodeEvent =
122
+ | { type: "code"; kind: "info"; text: string }
123
+ | { type: "code"; kind: "tool"; text: string; ok: boolean }
124
+ | { type: "code"; kind: "say"; text: string }
125
+ | { type: "code"; kind: "test"; text: string; ok: boolean; attempt: number }
126
+ | { type: "code"; kind: "usage"; usage: Usage }
127
+
128
+ export type AgentEvent =
129
+ | { type: "log"; level: "info" | "warn" | "error"; text: string }
130
+ | CodeEvent
131
+ | { type: "step"; step: number; url: string; title: string; category: string; source?: "memory" }
132
+ | { type: "memory"; text: string }
133
+ | { type: "thought"; step: number; text: string }
134
+ | { type: "outcome"; step: number; ok: boolean; text: string }
135
+ | { type: "usage"; usage: Usage }
136
+ | { type: "done"; result: RunResult }