@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,202 @@
1
+ import { readFileSync, statSync } from "node:fs"
2
+ import path from "node:path"
3
+
4
+ import type { CodeEvent, CodeReview, Usage } from "../types.ts"
5
+ import { privateEnvNames } from "../childEnv.ts"
6
+ import type { AppConfig } from "../config.ts"
7
+ import type { Handoff } from "./handoff.ts"
8
+ import { openSession, type AgentSpec, type OpencodeSession } from "./runtime.ts"
9
+ import type { AgentReport, ReviewIssue } from "./prompt.ts"
10
+ import { loadSkills } from "../skills/registry.ts"
11
+ import type { Price } from "../usage.ts"
12
+ import { prompt } from "../prompts.ts"
13
+ import { domainNotes } from "../domain.ts"
14
+
15
+ const OUTPUT_CHARS = 4_000
16
+ /** Changed files are shown in the prompt up to these sizes; the reviewer reads the rest itself. */
17
+ const FILE_CHARS = 12_000
18
+ const FILES_CHARS = 40_000
19
+
20
+ export const reviewAgentPrompt = () => prompt("test-reviewer")
21
+
22
+ export const REVIEW_AGENT: AgentSpec = {
23
+ name: "forgeqe-review",
24
+ description: "Checks a test ForgeQE's coding agent wrote against the goal and the test rules, without changing anything",
25
+ get prompt() {
26
+ return reviewAgentPrompt()
27
+ },
28
+ permission: {
29
+ edit: "deny",
30
+ bash: { "*": "deny", "git diff*": "allow", "git status*": "allow", "git log*": "allow", "git show*": "allow", "ls*": "allow" },
31
+ webfetch: "deny",
32
+ task: "deny",
33
+ },
34
+ skills: "scribe",
35
+ // A review reads the changed files it is given and a few around them; it never needs a long turn.
36
+ steps: 25,
37
+ }
38
+
39
+ export const REVIEW_SCHEMA = {
40
+ type: "object",
41
+ properties: {
42
+ approved: { type: "boolean", description: "true only when the test does what the goal says and keeps to the rules" },
43
+ summary: { type: "string", description: "One or two sentences on the verdict" },
44
+ issues: {
45
+ type: "array",
46
+ items: {
47
+ type: "object",
48
+ properties: {
49
+ file: { type: "string", description: "Path relative to the repository root" },
50
+ problem: { type: "string", description: "What is wrong" },
51
+ fix: { type: "string", description: "What to change" },
52
+ kind: { type: "string", enum: ["correctness", "standards"], description: "correctness: it does not test what the goal says, or not reliably. standards: it works, but not in the repository's way" },
53
+ },
54
+ required: ["problem", "kind"],
55
+ },
56
+ },
57
+ },
58
+ required: ["approved", "summary", "issues"],
59
+ } as const
60
+
61
+ type ReviewReport = { approved?: boolean; summary?: string; issues?: ReviewIssue[] }
62
+
63
+ export type ReviewInput = {
64
+ handoff: Handoff
65
+ report: AgentReport
66
+ output: string
67
+ browsers: string[]
68
+ seconds: number
69
+ /** Paths this job changed, from git; absent outside git. */
70
+ changes?: string[]
71
+ /** Uncommitted paths the user had before the job. */
72
+ before?: string[]
73
+ /** This reviewer's earlier verdict on this job, when the agent has since changed the test to answer it. */
74
+ earlier?: CodeReview
75
+ }
76
+
77
+ const LIST_MAX = 60
78
+ const list = (paths: string[]) => (paths.length > LIST_MAX ? `${paths.slice(0, LIST_MAX).join(", ")}, and ${paths.length - LIST_MAX} more` : paths.join(", "))
79
+
80
+ /** A review of one passing attempt; `error` when the reviewer gave no verdict. */
81
+ export type ReviewOutcome = { review?: CodeReview; error?: string }
82
+
83
+ /** `price`: the reviewer's model price (its own profile with code.review_profile); unset when unknown. */
84
+ export type Reviewer = { review(input: ReviewInput): Promise<ReviewOutcome>; close(): Promise<void>; usage: Usage; price?: Price }
85
+
86
+ /** The changed files' contents for the prompt, each up to FILE_CHARS and all up to FILES_CHARS. */
87
+ export function changedFiles(repo: string, paths: string[]): string[] {
88
+ const lines: string[] = []
89
+ let used = 0
90
+ for (const rel of paths) {
91
+ let text: string
92
+ try {
93
+ const file = path.join(repo, rel)
94
+ if (!statSync(file).isFile()) continue
95
+ text = readFileSync(file, "utf8")
96
+ } catch {
97
+ lines.push(`### ${rel}`, "(deleted)", "")
98
+ continue
99
+ }
100
+ if (used >= FILES_CHARS) {
101
+ lines.push(`### ${rel}`, "(not shown here; read it)", "")
102
+ continue
103
+ }
104
+ const room = Math.min(FILE_CHARS, FILES_CHARS - used)
105
+ const shown = text.length > room ? `${text.slice(0, room)}\n… (the rest is not shown here; read the file)` : text
106
+ used += shown.length
107
+ lines.push(`### ${rel}`, "```", shown, "```", "")
108
+ }
109
+ return lines
110
+ }
111
+
112
+ export function reviewPrompt(input: ReviewInput, repo?: string): string {
113
+ const { handoff, report } = input
114
+ const out = input.output.length > OUTPUT_CHARS ? `…${input.output.slice(-OUTPUT_CHARS)}` : input.output
115
+ const changed = input.changes || report.changed || []
116
+ const checks = handoff.markdown.split("\n## Checks the browser agent made\n")[1]?.split("\n## ")[0]?.trim()
117
+ return [
118
+ `Review the test the coding agent wrote for this goal: ${handoff.goal}`,
119
+ `The handoff it worked from: ${handoff.file}`,
120
+ ...(checks ? ["", "The browser agent's checks, from the handoff:", checks] : []),
121
+ "",
122
+ `Test file: ${report.test_file}`,
123
+ `Command: ${report.command}`,
124
+ input.changes
125
+ ? `Files this job changed (git, against the working tree before the job): ${list(input.changes) || "(none)"}`
126
+ : `Files it reports changing: ${(report.changed || []).join(", ") || "(none reported)"}`,
127
+ input.before?.length ? `Already modified or untracked before the job, the user's work and not under review: ${list(input.before)}` : "",
128
+ report.notes ? `Its notes: ${report.notes}` : "",
129
+ "",
130
+ `ForgeQE ran the command: it passed in ${input.seconds}s with ${input.browsers.length ? input.browsers.join(", ") : `a browser on another machine (${report.remote_browser || "remote"})`}. End of the output:`,
131
+ "```",
132
+ out,
133
+ "```",
134
+ ...(repo && changed.length ? ["", "The changed files as they are now:", "", ...changedFiles(repo, changed)] : []),
135
+ ...(input.earlier
136
+ ? [
137
+ "",
138
+ "This is a follow-up review. Your earlier review raised these issues, and the coding agent has changed the test to answer them:",
139
+ ...input.earlier.issues.map((i, n) => `${n + 1}. [${i.kind || "correctness"}] ${i.file ? `${i.file}: ` : ""}${i.problem}`),
140
+ "Check each one: fixed, or still there. Raise a new correctness issue only when this change introduced it (it broke or weakened what the test checks); anything else you notice now is a standards issue at most. The test case and the handoff decide what the test must do: do not ask for more than they say.",
141
+ ]
142
+ : []),
143
+ ]
144
+ .filter((l, i, all) => l !== "" || all[i - 1] !== "")
145
+ .join("\n")
146
+ }
147
+
148
+ /** A read-only Scribe session, opened on the first review and kept for the next attempts. */
149
+ export function openReviewer(opts: {
150
+ config: AppConfig
151
+ repo: string
152
+ readDirs: string[]
153
+ signal?: AbortSignal
154
+ onEvent?: (e: CodeEvent) => void
155
+ open?: (o: Parameters<typeof openSession>[0]) => Promise<OpencodeSession>
156
+ }): Reviewer {
157
+ let session: OpencodeSession<ReviewReport> | null = null
158
+ const usage: Usage = { input: 0, output: 0 }
159
+ // code.review_profile: a cheaper llms.json profile for reading and judging; else the run's profile (or code.model).
160
+ const named = opts.config.code.reviewProfile ? opts.config.profiles[opts.config.code.reviewProfile] : undefined
161
+ const reviewProfile = named || opts.config.llm
162
+ const price = named ? named.price : opts.config.code.model ? undefined : opts.config.llm.price
163
+ const emit = opts.onEvent || (() => {})
164
+ return {
165
+ usage,
166
+ price,
167
+ async review(input) {
168
+ if (!session) {
169
+ const open = (opts.open || openSession) as (o: Parameters<typeof openSession>[0]) => Promise<OpencodeSession<ReviewReport>>
170
+ session = await open({
171
+ repo: opts.repo,
172
+ profile: reviewProfile,
173
+ domain: domainNotes(opts.config),
174
+ model: reviewProfile === opts.config.llm ? opts.config.code.model : undefined,
175
+ bin: opts.config.code.opencode,
176
+ readDirs: opts.readDirs,
177
+ hide: privateEnvNames(opts.config),
178
+ role: "reviewer",
179
+ signal: opts.signal,
180
+ onEvent: (e) => {
181
+ if (e.kind === "tool" || e.kind === "say") emit({ ...e, text: `review: ${e.text}` })
182
+ else if (e.kind === "info") emit(e)
183
+ },
184
+ agent: REVIEW_AGENT,
185
+ skills: loadSkills(opts.config.skills, opts.repo).skills,
186
+ schema: REVIEW_SCHEMA as unknown as Record<string, unknown>,
187
+ title: "ForgeQE review",
188
+ })
189
+ }
190
+ let reply = await session.prompt(reviewPrompt(input, opts.repo))
191
+ if (!reply.report && !reply.error) reply = await session.prompt("Give the structured result: approved, summary, and issues.")
192
+ Object.assign(usage, session.usage)
193
+ const r = reply.report
194
+ if (!r || typeof r.approved !== "boolean") return { error: reply.error || "the reviewer gave no verdict" }
195
+ const issues = (r.issues || []).filter((i) => i?.problem).map((i) => ({ ...i, ...(i.kind === "standards" || i.kind === "correctness" ? {} : { kind: "correctness" as const }) }))
196
+ return { review: { approved: r.approved, summary: String(r.summary || ""), issues } }
197
+ },
198
+ async close() {
199
+ if (session) await session.close()
200
+ },
201
+ }
202
+ }