<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aniekan Okono</title>
    <description>The latest articles on DEV Community by Aniekan Okono (@anioko1).</description>
    <link>https://dev.to/anioko1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3951393%2F315b25b0-eda7-4dda-afb2-c1606674cc6c.png</url>
      <title>DEV Community: Aniekan Okono</title>
      <link>https://dev.to/anioko1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anioko1"/>
    <language>en</language>
    <item>
      <title>Spec-Driven Development Without an IDE: I Generated NestJS, Go, Spring Boot, Laravel, and Rust Apps From a Single PRD File</title>
      <dc:creator>Aniekan Okono</dc:creator>
      <pubDate>Mon, 25 May 2026 21:24:23 +0000</pubDate>
      <link>https://dev.to/anioko1/spec-driven-development-without-an-ide-i-generated-nestjs-go-spring-boot-laravel-and-rust-3p26</link>
      <guid>https://dev.to/anioko1/spec-driven-development-without-an-ide-i-generated-nestjs-go-spring-boot-laravel-and-rust-3p26</guid>
      <description>&lt;p&gt;Amazon launched Kiro in 2025 with a waiting list and a clear thesis: the problem with AI coding tools is not that they write bad code, it is that they write code with no connection to your requirements. Kiro's answer is spec-driven development — you write a spec, the tooling generates from it, and the spec stays authoritative.&lt;/p&gt;

&lt;p&gt;Kiro is a good idea. But it is also a proprietary IDE you have to install, sign in to, and trust with your codebase.&lt;/p&gt;

&lt;p&gt;I built the same concept as a set of open-source CLI tools: one per ecosystem, published to the registry your team already uses. No IDE. No account. No network calls. A text file goes in, a working application comes out.&lt;/p&gt;

&lt;p&gt;Here is what I built, how it works, and why the architecture matters more than the code.&lt;/p&gt;




&lt;p&gt;Try it right now — pick your stack&lt;/p&gt;

&lt;p&gt;# NestJS / TypeScript&lt;br&gt;
 npm install -g archiet-microcodegen-nestjs&lt;br&gt;
 archiet-microcodegen-nestjs --sample &amp;gt; prd.md&lt;br&gt;
 archiet-microcodegen-nestjs prd.md --out ./my-app&lt;br&gt;
 cd my-app &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; docker compose up&lt;/p&gt;

&lt;p&gt;# Go Chi&lt;br&gt;
 go install github.com/aniekanasuquookono-web/archiet-microcodegen-go@latest&lt;br&gt;
 archiet-microcodegen-go prd.md --out ./my-app&lt;br&gt;
 cd my-app &amp;amp;&amp;amp; make run&lt;/p&gt;

&lt;p&gt;# Laravel (PHP)&lt;br&gt;
 composer global require archiet/microcodegen-laravel&lt;br&gt;
 archiet-microcodegen-laravel prd.md --out ./my-app&lt;br&gt;
 cd my-app &amp;amp;&amp;amp; composer install &amp;amp;&amp;amp; docker compose up&lt;/p&gt;

&lt;p&gt;# Spring Boot (Java)&lt;br&gt;
 java -jar archiet-microcodegen-java.jar prd.md --out ./my-app&lt;br&gt;
 cd my-app &amp;amp;&amp;amp; mvn spring-boot:run&lt;/p&gt;

&lt;p&gt;# Tauri (Rust + desktop)&lt;br&gt;
 cargo install archiet-microcodegen-tauri&lt;br&gt;
 archiet-microcodegen-tauri prd.md --out ./my-app&lt;br&gt;
 cd my-app &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; npm run tauri dev&lt;/p&gt;

&lt;p&gt;Your app has full CRUD, JWT auth with httpOnly cookies, Postgres 16, and per-user data isolation — before you have finished reading this article.&lt;/p&gt;




&lt;p&gt;What spec-driven development actually means&lt;/p&gt;

&lt;p&gt;Spec-driven development is a 25-year-old idea from model-driven architecture (MDA): write a formal model of your system, then derive the implementation from it. The model is the source of truth. The code is an artefact of the model.&lt;/p&gt;

&lt;p&gt;The reason this did not become mainstream is that writing formal models used to require UML tools and an enterprise architect. Kiro's insight (and mine) is that a plain text requirements file is a formal model — it just needs a parser that takes it seriously.&lt;/p&gt;

