{"id":172479,"date":"2025-10-01T09:33:12","date_gmt":"2025-10-01T09:33:12","guid":{"rendered":"https:\/\/www.newsbeep.com\/uk\/172479\/"},"modified":"2025-10-01T09:33:12","modified_gmt":"2025-10-01T09:33:12","slug":"spec-driven-development-using-markdown-as-a-programming-language-when-building-with-ai","status":"publish","type":"post","link":"https:\/\/www.newsbeep.com\/uk\/172479\/","title":{"rendered":"Spec-driven development: Using Markdown as a programming language when building with AI"},"content":{"rendered":"<p>The usual workflow with AI coding agents like GitHub Copilot is simple: \u201cWrite app A that does X.\u201c You start with that seed, then iterate: \u201cAdd feature Y,\u201d \u201cFix bug Z.\u201c This works, at least until the agent loses track of your app\u2019s purpose or past decisions.\u00a0<\/p>\n<p>If you\u2019re new to AI coding agents, the change is subtle. Suddenly, the agent asks you to repeat things you\u2019ve already explained, or suggests changes that ignore your previous instructions. Sometimes, it forgets why a feature exists, or proposes solutions that contradict earlier choices.<\/p>\n<p>Some AI coding agents try to address this by supporting custom instructions files. For example, GitHub Copilot <a href=\"https:\/\/docs.github.com\/en\/copilot\/how-tos\/configure-custom-instructions\/add-repository-instructions?tool=vscode\" rel=\"nofollow noopener\" target=\"_blank\">supports<\/a> copilot-instructions.md. You can put your app\u2019s purpose and design decisions in this Markdown file, and GitHub Copilot will read it every time it generates code.<\/p>\n<p>When I\u2019m in a coding rush, I often forget to update copilot-instructions.md after asking GitHub Copilot to do things. It feels redundant to put the same information into both the chat prompt and the instructions file.<\/p>\n<p>Try Spec Kit: Spec-driven development made practical<\/p>\n<p>Spec Kit provides a structured process to bring spec-driven development to your coding agent workflows with tools including GitHub Copilot, Claude Code, and Gemini CLI. Plus, it\u2019s open source (and we\u2019re updating it constantly).\u00a0<\/p>\n<p>\ud83d\udc49<a href=\"https:\/\/github.blog\/ai-and-ml\/generative-ai\/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit\/\" rel=\"nofollow noopener\" target=\"_blank\"> Get started with Spec Kit on GitHub<\/a><\/p>\n<p>Which made me wonder: What if I \u201cwrote\u201d the entire app in the Markdown instructions file?<\/p>\n<p>For my latest pet project \u2014 <a href=\"https:\/\/github.com\/wham\/github-brain\" rel=\"nofollow noopener\" target=\"_blank\">GitHub Brain MCP Server<\/a> \u2014 I tried exactly that by writing the app code in Markdown and letting GitHub Copilot compile it into actual Go code. As a result, I rarely edit or view the app\u2019s Go code directly.\u00a0<\/p>\n<p>This process should work with any AI coding agent and programming language, though I\u2019ll use VS Code, GitHub Copilot, and Go as examples. <a href=\"https:\/\/github.com\/wham\/github-brain\" rel=\"nofollow noopener\" target=\"_blank\">GitHub Brain MCP Server<\/a> will be my example app throughout this post.<\/p>\n<p>Let\u2019s jump in.\u00a0<\/p>\n<p>Setup: What I used to get started<\/p>\n<p>There are four key files:<\/p>\n<p>.<br \/>\n\u251c\u2500\u2500 .github\/<br \/>\n\u2502   \u2514\u2500\u2500 prompts\/<br \/>\n\u2502       \u2514\u2500\u2500 compile.prompt.md<br \/>\n\u251c\u2500\u2500 main.go<br \/>\n\u251c\u2500\u2500 main.md<br \/>\n\u2514\u2500\u2500 README.md<\/p>\n<p>At a high level, I edit README.md or main.md to develop the app, invoke compile.prompt.md to let the AI coding agent generate main.go, then build and run main.go like any other Go app. Next, I\u2019ll break down each file and the workflow.<\/p>\n<p>README.md: User-facing documentation<\/p>\n<p>The example app, <a href=\"https:\/\/github.com\/wham\/github-brain\" rel=\"nofollow noopener\" target=\"_blank\">GitHub Brain MCP Server<\/a>, is a command-line tool. Its README.md provides clear, user-facing instructions for installation and usage. If you write libraries, this file should contain API documentation. Below is a condensed excerpt from the example app\u2019s <a href=\"https:\/\/github.com\/wham\/github-brain\/blob\/main\/README.md\" rel=\"nofollow noopener\" target=\"_blank\">README.md<\/a>:<\/p>\n<p># GitHub Brain MCP Server<\/p>\n<p>**GitHub Brain** is an experimental MCP server for summarizing GitHub discussions, issues, and pull requests.<\/p>\n<p>## Usage<\/p>\n<p>&#8220;`sh<br \/>\ngo run main.go  []<br \/>\n&#8220;`<\/p>\n<p>**Workflow:**<\/p>\n<p>1. Populate the local database with the `pull` command.<br \/>\n2. Start the MCP server with the `mcp` command.<\/p>\n<p>### `pull`<\/p>\n<p>Populate the local database with GitHub data.<\/p>\n<p>Example:<\/p>\n<p>&#8220;`sh<br \/>\ngo run main.go pull -o my-org<br \/>\n&#8220;`<\/p>\n<p>Arguments:<\/p>\n<p>&#8211; `-t`: Your GitHub personal access token. **Required.**<br \/>\n&#8211; `-o`: The GitHub organization to pull data from. **Required.**<br \/>\n&#8211; `-db`: Path to the SQLite database directory. Default: `db` folder in the current directory.<\/p>\n<p>### `mcp`<\/p>\n<p>Start the MCP server using the local database.<\/p>\n<p>&#8230;README.md continues&#8230;<\/p>\n<p>Nothing special here\u200a, \u200ajust regular documentation. But it gets interesting when this file is included in main.md.<\/p>\n<p>main.md: AI coding agent specification<\/p>\n<p>main.md is the actual source code of the app: the Markdown instructions file. Whenever I need to add features or fix bugs, I edit this file. Here\u2019s the opening of the example app\u2019s <a href=\"https:\/\/github.com\/wham\/github-brain\/blob\/main\/main.md\" rel=\"nofollow noopener\" target=\"_blank\">main.md<\/a>:<\/p>\n<p># GitHub Brain MCP Server<\/p>\n<p>AI coding agent specification. User-facing documentation in [README.md](README.md).<\/p>\n<p>## CLI<\/p>\n<p>Implement CLI from [Usage](README.md#usage) section. Follow exact argument\/variable names. Support only `pull` and `mcp` commands.<\/p>\n<p>## pull<\/p>\n<p>&#8211; Resolve CLI arguments and environment variables into `Config` struct:<br \/>\n  &#8211; `Organization`: Organization name (required)<br \/>\n  &#8211; `GithubToken`: GitHub API token (required)<br \/>\n  &#8211; `DBDir`: SQLite database path (default: `.\/db`)<br \/>\n&#8211; Use `Config` struct consistently, avoid multiple environment variable reads<br \/>\n&#8211; Pull items: Repositories, Discussions, Issues, Pull Requests, Teams<br \/>\n&#8211; Use `log\/slog` custom logger for last 5 log messages with timestamps in console output<\/p>\n<p>&#8230;main.md continues&#8230;<\/p>\n<p>Notice how the user-facing documentation from README.md is embedded in the specification. This keeps documentation and implementation in sync. If I want to add an alias for the -o argument, I just update <a href=\"http:\/\/readme.md\" rel=\"nofollow noopener\" target=\"_blank\">README.md<\/a> with no extra steps required.<\/p>\n<p>Here\u2019s another snippet from the example app\u2019s <a href=\"https:\/\/github.com\/wham\/github-brain\/blob\/main\/main.md\" rel=\"nofollow noopener\" target=\"_blank\">main.md<\/a>:<\/p>\n<p>### Discussions<\/p>\n<p>&#8211; Query discussions for each repository with `has_discussions_enabled: true`<br \/>\n&#8211; Record most recent repository discussion `updated_at` timestamp from database before pulling first page<\/p>\n<p>&#8220;`graphql<br \/>\n{<br \/>\n  repository(owner: &#8220;&#8221;, name: &#8220;&#8221;) {<br \/>\n    discussions(first: 100, orderBy: { field: UPDATED_AT, direction: DESC }) {<br \/>\n      nodes {<br \/>\n        url<br \/>\n        title<br \/>\n        body<br \/>\n        createdAt<br \/>\n        updatedAt<br \/>\n        author {<br \/>\n          login<br \/>\n        }<br \/>\n      }<br \/>\n    }<br \/>\n  }<br \/>\n}<br \/>\n&#8220;`<\/p>\n<p>&#8211; If repository doesn&#8217;t exist, remove the repository, and all associated items from the database and continue<br \/>\n&#8211; Query discussions ordered by most recent `updatedAt`<br \/>\n&#8211; Stop pulling when hitting discussions with `updatedAt` older than recorded timestamp<br \/>\n&#8211; Save or update by primary key `url`<br \/>\n&#8211; Preserve the discussion markdown body<\/p>\n<p>&#8230;main.md continues&#8230;<\/p>\n<p>This is effectively programming in Markdown and plain English: storing variables, loops, and logical conditions. You get all the usual keywords \u2014 if, foreach, or continue. It\u2019s a blend of structural and declarative styles, with Markdown links []() for imports.<\/p>\n<p>The database schema is also coded in Markdown:<\/p>\n<p>## Database<\/p>\n<p>SQLite database in `{Config.DbDir}\/{Config.Organization}.db` (create folder if needed). Avoid transactions. Save each GraphQL item immediately.<\/p>\n<p>### Tables<\/p>\n<p>#### table:repositories<\/p>\n<p>&#8211; Primary key: `name`<br \/>\n&#8211; Index: `updated_at`<\/p>\n<p>&#8211; `name`: Repository name (e.g., `repo`), without organization prefix<br \/>\n&#8211; `has_discussions_enabled`: Boolean indicating if the repository has discussions feature enabled<br \/>\n&#8211; `has_issues_enabled`: Boolean indicating if the repository has issues feature enabled<br \/>\n&#8211; `updated_at`: Last update timestamp<\/p>\n<p>&#8230;main.md continues&#8230;<\/p>\n<p>compile.prompt.md: AI coding agent prompt<\/p>\n<p>compile.prompt.md uses GitHub Copilot\u2019s <a href=\"https:\/\/docs.github.com\/en\/copilot\/tutorials\/customization-library\/prompt-files\/your-first-prompt-file\" rel=\"nofollow noopener\" target=\"_blank\">prompt file<\/a> format. This repeatable prompt tells the agent to compile main.md into main.go. Here\u2019s <a href=\"https:\/\/github.com\/wham\/github-brain\/blob\/main\/.github\/prompts\/compile.prompt.md\" rel=\"nofollow noopener\" target=\"_blank\">compile.prompt.md<\/a> from the example app:<\/p>\n<p>&#8212;<br \/>\nmode: agent<br \/>\n&#8212;<\/p>\n<p>&#8211; Update the app to follow [the specification](..\/..\/main.md)<br \/>\n&#8211; Build the code with the VS Code tasks. Avoid asking me to run `go build` or `go test` commands manually.<br \/>\n&#8211; Fetch the GitHub home page for each used library to get a documentation and examples.<\/p>\n<p>I keep this prompt simple\u200a. \u200aThe real information is in <a href=\"http:\/\/main.md\" rel=\"nofollow noopener\" target=\"_blank\">main.md<\/a>, after all. This example uses GitHub Copilot\u2019s format, but keeping it simple makes it portable to other AI coding agents.<\/p>\n<p>The workflow to bring this all together<\/p>\n<p>The development loop is straightforward:<\/p>\n<p>Edit the specification in main.md or README.md.<\/p>\n<p>Ask the AI coding agent to compile it into Go code.<\/p>\n<p>Run and test the app. Update the spec if something doesn\u2019t work as expected.<\/p>\n<p>Repeat.<\/p>\n<p>In GitHub Copilot for VS Code, use the \/ command to invoke the prompt.<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" height=\"516\" width=\"1024\" src=\"https:\/\/www.newsbeep.com\/uk\/wp-content\/uploads\/2025\/10\/image-2.png\" alt=\"Screenshot showing the use of the \/ command in GitHub Copilot for VS Code to invoke the AI coding agent prompt.\" class=\"wp-image-91221\"  \/><\/p>\n<p>For smaller specs, GitHub Copilot usually catches changes automatically. As the spec grows, I nudge it in the right direction by appending \u201dfocus on \u201d.<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" width=\"1770\" height=\"2836\" loading=\"lazy\" src=\"https:\/\/www.newsbeep.com\/uk\/wp-content\/uploads\/2025\/10\/image-2_87448b.png\" alt=\"Screenshot demonstrating how to prompt GitHub Copilot in VS Code to focus on a specific change using the \/ command.\" class=\"wp-image-91245\"  \/><\/p>\n<p>Coding<\/p>\n<p>Coding in main.md is sometimes harder than writing Go directly\u200a. You have to clearly describe what you want, which might be the hardest part of software development \ud83d\ude05. Fortunately, you can use GitHub Copilot to help with this, just like you probably do with your Go code daily.<\/p>\n<p>Here we ask it to add pagination to all MCP tools in main.md. Copilot not only saves us from doing repetitive work, but it also recommends proper pagination style and parameter names.<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" width=\"1760\" height=\"2672\" loading=\"lazy\" src=\"https:\/\/www.newsbeep.com\/uk\/wp-content\/uploads\/2025\/10\/image-4.png\" alt=\"Screenshot showing GitHub Copilot in VS Code recommending pagination style and parameter names for MCP tools in the Markdown specification.\" class=\"wp-image-91247\"  \/><\/p>\n<p>Linting<\/p>\n<p>main.md can get messy like any code. To help with this, you can ask Copilot to clean it up. Here\u2019s <a href=\"https:\/\/github.com\/wham\/github-brain\/blob\/main\/.github\/prompts\/lint.prompt.md\" rel=\"nofollow noopener\" target=\"_blank\">lint.prompt.md<\/a> from the example app:<\/p>\n<p>&#8212;<br \/>\nmode: agent<br \/>\n&#8212;<\/p>\n<p>&#8211; Optimize [the app specification](..\/..\/main.md) for clarity and conciseness<br \/>\n&#8211; Treat the english language as a programming language<br \/>\n&#8211; Minimize the number of synonyms &#8211; i.e. pull\/get\/fetch. Stick to one term.<br \/>\n&#8211; Remove duplicate content<br \/>\n&#8211; Preserve all important details<br \/>\n&#8211; Do not modify the Go code with this. Only optimize the Markdown file.<br \/>\n&#8211; Do not modify this prompt itself.<\/p>\n<p>Like with compile.prompt.md, I use the \/ command to invoke this prompt. The AI coding agent lints main.md, and if the result looks good, I can compile it to Go with compile.prompt.md.<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" width=\"1742\" height=\"2416\" loading=\"lazy\" src=\"https:\/\/www.newsbeep.com\/uk\/wp-content\/uploads\/2025\/10\/image-3_bc6205.png\" alt=\"Screenshot of GitHub Copilot in VS Code cleaning up and linting the Markdown specification for improved clarity and conciseness.\" class=\"wp-image-91246\"  \/><\/p>\n<p>Closing thoughts<\/p>\n<p>After a few months using this workflow, here are my observations:<\/p>\n<p>It works! And it gets better with each agentic update to Copilot.<\/p>\n<p>Compilation slows down as main.go grows. Something I want to work on next is modifying the spec to break compiled code into multiple modules \u2014 by adding \u201cBreak each ## section into its own code module.\u201d<\/p>\n<p>Testing? I haven\u2019t tried adding tests yet. But even with spec-driven workflows, testing remains essential. The spec may describe intended behavior, but tests verify it.<\/p>\n<p>Something else I want to try next? Discarding all Go code and regenerating the app from scratch in another language. Will the new code work right away?<\/p>\n<p>The rapid advances in this field are really encouraging, and I hope my experimental workflows give you some practical ideas to try.\u00a0<\/p>\n<p>\t\tWritten by\t<\/p>\n<p>\t\t\t\t\t<img class=\"d-block circle\" src=\"https:\/\/www.newsbeep.com\/uk\/wp-content\/uploads\/2025\/10\/448809\" alt=\"Tomas Vesely\" width=\"80\" height=\"80\" loading=\"lazy\" decoding=\"async\"\/><\/p>\n<p>Engineering manager on the GitHub Licensing team.<\/p>\n","protected":false},"excerpt":{"rendered":"The usual workflow with AI coding agents like GitHub Copilot is simple: \u201cWrite app A that does X.\u201c&hellip;\n","protected":false},"author":2,"featured_media":172480,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20],"tags":[554,733,4308,86,56,54,55],"class_list":["post-172479","post","type-post","status-publish","format-standard","has-post-thumbnail","category-artificial-intelligence","tag-ai","tag-artificial-intelligence","tag-artificialintelligence","tag-technology","tag-uk","tag-united-kingdom","tag-unitedkingdom"],"_links":{"self":[{"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/posts\/172479","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/comments?post=172479"}],"version-history":[{"count":0,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/posts\/172479\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/media\/172480"}],"wp:attachment":[{"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/media?parent=172479"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/categories?post=172479"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/tags?post=172479"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}