Faceted Prompting 導入: knowledge セクション追加、スキルドキュメント刷新、コマンドファイル廃止

This commit is contained in:
nrslib 2026-02-07 17:53:39 +09:00
parent ea7ce54912
commit 247b500449
40 changed files with 2613 additions and 797 deletions

View File

@ -787,6 +787,7 @@ export TAKT_OPENAI_API_KEY=sk-...
## Documentation
- [Faceted Prompting](./docs/prompt-composition.md) - Separation of Concerns for AI prompts (Persona, Stance, Instruction, Knowledge, Report Format)
- [Piece Guide](./docs/pieces.md) - Creating and customizing pieces
- [Agent Guide](./docs/agents.md) - Configuring custom agents
- [Changelog](../CHANGELOG.md) - Version history

View File

@ -0,0 +1,485 @@
# Backend Expertise
## Hexagonal Architecture (Ports and Adapters)
Dependency direction flows from outer to inner layers. Reverse dependencies are prohibited.
```
adapter (external) → application (use cases) → domain (business logic)
```
Directory structure:
```
{domain-name}/
├── domain/ # Domain layer (framework-independent)
│ ├── model/
│ │ └── aggregate/ # Aggregate roots, value objects
│ └── service/ # Domain services
├── application/ # Application layer (use cases)
│ ├── usecase/ # Orchestration
│ └── query/ # Query handlers
├── adapter/ # Adapter layer (external connections)
│ ├── inbound/ # Input adapters
│ │ └── rest/ # REST Controller, Request/Response DTOs
│ └── outbound/ # Output adapters
│ └── persistence/ # Entity, Repository implementations
└── api/ # Public interface (referenceable by other domains)
└── events/ # Domain events
```
Layer responsibilities:
| Layer | Responsibility | May Depend On | Must Not Depend On |
|-------|---------------|---------------|-------------------|
| domain | Business logic, invariants | Standard library only | Frameworks, DB, external APIs |
| application | Use case orchestration | domain | Concrete adapter implementations |
| adapter/inbound | HTTP request handling, DTO conversion | application, domain | outbound adapter |
| adapter/outbound | DB persistence, external API calls | domain (interfaces) | application |
```kotlin
// CORRECT - Domain layer is framework-independent
data class Order(val orderId: String, val status: OrderStatus) {
fun confirm(confirmedBy: String): OrderConfirmedEvent {
require(status == OrderStatus.PENDING)
return OrderConfirmedEvent(orderId, confirmedBy)
}
}
// WRONG - Spring annotations in domain layer
@Entity
data class Order(
@Id val orderId: String,
@Enumerated(EnumType.STRING) val status: OrderStatus
) {
fun confirm(confirmedBy: String) { ... }
}
```
| Criteria | Judgment |
|----------|----------|
| Framework dependencies in domain layer (@Entity, @Component, etc.) | REJECT |
| Controller directly referencing Repository | REJECT. Must go through UseCase layer |
| Outward dependencies from domain layer (DB, HTTP, etc.) | REJECT |
| Direct dependencies between adapters (inbound → outbound) | REJECT |
## API Layer Design (Controller)
Keep Controllers thin. Their only job: receive request → delegate to UseCase → return response.
```kotlin
// CORRECT - Thin Controller
@RestController
@RequestMapping("/api/orders")
class OrdersController(
private val placeOrderUseCase: PlaceOrderUseCase,
private val queryGateway: QueryGateway
) {
// Command: state change
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun post(@Valid @RequestBody request: OrderPostRequest): OrderPostResponse {
val output = placeOrderUseCase.execute(request.toInput())
return OrderPostResponse(output.orderId)
}
// Query: read
@GetMapping("/{id}")
fun get(@PathVariable id: String): ResponseEntity<OrderGetResponse> {
val detail = queryGateway.query(FindOrderQuery(id), OrderDetail::class.java).join()
?: return ResponseEntity.notFound().build()
return ResponseEntity.ok(OrderGetResponse.from(detail))
}
}
// WRONG - Business logic in Controller
@PostMapping
fun post(@RequestBody request: OrderPostRequest): ResponseEntity<Any> {
// Validation, stock check, calculation... should NOT be in Controller
val stock = inventoryRepository.findByProductId(request.productId)
if (stock.quantity < request.quantity) {
return ResponseEntity.badRequest().body("Insufficient stock")
}
val total = request.quantity * request.unitPrice * 1.1 // Tax calculation
orderRepository.save(OrderEntity(...))
return ResponseEntity.ok(...)
}
```
### Request/Response DTO Design
Define Request and Response as separate types. Never expose domain models directly via API.
```kotlin
// Request: validation annotations + init block
data class OrderPostRequest(
@field:NotBlank val customerId: String,
@field:NotNull val items: List<OrderItemRequest>
) {
init {
require(items.isNotEmpty()) { "Order must contain at least one item" }
}
fun toInput() = PlaceOrderInput(customerId = customerId, items = items.map { it.toItem() })
}
// Response: factory method from() for conversion
data class OrderGetResponse(
val orderId: String,
val status: String,
val customerName: String
) {
companion object {
fun from(detail: OrderDetail) = OrderGetResponse(
orderId = detail.orderId,
status = detail.status.name,
customerName = detail.customerName
)
}
}
```
| Criteria | Judgment |
|----------|----------|
| Returning domain model directly as response | REJECT |
| Business logic in Request DTO | REJECT. Only validation is allowed |
| Domain logic (calculations, etc.) in Response DTO | REJECT |
| Same type for Request and Response | REJECT |
### RESTful Action Design
Express state transitions as verb sub-resources.
```
POST /api/orders → Create order
GET /api/orders/{id} → Get order
GET /api/orders → List orders
POST /api/orders/{id}/approve → Approve (state transition)
POST /api/orders/{id}/cancel → Cancel (state transition)
```
| Criteria | Judgment |
|----------|----------|
| PUT/PATCH for domain operations (approve, cancel, etc.) | REJECT. Use POST + verb sub-resource |
| Single endpoint branching into multiple operations | REJECT. Separate endpoints per operation |
| DELETE for soft deletion | REJECT. Use POST + explicit operation like cancel |
## Validation Strategy
Validation has different roles at each layer. Do not centralize everything in one place.
| Layer | Responsibility | Mechanism | Example |
|-------|---------------|-----------|---------|
| API layer | Structural validation | `@NotBlank`, `init` block | Required fields, types, format |
| UseCase layer | Business rule verification | Read Model queries | Duplicate checks, precondition existence |
| Domain layer | State transition invariants | `require` | "Cannot approve unless PENDING" |
```kotlin
// API layer: "Is the input structurally correct?"
data class OrderPostRequest(
@field:NotBlank val customerId: String,
val from: LocalDateTime,
val to: LocalDateTime
) {
init {
require(!to.isBefore(from)) { "End date must be on or after start date" }
}
}
// UseCase layer: "Is this business-wise allowed?" (Read Model reference)
fun execute(input: PlaceOrderInput) {
customerRepository.findById(input.customerId)
?: throw CustomerNotFoundException("Customer does not exist")
validateNoOverlapping(input) // Duplicate check
commandGateway.send(buildCommand(input))
}
// Domain layer: "Is this operation allowed in current state?"
fun confirm(confirmedBy: String): OrderConfirmedEvent {
require(status == OrderStatus.PENDING) { "Cannot confirm in current state" }
return OrderConfirmedEvent(orderId, confirmedBy)
}
```
| Criteria | Judgment |
|----------|----------|
| Domain state transition rules in API layer | REJECT |
| Business rule verification in Controller | REJECT. Belongs in UseCase layer |
| Structural validation (@NotBlank, etc.) in domain | REJECT. Belongs in API layer |
| UseCase-level validation inside Aggregate | REJECT. Read Model queries belong in UseCase layer |
## Error Handling
### Exception Hierarchy Design
Domain exceptions are hierarchized using sealed classes. HTTP status code mapping is done at the Controller layer.
```kotlin
// Domain exceptions: sealed class ensures exhaustiveness
sealed class OrderException(message: String) : RuntimeException(message)
class OrderNotFoundException(message: String) : OrderException(message)
class InvalidOrderStateException(message: String) : OrderException(message)
class InsufficientStockException(message: String) : OrderException(message)
// Controller layer maps to HTTP status codes
@RestControllerAdvice
class OrderExceptionHandler {
@ExceptionHandler(OrderNotFoundException::class)
fun handleNotFound(e: OrderNotFoundException) =
ResponseEntity.status(HttpStatus.NOT_FOUND).body(ErrorResponse(e.message))
@ExceptionHandler(InvalidOrderStateException::class)
fun handleInvalidState(e: InvalidOrderStateException) =
ResponseEntity.status(HttpStatus.CONFLICT).body(ErrorResponse(e.message))
@ExceptionHandler(InsufficientStockException::class)
fun handleInsufficientStock(e: InsufficientStockException) =
ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(ErrorResponse(e.message))
}
```
| Criteria | Judgment |
|----------|----------|
| HTTP status codes in domain exceptions | REJECT. Domain must not know about HTTP |
| Throwing generic Exception or RuntimeException | REJECT. Use specific exception types |
| Empty try-catch blocks | REJECT |
| Controller swallowing exceptions and returning 200 | REJECT |
## Domain Model Design
### Immutable + require
Domain models are designed as `data class` (immutable), with invariants enforced via `init` blocks and `require`.
```kotlin
data class Order(
val orderId: String,
val status: OrderStatus = OrderStatus.PENDING
) {
// Static factory method via companion object
companion object {
fun place(orderId: String, customerId: String): OrderPlacedEvent {
require(customerId.isNotBlank()) { "Customer ID cannot be blank" }
return OrderPlacedEvent(orderId, customerId)
}
}
// Instance method for state transition → returns event
fun confirm(confirmedBy: String): OrderConfirmedEvent {
require(status == OrderStatus.PENDING) { "Cannot confirm in current state" }
return OrderConfirmedEvent(orderId, confirmedBy, LocalDateTime.now())
}
// Immutable state update
fun apply(event: OrderEvent): Order = when (event) {
is OrderPlacedEvent -> Order(orderId = event.orderId)
is OrderConfirmedEvent -> copy(status = OrderStatus.CONFIRMED)
is OrderCancelledEvent -> copy(status = OrderStatus.CANCELLED)
}
}
```
| Criteria | Judgment |
|----------|----------|
| `var` fields in domain model | REJECT. Use `copy()` for immutable updates |
| Factory without validation | REJECT. Enforce invariants with `require` |
| Domain model calling external services | REJECT. Pure functions only |
| Direct field mutation via setters | REJECT |
### Value Objects
Wrap primitive types (String, Int) with domain meaning.
```kotlin
// ID types: prevent mix-ups via type safety
data class OrderId(@get:JsonValue val value: String) {
init { require(value.isNotBlank()) { "Order ID cannot be blank" } }
override fun toString(): String = value
}
// Range types: enforce compound invariants
data class DateRange(val from: LocalDateTime, val to: LocalDateTime) {
init { require(!to.isBefore(from)) { "End date must be on or after start date" } }
}
// Metadata types: ancillary information in event payloads
data class ApprovalInfo(val approvedBy: String, val approvalTime: LocalDateTime)
```
| Criteria | Judgment |
|----------|----------|
| Same-typed IDs that can be mixed up (orderId and customerId both String) | Consider wrapping in value objects |
| Same field combinations (from/to, etc.) appearing in multiple places | Extract to value object |
| Value object without init block | REJECT. Enforce invariants |
## Repository Pattern
Define interface in domain layer, implement in adapter/outbound.
```kotlin
// domain/: Interface (port)
interface OrderRepository {
fun findById(orderId: String): Order?
fun save(order: Order)
}
// adapter/outbound/persistence/: Implementation (adapter)
@Repository
class JpaOrderRepository(
private val jpaRepository: OrderJpaRepository
) : OrderRepository {
override fun findById(orderId: String): Order? {
return jpaRepository.findById(orderId).orElse(null)?.toDomain()
}
override fun save(order: Order) {
jpaRepository.save(OrderEntity.from(order))
}
}
```
### Read Model Entity (JPA Entity)
Read Model JPA Entities are defined separately from domain models. `var` (mutable) fields are acceptable here.
```kotlin
@Entity
@Table(name = "orders")
data class OrderEntity(
@Id val orderId: String,
var customerId: String,
@Enumerated(EnumType.STRING) var status: OrderStatus,
var metadata: String? = null
)
```
| Criteria | Judgment |
|----------|----------|
| Domain model doubling as JPA Entity | REJECT. Separate them |
| Business logic in Entity | REJECT. Entity is data structure only |
| Repository implementation in domain layer | REJECT. Belongs in adapter/outbound |
## Authentication & Authorization Placement
Authentication and authorization are cross-cutting concerns handled at the appropriate layer.
| Concern | Placement | Mechanism |
|---------|-----------|-----------|
| Authentication (who) | Filter / Interceptor layer | JWT verification, session validation |
| Authorization (permissions) | Controller layer | `@PreAuthorize("hasRole('ADMIN')")` |
| Data access control (own data only) | UseCase layer | Verified as business rule |
```kotlin
// Controller layer: role-based authorization
@PostMapping("/{id}/approve")
@PreAuthorize("hasRole('FACILITY_ADMIN')")
fun approve(@PathVariable id: String, @RequestBody request: ApproveRequest) { ... }
// UseCase layer: data access control
fun execute(input: DeleteInput, currentUserId: String) {
val entity = repository.findById(input.id)
?: throw NotFoundException("Not found")
require(entity.ownerId == currentUserId) { "Cannot operate on another user's data" }
// ...
}
```
| Criteria | Judgment |
|----------|----------|
| Authorization logic in UseCase or domain layer | REJECT. Belongs in Controller layer |
| Data access control in Controller | REJECT. Belongs in UseCase layer |
| Authentication processing inside Controller | REJECT. Belongs in Filter/Interceptor |
## Test Strategy
### Test Pyramid
```
┌─────────────┐
│ E2E Test │ ← Few: verify full API flow
├─────────────┤
│ Integration │ ← Repository, Controller integration verification
├─────────────┤
│ Unit Test │ ← Many: independent tests for domain models, UseCases
└─────────────┘
```
### Domain Model Testing
Domain models are framework-independent, enabling pure unit tests.
```kotlin
class OrderTest {
// Helper: build aggregate in specific state
private fun pendingOrder(): Order {
val event = Order.place("order-1", "customer-1")
return Order.from(event)
}
@Nested
inner class Confirm {
@Test
fun `can confirm from PENDING state`() {
val order = pendingOrder()
val event = order.confirm("admin-1")
assertEquals("order-1", event.orderId)
}
@Test
fun `cannot confirm from CONFIRMED state`() {
val order = pendingOrder().let { it.apply(it.confirm("admin-1")) }
assertThrows<IllegalArgumentException> {
order.confirm("admin-2")
}
}
}
}
```
Testing rules:
- Build state transitions via helper methods (each test is independent)
- Group by operation using `@Nested`
- Test both happy path and error cases (invalid state transitions)
- Verify exception types with `assertThrows`
### UseCase Testing
Test UseCases with mocks. Inject external dependencies.
```kotlin
class PlaceOrderUseCaseTest {
private val commandGateway = mockk<CommandGateway>()
private val customerRepository = mockk<CustomerRepository>()
private val useCase = PlaceOrderUseCase(commandGateway, customerRepository)
@Test
fun `throws error when customer does not exist`() {
every { customerRepository.findById("unknown") } returns null
assertThrows<CustomerNotFoundException> {
useCase.execute(PlaceOrderInput(customerId = "unknown", items = listOf(...)))
}
}
}
```
| Criteria | Judgment |
|----------|----------|
| Using mocks for domain model tests | REJECT. Test domain purely |
| UseCase tests connecting to real DB | REJECT. Use mocks |
| Tests requiring framework startup | REJECT for unit tests |
| Missing error case tests for state transitions | REJECT |
## Anti-Pattern Detection
REJECT when these patterns are found:
| Anti-Pattern | Problem |
|--------------|---------|
| Smart Controller | Business logic concentrated in Controller |
| Anemic Domain Model | Domain model is just a data structure with setters/getters |
| God Service | All operations concentrated in a single Service class |
| Direct Repository Access | Controller directly referencing Repository |
| Domain Leakage | Domain logic leaking into adapter layer |
| Entity Reuse | JPA Entity reused as domain model |
| Swallowed Exceptions | Empty catch blocks |
| Magic Strings | Hardcoded status strings, etc. |

View File

@ -1,46 +1,38 @@
piece_categories:
"🚀 Quick Start":
🚀 Quick Start:
pieces:
- default
- passthrough
- coding
- minimal
"🔍 Review & Fix":
🔍 Review & Fix:
pieces:
- review-fix-minimal
"🎨 Frontend":
{}
"⚙️ Backend":
{}
"🔧 Expert":
"Full Stack":
🎨 Frontend: {}
⚙️ Backend: {}
🔧 Expert:
Full Stack:
pieces:
- expert
- expert-cqrs
"🔀 Hybrid (Codex Coding)":
"🚀 Quick Start":
🔀 Hybrid (Codex Coding):
🚀 Quick Start:
pieces:
- coding-hybrid-codex
- default-hybrid-codex
- passthrough-hybrid-codex
- minimal-hybrid-codex
"🔍 Review & Fix":
- passthrough-hybrid-codex
🔧 Expert:
pieces:
- expert-cqrs-hybrid-codex
- expert-hybrid-codex
🔍 Review & Fix:
pieces:
- review-fix-minimal-hybrid-codex
"🔧 Expert":
pieces:
- expert-hybrid-codex
- expert-cqrs-hybrid-codex
"Others":
Others:
pieces:
- research
- magi
- review-only
show_others_category: true
others_category_name: "Others"
others_category_name: Others

View File

@ -1,62 +1,31 @@
# Coding TAKT Piece
# Plan -> Implement -> Parallel Review (AI + Architecture) -> Fix if needed
#
# Lightweight development piece with planning and parallel reviews.
# architect-planner investigates and organizes requirements, resolving unknowns by reading code.
# After parallel reviews, completes directly if no issues, enabling fast feedback loops.
#
# Flow:
# plan (requirements investigation & planning)
# ↓
# implement (implementation)
# ↓
# reviewers (parallel review)
# ├─ ai_review (AI-specific issue detection)
# └─ arch-review (design compliance check)
# ↓
# [judgment]
# ├─ all(approved) → COMPLETE
# └─ any(needs_fix) → fix → reviewers (re-review)
#
# Template Variables (auto-injected by buildInstruction):
# {iteration} - Piece-wide turn count (total movements executed across all agents)
# {max_iterations} - Maximum iterations allowed for the piece
# {movement_iteration} - Per-movement iteration count (how many times THIS movement has been executed)
# {task} - Original user request
# {previous_response} - Output from the previous movement
# {user_inputs} - Accumulated user inputs during piece
# {report_dir} - Report directory name (e.g., "20250126-143052-task-summary")
# Auto-generated from coding.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: coding-hybrid-codex
description: Lightweight development piece with planning and parallel reviews (plan -> implement -> parallel review -> complete)
max_iterations: 20
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
knowledge:
architecture: ../knowledge/architecture.md
personas:
architect-planner: ../personas/architect-planner.md
coder: ../personas/coder.md
ai-antipattern-reviewer: ../personas/ai-antipattern-reviewer.md
architecture-reviewer: ../personas/architecture-reviewer.md
instructions:
plan: ../instructions/plan.md
implement: ../instructions/implement.md
ai-review: ../instructions/ai-review.md
review-arch: ../instructions/review-arch.md
fix: ../instructions/fix.md
report_formats:
plan: ../report-formats/plan.md
ai-review: ../report-formats/ai-review.md
architecture-review: ../report-formats/architecture-review.md
initial_movement: plan
movements:
- name: plan
edit: false
@ -79,15 +48,15 @@ movements:
- condition: Requirements are unclear, insufficient information
next: ABORT
instruction: plan
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge: architecture
report:
- Scope: 02-coder-scope.md
- Decisions: 03-coder-decisions.md
@ -113,7 +82,6 @@ movements:
requires_user_input: true
interactive_only: true
instruction: implement
- name: reviewers
parallel:
- name: ai_review
@ -133,11 +101,11 @@ movements:
- condition: No AI-specific issues
- condition: AI-specific issues found
instruction: ai-review
- name: arch-review
edit: false
persona: architecture-reviewer
stance: review
knowledge: architecture
report:
name: 05-architect-review.md
format: architecture-review
@ -151,20 +119,19 @@ movements:
- condition: approved
- condition: needs_fix
instruction: review-arch
rules:
- condition: all("No AI-specific issues", "approved")
next: COMPLETE
- condition: any("AI-specific issues found", "needs_fix")
next: fix
- name: fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge: architecture
allowed_tools:
- Read
- Glob