&lt;p&gt;The pipeline has four stages. I implemented all four in every language, which is why each generator produces genuinely correct output rather than a template with blanks left for you to fill in.&lt;/p&gt;




&lt;p&gt;The four stages&lt;/p&gt;

&lt;p&gt;Stage 1 — parse_prd(text) → Manifest&lt;/p&gt;

&lt;p&gt;Reads your text file. Extracts every entity definition (e.g. Task, Project, User), field names with types, user stories, and integration references — using regex, not an LLM. The output is a language-agnostic Manifest.&lt;/p&gt;

&lt;p&gt;# Example PRD snippet:&lt;br&gt;
 # "The system manages Projects and Tasks. A Project has a name, description, and status.&lt;br&gt;
 #  A Task has a title, body, due_date, and belongs to a Project."&lt;/p&gt;

&lt;p&gt;# Manifest output:&lt;br&gt;
 Entities: [Project, Task]&lt;br&gt;
 Fields:&lt;br&gt;
   Project: name (string, required), description (text), status (string)&lt;br&gt;
   Task: title (string, required), body (text), due_date (string), project_id (FK→Project)&lt;/p&gt;

&lt;p&gt;Stage 2 — manifest_to_genome(manifest) → Genome&lt;/p&gt;

&lt;p&gt;Converts the Manifest into an Architectural Genome — a typed intermediate representation using ArchiMate 3.2 element categories: ApplicationComponent, ApplicationService, DataObject, ApplicationInterface.&lt;/p&gt;

&lt;p&gt;Every entity automatically receives id, user_id (for per-tenant isolation), and created_at. Relationships are made explicit. The Genome is still language-agnostic — it drives NestJS output and Go output and Laravel output equally.&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
   "solution_name": "TaskManager",&lt;br&gt;
   "entities": [&lt;br&gt;
     {&lt;br&gt;
       "name": "Task",&lt;br&gt;
       "archimate_type": "DataObject",&lt;br&gt;
       "fields": {&lt;br&gt;
         "id": { "type": "integer", "required": true },&lt;br&gt;
         "user_id": { "type": "integer", "required": true },&lt;br&gt;
         "title": { "type": "string", "required": true },&lt;br&gt;
         "due_date": { "type": "string", "required": false },&lt;br&gt;
         "created_at": { "type": "datetime", "required": true }&lt;br&gt;
       },&lt;br&gt;
       "relationships": [&lt;br&gt;
         { "type": "association", "target": "Project", "cardinality": "many-to-one" }&lt;br&gt;
       ]&lt;br&gt;
     }&lt;br&gt;
   ]&lt;br&gt;
 }&lt;/p&gt;

&lt;p&gt;This is where the approach diverges from a template system. The Genome is your architecture document in machine-readable form. Stages 3 and 4 are pure rendering — they never make decisions about what the system is.&lt;/p&gt;

&lt;p&gt;Stage 3 — render_genome(genome) → {path: content}&lt;/p&gt;

&lt;p&gt;The language-specific stage. The NestJS renderer generates TypeScript with TypeORM. The Go renderer generates idiomatic Chi handlers with GORM. The Laravel renderer generates Eloquent models, controllers with Form Requests, and Blade-free API resources. The Rust renderer generates Tauri IPC commands with rusqlite.&lt;/p&gt;

&lt;p&gt;Stages 1 and 2 are shared across all ten packages. Only stage 3 differs. That is why it was possible to ship ten ecosystems in one week — the architecture thinking was done once, not ten times.&lt;/p&gt;

&lt;p&gt;Stage 4 — pack(files) → ZIP or directory&lt;/p&gt;

&lt;p&gt;Writes to disk or bundles a ZIP. Pure stdlib in every implementation — no external zip library.&lt;/p&gt;




&lt;p&gt;The generated NestJS app (for NestJS developers)&lt;/p&gt;

&lt;p&gt;Running the generator against a task-manager PRD produces:&lt;/p&gt;

