| name | technical-spec |
| description | Produce the technical-specs/ document set (numbered NN-topic.md files plus _index.md) from the groomed business docs, in the Terral/Tatanan format. Grills the user on tech stack and tooling first — this is the architectural keystone the rest of the pipeline depends on — then writes overview, architecture, repo structure, tech stack, module definitions, data model, security, NFRs, auth, integrations, environment config, and ad-hoc trailing specs for areas that need special technical attention. Use when the user wants a technical specification, TSD, architecture/data-model doc, or asks to "write the technical specs". |
Technical Specification
This is the architectural keystone of the doc pipeline: it runs after the business docs exist (docs/business/ — user stories, AC, sprint breakdown) and before the API specs, task breakdown, coding standard, and deployment plan, all of which depend on the decisions made here. Get the tech stack and data model right and everything downstream is buildable; get them wrong and every later doc inherits the error.
When this set is written and confirmed, the next step is api-spec, which projects the module definitions and data model into the docs/api-specs/ endpoint contracts in whatever protocol the tech stack chose.
Output is a numbered file set under docs/technical-specs/, not one monolith — _index.md plus NN-topic.md files — so sections are linkable from task cards and reviews (e.g. technical-specs/06-data-model.md §6.5).
Two phases: grill on the architecture, then write the set.
Phase 1 — Grill (tech stack and tooling first)
Read the source of truth before asking: all of docs/business/, plus any existing CLAUDE.md, README, or partial specs. The business docs define what is built; this document defines how. Then interview the user one question at a time, recommending an answer for each and explaining the trade-off, until every architectural branch is resolved.
This is a genuine grill, not a form. Hold the discipline:
- One decision per turn. Do not paste the bullet lists below at the user as a questionnaire to fill in. Ask the live question, give your recommended answer and the one reason it wins over the runner-up, and wait. The lists are your checklist of what must be closed, not the user's intake form.
- Walk the branch the answer opens. Each answer changes what to ask next (picking microservices opens service boundaries, transport, and data ownership; picking a modular monolith does not). Follow the branch the user's choice creates before starting a new one; don't return to a flat list.
- Confirm before moving on. Restate the decision in one line and get explicit agreement before leaving it, especially for the hard-to-reverse ones (architecture style, module boundaries, primary datastore).
Tech stack and tooling is mandatory and comes first — it cascades into every other section:
- Runtime & language — runtime (Node/Bun/Deno/JVM/Go/Python/…), language + version, strictness.
- Backend — framework, API style (REST/GraphQL/RPC), validation/schema layer, ORM/data layer.
- Frontend — framework, rendering model (SSR/SPA/SSG), styling, state, forms.
- Datastores — primary DB + engine/version, cache, queue, object storage, search. Also nail the migration and seed mechanism (whatever the stack): the migration tool, and that migrations and seeds run through a runner that resolves the migrations location relative to its own module/package (not an absolute path) and takes the connection string from the environment, exposed as a stable migrate/seed command. Forbid hardcoded absolute migration paths and inline raw-SQL file reads; they break inside distroless/minimal images where the operator cannot locate the files.
- Repository shape — monorepo vs polyrepo, workspace layout, shared-code package.
- Tooling — package manager, type-check / lint / format / test tools, build, CI, e2e.
- Infra & integrations — hosting target, containerization, external services/APIs, auth provider.
Then resolve the rest. Architecture style and module boundaries are the hardest to reverse — grill them hardest. Don't accept the first style the user names: lay out the realistic options against this project's NFRs and team size, give a recommendation with the trade-off, and get explicit buy-in before treating it as decided. Then derive module boundaries from the bounded areas in the business docs one at a time, confirming each owns a coherent slice before naming the next.
Design each module to be deep, borrowing the lens from the improve-codebase-architecture skill (vocabulary in ../improve-codebase-architecture/LANGUAGE.md):
-
A module is deep when a lot of behaviour sits behind a small interface — and "interface" means everything a caller must know (invariants, error modes, ordering, config), not just the signatures. Push back on shallow modules whose interface is nearly as wide as their implementation; that is usually a sign two modules should merge or one should absorb a pass-through.
-
Apply the deletion test to every proposed boundary: imagine deleting the module. If the complexity just moves to its callers, the boundary earns its keep; if complexity vanishes, it was a pass-through and the boundary is wrong.
-
Classify every cross-module or cross-service dependency by the categories in ../improve-codebase-architecture/DEEPENING.md (in-process, local-substitutable, remote-but-owned, true-external). Where the answer is remote-but-owned or true-external, define a port at the seam and inject the transport as an adapter, so the logic stays in one deep module testable behind an in-memory adapter. Only introduce a port where two adapters are actually justified (production plus test); a single-adapter seam is just indirection.
-
For the one or two riskiest module interfaces (the ones most callers depend on, or hardest to change later), offer the Design It Twice parallel exploration in ../improve-codebase-architecture/INTERFACE-DESIGN.md: spawn sub-agents to draft radically different interfaces, compare by depth and seam placement, and let the user pick before the shape is locked into the spec.
-
Architecture — overall style (monolith/modular-monolith/microservices), module/service boundaries, request flow.
-
Module definitions — one per bounded area; its responsibility, public surface, and the AC/US it serves.
-
Data model — every entity, field, type, nullability, default, key, relationship; the ERD; common columns (id/timestamps); state machines for lifecycle entities.
-
Security & auth — authn mechanism, authz model (roles/RBAC), session/token strategy, secrets, threat surface.
-
NFRs — volume, throughput, latency, availability targets, scaling assumptions.
-
Integration points — each external system, its contract, failure mode, and fallback.
-
Environment configuration — the full env-var set per environment.
-
Operational endpoints — every project gets two, by default, so deployment and QA have a contract to rely on:
- A health-monitor endpoint (e.g.
GET /health) that reports liveness and the status of each critical dependency (database, cache, queue, external APIs), so an operator or load balancer can tell at a glance whether the app and its datastores are reachable. Decide the response shape (overall status plus a per-dependency breakdown), whether it is public or guarded, and whether there is a deeper /health/ready vs /health/live split. This is the redundancy measure that catches a half-broken state, such as the app running but its database wiped or unreachable on a separate VM.
- A reset-db-state endpoint (e.g.
POST /admin/reset-state) that wipes, re-migrates, and reseeds the database back to a known initial state, choosing the dev seed or the QA seed. This exists so QA can request a reset to initial state without a manual host session. It must be mounted only in non-production environments (dev / test / SIT / UAT) and absent (not merely access-controlled) in production — gate it on an environment flag so the route does not exist in prod. Decide the seed-selection input (path, body field, or env), whether it runs synchronously or as a job, and how it is authenticated in the environments where it does exist.
As you go, surface contradictions against the business docs ("AC-10.02 says only Super Admin edits this field, but the data model has no owner/role column — where does that rule live?") and flag decisions that are hard to reverse.
Identify ad-hoc topics
The Terral/Tatanan format ends with ad-hoc trailing specs: a numbered doc per cross-cutting concern that needs focused technical attention and doesn't fit the standard sections — Terral's 12-autocomplete-strategy.md is the model. During the grill, watch for these: a recurring pattern used across modules, a non-obvious algorithm, a performance-sensitive path, a tricky state machine, a strategy with real alternatives. Propose each candidate to the user and confirm before adding it.
Each ad-hoc doc is the single source of truth for its concern — other docs and task cards cite it rather than restate it (open the doc with that instruction). Structure it like 12-autocomplete-strategy.md:
- A one-paragraph statement of the concern and where it applies, plus a "cite this, don't repeat it" note.
- A decision matrix table:
Concern | Decision | Rationale, one row per resolved choice (the heart of the doc).
- The interface / request shape — the exact API call, function signature, or data contract, in a code block.
- A performance targets table with a source for each number (cross-referencing the NFR doc).
- Any rate-limit / security constraints specific to this concern.
- The component/contract it produces (file layout + the locked public type signature).
- A "What this does NOT do" section — explicit non-goals, each saying why and where the responsibility actually lives.
- A cross-references table mapping each related concern to its source doc/card.
- An open follow-ups list — deferred-but-flagged decisions, each with the trigger condition for revisiting.
When every branch is resolved, summarize the stack and the planned file list, and confirm before writing.
Phase 2 — Write the set
Write to docs/technical-specs/. Standard core, in order, each as NN-topic.md:
-
Overview — open with a prose intro, then a Terminology callout (point at the glossary) and, for multilingual projects, a Language policy callout (which language is UI copy, which is code/identifiers). Then goals, scope-by-module (a subsection per module listing in-scope items with their AC/US, plus an explicit Out of Scope subsection), user base, a business-workflow summary as pseudocode (the real flow per role, like the SPK example), and a numbered Assumptions & Known Constraints list.
-
System Architecture — the style and why ("Why This Shape" rationale), plus Mermaid diagrams: a high-level component graph, a request-lifecycle sequence diagram, and a deployment graph. Close with a service/module boundary map table and the rule for cross-module calls (modules don't import each other's internals).
-
Repository Structure — the directory tree with a per-folder purpose annotation.
-
Tech Stack — dense tables grouped by concern (runtime/language, backend, frontend, datastores, security, testing/CI, deployment, storage), each row Component | Technology | Justification. Pin versions. Add a "What we deliberately do NOT use" table (tool → reason) and a version-pinning policy. Every choice is one the user confirmed in the grill.
-
Module Definitions — one numbered subsection per module: responsibility, public surface/API, the entities it owns, and the AC/US it serves. Write the public surface as the module's full interface (invariants, error modes, ordering, config), not just method signatures, and name the seam where each external dependency is injected and which adapters sit there (production vs test). State why the module is deep — the behaviour it hides behind that surface. Note shared-package usage and the no-cross-internal-import rule. Include the operational endpoints (health monitor and, where mounted, reset-db-state) in whichever module owns them: document the route, method, request/response shape, the per-dependency health breakdown, the dev/QA seed-selection input, and the environment gating that keeps reset-state out of production.
-
Data Model — an ERD (Mermaid erDiagram or equivalent), then table definitions with column | type | nullable | default | key | notes and example values, common-column conventions (id/timestamps), and state machines for lifecycle entities. Mirror the glossary: ### CUSTOMERS (UI label: "Pelanggan"). Close with a Migrations and seeding subsection that locks the mechanism for whatever stack the project chose: a runner whose migrations location is resolved relative to its own module/package and whose connection string comes from the environment, surfaced as a stable migrate/seed command. State the rule, then show it in the project's actual language. The TypeScript/Bun shape, as one example:
import { migrate } from "drizzle-orm/bun-sql/migrator";
import { db, sql } from "./client";
import { join } from "path";
await migrate(db, { migrationsFolder: join(import.meta.dir, "migrations") });
await sql.close();
Forbid the inverse in any language: a hardcoded absolute path with an inlined connection string and a raw read/exec of one .sql file; that applies schema outside the migration ledger and cannot find its files in a distroless image. The dev seed and QA seed are separate, idempotent, version-controlled scripts selected by the reset-db-state endpoint.
-
Security — authn/authz, rate limiting, CORS, CSP, request hardening, object-store access; table per concern with the implementation. Document the operational-endpoint policy here: the health endpoint's exposure (public vs guarded), and the reset-db-state endpoint's hard rule — mounted only in dev / test / SIT / UAT, conditionally registered behind an environment flag so the route does not exist in production, with a note that this is enforced at route registration, not just by authorization.
-
Non-Functional Requirements — concrete numbers (volume, latency budgets, throughput, availability), each with a source; these become the targets later sections and ad-hoc docs reference.
-
Authentication and Authorization — mechanism, token/session strategy, the role matrix.
-
Integration Points — each external system: contract, failure mode, fallback, cache/invalidation rules.
-
Environment Configuration — the full env-var set, annotated, per environment. Include the flag that gates the reset-db-state endpoint (e.g. ENABLE_RESET_API / APP_ENV) and the seed-selection variable, with their values per environment shown explicitly as off/absent in production.
Then the ad-hoc trailing docs (12, 13, …), one per confirmed special-attention topic.
Finally _index.md: the version/date/author/status/phase header, a numbered Table of Contents linking every file, and a Companion Documents table linking the sibling docs (../business/, ../GLOSSARY.md, ../CODING_STANDARD.md, ../TASK_BREAKDOWN.md, ../DEPLOYMENT_PLAN.md, ../api-specs/, etc.) — link them even if they don't exist yet, since they're produced later in the pipeline.
Writing rules
- Number sections within each file (
## 6.1, ### 6.2) so they're citable as stable anchors.
- Trace every module and data-model decision back to the AC/US it serves; this is a spec of the business docs, not free invention. Where the business docs are silent, raise it during the grill rather than guessing.
- Preserve domain terms and non-English UI labels verbatim; mirror the glossary (
### CUSTOMERS (UI label: "Pelanggan")).
- Keep
_index.md and the file numbering consistent; if you add or reorder files, update the TOC.
- If a technical-specs set already exists, read it and update affected files in place rather than clobbering; report what changed.
Helper script
After writing or reordering files, verify _index.md still matches the file set:
python scripts/check_index.py --specs-dir docs/technical-specs
It reports entries linked in the index but missing on disk, files on disk not
linked from the index, and gaps in the NN numbering. Exit is non-zero on any
mismatch.
Writing conventions
- No AI slop: no filler or hedging; every sentence informs. Use the
stop-slop skill on prose when unsure.
- No em-dashes, no double-dashes (
--) in prose; dashes only as Markdown syntax (list bullets, table rules) or in literal code/CLI flags (e.g. --no-deps).
- No emoji. Professional, declarative tone.
- Metadata header lines (
**Version:**, **Date:**, **Author:**, **Status:**, **Phase:**) each end with two trailing spaces so Markdown renders them on separate lines.