View File

@ -37,6 +37,9 @@ stances:
review: ../stances/review.md
testing: ../stances/testing.md
knowledge:
architecture: ../knowledge/architecture.md
personas:
architect-planner: ../personas/architect-planner.md
coder: ../personas/coder.md
@ -87,6 +90,7 @@ movements:
- coding
- testing
session: refresh
knowledge: architecture
report:
- Scope: 02-coder-scope.md
- Decisions: 03-coder-decisions.md
@ -137,6 +141,7 @@ movements:
edit: false
persona: architecture-reviewer
stance: review
knowledge: architecture
report:
name: 05-architect-review.md
format: architecture-review
@ -163,6 +168,7 @@ movements:
stance:
- coding
- testing
knowledge: architecture
allowed_tools:
- Read
- Glob

View File

@ -1,34 +1,16 @@
# Default TAKT Piece
# Plan -> Architect -> Implement -> AI Review -> Reviewers (parallel: Architect + QA) -> Supervisor Approval
#
# Boilerplate sections (Piece Context, User Request, Previous Response,
# Additional User Inputs, Instructions heading) are auto-injected by buildInstruction().
# Only movement-specific content belongs in instruction_template.
#
# Template Variables (available in instruction_template):
# {iteration} - Piece-wide turn count (total movements executed across all agents)
# {max_iterations} - Maximum iterations allowed for the piece
# {movement_iteration} - Per-movement iteration count (how many times THIS movement has been executed)
# {previous_response} - Output from the previous movement (only when pass_previous_response: true)
# {report_dir} - Report directory name (e.g., "20250126-143052-task-summary")
#
# Movement-level Fields:
# report: - Report file(s) for the movement (auto-injected as Report File/Files in Piece Context)
# Single: report: 00-plan.md
# Multiple: report:
# - Scope: 01-coder-scope.md
# - Decisions: 02-coder-decisions.md
# Auto-generated from default.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: default-hybrid-codex
description: Standard development piece with planning and specialized reviews
max_iterations: 30
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
knowledge:
backend: ../knowledge/backend.md
architecture: ../knowledge/architecture.md
personas:
planner: ../personas/planner.md
architect-planner: ../personas/architect-planner.md
@ -37,7 +19,6 @@ personas:
architecture-reviewer: ../personas/architecture-reviewer.md
qa-reviewer: ../personas/qa-reviewer.md
supervisor: ../personas/supervisor.md
instructions:
plan: ../instructions/plan.md
architect: ../instructions/architect.md
@ -49,7 +30,6 @@ instructions:
review-qa: ../instructions/review-qa.md
fix: ../instructions/fix.md
supervise: ../instructions/supervise.md
report_formats:
plan: ../report-formats/plan.md
architecture-design: ../report-formats/architecture-design.md
@ -58,11 +38,11 @@ report_formats:
qa-review: ../report-formats/qa-review.md
validation: ../report-formats/validation.md
summary: ../report-formats/summary.md
initial_movement: plan
loop_monitors:
- cycle: [ai_review, ai_fix]
- cycle:
- ai_review
- ai_fix
threshold: 3
judge:
persona: supervisor
@ -84,7 +64,6 @@ loop_monitors:
next: ai_review
- condition: Unproductive (no improvement)
next: reviewers
movements:
- name: plan
edit: false
@ -111,7 +90,6 @@ movements:
- {Question 1}
- {Question 2}
instruction: plan
- name: architect
edit: false
persona: architect-planner
@ -132,15 +110,17 @@ movements:
- condition: Insufficient info, cannot proceed
next: ABORT
instruction: architect
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- backend
- architecture
report:
- Scope: 02-coder-scope.md
- Decisions: 03-coder-decisions.md
@ -166,7 +146,6 @@ movements:
requires_user_input: true
interactive_only: true
instruction: implement
- name: ai_review
edit: false
persona: ai-antipattern-reviewer
@ -186,15 +165,17 @@ movements:
- condition: AI-specific issues found
next: ai_fix
instruction: ai-review
- name: ai_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- backend
- architecture
allowed_tools:
- Read
- Glob
@ -213,7 +194,6 @@ movements:
- condition: Cannot proceed, insufficient info
next: ai_no_fix
instruction: ai-fix
- name: ai_no_fix
edit: false
persona: architecture-reviewer
@ -228,13 +208,15 @@ movements:
- condition: ai_fix's judgment is valid (no fix needed)
next: reviewers
instruction: arbitrate
- name: reviewers
parallel:
- name: arch-review
edit: false
persona: architecture-reviewer
stance: review
knowledge:
- architecture
- backend
report:
name: 05-architect-review.md
format: architecture-review
@ -248,7 +230,6 @@ movements:
- condition: approved
- condition: needs_fix
instruction: review-arch
- name: qa-review
edit: false
persona: qa-reviewer
@ -271,14 +252,16 @@ movements:
next: supervise
- condition: any("needs_fix")
next: fix
- name: fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge:
- backend
- architecture
allowed_tools:
- Read
- Glob
@ -295,7 +278,6 @@ movements:
- condition: Cannot proceed, insufficient info
next: plan
instruction: fix
- name: supervise
edit: false
persona: supervisor

View File

@ -21,6 +21,7 @@ stances:
testing: ../stances/testing.md
knowledge:
backend: ../knowledge/backend.md
architecture: ../knowledge/architecture.md
personas:
@ -134,6 +135,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [backend, architecture]
report:
- Scope: 02-coder-scope.md
- Decisions: 03-coder-decisions.md
@ -187,6 +189,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [backend, architecture]
allowed_tools:
- Read
- Glob
@ -227,7 +230,7 @@ movements:
edit: false
persona: architecture-reviewer
stance: review
knowledge: architecture
knowledge: [architecture, backend]
report:
name: 05-architect-review.md
format: architecture-review
@ -271,6 +274,7 @@ movements:
stance:
- coding
- testing
knowledge: [backend, architecture]
allowed_tools:
- Read
- Glob

View File

@ -1,33 +1,19 @@
# Expert CQRS Review Piece
# Review piece with CQRS+ES, Frontend, Security, and QA experts
#
# Flow:
# plan -> implement -> ai_review -> reviewers (parallel) -> supervise -> COMPLETE
# ↓ ├─ cqrs-es-review ↓
# ai_fix ├─ frontend-review fix_supervisor
# ├─ security-review
# └─ qa-review
# any("needs_fix") → fix → reviewers
#
# Template Variables:
# {iteration} - Piece-wide turn count (total movements executed across all agents)
# {max_iterations} - Maximum iterations allowed for the piece
# {movement_iteration} - Per-movement iteration count (how many times THIS movement has been executed)
# {task} - Original user request
# {previous_response} - Output from the previous movement
# {user_inputs} - Accumulated user inputs during piece
# {report_dir} - Report directory name (e.g., "20250126-143052-task-summary")
# Auto-generated from expert-cqrs.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: expert-cqrs-hybrid-codex
description: CQRS+ES, Frontend, Security, QA Expert Review
max_iterations: 30
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
knowledge:
frontend: ../knowledge/frontend.md
backend: ../knowledge/backend.md
cqrs-es: ../knowledge/cqrs-es.md
security: ../knowledge/security.md
architecture: ../knowledge/architecture.md
personas:
planner: ../personas/planner.md
coder: ../personas/coder.md
@ -38,7 +24,6 @@ personas:
security-reviewer: ../personas/security-reviewer.md
qa-reviewer: ../personas/qa-reviewer.md
expert-supervisor: ../personas/expert-supervisor.md
instructions:
plan: ../instructions/plan.md
implement: ../instructions/implement.md
@ -52,7 +37,6 @@ instructions:
fix: ../instructions/fix.md
supervise: ../instructions/supervise.md
fix-supervisor: ../instructions/fix-supervisor.md
report_formats:
plan: ../report-formats/plan.md
ai-review: ../report-formats/ai-review.md
@ -62,13 +46,8 @@ report_formats:
qa-review: ../report-formats/qa-review.md
validation: ../report-formats/validation.md
summary: ../report-formats/summary.md
initial_movement: plan
movements:
# ===========================================
# Movement 0: Planning
# ===========================================
- name: plan
edit: false
persona: planner
@ -88,18 +67,20 @@ movements:
next: implement
- condition: Requirements are unclear and planning cannot proceed
next: ABORT
# ===========================================
# Movement 1: Implementation
# ===========================================
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- frontend
- backend
- cqrs-es
- security
- architecture
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -124,10 +105,6 @@ movements:
next: implement
requires_user_input: true
interactive_only: true
# ===========================================
# Movement 2: AI Review
# ===========================================
- name: ai_review
edit: false
persona: ai-antipattern-reviewer
@ -147,15 +124,20 @@ movements:
next: reviewers
- condition: AI-specific issues detected
next: ai_fix
- name: ai_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- frontend
- backend
- cqrs-es
- security
- architecture
allowed_tools:
- Read
- Glob
@ -173,7 +155,6 @@ movements:
next: ai_no_fix
- condition: Unable to proceed with fixes
next: ai_no_fix
- name: ai_no_fix
edit: false
persona: architecture-reviewer
@ -188,16 +169,15 @@ movements:
- condition: ai_fix's judgment is valid (no fix needed)
next: reviewers
instruction: arbitrate
# ===========================================
# Movement 3: Expert Reviews (Parallel)
# ===========================================
- name: reviewers
parallel:
- name: cqrs-es-review
edit: false
persona: cqrs-es-reviewer
stance: review
knowledge:
- cqrs-es
- backend
report:
name: 04-cqrs-es-review.md
format: cqrs-es-review
@ -205,18 +185,17 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-cqrs-es
- name: frontend-review
edit: false
persona: frontend-reviewer
stance: review
knowledge: frontend
report:
name: 05-frontend-review.md
format: frontend-review
@ -224,18 +203,17 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-frontend
- name: security-review
edit: false
persona: security-reviewer
stance: review
knowledge: security
report:
name: 06-security-review.md
format: security-review
@ -243,14 +221,12 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-security
- name: qa-review
edit: false
persona: qa-reviewer
@ -262,7 +238,6 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
@ -274,14 +249,19 @@ movements:
next: supervise
- condition: any("needs_fix")
next: fix
- name: fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge:
- frontend
- backend
- cqrs-es
- security
- architecture
allowed_tools:
- Read
- Glob
@ -298,10 +278,6 @@ movements:
- condition: Cannot proceed, insufficient info
next: plan
instruction: fix
# ===========================================
# Movement 4: Supervision
# ===========================================
- name: supervise
edit: false
persona: expert-supervisor
@ -321,14 +297,19 @@ movements:
next: COMPLETE
- condition: Issues detected during final review
next: fix_supervisor
- name: fix_supervisor
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge:
- frontend
- backend
- cqrs-es
- security
- architecture
allowed_tools:
- Read
- Glob

View File

@ -30,8 +30,10 @@ stances:
knowledge:
frontend: ../knowledge/frontend.md
backend: ../knowledge/backend.md
cqrs-es: ../knowledge/cqrs-es.md
security: ../knowledge/security.md
architecture: ../knowledge/architecture.md
personas:
planner: ../personas/planner.md
@ -104,6 +106,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [frontend, backend, cqrs-es, security, architecture]
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -159,6 +162,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [frontend, backend, cqrs-es, security, architecture]
allowed_tools:
- Read
- Glob
@ -201,7 +205,7 @@ movements:
edit: false
persona: cqrs-es-reviewer
stance: review
knowledge: cqrs-es
knowledge: [cqrs-es, backend]
report:
name: 04-cqrs-es-review.md
format: cqrs-es-review
@ -287,6 +291,7 @@ movements:
stance:
- coding
- testing
knowledge: [frontend, backend, cqrs-es, security, architecture]
allowed_tools:
- Read
- Glob
@ -333,6 +338,7 @@ movements:
stance:
- coding
- testing
knowledge: [frontend, backend, cqrs-es, security, architecture]
allowed_tools:
- Read
- Glob

View File

@ -1,45 +1,18 @@
# Expert Review Piece
# Review piece with Architecture, Frontend, Security, and QA experts
#
# Flow:
# plan -> implement -> ai_review -> reviewers (parallel) -> supervise -> COMPLETE
# ↓ ├─ arch-review ↓
# ai_fix ├─ frontend-review fix_supervisor
# ├─ security-review
# └─ qa-review
# any("needs_fix") → fix → reviewers
#
# AI review runs immediately after implementation to catch AI-specific issues early,
# before expert reviews begin.
#
# Boilerplate sections (Piece Context, User Request, Previous Response,
# Additional User Inputs, Instructions heading) are auto-injected by buildInstruction().
# Only movement-specific content belongs in instruction_template.
#
# Template Variables (available in instruction_template):
# {iteration} - Piece-wide turn count (total movements executed across all agents)
# {max_iterations} - Maximum iterations allowed for the piece
# {movement_iteration} - Per-movement iteration count (how many times THIS movement has been executed)
# {previous_response} - Output from the previous movement (only when pass_previous_response: true)
# {report_dir} - Report directory name (e.g., "20250126-143052-task-summary")
#
# Movement-level Fields:
# report: - Report file(s) for the movement (auto-injected as Report File/Files in Piece Context)
# Single: report: 00-plan.md
# Multiple: report:
# - Scope: 01-coder-scope.md
# - Decisions: 02-coder-decisions.md
# Auto-generated from expert.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: expert-hybrid-codex
description: Architecture, Frontend, Security, QA Expert Review
max_iterations: 30
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
knowledge:
frontend: ../knowledge/frontend.md
backend: ../knowledge/backend.md
security: ../knowledge/security.md
architecture: ../knowledge/architecture.md
personas:
planner: ../personas/planner.md
coder: ../personas/coder.md
@ -49,7 +22,6 @@ personas:
security-reviewer: ../personas/security-reviewer.md
qa-reviewer: ../personas/qa-reviewer.md
expert-supervisor: ../personas/expert-supervisor.md
instructions:
plan: ../instructions/plan.md
implement: ../instructions/implement.md
@ -63,7 +35,6 @@ instructions:
fix: ../instructions/fix.md
supervise: ../instructions/supervise.md
fix-supervisor: ../instructions/fix-supervisor.md
report_formats:
plan: ../report-formats/plan.md
ai-review: ../report-formats/ai-review.md
@ -73,13 +44,8 @@ report_formats:
qa-review: ../report-formats/qa-review.md
validation: ../report-formats/validation.md
summary: ../report-formats/summary.md
initial_movement: plan
movements:
# ===========================================
# Movement 0: Planning
# ===========================================
- name: plan
edit: false
persona: planner
@ -99,18 +65,19 @@ movements:
next: implement
- condition: Requirements are unclear and planning cannot proceed
next: ABORT
# ===========================================
# Movement 1: Implementation
# ===========================================
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- frontend
- backend
- security
- architecture
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -135,10 +102,6 @@ movements:
next: implement
requires_user_input: true
interactive_only: true
# ===========================================
# Movement 2: AI Review
# ===========================================
- name: ai_review
edit: false
persona: ai-antipattern-reviewer
@ -158,15 +121,19 @@ movements:
next: reviewers
- condition: AI-specific issues detected
next: ai_fix
- name: ai_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- frontend
- backend
- security
- architecture
allowed_tools:
- Read
- Glob
@ -184,7 +151,6 @@ movements:
next: ai_no_fix
- condition: Unable to proceed with fixes
next: ai_no_fix
- name: ai_no_fix
edit: false
persona: architecture-reviewer
@ -199,16 +165,15 @@ movements:
- condition: ai_fix's judgment is valid (no fix needed)
next: reviewers
instruction: arbitrate
# ===========================================
# Movement 3: Expert Reviews (Parallel)
# ===========================================
- name: reviewers
parallel:
- name: arch-review
edit: false
persona: architecture-reviewer
stance: review
knowledge:
- architecture
- backend
report:
name: 04-architect-review.md
format: architecture-review
@ -216,18 +181,17 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-arch
- name: frontend-review
edit: false
persona: frontend-reviewer
stance: review
knowledge: frontend
report:
name: 05-frontend-review.md
format: frontend-review
@ -235,18 +199,17 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-frontend
- name: security-review
edit: false
persona: security-reviewer
stance: review
knowledge: security
report:
name: 06-security-review.md
format: security-review
@ -254,14 +217,12 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-security
- name: qa-review
edit: false
persona: qa-reviewer
@ -273,7 +234,6 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
@ -285,14 +245,18 @@ movements:
next: supervise
- condition: any("needs_fix")
next: fix
- name: fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge:
- frontend
- backend
- security
- architecture
allowed_tools:
- Read
- Glob
@ -309,10 +273,6 @@ movements:
- condition: Cannot proceed, insufficient info
next: plan
instruction: fix
# ===========================================
# Movement 4: Supervision
# ===========================================
- name: supervise
edit: false
persona: expert-supervisor
@ -332,14 +292,18 @@ movements:
next: COMPLETE
- condition: Issues detected during final review
next: fix_supervisor
- name: fix_supervisor
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge:
- frontend
- backend
- security
- architecture
allowed_tools:
- Read
- Glob

View File

@ -30,6 +30,7 @@ stances:
knowledge:
frontend: ../knowledge/frontend.md
backend: ../knowledge/backend.md
security: ../knowledge/security.md
architecture: ../knowledge/architecture.md
@ -103,6 +104,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [frontend, backend, security, architecture]
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -158,6 +160,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [frontend, backend, security, architecture]
allowed_tools:
- Read
- Glob
@ -200,7 +203,7 @@ movements:
edit: false
persona: architecture-reviewer
stance: review
knowledge: architecture
knowledge: [architecture, backend]
report:
name: 04-architect-review.md
format: architecture-review
@ -286,6 +289,7 @@ movements:
stance:
- coding
- testing
knowledge: [frontend, backend, security, architecture]
allowed_tools:
- Read
- Glob
@ -332,6 +336,7 @@ movements:
stance:
- coding
- testing
knowledge: [frontend, backend, security, architecture]
allowed_tools:
- Read
- Glob

View File

@ -1,51 +1,34 @@
# Minimal TAKT Piece
# Implement -> Parallel Review (AI + Supervisor) -> Fix if needed -> Complete
# (Simplest configuration - no plan, no architect review)
#
# Template Variables (auto-injected):
# {iteration} - Piece-wide turn count (total movements executed across all agents)
# {max_iterations} - Maximum iterations allowed for the piece
# {movement_iteration} - Per-movement iteration count (how many times THIS movement has been executed)
# {task} - Original user request (auto-injected)
# {previous_response} - Output from the previous movement (auto-injected)
# {user_inputs} - Accumulated user inputs during piece (auto-injected)
# {report_dir} - Report directory name (e.g., "20250126-143052-task-summary")
# Auto-generated from minimal.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: minimal-hybrid-codex
description: Minimal development piece (implement -> parallel review -> fix if needed -> complete)
max_iterations: 20
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
personas:
coder: ../personas/coder.md
ai-antipattern-reviewer: ../personas/ai-antipattern-reviewer.md
supervisor: ../personas/supervisor.md
instructions:
implement: ../instructions/implement.md
review-ai: ../instructions/review-ai.md
ai-fix: ../instructions/ai-fix.md
supervise: ../instructions/supervise.md
fix-supervisor: ../instructions/fix-supervisor.md
report_formats:
ai-review: ../report-formats/ai-review.md
initial_movement: implement
movements:
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -69,7 +52,6 @@ movements:
next: implement
requires_user_input: true
interactive_only: true
- name: reviewers
parallel:
- name: ai_review
@ -83,14 +65,12 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
instruction: review-ai
rules:
- condition: No AI-specific issues
- condition: AI-specific issues found
- name: supervise
edit: false
persona: supervisor
@ -102,7 +82,6 @@ movements:
- Read
- Glob
- Grep
- Bash
- WebSearch
- WebFetch
@ -110,7 +89,6 @@ movements:
rules:
- condition: All checks passed
- condition: Requirements unmet, tests failing
rules:
- condition: all("No AI-specific issues", "All checks passed")
next: COMPLETE
@ -120,22 +98,20 @@ movements:
next: ai_fix
- condition: any("Requirements unmet, tests failing")
next: supervise_fix
- name: fix_both
parallel:
- name: ai_fix_parallel
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Bash
- WebSearch
- WebFetch
@ -145,20 +121,18 @@ movements:
- condition: No fix needed (verified target files/spec)
- condition: Cannot proceed, insufficient info
instruction: ai-fix
- name: supervise_fix_parallel
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Bash
- WebSearch
- WebFetch
@ -167,20 +141,18 @@ movements:
- condition: Supervisor's issues fixed
- condition: Cannot proceed, insufficient info
instruction: fix-supervisor
rules:
- condition: all("AI Reviewer's issues fixed", "Supervisor's issues fixed")
next: reviewers
- condition: any("No fix needed (verified target files/spec)", "Cannot proceed, insufficient info")
next: implement
- name: ai_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
@ -199,14 +171,13 @@ movements:
- condition: Cannot proceed, insufficient info
next: implement
instruction: ai-fix
- name: supervise_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob

