@capagents/forgeqe 1.1.1 → 1.1.3

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 (71) hide show
  1. package/HOW_TO_USE.md +25 -9
  2. package/README.md +30 -1
  3. package/browserstack.example.yaml +14 -0
  4. package/docs/security-data-flow.html +154 -86
  5. package/docs/user-guide.html +329 -135
  6. package/evals/prompts/brain/capability-question.yaml +12 -0
  7. package/evals/prompts/brain/failed-runs-cause.yaml +12 -0
  8. package/evals/prompts/brain/release-route.yaml +14 -0
  9. package/evals/prompts/brain/report-request.yaml +13 -0
  10. package/evals/prompts/brain/secret-request.yaml +9 -0
  11. package/evals/prompts/explore-charters/lockout.yaml +18 -0
  12. package/evals/prompts/explore-charters/transfer-limit.yaml +16 -0
  13. package/evals/prompts/refine/current-release.yaml +26 -0
  14. package/evals/prompts/refine/missing-item.yaml +10 -0
  15. package/evals/prompts/refine/question-stays-question.yaml +13 -0
  16. package/evals/prompts/refine/report-request.yaml +19 -0
  17. package/forgeqe.example.yaml +51 -0
  18. package/package.json +5 -2
  19. package/prompts/README.md +28 -7
  20. package/prompts/api-scenarios.md +56 -24
  21. package/prompts/api-tests.md +67 -51
  22. package/prompts/brain.md +277 -268
  23. package/prompts/browser-capture.md +8 -7
  24. package/prompts/browser-case-report.md +13 -12
  25. package/prompts/browser-checks.md +17 -18
  26. package/prompts/browser-efficiency.md +4 -1
  27. package/prompts/browser-error-recovery.md +5 -4
  28. package/prompts/browser-extra.md +1 -0
  29. package/prompts/browser-goal-verification.md +8 -7
  30. package/prompts/browser-skill-select.md +28 -1
  31. package/prompts/conventions.md +10 -0
  32. package/prompts/domain-rule.md +17 -14
  33. package/prompts/eval-judge.md +36 -13
  34. package/prompts/explore-charters.md +53 -22
  35. package/prompts/find-tests.md +45 -30
  36. package/prompts/heal.md +42 -32
  37. package/prompts/pr-review.md +44 -28
  38. package/prompts/refine.md +73 -40
  39. package/prompts/skill-rule.md +4 -1
  40. package/prompts/test-agent.md +61 -45
  41. package/prompts/test-reviewer.md +64 -37
  42. package/prompts/test-rules.md +59 -56
  43. package/py/forgeqe_engine/browser.py +111 -1
  44. package/py/forgeqe_engine/capture.py +8 -1
  45. package/py/forgeqe_engine/checks.py +20 -8
  46. package/py/forgeqe_engine/prompts.py +9 -0
  47. package/py/forgeqe_engine/report.py +15 -8
  48. package/py/forgeqe_engine/service.py +50 -27
  49. package/py/forgeqe_engine/skills.py +31 -7
  50. package/src/auth/signin.ts +30 -1
  51. package/src/bi.ts +914 -0
  52. package/src/cache.ts +92 -0
  53. package/src/capabilities/guide.ts +322 -140
  54. package/src/capabilities/registry.ts +3 -3
  55. package/src/childEnv.ts +4 -0
  56. package/src/cli/help.ts +18 -4
  57. package/src/cli/index.ts +149 -15
  58. package/src/config.ts +272 -0
  59. package/src/engine/bridge.ts +32 -2
  60. package/src/evals/prompts.ts +288 -0
  61. package/src/heal/loop.ts +3 -1
  62. package/src/init.ts +18 -0
  63. package/src/mcp/server.ts +8 -0
  64. package/src/mcp/tools.ts +30 -4
  65. package/src/monitor/run.ts +3 -1
  66. package/src/prompts.ts +6 -3
  67. package/src/promptstyle.ts +159 -0
  68. package/src/run.ts +4 -1
  69. package/src/ste.ts +8 -2
  70. package/src/suite/run.ts +3 -1
  71. package/testmu.example.yaml +14 -0
