@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,214 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process"
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
import type { CodeReview } from "../types.ts"
|
|
6
|
+
import { jobChanges, type WorkTree } from "./worktree.ts"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* When a coding job ends without a passing, approved test, what it wrote does not stay among the repository's tests,
|
|
10
|
+
* where it would fail every run, and is not thrown away either: it goes to a needs-attention folder next to the tests,
|
|
11
|
+
* with a README saying which test case it is for, why it stopped, and how to pick it up.
|
|
12
|
+
*
|
|
13
|
+
* - Files the job created are moved there, their relative imports rewritten so they still point at the same modules.
|
|
14
|
+
* - Edits the job made to files that were committed and unchanged before it are saved as changes.patch and reverted,
|
|
15
|
+
* so the repository's existing tests and page objects are as they were. Files the user had already changed are left.
|
|
16
|
+
* - The folder is kept out of builds and test runs: Playwright's config gets a testIgnore for it, TypeScript files a
|
|
17
|
+
* @ts-nocheck line, a Python folder a conftest.py that collects nothing, and files of compiled languages (C#, Java,
|
|
18
|
+
* Kotlin) a .txt suffix so the project does not compile them.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export const ATTENTION_DIR = "needs-attention"
|
|
22
|
+
|
|
23
|
+
export type AttentionInput = {
|
|
24
|
+
repo: string
|
|
25
|
+
tree: WorkTree
|
|
26
|
+
testFile?: string
|
|
27
|
+
command?: string
|
|
28
|
+
caseId?: string
|
|
29
|
+
title?: string
|
|
30
|
+
url?: string
|
|
31
|
+
/** Why the job stopped, in a sentence or two. */
|
|
32
|
+
why: string
|
|
33
|
+
/** The end of the last test run's output. */
|
|
34
|
+
output?: string
|
|
35
|
+
review?: CodeReview
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type Attention = { dir: string; files: string[]; patch?: string; readme: string }
|
|
39
|
+
|
|
40
|
+
const git = (repo: string, args: string[]) => spawnSync("git", args, { cwd: repo, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 })
|
|
41
|
+
const posix = (p: string) => p.split(path.sep).join("/")
|
|
42
|
+
const COMPILED = /\.(cs|java|kt|kts|scala|go|rs|swift|vb|fs)$/i
|
|
43
|
+
const SCRIPT = /\.(m|c)?[jt]sx?$/i
|
|
44
|
+
|
|
45
|
+
function slugOf(s: string): string {
|
|
46
|
+
return s.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "test"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The needs-attention folder for a job: beside the test's folder, one subfolder per test case. */
|
|
50
|
+
export function attentionDir(repo: string, testFile: string | undefined, caseId: string | undefined): string {
|
|
51
|
+
const base = testFile ? path.dirname(testFile) : "."
|
|
52
|
+
return posix(path.join(base, ATTENTION_DIR, slugOf(caseId || (testFile ? path.basename(testFile).replace(/\..*$/, "") : "unfinished-test"))))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Rewrite a moved script's relative imports so they reach the same modules from its new place. */
|
|
56
|
+
export function rewriteImports(source: string, fromDir: string, toDir: string, moved: Map<string, string>): string {
|
|
57
|
+
const fix = (spec: string) => {
|
|
58
|
+
if (!spec.startsWith(".")) return spec
|
|
59
|
+
const target = path.resolve(fromDir, spec)
|
|
60
|
+
// Another file this job created, moved alongside: by its new name, in the same folder.
|
|
61
|
+
for (const [oldAbs, newAbs] of moved) {
|
|
62
|
+
const stem = oldAbs.replace(/\.(m|c)?[jt]sx?$/i, "")
|
|
63
|
+
if (target === oldAbs || target === stem) return `./${path.basename(target === oldAbs ? newAbs : newAbs.replace(/\.(m|c)?[jt]sx?$/i, ""))}`
|
|
64
|
+
}
|
|
65
|
+
let rel = posix(path.relative(toDir, target))
|
|
66
|
+
if (!rel.startsWith(".")) rel = `./${rel}`
|
|
67
|
+
return rel
|
|
68
|
+
}
|
|
69
|
+
return source
|
|
70
|
+
.replace(/(\bfrom\s*|\bimport\s*\(\s*|\brequire\s*\(\s*|^\s*import\s+)(['"])(\.{1,2}\/[^'"]*)\2/gm, (_m, lead: string, q: string, spec: string) => `${lead}${q}${fix(spec)}${q}`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Add testIgnore for the folder to the nearest Playwright config above it; false when there is none to change. */
|
|
74
|
+
export function ignoreInPlaywright(repo: string, dirAbs: string): string | null {
|
|
75
|
+
let dir = dirAbs
|
|
76
|
+
while (true) {
|
|
77
|
+
for (const name of ["playwright.config.ts", "playwright.config.js", "playwright.config.mjs", "playwright.config.cjs"]) {
|
|
78
|
+
const file = path.join(dir, name)
|
|
79
|
+
if (!existsSync(file)) continue
|
|
80
|
+
const text = readFileSync(file, "utf8")
|
|
81
|
+
if (text.includes(ATTENTION_DIR)) return null
|
|
82
|
+
const glob = `'**/${ATTENTION_DIR}/**'`
|
|
83
|
+
let next: string | null = null
|
|
84
|
+
if (/\btestIgnore\s*:\s*\[/.test(text)) next = text.replace(/\btestIgnore\s*:\s*\[/, (m) => `${m}${glob}, `)
|
|
85
|
+
else if (/\btestIgnore\s*:/.test(text)) next = null
|
|
86
|
+
else if (/defineConfig\(\s*\{/.test(text)) next = text.replace(/defineConfig\(\s*\{/, (m) => `${m}\n testIgnore: [${glob}],`)
|
|
87
|
+
else if (/module\.exports\s*=\s*\{/.test(text)) next = text.replace(/module\.exports\s*=\s*\{/, (m) => `${m}\n testIgnore: [${glob}],`)
|
|
88
|
+
if (!next) return null
|
|
89
|
+
writeFileSync(file, next)
|
|
90
|
+
return posix(path.relative(repo, file))
|
|
91
|
+
}
|
|
92
|
+
const up = path.dirname(dir)
|
|
93
|
+
if (up === dir || path.relative(repo, up).startsWith("..")) return null
|
|
94
|
+
dir = up
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Move what an unfinished job wrote into the needs-attention folder and write its README. Null outside git, where
|
|
100
|
+
* ForgeQE cannot tell the job's files from the user's, and when the job changed nothing.
|
|
101
|
+
*/
|
|
102
|
+
export function moveToAttention(input: AttentionInput): Attention | null {
|
|
103
|
+
const { repo, tree } = input
|
|
104
|
+
const changes = jobChanges(tree)
|
|
105
|
+
if (!changes?.length) return null
|
|
106
|
+
const status = new Map<string, string>()
|
|
107
|
+
const st = git(repo, ["status", "--porcelain=v1", "-z", "-uall", "--no-renames"])
|
|
108
|
+
for (const rec of st.stdout.split("\0")) if (rec.length > 3) status.set(rec.slice(3), rec.slice(0, 2))
|
|
109
|
+
const created = changes.filter((rel) => !tree.entries.has(rel) && status.get(rel) === "??" && existsSync(path.join(repo, rel)))
|
|
110
|
+
const edited = changes.filter((rel) => !tree.entries.has(rel) && status.has(rel) && status.get(rel) !== "??" && existsSync(path.join(repo, rel)))
|
|
111
|
+
const kept = changes.filter((rel) => tree.entries.has(rel) && existsSync(path.join(repo, rel)))
|
|
112
|
+
if (!created.length && !edited.length) return null
|
|
113
|
+
|
|
114
|
+
const relDir = attentionDir(repo, input.testFile && created.includes(input.testFile) ? input.testFile : created[0] || input.testFile, input.caseId)
|
|
115
|
+
const dirAbs = path.join(repo, relDir)
|
|
116
|
+
mkdirSync(dirAbs, { recursive: true })
|
|
117
|
+
|
|
118
|
+
// Edits to committed files: kept as a patch, then reverted.
|
|
119
|
+
let patch: string | undefined
|
|
120
|
+
if (edited.length) {
|
|
121
|
+
const diff = git(repo, ["diff", "--no-color", "--", ...edited]).stdout
|
|
122
|
+
if (diff.trim()) {
|
|
123
|
+
patch = posix(path.join(relDir, "changes.patch"))
|
|
124
|
+
writeFileSync(path.join(repo, patch), diff)
|
|
125
|
+
git(repo, ["checkout", "--", ...edited])
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Created files, flat in the folder, with a suffix when two share a name.
|
|
130
|
+
const moved = new Map<string, string>()
|
|
131
|
+
const used = new Set<string>()
|
|
132
|
+
for (const rel of created) {
|
|
133
|
+
let name = path.basename(rel)
|
|
134
|
+
for (let n = 2; used.has(name); n++) name = path.basename(rel).replace(/^([^.]*)/, `$1-${n}`)
|
|
135
|
+
used.add(name)
|
|
136
|
+
moved.set(path.join(repo, rel), path.join(dirAbs, name))
|
|
137
|
+
}
|
|
138
|
+
const files: string[] = []
|
|
139
|
+
let python = false
|
|
140
|
+
for (const [oldAbs, newAbs] of moved) {
|
|
141
|
+
let text: string | null = null
|
|
142
|
+
try {
|
|
143
|
+
text = statSync(oldAbs).size < 2_000_000 ? readFileSync(oldAbs, "utf8") : null
|
|
144
|
+
} catch {}
|
|
145
|
+
let target = newAbs
|
|
146
|
+
if (text !== null && SCRIPT.test(oldAbs)) {
|
|
147
|
+
text = rewriteImports(text, path.dirname(oldAbs), dirAbs, moved)
|
|
148
|
+
if (/\.tsx?$/i.test(oldAbs) && !text.startsWith("// @ts-nocheck")) text = `// @ts-nocheck: unfinished test, see README.md in this folder\n${text}`
|
|
149
|
+
}
|
|
150
|
+
if (COMPILED.test(oldAbs)) target = `${newAbs}.txt`
|
|
151
|
+
if (/\.py$/i.test(oldAbs)) python = true
|
|
152
|
+
if (text !== null) {
|
|
153
|
+
writeFileSync(target, text)
|
|
154
|
+
rmSync(oldAbs, { force: true })
|
|
155
|
+
} else {
|
|
156
|
+
renameSync(oldAbs, target)
|
|
157
|
+
}
|
|
158
|
+
files.push(posix(path.relative(repo, target)))
|
|
159
|
+
}
|
|
160
|
+
if (python) writeFileSync(path.join(dirAbs, "conftest.py"), "# Unfinished tests: pytest collects nothing here. See README.md.\ncollect_ignore_glob = [\"*\"]\n")
|
|
161
|
+
const ignored = [...moved.keys()].some((f) => SCRIPT.test(f)) ? ignoreInPlaywright(repo, dirAbs) : null
|
|
162
|
+
|
|
163
|
+
const readme = posix(path.join(relDir, "README.md"))
|
|
164
|
+
const original = [...moved.keys()].map((f) => posix(path.relative(repo, f)))
|
|
165
|
+
writeFileSync(
|
|
166
|
+
path.join(repo, readme),
|
|
167
|
+
[
|
|
168
|
+
`# Needs attention: ${input.caseId ? `${input.caseId} ` : ""}${input.title || input.testFile || "unfinished test"}`,
|
|
169
|
+
"",
|
|
170
|
+
input.url ? `Test case: ${input.url}` : "",
|
|
171
|
+
`Written by ForgeQE's coding agent on ${new Date().toISOString().slice(0, 10)}. It did not end with a passing, approved test, so it is kept here, out of the test runs, instead of among the tests.`,
|
|
172
|
+
"",
|
|
173
|
+
"## Why it stopped",
|
|
174
|
+
"",
|
|
175
|
+
input.why.trim(),
|
|
176
|
+
...(input.review && !input.review.approved
|
|
177
|
+
? ["", "The reviewer's last verdict:", "", input.review.summary, ...input.review.issues.map((i) => `- ${i.file ? `${i.file}: ` : ""}${i.problem}${i.fix ? ` Fix: ${i.fix}` : ""}`)]
|
|
178
|
+
: []),
|
|
179
|
+
...(input.output?.trim() ? ["", "The end of the last test run:", "", "```", input.output.trim().split("\n").slice(-30).join("\n"), "```"] : []),
|
|
180
|
+
"",
|
|
181
|
+
"## What is here",
|
|
182
|
+
"",
|
|
183
|
+
...files.map((f, i) => `- ${path.basename(f)} (was ${original[i]})`),
|
|
184
|
+
...(patch ? [`- changes.patch: the job's edits to existing files (${edited.join(", ")}), taken out of them; apply with git apply`] : []),
|
|
185
|
+
...(kept.length ? ["", `Also changed, and left in place because they had uncommitted changes before the job: ${kept.join(", ")}.`] : []),
|
|
186
|
+
"",
|
|
187
|
+
"## Picking it up",
|
|
188
|
+
"",
|
|
189
|
+
`1. Move the files back to where they were${patch ? " and apply changes.patch (git apply)" : ""}.`,
|
|
190
|
+
`2. Fix what is described above, then run it${input.command ? `: ${input.command}` : "."}`,
|
|
191
|
+
"3. Or ask ForgeQE to finish it, naming this folder.",
|
|
192
|
+
"",
|
|
193
|
+
`This folder is kept out of test runs${ignored ? ` (testIgnore in ${ignored})` : python ? " (conftest.py)" : ""}${files.some((f) => f.endsWith(".txt")) ? "; compiled files have a .txt suffix so the project does not build them" : ""}.`,
|
|
194
|
+
]
|
|
195
|
+
.filter((l, i, all) => l !== "" || all[i - 1] !== "")
|
|
196
|
+
.join("\n") + "\n",
|
|
197
|
+
)
|
|
198
|
+
writeIndex(path.dirname(dirAbs))
|
|
199
|
+
return { dir: relDir, files, ...(patch ? { patch } : {}), readme }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** needs-attention/README.md: one line per unfinished test, from each subfolder's README title. */
|
|
203
|
+
function writeIndex(rootAbs: string) {
|
|
204
|
+
const entries = readdirSync(rootAbs, { withFileTypes: true })
|
|
205
|
+
.filter((e) => e.isDirectory() && existsSync(path.join(rootAbs, e.name, "README.md")))
|
|
206
|
+
.map((e) => {
|
|
207
|
+
const title = readFileSync(path.join(rootAbs, e.name, "README.md"), "utf8").split("\n")[0].replace(/^#\s*Needs attention:\s*/, "")
|
|
208
|
+
return `- [${e.name}](${e.name}/README.md): ${title}`
|
|
209
|
+
})
|
|
210
|
+
writeFileSync(
|
|
211
|
+
path.join(rootAbs, "README.md"),
|
|
212
|
+
["# Tests that need attention", "", "ForgeQE's coding agent wrote these but did not finish them: each folder says why and how to pick it up. They are kept out of test runs.", "", ...entries, ""].join("\n"),
|
|
213
|
+
)
|
|
214
|
+
}
|