View File

@ -1,33 +1,23 @@
# Passthrough TAKT Piece
# Thinnest wrapper. Pass task directly to coder as-is.
#
# Flow:
# execute (do the task)
# ↓
# COMPLETE
# Auto-generated from passthrough.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: passthrough-hybrid-codex
description: Single-agent thin wrapper. Pass task directly to coder as-is.
max_iterations: 10
stances:
coding: ../stances/coding.md
testing: ../stances/testing.md
personas:
coder: ../personas/coder.md
initial_movement: execute
movements:
- name: execute
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
report:
- Summary: summary.md
allowed_tools:

View File

@ -1,51 +1,34 @@
# Review-Fix Minimal TAKT Piece
# Review -> Fix (if needed) -> Re-review -> Complete
# (Starts with review, no implementation movement)
#
# Template Variables (auto-injected):
# {iteration} - Piece-wide turn count (total movements executed across all agents)
# {max_iterations} - Maximum iterations allowed for the piece
# {movement_iteration} - Per-movement iteration count (how many times THIS movement has been executed)
# {task} - Original user request (auto-injected)
# {previous_response} - Output from the previous movement (auto-injected)
# {user_inputs} - Accumulated user inputs during piece (auto-injected)
# {report_dir} - Report directory name (e.g., "20250126-143052-task-summary")
# Auto-generated from review-fix-minimal.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: review-fix-minimal-hybrid-codex
description: Review and fix piece for existing code (starts with review, no implementation)
max_iterations: 20
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
personas:
coder: ../personas/coder.md
ai-antipattern-reviewer: ../personas/ai-antipattern-reviewer.md
supervisor: ../personas/supervisor.md
instructions:
implement: ../instructions/implement.md
review-ai: ../instructions/review-ai.md
ai-fix: ../instructions/ai-fix.md
supervise: ../instructions/supervise.md
fix-supervisor: ../instructions/fix-supervisor.md
report_formats:
ai-review: ../report-formats/ai-review.md
initial_movement: reviewers
movements:
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -69,7 +52,6 @@ movements:
next: implement
requires_user_input: true
interactive_only: true
- name: reviewers
parallel:
- name: ai_review
@ -83,14 +65,12 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
instruction: review-ai
rules:
- condition: No AI-specific issues
- condition: AI-specific issues found
- name: supervise
edit: false
persona: supervisor
@ -102,7 +82,6 @@ movements:
- Read
- Glob
- Grep
- Bash
- WebSearch
- WebFetch
@ -110,7 +89,6 @@ movements:
rules:
- condition: All checks passed
- condition: Requirements unmet, tests failing
rules:
- condition: all("No AI-specific issues", "All checks passed")
next: COMPLETE
@ -120,22 +98,20 @@ movements:
next: ai_fix
- condition: any("Requirements unmet, tests failing")
next: supervise_fix
- name: fix_both
parallel:
- name: ai_fix_parallel
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Bash
- WebSearch
- WebFetch
@ -145,20 +121,18 @@ movements:
- condition: No fix needed (verified target files/spec)
- condition: Cannot proceed, insufficient info
instruction: ai-fix
- name: supervise_fix_parallel
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Bash
- WebSearch
- WebFetch
@ -167,20 +141,18 @@ movements:
- condition: Supervisor's issues fixed
- condition: Cannot proceed, insufficient info
instruction: fix-supervisor
rules:
- condition: all("AI Reviewer's issues fixed", "Supervisor's issues fixed")
next: reviewers
- condition: any("No fix needed (verified target files/spec)", "Cannot proceed, insufficient info")
next: implement
- name: ai_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
@ -199,14 +171,13 @@ movements:
- condition: Cannot proceed, insufficient info
next: implement
instruction: ai-fix
- name: supervise_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob

View File

@ -27,6 +27,10 @@ max_iterations: 10
stances:
review: ../stances/review.md
knowledge:
architecture: ../knowledge/architecture.md
security: ../knowledge/security.md
personas:
planner: ../personas/planner.md
architecture-reviewer: ../personas/architecture-reviewer.md
@ -90,6 +94,7 @@ movements:
edit: false
persona: architecture-reviewer
stance: review
knowledge: architecture
report:
name: 01-architect-review.md
format: architecture-review
@ -109,6 +114,7 @@ movements:
edit: false
persona: security-reviewer
stance: review
knowledge: security
report:
name: 02-security-review.md
format: security-review

View File

