refactor(docs): Optimize CLAUDE.md structure (47KB→12KB) + expand AGENTS.md v16.0

## Summary
- **CLAUDE.md optimization:** Move engineering guidelines to AGENTS.md only (governance lock)
  - Removed: Governance, Testing Strategy, Observability details, Common Workflows, Guardrails
  - Kept: Project status, timeline, architecture high-level overview, quick reference
  - Result: 47KB → 12.1KB (75% reduction, well within 40KB limit)

- **AGENTS.md expansion:** Add 5 missing engineering procedure sections
  - v16.0 Testing Strategy (xUnit/Vitest/Playwright organization, commands, rules)
  - v16.0 Backend Architecture (Vertical Slice, Database/Migrations, Hangfire Job Design)
  - v16.0 Frontend Architecture (Registry-driven screens, KBX contracts, UI adapter boundary)
  - v16.0 Observability (Logging, Tracing, Dashboards, Metrics)
  - v16.0 Common Workflows (Adding Vertical Slices, Refactoring, Creating Jobs)

- **New companion docs** (no duplication, supplement AGENTS.md):
  - docs/ARCHITECTURE_DETAILED.md — Deep dive on backend/frontend patterns
  - docs/COMMON_WORKFLOWS.md — Workflow procedures with examples
  - docs/GITEA_API_REFERENCE.md — Gitea API + External data sources

## Governance (enforced)
- All engineering procedures now in AGENTS.md ONLY
- CLAUDE.md = project context only (status, timeline, overview)
- Companion docs reference AGENTS.md (no duplicate guidance)
- No conflicting guidance across multiple sources

