From 69a0c4fd932a1e5f592f41212cd6b9a8bf18a41a Mon Sep 17 00:00:00 2001 From: Suleiman Shahbari Date: Wed, 15 Jul 2026 22:05:22 +0300 Subject: [PATCH] refactor(framework): move all prompting into prompts/**/*.md (#551) Every agent-facing prompt now lives as markdown under packages/framework/prompts/: the system prompt (#326), the two emit protocols, and the five preset prompts. No prompt text is written in TypeScript any more. The text is unchanged. Verified byte-identical against the previous build for all eight constants and for the composed run system channel (3874 chars). scripts/gen-prompts.mjs compiles prompts/**/*.md into src/prompts.generated.ts (git-ignored), which the code imports. Generated rather than read from disk with node:fs like @gemstack/ai-autopilot does, because the system prompt and presets are reachable from src/client.ts, which the dashboard imports in the browser to show the prompt before a run (#520). A node:fs edge there breaks the browser bundle and client.test.ts fails the build over it. A module of plain strings crosses that boundary for free and keeps the package files: ["dist"]. --- .gitignore | 3 + packages/framework/package.json | 11 +-- packages/framework/prompts/README.md | 34 ++++++++++ .../prompts/presets/maintainability.md | 2 + .../framework/prompts/presets/readability.md | 16 +++++ .../framework/prompts/presets/research.md | 13 ++++ .../prompts/presets/security_audit.md | 5 ++ packages/framework/prompts/presets/ux.md | 6 ++ packages/framework/prompts/protocols/await.md | 24 +++++++ .../framework/prompts/protocols/signal.md | 11 +++ packages/framework/prompts/system_prompt.md | 36 ++++++++++ packages/framework/scripts/gen-prompts.mjs | 67 +++++++++++++++++++ .../framework/src/maintainability-preset.ts | 6 +- packages/framework/src/readability-preset.ts | 18 +---- packages/framework/src/research-preset.ts | 15 +---- .../framework/src/security-audit-preset.ts | 7 +- packages/framework/src/system-prompt.ts | 42 ++---------- packages/framework/src/turn-gate.ts | 47 ++----------- packages/framework/src/ux-preset.ts | 8 +-- 19 files changed, 244 insertions(+), 127 deletions(-) create mode 100644 packages/framework/prompts/README.md create mode 100644 packages/framework/prompts/presets/maintainability.md create mode 100644 packages/framework/prompts/presets/readability.md create mode 100644 packages/framework/prompts/presets/research.md create mode 100644 packages/framework/prompts/presets/security_audit.md create mode 100644 packages/framework/prompts/presets/ux.md create mode 100644 packages/framework/prompts/protocols/await.md create mode 100644 packages/framework/prompts/protocols/signal.md create mode 100644 packages/framework/prompts/system_prompt.md create mode 100644 packages/framework/scripts/gen-prompts.mjs diff --git a/.gitignore b/.gitignore index 7f9f69e..7717a5e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ test-results/ # VitePress dev cache (build output goes to .vitepress/dist, covered by dist/) docs/.vitepress/cache/ + +# Generated from packages/framework/prompts/**/*.md by its gen-prompts script (#551). +packages/framework/src/prompts.generated.ts diff --git a/packages/framework/package.json b/packages/framework/package.json index 6d5fddc..79872b6 100644 --- a/packages/framework/package.json +++ b/packages/framework/package.json @@ -50,12 +50,13 @@ } }, "scripts": { - "build": "tsc -p tsconfig.build.json", - "dev": "tsc -p tsconfig.build.json --watch", - "typecheck": "tsc --noEmit", - "test": "tsc -p tsconfig.test.json && cd dist-test && node --test", + "build": "node scripts/gen-prompts.mjs && tsc -p tsconfig.build.json", + "dev": "node scripts/gen-prompts.mjs && tsc -p tsconfig.build.json --watch", + "typecheck": "node scripts/gen-prompts.mjs && tsc --noEmit", + "test": "node scripts/gen-prompts.mjs && tsc -p tsconfig.test.json && cd dist-test && node --test", "bundle:dashboard": "node scripts/bundle-dashboard.mjs", - "clean": "rm -rf dist dist-test" + "clean": "rm -rf dist dist-test src/prompts.generated.ts", + "gen:prompts": "node scripts/gen-prompts.mjs" }, "dependencies": { "@gemstack/ai-autopilot": "workspace:^", diff --git a/packages/framework/prompts/README.md b/packages/framework/prompts/README.md new file mode 100644 index 0000000..cd8cfc1 --- /dev/null +++ b/packages/framework/prompts/README.md @@ -0,0 +1,34 @@ +# Prompts + +Every prompt the framework sends an agent lives here as markdown (#551). Nothing agent-facing +is written in TypeScript any more, so prompting can change without touching the code. + +| file | what it is | +|---|---| +| `system_prompt.md` | The built-in system prompt (#326). Rom's doc. | +| `protocols/await.md` | How to emit an awaited choice so the turn-boundary gate can detect it (#337/#339). | +| `protocols/signal.md` | How to emit `setSessionName()` / `setReadyForMerge()` (#326). | +| `presets/*.md` | One file per preset button: research (#331), readability (#360), maintainability (#361), security_audit (#461), ux (#472). | + +## Editing + +Edit the markdown, then `pnpm build`. `scripts/gen-prompts.mjs` compiles this directory into +`src/prompts.generated.ts` (git-ignored, rebuilt by `build` / `test` / `typecheck`), which is +what the code imports. The markdown is the only source of truth. + +Adding `foo/bar.md` exports `FOO_BAR`. A file's exact bytes become the string, minus one +trailing newline. + +## Why generated instead of read from disk + +`@gemstack/ai-autopilot` reads its `prompts/*.md` with `node:fs` at run time. That does not work +here: the system prompt and the presets are reachable from `src/client.ts`, which the dashboard +imports **in the browser** to show the user the prompt before a run (#520). A `node:fs` edge +there breaks the browser bundle, and `client.test.ts` fails the build over it. Generating a +module of plain strings crosses that boundary for free and keeps the package `files: ["dist"]`. + +## Two rules + +- **The system prompt is Rom's** (#500/#547). Change it on #326 first, then sync the markdown. +- **Prompts get a review round before they land in production** (#547). The point of this + directory is that a prompt change is a readable markdown diff. diff --git a/packages/framework/prompts/presets/maintainability.md b/packages/framework/prompts/presets/maintainability.md new file mode 100644 index 0000000..64584b4 --- /dev/null +++ b/packages/framework/prompts/presets/maintainability.md @@ -0,0 +1,2 @@ +Refactor to make it as maintainable as possible: +- Look for maintainability red flags, and fix them. diff --git a/packages/framework/prompts/presets/readability.md b/packages/framework/prompts/presets/readability.md new file mode 100644 index 0000000..b2e5ef0 --- /dev/null +++ b/packages/framework/prompts/presets/readability.md @@ -0,0 +1,16 @@ +Refactor to make it as easy as possible for humans to read: +- Pinnacle architectural split + - Does each file and each represent a sensible and natural abstraction? + - Rate the *seams*, not just the boxes: for each call site, ask whether the responsibility sits on the right side of the boundary — should a caller's wrapper move down into the callee (or vice versa)? A can be clean, DRY and well-tested in isolation yet still be in the wrong place. "Well-factored" is not "well-located". +- Linearity (for humans) + - Put yourself in the shoes of a human reader who reads everything in a linear fashion + - Top to bottom: place callers above callees so readers encounter high-level logic before implementation details (humans think high-level first) + - Altitude pass: for each entry-point / orchestration , read it top-to-bottom as prose. Flag any line that drops the reader into lower-level mechanism (a flag, a thunk, a log verb, error plumbing) in the middle of what should be a high-level narrative. For each, ask: can that mechanism move down into the callee so the caller reads at one consistent altitude? Prioritize the reading path of all the a reader hits first. +- Before starting to work: list *ALL* files and *ALL* in this chat, rate them all (0: convoluted abstraction, hard to read, wrong place — 10: perfect), and give a reason for your rating + - DON'T skip any file nor any in your rating list — write an extra separated list in this chat of all files and all and put a ✅ tick to each entry to double check whether you forgot to rate something. So two lists: one list of ratings & explanation, and a second confirmation list. +- Separate commit for each refactor +- Work until it's exceptionally good. We as an expert team will check against every little detail. + - If we see that you gave mostly a 10/10 rating, that's a sign you've been lazy... so make sure you scrutinize everything and spend a substantial amount of time. We don't want to prompt you again and again to achieve quality — autonomously strive for quality on your own without us pushing you. +- Give summary of what you worked on: print the lists again with old rating => new rating with link to commit(s) + +FUNCTION: an actual function or a class, procedure, etc. (anything that represents a unit of logic) diff --git a/packages/framework/prompts/presets/research.md b/packages/framework/prompts/presets/research.md new file mode 100644 index 0000000..0aa11d0 --- /dev/null +++ b/packages/framework/prompts/presets/research.md @@ -0,0 +1,13 @@ +Measure "problem variability" of +- List all high-level flows the code implements, i.e. the list of all "problems" the code solves +- Give a rating for each problem (from 0 to 10) following this criteria: does the code solves the problem in an obviously optimal way (10), or is it highly unclear whether the problem can be solved in a better way (0)? +- Write down the ratings in a new file +- Show the list to the user and enable him to select problems via `showMultiSelect()`, + - Set default to `true` for entries with low rating +- For all problems the user selected, add a new entry to + - The entry: "Deep-dive research for alternative solutions, see " + +AWAIT: Stop, await user answer before resuming +REVIEW_FILE: `REVIEW-PROBLEMS_.agent.md` +TODO_FILE: `TODO_.agent.md` +SESSION_NAME: the name of the current Git branch — sanitize it to be a SLUG, if name is generic (e.g. `main`) then create a succinct SLUG diff --git a/packages/framework/prompts/presets/security_audit.md b/packages/framework/prompts/presets/security_audit.md new file mode 100644 index 0000000..5cfc918 --- /dev/null +++ b/packages/framework/prompts/presets/security_audit.md @@ -0,0 +1,5 @@ +Security audit +- Scrutinize the entire code for potential security issues +- Make it exhaustive (100% coverage) +- List every aspect you considered and, for each aspect, include a verdict as well as an explanation for non-obvious verdicts +- If you see any security issue, fix each security issue in a separate commit diff --git a/packages/framework/prompts/presets/ux.md b/packages/framework/prompts/presets/ux.md new file mode 100644 index 0000000..e70dee6 --- /dev/null +++ b/packages/framework/prompts/presets/ux.md @@ -0,0 +1,6 @@ +Thoroughly review UX of and make proposals to improve. Focus your review from a usability perspective: is using the UI and all the functionalities a nice user experience? +1. Enumerate *all* your findings (in a sensible order and categorized), with reference numbers (so that it's easy to reference each point in follow up conversations), and make it a list of choices shown via `showChoices()` +2. +3. Work on all accepted proposals + +AWAIT: Stop, await user answer before resuming diff --git a/packages/framework/prompts/protocols/await.md b/packages/framework/prompts/protocols/await.md new file mode 100644 index 0000000..08154ad --- /dev/null +++ b/packages/framework/prompts/protocols/await.md @@ -0,0 +1,24 @@ +## Awaiting a choice +When these instructions tell you to showChoices() / showMultiSelect() / showMarkdown() and then AWAIT, do not decide for the user. +End your turn with one fenced code block, then stop. +For a single choice (showChoices, pick one), tag it `await-choices`: +```await-choices +{ "title": "", "options": [{ "label": "