@ -0,0 +1,485 @@
# バックエンド専門知識
## ヘキサゴナルアーキテクチャ(ポートとアダプター)
依存方向は外側から内側へ。逆方向の依存は禁止。
```
adapter外部 → applicationユースケース → domainビジネスロジック
```
ディレクトリ構成:
```
{domain-name}/
├── domain/ # ドメイン層(フレームワーク非依存)
│ ├── model/
│ │ └── aggregate/ # 集約ルート、値オブジェクト
│ └── service/ # ドメインサービス
├── application/ # アプリケーション層(ユースケース)
│ ├── usecase/ # オーケストレーション
│ └── query/ # クエリハンドラ
├── adapter/ # アダプター層(外部接続)
│ ├── inbound/ # 入力アダプター
│ │ └── rest/ # REST Controller, Request/Response DTO
│ └── outbound/ # 出力アダプター
│ └── persistence/ # Entity, Repository実装
└── api/ # 公開インターフェース(他ドメインから参照可能)
└── events/ # ドメインイベント
```
各層の責務:
| 層 | 責務 | 依存してよいもの | 依存してはいけないもの |
|----|------|----------------|---------------------|
| domain | ビジネスロジック、不変条件 | 標準ライブラリのみ | フレームワーク、DB、外部API |
| application | ユースケースのオーケストレーション | domain | adapter の具体実装 |
| adapter/inbound | HTTPリクエスト受信、DTO変換 | application, domain | outbound adapter |
| adapter/outbound | DB永続化、外部API呼び出し | domainインターフェース | application |
```kotlin
// CORRECT - ドメイン層はフレームワーク非依存
data class Order(val orderId: String, val status: OrderStatus) {
fun confirm(confirmedBy: String): OrderConfirmedEvent {
require(status == OrderStatus.PENDING)
return OrderConfirmedEvent(orderId, confirmedBy)
}
}
// WRONG - ドメイン層にSpringアテーション
@Entity
data class Order(
@Id val orderId: String,
@Enumerated(EnumType.STRING) val status: OrderStatus
) {
fun confirm(confirmedBy: String) { ... }
}
```
| 基準 | 判定 |
|------|------|
| ドメイン層にフレームワーク依存(@Entity, @Component等 | REJECT |
| Controller から Repository を直接参照 | REJECT。UseCase層を経由 |
| ドメイン層から外向きの依存DB, HTTP等 | REJECT |
| adapter 間の直接依存inbound → outbound | REJECT |
## API層設計Controller
Controller は薄く保つ。リクエスト受信 → UseCase委譲 → レスポンス返却のみ。
```kotlin
// CORRECT - Controller は薄い
@RestController
@RequestMapping("/api/orders")
class OrdersController(
private val placeOrderUseCase: PlaceOrderUseCase,
private val queryGateway: QueryGateway
) {
// Command: 状態変更
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun post(@Valid @RequestBody request: OrderPostRequest): OrderPostResponse {
val output = placeOrderUseCase.execute(request.toInput())
return OrderPostResponse(output.orderId)
}
// Query: 参照
@GetMapping("/{id}")
fun get(@PathVariable id: String): ResponseEntity<OrderGetResponse> {
val detail = queryGateway.query(FindOrderQuery(id), OrderDetail::class.java).join()
?: return ResponseEntity.notFound().build()
return ResponseEntity.ok(OrderGetResponse.from(detail))
}
}
// WRONG - Controller にビジネスロジック
@PostMapping
fun post(@RequestBody request: OrderPostRequest): ResponseEntity<Any> {
// バリデーション、在庫チェック、計算... Controller に書いてはいけない
val stock = inventoryRepository.findByProductId(request.productId)
if (stock.quantity < request.quantity) {
return ResponseEntity.badRequest().body("在庫不足")
}
val total = request.quantity * request.unitPrice * 1.1 // 税計算
orderRepository.save(OrderEntity(...))
return ResponseEntity.ok(...)
}
```
### Request/Response DTO 設計
Request と Response は別の型として定義する。ドメインモデルをそのままAPIに露出しない。
```kotlin
// Request: バリデーションアノテーション + init ブロック
data class OrderPostRequest(
@field:NotBlank val customerId: String,
@field:NotNull val items: List<OrderItemRequest>
) {
init {
require(items.isNotEmpty()) { "注文には1つ以上の商品が必要です" }
}
fun toInput() = PlaceOrderInput(customerId = customerId, items = items.map { it.toItem() })
}
// Response: ファクトリメソッド from() で変換
data class OrderGetResponse(
val orderId: String,
val status: String,
val customerName: String
) {
companion object {
fun from(detail: OrderDetail) = OrderGetResponse(
orderId = detail.orderId,
status = detail.status.name,
customerName = detail.customerName
)
}
}
```
| 基準 | 判定 |
|------|------|
| ドメインモデルをそのままレスポンスに返す | REJECT |
| Request DTOにビジネスロジック | REJECT。バリデーションのみ許容 |
| Response DTOにドメインロジック計算等 | REJECT |
| Request/Responseが同一の型 | REJECT |
### RESTful なアクション設計
状態遷移は動詞をサブリソースとして表現する。
```
POST /api/orders → 注文作成
GET /api/orders/{id} → 注文取得
GET /api/orders → 注文一覧
POST /api/orders/{id}/approve → 承認(状態遷移)
POST /api/orders/{id}/cancel → キャンセル(状態遷移)
```
| 基準 | 判定 |
|------|------|
| PUT/PATCH でドメイン操作approve, cancel等 | REJECT。POST + 動詞サブリソース |
| 1つのエンドポイントで複数の操作を分岐 | REJECT。操作ごとにエンドポイントを分ける |
| DELETE で論理削除 | REJECT。POST + cancel 等の明示的操作 |
## バリデーション戦略
バリデーションは層ごとに役割が異なる。すべてを1箇所に集めない。
| 層 | 責務 | 手段 | 例 |
|----|------|------|-----|
| API層 | 構造的バリデーション | `@NotBlank`, `init` ブロック | 必須項目、型、フォーマット |
| UseCase層 | ビジネスルール検証 | Read Modelへの問い合わせ | 重複チェック、前提条件の存在確認 |
| ドメイン層 | 状態遷移の不変条件 | `require` | 「PENDINGでないと承認できない」 |
```kotlin
// API層: 「入力の形が正しいか」
data class OrderPostRequest(
@field:NotBlank val customerId: String,
val from: LocalDateTime,
val to: LocalDateTime
) {
init {
require(!to.isBefore(from)) { "終了日時は開始日時以降でなければなりません" }
}
}
// UseCase層: 「ビジネス的に許可されるか」Read Model参照
fun execute(input: PlaceOrderInput) {
customerRepository.findById(input.customerId)
?: throw CustomerNotFoundException("顧客が存在しません")
validateNoOverlapping(input) // 重複チェック
commandGateway.send(buildCommand(input))
}
// ドメイン層: 「今の状態でこの操作は許されるか」
fun confirm(confirmedBy: String): OrderConfirmedEvent {
require(status == OrderStatus.PENDING) { "確定できる状態ではありません" }
return OrderConfirmedEvent(orderId, confirmedBy)
}
```
| 基準 | 判定 |
|------|------|
| ドメインの状態遷移ルールがAPI層にある | REJECT |
| ビジネスルール検証がControllerにある | REJECT。UseCase層に |
| 構造バリデーション(@NotBlank等)がドメインにある | REJECT。API層で |
| UseCase層のバリデーションがAggregate内にある | REJECT。Read Model参照はUseCase層 |
## エラーハンドリング
### 例外階層設計
ドメイン例外は sealed class で階層化する。HTTP ステータスコードへのマッピングは Controller 層で行う。
```kotlin
// ドメイン例外: sealed class で網羅性を保証
sealed class OrderException(message: String) : RuntimeException(message)
class OrderNotFoundException(message: String) : OrderException(message)
class InvalidOrderStateException(message: String) : OrderException(message)
class InsufficientStockException(message: String) : OrderException(message)
// Controller 層でHTTPステータスにマッピング
@RestControllerAdvice
class OrderExceptionHandler {
@ExceptionHandler(OrderNotFoundException::class)
fun handleNotFound(e: OrderNotFoundException) =
ResponseEntity.status(HttpStatus.NOT_FOUND).body(ErrorResponse(e.message))
@ExceptionHandler(InvalidOrderStateException::class)
fun handleInvalidState(e: InvalidOrderStateException) =
ResponseEntity.status(HttpStatus.CONFLICT).body(ErrorResponse(e.message))
@ExceptionHandler(InsufficientStockException::class)
fun handleInsufficientStock(e: InsufficientStockException) =
ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(ErrorResponse(e.message))
}
```
| 基準 | 判定 |
|------|------|
| ドメイン例外にHTTPステータスコードが含まれる | REJECT。ドメインはHTTPを知らない |
| 汎用的な Exception や RuntimeException を throw | REJECT。具体的な例外型を使う |
| try-catch の空 catch | REJECT |
| Controller 内で例外を握りつぶして 200 を返す | REJECT |
## ドメインモデル設計
### イミュータブル + require
ドメインモデルは `data class`(イミュータブル)で設計し、`init` ブロックと `require` で不変条件を保証する。
```kotlin
data class Order(
val orderId: String,
val status: OrderStatus = OrderStatus.PENDING
) {
// companion object の static メソッドで生成
companion object {
fun place(orderId: String, customerId: String): OrderPlacedEvent {
require(customerId.isNotBlank()) { "Customer ID cannot be blank" }
return OrderPlacedEvent(orderId, customerId)
}
}
// インスタンスメソッドで状態遷移 → イベント返却
fun confirm(confirmedBy: String): OrderConfirmedEvent {
require(status == OrderStatus.PENDING) { "確定できる状態ではありません" }
return OrderConfirmedEvent(orderId, confirmedBy, LocalDateTime.now())
}
// イミュータブルな状態更新
fun apply(event: OrderEvent): Order = when (event) {
is OrderPlacedEvent -> Order(orderId = event.orderId)
is OrderConfirmedEvent -> copy(status = OrderStatus.CONFIRMED)
is OrderCancelledEvent -> copy(status = OrderStatus.CANCELLED)
}
}
```
| 基準 | 判定 |
|------|------|
| ドメインモデルに var フィールド | REJECT。`copy()` でイミュータブルに更新 |
| バリデーションなしのファクトリ | REJECT。`require` で不変条件を保証 |
| ドメインモデルが外部サービスを呼ぶ | REJECT。純粋な関数のみ |
| setter でフィールドを直接変更 | REJECT |
### 値オブジェクト
プリミティブ型String, Intをドメインの意味でラップする。
```kotlin
// ID系: 型で取り違えを防止
data class OrderId(@get:JsonValue val value: String) {
init { require(value.isNotBlank()) { "Order ID cannot be blank" } }
override fun toString(): String = value
}
// 範囲系: 複合的な不変条件を保証
data class DateRange(val from: LocalDateTime, val to: LocalDateTime) {
init { require(!to.isBefore(from)) { "終了日は開始日以降でなければなりません" } }
}
// メタ情報系: イベントペイロード内の付随情報
data class ApprovalInfo(val approvedBy: String, val approvalTime: LocalDateTime)
```
| 基準 | 判定 |
|------|------|
| 同じ型のIDが取り違えられるorderId と customerId が両方 String | 値オブジェクト化を検討 |
| 同じフィールドの組み合わせfrom/to等が複数箇所に | 値オブジェクトに抽出 |
| 値オブジェクトに init ブロックがない | REJECT。不変条件を保証する |
## リポジトリパターン
ドメイン層でインターフェースを定義し、adapter/outbound で実装する。
```kotlin
// domain/: インターフェース(ポート)
interface OrderRepository {
fun findById(orderId: String): Order?
fun save(order: Order)
}
// adapter/outbound/persistence/: 実装(アダプター)
@Repository
class JpaOrderRepository(
private val jpaRepository: OrderJpaRepository
) : OrderRepository {
override fun findById(orderId: String): Order? {
return jpaRepository.findById(orderId).orElse(null)?.toDomain()
}
override fun save(order: Order) {
jpaRepository.save(OrderEntity.from(order))
}
}
```
### Read Model EntityJPA Entity
Read Model 用の JPA Entity はドメインモデルとは別に定義する。varmutableが許容される。
```kotlin
@Entity
@Table(name = "orders")
data class OrderEntity(
@Id val orderId: String,
var customerId: String,
@Enumerated(EnumType.STRING) var status: OrderStatus,
var metadata: String? = null
)
```
| 基準 | 判定 |
|------|------|
| ドメインモデルを JPA Entity として兼用 | REJECT。分離する |
| Entity に ビジネスロジック | REJECT。Entity はデータ構造のみ |
| Repository 実装がドメイン層にある | REJECT。adapter/outbound に |
## 認証・認可の配置
認証・認可は横断的関心事として適切な層で処理する。
| 関心事 | 配置 | 手段 |
|-------|------|------|
| 認証(誰か) | Filter / Interceptor層 | JWT検証、セッション確認 |
| 認可(権限) | Controller層 | `@PreAuthorize("hasRole('ADMIN')")` |
| データアクセス制御(自分のデータのみ) | UseCase層 | ビジネスルールとして検証 |
```kotlin
// Controller層: ロールベースの認可
@PostMapping("/{id}/approve")
@PreAuthorize("hasRole('FACILITY_ADMIN')")
fun approve(@PathVariable id: String, @RequestBody request: ApproveRequest) { ... }
// UseCase層: データアクセス制御
fun execute(input: DeleteInput, currentUserId: String) {
val entity = repository.findById(input.id)
?: throw NotFoundException("見つかりません")
require(entity.ownerId == currentUserId) { "他のユーザーのデータは操作できません" }
// ...
}
```
| 基準 | 判定 |
|------|------|
| 認可ロジックが UseCase 層やドメイン層にある | REJECT。Controller層で |
| データアクセス制御が Controller にある | REJECT。UseCase層で |
| 認証処理が Controller 内にある | REJECT。Filter/Interceptor で |
## テスト戦略
### テストピラミッド
```
┌─────────────┐
│ E2E Test │ ← 少数: API全体フロー確認
├─────────────┤
│ Integration │ ← Repository, Controller の統合確認
├─────────────┤
│ Unit Test │ ← 多数: ドメインモデル、UseCase の独立テスト
└─────────────┘
```
### ドメインモデルのテスト
ドメインモデルはフレームワーク非依存なので、純粋なユニットテストが書ける。
```kotlin
class OrderTest {
// ヘルパー: 特定の状態の集約を構築
private fun pendingOrder(): Order {
val event = Order.place("order-1", "customer-1")
return Order.from(event)
}
@Nested
inner class Confirm {
@Test
fun `PENDING状態から確定できる`() {
val order = pendingOrder()
val event = order.confirm("admin-1")
assertEquals("order-1", event.orderId)
}
@Test
fun `CONFIRMED状態からは確定できない`() {
val order = pendingOrder().let { it.apply(it.confirm("admin-1")) }
assertThrows<IllegalArgumentException> {
order.confirm("admin-2")
}
}
}
}
```
テストのルール:
- 状態遷移をヘルパーメソッドで構築(テストごとに独立)
- `@Nested` で操作単位にグループ化
- 正常系と異常系(不正な状態遷移)を両方テスト
- `assertThrows` で例外の型を検証
### UseCase のテスト
UseCase はモックを使ってテスト。外部依存を注入する。
```kotlin
class PlaceOrderUseCaseTest {
private val commandGateway = mockk<CommandGateway>()
private val customerRepository = mockk<CustomerRepository>()
private val useCase = PlaceOrderUseCase(commandGateway, customerRepository)
@Test
fun `顧客が存在しない場合はエラー`() {
every { customerRepository.findById("unknown") } returns null
assertThrows<CustomerNotFoundException> {
useCase.execute(PlaceOrderInput(customerId = "unknown", items = listOf(...)))
}
}
}
```
| 基準 | 判定 |
|------|------|
| ドメインモデルのテストにモックを使用 | REJECT。ドメインは純粋にテスト |
| UseCase テストで実DBに接続 | REJECT。モックを使う |
| テストがフレームワークの起動を必要とする | ユニットテストなら REJECT |
| 状態遷移の異常系テストがない | REJECT |
## アンチパターン検出
以下を見つけたら REJECT:
| アンチパターン | 問題 |
|---------------|------|
| Smart Controller | Controller にビジネスロジックが集中 |
| Anemic Domain Model | ドメインモデルが setter/getter だけのデータ構造 |
| God Service | 1つの Service クラスに全操作が集中 |
| Repository直叩き | Controller が Repository を直接参照 |
| ドメイン漏洩 | adapter 層にドメインロジックが漏れる |
| Entity兼用 | JPA Entity をドメインモデルとして使い回す |
| 例外握りつぶし | 空の catch ブロック |
| Magic String | ハードコードされたステータス文字列等 |

View File

@ -60,6 +60,97 @@ data class Notification(val notificationId: String) {
}
```
### Adapterパターンドメインとフレームワークの分離
ドメインモデルにフレームワークのアノテーション(`@Aggregate`, `@CommandHandler`を直接付けない。Adapterクラスがフレームワーク統合を担当し、ドメインモデルはビジネスロジックに専念する。
```kotlin
// ドメインモデル: フレームワーク非依存。ビジネスロジックのみ
data class Order(
val orderId: String,
val status: OrderStatus = OrderStatus.PENDING
) {
companion object {
fun place(orderId: String, customerId: String): OrderPlacedEvent {
require(customerId.isNotBlank()) { "Customer ID cannot be blank" }
return OrderPlacedEvent(orderId, customerId)
}
fun from(event: OrderPlacedEvent): Order {
return Order(orderId = event.orderId, status = OrderStatus.PENDING)
}
}
fun confirm(confirmedBy: String): OrderConfirmedEvent {
require(status == OrderStatus.PENDING) { "確定できる状態ではありません" }
return OrderConfirmedEvent(orderId, confirmedBy, LocalDateTime.now())
}
fun apply(event: OrderEvent): Order = when (event) {
is OrderPlacedEvent -> from(event)
is OrderConfirmedEvent -> copy(status = OrderStatus.CONFIRMED)
is OrderCancelledEvent -> copy(status = OrderStatus.CANCELLED)
}
}
// Adapter: フレームワーク統合。ドメイン呼び出し → イベント発行の中継
@Aggregate
class OrderAggregateAdapter() {
private var order: Order? = null
@AggregateIdentifier
fun orderId(): String? = order?.orderId
@CommandHandler
constructor(command: PlaceOrderCommand) : this() {
val event = Order.place(command.orderId, command.customerId)
AggregateLifecycle.apply(event)
}
@CommandHandler
fun handle(command: ConfirmOrderCommand) {
val event = order!!.confirm(command.confirmedBy)
AggregateLifecycle.apply(event)
}
@EventSourcingHandler
fun on(event: OrderEvent) {
this.order = when (event) {
is OrderPlacedEvent -> Order.from(event)
else -> order?.apply(event)
}
}
}
```
分離の利点:
- ドメインモデル単体でユニットテスト可能(フレームワーク不要)
- フレームワーク移行時にドメインモデルは変更不要
- Adapterはコマンド受信 → ドメイン呼び出し → イベント発行の定型コード
### apply/from パターン(イベント再生)
ドメインモデルが自身の状態をイベントから再構築するパターン。
- `from(event)`: 生成イベントから初期状態を構築するファクトリ
- `apply(event)`: イベントを受けて新しい状態を返す(`copy()` でイミュータブルに更新)
- `when` 式 + sealed interface で全イベント型の網羅性をコンパイラが保証
```kotlin
fun apply(event: OrderEvent): Order = when (event) {
is OrderPlacedEvent -> from(event)
is OrderConfirmedEvent -> copy(status = OrderStatus.CONFIRMED)
is OrderShippedEvent -> copy(status = OrderStatus.SHIPPED)
// sealed interface なので、イベント型の追加漏れはコンパイルエラーになる
}
```
| 基準 | 判定 |
|------|------|
| apply 内にビジネスロジック(バリデーション等) | REJECT。applyは状態復元のみ |
| apply が副作用を持つDB操作、イベント発行等 | REJECT |
| apply が例外をスローする | REJECT。再生時の失敗は許容しない |
## イベント設計
| 基準 | 判定 |
@ -79,6 +170,36 @@ OrderPlaced, PaymentReceived, ItemShipped
OrderUpdated, OrderDeleted
```
### sealed interface によるイベント型階層
集約のイベントは sealed interface で型階層化する。集約ルートIDを共通フィールドとして強制し、`when` 式の網羅性チェックを有効にする。
```kotlin
sealed interface OrderEvent {
val orderId: String // 全イベントに必須
}
data class OrderPlacedEvent(
override val orderId: String,
val customerId: String
) : OrderEvent
data class OrderConfirmedEvent(
override val orderId: String,
val approvalInfo: ApprovalInfo
) : OrderEvent
data class OrderCancelledEvent(
override val orderId: String,
val cancellationInfo: CancellationInfo
) : OrderEvent
```
利点:
- `when (event)` で全イベント型を列挙しないとコンパイルエラー(`apply` メソッドで特に重要)
- 集約ルートIDの存在をコンパイラが保証
- 型ベースのイベントハンドラ分岐が安全
イベント粒度:
- 細かすぎ: `OrderFieldChanged` → ドメインの意図が不明
- 適切: `ShippingAddressChanged` → 意図が明確
@ -101,6 +222,86 @@ OrderUpdated, OrderDeleted
4. 発行されたイベントを保存
```
### 多層バリデーション
バリデーションは層ごとに役割が異なる。すべてを1箇所に集めない。
| 層 | 責務 | 手段 | 例 |
|----|------|------|-----|
| API層 | 構造的バリデーション | `@NotBlank`, `init` ブロック | 必須項目、型、フォーマット |
| UseCase層 | ビジネスルール検証 | Read Modelへの問い合わせ | 重複チェック、前提条件の存在確認 |
| ドメイン層 | 状態遷移の不変条件 | `require` | 「PENDINGでないと承認できない」 |
```kotlin
// API層: 構造的バリデーション
data class OrderPostRequest(
@field:NotBlank val customerId: String,
@field:NotNull val items: List<OrderItemRequest>
) {
init {
require(items.isNotEmpty()) { "注文には1つ以上の商品が必要です" }
}
}
// UseCase層: ビジネスルール検証Read Model参照
@Service
class PlaceOrderUseCase(
private val commandGateway: CommandGateway,
private val customerRepository: CustomerRepository,
private val inventoryRepository: InventoryRepository
) {
fun execute(input: PlaceOrderInput): Mono<PlaceOrderOutput> {
return Mono.fromCallable {
// 顧客の存在確認
customerRepository.findById(input.customerId)
?: throw CustomerNotFoundException("顧客が存在しません")
// 在庫の事前確認
validateInventory(input.items)
// コマンド送信
val orderId = UUID.randomUUID().toString()
commandGateway.send<Any>(PlaceOrderCommand(orderId, input.customerId, input.items))
PlaceOrderOutput(orderId)
}
}
}
// ドメイン層: 状態遷移の不変条件
fun confirm(confirmedBy: String): OrderConfirmedEvent {
require(status == OrderStatus.PENDING) { "確定できる状態ではありません" }
return OrderConfirmedEvent(orderId, confirmedBy, LocalDateTime.now())
}
```
| 基準 | 判定 |
|------|------|
| ドメイン層のバリデーションがAPI層にある | REJECT。状態遷移ルールはドメインに |
| UseCase層のバリデーションがController内にある | REJECT。UseCase層に分離 |
| API層のバリデーション@NotBlank等)がドメインにある | REJECT。構造検証はAPI層で |
## UseCase層オーケストレーション
Controller と CommandGateway の間にUseCase層を置く。コマンド発行前に複数集約のRead Modelを参照してバリデーションし、必要な前処理を行う。
```
Controller → UseCase → CommandGateway → Aggregate
QueryGateway / RepositoryRead Model参照
```
UseCaseが必要なケース:
- コマンド発行前にRead Modelから他集約の状態を確認する
- 複数のバリデーションを直列に実行する
- コマンド送信後の結果整合性を待機する(ポーリング等)
UseCaseが不要なケース:
- Controllerからコマンドを1つ送るだけで完結する単純な操作
| 基準 | 判定 |
|------|------|
| ControllerがRepository直接参照してバリデーション | UseCase層に分離 |
| UseCaseがHTTPリクエスト/レスポンスに依存 | REJECT。UseCaseはプロトコル非依存 |
| UseCaseがAggregate内部状態を直接変更 | REJECT。CommandGateway経由 |
## プロジェクション設計
| 基準 | 判定 |
@ -115,6 +316,58 @@ OrderUpdated, OrderDeleted
- イベントから冪等に再構築可能
- Writeモデルから完全に独立
### Projection と EventHandlerサイドエフェクトの区別
どちらも `@EventHandler` を使うが、責務が異なる。混同しない。
| 種類 | 責務 | やること | やらないこと |
|------|------|---------|-------------|
| Projection | Read Model 更新 | Entity の保存・更新 | コマンド送信、外部API呼び出し |
| EventHandler | サイドエフェクト | 他集約へのコマンド送信 | Read Model 更新 |
```kotlin
// Projection: Read Model 更新のみ
@Component
class OrderProjection(private val orderRepository: OrderRepository) {
@EventHandler
fun on(event: OrderPlacedEvent) {
val entity = OrderEntity(
orderId = event.orderId,
customerId = event.customerId,
status = OrderStatus.PENDING
)
orderRepository.save(entity)
}
@EventHandler
fun on(event: OrderConfirmedEvent) {
orderRepository.findById(event.orderId).ifPresent { entity ->
entity.status = OrderStatus.CONFIRMED
orderRepository.save(entity)
}
}
}
// EventHandler: サイドエフェクト(他集約へのコマンド送信)
@Component
class InventoryReleaseHandler(private val commandGateway: CommandGateway) {
@EventHandler
fun on(event: OrderCancelledEvent) {
val command = ReleaseInventoryCommand(
productId = event.productId,
quantity = event.quantity
)
commandGateway.send<Any>(command)
}
}
```
| 基準 | 判定 |
|------|------|
| Projection 内で CommandGateway を使用 | REJECT。EventHandler に分離 |
| EventHandler 内で Repository に save | REJECT。Projection に分離 |
| 1クラスに Projection と EventHandler の責務が混在 | REJECT。クラスを分離 |
## Query側の設計
ControllerはQueryGatewayを使う。Repositoryを直接使わない。
@ -408,6 +661,66 @@ fun `注文詳細が取得できる`() {
| Query側テストがCommand経由でデータを作っていない | 推奨 |
| 統合テストでAxonの非同期処理を考慮している | 必須 |
## 値オブジェクト設計
Aggregate とイベントの構成要素として値オブジェクトを使う。プリミティブ型String, Intで済ませない。
```kotlin
// NG - プリミティブ型のまま
data class OrderPlacedEvent(
val orderId: String,
val categoryId: String, // ただの文字列
val from: LocalDateTime, // 意味が不明確
val to: LocalDateTime
)
// OK - 値オブジェクトで意味と制約を表現
data class OrderPlacedEvent(
val orderId: String,
val categoryId: CategoryId,
val period: OrderPeriod
)
```
値オブジェクトの設計ルール:
- `data class` で equals/hashCode を自動生成(同値性で比較)
- `init` ブロックで不変条件を保証(生成時に必ず検証)
- ドメインロジック(計算)は含まない(純粋なデータホルダー)
- `@JsonValue` でシリアライゼーションを制御
```kotlin
// ID系: 単一値ラッパー
data class CategoryId(@get:JsonValue val value: String) {
init {
require(value.isNotBlank()) { "Category ID cannot be blank" }
}
override fun toString(): String = value
}
// 範囲系: 複数値の不変条件を保証
data class OrderPeriod(
val from: LocalDateTime,
val to: LocalDateTime
) {
init {
require(!to.isBefore(from)) { "終了日は開始日以降でなければなりません" }
}
}
// メタ情報系: イベントペイロード内の付随情報
data class ApprovalInfo(
val approvedBy: String,
val approvalTime: LocalDateTime
)
```
| 基準 | 判定 |
|------|------|
| IDをStringのまま使い回す | 値オブジェクト化を検討 |
| 同じフィールドの組み合わせfrom/to等が複数箇所に | 値オブジェクトに抽出 |
| 値オブジェクトにビジネスロジック(状態遷移等) | REJECT。Aggregateの責務 |
| init ブロックなしで不変条件が保証されない | REJECT |
## インフラ層
確認事項:

View File

@ -1,5 +1,42 @@
# フロントエンド専門知識
## フロントエンドの層構造
依存方向は一方向。逆方向の依存は禁止。
```
app/routes/ → features/ → shared/
```
| 層 | 責務 | ルール |
|---|------|--------|
| `app/routes/` | ルート定義のみ | UIロジックを持たない。feature の View を呼ぶだけ |
| `features/` | 機能単位の自己完結モジュール | 他の feature を直接参照しない |
| `shared/` | 全 feature 横断の共有コード | feature に依存しない |
ルートファイルは薄いラッパーに徹する。
```tsx
// CORRECT - ルートは薄い
// app/routes/schedule-management.tsx
export default function ScheduleManagementRoute() {
return <ScheduleManagementView />
}
// WRONG - ルートにロジックを書く
export default function ScheduleManagementRoute() {
const [filter, setFilter] = useState('all')
const { data } = useListSchedules({ filter })
return <ScheduleTable data={data} onFilterChange={setFilter} />
}
```
View コンポーネント(`features/*/components/*-view.tsx`)がデータ取得・状態管理を担当する。
```
ルートroute → Viewデータ取得・状態管理 → 子コンポーネント(表示)
```
## コンポーネント設計
1ファイルにベタ書きしない。必ずコンポーネント分割する。
@ -33,6 +70,37 @@
| Layout | 配置・構造 | `PageLayout`, `Grid` |
| Utility | 共通機能 | `ErrorBoundary`, `Portal` |
### UIプリミティブの設計原則
shared/components/ui/ に配置するHTML要素ラッパーの設計ルール:
- `forwardRef` で ref を転送する(外部からの制御を可能にする)
- `className` を受け取り、外からスタイル拡張可能にする
- ネイティブ props をスプレッドで透過する(`...props`
- variants は別ファイルに分離する(`button.variants.ts`
```tsx
// CORRECT - プリミティブの設計
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant, size, className, children, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(buttonVariants({ variant, size }), className)}
{...props}
>
{children}
</button>
)
}
)
// WRONG - refもclassNameも透過しない閉じたコンポーネント
export const Button = ({ label, onClick }: { label: string; onClick: () => void }) => {
return <button className="fixed-style" onClick={onClick}>{label}</button>
}
```
ディレクトリ構成:
```
features/{feature-name}/
@ -151,12 +219,62 @@ const WeeklyCalendar = ({ facilityId }) => {
| ケース | 理由 |
|--------|------|
| 独立ウィジェット | どのページにも置ける自己完結型コンポーネント |
| 無限スクロール | スクロール位置というUI内部状態に依存 |
| 検索オートコンプリート | 入力値に依存したリアルタイム検索 |
| 独立したウィジェット | 通知バッジ、天気等。親のデータと完全に無関係 |
| リアルタイム更新 | WebSocket/Pollingでの自動更新 |
| モーダル内の詳細取得 | 開いたときだけ追加データを取得 |
### 独立ウィジェットパターン
WordPress のサイドバーウィジェットのように、どのページにも「置くだけ」で動くコンポーネント。親のデータフローに参加しない自己完結型。
該当する例:
- 通知バッジ・通知ベル(未読数を自分で取得)
- ログインユーザー情報表示(ヘッダーのアバター等)
- お知らせバナー
- 天気・為替など外部データ表示
- アクティビティフィード(サイドバー)
```tsx
// OK - 独立ウィジェット。どのページに置いても自分で動く
const NotificationBell = () => {
const { data } = useNotificationCount({ refetchInterval: 30000 })
return (
<button aria-label="通知">
<Bell />
{data?.unreadCount > 0 && <span className="badge">{data.unreadCount}</span>}
</button>
)
}
// OK - ヘッダーに常駐するユーザーメニュー
const UserMenu = () => {
const { data: user } = useCurrentUser()
return <Avatar name={user?.name} />
}
```
ウィジェットと判定する条件(すべて満たすこと):
- 親のデータと**完全に無関係**(親から props でデータを受け取る必要がない)
- 親の状態に**影響を与えない**(結果を親にバブリングしない)
- **どのページに置いても同じ動作**をする(ページ固有のコンテキストに依存しない)
1つでも満たさない場合は View でデータ取得し、props で渡す。
```tsx
// WRONG - ウィジェットに見えるが、orderId という親のコンテキストに依存
const OrderStatusWidget = ({ orderId }: { orderId: string }) => {
const { data } = useGetOrder(orderId)
return <StatusBadge status={data?.status} />
}
// CORRECT - 親のデータフローに参加するならpropsで受け取る
const OrderStatusWidget = ({ status }: { status: OrderStatus }) => {
return <StatusBadge status={status} />
}
```
判断基準: 「親が管理する意味がない / 親に影響を与えない」ケースのみ許容。
| 基準 | 判定 |
@ -169,6 +287,33 @@ const WeeklyCalendar = ({ facilityId }) => {
## 共有コンポーネントと抽象化
### カテゴリ分類
shared コンポーネントは責務別にサブディレクトリで分類する。
```
shared/components/
├── ui/ # HTMLプリミティブのラッパーButton, Card, Badge, Dialog
├── form/ # フォーム入力要素TextInput, Select, Checkbox
├── layout/ # ページ構造・ルート保護Layout, ProtectedRoute
├── navigation/ # ナビゲーションTabs, BackLink, SidebarItem
├── data-display/ # データ表示Table, DetailField, Calendar
├── feedback/ # 状態フィードバックLoadingState, ErrorState
├── domain/ # ドメイン固有だが横断的StatusBadge, CategoryBadge
└── index.ts # barrel export
```
| カテゴリ | 配置基準 |
|---------|---------|
| ui/ | HTML要素を薄くラップ。ドメイン知識を持たない |
| form/ | ラベル・エラー・必須マークを統合したフォーム部品 |
| layout/ | ページ全体の骨格。認証・ロール制御を含む |
| domain/ | 特定ドメインに依存するが、複数 feature で共有 |
ui/ と domain/ の判断基準: ドメイン用語がコンポーネント名やpropsに含まれるなら domain/。
### 共有化の基準
同じパターンのUIは共有コンポーネント化する。インラインスタイルのコピペは禁止。
```tsx
@ -411,6 +556,62 @@ function TaskCard({ task }: { task: Task }) {
- YES → バックエンドに配置(ドメインロジック)
- NO → フロントエンドでもOK表示ロジック
## 横断的関心事の処理層
横断的関心事は適切な層で処理する。コンポーネント内に散在させない。
| 関心事 | 処理層 | パターン |
|-------|--------|---------|
| 認証トークン付与 | APIクライアント層 | リクエストインターセプタ |
| 認証エラー401/403 | APIクライアント層 | レスポンスインターセプタ |
| ルート保護 | レイアウト層 | ProtectedRoute + Outlet |
| ロール別振り分け | レイアウト層 | ユーザー種別による分岐 |
| ローディング/エラー表示 | ViewContainer層 | 早期リターン |
```tsx
// CORRECT - 横断的関心事はインターセプタ層で処理
// api/axios-instance.ts
instance.interceptors.request.use((config) => {
const token = localStorage.getItem('auth_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// WRONG - 各コンポーネントで個別にトークンを付与
const MyComponent = () => {
const token = localStorage.getItem('auth_token')
const { data } = useQuery({
queryFn: () => fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` },
}),
})
}
```
```tsx
// CORRECT - ルート保護はレイアウト層で
// shared/components/layout/protected-route.tsx
function ProtectedRoute() {
const { isAuthenticated } = useAuthStore()
if (!isAuthenticated) return <Navigate to="/login" replace />
return <Layout><Outlet /></Layout>
}
// routes でラップ
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<DashboardView />} />
</Route>
// WRONG - 各ページで個別に認証チェック
function DashboardView() {
const { isAuthenticated } = useAuthStore()
if (!isAuthenticated) return <Navigate to="/login" />
return <div>...</div>
}
```
## パフォーマンス
| 基準 | 判定 |

View File

@ -1,46 +1,37 @@
piece_categories:
"🚀 クイックスタート":
🚀 クイックスタート:
pieces:
- default
- passthrough
- coding
- minimal
"🔍 レビュー&修正":
🔍 レビュー&修正:
pieces:
- review-fix-minimal
"🎨 フロントエンド":
{}
"⚙️ バックエンド":
{}
"🔧 フルスタック":
🎨 フロントエンド: {}
⚙️ バックエンド: {}
🔧 フルスタック:
pieces:
- expert
- expert-cqrs
"🔀 ハイブリッド (Codex Coding)":
"🚀 クイックスタート":
🔀 ハイブリッド (Codex Coding):
🚀 クイックスタート:
pieces:
- default-hybrid-codex
- passthrough-hybrid-codex
- coding-hybrid-codex
- default-hybrid-codex
- minimal-hybrid-codex
"🔍 レビュー&修正":
- passthrough-hybrid-codex
🔧 フルスタック:
pieces:
- expert-cqrs-hybrid-codex
- expert-hybrid-codex
🔍 レビュー&修正:
pieces:
- review-fix-minimal-hybrid-codex
"🔧 フルスタック":
pieces:
- expert-hybrid-codex
- expert-cqrs-hybrid-codex
"その他":
その他:
pieces:
- research
- magi
- review-only
show_others_category: true
others_category_name: "その他"
others_category_name: その他

View File

@ -1,62 +1,31 @@
# Coding TAKT Piece
# Plan -> Implement -> Parallel Review (AI + Architecture) -> Fix if needed
#
# 計画と並列レビューを備えた軽量な開発ピース。
# architect-plannerが要件を調査・整理し、不明点はコードを読んで自力で解決する。
# 並列レビュー後、問題がなければ直接完了し、高速なフィードバックループを実現。
#
# フロー:
# plan (要件調査・計画)
# ↓
# implement (実装)
# ↓
# reviewers (並列レビュー)
# ├─ ai_review (AI特有問題検出)
# └─ arch-review (設計準拠性確認)
# ↓
# [判定]
# ├─ all(approved) → COMPLETE
# └─ any(needs_fix) → fix → reviewers (再レビュー)
#
# Template Variables (auto-injected by buildInstruction):
# {iteration} - Piece-wide turn count (total movements executed across all agents)
# {max_iterations} - Maximum iterations allowed for the piece
# {movement_iteration} - Per-movement iteration count (how many times THIS movement has been executed)
# {task} - Original user request
# {previous_response} - Output from the previous movement
# {user_inputs} - Accumulated user inputs during piece
# {report_dir} - Report directory name (e.g., "20250126-143052-task-summary")
# Auto-generated from coding.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: coding-hybrid-codex
description: Lightweight development piece with planning and parallel reviews (plan -> implement -> parallel review -> complete)
max_iterations: 20
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
knowledge:
architecture: ../knowledge/architecture.md
personas:
architect-planner: ../personas/architect-planner.md
coder: ../personas/coder.md
ai-antipattern-reviewer: ../personas/ai-antipattern-reviewer.md
architecture-reviewer: ../personas/architecture-reviewer.md
instructions:
plan: ../instructions/plan.md
implement: ../instructions/implement.md
ai-review: ../instructions/ai-review.md
review-arch: ../instructions/review-arch.md
fix: ../instructions/fix.md
report_formats:
plan: ../report-formats/plan.md
ai-review: ../report-formats/ai-review.md
architecture-review: ../report-formats/architecture-review.md
initial_movement: plan
movements:
- name: plan
edit: false
@ -79,15 +48,15 @@ movements:
- condition: 要件が不明確、情報不足
next: ABORT
instruction: plan
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge: architecture
report:
- Scope: 02-coder-scope.md
- Decisions: 03-coder-decisions.md
@ -113,7 +82,6 @@ movements:
requires_user_input: true
interactive_only: true
instruction: implement
- name: reviewers
parallel:
- name: ai_review
@ -133,11 +101,11 @@ movements:
- condition: AI特有の問題なし
- condition: AI特有の問題あり
instruction: ai-review
- name: arch-review
edit: false
persona: architecture-reviewer
stance: review
knowledge: architecture
report:
name: 05-architect-review.md
format: architecture-review
@ -151,20 +119,19 @@ movements:
- condition: approved
- condition: needs_fix
instruction: review-arch
rules:
- condition: all("AI特有の問題なし", "approved")
next: COMPLETE
- condition: any("AI特有の問題あり", "needs_fix")
next: fix
- name: fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge: architecture
allowed_tools:
- Read
- Glob

View File

@ -37,6 +37,9 @@ stances:
review: ../stances/review.md
testing: ../stances/testing.md
knowledge:
architecture: ../knowledge/architecture.md
personas:
architect-planner: ../personas/architect-planner.md
coder: ../personas/coder.md
@ -87,6 +90,7 @@ movements:
- coding
- testing
session: refresh
knowledge: architecture
report:
- Scope: 02-coder-scope.md
- Decisions: 03-coder-decisions.md
@ -137,6 +141,7 @@ movements:
edit: false
persona: architecture-reviewer
stance: review
knowledge: architecture
report:
name: 05-architect-review.md
format: architecture-review
@ -163,6 +168,7 @@ movements:
stance:
- coding
- testing
knowledge: architecture
allowed_tools:
- Read
- Glob

View File

@ -1,25 +1,16 @@
# Default TAKT Piece
# Plan -> Architect -> Implement -> AI Review -> Reviewers (parallel: Architect + QA) -> Supervisor Approval
#
# Template Variables (auto-injected by buildInstruction):
# {iteration} - Piece-wide turn count (total movements executed across all agents)
# {max_iterations} - Maximum iterations allowed for the piece
# {movement_iteration} - Per-movement iteration count (how many times THIS movement has been executed)
# {task} - Original user request
# {previous_response} - Output from the previous movement
# {user_inputs} - Accumulated user inputs during piece
# {report_dir} - Report directory name (e.g., "20250126-143052-task-summary")
# Auto-generated from default.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: default-hybrid-codex
description: Standard development piece with planning and specialized reviews
max_iterations: 30
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
knowledge:
architecture: ../knowledge/architecture.md
backend: ../knowledge/backend.md
personas:
planner: ../personas/planner.md
architect-planner: ../personas/architect-planner.md
@ -28,7 +19,6 @@ personas:
architecture-reviewer: ../personas/architecture-reviewer.md
qa-reviewer: ../personas/qa-reviewer.md
supervisor: ../personas/supervisor.md
instructions:
plan: ../instructions/plan.md
architect: ../instructions/architect.md
@ -40,7 +30,6 @@ instructions:
review-qa: ../instructions/review-qa.md
fix: ../instructions/fix.md
supervise: ../instructions/supervise.md
report_formats:
plan: ../report-formats/plan.md
architecture-design: ../report-formats/architecture-design.md
@ -49,11 +38,11 @@ report_formats:
qa-review: ../report-formats/qa-review.md
validation: ../report-formats/validation.md
summary: ../report-formats/summary.md
initial_movement: plan
loop_monitors:
- cycle: [ai_review, ai_fix]
- cycle:
- ai_review
- ai_fix
threshold: 3
judge:
persona: supervisor
@ -75,7 +64,6 @@ loop_monitors:
next: ai_review
- condition: 非生産的(改善なし)
next: reviewers
movements:
- name: plan
edit: false
@ -102,7 +90,6 @@ movements:
- {質問1}
- {質問2}
instruction: plan
- name: architect
edit: false
persona: architect-planner
@ -123,15 +110,17 @@ movements:
- condition: 情報不足、判断できない
next: ABORT
instruction: architect
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- backend
- architecture
report:
- Scope: 02-coder-scope.md
- Decisions: 03-coder-decisions.md
@ -157,7 +146,6 @@ movements:
requires_user_input: true
interactive_only: true
instruction: implement
- name: ai_review
edit: false
persona: ai-antipattern-reviewer
@ -177,15 +165,17 @@ movements:
- condition: AI特有の問題あり
next: ai_fix
instruction: ai-review
- name: ai_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- backend
- architecture
allowed_tools:
- Read
- Glob
@ -204,7 +194,6 @@ movements:
- condition: 判断できない、情報不足
next: ai_no_fix
instruction: ai-fix
- name: ai_no_fix
edit: false
persona: architecture-reviewer
@ -219,13 +208,15 @@ movements:
- condition: ai_fixの判断が妥当修正不要
next: reviewers
instruction: arbitrate
- name: reviewers
parallel:
- name: arch-review
edit: false
persona: architecture-reviewer
stance: review
knowledge:
- architecture
- backend
report:
name: 05-architect-review.md
format: architecture-review
@ -239,7 +230,6 @@ movements:
- condition: approved
- condition: needs_fix
instruction: review-arch
- name: qa-review
edit: false
persona: qa-reviewer
@ -262,14 +252,16 @@ movements:
next: supervise
- condition: any("needs_fix")
next: fix
- name: fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge:
- backend
- architecture
allowed_tools:
- Read
- Glob
@ -286,7 +278,6 @@ movements:
- condition: 判断できない、情報不足
next: plan
instruction: fix
- name: supervise
edit: false
persona: supervisor

View File

@ -22,6 +22,7 @@ stances:
knowledge:
architecture: ../knowledge/architecture.md
backend: ../knowledge/backend.md
personas:
planner: ../personas/planner.md
@ -134,6 +135,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [backend, architecture]
report:
- Scope: 02-coder-scope.md
- Decisions: 03-coder-decisions.md
@ -187,6 +189,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [backend, architecture]
allowed_tools:
- Read
- Glob
@ -227,7 +230,7 @@ movements:
edit: false
persona: architecture-reviewer
stance: review
knowledge: architecture
knowledge: [architecture, backend]
report:
name: 05-architect-review.md
format: architecture-review
@ -271,6 +274,7 @@ movements:
stance:
- coding
- testing
knowledge: [backend, architecture]
allowed_tools:
- Read
- Glob

View File

@ -1,42 +1,19 @@
# Expert Review Piece
# CQRS+ES、フロントエンド、セキュリティ、QAの専門家によるレビューピース
#
# フロー:
# plan -> implement -> ai_review -> reviewers (parallel) -> supervise -> COMPLETE
# ↓ ├─ cqrs-es-review ↓
# ai_fix ├─ frontend-review fix_supervisor
# ├─ security-review
# └─ qa-review
# any("needs_fix") → fix → reviewers
#
# ボイラープレートセクションPiece Context, User Request, Previous Response,
# Additional User Inputs, Instructions headingはbuildInstruction()が自動挿入。
# instruction_templateにはムーブメント固有の内容のみ記述。
#
# テンプレート変数instruction_template内で使用可能:
# {iteration} - ピース全体のターン数(全エージェントで実行されたムーブメントの合計)
# {max_iterations} - ピースの最大イテレーション数
# {movement_iteration} - ムーブメントごとのイテレーション数(このムーブメントが何回実行されたか)
# {previous_response} - 前のムーブメントの出力pass_previous_response: true の場合のみ)
# {report_dir} - レポートディレクトリ名(例: "20250126-143052-task-summary"
#
# ムーブメントレベルフィールド:
# report: - ムーブメントのレポートファイルPiece ContextにReport File/Filesとして自動挿入
# 単一: report: 00-plan.md
# 複数: report:
# - Scope: 01-coder-scope.md
# - Decisions: 02-coder-decisions.md
# Auto-generated from expert-cqrs.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: expert-cqrs-hybrid-codex
description: CQRS+ES・フロントエンド・セキュリティ・QA専門家レビュー
max_iterations: 30
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
knowledge:
frontend: ../knowledge/frontend.md
backend: ../knowledge/backend.md
cqrs-es: ../knowledge/cqrs-es.md
security: ../knowledge/security.md
architecture: ../knowledge/architecture.md
personas:
planner: ../personas/planner.md
coder: ../personas/coder.md
@ -47,7 +24,6 @@ personas:
security-reviewer: ../personas/security-reviewer.md
qa-reviewer: ../personas/qa-reviewer.md
expert-supervisor: ../personas/expert-supervisor.md
instructions:
plan: ../instructions/plan.md
implement: ../instructions/implement.md
@ -61,7 +37,6 @@ instructions:
fix: ../instructions/fix.md
supervise: ../instructions/supervise.md
fix-supervisor: ../instructions/fix-supervisor.md
report_formats:
plan: ../report-formats/plan.md
ai-review: ../report-formats/ai-review.md
@ -71,13 +46,8 @@ report_formats:
qa-review: ../report-formats/qa-review.md
validation: ../report-formats/validation.md
summary: ../report-formats/summary.md
initial_movement: plan
movements:
# ===========================================
# Movement 0: Planning
# ===========================================
- name: plan
edit: false
persona: planner
@ -97,18 +67,20 @@ movements:
next: implement
- condition: 要件が不明確で計画を立てられない
next: ABORT
# ===========================================
# Movement 1: Implementation
# ===========================================
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- frontend
- backend
- cqrs-es
- security
- architecture
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -133,10 +105,6 @@ movements:
next: implement
requires_user_input: true
interactive_only: true
# ===========================================
# Movement 2: AI Review
# ===========================================
- name: ai_review
edit: false
persona: ai-antipattern-reviewer
@ -156,15 +124,20 @@ movements:
next: reviewers
- condition: AI特有の問題が検出された
next: ai_fix
- name: ai_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- frontend
- backend
- cqrs-es
- security
- architecture
allowed_tools:
- Read
- Glob
@ -182,7 +155,6 @@ movements:
next: ai_no_fix
- condition: 修正を進行できない
next: ai_no_fix
- name: ai_no_fix
edit: false
persona: architecture-reviewer
@ -197,16 +169,15 @@ movements:
- condition: ai_fixの判断が妥当修正不要
next: reviewers
instruction: arbitrate
# ===========================================
# Movement 3: Expert Reviews (Parallel)
# ===========================================
- name: reviewers
parallel:
- name: cqrs-es-review
edit: false
persona: cqrs-es-reviewer
stance: review
knowledge:
- cqrs-es
- backend
report:
name: 04-cqrs-es-review.md
format: cqrs-es-review
@ -214,18 +185,17 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-cqrs-es
- name: frontend-review
edit: false
persona: frontend-reviewer
stance: review
knowledge: frontend
report:
name: 05-frontend-review.md
format: frontend-review
@ -233,18 +203,17 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-frontend
- name: security-review
edit: false
persona: security-reviewer
stance: review
knowledge: security
report:
name: 06-security-review.md
format: security-review
@ -252,14 +221,12 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-security
- name: qa-review
edit: false
persona: qa-reviewer
@ -271,7 +238,6 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
@ -283,14 +249,19 @@ movements:
next: supervise
- condition: any("needs_fix")
next: fix
- name: fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge:
- frontend
- backend
- cqrs-es
- security
- architecture
allowed_tools:
- Read
- Glob
@ -307,10 +278,6 @@ movements:
- condition: 修正を進行できない
next: plan
instruction: fix
# ===========================================
# Movement 4: Supervision
# ===========================================
- name: supervise
edit: false
persona: expert-supervisor
@ -330,14 +297,19 @@ movements:
next: COMPLETE
- condition: 問題が検出された
next: fix_supervisor
- name: fix_supervisor
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge:
- frontend
- backend
- cqrs-es
- security
- architecture
allowed_tools:
- Read
- Glob

View File

@ -39,8 +39,10 @@ stances:
knowledge:
frontend: ../knowledge/frontend.md
backend: ../knowledge/backend.md
cqrs-es: ../knowledge/cqrs-es.md
security: ../knowledge/security.md
architecture: ../knowledge/architecture.md
personas:
planner: ../personas/planner.md
@ -113,6 +115,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [frontend, backend, cqrs-es, security, architecture]
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -168,6 +171,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [frontend, backend, cqrs-es, security, architecture]
allowed_tools:
- Read
- Glob
@ -210,7 +214,7 @@ movements:
edit: false
persona: cqrs-es-reviewer
stance: review
knowledge: cqrs-es
knowledge: [cqrs-es, backend]
report:
name: 04-cqrs-es-review.md
format: cqrs-es-review
@ -296,6 +300,7 @@ movements:
stance:
- coding
- testing
knowledge: [frontend, backend, cqrs-es, security, architecture]
allowed_tools:
- Read
- Glob
@ -342,6 +347,7 @@ movements:
stance:
- coding
- testing
knowledge: [frontend, backend, cqrs-es, security, architecture]
allowed_tools:
- Read
- Glob

View File

@ -1,33 +1,18 @@
# Expert Review Piece
# アーキテクチャ、フロントエンド、セキュリティ、QAの専門家によるレビューピース
#
# フロー:
# plan -> implement -> ai_review -> reviewers (parallel) -> supervise -> COMPLETE
# ↓ ├─ arch-review ↓
# ai_fix ├─ frontend-review fix_supervisor
# ├─ security-review
# └─ qa-review
# any("needs_fix") → fix → reviewers
#
# テンプレート変数:
# {iteration} - ピース全体のターン数(全エージェントで実行されたムーブメントの合計)
# {max_iterations} - ピースの最大イテレーション数
# {movement_iteration} - ムーブメントごとのイテレーション数(このムーブメントが何回実行されたか)
# {task} - 元のユーザー要求
# {previous_response} - 前のムーブメントの出力
# {user_inputs} - ピース中に蓄積されたユーザー入力
# {report_dir} - レポートディレクトリ名(例: "20250126-143052-task-summary"
# Auto-generated from expert.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: expert-hybrid-codex
description: アーキテクチャ・フロントエンド・セキュリティ・QA専門家レビュー
max_iterations: 30
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
knowledge:
frontend: ../knowledge/frontend.md
backend: ../knowledge/backend.md
security: ../knowledge/security.md
architecture: ../knowledge/architecture.md
personas:
planner: ../personas/planner.md
coder: ../personas/coder.md
@ -37,7 +22,6 @@ personas:
security-reviewer: ../personas/security-reviewer.md
qa-reviewer: ../personas/qa-reviewer.md
expert-supervisor: ../personas/expert-supervisor.md
instructions:
plan: ../instructions/plan.md
implement: ../instructions/implement.md
@ -51,7 +35,6 @@ instructions:
fix: ../instructions/fix.md
supervise: ../instructions/supervise.md
fix-supervisor: ../instructions/fix-supervisor.md
report_formats:
plan: ../report-formats/plan.md
ai-review: ../report-formats/ai-review.md
@ -61,13 +44,8 @@ report_formats:
qa-review: ../report-formats/qa-review.md
validation: ../report-formats/validation.md
summary: ../report-formats/summary.md
initial_movement: plan
movements:
# ===========================================
# Movement 0: Planning
# ===========================================
- name: plan
edit: false
persona: planner
@ -87,18 +65,19 @@ movements:
next: implement
- condition: 要件が不明確で計画を立てられない
next: ABORT
# ===========================================
# Movement 1: Implementation
# ===========================================
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- frontend
- backend
- security
- architecture
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -123,10 +102,6 @@ movements:
next: implement
requires_user_input: true
interactive_only: true
# ===========================================
# Movement 2: AI Review
# ===========================================
- name: ai_review
edit: false
persona: ai-antipattern-reviewer
@ -146,15 +121,19 @@ movements:
next: reviewers
- condition: AI特有の問題が検出された
next: ai_fix
- name: ai_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
session: refresh
knowledge:
- frontend
- backend
- security
- architecture
allowed_tools:
- Read
- Glob
@ -172,7 +151,6 @@ movements:
next: ai_no_fix
- condition: 修正を進行できない
next: ai_no_fix
- name: ai_no_fix
edit: false
persona: architecture-reviewer
@ -187,16 +165,15 @@ movements:
- condition: ai_fixの判断が妥当修正不要
next: reviewers
instruction: arbitrate
# ===========================================
# Movement 3: Expert Reviews (Parallel)
# ===========================================
- name: reviewers
parallel:
- name: arch-review
edit: false
persona: architecture-reviewer
stance: review
knowledge:
- architecture
- backend
report:
name: 04-architect-review.md
format: architecture-review
@ -204,18 +181,17 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-arch
- name: frontend-review
edit: false
persona: frontend-reviewer
stance: review
knowledge: frontend
report:
name: 05-frontend-review.md
format: frontend-review
@ -223,18 +199,17 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-frontend
- name: security-review
edit: false
persona: security-reviewer
stance: review
knowledge: security
report:
name: 06-security-review.md
format: security-review
@ -242,14 +217,12 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction: review-security
- name: qa-review
edit: false
persona: qa-reviewer
@ -261,7 +234,6 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
@ -273,14 +245,18 @@ movements:
next: supervise
- condition: any("needs_fix")
next: fix
- name: fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge:
- frontend
- backend
- security
- architecture
allowed_tools:
- Read
- Glob
@ -297,10 +273,6 @@ movements:
- condition: 修正を進行できない
next: plan
instruction: fix
# ===========================================
# Movement 4: Supervision
# ===========================================
- name: supervise
edit: false
persona: expert-supervisor
@ -320,14 +292,18 @@ movements:
next: COMPLETE
- condition: 問題が検出された
next: fix_supervisor
- name: fix_supervisor
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
knowledge:
- frontend
- backend
- security
- architecture
allowed_tools:
- Read
- Glob

View File

@ -30,6 +30,7 @@ stances:
knowledge:
frontend: ../knowledge/frontend.md
backend: ../knowledge/backend.md
security: ../knowledge/security.md
architecture: ../knowledge/architecture.md
@ -103,6 +104,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [frontend, backend, security, architecture]
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -158,6 +160,7 @@ movements:
- coding
- testing
session: refresh
knowledge: [frontend, backend, security, architecture]
allowed_tools:
- Read
- Glob
@ -200,7 +203,7 @@ movements:
edit: false
persona: architecture-reviewer
stance: review
knowledge: architecture
knowledge: [architecture, backend]
report:
name: 04-architect-review.md
format: architecture-review
@ -286,6 +289,7 @@ movements:
stance:
- coding
- testing
knowledge: [frontend, backend, security, architecture]
allowed_tools:
- Read
- Glob
@ -332,6 +336,7 @@ movements:
stance:
- coding
- testing
knowledge: [frontend, backend, security, architecture]
allowed_tools:
- Read
- Glob

View File

@ -1,51 +1,34 @@
# Simple TAKT Piece
# Implement -> AI Review -> Supervisor Approval
# (最もシンプルな構成 - plan, architect review, fix ムーブメントなし)
#
# Template Variables (auto-injected):
# {iteration} - Piece-wide turn count (total movements executed across all agents)
# {max_iterations} - Maximum iterations allowed for the piece
# {movement_iteration} - Per-movement iteration count (how many times THIS movement has been executed)
# {task} - Original user request (auto-injected)
# {previous_response} - Output from the previous movement (auto-injected)
# {user_inputs} - Accumulated user inputs during piece (auto-injected)
# {report_dir} - Report directory name (e.g., "20250126-143052-task-summary")
# Auto-generated from minimal.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: minimal-hybrid-codex
description: Minimal development piece (implement -> parallel review -> fix if needed -> complete)
max_iterations: 20
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
personas:
coder: ../personas/coder.md
ai-antipattern-reviewer: ../personas/ai-antipattern-reviewer.md
supervisor: ../personas/supervisor.md
instructions:
implement: ../instructions/implement.md
review-ai: ../instructions/review-ai.md
ai-fix: ../instructions/ai-fix.md
supervise: ../instructions/supervise.md
fix-supervisor: ../instructions/fix-supervisor.md
report_formats:
ai-review: ../report-formats/ai-review.md
initial_movement: implement
movements:
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -69,7 +52,6 @@ movements:
next: implement
requires_user_input: true
interactive_only: true
- name: reviewers
parallel:
- name: ai_review
@ -83,14 +65,12 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
instruction: review-ai
rules:
- condition: "AI特有の問題なし"
- condition: "AI特有の問題あり"
- condition: AI特有の問題なし
- condition: AI特有の問題あり
- name: supervise
edit: false
persona: supervisor
@ -102,15 +82,13 @@ movements:
- Read
- Glob
- Grep
- Bash
- WebSearch
- WebFetch
instruction: supervise
rules:
- condition: "すべて問題なし"
- condition: "要求未達成、テスト失敗、ビルドエラー"
- condition: すべて問題なし
- condition: 要求未達成、テスト失敗、ビルドエラー
rules:
- condition: all("AI特有の問題なし", "すべて問題なし")
next: COMPLETE
@ -120,22 +98,20 @@ movements:
next: ai_fix
- condition: any("要求未達成、テスト失敗、ビルドエラー")
next: supervise_fix
- name: fix_both
parallel:
- name: ai_fix_parallel
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Bash
- WebSearch
- WebFetch
@ -145,20 +121,18 @@ movements:
- condition: 修正不要(指摘対象ファイル/仕様の確認済み)
- condition: 判断できない、情報不足
instruction: ai-fix
- name: supervise_fix_parallel
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Bash
- WebSearch
- WebFetch
@ -167,20 +141,18 @@ movements:
- condition: 監督者の指摘に対する修正が完了した
- condition: 修正を進行できない
instruction: fix-supervisor
rules:
- condition: all("AI問題の修正完了", "監督者の指摘に対する修正が完了した")
next: reviewers
- condition: any("修正不要(指摘対象ファイル/仕様の確認済み)", "判断できない、情報不足", "修正を進行できない")
next: implement
- name: ai_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
@ -199,14 +171,13 @@ movements:
- condition: 判断できない、情報不足
next: implement
instruction: ai-fix
- name: supervise_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob

View File

@ -1,33 +1,23 @@
# Passthrough TAKT Piece
# タスクをそのままエージェントに渡す最薄ラッパー。
#
# フロー:
# execute (タスク実行)
# ↓
# COMPLETE
# Auto-generated from passthrough.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: passthrough-hybrid-codex
description: Single-agent thin wrapper. Pass task directly to coder as-is.
max_iterations: 10
stances:
coding: ../stances/coding.md
testing: ../stances/testing.md
personas:
coder: ../personas/coder.md
initial_movement: execute
movements:
- name: execute
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
report:
- Summary: summary.md
allowed_tools:

View File

@ -1,51 +1,34 @@
# Review-Fix Minimal TAKT Piece
# Review -> Fix (if needed) -> Re-review -> Complete
# (レビューから開始、実装ムーブメントなし)
#
# Template Variables (auto-injected):
# {iteration} - Piece-wide turn count (total movements executed across all agents)
# {max_iterations} - Maximum iterations allowed for the piece
# {movement_iteration} - Per-movement iteration count (how many times THIS movement has been executed)
# {task} - Original user request (auto-injected)
# {previous_response} - Output from the previous movement (auto-injected)
# {user_inputs} - Accumulated user inputs during piece (auto-injected)
# {report_dir} - Report directory name (e.g., "20250126-143052-task-summary")
# Auto-generated from review-fix-minimal.yaml by tools/generate-hybrid-codex.mjs
# Do not edit manually. Edit the source piece and re-run the generator.
name: review-fix-minimal-hybrid-codex
description: 既存コードのレビューと修正ピース(レビュー開始、実装なし)
max_iterations: 20
stances:
coding: ../stances/coding.md
review: ../stances/review.md
testing: ../stances/testing.md
personas:
coder: ../personas/coder.md
ai-antipattern-reviewer: ../personas/ai-antipattern-reviewer.md
supervisor: ../personas/supervisor.md
instructions:
implement: ../instructions/implement.md
review-ai: ../instructions/review-ai.md
ai-fix: ../instructions/ai-fix.md
supervise: ../instructions/supervise.md
fix-supervisor: ../instructions/fix-supervisor.md
report_formats:
ai-review: ../report-formats/ai-review.md
initial_movement: reviewers
movements:
- name: implement
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -69,7 +52,6 @@ movements:
next: implement
requires_user_input: true
interactive_only: true
- name: reviewers
parallel:
- name: ai_review
@ -83,14 +65,12 @@ movements:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
instruction: review-ai
rules:
- condition: "AI特有の問題なし"
- condition: "AI特有の問題あり"
- condition: AI特有の問題なし
- condition: AI特有の問題あり
- name: supervise
edit: false
persona: supervisor
@ -102,15 +82,13 @@ movements:
- Read
- Glob
- Grep
- Bash
- WebSearch
- WebFetch
instruction: supervise
rules:
- condition: "すべて問題なし"
- condition: "要求未達成、テスト失敗、ビルドエラー"
- condition: すべて問題なし
- condition: 要求未達成、テスト失敗、ビルドエラー
rules:
- condition: all("AI特有の問題なし", "すべて問題なし")
next: COMPLETE
@ -120,22 +98,20 @@ movements:
next: ai_fix
- condition: any("要求未達成、テスト失敗、ビルドエラー")
next: supervise_fix
- name: fix_both
parallel:
- name: ai_fix_parallel
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Bash
- WebSearch
- WebFetch
@ -145,20 +121,18 @@ movements:
- condition: 修正不要(指摘対象ファイル/仕様の確認済み)
- condition: 判断できない、情報不足
instruction: ai-fix
- name: supervise_fix_parallel
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Bash
- WebSearch
- WebFetch
@ -167,20 +141,18 @@ movements:
- condition: 監督者の指摘に対する修正が完了した
- condition: 修正を進行できない
instruction: fix-supervisor
rules:
- condition: all("AI問題の修正完了", "監督者の指摘に対する修正が完了した")
next: reviewers
- condition: any("修正不要(指摘対象ファイル/仕様の確認済み)", "判断できない、情報不足", "修正を進行できない")
next: implement
- name: ai_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob
@ -199,14 +171,13 @@ movements:
- condition: 判断できない、情報不足
next: implement
instruction: ai-fix
- name: supervise_fix
edit: true
persona: coder
provider: codex
stance:
- coding
- testing
provider: codex
allowed_tools:
- Read
- Glob

View File

@ -27,6 +27,10 @@ max_iterations: 10
stances:
review: ../stances/review.md
knowledge:
architecture: ../knowledge/architecture.md
security: ../knowledge/security.md
personas:
planner: ../personas/planner.md
architecture-reviewer: ../personas/architecture-reviewer.md
@ -90,6 +94,7 @@ movements:
edit: false
persona: architecture-reviewer
stance: review
knowledge: architecture
report:
name: 01-architect-review.md
format: architecture-review
@ -109,6 +114,7 @@ movements:
edit: false
persona: security-reviewer
stance: review
knowledge: security
report:
name: 02-security-review.md
format: security-review

View File

@ -1,10 +1,39 @@
---
name: takt-engine
description: TAKT ピースエンジン。Agent Team を使ったマルチエージェントオーケストレーション。/takt コマンドから使用される。
name: takt
description: TAKT ピースエンジン。Agent Team を使ったマルチエージェントオーケストレーション。ピースYAMLワークフローに従ってマルチエージェントを実行する。
user-invocable: true
---
# TAKT Piece Engine
## 引数の解析
$ARGUMENTS を以下のように解析する:
```
/takt {piece} [permission] {task...}
```
- **第1トークン**: ピース名またはYAMLファイルパス必須
- **第2トークン**: 権限モード(任意)。以下のキーワードの場合は権限モードとして解釈する:
- `--permit-full` — 全権限付与mode: "bypassPermissions"
- `--permit-edit` — 編集許可mode: "acceptEdits"
- 上記以外 → タスク内容の一部として扱う
- **残りのトークン**: タスク内容(省略時は AskUserQuestion でユーザーに入力を求める)
- **権限モード省略時のデフォルト**: `"default"`(権限確認あり)
例:
- `/takt coding FizzBuzzを作って` → coding ピース、default 権限
- `/takt coding --permit-full FizzBuzzを作って` → coding ピース、bypassPermissions
- `/takt /path/to/custom.yaml 実装して` → カスタムYAML、default 権限
## 事前準備: リファレンスの読み込み
手順を開始する前に、以下の2ファイルを **Read tool で読み込む**:
1. `~/.claude/skills/takt/references/engine.md` - プロンプト構築、レポート管理、ループ検出の詳細
2. `~/.claude/skills/takt/references/yaml-schema.md` - ピースYAMLの構造定義
## あなたの役割: Team Lead
あなたは **Team Leadオーケストレーター** である。
@ -30,11 +59,11 @@ description: TAKT ピースエンジン。Agent Team を使ったマルチエー
| やること | 使うツール | 説明 |
|---------|-----------|------|
| チーム作成 | **Teammate** tool (operation: "spawnTeam") | 最初に1回だけ呼ぶ |
| チーム解散 | **Teammate** tool (operation: "cleanup") | 最後に1回だけ呼ぶ |
| チーム作成 | **TeamCreate** tool | 最初に1回だけ呼ぶ |
| チーム解散 | **TeamDelete** tool | 最後に1回だけ呼ぶ |
| チームメイト起動 | **Task** tool (team_name 付き) | movement ごとに呼ぶ。**結果は同期的に返る** |
**Teammate tool でチームメイトを個別に起動することはできない。** チームメイトの起動は必ず Task tool を使う。
**TeamCreate / TeamDelete でチームメイトを個別に起動することはできない。** チームメイトの起動は必ず Task tool を使う。
**Task tool は同期的に結果を返す。** TaskOutput やポーリングは不要。呼べば結果が返ってくる。
## 手順(この順序で厳密に実行せよ)
@ -55,24 +84,24 @@ description: TAKT ピースエンジン。Agent Team を使ったマルチエー
YAMLから以下を抽出する→ references/yaml-schema.md 参照):
- `name`, `max_iterations`, `initial_movement`, `movements` 配列
- セクションマップ: `personas`, `stances`, `instructions`, `report_formats`, `knowledge`
### 手順 2: エージェント .md の事前読み込み
### 手順 2: セクションリソースの事前読み込み
全 movementparallel のサブステップ含む)から `agent:` パスを収集する。
ピースYAMLのセクションマップ`personas:`, `stances:`, `instructions:`, `report_formats:`, `knowledge:`)から全ファイルパスを収集する。
パスは **ピースYAMLファイルのディレクトリからの相対パス** で解決する。
例: ピースが `~/.claude/skills/takt/pieces/coding.yaml` にあり、`agent: ../agents/default/coder.md`場合
→ 絶対パスは `~/.claude/skills/takt/agents/default/coder.md`
例: ピースが `~/.claude/skills/takt/pieces/default.yaml` にあり、`personas:``coder: ../personas/coder.md` がある場合
→ 絶対パスは `~/.claude/skills/takt/personas/coder.md`
重複を除いて Read で全て読み込む。読み込んだ内容はチームメイトへのプロンプトに使う。
重複を除いて Read で全て読み込む。読み込んだ内容はチームメイトへのプロンプト構築に使う。
### 手順 3: Agent Team 作成
**今すぐ** Teammate tool を呼べ:
**今すぐ** TeamCreate tool を呼べ:
```
Teammate tool を呼ぶ:
operation: "spawnTeam"
TeamCreate tool を呼ぶ:
team_name: "takt"
description: "TAKT {piece_name} ワークフロー"
```
@ -84,7 +113,7 @@ Teammate tool を呼ぶ:
- `iteration = 1`
- `current_movement = initial_movement の movement 定義`
- `previous_response = ""`
- `permission_mode = コマンドで解析された権限モード("bypassPermissions" または "default"`
- `permission_mode = コマンドで解析された権限モード("bypassPermissions" / "acceptEdits" / "default"`
- `movement_history = []`遷移履歴。Loop Monitor 用)
**レポートディレクトリ**: いずれかの movement に `report` フィールドがある場合、`.takt/reports/{YYYYMMDD-HHmmss}-{slug}/` を作成し、パスを `report_dir` 変数に保持する。
@ -97,6 +126,14 @@ Teammate tool を呼ぶ:
current_movement のプロンプトを構築する(→ references/engine.md のプロンプト構築を参照)。
プロンプト構築の要素:
1. **ペルソナ**: `persona:` キー → `personas:` セクション → .md ファイル内容
2. **スタンス**: `stance:` キー → `stances:` セクション → .md ファイル内容(複数可、末尾にリマインダー再掲)
3. **実行コンテキスト**: cwd, ピース名, movement名, イテレーション情報
4. **ナレッジ**: `knowledge:` キー → `knowledge:` セクション → .md ファイル内容
5. **インストラクション**: `instruction:` キー → `instructions:` セクション → .md ファイル内容(テンプレート変数展開済み)
6. **タスク/前回出力/レポート指示/タグ指示**: 自動注入
**通常 movement の場合parallel フィールドなし):**
Task tool を1つ呼ぶ。**Task tool は同期的に結果を返す。待機やポーリングは不要。**
@ -183,10 +220,9 @@ matched_rule の `next` を確認する:
### 手順 8: 終了
1. Teammate tool を呼ぶ:
1. TeamDelete tool を呼ぶ:
```
Teammate tool を呼ぶ:
operation: "cleanup"
TeamDelete tool を呼ぶ
```
2. ユーザーに結果を報告する:

View File

@ -20,7 +20,8 @@ Task tool:
### permission_mode
コマンド引数で解析された `permission_mode` をそのまま Task tool の `mode` に渡す。
- `/takt coding yolo タスク``permission_mode = "bypassPermissions"`(確認なし)
- `/takt coding --permit-full タスク``permission_mode = "bypassPermissions"`(確認なし)
- `/takt coding --permit-edit タスク``permission_mode = "acceptEdits"`(編集は自動許可)
- `/takt coding タスク``permission_mode = "default"`(権限確認あり)
## 通常 Movement の実行
@ -42,7 +43,7 @@ Task tool:
2. **全ての Task tool を1つのメッセージで並列に呼び出す**(依存関係がないため)
3. 全チームメイトの完了を待つ
4. 各サブステップの出力を収集する
5. 各サブステップの出力に対して、そのサブステップの `rules` で条件マッチを判定する
5. 各サブステップの出力に対して、そのサブステップの `rules` で条件マッチを判定
6. 親 movement の `rules` で aggregate 評価all()/any())を行う
### サブステップの条件マッチ判定
@ -55,6 +56,21 @@ Task tool:
マッチした condition 文字列を記録する(次の aggregate 評価で使う)。
## セクションマップの解決
ピースYAMLのトップレベルにある `personas:`, `stances:`, `instructions:`, `report_formats:`, `knowledge:` はキーとファイルパスの対応表。movement 内ではキー名で参照する。
### 解決手順
1. ピースYAMLを読み込む
2. 各セクションマップのパスを、**ピースYAMLファイルのディレクトリ**を基準に絶対パスに変換する
3. movement の `persona: coder``personas:` セクションの `coder` キー → ファイルパス → Read で内容を取得
例: ピースが `~/.claude/skills/takt/pieces/default.yaml` の場合
- `personas.coder: ../personas/coder.md``~/.claude/skills/takt/personas/coder.md`
- `stances.coding: ../stances/coding.md``~/.claude/skills/takt/stances/coding.md`
- `instructions.plan: ../instructions/plan.md``~/.claude/skills/takt/instructions/plan.md`
## プロンプト構築
各チームメイト起動時、以下を結合してプロンプトを組み立てる。
@ -62,16 +78,54 @@ Task tool:
### 構成要素(上から順に結合)
```
1. エージェントプロンプトagent: で参照される .md の全内容)
1. ペルソナプロンプトpersona: で参照される .md の全内容)
2. ---(区切り線)
3. 実行コンテキスト情報
4. instruction_template の内容(テンプレート変数を展開済み)
5. ユーザーのタスク({task} が template に含まれない場合、末尾に自動追加)
6. 前の movement の出力pass_previous_response: true の場合、自動追加)
7. レポート出力指示report フィールドがある場合、自動追加)
8. ステータスタグ出力指示rules がある場合、自動追加)
3. スタンスstance: で参照される .md の内容。複数ある場合は結合)
4. ---(区切り線)
5. 実行コンテキスト情報
6. ナレッジknowledge: で参照される .md の内容)
7. インストラクション内容instruction: で参照される .md、または instruction_template のインライン内容)
8. ユーザーのタスク({task} が template に含まれない場合、末尾に自動追加)
9. 前の movement の出力pass_previous_response: true の場合、自動追加)
10. レポート出力指示report フィールドがある場合、自動追加)
11. ステータスタグ出力指示rules がある場合、自動追加)
12. スタンスリマインダー(スタンスがある場合、末尾に再掲)
```
### ペルソナプロンプト
movement の `persona:` キーからセクションマップを経由して .md ファイルを解決し、その全内容をプロンプトの冒頭に配置する。ペルソナはドメイン知識と行動原則のみを含む(ピース固有の手順は含まない)。
### スタンス注入
movement の `stance:` キー(単一または配列)からスタンスファイルを解決し、内容を結合する。スタンスは行動ルール(コーディング規約、レビュー基準等)を定義する。
**Lost in the Middle 対策**: スタンスはプロンプトの前半に配置し、末尾にリマインダーとして再掲する。
```
(プロンプト冒頭付近)
## スタンス(行動ルール)
{スタンスの内容}
(プロンプト末尾)
---
**リマインダー**: 以下のスタンスに従ってください。
{スタンスの内容(再掲)}
```
### ナレッジ注入
movement の `knowledge:` キーからナレッジファイルを解決し、ドメイン固有の参考情報としてプロンプトに含める。
```
## ナレッジ
{ナレッジの内容}
```
### インストラクション
movement の `instruction:` キーから指示テンプレートファイルを解決する。または `instruction_template:` でインライン記述。テンプレート変数({task}, {previous_response} 等)を展開した上でプロンプトに含める。
### 実行コンテキスト情報
```
@ -85,7 +139,7 @@ Task tool:
### テンプレート変数の展開
`instruction_template` 内の以下のプレースホルダーを置換する:
インストラクション内の以下のプレースホルダーを置換する:
| 変数 | 値 |
|-----|-----|
@ -99,31 +153,28 @@ Task tool:
### {report:ファイル名} の処理
`instruction_template` 内に `{report:04-ai-review.md}` のような記法がある場合:
インストラクション内に `{report:04-ai-review.md}` のような記法がある場合:
1. レポートディレクトリ内に対応するレポートファイルがあれば Read で読む
2. 読み込んだ内容をプレースホルダーに展開する
3. ファイルが存在しない場合は「(レポート未作成)」に置換する
### agent フィールドがない場合
### persona フィールドがない場合
`agent:` が指定されていない movement の場合、エージェントプロンプト部分を省略し、`instruction_template` の内容のみでプロンプトを構成する。
`persona:` が指定されていない movement の場合、ペルソナプロンプト部分を省略し、インストラクションの内容のみでプロンプトを構成する。
## レポート出力指示の自動注入
movement に `report` フィールドがある場合、プロンプト末尾にレポート出力指示を自動追加する。
### 形式1: name + format
### 形式1: name + format(キー参照)
```yaml
report:
name: 01-plan.md
format: |
# タスク計画
## 元の要求
...
format: plan # report_formats セクションのキー
```
プロンプトに追加する指示:
`report_formats:` セクションの `plan` キーから .md ファイルを解決し、Read で読んだ内容をフォーマット指示に使う:
```
---
@ -133,9 +184,7 @@ report:
ファイル名: 01-plan.md
フォーマット:
# タスク計画
## 元の要求
...
{report_formats の plan キーの .md ファイル内容}
```
### 形式2: 配列(複数レポート)
@ -288,7 +337,7 @@ loop_monitors:
- cycle: [ai_review, ai_fix]
threshold: 3
judge:
agent: ../agents/default/supervisor.md
persona: supervisor
instruction_template: |
サイクルが {cycle_count} 回繰り返されました...
rules:
@ -303,7 +352,7 @@ loop_monitors:
1. movement 遷移履歴を記録する(例: `[plan, implement, ai_review, ai_fix, ai_review, ai_fix, ...]`
2. 各 loop_monitor の `cycle` パターンが履歴の末尾に `threshold` 回以上連続で出現するかチェックする
3. 閾値に達した場合:
a. judge の `agent` を Read で読み込む
a. judge の `persona` キーからペルソナファイルを Read で読み込む
b. `instruction_template``{cycle_count}` を実際のサイクル回数に置換する
c. Task tool でチームメイトjudgeを起動する
d. judge の出力を judge の `rules` で評価する
@ -332,16 +381,16 @@ loop_monitors:
### レポートの参照
後続の movement の `instruction_template` 内で `{report:ファイル名}` として参照すると、そのレポートファイルを Read して内容をプレースホルダーに展開する。
後続の movement のインストラクション内で `{report:ファイル名}` として参照すると、そのレポートファイルを Read して内容をプレースホルダーに展開する。
## 状態遷移の全体像
```
[開始]
ピースYAML読み込み + エージェント .md 読み込み
ピースYAML読み込み + セクションマップ解決personas, stances, instructions, report_formats, knowledge
Teammate(spawnTeam) でチーム作成
TeamCreate でチーム作成
レポートディレクトリ作成
@ -349,8 +398,9 @@ initial_movement を取得
┌─→ Task tool でチームメイト起動
│ ├── 通常: 1つの Task tool 呼び出し
│ │ prompt = agent.md + context + instruction + task
│ │ + previous_response + レポート指示 + タグ指示
│ │ prompt = persona + stance + context + knowledge
│ │ + instruction + task + previous_response
│ │ + レポート指示 + タグ指示 + スタンスリマインダー
│ └── parallel: 複数の Task tool を1メッセージで並列呼び出し
│ 各サブステップを別々のチームメイトとして起動
│ ↓
@ -366,8 +416,8 @@ initial_movement を取得
│ ├── parallel: サブステップ条件 → aggregate(all/any)
│ ↓
│ next を決定
│ ├── COMPLETE → Teammate(cleanup) → ユーザーに結果報告
│ ├── ABORT → Teammate(cleanup) → ユーザーにエラー報告
│ ├── COMPLETE → TeamDelete → ユーザーに結果報告
│ ├── ABORT → TeamDelete → ユーザーにエラー報告
│ └── movement名 → ループ検出チェック → 次の movement
│ ↓
└──────────────────────────────────────────────┘

View File

@ -9,58 +9,92 @@ name: piece-name # ピース名(必須)
description: 説明テキスト # ピースの説明(任意)
max_iterations: 10 # 最大イテレーション数(必須)
initial_movement: plan # 最初に実行する movement 名(必須)
# セクションマップ(キー → ファイルパスの対応表)
stances: # スタンス定義(任意)
coding: ../stances/coding.md
review: ../stances/review.md
personas: # ペルソナ定義(任意)
coder: ../personas/coder.md
reviewer: ../personas/architecture-reviewer.md
instructions: # 指示テンプレート定義(任意)
plan: ../instructions/plan.md
implement: ../instructions/implement.md
report_formats: # レポートフォーマット定義(任意)
plan: ../report-formats/plan.md
review: ../report-formats/architecture-review.md
knowledge: # ナレッジ定義(任意)
architecture: ../knowledge/architecture.md
movements: [...] # movement 定義の配列(必須)
loop_monitors: [...] # ループ監視設定(任意)
```
### セクションマップの解決
各セクションマップのパスは **ピースYAMLファイルのディレクトリからの相対パス** で解決する。
movement 内では**キー名**で参照する(パスを直接書かない)。
例: ピースが `~/.claude/skills/takt/pieces/coding.yaml` にあり、`personas:` セクションに `coder: ../personas/coder.md` がある場合
→ 絶対パスは `~/.claude/skills/takt/personas/coder.md`
→ movement では `persona: coder` で参照
## Movement 定義
### 通常 Movement
```yaml
- name: movement-name # movement 名(必須、一意)
agent: ../agents/path.md # エージェントプロンプトへの相対パス(任意)
agent_name: coder # 表示名(任意)
persona: coder # ペルソナキーpersonas マップを参照、任意)
stance: coding # スタンスキーstances マップを参照、任意)
stance: [coding, testing] # 複数指定も可(配列)
instruction: implement # 指示テンプレートキーinstructions マップを参照、任意)
knowledge: architecture # ナレッジキーknowledge マップを参照、任意)
edit: true # ファイル編集可否(必須)
permission_mode: edit # 権限モード: edit / readonly / full任意
session: refresh # セッション管理(任意)
pass_previous_response: true # 前の出力を渡すか(デフォルト: true
allowed_tools: [...] # 許可ツール一覧(任意、参考情報)
instruction_template: | # ステップ固有の指示テンプレート(任意)
instruction_template: | # インライン指示テンプレートinstruction キーの代替、任意)
指示内容...
report: ... # レポート設定(任意)
rules: [...] # 遷移ルール(必須)
```
**`instruction` vs `instruction_template`**: `instruction` はトップレベル `instructions:` セクションのキー参照。`instruction_template` はインラインで指示を記述。どちらか一方を使用する。
### Parallel Movement
```yaml
- name: reviewers # 親 movement 名(必須)
parallel: # 並列サブステップ配列(これがあると parallel movement
- name: sub-step-1 # サブステップ名
agent: ../agents/a.md
- name: arch-review
persona: architecture-reviewer
stance: review
knowledge: architecture
edit: false
instruction_template: |
...
rules: # サブステップの rulescondition のみ、next は無視される)
instruction: review-arch
report:
name: 05-architect-review.md
format: architecture-review
rules:
- condition: "approved"
- condition: "needs_fix"
# report, allowed_tools 等も指定可能
- name: sub-step-2
agent: ../agents/b.md
- name: qa-review
persona: qa-reviewer
stance: review
edit: false
instruction_template: |
...
instruction: review-qa
rules:
- condition: "passed"
- condition: "failed"
- condition: "approved"
- condition: "needs_fix"
rules: # 親の rulesaggregate 条件で遷移先を決定)
- condition: all("approved", "passed")
next: complete-step
- condition: any("needs_fix", "failed")
next: fix-step
- condition: all("approved")
next: supervise
- condition: any("needs_fix")
next: fix
```
**重要**: サブステップの `rules` は結果分類のための condition 定義のみ。`next` は無視される(親の rules が遷移先を決定)。
@ -97,20 +131,26 @@ rules:
## Report 定義
### 形式1: 単一レポートname + format
### 形式1: 単一レポートname + format キー参照
```yaml
report:
name: 01-plan.md
format: |
```markdown
format: plan # report_formats マップのキーを参照
```
`format` がキー文字列の場合、トップレベル `report_formats:` セクションから対応する .md ファイルを読み込み、フォーマット指示として使用する。
### 形式1b: 単一レポートname + format インライン)
```yaml
report:
name: 01-plan.md
format: | # インラインでフォーマットを記述
# レポートタイトル
## セクション
{内容}
```
```
`format` はエージェントへの出力フォーマット指示。レポート抽出時の参考情報。
### 形式2: 複数レポート(配列)
@ -125,7 +165,7 @@ report:
## テンプレート変数
`instruction_template` 内で使用可能な変数:
`instruction_template`(またはインストラクションファイル)内で使用可能な変数:
| 変数 | 説明 |
|-----|------|
@ -146,7 +186,7 @@ loop_monitors:
- cycle: [movement_a, movement_b] # 監視対象の movement サイクル
threshold: 3 # 発動閾値(サイクル回数)
judge:
agent: ../agents/supervisor.md # 判定エージェント
persona: supervisor # ペルソナキー参照
instruction_template: | # 判定用指示
サイクルが {cycle_count} 回繰り返されました。
健全性を判断してください。
@ -157,7 +197,7 @@ loop_monitors:
next: alternative_movement
```
特定の movement 間のサイクルが閾値に達した場合、judge エージェントが介入して遷移先を判断する。
特定の movement 間のサイクルが閾値に達した場合、judge が介入して遷移先を判断する。
## allowed_tools について

View File

@ -1,37 +0,0 @@
---
name: takt
description: TAKT ピースランナー。ピースYAMLワークフローに従ってマルチエージェントを実行する。
---
TAKT ピースランナーを実行する。
## 引数
$ARGUMENTS を以下のように解析する:
```
/takt {piece} [permission] {task...}
```
- **第1トークン**: ピース名またはYAMLファイルパス必須
- **第2トークン**: 権限モード(任意)。以下のキーワードの場合は権限モードとして解釈する:
- `yolo` — 全権限付与mode: "bypassPermissions"
- 上記以外 → タスク内容の一部として扱う
- **残りのトークン**: タスク内容(省略時は AskUserQuestion でユーザーに入力を求める)
- **権限モード省略時のデフォルト**: `"default"`(権限確認あり)
例:
- `/takt coding FizzBuzzを作って` → coding ピース、default 権限
- `/takt coding yolo FizzBuzzを作って` → coding ピース、bypassPermissions
- `/takt passthrough yolo 全テストを実行` → passthrough ピース、bypassPermissions
- `/takt /path/to/custom.yaml 実装して` → カスタムYAML、default 権限
## 実行手順
以下のファイルを **Read tool で読み込み**、記載された手順に従って実行する:
1. `~/.claude/skills/takt/SKILL.md` - エンジン概要とピース解決
2. `~/.claude/skills/takt/references/engine.md` - 実行エンジンの詳細ロジック
3. `~/.claude/skills/takt/references/yaml-schema.md` - ピースYAML構造リファレンス
**重要**: これら3ファイルを最初に全て読み込んでから、SKILL.md の「手順」に従って処理を開始する。

View File

@ -783,6 +783,7 @@ export TAKT_OPENAI_API_KEY=sk-...
## ドキュメント
- [Faceted Prompting](./prompt-composition.ja.md) - AIプロンプトへの関心の分離Persona, Stance, Instruction, Knowledge, Report Format
- [Piece Guide](./pieces.md) - ピースの作成とカスタマイズ
- [Agent Guide](./agents.md) - カスタムエージェントの設定
- [Changelog](../CHANGELOG.md) - バージョン履歴

View File

@ -0,0 +1,149 @@
# Faceted Prompting: AIプロンプトへの関心の分離
## 問題
マルチエージェントシステムが複雑になるにつれ、プロンプトはモリシックになる。1つのプロンプトファイルにエージェントの役割、行動規範、タスク固有の指示、ドメイン知識、出力形式がすべて混在する。これは3つの問題を生む。
1. **再利用できない** — 2つのステップが同じレビュアーのペルソナを必要としつつ指示が異なる場合、プロンプト全体を複製するしかない
2. **暗黙的な結合** — コーディング規約を変更すると、それを参照するすべてのプロンプトを編集する必要がある
3. **責任の不明確さ** — プロンプトのどの部分がエージェントの「役割」を定義し、どの部分が「やるべきこと」を定義しているのか区別がつかない
## アイデア
ソフトウェア工学の基本原則である**関心の分離Separation of Concerns**をプロンプト設計に適用する。
エージェントごとに1つのモリシックなプロンプトを書く代わりに、「何の関心を扱っているか」で独立した再利用可能なファイルに分解する。そしてワークフローのステップごとに宣言的に合成する。
## 5つの関心
Faceted Promptingはプロンプトを5つの直交する関心に分解する。
| 関心 | 答える問い | 例 |
|------|-----------|-----|
| **Persona** | エージェントは*誰*か? | 役割定義、専門性、判断基準 |
| **Stance** | *どう*振る舞うべきか? | コーディング規約、レビュー基準、行動規範 |
| **Instruction** | *何を*すべきか? | ステップ固有の手順、変数付きテンプレート |
| **Knowledge** | *何を*知っているか? | アーキテクチャ文書、API仕様、例示、参照資料 |
| **Report Format** | *何を*出力すべきか? | 出力構造、レポートテンプレート |
各関心はそれぞれのディレクトリに独立したファイルMarkdownまたはテンプレートとして格納される。
```
workflows/ # ワークフロー定義
personas/ # WHO — 役割定義
stances/ # HOW — 行動規範
instructions/ # WHAT — ステップ手順
knowledge/ # CONTEXT — ドメイン情報
report-formats/ # OUTPUT — レポートテンプレート
```
### なぜこの5つか
**Persona** と **Instruction** は最低限必要なもの — エージェントが誰で、何をすべきかを定義する必要がある。しかし実際には、さらに3つの関心が独立した軸として現れる。
- **Stance** はタスクをまたがって適用される行動規範を捉える。「コーディングスタンス」(命名規則、エラーハンドリングのルール、テスト要件)は、機能実装でもバグ修正でも同じように適用される。スタンスは「横断的関心事」であり、作業内容に関係なく作業の*やり方*を規定する。
- **Knowledge** は複数のエージェントが必要とするドメイン知識を捉える。アーキテクチャ文書はプランナーにもレビュアーにも関係がある。ナレッジをインストラクションから分離することで重複を防ぎ、インストラクションを手順に集中させる。
- **Report Format** は作業そのものとは独立した出力構造を捉える。同じレビューフォーマットをアーキテクチャレビュアーとセキュリティレビュアーの両方で使える。出力形式の変更がエージェントの振る舞いに影響しない。
## 宣言的な合成
Faceted Promptingの中核メカニズムは**宣言的な合成**である。ワークフロー定義が各ステップでプロンプトの内容を直接埋め込むのではなく、*どの*関心を組み合わせるかを宣言する。
主要な特性は次の通り。
- **各ファイルは1つの関心だけを持つ。** ペルソナファイルには役割と専門性のみを記述し、ステップ固有の手順は書かない。
- **合成は宣言的。** ワークフローは*どの*関心を組み合わせるかを記述し、プロンプトを*どう*組み立てるかは記述しない。
- **自由に組み合わせ可能。** 同じ `coder` ペルソナを異なるスタンスとインストラクションで異なるステップに使える。
- **ファイルが再利用の単位。** 同じファイルを指すことでスタンスをワークフロー間で共有する。
### TAKTでの実装例
[TAKT](https://github.com/nrslib/takt) はFaceted PromptingをYAMLベースのワークフロー定義「ピース」と呼ぶで実装している。各関心はセクションマップで短いキーにマッピングされ、各ステップTAKTでは「ムーブメント」と呼ぶからキーで参照される。
```yaml
name: my-workflow
max_iterations: 10
initial_movement: plan
# セクションマップ — キー: ファイルパスこのYAMLからの相対パス
personas:
coder: ../personas/coder.md
reviewer: ../personas/architecture-reviewer.md
stances:
coding: ../stances/coding.md
review: ../stances/review.md
instructions:
plan: ../instructions/plan.md
implement: ../instructions/implement.md
knowledge:
architecture: ../knowledge/architecture.md
report_formats:
review: ../report-formats/review.md
movements:
- name: implement
persona: coder # WHO — personas.coder を参照
stance: coding # HOW — stances.coding を参照
instruction: implement # WHAT — instructions.implement を参照
knowledge: architecture # CONTEXT — knowledge.architecture を参照
edit: true
rules:
- condition: Implementation complete
next: review
- name: review
persona: reviewer # 異なる WHO
stance: review # 異なる HOW
instruction: review # 異なる WHAT共有も可能
knowledge: architecture # 同じ CONTEXT — 再利用
report:
name: review.md
format: review # OUTPUT — report_formats.review を参照
edit: false
rules:
- condition: Approved
next: COMPLETE
- condition: Needs fix
next: implement
```
エンジンは各キーをファイルに解決し、内容を読み込み、実行時に最終的なプロンプトを組み立てる。ワークフローの作者がモノリシックなプロンプトを書くことはない — どのファセットを組み合わせるかを選択するだけである。
## 既存手法との違い
| 手法 | 内容 | 本手法との違い |
|------|------|--------------|
| **Decomposed Prompting** (Khot et al.) | *タスク*をサブタスクに分解して異なるLLMに委任 | 分解するのはタスクではなく*プロンプトの構造* |
| **Modular Prompting** | XML/HTMLタグを使った単一プロンプト内のセクション分け | 関心を*独立ファイル*に分離し、宣言的に合成する |
| **Prompt Layering** (Airia) | エンタープライズ向けのスタック可能なプロンプトセグメント | 管理ツールであり、プロンプト設計のデザインパターンではない |
| **PDL** (IBM) | データパイプライン向けのYAMLベースプロンプトプログラミング言語 | 制御フローif/for/model呼び出しが焦点で、関心の分離ではない |
| **Role/Persona Prompting** | 役割を割り当ててレスポンスを方向付ける | ペルソナは5つの関心の1つにすぎない — スタンス、インストラクション、ナレッジ、出力形式も分離する |
核心的な違いは次の点にある。既存手法は*タスク*(何をするか)を分解するか、*プロンプトの構造*どう書式化するかを整理する。Faceted Promptingは*プロンプトの関心*(各部分がなぜ存在するか)を独立した再利用可能な単位に分解する。
## 実用上の利点
**ワークフロー作者にとって:**
- コーディング規約を1つのスタンスファイルで変更すれば、それを使うすべてのワークフローに反映される
- 既存のペルソナ、スタンス、インストラクションを組み合わせて新しいワークフローを作れる
- 各ファイルを単一の責務に集中させられる
**チームにとって:**
- プロンプトを複製せずにプロジェクト間で振る舞い(スタンス)を標準化できる
- ドメイン専門家がナレッジファイルを管理し、ワークフロー設計者がインストラクションを管理する分業ができる
- 個々の関心を独立してレビューできる
**エンジンにとって:**
- プロンプト組み立ては決定的 — 同じワークフロー定義とファイルからは同じプロンプトが構築される
- スタンスの配置を最適化できる(例: 「Lost in the Middle」効果に対抗するためプロンプトの先頭と末尾に配置
- 各関心を他の部分に影響を与えずにステップごとに注入・省略・上書きできる
## まとめ
Faceted Promptingは、関心の分離Separation of ConcernsをAIプロンプト工学に適用するデザインパターンである。プロンプトを5つの独立した関心 — Persona、Stance、Instruction、Knowledge、Report Format — に分解し、宣言的に合成することで、再利用可能で保守しやすく透明なマルチエージェントワークフローを実現する。

149
docs/prompt-composition.md Normal file
View File

@ -0,0 +1,149 @@
# Faceted Prompting: Separation of Concerns for AI Prompts
## The Problem
As multi-agent systems grow complex, prompts become monolithic. A single prompt file contains the agent's role, behavioral rules, task-specific instructions, domain knowledge, and output format — all tangled together. This creates three problems:
1. **No reuse** — When two steps need the same reviewer persona but different instructions, you duplicate the entire prompt
2. **Hidden coupling** — Changing a coding standard means editing every prompt that references it
3. **Unclear ownership** — It's impossible to tell which part of a prompt defines *who* the agent is versus *what* it should do
## The Idea
Apply **Separation of Concerns** — a foundational software engineering principle — to prompt design.
Instead of one monolithic prompt per agent, decompose it into independent, reusable files organized by *what concern they address*. Then compose them declaratively per workflow step.
## Five Concerns
Faceted Prompting decomposes prompts into five orthogonal concerns:
| Concern | Question it answers | Example |
|---------|-------------------|---------|
| **Persona** | *Who* is the agent? | Role definition, expertise, judgment criteria |
| **Stance** | *How* should it behave? | Coding standards, review criteria, behavioral rules |
| **Instruction** | *What* should it do? | Step-specific procedures, templates with variables |
| **Knowledge** | *What* does it know? | Architecture docs, API specs, examples, references |
| **Report Format** | *What* should it output? | Output structure, report templates |
Each concern is a standalone file (Markdown or template) stored in its own directory:
```
workflows/ # Workflow definitions
personas/ # WHO — role definitions
stances/ # HOW — behavioral rules
instructions/ # WHAT — step procedures
knowledge/ # CONTEXT — domain information
report-formats/ # OUTPUT — report templates
```
### Why These Five?
**Persona** and **Instruction** are the minimum — you need to define who the agent is and what it should do. But in practice, three more concerns emerge as independent axes:
- **Stance** captures behavioral rules that apply across different tasks. A "coding stance" (naming conventions, error handling rules, test requirements) applies whether the agent is implementing a feature or fixing a bug. Stances are *cross-cutting concerns* — they modify how work is done regardless of what the work is.
- **Knowledge** captures domain context that multiple agents may need. An architecture document is relevant to both the planner and the reviewer. Separating knowledge from instructions prevents duplication and keeps instructions focused on procedures.
- **Report Format** captures output structure independently of the work itself. The same review format can be used by an architecture reviewer and a security reviewer. Separating it allows format changes without touching agent behavior.
## Declarative Composition
The core mechanism of Faceted Prompting is **declarative composition**: a workflow definition declares *which* concerns to combine for each step, rather than embedding prompt content directly.
Key properties:
- **Each file has one concern.** A persona file contains only role and expertise — never step-specific procedures.
- **Composition is declarative.** The workflow says *which* concerns to combine, not *how* to assemble the prompt.
- **Mix and match.** The same `coder` persona can appear with different stances and instructions in different steps.
- **Files are the unit of reuse.** Share a stance across workflows by pointing to the same file.
### Implementation Example: TAKT
[TAKT](https://github.com/nrslib/takt) implements Faceted Prompting using YAML-based workflow definitions called "pieces." Concerns are mapped to short keys via section maps, then referenced by key in each step (called "movement" in TAKT):
```yaml
name: my-workflow
max_iterations: 10
initial_movement: plan
# Section maps — key: file path (relative to this YAML)
personas:
coder: ../personas/coder.md
reviewer: ../personas/architecture-reviewer.md
stances:
coding: ../stances/coding.md
review: ../stances/review.md
instructions:
plan: ../instructions/plan.md
implement: ../instructions/implement.md
knowledge:
architecture: ../knowledge/architecture.md
report_formats:
review: ../report-formats/review.md
movements:
- name: implement
persona: coder # WHO — references personas.coder
stance: coding # HOW — references stances.coding
instruction: implement # WHAT — references instructions.implement
knowledge: architecture # CONTEXT — references knowledge.architecture
edit: true
rules:
- condition: Implementation complete
next: review
- name: review
persona: reviewer # Different WHO
stance: review # Different HOW
instruction: review # Different WHAT (but could share)
knowledge: architecture # Same CONTEXT — reused
report:
name: review.md
format: review # OUTPUT — references report_formats.review
edit: false
rules:
- condition: Approved
next: COMPLETE
- condition: Needs fix
next: implement
```
The engine resolves each key to its file, reads the content, and assembles the final prompt at runtime. The workflow author never writes a monolithic prompt — only selects which facets to combine.
## How It Differs from Existing Approaches
| Approach | What it does | How this differs |
|----------|-------------|-----------------|
| **Decomposed Prompting** (Khot et al.) | Breaks *tasks* into sub-tasks delegated to different LLMs | We decompose the *prompt structure*, not the task |
| **Modular Prompting** | Sections within a single prompt using XML/HTML tags | We separate concerns into *independent files* with declarative composition |
| **Prompt Layering** (Airia) | Stackable prompt segments for enterprise management | A management tool, not a design pattern for prompt architecture |
| **PDL** (IBM) | YAML-based prompt programming language for data pipelines | Focuses on control flow (if/for/model calls), not concern separation |
| **Role/Persona Prompting** | Assigns a role to shape responses | Persona is one of five concerns — we also separate stance, instruction, knowledge, and output format |
The key distinction: existing approaches either decompose *tasks* (what to do) or *structure prompts* (how to format). Faceted Prompting decomposes *prompt concerns* (why each part exists) into independent, reusable units.
## Practical Benefits
**For workflow authors:**
- Change a coding standard in one stance file; every workflow using it gets the update
- Create a new workflow by combining existing personas, stances, and instructions
- Focus each file on a single responsibility
**For teams:**
- Standardize behavior (stances) across projects without duplicating prompts
- Domain experts maintain knowledge files; workflow designers maintain instructions
- Review individual concerns independently
**For the engine:**
- Prompt assembly is deterministic — given the same workflow definition and files, the same prompt is built
- Stance placement can be optimized (e.g., placed at top and bottom to counter the "Lost in the Middle" effect)
- Concerns can be injected, omitted, or overridden per step without touching other parts
## Summary
Faceted Prompting is a design pattern that applies Separation of Concerns to AI prompt engineering. By decomposing prompts into five independent concerns — Persona, Stance, Instruction, Knowledge, and Report Format — and composing them declaratively, it enables reusable, maintainable, and transparent multi-agent workflows.

View File

@ -1,17 +1,16 @@
/**
* takt export-cc Deploy takt skill files to Claude Code.
*
* Copies the following to ~/.claude/:
* commands/takt.md /takt command entry point
* skills/takt/SKILL.md Engine overview
* skills/takt/references/ Engine logic + YAML schema
* skills/takt/pieces/ Builtin piece YAML files
* skills/takt/personas/ Builtin persona .md files
* skills/takt/stances/ Builtin stance files
* skills/takt/instructions/ Builtin instruction files
* skills/takt/knowledge/ Builtin knowledge files
* skills/takt/report-formats/ Builtin report format files
* skills/takt/templates/ Builtin template files
* Copies the following to ~/.claude/skills/takt/:
* SKILL.md Engine overview (user-invocable as /takt)
* references/ Engine logic + YAML schema
* pieces/ Builtin piece YAML files
* personas/ Builtin persona .md files
* stances/ Builtin stance files
* instructions/ Builtin instruction files
* knowledge/ Builtin knowledge files
* report-formats/ Builtin report format files
* templates/ Builtin template files
*
* Piece YAML persona paths (../personas/...) work as-is because
* the directory structure is mirrored.
@ -34,10 +33,6 @@ function getSkillDir(): string {
return join(homedir(), '.claude', 'skills', 'takt');
}
function getCommandDir(): string {
return join(homedir(), '.claude', 'commands');
}
/** Directories within builtins/{lang}/ to copy as resource types */
const RESOURCE_DIRS = [
'pieces',
@ -59,7 +54,6 @@ export async function deploySkill(): Promise<void> {
const skillResourcesDir = join(getResourcesDir(), 'skill');
const langResourcesDir = getLanguageResourcesDir(lang);
const skillDir = getSkillDir();
const commandDir = getCommandDir();
// Verify source directories exist
if (!existsSync(skillResourcesDir)) {
@ -84,23 +78,18 @@ export async function deploySkill(): Promise<void> {
const copiedFiles: string[] = [];
// 1. Deploy command file: ~/.claude/commands/takt.md
const commandSrc = join(skillResourcesDir, 'takt-command.md');
const commandDest = join(commandDir, 'takt.md');
copyFile(commandSrc, commandDest, copiedFiles);
// 2. Deploy SKILL.md
// 1. Deploy SKILL.md
const skillSrc = join(skillResourcesDir, 'SKILL.md');
const skillDest = join(skillDir, 'SKILL.md');
copyFile(skillSrc, skillDest, copiedFiles);
// 3. Deploy references/ (engine.md, yaml-schema.md)
// 2. Deploy references/ (engine.md, yaml-schema.md)
const refsSrcDir = join(skillResourcesDir, 'references');
const refsDestDir = join(skillDir, 'references');
cleanDir(refsDestDir);
copyDirRecursive(refsSrcDir, refsDestDir, copiedFiles);
// 4. Deploy all resource directories from builtins/{lang}/
// 3. Deploy all resource directories from builtins/{lang}/
for (const resourceDir of RESOURCE_DIRS) {
const srcDir = join(langResourcesDir, resourceDir);
const destDir = join(skillDir, resourceDir);
@ -116,7 +105,6 @@ export async function deploySkill(): Promise<void> {
// Show summary by category
const skillBase = join(homedir(), '.claude');
const commandFiles = copiedFiles.filter((f) => f.startsWith(commandDir));
const skillFiles = copiedFiles.filter(
(f) =>
f.startsWith(skillDir) &&
@ -130,12 +118,6 @@ export async function deploySkill(): Promise<void> {
const reportFormatFiles = copiedFiles.filter((f) => f.includes('/report-formats/'));
const templateFiles = copiedFiles.filter((f) => f.includes('/templates/'));
if (commandFiles.length > 0) {
info(` コマンド: ${commandFiles.length} ファイル`);
for (const f of commandFiles) {
info(` ${relative(skillBase, f)}`);
}
}
if (skillFiles.length > 0) {
info(` スキル: ${skillFiles.length} ファイル`);
for (const f of skillFiles) {

View File

@ -0,0 +1,260 @@
#!/usr/bin/env node
/**
* Generate hybrid-codex piece variants from standard pieces.
*
* For each standard piece (not already -hybrid-codex, not in skip list):
* 1. Parse the YAML
* 2. Add `provider: codex` to all coder movements (including parallel sub-movements)
* 3. Change name to {name}-hybrid-codex
* 4. Write the hybrid-codex YAML file
* 5. Update piece-categories.yaml to include generated hybrids
*
* Usage:
* node tools/generate-hybrid-codex.mjs # Generate all
* node tools/generate-hybrid-codex.mjs --dry-run # Preview only
*/
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
import { join, basename, dirname } from 'node:path';
import { parse, stringify } from 'yaml';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');
const BUILTINS = join(ROOT, 'builtins');
const LANGUAGES = ['en', 'ja'];
/** Pieces that should NOT get hybrid variants (no coder involvement or special purpose) */
const SKIP_PIECES = new Set(['magi', 'research', 'review-only']);
const CODER_PERSONA = 'coder';
const dryRun = process.argv.includes('--dry-run');
// ─────────────────────────────────────────
// Movement transformation
// ─────────────────────────────────────────
function hasCoderPersona(movement) {
if (movement.persona === CODER_PERSONA) return true;
if (movement.parallel) return movement.parallel.some(sub => sub.persona === CODER_PERSONA);
return false;
}
/**
* Insert a field into an object after specified anchor fields (preserves key order).
* If anchor not found, appends at end.
*/
function insertFieldAfter(obj, key, value, anchorFields) {
if (obj[key] === value) return obj;
const result = {};
let inserted = false;
for (const [k, v] of Object.entries(obj)) {
if (k === key) continue; // Remove existing (will re-insert)
result[k] = v;
if (!inserted && anchorFields.includes(k)) {
result[key] = value;
inserted = true;
}
}
if (!inserted) result[key] = value;
return result;
}
/**
* Add `provider: codex` to all coder movements (recursively handles parallel).
*/
function addCodexToCoders(movements) {
return movements.map(m => {
if (m.parallel) {
return { ...m, parallel: addCodexToCoders(m.parallel) };
}
if (m.persona === CODER_PERSONA) {
return insertFieldAfter(m, 'provider', 'codex', ['knowledge', 'stance', 'persona']);
}
return m;
});
}
// ─────────────────────────────────────────
// Hybrid piece builder
// ─────────────────────────────────────────
/** Top-level field order for readable output */
const TOP_FIELD_ORDER = [
'name', 'description', 'max_iterations',
'stances', 'knowledge', 'personas', 'instructions', 'report_formats',
'initial_movement', 'loop_monitors', 'answer_agent', 'movements',
];
function buildHybrid(parsed) {
const hybrid = {};
for (const field of TOP_FIELD_ORDER) {
if (field === 'name') {
hybrid.name = `${parsed.name}-hybrid-codex`;
} else if (field === 'movements') {
hybrid.movements = addCodexToCoders(parsed.movements);
} else if (parsed[field] != null) {
hybrid[field] = parsed[field];
}
}
// Carry over any extra top-level fields not in the order list
for (const key of Object.keys(parsed)) {
if (!(key in hybrid) && key !== 'name') {
hybrid[key] = parsed[key];
}
}
return hybrid;
}
function generateHeader(sourceFile) {
return [
`# Auto-generated from ${sourceFile} by tools/generate-hybrid-codex.mjs`,
'# Do not edit manually. Edit the source piece and re-run the generator.',
'',
'',
].join('\n');
}
// ─────────────────────────────────────────
// Category handling
// ─────────────────────────────────────────
/** Recursively collect all piece names from a category tree */
function collectPieces(obj) {
const pieces = [];
if (!obj || typeof obj !== 'object') return pieces;
if (Array.isArray(obj.pieces)) pieces.push(...obj.pieces);
for (const [key, val] of Object.entries(obj)) {
if (key === 'pieces') continue;
if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
pieces.push(...collectPieces(val));
}
}
return pieces;
}
/** Find the key for the hybrid top-level category */
function findHybridTopKey(categories) {
for (const key of Object.keys(categories)) {
if (key.includes('Hybrid') || key.includes('ハイブリッド')) return key;
}
return null;
}
/**
* Build mapping: standard piece name top-level category key.
* Excludes the hybrid category and "Others" category.
*/
function getTopLevelMapping(categories, hybridKey, othersKey) {
const map = new Map();
for (const [key, val] of Object.entries(categories)) {
if (key === hybridKey) continue;
if (othersKey && key === othersKey) continue;
if (typeof val !== 'object' || val === null) continue;
const pieces = collectPieces(val);
for (const p of pieces) map.set(p, key);
}
return map;
}
/**
* Build the hybrid category section by mirroring standard categories.
*/
function buildHybridCategories(generatedNames, topMap) {
// Group hybrids by their source piece's top-level category
const grouped = new Map();
for (const hybridName of generatedNames) {
const sourceName = hybridName.replace('-hybrid-codex', '');
const topCat = topMap.get(sourceName);
if (!topCat) continue;
if (!grouped.has(topCat)) grouped.set(topCat, []);
grouped.get(topCat).push(hybridName);
}
const section = {};
for (const [catKey, hybrids] of grouped) {
section[catKey] = { pieces: hybrids.sort() };
}
return section;
}
// ─────────────────────────────────────────
// Main
// ─────────────────────────────────────────
console.log('=== Generating hybrid-codex pieces ===\n');
for (const lang of LANGUAGES) {
console.log(`[${lang}]`);
const generatedNames = [];
const piecesDir = join(BUILTINS, lang, 'pieces');
const files = readdirSync(piecesDir)
.filter(f => f.endsWith('.yaml') && !f.includes('-hybrid-codex'))
.sort();
for (const file of files) {
const name = basename(file, '.yaml');
if (SKIP_PIECES.has(name)) {
console.log(` Skip: ${name} (in skip list)`);
continue;
}
const content = readFileSync(join(piecesDir, file), 'utf-8');
const parsed = parse(content);
if (!parsed.movements?.some(hasCoderPersona)) {
console.log(` Skip: ${name} (no coder movements)`);
continue;
}
const hybrid = buildHybrid(parsed);
const header = generateHeader(file);
const yamlOutput = stringify(hybrid, { lineWidth: 120, indent: 2 });
const outputPath = join(piecesDir, `${name}-hybrid-codex.yaml`);
if (dryRun) {
console.log(` Would generate: ${name}-hybrid-codex.yaml`);
} else {
writeFileSync(outputPath, header + yamlOutput, 'utf-8');
console.log(` Generated: ${name}-hybrid-codex.yaml`);
}
generatedNames.push(`${name}-hybrid-codex`);
}
// ─── Update piece-categories.yaml ───
const catPath = join(BUILTINS, lang, 'piece-categories.yaml');
const catRaw = readFileSync(catPath, 'utf-8');
const catParsed = parse(catRaw);
const cats = catParsed.piece_categories;
if (cats) {
const hybridKey = findHybridTopKey(cats);
const othersKey = Object.keys(cats).find(k =>
k === 'Others' || k === 'その他'
);
if (hybridKey) {
const topMap = getTopLevelMapping(cats, hybridKey, othersKey);
const newSection = buildHybridCategories(generatedNames, topMap);
cats[hybridKey] = newSection;
if (dryRun) {
console.log(` Would update: piece-categories.yaml`);
console.log(` Hybrid pieces: ${generatedNames.join(', ')}`);
} else {
const catOut = stringify(catParsed, { lineWidth: 120, indent: 2 });
writeFileSync(catPath, catOut, 'utf-8');
console.log(` Updated: piece-categories.yaml`);
}
} else {
console.log(` Warning: No hybrid category found in piece-categories.yaml`);
}
}
console.log();
}
console.log('Done!');
if (dryRun) console.log('(dry-run mode, no files were written)');