@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,170 @@
1
+ import type { JsonSchema, KeyField } from "./types.ts"
2
+
3
+ /** Schemas, examples, and key fields for contracts: inferred from answers seen, checked against answers got. */
4
+
5
+ const MAX_ERRORS = 10
6
+ const MAX_DEPTH = 8
7
+
8
+ export function typeOf(v: unknown): string {
9
+ if (v === null) return "null"
10
+ if (Array.isArray(v)) return "array"
11
+ if (typeof v === "number") return Number.isInteger(v) ? "integer" : "number"
12
+ return typeof v
13
+ }
14
+
15
+ function types(s: JsonSchema): string[] {
16
+ return s.type === undefined ? [] : Array.isArray(s.type) ? s.type : [s.type]
17
+ }
18
+
19
+ /** One schema that every value given fits: a field present in every object is required, one seen as null is nullable. */
20
+ export function inferSchema(values: unknown[], depth = 0): JsonSchema {
21
+ const seen = values.filter((v) => v !== undefined)
22
+ if (!seen.length) return {}
23
+ // An observed 10 can be 10.5 next time (a price): numbers seen in answers are numbers, not integers.
24
+ const kinds = new Set(seen.map((v) => (typeOf(v) === "integer" ? "number" : typeOf(v))))
25
+ const nullable = kinds.delete("null")
26
+ if (!kinds.size) return { type: "null" }
27
+ if (kinds.size > 1) return { anyOf: [...kinds].map((k) => inferSchema(seen.filter((v) => typeOf(v) === k || (k === "number" && typeof v === "number")), depth)), ...(nullable ? { nullable } : {}) }
28
+ const [kind] = [...kinds]
29
+ const out: JsonSchema = { type: kind, ...(nullable ? { nullable } : {}) }
30
+ if (depth >= MAX_DEPTH) return out
31
+ if (kind === "object") {
32
+ const objects = seen.filter((v) => typeOf(v) === "object") as Record<string, unknown>[]
33
+ const keys = [...new Set(objects.flatMap((o) => Object.keys(o)))]
34
+ out.properties = Object.fromEntries(keys.map((k) => [k, inferSchema(objects.map((o) => o[k]), depth + 1)]))
35
+ const required = keys.filter((k) => objects.every((o) => k in o))
36
+ if (required.length) out.required = required
37
+ } else if (kind === "array") {
38
+ const items = (seen.filter(Array.isArray) as unknown[][]).flat()
39
+ if (items.length) out.items = inferSchema(items.slice(0, 200), depth + 1)
40
+ } else if (kind === "string") {
41
+ const strings = seen.filter((v) => typeof v === "string") as string[]
42
+ if (strings.every((s) => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(s))) out.format = "date-time"
43
+ else if (strings.every((s) => /^\d{4}-\d{2}-\d{2}$/.test(s))) out.format = "date"
44
+ else if (strings.every((s) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s))) out.format = "uuid"
45
+ else if (strings.every((s) => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s))) out.format = "email"
46
+ }
47
+ return out
48
+ }
49
+
50
+ /** What is wrong with a value against a schema, as "path: problem" lines (none when it fits). */
51
+ export function validate(value: unknown, schema: JsonSchema | undefined, at = "$", errors: string[] = []): string[] {
52
+ if (!schema || errors.length >= MAX_ERRORS) return errors
53
+ if (schema.allOf) for (const s of schema.allOf) validate(value, s, at, errors)
54
+ const alts = schema.anyOf || schema.oneOf
55
+ if (alts?.length && !(value === null && schema.nullable)) {
56
+ if (!alts.some((s) => !validate(value, s, at, []).length)) errors.push(`${at}: matches none of ${alts.length} allowed shapes`)
57
+ return errors
58
+ }
59
+ const allowed = types(schema)
60
+ if (value === null) {
61
+ if (schema.nullable || allowed.includes("null") || !allowed.length) return errors
62
+ errors.push(`${at}: is null, expected ${allowed.join(" or ")}`)
63
+ return errors
64
+ }
65
+ const actual = typeOf(value)
66
+ if (allowed.length && !allowed.includes(actual) && !(actual === "integer" && allowed.includes("number"))) {
67
+ errors.push(`${at}: is ${actual}, expected ${allowed.join(" or ")}`)
68
+ return errors
69
+ }
70
+ if (schema.enum?.length && !schema.enum.some((e) => JSON.stringify(e) === JSON.stringify(value))) errors.push(`${at}: ${JSON.stringify(value)} is not one of ${schema.enum.map((e) => JSON.stringify(e)).join(", ")}`)
71
+ if (actual === "object") {
72
+ const obj = value as Record<string, unknown>
73
+ for (const k of schema.required || []) if (!(k in obj)) errors.push(`${at}.${k}: missing`)
74
+ for (const [k, s] of Object.entries(schema.properties || {})) if (k in obj && obj[k] !== undefined) validate(obj[k], s, `${at}.${k}`, errors)
75
+ } else if (actual === "array" && schema.items) {
76
+ ;(value as unknown[]).slice(0, 50).forEach((v, i) => validate(v, schema.items, `${at}[${i}]`, errors))
77
+ }
78
+ return errors.slice(0, MAX_ERRORS)
79
+ }
80
+
81
+ /** The value at a path like data.orders[0].id (or $.data...). */
82
+ export function getPath(value: unknown, path: string): unknown {
83
+ const parts = path.replace(/^\$\.?/, "").match(/[^.[\]]+|\[\d+\]/g) || []
84
+ let cur: unknown = value
85
+ for (const p of parts) {
86
+ if (cur === null || cur === undefined) return undefined
87
+ cur = p.startsWith("[") ? (cur as unknown[])[Number(p.slice(1, -1))] : (cur as Record<string, unknown>)[p]
88
+ }
89
+ return cur
90
+ }
91
+
92
+ const KEY_NAME = /(^|_|\b)(id|uuid|key|code|status|state|type|kind|name|title|total|count|amount|price|currency|email|success|ok|result|message)$/i
93
+ const STABLE_NAME = /(^|_)(status|state|type|kind|currency|success|ok|result)$/i
94
+
95
+ /** Scalar fields under a value, with their paths (into the first element of arrays). */
96
+ function scalars(value: unknown, at = "", depth = 0, out: [string, unknown][] = []): [string, unknown][] {
97
+ if (depth > 4 || out.length > 200) return out
98
+ if (Array.isArray(value)) {
99
+ if (value.length) scalars(value[0], `${at}[0]`, depth + 1, out)
100
+ } else if (value && typeof value === "object") {
101
+ for (const [k, v] of Object.entries(value)) scalars(v, at ? `${at}.${k}` : k, depth + 1, out)
102
+ } else if (at) out.push([at, value])
103
+ return out
104
+ }
105
+
106
+ /**
107
+ * The fields worth checking in an answer: ids, statuses, names, totals, and the like, present in every sample, at most
108
+ * `max`. A value is pinned with `equals` only when every sample had it and the field is a status or type, which does not
109
+ * change from one call to the next; other values change with data, so only their type is checked.
110
+ */
111
+ export function keyFields(samples: unknown[], max = 8): KeyField[] {
112
+ if (!samples.length) return []
113
+ const all = samples.map((s) => new Map(scalars(s)))
114
+ const first = scalars(samples[0])
115
+ const fields: KeyField[] = []
116
+ const ranked = first
117
+ .filter(([p]) => all.every((m) => m.has(p)))
118
+ .map(([p, v]) => ({ p, v, leaf: p.split(/[.[]/).pop()!.replace(/]$/, ""), depth: p.split(/[.[]/).length }))
119
+ .filter((f) => KEY_NAME.test(f.leaf))
120
+ .sort((a, b) => a.depth - b.depth)
121
+ for (const f of ranked) {
122
+ if (fields.length >= max) break
123
+ const values = all.map((m) => m.get(f.p))
124
+ const same = values.every((v) => JSON.stringify(v) === JSON.stringify(values[0]))
125
+ const pinned = same && STABLE_NAME.test(f.leaf) && f.v !== null && typeof f.v !== "object"
126
+ fields.push({ path: f.p, type: typeof f.v === "number" ? "number" : typeOf(f.v), ...(pinned ? { equals: f.v } : {}) })
127
+ }
128
+ return fields
129
+ }
130
+
131
+ /** Check key fields against an answer: "path: problem" for each one that is wrong. */
132
+ export function checkFields(value: unknown, fields: KeyField[]): string[] {
133
+ const out: string[] = []
134
+ for (const f of fields) {
135
+ const v = getPath(value, f.path)
136
+ if (v === undefined) out.push(`${f.path}: missing`)
137
+ else if (f.equals !== undefined && JSON.stringify(v) !== JSON.stringify(f.equals)) out.push(`${f.path}: is ${JSON.stringify(v)}, expected ${JSON.stringify(f.equals)}`)
138
+ else if (f.type && v !== null && typeOf(v) !== f.type && !(f.type === "number" && typeOf(v) === "integer")) out.push(`${f.path}: is ${typeOf(v)}, expected ${f.type}`)
139
+ }
140
+ return out
141
+ }
142
+
143
+ /** A value that fits a schema, from its examples where it has them: a request body to start from. */
144
+ export function exampleFor(schema: JsonSchema | undefined, depth = 0, name = ""): unknown {
145
+ if (!schema) return null
146
+ if (schema.example !== undefined) return schema.example
147
+ if (schema.enum?.length) return schema.enum[0]
148
+ const alt = schema.anyOf?.[0] || schema.oneOf?.[0]
149
+ if (alt) return exampleFor(alt, depth, name)
150
+ if (schema.allOf?.length) return Object.assign({}, ...schema.allOf.map((s) => exampleFor(s, depth, name)).filter((v) => v && typeof v === "object"))
151
+ const t = types(schema).find((x) => x !== "null") || (schema.properties ? "object" : schema.items ? "array" : "string")
152
+ if (depth > 5) return t === "array" ? [] : t === "object" ? {} : null
153
+ switch (t) {
154
+ case "object":
155
+ return Object.fromEntries(Object.entries(schema.properties || {}).map(([k, s]) => [k, exampleFor(s, depth + 1, k)]))
156
+ case "array":
157
+ return schema.items ? [exampleFor(schema.items, depth + 1, name)] : []
158
+ case "integer":
159
+ case "number":
160
+ return 1
161
+ case "boolean":
162
+ return true
163
+ default:
164
+ if (schema.format === "date-time") return "2026-01-01T00:00:00Z"
165
+ if (schema.format === "date") return "2026-01-01"
166
+ if (schema.format === "email" || /email/i.test(name)) return "user@example.com"
167
+ if (schema.format === "uuid") return "00000000-0000-0000-0000-000000000001"
168
+ return name ? `sample-${name}` : "sample"
169
+ }
170
+ }