Generate Feat Spec
Announce at start: "I'm using the generate-feat-spec skill to produce a high-level ABP/DDD Feature Specification from the GitLab milestone FRS issues. I'll write the spec and supporting DDD node pages to the wiki repo first, then create a short coordination issue in GitLab that links to them."
Orchestrator model: Sonnet. Phase 10.5 manifest validation, envelope identity checks (consumes_phase_id / consumes_secondary_phase_ids), and the post-write tree walk in Phase 11 all exceeded haiku's reliable execution range in captured runs. Sonnet is required for the orchestrator; sub-agent model assignments (some still Haiku) are documented in the Sub-agent Contracts Summary.
Hard Rules
Phase identity & dispatch
- Phase status announcements are required before and after every phase. Sub-agent failures must surface a user-facing message and invoke
AskUserQuestion. - Phase identity is exact. Before dispatching any phase, emit the phase number, name, and sub-agent name verbatim from the Quick Reference table — no merging, renaming, renumbering, or skipping. This applies to every phase including the main-agent phases (0, 1, 2.5, 4, 5, 8, 10, 10.5, 11); in particular Phases 4 and 5 MUST emit their
→ Phase 4: .../→ Phase 5: ...headers even when their work is light. - Sub-agent allowlist is closed. Only the six sub-agents in the Sub-agent Contracts table may be dispatched:
frs-retriever,clause-normalizer,clause-mapper,ddd-synthesizer,feat-spec-validator,docs-writer. Any other name (e.g.clause-processor) is forbidden. - One sub-agent per phase, no exceptions. In Phases 2, 3, 6, 7, 9, and 11, the main agent MUST dispatch exactly the named sub-agent for that phase.
- No multi-phase mega-dispatches. Generic
Agent(...)invocations whosedescriptionorsubagent_typecovers more than one phase (e.g.,"Full pipeline: FRS → normalize → map → synthesize → assemble","Phase 2-9") are forbidden. subagent_typemust match the phase being announced. The dispatchedsubagent_typemust be one of{frs-retriever, clause-normalizer, clause-mapper, ddd-synthesizer, feat-spec-validator, docs-writer}and must match the phase named in the preamble.- Phase envelopes carry phase identity. Every sub-agent envelope (input AND output) carries
phase_id(e.g.,"phase-2") andproduced_by(the sub-agent name). The next phase's input MUST cite the prior phase'sphase_id. A sub-agent that does not see the expectedphase_idin its input HALTS andAskUserQuestions. This makes phase-skipping mechanically detectable.
Other
- No VCS fallback. If GitLab MCP fails or returns an error, halt and
AskUserQuestionwith optionsretry/cancel. Never substitute GitHub, Bitbucket, or any other VCS. Never write the coordination issue to a different system than the FRS source.
Implementation note (forensic affordance, not a hard-gate rule): if the runtime cannot enforce
subagent_typedirectly, the agent's prompt SHOULD open withActing as <sub-agent-name> for Phase <N>so the contract is auditable in logs. The actual halt for phase-skipping is theconsumes_phase_idmismatch check in the Phase Envelope Contract; the audit prefix is purely diagnostic and is not enforced by any of the four checkpoints.
Overview
Generates a layered, ABP-shaped Feature Specification from one or more FRS issues belonging to a single GitLab milestone. The Feat Spec is produced as a set of Markdown pages in the wiki repo, not as the body of a GitLab issue. The GitLab Feat Spec issue is a short coordination artifact pointing to the wiki and listing linked FRS IIDs.
The skill reads project context from CLAUDE.md (ABP layout, library choices, API routing conventions), uses GitLab MCP tools to resolve milestones and hydrate issues, classifies each issue by source type, runs a monolith detection check before normalization, and maps every requirement clause into a formal DDD/ABP category. Queries are first-class, separate from Commands. UI-related clauses are captured in a dedicated UI-API Integration Points section. Critical blockers surface at the top of the assembled spec. The draft is validated before preview and only formalized — wiki files written, Feat Spec issue created, FRS issues linked — after explicit user approval.
Heavy phases are delegated to purpose sub-agents at the I/O boundary. Retrieval, synthesis, and file writing support parallel dispatch.
Core Principle
Every FRS clause must map to exactly one primary Feat Spec section or be escalated to Conflicts — no clause may remain as loose narrative.
- GitLab FRS clause content is the canonical source of truth for requirement meaning.
- CLAUDE.md is the authoritative source of project conventions. Synthesis must honor every convention CLAUDE.md declares; deviations require a Decision node.
- Ambiguity is preserved in Conflicts, never silently resolved.
- UI clauses route to UI-API Integration Points (not discarded), capturing the prototype-to-backend contract.
- The wiki is the canonical home of the spec. The GitLab issue is a pointer, not a duplicate.
- Clause sources are deep-linked into GitLab issues via auto-generated section anchors.
CLAUDE.md Convention Contract
The skill reads the following fields. Required fields block Phase 0 (via AskUserQuestion) if missing; optional fields have sensible defaults.
| Field | Required | Default | Used by |
|---|---|---|---|
gitlab_project_id |
yes | — | All GitLab MCP calls |
gitlab_base_url |
yes | — | Deep-link generation for clause sources |
wiki_url |
yes | — | Canonical spec URL and all wiki-style links |
wiki_local_path |
no | docs |
On-disk location for wiki file writes |
tenancy_model |
recommended | — | Multi-tenancy resolution in Phase 4 |
project_root_namespace |
no | derived from project name | ABP Artifact Map namespaces |
module_project_layout |
no | ABP defaults | Paths for Domain, Application, EF Core projects |
api_routing_conventions |
no | /api/app/... |
HTTP API section; Public/Private split if declared |
validation_library |
no | FluentValidation |
Command validator synthesis |
object_mapping_library |
no | Mapperly |
Object Mapping section |
permissions_class |
no | <Module>Permissions |
Permissions Map pattern |
db_table_prefix |
no | App |
Infrastructure table names |
sorting_strategy |
no | explicit-switch |
Query entries; bans System.Linq.Dynamic.Core |
enum_serialization |
no | camelCase strings, global |
State storage notes; DTO enum notes |
notable_gotchas |
no | — | Passed verbatim to ddd-synthesizer as context |
wiki_local_path normalization — strip leading and trailing slashes from the value before use. Reject absolute paths (values starting with /, \, or a drive letter such as C:\docs); the path is always relative to the project root. A literal /docs becomes docs; a literal C:\docs halts Phase 1 with AskUserQuestion.
Public/Private AppService split — when api_routing_conventions declares public_prefix and private_prefix, every Command and Query is tagged **Audience:** Public | Private based on the invoking Actor, and route generation uses the appropriate prefix.
If CLAUDE.md does not declare an optional convention, Phase 1 emits a one-time soft warning listing the defaults being used.
Wiki Link Format
The wiki's path on disk (wiki_local_path) is separate from its published URL (wiki_url). All path and link rules — including the disallowed forms — live in references/path-contract.md (sections 1–3). Read it once at Phase 1; consult it whenever you compute a path or render a link.
Quick recap of the four link contexts:
| Context | Link format |
|---|---|
| Feat Spec → DDD node page | [<node name>](<wiki_url>/<node-type>/<NodeName>) |
| Feat Spec → another feat spec | [<title>](<wiki_url>/feat-specs/<slug>/feat-spec) |
| Coord issue → Feat Spec | [feat-spec](<wiki_url>/feat-specs/<slug>/feat-spec) |
| DDD node → related node | [<related node>](<wiki_url>/<node-type>/<RelatedName>) |
For the precise grammar, the Forbidden regex (F1–F5, L1–L3), and worked examples, see references/path-contract.md.
Clause Source Deep-Linking
Every synthesized entry carries a **Source:** field with GitLab-rendered deep links into source FRS issue(s) — not opaque clause IDs.
GitLab auto-generates anchor IDs for headings via:
- Lowercase the heading text.
- Replace spaces with hyphens.
- Strip punctuation (
.,,,:,;,(,),[,],!,?,',", etc.). - Collapse consecutive hyphens.
- Append
-N(1-indexed) for duplicate headings.
Examples:
| Heading | Anchor | Full URL |
|---|---|---|
## 3. Actors |
#3-actors |
<base>/issues/11#3-actors |
## 4. Success Outcomes |
#4-success-outcomes |
<base>/issues/11#4-success-outcomes |
### 4.1 Primary flow |
#41-primary-flow |
<base>/issues/11#41-primary-flow |
**Source:** lists one link per contributing section:
**Source:**
- [FRS #11 — Actors](http://localhost:8080/root/trade-finance/-/issues/11#3-actors)
- [FRS #11 — Success Outcomes](http://localhost:8080/root/trade-finance/-/issues/11#4-success-outcomes)
Edge cases: Issues without headings → fall back to <base>/issues/<iid>, label FRS #<iid> — description; clause-normalizer warns. List-item granularity: GitLab anchors headings only, so the link points to the enclosing heading. Duplicate headings: clause-normalizer appends -N per GitLab's rule and warns.
Internally, the normalizer assigns stable clause keys for sub-agent handoff, but they never appear in published output.
When NOT to Use
- FRS source is not in GitLab.
- No active GitLab MCP connection (
mcp__gitlabunavailable). - CLAUDE.md is absent or lacks
gitlab_project_id/gitlab_base_url/wiki_url. - User has not specified a milestone name or issue ID.
MCP Tool Ownership (Exclusive)
| Tool | Exclusive owner | Phase | Type |
|---|---|---|---|
list_milestones |
main agent only | 0, 1 | read |
get_milestone |
main agent only | 0, 1 | read |
get_milestone_issue |
main agent only | 0, 2 | read |
get_issue |
frs-retriever only |
2 | read |
list_issue_links |
frs-retriever only |
2, 11 pre-check | read |
list_issues |
main agent only | 11 | read |
create_issue |
main agent only | 11 | write |
create_issue_link |
main agent only | 11 | write |
update_issue is never permitted, by any agent. clause-normalizer, clause-mapper, ddd-synthesizer, feat-spec-validator, and docs-writer may not call any GitLab MCP tool under any circumstance.
frs-retriever Output Contract
frs-retriever returns a structured envelope with raw FRS bodies inline. scratch_dir is internal to frs-retriever only.
Each issue entry includes: iid, title, source_type (FRS / linked / unknown), monolith_signals, halt_flag, open_questions (list of {heading, anchor, text}), body_text (full description, inline), section_catalog (list of {heading, anchor}).
The main agent forwards body_text and section_catalog directly into the clause-normalizer envelope. Downstream sub-agents must not and cannot re-fetch issue content from GitLab.
Conflict Node Naming
Conflict nodes use a title-derived slug for filenames and rendered wiki links. Internal identifiers (e.g. CONFLICT-01) MUST NOT appear in any published file — neither in body Markdown nor in YAML frontmatter (the frontmatter id is the title slug per agents/docs-writer.md step 1).
Slug rule: see <title-slug> definition in references/path-contract.md § 1 (canonical).
| Conflict title | Correct filename | Wrong |
|---|---|---|
| Tenant vs Entity Scoping Ambiguity | tenant-vs-entity-scoping-ambiguity.md |
conflict-01.md |
| Missing Query for Dashboard Summary | missing-query-for-dashboard-summary.md |
CONFLICT-02.md |
Rendered: [Tenant vs Entity Scoping Ambiguity](<wiki_url>/conflicts/tenant-vs-entity-scoping-ambiguity).
Phase Reporting & Failure Handling
Before dispatching each phase, emit one line:
→ Phase <N>: <action> — dispatching <sub-agent or 'main agent'>...
After completion:
✓ Phase <N> complete. <brief result>
Silent transitions are not permitted. This applies to repair loops too.
On sub-agent failure (interrupt, timeout, empty output):
- Emit
⚠ Phase <N> — sub-agent <n> failed or was interrupted. - Do NOT silently stop or proceed with partial data.
- Invoke
AskUserQuestionwith options:retry/skip-and-continue (surface as high-severity blocker)/cancel. - On
skip-and-continue: record as high-severity Open Blocker, continue remaining phases where possible. - On
retry: re-dispatch with the same input envelope. - On
cancel: emit a final status summary of completed phases and stop.
Quick Reference
| Phase | Action | Delegated to | Parallel? | Gate |
|---|---|---|---|---|
| 0 | Scope preview | main | — | User confirms before retrieval |
| 1 | Read CLAUDE.md convention contract, resolve milestone | main | — | Halt if required fields missing |
| 2 | Retrieve + hydrate issues, classify, detect monolith | frs-retriever |
Yes — per issue | Halt per-issue if rule triggers |
| 2.5 | Open-Questions gate | main | — | resolve-first / continue-anyway / pause |
| 3 | Normalize clauses, capture anchors, exclusion ledger | clause-normalizer |
No | Exclusion ledger complete |
| 4 | Context resolution (naming, tenancy) | main | — | Tenancy conflict if ambiguous |
| 5 | Module classification | main | — | Every mapped clause has a module |
| 6 | Clause-to-category mapping | clause-mapper |
No | Unmapped count = 0 |
| 7 | DDD/ABP synthesis | ddd-synthesizer |
Yes — per module if ≥2 | Fully expanded; no code fences |
| 8 | Assemble Feat Spec | main | — | No compression, no stubs |
| 9 | Validation checklist | feat-spec-validator |
No | All critical/high checks pass |
| 10 | Preview gate (AskUserQuestion) |
main | — | No side effects without approval |
| 10.5 | Path Manifest gate (validate every computed path against path-contract.md) |
main | — | Halt on any F1–F5 / L1–L3 violation |
| 11 | Write wiki files, create coord issue, link FRS | docs-writer (sole writer) + main (GitLab only) |
Yes — file writes | Post-approval only; wiki first |
Parallel Dispatch
Each worker receives a focused input envelope; do not forward full session context. Per-worker schemas are in agents/<sub-agent>.md.
Phase 2 — frs-retriever parallel fetches. If milestone has ≥3 issues, dispatch parallel get_issue calls (plus their linked issues); sequential otherwise. One issue IID + linked issues per worker. Parallel-safe: get_issue is read-only and issues are independent.
Phase 7 — ddd-synthesizer parallel by module. If Phase 5 produced ≥2 modules, dispatch one synthesizer per module; single pass otherwise. Each worker gets one module's clause mappings + Conflicts, all reference files, ABP catalogs, CLAUDE.md contract. Parallel-safe: aggregate boundaries don't cross modules; cross-module references resolve at assembly.
Phase 11 — docs-writer parallel file writes. Parallel batches when >5 files. Each worker gets a batch of {filepath, content}; returns a write manifest. Parallel-safe: paths are pre-computed and non-overlapping.
Not parallelized — clause-normalizer (shares exclusion ledger and naming state), clause-mapper (needs consistent view of all clauses for contradiction detection), feat-spec-validator (runs once against the assembled whole), repair loop (surgical, serial).
Per-Phase Preamble (mandatory)
Before any work in Phase N, emit exactly one announcement line of the form:
→ Phase N: <name from Quick Reference> — dispatching <sub-agent name from Sub-agent Contracts, or 'main agent' for Phases 0, 1, 2.5, 4, 5, 8, 10, 11>...
The phase number, name, and sub-agent name MUST be copied verbatim from the Quick Reference and Sub-agent Contracts tables. If any of the three placeholders cannot be filled in verbatim from those tables, halt and AskUserQuestion rather than improvise. Do not collapse adjacent phases into a single announcement; do not rename phases; do not invent sub-agent names.
After completion, emit:
✓ Phase N complete. <brief result>
This applies to every phase including the main-agent phases (0, 1, 2.5, 4, 5, 8, 10, 10.5, 11) and the repair loop.
Phase Envelope Contract
Every sub-agent input AND output envelope carries identity fields. They make phase-skipping mechanically detectable and let the validator verify lineage.
Envelope identity fields
| Field | Type | Set by | Purpose |
|---|---|---|---|
phase_id |
string, e.g., "phase-2", "phase-7-repair-1" |
dispatcher (main agent) on input; sub-agent echoes on output | Identifies which phase produced the envelope |
produced_by |
string, e.g., "frs-retriever" |
sub-agent on output | Identifies which sub-agent ran |
consumes_phase_id |
string (the prior phase's phase_id) or null |
dispatcher on input | Cites the primary upstream envelope this phase depends on |
consumes_secondary_phase_ids |
array of strings, or [] |
dispatcher on input | Cites any additional upstream envelopes (e.g., Phase 6 needs phase-3 clause text alongside phase-5 module assignments). Empty array when no secondary upstream exists. |
Per-phase canonical phase_id values
| Phase | phase_id |
Sub-agent (or main) |
consumes_phase_id (primary) |
consumes_secondary_phase_ids |
|---|---|---|---|---|
| 0 | phase-0 |
main | — | [] |
| 1 | phase-1 |
main | phase-0 |
[] |
| 2 | phase-2 |
frs-retriever |
phase-1 |
[] |
| 2.5 | phase-2.5 |
main | phase-2 |
[] |
| 3 | phase-3 |
clause-normalizer |
phase-2 |
[] |
| 4 | phase-4 |
main | phase-3 |
[] |
| 5 | phase-5 |
main | phase-4 |
[] |
| 6 | phase-6 |
clause-mapper |
phase-5 |
["phase-3"] (clause text) |
| 7 | phase-7 |
ddd-synthesizer |
phase-6 |
[] |
| 7-repair-N | phase-7-repair-<N> |
ddd-synthesizer (repair mode) |
phase-9 |
["phase-7"] (prior synthesis envelope) |
| 8 | phase-8 |
main | phase-7 |
[] |
| 9 | phase-9 |
feat-spec-validator |
phase-8 |
[] |
| 10 | phase-10 |
main | phase-9 |
[] |
| 10.5 | phase-10.5 |
main | phase-9 (validator's published path_regex_set + link_regex_set) |
["phase-7"] (file paths derived from synthesizer's node_entries) |
| 11 | phase-11 |
docs-writer (writes) + main (GitLab) |
phase-10.5 |
[] |
A sub-agent that expects a non-empty consumes_secondary_phase_ids MUST verify each listed phase is present (per envelope's phase_id field), and HALT per the rule below if any is missing or wrong.
Phase ID note (deferred): the fractional
phase-10.5ID is a deliberate choice to avoid disrupting external references that already cite phase numbers (e.g., thephase-7-repair-<N>envelope'sconsumes_phase_id: "phase-9"). Integer-only IDs would be friendlier to tooling that parses phase numbers as integers; a future major version may renumber (phase-10→phase-11, currentphase-10.5→phase-11, currentphase-11→phase-12). Until then, treatphase-10.5as an opaque string identifier.
Main-agent envelope shape
Main-agent phases (0, 1, 2.5, 4, 5, 8, 10, 10.5) do not invoke a sub-agent, but they still produce envelopes for downstream verification. The shape is:
{
"phase_id": "phase-<N>",
"produced_by": "main",
"consumes_phase_id": "<prior>",
"consumes_secondary_phase_ids": [],
...payload specific to the phase
}
For example, Phase 10.5's envelope contains the cleaned Path Manifest and is the immediate upstream for docs-writer (consumes_phase_id: "phase-10.5"). Sub-agents check the upstream phase regardless of whether produced_by is "main" or a sub-agent name.
Halt rule
If a sub-agent's input does NOT contain the expected consumes_phase_id (e.g., clause-normalizer is dispatched without a phase_id: "phase-2" envelope from frs-retriever), OR if any required entry from consumes_secondary_phase_ids is missing/wrong (e.g., clause-mapper invoked without "phase-3" in the secondary list), the sub-agent HALTS and returns:
{
"phase_id": "<expected phase_id>",
"produced_by": "<self>",
"halted": true,
"halt_reason": "missing upstream envelope: expected consumes_phase_id=<phase> [+ secondary=<phases>], got <actual>"
}
The main agent surfaces this to the user via AskUserQuestion(retry / cancel). Phase-skipping is not silently permitted.
This rule defeats the "single mega-agent does Phases 2–9 in one shot" failure mode observed in production runs.
The Process
Phase 0: Scope Preview
Read
gitlab_project_idfrom CLAUDE.md. Missing →AskUserQuestion.list_milestones(project_id).Match user-provided milestone. Ambiguous →
AskUserQuestion.get_milestone+get_milestone_issuefor count and titles.Do not call
get_issuehere. Preview content uses only theget_milestone_issueresponse. If titles aren't available, show IIDs only.AskUserQuestion:I'm about to process milestone containing N FRS issue(s). This will produce an estimated M–P DDD node files under your wiki and one short coordination issue in GitLab. Proceed?
Options:
proceed/change scope/cancel. Do not continue withoutproceed.
Phase 1: Configuration
- Read CLAUDE.md convention contract. Required missing →
AskUserQuestion. Emit one-line soft warning listing optional defaults used. - Compute milestone slug: kebab-case of title, strip punctuation, max 48 chars.
- Record user scope constraints. If AGENTS.md present, read naming conventions.
Phase 2: Retrieval (frs-retriever)
See agents/frs-retriever.md.
- Input:
project_id,milestone_id,gitlab_base_url, list of IIDs fromget_milestone_issue. - Tools (frs-retriever only):
get_issue,list_issue_links. - Parallel: ≥3 issues → parallel fetch.
- Returns: envelope per issue —
iid,title,source_type,monolith_signals,halt_flag,open_questions,body_text(inline),section_catalog.
Phase 2.5: Open-Questions Gate
If any non-halted issue has open_questions non-empty, halt before normalization and AskUserQuestion:
FRS issues have N unresolved open question(s). Resolving these first typically avoids conflicts downstream. How do you want to proceed?
Options: resolve-first (pause, return to FRS editing), continue-anyway (proceed; unresolved questions surface in Open Blockers as high), pause.
On resolve-first or pause: stop. On continue-anyway: record questions with source URLs for Phase 8 and Phase 10.
Phase 3: Normalization (clause-normalizer)
See agents/clause-normalizer.md. No GitLab MCP access.
- Input: non-halted issues (
body_textandsection_catalogfrom envelope), naming hints,tenancy_model. - Returns: structured clauses with
source_section_heading,source_anchor, classification.
Classification: ddd-mapped (maps to a DDD/ABP category), ui-integration (UI-API contract concern — screen→endpoint, field mapping, loading/error backend requirement), excluded (pure visual detail, wiki meta, sprint ritual).
Atomicity: one clause per distinct requirement intent — never merge multiple clauses into one narrative.
Phase 4: Context Resolution (main)
- Apply naming hints; do not alter clause intent.
- Tenancy:
tenancy_modeldefined → use it, annotate Entities. Absent AND bothTenantId+EntityIdin clauses → Conflictscoping_ambiguity, severityhigh. Absent AND onlyTenantId→ assignIMultiTenantnormally. - CLAUDE.md contradicting FRS → Conflict; FRS meaning wins.
Phase 5: Module Classification (main)
- Group clauses by business capability, aggregate boundary, integration area.
- Business-capability grouping, not UI-page.
- Tag cross-cutting concerns. Detect duplicate/overlapping intent.
- Every
ddd-mappedandui-integrationclause belongs to a named module. - Record module count for Phase 7 parallel dispatch decision.
Phase 6: Clause-to-Category Mapping (clause-mapper)
See agents/clause-mapper.md. No GitLab MCP access. intended_nodes is a prior, not a filter.
| If the clause… | Primary category |
|---|---|
| Identifies a participant | Actor |
| Describes persistent identity/lifecycle | Entity |
| Describes an immutable value structure | Value Object |
| Expresses a write action | Command |
| Retrieves data with no side effects | Query |
| Defines an ordered multi-step process | Flow |
| Constrains lifecycle transitions | State |
| Records an approach with trade-offs | Decision |
| Binds to an external system / ABP infra | Integration |
| Expresses topology or patterns | Architecture Blueprint |
| Is ambiguous or contradictory | Conflict |
| Concerns UI-API contract | UI-API Integration Points |
| Pure visual detail | exclusion ledger |
Queries are a distinct node type — never group under Commands. System actor permitted only when named as background job / scheduled task / event handler. Unmapped count must reach zero.
Phase 7: DDD/ABP Synthesis (ddd-synthesizer)
See agents/ddd-synthesizer.md. No GitLab MCP access.
Parallel by module when ≥2 modules. Single pass when 1 module (preserves Entity↔Command↔Query↔State consistency).
- Input per worker: module's clauses + Conflicts, all reference files, ABP catalogs, CLAUDE.md contract. (
body_textnot needed — clause text is sufficient.) - Returns per worker: envelope with node entries + partial Permissions Map + partial ABP Artifact Map + naming index.
Sub-agent enforces: ABP built-in check before Entity creation; base class per references/abp-base-classes.md; interfaces per tenancy + ISoftDelete / IHasConcurrencyStamp; domain events as Required or Optional / future integration hook; Commands with DTO inputs (PascalCase), **Validation:** referencing <CommandName>InputValidator per validation_library, domain events, **Audience:** if Public/Private declared; Queries with filter inputs, default sort per sorting_strategy, PagedAndSortedResultRequestDto, PagedResultDto<TDto> output, authorization, scoping, **Audience:**; Permissions Map rows per Actor + Command/Query, pattern per permissions_class; ABP Artifact Map across all six layers, namespaces from project_root_namespace, table prefix from db_table_prefix; **Source:** field on every entry with GitLab section-anchor links; Conflict filenames as title slugs.
Rejoin (main): concat per-node-type lists, merge Permissions Map and ABP Artifact Map, build combined naming index, detect cross-module collisions → Conflict + targeted repair if any.
Phase 8: Assembly (main)
See templates/feat-spec-template.md.
Section order:
- Feature Title
- Feature Overview
- Open Blockers (only if critical/high Conflicts)
- Related FRS
- Bounded Context and Affected Layers (references CLAUDE.md for full ABP layout — do not duplicate it)
- Domain Layer Design
- Application Layer Design (Commands, Queries, DTOs, Validators, Mappers)
- Infrastructure and Persistence Design
- HTTP API Design (Public/Private routing per CLAUDE.md)
- Permissions, Security, and Multi-Tenancy
- Integration, Background Jobs, and Distributed Events
- UI-API Integration Points (only if
ui-integrationclauses exist) - Error Handling, Auditing, and Logging
- Performance and Scalability
- Deployment Considerations
- Open Questions and Future Enhancements
All rendered links use wiki_url (no .md, no wiki_local_path prefix).
UI-API Integration Points (Section 12) — included only when ui-integration clauses exist. The UI prototype is the source of truth for visual design; this section documents what the backend must deliver. Sub-sections: screen-to-endpoint map; DTO field deviations (e.g., UI shows composed "display name", backend exposes FirstName + LastName); loading/error state backend requirements (pagination, polling intervals, partial responses — not visual rendering); gap analysis (data UI needs that no Command/Query produces — each becomes a Conflict missing_query or missing_command); prototype reference link. Excludes pure visual specs (colors, icons, toasts) and UI-internal routing.
Phase 9: Validation (feat-spec-validator)
See agents/feat-spec-validator.md.
- Input: assembled Feat Spec + DDD entries + merged Permissions Map + merged ABP Artifact Map + UI-API Integration Points + CLAUDE.md contract + expected file paths.
- Returns:
{passed, defects, defect_count_by_severity, readiness}.
Check categories: structural / content purity / ABP compliance / project convention compliance / section completeness / byte-length floors / required-field presence / UI-API Integration Points / wiki link format / Source field format / Conflict filename slug compliance / coord issue body / FRS integrity.
On passed: false: dispatch ddd-synthesizer in repair mode (targeted). After phase-7-repair-<N> completes, the main agent re-runs Phase 8 (assembly) to produce a fresh phase-8 envelope, then re-dispatches feat-spec-validator. The validator's consumes_phase_id halt rule is unaffected — it always expects phase-8. Loop until passed.
Phase 10: Preview Gate (main)
Present assembled Feat Spec preview.
Highlight blocking Conflicts.
List halted issues + split suggestions.
Show validation summary.
List CLAUDE.md convention defaults used.
Render the Path Manifest inline (the same manifest that will be re-validated mechanically in Phase 10.5). Show one row per file the pipeline plans to write, marking each as
✓ allowedor✗ rejected by <rule>. The user can see at a glance whether any path is non-compliant.If user chose
continue-anywayin Phase 2.5, list all unresolved open questions in Open Blockers — each with severityhighand FRS deep link.AskUserQuestion. The available options depend on the manifest:- Manifest is fully
✓ allowed(no F1–F5 hits and no L1–L3 hits):Approve this preview for formal publication? approve — write wiki files, create coord issue, link FRS revise — revision loop; provide feedback defer — keep preview only; no side effects - Manifest contains any
✗ rejected by <rule>row: theapproveoption is REMOVED. The question becomes:Path Manifest is non-compliant. Approval is blocked. revise — return to Phase 7 to regenerate the rejected paths defer — keep preview only; no side effects
This prevents users from approving a publish that Phase 10.5 will then reject — single point of decision.
- Manifest is fully
Phase 10.5: Path Manifest Gate (main)
Runs after the user approves Phase 10 and before any sub-agent dispatch in Phase 11. This is a mechanical, non-interactive gate — it does not call AskUserQuestion on success.
Shared regex set. Phase 10.5 MUST consume the link_regex_set and path_regex_set exposed on the Phase 9 (feat-spec-validator) output envelope verbatim. It MUST NOT independently re-compile path-contract.md's patterns; that would let the validator and gate drift. If those sets are missing on the upstream envelope, halt and AskUserQuestion(retry / cancel) — retry re-dispatches feat-spec-validator; this is a contract bug in the validator and SHOULD be fixed there rather than worked around here.
- Build the full manifest of files the pipeline intends to write. For each
node_entriesentry from the synthesizer envelope, compute the targetfilepathperreferences/path-contract.md§ 1. Add the Feat Spec'sfeat-specs/<slug>/feat-spec.mdentry. - Validate every computed
filepathagainstpath_regex_set(F1–F5) from the validator envelope. - Validate every rendered wiki link in the assembled Feat Spec body against
link_regex_set(L1–L3) from the validator envelope. - Emit the manifest:
→ Phase 10.5: Path Manifest — main agent Path Manifest (N files): ✓ docs/entities/ChecklistItem.md ✓ docs/commands/CreateChecklistItem.md ... Rendered links: M checked, all compliant. ✓ Phase 10.5 complete. Proceeding to Phase 11. - On any F1–F5 violation: emit
⚠ Phase 10.5 — N path violation(s):followed by per-violation lines<filepath> ← rejected by F<n>. Then halt andAskUserQuestion:
OnPath Manifest contains N forbidden path(s). Phase 11 cannot proceed. revise — return to Phase 7 to regenerate node filepaths cancel — stop the pipeline; nothing is writtenrevise: re-dispatchddd-synthesizerin repair mode withrepair_targets= the rejected files. Repair loop cap: 3 cycles. If the manifest is still non-compliant after the third repair cycle, emit the full failing manifest verbatim to chat (one line per file, with the offending rule for each) and halt with no side effects — Phase 11 is NOT entered. The user has the manifest in chat for forensics; nothing is written to disk. - On any L1–L3 violation: re-dispatch
feat-spec-validatorrepair loop targeting the assembled spec body — wiki link defects are content defects, not file-system defects. Same 3-cycle cap as the path repair loop above; on exhaustion, emit the failing links verbatim to chat and halt with no side effects. - The cleaned manifest is passed verbatim to
docs-writeras the authoritative file list.
Why this phase exists: the same nested-path violation occurred in 3/3 captured runs, despite the rule being stated in path-contract.md. Phase 10.5 is the mechanical checkpoint — docs-writer re-validates each path on receipt as a defense-in-depth measure, but the user-visible halt happens here.
Phase 11: Publish (post-approval only)
Order is mandatory: wiki files first, then GitLab.
- Dispatch
docs-writerwith the cleaned Path Manifest from Phase 10.5. All DDD node files and the Feat Spec are written bydocs-writeronly.docs-writerMUST re-validate eachfilepathagainstpath-contract.md§ 2 (F1–F5) and refuse any non-compliant write. Direct calls toWrite,Edit,serena,Bash(mkdir/cp/cat/python/echo/heredoc), or any other file-write surface are forbidden in Phase 11 — including indirect file writes via genericAgent(...)dispatches that do not namedocs-writeras theirsubagent_type. - Conflict filenames use the title-derived slug per
path-contract.md§ 2 rule F4 (never internal IDs). - Verify all expected files exist on disk: DDD node files at
<wiki_local_path>/<node-type>/<NodeName>.md(sibling layout) and the Feat Spec at<wiki_local_path>/feat-specs/<slug>/feat-spec.md. Missing or unexpected location → abort before any GitLab side effect. - Post-write path audit (defense-in-depth, main agent): walk the on-disk tree under
<wiki_local_path>and assert no file exists at a forbidden location, using thepath_regex_setexposed on the Phase 9 envelope (F1–F5). Any hit → halt; report; do not proceed to GitLab. - Duplicate check:
list_issues(project_id, milestone_id); match by title. If match,AskUserQuestion. - Create coordination issue per
templates/coord-issue-template.md. Title:[FEAT] <Milestone> — <Title>. Body: summary + canonical wiki URL + FRS IIDs + Open Blockers (critical/high only). - Link FRS issues: for each FRS,
list_issue_links; existing link → skip; otherwisecreate_issue_link(project_id, frs_iid, feat_spec_iid, link_type="relates_to"). The skill's final summary MUST list eachcreate_issue_linkcall (orskipped — already linked) per FRS — generic phrasing like "FRS references documented" is non-compliant. - Never
update_issueon FRS. - Verify end-state; report failures; stop on any failure.
Handling Outcomes
| Outcome | Action |
|---|---|
| PREVIEWABLE | Proceed to Phase 10. |
| NEEDS_REPAIR | Targeted re-synthesis; re-validate. |
| MILESTONE_NOT_FOUND | Stop. list_milestones + AskUserQuestion. |
| EMPTY_MILESTONE | Stop. Empty-scope report. |
| ALL_ISSUES_HALTED | Stop. List halted + splits. |
| MONOLITH_DETECTED (partial) | Continue non-halted; surface halted. |
| CONFLICT_ESCALATION | Promote to Conflicts; surface in Open Blockers. |
| NAMING_COLLISION_ACROSS_MODULES | Targeted repair on affected modules. |
| USER_REVISION_REQUESTED | Collect feedback; return to Phase 7 or 8. |
| APPROVAL_DEFERRED | No side |
…(truncated)