&lt;p&gt;src/&lt;br&gt;
   auth/&lt;br&gt;
     auth.module.ts&lt;br&gt;
     auth.controller.ts     ← /auth/register, /auth/login, /auth/me, /auth/logout&lt;br&gt;
     jwt.strategy.ts        ← reads httpOnly cookie, never Authorization header&lt;br&gt;
     jwt-auth.guard.ts&lt;br&gt;
   task/&lt;br&gt;
     task.controller.ts     ← GET /tasks, POST /tasks, GET /tasks/:id, PUT, DELETE&lt;br&gt;
     task.service.ts        ← every method filters by userId&lt;br&gt;
     task.entity.ts         ← TypeORM &lt;a class="mentioned-user" href="https://dev.to/entity"&gt;@entity&lt;/a&gt;, &lt;a class="mentioned-user" href="https://dev.to/column"&gt;@column&lt;/a&gt;, @PrimaryGeneratedColumn&lt;br&gt;
     dto/&lt;br&gt;
       create-task.dto.ts   ← class-validator decorators, correct 422 on failure&lt;br&gt;
       update-task.dto.ts&lt;br&gt;
 docker-compose.yml         ← Postgres 16, healthcheck-gated startup&lt;br&gt;
 ARCHITECTURE.md            ← ArchiMate 3.2 element inventory&lt;br&gt;
 openapi.yaml               ← machine-readable API contract&lt;br&gt;
 test/&lt;br&gt;
   task.controller.spec.ts  ← happy-path Jest tests per controller&lt;/p&gt;

&lt;p&gt;Three security properties the generator enforces that AI assistants routinely get wrong:&lt;/p&gt;

&lt;p&gt;httpOnly cookies, not localStorage. JwtStrategy reads from req.cookies['access_token']. AuthController sets httpOnly: true, secure: true, sameSite: 'strict'. localStorage is an XSS vulnerability. The generator does not offer it as an option.&lt;/p&gt;

&lt;p&gt;Per-user isolation on every query. taskService.findAll(userId) executes WHERE user_id = $1. Every service method receives the authenticated user's ID from the guard. There is no code path that returns another user's data.&lt;/p&gt;

&lt;p&gt;Correct HTTP status codes. 201 on create. 422 on validation failure. 403 on auth failure. 404 on not-found. This is the generated code, not the documentation.&lt;/p&gt;




&lt;p&gt;The generated Laravel app (for PHP/Laravel developers)&lt;/p&gt;

&lt;p&gt;Laravel is one of the most-searched scaffolding targets because the ecosystem is large and opinionated. The generator produces:&lt;/p&gt;

&lt;p&gt;app/&lt;br&gt;
   Models/&lt;br&gt;
     Task.php               ← Eloquent model, $fillable, $casts, userId scope&lt;br&gt;
   Http/&lt;br&gt;
     Controllers/&lt;br&gt;
       TaskController.php   ← full CRUD resource controller&lt;br&gt;
     Requests/&lt;br&gt;
       StoreTaskRequest.php ← FormRequest validation, 422 on failure&lt;br&gt;
     Resources/&lt;br&gt;
       TaskResource.php     ← API resource, hides internal fields&lt;br&gt;
 routes/&lt;br&gt;
   api.php                  ← Route::apiResource + auth middleware&lt;br&gt;
 database/&lt;br&gt;
   migrations/&lt;br&gt;
     create_tasks_table.php ← with user_id FK index&lt;br&gt;
 docker-compose.yml&lt;br&gt;
 ARCHITECTURE.md&lt;br&gt;
 openapi.yaml&lt;/p&gt;

&lt;p&gt;The Task model has a global scope that automatically filters by the authenticated user — the same per-tenant isolation principle, expressed in Laravel idioms.&lt;/p&gt;




&lt;p&gt;The generated Go app (for Go developers)&lt;/p&gt;

&lt;p&gt;Go developers are allergic to magic. The generated app uses net/http (via Chi for routing), GORM, and golang-jwt/jwt. No reflection-heavy frameworks.&lt;/p&gt;

&lt;p&gt;func (h *TaskHandler) ListTasks(w http.ResponseWriter, r *http.Request) {&lt;br&gt;
     userID := r.Context().Value(contextKeyUserID).(int64)&lt;br&gt;
     tasks, err := h.service.FindAllByUser(r.Context(), userID)&lt;br&gt;
     // ...&lt;br&gt;
 }&lt;/p&gt;

&lt;p&gt;func (s *TaskService) FindAllByUser(ctx context.Context, userID int64) ([]Task, error) {&lt;br&gt;
     var tasks []Task&lt;br&gt;
     result := s.db.WithContext(ctx).Where("user_id = ?", userID).Find(&amp;amp;tasks)&lt;br&gt;
     return tasks, result.Error&lt;br&gt;
 }&lt;/p&gt;

&lt;p&gt;Generated Makefile includes make build, make test, make migrate. Zero magic.&lt;/p&gt;




