diff --git a/AGENTS.md b/AGENTS.md index 20fb44d..15bdfa5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,6 +39,30 @@ Set `GITEA_TOKEN` or write the token to `.gitea-credentials` (gitignored). Never - Open a PR on Gitea and squash-merge after review - CI runs `pnpm typecheck && pnpm lint` on every PR automatically +## Testing + +Executor agents should follow this three-step workflow when implementing or verifying any change: + +**1. Fetch the test plan before starting** +```bash +curl /api/testkit/plan +``` +Returns `TIMMY_TEST_PLAN.md` — full architecture notes, route descriptions, and expected behaviour for all 24 tests. Read this first so you understand what each endpoint is supposed to do before touching the code. + +**2. Run the full test suite after implementing** +```bash +curl -s /api/testkit | bash +``` +The server returns a self-contained bash script with the base URL already baked in. Requirements: `curl`, `bash`, `jq` — nothing else. All 24 tests must pass (FAIL=0) before submitting. + +**3. Fill in and submit the report** +```bash +curl /api/testkit/report +``` +Returns just the report template section ready to copy and fill in. Attach the completed report to your PR or task output. + +Where `` is the running server URL, e.g. `http://localhost:8080` locally or the Replit dev URL in CI. + ## Stub mode The API server starts without Lightning or AI credentials: diff --git a/artifacts/api-server/src/routes/testkit.ts b/artifacts/api-server/src/routes/testkit.ts index 6c2a6ee..546c81c 100644 --- a/artifacts/api-server/src/routes/testkit.ts +++ b/artifacts/api-server/src/routes/testkit.ts @@ -1,4 +1,7 @@ import { Router, type Request, type Response } from "express"; +import { readFileSync } from "fs"; +import { resolve, dirname } from "path"; +import { fileURLToPath } from "url"; const router = Router(); @@ -757,4 +760,50 @@ if [[ "\$FAIL" -gt 0 ]]; then exit 1; fi res.send(script); }); +/** + * GET /api/testkit/plan + * + * Returns TIMMY_TEST_PLAN.md verbatim as text/markdown. + * Path resolves from the project root regardless of cwd. + */ +const _dirname = dirname(fileURLToPath(import.meta.url)); +const PLAN_PATH = resolve(_dirname, "../../../../TIMMY_TEST_PLAN.md"); + +router.get("/testkit/plan", (_req: Request, res: Response) => { + let content: string; + try { + content = readFileSync(PLAN_PATH, "utf-8"); + } catch { + res.status(500).json({ error: "TIMMY_TEST_PLAN.md not found on server" }); + return; + } + res.setHeader("Content-Type", "text/markdown; charset=utf-8"); + res.send(content); +}); + +/** + * GET /api/testkit/report + * + * Returns only the report template section from TIMMY_TEST_PLAN.md — + * everything from the "## Report template" heading to end-of-file. + * Returned as text/plain so agents can copy and fill in directly. + */ +router.get("/testkit/report", (_req: Request, res: Response) => { + let content: string; + try { + content = readFileSync(PLAN_PATH, "utf-8"); + } catch { + res.status(500).json({ error: "TIMMY_TEST_PLAN.md not found on server" }); + return; + } + const marker = "## Report template"; + const idx = content.indexOf(marker); + if (idx === -1) { + res.status(500).json({ error: "Report template section not found in plan" }); + return; + } + res.setHeader("Content-Type", "text/plain; charset=utf-8"); + res.send(content.slice(idx)); +}); + export default router;