## Result
- CLAUDE.md: 12.1KB  (within 40KB limit)
- AGENTS.md: 44.8KB (comprehensive procedures)
- Single source of truth for all engineering guidelines

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 14:47:41 +09:00
parent 266db96576
commit 07ad98ec12
5 changed files with 1100 additions and 935 deletions
+401
View File
@@ -542,3 +542,404 @@ Every task — code change, refactor, new feature, tooling, infrastructure — m
- Never claim completion from an intended command. Record the actual command result and artifact path.
- For migrations, preserve fresh-install, upgrade, re-run, and failure-rehearsal evidence before calling the Slice complete.
- When a change fails validation, revert or isolate the failed draft before starting the next Slice; do not leave an unapplied journal or partial scaffold as if it were approved.
---
## v16.0 Testing Strategy
### Backend Testing (xUnit)
**Test Organization:**
```
tests/
KArtSell.ArchitectureTests/ # SOLID + pattern compile-time rules
KArtSell.ModelOperations.UnitTests/
KArtSell.SignalEngine.UnitTests/
KArtSell.Integration.Tests/ # With real PostgreSQL
```
**Test Levels (in order of precedence):**
1. **Unit:** Pure functions (Policy, Mapper), no I/O. Fast, deterministic. NO mocks for domain logic.
2. **Integration:** Handler + Dapper + real PostgreSQL. Validates transaction boundaries, Outbox/Inbox idempotency, cascade behavior.
3. **Data:** SQL query validation, schema conformance, index effectiveness, PIT correctness.
4. **E2E:** Full HTTP stack + real DB; used sparingly for critical paths only.
5. **Golden/Frozen OOS:** Before merging algorithm changes, lock baseline and diff against new run.
**Run Tests:**
```bash
# All tests
dotnet test KArtSell.sln -c Release
# By category
dotnet test --filter "Category=Integration" -c Release
dotnet test --filter "FullyQualifiedName~UnitTests" -c Release
# Single test
dotnet test --filter "FullyQualifiedName=Namespace.Class.Method" -c Release
```
**Rules:**
- Integration tests MUST use real database. Never mock Dapper or EF.
- All tests must be repeatable. No DateTime.Now, no random seed, no network.
- Skipped tests MUST be recorded in TECH_DEBT_REGISTER with DECISION_REQUIRED.
- Failed tests are not skipped; they are fixed or marked as KNOWN_ISSUE with reproduction steps.
### Frontend Testing (Vitest + Playwright)
**Unit Tests (Vitest):**
```bash
cd frontend
pnpm test # Run all
pnpm test -- --reporter=verbose
pnpm test -- <test-file-pattern>
pnpm test -- --coverage
```
**E2E Tests (Playwright):**
```bash
cd frontend
pnpm exec playwright install --with-deps chromium
pnpm e2e # Headless
pnpm e2e -- --debug # Debug mode (browser open)
pnpm exec playwright test --headed # UI visible
```
**Coverage Expectations:**
- **Unit:** Screen/page components: ≥70% line coverage. Composables/hooks: ≥80%.
- **E2E:** Critical user workflows only (auth, search, create, approve, export). Do not aim for 100% E2E.
---
## v16.0 Backend Architecture
### Module Structure & Vertical Slices
Each feature is complete: `Endpoint → Handler → Policy → Sql → Outbox`
```
Features/<SliceName>/
Endpoint.cs # FastEndpoints handler (HTTP contract)
Request.cs # Input DTO + validation rules
Response.cs # Output DTO
Validator.cs # Fluent/Zod-style validation
Handler.cs # Use case orchestration (Application)
Policy.cs # Pure domain logic (Domain layer)
Sql.cs # Dapper queries (Data layer)
Mapper.cs # Entity ↔ DTO
Jobs/ # Related Hangfire jobs
Contracts/ # Event & Job schemas
Tests/ # Unit + integration tests
README.md # Traceability: Requirement, ADR, assumptions
```
**Key Rules:**
- Endpoint: HTTP concerns only (routing, content negotiation, status codes)
- Handler: Transaction boundary; orchestrates Policy + Sql
- Policy: Pure business logic; no I/O, no DateTime.Now, no mocks in tests
- Sql: Dapper with explicit columns, schema-qualified names, NO SELECT *
**Design Anti-Patterns (FORBIDDEN):**
- ❌ Generic Repository
- ❌ Service Layer (Handler + Policy + Sql replaces it)
- ❌ Cross-module direct table access
- ❌ DateTime.Now (use IClock)
- ❌ Reflection-based plugin framework
- ❌ Premature microservice split
### Database & Migrations
**DbUp (Single Source of Truth):**
- Runs at Host startup via `KArtSell.DbMigrator`
- Each module owns its schema (e.g., `model_operations.*`, `signal_engine.*`)
- Migrations are immutable; failed migration halts and requires manual recovery
- Every migration must have fresh-install, upgrade, re-run, and failure-recovery tests in CI
**Query Patterns (Dapper):**
```csharp
// ✅ DO: Schema-qualified, explicit columns, PIT condition, cancellation token
const string sql = """
SELECT id, name, created_at
FROM model_operations.signals
WHERE published_at <= @cutoff
AND status = @status
ORDER BY created_at DESC
""";
var result = await connection.QueryAsync<SignalDto>(sql, new { cutoff, status }, commandTimeout: 30);
// ❌ DON'T: SELECT *, no PIT, generic repo, no cancellation
const string sql = "SELECT * FROM signals";
```
**PIT (Point-in-Time) Queries (Mandatory for Audit):**
- Every query against time-series data must include: `WHERE published_at <= @cutoff`
- Revision resolver must select the latest non-deleted revision per entity
- Audit/compliance queries can use time-travel; business queries cannot
**Async Coupling (Outbox → Inbox):**
- When a command succeeds, events inserted into `outbox` in same transaction (atomic with command result)
- Hangfire job polls outbox, publishes to subscribers, marks processed
- Every inbox handler is idempotent; replay of same event = no-op
- Idempotency key ensures duplicate events are detected and skipped
### Hangfire (Background Jobs & Scheduling)
**Job Design Rules:**
- **Not a policy engine:** Jobs execute Commands; they do NOT make business decisions (Policy does)
- **Idempotency key:** Each job must be replayable with same input = same output
- **Watermark & version set:** Track job progress state across retries
- **Queue isolation:** `q-customer-sla` (business SLA) separate from `q-research` (non-critical)
- **Retry classification:**
- `transient` (network glitch) → retry immediately
- `permanent` (bad input, constraint violation) → log & alert
- `dq` (data quality issue) → quarantine for manual review
- `business-hold` (waiting for approval/external event) → hold until ready
**Job Structure:**
```csharp
public class MyJobCommand : ICommand
{
public string IdempotencyKey { get; set; } // Unique per logical job
public Guid JobRunId { get; set; } // Hangfire instance ID
public Guid CorrelationId { get; set; } // Trace correlation
public Guid? Watermark { get; set; } // Job progress state
}
public class MyJobHandler : ICommandHandler<MyJobCommand>
{
public async Task Handle(MyJobCommand cmd, CancellationToken ct)
{
// Idempotent: safe to replay
// Must emit to Outbox on success
// Must classify failure and throw appropriate exception
}
}
```
**Job Execution:**
```csharp
// Enqueue via client
await backgroundJobClient.EnqueueAsync<MyJobHandler>(h => h.Handle(command, CancellationToken.None));
// Never call jobs directly from other jobs. Instead:
// 1. Emit event to Outbox
// 2. Inbox handler subscribes and enqueues next job
```
---
## v16.0 Frontend Architecture
### Registry-Driven Screen Registry
**Single Source of Truth:** Screen definition is the contract for routing, permissions, help, grid config, and component layout.
```typescript
// features/<feature>/registry.ts
export interface ScreenDefinition {
screenId: string; // e.g., "oms.orders.list"
title: string; // Display name
module: "OMS" | "WMS" | "ERP"; // Functional area
path: string; // Vue Router path
component: () => Promise<any>; // Lazy-loaded page component
permissions: string[]; // Required RBAC permissions
help?: HelpDefinition; // Contextual help
grid?: GridDefinition; // AG Grid config
shortcut?: string; // Keyboard shortcut
}
export const myListScreen: ScreenDefinition = {
screenId: "oms.orders.list",
title: "Orders",
path: "/oms/orders",
component: () => import("./pages/OrdersList.vue"),
permissions: ["order.view"],
help: { title: "...", sections: [...] },
grid: { columnDefs: [...], rowHeight: "auto" },
shortcut: "Ctrl+Shift+O"
}
export default [myListScreen]
```
**Central Registry:**
```typescript
// frontend/src/registry/index.ts
// Import all feature registries and merge into ScreenRegistry
// Used by app initialization, permission checks, help system, routing
```
**Route Generation:**
```typescript
// app/installKbx.ts
const registry = await loadScreenRegistry()
const routes = buildRouterFromRegistry(registry) // Page routes only
```
**Rules:**
- Routing is generated from registry. DO NOT define routes in `app/router.ts`
- Each screen is a top-level route. NO nested routing.
- Registry is immutable at runtime; use `useRegistry()` composable to access
### UI Adapter Boundary (Framework Isolation)
**Mandatory Pattern:** All PrimeVue and AG Grid usage goes through `@kbx/ui/adapter/`
```typescript
// ❌ DON'T: Use PrimeVue directly in screens
import { Button } from 'primevue/button'
<PButton label="Save" @click="save" />
// ✅ DO: Use KBX adapter (framework-agnostic)
import { KbxButton } from '@shared/ui/adapter'
<KbxButton label="Save" @click="save" />
// Adapter handles:
// - Theme switching (dark/light/system)
// - Density token application
// - Accessibility (ARIA, focus management)
// - Keyboard shortcuts
```
**Adapter exports:**
- `KbxButton`, `KbxInput`, `KbxSelect`, `KbxDialog`, etc.
- `useGridTheme()` for AG Grid configuration
- `useDesignToken(name)` for CSS custom properties
### State Management (Contract-Based)
| State | Owner | Tool | Registry Link |
|-------|-------|------|---|
| API responses, cache, stale, retry | TanStack Query | @tanstack/vue-query | → OpenAPI contracts |
| Session, role, UI preferences | Global Pinia | `authStore`, `registryStore` | → PermissionDefinition |
| Form values, errors, touched | Form library | vee-validate + Zod | → Screen.forms contract |
| URL filters, pagination, sorting | Router | vue-router query/params | → ScreenDefinition.grid |
| Large data tables | Server-side row model | AG Grid server mode | → GridDefinition contract |
**Rules:**
- ❌ Do NOT duplicate API responses in Pinia (use TanStack Query cache)
- ❌ Do NOT write error handling in every screen (use ErrorBoundary + QueryStateBoundary)
- ✅ DO cache only session/auth data in Pinia (global, cross-screen)
- ✅ DO use TanStack Query for all API state
### Component Elevation Criteria
Promote to `shared/ui/components/` only when:
1. **Same business meaning & permissions** across 3+ consumers
2. **Repeated state/error handling logic** (not 1-off variations)
3. **Accessibility & testing** fully implemented
4. **Contract-driven** (implements @kbx/contracts interface)
**Always-Shared Components (KBX System):**
- `QueryStateBoundary` (loading/error/empty)
- `PermissionGuard` (RBAC via registry)
- `ScreenHeader` (title, help, export buttons from registry)
- `AgGridShell` (AG Grid adapter with density tokens)
- `KbxStatus` (status display per contract)
- `KbxHelpPanel` (registry-driven help)
- `SkeletonLoader` (animated shimmer while loading)
- `EmptyStatePlaceholder` (zero-record state)
---
## v16.0 Observability
### Logging (Serilog)
**Correlation & Structure:**
- All logs tagged with `CorrelationId`, `JobRunId`, `EvidenceId`
- Structured properties enable filtering and analysis
- Sensitive data (PII, tokens, API keys) NEVER logged (use redaction middleware)
**Log Levels:**
- **INFO:** User actions, job completion, state changes
- **DEBUG:** Internal flow, decision branches, cache hits/misses
- **WARN:** Recoverable issues, retries, fallback activation
- **ERROR:** Unrecoverable failures, requires alert
### Tracing & Metrics (OpenTelemetry)
**Spans:** HTTP requests, database queries, job execution, event processing, policy decisions
**Metrics:** Instrumented for:
- Job completion time, queue depth
- Query latency, row count
- Event throughput, retry rate
### Operational Dashboards (Priority Order)
1. **Batch SLA:** Job completion times, queue depths (`q-customer-sla` vs `q-research`)
2. **Data Quality Quarantine:** Jobs marked `dq` for manual review
3. **Duplicate Detection:** Outbox duplicate events
4. **Reconciliation Breaks:** State mismatch (Evidence vs current)
5. **Model Drift:** OOS (out-of-sample) performance metrics
---
## v16.0 Common Workflows
### Adding a New Vertical Slice
**Before Code:**
1. Scaffold: `python tools/scaffold_vertical_slice.py --name MyFeature --module ModelOperations`
2. Define contract: Request/Response DTOs, Event schema, Validation rules
**Backend Implementation:**
1. Handler: Orchestration, transaction handling
2. Policy: Pure business logic
3. Sql: Dapper queries (schema-qualified, explicit columns, PIT)
4. Endpoint: HTTP routing & status codes
5. Tests: Unit (Policy), Integration (Handler + Sql + real DB)
6. README.md: Traceability link
**Frontend Implementation:**
1. Feature registry: `ScreenDefinition` entry
2. Pages: Router-level components under `features/<feature>/pages/`
3. Components: Feature-scoped under `features/<feature>/components/`
4. Stores/Composables: Feature-specific state and logic
5. Form validation: vee-validate + Zod schema from BE contract
**Pre-Merge Validation Gates:**
- Architecture tests pass
- DB migration is idempotent (fresh/upgrade/re-run/failure tests)
- No SELECT *, no cross-module queries
- Outbox/Inbox tests if async
- Frontend typecheck + test + build passes
- E2E smoke test (if user-facing)
### Refactoring (Characterized, Isolated, Verified)
1. **Characterize:** Lock current behavior with tests, perf baseline, Golden data
2. **Isolate:** Separate I/O (Dapper, HTTP) from logic (Policy)
3. **Transform:** One small change at a time (rename, extract, move)
4. **Verify:** All tests pass, no perf regression, algorithm changes vs Golden
5. **Simplify:** Delete dead abstractions, feature flags, branches
6. **Observe:** Post-release monitoring (SLO, data quality, model drift)
7. **Close Debt:** Update Tech Debt Register, leave ADR
### Creating a Background Job
1. **Define command:**
```csharp
public class MyJobCommand : ICommand
{
public string IdempotencyKey { get; set; }
public Guid CorrelationId { get; set; }
}
```
2. **Implement handler:**
- Idempotent: re-run = same result
- Classify failures: transient/permanent/dq/business-hold
- Emit to Outbox on success
3. **Schedule via Hangfire:**
```csharp
await backgroundJobClient.EnqueueAsync<MyJobHandler>(h => h.Handle(command, CancellationToken.None));
```
4. **Test scenarios:**
- Normal execution
- Retry on transient failure
- Replay from cold state (idempotency verification)
- Data quality quarantine