@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.
- 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 +757 -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 +136 -0
- package/src/update.ts +311 -0
- 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
|
+
}
|