From 56eb7bc56e1468f50cf02e63e7e284f82366839f Mon Sep 17 00:00:00 2001
From: alexpaynex <55271826-alexpaynex@users.noreply.replit.com>
Date: Thu, 19 Mar 2026 21:02:43 +0000
Subject: [PATCH] task/34: Testkit self-serve plan + report endpoints
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Routes added to artifacts/api-server/src/routes/testkit.ts
### GET /api/testkit/plan
- Returns TIMMY_TEST_PLAN.md verbatim as text/markdown; charset=utf-8
- Reads file at request time (not on startup) so edits to the plan are picked
up without server restart
- Path resolves via import.meta.url + dirname() → 4 levels up to project root
(handles both dev/tsx and compiled dist/routes/ directories)
### GET /api/testkit/report
- Returns only the content from "## Report template" heading to end-of-file
- Content-Type: text/plain; charset=utf-8 — ready to copy and fill in
- Slice is found with indexOf("## Report template"); 500 if marker absent
- Uses the same PLAN_PATH as /api/testkit/plan (single source of truth)
## Deviation: __dirname → import.meta.url
Original plan said "resolve relative to project root regardless of cwd".
The codebase runs as ESM (tsx / ts-node with ESM), so __dirname is not
defined. Fixed by using dirname(fileURLToPath(import.meta.url)) instead —
equivalent semantics, correct in both dev and compiled output.
## AGENTS.md — Testing section added
Three-step workflow documented between "Branch and PR conventions" and
"Stub mode" sections:
1. curl /api/testkit/plan — fetch plan before starting
2. curl -s /api/testkit | bash — run suite after implementing
3. curl /api/testkit/report — fetch report template to fill in
## Unchanged
- GET /api/testkit bash script generation: untouched
- No new test cases or script modifications
## TypeScript: 0 errors. Smoke tests all pass:
- /api/testkit/plan → 200 text/markdown, full TIMMY_TEST_PLAN.md content
- /api/testkit/report → 200 text/plain, starts at "## Report template"
- /api/testkit → 200 bash script, unchanged
---
AGENTS.md | 24 +++++++++++
artifacts/api-server/src/routes/testkit.ts | 49 ++++++++++++++++++++++
2 files changed, 73 insertions(+)
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;