task/34: Testkit self-serve plan + report endpoints
## 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 <BASE>/api/testkit/plan — fetch plan before starting
2. curl -s <BASE>/api/testkit | bash — run suite after implementing
3. curl <BASE>/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
This commit is contained in:
24
AGENTS.md
24
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
|
- Open a PR on Gitea and squash-merge after review
|
||||||
- CI runs `pnpm typecheck && pnpm lint` on every PR automatically
|
- 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 <BASE>/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 <BASE>/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 <BASE>/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 `<BASE>` is the running server URL, e.g. `http://localhost:8080` locally or the Replit dev URL in CI.
|
||||||
|
|
||||||
## Stub mode
|
## Stub mode
|
||||||
|
|
||||||
The API server starts without Lightning or AI credentials:
|
The API server starts without Lightning or AI credentials:
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { Router, type Request, type Response } from "express";
|
import { Router, type Request, type Response } from "express";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import { resolve, dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -757,4 +760,50 @@ if [[ "\$FAIL" -gt 0 ]]; then exit 1; fi
|
|||||||
res.send(script);
|
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;
|
export default router;
|
||||||
|
|||||||
Reference in New Issue
Block a user