@@ -0,0 +1,159 @@
1
+ import { steParagraphs, steScore, STE_TARGET, RFC_LEAD } from "./ste.ts"
2
+
3
+ /**
4
+ * The prompting standard every ForgeQE prompt follows, and what of it can be checked in the text. Each technique is
5
+ * used where it fits the kind of prompt (frontmatter `kind`):
6
+ *
7
+ * | kind | what it is | structure | few-shot | execution contract |
8
+ * |------------|------------------------------------------------|-------------------------------------------------------------|----------|--------------------|
9
+ * | agent | works with tools over many steps | Role, Objective, Execution contract, Rules, Output | optional | required |
10
+ * | structured | one answer in a fixed shape (plans, verdicts) | Role, Objective, Inputs, Rules, Output, Examples | required | no |
11
+ * | fragment | rules included in other prompts | Rules | no | no |
12
+ * | user | the user's own text (browser-extra) | not checked | no | no |
13
+ *
14
+ * Every kind but user is written in ASD-STE100 (src/ste.ts) and states its rules with the RFC 2119 key words: MUST and
15
+ * MUST NOT for absolute rules, SHOULD and SHOULD NOT for strong defaults, MAY for a free choice. A lower-case "must",
16
+ * "should", or "shall" is ambiguous, so it is flagged. Evaluation cases (evals/prompts/<name>/) measure how reliably
17
+ * a prompt does its task; `forgeqe prompts eval` runs them.
18
+ */
19
+
20
+ export type PromptKind = "agent" | "structured" | "fragment" | "user"
21
+ export const PROMPT_KINDS: PromptKind[] = ["agent", "structured", "fragment", "user"]
22
+
23
+ /** The `##` sections each kind has, in this order. Other sections may come between them. */
24
+ export const SECTIONS: Record<PromptKind, string[]> = {
25
+ agent: ["Role", "Objective", "Execution contract", "Rules", "Output"],
26
+ structured: ["Role", "Objective", "Inputs", "Rules", "Output", "Examples"],
27
+ fragment: [],
28
+ user: [],
29
+ }
30
+
31
+ /** The parts of an execution contract, each a bold label or a `###` heading inside it. */
32
+ export const CONTRACT_PARTS = ["Preconditions", "Tools", "Procedure", "Stop when", "On failure", "Evidence"]
33
+
34
+ /** Fewest worked examples a structured prompt gives. */
35
+ export const MIN_EXAMPLES = 2
36
+
37
+ /** Share of rule lines (list items under a Rules section) that carry an RFC 2119 key word. */
38
+ export const RFC_TARGET = 0.8
39
+
40
+ const KEYWORD = /\b(MUST NOT|MUST|SHOULD NOT|SHOULD|MAY|SHALL NOT|SHALL|REQUIRED|RECOMMENDED|OPTIONAL)\b/
41
+ /** Lower-case words that read like a rule but say nothing of its strength. */
42
+ const AMBIGUOUS = /\b(must|should|shall)\b/
43
+
44
+ export type StyleIssue = { rule: "ste" | "rfc" | "ambiguous" | "section" | "order" | "examples" | "contract" | "kind"; detail: string }
45
+
46
+ export type StyleReport = {
47
+ kind: PromptKind
48
+ ste: number
49
+ /** Rule lines with a key word, of all rule lines; 1 when the prompt has none. */
50
+ rfc: number
51
+ ruleLines: number
52
+ examples: number
53
+ issues: StyleIssue[]
54
+ }
55
+
56
+ /** The `##` headings of a prompt, in order, without code blocks. */
57
+ export function headings(markdown: string, level = 2): string[] {
58
+ const out: string[] = []
59
+ let fence = false
60
+ const mark = `${"#".repeat(level)} `
61
+ for (const line of markdown.split("\n")) {
62
+ if (/^\s*(```|~~~)/.test(line)) fence = !fence
63
+ else if (!fence && line.startsWith(mark)) out.push(line.slice(mark.length).trim())
64
+ }
65
+ return out
66
+ }
67
+
68
+ /** The text under a `##` heading, up to the next `##` heading. */
69
+ export function section(markdown: string, name: string): string {
70
+ const lines = markdown.split("\n")
71
+ const at = lines.findIndex((l) => l.trim().toLowerCase() === `## ${name}`.toLowerCase())
72
+ if (at < 0) return ""
73
+ const end = lines.findIndex((l, i) => i > at && /^## /.test(l))
74
+ return lines.slice(at + 1, end < 0 ? undefined : end).join("\n")
75
+ }
76
+
77
+ /** Text with code, quotes, and placeholders taken out: a quoted example is not a rule. */
78
+ const prose = (text: string) =>
79
+ text
80
+ .replace(/```[\s\S]*?```/g, "")
81
+ .replace(/`[^`]*`/g, "")
82
+ .replace(/"[^"\n]*"/g, "")
83
+ .replace(/“[^”\n]*”/g, "")
84
+ .replace(/\{\{[^}]*\}\}/g, "")
85
+
86
+ /**
87
+ * The rules of the Rules sections (and of a fragment's whole text). A list item is one rule, with what is nested under
88
+ * it as its details; only an item without a key word that opens a list ("Write replies so that they read quickly:")
89
+ * hands its nested items on, each a rule of its own.
90
+ */
91
+ export function ruleLines(markdown: string, kind: PromptKind): string[] {
92
+ const parts = kind === "fragment" ? [markdown] : headings(markdown).filter((h) => /rules/i.test(h)).map((h) => section(markdown, h))
93
+ const items: string[] = []
94
+ for (const part of parts) {
95
+ // The indent of the items that count as rules at each depth; deeper items under a rule are its details.
96
+ let ruleIndent = -1
97
+ for (const line of prose(part).split("\n")) {
98
+ const m = /^(\s*)(?:[-*+]|\d+[.)])\s+(.*)$/.exec(line)
99
+ if (!m) continue
100
+ const indent = m[1]!.length
101
+ const text = m[2]!.trim()
102
+ if (!text) continue
103
+ if (ruleIndent >= 0 && indent > ruleIndent) continue
104
+ // A numbered step is a MUST by the conventions, unless it says SHOULD or MAY.
105
+ const numbered = /^\s*\d+[.)]/.test(line)
106
+ const opensList = /:\s*$/.test(text) && !KEYWORD.test(text) && !numbered
107
+ if (opensList) {
108
+ ruleIndent = -1
109
+ continue
110
+ }
111
+ items.push(numbered && !KEYWORD.test(text) ? `MUST ${text}` : text)
112
+ ruleIndent = indent
113
+ }
114
+ }
115
+ return items
116
+ }
117
+
118
+ export function checkStyle(markdown: string, kind: PromptKind): StyleReport {
119
+ const issues: StyleIssue[] = []
120
+ if (kind === "user") return { kind, ste: 1, rfc: 1, ruleLines: 0, examples: 0, issues }
121
+ const ste = steScore(markdown).score
122
+ if (ste < STE_TARGET) issues.push({ rule: "ste", detail: `${Math.round(ste * 100)}% of sentences keep ASD-STE100; the target is ${Math.round(STE_TARGET * 100)}%` })
123
+
124
+ const rules = ruleLines(markdown, kind)
125
+ const keyed = rules.filter((r) => KEYWORD.test(r)).length
126
+ const rfc = rules.length ? keyed / rules.length : 1
127
+ if (rules.length && rfc < RFC_TARGET) issues.push({ rule: "rfc", detail: `${keyed} of ${rules.length} rules use an RFC 2119 key word; the target is ${Math.round(RFC_TARGET * 100)}%` })
128
+ for (const sentence of steParagraphs(prose(markdown)).flat()) {
129
+ const m = AMBIGUOUS.exec(sentence)
130
+ if (m) issues.push({ rule: "ambiguous", detail: `"${m[0]}" in: ${sentence.slice(0, 100)} — use ${m[0].toUpperCase()} (RFC 2119), or say it as a fact` })
131
+ }
132
+
133
+ const have = headings(markdown)
134
+ const need = SECTIONS[kind]
135
+ for (const name of need) if (!have.some((h) => h.toLowerCase() === name.toLowerCase())) issues.push({ rule: "section", detail: `no "## ${name}" section` })
136
+ const order = need.map((n) => have.findIndex((h) => h.toLowerCase() === n.toLowerCase())).filter((i) => i >= 0)
137
+ if (order.some((v, i) => i > 0 && v < order[i - 1]!)) issues.push({ rule: "order", detail: `sections out of order; use ${need.join(", ")}` })
138
+
139
+ const examples = headings(markdown, 3).filter((h) => /^example\b/i.test(h)).length
140
+ if (kind === "structured" && examples < MIN_EXAMPLES) issues.push({ rule: "examples", detail: `${examples} worked examples ("### Example …"); a structured prompt gives ${MIN_EXAMPLES} or more` })
141
+
142
+ if (kind === "agent") {
143
+ const contract = section(markdown, "Execution contract")
144
+ for (const part of CONTRACT_PARTS) {
145
+ const label = new RegExp(`(\\*\\*${part}\\*\\*|^###\\s+${part}\\b)`, "im")
146
+ if (contract && !label.test(contract)) issues.push({ rule: "contract", detail: `the execution contract has no "${part}" part` })
147
+ }
148
+ }
149
+ return { kind, ste, rfc, ruleLines: rules.length, examples, issues }
150
+ }
151
+
152
+ export function promptKind(value: string | undefined): PromptKind | undefined {
153
+ return PROMPT_KINDS.find((k) => k === value)
154
+ }
155
+
156
+ /** Whether a sentence states a rule with an RFC 2119 key word ("You MUST …", "… MUST NOT …"). */
157
+ export function isRule(sentence: string): boolean {
158
+ return RFC_LEAD.test(sentence.trim()) || KEYWORD.test(sentence)
159
+ }
package/src/run.ts CHANGED
@@ -5,7 +5,7 @@ import path from "node:path"
5
5
  import { assertGoalUrlsAllowed, hostAllowed, normalizeHttpUrl } from "./guard.ts"
6
6
  import { missingKey } from "./llm.ts"
7
7
  import type { AgentEvent, ReplayMode, RunResult } from "./types.ts"
8
- import type { AppConfig } from "./config.ts"
8
+ import { browserConfigFor, type AppConfig, type BrowserProvider } from "./config.ts"
9
9
  import { runEngine } from "./engine/bridge.ts"
10
10
  import { MOCK_ENGINE } from "./engine/mock.ts"
11
11
  import { ensureEngine } from "./engine/setup.ts"
@@ -23,6 +23,7 @@ import { knowledgeBlock, readKnowledge, relevantKnowledge } from "./knowledge.ts
23
23
  export type ExecuteOptions = {
24
24
  config: AppConfig
25
25
  goal: string
26
+ browserProvider?: BrowserProvider
26
27
  url?: string
27
28
  headed?: boolean
28
29
  maxSteps?: number
@@ -148,6 +149,7 @@ export async function executeRun(opts: ExecuteOptions): Promise<{ result: RunRes
148
149
 
149
150
  const dir = opts.outDir || runDirectory(opts.config.export.outDir)
150
151
  const maxSteps = opts.maxSteps || opts.config.browser.maxSteps
152
+ const browser = browserConfigFor(opts.config, opts.browserProvider)
151
153
  const t0 = Date.now()
152
154
  const withReport = (out: { result: RunResult; files: string[] }) => {
153
155
  out.result.cost = costOf(out.result.usage, opts.config.llm.price)
@@ -216,6 +218,7 @@ export async function executeRun(opts: ExecuteOptions): Promise<{ result: RunRes
216
218
  headed: Boolean(opts.headed) || !opts.config.browser.headless,
217
219
  maxSteps,
218
220
  allowedHosts: allowed,
221
+ browser,
219
222
  extendSystemMessage: extend || undefined,
220
223
  memory,
221
224
  env,
package/src/ste.ts CHANGED
@@ -91,8 +91,13 @@ export function countWords(text: string): number {
91
91
  return (collapse(text).match(/[A-Za-z0-9][\w'’./-]*/g) || []).length
92
92
  }
93
93
 
94
+ /** "You MUST", "The agent SHOULD NOT": the RFC 2119 key word that makes a sentence a rule, with its subject. */
95
+ export const RFC_LEAD = /^(([Yy]ou|[Tt]he agent|[Tt]he reply|[Ee]ach \w+|[Ee]very \w+)\s+)?(MUST NOT|MUST|SHOULD NOT|SHOULD|MAY|SHALL NOT|SHALL)\s+/
96
+
94
97
  export function isInstruction(sentence: string): boolean {
95
- const s = collapse(sentence).replace(/^[\s>*_-]+/, "").replace(LEAD, "")
98
+ const lead = collapse(sentence).replace(/^[\s>*_-]+/, "").replace(LEAD, "")
99
+ // "You MUST run the tests." is an instruction: the verb after the key word decides, as without it.
100
+ const s = RFC_LEAD.test(lead) ? lead.replace(RFC_LEAD, "") : lead
96
101
  const first = (s.match(/^[A-Za-z']+/)?.[0] || "").toLowerCase()
97
102
  return VERBS.has(first)
98
103
  }
@@ -151,7 +156,8 @@ function splitSentences(text: string): string[] {
151
156
  }
152
157
 
153
158
  export function checkSentence(text: string): SteSentence {
154
- const words = countWords(text)
159
+ // The subject and RFC 2119 key word of a rule ("You MUST NOT") do not count toward its length.
160
+ const words = countWords(text) - countWords(collapse(text).replace(/^[\s>*_-]+/, "").replace(LEAD, "").match(RFC_LEAD)?.[0] || "")
155
161
  const instruction = isInstruction(text)
156
162
  const issues: SteSentence["issues"] = []
157
163
  const limit = instruction ? 20 : 25
package/src/suite/run.ts CHANGED
@@ -6,7 +6,7 @@ import { costOf } from "../cases/types.ts"
6
6
  import { addUsage, NO_USAGE } from "../usage.ts"
7
7
  import type { AgentEvent, CodeResult, ReplayMode, RunResult } from "../types.ts"
8
8
  import { codeSkipReason, runCodeAgent } from "../code/index.ts"
9
- import type { AppConfig } from "../config.ts"
9
+ import type { AppConfig, BrowserProvider } from "../config.ts"
10
10
  import { fileBugs } from "../cases/bugs.ts"
11
11
  import { classifyFailure, FAILURE_LABEL } from "../cases/failure.ts"
12
12
  import { associateAdoAutomation, readMappings } from "../code/traceability.ts"
@@ -43,6 +43,7 @@ export type SuiteOptions = {
43
43
  cases: TestCase[]
44
44
  /** Repo for the coding agent; unset: browser runs only. */
45
45
  repo?: string
46
+ browserProvider?: BrowserProvider
46
47
  parallel?: number
47
48
  outDir?: string
48
49
  writeBack?: boolean
@@ -104,6 +105,7 @@ export async function runSuite(opts: SuiteOptions): Promise<SuiteResult> {
104
105
  const { result } = await execute({
105
106
  config,
106
107
  goal: goalFor(tc),
108
+ browserProvider: opts.browserProvider,
107
109
  url: tc.startUrl || opts.url,
108
110
  headed: opts.headed,
109
111
  maxSteps: opts.maxSteps,
@@ -0,0 +1,14 @@
1
+ provider: testmu
2
+ remote:
3
+ browser: pw-chromium
4
+ browser_version: latest
5
+ platform: Windows 11
6
+ username_env: LT_USERNAME
7
+ access_key_env: LT_ACCESS_KEY
8
+ project: ForgeQE
9
+ build: nightly
10
+ name: checkout smoke
11
+ local: false
12
+ extra:
13
+ "lt:options":
14
+ console: true