&lt;p&gt;The Tauri generator is intentionally different&lt;/p&gt;

&lt;p&gt;Desktop apps have different constraints. The generated Tauri app uses SQLite instead of Postgres. Auth uses Argon2id + UUID session tokens in application memory — there is no HTTP layer in Tauri, only IPC commands.&lt;/p&gt;

&lt;p&gt;#[tauri::command]&lt;br&gt;
 async fn list_tasks(state: State&amp;lt;'_, AppState&amp;gt;, session: String) -&amp;gt; Result, String&amp;gt; {&lt;br&gt;
     let user_id = state.sessions.lock().unwrap()&lt;br&gt;
         .get(&amp;amp;session).copied().ok_or("Unauthorised")?;&lt;br&gt;
     // every query filters by user_id — same principle, different idiom&lt;br&gt;
 }&lt;/p&gt;




&lt;p&gt;How this compares to Amazon Kiro&lt;/p&gt;

&lt;p&gt;Kiro and these generators share the same core insight: spec-first produces better software than prompt-and-pray. The differences are practical:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are different tools. If you are already using Kiro, you can use these generators to bootstrap the initial project before Kiro helps you extend it.&lt;/p&gt;




&lt;p&gt;Why pure stdlib — the Karpathy constraint&lt;/p&gt;

&lt;p&gt;Each generator is one file, under 1,400 lines, zero external dependencies. The Go generator uses only archive/zip, encoding/json, and os. The Node.js generator uses only fs, path, crypto, and zlib. The Rust generator uses only std.&lt;/p&gt;

&lt;p&gt;This comes from Andrej Karpathy's micrograd philosophy: if you cannot express the complete algorithm in a small, dependency-free file, you do not fully understand it. Every generator is auditable in a single reading. No transitive dependencies, no supply-chain risks, no version conflicts.&lt;/p&gt;

&lt;p&gt;It also means the generator never breaks because a dependency changed its API.&lt;/p&gt;




&lt;p&gt;The architecture document that survives your codebase&lt;/p&gt;

&lt;p&gt;Every generated project includes ARCHITECTURE.md with a typed ArchiMate 3.2 inventory:&lt;/p&gt;

&lt;p&gt;### ApplicationComponent: TaskManagerAPI&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Realises: TaskManagementService&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;### DataObject: Task&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fields: id, user_id, title, body, due_date, status, created_at&lt;/li&gt;
&lt;li&gt;Association: Task → Project (many-to-one)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Six months from now, a new engineer reads ARCHITECTURE.md and understands the system without deciphering the implementation.&lt;/p&gt;




&lt;p&gt;Distribution: why ten registries matter&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NestJS&lt;/strong&gt; → &lt;code&gt;npm install -g archiet-microcodegen-nestjs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go Chi&lt;/strong&gt; → &lt;code&gt;go install github.com/aniekanasuquookono-web/archiet-microcodegen-go@latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laravel&lt;/strong&gt; → &lt;code&gt;composer global require archiet/microcodegen-laravel&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring Boot&lt;/strong&gt; → &lt;code&gt;java -jar archiet-microcodegen-java.jar&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rails&lt;/strong&gt; → &lt;code&gt;gem install archiet-microcodegen-rails&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET&lt;/strong&gt; → &lt;code&gt;dotnet tool install -g archiet-microcodegen-dotnet&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tauri&lt;/strong&gt; → &lt;code&gt;cargo install archiet-microcodegen-tauri&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; → &lt;code&gt;pip install archiet-microcodegen&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flask&lt;/strong&gt; → &lt;code&gt;pip install archiet-microcodegen-flask&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Django&lt;/strong&gt; → &lt;code&gt;pip install archiet-microcodegen-django&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The underlying platform&lt;/p&gt;

&lt;p&gt;These generators are the offline distribution of Archiet — a platform that applies the same spec-driven pipeline at enterprise scale: multi-stack simultaneous generation, quality scoring, delivery gates, and a live genome editor.&lt;/p&gt;

&lt;p&gt;Spec-driven development should not require a proprietary IDE. These tools exist to make it available everywhere developers already work.&lt;/p&gt;

&lt;p&gt;If you want the full platform — try &lt;a href="https://dev.tourl"&gt;Archiet.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Source is public on GitHub. If you hit an issue, the algorithm is short enough that a fix is usually a one-line PR.&lt;/p&gt;




</description>
      <category>webdev</category>
      <category>ai</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
