commit 4d6495ffda5af53a884924c2004ee53d3835c623 Author: ada Date: Mon May 25 05:47:28 2026 +0000 Initial commit diff --git a/.agents/checks/effect-review.md b/.agents/checks/effect-review.md new file mode 100644 index 0000000..81aa31b --- /dev/null +++ b/.agents/checks/effect-review.md @@ -0,0 +1,21 @@ +--- +name: effect-review +description: Review code against Effect Patterns best practices +severity-default: medium +tools: [Read, Grep, glob, get_diagnostics, Bash, oracle] +--- + +# Effect Patterns Code Review + +Review the changed files to ensure they follow Effect Patterns best practices according to the canonical `@docs/Effect-Patterns-Rules.md`. + +## Instructions for the agent +1. **Run the AST Analyzer**: You MUST use `Bash` to run `bun run scripts/effect-analyzer/bin/check.ts ` on each modified TypeScript file. Report any warnings or errors it finds. +2. Read the relevant parts of `@docs/Effect-Patterns-Rules.md` if you are unsure. +3. Focus exclusively on Effect-TS usage. Do not comment on general formatting unless it obscures Effect best practices. +4. Point out any code that uses `Promise.then` or `try/catch` incorrectly instead of `Effect.tryPromise` or `Effect.try`. +5. Flag usage of unstructured errors (`throw new Error()`) instead of typed errors (`Effect.fail`). + +## Key aspects to check manually + +If there are any violations, report them and suggest the Effect-native alternative. diff --git a/.agents/commands/design-coder.md b/.agents/commands/design-coder.md new file mode 100644 index 0000000..15deb10 --- /dev/null +++ b/.agents/commands/design-coder.md @@ -0,0 +1,36 @@ +# Design Mode: Coder + +You are now in **Coder Mode** for the Type-Driven Functional Domain-Driven Design (TDFDDD) process. + +## Your Role +You are the **Implementer/Coder**. The user is the **Domain Expert**. + +## Your Behavior +1. **Drive the technical process.** You will walk through the phases (Event Storming -> Domain Modeling -> Contract -> Implementation). +2. **Ask clarifying questions about the business domain.** Do not assume. Ask things like: + * "What happens if the truck is already sealed?" + * "Can a package ever be partially loaded?" + * "What are all the reasons a load could fail?" +3. **Produce artifacts.** After each phase, output the F# pseudo-code types and signatures. +4. **Pause for review.** After Phase 3 (Domain Modeling), stop and ask the user to validate the types before proceeding to implementation. + +## The Process +Follow the protocol in `docs/core-rules/topics/domain-modeling.md`. + +## Output Location +Save all design artifacts to `design//`: +- `01-event-storming.md` — Phase 1 output +- `02-domain-model.md` — Phase 2-3 output (F# types) +- `03-contract.md` — Phase 4 output (Final signatures) + +## Output Format +For each phase, produce a Markdown code block with F# pseudo-code. Example: +```fsharp +type PackageLoaded = { + TruckId: TruckId + Package: Package +} +``` + +## Start +Begin by asking: **"What feature or workflow are we designing today? I'll create a folder in `design/` for it."** diff --git a/.agents/commands/design-mentor.md b/.agents/commands/design-mentor.md new file mode 100644 index 0000000..3a8ba6e --- /dev/null +++ b/.agents/commands/design-mentor.md @@ -0,0 +1,33 @@ +# Design Mode: Mentor + +You are now in **Mentor Mode** for the Type-Driven Functional Domain-Driven Design (TDFDDD) process. + +## Your Role +You are the **Mentor/Reviewer**. The user is the **Learner** (practicing the design process). + +## Your Behavior +1. **Guide, don't drive.** Let the user produce the artifacts. Your job is to critique and refine. +2. **Ask Socratic questions.** When you see a gap, ask: + * "What happens if the input is invalid?" + * "Is there another state the entity could be in?" + * "How would you handle this edge case?" +3. **Point out violations.** If the user's design violates the rules (e.g., impure logic in a Policy, primitive obsession), call it out explicitly. +4. **Validate progress.** After each phase, confirm what is correct and suggest improvements. + +## The Process +The user should follow the protocol in `docs/core-rules/topics/domain-modeling.md`. Remind them of the current phase if they get lost. + +## Feedback Format +Use this structure for feedback: +* ✅ **Correct:** [What they did well] +* ⚠️ **Consider:** [Suggestions or missing pieces] +* ❌ **Issue:** [Violations of the rules] + +## Output Location +Remind the user to save artifacts to `design//`: +- `01-event-storming.md` — Phase 1 output +- `02-domain-model.md` — Phase 2-3 output (F# types) +- `03-contract.md` — Phase 4 output (Final signatures) + +## Start +Begin by saying: **"Let's practice the design process. What feature are you working on? Create a folder in `design//` and start with Phase 1: Event Storming — describe the user story and identify the Command and Events."** diff --git a/.agents/core-rules/README.md b/.agents/core-rules/README.md new file mode 100644 index 0000000..c0426b3 --- /dev/null +++ b/.agents/core-rules/README.md @@ -0,0 +1,28 @@ +# Agent Core Rules + +This directory contains the compressed rule set for agents. +The human-facing explanations live in `docs/` and should be treated as the canonical rationale and teaching material. + +## 1. [Global Context](./global-context.md) +Read this first for the project-wide operating constraints. + +## 2. Topics + +- **[Architecture](./topics/architecture.md)**: Layer boundaries and purity rules. +- **[Data Modeling](./topics/data-modeling.md)**: Schema, rich types, and immutability. +- **[Workflows](./topics/workflows.md)**: Effect orchestration and workflow constraints. +- **[Testing](./topics/testing.md)**: Testing heuristics and layer-based testing. +- **[Naming](./topics/naming.md)**: Naming rules for code and design artifacts. +- **[Design Process](./topics/design-process.md)**: The agent-facing overlay for TDFDDD artifact generation. + +## Canonical human docs + +When you need the explanation behind a rule, prefer these pages: + +- `docs/README.md` +- `docs/explanation/tdfddd-manifesto.md` +- `docs/explanation/tdfddd-protocol.md` +- `docs/explanation/architecture/index.md` +- `docs/tutorials/worked-example-truck-loading.md` +- `docs/reference/review-checklist.md` + diff --git a/.agents/core-rules/global-context.md b/.agents/core-rules/global-context.md new file mode 100644 index 0000000..e0421ae --- /dev/null +++ b/.agents/core-rules/global-context.md @@ -0,0 +1,36 @@ +# Global Context & Core Philosophy + +**Root Philosophy**: Functional Domain-Driven Design (Scott Wlaschin). +*Make illegal states unrepresentable. Use types as documentation.* + +## Guiding Preference +- Prefer correctness, simplicity, and elegance over backwards compatibility. +- Do not preserve awkward APIs or structures unless there is a clear, explicit requirement to do so. + +## 1. The Golden Rule: Purity vs. IO +- **Domain (Pure)**: `src/domain/models` and `src/policies` MUST be 100% pure. Deterministic, no side effects, no service dependency. +- **Workflows (Impure)**: `src/workflows` orchestrate logic. They verify policies and call Services. +- **Services (IO)**: `src/services` handle all I/O (DB, API). They are called *only* by Workflows. + +## 2. Data & Composition +- **Immutable**: Never mutate. Return new copies. +- **Data-Last**: Functions take data as the last argument to support `pipe`. +- **Schema First**: Use `@effect/schema` for all domain objects. No Classes. +- **Branded Types**: Use `Brand<"USD">` instead of raw primitives. + +## 3. Error Handling +- **Typed Errors**: Use discriminated unions for errors. No generic `throw`. +- **Fail Fast**: Validate inputs at the edge (Workflows/Controllers). + +## 4. Structure +- `domain/models/`: Schema + Pure Ops (one file per entity or folder with index). +- `policies/`: Cross-entity business rules (Pure). Returns Decision objects. +- `workflows/`: The "glue". `Effect.gen`, Service wiring. +- `services/`: Interfaces (Ports) and Implementations (Adapters). + +## 5. The Design Protocol (TDFDDD) +1. **Flow**: Define `Command` (Input) → `Event` (Output). +2. **Contract**: Write `(Input) => Effect` signatures. +3. **Partition**: Isolate pure rules (Policies) from side-effects (Services). +4. **Model**: Bridge Input to Output with specific types (Success & Failure, `TrustedInput`/`TaintedInput` Wrappers). +5. **Assembly**: Implement logic only after types verify. diff --git a/.agents/core-rules/topics/architecture.md b/.agents/core-rules/topics/architecture.md new file mode 100644 index 0000000..0d7ec02 --- /dev/null +++ b/.agents/core-rules/topics/architecture.md @@ -0,0 +1,37 @@ +--- +globs: + - "src/**/*.ts" +--- +# Architecture & Purity Rules + +1. **Strict Separation of Concerns**: + - **Domain Models**: `src/domain/models`. Pure data + Ops. (Zero dependencies). + - **Policies**: `src/policies`. Pure business rules. (Zero dependencies). + - **Workflows**: `src/workflows`. Orchestration & I/O. (Depends on Services/Policies). + - **Services**: `src/services`. I/O interfaces & adapters. + +2. **The Purity Mandate**: + - **Domain & Policies MUST be 100% Pure**. No `Effect.gen`, no `Promise`, no external imports. + - **Why**: Guarantees testability and deterministic behavior for complex rules. + +3. **Policies Return Decisions**: + - Policies should return rich objects (Discriminated Unions), not booleans. + - `const canAccess = (u: User): AccessDecision => ({ allowed: false, reason: "Expired" })` + - A pure decision should usually still be a function, but it does **not** always need a top-level `src/policies/` module. + - Promote a decision to `src/policies/` only when extraction materially improves **nameability, testability, reuse, or boundary clarity**. + - Prefer a local pure function when extraction would only add indirection. + +4. **No Orphaned Logic (Strict Boundaries)**: + - **Policies** define *Decisions* (business rules). + - **Effect.Schema** defines *Shape* (validation). If a payload fails schema check, it's an infrastructure error (Parse, Don't Validate). + - **Workflows** orchestrate logic but MUST NOT contain `if` statements for business rules. Use `Match.value` to branch on a Policy's decision. + - **Models** apply pure mathematical state changes without deciding business rules. + +5. **Cross-Module Imports Use Public APIs**: + - When code in one module imports from another module, import through that module's public API (`index.ts` or other documented entrypoint). + - Do not reach into another module's private internal files from the outside. + +6. **No Business Logic in Services**: + - Services are for *capabilities* (Store DB, Send Email), not *decisions*. + - ❌ `UserService.createIfAllowed()` + - ✅ `Workflow: if (Policy.isAllowed(user)) yield* UserService.create()` diff --git a/.agents/core-rules/topics/data-modeling.md b/.agents/core-rules/topics/data-modeling.md new file mode 100644 index 0000000..d4c258b --- /dev/null +++ b/.agents/core-rules/topics/data-modeling.md @@ -0,0 +1,21 @@ +--- +globs: + - "src/domain/models/**/*.ts" + - "src/domain/data/**/*.ts" +--- +# Data Modeling & Schema Rules + +1. **Use Schema.Struct (Not Classes)**: Define data as plain objects with `Schema.Struct` for serializability. Logic goes in separate `Ops` modules. + `const Cart = Schema.Struct({ items: Schema.Array(Item) })` + +2. **Branded Primitives**: Always brand IDs and units (Money, Email) to prevent type collisions. + `type USD = number & Brand.Brand<"USD">; const price = Brand.nominally(Schema.Number, "USD")(19.99)` + +3. **Schema Validation**: Enforce invariants (regex, range) via `pipe(Schema.filter/pattern)`. + `const Email = Schema.String.pipe(Schema.pattern(/^@/), Schema.brand("Email"))` + +4. **Immutability**: Never mutate state. Return new copies using spread syntax. + `const add = (c: Cart, i: Item): Cart => ({ ...c, items: [...c.items, i] })` + +5. **Rich Types**: Encode state in types (e.g., `PendingOrder` vs `PaidOrder`) rather than using optional flags. + `type Order = PendingOrder | PaidOrder` (Discriminated Union) diff --git a/.agents/core-rules/topics/design-process.md b/.agents/core-rules/topics/design-process.md new file mode 100644 index 0000000..19f577e --- /dev/null +++ b/.agents/core-rules/topics/design-process.md @@ -0,0 +1,48 @@ +--- +globs: + - "design/**/*.md" +--- +# Design Process Rules + +This file is the agent-facing overlay for the TDFDDD design process. +Use the human docs as the canonical explanation: + +- `docs/explanation/tdfddd-manifesto.md` +- `docs/explanation/tdfddd-protocol.md` +- `docs/tutorials/worked-example-truck-loading.md` +- `docs/reference/design-artifact-template.md` +- `docs/reference/review-checklist.md` + +## Required artifact shape + +For each feature design in `design/`, produce artifacts that make these phases explicit: + +1. Story in domain language +2. Command and outcomes +3. Policy sketch +4. Domain model +5. Final contract +6. Reviewer notes or open questions + +## Design constraints + +- Keep framework and infrastructure terms out of the early design phases. +- Prefer explicit state variants over generic objects with broad `status` fields. +- Return events or rich decisions, not booleans, when business meaning matters. +- Freeze the design before translating it into TypeScript and Effect. +- Make the artifact easy for a human reviewer to compare against the truck-loading example and checklist. +- When proposing a refactor, require an explicit reason if preserving backwards compatibility would keep a worse design. Otherwise prefer the direct change over compatibility-preserving cruft. + +## Output standard + +A design artifact is only ready when a human reviewer can inspect each phase without inventing missing domain meaning. + +## Security verification expectation + +Security review is a verification gate around the design and implementation work. +When the user asks for security review, use the dedicated review-only skills: + +- `tdfddd-design-security-verification` for requirements, domain models, contracts, ADRs, and workflow design +- `tdfddd-implementation-security-verification` for changed code and adjacent seams, with optional expanded-surface review + +Treat missing security design information as reviewable findings rather than silently assuming it away. diff --git a/.agents/core-rules/topics/domain-modeling.md b/.agents/core-rules/topics/domain-modeling.md new file mode 100644 index 0000000..d9365b8 --- /dev/null +++ b/.agents/core-rules/topics/domain-modeling.md @@ -0,0 +1,186 @@ +# Domain Modeling + +> **Also known as:** Type-Driven Functional Domain-Driven Design (TDFDDD). +> In casual contexts, we call this **Functional Domain Modeling**. + +## Design Artifacts Location +All design specs go in the `design/` directory: +``` +design/ +├── / +│ ├── 01-requirements.md # Phase 1: Commands, Events, Story +│ ├── 02-domain-model.md # Phase 2-3: Types in F# pseudo-code +│ ├── 03-contract.md # Phase 4: Final signatures +│ └── notes.md # Optional: Domain expert Q&A, decisions +``` + +## Philosophy +**"Make Illegal States Unrepresentable."** + +Use types to encode business rules. If invalid data cannot be constructed, it cannot cause bugs. + +## The Design Protocol (TDFDDD) + +### Phase 1: Event Storming +1. Identify the **Command** (User Intent). +2. Identify the **Events** (What Happened: Success/Failure). +3. Gather domain context from the expert. + +### Phase 2: Core Sketch +1. Draft the Policy signature: `decide : Input -> State -> Result` +2. List the information required to make the decision. + +### Phase 3: Domain Modeling +1. **Primitives:** Branded types for units (`Weight`, `Volume`). +2. **Compounds:** Group primitives into Structs (`Package`, `TruckCapacity`). +3. **Aggregates:** Define State variants (`LoadingTruck | SealedTruck`). +4. **Events:** Define Outcomes (`PackageLoaded`, `LoadFailure`). + +### Phase 4: The Contract +1. **Policy:** `decide : Input -> State -> Result` (Pure) +2. **Model:** `apply : State -> SuccessEvent -> State` (Pure) +3. **Workflow:** `workflow : InputId -> Effect` (Impure) + +### Phase 5: Implementation +1. Translate F# types to Effect Schemas. +2. Implement Policy (Pure). +3. Implement Workflow: Decide -> Match -> Apply. + +## The Orchestration Pattern + +```typescript +const workflow = (id) => Effect.gen(function*(_) { + // 1. Gather (IO) + const state = yield* _(Repo.get(id)); + + // 2. Decide (Pure) + const decision = Policy.decide(input, state); + + // 3. Match & Apply + return yield* _(Match.value(decision).pipe( + Match.when({ _tag: "Success" }, (event) => Effect.gen(function*(_) { + const newState = Model.apply(state, event); + yield* _(Repo.save(newState)); + return { success: true }; + })), + Match.when({ _tag: "Failure" }, (err) => Effect.fail(err)), + Match.exhaustive + )); +}); +``` + +## Rules +1. **Policies are Pure:** No IO, no services, only data in -> decision out. +2. **Models are Pure:** State transitions are deterministic. +3. **Workflows are Impure:** They orchestrate IO and call Policies/Models. +4. **Events are Data:** They describe "What Happened," not "What To Do." +5. **Domain Events vs Audit Logs:** Events must be *business facts* (Domain), not system operations (CRUD/Audit). + - ❌ BAD (Audit/CRUD): `UserUpdated`, `CheckoutButtonClicked`, `DatabaseSaved` + - ✅ GOOD (Domain): `AddressChanged`, `OrderPriced`, `PaymentAuthorized` + - **Design Backwards:** Start by defining the terminal Event you want to achieve, then define the State required to validate that Event, and finally the Command that initiates it. +6. **Trust Boundaries (Input Security):** Untrusted input (e.g. from users or external systems) must never use raw strings at sensitive boundaries. Define strict wrapper boundaries (`TrustedInput`, `TaintedInput`) to ensure tracking and strict parsing before reaching pure logic or safe adapters. (See `docs/explanation/architecture/input-security.md`) + +## When To Split: Policy vs Model vs Service vs Task vs Module + +Use the smallest abstraction that matches the job. Start simple, then split only when the seam becomes meaningful. + +### Policy +Choose a **Policy** when the code answers a business question from data alone. +- Pure decision +- No IO +- Returns a rich success/failure fact +- Reusable across workflows or tasks + +Ask: +- "Is this deciding whether something is allowed, valid, eligible, or complete?" +- "Could I test this with plain values and no mocks?" + +### Model +Choose a **Model** operation when the code performs a deterministic state transition. +- Pure transformation +- No IO +- No business authority lookup +- Usually runs after a successful policy decision + +Ask: +- "Is this just applying a known event or computing a new state?" +- "If the event is already approved, can this be a pure function?" + +### Service +Choose a **Service** when the code represents a capability or integration. +- Database, API, queue, filesystem, clock, email, payment, AI call +- May fail for infrastructure reasons +- Must not hide business decisions + +Ask: +- "Is this about what the system can do, not what the business allows?" +- "Would changing vendors or infrastructure affect this code?" + +### Task +Choose a **Task** when a workflow grows and a chunk of orchestration becomes a named step with its own contract. +- Still orchestration +- May call policies, models, and services +- May gather data, perform checks, and return a domain-meaningful result to a parent workflow +- Smaller than a top-level workflow; extracted for clarity, reuse, or easier refactoring + +A Task is appropriate when: +- the parent workflow has become hard to read end-to-end +- the step has a clear intent name (`priceOrder`, `reserveInventory`, `chooseNextLesson`) +- the step can be tested independently with its own scenarios +- the step may later become a separate workflow or module + +A Task is not appropriate when: +- it is only a thin wrapper around one policy or one service call +- it exists only to create extra layers +- it cannot be understood without the full caller context + +### Module +Choose a **Module** when a cluster of policies, models, tasks, and services forms a bounded capability. +- Has a coherent language and responsibility +- Has a small public API +- Hides internal helpers and internal orchestration +- May internally contain its own workflow-like structure + +Ask: +- "Would another part of the system use this as a capability boundary?" +- "Can I expose intention-based entrypoints and keep the internals private?" + +## Refactoring Guidance + +Do not try to predict the final tree up front. Prefer this progression: +1. Start with one workflow. +2. As it grows, extract a **Task** when a step gains a stable intent and scenario surface. +3. Extract **Policies** and **Models** aggressively when pure logic appears. +4. Promote a group of related tasks/policies/models into a **Module** only when the boundary is repeatedly useful. + +This keeps the codebase refactorable: +- avoid premature indirection +- keep boundaries intention-based +- allow deeper structure to emerge from real pressure +- preserve strong seams so future LLM or human refactors stay local + +## Split Heuristics + +A new seam is justified when at least one of these is true: +- the name of the step is clearer than its implementation +- the caller should care about the result, not the mechanics +- three or more scenario variations are emerging +- the code needs independent tests to stay reviewable +- the step has narrower dependencies than its parent + +Keep it inline when: +- the logic is still trivial +- the step has no stable domain meaning yet +- extraction would force awkward pass-through parameters +- the new abstraction would only have one obvious implementation and no simplification benefit + +## Composition Rule + +Prefer this direction of composition: +- workflows coordinate tasks +- tasks call policies, models, and services +- modules expose intention-based entrypoints + +A workflow may call another workflow, but treat that as a higher-cost move. Do it only when the callee is truly a reusable business capability with its own command/result contract, lifecycle concerns, and test surface. Otherwise, extract a Task or Module instead. + +The goal is not a perfect static hierarchy. The goal is a codebase that can be safely reshaped as the workflow graph changes over time. diff --git a/.agents/core-rules/topics/naming.md b/.agents/core-rules/topics/naming.md new file mode 100644 index 0000000..b934b9f --- /dev/null +++ b/.agents/core-rules/topics/naming.md @@ -0,0 +1,75 @@ +--- +globs: + - "src/**/*.ts" + - "design/**/*.md" +--- +# Naming Conventions + +## 1. Ubiquitous Language & CONTEXT.md + + When a domain term stabilizes, it must be documented in a `CONTEXT.md` file located at the root of the Bounded Context under `src//CONTEXT.md` (or at the repo root for single contexts). If dealing with a multi-context repo, a root `CONTEXT-MAP.md` should link to these. + + The `CONTEXT.md` file MUST be a pure glossary of domain language, completely devoid of implementation details. + + - **Format**: Define concepts using `**Term**: definition`, followed by an `_Avoid_: ` line. + - **Example dialogue**: Include a short mock conversation between a Developer and a Domain Expert to show how terms interact naturally. + - **Flag conflicts explicitly**: If a term is contested or overloaded, use a "Flagged ambiguities" section to explicitly resolve it. + + *Note: This dictionary format only applies to entries in `CONTEXT.md` files; it does not alter the naming heuristics for variables, types, or services below.* + + ## 2. Structural Naming (File & Module Level) + +1. **Workflows**: Use `Verb-Noun` or `Scenario` names. + - They represent *actions* or *processes*. + - ✅ `checkout`, `fulfillOrder`, `registerUser` + - ❌ `userOrder` (Ambiguous), `orders.ts` (Too generic) + +2. **Policies**: Use `Verb` with `decide`, `validate`, or `calculate`. + - They represent *decisions*. + - ✅ `decideDiscount`, `validateCart`, `calculateShipping` + - ❌ `discountRules`, `cartLogic` + +3. **Ops Modules**: Use `Entity` + `Ops`. + - ✅ `MoneyOps`, `CartOps` + - ❌ `MoneyUtils`, `CartHelper` + +4. **Variables**: + - **Services**: PascalCase when used as a Tag/dependency. `yield* DatabaseService`. + - **Data**: camelCase. `const user = ...` + +## 2. Semantic Heuristics (Domain Modeling) + +1. **State-First Types**: Avoid generic containers with status fields. + - ❌ `Item` (with `status: 'sold'`) + - ✅ `DraftItem` → `OpenItem` → `SoldItem` + +2. **Intent-First Names**: Name by caller intent and stable business meaning, not current implementation shape. + - ❌ `nextLesson` + - ✅ `getNextTask` + - Ask: would this name still be correct if the implementation changed but the user goal stayed the same? + +3. **Capability-Based Boundaries**: Name authority and role explicitly when the boundary is about what a caller may do. + - ✅ `ReadCurriculum`, `WriteCurriculum` + - ✅ `BiddableItem`, `BidHistory` + - ❌ `ItemWithBids` (data description when role/capability matters) + +4. **Bounded Seams Need Strong Names**: At reviewable boundaries, names should expose intent, authority, and protected invariants. + - Prefer seam names that tell the reviewer what the caller is trying to achieve. + - Be suspicious of implementation-shaped names at public seams. + +5. **Three-Alternatives Rule**: For important names, seams, types, and workflows, compare three plausible alternatives before choosing. + - Ask what each alternative implies about caller intent, domain model, and future change pressure. + - Prefer the name that preserves domain intent across likely model evolution. + +6. **The Domain Grammar**: + - **Objects** = Nouns (`ActiveItem`) + - **Events** = Past Verbs (`BidPlaced`) + - **Commands** = Imperative Verbs (`PlaceBid`) + +## 3. Review Smells + +Be suspicious when you see: + +- `Manager`, `Handler`, `Processor`, `Data`, `Info` +- one concept stretched to cover future cases because the name is too narrow +- names that describe storage shape or current implementation instead of domain meaning diff --git a/.agents/core-rules/topics/testing.md b/.agents/core-rules/topics/testing.md new file mode 100644 index 0000000..134f1a5 --- /dev/null +++ b/.agents/core-rules/topics/testing.md @@ -0,0 +1,27 @@ +--- +globs: + - "**/*.test.ts" + - "**/test/**" +--- +# Testing Strategy + +## 1. The "Triad" Rule (Coverage Heuristic) +For every feature/workflow, you must write at least three specs: +1. **Happy Path**: The standard success case. +2. **Sad Path**: The expected failure mode (Typed Errors). +3. **Edge Case**: Boundary conditions or empty states. + +## 2. Unit Tests for Pure Logic +- Test `domain/ops` and `policies` using standard `vitest` assertions. No mocks needed. +- `expect(MoneyOps.add(a, b)).toEqual(c)` + +## 3. Test Layers for Workflows +- Test Workflows by providing *Test Layers* (in-memory implementations) for Services. +- `const result = await program.pipe(Effect.provide(TestDatabaseLayer), Effect.runPromise)` + +## 4. Integration Tests for Adapters +- Test `src/adapters/adapters` against real infrastructure (Docker/TestContainers). +- Do not mock the database in an adapter test; that defeats the purpose. + +## 5. No Mocks +- Avoid `jest.fn()` or traditional spies. Use `Effect` layers to swap implementations. diff --git a/.agents/core-rules/topics/workflows.md b/.agents/core-rules/topics/workflows.md new file mode 100644 index 0000000..2e13b54 --- /dev/null +++ b/.agents/core-rules/topics/workflows.md @@ -0,0 +1,30 @@ +--- +globs: + - "src/workflows/**/*.ts" +--- +# Workflows & Effects Rules + +1. **Use Effect.gen**: Prefer `Effect.gen` generators over chained combinators for readability. + `Effect.gen(function*() { const user = yield* UserService.get(id); ... })` + +2. **Data-Last Signatures**: Functions should accept data as the last argument to support `pipe`. + `const multiply = (factor: number) => (value: number) => value * factor` + +3. **Typed Errors**: Use Discriminated Unions for errors. Avoid generic `Error`. + `class UserNotFound extends Data.TaggedError("UserNotFound")<{ id: string }> {}` + +4. **Explicit Service Dependencies**: Workflows must consume Services via Context/Tag, never direct import of implementations. + `const program = Effect.gen(function*() { const db = yield* DatabaseService; ... })` + +5. **Safe Ordering**: Perform irreversible effects (Emails, Charges) *last* in the workflow. + `yield* Database.save(); yield* Email.send();` (Retrying DB is safe; retrying Email is spam). + +6. **Error Handling Philosophy**: + - Workflows must handle failures (`Either.Left` / `Result.fail`) from Policies. + - A Policy failing is simply a domain fact (e.g., "Truck is full"). The Workflow decides what to do with that fact based on its context (e.g., return HTTP 400, trigger a compensating action, or send an alert). + - Do not throw raw Exceptions for domain errors. Use typed errors and `Effect.fail` or `Effect.catchAll`. + +7. **Idempotency**: All Workflows should be designed to be retried. Accept `idempotencyKey` where applicable. + +7. **State Persistence**: If a workflow is critical (money, data), persist state transitions (e.g., "OrderCreated" event) *before* performing external effects. This enables replay/recovery. + `yield* EventStore.append("OrderCreated"); yield* Payment.charge();` diff --git a/.agents/skills/README.md b/.agents/skills/README.md new file mode 100644 index 0000000..bbe1fd8 --- /dev/null +++ b/.agents/skills/README.md @@ -0,0 +1,54 @@ +# Effect Patterns Skills + +This directory contains 24 auto-generated Claude Code skills for Effect-TS patterns. + +## Skills Included + +All skills are organized by category and include beginner, intermediate, and advanced patterns: + +- `effect-patterns-error-handling` - Error handling and recovery patterns +- `effect-patterns-concurrency` - Concurrent and parallel execution patterns +- `effect-patterns-core-concepts` - Fundamental Effect-TS concepts +- `effect-patterns-streams` - Stream processing patterns +- `effect-patterns-domain-modeling` - Domain modeling with branded types +- `effect-patterns-building-apis` - HTTP API development patterns +- `effect-patterns-resource-management` - Resource lifecycle management +- `effect-patterns-testing` - Testing Effect applications +- `effect-patterns-observability` - Logging, tracing, and metrics +- `effect-patterns-platform` - Platform operations (filesystem, commands, etc.) +- And 14 more categories... + +## For Developers + +### Regenerating Skills + +When patterns in `content/published/patterns/` are updated: + +```bash +bun run generate:skills +``` + +This regenerates all skills in both: +- `content/published/skills/claude/` (gitignored dev artifacts) +- `.claude-plugin/plugins/effect-patterns/skills/` (committed for plugin distribution) + +### Skill Structure + +Each skill follows the format: +``` +effect-patterns-{category}/ +└── SKILL.md # YAML frontmatter + pattern content +``` + +Skills are auto-discovered by Claude Code from this directory. + +### Skill Content + +Each SKILL.md file contains: +- **YAML frontmatter**: name, description +- **Pattern sections**: Organized by skill level (beginner → intermediate → advanced) +- **For each pattern**: + - Rule/guideline + - Good example + - Anti-pattern + - Rationale/explanation diff --git a/.agents/skills/agent-skill-creator/SKILL.md b/.agents/skills/agent-skill-creator/SKILL.md new file mode 100644 index 0000000..3a0b6db --- /dev/null +++ b/.agents/skills/agent-skill-creator/SKILL.md @@ -0,0 +1,4116 @@ +--- +name: agent-skill-creator +description: This enhanced skill should be used when the user asks to create an agent, automate a repetitive workflow, create a custom skill, or needs advanced agent creation capabilities. Activates with phrases like every day, daily I have to, I need to repeat, create agent for, automate workflow, create skill for, need to automate, turn process into agent. Supports single agents, multi-agent suites, transcript processing, template-based creation, and interactive configuration. Claude will use the enhanced protocol to research APIs, define analyses, structure everything, implement functional code, and create complete skills autonomously with optional user guidance. +--- +# Agent Creator - Meta-Skill + +This skill teaches Claude Code how to autonomously create complete agents with Claude Skills. + +## When to Use This Skill + +Claude should automatically activate this skill when the user: + +✅ **Asks to create an agent** + +- "Create an agent for [objective]" +- "I need an agent that [description]" +- "Develop an agent to automate [workflow]" + +✅ **Asks to automate a workflow** + +- "Automate this process: [description]" +- "Every day I do [repetitive task], automate this" +- "Turn this workflow into an agent" + +✅ **Asks to create a skill** + +- "Create a skill for [objective]" +- "Develop a custom skill for [domain]" + +✅ **Describes a repetitive process** + +- "Every day I [process]... takes Xh" +- "I repeatedly need to [task]" +- "Manual workflow: [description]" + +## Overview + +When activated, this skill guides Claude through **5 autonomous phases** to create a complete production-ready agent: + +``` +PHASE 1: DISCOVERY +├─ Research available APIs +├─ Compare options +└─ DECIDE which to use (with justification) + +PHASE 2: DESIGN +├─ Think about use cases +├─ DEFINE useful analyses +└─ Specify methodologies + +PHASE 3: ARCHITECTURE +├─ STRUCTURE folders and files +├─ Define necessary scripts +└─ Plan caching and performance + +PHASE 4: DETECTION +├─ DETERMINE keywords +└─ Create precise description + +PHASE 5: IMPLEMENTATION +├─ 🚨 FIRST: Create marketplace.json (MANDATORY!) +├─ Create SKILL.md (5000+ words) +├─ Implement Python scripts (functional!) +├─ Write references (useful!) +├─ Generate configs (real!) +├─ Create README +└─ ✅ FINAL: Test installation +``` + +**Output**: Complete agent in subdirectory ready to install. + +--- + +## 🏗️ **Claude Skills Architecture: Understanding What We Create** + +### **Important Terminology Clarification** + +This meta-skill creates **Claude Skills**, which come in different architectural patterns: + +#### **📋 Skill Types We Can Create** + +**1. Simple Skill** (Single focused capability) +``` +skill-name/ +├── SKILL.md ← Single comprehensive skill file +├── scripts/ ← Optional supporting code +├── references/ ← Optional documentation +└── assets/ ← Optional templates +``` +*Use when: Single objective, simple workflow, <1000 lines code* + +**2. Complex Skill Suite** (Multiple specialized capabilities) +``` +skill-suite/ +├── .claude-plugin/ +│ └── marketplace.json ← Organizes multiple component skills +├── component-1/ +│ └── SKILL.md ← Specialized sub-skill +├── component-2/ +│ └── SKILL.md ← Another specialized sub-skill +└── shared/ ← Shared resources +``` +*Use when: Multiple related workflows, >2000 lines code, team maintenance* + +#### **🎯 Architecture Decision Process** + +During **PHASE 3: ARCHITECTURE**, this skill will: + +1. **Analyze Complexity Requirements** + - Number of distinct workflows + - Code complexity estimation + - Maintenance considerations + +2. **Choose Appropriate Architecture** + - Simple task → Simple Skill + - Complex multi-domain task → Skill Suite + - Hybrid requirements → Simple skill with components + +3. **Apply Naming Convention** + - Generate descriptive base name from requirements + - Add "-cskill" suffix to identify as Claude Skill created by Agent-Skill-Creator + - Ensure consistent, professional naming across all created skills + +4. **Document the Decision** + - Create `DECISIONS.md` explaining architecture choice + - Provide rationale for selected pattern + - Include migration path if needed + - Document naming convention applied + +#### **🏷️ Naming Convention: "-cskill" Suffix** + +**All skills created by this Agent-Skill-Creator use the "-cskill" suffix:** + +**Simple Skills:** +- `pdf-text-extractor-cskill/` +- `csv-data-cleaner-cskill/` +- `weekly-report-generator-cskill/` + +**Complex Skill Suites:** +- `financial-analysis-suite-cskill/` +- `e-commerce-automation-cskill/` +- `research-workflow-cskill/` + +**Component Skills (within suites):** +- `data-acquisition-cskill/` +- `technical-analysis-cskill/` +- `reporting-generator-cskill/` + +**Purpose of "-cskill" suffix:** +- ✅ **Clear Identification**: Immediately recognizable as a Claude Skill +- ✅ **Origin Attribution**: Created by Agent-Skill-Creator +- ✅ **Consistent Convention**: Professional naming standard +- ✅ **Avoids Confusion**: Distinguishes from manually created skills +- ✅ **Easy Organization**: Simple to identify and group created skills + +#### **📚 Reference Documentation** + +For complete understanding of Claude Skills architecture, see: +- `docs/CLAUDE_SKILLS_ARCHITECTURE.md` (comprehensive guide) +- `docs/DECISION_LOGIC.md` (architecture decision framework) +- `examples/` (simple vs complex examples) +- `examples/simple-skill/` (minimal example) +- `examples/complex-skill-suite/` (comprehensive example) + +#### **✅ What We Create** + +**ALWAYS creates a valid Claude Skill** - either: +- **Simple Skill** (single SKILL.md) +- **Complex Skill Suite** (multiple component skills with marketplace.json) + +**NEVER creates "plugins" in the traditional sense** - we create Skills, which may be organized using marketplace.json for complex suites. + +This terminology consistency eliminates confusion between Skills and Plugins. + +--- + +## 🧠 Invisible Intelligence: AgentDB Integration + +### Enhanced Intelligence (v2.1) + +This skill now includes **invisible AgentDB integration** that learns from every agent creation and provides progressively smarter assistance. + +**What happens automatically:** +- 🧠 **Learning Memory**: Stores every creation attempt as episodes +- ⚡ **Progressive Enhancement**: Each creation becomes faster and more accurate +- 🎯 **Smart Validation**: Mathematical proofs for all decisions +- 🔄 **Graceful Operation**: Works perfectly with or without AgentDB + +**User Experience**: Same simple commands, agents get smarter magically! + +### Integration Points + +The AgentDB integration is woven into the 5 phases: + +``` +PHASE 1: DISCOVERY +├─ Research APIs +├─ 🧠 Query AgentDB for similar past successes +├─ Compare options using learned patterns +└─ DECIDE with historical confidence + +PHASE 2: DESIGN +├─ Think about use cases +├─ 🧠 Retrieve successful analysis patterns +├─ DEFINE using proven methodologies +└─ Enhance with learned improvements + +PHASE 3: ARCHITECTURE +├─ STRUCTURE using validated patterns +├─ 🧠 Apply proven architectural decisions +├─ Plan based on success history +└─ Optimize with learned insights + +PHASE 4: DETECTION +├─ DETERMINE keywords using learned patterns +├─ 🧠 Use successful keyword combinations +└─ Create optimized description + +PHASE 5: IMPLEMENTATION +├─ Create marketplace.json +├─ 🧠 Apply proven code patterns +├─ Store episode for future learning +└─ ✅ Complete with enhanced validation +``` + +### Learning Progression + +**First Creation:** +``` +"Create financial analysis agent" +→ Standard agent creation process +→ Episode stored for learning +→ No visible difference to user +``` + +**After 10+ Creations:** +``` +"Create financial analysis agent" +→ 40% faster (learned optimal queries) +→ Better API selection (historical success) +→ Proven architectural patterns +→ User sees: "⚡ Optimized based on similar successful agents" +``` + +**After 30+ Days:** +``` +"Create financial analysis agent" +→ Personalized recommendations based on patterns +→ Predictive insights about user preferences +→ Automatic skill consolidation +→ User sees: "🌟 I notice you prefer comprehensive financial agents - shall I include portfolio optimization?" +``` + +--- + +## 🚀 Enhanced Features (v2.0) + +### Multi-Agent Architecture + +The enhanced agent-creator now supports: + +**✅ Single Agent Creation** (Original functionality) +``` +"Create an agent for stock analysis" +→ ./stock-analysis-agent/ +``` + +**✅ Multi-Agent Suite Creation** (NEW) +``` +"Create a financial analysis suite with 4 agents: +fundamental analysis, technical analysis, +portfolio management, and risk assessment" +→ ./financial-suite/ + ├── fundamental-analysis/ + ├── technical-analysis/ + ├── portfolio-management/ + └── risk-assessment/ +``` + +**✅ Transcript Intelligence Processing** (NEW) +``` +"I have a YouTube transcript about e-commerce analytics, +can you create agents based on the workflows described?" +→ Automatically extracts multiple workflows +→ Creates integrated agent suite +``` + +**✅ Template-Based Creation** (NEW) +``` +"Create an agent using the financial-analysis template" +→ Uses pre-configured APIs and analyses +→ 80% faster creation +``` + +**✅ Interactive Configuration** (NEW) +``` +"Help me create an agent with preview options" +→ Step-by-step wizard +→ Real-time preview +→ Iterative refinement +``` + +### Enhanced Marketplace.json Support + +**v1.0 Format** (Still supported): +```json +{ + "name": "single-agent", + "plugins": [ + { + "skills": ["./"] + } + ] +} +``` + +**v2.0 Format** (NEW - Multi-skill support): +```json +{ + "name": "agent-suite", + "plugins": [ + { + "name": "fundamental-analysis", + "source": "./fundamental-analysis/", + "skills": ["./SKILL.md"] + }, + { + "name": "technical-analysis", + "source": "./technical-analysis/", + "skills": ["./SKILL.md"] + } + ] +} +``` + +--- + +## Autonomous Creation Protocol + +### Fundamental Principles + +**Autonomy**: + +- ✅ Claude DECIDES which API to use (doesn't ask user) +- ✅ Claude DEFINES which analyses to perform (based on value) +- ✅ Claude STRUCTURES optimally (best practices) +- ✅ Claude IMPLEMENTS complete code (no placeholders) +- ✅ **NEW**: Claude LEARNS from experience (AgentDB integration) + +**Quality**: + +- ✅ Production-ready code (no TODOs) +- ✅ Useful documentation (not "see docs") +- ✅ Real configs (no placeholders) +- ✅ Robust error handling +- ✅ **NEW**: Intelligence validated with mathematical proofs + +**Completeness**: + +- ✅ Complete SKILL.md (5000+ words) +- ✅ Functional scripts (1000+ lines total) +- ✅ References with content (3000+ words) +- ✅ Valid assets/configs +- ✅ README with instructions + +### Requirements Extraction + +When user describes workflow vaguely, extract: + +**From what the user said**: + +- Domain (agriculture? finance? weather?) +- Data source (mentioned? if not, research) +- Main tasks (download? analyze? compare?) +- Frequency (daily? weekly? on-demand?) +- Current time spent (to calculate ROI) + +**🆕 Enhanced Analysis (v2.0)**: + +- **Multi-Agent Detection**: Look for keywords like "suite", "multiple", "separate agents" +- **Transcript Analysis**: Detect if input is a video/transcript requiring workflow extraction +- **Template Matching**: Identify if user wants template-based creation +- **Interactive Preference**: Detect if user wants guidance vs full autonomy +- **Integration Needs**: Determine if agents should communicate with each other + +**🆕 Transcript Processing**: + +When user provides transcripts: +```python +# Enhanced transcript analysis +def analyze_transcript(transcript: str) -> List[WorkflowSpec]: + """Extract multiple workflows from transcripts automatically""" + workflows = [] + + # 1. Identify distinct processes + processes = extract_processes(transcript) + + # 2. Group related steps + for process in processes: + steps = extract_sequence_steps(transcript, process) + apis = extract_mentioned_apis(transcript, process) + outputs = extract_desired_outputs(transcript, process) + + workflows.append(WorkflowSpec( + name=process, + steps=steps, + apis=apis, + outputs=outputs + )) + + return workflows +``` + +**🆕 Multi-Agent Strategy Decision**: + +```python +def determine_creation_strategy(user_input: str, workflows: List[WorkflowSpec]) -> CreationStrategy: + """Decide whether to create single agent, suite, or integrated system""" + + if len(workflows) > 1: + if workflows_are_related(workflows): + return CreationStrategy.INTEGRATED_SUITE + else: + return CreationStrategy.MULTI_AGENT_SUITE + else: + return CreationStrategy.SINGLE_AGENT +``` + +**Questions to ask** (only if critical and not inferable): + +- "Prefer free API or paid is ok?" +- "Need historical data for how many years?" +- "Focus on which geography/country?" +- **🆕 "Create separate agents or integrated suite?"** (if multiple workflows detected) +- **🆕 "Want interactive preview before creation?"** (for complex projects) + +**Rule**: Minimize questions. Infer/decide whenever possible. + +## 🎯 Template-Based Creation (NEW v2.0) + +### Available Templates + +The enhanced agent-creator includes pre-built templates for common domains: + +**📊 Financial Analysis Template** +```json +Domain: Finance & Investments +APIs: Alpha Vantage, Yahoo Finance +Analyses: Fundamental, Technical, Portfolio +Time: 15-20 minutes +``` + +**🌡️ Climate Analysis Template** +```json +Domain: Climate & Environmental +APIs: Open-Meteo, NOAA +Analyses: Anomalies, Trends, Seasonal +Time: 20-25 minutes +``` + +**🛒 E-commerce Analytics Template** +```json +Domain: Business & E-commerce +APIs: Google Analytics, Stripe, Shopify +Analyses: Traffic, Revenue, Cohort, Products +Time: 25-30 minutes +``` + +### Template Matching Process + +```python +def match_template(user_input: str) -> TemplateMatch: + """Automatically suggest best template based on user input""" + + # 1. Extract keywords from user input + keywords = extract_keywords(user_input) + + # 2. Calculate similarity scores with all templates + matches = [] + for template in available_templates: + score = calculate_similarity(keywords, template.keywords) + matches.append((template, score)) + + # 3. Rank by similarity + matches.sort(key=lambda x: x[1], reverse=True) + + # 4. Return best match if confidence > threshold + if matches[0][1] > 0.7: + return TemplateMatch(template=matches[0][0], confidence=matches[0][1]) + else: + return None # No suitable template found +``` + +### Template Usage Examples + +**Direct Template Request:** +``` +"Create an agent using the financial-analysis template" +→ Uses pre-configured structure +→ 80% faster creation +→ Proven architecture +``` + +**Automatic Template Detection:** +``` +"I need to analyze stock performance and calculate RSI, MACD" +→ Detects financial domain +→ Suggests financial-analysis template +→ User confirms or continues custom +``` + +**Template Customization:** +``` +"Use the climate template but add drought analysis" +→ Starts with climate template +→ Adds custom drought analysis +→ Modifies structure accordingly +``` + +## 🚀 Batch Agent Creation (NEW v2.0) + +### Multi-Agent Suite Creation + +The enhanced agent-creator can create multiple agents in a single operation: + +**When to Use Batch Creation:** +- Transcript describes multiple distinct workflows +- User explicitly asks for multiple agents +- Complex system requiring specialized components +- Microservices architecture preferred + +### Batch Creation Process + +```python +def create_agent_suite(user_input: str, workflows: List[WorkflowSpec]) -> AgentSuite: + """Create multiple related agents in one operation""" + + # 1. Analyze workflow relationships + relationships = analyze_workflow_relationships(workflows) + + # 2. Determine optimal structure + if workflows_are_tightly_coupled(workflows): + structure = "integrated_suite" + else: + structure = "independent_agents" + + # 3. Create suite directory + suite_name = generate_suite_name(user_input) + create_suite_directory(suite_name) + + # 4. Create each agent + agents = [] + for workflow in workflows: + agent = create_single_agent(workflow, suite_name) + agents.append(agent) + + # 5. Create integration layer (if needed) + if structure == "integrated_suite": + create_integration_layer(agents, suite_name) + + # 6. Create suite-level marketplace.json + create_suite_marketplace_json(suite_name, agents) + + return AgentSuite(name=suite_name, agents=agents, structure=structure) +``` + +### Batch Creation Examples + +**Financial Suite Example:** +``` +"Create a complete financial analysis system with 4 agents: +1. Fundamental analysis for company valuation +2. Technical analysis for trading signals +3. Portfolio management and optimization +4. Risk assessment and compliance" + +→ ./financial-analysis-suite/ + ├── .claude-plugin/marketplace.json (multi-skill) + ├── fundamental-analysis/ + │ ├── SKILL.md + │ ├── scripts/ + │ └── tests/ + ├── technical-analysis/ + ├── portfolio-management/ + └── risk-assessment/ +``` + +**E-commerce Suite Example:** +``` +"Build an e-commerce analytics system based on this transcript: +- Traffic analysis from Google Analytics +- Revenue tracking from Stripe +- Product performance from Shopify +- Customer cohort analysis +- Automated reporting dashboard" + +→ ./e-commerce-analytics-suite/ + ├── traffic-analysis-agent/ + ├── revenue-tracking-agent/ + ├── product-performance-agent/ + ├── cohort-analysis-agent/ + └── reporting-dashboard-agent/ +``` + +### Multi-Skill Marketplace.json Structure + +**Suite-Level Configuration:** +```json +{ + "name": "financial-analysis-suite", + "metadata": { + "description": "Complete financial analysis system with fundamental, technical, portfolio, and risk analysis", + "version": "1.0.0", + "suite_type": "financial_analysis" + }, + "plugins": [ + { + "name": "fundamental-analysis-plugin", + "description": "Fundamental analysis for company valuation and financial metrics", + "source": "./fundamental-analysis/", + "skills": ["./SKILL.md"] + }, + { + "name": "technical-analysis-plugin", + "description": "Technical analysis with trading indicators and signals", + "source": "./technical-analysis/", + "skills": ["./SKILL.md"] + }, + { + "name": "portfolio-management-plugin", + "description": "Portfolio optimization and management analytics", + "source": "./portfolio-management/", + "skills": ["./SKILL.md"] + }, + { + "name": "risk-assessment-plugin", + "description": "Risk analysis and compliance monitoring", + "source": "./risk-assessment/", + "skills": ["./SKILL.md"] + } + ], + "integrations": { + "data_sharing": true, + "cross_agent_communication": true, + "shared_utils": "./shared/" + } +} +``` + +### Batch Creation Benefits + +**✅ Time Efficiency:** +- Create 4 agents in ~60 minutes (vs 4 hours individually) +- Shared utilities and infrastructure +- Consistent architecture and documentation + +**✅ Integration Benefits:** +- Agents designed to work together +- Shared data structures and formats +- Unified testing and deployment + +**✅ Maintenance Benefits:** +- Single marketplace.json for installation +- Coordinated versioning and updates +- Shared troubleshooting documentation + +### Batch Creation Commands + +**Explicit Multi-Agent Request:** +``` +"Create 3 agents for climate analysis: +1. Temperature anomaly detection +2. Precipitation pattern analysis +3. Extreme weather event tracking + +Make them work together as a system." +``` + +**Transcript-Based Batch Creation:** +``` +"Here's a transcript of a 2-hour tutorial on building +a complete business intelligence system. Create agents +for all the workflows described in the video." +``` + +**Template-Based Batch Creation:** +``` +"Use the e-commerce template to create a full analytics suite: +- Traffic analysis +- Revenue tracking +- Customer analytics +- Product performance +- Marketing attribution" +``` + +## 🎮 Interactive Configuration Wizard (NEW v2.0) + +### When to Use Interactive Mode + +The enhanced agent-creator includes an interactive wizard for: + +- **Complex Projects**: Multi-agent systems, integrations +- **User Preference**: When users want guidance vs full autonomy +- **High-Stakes Projects**: When preview and iteration are important +- **Learning**: Users who want to understand the creation process + +### Interactive Wizard Process + +```python +def interactive_agent_creation(): + """ + Step-by-step guided agent creation with real-time preview + """ + + # Step 1: Welcome and Requirements Gathering + print("🚀 Welcome to Enhanced Agent Creator!") + print("I'll help you create custom agents through an interactive process.") + + user_needs = gather_requirements_interactively() + + # Step 2: Workflow Analysis + print("\n📋 Analyzing your requirements...") + workflows = analyze_and_confirm_workflows(user_needs) + + # Step 3: Strategy Selection + strategy = select_creation_strategy(workflows) + print(f"🎯 Recommended: {strategy.description}") + + # Step 4: Preview and Refinement + while True: + preview = generate_interactive_preview(strategy) + show_preview(preview) + + if user_approves(): + break + else: + strategy = refine_based_on_feedback(strategy, preview) + + # Step 5: Creation + print("\n⚙️ Creating your agent(s)...") + result = execute_creation(strategy) + + # Step 6: Validation and Tutorial + validate_created_agents(result) + provide_usage_tutorial(result) + + return result +``` + +### Interactive Interface Examples + +**Step 1: Requirements Gathering** +``` +🚀 Welcome to Enhanced Agent Creator! + +Let me understand what you want to build: + +1. What's your main goal? + [ ] Automate a repetitive workflow + [ ] Analyze data from specific sources + [ ] Create custom tools for my domain + [ ] Build a complete system with multiple components + +2. What's your domain/industry? + [ ] Finance & Investing + [ ] E-commerce & Business + [ ] Climate & Environment + [ ] Healthcare & Medicine + [ ] Other (please specify): _______ + +3. Do you have existing materials? + [ ] YouTube transcript or video + [ ] Documentation or tutorials + [ ] Existing code/scripts + [ ] Starting from scratch + +Your responses: [Finance & Investing] [Starting from scratch] +``` + +**Step 2: Workflow Analysis** +``` +📋 Based on your input, I detect: + +Domain: Finance & Investing +Potential Workflows: +1. Fundamental Analysis (P/E, ROE, valuation metrics) +2. Technical Analysis (RSI, MACD, trading signals) +3. Portfolio Management (allocation, optimization) +4. Risk Assessment (VaR, drawdown, compliance) + +Which workflows interest you? Select all that apply: +[✓] Technical Analysis +[✓] Portfolio Management +[ ] Fundamental Analysis +[ ] Risk Assessment + +Selected: 2 workflows detected +``` + +**Step 3: Strategy Selection** +``` +🎯 Recommended Creation Strategy: + +Multi-Agent Suite Creation +- Create 2 specialized agents +- Each agent handles one workflow +- Agents can communicate and share data +- Unified installation and documentation + +Estimated Time: 35-45 minutes +Output: ./finance-suite/ (2 agents) + +Options: +[✓] Accept recommendation +[ ] Create single integrated agent +[ ] Use template-based approach +[ ] Customize strategy +``` + +**Step 4: Interactive Preview** +``` +📊 Preview of Your Finance Suite: + +Structure: +./finance-suite/ +├── .claude-plugin/marketplace.json +├── technical-analysis-agent/ +│ ├── SKILL.md (2,100 words) +│ ├── scripts/ (Python, 450 lines) +│ └── tests/ (15 tests) +└── portfolio-management-agent/ + ├── SKILL.md (1,800 words) + ├── scripts/ (Python, 380 lines) + └── tests/ (12 tests) + +Features: +✅ Real-time stock data (Alpha Vantage API) +✅ 10 technical indicators (RSI, MACD, Bollinger...) +✅ Portfolio optimization algorithms +✅ Risk metrics and rebalancing alerts +✅ Automated report generation + +APIs Required: +- Alpha Vantage (free tier available) +- Yahoo Finance (no API key needed) + +Would you like to: +[✓] Proceed with creation +[ ] Modify technical indicators +[ ] Add risk management features +[ ] Change APIs +[ ] See more details +``` + +### Wizard Benefits + +**🎯 User Empowerment:** +- Users see exactly what will be created +- Can modify and iterate before implementation +- Learn about the process and architecture +- Make informed decisions + +**⚡ Efficiency:** +- Faster than custom development +- Better than black-box creation +- Reduces rework and iterations +- Higher satisfaction rates + +**🛡️ Risk Reduction:** +- Preview prevents misunderstandings +- Iterative refinement catches issues early +- Users can validate requirements +- Clear expectations management + +### Interactive Commands + +**Start Interactive Mode:** +``` +"Help me create an agent with interactive options" +"Walk me through creating a financial analysis system" +"I want to use the configuration wizard" +``` + +**Resume from Preview:** +``` +"Show me the preview again before creating" +"Can I modify the preview you showed me?" +"I want to change something in the proposed structure" +``` + +**Learning Mode:** +``` +"Create an agent and explain each step as you go" +"Teach me how agent creation works while building" +"I want to understand the architecture decisions" +``` + +### Wizard Customization Options + +**Advanced Mode:** +``` +⚙️ Advanced Configuration Options: + +1. API Selection Strategy + [ ] Prefer free APIs + [ ] Prioritize data quality + [ ] Minimize rate limits + [ ] Multiple API fallbacks + +2. Architecture Preference + [ ] Modular (separate scripts per function) + [ ] Integrated (all-in-one scripts) + [ ] Hybrid (core + specialized modules) + +3. Testing Strategy + [ ] Basic functionality tests + [ ] Comprehensive test suite + [ ] Integration tests + [ ] Performance benchmarks + +4. Documentation Level + [ ] Minimal (API docs only) + [ ] Standard (complete usage guide) + [ ] Extensive (tutorials + examples) + [ ] Academic (methodology + research) +``` + +**Template Customization:** +``` +🎨 Template Customization: + +Base Template: Financial Analysis +✓ Include technical indicators: RSI, MACD, Bollinger Bands +✓ Add portfolio optimization: Modern Portfolio Theory +✓ Risk metrics: VaR, Maximum Drawdown, Sharpe Ratio + +Additional Features: +[ ] Machine learning predictions +[ ] Sentiment analysis from news +[ ] Options pricing models +[ ] Cryptocurrency support + +Remove Features: +[ ] Fundamental analysis (not needed) +[ ] Economic calendar integration +``` + +## 🧠 Invisible Intelligence: AgentDB Integration (NEW v2.1) + +### What This Means for Users + +**The agent-creator now has "memory" and gets smarter over time - automatically!** + +✅ **No setup required** - AgentDB initializes automatically in the background +✅ **No commands to learn** - You use the exact same natural language commands +✅ **Invisible enhancement** - Agents become more intelligent without you doing anything +✅ **Progressive learning** - Each agent learns from experience and shares knowledge + +### How It Works (Behind the Scenes) + +When you create an agent: +``` +User: "Create agent for financial analysis" + +🤖 Agent-Creator (v2.1): +"✅ Creating financial-analysis-agent with learned intelligence..." +"✅ Using template with 94% historical success rate..." +"✅ Applied 12 learned improvements from similar agents..." +"✅ Mathematical proof: template choice validated with 98% confidence..." +``` + +### Key Benefits (Automatic & Invisible) + +**🧠 Learning Memory:** +- Agents remember what works and what doesn't +- Successful patterns are automatically reused +- Failed approaches are automatically avoided + +**📊 Smart Decisions:** +- Template selection based on real success data +- Architecture optimized from thousands of similar agents +- API choices validated with mathematical proofs + +**🔄 Continuous Improvement:** +- Each agent gets smarter with use +- Knowledge shared across all agents automatically +- Nightly reflection system refines capabilities + +### User Experience: "The Magic Gets Better" + +**First Week:** +``` +"Analyze Tesla stock" +🤖 "📊 Tesla analysis: RSI 65.3, MACD bullish" +``` + +**After One Month:** +``` +"Analyze Tesla stock" +🤖 "📊 Tesla analysis: RSI 65.3, MACD bullish (enhanced with your patterns)" +🤖 "🧠 Pattern detected: You always ask on Mondays - prepared weekly analysis" +🤖 "📈 Added volatility prediction based on your usage patterns" +``` + +### Technical Implementation (Invisible to Users) + +```python +# This happens automatically behind the scenes +class AgentCreatorV21: + def create_agent(self, user_input): + # AgentDB enhancement (invisible) + intelligence = enhance_agent_creation(user_input) + + # Enhanced template selection + template = intelligence.template_choice or self.default_template + + # Learned improvements automatically applied + improvements = intelligence.learned_improvements + + # Create agent with enhanced intelligence + return self.create_with_intelligence(template, improvements) +``` + +### Graceful Fallback + +If AgentDB isn't available (rare), the agent-creator works exactly like v2.0: +``` +"Create agent for financial analysis" +🤖 "✅ Agent created (standard mode)" +``` + +No interruption, no errors, just no learning enhancements. + +### Privacy & Performance + +- ✅ All learning happens locally on your machine +- ✅ No external dependencies required +- ✅ Automatic cleanup and optimization +- ✅ Zero impact on creation speed + +--- + +## 📦 Cross-Platform Export (NEW v3.2) + +### What This Feature Does + +**Automatically package skills for use across all Claude platforms:** + +Skills created in Claude Code can be exported for: +- ✅ **Claude Desktop** - Manual .zip upload +- ✅ **claude.ai** (Web) - Browser-based upload +- ✅ **Claude API** - Programmatic integration + +This makes your skills portable and shareable across all Claude ecosystems. + +### When to Activate Export + +Claude should activate export capabilities when user says: + +✅ **Export requests:** +- "Export [skill-name] for Desktop" +- "Package [skill-name] for claude.ai" +- "Create API package for [skill-name]" +- "Export [skill-name] for all platforms" + +✅ **Cross-platform requests:** +- "Make [skill-name] compatible with Claude Desktop" +- "I need to share [skill-name] with Desktop users" +- "Package [skill-name] as .zip" +- "Create cross-platform version of [skill-name]" + +✅ **Version-specific exports:** +- "Export [skill-name] with version 2.0.1" +- "Package [skill-name] v1.5.0 for API" + +### Export Process + +When user requests export: + +**Step 1: Locate Skill** +```python +# Search common locations +locations = [ + f"./{skill_name}-cskill/", # Current directory + f"references/examples/{skill_name}-cskill/", # Examples + user_specified_path # If provided +] + +skill_path = find_skill(locations) +``` + +**Step 2: Validate Structure** +```python +# Ensure skill is export-ready +valid, issues = validate_skill_structure(skill_path) + +if not valid: + report_issues_to_user(issues) + return +``` + +**Step 3: Execute Export** +```bash +# Run export utility +python scripts/export_utils.py {skill_path} \ + --variant {desktop|api|both} \ + --version {version} \ + --output-dir exports/ +``` + +**Step 4: Report Results** +``` +✅ Export completed! + +📦 Packages created: + - Desktop: exports/{skill}-desktop-v1.0.0.zip (2.3 MB) + - API: exports/{skill}-api-v1.0.0.zip (1.2 MB) + +📄 Installation guide: exports/{skill}-v1.0.0_INSTALL.md + +🎯 Ready for: + ✅ Claude Desktop upload + ✅ claude.ai upload + ✅ Claude API integration +``` + +### Post-Creation Export (Opt-In) + +After successfully creating a skill in PHASE 5, offer export: + +``` +✅ Skill created successfully: {skill-name-cskill}/ + +📦 Cross-Platform Export Options: + +Would you like to create export packages for other Claude platforms? + + 1. Desktop/Web (.zip for manual upload) + 2. API (.zip for programmatic use) + 3. Both (comprehensive package) + 4. Skip (Claude Code only) + +Choice: _ +``` + +**If user chooses 1, 2, or 3:** +- Execute export_utils.py with selected variants +- Report package locations +- Provide next steps for each platform + +**If user chooses 4 or skips:** +- Continue with normal completion +- Skill remains Claude Code only + +### Export Variants + +**Desktop/Web Package** (`*-desktop-*.zip`): +- Complete documentation +- All scripts and assets +- Full references +- Optimized for user experience +- Typical size: 2-5 MB + +**API Package** (`*-api-*.zip`): +- Execution-focused +- Size-optimized (< 8MB) +- Minimal documentation +- Essential scripts only +- Typical size: 0.5-2 MB + +### Version Detection + +Automatically detect version from: + +1. **Git tags** (priority): + ```bash + git describe --tags --abbrev=0 + ``` + +2. **SKILL.md frontmatter**: + ```yaml + --- + name: skill-name + version: 1.2.3 + --- + ``` + +3. **Default**: `v1.0.0` + +**User can override**: +- "Export with version 2.1.0" +- `--version 2.1.0` flag + +### Export Validation + +Before creating packages, validate: + +✅ **Required:** +- SKILL.md exists +- Valid frontmatter (---...---) +- `name:` field present (≤ 64 chars) +- `description:` field present (≤ 1024 chars) + +✅ **Size Checks:** +- Desktop: Reasonable size +- API: < 8MB (hard limit) + +✅ **Security:** +- No .env files +- No credentials.json +- No sensitive data + +If validation fails, report specific issues to user. + +### Installation Guides + +Auto-generate platform-specific guides: + +**File**: `exports/{skill}-v{version}_INSTALL.md` + +**Contents:** +- Package information +- Installation steps for Desktop +- Installation steps for claude.ai +- API integration code examples +- Platform comparison table +- Troubleshooting tips + +### Export Commands Reference + +```bash +# Export both variants (default) +python scripts/export_utils.py ./skill-name-cskill + +# Export only Desktop +python scripts/export_utils.py ./skill-name-cskill --variant desktop + +# Export only API +python scripts/export_utils.py ./skill-name-cskill --variant api + +# With custom version +python scripts/export_utils.py ./skill-name-cskill --version 2.0.1 + +# To custom directory +python scripts/export_utils.py ./skill-name-cskill --output-dir ./releases +``` + +### Documentation References + +Point users to comprehensive guides: +- **Export Guide**: `references/export-guide.md` +- **Cross-Platform Guide**: `references/cross-platform-guide.md` +- **Exports README**: `exports/README.md` + +### Integration with AgentDB + +Export process can leverage AgentDB learning: +- Remember successful export configurations +- Suggest optimal variant based on use case +- Track which exports are most commonly used +- Learn from export failures to improve validation + +--- + +## PHASE 1: Discovery and Research + +**Objective**: DECIDE which API/data source to use with AgentDB intelligence + +### Process + +**1.1 Identify domain and query AgentDB** + +From user input, identify the domain and immediately query AgentDB for learned patterns: + +```python +# Import AgentDB bridge (invisible to user) +from integrations.agentdb_bridge import get_agentdb_bridge + +# Get AgentDB intelligence +bridge = get_agentdb_bridge() +intelligence = bridge.enhance_agent_creation(user_input, domain) + +# Log: AgentDB provides insights if available +if intelligence.learned_improvements: + print(f"🧠 Found {len(intelligence.learned_improvements)} relevant patterns") +``` + +**Domain mapping with AgentDB insights:** +- Agriculture → APIs: USDA NASS, FAO, World Bank Ag +- Finance → APIs: Alpha Vantage, Yahoo Finance, Fed Economic Data +- Weather → APIs: NOAA, OpenWeather, Weather.gov +- Economy → APIs: World Bank, IMF, FRED + +**1.2 Research available APIs with learned preferences** + +For the domain, use WebSearch to find: + +- Available public APIs +- Documentation +- Characteristics (free? rate limits? coverage?) + +**AgentDB Enhancement**: Prioritize APIs that have shown higher success rates: +```python +# AgentDB influences search based on historical success +if intelligence.success_probability > 0.8: + print(f"🎯 High success domain detected - optimizing API selection") +``` + +**Example with AgentDB insights**: + +``` +WebSearch: "US agriculture API free historical data" +WebSearch: "USDA API documentation" +WebFetch: [doc URLs found] + +# AgentDB check: "Has similar domain been successful before?" +# AgentDB provides: "USDA NASS: 94% success rate in agriculture domain" +``` + +**1.3 Compare options with AgentDB validation** + +Create mental table comparing: + +- Data coverage (fit with need) +- Cost (free vs paid) +- Rate limits (sufficient?) +- Data quality (official? reliable?) +- Documentation (good? examples?) +- Ease of use +- **🧠 AgentDB Success Rate** (historical validation) + +**AgentDB Mathematical Validation**: +```python +# AgentDB provides mathematical proof for selection +if intelligence.mathematical_proof: + print(f"📊 API selection validated: {intelligence.mathematical_proof}") +``` + +**1.4 DECIDE with AgentDB confidence** + +Choose 1 API and justify with AgentDB backing: + +**Decision with AgentDB confidence:** +- **Selected API**: [API name] +- **Success Probability**: {intelligence.success_probability:.1%} +- **Mathematical Proof**: {intelligence.mathematical_proof} +- **Learned Improvements**: {intelligence.learned_improvements} + +**Document decision** in separate file: + +```markdown +# Architecture Decisions + +## Selected API: [Name] + +**Justification**: +- ✅ Coverage: [details] +- ✅ Cost: [free/paid] +- ✅ Rate limit: [number] +- ✅ Quality: [official/private] +- ✅ Docs: [quality] + +**Alternatives considered**: +- API X: Rejected because [reason] +- API Y: Rejected because [reason] + +**Conclusion**: [Chosen API] is the best option because [synthesis] +``` + +**1.5 Research technical details** + +Use WebFetch to load API documentation and extract: + +- Base URL +- Main endpoints +- Authentication +- Important parameters +- Response format +- Rate limits +- Request/response examples + +**See** `references/phase1-discovery.md` for complete details. + +## PHASE 2: Analysis Design + +**Objective**: DEFINE which analyses the agent will perform + +### Process + +**2.1 Think about use cases** + +For the described workflow, which questions will the user ask frequently? + +**Brainstorm**: List 10-15 typical questions + +**2.2 Group by analysis type** + +Group similar questions: + +- Simple queries (fetch + format) +- Temporal comparisons (YoY) +- Rankings (sort + share) +- Trends (time series + CAGR) +- Projections (forecasting) +- Aggregations (regional/categorical) + +**2.3 DEFINE priority analyses** + +Choose 4-6 analyses that cover 80% of use cases. + +For each analysis: + +- Name +- Objective +- Required inputs +- Expected outputs +- Methodology (formulas, transformations) +- Interpretation + +**2.4 ADD Comprehensive Report Function** (🆕 Enhancement #8 - MANDATORY!) + +**⚠️ COMMON PROBLEM:** v1.0 skills had isolated functions. When user asks for "complete report", Claude didn't know how to combine all analyses. + +**Solution:** ALWAYS include as last analysis function: + +```python +def comprehensive_{domain}_report( + entity: str, + year: Optional[int] = None, + include_metrics: Optional[List[str]] = None, + client: Optional[Any] = None +) -> Dict: + """ + Generate comprehensive report combining ALL available metrics. + + This is a "one-stop" function that users can call to get + complete picture without knowing individual functions. + + Args: + entity: Entity to analyze (e.g., commodity, stock, location) + year: Year (None for current year with auto-detection) + include_metrics: Which metrics to include (None = all available) + client: API client instance (optional, created if None) + + Returns: + Dict with ALL metrics consolidated: + { + 'entity': str, + 'year': int, + 'year_info': str, + 'generated_at': str (ISO timestamp), + 'metrics': { + 'metric1_name': {metric1_data}, + 'metric2_name': {metric2_data}, + ... + }, + 'summary': str (overall insights), + 'alerts': List[str] (important findings) + } + + Example: + >>> report = comprehensive_{domain}_report("CORN") + >>> print(report['summary']) + "CORN 2025: Production up 5% YoY, yield at record high..." + """ + from datetime import datetime + from utils.helpers import get_{domain}_year_with_fallback, format_year_message + + # Auto-detect year + year_requested = year + if year is None: + year, _ = get_{domain}_year_with_fallback() + + # Initialize report + report = { + 'entity': entity, + 'year': year, + 'year_requested': year_requested, + 'year_info': format_year_message(year, year_requested), + 'generated_at': datetime.now().isoformat(), + 'metrics': {}, + 'alerts': [] + } + + # Determine which metrics to include + if include_metrics is None: + # Include ALL available metrics + metrics_to_fetch = ['{metric1}', '{metric2}', '{metric3}', ...] + else: + metrics_to_fetch = include_metrics + + # Call ALL individual analysis functions + # Graceful degradation: if one fails, others still run + + if '{metric1}' in metrics_to_fetch: + try: + report['metrics']['{metric1}'] = {metric1}_analysis(entity, year, client) + except Exception as e: + report['metrics']['{metric1}'] = { + 'error': str(e), + 'status': 'unavailable' + } + report['alerts'].append(f"{metric1} data unavailable: {e}") + + if '{metric2}' in metrics_to_fetch: + try: + report['metrics']['{metric2}'] = {metric2}_analysis(entity, year, client) + except Exception as e: + report['metrics']['{metric2}'] = { + 'error': str(e), + 'status': 'unavailable' + } + + # Repeat for ALL metrics... + + # Generate summary based on all available data + report['summary'] = _generate_summary(report['metrics'], entity, year) + + # Detect important findings + report['alerts'].extend(_detect_alerts(report['metrics'])) + + return report + + +def _generate_summary(metrics: Dict, entity: str, year: int) -> str: + """Generate human-readable summary from all metrics.""" + insights = [] + + # Extract key insights from each metric + for metric_name, metric_data in metrics.items(): + if 'error' not in metric_data: + # Extract most important insight from this metric + key_insight = _extract_key_insight(metric_name, metric_data) + if key_insight: + insights.append(key_insight) + + # Combine into coherent summary + if insights: + summary = f"{entity} {year}: " + ". ".join(insights[:3]) # Top 3 insights + else: + summary = f"{entity} {year}: No data available" + + return summary + + +def _detect_alerts(metrics: Dict) -> List[str]: + """Detect significant findings that need attention.""" + alerts = [] + + # Check each metric for alert conditions + for metric_name, metric_data in metrics.items(): + if 'error' in metric_data: + continue + + # Domain-specific alert logic + # Example: Large changes, extreme values, anomalies + if metric_name == '{metric1}' and 'change_percent' in metric_data: + if abs(metric_data['change_percent']) > 15: + alerts.append( + f"⚠ Large {metric1} change: {metric_data['change_percent']:.1f}%" + ) + + return alerts +``` + +**Why it's mandatory:** +- ✅ Users want "complete report" → 1 function does everything +- ✅ Ideal for executive dashboards +- ✅ Facilitates sales ("everything in one report") +- ✅ Much better UX (no need to know individual functions) + +**When to mention in SKILL.md:** + +```markdown +## Comprehensive Analysis (All-in-One) + +To get a complete report combining ALL metrics: + +Use the `comprehensive_{domain}_report()` function. + +This function: +- Fetches ALL available metrics +- Combines into single report +- Generates automatic summary +- Detects important alerts +- Degrades gracefully (if 1 metric fails, others work) + +Usage example: +"Generate complete report for {entity}" +"Complete dashboard for {entity}" +"All metrics for {entity}" +``` + +**Impact:** +- ✅ 10x better UX (1 query = everything) +- ✅ More useful skills for end users +- ✅ Facilitates commercial adoption + +**2.5 Specify methodologies** + +For quantitative analyses, define: + +- Mathematical formulas +- Statistical validations +- Interpretations +- Edge cases + +**See** `references/phase2-design.md` for detailed methodologies. + +## PHASE 3: Architecture + +**Objective**: STRUCTURE the agent optimally + +### Process + +**3.1 Define folder structure** + +Based on analyses and API: + +``` +agent-name/ +├── SKILL.md +├── scripts/ +│ ├── [fetch/parse/analyze separate or together?] +│ └── utils/ +│ └── [cache? rate limiter? validators?] +├── references/ +│ └── [API docs? methodologies? troubleshooting?] +└── assets/ + └── [configs? metadata?] +``` + +**Decisions**: + +- Separate scripts (modular) vs monolithic? +- Which utilities needed? +- Which references useful? +- Which configs/assets? + +**3.2 Define responsibilities** + +For each script, specify: + +- File name +- Function/purpose +- Input and output +- Specific responsibilities +- ~Expected number of lines + +**3.3 Plan references** + +Which reference files to create? + +- API guide (how to use API) +- Analysis methods (methodologies) +- Troubleshooting (common errors) +- Domain knowledge (domain context) + +**3.4 Performance strategy** + +- Cache: What to cache? TTL? +- Rate limiting: How to control? +- Optimizations: Parallelization? Lazy loading? + +**See** `references/phase3-architecture.md` for structuring patterns. + +## PHASE 4: Automatic Detection + +**Objective**: DETERMINE keywords for automatic activation + +### Process + +**4.1 List domain entities** + +- Organizations/data sources +- Main metrics +- Geography (countries, regions, states) +- Temporality (years, periods) + +**4.2 List typical actions** + +- Query: "what", "how much", "show" +- Compare: "compare", "vs", "versus" +- Rank: "top", "best", "ranking" +- Analyze: "trend", "growth", "analyze" +- Forecast: "predict", "project", "forecast" + +**4.3 List question variations** + +For each analysis type, how might the user ask? + +**4.4 Define negative scope** + +Important! What should NOT activate the skill? + +**4.5 Create precise description** + +With all keywords identified, create ~200 word description that: + +- Mentions domain +- Lists main keywords +- Gives examples +- Defines negative scope + +**See** `references/phase4-detection.md` for complete guide. + +### 🎯 3-Layer Activation System (v3.0) + +**Important**: As of Agent-Skill-Creator v3.0, we now use a **3-Layer Activation System** to achieve 95%+ activation reliability. + +#### Why 3 Layers? + +Previous skills that relied only on description achieved ~70% activation reliability. The 3-layer system dramatically improves this to 95%+ by combining: + +1. **Layer 1: Keywords** - Exact phrase matching (high precision) +2. **Layer 2: Patterns** - Regex flexible matching (coverage for variations) +3. **Layer 3: Description + NLU** - Claude's understanding (fallback for edge cases) + +#### Quick Implementation Guide + +**Layer 1: Keywords (10-15 phrases)** +```json +"activation": { + "keywords": [ + "create an agent for", + "automate workflow", + "technical analysis for", + "RSI indicator", + // 10-15 total complete phrases + ] +} +``` + +**Requirements:** +- ✅ Complete phrases (2+ words) +- ✅ Action verb + entity +- ✅ Domain-specific terms +- ❌ No single words +- ❌ No overly generic phrases + +**Layer 2: Patterns (5-7 regex)** +```json +"patterns": [ + "(?i)(create|build)\\s+(an?\\s+)?agent\\s+for", + "(?i)(automate|automation)\\s+(workflow|process)", + "(?i)(analyze|analysis)\\s+.*\\s+(stock|data)", + // 5-7 total patterns +] +``` + +**Requirements:** +- ✅ Start with `(?i)` for case-insensitivity +- ✅ Include action verbs + entities +- ✅ Allow flexible word order +- ✅ Specific enough to avoid false positives +- ✅ Flexible enough to capture variations + +**Layer 3: Enhanced Description (300-500 chars, 60+ keywords)** +``` +Comprehensive [domain] tool. [Primary capability] including [specific-feature-1], +[specific-feature-2], and [specific-feature-3]. Generates [output-type] based on +[method]. Compares [entity-type] for [analysis-type]. Monitors [target] and tracks +[metric]. Perfect for [user-persona] needing [use-case-1], [use-case-2], and +[use-case-3] using [methodology]. +``` + +**Requirements:** +- ✅ 60+ unique keywords +- ✅ All Layer 1 keywords included naturally +- ✅ Domain-specific terminology +- ✅ Use cases clearly stated +- ✅ Natural language flow + +#### Usage Sections + +Add to marketplace.json: + +```json +"usage": { + "when_to_use": [ + "User explicitly asks to [capability-1]", + "User mentions [indicator-name] or [domain-term]", + "User describes [use-case-scenario]", + // 5+ use cases + ], + "when_not_to_use": [ + "User asks for [out-of-scope-1]", + "User wants [different-skill-capability]", + // 3+ counter-cases + ] +} +``` + +#### Test Queries + +Add to marketplace.json: + +```json +"test_queries": [ + "Query testing keyword-1", + "Query testing pattern-2", + "Query testing description understanding", + "Natural language variation", + // 10+ total queries covering all layers +] +``` + +#### Complete Example + +See `references/examples/stock-analyzer-cskill/` for a complete working example demonstrating: +- All 3 layers properly configured +- 98% activation reliability +- Complete test suite +- Documentation with activation examples + +#### Quality Checklist + +Before completing Phase 4, verify: + +- [ ] 10-15 complete keyword phrases defined +- [ ] 5-7 regex patterns with verbs + entities +- [ ] 300-500 char description with 60+ keywords +- [ ] 5+ when_to_use cases documented +- [ ] 3+ when_not_to_use cases documented +- [ ] 10+ test_queries covering all layers +- [ ] Tested activation with sample queries +- [ ] Expected success rate: 95%+ + +#### Additional Resources + +- **Complete Guide**: `references/phase4-detection.md` +- **Pattern Library**: `references/activation-patterns-guide.md` (30+ reusable patterns) +- **Testing Guide**: `references/activation-testing-guide.md` (5-phase testing) +- **Quality Checklist**: `references/activation-quality-checklist.md` +- **Templates**: `references/templates/marketplace-robust-template.json` +- **Example**: `references/examples/stock-analyzer-cskill/` + +--- + +## PHASE 5: Complete Implementation + +**Objective**: IMPLEMENT everything with REAL code + +### ⚠️ MANDATORY QUALITY STANDARDS + +Before starting implementation, read `references/quality-standards.md`. + +**NEVER DO**: + +- ❌ `# TODO: implement` +- ❌ `pass` in functions +- ❌ "See external documentation" +- ❌ Configs with "YOUR_KEY_HERE" without instructions +- ❌ Empty references or just links + +**ALWAYS DO**: + +- ✅ Complete and functional code +- ✅ Detailed docstrings +- ✅ Robust error handling +- ✅ Type hints +- ✅ Validations +- ✅ Real content in references +- ✅ Configs with real values + +### 🚨 STEP 0: BEFORE EVERYTHING - Marketplace.json (MANDATORY) + +**STOP! READ THIS BEFORE CONTINUING!** + +🛑 **CRITICAL BLOCKER**: You CANNOT create ANY other file until completing this step. + +**Why marketplace.json is step 0:** + +- ❌ Without this file, the skill CANNOT be installed via `/plugin marketplace add` +- ❌ All the work creating the agent will be USELESS without it +- ❌ This is the most common error when creating agents - DO NOT make this mistake! + +#### Step 0.1: Create basic structure + +```bash +mkdir -p agent-name/.claude-plugin +``` + +#### Step 0.2: Create marketplace.json IMMEDIATELY + +Create `.claude-plugin/marketplace.json` with this content: + +```json +{ + "name": "agent-name", + "owner": { + "name": "Agent Creator", + "email": "noreply@example.com" + }, + "metadata": { + "description": "Brief agent description", + "version": "1.0.0", + "created": "2025-10-17" + }, + "plugins": [ + { + "name": "agent-plugin", + "description": "THIS DESCRIPTION MUST BE IDENTICAL to the description in SKILL.md frontmatter that you'll create in the next step", + "source": "./", + "strict": false, + "skills": ["./"] + } + ] +} +``` + +**⚠️ CRITICAL FIELDS:** + +- `name`: Agent name (same as directory name) +- `plugins[0].description`: **MUST BE EXACTLY EQUAL** to SKILL.md frontmatter description +- `plugins[0].skills`: `["./"]` points to SKILL.md in root +- `plugins[0].source`: `"./"` points to agent root + +#### Step 0.3: VALIDATE IMMEDIATELY (before continuing!) + +**Execute NOW these validation commands:** + +```bash +# 1. Validate JSON syntax +python3 -c "import json; print('✅ Valid JSON'); json.load(open('agent-name/.claude-plugin/marketplace.json'))" + +# 2. Verify file exists +ls -la agent-name/.claude-plugin/marketplace.json + +# If any command fails: STOP and fix before continuing! +``` + +**✅ CHECKLIST - You MUST complete ALL before proceeding:** + +- [ ] ✅ File `.claude-plugin/marketplace.json` created +- [ ] ✅ JSON is syntactically valid (validated with python) +- [ ] ✅ Field `name` is correct +- [ ] ✅ Field `plugins[0].description` ready to receive SKILL.md description +- [ ] ✅ Field `plugins[0].skills` = `["./"]` +- [ ] ✅ Field `plugins[0].source` = `"./"` + +**🛑 ONLY PROCEED AFTER VALIDATING ALL ITEMS ABOVE!** + +--- + +### Implementation Order (AFTER marketplace.json validated) + +Now that marketplace.json is created and validated, proceed: + +**1. Create rest of directory structure** + +```bash +mkdir -p agent-name/{scripts/utils,references,assets,data/{raw,processed,cache,analysis}} +``` + +**2. Create SKILL.md** + +Mandatory structure: + +- Frontmatter (name, description) +- When to use +- How it works (overview) +- Data source (detailed API) +- Workflows (step-by-step by question type) +- Available scripts (each explained) +- Available analyses (each explained) +- Error handling (all expected errors) +- Mandatory validations +- Performance and cache +- Keywords for detection +- Usage examples (5+ complete) + +**Size**: 5000-7000 words + +**⚠️ AFTER creating SKILL.md: SYNCHRONIZE description with marketplace.json!** + +**CRITICAL**: Now that SKILL.md is created with its frontmatter, you MUST: + +```bash +# Edit marketplace.json to update description +# Copy EXACTLY the description from SKILL.md frontmatter +# Paste in .claude-plugin/marketplace.json → plugins[0].description +``` + +**Verify synchronization:** + +- SKILL.md frontmatter description = marketplace.json plugins[0].description +- Must be IDENTICAL (word for word!) +- Without this, skill won't activate automatically + +**3. Implement Python scripts** + +**Order** (MANDATORY): + +1. **Utils first** (including helpers.py + validators/ - CRITICAL!) + - `utils/helpers.py` (🔴 MANDATORY - already specified previously) + - `utils/cache_manager.py` + - `utils/rate_limiter.py` + - `utils/validators/` (🔴 MANDATORY - see Step 3.5 below) +2. **Fetch** (API client - 1 method per API metric) +3. **Parse** (🔴 MODULAR: 1 parser per data type! - see Step 3.2 below) +4. **Analyze** (analyses - include comprehensive_report already specified!) + +**Each script (in general)**: + +- Shebang: `#!/usr/bin/env python3` +- Complete module docstring +- Organized imports +- Classes/functions with docstrings +- Type hints +- Error handling +- Logging +- Main function with argparse +- if __name__ == "__main__" + +--- + +### Step 3.2: Modular Parser Architecture (🆕 Enhancement #5 - MANDATORY!) + +**⚠️ COMMON PROBLEM:** v1.0 had 1 generic parser. When adding new data types, architecture broke. + +**Solution:** **1 specific parser per API data type!** + +**Rule:** If API returns N data types (identified in Phase 1.6) → create N specific parsers + +**Mandatory structure:** + +``` +scripts/ +├── parse_{type1}.py # Ex: parse_conditions.py +├── parse_{type2}.py # Ex: parse_progress.py +├── parse_{type3}.py # Ex: parse_yield.py +├── parse_{type4}.py # Ex: parse_production.py +└── parse_{type5}.py # Ex: parse_area.py +``` + +**Template for each parser:** + +```python +#!/usr/bin/env python3 +""" +Parser for {type} data from {API_name}. +Handles {type}-specific transformations and validations. +""" + +import pandas as pd +from typing import List, Dict, Any, Optional +import logging + +logger = logging.getLogger(__name__) + + +def parse_{type}_response(data: List[Dict]) -> pd.DataFrame: + """ + Parse API response for {type} data. + + Args: + data: Raw API response (list of dicts) + + Returns: + DataFrame with standardized schema: + - entity: str + - year: int + - {type}_value: float + - unit: str + - {type}_specific_fields: various + + Raises: + ValueError: If data is invalid + ParseError: If parsing fails + + Example: + >>> data = [{'entity': 'CORN', 'year': 2025, 'value': '15,300,000'}] + >>> df = parse_{type}_response(data) + >>> df.shape + (1, 5) + """ + if not data: + raise ValueError("Data cannot be empty") + + # Convert to DataFrame + df = pd.DataFrame(data) + + # {Type}-specific transformations + df = _clean_{type}_values(df) + df = _extract_{type}_metadata(df) + df = _standardize_{type}_schema(df) + + # Validate + _validate_{type}_schema(df) + + return df + + +def _clean_{type}_values(df: pd.DataFrame) -> pd.DataFrame: + """Clean {type}-specific values (remove formatting, convert types).""" + # Example: Remove commas from numbers + if 'value' in df.columns: + df['value'] = df['value'].astype(str).str.replace(',', '') + df['value'] = pd.to_numeric(df['value'], errors='coerce') + + # {Type}-specific cleaning + # ... + + return df + + +def _extract_{type}_metadata(df: pd.DataFrame) -> pd.DataFrame: + """Extract {type}-specific metadata fields.""" + # Example for progress data: extract % from "75% PLANTED" + # Example for condition data: extract rating from "GOOD (60%)" + # Customize per data type! + + return df + + +def _standardize_{type}_schema(df: pd.DataFrame) -> pd.DataFrame: + """ + Standardize column names and schema for {type} data. + + Output schema: + - entity: str + - year: int + - {type}_value: float (main metric) + - unit: str + - additional_{type}_fields: various + """ + # Rename columns to standard names + column_mapping = { + 'api_entity_field': 'entity', + 'api_year_field': 'year', + 'api_value_field': '{type}_value', + # Add more as needed + } + df = df.rename(columns=column_mapping) + + # Ensure types + df['year'] = df['year'].astype(int) + df['{type}_value'] = pd.to_numeric(df['{type}_value'], errors='coerce') + + return df + + +def _validate_{type}_schema(df: pd.DataFrame) -> None: + """Validate {type} DataFrame schema.""" + required_columns = ['entity', 'year', '{type}_value'] + + missing = set(required_columns) - set(df.columns) + if missing: + raise ValueError(f"Missing required columns: {missing}") + + # Type validations + if not pd.api.types.is_integer_dtype(df['year']): + raise TypeError("'year' must be integer type") + + if not pd.api.types.is_numeric_dtype(df['{type}_value']): + raise TypeError("'{type}_value' must be numeric type") + + +def aggregate_{type}(df: pd.DataFrame, by: str) -> pd.DataFrame: + """ + Aggregate {type} data by specified level. + + Args: + df: Parsed {type} DataFrame + by: Aggregation level ('national', 'state', 'region') + + Returns: + Aggregated DataFrame + + Example: + >>> agg = aggregate_{type}(df, by='state') + """ + # Aggregation logic specific to {type} + if by == 'national': + return df.groupby(['year']).agg({ + '{type}_value': 'sum', + # Add more as needed + }).reset_index() + + elif by == 'state': + return df.groupby(['year', 'state']).agg({ + '{type}_value': 'sum', + }).reset_index() + + # Add more levels... + + +def format_{type}_report(df: pd.DataFrame) -> str: + """ + Format {type} data as human-readable report. + + Args: + df: Parsed {type} DataFrame + + Returns: + Formatted string report + + Example: + >>> report = format_{type}_report(df) + >>> print(report) + "{Type} Report: ..." + """ + lines = [f"## {Type} Report\n"] + + # Format based on {type} data + # Customize per type! + + return "\n".join(lines) + + +def main(): + """Test parser with sample data.""" + # Sample data for testing + sample_data = [ + { + 'entity': 'CORN', + 'year': 2025, + 'value': '15,300,000', + # Add {type}-specific fields + } + ] + + print("Testing parse_{type}_response()...") + df = parse_{type}_response(sample_data) + print(f"✓ Parsed {len(df)} records") + print(f"✓ Columns: {list(df.columns)}") + print(f"\n{df.head()}") + + print("\nTesting aggregate_{type}()...") + agg = aggregate_{type}(df, by='national') + print(f"✓ Aggregated: {agg}") + + print("\nTesting format_{type}_report()...") + report = format_{type}_report(df) + print(report) + + +if __name__ == "__main__": + main() +``` + +**Why create modular parsers:** +- ✅ Each data type has peculiarities (progress has %, yield has bu/acre, etc) +- ✅ Scalable architecture (easy to add new types) +- ✅ Isolated tests (each parser tested independently) +- ✅ Simple maintenance (bug in 1 type doesn't affect others) +- ✅ Organized code (clear responsibilities) + +**Impact:** Professional and scalable architecture from v1.0! + +--- + +### Step 3.5: Validation System (🆕 Enhancement #10 - MANDATORY!) + +**⚠️ COMMON PROBLEM:** v1.0 without data validation. User doesn't know if data is reliable. + +**Solution:** Complete validation system in `utils/validators/` + +**Mandatory structure:** + +``` +scripts/utils/validators/ +├── __init__.py +├── parameter_validator.py # Validate function parameters +├── data_validator.py # Validate API responses +├── temporal_validator.py # Validate temporal consistency +└── completeness_validator.py # Validate data completeness +``` + +**Template 1: parameter_validator.py** + +```python +#!/usr/bin/env python3 +""" +Parameter validators for {skill-name}. +Validates user inputs before making API calls. +""" + +from typing import Any, List, Optional +from datetime import datetime + + +class ValidationError(Exception): + """Raised when validation fails.""" + pass + + +def validate_entity(entity: str, valid_entities: Optional[List[str]] = None) -> str: + """ + Validate entity parameter. + + Args: + entity: Entity name (e.g., "CORN", "SOYBEANS") + valid_entities: List of valid entities (None to skip check) + + Returns: + str: Validated and normalized entity name + + Raises: + ValidationError: If entity is invalid + + Example: + >>> validate_entity("corn") + "CORN" # Normalized to uppercase + """ + if not entity: + raise ValidationError("Entity cannot be empty") + + if not isinstance(entity, str): + raise ValidationError(f"Entity must be string, got {type(entity)}") + + # Normalize + entity = entity.strip().upper() + + # Check if valid (if list provided) + if valid_entities and entity not in valid_entities: + suggestions = [e for e in valid_entities if entity[:3] in e] + raise ValidationError( + f"Invalid entity: {entity}\n" + f"Valid options: {', '.join(valid_entities[:10])}\n" + f"Did you mean: {', '.join(suggestions[:3])}?" + ) + + return entity + + +def validate_year( + year: Optional[int], + min_year: int = 1900, + allow_future: bool = False +) -> int: + """ + Validate year parameter. + + Args: + year: Year to validate (None returns current year) + min_year: Minimum valid year + allow_future: Whether future years are allowed + + Returns: + int: Validated year + + Raises: + ValidationError: If year is invalid + + Example: + >>> validate_year(2025) + 2025 + >>> validate_year(None) + 2025 # Current year + """ + current_year = datetime.now().year + + if year is None: + return current_year + + if not isinstance(year, int): + raise ValidationError(f"Year must be integer, got {type(year)}") + + if year < min_year: + raise ValidationError( + f"Year {year} is too old (minimum: {min_year})" + ) + + if not allow_future and year > current_year: + raise ValidationError( + f"Year {year} is in the future (current: {current_year})" + ) + + return year + + +def validate_state(state: str, country: str = "US") -> str: + """Validate state/region parameter.""" + # Country-specific validation + # ... + return state.upper() + + +# Add more validators for domain-specific parameters... +``` + +**Template 2: data_validator.py** + +```python +#!/usr/bin/env python3 +""" +Data validators for {skill-name}. +Validates API responses and analysis outputs. +""" + +import pandas as pd +from typing import Dict, List, Any +from dataclasses import dataclass +from enum import Enum + + +class ValidationLevel(Enum): + """Severity levels for validation results.""" + CRITICAL = "critical" # Must fix + WARNING = "warning" # Should review + INFO = "info" # FYI + + +@dataclass +class ValidationResult: + """Single validation check result.""" + check_name: str + level: ValidationLevel + passed: bool + message: str + details: Optional[Dict] = None + + +class ValidationReport: + """Collection of validation results.""" + + def __init__(self): + self.results: List[ValidationResult] = [] + + def add(self, result: ValidationResult): + """Add validation result.""" + self.results.append(result) + + def has_critical_issues(self) -> bool: + """Check if any critical issues found.""" + return any( + r.level == ValidationLevel.CRITICAL and not r.passed + for r in self.results + ) + + def all_passed(self) -> bool: + """Check if all validations passed.""" + return all(r.passed for r in self.results) + + def get_warnings(self) -> List[str]: + """Get all warning messages.""" + return [ + r.message for r in self.results + if r.level == ValidationLevel.WARNING and not r.passed + ] + + def get_summary(self) -> str: + """Get summary of validation results.""" + total = len(self.results) + passed = sum(1 for r in self.results if r.passed) + critical = sum( + 1 for r in self.results + if r.level == ValidationLevel.CRITICAL and not r.passed + ) + + return ( + f"Validation: {passed}/{total} passed " + f"({critical} critical issues)" + ) + + +class DataValidator: + """Validates API responses and DataFrames.""" + + def validate_response(self, data: Any) -> ValidationReport: + """ + Validate raw API response. + + Args: + data: Raw API response + + Returns: + ValidationReport with results + """ + report = ValidationReport() + + # Check 1: Not empty + report.add(ValidationResult( + check_name="not_empty", + level=ValidationLevel.CRITICAL, + passed=bool(data), + message="Data is empty" if not data else "Data present" + )) + + # Check 2: Correct type + expected_type = (list, dict) + is_correct_type = isinstance(data, expected_type) + report.add(ValidationResult( + check_name="correct_type", + level=ValidationLevel.CRITICAL, + passed=is_correct_type, + message=f"Expected {expected_type}, got {type(data)}" + )) + + # Check 3: Has expected structure + if isinstance(data, dict): + has_data_key = 'data' in data + report.add(ValidationResult( + check_name="has_data_key", + level=ValidationLevel.WARNING, + passed=has_data_key, + message="Response has 'data' key" if has_data_key else "No 'data' key" + )) + + return report + + def validate_dataframe(self, df: pd.DataFrame, data_type: str) -> ValidationReport: + """ + Validate parsed DataFrame. + + Args: + df: Parsed DataFrame + data_type: Type of data (for type-specific checks) + + Returns: + ValidationReport + """ + report = ValidationReport() + + # Check 1: Not empty + report.add(ValidationResult( + check_name="not_empty", + level=ValidationLevel.CRITICAL, + passed=len(df) > 0, + message=f"DataFrame has {len(df)} rows" + )) + + # Check 2: Required columns + required = ['entity', 'year'] # Customize per type + missing = set(required) - set(df.columns) + report.add(ValidationResult( + check_name="required_columns", + level=ValidationLevel.CRITICAL, + passed=len(missing) == 0, + message=f"Missing columns: {missing}" if missing else "All required columns present" + )) + + # Check 3: No excessive NaN values + if len(df) > 0: + nan_pct = (df.isna().sum() / len(df) * 100).max() + report.add(ValidationResult( + check_name="nan_threshold", + level=ValidationLevel.WARNING, + passed=nan_pct < 30, + message=f"Max NaN: {nan_pct:.1f}% ({'OK' if nan_pct < 30 else 'HIGH'})" + )) + + # Check 4: Data types correct + if 'year' in df.columns: + is_int = pd.api.types.is_integer_dtype(df['year']) + report.add(ValidationResult( + check_name="year_type", + level=ValidationLevel.CRITICAL, + passed=is_int, + message="'year' is integer" if is_int else "'year' is not integer" + )) + + return report + + +def validate_{type}_output(result: Dict) -> ValidationReport: + """ + Validate analysis output for {type}. + + Args: + result: Analysis result dict + + Returns: + ValidationReport + """ + report = ValidationReport() + + # Check required keys + required_keys = ['year', 'year_info', 'data'] + for key in required_keys: + report.add(ValidationResult( + check_name=f"has_{key}", + level=ValidationLevel.CRITICAL, + passed=key in result, + message=f"'{key}' present" if key in result else f"Missing '{key}'" + )) + + # Check data quality + if 'data' in result and result['data']: + report.add(ValidationResult( + check_name="data_not_empty", + level=ValidationLevel.CRITICAL, + passed=True, + message="Data is present" + )) + + return report + + +# Main for testing +if __name__ == "__main__": + print("Testing validators...") + + # Test entity validator + print("\n1. Testing validate_entity():") + try: + entity = validate_entity("corn", ["CORN", "SOYBEANS"]) + print(f" ✓ Valid: {entity}") + except ValidationError as e: + print(f" ✗ Error: {e}") + + # Test year validator + print("\n2. Testing validate_year():") + year = validate_year(2025) + print(f" ✓ Valid: {year}") + + # Test DataValidator + print("\n3. Testing DataValidator:") + validator = DataValidator() + sample_data = [{'entity': 'CORN', 'year': 2025}] + report = validator.validate_response(sample_data) + print(f" {report.get_summary()}") +``` + +**Template 3: temporal_validator.py** + +```python +#!/usr/bin/env python3 +""" +Temporal validators for {skill-name}. +Checks temporal consistency and data age. +""" + +import pandas as pd +from datetime import datetime, timedelta +from typing import List +from .data_validator import ValidationResult, ValidationReport, ValidationLevel + + +def validate_temporal_consistency(df: pd.DataFrame) -> ValidationReport: + """ + Check temporal consistency in data. + + Validations: + - No future dates + - Years in valid range + - No suspicious gaps in time series + - Data age is acceptable + + Args: + df: DataFrame with 'year' column + + Returns: + ValidationReport + """ + report = ValidationReport() + current_year = datetime.now().year + + if 'year' not in df.columns: + report.add(ValidationResult( + check_name="has_year_column", + level=ValidationLevel.CRITICAL, + passed=False, + message="Missing 'year' column" + )) + return report + + # Check 1: No future years + max_year = df['year'].max() + report.add(ValidationResult( + check_name="no_future_years", + level=ValidationLevel.CRITICAL, + passed=max_year <= current_year, + message=f"Max year: {max_year} ({'valid' if max_year <= current_year else 'FUTURE!'})" + )) + + # Check 2: Years in reasonable range + min_year = df['year'].min() + is_reasonable = min_year >= 1900 + report.add(ValidationResult( + check_name="reasonable_year_range", + level=ValidationLevel.WARNING, + passed=is_reasonable, + message=f"Year range: {min_year}-{max_year}" + )) + + # Check 3: Data age (is data recent enough?) + data_age_years = current_year - max_year + is_recent = data_age_years <= 2 + report.add(ValidationResult( + check_name="data_freshness", + level=ValidationLevel.WARNING, + passed=is_recent, + message=f"Data age: {data_age_years} years ({'recent' if is_recent else 'STALE'})" + )) + + # Check 4: No suspicious gaps in time series + if len(df['year'].unique()) > 2: + years_sorted = sorted(df['year'].unique()) + gaps = [ + years_sorted[i+1] - years_sorted[i] + for i in range(len(years_sorted)-1) + ] + max_gap = max(gaps) if gaps else 0 + has_large_gap = max_gap > 2 + + report.add(ValidationResult( + check_name="no_large_gaps", + level=ValidationLevel.WARNING, + passed=not has_large_gap, + message=f"Max gap: {max_gap} years" + (" (suspicious)" if has_large_gap else "") + )) + + return report + + +def validate_week_number(week: int, year: int) -> ValidationResult: + """Validate week number is in valid range for year.""" + # Most data types use weeks 1-53 + is_valid = 1 <= week <= 53 + + return ValidationResult( + check_name="valid_week", + level=ValidationLevel.CRITICAL, + passed=is_valid, + message=f"Week {week} ({'valid' if is_valid else 'INVALID: must be 1-53'})" + ) + + +# Add more temporal validators as needed... +``` + +**Template 4: completeness_validator.py** + +```python +#!/usr/bin/env python3 +""" +Completeness validators for {skill-name}. +Checks data completeness and coverage. +""" + +import pandas as pd +from typing import List, Set +from .data_validator import ValidationResult, ValidationReport, ValidationLevel + + +def validate_completeness( + df: pd.DataFrame, + expected_entities: Optional[List[str]] = None, + expected_years: Optional[List[int]] = None +) -> ValidationReport: + """ + Validate data completeness. + + Args: + df: DataFrame to validate + expected_entities: Expected entities (None to skip) + expected_years: Expected years (None to skip) + + Returns: + ValidationReport + """ + report = ValidationReport() + + # Check 1: All expected entities present + if expected_entities: + actual_entities = set(df['entity'].unique()) + expected_set = set(expected_entities) + missing = expected_set - actual_entities + + report.add(ValidationResult( + check_name="all_entities_present", + level=ValidationLevel.WARNING, + passed=len(missing) == 0, + message=f"Missing entities: {missing}" if missing else "All entities present", + details={'missing': list(missing)} + )) + + # Check 2: All expected years present + if expected_years: + actual_years = set(df['year'].unique()) + expected_set = set(expected_years) + missing = expected_set - actual_years + + report.add(ValidationResult( + check_name="all_years_present", + level=ValidationLevel.WARNING, + passed=len(missing) == 0, + message=f"Missing years: {missing}" if missing else "All years present" + )) + + # Check 3: No excessive nulls in critical columns + critical_columns = ['entity', 'year'] # Customize + for col in critical_columns: + if col in df.columns: + null_count = df[col].isna().sum() + report.add(ValidationResult( + check_name=f"{col}_no_nulls", + level=ValidationLevel.CRITICAL, + passed=null_count == 0, + message=f"'{col}' has {null_count} nulls" + )) + + # Check 4: Coverage percentage + if expected_entities and expected_years: + expected_total = len(expected_entities) * len(expected_years) + actual_total = len(df) + coverage_pct = (actual_total / expected_total) * 100 if expected_total > 0 else 0 + + report.add(ValidationResult( + check_name="coverage_percentage", + level=ValidationLevel.INFO, + passed=coverage_pct >= 80, + message=f"Coverage: {coverage_pct:.1f}% ({actual_total}/{expected_total})" + )) + + return report +``` + +**Integration in analysis functions:** + +```python +def {analysis_function}(entity: str, year: Optional[int] = None, ...) -> Dict: + """Analysis function with validation.""" + from utils.validators.parameter_validator import validate_entity, validate_year + from utils.validators.data_validator import DataValidator + from utils.validators.temporal_validator import validate_temporal_consistency + + # VALIDATE INPUTS (before doing anything!) + entity = validate_entity(entity, valid_entities=[...]) + year = validate_year(year) + + # Fetch data + data = fetch_{metric}(entity, year) + + # VALIDATE API RESPONSE + validator = DataValidator() + response_validation = validator.validate_response(data) + + if response_validation.has_critical_issues(): + raise DataQualityError( + f"API response validation failed: {response_validation.get_summary()}" + ) + + # Parse + df = parse_{type}(data) + + # VALIDATE PARSED DATA + df_validation = validator.validate_dataframe(df, '{type}') + temporal_validation = validate_temporal_consistency(df) + + if df_validation.has_critical_issues(): + raise DataQualityError( + f"Data validation failed: {df_validation.get_summary()}" + ) + + # Analyze + results = analyze(df) + + # Return with validation info + return { + 'data': results, + 'year': year, + 'year_info': format_year_message(year, year_requested), + 'validation': { + 'passed': df_validation.all_passed(), + 'warnings': df_validation.get_warnings(), + 'report': df_validation.get_summary() + } + } +``` + +**Impact:** +- ✅ Reliable data (validated at multiple layers) +- ✅ Transparency (user sees validation report) +- ✅ Clear error messages (not just "generic error") +- ✅ Problem detection (gaps, nulls, inconsistencies) + +--- + +**4. Write references** + +For each reference file: + +- 1000-2000 words +- Useful content (examples, methodologies, guides) +- Well structured (headings, lists, code blocks) +- Well-formatted markdown + +**5. Create assets** + +- Syntactically valid JSONs +- Real values with comments +- Logical structure + +**6. Write README.md** + +- Step-by-step installation +- Required configuration +- Usage examples +- Troubleshooting + +**7. Create DECISIONS.md** + +Document all decisions made: + +- Which API chosen and why +- Which analyses defined and justification +- Structure chosen and rationale +- Trade-offs considered + +**8. Create VERSION and CHANGELOG.md** (🆕 Enhancement #7 - Versioning) + +**8.1 Create VERSION file:** + +``` +1.0.0 +``` + +**8.2 Create CHANGELOG.md:** + +```markdown +# Changelog + +All notable changes to {skill-name} will be documented here. + +Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +Versioning follows [Semantic Versioning](https://semver.org/). + +## [1.0.0] - {current_date} + +### Added + +**Core Functionality:** +- {function1}: {Description of what it does} +- {function2}: {Description of what it does} +- {function3}: {Description of what it does} +... + +**Data Sources:** +- {API_name}: {Coverage description} +- Authentication: {auth_method} +- Rate limit: {limit} + +**Analysis Capabilities:** +- {analysis1}: {Description and methodology} +- {analysis2}: {Description and methodology} +... + +**Utilities:** +- Cache system with {TTL} TTL +- Rate limiting: {limit} per {period} +- Error handling with automatic retries +- Data validation and quality checks + +### Data Coverage + +**Metrics implemented:** +- {metric1}: {Coverage details} +- {metric2}: {Coverage details} +... + +**Geographic coverage:** {geo_coverage} +**Temporal coverage:** {temporal_coverage} + +### Known Limitations + +- {limitation1} +- {limitation2} +... + +### Planned for v2.0 + +- {planned_feature1} +- {planned_feature2} +... + +## [Unreleased] + +### Planned + +- Add support for {feature} +- Improve performance for {scenario} +- Expand coverage to {new_area} +``` + +**8.3 Update marketplace.json with version:** + +Edit `.claude-plugin/marketplace.json` to include: + +```json +{ + "metadata": { + "description": "...", + "version": "1.0.0", + "created": "{current_date}", + "updated": "{current_date}" + } +} +``` + +**8.4 Create .bumpversion.cfg (optional):** + +If you want version automation: + +```ini +[bumpversion] +current_version = 1.0.0 +commit = False +tag = False + +[bumpversion:file:VERSION] + +[bumpversion:file:.claude-plugin/marketplace.json] +search = "version": "{current_version}" +replace = "version": "{new_version}" + +[bumpversion:file:CHANGELOG.md] +search = ## [Unreleased] +replace = ## [Unreleased] + +## [{new_version}] - {now:%Y-%m-%d} +``` + +**Impact:** +- ✅ Change traceability +- ✅ Professionalism +- ✅ Facilitates future updates +- ✅ Users know what changed between versions + +**9. Create INSTALLATION.md** (Didactic Tutorial) + +[Content of INSTALLATION.md as previously specified] + +### Practical Implementation + +**Create agent in subdirectory**: + +```bash +# Agent name based on domain/objective +agent_name="nass-usda-agriculture" # example + +# Create structure +mkdir -p $agent_name/{scripts/utils,references,assets,data} + +# Implement each file +# [Claude creates each file with Write tool] +``` + +**At the end, inform user**: + +``` +✅ Agent created in ./{agent_name}/ + +📁 Structure: +- .claude-plugin/marketplace.json ✅ (installation + version) +- SKILL.md (6,200 words) +- scripts/ (2,500+ lines of code) + ├─ utils/helpers.py ✅ (temporal context) + ├─ utils/validators/ ✅ (4 validators, ~800 lines) + ├─ parse_{type}*.py ✅ (1 per data type, modular) + └─ comprehensive_{domain}_report() ✅ +- tests/ (25+ tests, ~800 lines) ✅ + ├─ test_integration.py (end-to-end) + ├─ test_parse.py (all parsers) + ├─ test_helpers.py (temporal) + └─ test_validation.py (validators) +- references/ (5,000 words) +- assets/ (2 configs) +- README.md (1,000+ words with Testing section) +- INSTALLATION.md (1,500 words) ✅ +- DECISIONS.md (justifications) +- VERSION (1.0.0) ✅ +- CHANGELOG.md (release notes) ✅ + +🚀 To install: +/plugin marketplace add ./{agent_name} + +💡 Usage examples: +"[example 1]" +"[example 2]" +``` + +**See** `references/phase5-implementation.md` for complete implementation guide. + +## Complete Flow: Step-by-Step + +### User Input + +User describes workflow/objective: + +``` +"Every day I download US crop data from USDA, +compare current year vs previous, create state ranking +by production, and generate report. Takes 2 hours. +Automate this." +``` + +### Autonomous Execution + +**Claude executes internally** (doesn't need to show everything): + +**PHASE 1**: Discovery (~5-10 min) + +``` +[WebSearch: "USDA API free agriculture data"] +[WebFetch: NASS API docs] +[Compare: NASS vs ERS vs FAO] +→ DECISION: NASS QuickStats API + Justification: Free, data since 1866, all crops, state-level +``` + +**PHASE 2**: Design (~5 min) + +``` +[Analyze: "compare years", "state ranking", "generate report"] +→ DEFINE analyses: + 1. YoY comparison (compare years) + 2. State ranking (state ranking) + 3. Trend analysis (if user asks for trends) + 4. Report generator (generate report) +``` + +**PHASE 3**: Architecture (~5 min) + +``` +→ STRUCTURE: + scripts/ + - fetch_nass.py (API requests) + - parse_nass.py (parsing) + - analyze_nass.py (YoY, ranking, trends) + - generate_report.py (report) + utils/ + - cache_manager.py + - rate_limiter.py + references/ + - nass-api-guide.md + - analysis-methods.md + - report-templates.md + assets/ + - config.json + - metadata.json +``` + +**PHASE 4**: Detection (~2 min) + +``` +→ KEYWORDS: + Entities: USDA, NASS, agriculture, crops + Commodities: corn, soybeans, wheat + Metrics: production, area, yield + Actions: compare, ranking, trend + Geography: US, states, Iowa, Illinois + +→ DESCRIPTION: "This skill should be used when user + asks about US agriculture, production, area, + crop yield..." +``` + +**PHASE 5**: Implementation (~45 min) + +``` +[Write: SKILL.md] ✅ 6,000 words +[Write: fetch_nass.py] ✅ 280 lines +[Write: parse_nass.py] ✅ 180 lines +[Write: analyze_nass.py] ✅ 520 lines +[Write: generate_report.py] ✅ 200 lines +[Write: utils/cache_manager.py] ✅ 120 lines +[Write: utils/rate_limiter.py] ✅ 110 lines +[Write: references/nass-api-guide.md] ✅ 1,500 words +[Write: references/analysis-methods.md] ✅ 2,000 words +[Write: references/report-templates.md] ✅ 1,000 words +[Write: assets/config.json] ✅ +[Write: assets/metadata.json] ✅ +[Write: README.md] ✅ 800 words +[Write: DECISIONS.md] ✅ 600 words +``` + +### User Output + +``` +✅ Agent created: ./usda-agriculture-agent/ + +📊 Technical Decisions: +- API: USDA NASS QuickStats (free, data since 1866) +- Analyses: YoY, Ranking, Trends, Report Generation +- Structure: 4 main scripts + 2 utils +- Cache: Historical permanent, current 7 days + +📁 Files Created: +- SKILL.md: 6,000 words +- Python code: 2,500+ lines + ├─ Modular parsers (1 per type) + ├─ Validation system (800 lines) + ├─ Temporal helpers + └─ comprehensive_report() +- Tests: 25+ tests (800 lines) +- References: 4,500 words +- Configs: 2 files +- README: Complete (with Testing) +- INSTALLATION.md: Didactic tutorial +- VERSION: 1.0.0 +- CHANGELOG.md: Complete release notes + +🎯 Estimated Savings: +- Before: 2 hours/day +- After: 3 minutes/day +- Savings: 98.5% (117h/month → 1.5h/month) + +🚀 To install and use: + +# 1. Get API key (free): +Visit: https://quickstats.nass.usda.gov/api#registration + +# 2. Configure: +export NASS_API_KEY="your_key_here" + +# 3. Install skill: +/plugin marketplace add ./usda-agriculture-agent + +# 4. Use (examples): +"US corn production in 2023" +"Compare soybeans this year vs last year" +"Ranking of wheat producing states" +"Generate current crop report" +``` + +## Detailed References + +For details of each phase, load references: + +- `references/phase1-discovery.md`: API research and decision +- `references/phase2-design.md`: Analysis definition +- `references/phase3-architecture.md`: Project structuring +- `references/phase4-detection.md`: Keywords and automatic activation +- `references/phase5-implementation.md`: Code implementation +- `references/quality-standards.md`: Mandatory standards +- `references/examples.md`: Complete examples of created agents + +## Meta-Skill Usage Examples + +### Example 1: Simple Workflow + +``` +👤 "Automate: download weather data, calculate averages, + generate chart. I do this every week, takes 1h." + +🤖 [Activates agent-creator] + [Phase 1]: Research → NOAA API + [Phase 2]: Defines → Download, Aggregation, Visualization + [Phase 3]: Structure → 3 scripts + 2 utils + [Phase 4]: Keywords → weather, climate, temperature, NOAA + [Phase 5]: Implements everything + + ✅ Agent created: ./weather-analysis-agent/ + [Installation instructions] +``` + +### Example 2: Complex Workflow + +``` +👤 "I need an agent for stock financial analysis: + fetch prices, calculate technical indicators (RSI, MACD), + compare with benchmarks, generate alerts. Daily, 3h." + +🤖 [Activates agent-creator] + [Phase 1]: Research → Alpha Vantage API (or Yahoo Finance) + [Phase 2]: Defines → Price fetching, Technical indicators, + Benchmark comparison, Alert system + [Phase 3]: Structure → 5 scripts + 4 utils + [Phase 4]: Keywords → stock, RSI, MACD, technical analysis + [Phase 5]: Implements + + ✅ Agent created: ./stock-technical-analysis-agent/ +``` + +### Example 3: Just Vague Description + +``` +👤 "Create an agent for Brazilian agriculture" + +🤖 [Activates agent-creator] + + "Got it! I'll create an agent for Brazilian agriculture. + + A few quick questions: + - Focus on which crops? (soy, corn, coffee, all?) + - Desired analyses? (production, prices, exports?) + - Preferred source? (CONAB, IBGE, both?) + + Or I can decide based on most common use cases + (grain production via CONAB). Prefer I decide?" + +👤 "You decide, focus on grain production." + +🤖 [Phase 1]: CONAB Grain Survey + [Phase 2]: YoY, Ranking, Regional, Trends + [Phase 3-5]: Creates everything + + ✅ Agent created: ./conab-agriculture-agent/ +``` + +--- + +## PHASE 6: Test Suite Generation (🆕 Enhancement #4 - MANDATORY!) + +**Objective**: Generate comprehensive test suite that validates ALL functions + +**⚠️ COMMON PROBLEM:** v1.0 without tests. Difficult to validate code works, impossible to do regression testing. + +**Solution:** Automatically generate 25+ tests covering all layers! + +### Test Structure + +``` +tests/ +├── __init__.py +├── test_fetch.py # Test API fetch functions +├── test_parse.py # Test each parser +├── test_analyze.py # Test analysis functions +├── test_integration.py # End-to-end tests +├── test_validation.py # Test validators +├── test_helpers.py # Test temporal helpers +└── conftest.py # Shared fixtures (pytest) +``` + +### Template 1: test_integration.py (MAIN!) + +```python +#!/usr/bin/env python3 +""" +Integration tests for {skill-name}. +Tests complete workflows from query to result. +""" + +import sys +from pathlib import Path + +# Add scripts to path +sys.path.insert(0, str(Path(__file__).parent.parent / 'scripts')) + +from analyze_{domain} import ( + {function1}, + {function2}, + {function3}, + comprehensive_{domain}_report +) + + +def test_{function1}_basic(): + """Test {function1} with auto-year detection.""" + print(f"\n✓ Testing {function1}()...") + + try: + # Test auto-year detection (year=None) + result = {function1}('{example_entity}') + + # Validations + assert 'year' in result, "Missing 'year' in result" + assert 'year_info' in result, "Missing 'year_info'" + assert 'data' in result, "Missing 'data'" + assert result['year'] >= 2024, f"Year too old: {result['year']}" + + print(f" ✓ Auto-year working: {result['year']}") + print(f" ✓ Year info: {result['year_info']}") + print(f" ✓ Data present: {len(result.get('data', {}))} fields") + + return True + + except Exception as e: + print(f" ✗ FAILED: {e}") + import traceback + traceback.print_exc() + return False + + +def test_{function1}_specific_year(): + """Test {function1} with specific year.""" + print(f"\n✓ Testing {function1}(year=2024)...") + + try: + result = {function1}('{example_entity}', year=2024) + + assert result['year'] == 2024, "Requested year not used" + assert result['year_requested'] == 2024, "year_requested not tracked" + + print(f" ✓ Specific year working: {result['year']}") + + return True + + except Exception as e: + print(f" ✗ FAILED: {e}") + return False + + +def test_{function2}_comparison(): + """Test {function2} (comparison function).""" + print(f"\n✓ Testing {function2}()...") + + try: + result = {function2}('{example_entity}', year1=2024, year2=2023) + + # Validations specific to comparison + assert 'change_percent' in result, "Missing 'change_percent'" + assert isinstance(result['change_percent'], (int, float)), "change_percent not numeric" + + print(f" ✓ Comparison working: {result.get('change_percent')}% change") + + return True + + except Exception as e: + print(f" ✗ FAILED: {e}") + return False + + +def test_comprehensive_report(): + """Test comprehensive report (all-in-one function).""" + print(f"\n✓ Testing comprehensive_{domain}_report()...") + + try: + result = comprehensive_{domain}_report('{example_entity}') + + # Validations + assert 'metrics' in result, "Missing 'metrics'" + assert 'summary' in result, "Missing 'summary'" + assert 'alerts' in result, "Missing 'alerts'" + assert isinstance(result['metrics'], dict), "'metrics' must be dict" + + metrics_count = len(result['metrics']) + print(f" ✓ Comprehensive report working") + print(f" ✓ Metrics combined: {metrics_count}") + print(f" ✓ Summary: {result['summary'][:100]}...") + print(f" ✓ Alerts: {len(result['alerts'])}") + + return True + + except Exception as e: + print(f" ✗ FAILED: {e}") + return False + + +def test_validation_integration(): + """Test that validation is integrated in functions.""" + print(f"\n✓ Testing validation integration...") + + try: + result = {function1}('{example_entity}') + + # Check validation info is present + assert 'validation' in result, "Missing 'validation' info" + assert 'passed' in result['validation'], "Missing validation.passed" + assert 'report' in result['validation'], "Missing validation.report" + + print(f" ✓ Validation present: {result['validation']['report']}") + + return True + + except Exception as e: + print(f" ✗ FAILED: {e}") + return False + + +def main(): + """Run all integration tests.""" + print("=" * 70) + print("INTEGRATION TESTS - {skill-name}") + print("=" * 70) + + tests = [ + ("Auto-year detection", test_{function1}_basic), + ("Specific year", test_{function1}_specific_year), + ("Comparison function", test_{function2}_comparison), + ("Comprehensive report", test_comprehensive_report), + ("Validation integration", test_validation_integration), + ] + + results = [] + for test_name, test_func in tests: + passed = test_func() + results.append((test_name, passed)) + + # Summary + print("\n" + "=" * 70) + print("SUMMARY") + print("=" * 70) + + for test_name, passed in results: + status = "✅ PASS" if passed else "❌ FAIL" + print(f"{status}: {test_name}") + + passed_count = sum(1 for _, p in results if p) + total_count = len(results) + + print(f"\nResults: {passed_count}/{total_count} passed") + + return passed_count == total_count + + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) +``` + +### Template 2: test_parse.py + +```python +#!/usr/bin/env python3 +"""Tests for parsers.""" + +import sys +from pathlib import Path +import pandas as pd + +sys.path.insert(0, str(Path(__file__).parent.parent / 'scripts')) + +from parse_{type1} import parse_{type1}_response +from parse_{type2} import parse_{type2}_response +# Import all parsers... + + +def test_parse_{type1}(): + """Test {type1} parser.""" + print("\n✓ Testing parse_{type1}_response()...") + + sample_data = [ + {'{field1}': 'VALUE1', '{field2}': 2025, '{field3}': '1,234,567'} + ] + + try: + df = parse_{type1}_response(sample_data) + + # Validations + assert isinstance(df, pd.DataFrame), "Must return DataFrame" + assert len(df) == 1, f"Expected 1 row, got {len(df)}" + assert 'entity' in df.columns, "Missing 'entity' column" + assert 'year' in df.columns, "Missing 'year' column" + + print(f" ✓ Parsed: {len(df)} records") + print(f" ✓ Columns: {list(df.columns)}") + + return True + + except Exception as e: + print(f" ✗ FAILED: {e}") + return False + + +# Repeat for all parsers... + +def main(): + """Run parser tests.""" + tests = [ + test_parse_{type1}, + test_parse_{type2}, + # Add all... + ] + + passed = sum(1 for test in tests if test()) + print(f"\nResults: {passed}/{len(tests)} passed") + + return passed == len(tests) + + +if __name__ == "__main__": + sys.exit(0 if main() else 1) +``` + +### Template 3: test_helpers.py + +```python +#!/usr/bin/env python3 +"""Tests for temporal helpers.""" + +import sys +from pathlib import Path +from datetime import datetime + +sys.path.insert(0, str(Path(__file__).parent.parent / 'scripts')) + +from utils.helpers import ( + get_current_{domain}_year, + get_{domain}_year_with_fallback, + should_try_previous_year, + format_year_message +) + + +def test_get_current_year(): + """Test current year detection.""" + year = get_current_{domain}_year() + current = datetime.now().year + + assert year == current, f"Expected {current}, got {year}" + print(f"✓ Current year: {year}") + return True + + +def test_year_with_fallback(): + """Test year fallback logic.""" + primary, fallback = get_{domain}_year_with_fallback(2024) + + assert primary == 2024, "Primary should be 2024" + assert fallback == 2023, "Fallback should be 2023" + + print(f"✓ Fallback: {primary} → {fallback}") + return True + + +def test_format_year_message(): + """Test year message formatting.""" + msg = format_year_message(2024, 2025) + + assert '2024' in msg, "Must mention year used" + assert '2025' in msg, "Must mention year requested" + + print(f"✓ Message: {msg}") + return True + + +def main(): + """Run helper tests.""" + tests = [ + test_get_current_year, + test_year_with_fallback, + test_format_year_message + ] + + passed = sum(1 for test in tests if test()) + print(f"\nResults: {passed}/{len(tests)} passed") + + return passed == len(tests) + + +if __name__ == "__main__": + sys.exit(0 if main() else 1) +``` + +### Minimum Test Coverage + +**For skill to be considered complete, needs:** + +- [ ] test_integration.py with ≥5 end-to-end tests +- [ ] test_parse.py with 1 test per parser +- [ ] test_analyze.py with 1 test per analysis function +- [ ] test_helpers.py with ≥3 tests +- [ ] test_validation.py with ≥5 tests +- [ ] **Total:** ≥25 tests +- [ ] **Coverage:** ≥80% of code +- [ ] **All tests PASS** + +### How to test + +Include in README.md: + +```markdown +## Testing + +### Run All Tests + +```bash +cd {skill-name} +python3 tests/test_integration.py +``` + +### Run Specific Tests + +```bash +python3 tests/test_parse.py +python3 tests/test_helpers.py +python3 tests/test_validation.py +``` + +### Expected Output + +``` +====================================================================== +INTEGRATION TESTS - {skill-name} +====================================================================== + +✓ Testing {function1}()... + ✓ Auto-year working: 2025 + ✓ Data present: 8 fields + +✓ Testing {function2}()... + ✓ Comparison working: +12.3% change + +... + +====================================================================== +SUMMARY +====================================================================== +✅ PASS: Auto-year detection +✅ PASS: Specific year +✅ PASS: Comparison function +✅ PASS: Comprehensive report +✅ PASS: Validation integration + +Results: 5/5 passed +``` +``` + +### Test Suite Benefits + +- ✅ Reliability: Tested and working code +- ✅ Regression testing: Detects breaks when modifying +- ✅ Executable documentation: Tests show how to use +- ✅ CI/CD ready: Can run automatically +- ✅ Professionalism: Production-quality skills + +**Impact:** Generated skills are tested and reliable from v1.0! + +--- + +## Agent Creation Workflow: Checklist + +When creating an agent, follow this checklist RIGOROUSLY in order: + +--- + +### 🚨 STEP 0: MANDATORY - FIRST STEP + +**Execute BEFORE anything else:** + +- [ ] 🚨 Create `.claude-plugin/marketplace.json` +- [ ] 🚨 Validate JSON syntax with python +- [ ] 🚨 Verify mandatory fields filled +- [ ] 🚨 Confirm: "Marketplace.json created and validated - can proceed" + +**🛑 DO NOT PROCEED without completing ALL items above!** + +--- + +### ✅ Phase 1-4: Planning + +- [ ] Domain identified +- [ ] API researched and decided (with justification) +- [ ] **API completeness analysis** (Phase 1.6 - coverage ≥50%) +- [ ] Analyses defined (4-6 main + comprehensive_report) +- [ ] Structure planned (modular parsers, validators/) +- [ ] Keywords determined (≥60 unique) + +--- + +### ✅ Phase 5: Implementation + +- [ ] .claude-plugin/marketplace.json created FIRST +- [ ] marketplace.json validated (syntax + fields) +- [ ] SKILL.md created with correct frontmatter +- [ ] **CRITICAL:** SKILL.md description copied to marketplace.json → plugins[0].description (IDENTICAL!) +- [ ] Validate synchronization: SKILL.md description === marketplace.json +- [ ] **MANDATORY:** utils/helpers.py created (temporal context) +- [ ] **MANDATORY:** utils/validators/ created (4 validators) +- [ ] **MANDATORY:** Modular parsers (1 per data type) +- [ ] **MANDATORY:** comprehensive_{domain}_report() implemented +- [ ] DECISIONS.md documenting choices +- [ ] VERSION file created (e.g., 1.0.0) +- [ ] CHANGELOG.md created with complete v1.0.0 entry +- [ ] marketplace.json with version field +- [ ] Implement functional code (no TODOs) +- [ ] Write complete docstrings +- [ ] Add error handling +- [ ] Write references with useful content +- [ ] Create real configs +- [ ] Write complete README +- [ ] INSTALLATION.md with complete tutorial + +--- + +### ✅ Phase 6: Test Suite + +- [ ] tests/ directory created +- [ ] test_integration.py with ≥5 end-to-end tests +- [ ] test_parse.py with 1 test per parser +- [ ] test_analyze.py with 1 test per analysis function +- [ ] test_helpers.py with ≥3 tests +- [ ] test_validation.py with ≥5 tests +- [ ] **Total:** ≥25 tests implemented +- [ ] **ALL tests PASS** (execute and validate!) +- [ ] "Testing" section added to README.md + +--- + +### ✅ Final Validation + +- [ ] Validate marketplace.json again (syntax + synchronized description) +- [ ] Validate other JSONs (configs, assets) +- [ ] Verify imports work +- [ ] Check no placeholder/TODO +- [ ] Test main logic manually +- [ ] Verify README has all instructions +- [ ] Calculate estimated ROI (time before vs after) + +--- + +### 🚀 MANDATORY TEST - DO NOT SKIP THIS STEP! + +**Execute this command MANDATORY before delivering:** + +```bash +cd /path/to/skills +/plugin marketplace add ./agent-name +``` + +**Verifications:** + +- [ ] ✅ Command executed without errors +- [ ] ✅ Skill appears in installed plugins list +- [ ] ✅ Claude recognizes the skill (do test question) + +**🛑 If test fails:** + +1. Verify marketplace.json exists +2. Verify JSON is valid +3. Verify description is synchronized +4. Fix and test again + +**Only deliver to user AFTER installation test passes!** + +--- + +### ✅ Deliver to User + +- [ ] Show created structure +- [ ] Summarize main decisions +- [ ] List files and sizes +- [ ] Give installation instructions (command tested above) +- [ ] Give 3-5 usage examples +- [ ] Inform estimated ROI +- [ ] **Confirm: "Skill tested and installed successfully"** + +## User Communication + +### During Creation + +**Show high-level progress**: + +``` +🔍 Phase 1: Researching APIs... + ✓ 5 options found + ✓ Decided: NASS API (free, complete data) + +🎨 Phase 2: Defining analyses... + ✓ 15 typical questions identified + ✓ 5 main analyses defined + +🏗️ Phase 3: Structuring project... + ✓ 3 scripts + 2 utils planned + +🎯 Phase 4: Defining detection... + ✓ 50+ keywords identified + +⚙️ Phase 5: Implementing code... + [Progress while creating files] + ✓ SKILL.md (6,200 words) + ✓ fetch_nass.py (280 lines) + ✓ parse_nass.py (180 lines) + [...] +``` + +**Don't show**: Technical details during creation (code blocks, etc). Just progress. + +### After Completion + +**Executive summary**: + +``` +✅ AGENT CREATED SUCCESSFULLY! + +📂 Location: ./usda-agriculture-agent/ + +📊 Main Decisions: +- API: USDA NASS QuickStats +- Analyses: YoY, Ranking, Trends, Reports +- Implementation: 1,410 lines Python + 4,500 words docs + +💰 Estimated ROI: +- Time before: 2h/day +- Time after: 3min/day +- Savings: 117h/month + +🎓 See DECISIONS.md for complete justifications. + +🚀 NEXT STEPS: + +1. Get API key (free): + https://quickstats.nass.usda.gov/api#registration + +2. Configure: + export NASS_API_KEY="your_key" + +3. Install: + /plugin marketplace add ./usda-agriculture-agent + +4. Test: + "US corn production in 2023" + "Compare soybeans this year vs last year" + +See README.md for complete instructions. +``` + +## Keywords for This Meta-Skill Detection + +This meta-skill (agent-creator) is activated when user mentions: + +**Create/Develop**: + +- "Create an agent" +- "Develop agent" +- "Create skill" +- "Develop skill" +- "Build agent" + +**Automate**: + +- "Automate this workflow" +- "Automate this process" +- "Automate this task" +- "Need to automate" +- "Turn into agent" + +**Repetitive Workflow**: + +- "Every day I do" +- "Repeatedly need to" +- "Manual process" +- "Workflow that takes Xh" +- "Task I repeat" + +**Agent for Domain**: + +- "Agent for [domain]" +- "Custom skill for [domain]" +- "Specialize Claude in [domain]" + +## ⚠️ Troubleshooting: Common Marketplace.json Errors + +### Error: "Failed to install plugin" + +**Most common cause:** marketplace.json doesn't exist or is poorly formatted + +**Diagnosis:** + +```bash +# 1. Verify file exists +ls -la agent-name/.claude-plugin/marketplace.json + +# 2. Validate JSON +python3 -c "import json; json.load(open('agent-name/.claude-plugin/marketplace.json'))" + +# 3. View content +cat agent-name/.claude-plugin/marketplace.json +``` + +**Solutions:** + +1. If file doesn't exist: Go back to STEP 0 and create +2. If invalid JSON: Fix syntax errors +3. If missing fields: Compare with STEP 0 template + +### Error: "Skill not activating" + +**Cause:** marketplace.json description ≠ SKILL.md description + +**Diagnosis:** + +```bash +# Compare descriptions +grep "description:" agent-name/SKILL.md +grep "\"description\":" agent-name/.claude-plugin/marketplace.json +``` + +**Solution:** + +1. Copy EXACT description from SKILL.md frontmatter +2. Paste in marketplace.json → plugins[0].description +3. Ensure they are IDENTICAL (word for word) +4. Save and test installation again + +### Error: "Invalid plugin structure" + +**Cause:** Mandatory marketplace.json fields incorrect + +**Verify:** + +- ✅ `plugins[0].skills` = `["./"]` (not `["SKILL.md"]` or other value) +- ✅ `plugins[0].source` = `"./"` (not empty or other value) +- ✅ `name` in JSON root matches directory name + +**Solution:** +Edit marketplace.json and fix fields above according to STEP 0 template. + +## 🧠 Final Step: Store Episode for Learning + +**⚠️ CRITICAL**: After successful agent creation, store the episode in AgentDB for future learning. + +### Automatic Episode Storage + +```python +# Store this successful creation for future learning +from integrations.agentdb_bridge import get_agentdb_bridge +from integrations.agentdb_real_integration import Episode + +try: + bridge = get_real_agentdb_bridge() + + # Create episode from this creation + episode = Episode( + session_id=f"agent-creation-{datetime.now().strftime('%Y%m%d-%H%M%S')}", + task=user_input, # Original user request + input=f"Domain: {domain}, API: {selected_api}, Structure: {architecture}", + output=f"Created: {agent_name}/ with {len(scripts)} scripts", + critique=f"Success: {'✅ High quality' if all_tests_passed else '⚠️ Needs refinement'}", + reward=0.9 if all_tests_passed else 0.7, + success=all_tests_passed, + latency_ms=creation_time_seconds * 1000, + tokens_used=estimated_tokens, + tags=[domain, selected_api, architecture_type], + metadata={ + "agent_name": agent_name, + "domain": domain, + "api": selected_api, + "complexity": complexity, + "files_created": len(all_files), + "validation_passed": all_tests_passed + } + ) + + # Store episode for learning + episode_id = bridge.store_episode(episode) + print(f"🧠 Episode stored for learning: #{episode_id}") + + # If successful, create skill + if all_tests_passed and bridge.is_available: + skill_name = f"{domain}_agent_template" + skill = Skill( + name=skill_name, + description=f"Proven template for {domain} agents", + code=f"API: {selected_api}, Structure: {architecture}", + success_rate=1.0, + uses=1, + avg_reward=0.9, + metadata={"domain": domain, "api": selected_api} + ) + + skill_id = bridge.create_skill(skill) + print(f"🎯 Skill created: #{skill_id}") + +except Exception as e: + # AgentDB failure should not break agent creation + print("🔄 AgentDB learning unavailable - agent creation completed successfully") + pass +``` + +### Learning Progress Indicators + +**Provide subtle feedback to user about learning progress:** + +```python +# Check learning milestones +if episode_id: + from integrations.learning_feedback import analyze_agent_execution + + feedback = analyze_agent_execution( + agent_name=agent_name, + user_input=user_input, + execution_time=creation_time_seconds, + success=all_tests_passed, + result_quality=0.9 if all_tests_passed else 0.7 + ) + + if feedback: + print(feedback) # Subtle milestone feedback +``` + +**Example user feedback:** +- First creation: "🎉 First agent created successfully!" +- After 10 creations: "⚡ Agent creation optimized based on 10 successful patterns" +- After 30 days: "🌟 I've learned your preferences - shall I optimize this agent?" + +### Invisible Learning Complete + +**What happens behind the scenes:** +- ✅ Episode stored with full creation context +- ✅ Success patterns learned for future use +- ✅ Skills consolidated from successful templates +- ✅ Causal relationships established (API → success rate) +- ✅ User sees only: "Agent created successfully!" + +**Next user gets benefits:** +- Faster creation (learned optimal patterns) +- Better API selection (historical success rates) +- Proven architectures (domain-specific success) +- Personalized suggestions (learned preferences) + +--- + +## Limitations and Warnings + +### When NOT to use + +❌ Don't use this skill for: + +- Editing existing skills (use directly) +- Debugging skills (use directly) +- Questions about skills (answer directly) + +### Warnings + +⚠️ **Creation time**: + +- Simple agents: ~30-60 min +- Complex agents: ~60-120 min +- It's normal to take time (creating everything from scratch) + +⚠️ **Review needed**: + +- Created agent is functional but may need adjustments +- Test examples in README +- Iterate if necessary + +⚠️ **API keys**: + +- User needs to obtain API key +- Instructions in created agent's README diff --git a/.agents/skills/effect-ast-analyzer/SKILL.md b/.agents/skills/effect-ast-analyzer/SKILL.md new file mode 100644 index 0000000..5e4c6e3 --- /dev/null +++ b/.agents/skills/effect-ast-analyzer/SKILL.md @@ -0,0 +1,25 @@ +--- +name: effect-ast-analyzer +description: Run the Effect-TS AST analyzer to check for anti-patterns in local files. +--- + +# Effect AST Analyzer + +You can use the local Effect AST Analyzer to check any TypeScript file for Effect anti-patterns. +This is highly recommended before finalizing any complex changes to ensure you haven't introduced things like floating promises, bad error handling, or generic try/catch blocks. + +To run the analyzer on a file, use the Bash tool: + +```bash +./node_modules/.bin/effect-patterns-check path/to/your/file.ts +``` + +If it returns warnings or errors, you MUST fix them before considering your task complete. + +## How it works +It parses the local TypeScript AST and checks against over 50 specific Effect-TS rules, like: +- `try-catch-in-effect`: Ensure `Effect.try` is used instead of `try/catch` +- `async-await`: Ensure `Effect.gen` is used instead of `async`/`await` +- `missing-error-channel`: Ensure typed errors are propagated + +Always run this when refactoring or creating new Effect code! diff --git a/.agents/skills/effect-expert/SKILL.md b/.agents/skills/effect-expert/SKILL.md new file mode 100644 index 0000000..271bebd --- /dev/null +++ b/.agents/skills/effect-expert/SKILL.md @@ -0,0 +1,10 @@ +--- +name: effect-expert +description: Teaches the AI agent about Effect best practices using the Effect MCP server +--- + +# Effect Expert + +This skill activates the Effect MCP Server. It provides live, contextual information about the application's structure directly to AI coding agents. + +Use this skill when you need deep context about the application's services, layers, and their types in order to use Effect properly. diff --git a/.agents/skills/effect-expert/mcp.json b/.agents/skills/effect-expert/mcp.json new file mode 100644 index 0000000..3734dfd --- /dev/null +++ b/.agents/skills/effect-expert/mcp.json @@ -0,0 +1,13 @@ +{ + "mcpServers": { + "effect": { + "command": "npx", + "args": [ + "-y", + "@effect/mcp-server", + "--layer", + "src/layers.ts:AppLayer" + ] + } + } +} diff --git a/.agents/skills/tdfddd-assembly/SKILL.md b/.agents/skills/tdfddd-assembly/SKILL.md new file mode 100644 index 0000000..8eb44d6 --- /dev/null +++ b/.agents/skills/tdfddd-assembly/SKILL.md @@ -0,0 +1,135 @@ +--- +name: tdfddd-assembly +description: "Phase 4: Implementation Assembly. Translates F# models into TypeScript/Effect code. Triggers on: Implement the code, Phase 4, Start assembly, Generate TypeScript, Write Effect code." +--- + +# Assembly Agent: The Implementer + +## Description + +Phase 4: Implementation Assembly. Translates F# models into TypeScript/Effect code. + +## When to Use This Skill + +Activate when the user: + +- Says "Implement the code". +- Says "Phase 4" or "Start assembly". +- Says "Generate TypeScript" or "Write Effect code". +- Has already completed Blueprint for one workflow slice. + +## Core Function: The Translator + +**Goal:** Mechanically translate the F# Domain Model into idiomatic TypeScript/Effect code. + +F# is the primary design language and contract artifact for this phase. Assembly is a translation step from the frozen F# blueprint, not a redesign step. + +Use TDD during assembly, but only after the design is frozen. The contract from the blueprint determines the test surface. + +**Constraints:** + +- Read `design/workflows//shared-model.fs` when present and `design/workflows///04-blueprint.fs` as input. +- Generate valid, **type-safe** TypeScript code using the Effect library. +- **Do not** deviate from the blueprint. +- **Do not** invent new business rules. +- Implement exactly one workflow slice at a time. + +**Instructions:** + +1. Read `design/workflows//shared-model.fs` when present and `design/workflows///04-blueprint.fs`. +2. Read `design/feature//status.md` and confirm this workflow slice is selected for implementation. +3. Confirm the design/security gate is approved before writing implementation code. +4. Treat the selected `04-blueprint.fs` as the complete contract for exactly one workflow slice. Do not look for or assume additional slice blueprints in the same artifact. +5. Defer all other workflow slices to their own blueprint and assembly passes. +6. **Follow Strict Project Structure:** +7. Derive tests from the frozen contract before or alongside code: + - workflow scenarios from commands and events + - policy examples from decision points + - model properties from invariants and state transitions +8. Implement in vertical **RED -> GREEN -> REFACTOR** cycles. + - **RED:** pick one behavior from the frozen contract, write one test through the public seam, and confirm it fails for the expected reason. + - **GREEN:** write the minimum production code needed to make that single test pass. Do not implement extra behaviors early. + - **REFACTOR:** once green, improve names, duplication, and module depth without changing behavior. Re-run tests after each refactor. + - Avoid premature indirection during refactor: keep logic combined by default, and extract only when the green code shows a real gain in nameability, testability, reuse, boundary clarity, or duplicated complexity. + - Good names help reveal useful extractions, but they do not justify an unnecessary layer by themselves. + - Start with one **tracer bullet** test that proves the end-to-end path, then add one behavior at a time. + - Never refactor while red. +9. **Follow Strict Project Structure:** + - **`src/domain/models/`** (Pure Domain Knowledge): + - Place `Effect.Schema` definitions here. + - Place `Brand` types here. + - Place Pure Logic (e.g., `Money.add`, `Cart.isEmpty`). + - **NO** external dependencies, **NO** `Effect<...>`, **NO** Side Effects. + - **`src/domain/interfaces/`** (Service Contracts): + - Define interfaces for repositories and services (e.g., `interface PaymentRepo`). + - Use `Context.Tag` for dependency injection. + - **`src/policies/`** (Business Decisions): + - Pure functions that make decisions based on multi-entity context. + - Returns `Result` or `Strategy Decision`. + - **NO** Side Effects. + - Example: `determinePaymentStrategy(amount)`, `canAccessResource(user, resource)`. + - **`src/workflows/`** (Orchestration): + - The "Impure Shell". Connects pieces, manages transaction flow. + - Uses `Effect.gen(function*(_) { ... })`. + - Orchestrates: 1. Fetch State (Repo) -> 2. Policy Decision (Policy) -> 3. Apply Change (Model) -> 4. Save (Repo). + - **`src/registries/`** (Dynamic Wiring): + - Strategy Pattern implementation (e.g., selecting Stripe vs PayPal based on Policy). + - **`src/adapters/`** (Infrastructure): + - Concrete implementations of interfaces (e.g., `Stripe.service.ts`). + - Place adapters here. + - **`src/layers/`** (Dependency Injection): + - `Main.layer.ts` for wiring production dependencies. +10. Integrate **Input Security** (See docs/explanation/architecture/input-security.md): + - **Wrappers:** Never pass raw strings to sensitive sinks. Translate raw string inputs into strongly typed wrappers (e.g., `TrustedInput`, `TaintedInput`) at the boundary. + - **Provenance:** Ensure the type explicitly states the source and trust level. + - **Strict Ingestion:** Implement strict filtering (length, type, format) at the ingestion point (e.g., in the Controller or the very beginning of the Workflow). + - **Late Sanitization:** Apply encoding or context-specific escaping right before the string is consumed (e.g., in an Adapter right before a DB call or API request). + - **Structural Safety:** Use parameterized queries or structural objects instead of string concatenation. +11. Translate **Primitives**: + - `type Weight = int` -> `const Weight = Schema.Number.pipe(Schema.brand("Kg"))` + - Always export the type: `export type Weight = typeof Weight.Type` +12. Translate **Structures (Records)**: + - `type User = { Name: string }` -> `const User = Schema.Struct({ name: Schema.String })` +13. Translate **Unions (Aggregates/Events)**: + - `type State = A | B` -> `Schema.Union(A, B)` (Discriminated Union) +14. Implement the **Contract**: + - `decide : State -> Command -> Result` -> `export const decide = (state: State, cmd: Command): Either => ...` + - `apply : State -> Event -> State` -> `export const apply = (state: State, event: Event): State => ...` + - `workflow : Command -> Effect` -> `export const workflow = (cmd: Command) => Effect.gen(...)` +15. **Action:** Write the implementation files to `src/` and the tests to `test/`, then update `design/feature//status.md` for the current workflow slice. + - `src/domain/models/`: Types & Schemas + - `src/policies/`: Pure Logic (`decide`) + - `src/workflows/`: Effect Orchestration (`workflow`) +16. **Format:** Use strict TypeScript with Effect. +17. **Test style by layer:** + - `src/domain/models/` and `src/policies/`: example tests plus property-based tests for invariants. + - `src/workflows/`: scenario tests with Effect test layers / in-memory adapters. + - `src/adapters/`: contract tests and integration tests where appropriate. +18. **Per-cycle checklist:** + - test describes behavior, not implementation + - test uses the public interface only + - code change is minimal for the current failing test + - no speculative features or extra cases are added +19. **Verification before completion:** + - Run `pnpm verify` before declaring the implementation complete. + - Run `pnpm effect:check ` for each modified Effect file. + - Report verification results faithfully, including any failing command output. + +## Translation Table + +| F# Concept | TypeScript / Effect Implementation | +| :-------------------- | :---------------------------------------------------- | -------------------- | +| `type T = int` | `const T = Schema.Number.pipe(Schema.brand("Brand"))` | +| `type R = { f: T }` | `const R = Schema.Struct({ f: T })` | +| `type U = A | B` | `Schema.Union(A, B)` | +| `Result` | `Either` | +| `Async>` | `Effect` | + +## Usage Examples + +**User:** "Implement it." +**Agent (Implementer):** [Reads `design/workflows/loading/load-truck/04-blueprint.fs` and `design/feature/truck-loading/status.md`] +[Generates `src/domain/models/Truck.ts`] +[Generates `src/policies/LoadPolicy.ts`] +[Generates `src/workflows/LoadWorkflow.ts`] +"Implementation complete for the selected workflow slice based on the blueprint." diff --git a/.agents/skills/tdfddd-blueprint/SKILL.md b/.agents/skills/tdfddd-blueprint/SKILL.md new file mode 100644 index 0000000..70c944b --- /dev/null +++ b/.agents/skills/tdfddd-blueprint/SKILL.md @@ -0,0 +1,93 @@ +--- +name: tdfddd-blueprint +description: "Phase 5: Domain Modeling & Contract. Generates F# domain models from core sketches one workflow slice at a time. Use after core sketch and before assembly. Triggers on: blueprint the model, phase 5, start modeling, generate domain model." +--- + +# Blueprint Agent: The Architect + +## Description + +Phase 5: Domain Modeling & Contract. Generates F# domain models from one workflow slice's core sketch plus the bounded context shared language. F# remains the primary design language and the frozen contract that assembly translates from. + +## When to Use This Skill + +Activate when the user: + +- Says "Blueprint the model" or "Blueprint workflow X". +- Says "Phase 5" or "Start modeling". +- Says "Generate domain model". +- Has already completed Core Sketch for one workflow slice and wants to formalize the design for that specific workflow. + +## Core Function: The F# Architect + +**Goal:** Create a strict, type-driven F# domain model that captures the requirements. + +**Constraints:** + +- Read `design/workflows//shared-language.md` and `design/workflows///03-core-sketch.md`. +- Generate **valid F# pseudo-code** that captures the types, invariants, and function signatures. +- Treat the F# blueprint as the authoritative contract for the slice. +- **Do not** generate implementation code (TypeScript/Effect) yet. +- **Do not** skip the domain modeling phase. +- Model exactly one workflow slice at a time. + +**Instructions:** + +1. Read the bounded context shared language and the workflow slice core sketch. +2. **Check Shared Domain:** If there is an existing `design/workflows//shared-model.fs`, read it to ensure you reuse existing primitives and aggregates owned by that context. +3. **Apply Core Rules (from docs/core-rules/):** + - **Make Illegal States Unrepresentable:** + - Avoid primitive obsession + - Never use `string` for an ID; use `type OrderId = OrderId of string`. + - Never use `int` for a quantity; use `type Quantity = Quantity of int`. + - If a Truck cannot be loaded while Sealed, the `Load` command must strictly require `LoadingTruck` state. + - **Input Security (Wrappers):** + - Avoid raw strings at boundaries. Define specific wrapper types for untrusted data (e.g., `type TrustedInput = TrustedInput of string`, `type TaintedInput = TaintedInput of string`) to represent provenance and trust boundaries. (See docs/explanation/architecture/input-security.md) + - **Parse, Don't Validate:** + - The model should enforce invariants at the type level where possible. + - **Pure Policy:** + - The `decide` function **MUST** be pure. No `Async`, no `Task`, no Side Effects. + - Signature: `decide : State -> Command -> Result` +4. Identify: + - **Primitives:** (Weight, Volume, ID, etc.) -> Use distinct types (e.g., `type Weight = int`). + - **Compounds:** (Package, TruckCapacity) -> Use Records. + - **Aggregates:** (LoadingTruck vs SealedTruck) -> Use Discriminated Unions for states. + - **Events:** (PackageLoaded, LoadFailure) -> Use Discriminated Unions for outcomes. +5. Define the **Contract Signatures**: + - **Policy:** `decide : State -> Command -> Result` + - **Reducer:** `apply : State -> Event -> State` + - **Workflow:** `workflow : Command -> Effect` +6. **Action:** Write the model to `design/workflows///04-blueprint.fs`. If the slice introduces stable shared context primitives, update `design/workflows//shared-model.fs`. +7. **Format:** + + ```fsharp + // in 04-blueprint.fs + module WorkflowName + + // 1. Primitives + type ... + + // 2. Commands (Inputs) + type ... + + // 3. Events (Facts) + type ... + + // 4. State (Aggregates) + type State = + | Case1 of ... + | Case2 of ... + + // 5. Signatures (Contract) + val decide : State -> Command -> Result + val apply : State -> Event -> State + ``` + +8. **Final Output:** "Domain Model generated at `design/workflows///04-blueprint.fs`. Review the types, workflow boundaries, and slice plan. If approved, update `design/feature//status.md`." + +## Usage Examples + +**User:** "Blueprint the checkout workflow." +**Agent (Architect):** [Reads `design/workflows/orders/shared-language.md` and `design/workflows/orders/checkout/03-core-sketch.md`] +[Generates `design/workflows/orders/checkout/04-blueprint.fs` and updates `design/workflows/orders/shared-model.fs` if needed] +"Domain Model generated. Ready for design security review or assembly for this workflow slice." diff --git a/.agents/skills/tdfddd-context-workflow-decomposition/SKILL.md b/.agents/skills/tdfddd-context-workflow-decomposition/SKILL.md new file mode 100644 index 0000000..b4562f3 --- /dev/null +++ b/.agents/skills/tdfddd-context-workflow-decomposition/SKILL.md @@ -0,0 +1,157 @@ +--- +name: tdfddd-context-workflow-decomposition +description: "Phase 2: Context and workflow decomposition. Maps a discovered feature into bounded contexts, workflow slices, shared-language artifacts, handoffs, and a slice-first execution order. Use after feature discovery and before slice discovery/core sketching. Triggers on: context decomposition, workflow decomposition, decompose feature, identify bounded contexts, map workflows." +--- + +# Context & Workflow Decomposition Agent: The Mapper + +## Description + +Phase 2: Context & Workflow Decomposition. +Turns a feature-level discovery artifact into bounded contexts, per-context workflow slices, shared-language artifacts, and a feature-to-slice execution map. + +## When to Use This Skill + +Activate when the user: + +- Says "decompose the feature". +- Says "identify bounded contexts" or "map workflows". +- Says "Phase 2" after feature discovery is frozen. +- Wants to break a feature into workflow slices before deeper design. + +## Core Function: The Mapper + +**Goal:** Convert a feature-level discovery into a slice-first design map so downstream work happens one workflow inside one bounded context at a time. + +**Constraints:** + +- Read `design/feature//discovery.md` as primary input. +- The unit of decomposition is **one workflow slice inside one bounded context**. +- Default rule: **no cross-context decision logic inside a workflow slice**. +- Cross-context behavior belongs in feature-level handoff/orchestration notes, not in a slice contract. +- Do not do deep per-slice interrogation here; that belongs to Slice Discovery. + +## Instructions + +1. Read `design/feature//discovery.md`. +2. Identify the bounded contexts touched by the feature. +3. For each bounded context, identify the workflows that are truly owned by that context. +4. Create or update `design/feature//design.md` with: + - bounded context inventory + - feature steps mapped to workflow slices + - cross-context handoffs + - recommended implementation order + - unresolved dependency notes +5. For each bounded context, create or update `design/workflows//shared-language.md`. + - Record context-local ubiquitous language. + - Reuse global terms from `docs/reference/shared-language.md` when appropriate. + - Prefer short domain definitions and preferred/rejected synonyms. +6. For each workflow slice, create `design/workflows///01-decomposition.md` containing: + - workflow name and owning bounded context + - trigger / command summary + - purpose and success outcome + - upstream inputs / snapshots + - downstream handoffs / emitted events + - dependencies on other slices + - explicit statement that decision logic stays inside the owning context only +7. Create or update `design/feature//status.md` so it tracks: + - discovery status + - decomposition status + - bounded contexts + - workflow slices + - current slice in progress + - per-slice gate progression +8. Final output should tell the user which workflow slice is the best next candidate for Slice Discovery. + +## Required Artifacts + +### `design/feature//design.md` + +```markdown +# Feature Design Map: + +## Bounded Contexts + +- `` — `` + +## Feature Step to Workflow Slice Map + +| Feature Step | Bounded Context | Workflow Slice | Notes | +| :----------- | :-------------- | :------------- | :--------------- | +| `` | `` | `` | `` | + +## Cross-Context Handoffs + +- `` -> `` via `` because `` + +## Recommended Slice Order + +1. `/` — `` +2. `/` — `` + +## Orchestration Notes + +- `` + +## Open Questions + +- `` +``` + +### `design/workflows//shared-language.md` + +```markdown +# Shared Language: + +## Context Meaning + +- `` + +## Preferred Terms + +| Term | Meaning | Use this, not that | Notes | +| :---------------- | :---------- | :---------------------------------------- | :------- | +| `` | `` | `` not `` | `` | +``` + +### `design/workflows///01-decomposition.md` + +```markdown +# Workflow Decomposition: + +- Bounded context: `` +- Workflow slug: `` +- Trigger: `` +- Success outcome: `` + +## Inputs Owned by This Context + +- `` + +## Inputs Observed from Other Contexts + +- `` + +## Downstream Handoffs + +- `` + +## Slice Boundaries + +- Decision logic is owned only by ``. +- Cross-context orchestration belongs in feature-level notes. + +## Dependencies + +- Requires `` before/after because `` +``` + +## Success Criteria + +A good decomposition leaves behind: + +- a stable feature-to-slice map +- per-context shared-language artifacts +- workflow folders with clear ownership +- a recommended next slice for deep discovery +- no ambiguity that downstream phases operate on one slice at a time diff --git a/.agents/skills/tdfddd-core-sketch/SKILL.md b/.agents/skills/tdfddd-core-sketch/SKILL.md new file mode 100644 index 0000000..8b104c9 --- /dev/null +++ b/.agents/skills/tdfddd-core-sketch/SKILL.md @@ -0,0 +1,50 @@ +--- +name: tdfddd-core-sketch +description: "Phase 4: Core sketch. Translates one workflow slice inside one bounded context into required state, commands, events, and pure policy signatures. Use after slice discovery and before blueprinting. Triggers on: core sketch, identify state, draft policies, sketch this workflow." +--- + +# Core Sketch Agent: The Systems Analyst + +## Description + +Phase 4: Core Sketch. Bridges slice discovery and blueprinting by turning one workflow slice inside one bounded context into explicit required state, commands, events, and pure policy signatures. + +## When to Use This Skill + +Activate when the user: + +- Says "Create core sketch" or "Draft policies". +- Says "Phase 4" or "Identify state". +- Has completed slice discovery for one workflow slice and wants to figure out the data needed before blueprinting. + +## Core Function: The Analyst + +**Goal:** Translate one workflow slice into a reviewable sketch, preventing monolithic designs later while reusing the bounded context's shared language. + +**Constraints:** + +- Read `design/workflows//shared-language.md`, `design/workflows///01-decomposition.md`, and `design/workflows///02-discovery.md` as input. +- **Do not** write F# code. Use plain English and basic pseudo-signatures to identify data needs. +- **Do not** combine multiple workflows into a single file. +- Keep decision logic inside the owning bounded context. + +**Instructions:** + +1. Read the selected workflow slice artifacts. +2. **Identify the Command:** What user action or trigger starts this workflow? +3. **Extract Shared Concepts:** Reuse domain primitives and entities already named in the bounded context shared language. +4. **Sketch the Workflow:** Output `design/workflows///03-core-sketch.md`. + - **Command:** What triggers it? + - **Required State:** What state owned by this context is required to decide it? + - **Observed Inputs:** What snapshots or handoffs from other contexts are read, if any? + - **Policy Signature (Pseudo):** `decide : Command -> State -> Result` + - **Events:** What does it output? + - **Boundary Notes:** What must remain feature-level orchestration rather than slice logic? +5. **Final Output:** Inform the user: "Core Sketch completed for `design/workflows///03-core-sketch.md`. Ready for Blueprint for this workflow slice." + +## Usage Examples + +**User:** "Create core sketch for the checkout workflow slice." +**Agent (Analyst):** [Reads `design/workflows/orders/shared-language.md`, `design/workflows/orders/checkout/01-decomposition.md`, and `design/workflows/orders/checkout/02-discovery.md`] +[Generates `design/workflows/orders/checkout/03-core-sketch.md`] +"Core Sketch completed for the checkout workflow slice. Ready for Blueprint." diff --git a/.agents/skills/tdfddd-discovery/SKILL.md b/.agents/skills/tdfddd-discovery/SKILL.md new file mode 100644 index 0000000..22ae5e9 --- /dev/null +++ b/.agents/skills/tdfddd-discovery/SKILL.md @@ -0,0 +1,113 @@ +--- +name: tdfddd-discovery +description: "Phase 1: Feature discovery. Interrogates the user to uncover edge cases, candidate bounded contexts, candidate workflow slices, and shared language, then generates the feature discovery artifact. Triggers on: start discovery phase, interrogate me, phase 1, requirements gathering." +--- + +# Discovery Agent: Feature Detective + +## Description + +Phase 1: Feature Discovery & Consolidation. Interrogates the user to understand the full feature, uncover edge cases, identify candidate bounded contexts and workflows, and generate the feature discovery artifact. + +## When to Use This Skill + +Activate when the user: + +- Starts a new feature or design process. +- Says "Start discovery phase". +- Says "Interrogate me about [feature]". +- Mentions "Phase 1" or "Requirements gathering". + +## Core Function: The Interrogation Protocol + +You operate in two strict modes. You start in **Detective Mode**. + +### Mode 1: Detective Mode (Default) + +**Goal:** Understand the feature as a whole: edge cases, missing requirements, ambiguous business rules, candidate bounded contexts, candidate workflows, and the feature-level shared language. +**Constraint:** You are **FORBIDDEN** from writing the discovery artifact in this mode. + +**Instructions:** + +1. Analyze the user's request. +2. Identify the "Happy Path" (what happens when everything goes right). +3. Identify the "Unhappy Paths" (what happens when things go wrong). +4. Identify potential "Race Conditions" or "State Conflicts". +5. Walk the decision tree one branch at a time. Resolve one ambiguity before opening several new branches. +6. Ask clarifying questions one at a time when possible. +7. Periodically restate the current shared understanding in plain domain language so the user can confirm or correct it. +8. When terminology becomes stable, document domain terms in `src//CONTEXT.md` (or repo root for single contexts) using the strict Glossary Format. Update this file *inline* during the session rather than waiting. + 9. **Active Grilling**: Challenge fuzzy language actively ("Your glossary defines *cancellation* as X, but you seem to mean Y—which is it?"). Cross-reference definitions with the codebase and demand concrete scenarios to stress-test boundaries. Flag ambiguities explicitly. + 10. Identify candidate bounded contexts and candidate workflow slices, but do **not** deeply interrogate each slice yet. + 11. **Output:** A response that _must_ end with clarifying question(s). + - Do _not_ propose a solution yet. + - Do _not_ write code. + - Ask questions like: "What happens if X fails?", "Can Y happen before Z?", "Is this operation idempotent?" + +**Transition Trigger:** +When the user says **"Consolidate"**, **"Ready to spec"**, or **"Freeze discovery"**, switch to **Scribe Mode**. + +### Mode 2: Scribe Mode + +**Goal:** Freeze the feature discovery into a formal artifact. +**Constraint:** Do not ask new questions UNLESS a critical ambiguity blocks the specification. + +**Instructions:** + +1. Review the entire conversation history for this feature. +2. Synthesize the findings into a structured Markdown file. +3. **Critical Check:** If you find a logical contradiction or missing core requirement during synthesis: + - **STOP.** + - Do not write the file. + - Report the issue to the user: "I cannot consolidate yet because [Reason]. What should happen in this case?" + - Return to **Detective Mode**. +4. **Action:** If all is clear, create `design/feature//` if needed, write the file to `design/feature//discovery.md`, and create or update `design/feature//status.md` from `docs/reference/design-status-template.md`. +5. **Format:** + + ```markdown + # Feature Discovery: [Feature Name] + + ## 1. Commands (User Intents) + + - [Actor] wants to [Action] because [Reason]. + + ## 2. Events (Domain Facts) + + - [EventName] happened (payload: ...). + + ## 3. Business Rules & Invariants + + - Rule: [Description] + - Invariant: [Description] + + ## 4. Edge Cases Handled + + - Case: [Description] -> [Resolution] + + ## 5. Candidate Bounded Contexts + + - [ContextName]: [What it appears to own] + + ## 6. Candidate Workflow Slices + + - [ContextName]/[workflow-slug]: [Purpose] + + ## 7. Shared Language Notes + + - Preferred term: [Term] = [Meaning] + - Avoid: [Synonym] when you mean [Preferred term] + ``` + +6. If stable terminology emerged, explicitly define it in the relevant `src//CONTEXT.md` using the domain-specific dictionary format (with `_Avoid_` aliases, Example dialogues, etc.). +7. **Final Output:** "Feature discovery frozen at `design/feature//discovery.md`. Ready for Phase 2 (Context & Workflow Decomposition)." + +## Usage Examples + +**User:** "I want a truck loading system." +**Agent (Detective):** "Okay. When a truck arrives, do we know its capacity beforehand? What happens if a package is scanned but doesn't fit?" + +**User:** "Capacity is known. If it doesn't fit, reject it." +**Agent (Detective):** "Understood. What if the truck is already sealed? Can we unseal it? Or is that a strict error?" + +**User:** "Strict error. Consolidate." +**Agent (Scribe):** [Writes `design/feature//discovery.md`] "Feature discovery frozen." diff --git a/.agents/skills/tdfddd-refactor-diagnosis/SKILL.md b/.agents/skills/tdfddd-refactor-diagnosis/SKILL.md new file mode 100644 index 0000000..6061634 --- /dev/null +++ b/.agents/skills/tdfddd-refactor-diagnosis/SKILL.md @@ -0,0 +1,110 @@ +--- +name: tdfddd-refactor-diagnosis +description: "Refactor phase: scope, change-pressure, and seam diagnosis. Reviews existing code to explain what should be split, merged, renamed, or kept inline before refactoring. Use when planning a refactor, diagnosing seam problems, or deciding whether code should become a policy, model, service, task, or module." +--- + +# Refactor Diagnosis Agent: The Seam Analyst + +## Description + +Refactor phase: scope, change-pressure, and seam diagnosis. +Use this skill to inspect existing code and explain what shape problem actually exists before changing structure. + +## When to Use This Skill + +Activate when the user: + +- Says "diagnose this refactor". +- Wants to know whether to split, merge, rename, or leave code alone. +- Wants help identifying weak seams, tangled responsibilities, or top-level workflow sprawl. +- Wants to know whether logic should be a policy, model, service, task, or module. +- Wants a refactor plan before execution. + +## Core Function: The Refactor Diagnosis Protocol + +**Goal:** Produce a reviewable diagnosis of current shape problems and a clear target seam before code moves begin. + +**Required input:** + +- a declared scope: `micro`, `meso`, or `macro` +- target code or files +- the current pain or trigger for the refactor + +If the user does not provide scope, infer a provisional scope and state it explicitly. + +## Instructions + +1. Read the relevant code before making any structural claim. +2. Start by restating the refactor scope: + - **micro:** function, type, parameter list, local extraction + - **meso:** task, workflow, module seam + - **macro:** cross-module or bounded-context reshaping +3. State the preserved behavior and non-goals if they are known. + - If missing and critical, ask for them. +4. Perform a lightweight **change-pressure scan** using current evidence: + - what is changing together? + - what should change independently? + - where are there pass-through parameters? + - where is repeated branching happening? + - where is top-level coordination growing into a pile? +5. Diagnose candidate seams using these questions: + - What is the real purpose of this code? + - Is the name intention-based or mechanical? + - What should the caller know, and what should stay hidden? + - Is this really a policy, model operation, service, task, or module? + - Does it require too much authority? + - Does it force too much caller knowledge? + - Would three likely variations still fit naturally? +6. Distinguish between: + - a real abstraction + - a temporary extraction for readability + - a false seam that only renames plumbing +7. Recommend the smallest justified shape change. + - prefer inline code if the seam is not real yet + - prefer task extraction before workflow-to-workflow calls when possible + - prefer module extraction only when a bounded capability is repeatedly useful +8. Output a compact diagnosis report with these sections: + +```markdown +## Refactor Scope + +- Scope: micro|meso|macro +- Preserved behavior: ... +- Non-goals: ... + +## Change Pressure + +- Changes together: ... +- Should change independently: ... +- Evidence: ... + +## Seam Diagnosis + +- Current seam/problem: ... +- Why it is weak or overloaded: ... +- Correct shape: policy|model|service|task|module|keep inline +- 3-variation check: ... + +## Target Shape + +- Proposed boundary name: ... +- Public contract: ... +- What stays hidden: ... +- Why this reduces review load: ... +``` + +## Constraints + +- Do not write implementation code unless the user explicitly asks to proceed. +- Do not recommend extra layers without clear pressure. +- Do not confuse services with business decisions. +- Do not assume the top level must stay monolithic; top-level coordination seams are valid refactor targets. + +## Success Criteria + +A good diagnosis should let a human reviewer answer: + +- what problem is being fixed +- why this seam is real +- why the proposed shape is smaller or clearer +- what should be preserved during execution diff --git a/.agents/skills/tdfddd-refactor-execution/SKILL.md b/.agents/skills/tdfddd-refactor-execution/SKILL.md new file mode 100644 index 0000000..ad29c87 --- /dev/null +++ b/.agents/skills/tdfddd-refactor-execution/SKILL.md @@ -0,0 +1,103 @@ +--- +name: tdfddd-refactor-execution +description: "Refactor phase: safety net, small-step reshape, and proof. Executes a scoped refactor while preserving behavior, tightening seams, and verifying that the new shape is easier to review. Use when a refactor diagnosis or target seam is already clear and code should be changed." +--- + +# Refactor Execution Agent: The Reshaper + +## Description + +Refactor phase: safety net, small-step reshape, and proof. +Use this skill when the target seam is already clear enough to change code safely. + +## When to Use This Skill + +Activate when the user: + +- Says "execute this refactor". +- Has already identified a seam problem and wants code changed. +- Wants to extract a policy, model operation, task, or module. +- Wants to simplify a workflow without changing behavior. + +## Core Function: The Refactor Execution Protocol + +**Goal:** Reshape existing code in small verified moves while preserving behavior and improving seam quality. + +**Required input:** + +- a declared scope: `micro`, `meso`, or `macro` +- target code or files +- preserved behavior +- target seam or intended new shape + +If a diagnosis is missing, do a minimal inline diagnosis before changing code. + +## Instructions + +1. Read the current code and restate: + - scope + - preserved behavior + - non-goals + - target seam +2. Create or tighten a **safety net** before structural moves: + - add or improve tests at the public seam when needed + - rely on types where they already give strong guarantees + - stop and clarify if preserved behavior is ambiguous +3. Reshape in small steps: + - move one responsibility at a time + - keep the system runnable after each move + - extract policies for pure business decisions + - extract model operations for deterministic state transitions + - extract tasks for meaningful orchestration steps + - extract modules only when a bounded capability is truly emerging +4. Keep boundaries intention-based: + - prefer APIs named by caller intent + - hide mechanics and helper sequencing inside the seam +5. Avoid false progress: + - do not create wrappers that only rename plumbing + - do not widen interfaces for convenience + - do not move business logic into services + - do not expand scope into unrelated cleanup +6. Verify after each meaningful move and again at the end. +7. Report proof, not vibes. + +## Output Format + +```markdown +## Refactor Outcome + +- Scope: micro|meso|macro +- Preserved behavior: ... +- Target seam: ... + +## Changes Made + +- ... + +## Verification + +- Tests/types/checks run: ... +- Result: ... + +## Seam Improvement + +- Caller knows less about: ... +- Hidden internally now: ... +- Authority/dependency surface now: ... +``` + +## Constraints + +- Preserve behavior unless the user explicitly approves a behavior change. +- Prefer the smallest correct refactor. +- Use public module APIs for cross-module calls when available. +- Stop if the refactor reveals a design contradiction that requires user input. + +## Success Criteria + +A good execution leaves behind: + +- preserved behavior +- a seam that is easier to explain +- smaller or clearer dependency authority +- lower review load for future human or LLM work diff --git a/.agents/skills/tdfddd-security-verification/SKILL.md b/.agents/skills/tdfddd-security-verification/SKILL.md new file mode 100644 index 0000000..b209270 --- /dev/null +++ b/.agents/skills/tdfddd-security-verification/SKILL.md @@ -0,0 +1,121 @@ +--- +name: tdfddd-security-verification +description: "Reviews TDFDDD design artifacts or implemented code for security risks. Use when manually asked to review requirements, domain models, workflow designs, ADRs, or changed code for trust boundaries, least privilege, zero trust, blast radius, and related security concerns." +--- + +# TDFDDD Security Verification + +Reviews design artifacts or implemented code for security risks. + +## When to Use This Skill + +Activate only when manually requested for a security review. + +This skill handles both **Design** and **Implementation** reviews depending on what the user asks you to look at. + +Do **not** implement fixes. This skill is **review-only**. + +## Security Philosophy + +The mechanical checks in this skill serve to enforce the following guiding principles: + +- **Assume Breach**: Understand that perimeters fail; assume malicious input and compromised components already exist inside the system. +- **Zero Trust**: Never grant implicit trust based on network location or component origin. Verify identity, context, and input explicitly at every bounded seam. +- **Least Privilege**: Ensure components, workflows, and services only possess the exact authority (capabilities, data access) required for the current slice, and nothing more. +- **Defense in Depth**: Require multiple layers of security controls so that if one fails, others mitigate the impact. +- **Blast Radius Reduction**: Ensure isolation boundaries prevent a compromise in one workflow or dependency from spreading across the rest of the system. + +## Core Rules + +1. **Grill the human first**: If scope is ambiguous, ask what to review before starting. +2. **Review Target**: Default to reviewing the listed artifacts or the changed files plus adjacent seams. Check dependencies/versions *only* if the user requests "Expanded Surface Mode." +3. **Findings Only**: Do not pad the report with positive confirmations. +4. **Cite Evidence**: Every concrete finding must cite the exact file/line/section it comes from. +5. **Categorize Risk**: + - If you suspect a problem but cannot prove it, label it **Possible Concern**. + - If you cannot assess a category from the provided materials, label it **Insufficient Evidence**. +6. Reference relevant external standards when useful, especially OWASP ASVS and OWASP cheat sheets. + +## Master Review Categories + +Review every category below. If a category is not visible in the provided scope, mark it accordingly rather than skipping it. + +1. Trust Boundaries & Data Flow +2. Input Parsing & Validation (See `docs/explanation/architecture/input-security.md` for specific tainted string wrappers) +3. Sink Handling & Contextual Sanitization +4. Capabilities & Least Privilege +5. Secrets & Sensitive Config +6. Isolation & Blast Radius +7. Dependency & Supply Chain Assumptions +8. AuthN/AuthZ if applicable +9. Logging, Telemetry & Detection +10. Recovery & Failure Modes +11. Unsafe Dynamic Behavior +12. Security Testing Coverage + +## Phase-Specific Heuristics + +### If reviewing DESIGN artifacts: +- Do not demand code-level proof from design artifacts. +- Do demand that risky areas are named and bounded. +- Missing security design information is itself a valid finding. +- Treat “we will sanitize later” as weak unless the sink categories and ownership are explicit. +- Prefer structural security reasoning: + - where untrusted data enters + - how it is parsed into trusted types + - what authority the workflow slice requires + +### If reviewing IMPLEMENTATION: +- Look for raw untrusted data crossing into trusted domain types without parsing. +- Look for sink misuse: SQL building, shell execution, HTML rendering, URLs, file paths, template rendering, logging, dynamic code loading. +- Prefer parameterized APIs and framework safe sinks over handwritten escaping. +- Look for over-broad injected capabilities, especially services that grant more authority than the workflow needs. +- Distinguish purity from privilege: pure code can still orchestrate overly powerful services. +- Treat missing tests at risky seams as a security finding. + +## Required Output Format + +```markdown +# Security Verification Report + +## Phase: [Design / Implementation] + +## Scope Reviewed + +- ... + +## Artifacts Read + +- ... + +## Security Assumptions + +- ... + +## Overall Risk Summary + +- ... + +## 1. Trust Boundaries & Data Flow + +- [Finding|Possible Concern|Insufficient Evidence] Severity: X/10 + - Evidence: file/section/line + - Why it matters: ... + - Suggested fix: ... + +## 2. Input Parsing & Validation + +... + +## 3. Sink Handling & Contextual Sanitization + +... (continue for all 12 categories) ... + +## Cross-Cutting Questions + +- ... + +## Suggested Next Actions + +- ... +``` diff --git a/.agents/skills/tdfddd-slice-discovery/SKILL.md b/.agents/skills/tdfddd-slice-discovery/SKILL.md new file mode 100644 index 0000000..4249475 --- /dev/null +++ b/.agents/skills/tdfddd-slice-discovery/SKILL.md @@ -0,0 +1,88 @@ +--- +name: tdfddd-slice-discovery +description: "Phase 3: Slice discovery. Interrogates a single workflow slice inside one bounded context to tighten requirements, edge cases, invariants, and handoff assumptions before core sketching. Use after context/workflow decomposition and before core sketch. Triggers on: slice discovery, workflow discovery, interrogate this workflow, refine workflow slice." +--- + +# Slice Discovery Agent: The Slice Detective + +## Description + +Phase 3: Slice Discovery. +Deepens discovery for one workflow slice so Core Sketch starts from a well-bounded, well-questioned contract. + +## When to Use This Skill + +Activate when the user: + +- Says "discover this workflow". +- Says "interrogate this slice". +- Says "workflow discovery" after decomposition is complete. +- Wants to prepare one workflow slice for core sketching. + +## Core Function: The Slice Interrogation Protocol + +**Goal:** Clarify one workflow slice deeply enough that Core Sketch can identify state and policy shape without guessing. + +**Constraints:** + +- Operate on exactly one workflow at a time. +- Read `design/feature//discovery.md`, `design/feature//design.md`, the relevant `src//CONTEXT.md`, and `design/workflows///01-decomposition.md`. +- Do not reopen the whole feature unless a contradiction forces it. +- Default rule: no cross-context decision logic inside the slice. +- Record feature-level handoff assumptions, but keep slice decisions local. + +## Instructions + +1. Read the feature and workflow decomposition artifacts for the selected slice. +2. Identify the happy path, unhappy paths, invariant checks, timing issues, ownership boundaries, and handoff assumptions for this slice only. +3. Ask clarifying questions one branch at a time when needed. Actively "grill" the user if definitions clash or if terms are ambiguous (`"You say 'Account', but earlier we defined that as 'Customer'. Which is it?"`). + 4. Restate the slice in plain domain language as understanding stabilizes. + 5. Capture stable naming decisions in `src//CONTEXT.md` inline during the session. Use the strict Glossary format (`**Term**`, `_Avoid_`, Example dialogues) and do not include implementation details. +6. When the user says `Consolidate`, `Ready to sketch`, or `Freeze slice`, write `design/workflows///02-discovery.md`. +7. If a contradiction or missing prerequisite slice blocks progress, stop and report it rather than inventing an answer. +8. Final output should state that the slice is ready for Core Sketch. + +## Required Artifact + +### `design/workflows///02-discovery.md` + +```markdown +# Slice Discovery: + +- Bounded context: `` +- Workflow slug: `` + +## Happy Path + +- `` + +## Edge Cases + +- `` -> `` + +## Business Rules & Invariants + +- Rule: `` +- Invariant: `` + +## Required Decisions Owned by This Context + +- `` + +## Handoff Assumptions + +- `` + +## Open Questions + +- `` +``` + +## Success Criteria + +A good slice discovery leaves behind: + +- one workflow slice clarified deeply enough for sketching +- edge cases and invariants captured +- context language tightened +- cross-context assumptions made explicit without moving decision logic out of the owning context diff --git a/.config/amp/toolboxes/check-types b/.config/amp/toolboxes/check-types new file mode 100755 index 0000000..a4dabdb --- /dev/null +++ b/.config/amp/toolboxes/check-types @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import process from 'node:process'; + + +const action = process.env.TOOLBOX_ACTION; + +if (action === 'describe') { + process.stdout.write( + [ + 'name: check-types', + 'description: Run TypeScript compiler to check for type errors', + 'dir: string The workspace directory', + ].join('\n') + ); +} else if (action === 'execute') { + const input = fs.readFileSync(0, 'utf-8'); + let dir = '.'; + + const lines = input.split('\n'); + for (const line of lines) { + if (line.startsWith('dir: ')) { + dir = line.substring(5).trim(); + } + } + + const result = spawnSync('npx', ['tsc', '--noEmit'], { + cwd: dir, + stdio: 'inherit', + shell: true + }); + + process.exit(result.status ?? 1); +} diff --git a/.config/amp/toolboxes/hover b/.config/amp/toolboxes/hover new file mode 100755 index 0000000..a0ab772 --- /dev/null +++ b/.config/amp/toolboxes/hover @@ -0,0 +1,111 @@ +#!/usr/bin/env node + +import ts from 'typescript'; +import fs from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; + +const action = process.env.TOOLBOX_ACTION; + +if (action === 'describe') { + process.stdout.write( + [ + 'name: hover', + 'description: Get type information at a specific position', + 'dir: string The workspace directory', + 'file: string The absolute path to the file', + 'line: number The line number (1-based)', + 'col: number The column number (1-based)', + ].join('\n') + ); +} else if (action === 'execute') { + try { + const input = fs.readFileSync(0, 'utf-8'); + const params = parseInput(input); + + if (!params.dir || !params.file || !params.line || !params.col) { + console.error('Missing required parameters (dir, file, line, col)'); + process.exit(1); + } + + const configPath = ts.findConfigFile(params.dir, ts.sys.fileExists, 'tsconfig.json'); + if (!configPath) { + console.error('Could not find tsconfig.json in ' + params.dir); + process.exit(1); + } + + const configFile = ts.readConfigFile(configPath, ts.sys.readFile); + const parsedConfig = ts.parseJsonConfigFileContent( + configFile.config, + ts.sys, + path.dirname(configPath) + ); + + const servicesHost = { + getScriptFileNames: () => parsedConfig.fileNames, + getScriptVersion: () => '0', + getScriptSnapshot: (fileName) => { + if (!fs.existsSync(fileName)) { + return undefined; + } + return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString()); + }, + getCurrentDirectory: () => params.dir, + getCompilationSettings: () => parsedConfig.options, + getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options), + fileExists: ts.sys.fileExists, + readFile: ts.sys.readFile, + readDirectory: ts.sys.readDirectory, + directoryExists: ts.sys.directoryExists, + getDirectories: ts.sys.getDirectories, + }; + + const service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); + + const filePath = params.file; + + let sourceFile = service.getProgram().getSourceFile(filePath); + + if (!sourceFile) { + console.error(`File not found in program: ${filePath}`); + process.exit(1); + } + + const line = Number(params.line); + const col = Number(params.col); + + const position = sourceFile.getPositionOfLineAndCharacter(line - 1, col - 1); + + const info = service.getQuickInfoAtPosition(filePath, position); + + if (info) { + const display = ts.displayPartsToString(info.displayParts); + const doc = ts.displayPartsToString(info.documentation); + console.log('```typescript'); + console.log(display); + console.log('```'); + if (doc) { + console.log(doc); + } + } else { + console.log('No info available at this position'); + } + } catch (e) { + console.error(e); + process.exit(1); + } +} + +function parseInput(input) { + const params = {}; + const lines = input.split('\n'); + for (const line of lines) { + const idx = line.indexOf(': '); + if (idx !== -1) { + const key = line.substring(0, idx).trim(); + const value = line.substring(idx + 2).trim(); + params[key] = value; + } + } + return params; +} diff --git a/.config/amp/toolboxes/run-tests b/.config/amp/toolboxes/run-tests new file mode 100755 index 0000000..8152fba --- /dev/null +++ b/.config/amp/toolboxes/run-tests @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import process from 'node:process'; + + +const action = process.env.TOOLBOX_ACTION; + +if (action === 'describe') { + process.stdout.write( + [ + 'name: run-tests', + 'description: Run Vitest tests', + 'dir: string The workspace directory', + ].join('\n') + ); +} else if (action === 'execute') { + const input = fs.readFileSync(0, 'utf-8'); + let dir = '.'; + + const lines = input.split('\n'); + for (const line of lines) { + if (line.startsWith('dir: ')) { + dir = line.substring(5).trim(); + } + } + + const result = spawnSync('npx', ['vitest', 'run'], { + cwd: dir, + stdio: 'inherit', + shell: true + }); + + process.exit(result.status ?? 1); +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9220cf8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +dist/ +*.tsbuildinfo +.idea/ +.config/amp/AGENTS.md +.config/settings.json +.DS_Store + +.amp-usage.db diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..97b895e --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +ignore-scripts=true diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..60f6a9e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,25 @@ +# Amp Agent Configuration + +## Primary Context +@.agents/core-rules/global-context.md +@CONTEXT.md +@.agents/core-rules/topics/naming.md + +## Topic-Specific Rules (Conditional) +@.agents/core-rules/topics/architecture.md +@.agents/core-rules/topics/data-modeling.md +@.agents/core-rules/topics/workflows.md +@.agents/core-rules/topics/testing.md +@.agents/core-rules/topics/design-process.md + +## Common Commands +- Build: `pnpm build` +- Test: `pnpm test` +- Typecheck: `pnpm tsc --noEmit` +- Lint: `pnpm lint` +- Format: `pnpm prettier --write .` + +## Development Workflow +1. **Types**: Iterate with `tb__check-types` until valid. Use `tb__hover` to debug. +2. **Effect AST**: Run `./node_modules/.bin/effect-patterns-check ` on any modified Effect files to check for anti-patterns. Address any findings. +3. **Tests**: Once types pass and AST is clear, iterate with `tb__run-tests` until passing. diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..3d1938e --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,27 @@ +# Template Context + + This file defines the shared meta-language used by engineers and AI agents when discussing and developing within this template repository. + + ## Language + + **Bounded seam**: + A reviewable boundary in the code where a human cares about the contract (intent, authority, protected invariants) and can treat the inside implementation as a black box. + _Avoid_: Interface, abstraction, boundary (when used generically) + + **Three-alternatives rule**: + A design constraint requiring the generation of three plausible names or seams to expose hidden assumptions before settling on an implementation choice. + _Avoid_: Brainstorming, ideation + + **Intent-first name**: + A name given to a seam, workflow, or type that focuses on the caller's explicit goal rather than the shape of the current underlying implementation. + _Avoid_: Implementation-shaped name, structural name + + ## Example dialogue + + > **Reviewer:** "This module exposes a lot of internal helpers. Where is the actual **Bounded seam**?" + > **Developer:** "Right now it's just a folder. I'll consolidate the exports so the **Bounded seam** presents a clear, **Intent-first name** like `getNextTask`." + > **Reviewer:** "Great! Don't forget to apply the **Three-alternatives rule** before finalizing the name." + + ## Flagged ambiguities + + - "Interface" was previously used to describe a structural typing concept as well as a psychological code boundary. Resolved: Use **Bounded seam** when discussing psychological boundaries and contracts, and reserve "interface" for TypeScript structural typings. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..226c4f2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Elizabeth Wynn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..95f5703 --- /dev/null +++ b/README.md @@ -0,0 +1,138 @@ +# effect-template +A template repository for LLM-driven development of Effect applications following **workflow-centric functional domain-driven design**, with comprehensive documentation, rules, and checklists designed for AI consumption. + +## What Is This? + +This is **not** a traditional application template. Instead, it's a meta-template designed to accelerate LLM-assisted development by providing: + +1. **Centralized Rules & Patterns**: A single source of truth for architectural patterns, coding conventions, and best practices for Effect-based TypeScript applications +2. **LLM-Optimized Documentation**: Structured documentation that serves as context for AI assistants to scaffold new projects correctly +3. **Automated Code Review**: Checklists that enable LLMs to self-review their output before human review, catching common issues automatically +4. **Reusable Foundation**: Fork this repository to quickly start new projects with pre-configured architectural guidance + +## How to Use + +1. **Fork this repository** when starting a new Effect project +2. **Provide relevant documentation** from `docs/` as context to your LLM +3. **Ask the LLM to scaffold** your application following the documented patterns +4. **Run automated checks** with `pnpm lint`, `pnpm tsc --noEmit`, `pnpm test`, and `./node_modules/.bin/effect-patterns-check ` for modified Effect files +5. **Use checklists** (in `docs/framework/effect/checklists/`) to have the LLM review its own code +6. **Review the LLM's work** knowing common issues have already been addressed + +## The Design Process + +When starting a new feature, create design artifacts in the `design/` directory. +Use the human docs in `docs/` as the canonical explanation of the process, and let agents use the compressed overlays in `.agents/`. + +A good prompt is: +"Help me design [Feature Name] following the TDFDDD process described in `docs/explanation/tdfddd-protocol.md`, and format the result using `docs/reference/design-artifact-template.md`." + +## Design Overview + +This template promotes **workflow-centric functional domain-driven design** - an architectural approach where workflows orchestrate pure business logic through composable layers, combining the pipeline patterns from functional programming with domain-driven design principles. + +The architecture follows an "onion" pattern: **pure functions at the center, data validation and I/O at the edges**. Your core business logic remains pure and testable, while side effects are pushed to the outer layers where they can be managed through Effect's service system. + +### Books +This is a project template that is based on Scott Wlaschin's [*Domain Modeling Made Functional*](https://pragprog.com/titles/swdddf/domain-modeling-made-functional/). It also uses some of the ideas from Eric Normand's [*Grokking Simplicity*](https://www.manning.com/books/grokking-simplicity). + +### General Ideas +The project is focused on allowing you to do domain driven design with a pre-planned layout for following the pipeline style that Wlaschin describes in his book. Both Wlaschin and Normand talk about separating your project into several parts. As Normand puts it there are "Data, Calculations, and Actions". + +- Data: The actual data, like `5`, `value`, `` +- Calculations: Pure functions that have no side effects +- Actions: Functions that change the world, they take "action", like writing to the database + +Wlaschin describes similar things in his book. He talks about the idea of an "onion architecture" - you do data validation and I/O on the outermost layers, and get more functional as you go deeper into the program. At the center are pure pipelines containing your business logic. + +### TypeScript's Effect +This project uses `Effect` for its improvement of the standard library, functional features, `ZIO` type system, and relies heavily on explicit interfaces, adapters, and layers. Because adapters and layers provide such an easy way to separate your application into composable pieces the project is laid out in the onion fashion Normand and Wlaschin talk about. + +### Architecture Reasoning +#### Layers +This architecture is designed to take advantage of the layer system provided by effect and give some guidelines that allow room for abstraction as well as swapable layers for testing, and different backends. + +The decision to go with "policies" and "checks" has unknown effects at this time. The reason for breaking that out was since there seemed to be two categories of decisions inside of workflows. There were the things that really had no reason to return anything other than a boolean. There were those that needed complex decisions and return values and those seemed like they should be separate in the code. + +The platform layer and domain layer are designed such that it ensures easy abstractions to allow you to change the storage backend from neo4j to a filesystem with ease + +#### Future +There might be a need to split the workflows into two layers: `workflows` and `orchestration` because the workflows might get hard to read if they are both doing the checks and gathering all the information for the policies + +### Architecture Layers + +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#f7fafc','primaryTextColor':'#2d3748','primaryBorderColor':'#4a5568','lineColor':'#a0aec0','secondaryColor':'#f7fafc','tertiaryColor':'#f7fafc'}}}%% +flowchart LR + subgraph DOM[" DOMAIN (Pure) "] + M[Models
Schema + Pure Funcs] + I[Interfaces
Service Contracts] + end + + subgraph LOGIC[" LOGIC (Pure) "] + P[Policies
Decisions & Rules] + end + + subgraph ORCH[" ORCHESTRATION (Impure) "] + W[Workflows
Transaction Scripts] + R[Registries
Strategy Selection] + end + + subgraph SYS[" INFRASTRUCTURE (Impure) "] + A[Adapters
Platform Implementations] + L[Lib
Generic Tools] + end + + M -.->|used by| P + M -.->|used by| W + P -->|used by| W + W -->|calls| I + R -->|wires| I + A -.->|implements| I + A -->|uses| L + + classDef domain fill:#2d3748,stroke:#a0aec0,color:#fff,stroke-width:2px + classDef logic fill:#2c5282,stroke:#90cdf4,color:#fff,stroke-width:2px + classDef orch fill:#744210,stroke:#ecc94b,color:#fff,stroke-width:2px + classDef sys fill:#276749,stroke:#48bb78,color:#fff,stroke-width:2px + + class M,I domain + class P logic + class W,R orch + class A,L sys +``` + +**Layer Descriptions:** + +- **Domain (Pure)** + - **Models**: Data schemas and co-located pure logic (e.g., `Cart.isEmpty`). + - **Interfaces**: Contracts for external capabilities (e.g., `PaymentRepo`). + +- **Logic (Pure)** + - **Policies**: Complex business rules and decision making (e.g., `determinePaymentStrategy`). + +- **Orchestration (Impure)** + - **Workflows**: The "script" of the application. Coordinates domain, policies, and interfaces. + - **Registries**: Handles dynamic runtime selection of service implementations (Strategy Pattern). + +- **Infrastructure (Impure)** + - **Adapters**: Concrete implementations of domain interfaces (e.g., `StripeAdapter`). + - **Lib**: Generic internal libraries and helpers (e.g., `Neo4jClient`). + +For a detailed source code directory layout, see [docs/reference/directory-layout.md](docs/reference/directory-layout.md). + +## Documentation Structure + +The repository now uses a human-first documentation layout in `docs/` and a thin agent overlay in `.agents/`. + +- `docs/README.md` is the front door for humans. +- `docs/tutorials/` contains worked examples and practice material. +- `docs/how-to/` contains task-oriented guides such as reviewing LLM design output. +- `docs/explanation/` contains rationale, tradeoffs, and architecture reasoning. +- `docs/reference/` contains templates, checklists, and stable lookup material. +- `.agents/core-rules/` contains compressed operational rules for agents and points back to the canonical human docs. + +For the human documentation entrypoint, start at [docs/README.md](docs/README.md). +For rule examples, see [docs/reference/rule-examples.md](docs/reference/rule-examples.md). +For architecture rationale, see [docs/explanation/architecture/index.md](docs/explanation/architecture/index.md). + diff --git a/design/shipping-company-domain.md b/design/shipping-company-domain.md new file mode 100644 index 0000000..49b39b2 --- /dev/null +++ b/design/shipping-company-domain.md @@ -0,0 +1,25 @@ +# Domain: Full Shipping Company + +This document serves as the high-level domain breakdown for the "Golden Example" implementation of the Effect TDFDDD Template. We will use the TDFDDD agent suite to iteratively build out these workflows. + +## 1. The Booking & Quoting Context (The "Order") +* **Goal:** A customer wants to ship a package from Origin A to Destination B. +* **Key Workflows:** + * **`requestQuote`**: Takes dimensions, weight, origin, destination, and service level (e.g., Express vs. Ground) and calculates a price. This is a great example of a pure mathematical Policy combined with an Effect workflow. + * **`bookShipment`**: Validates the quote, charges the customer, and generates a shipping label/tracking number. This shows orchestrating multiple side-effects (payment + DB + third-party label generation). + +## 2. The Logistics & Routing Context (The "Network") +* **Goal:** Figuring out how a package gets from Origin A to Destination B through our network of hubs. +* **Key Workflows:** + * **`routePackage`**: When a package is dropped off, determine the sequence of hubs it must visit. + * **`loadTruck`**: The strict rules around weight, volume, and hazardous materials compatibility (e.g., "You can't load flammable liquids on the same truck as explosives"). This is perfect for complex Discriminated Union states and pure Policies. + +## 3. The Tracking & Fulfillment Context (The "State") +* **Goal:** Tracking the physical location of the package and notifying the customer. +* **Key Workflows:** + * **`scanPackage`**: A worker scans a package at a hub. This updates the package's state (`Pending` -> `InTransit` -> `OutForDelivery` -> `Delivered`). This demonstrates strict state machine enforcement using TDFDDD. + +--- + +## Next Steps +When resuming work, pick **one specific workflow** (e.g., `loadTruck` or `bookShipment`) and invoke the **Discovery Agent (Detective Mode)** to begin uncovering edge cases and rules. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..049c70d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,82 @@ +# Effect TDFDDD Template Documentation + +Welcome to the documentation for the Effect Type-Driven Functional Domain-Driven Design template. +This repo uses a modified [Diátaxis](https://diataxis.fr/) structure so the docs serve both of these jobs: + +- teach humans how to understand and practice the method +- help humans review LLM-generated design artifacts with confidence + +## Start here by goal + +### I want to learn the method by hand + +Read these in order: + +1. [TDFDDD Manifesto](explanation/tdfddd-manifesto.md) +2. [By-hand practice guide](by-hand/README.md) +3. [Worked Example: Truck Loading](tutorials/worked-example-truck-loading.md) +4. [Practicing TDFDDD by Hand](tutorials/practicing-tdfddd-by-hand.md) +5. [Design Artifact Template](reference/design-artifact-template.md) + +### I want to review LLM-generated design output + +Read these in order: + +1. [TDFDDD Manifesto](explanation/tdfddd-manifesto.md) +2. [Worked Example: Truck Loading](tutorials/worked-example-truck-loading.md) +3. [How to Review an LLM-Generated Design](how-to/review-an-llm-generated-design.md) +4. [Review Checklist for TDFDDD Artifacts](reference/review-checklist.md) +5. [TDFDDD Protocol](explanation/tdfddd-protocol.md) for the design and implementation security verification gates + +## Tutorials + +_Learning-oriented guides for building intuition through examples and practice._ + +- [Effect Starting Guide](tutorials/effect-starting-guide.md) +- [Worked Example: Truck Loading](tutorials/worked-example-truck-loading.md) +- [Practicing TDFDDD by Hand](tutorials/practicing-tdfddd-by-hand.md) + +## How-to guides + +_Task-oriented guides for doing specific work._ + +- [Implementation Guide](how-to/implementation-guide.md) +- [How to Review an LLM-Generated Design](how-to/review-an-llm-generated-design.md) + +## Explanation + +_Understanding-oriented material for the ideas, tradeoffs, and philosophy behind the method._ + +- [TDFDDD Manifesto](explanation/tdfddd-manifesto.md) +- [TDFDDD Protocol](explanation/tdfddd-protocol.md) +- [Naming for Domain Modeling](explanation/naming-for-domain-modeling.md) +- [Why Events, Not Booleans](explanation/why-events-not-booleans.md) +- [Architecture Reasoning](explanation/architecture/index.md) +- [Bounded Context Architecture Statement](explanation/architecture/bounded-contexts.md) +- [Architecture Rationale Summary](explanation/architecture/rationale-summary.md) +- [Security Verification Rationale](explanation/architecture/security-verification-rationale.md) + +## Reference + +_Information-oriented material for lookups, templates, and stable rules._ + +- [Directory Layout](reference/directory-layout.md) +- [Project Structure Overview](reference/overview.md) +- [Conventions](reference/conventions.md) +- [Rule Examples](reference/rule-examples.md) +- [Naming Lexicon](reference/naming-lexicon.md) +- [Review Checklist for TDFDDD Artifacts](reference/review-checklist.md) +- [Design Artifact Template](reference/design-artifact-template.md) +- [Design Artifact Structure](reference/design-artifact-structure.md) + +## By-hand practice + +_Human-first material for learning the original 5-phase design flow manually._ + +- [By-hand practice guide](by-hand/README.md) + +--- + +## Agent Guidance + +If you are an AI agent working in this repository, consult `.agents/` or `AGENTS.md` for operational rules. Contextual rules are loaded based on the files you edit. diff --git a/docs/by-hand/README.md b/docs/by-hand/README.md new file mode 100644 index 0000000..200cfe6 --- /dev/null +++ b/docs/by-hand/README.md @@ -0,0 +1,31 @@ +# Practicing TDFDDD by Hand + +This directory preserves the human-first **5-phase** practice flow. +Use it when you want to learn the design method manually, on paper or in scratch markdown, without the full feature-to-slice artifact workflow used by the agent pipeline. + +## What stays here + +These docs are intentionally optimized for **human practice**: + +1. Event storming / discovery +2. Core sketch +3. Domain modeling +4. Contract +5. Implementation thinking + +They are not the source of truth for the agent artifact layout. +The operational artifact structure for real feature work now lives under: + +- `design/feature//...` +- `design/workflows///...` + +## Read these in order + +- `../tutorials/worked-example-truck-loading.md` +- `../tutorials/practicing-tdfddd-by-hand.md` +- `../reference/design-artifact-template.md` + +## How to use both doc sets together + +- Use `docs/by-hand/` to build judgment and practice the 5 design phases manually. +- Use the main docs for the current feature/slice artifact structure, bounded-context decomposition, and review of generated artifacts. diff --git a/docs/explanation/architecture/bounded-contexts.md b/docs/explanation/architecture/bounded-contexts.md new file mode 100644 index 0000000..2b30135 --- /dev/null +++ b/docs/explanation/architecture/bounded-contexts.md @@ -0,0 +1,199 @@ +# Bounded Context Architecture Statement + +This repository treats **bounded contexts as first-class language boundaries**. +They should be identified early enough to shape naming, ownership, review, and code placement, but they are **not permanent**. +A bounded context is a provisional domain boundary that may be split, merged, or reshaped later when real change pressure shows the current boundary is wrong. + +The goal is to combine: + +- the clarity of Domain-Driven Design language boundaries +- the speed of feature-by-feature implementation +- the reviewability benefits of smaller bounded seams +- the safety of continuous refactoring over waterfall lock-in + +## Core position + +### 1. Bounded contexts are planned early, not frozen early + +This repo does **not** treat bounded contexts as something that should be discovered only after a large amount of code exists. +It is usually worth defining an initial bounded-context map early because it gives: + +- a stable language boundary +- a first-pass ownership model +- an initial code organization strategy +- a clearer review surface for human and AI contributors + +However, this initial map is only a starting point. +It should be treated as a hypothesis that is refined as the system evolves. + +### 2. Cross-context business processes are modeled as top-level workflows + +A business process such as checkout may involve multiple bounded contexts such as inventory, payment, and receipts. +That does **not** mean those contexts should call each other directly. + +Instead: + +- cross-context coordination belongs in top-level workflows +- workflows may orchestrate multiple contexts +- each context keeps its own internal policies, models, and implementation details local + +This makes the workflow graph the place where cross-context business processes are visible and reviewable. + +### 3. Contexts do not talk to each other directly + +A bounded context should not directly import another bounded context's internal files or make decisions inside another context's language. +If one context needs something from another, that interaction should happen through a top-level workflow that calls each context through its public API. + +The intended rule is: + +> Cross-context access goes through public intent APIs via top-level workflows. + +This keeps context ownership clear and prevents hidden coupling between contexts. + +### 4. Internal seams are smaller than bounded contexts + +A bounded context is not the smallest review unit. +Inside a bounded context there may still be smaller seams such as: + +- public intent APIs +- workflow steps +- policies +- translators +- adapters +- module entrypoints + +These seams exist to keep implementation reviewable and refactorable. +A bounded context gives the outer language boundary. +Internal seams give local structure inside that boundary. + +## Repository structure implications + +The primary organization should be **by context**, not by global layer. +Top-level folders should stay small and reserved for true cross-context concerns. + +A typical direction is: + +```text +src/ +├── shared/ # tiny ubiquitous primitives only +├── workflows/ # top-level cross-context orchestration only +├── tasks/ # optional cross-context workflow steps, only if needed +└── contexts/ # first-class bounded contexts +``` + +Within each context, the context may contain some combination of: + +- domain models +- policies +- workflows or tasks +- layer definitions +- adapters +- registries +- translators +- lib + +But these should be treated as an **allowed menu**, not a mandatory checklist. +Do not create internal folders just to satisfy symmetry. +Only create the internal seams that the current context actually needs. + +## Shared types and translation + +The repository should avoid sharing **rich domain types** across bounded contexts. +A type that looks generic often becomes a hidden coupling point if different contexts need different meaning, rules, or fields. + +Examples: + +- a shipping address may not be the same concept as a profile address +- a payment identifier may not mean the same thing as an accounting identifier +- an inventory quantity may not carry the same rules as a purchasing quantity + +So the rule is: + +- keep `shared/` small +- use it only for truly ubiquitous primitives and low-meaning technical building blocks +- keep rich domain models local to their context +- translate at the boundary when data moves between contexts + +This follows the DDD idea that context edges are where translation belongs. +The purpose of translation is not ceremony for its own sake. +It is to stop one context's language from silently taking over another. + +## Policies at the top level + +This repository should **not** start with a top-level `policies/` folder by default. +Most policy logic belongs inside a bounded context because most business rules are written in the language of one context. + +For cross-context workflows: + +- start with orchestration logic in the workflow +- keep cross-context policy inline until it becomes a real stable seam +- only extract a top-level policy when the rule is truly about the relationship between contexts or the process as a whole + +This avoids blessing a global policy layer before there is evidence that it is needed. + +## Folders before packages + +Bounded contexts should usually start as **folders/modules in one package**, not as separate packages. + +Why folders first: + +- lower tooling overhead +- easier to reshape while boundaries are still provisional +- simpler imports and refactors during early design pressure +- enough structure to enforce public APIs and context ownership + +Why not packages immediately: + +- package boundaries are harder to change +- they add build and tooling complexity early +- they can create premature rigidity before the domain map has earned it + +Packages may make sense later if a context becomes operationally independent enough to justify stronger enforcement. +But the default should be folders first, packages later if proven necessary. + +## Refactoring stance + +Refactoring is part of the architecture, not a repair step after the fact. +This repo should assume that: + +- some initial bounded contexts will be wrong +- some contexts will need to split +- some initially separate contexts will later merge +- internal seams will evolve under real use + +That is not a failure of DDD. +It is how a domain model becomes more accurate over time. + +The important thing is to preserve a stable review model while allowing structure to improve. +The combination used here is: + +- define provisional bounded contexts early +- keep cross-context coordination at top-level workflows +- keep rich models inside contexts +- prefer public APIs at boundaries +- refine seams and boundaries through refactoring when change pressure justifies it + +## Lightweight principles + +Use these as the short version: + +1. Bounded contexts are first-class language boundaries. +2. Bounded contexts are planned early but treated as provisional. +3. Only top-level workflows coordinate across contexts. +4. Contexts do not call each other directly. +5. Cross-context access goes through public intent APIs via top-level workflows. +6. `shared/` is tiny and contains only truly ubiquitous primitives. +7. Rich domain models stay local to their context. +8. Translation happens at context edges when data crosses boundaries. +9. Internal seams are smaller than bounded contexts and should emerge under real pressure. +10. Split or merge bounded contexts based on change pressure and reviewability, not theory alone. + +## What this architecture is optimizing for + +This shape is intended to optimize for: + +- strong domain language boundaries +- low hidden coupling +- easier human review of AI-generated code +- safer localized refactoring +- incremental delivery without pretending the first domain map is final diff --git a/docs/explanation/architecture/composition.md b/docs/explanation/architecture/composition.md new file mode 100644 index 0000000..6a62316 --- /dev/null +++ b/docs/explanation/architecture/composition.md @@ -0,0 +1,26 @@ +# Composition over Events + +## The Decision +We prioritize **Direct Function Composition** (via `pipe`) for core business logic. We use **Events** only for side effects that do not impact the transaction's success. + +## The Reasoning + +### 1. The "Microservice Envy" Trap +Developers often break monoliths into event-driven fragments to "decouple" them. Inside a single application, this leads to: +- **Loss of Observability**: You can't trace a request top-to-bottom. +- **Error Handling Complexity**: If the event consumer fails, the producer doesn't know. +- **Refactoring Pain**: "Find Usages" breaks. + +### 2. Local Reasoning +With Effect, `step1.pipe(step2)` provides: +- **Transactional Integrity**: If step2 fails, the whole block fails (or recovers). +- **Type Safety**: The output of step1 is typed as the input of step2. +- **Readability**: The linear flow is visible. + +### 3. When to use Events +Use events for **Fire-and-Forget** side effects: +- Sending analytics +- Sending welcome emails +- Notifying external webhooks + +If the business outcome depends on it (e.g., "Charge Card"), it must be a function call, not an event. diff --git a/docs/explanation/architecture/history.md b/docs/explanation/architecture/history.md new file mode 100644 index 0000000..07df5fe --- /dev/null +++ b/docs/explanation/architecture/history.md @@ -0,0 +1,65 @@ +# History & Architectural Decisions + +This document tracks the evolution of our architectural decisions, including the paths we considered and ultimately rejected. + +## 1. The "3-Layer" vs "Granular Seams" Debate + +**The Consideration:** +Should we stick to the industry-standard 3-Layer architecture (Controller -> Service -> Repository) for simplicity? + +**The Counter-Argument (Why we rejected 3-Layer):** +- **The "Fat Service" Problem:** In 3-layer systems, the "Service" layer becomes a magnet for *everything*—business logic, validation, I/O, and orchestration. +- **Testing Difficulty:** To test a business rule in a Service, you often have to mock the Repository, because they are tightly coupled. +- **Effect Granularity:** In Effect, seams are cheap. There is no performance penalty for splitting decisions from mechanics. + +**The First Decision:** +We initially described the repo as an **Onion Architecture** with granular layers: +1. Domain Models (Pure Logic) +2. Policies (Pure Decisions) +3. Workflows (Impure Orchestration) +4. Infrastructure (Impure I/O) + +**The Later Reframe:** +We kept the purity-based seam logic, but stopped treating the global layer tree as the main repository map. +The repo now prefers **bounded contexts first**, with those smaller seams living mostly *inside each context*. + +## 2. The "Event-Driven Everything" Debate + +**The Consideration:** +Should we use Effect's Event system (Pub/Sub) to decouple workflows? (e.g., `Workflow A` emits `Event X`, `Workflow B` listens). + +**The Counter-Argument (Why we rejected internal events):** +- **Loss of Locality:** You cannot click "Go to Definition" to see what happens next. The control flow becomes invisible. +- **Error Propagation:** If the listener fails, the emitter doesn't know. You lose the transactional integrity of `Effect.gen`/`pipe`. +- **Complexity:** It requires an internal event bus infrastructure that mimics microservices but inside a monolith ("Microservice Envy"). + +**The Decision:** +Use **Direct Composition** (Functions calling Functions) for all core business logic. +Use **Events** ONLY for "fire-and-forget" side effects (e.g., sending analytics, welcome emails) where failure does not invalidate the transaction. + +## 3. The naming of adapters + +**The Consideration:** +Should we name adapters by their implementation (e.g., `StripeAdapter`) or their role (e.g., `SmallPaymentService`)? + +**The Counter-Argument:** +- **Implementation Names (`StripeAdapter`)**: Couples the domain to the vendor. If we switch to PayPal, we have to rename everything. +- **Role Names (`SmallPaymentService`)**: Can drift semantically. What if "Small" becomes "VIP"? The name becomes a lie. + +**The Decision:** +Use **Strategy Pattern**: +- **Interface**: `PaymentInterface` (Generic). +- **Policy**: Returns a Strategy Value (e.g., `RETAIL_CHANNEL`). +- **Registry**: Maps `RETAIL_CHANNEL` -> `StripeAdapter`. +- **Workflow**: Asks Registry for the implementation. + +## 4. The "Checks" Layer + +**The Consideration:** +Should we have a top-level `checks/` folder for simple predicates? + +**The Rejection:** +It encouraged "Anemic Domain Models" where logic was stripped away from the data types. + +**The Decision:** +Move "Checks" into the **Domain Model** modules (`src/domain/models/`). Logic about an entity belongs *with* the entity schema. diff --git a/docs/explanation/architecture/index.md b/docs/explanation/architecture/index.md new file mode 100644 index 0000000..dcebb57 --- /dev/null +++ b/docs/explanation/architecture/index.md @@ -0,0 +1,16 @@ +# Architecture Reasoning + +This folder contains deep-dive explanations for the architectural decisions made in this template. +The current governing direction is **context-first architecture**: bounded contexts are the main language boundary, while purity and orchestration rules still shape the seams inside and between contexts. + +## Contents + +- [Bounded Context Architecture Statement](bounded-contexts.md): The current architecture baseline for code placement, seams, and cross-context coordination. +- [Architecture Rationale Summary](rationale-summary.md): The short version for review refreshers and interviews. +- [Layers & Granularity](layers-granularity.md): How purity-based seams still matter once organization becomes context-first. +- [Pure Logic vs Orchestration](pure-logic.md): The separation of decisions from orchestration inside contexts and top-level workflows. +- [Service Strategy](service-strategy.md): How public APIs, adapters, and optional registries fit inside bounded contexts. +- [Composition over Events](composition.md): Why we prefer direct function composition over internal event buses for core logic. +- [Type-Driven Design](type-driven-design.md): Encoding business rules into the type system ("Make Illegal States Unrepresentable"). +- [History & Decisions](history.md): A log of architectural alternatives considered, adopted, or later reframed. +- [Security Verification Rationale](security-verification-rationale.md): Why security review is modeled as verification gates, why there are separate design and implementation skills, and why sink handling and capability scope are emphasized. diff --git a/docs/explanation/architecture/input-security.md b/docs/explanation/architecture/input-security.md new file mode 100644 index 0000000..b1e6a03 --- /dev/null +++ b/docs/explanation/architecture/input-security.md @@ -0,0 +1,30 @@ +### Core String Security & AI Architecture Principles + +#### 1. Type-Level Security (Data Provenance & Taint Tracking) +* **Never pass raw strings:** A raw string has no memory. Do not pass raw `string` types to sensitive sinks (database queries, AI contexts, DOM rendering). +* **Enforce wrapper objects:** Use strongly typed objects (e.g., `TrustedInput`, `TaintedInput`) to carry the string. +* **Track provenance:** The type must explicitly state where the string came from (its source) and whether it is trusted or tainted. *Enforce this at the compiler/type-checker level if your language allows it (e.g., TypeScript, Rust).* + +#### 2. Defense in Depth (Ingestion vs. Use) +* **Filter strictly at Ingestion:** Reject bad data early. Enforce maximum lengths, validate file extensions/magic numbers, check data types, and drop edge cases (like null bytes). This keeps the system state clean. +* **Sanitize/Encode AT the Point of Use:** Remember that "dangerous" is context-dependent. Apply specific encoding right before the string is consumed (e.g., HTML escaping for web renders, Parameterized Binding for SQL, strict XML encapsulation for LLMs). + +#### 3. Structural Boundaries (No Concatenation) +* **Parameterized Queries & Messages:** Never concatenate strings to build a query or an AI prompt. For databases, rely on the ORM or parameterized query objects. For AI, use API message arrays (e.g., `[{role: "system", content: "..."}, {role: "user", content: "..."}]`) to structuralize the conversation. + +#### 4. The Data Abstraction (Control vs. Data Planes) +* **Isolate Sub-Agents:** When handling untrusted data, pass the data to an isolated "Worker" AI that has zero access to tools or APIs. +* **Shield the Orchestrator:** Strive to return the Worker AI's output directly to the user. Avoid sending the tainted output back into the context window of your main "Boss/Orchestrator" AI. + +#### 5. Mitigation for Orchestrator Taint (If you *must* feed it back) +* **Principle of Least Privilege:** If the orchestrator *must* read a tainted summary, restrict its available tools to the absolute minimum necessary for that specific turn of conversation. +* **Human-in-the-Loop (HITL):** Any destructive or sensitive command invoked by an AI (e.g., dropping databases, sending emails, transferring funds) must pause execution and require explicit human authorization. +* *(New)* **Explicit Delimiters:** When injecting untrusted text into a prompt, lock it inside clear, hard-to-guess delineators (e.g., ` [DATA] `) so the model knows where the data starts and stops. + + +#### 6. AI Guardrails (Pre-computation Filtering) + * Before sending user input to a slow, expensive AI agent, pass it through a tiny, fast classification model (like Llama Guard) designed solely to detect prompt injection or jailbreak attempts. If it flags as malicious, block the request before it even reaches your main orchestration logic. + +#### 7. Audit Logging + * Because AI agents are non-deterministic, you must log all tool invocations and the exact context (the prompts and tainted strings) that led to that tool being called. If a prompt injection attack *does* succeed, you need this log to figure out how they tricked the agent. + diff --git a/docs/explanation/architecture/layers-granularity.md b/docs/explanation/architecture/layers-granularity.md new file mode 100644 index 0000000..6014e04 --- /dev/null +++ b/docs/explanation/architecture/layers-granularity.md @@ -0,0 +1,59 @@ +# Layers & Granularity After the Bounded-Context Shift + +## The Decision +We no longer treat a global N-layer onion as the primary repository map. +Instead, we organize the repo by **bounded context first**, while still using **purity-based seams** inside each context and across top-level workflows. + +## The Reasoning + +### 1. The trap of the global service layer still exists +In traditional 3-layer architectures, the "Service" layer becomes a dumping ground. It often handles: +- Validation +- Business Rules +- Orchestration +- I/O +- Data Transformation + +That problem still matters. +The fix is still to separate concerns by purity and intent. +What changed is **where that separation is expressed**: mostly inside each context, not as one global folder tree. + +### 2. Context-first, seams-second +In this template, bounded contexts give the outer language boundary. +Inside those boundaries, we still use smaller seams based on intent. + +| Seam inside a context | Purpose | +|-----------------------|---------| +| **Models** | Rich local state and pure operations | +| **Policies** | Pure local decisions | +| **Context workflows/tasks** | Local orchestration when a context needs it | +| **Public API** | The intent surface other code is allowed to call | +| **Interfaces / capabilities** | The internal contract between domain-facing code and effectful or replaceable collaborators | +| **Adapters / registries / translators** | Boundary and implementation details | + +At the repository top level, `src/workflows/` remains the place for **cross-context orchestration**. + +### 3. Why this is better than one global onion tree +A global folder tree keeps technical roles visible, but it weakens language ownership. +You end up grouping code by purity category even when the more important question is, "Which domain language owns this concept?" + +Context-first structure improves: +- **Ownership clarity**: related concepts stay together +- **Boundary reviewability**: cross-context interactions are easier to spot +- **Refactoring safety**: a context can split or merge without rewriting the whole repo map + +### 4. What still stays true +The bounded-context shift did **not** repeal the old purity guidance. +These points still stand: +- pure decisions should stay out of I/O-heavy orchestration code +- workflows should coordinate rather than bury business rules +- interfaces/capabilities remain crucial internal seams +- adapters should implement capabilities rather than own domain policy +- registries are optional seams, not default architecture furniture + +## Practical takeaway +Think about architecture in two levels: +1. **Outer boundary**: which bounded context owns the language? +2. **Inner seams**: within that context, what should be a model, policy, workflow, translator, adapter, or public API? + +That gives you the benefits of granular design without forcing the entire repo into one technical-layer-first shape. diff --git a/docs/explanation/architecture/pure-logic.md b/docs/explanation/architecture/pure-logic.md new file mode 100644 index 0000000..3fed0df --- /dev/null +++ b/docs/explanation/architecture/pure-logic.md @@ -0,0 +1,44 @@ +# Pure Logic vs Orchestration + +## The Decision +We still separate **decisions** from **mechanics**, but now we describe that separation in a context-first way: + +- pure logic lives inside the bounded context that owns the language +- top-level workflows coordinate across contexts +- cross-context coordination should not erase local policy ownership + +## The Reasoning + +### 1. The "process vs rule" conflict still matters +In many codebases, the rule "User must be over 18" is mixed with the code that "fetches the user from DB." +That means to test the rule, you have to mock the DB. + +We still separate them: +- **Policy or model logic**: `canAccess(user)` -> pure function +- **Workflow**: `fetchUser()` -> `canAccess(user)` -> `return` + +### 2. Where pure logic belongs now +The old docs sometimes implied that all decisions should be expressed through one top-level `src/policies/` folder. +That is no longer the default. + +Pure logic now usually lives in two places **inside a context**: +1. **Models**: pure logic about one entity or state shape +2. **Policies**: pure logic about decisions or relationships within that context + +For cross-context workflows: +- start with orchestration in the top-level workflow +- keep cross-context rule logic inline until it earns a stable seam +- only extract a top-level policy when the rule is genuinely about the relationship between contexts or the whole process + +### 3. Determinism is still the payoff +By keeping policy logic pure, we ensure that for any given input, the business decision is always the same. +That makes testing easier, debugging easier, and review easier. + +### 4. The workflow's job +A workflow gathers data, invokes pure logic, coordinates effects, and makes the cross-context process visible. +It should not quietly become the place where all business meaning accumulates. + +That is why the repo now prefers: +- local pure decisions inside contexts +- public APIs at context boundaries +- top-level workflows for visible cross-context coordination diff --git a/docs/explanation/architecture/rationale-summary.md b/docs/explanation/architecture/rationale-summary.md new file mode 100644 index 0000000..4913fd9 --- /dev/null +++ b/docs/explanation/architecture/rationale-summary.md @@ -0,0 +1,90 @@ +# Architecture Rationale Summary + +This page is the short version of why this template uses its current architecture. +Use it as a refresher before design reviews, architecture discussions, or interviews. + +## The central goal + +The architecture is designed to make business logic easier to design, easier to review, and easier to change without dragging technical and collaborator concerns through every part of the codebase. + +## The current shape + +The repository is now optimized around **bounded contexts first**. +That means the main organizing boundary is domain language ownership, not one global tree of layers. +Purity still matters, but it mostly shows up **inside a context** and at **top-level workflow seams**. + +## Why this structure exists + +### Bounded contexts give language boundaries + +A bounded context gives a local vocabulary, ownership seam, and review surface. +That reduces hidden coupling and makes it easier to see where translation is required. + +### Workflows should coordinate across contexts, not contexts calling each other directly + +Cross-context business processes belong in top-level workflows. +That keeps coordination visible and prevents one context from reaching into another context's internal language. + +### Pure logic should stay easy to reason about + +Inside a context, domain models and decisions should stay free of I/O where practical. +That keeps the important rules easier to test, easier to review, and easier to trust. + +### Types should carry business meaning + +The design aims to make illegal states unrepresentable where practical. +That is why the architecture prefers rich local domain types and explicit state variants instead of generic shapes plus status flags. + +### Shared code should stay tiny + +A type that looks reusable can become hidden cross-context coupling. +The architecture therefore keeps `shared/` small and prefers translation at context boundaries. + +## Why not one global layer tree + +A global `src/domain/`, `src/policies/`, `src/adapters/`, and similar layout can keep purity visible, but it tends to weaken bounded language ownership. +Over time it encourages unrelated concepts to sit beside each other just because they share a technical role. + +This template now prefers: + +- contexts as the first-class organizational boundary +- local models, policies, and capability interfaces inside each context +- top-level workflows for cross-context coordination +- public APIs and translators at boundaries + +## Why not events for everything + +The architecture prefers direct composition for core application flow. +Internal events are useful, but if they become the default glue, the system can lose local traceability and make refactoring harder. + +The template therefore favors explicit function calls for the core path and reserves event-style thinking for domain outcomes and selected integration boundaries. + +## Main tradeoffs + +This architecture gives you: + +- stronger language boundaries +- lower hidden coupling +- clearer review surfaces for cross-context flows +- more explicit local domain models +- safer localized refactoring + +It also costs you: + +- more up-front naming and boundary decisions +- more translation at context edges +- a need for discipline so `shared/` stays small and public APIs stay real + +## What to say in an interview + +A short summary is: + +> We organize the system around bounded contexts so domain language and ownership stay explicit. Cross-context business processes are coordinated in top-level workflows, while pure decisions and state logic stay local to each context. We keep shared code small, translate at boundaries, and use rich types so business meaning stays reviewable and change remains localized. + +## Where to go next + +- Read `bounded-contexts.md` for the governing architecture statement. +- Read `index.md` for the full architecture map. +- Read `history.md` for alternatives considered and reframed. +- Read `type-driven-design.md` for the type-modeling rationale. +- Read `pure-logic.md` for the decision versus workflow split. diff --git a/docs/explanation/architecture/security-verification-rationale.md b/docs/explanation/architecture/security-verification-rationale.md new file mode 100644 index 0000000..4ceee88 --- /dev/null +++ b/docs/explanation/architecture/security-verification-rationale.md @@ -0,0 +1,457 @@ +# Security Verification Rationale + +This document explains why the repository now treats security review as an explicit verification activity and why the new security-review skills are shaped the way they are. + +It is meant to be a durable reference for future contributors who ask questions such as: + +- Why is security review a verification gate instead of a numbered TDFDDD phase? +- Why are there two skills instead of one giant security reviewer? +- Why is the review report structured the way it is? +- Why do we emphasize trust boundaries, sinks, capability scope, and blast radius so heavily? +- Why do we reject a single generic sanitizer in favor of context-specific handling? + +## 1. Why security review was added + +The original TDFDDD process already pushed the project toward strong design boundaries: + +- parse untrusted data at the edge +- keep domain logic pure +- make illegal states harder to express +- isolate infrastructure behind explicit seams + +Those choices improve security indirectly because they make review easier and reduce accidental complexity. +However, they do not by themselves guarantee secure software. + +A codebase can still have: + +- over-broad runtime authority +- unsafe sink usage +- missing recovery and detection planning +- dangerous supply-chain assumptions +- poor secret handling +- security-sensitive omissions in tests and design docs + +That is why the repository now treats security review as a first-class part of the development workflow. +The goal is not to replace the design process with a security process. +The goal is to add explicit checkpoints where reviewers can ask security-specific questions before risk silently ships. + +## 2. Why security verification is a gate, not a core phase + +The main TDFDDD phases exist to discover and freeze domain meaning: + +1. discovery +2. policy sketch +3. domain modeling +4. contract finalization +5. implementation + +Those phases answer questions like: + +- What is the business action? +- What information is required to decide it? +- What states and events exist? +- What is the contract between pure logic and orchestration? + +Security review plays a different role. +It does not create domain meaning. +It evaluates whether the emerging design or implementation introduces avoidable risk. + +That distinction matters. +If security review were framed as just another design phase, it would encourage the wrong mental model: +security would look like one more artifact to generate rather than a quality gate that can block, refine, or redirect work. + +Calling it a **verification gate** keeps the relationship clear: + +- the design phases build the artifact +- the verification gate reviews the artifact +- findings can send the work back for clarification or redesign + +This is similar to testing, architecture review, or type-checking. +They are essential to delivery, but they are not themselves the business-design phases. + +## 3. Why there is a single skill with two phases + +We chose to consolidate security review into one skill: + +- `tdfddd-security-verification` (covers both Design and Implementation) + +This was intentional to reduce instruction duplication, but internally it still branches its logic based on the phase. + +### 3.1 Design review and implementation review answer different questions + +Design review asks: + +- Are trust boundaries visible? +- Are capability boundaries explicit? +- Are likely dangerous sinks named? +- Are secrets, blast radius, recovery, and testing expectations discussed? +- Is the proposed architecture setting up an implementation that can be secure? + +Implementation review asks: + +- Did the code actually parse untrusted input before treating it as trusted? +- Did the implementation use safe sink patterns? +- Did the workflow receive too much authority at runtime? +- Did the tests cover the risky seam? +- Did config or setup choices introduce additional exposure? + +A single set of generic rules would blur these concerns. +That would create two failure modes: + +1. **False precision during design review**: the reviewer would start demanding code-level proof from artifacts that do not yet contain code. +2. **Shallow code review during implementation**: the reviewer would spend too much time repeating design-level concerns instead of checking the concrete implementation. + +The skill uses phase-specific heuristics so it can operate at the right level of evidence. + +### 3.2 Early review is cheaper than late review + +A security issue found in the design is usually cheaper to fix than the same issue found after implementation. + +Examples: + +- realizing that a workflow needs a read-only capability instead of a general admin capability +- noticing that dangerous sinks exist but the ownership of sanitization/escaping is unspecified +- discovering that secrets and operator assumptions are absent from the design +- identifying that no detection or recovery story exists for the workflow + +These are architecture-shaping issues. +Catching them before assembly avoids rework and reduces the chance that an insecure pattern gets copied into multiple concrete implementations. + +### 3.3 Review-only behavior is safer + +Both skills are deliberately review-only. +They do not auto-apply fixes. + +This decision reflects a trust and workflow concern: +security review often requires human judgment about context, tradeoffs, operational constraints, and false positives. +A reviewer may correctly identify a risk but still misunderstand the broader product context. + +By keeping the skills review-only: + +- the human stays in control of interpretation +- the review can raise questions before code changes happen +- the system avoids auto-patching based on assumptions +- remediation can be handled by a separate implementation pass once the human confirms direction + +This matches the repository philosophy that design and review should reduce improvisation rather than automate it blindly. + +## 4. Why the report is structured and evidence-based + +The skills use a fixed report shape with sections such as: + +- Scope Reviewed +- Artifacts Read +- Security Assumptions +- Overall Risk Summary +- one section per security concern category +- Cross-Cutting Questions +- Suggested Next Actions + +The report also distinguishes between: + +- **Finding** +- **Possible Concern** +- **Insufficient Evidence** + +### 4.1 Why use a fixed format + +A fixed structure prevents the review from skipping important categories just because the current feature is small or because one concern happens to dominate attention. + +Without a fixed structure, reviews often become uneven: + +- one reviewer focuses heavily on input validation but misses privilege boundaries +- another focuses on secrets but ignores unsafe sinks +- another talks only about abstract best practices without grounding them in the artifacts + +The fixed format forces coverage consistency. +It also makes reports easier to compare across features and easier for a human to skim. + +### 4.2 Why include evidence levels + +Security review always includes uncertainty. +A reviewer may see a likely issue without enough evidence to state it as fact. +If the skill is forced to choose only between “secure” and “insecure,” it will either hallucinate confidence or stay overly silent. + +The three evidence levels solve that: + +- **Finding** keeps the bar high for concrete claims. +- **Possible Concern** makes room for reviewer intuition without overstating certainty. +- **Insufficient Evidence** explicitly allows “I cannot responsibly assess this from what I read.” + +This design reflects a key principle: honest uncertainty is better than invented precision. + +### 4.3 Why findings include severity per issue + +Each issue receives its own severity score rather than producing a single binary verdict. +This supports prioritization. + +A feature may have: + +- one high-severity sink flaw +- several medium-severity capability concerns +- a handful of low-severity documentation omissions + +Collapsing everything into pass/fail would hide useful nuance. +Per-issue severity makes the output actionable without pretending that all flaws are equal. + +## 5. Why the same master categories are used for both reviews + +The design and implementation skills share the same master concern categories: + +1. Trust Boundaries & Data Flow +2. Input Parsing & Validation +3. Sink Handling & Contextual Sanitization +4. Capabilities & Least Privilege +5. Secrets & Sensitive Config +6. Isolation & Blast Radius +7. Dependency & Supply Chain Assumptions +8. AuthN/AuthZ if applicable +9. Logging, Telemetry & Detection +10. Recovery & Failure Modes +11. Unsafe Dynamic Behavior +12. Security Testing Coverage + +The reason is consistency. + +Contributors should not need one mental model for design review and a different one for implementation review. +Using the same categories gives the process continuity: + +- at design time you ask whether the category is visible and sufficiently specified +- at implementation time you ask whether the code and config actually satisfy the category + +This makes handoff cleaner. +A design review finding in “Capabilities & Least Privilege” can later be checked concretely during implementation under the same category name. + +## 6. Why context-specific sink handling was chosen over a generic sanitizer + +One of the most important design decisions was to reject the idea of a single generic sanitizer. +At first glance, a universal sanitizer sounds safer because it centralizes responsibility. +In practice, it creates a false sense of completeness. + +The problem is that different sinks interpret data differently. +A value that is safe in one context may be dangerous in another. + +Examples: + +- HTML body rendering +- HTML attribute rendering +- JavaScript string interpolation +- URL query construction +- SQL statements +- shell commands +- file paths +- email templates +- logs + +There is no single transformation that makes arbitrary input safe for every one of those interpreters. +That is why the repository direction is: + +- centralize the **policy and approved APIs by sink category** +- use vetted libraries and safe framework primitives +- keep the handling context-specific +- prefer parameterized APIs where available instead of string escaping + +This produces a better form of centralization: +not “one sanitizer for everything,” but “one documented, reusable, approved strategy per sink class.” + +### 6.1 Why generic sanitization is risky + +A universal sanitizer encourages developers to think: + +> this string is now clean forever + +But security is not a permanent property of the raw string. +It depends on how that data is used. + +For example: + +- SQL injection is best prevented with parameterized queries, not a magical sanitized string +- XSS prevention depends on the output context and framework sink +- shell safety depends on command construction strategy, not generic string cleanup +- path safety depends on path semantics and allowlists, not generic character stripping + +The right abstraction is usually not `SanitizedString`. +It is more often one of: + +- a parsed domain type +- a context-specific safe output operation +- a parameterized call into a safer API +- a narrowly defined value object such as `EmailAddress`, `Url`, `Slug`, or `SqlIdentifier` + +### 6.2 What gets centralized instead + +Although a generic sanitizer was rejected, the project still benefits from centralization in these forms: + +- a documented catalog of sink categories and approved defenses +- reusable utilities for context-specific escaping where that is the correct defense +- preferred libraries such as HTML sanitizers instead of handwritten logic +- review rules that force the agent to inspect sink handling explicitly + +This balances consistency with correctness. + +## 7. Why capability scope and injected authority matter even in functional code + +A major theme of the new docs is that purity is not the same thing as least privilege. +This is an easy place for confusion. + +In a functional architecture, code often looks clean because: + +- dependencies are injected explicitly +- data is immutable +- workflows compose functions rather than mutating shared objects + +Those are valuable properties. +But they do not automatically limit runtime authority. + +A workflow can still receive a capability that is broader than it needs. +For example, a workflow that only needs to read data might be handed a service that can: + +- read data +- write data +- delete data +- access unrelated network systems +- administer resources + +The code is still functional. +It is also still over-privileged. + +That is why the review language emphasizes **capabilities** and **injected authority**. +What matters is not only how pure the call graph is, but also what real-world powers the injected service represents. + +### 7.1 What “injected authority” means here + +Injected authority is the runtime power available through a dependency. +This may include: + +- database credentials with read/write/admin access +- tokens that can call external APIs +- filesystem access +- shell execution access +- network reachability to sensitive systems + +The concern is not object-oriented statefulness. +It is authority. +A pure function can still orchestrate a dangerously powerful adapter. + +### 7.2 Why small capabilities reduce blast radius + +Narrow capabilities make compromise more containable. +If a workflow only receives what it genuinely needs, then: + +- accidental misuse is harder +- malicious use has fewer options +- review becomes more precise +- tests can assert the intended seam more clearly + +This is where the architecture helps. +The repository’s use of explicit interfaces and workflow seams makes it easier to design small, purpose-specific capabilities rather than god-objects or god-services. +The architecture does not guarantee least privilege automatically, but it makes it much easier to enforce. + +## 8. Why blast radius is treated as a design concern, not just an ops concern + +Blast radius is often discussed only in operational terms such as network segmentation, container boundaries, or IAM roles. +Those matter, but application design also shapes blast radius. + +Examples of design choices that affect blast radius: + +- whether workflows depend on broad or narrow capabilities +- whether dangerous sinks are concentrated and reviewable +- whether secrets are globally available or tightly scoped +- whether untrusted data becomes trusted only after explicit parsing +- whether the system documents recovery and detection expectations + +By reviewing blast radius at design time, the repository encourages contributors to ask: + +- If this adapter or workflow is compromised, what else can it reach? +- If this parsing boundary fails, where can unsafe data flow? +- If this secret leaks, what authority does it grant? +- If this workflow is abused, what bounded surface limits impact? + +These are not only deployment questions. +They are architectural questions. + +## 9. Why manual activation was chosen + +The new skills are manual rather than automatically triggered. +This was chosen for two reasons. + +### 9.1 Security review should be intentional + +Security review can be expensive in both time and attention. +Auto-triggering it on every related phrase would create noise and reduce trust in the output. +Manual invocation makes the act deliberate. + +### 9.2 Scope often needs clarification + +Security review quality depends heavily on scope. +A reviewer should know whether it is inspecting: + +- only a single workflow design +- a set of changed files +- the adjacent seams +- the wider project surface + +Forcing manual activation makes it more natural to start with a short “grill the human” step if the requested scope is unclear. + +## 10. Why the implementation review defaults to changed files plus adjacent seams + +Implementation review can become expensive and noisy if it always expands to the whole repository. +Most feature work changes a narrow slice. +That means the security review should usually focus on: + +- the changed workflow +- touched policies, models, interfaces, services, layers, and tests +- the nearby seam where risk actually appears + +This keeps the review useful and proportionate. + +Expanded-surface review still exists for times when a change touches broader concerns such as: + +- dependency updates +- setup scripts +- CI configuration +- agent/MCP config +- deployment-related settings + +But that broader review is opt-in because it should match the real change surface. + +## 11. Why external standards are referenced + +The skills explicitly allow and encourage references to external standards such as OWASP ASVS and OWASP cheat sheets. +This was chosen to avoid two bad extremes: + +1. a repo-local security process that reinvents common security guidance badly +2. a review process that cites standards so generically that it stops being useful + +External standards help anchor findings in established practice. +Repo-local guidance then adapts those standards to this architecture. + +This gives contributors both: + +- a principled external reference +- a concrete local interpretation + +## 12. What this means for future work + +The current decisions create a foundation, not a final destination. +Future work may include: + +- documenting approved sink-specific handling patterns in more detail +- adding threat-model sections to design artifacts +- creating a package-version lookup tool for agents so they can find recent versions before pinning +- expanding the review checklist with more operational security prompts +- documenting secure capability design examples for workflows and adapters + +The important point is that security review is now explicit. +It is part of how the template thinks, not just a separate external pipeline. + +## 13. Short summary + +The repository added security verification because architecture alone is not enough. +It chose verification gates instead of a new numbered phase because security review evaluates artifacts rather than defining domain meaning. +It split the work into design and implementation skills because they operate with different evidence and different goals. +It made those skills review-only to preserve human judgment. +It rejected a generic sanitizer in favor of sink-specific safe handling backed by centralized policy and approved APIs. +And it emphasized narrow capabilities and injected authority because functional purity does not automatically provide least privilege. + +These decisions aim to make the template more secure without making it vague, magical, or auto-patching by assumption. diff --git a/docs/explanation/architecture/service-strategy.md b/docs/explanation/architecture/service-strategy.md new file mode 100644 index 0000000..c26fa9d --- /dev/null +++ b/docs/explanation/architecture/service-strategy.md @@ -0,0 +1,61 @@ +# Adapter Strategy: Public APIs, Adapters, and Registries + +## The Decision +In the bounded-context direction, the main seam is not a global domain-layer interface tree. +The first seam is the **context's public API**. +But inside a bounded context, **interfaces/capabilities are still a crucial seam** for isolating pure logic from effectful or replaceable collaborators and for keeping workflows reviewable. +Inside or behind the public API, contexts may define capabilities, adapters, and optional registries as needed. + +**Vocabulary Note:** In Effect, a "Layer" is a *constructor/provider* for an implementation, not the interface itself. `PostgresLive` is a Layer that constructs and provides the `Postgres` implementation. + +## The Reasoning + +### 1. The public API is the boundary seam +Other contexts and top-level workflows should not know a context's internal files. +They should call the context through a public entrypoint that expresses **intent** in that context's language. + +That public API may internally depend on narrower capabilities, but those are not throwaway details. +They are often the key seam that lets a context keep policy and workflow code stable while implementations and collaborators change behind it. + +### 2. Interfaces are still the internal seam +Domain logic should not know about `Stripe` or `Neo4j`. +It should speak in the language of the owning context, while interfaces/capabilities define what collaborators can do without leaking implementation details. +Adapters or other implementations then satisfy those capabilities. + +So the preferred shape is: +- **Public context API**: what other code may call +- **Local capability/interface seam**: the contract the context depends on internally +- **Adapter**: concrete implementation +- **Layer**: wiring/provider + +When a context talks to storage, external APIs, queues, filesystems, time, identity, configuration, feature flags, or other collaborators, an internal interface is often the thing that keeps: +- workflows focused on orchestration instead of concrete implementation details +- policy and model code free of effectful collaborator concerns +- tests able to swap implementations cleanly +- collaborator churn from rewriting domain logic + +### 3. The naming problem still exists +- **Bad**: another context imports `billing/adapters/StripeAdapter` directly +- **Bad**: broad vague names like `SmallPaymentService` +- **Good**: a top-level workflow calls the billing context's public API, and the billing context hides its wiring details + +### 4. Strategy pattern and registries +Sometimes implementation selection depends on domain data. +That still does not belong in an adapter, and it should not be scattered through the workflow graph. + +Use a registry only when dynamic runtime selection is a real seam: +1. **Local policy** decides a strategy value +2. **Registry** maps that strategy to an implementation of a capability/interface +3. **Workflow or context API** asks the registry for the implementation + +Registries are optional. +Do not create them by default when direct wiring is enough. + +## Practical takeaway +Prefer this order of thought: +1. What is the public intent API for this context? +2. What capability/interface seams are needed behind it? +3. Which adapters implement those capabilities? +4. Is a registry actually needed, or would it be architecture furniture? + +The important correction is: **bounded contexts replaced the global interface tree as the repository map, but they did not replace interfaces as a crucial internal seam.** diff --git a/docs/explanation/architecture/type-driven-design.md b/docs/explanation/architecture/type-driven-design.md new file mode 100644 index 0000000..4b8c88a --- /dev/null +++ b/docs/explanation/architecture/type-driven-design.md @@ -0,0 +1,60 @@ +# Type-Driven Design & Rich Domain Models + +## The Philosophy +We follow the "Make Illegal States Unrepresentable" philosophy from Scott Wlaschin's *Domain Modeling Made Functional*. +Instead of writing runtime checks for everything, we use the Type System to ensure that if code compiles, the data is likely valid. + +## Key Concepts + +### 1. Branded Types (Single-Case Union Models) +Never use bare primitives for domain concepts. `string` is too broad for an Email. `number` is too dangerous for a Price. + +- **Bad**: `const sendEmail = (to: string, body: string)` (Easy to mix up arguments) +- **Good**: `const sendEmail = (to: Email, body: Body)` + +In Effect, we use `Schema.brand` to create these distinct types with zero runtime overhead after validation. + +### 2. Making Illegal States Unrepresentable +If a User cannot have a "PaidDate" unless they have a "PaidStatus", do not make `paidDate` optional on the generic User. Create two types. + +- **Bad**: + ```typescript + type Order = { + state: 'Unpaid' | 'Paid'; + paidAt?: Date; // Ambiguous: Can I have an Unpaid order with a paidAt date? + } + ``` + +- **Good**: + ```typescript + type UnpaidOrder = { state: 'Unpaid' } + type PaidOrder = { state: 'Paid', paidAt: Date } + type Order = UnpaidOrder | PaidOrder + ``` + Now it is *impossible* to access `paidAt` on an Unpaid order. The compiler forces you to check the state first. + +### 3. Parse, Don't Validate +"Validation" implies checking a value and returning true/false, but keeping the original untrusted type (e.g., `string`). +"Parsing" implies checking a value and returning a *new, trusted type* (e.g., `Email`). + +- **Workflow**: + 1. Receive `unknown` input. + 2. **Parse** it into a Domain Type (`Schema.decode`). + 3. Pass the Domain Type to your logic. + 4. Your logic never handles validation errors; it trusts the type. + +### 4. The "Always Valid" Domain Model +Functions in the `domain/` layer should generally assume their inputs are valid. +- `calculateTotal(cart: Cart)`: Assumes `Cart` is a valid structure. +- Validation happens at the *edges* (in the Workflow or Input Adapters), converting `RawJson` -> `Cart`. + +### 5. Pipeline States (Intermediate Types) +In Workflows, use specific types to represent the progress of a process. This ensures that steps cannot be executed out of order. You cannot "Ship" an order that hasn't been "Paid" because the `ship()` function demands a `PaidOrder` type, which is only created by the `pay()` function. + +- **Pattern**: + ```typescript + validateOrder(order: UnvalidatedOrder): ValidatedOrder // ensures address exists + priceOrder(order: ValidatedOrder): PricedOrder // calculates taxes/totals + makePayment(order: PricedOrder): PaidOrder // confirms transaction + shipOrder(order: PaidOrder): ShippingConfirmation + ``` diff --git a/docs/explanation/naming-for-domain-modeling.md b/docs/explanation/naming-for-domain-modeling.md new file mode 100644 index 0000000..71ca18a --- /dev/null +++ b/docs/explanation/naming-for-domain-modeling.md @@ -0,0 +1,162 @@ +# Naming for Domain Modeling + +Good naming is not cosmetic in TDFDDD. +It is part of the design. +If the names are vague, the model is usually vague too. + +## Why names matter so much + +The design artifacts are the thing the human reviewer audits. +That means names must communicate domain meaning without requiring the reader to reverse-engineer intent from implementation details. + +A weak name hides structure. +A strong name exposes purpose, lifecycle, and boundaries. + +## The three core naming laws + +### 1. Lifecycle rule + +Do not use one generic type name when the business actually cares about stages. + +- Bad: `Item` with `status: "draft" | "open" | "sold"` +- Better: `DraftItem | OpenItem | SoldItem` + +If the state matters to the rules, the type names should usually reflect it. + +### 2. Perspective rule + +Name things by the role they play in the decision, not only by the data they happen to contain. + +- Weak: `ItemWithBids` +- Better: `BiddableItem` +- Weak: `TotalBids` +- Better: `BidHistory` or `BidCounter` + +A good name answers the question: what is this for in the workflow? + +### 3. Grammar rule + +Use a stable grammar across artifacts: + +- **Objects and states** are nouns: `Truck`, `LoadingTruck`, `BidHistory` +- **Events** are past-tense facts: `PackageLoaded`, `BidPlaced` +- **Commands** are imperative intents: `LoadPackage`, `PlaceBid` + +This makes the design easier to scan and easier to review. + +## Name the boundary, not just the data + +A common failure mode is naming by storage shape rather than boundary meaning. + +- Weak: `TruckData` +- Better: `LoadingTruck` +- Weak: `UserInfo` +- Better: `AuthorizedUser` or `PendingUser` + +The stronger name usually reveals which rules apply to the object. + +## Prefer intent-first names over implementation-first names + +A good name should describe what the caller is trying to accomplish, not the current internal shape of the model. + +- Weak: `student.nextLesson()` +- Better: `getNextTask(studentId)` +- Weak: `markLessonComplete(studentId, lessonId)` +- Better: `completeTask(studentId, taskId)` + +The implementation may begin with lessons, but the workflow intent is often broader. +Once the domain grows to include assessments, partial lessons, retries, or other progression units, an implementation-first name starts forcing one concept to masquerade as another. +That is usually the beginning of model drift. + +A practical review question is: + +- Would this name still be correct if the underlying implementation changed but the user goal stayed the same? + +If the answer is no, the name is probably too close to implementation detail. + +## Intent-first naming applies beyond interfaces + +This is not only about public APIs. +The same rule helps with: + +- **Functions**: `getNextTask` is usually stronger than `nextLesson` +- **Types**: `ProgressItem` or `AssignableTask` may be stronger than `LessonRecord` +- **Modules**: `advanceStudentProgress` is often stronger than `lessonSequencing` +- **Workflows**: name the business outcome, not the current storage or transport representation + +The goal is to make the code read in ubiquitous language even as the implementation evolves. + +## Bounded seams should use the strongest names + +Naming matters most at bounded seams. +A bounded seam is the reviewable boundary where a human cares about the contract and is willing to treat the inside as a black box. + +At those seams, names should communicate: + +- the caller's intent +- the capability or authority being exercised +- the invariants that the boundary is protecting +- the business meaning of the input and output + +This is where vague names do the most damage. +If the seam is named after implementation details, the whole area around it becomes harder to evolve safely. +If the seam is named after intent and domain meaning, the implementation behind it can change with much less churn. + +## Separate process from object + +Do not mix workflow stages and domain nouns into one fuzzy container. + +- Weak: `Video` with a `processed` flag +- Better: `UnprocessedVideo | ProcessedVideo` +- Weak: `Job` with a `state` string used everywhere +- Better: `PendingJob | ActiveJob | CompletedJob` + +If different stages have different allowed operations, model them as different states. + +## Structural naming guidance + +These conventions help keep the code and design artifacts aligned: + +- **Workflows**: `verbNoun` or scenario names such as `checkout`, `registerUser`, `loadPackage` +- **Policies**: decision verbs such as `decideDiscount`, `validateCart`, `authorizePayment` +- **Pure model helpers**: entity-centered names such as `CartOps`, `MoneyOps`, `Truck` + +## What the reviewer should look for + +When reviewing LLM-generated design output, naming is often the quickest quality signal. + +Be suspicious when you see: + +- generic words like `data`, `info`, `manager`, `handler`, `processor` +- objects carrying a broad `status` field that hides meaningful lifecycle states +- events named like commands +- commands named like processes +- names that describe implementation detail instead of domain meaning +- one domain concept stretched to cover unrelated future cases because the name is too narrow + +A good reviewer asks whether the names would still make sense to a domain expert who never saw the code. + +## The three-alternatives review rule + +A useful way to pressure-test naming is to require three alternatives before settling on a seam, function, type, or workflow name. + +For each alternative, ask: + +- What caller intent does this name imply? +- What domain model does this name assume? +- What future changes would make this name misleading? +- Is this naming a capability, an intent, or an implementation detail? + +Example: + +- `nextLesson` assumes the progression unit is a lesson +- `nextAssignableUnit` is flexible but may be too abstract for the domain +- `getNextTask` names the user-facing intent while leaving room for lessons, assessments, and partial work + +The goal is not to maximize options. +The goal is to make hidden assumptions visible early, while the seam is still cheap to change. + +## Companion references + +- Use `../reference/naming-lexicon.md` when you need vocabulary prompts. +- Use `../reference/review-checklist.md` when reviewing complete design artifacts. diff --git a/docs/explanation/tdfddd-manifesto.md b/docs/explanation/tdfddd-manifesto.md new file mode 100644 index 0000000..0e46131 --- /dev/null +++ b/docs/explanation/tdfddd-manifesto.md @@ -0,0 +1,90 @@ +# TDFDDD Manifesto + +Type-Driven Functional Domain-Driven Design is the design discipline behind this template. +In casual conversation we call it **Functional Domain Modeling**. + +## What the method is trying to achieve + +The goal is not to move fast by skipping design. +The goal is to make the important design decisions explicit enough that implementation becomes mechanical and review becomes reliable. + +The core idea is simple: + +> Make illegal states unrepresentable. + +Instead of writing more and more runtime checks, use types and explicit state shapes so invalid situations are harder to express in the first place. + +## The design pressure should come from the workflow + +A common mistake is trying to invent the perfect domain model too early. +That usually produces elegant-looking types that do not actually serve the workflow. + +A better heuristic is: + +> Let the workflow dictate the state. + +Start with the decision the system must make. +Sketch the policy signature with temporary or incomplete types if needed. +Once you know what information the workflow needs, model those concepts precisely. + +This keeps the design grounded in real use instead of speculation. + +## Pure core, impure shell + +We separate high-inference design work from mechanical implementation work. + +- The **policy** is the pure core. It makes a domain decision from explicit inputs. +- The **model** applies a state transition using pure data transformations. +- The **workflow** is the impure shell. It gathers data, calls the policy, applies state changes, persists results, and invokes external capabilities. + +This separation is what makes the method reviewable. +A human can inspect the design in layers instead of trying to reason about everything at once. + +## Design before code, but not design as guesswork + +You should not jump straight into implementation code. +But that does not mean you need a perfect domain ontology before you start. + +A practical sequence is: + +1. Write the story in domain language. +2. Identify the command and possible events. +3. Sketch the policy signature. +4. Invent the minimum types required to make the decision precise. +5. Freeze the contract. +6. Translate the design into TypeScript and Effect. + +That is why design artifacts matter. +They preserve the reasoning that a human reviewer is validating. + +## Why the human reviewer matters in an LLM-first workflow + +In an LLM-first workflow, the model can produce the first draft of the design artifacts. +The human still has to judge whether the artifacts are sound. + +The reviewer is checking questions like these: + +- Did the model identify the right command and outcomes? +- Did it model state transitions instead of hiding them behind status flags? +- Did it choose types that encode the business constraints? +- Did it keep policies pure and move orchestration into workflows? +- Did it return domain facts instead of vague booleans? + +The review process only works if the repository preserves examples, templates, and clear standards for comparison. + +## What a good finished design feels like + +A good TDFDDD design usually has these properties: + +- The story can be explained in domain language without mentioning framework details. +- The command, events, and state variants are easy to name. +- The policy signature makes the decision boundary obvious. +- The final implementation looks like translation, not invention. +- A reviewer can tell what is being verified at each phase. + +## Where to go next + +- If you want to learn by imitation, start with `../tutorials/worked-example-truck-loading.md`. +- If you want to practice manually, use `../tutorials/practicing-tdfddd-by-hand.md`. +- If you want the formal phase breakdown, read `./tdfddd-protocol.md`. +- If you want to review model output, use `../how-to/review-an-llm-generated-design.md` and `../reference/review-checklist.md`. diff --git a/docs/explanation/tdfddd-protocol.md b/docs/explanation/tdfddd-protocol.md new file mode 100644 index 0000000..56b3ba9 --- /dev/null +++ b/docs/explanation/tdfddd-protocol.md @@ -0,0 +1,267 @@ +# 2. The Design Protocol (TDFDDD) + +**Type-Driven Functional Domain-Driven Design** + +This is the rigorous process we use to discover, design, and implement features. +Do not skip steps. +Do not write implementation code until the design is frozen. + +## How to read this page + +This page is the **current protocol** for real work in this repository. +It keeps the explanatory logic from the original 5-phase method, but rearranges that logic to fit the new **feature-first, slice-first** process. + +- The old 5-phase design logic still exists as the conceptual backbone. +- The repository workflow now applies that logic across **feature artifacts** and then **one workflow slice at a time**. +- The simpler human practice flow is preserved in [../by-hand/README.md](../by-hand/README.md). + +## The governing idea + +The key shift is: + +1. **Discover the feature broadly first** so bounded contexts, handoffs, and workflow inventory are visible. +2. **Then design and implement one workflow slice at a time** inside one bounded context. + +This keeps the rich domain reasoning from TDFDDD while avoiding monolithic feature-wide blueprints and bloated implementation passes. + +## Phase 1: Feature Discovery + +_Goal: Understand the feature as a whole before designing any single slice._ + +This phase inherits the old **event storming / discovery** logic, but applies it at the **feature level**. +The job is to identify **what happens** in the business story without worrying about code yet. + +### What to do + +1. Identify the feature story, actors, commands, and outcomes. +2. Identify the main business process and major domain facts. +3. Capture feature-level business rules, invariants, and edge cases. +4. Resolve the decision tree one branch at a time instead of jumping across many ambiguities at once. +5. Ask clarifying questions one at a time, and periodically restate the current shared understanding before moving on. +6. Identify candidate bounded contexts. +7. Identify candidate workflow slices. +8. When terminology stabilizes, record: + - project-wide terms in `docs/reference/shared-language.md` + - feature-level terms in `design/feature//discovery.md` +9. Write `design/feature//discovery.md`. +10. Create or update `design/feature//status.md`. + +### Why this phase exists + +If the feature story itself is muddy, everything downstream will be muddy too. +A weak feature discovery causes false bounded contexts, confused workflow ownership, and shallow slice design later. + +### Output + +- `design/feature//discovery.md` +- `design/feature//status.md` + +## Phase 2: Context & Workflow Decomposition + +_Goal: Map the feature into bounded contexts, workflow slices, and handoffs._ + +This is the major addition to the protocol. +It sits between broad feature discovery and deep single-slice design. +It exists so we do not accidentally core-sketch a workflow without understanding where it lives, what it owns, and what it hands off. + +### What to do + +1. Confirm the bounded contexts touched by the feature. +2. Map feature steps to workflow slices. +3. Record cross-context handoffs and orchestration notes in `design/feature//design.md`. +4. Create or update `design/workflows//shared-language.md` for each context involved. +5. Create `design/workflows///01-decomposition.md` for each slice. +6. Choose a recommended slice implementation order. +7. Update the feature status file so the decomposition and slice inventory are visible. + +### Why this phase exists + +The old process worked well for a single workflow, but it was easy to over-apply it to a whole feature. +That created large sketches, blurred bounded contexts, and implementation passes that were too big. +Decomposition fixes that by making workflow ownership and handoffs explicit before deep design starts. + +### Output + +- `design/feature//design.md` +- `design/workflows//shared-language.md` +- `design/workflows///01-decomposition.md` +- updated `design/feature//status.md` + +## Phase 3: Slice Discovery + +_Goal: Interrogate one workflow slice deeply enough to sketch it well._ + +This phase brings the old discovery questions back, but only for one selected workflow slice. +The point is to prevent the later F# sketch from being shallow or under-informed. + +### What to do + +1. Select exactly one workflow slice. +2. Clarify the happy path, unhappy paths, invariants, and edge cases for that slice. +3. Clarify what the slice owns versus what it only observes from other contexts. +4. Record handoff assumptions. +5. Restate the slice in plain domain language until the ownership boundary feels crisp. +6. Update bounded-context shared language if terminology stabilizes. +7. Write `design/workflows///02-discovery.md`. + +### Why this phase exists + +Your worry here is the right one: a core sketch is weak if the workflow-specific discovery is weak. +Feature discovery should map the terrain. +Slice discovery should interrogate the one path you are actually about to design. + +### Output + +- `design/workflows///02-discovery.md` +- optional updates to `design/workflows//shared-language.md` + +## Phase 4: Core Sketch + +_Goal: Figure out what information is required to make the decision for one workflow slice._ + +This is still the old **core sketch / policy signature** phase, now applied to exactly one bounded-context workflow. +At this stage, the emphasis is still on **what information is needed**, not on fully modeled types. + +### What to do + +1. Draft the slice policy signature in **F# pseudo-code**. +2. Determine the required owned state needed to answer the question. +3. Identify any observed external inputs or handoff data that the slice may read. +4. Record the command, events, and boundary notes. +5. Write `design/workflows///03-core-sketch.md`. + +### What this phase is not + +- It is **not** the full domain model yet. +- It is **not** implementation. +- It is **not** cross-context orchestration design. + +The job is to reveal the decision boundary clearly enough that modeling can become precise. + +### Output + +- `design/workflows///03-core-sketch.md` + +## Phase 5: Blueprint + +_Goal: Freeze the contract in F# for one workflow slice._ + +This phase combines the old **domain modeling** and **contract finalization** logic for one slice. +F# remains the primary design language and frozen contract before assembly. + +### What to do + +1. Model the **primitives**. +2. Model the **compounds**. +3. Model the **aggregates / states**. +4. Model the **events**. +5. Finalize the signatures for: + - `decide` + - `apply` + - the workflow boundary +6. Treat the F# blueprint as the authoritative design contract. +7. Write `design/workflows///04-blueprint.fs`. +8. Update `design/workflows//shared-model.fs` only when the context gains reusable shared F# pieces. + +### Design standards in this phase + +- Avoid primitive obsession. +- Make illegal states unrepresentable. +- Keep the policy pure. +- Model outputs as domain facts, not booleans. +- Separate: + - policy for pure decisions + - model for pure state transition math + - workflow for impure orchestration + +### Why F# matters here + +F# is not an optional flavoring step. +It is the design language that makes the contract precise before implementation starts. +If the blueprint is weak, assembly will start inventing domain meaning instead of translating it. + +### Output + +- `design/workflows///04-blueprint.fs` +- optional updates to `design/workflows//shared-model.fs` + +## Phase 6: Assembly + +_Goal: Translate one frozen F# blueprint into code._ + +This is still the old implementation / assembly phase, but it now runs on **exactly one workflow slice at a time**. +Assembly is a translation step from the frozen F# contract, not a redesign step. + +### What to do + +1. Read one `04-blueprint.fs` file. +2. Translate F# types to Effect schemas and TypeScript constructs. +3. Derive executable specifications from the frozen contract: + - workflow scenarios from the command and events + - policy examples from the decision rules + - model invariants from the state definitions +4. Implement using vertical **RED -> GREEN -> REFACTOR** cycles: + - write one failing test for one observable behavior at the public seam + - write the minimal code to make it pass + - refactor only while green +5. Keep tests focused on behavior through the contract, not implementation details. +6. Concentrate review rigor at the design and seam level. +7. Update `design/feature//status.md` for the current slice. + +### Test style by seam + +- **Models / Policies (Pure):** example-based tests plus property-based tests for invariants and edge cases. +- **Workflows (Impure):** scenario tests using Effect test layers or in-memory adapters. +- **Services / Adapters:** contract tests and integration tests against real infrastructure when needed. + +### Review emphasis + +- Review policies and workflows closely because they carry business meaning and orchestration correctness. +- Review service and adapter internals mainly for seam correctness, capability behavior, and absence of misplaced business logic. +- Trust strong domain types and contracts to reduce how much infrastructure code must be re-derived line by line. + +## Rules that apply across phases + +- One workflow slice belongs to one bounded context. +- No cross-context decision logic belongs inside a workflow slice. +- Cross-context behavior belongs in feature-level handoff and orchestration notes. +- Shared language should exist both globally and per bounded context. +- F# remains the primary design language and frozen contract before assembly. +- Implementation should proceed one workflow slice at a time. +- For the exact artifact layout, see [../reference/design-artifact-structure.md](../reference/design-artifact-structure.md). + +## Security Verification + +Security review is a required part of the process even though it is not itself a core design phase. +It acts as a verification gate around the design and implementation work. + +### Design security verification + +Run a security review on the frozen design before or during the transition from blueprint to assembly, when the slice has enough shape to inspect. + +Review at least: + +- the workflow story and trust boundaries +- the domain model and capability boundaries +- sink categories and sanitization ownership +- secrets handling assumptions +- blast-radius and isolation assumptions +- detection, recovery, and security test expectations + +In Amp, use the `tdfddd-security-verification` skill when you want a structured, review-only report. + +### Implementation security verification + +Run a second security review after implementation on the changed workflow slice and its adjacent seams. +Expand the scope only when needed. + +Review at least: + +- untrusted input parsing into trusted domain types +- dangerous sinks and context-specific protections +- over-broad capabilities or injected authority +- secret and sensitive-config handling +- unsafe dynamic behavior +- security-focused test coverage at risky seams + +In Amp, use the `tdfddd-security-verification` skill for a structured, review-only report over code and nearby surfaces. diff --git a/docs/explanation/tdfddd-refactoring.md b/docs/explanation/tdfddd-refactoring.md new file mode 100644 index 0000000..af00ce7 --- /dev/null +++ b/docs/explanation/tdfddd-refactoring.md @@ -0,0 +1,132 @@ +# TDFDDD Refactoring Protocol + +This is the process for evolving existing code without losing correctness or reviewability. +It complements the design protocol. +The design protocol helps you create a good shape. +The refactoring protocol helps you recover, improve, or reshape that shape as the system grows. + +Do not treat refactoring as random cleanup. +Use a phased process with an explicit scope, preserved behavior, and reviewable seam decisions. + +## The Checklist + +### Phase 0: Scope Gate + +_Goal: Choose the size of the refactor and define what must stay true._ + +1. Pick one scope: + - function + - task + - workflow + - module + - cross-module slice +2. State the preserved behavior. +3. State the non-goals. +4. State the success boundary: + - what will be easier to review + - what seam will become clearer + - what authority or dependency surface should shrink + +### Phase 1: Change Pressure + +_Goal: Identify where the current shape is fighting real change._ + +Look for: + +- code that changes together across multiple edits +- code that should change independently but is tangled together +- pass-through parameters that leak mechanics +- repeated branching at one boundary +- top-level workflows accumulating too much coordination detail + +Without automation, keep this lightweight. +Use recent edits, current pain, and active feature pressure rather than trying to analyze the whole repository. + +### Phase 2: Seam Diagnosis + +_Goal: Explain what is wrong with the current boundary before changing it._ + +For each candidate seam, ask: + +- What is its real purpose? +- What should the caller know, and what should stay hidden? +- Is the name intention-based or mechanical? +- Is this really a policy, model, service, task, or module? +- Does it require too much authority? +- Does it force the caller to understand internal details? +- Would three likely variations still fit this boundary naturally? + +A seam is weak when it only moves code around without reducing context load. + +### Phase 3: Target Shape + +_Goal: Define the new shape before large moves begin._ + +Describe: + +- the new seam name +- the new public contract +- what stays internal +- what dependencies are allowed +- whether the result should be a policy, model operation, service, task, or module + +Prefer intention-based APIs. +Name the boundary by what the caller is trying to accomplish, not by how the work is performed. + +### Phase 4: Safety Net + +_Goal: Lock observable behavior before reshaping internals._ + +1. Add or tighten tests at the public seam. +2. Preserve domain behavior first. +3. Let types define what cannot be broken silently. +4. If behavior is unclear, stop and clarify before moving code. + +The purpose of the safety net is not perfect coverage. +It is to make the next move safe enough to review confidently. + +### Phase 5: Reshape + +_Goal: Change the structure in small verified moves._ + +1. Move one responsibility at a time. +2. Keep the code running after each move. +3. Extract policies and model operations aggressively when pure logic appears. +4. Extract tasks when orchestration steps gain stable intent. +5. Promote a bounded module only when the seam repeatedly proves useful. +6. Prefer public APIs for cross-module calls. + +Do not introduce more layers than the current pressure justifies. + +### Phase 6: Proof + +_Goal: Show that the refactor improved the shape without breaking behavior._ + +Verify: + +- preserved behavior still holds +- tests and types still pass +- the seam is easier to explain +- the caller now needs less context +- the dependency or authority surface is smaller or clearer + +A successful refactor should reduce review load, not merely relocate code. + +## One Protocol, Multiple Scales + +Use the same protocol at different sizes. +Only the scope changes. + +- **Micro:** function, type, parameter list, local extraction +- **Meso:** task, workflow, module seam +- **Macro:** cross-module or bounded-context reshaping + +The questions stay the same. +The blast radius changes. + +## Manual First, Tool Later + +A future tool may help identify change pressure by mining coordinated edits. +Do not block on that tool. +Start manually with current pain and recent changes. +Automation should strengthen judgment, not replace it. diff --git a/docs/explanation/why-events-not-booleans.md b/docs/explanation/why-events-not-booleans.md new file mode 100644 index 0000000..703ef1e --- /dev/null +++ b/docs/explanation/why-events-not-booleans.md @@ -0,0 +1,61 @@ +# Why Events, Not Booleans + +A policy should usually return a rich domain fact, not just `true` or `false`. + +## The problem with booleans + +A boolean only tells you whether something was allowed. +It does not tell you what happened, why it happened, or what data the rest of the system needs next. + +For example, this policy is usually too thin: + +```ts +const canLoadPackage = (truck: LoadingTruck, pkg: Package): boolean => { + // ... +} +``` + +The workflow now has to reconstruct meaning from the inputs and repeat domain reasoning elsewhere. + +## What an event gives you + +A better policy returns a success event or a failure event. + +```ts +Result +``` + +That gives the workflow concrete domain facts: + +- `PackageLoaded` says what succeeded +- `LoadFailure` says why the decision failed +- the event payload can carry the exact information needed for the next step + +This keeps the workflow mechanical. +It does not need to invent new business meaning after the decision has already been made. + +## Events improve reviewability + +Events are also easier for humans to review than booleans. +A reviewer can ask: + +- Are these the right outcomes? +- Does each outcome carry the data the system needs next? +- Are we returning domain facts instead of UI phrasing or infrastructure detail? + +Those are much easier questions to answer when the outputs are explicit event types. + +## Events are data, not instructions + +An event is a fact about what happened. +It is not a command to perform side effects. + +- Good: `PackageLoaded` +- Bad: `SendPackageLoadedEmail` + +The workflow decides what to do with the fact in context. +That is where orchestration belongs. + +## A good rule of thumb + +If a policy output would force the workflow to ask "yes, but what exactly happened?", return an event instead of a boolean. diff --git a/docs/how-to/implementation-guide.md b/docs/how-to/implementation-guide.md new file mode 100644 index 0000000..06f94c3 --- /dev/null +++ b/docs/how-to/implementation-guide.md @@ -0,0 +1,167 @@ +# 4. Implementation Guide + +**Goal:** Mechanical translation from F# Design to TypeScript/Effect Code, using TDD during implementation. + +## TDD Inside Phase 5 + +Keep the F# design fixed first. TDD starts only after the contract is frozen. + +Use short vertical cycles: + +1. Pick one behavior from the contract. +2. Write one failing test at the module's public seam. +3. Implement the smallest change that passes. +4. Refactor only after the test suite is green. +5. Avoid premature indirection: keep logic combined by default, and extract only when the green code shows a real gain in nameability, testability, reuse, boundary clarity, or duplicated complexity. +6. Good names help reveal useful extractions, but they do not justify an unnecessary layer by themselves. + +Choose the test style by seam: + +- **context-local models and policies**: favor example-based tests plus property-based tests for invariants, boundary conditions, and illegal-state protection. +- **top-level `src/workflows/`**: favor scenario tests using Effect layers or in-memory adapters. Verify the workflow's observable result, not internal calls. +- **context adapters**: use contract tests for shared adapter behavior and integration tests when talking to real infrastructure. + +The frozen design tells you what to test: + +- **Command + Events** -> workflow scenarios +- **Policy signature** -> decision examples and failure cases +- **State model** -> invariants and state-transition properties + +The point is not to replace TDFDDD with TDD. The design process decides _what must be true_; TDD drives the safe, incremental implementation of that design. + +## Slice Implementation by Workflow + +The current artifact structure already assumes **one F# blueprint per workflow slice**. +Do not combine multiple workflow slices into one assembly pass. + +Prefer one workflow slice per implementation pass: + +- one `04-blueprint.fs` contract +- one focused set of tests +- one reviewable code change +- one fresh context if the work is large enough to benefit from it + +This keeps context windows smaller, review easier, and generated code more reliable. +The feature-level status file plus the workflow-slice artifacts should be enough to let a new context pick up the next slice safely. + +## Review Focus After Implementation + +Review rigor should concentrate at the places where business meaning and system shape live: + +- **Review closely:** contracts, domain types, policies, workflows, and adapter seams. +- **Review more lightly:** service and adapter internals, as long as they satisfy the seam, keep business logic out, and behave correctly through tests. + +This is not permission to be sloppy in infrastructure code. The goal is locality: when types are strong, policies are pure, and seams are well-designed, a reviewer should not need to re-derive the whole business model from inside each adapter. + +## The Pattern: Decide -> Match -> Apply + +This is the standard orchestration pattern for all workflows. It keeps the rules pure, the math pure, and the side effects isolated. + +### 1. The Policy (Pure Rules) + +_Located in: the owning bounded context by default; only top-level when the seam is truly cross-context_ + +A pure decision should usually still be a function, but it does **not** always need its own top-level policy module. +Keep a decision as a local pure function inside the owning context when that is the clearest choice. +Promote it to a broader seam only when extraction materially improves **nameability, testability, reuse, or boundary clarity**. +Prefer local logic when extraction would only add indirection. + +```typescript +// LoadPolicy.ts +export const decide = (truck: LoadingTruck, pkg: Package): Result => { + // 1. Check Rules + if (truck.currentLoad.weight + pkg.weight > truck.capacity.maxWeight) { + return Result.fail({ tag: "LoadFailure", reason: "OverWeight", ... }) + } + + if (truck.currentLoad.volume + pkg.volume > truck.capacity.maxVolume) { + return Result.fail({ tag: "LoadFailure", reason: "InsufficientVolume", ... }) + } + + // 2. Return Success Event (Do NOT calculate new state here, just return the fact) + return Result.succeed({ + tag: "PackageLoaded", + package: pkg, + truckId: truck.id + }) +} +``` + +### 2. The Model (Pure Math) + +_Located in: the owning bounded context's local model seam_ + +```typescript +// Truck.ts +// The "Reducer" - takes state + data -> new state +export const applyLoad = (truck: LoadingTruck, pkg: Package): LoadingTruck => ({ + ...truck, + currentLoad: { + weight: truck.currentLoad.weight + pkg.weight, + volume: truck.currentLoad.volume + pkg.volume, + }, +}); +``` + +### 3. The Workflow (Impure Shell) + +_Located in: the owning context for local orchestration, or top-level `src/workflows/` for cross-context orchestration_ + +```typescript +// LoadWorkflow.ts +const loadPackageWorkflow = (truckId: TruckId, packageId: PackageId) => + Effect.gen(function* (_) { + // 0. Get Dependencies + const repo = yield* Database; + + // 1. Gather Data (IO) + const truck = yield* _(repo.getTruck(truckId)); + const pkg = yield* _(repo.getPackage(packageId)); + + // 2. Execute Policy (Decide) + // The Workflow does not know logic. It just asks the Policy. + const decision = LoadPolicy.decide(truck, pkg); + + // 3. Match & Apply (Orchestration) + return yield* _( + Match.value(decision).pipe( + // CASE: SUCCESS + Match.when({ _tag: "PackageLoaded" }, (event) => + Effect.gen(function* (_) { + // A. Apply the change (Pure Math) + // The Policy said "Yes", so we calculate the new state. + const newTruckState = Truck.applyLoad(truck, event.package); + + // B. Persist the new state (IO) + yield* _(repo.saveTruck(newTruckState)); + + // C. Return the response + return { + success: true, + updatedCapacity: newTruckState.currentLoad, + }; + }), + ), + + // CASE: FAILURE + Match.when({ _tag: "LoadFailure" }, (failure) => + // We can choose to return a failure response OR fail the effect + Effect.fail(new BusinessError(failure.reason)), + ), + + Match.exhaustive, + ), + ); + }); +``` + +## Translation Table + +| Concept | F# Design | TypeScript / Effect Implementation | +| :------------ | :----------------------------- | :---------------------------------------------------- | +| **Primitive** | `type Weight = int` | `type Weight = number & Brand<"Kg">` | +| **Structure** | `type User = { Name: string }` | `const User = Schema.Struct({ name: Schema.String })` | +| **Union** | `type State = A \| B` | `Schema.Union(A, B)` (Discriminated Union) | +| **Function** | `Input -> Output` | `(input: Input) => Output` | +| **Result** | `Result` | `Either` (Effect's Either) | +| **Async** | `Async>` | `Effect` | diff --git a/docs/how-to/refactor-with-tdfddd.md b/docs/how-to/refactor-with-tdfddd.md new file mode 100644 index 0000000..c786a52 --- /dev/null +++ b/docs/how-to/refactor-with-tdfddd.md @@ -0,0 +1,87 @@ +# How to Refactor with TDFDDD + +Use this guide when existing code no longer matches the workflow graph cleanly. +The goal is to preserve behavior while improving seams, names, and reviewability. + +## Before you start + +Write down five things: + +1. **Scope:** function, task, workflow, module, or cross-module slice +2. **Preserved behavior:** what must remain true +3. **Pain:** what is hard to change or review today +4. **Target seam:** what boundary should become clearer +5. **Non-goals:** what you will not clean up in this pass + +If you cannot write those down, the refactor scope is probably too vague. + +## Quick seam review + +Ask these questions before extracting or splitting anything: + +- What is changing together today? +- What should be able to change independently? +- Does the current API expose mechanics instead of intent? +- Does the caller need too much knowledge to use this correctly? +- Is this logic actually a policy, model operation, service, task, or module? +- Would three likely variations still fit this boundary? + +## Refactor moves by shape + +### Extract a policy when + +- the code is making a pure business decision +- the workflow contains business `if` logic +- you can test it with plain values + +### Extract a model operation when + +- the code is applying an already-approved event +- the work is deterministic state math +- the workflow is mixing decision and state transition logic + +### Extract a task when + +- one workflow step has a stable intention name +- the parent workflow is becoming hard to read +- the step has its own scenario surface + +### Extract a module when + +- a cluster of tasks, policies, and services forms one capability +- you want a smaller public API and hidden internals +- multiple callers should depend on a shared intent boundary + +### Keep code inline when + +- the logic is still trivial +- the boundary has no stable meaning yet +- extraction would increase pass-through plumbing without reducing complexity + +## Safe execution pattern + +1. Add or tighten a test at the public seam. +2. Make one structural move. +3. Run verification. +4. Stop if behavior becomes unclear. +5. Repeat until the target seam is real and reviewable. + +Prefer many small green moves over one heroic rewrite. + +## What good looks like + +A good refactor leaves behind: + +- a clearer intention-based API +- less caller knowledge required +- less hidden authority +- stronger locality for future changes +- a smaller review surface for humans and LLMs + +## What to avoid + +- extracting wrappers that only rename plumbing +- introducing modules before there is real pressure +- keeping broad god-interfaces for convenience +- mixing business decisions into services +- using refactoring as an excuse to redesign unrelated areas diff --git a/docs/how-to/review-an-llm-generated-design.md b/docs/how-to/review-an-llm-generated-design.md new file mode 100644 index 0000000..57d9fc1 --- /dev/null +++ b/docs/how-to/review-an-llm-generated-design.md @@ -0,0 +1,107 @@ +# How to Review an LLM-Generated Design + +This guide is for the human reviewer. +Use it when the LLM has produced a design artifact and you need to decide whether it is safe to accept, revise, or reject. + +## What you are reviewing + +You are not primarily reviewing style. +You are reviewing whether the design artifact captures the domain correctly enough that implementation will be mostly mechanical. + +## Recommended review order + +### 1. Read the feature discovery first + +Start with `design/feature//discovery.md`. +Ignore code and framework details at the start. +Ask whether the artifact reflects the real business situation, user intent, candidate bounded contexts, and possible outcomes. + +If the feature story is muddy, everything downstream will be muddy too. + +### 2. Check the decomposition map + +Read `design/feature//design.md`. +Confirm that: + +- bounded contexts are explicit +- feature steps map to workflow slices +- cross-context handoffs are recorded +- the chosen slice actually belongs to one bounded context + +### 3. Inspect the slice discovery and core sketch + +Read `02-discovery.md` and `03-core-sketch.md` for the selected workflow slice. +The policy sketch should reveal the decision boundary. +It should be obvious what information is needed to make the decision. + +This is where shallow model output often shows up. +If the signature still contains vague blobs like `Data`, `Context`, or `Info`, the design is probably not ready. + +### 4. Inspect the F# blueprint + +Read `04-blueprint.fs`. +Look for evidence that the model is encoding business meaning rather than storing everything in generic shapes. + +Ask: + +- Are important lifecycle states explicit? +- Are primitives replaced with domain concepts where it matters? +- Are invalid combinations harder to express? +- Is the naming domain-specific and precise? +- Is the slice contract frozen clearly enough that assembly is mechanical? + +### 5. Inspect separation and boundaries + +The final contract should separate concerns clearly: + +- policy for pure decisions +- model for pure state transitions +- workflow for impure orchestration +- feature-level orchestration separate from slice-local decision logic + +If those concerns blur together, the implementation will likely blur too. + +### 6. Compare against the reference example + +Use these reference docs as comparison material: + +- `../tutorials/worked-example-truck-loading.md` +- `../reference/design-artifact-template.md` +- `../reference/review-checklist.md` +- `../explanation/naming-for-domain-modeling.md` + +You are not comparing domain details. +You are comparing clarity, shape, and separation of concerns. + +## Red flags + +Common signs that the artifact is not ready: + +- a generic object with a `status` field where separate states should exist +- policy outputs like `true` or `false` with no domain fact payload +- workflow concerns mixed into the policy +- infrastructure types leaking into the design artifact +- naming that sounds like programming jargon instead of domain language +- a final contract that still feels invented rather than discovered +- a refactor proposal that preserves backwards compatibility without an explicit reason, even though the compatibility requirement keeps a worse design in place + +## When to ask the LLM for a revision + +Ask for a revision when the design fails for reasons of shape, not just polish. +For example: + +- the command or events are wrong +- the state model hides important boundaries +- the policy does not expose the needed information +- the artifact skips from story to code without freezing the design + +A good revision request is specific. +Say what phase is weak and what you want clarified. + +## A practical review question + +A useful test is this: + +> If I handed this artifact to a careful engineer, could they implement it without inventing missing domain meaning? + +If the answer is no, the artifact is not ready. diff --git a/docs/reference/conventions.md b/docs/reference/conventions.md new file mode 100644 index 0000000..3cea0b4 --- /dev/null +++ b/docs/reference/conventions.md @@ -0,0 +1,64 @@ +# Project Conventions + +This page is the quick reference for conventions that a human reviewer is likely to check first. +For the philosophy behind these conventions, read the explanation docs. + +## Naming + +- Use domain language, not generic programmer words. +- Prefer explicit lifecycle states such as `DraftItem` or `ActiveOrder` over one object with a broad `status` field. +- Use nouns for stateful objects, past-tense facts for events, and imperative verb phrases for commands. +- Maintain a project-wide shared language in `shared-language.md`. +- Maintain bounded-context shared language in `design/workflows//shared-language.md`. +- Record feature-level naming decisions in `design/feature//discovery.md` and slice-level naming decisions in the workflow artifacts. +- See `naming-lexicon.md`, `shared-language.md`, and `../explanation/naming-for-domain-modeling.md` for guidance. + +## Design artifact shape + +A complete operational design set now usually includes: + +- feature discovery in `design/feature//discovery.md` +- feature decomposition in `design/feature//design.md` +- bounded-context shared language in `design/workflows//shared-language.md` +- slice discovery, core sketch, and F# blueprint under `design/workflows///` +- status tracking in `design/feature//status.md` + +Use `design-artifact-template.md` for by-hand practice and `design-artifact-structure.md` for the repository artifact layout. + +## Documentation organization + +This repo uses a modified Diátaxis structure: + +- `docs/tutorials/` for learning by example and practice +- `docs/how-to/` for specific tasks such as reviewing LLM output +- `docs/explanation/` for philosophy and architectural reasoning +- `docs/reference/` for templates, checklists, and stable lookup material + +## Code structure pointers + +- `src/contexts/` is the default home for business code. +- Each context keeps its own models, policies, workflows, translators, adapters, and other seams as needed. +- `src/workflows/` is reserved for top-level workflows that coordinate multiple contexts. +- `src/shared/` should contain only tiny ubiquitous primitives and low-meaning technical building blocks. + +## Module boundaries + +- Cross-context imports should go through the target context's public API. +- Do not import another context's internal implementation files. +- When data crosses context boundaries, prefer translation at the edge over sharing one rich domain type everywhere. + +## Decision preference + +- Prefer correctness, simplicity, and elegance over backwards compatibility. +- If compatibility would preserve a worse design, require an explicit reason before keeping it. +- Prefer combined local logic by default during initial implementation. +- Extract a new layer, module, or helper only when it materially improves nameability, testability, reuse, boundary clarity, or duplicated complexity. +- Good names can expose a useful seam, but naming alone does not justify extra indirection. + +## Review reminder + +When in doubt, compare the artifact being reviewed against: + +- `../tutorials/worked-example-truck-loading.md` +- `review-checklist.md` +- `design-artifact-template.md` diff --git a/docs/reference/design-artifact-structure.md b/docs/reference/design-artifact-structure.md new file mode 100644 index 0000000..4bafce5 --- /dev/null +++ b/docs/reference/design-artifact-structure.md @@ -0,0 +1,143 @@ +# Design Artifact Structure + +This is the current artifact layout for real feature work. +Use this structure for feature discovery, bounded-context decomposition, workflow-slice design, implementation tracking, and handoff between agents or humans. + +## Top-level idea + +- **Feature discovery** happens once per feature. +- **Decomposition** maps the feature into bounded contexts and workflow slices. +- **Slice design and implementation** happen one workflow slice at a time. +- **F# blueprint artifacts** are the frozen contract that implementation translates from. + +## Feature-level artifacts + +Store feature-level documents in: + +```text +design/feature// +├── discovery.md +├── design.md +└── status.md +``` + +### `discovery.md` + +Capture: + +- feature story +- commands and events at feature level +- business rules and invariants at feature level +- edge cases at feature level +- candidate bounded contexts +- candidate workflow slices +- feature-level shared language notes + +### `design.md` + +Capture: + +- bounded context inventory +- feature-step to workflow-slice map +- cross-context handoffs +- recommended slice order +- orchestration notes +- open questions + +### `status.md` + +Track: + +- current feature phase +- current workflow slice +- decomposition completion +- per-slice gate status +- blockers and handoff notes + +Use [design-status-template.md](design-status-template.md) as the base template. + +## Workflow-level artifacts + +Store workflow-slice documents in: + +```text +design/workflows// +├── shared-language.md +├── shared-model.fs # optional, when the context has reusable F# domain pieces +└── / + ├── 01-decomposition.md + ├── 02-discovery.md + ├── 03-core-sketch.md + └── 04-blueprint.fs +``` + +### `shared-language.md` + +Capture bounded-context-local ubiquitous language: + +- what the context owns +- preferred terms +- rejected synonyms +- notes about overlap with project-wide language in [shared-language.md](shared-language.md) + +### `01-decomposition.md` + +Capture: + +- owning bounded context +- trigger +- purpose and success outcome +- inputs owned by this context +- observed inputs from other contexts +- downstream handoffs +- dependencies on other slices +- explicit note that decision logic stays inside the owning context + +### `02-discovery.md` + +Capture: + +- happy path +- edge cases +- business rules and invariants +- decisions owned by this context +- handoff assumptions +- open questions + +### `03-core-sketch.md` + +Capture: + +- command +- required owned state +- observed external inputs +- pseudo policy signature +- events +- boundary notes about what remains feature-level orchestration + +### `04-blueprint.fs` + +This is the frozen F# contract for one workflow slice. +It should include: + +- domain primitives +- commands +- events +- states / aggregates +- `decide` +- `apply` +- workflow contract signature + +## Rules of use + +- One workflow slice belongs to one bounded context. +- No cross-context decision logic belongs inside a workflow slice. +- Cross-context behavior belongs in feature-level handoff and orchestration notes. +- Shared language should exist both globally and per bounded context. +- Implementation should proceed one slice at a time from the frozen F# blueprint. + +## Relationship to the by-hand docs + +The [../by-hand/README.md](../by-hand/README.md) material keeps the simpler 5-phase human practice flow. +Use that to build judgment. +Use this page for the actual repository artifact structure. diff --git a/docs/reference/design-artifact-template.md b/docs/reference/design-artifact-template.md new file mode 100644 index 0000000..d880419 --- /dev/null +++ b/docs/reference/design-artifact-template.md @@ -0,0 +1,111 @@ +# Design Artifact Template + +Use this template when drafting a design manually or when asking an LLM to produce a design artifact for review. + +## 1. Story + +Write the business situation in plain language. + +```text +A attempts to . +If , it succeeds. +If , it fails. +``` + +## 2. Event storming + +### Command + +```text + +``` + +### Timeline + +1. +2. +3. + +### Events + +```text + + +``` + +## 3. Core sketch + +Write the first policy sketch with rough concepts if needed. + +```fsharp +decide : NeededInput -> NeededState -> Result +``` + +Then rewrite it using domain-specific concepts. + +## 4. Domain model + +### Primitives + +```fsharp +type ExampleId = string +``` + +### Compound objects + +```fsharp +type Example = { + Id: ExampleId +} +``` + +### State variants + +```fsharp +type ActiveExample = { Id: ExampleId } +type ArchivedExample = { Id: ExampleId } + +type ExampleState = + | Active of ActiveExample + | Archived of ArchivedExample +``` + +### Events + +```fsharp +type ExampleSucceeded = { Id: ExampleId } +type ExampleRejected = { Reason: RejectionReason } +``` + +## 5. Final contract + +```fsharp +decide : Input -> State -> Result +apply : State -> SuccessEvent -> State +workflow : InputId -> Effect +``` + +## 6. Shared language and naming decisions + +Record the canonical words for this feature so design and code stay consistent. If a term is project-wide rather than feature-local, also add it to `docs/reference/shared-language.md`. + +### Canonical terms + +- ``: short definition +- ``: short definition + +### Rejected synonyms + +- Use ``, not `` +- Use ``, not `` + +### Naming grammar for this feature + +- Commands use: `` +- Events use: `` +- States / objects use: `` +- Workflow names use: `` + +## 7. Reviewer notes + +Document any assumptions, open domain questions, or naming choices the reviewer should inspect. diff --git a/docs/reference/design-status-template.md b/docs/reference/design-status-template.md new file mode 100644 index 0000000..d0050da --- /dev/null +++ b/docs/reference/design-status-template.md @@ -0,0 +1,117 @@ +# Design Status Template + +Use this file as `design/feature//status.md`. +It tracks feature-level discovery/decomposition, workflow slices, and per-slice progress through the downstream pipeline. + +## Feature + +- Name: `` +- Feature slug: `` +- Current phase: `Feature Discovery | Context & Workflow Decomposition | Slice Discovery | Core Sketch | Blueprint | Design Security Review | Assembly | Implementation Security Review | Refactor Diagnosis | Refactor Execution | Done` +- Overall status: `In Progress | Blocked | Ready for Next Phase | Complete` +- Security verification status: `Not Started | Design Review Needed | Design Review Complete | Implementation Review Needed | Implementation Review Complete` +- Current workflow slice: `/ | none` + +## Feature Artifacts + +- [ ] `design/feature//discovery.md` +- [ ] `design/feature//design.md` +- [ ] `design/feature//status.md` + +## Feature Discovery Gate + +- [ ] feature goal and actor intents captured +- [ ] commands and events identified at feature level +- [ ] business rules and invariants captured at feature level +- [ ] edge cases captured at feature level +- [ ] candidate bounded contexts identified +- [ ] candidate workflow inventory identified +- [ ] project-wide shared-language updates captured +- [ ] approved for context and workflow decomposition + +## Context & Workflow Decomposition Gate + +- [ ] bounded contexts confirmed +- [ ] feature steps mapped to workflow slices +- [ ] cross-context handoffs recorded +- [ ] per-context shared-language files created or updated +- [ ] workflow folders created with `01-decomposition.md` +- [ ] recommended slice order recorded +- [ ] approved to begin slice discovery + +## Workflow Slice Tracker + +| Bounded Context | Workflow Slice | Slice Discovery | Core Sketch | Blueprint | Design Security | Assembly | Impl Security | Refactor | Notes | +| :--------------- | :---------------- | :-------------- | :------------ | :------------ | :-------------- | :------------ | :------------ | :------------ | :------- | +| `` | `` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `` | +| `` | `` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `Not Started` | `` | + +## Current Slice Gates + +### Slice Discovery Gate + +- [ ] selected slice named explicitly +- [ ] happy path captured +- [ ] edge cases captured +- [ ] business rules and invariants captured +- [ ] handoff assumptions captured +- [ ] context shared-language updates captured +- [ ] approved for core sketch + +### Core Sketch Gate + +- [ ] required state is explicit +- [ ] command and events are explicit +- [ ] policy signature is explicit +- [ ] slice boundaries are explicit +- [ ] no cross-context decision logic inside the slice +- [ ] approved for blueprint + +### Blueprint Gate + +- [ ] domain types make illegal states harder to express +- [ ] shared concepts reused appropriately +- [ ] policy is pure +- [ ] reducer/apply shape is explicit +- [ ] workflow contract is explicit +- [ ] approved for design security review or assembly + +### Design Security Gate + +- [ ] trust boundaries reviewed +- [ ] authority and least privilege reviewed +- [ ] sink and data-flow risks reviewed +- [ ] blocking findings resolved or explicitly accepted +- [ ] approved for assembly + +### Assembly Gate + +- [ ] tests added +- [ ] implementation completed +- [ ] types pass +- [ ] tests passing +- [ ] effect AST checks run for modified Effect files +- [ ] approved for implementation security review or next slice + +### Implementation Security Gate + +- [ ] implementation security review completed or explicitly deferred +- [ ] blocking findings resolved or explicitly accepted +- [ ] approved for refactor consideration or next slice + +### Refactor Gate + +- [ ] diagnosis completed if structural changes were needed +- [ ] execution completed if approved +- [ ] verification rerun after refactor +- [ ] slice complete + +## Open Questions / Blockers + +- `` + +## Context Handoff Notes + +- Read first: `` +- Current focus: `` +- Do not change: `` diff --git a/docs/reference/directory-layout.md b/docs/reference/directory-layout.md new file mode 100644 index 0000000..c823757 --- /dev/null +++ b/docs/reference/directory-layout.md @@ -0,0 +1,50 @@ +# Directory Layout + +This page describes the **target shape** of the repository after the bounded-context shift. +The current codebase may still contain older top-level layer folders while the transition is in progress. + +## Preferred top-level structure + +```text +src/ +├── shared/ # tiny ubiquitous primitives only +├── workflows/ # top-level cross-context orchestration only +├── tasks/ # optional cross-context steps, only if needed +└── contexts/ # first-class bounded contexts +``` + +## Inside a bounded context + +A context is organized around its own language and review seams, not around a mandatory global layer checklist. +A context may contain only the folders it actually needs. + +```text +src/contexts/ +└── billing/ + ├── index.ts # public context API / intent entrypoints + ├── models/ # local rich domain models + ├── policies/ # local pure decisions + ├── interfaces/ # internal capability seams for effectful or replaceable collaborators + ├── workflows/ # local workflow or task logic if helpful + ├── translators/ # boundary translation to and from other contexts + ├── adapters/ # concrete implementations owned by this context + ├── registries/ # optional runtime strategy mapping + └── lib/ # local support code with no broader meaning +``` + +## Layout rules + +- Organize primarily by **bounded context**, not by one global layer tree. +- Keep top-level `workflows/` for business processes that coordinate multiple contexts. +- Do not let one context reach into another context's internals. +- Cross-context access should go through the target context's **public API** and be coordinated by a top-level workflow. +- Keep `shared/` tiny. It is for truly ubiquitous primitives and low-meaning technical building blocks, not rich domain models. +- Translate data at context boundaries instead of sharing one rich type everywhere. +- Start with folders in one package. Split into packages only when the boundary has proven stable enough to justify the extra rigidity. + +## During migration + +Older docs and code may still mention top-level folders like `src/domain/`, `src/policies/`, `src/adapters/`, or `src/registries/`. +Treat those as a legacy layout being phased out, not as the preferred structure for new work. + +For the architectural rationale behind this direction, read [../explanation/architecture/bounded-contexts.md](../explanation/architecture/bounded-contexts.md). diff --git a/docs/reference/naming-lexicon.md b/docs/reference/naming-lexicon.md new file mode 100644 index 0000000..c8dbc53 --- /dev/null +++ b/docs/reference/naming-lexicon.md @@ -0,0 +1,101 @@ +# Naming Lexicon + +Use this page as a vocabulary prompt when refining domain names. +It is not a source of mandatory terms. +Its purpose is to help you replace generic programmer language with more precise domain language. + +## Lifecycle and state + +| Concept | Useful terms | Examples | +| :--- | :--- | :--- | +| Beginning | `Initiate`, `Register`, `Onboard`, `Provision`, `Draft` | `InitiateTransfer`, `ProvisionServer` | +| Middle, active | `Active`, `Pending`, `InFlight`, `Outstanding`, `Live`, `Provisional`, `Tentative` | `OutstandingInvoice`, `ProvisionalBooking` | +| Middle, paused | `Suspended`, `Dormant`, `Held`, `Frozen` | `AccountFrozen`, `DormantUser` | +| Ending, good | `Complete`, `Fulfill`, `Settle`, `Finalize`, `Resolve` | `SettlePayment`, `FulfillOrder` | +| Ending, bad | `Fail`, `Reject`, `Decline`, `Abort`, `Revoke`, `Void`, `Expire` | `VoidTransaction`, `JobFailed` | +| Ending, neutral | `Archive`, `Retire`, `Conclude`, `Terminate` | `RetireProduct`, `TerminateSession` | + +## Corrections and changes + +| Concept | Useful terms | Examples | +| :--- | :--- | :--- | +| Making correct | `Rectify`, `Reconcile`, `Adjust`, `Correct`, `Align` | `ReconcileInventory`, `RectifyBalance` | +| Difference | `Variance`, `Discrepancy`, `Delta`, `Offset`, `Deviance` | `InventoryVariance`, `PriceDelta` | +| Defect | `Anomaly`, `Irregularity`, `Outlier`, `Malformation` | `DetectAnomaly`, `SignalIrregularity` | + +## Data and collections + +| Concept | Useful terms | Examples | +| :--- | :--- | :--- | +| Subset | `Cohort`, `Segment`, `Batch`, `Cluster` | `UserCohort`, `ProcessBatch` | +| Summary | `Digest`, `Manifest`, `Summary`, `Snapshot` | `ShippingManifest`, `DailyDigest` | +| Searching | `Locate`, `Query`, `Scan`, `Inspect`, `Retrieve`, `Fetch`, `Filter`, `Sift` | `LocateParcel`, `RetrieveRecords` | +| Assigning | `Allocate`, `Assign`, `Designate`, `Map` | `AllocateResources`, `DesignateOwner` | +| Communication | `Notify`, `Alert`, `Dispatch`, `Broadcast`, `Deliver` | `DispatchEmail`, `BroadcastEvent` | + +## Verification and security + +| Concept | Useful terms | Examples | +| :--- | :--- | :--- | +| Checking | `Validate`, `Verify`, `Authenticate`, `Authorize`, `Audit` | `AuthorizePayment`, `AuditLog` | +| Allowing | `Grant`, `Permit`, `Enable`, `Whitelist` | `GrantPermission`, `WhitelistIP` | +| Blocking | `Deny`, `Restrict`, `Revoke`, `Ban`, `Blacklist` | `DenyEntry`, `RevokeCertificate` | +| Subjects | `Actor`, `Principal`, `Subject`, `Resource` | `RequestActor`, `TargetResource` | + +## Time and scheduling + +| Concept | Useful terms | Examples | +| :--- | :--- | :--- | +| Delaying | `Defer`, `Postpone`, `Reschedule`, `Queue` | `DeferExecution`, `PostponeMeeting` | +| Recurring | `Interval`, `Cadence`, `Frequency`, `Period` | `BillingCadence`, `RefreshInterval` | +| Deadlines | `Deadline`, `Cutoff`, `Expiry`, `Threshold` | `SubmissionCutoff`, `ExpiryDate` | + +## Financial and transactional + +| Concept | Useful terms | Examples | +| :--- | :--- | :--- | +| Moving money | `Transfer`, `Remit`, `Disburse`, `Deposit`, `Withdraw` | `DisburseFunds`, `RemitTax` | +| Allocating | `Earmark`, `Reserve`, `Allocate`, `Encumber` | `EarmarkFunds`, `EncumberBudget` | +| Undoing | `Revert`, `Rollback`, `Compensate`, `Refund` | `CompensateTransaction` | + +## Boundary, intent, and capability prompts + +| Concept | Useful terms | Examples | +| :--- | :--- | :--- | +| Caller intent | `GetNextTask`, `CompleteTask`, `PlanProgress`, `AdvanceProgress`, `AssignWork` | `getNextTask`, `completeTask`, `advanceStudentProgress` | +| Capability and authority | `Read`, `Write`, `Plan`, `Authorize`, `Approve`, `Administer` | `ReadCurriculum`, `WriteCurriculum`, `ApproveEnrollment` | +| Boundary meaning | `Projection`, `Snapshot`, `Decision`, `Eligibility`, `Assignment`, `Progress` | `CurriculumSnapshot`, `EnrollmentDecision`, `TaskAssignment` | +| Implementation-shaped names to question | `LessonRecord`, `Manager`, `Handler`, `Processor`, `Data`, `Info` | `LessonManager`, `StudentInfo`, `TaskProcessor` | + +## Compare alternatives before settling on a name + +| Naming pressure test | Alternative 1 | Alternative 2 | Alternative 3 | +| :--- | :--- | :--- | :--- | +| Progression unit | `nextLesson` | `nextAssignableUnit` | `getNextTask` | +| Completion action | `markLessonComplete` | `closeProgressUnit` | `completeTask` | +| Sequencing module | `lessonSequencing` | `progressUnitPlanner` | `advanceStudentProgress` | + +Use these comparisons to expose hidden assumptions: + +- does the name lock the model to one current concept? +- does it express caller intent or internal structure? +- is it naming a capability boundary or only an implementation detail? + +## Bounded seam prompts + +| Seam concern | Useful terms | Examples | +| :--- | :--- | :--- | +| Reviewable contract | `Decision`, `Command`, `Event`, `Assignment`, `Plan`, `Result` | `PlacementDecision`, `AssignTask`, `TaskPlanned` | +| Protected invariant | `Eligible`, `Authorized`, `Assignable`, `Settled`, `Balanced` | `EligibleStudent`, `AssignableTask` | +| Black-box boundary | `Service`, `Workflow`, `Policy`, `Gateway`, `Repository` | `StudentProgressWorkflow`, `AuthorizationPolicy` | + +At a bounded seam, prefer names that tell the reviewer: + +- what the caller is trying to achieve +- what authority is being exercised +- what invariant or decision the boundary protects + +## Reminder + +Prefer domain language from the real problem space over any generic lexicon. +This page is a prompt, not a substitute for talking to a domain expert. diff --git a/docs/reference/overview.md b/docs/reference/overview.md new file mode 100644 index 0000000..43fe314 --- /dev/null +++ b/docs/reference/overview.md @@ -0,0 +1,22 @@ +# Project Structure Overview + +This template now treats **bounded contexts as the primary organizing boundary**. +Pure vs impure separation still matters, but it now lives mostly **inside each context** instead of as one global folder tree. + +## Key documentation + +- [Directory Layout](directory-layout.md): The preferred context-first folder map. +- [Bounded Context Architecture Statement](../explanation/architecture/bounded-contexts.md): The governing architecture direction. +- [Architecture Reasoning](../explanation/architecture/index.md): The deeper rationale behind context boundaries, workflow seams, and purity decisions. +- [Architecture Rationale Summary](../explanation/architecture/rationale-summary.md): A short refresher for reviews and interviews. +- [Conventions](conventions.md): Naming, file organization, and documentation conventions. +- [Review Checklist for TDFDDD Artifacts](review-checklist.md): The quickest way to inspect a generated design artifact. + +## Short version + +- `src/contexts/` holds first-class bounded contexts. +- `src/workflows/` holds top-level cross-context orchestration. +- `src/shared/` stays tiny. +- Cross-context calls go through public APIs, not internal file imports. +- Rich domain types stay local to their context unless a primitive is truly ubiquitous. + diff --git a/docs/reference/refactor-checklist.md b/docs/reference/refactor-checklist.md new file mode 100644 index 0000000..24c2ae6 --- /dev/null +++ b/docs/reference/refactor-checklist.md @@ -0,0 +1,46 @@ +# Refactor Checklist + +Use this checklist when reviewing or planning a refactor. + +## Scope + +- Is the refactor scope explicit? +- Is preserved behavior stated? +- Are non-goals stated? +- Is the success boundary clear? + +## Change pressure + +- What is currently changing together? +- What should be able to change independently? +- Is there evidence of tangling, pass-through plumbing, or repeated branching? +- Is the top-level workflow carrying too much coordination detail? + +## Seam quality + +- Is the seam named by intent rather than mechanism? +- Does the contract hide mechanics? +- Does the caller need less context after the refactor? +- Does the seam require narrower authority than before? +- Would at least three likely variations still fit the boundary naturally? + +## Correct shape + +- Is pure decision logic in a policy? +- Is deterministic state transition logic in a model operation? +- Are services limited to capabilities and integration? +- Are tasks used for meaningful orchestration steps rather than thin wrappers? +- Is a module justified by a real bounded capability? + +## Safety + +- Are there tests or type constraints protecting the public seam? +- Is behavior clarified before structure is moved? +- Are changes being made in small reviewable steps? + +## Outcome + +- Is preserved behavior still intact? +- Is the code easier to explain? +- Is review load lower? +- Is the new boundary more stable under likely future changes? diff --git a/docs/reference/review-checklist.md b/docs/reference/review-checklist.md new file mode 100644 index 0000000..8cc8cc2 --- /dev/null +++ b/docs/reference/review-checklist.md @@ -0,0 +1,73 @@ +# Review Checklist for TDFDDD Artifacts + +Use this checklist when reviewing a design produced by a human or an LLM. + +## Feature story and scope + +- Does `design/feature//discovery.md` describe a real business action without implementation detail? +- Is the user intent clear? +- Are candidate bounded contexts and candidate workflows visible? +- Are the feature-level outcomes both visible? + +## Decomposition + +- Does `design/feature//design.md` map feature steps to workflow slices? +- Is each workflow slice owned by one bounded context? +- Are cross-context handoffs explicit? +- Is the recommended slice order coherent? + +## Slice discovery and core sketch + +- Does `02-discovery.md` capture the happy path, edge cases, and invariants for the slice? +- Does `03-core-sketch.md` make the decision boundary obvious? +- Is the required owned state explicit? +- Are vague placeholders eliminated before the design is frozen? + +## F# blueprint + +- Does `04-blueprint.fs` model important lifecycle states explicitly? +- Are illegal combinations harder to express? +- Are key primitives wrapped in domain concepts where it matters? +- Is the naming precise and domain-oriented? +- Is business meaning encoded in the model instead of hidden in generic flags? + +## Contract separation + +- Is the policy pure? +- Is the model responsible only for state transition math? +- Is the workflow responsible only for orchestration and I/O? +- Are infrastructure concerns kept out of the design artifact? +- Do cross-module imports go through module public APIs instead of reaching into internal files? + +## Review focus + +- Are the contracts, domain types, policies, workflows, and adapter seams explicit enough to review with high confidence? +- Does the architecture keep business meaning concentrated in policies and workflows instead of buried in service internals? +- Can service and adapter internals be trusted mostly through seam tests and type constraints rather than line-by-line domain review? +- Do service implementations avoid accumulating hidden business logic? + +## Security review readiness + +- Are trust boundaries visible enough for a reviewer to identify where untrusted data enters? +- Are parsing boundaries explicit enough to tell where raw input becomes trusted domain types? +- Are likely dangerous sinks or sink categories named, even if implementation details come later? +- Are capability boundaries explicit enough to spot over-broad authority before assembly? +- Are secrets, sensitive config, and external trust assumptions called out where relevant? +- Are blast-radius, detection, recovery, and security-testing expectations visible enough to review? + +## Readiness + +- Could an engineer implement this without inventing missing domain meaning? +- Could a reviewer explain what is being verified at each phase? +- Does the final artifact feel discovered rather than improvised? +- When tradeoffs appear, does the result prefer correctness, simplicity, and elegance over backwards compatibility unless compatibility is explicitly required? +- For refactors, is backwards compatibility preserved only when there is an explicit reason to keep it, rather than by default? + +## Reference docs + +Compare against: + +- `../tutorials/worked-example-truck-loading.md` +- `../reference/design-artifact-template.md` +- `../explanation/naming-for-domain-modeling.md` +- `../explanation/why-events-not-booleans.md` diff --git a/docs/reference/rule-examples.md b/docs/reference/rule-examples.md new file mode 100644 index 0000000..dced4a6 --- /dev/null +++ b/docs/reference/rule-examples.md @@ -0,0 +1,67 @@ +# Rule Examples + +## Core Rules +```markdown +- [Primary-No-this] never use `this` keyword +``` + +## level-1 +```markdown +- [Primary-No-This] Never use `this` keyword + ❌ `onClick={() => this.handleClick()}` + ✅ `onClick={() => handleClick()}` +``` + +## level-2 +~~~markdown +Interfaces use `Context.Tag` to declare dependencies, and layers provide adapter implementations. +Keep decisions as pure functions even when they stay local to a module. +Do not promote every small decision to `src/policies/`; only extract when it materially improves nameability, testability, reuse, or boundary clarity. + +**❌ WRONG - Do NOT use classes with `this`:** +```typescript +// DON'T DO THIS - uses 'this' and class-based approach +export class UserService extends Effect.Service()("UserService", { + effect: Effect.gen(function*() { + const db = yield* Database + + return { + getUser: (id: UserId) => this.db.query(...) // ❌ Uses 'this' + } + }) +}) +``` + +**✅ CORRECT - Use Context.Tag and plain objects:** +```typescript +// Define the service interface +export interface FileSystemStorageService { + readonly readFile: (path: string) => Effect.Effect + readonly writeFile: (path: string, content: string) => Effect.Effect + readonly listFiles: (path: string) => Effect.Effect + readonly commit: (message: string) => Effect.Effect +} + +// Create the service tag +export class FileSystemStorageService extends Context.Tag("FileSystemStorageService")< + FileSystemStorageService, + FileSystemStorageService +>() { + static of(impl: FileSystemStorageService): FileSystemStorageService { + return impl + } +} +``` +~~~ + +## checklist +```markdown + +- 1. [ ] [Primary-No-This] Never use `this` keyword + ❌ `onClick={() => this.handleClick()}` + ✅ `onClick={() => handleClick()}` +... +- 20. [ ] [Composition-Over-Inheritance] Always use composition, never inheritance + ❌ `interface Node() {}; class ContentNode extends Node {};` + ✅ `` +``` diff --git a/docs/reference/shared-language.md b/docs/reference/shared-language.md new file mode 100644 index 0000000..cb4c5de --- /dev/null +++ b/docs/reference/shared-language.md @@ -0,0 +1,43 @@ +# Shared Language + +Use this page to keep the repository's ubiquitous language stable. +It is the project-wide glossary for terms that should mean one thing everywhere in design artifacts, code, tests, and reviews. + +## Why this exists + +Naming inconsistency creates design inconsistency. +When the same concept is called three different things, reviewers and LLMs start inventing distinctions that are not real. +A stable shared language improves design quality, review quality, and implementation consistency. + +This file is for **project-wide terms**. +Feature-specific naming choices should also be recorded in the relevant design artifact. + +## How to use it + +- Add terms that appear across multiple features or modules. +- Prefer short domain definitions over implementation detail. +- Record preferred terms and rejected synonyms. +- Update this file when the team decides one name should win. +- Keep the grammar consistent with the naming rules in `../explanation/naming-for-domain-modeling.md`. + +## Core grammar + +- **Commands**: imperative intents such as `LoadPackage` +- **Events**: past-tense facts such as `PackageLoaded` +- **States / Objects**: domain nouns such as `LoadingTruck` +- **Workflows**: verb-noun names such as `loadPackage` +- **Policies**: decision-oriented names such as `decideLoadEligibility` + +## Project-wide glossary template + +| Term | Meaning | Use this, not that | Notes | +| :--- | :--- | :--- | :--- | +| `` | `` | `` not `` | `` | +| `` | `` | `` not `` | `` | + +## Review questions + +- Would a domain expert recognize these terms? +- Are the same concepts named consistently across story, events, model, code, and tests? +- Are we accidentally using programmer words where domain words should exist? +- Have we documented which synonym is preferred when multiple names are plausible? diff --git a/docs/todo/agent-pipeline-notes.md b/docs/todo/agent-pipeline-notes.md new file mode 100644 index 0000000..d7a0b73 --- /dev/null +++ b/docs/todo/agent-pipeline-notes.md @@ -0,0 +1,448 @@ +# Agent Pipeline Notes + +## 1. Reality Check + +- Fully automated code writing is close for bounded, low-risk, well-specified work. +- Fully unsupervised ownership of large, evolving, high-stakes systems is not close. +- Raw coding ability is improving faster than architectural consistency, uncertainty calibration, and trustworthy self-review. +- In the near term, the goal is not to remove review entirely. The goal is to move review up a level. + +## 2. Current Bottleneck + +- The early design phases in this repository are already relatively strong. +- The main bottleneck is assembly and the review/refactor thrash around assembly. +- The biggest time sink is repeated loops around common implementation issues, potential refactors, and reviewing too much low-level detail. +- Strong design artifacts reduce the need to reconstruct intent from code, but they do not yet fully remove the need for human judgment. + +## 3. What a Pipeline Adds Beyond Manual Skill Use + +Right now, the human is acting as the scheduler and state machine. +A pipeline externalizes that work so it is explicit and enforceable. + +Useful additions that were not as necessary when doing the process manually: + +- machine-checkable approval state +- explicit slice definitions +- spec-to-code traceability rules +- human-signoff criteria by phase +- artifact diffs between stages +- automatic verification bundles +- replayable evaluation runs +- thrash/change-war detection +- audit trail for decisions and outcomes + +The main benefit is not “agents do more coding.” +The main benefit is that the process becomes more stable, repeatable, and reviewable. + +## 4. What to Pipeline First + +Build the thinnest useful pipeline around the current process. +Do not start with a large swarm system. + +Best initial targets: + +- phase state machine +- artifact and gate manifests +- assembly slice runner +- verification bundle +- thrash detection +- small replay benchmark harness + +This should be built as a headless harness first, designed for Argo-style execution rather than an interactive coding CLI. +Use existing coding agents and projects like opencode as reference implementations for inner-loop patterns, not as the architectural foundation. + +## 5. What Should Stay Human + +Humans should still own the judgment-heavy transitions: + +- requirements freeze +- workflow or slice approval +- architecture exceptions +- security sign-off for risky changes +- final sign-off for high-blast-radius changes +- resolving ambiguity or contradictory requirements + +The pipeline should reduce line-by-line review, not eliminate human judgment. + +## 6. Review at a Higher Level + +The practical goal is to move review from low-level code inspection to higher-level conformance review. + +Instead of asking: +- What does this code do? +- Did the agent miss some implementation detail? + +Try to make review focus on: +- Does this slice match the frozen artifact? +- Does it violate any domain invariants or trust boundaries? +- Did it introduce any risky seams or suspicious shortcuts? +- Does it need redesign, human review, or is it safe to merge? + +This is realistic if artifacts become stricter and slices become smaller. + +## 7. Preventing Subtle Logic Errors + +The system will mostly catch errors early rather than prevent every error outright. +That is still valuable because catching errors before merge is much cheaper than catching them later. + +Helpful layers: + +- frozen design artifacts +- spec-to-code traceability +- property and mutation tests where useful +- risk-focused seam review +- deterministic boundary and architecture checks +- forcing agents to cite which invariant each change satisfies + +The point is not perfection. +The point is making subtle logic flaws rarer and cheaper to catch. + +## 8. Slices and Assembly Scope + +Assembly often does too much when it translates a whole blueprint at once. +A better pattern is: + +1. freeze the artifact +2. choose one workflow slice +3. implement one tracer-bullet path end to end +4. run tests/checks/review +5. expand behavior within that slice +6. move to the next slice + +A slice is not necessarily one PR per policy or adapter. +A slice is one coherent bounded vertical change that may touch a workflow, several policies, and several adapters if they belong to one contract. + +## 9. Cost Reality + +A pipeline can save money only if it reduces retries, review thrash, and change wars. +If it creates uncontrolled loops, it can absolutely increase token costs. + +The key metric is not cost per token. +The key metric is cost per accepted slice and time saved per accepted slice. + +Important ideas: + +- use expensive reasoning at phase boundaries +- use cheaper models only when artifacts are tight and the task is narrow +- detect thrash early +- benchmark replay on a small representative set, not everything +- optimize for time saved, not just token minimization + +If spending more on tokens saves multiple days of work, that can still be a clear win. + +## 10. Minimum Viable Build + +For the clarified goal, LangGraph is worth adopting in the MVP because orchestration, durable execution, and resumable state are now part of the project’s core value. +Start with a small headless TypeScript orchestrator built for batch execution inside Argo workflows. +Add Langfuse from the beginning so traces, spans, prompts, runs, and outcomes are observable in a way that is useful both operationally and on a resume. + +Suggested MVP pieces: + +- LangGraph workflow graph / state machine for bounded slices +- markdown artifacts with small machine-readable frontmatter or JSON manifests +- simple TypeScript parsers for artifacts and statuses +- Langfuse tracing for each run, stage, model call, and gate decision +- verification bundle assembled into one review packet +- cheap external thrash-detection heuristics +- small replay benchmark harness +- one headless executor path suitable for Argo + +This is enough to improve the real workflow and create a strong portfolio project while staying aligned with the desired end-state architecture. + +## 11. Portable Process Core, Disposable Runner + +The right design is a portable process core with a replaceable runner. + +- process rules, states, gates, policies, artifacts, and evaluation logic should be portable +- the runtime executor should be disposable or replaceable at the boundary level + +Even if LangGraph is used in the MVP, the value should still live primarily in the process definition, policies, artifacts, event schema, and evaluation logic rather than in LangGraph-specific node wiring. +The graph runtime should be treated as an adapter for execution, checkpointing, and resumability, not as the place where domain process knowledge gets trapped. + +## 12. Workflow / Service Boundary for the Pipeline Itself + +Follow the repository’s existing separation when building the pipeline. + +Pure/domain-like pieces: +- pipeline state +- gate rules +- slice metadata +- approval rules +- conformance decisions + +Workflow/service pieces: +- run coding agents +- invoke models +- read and write artifacts +- launch review jobs +- open PRs +- store logs and run records + +Example: +- “Can assembly start?” is a pure policy decision. +- “Launch the assembly agent with model X” is a service call inside a workflow. + +That makes the process logic portable and keeps LangGraph as a future adapter instead of a hard dependency. + +## 13. LangGraph: When It Helps + +LangGraph is useful here because orchestration is now part of the main project rather than a future optimization. +Its real benefits are: + +- durable execution +- resumability +- retries +- branching workflows +- checkpointed state +- human-in-the-loop pauses +- better operational management for complex graphs +- easier alignment with headless batch execution in Argo-style systems + +For this direction, it is reasonable to use LangGraph in the MVP. +The main caution is to avoid letting graph node wiring become the only place where business process rules live. +Keep process semantics portable even if LangGraph is the first runtime. + +## 14. Training Data Value + +A pipeline can produce much better training data than raw chat logs. +The valuable data is not just “the model wrote code.” +The valuable data is: + +- given this phase and artifact state +- with these checks failing or passing +- what action/tool/model choice was correct next +- what human correction was needed +- what result was ultimately accepted + +This is useful for future model training, replay evaluation, and process improvement. + +## 15. What to Log for Training and Replay + +Do not assume the orchestration engine will automatically create a clean training corpus. +Design explicit logging for the data you care about. + +Minimum useful fields: + +- run ID +- stage ID +- slice ID +- artifact versions and hashes +- prompt or template version +- model choice +- full input context given to the model for that step +- tool choice and tool arguments +- tool outputs or summaries +- verification results +- gate decision +- human corrections or overrides +- final disposition (accepted, rejected, redesign, escalated) + +If prompt design or routing rules materially affect cost or quality, log those too. + +## 16. LangGraph vs Training Logs + +LangGraph can persist execution state and checkpoints, and can be useful for debugging. +But that is different from having a clean, normalized dataset for: + +- training +- replay +- evaluation +- analytics +- cost analysis + +So the orchestration system and the training/event log should be treated as separate concerns. +Use orchestration state for execution and recovery. +Use explicit event logs for learning and analysis. + +## 17. Hooking Into the Executor Layer + +Start with a headless executor boundary, not with deep integration into an interactive coding CLI. +The first goal is not to instrument every internal token. +The first goal is to supervise bounded work reliably inside batch execution. + +What the executor boundary should do first: + +- launch a bounded task with a clear artifact bundle +- capture the returned summary, diff, and verification results +- classify the outcome as continue / needs-human / blocked / failed +- stop after one coherent slice or checkpoint +- emit structured events and traces to Langfuse + +If an existing coding agent can operate in a non-chatty batch mode for bounded tasks, it can sit behind this executor boundary. +If it cannot, keep it for human-guided stages and use a lower-level headless executor for automated assembly later. + +## 18. What to Capture From the Executor + +If possible, capture at least: + +- exact task input sent +- artifact bundle provided +- model used +- tool calls made +- tool arguments +- result summary +- changed files +- verification output +- whether the run completed, asked for help, or thrashed +- trace IDs, run IDs, span metadata, and checkpoint IDs + +For training-grade data, exact per-step context is better than only a final summary. +LangGraph persistence and Langfuse traces are helpful, but they are still not the same thing as a clean replay/eval dataset. +Explicit event capture is still required. + +## 19. Opencode-Specific Decision Point + +Opencode is now mainly a reference implementation for inner-loop agent behavior rather than the likely runtime foundation. + +Questions to answer before borrowing ideas from it or integrating with it: + +- Which parts are genuinely reusable in a headless Argo-oriented system? +- Can its agent loop run cleanly in bounded, low-chatter automation? +- Can you capture the effective prompt/context used? +- Can you capture tool-use data well enough for replay and training? +- Does borrowing from it reduce delivery time more than it increases architectural drag? + +If yes, copy patterns or adapt isolated pieces. +If no, then the best path is to keep opencode as a reference and build a smaller dedicated executor for automated assembly and evaluation. + +## 20. Portfolio Value + +This can be a strong AgentOps portfolio project if it is real and measured. +The impressive part is not size. +The impressive part is demonstrating: + +- staged orchestration +- model routing +- evaluation and replay +- cost and quality tradeoffs +- review reduction +- guardrails and risk controls +- operational judgment about where humans stay in the loop + +A compact, sharp system with metrics is better than a giant “swarm” that is hard to explain. + +## 21. Practical Next Step + +Build a thin layer around the current repository process and use it on real work. +Do not pause everything to build a big system. + +A good first direction: + +1. define machine-checkable phase and approval states +2. define slice metadata and human sign-off criteria +3. implement a small LangGraph state graph for one bounded slice +4. wrap verification into one bundle +5. add basic thrash detection and Langfuse tracing +6. run it on one live workflow inside a headless batch path +7. measure review time, retries, accepted-slice cost, and trace quality +8. add explicit event logging for replay and evals +9. decide whether any existing coding agent is good enough behind the executor boundary +10. iterate based on real pain + +## 22. Six-Step Build Order for the First Effect-Template Use Case + +For the first use case, the target is developing software inside this repository structure rather than supporting a fully general coding environment from day one. +The build order should stay tightly coupled to the minimum tool surface needed for one bounded workflow slice. + +### Step 1: Prove the End-to-End Slice Loop +Goal: +- get one bounded software-development slice running end to end in the effect-template style + +Tools to add: +- read_file +- list_files +- write_file +- edit_file +- run_tests + +Why this first: +- proves the basic harness can take a task, operate on repository artifacts, make code changes, run verification, and return a classified result +- keeps the tool surface small enough to debug failures clearly + +### Step 2: Add First-Class Observability +Goal: +- make each run inspectable so failures and bottlenecks are visible immediately + +Tools to add: +- trace_run or equivalent Langfuse instrumentation hooks + +Why now: +- once the first loop works, observability becomes the fastest way to learn from real runs +- tracing should come before adding much more autonomy so failures do not become opaque + +### Step 3: Add Replay and Evaluation Foundations +Goal: +- make runs reproducible and measurable rather than anecdotal + +Tools to add: +- save_replay_record +- replay_run + +Why now: +- replayability is one of the core differentiators of the harness +- this creates a basis for later evals, cost analysis, and regression checks + +### Step 4: Add Gates and Thrash Control +Goal: +- keep the system from looping uselessly or pushing low-quality output forward + +Tools to add: +- gate_evaluator +- thrash_detector + +Why now: +- after replay exists, it becomes easier to define and tune failure heuristics +- this is where the harness starts protecting time and token spend rather than only executing work + +### Step 5: Improve Execution Safety +Goal: +- make automated runs safer and more diagnosable in headless environments + +Tools to add: +- safer shell or sandbox executor +- git_diff + +Why now: +- once the harness can already complete bounded slices, safety and change inspection become more valuable than adding more raw capability +- diff visibility is especially useful for higher-level review + +### Step 6: Add Richer Orchestration +Goal: +- move from one bounded slice to more capable autonomous workflow behavior + +Tools to add: +- planning or decomposition tool +- multi-agent delegation or subtask dispatch + +Why last: +- richer orchestration compounds complexity quickly +- it should be added only after the basic loop, observability, replay, and control mechanisms already work + +## 23. Open Decisions Still Worth Discussing + +Before implementing too much, it would be useful to decide: + +- what exact human approvals are required at each phase +- how small slices should be in practice +- what counts as thrash vs healthy iteration +- what minimum event schema is worth storing now +- whether to store full prompts or prompt templates plus resolved context +- whether to store full tool outputs or normalized summaries plus raw attachments +- how much of the coding CLI can be wrapped without losing its advantages +- whether automated assembly should use the same executor as human-guided work +- what metrics will prove the pipeline is saving time rather than adding ceremony +- what data retention and privacy rules apply if you later train on this data + +## 24. Bottom Line + +The opportunity is real. +The trust barrier is still the main constraint. + +So the right move is: +- do not wait for models to become magically trustworthy +- do not overbuild a giant pipeline first +- formalize the process you already use +- move review up a level +- keep human judgment at the risky seams +- log the process in a training- and replay-friendly way +- measure whether the pipeline actually saves time diff --git a/docs/todo/detecting-bounded-contexts.md b/docs/todo/detecting-bounded-contexts.md new file mode 100644 index 0000000..6c041c3 --- /dev/null +++ b/docs/todo/detecting-bounded-contexts.md @@ -0,0 +1,83 @@ +# System Prompt: Evolutionary Architecture Pipeline Implementation + +**Goal:** Build a custom CI/CD script that combines spatial data (where code lives), temporal data (when code changes), and structural data (what the code does) to guide incremental refactoring in a TypeScript + Effect.ts codebase. + +**Tech Stack:** +1. **Dependency-Cruiser:** To map spatial boundaries (Seams). +2. **Hercules:** To map temporal evolutionary coupling. +3. **Opengrep:** To identify data-flow anti-patterns (Tramp Data, Branching). +4. **TypeScript (Node.js):** The "glue" script (`refactor-bot.ts`) that synthesizes the data. + +--- + +### Step 1: Establish Spatial Boundaries (Dependency-Cruiser) +**Agent Instructions:** +1. Install dependency-cruiser: `npm install -D dependency-cruiser` +2. Initialize it `npx depcruise --init` and configure it for a TypeScript environment. +3. Modify the `.dependency-cruiser.js` configuration to define "Seams". Group the application by folders (e.g., Domain boundaries, Layers, or Effect modules). +4. Create an npm script named `"map:boundaries"` that runs depcruise and outputs the architecture to a JSON file: `npx depcruise src --output-type json > ./.architecture/seams.json`. + +--- + +### Step 2: Establish Temporal Coupling (Hercules / Git Mining) +**Agent Instructions:** +1. Set up a tool to extract **Logical Coupling** from the git history. + *(Note for Agent: Use the `src-d/hercules` binary via Docker or Go, OR use the simpler `code-maat` Python/Clojure alternative if Hercules is blocked by local environment constraints).* +2. Execute a git log command to get history: + ```bash + git log --all --numstat --date=short --pretty=format:'--%h--%ad--%aN' --no-renames > ./.architecture/logfile.log + ``` +3. Run the coupling analysis tool against this log. The goal is to output a `.csv` or `.json` file (`coupling.json`) with three columns/fields: `[FileA, FileB, CoChangePercentage]`. +4. Ensure this output is saved to `/.architecture/coupling.json`. + +--- + +### Step 3: Implement Deep Static Analysis (Opengrep / Semgrep) +**Agent Instructions:** +1. Install the open-source CLI (e.g., `npm install -g opengrep` or use `pip install semgrep`). +2. Create a folder `/.architecture/rules/`. +3. Write custom YAML rules to detect code smells specific to **Effect.ts** and functional pipelines. +4. **Rule 1: Tramp Data in Pipelines.** Write a rule that looks for a variable passed into a `.pipe()` or `Effect.gen` that traverses across a recognized boundary un-mutated. Use the ellipsis operator (`...`). + *Example concept:* + ```yaml + rules: + - id: effect-tramp-data + pattern: pipe(..., $MOD_A.get($DATA), ..., $MOD_B.save($DATA), ...) + message: "Potential tramp data crossing seam boundaries." + languages: [typescript] + severity: WARNING + ``` +5. Ensure there is a command to run this and output to JSON: `opengrep --config ./.architecture/rules/ --json > ./.architecture/smells.json` + +--- + +### Step 4: Write the Synthesis Script (`refactor-bot.ts`) +**Agent Instructions:** +Write a Node.js TypeScript file (`scripts/refactor-bot.ts`) that acts as the brain. This script must: + +**1. Load the Data:** +* Parse `seams.json` (Which files belong to which boundary). +* Parse `coupling.json` (How often files change together). +* Parse `smells.json` (Where the AST anti-patterns are). + +**2. Detect Cross-Seam Leaks (High External Coupling):** +* Look at pairs in `coupling.json` that co-change > 60% of the time, but live in *different* Seams according to `seams.json`. +* *Action:* Flag these as **Architectural Leaks**. Check if `smells.json` found tramp data in these specific files. + +**3. Detect Splittable Contexts (Low Internal Cohesion):** +* Look at files that exist in the *same* Seam (e.g., both are in `src/payments/`), but have a co-change frequency of < 5%. +* *Action:* Flag this boundary as a candidate to be split into two smaller bounded contexts. + +**4. Generate the Output/Report:** +* Output a Markdown report summarizing: + * **⚠️ High Priority Refactors:** Cross-layer tightly coupled files (includes Semgrep AST context). + * **✂️ Suggested Seam Splits:** Modules with low internal cohesion. +* Exit with code `1` if the max coupling threshold is breached to fail the CI/CD pipeline, otherwise exit with `0`. + +--- + +### Definition of Done for Agent: +1. `dependency-cruiser` is configured and outputs `seams.json`. +2. Git history pipeline outputs `coupling.json`. +3. Opengrep/Semgrep YAML rules are written for Effect.ts and output `smells.json`. +4. `refactor-bot.ts` successfully reads all three files and prints an Evolutionary Architecture Report in the console. diff --git a/docs/todo/fixes.md b/docs/todo/fixes.md new file mode 100644 index 0000000..f00b097 --- /dev/null +++ b/docs/todo/fixes.md @@ -0,0 +1,5 @@ +the "domain-modeling.md" in agent core rules is way too long. should be in docs not filling agent context + +add notes for how agent should name commits. only makes commits after I review the code + +intra bounded contexts should be dry but different bounded contexts can implement the same function diff --git a/docs/todo/ideas.md b/docs/todo/ideas.md new file mode 100644 index 0000000..d561734 --- /dev/null +++ b/docs/todo/ideas.md @@ -0,0 +1,40 @@ +capability based use, not passing in the entire db +- see wlaschin's presentations on capability based code + +label input as trusted or not, strings are no longer raw + +sanitization notes add trusted vs untrusted string input? ie save string source? + +are the security boundaries comprehensive enough? with 12 concerns I worry that is too many for one agent, I guess you can just keep doing security over and over again. do we cover enough concerns? +add sanitization to strings +mention seams from legacy code book +make sure tests are property tests +tdd for domain objects as well + +sub-agent / orchestration documentation ideas: +- add shared orchestrator policy for scope control, restart criteria, and limits on agent freedom +- add a formal failure taxonomy for agent runs, including drift, tool misuse, silent failure, and recovery notes +- add evaluation thresholds and acceptance-check guidance for agent-produced work +- add model-routing and cost rules for choosing cheaper vs stronger models +- add escalation and recovery policies for when an agent should stop, hand off, or request a fresh context +- add guidance for refactors: preserve backwards compatibility only with an explicit reason, otherwise prefer the direct change +- add implementation guidance for future sub-agent benchmarks using real TypeScript tests as verification +- add lightweight evidence-capture guidance so agent runs can be reviewed later without heavy documentation overhead + + +# bounded seam identification tool: + it feels like you could have a tool that automatically identifies seams (ie seams are pretty obvious in this architecture right? they are layers in this template?) so you could have a tool that automatically traverses the changes so you can see what parts frequently need coordinated edits across seams? ie you could have this run every pr and then once there are places that get enough of a score you could start a refactor flow? or if there are pieces within a seam that always change independently then maybe they should be 2 bounded seams? +And yes, your coordinated-change idea is strong. This repo’s explicit layers help, but seams are not just layers; they are places where behavior can vary independently. A tool could mine PRs/commits for: +- files/functions frequently changed together +- edits that cross module APIs +- params/data passed through unchanged across many callers +- repeated branching at the same boundary + +# need find bounded contexts step of feature +need to find if there are new or existing bounded contexts. arrange repo by bounded context with shared primitives + +# do we have decompose feature into several workflows/tasks skill? +define the difference between workflows and tasks + +# need process for refactoring +ie I don't need to wait for the template to be done to use it I can just add the improvements later diff --git a/docs/todo/refactor-pipeline-steps.md b/docs/todo/refactor-pipeline-steps.md new file mode 100644 index 0000000..3c03b5d --- /dev/null +++ b/docs/todo/refactor-pipeline-steps.md @@ -0,0 +1,4 @@ +# ideas +- use sub package.json to isolate bounded contexts +- auto update and refactor, renovate updates dependencies, codemod cli does the refactor +- use sourcebot or cocoindex-code to look at the semantic meaning and check if there are similar functions that can be removed. ie "write everything twice" (wet) for projects, "don't repeat yourself" (dry) in bounded contexts. would need to write own pipeline diff --git a/docs/tutorials/effect-starting-guide.md b/docs/tutorials/effect-starting-guide.md new file mode 100644 index 0000000..a010a10 --- /dev/null +++ b/docs/tutorials/effect-starting-guide.md @@ -0,0 +1,637 @@ +# Effect-TS Best Practices Guide + +This guide documents best practices for writing Effect-TS code, following principles from: +- **"Grokking Simplicity"** by Eric Normand (functional programming fundamentals) +- **"Domain Modeling Made Functional"** by Scott Wlaschin (domain-driven design) +- **Effect-TS patterns** (modern TypeScript effect systems) + +## Core Principles + +### 1. Domain-Driven Design (Scott Wlaschin) + +#### Make Illegal States Unrepresentable + +Use the type system to prevent invalid domain states: + +```typescript +// ❌ BAD: Primitives allow invalid states +import {CpuCount} from "./VmSpec"; + +interface VmConfig { + cpus: number // Could be 0, negative, or 1000 + memory: number // Could be any number + status: string // Could be typo: "runing" +} + +// ✅ GOOD: Types enforce domain rules +const Slug = Schema.String.pipe( + Schema.pattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { + message: () => + "Slug must be lowercase letters, numbers, and hyphens only (e.g., 'my-snippet-name')", + }), + Schema.minLength(1), + Schema.maxLength(100), + Schema.brand('Slug'), +); +export type Slug = typeof Slug.Type; + + +export const CpuCount = Schema.Number.pipe( + Schema.int(), + Schema.between(1, 64), // Physical constraint + Schema.brand("CpuCountBrand" as const) +) +// Make sure to bind type +export type CpuCount = typeof CpuCount.Type; + +export const VmStatus = Schema.Literal("running", "stopped", "error").pipe( + Schema.brand("VmStatus") +) +export type VmStatus = typeof VmStatus.Type + +interface VmConfig { + slug: Slug + cpus: CpuCount // Guaranteed valid + memory: MemoryMB // Guaranteed valid + status: VmStatus // Typos impossible +} +``` + +#### Model the Domain Precisely + +Domain types should exactly match business concepts: + +```typescript +// ❌ BAD: Generic, imprecise +type NetworkConfig = { + vlan: number + subnet: string +} + +// ✅ GOOD: Precise domain model +export const VlanId = Schema.Number.pipe( + Schema.int(), + Schema.between(1, 4094), // IEEE 802.1Q valid range + Schema.brand("VlanId") +) +export type VlanId = typeof VlanId.Type; + +export const SubnetCidr = Schema.String.pipe( + Schema.pattern(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/), + Schema.brand("SubnetCidr") +) + +export type SubnetCidr = typeof SubnetCidr.Type; + +export const NetworkSegment = Schema.Struct({ + name: Schema.String, + vlanId: VlanId, + subnet: SubnetCidr, + gateway: IpAddress, +}) +``` + +#### Use Ubiquitous Language + +Domain types should use terminology from the problem domain: + +```typescript +// ❌ BAD: Technical/generic terms +interface Config { + val: number + data: string +} + +// ✅ GOOD: Domain language +export const TestSection = Schema.Struct({ + name: TestSectionName, + estimatedDuration: EstimatedDuration, + vmSpecs: Schema.Array(VmSpec), + requiresNestedVirt: Schema.Boolean +}) +``` + +## Tagged Error Handling + +### ❌ DON'T: Throw generic errors + +Never use generic `Error` or `throw` statements in Effect code. + +```typescript +// ❌ BAD - Generic Error loses type information +const validateTask = (data: unknown): Effect.Effect => { + return Effect.try({ + try: () => { + if (!data) { + throw new Error("Invalid data"); // ❌ Generic error + } + return data as Task; + }, + catch: (e) => new Error(String(e)) // ❌ Loses error context + }); +}; +``` + +### ✅ DO: Use tagged error classes + + +```typescript +class ValidationError extends Data.TaggedError("ValidationError")<{ + message: string +}> {} + + +// Use Effect.fail instead of throw +const validateTask = (data: unknown): Effect.Effect => { + if (!data || typeof data !== "object") { + return Effect.fail(new ValidationError("Data must be an object")); // ✅ + } + + const task = data as any; + if (!task.text) { + return Effect.fail(new ValidationError("Missing required field: text")); // ✅ + } + + return Effect.succeed(task as Task); +}; +``` + +### Why Tagged Errors? + +1. **Type-safe error handling**: Function signatures show exactly what can fail +2. **Pattern matching**: Use `Effect.catchTag` to handle specific errors +3. **Better debugging**: Error `_tag` shows up in stack traces +4. **Self-documenting**: Compiler enforces handling all error cases +5. **Composable**: Errors compose through Effect chains + +### Pattern Matching on Errors + +```typescript +const program = pipe( + loadTask(id), + // Catch specific error types by tag + Effect.catchTag("NotFoundError", (error) => + Effect.succeed(createDefaultTask(error.id)) + ), + Effect.catchTag("ValidationError", (error) => + Effect.fail(new BadRequestError(error.message)) + ), + // Catch all remaining errors + Effect.catchAll((error) => + Effect.fail(new UnknownError(String(error))) + ) +); +``` + +### Effect.fail vs throw + +```typescript +// ❌ NEVER use throw +const bad = () => { + if (condition) { + throw new Error("Bad!"); // ❌ Not type-safe + } +}; + +// ✅ ALWAYS use Effect.fail +const good = () => { + if (condition) { + return Effect.fail(new ValidationError("Good!")); // ✅ Type-safe + } + return Effect.succeed(result); +}; +``` + +--- + +## pipe vs Effect.gen + +### DEFAULT: Always use pipe + +**Golden Rule: Use `pipe` everywhere by default. Remember to use effects `do` notation + +### ✅ Use pipe for linear chains + +```typescript +// ✅ GOOD - Linear transformation chain +const updateTaskText = (id: TaskId, newText: string) => + pipe( + repo.getById(id), + Effect.flatMap((task) => updateText(task, newText)), + Effect.tap((updated) => repo.save(updated)) + ); +``` + +### ✅ Use pipe even with conditionals + +```typescript +// ✅ GOOD - Simple conditional with ternary +const completeTask = (id: TaskId) => + pipe( + repo.getById(id), + Effect.flatMap((task) => + task.state._tag === "Done" + ? Effect.succeed(task) // Already done + : pipe( + transitionState(task, Done), + Effect.tap((updated) => repo.save(updated)) + ) + ) + ); +``` + +### ❌ Bad: Nested pipes become unreadable + +```typescript +// ❌ BAD - Too many nested pipes (hard to read) +const complexOperation = (id: TaskId) => + pipe( + repo.getById(id), + Effect.flatMap((task) => + pipe( + repo.getParent(task.parentId), + Effect.flatMap((parent) => + pipe( + repo.getChildren(task.id), + Effect.flatMap((children) => + pipe( + validateHierarchy(parent, task, children), + Effect.flatMap((valid) => + valid + ? pipe( + updateTask(task), + Effect.flatMap(() => notifyParent(parent)) + ) + : Effect.fail(new ValidationError("Invalid")) + ) + ) + ) + ) + ) + ) + ) + ); // 😱 Pyramid of doom! +``` + +## Service Pattern + +### Effect.Service Pattern (Official) + +Use the official `Effect.Service` class pattern from the docs. + +// TODO convert to do notation + +```typescript +import { Effect, Layer } from "effect"; + +export class TaskQueryService extends Effect.Service()( + "TaskQueryService", + { + effect: Effect.gen(function* () { + // 1. Get dependencies from Context + const repo = yield* TaskRepository; + + // 2. Define operations as const functions + const getTask = (id: TaskId) => repo.getById(id); + + const getAllTasks = () => repo.getAll(); + + const getTaskTree = (rootId: TaskId) => + pipe( + repo.getAll(), + Effect.map((tasks) => buildTaskTree(tasks, rootId)), + Effect.flatMap((tree) => + tree + ? Effect.succeed(tree) + : Effect.fail(NotFoundError.make(rootId)) + ) + ); + + // 3. Return service object as const + return { + getTask, + getAllTasks, + getTaskTree, + } as const; + }), + dependencies: [], // Can list dependencies for Layer.provideMerge + } +) { + // 4. Optional: Add Test layer + static Test = this.Default.pipe( + Layer.provide(InMemoryTaskRepositoryTest) + ); +} +``` + +**Key Points:** +- Use `Effect.gen` inside `effect` (this is the ONE place it's OK) +- Close over dependencies with `yield*` +- Return operations as `const` object +- `.Default` provides the live layer automatically +- `.Test` can provide mock layers + +### Using Services + +```typescript +// In application code +export const updateName = ( + task: Task, + newName: validName +): E.Effect => + pipe( + PouchDBRepositoryLive, + E.tap((db) => db.create({...task, name: newName})), // the output is still the dep + E.tap((db) => db.sync()), // the output is still the dep + E.asVoid, // discarding all and returning void + ); + +const program = Effect.gen(function* () { + const queries = yield* TaskQueryService; + const tasks = yield* queries.getAllTasks(); + return tasks; +}); + +// With proper layers +const AppLive = Layer.mergeAll( + PouchDBRepositoryLive(db), + TaskQueryService.Default, + TaskCommandService.Default +); + +Effect.runPromise(program.pipe(Effect.provide(AppLive))); +``` + +#### Railway-Oriented Programming + +Use Effect's error handling for domain workflows: + +```typescript +// Each step can succeed or fail +const provisionWorkflow = (spec: VmSpec) => + Effect.gen(function* () { + // Validate + yield* validateVmSpec(spec) // Effect + + // Allocate resources + const allocation = yield* allocateResources(spec) // Effect + + // Provision + const vm = yield* provisionVm(spec, allocation) // Effect + + // Configure + yield* configureVm(vm) // Effect + + return vm + }).pipe( + // Handle errors at appropriate level + Effect.catchTag("ValidationError", (e) => /* handle */), + Effect.catchTag("ResourceError", (e) => /* handle */), + ) +``` + +### 2. Service Definition Pattern + +Always use `Effect.Service()()` pattern with explicit dependencies: + +```typescript +// ❌ BAD: Static methods, context.Tag +export class SSHConnection extends Context.Tag("SSHConnection")<...>() { + static live = Layer.effect(...) +} + +// ✅ GOOD: Service pattern with dependencies +export class SSHConnection extends Effect.Service()( + "SSHConnection", + { + effect: Effect.gen(function* () { + // Implementation + return { + execute: (cmd: Command) => // ... + } + }), + dependencies: [] // Explicit dependencies + } +) { + static Test = Layer.succeed(SSHConnection, { + execute: () => Effect.succeed(mockResult) + }) +} +``` + +### 3. Schema: Use Schema.Struct, Not Schema.Class + +```typescript +// ❌ BAD: Schema.Class with methods using `this` +export class Port extends Schema.Class("Port")({ + value: Schema.Number +}) { + toString(): string { + return this.value.toString() // Using `this`! + } +} + +// ✅ GOOD: Schema.Struct + pure functions +export const Port = Schema.Struct({ + value: Schema.Number.pipe(Schema.int(), Schema.between(1, 65535)) +}) +export interface Port extends Schema.Schema.Type {} + +// Pure function instead of method +export const portToString = (port: Port): string => + port.value.toString() + +// Or namespace for organization +export namespace Port { + export const toString = (port: Port): string => + port.value.toString() + + export const SSH = { value: 22 } as Port +} +``` + +### 4. Never Use `this` - Use Functional Style + +```typescript +// ❌ BAD: Using `this` +class CommandResult { + succeeded(): boolean { + return this.exitCode.value === 0 + } +} + +// ✅ GOOD: Pure function +export const isSuccess = (result: CommandResult): boolean => + result.exitCode.value === 0 + +// Or namespace pattern +export namespace CommandResult { + export const isSuccess = (result: CommandResult): boolean => + result.exitCode.value === 0 + + export const isFailed = (result: CommandResult): boolean => + !isSuccess(result) +} +``` + +### 5. One Concept Per File + +```typescript +// ❌ BAD: Multiple concepts in one file +// Command.ts +export class Command { } +export class CommandResult { } +export class ExitCode { } +export class Stdout { } +export class Stderr { } + +// ✅ GOOD: One concept per file +// Command.ts +export const Command = Schema.Struct({ ... }) + +// CommandResult.ts +export const CommandResult = Schema.Struct({ ... }) + +// ExitCode.ts +export const ExitCode = Schema.Struct({ ... }) + +// Stdout.ts +export const Stdout = Schema.Struct({ ... }) + +// Stderr.ts +export const Stderr = Schema.Struct({ ... }) +``` + +### 6. No For Loops - Use Functional Iteration + +```typescript +// ❌ BAD: Imperative for loop +for (const segment of topology.segments) { + const result = await test(segment) + results.push(result) +} + +// ✅ GOOD: Array methods for calculations +const totalCpus = vmSpecs.map(spec => spec.cpus.value) + .reduce((sum, cpu) => sum + cpu, 0) + +// ✅ GOOD: ReadonlyArray.map for immutability +import { ReadonlyArray } from "effect" + +const mapped = ReadonlyArray.map( + segments, + (segment) => segment.name +) +``` + +### 7. Avoid Primitive Obsession + +```typescript +// ❌ BAD: Primitives everywhere +function provision(name: string, cpus: number, memory: number) + +// ✅ GOOD: Rich domain types +export const VmName = Schema.String.pipe( + Schema.pattern(/^[a-z0-9-]+$/), + Schema.brand("VmName") +) +export type VmName = Schema.Schema.Type + +export const CpuCount = Schema.Number.pipe( + Schema.int(), + Schema.between(1, 64), + Schema.brand("CpuCount") +) +export type CpuCount = Schema.Schema.Type + +function provision(spec: VmSpec) +``` + + +### 9. Layer Composition Patterns + +```typescript +// Use pipe for clean composition +export const AppLayer = Layer.merge( + ConfigLayer, + LoggerLayer +).pipe( + Layer.provide(DatabaseLayer), + Layer.provide(ResourceLayer) +) + +// Factory functions for parameterized layers +export const makeSSHLayer = (connection: HostConnection) => + Layer.effect( + SSHConnection, + Effect.gen(function* () { + // Implementation + }) + ) +``` + +## Common Patterns + +### Repository Pattern + +```typescript +export class UsersRepo extends Effect.Service()( + "UsersRepo", + { + effect: Effect.gen(function* () { + const sql = yield* Sql + + return { + findById: (id: string) => + sql.query(/* ... */).pipe( + Effect.map(rows => rows[0]) + ), + + create: (user: User) => + sql.query(/* ... */) + } + }), + dependencies: [SqlLayer] + } +) { + static Test = Layer.succeed(UsersRepo, { + findById: (id: string) => Effect.succeed(mockUser), + create: (user: User) => Effect.succeed(user) + }) +} +``` + +## Checklist for Code Review + +- [ ] No `this` keyword anywhere +- [ ] No `for`/`while` loops - use functional iteration and `pipe` +- [ ] Schema.Struct, not Schema.Class +- [ ] One concept per file +- [ ] Services use `Effect.Service()()` pattern +- [ ] Branded types for domain concepts +- [ ] No primitive obsession +- [ ] Layer composition uses pipe +- [ ] Tagged errors for error handling +- [ ] Resources use Layer.scoped + Effect.acquireRelease + +## Domain-Driven Design Workflow + +Following Scott Wlaschin's approach: + +### 1. Understand the Domain +- Talk to domain experts +- Learn ubiquitous language +- Identify bounded contexts early enough to guide naming and ownership +- Treat those contexts as provisional boundaries that may later split or merge under real change pressure + +### 2. Model the Domain +```typescript +// Start with types that match domain language +export const NetworkSegment = Schema.Struct({ + name: Schema.String, // "Management", "DMZ" + vlanId: VlanId, // 10, 20, 30... + subnet: SubnetCidr, // "10.10.0.0/16" + gateway: IpAddress, // "10.10.0.1" + accessRules: Schema.Array(Schema.String) +}) +``` \ No newline at end of file diff --git a/docs/tutorials/practicing-tdfddd-by-hand.md b/docs/tutorials/practicing-tdfddd-by-hand.md new file mode 100644 index 0000000..429d56a --- /dev/null +++ b/docs/tutorials/practicing-tdfddd-by-hand.md @@ -0,0 +1,135 @@ +# Practicing TDFDDD by Hand + +This tutorial is for humans who want to build judgment before reviewing LLM-generated design artifacts. +The goal is to practice the method manually so you know what a good design artifact should feel like. + +## What you are practicing + +You are not trying to write production code yet. +You are practicing how to move from a business story to: + +- commands +- events +- state variants +- a policy signature +- a final contract + +## How to use this tutorial + +Pick a simple domain story and work through the phases on paper or in a scratch markdown file. +Do not look at implementation code until the design is stable. + +A good starter prompt is: + +> A librarian checks out a book to a member. If the member is suspended or the book is already on loan, checkout fails. + +## Step 1: Write the story in plain language + +Write two or three sentences that describe what a person is trying to do. +Do not mention framework details. +Do not mention databases. +Do not mention HTTP. + +Example: + +> A librarian attempts to check out a book to a member. The checkout succeeds only if the member is active and the book is available. + +## Step 2: Identify the command and events + +Write down: + +- the command: what someone is attempting +- the success event: what fact becomes true if it works +- the failure event: what fact becomes true if it does not + +Example: + +- Command: `CheckOutBook` +- Success event: `BookCheckedOut` +- Failure event: `CheckoutRefused` + +If you cannot name the events clearly, your understanding is probably still fuzzy. + +## Step 3: Ask what information the decision actually needs + +Now sketch the policy without worrying about perfect types. +Use placeholder names if necessary. + +```fsharp +checkOut : NeededBookInfo -> NeededMemberInfo -> Result +``` + +Then replace the vague placeholders with the actual concepts the rule depends on. +This is where the workflow starts shaping the model. + +## Step 4: Model the minimum useful types + +Define the nouns the policy needs. +Prefer explicit domain concepts over primitives. + +Ask yourself: + +- Do I need a state distinction here? +- Is this really one thing, or a union of stages? +- Am I hiding business meaning inside a generic `status` field? + +Example direction: + +```fsharp +type ActiveMember = { MemberId: MemberId } +type SuspendedMember = { MemberId: MemberId; Reason: SuspensionReason } +type AvailableBook = { BookId: BookId } +type LoanedBook = { BookId: BookId; DueDate: DueDate } +``` + +## Step 5: Freeze the contract + +Once the concepts are clear, write the final signatures. + +```fsharp +decide : AvailableBook -> ActiveMember -> Result +apply : LibraryState -> BookCheckedOut -> LibraryState +workflow : BookId -> MemberId -> Effect +``` + +At this point the design should feel boring in a good way. +The remaining implementation work should mostly be translation. + +## Step 6: Review your own artifact + +Before looking at a reference example, inspect your work. + +Ask: + +- Did I name the command, events, and states in domain language? +- Did I model meaningful lifecycle distinctions explicitly? +- Does the policy return facts instead of booleans? +- Is the workflow contract clearly separate from the policy contract? +- Could another person implement this without inventing missing domain meaning? + +## Step 7: Compare against a reference + +Now compare your work against: + +- `worked-example-truck-loading.md` +- `../reference/design-artifact-template.md` +- `../reference/review-checklist.md` + +Do not compare for identical names. +Compare for shape, clarity, and separation of concerns. + +## Practice loop + +To build review skill, repeat this exercise with three kinds of stories: + +- permission decisions +- state transition decisions +- allocation decisions + +Examples: + +- approve an expense request +- schedule a technician visit +- reserve inventory for an order + +The more hand practice you do, the easier it becomes to spot weak LLM output. diff --git a/docs/tutorials/worked-example-truck-loading.md b/docs/tutorials/worked-example-truck-loading.md new file mode 100644 index 0000000..e3e65d1 --- /dev/null +++ b/docs/tutorials/worked-example-truck-loading.md @@ -0,0 +1,238 @@ +# Worked Example: Truck Loading + +This tutorial is a complete walkthrough of a TDFDDD design from story to final artifact. +Use it as the primary reference example when learning the method or reviewing LLM-generated designs. + +## The story + +A warehouse worker attempts to put a package on a truck. +If the package fits and the truck is open for loading, it succeeds. +If the truck is full or sealed, it fails. + +Later we learn an additional detail: some trucks may be unavailable because they are in transit or under maintenance. + +## Phase 1: Event storming + +### Command + +`LoadPackage` + +### Business timeline + +1. A warehouse worker attempts to load a package onto a truck. +2. The system checks the truck state and capacity rules. +3. The system records either a success fact or a failure fact. + +### Events + +- `LoadPackage` +- `PackageLoaded` +- `LoadRefused` + +### What we learned from the domain + +The truck is not just a generic object with a `status` field. +Some states are loadable and some are not. +That is a clue that the model should likely distinguish the states explicitly. + +## Phase 2: Core sketch + +Now ask the design question: + +> To decide if a package can be loaded, what information must the policy know? + +Initial sketch: + +```fsharp +decideLoad : packageWeight -> + packageVolume -> + truckRemainingVolume -> + truckRemainingWeight -> + Result +``` + +This sketch is intentionally rough. +Its job is to reveal the concepts we need to model properly. + +## Phase 3: Domain modeling + +### Primitives + +```fsharp +type Weight = float +type Volume = float +type PackageId = string +type TruckId = string +``` + +### Compound objects + +```fsharp +type Package = { + Id: PackageId + Weight: Weight + Volume: Volume +} + +type TruckCapacity = { + MaxWeight: Weight + MaxVolume: Volume +} + +type CurrentLoad = { + TotalWeight: Weight + TotalVolume: Volume +} +``` + +### State variants + +We split the truck into loadable and non-loadable states instead of hiding the difference in one broad structure. + +```fsharp +type LoadingTruck = { + Id: TruckId + Capacity: TruckCapacity + CurrentLoad: CurrentLoad +} + +type SealedTruck = { + Id: TruckId + Status: "Sealed" | "InTransit" | "Maintenance" +} + +type Truck = + | Loading of LoadingTruck + | Sealed of SealedTruck +``` + +### Events + +The policy returns domain facts, not UI responses. + +```fsharp +type LoadFailureReason = + | OverWeight of { Limit: Weight; Actual: Weight } + | OverVolume of { Limit: Volume; Actual: Volume } + | TruckUnavailable of { Status: string } + +type PackageLoaded = { + TruckId: TruckId + PackageId: PackageId + NewLoadState: CurrentLoad + Timestamp: DateTime +} + +type LoadRefused = { + Reason: LoadFailureReason + TruckId: TruckId +} +``` + +## Why this model is better than a generic status object + +A single `Truck` object with a generic `status` field would force every caller to remember which combinations are valid. +By distinguishing `LoadingTruck` from `SealedTruck`, the design moves that rule into the type system. + +This is the kind of move a reviewer should look for in LLM output. + +## Phase 4: The contract + +```fsharp +decide : Package -> LoadingTruck -> Result +apply : LoadingTruck -> PackageLoaded -> LoadingTruck +loadPackage : TruckId -> PackageId -> Effect +``` + +Notice the separation: + +- `decide` is pure and domain-focused +- `apply` is pure and mechanical +- `loadPackage` is the impure workflow boundary + +## Phase 5: Implementation shape + +Once the design is frozen, the code follows the standard orchestration pattern: + +1. Gather data. +2. Call the policy. +3. Match on the returned event. +4. Apply the state transition. +5. Persist and return a workflow response. + +That is why a good design artifact makes implementation feel like translation. + +## Final artifact + +This is the kind of finished design artifact a human reviewer should be able to compare against. + +```fsharp +// 1. Primitives +type Weight = float +type Volume = float +type PackageId = string +type TruckId = string + +// 2. The Inputs (The Objects) +type Package = { + Id: PackageId + Weight: Weight + Volume: Volume +} + +type TruckCapacity = { + MaxWeight: Weight + MaxVolume: Volume +} + +type CurrentLoad = { + TotalWeight: Weight + TotalVolume: Volume +} + +// 3. The States (The Aggregates) +type LoadingTruck = { + Id: TruckId + Capacity: TruckCapacity + CurrentLoad: CurrentLoad +} + +type SealedTruck = { + Id: TruckId + Status: "Sealed" | "InTransit" | "Maintenance" +} + +type Truck = + | Loading of LoadingTruck + | Sealed of SealedTruck + +// 4. The Events (The Outputs) +type LoadFailureReason = + | OverWeight of { Limit: Weight; Actual: Weight } + | OverVolume of { Limit: Volume; Actual: Volume } + | TruckUnavailable of { Status: string } + +type PackageLoaded = { + TruckId: TruckId + PackageId: PackageId + NewLoadState: CurrentLoad + Timestamp: DateTime +} + +type LoadRefused = { + Reason: LoadFailureReason + TruckId: TruckId +} +``` + +## What to pay attention to when learning from this example + +Do not memorize the truck domain. +Pay attention to the structure of the reasoning: + +- the story becomes a command and events +- the policy sketch exposes the needed information +- the types become more specific as the design clarifies +- the final contract separates design concerns cleanly + +That structure is what you should reproduce by hand and review in LLM output. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..b11ed23 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,12 @@ +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + rules: { + // Effect development specific rules can go here if needed + }, + }, +); diff --git a/export-toolboxes.sh b/export-toolboxes.sh new file mode 100755 index 0000000..3d47712 --- /dev/null +++ b/export-toolboxes.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +export AMP_TOOLBOX="$PWD/.config/amp/toolboxes" \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..ee22492 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "effect-template", + "version": "0.0.1", + "type": "module", + "scripts": { + "build": "tsc -b", + "check": "tsc -b", + "test": "vitest run", + "lint": "eslint src", + "effect:check": "effect-patterns-check", + "verify": "eslint src && tsc --noEmit && vitest run" + }, + "dependencies": { + "@effect/platform": "^0.69.0", + "@effect/platform-node": "^0.64.0", + "@effect/schema": "^0.75.0", + "effect": "^3.10.0", + "prettier": "^3.8.3" + }, + "devDependencies": { + "@effect-patterns/analysis-core": "git+ssh://git@git.ewynn.in/ada/effect-analyzer-core.git", + "@effect/eslint-plugin": "^0.3.2", + "@eslint/js": "^10.0.1", + "@types/node": "^22.0.0", + "eslint": "^9.0.0", + "typescript": "^5.6.0", + "typescript-eslint": "^8.56.1", + "vitest": "^2.1.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..7ae3c8f --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2197 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@effect/platform': + specifier: ^0.69.0 + version: 0.69.31(effect@3.19.6) + '@effect/platform-node': + specifier: ^0.64.0 + version: 0.64.33(@effect/platform@0.69.31(effect@3.19.6))(effect@3.19.6) + '@effect/schema': + specifier: ^0.75.0 + version: 0.75.5(effect@3.19.6) + effect: + specifier: ^3.10.0 + version: 3.19.6 + prettier: + specifier: ^3.8.3 + version: 3.8.3 + devDependencies: + '@effect-patterns/analysis-core': + specifier: git+ssh://git@git.ewynn.in/ada/effect-analyzer-core.git + version: git+ssh://git@git.ewynn.in/ada/effect-analyzer-core.git#a3c274f81a8ad6db1098e574ea3c40be2021aef3 + '@effect/eslint-plugin': + specifier: ^0.3.2 + version: 0.3.2 + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@9.39.1) + '@types/node': + specifier: ^22.0.0 + version: 22.19.1 + eslint: + specifier: ^9.0.0 + version: 9.39.1 + typescript: + specifier: ^5.6.0 + version: 5.9.3 + typescript-eslint: + specifier: ^8.56.1 + version: 8.56.1(eslint@9.39.1)(typescript@5.9.3) + vitest: + specifier: ^2.1.0 + version: 2.1.9(@types/node@22.19.1) + +packages: + + '@dprint/formatter@0.4.1': + resolution: {integrity: sha512-IB/GXdlMOvi0UhQQ9mcY15Fxcrc2JPadmo6tqefCNV0bptFq7YBpggzpqYXldBXDa04CbKJ+rDwO2eNRPE2+/g==} + + '@dprint/typescript@0.91.8': + resolution: {integrity: sha512-tuKn4leCPItox1O4uunHcQF0QllDCvPWklnNQIh2PiWWVtRAGltJJnM4Cwj5AciplosD1Hiz7vAY3ew3crLb3A==} + + '@effect-patterns/analysis-core@git+ssh://git@git.ewynn.in/ada/effect-analyzer-core.git#a3c274f81a8ad6db1098e574ea3c40be2021aef3': + resolution: {commit: a3c274f81a8ad6db1098e574ea3c40be2021aef3, repo: ssh://git@git.ewynn.in/ada/effect-analyzer-core.git, type: git} + version: 0.1.0 + hasBin: true + + '@effect/eslint-plugin@0.3.2': + resolution: {integrity: sha512-c4Vs9t3r54A4Zpl+wo8+PGzZz3JWYsip41H+UrebRLjQ2Hk/ap63IeCgN/HWcYtxtyhRopjp7gW9nOQ2Snbl+g==} + + '@effect/platform-node-shared@0.19.32': + resolution: {integrity: sha512-qYimZLE2r3sqV8nCOVlOO8zVYozClpOeWbmZ9wtJ7gerEcYP/MRFaPVJ3YKuXvthVm8J/58HM5AE1IrT4VGgGg==} + peerDependencies: + '@effect/platform': ^0.69.31 + effect: ^3.10.19 + + '@effect/platform-node@0.64.33': + resolution: {integrity: sha512-75f3EwzQzLn4VSNAJnraRXveO2/v5vM/gzIDyYZqXQlhWokpsZt1Avm8ei3tE0r4bOxgRJXYvmNuvkvbuIxpeA==} + peerDependencies: + '@effect/platform': ^0.69.31 + effect: ^3.10.19 + + '@effect/platform@0.69.31': + resolution: {integrity: sha512-a4JQHieGF/kxH8ZSfEpVrxbdX9haAKFyoOQT5tXiiWoXSgvm+iQ/41FhXqwkWeMddh0hbYJmvmkEljcXUuxWcQ==} + peerDependencies: + effect: ^3.10.19 + + '@effect/schema@0.75.5': + resolution: {integrity: sha512-TQInulTVCuF+9EIbJpyLP6dvxbQJMphrnRqgexm/Ze39rSjfhJuufF7XvU3SxTgg3HnL7B/kpORTJbHhlE6thw==} + deprecated: this package has been merged into the main effect package + peerDependencies: + effect: ^3.9.2 + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.19.1': + resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==} + + '@typescript-eslint/eslint-plugin@8.56.1': + resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.56.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.56.1': + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.56.1': + resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.56.1': + resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + effect@3.19.19: + resolution: {integrity: sha512-Yc8U/SVXo2dHnaP7zNBlAo83h/nzSJpi7vph6Hzyl4ulgMBIgPmz3UzOjb9sBgpFE00gC0iETR244sfXDNLHRg==} + + effect@3.19.6: + resolution: {integrity: sha512-Eh1E/CI+xCAcMSDC5DtyE29yWJINC0zwBbwHappQPorjKyS69rCA8qzpsHpfhKnPDYgxdg8zkknii8mZ+6YMQA==} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-my-way-ts@0.1.6: + resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multipasta@0.2.7: + resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.56.1: + resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici@6.22.0: + resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==} + engines: {node: '>=18.17'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@dprint/formatter@0.4.1': {} + + '@dprint/typescript@0.91.8': {} + + '@effect-patterns/analysis-core@git+ssh://git@git.ewynn.in/ada/effect-analyzer-core.git#a3c274f81a8ad6db1098e574ea3c40be2021aef3': + dependencies: + effect: 3.19.19 + typescript: 5.9.3 + + '@effect/eslint-plugin@0.3.2': + dependencies: + '@dprint/formatter': 0.4.1 + '@dprint/typescript': 0.91.8 + prettier-linter-helpers: 1.0.1 + + '@effect/platform-node-shared@0.19.32(@effect/platform@0.69.31(effect@3.19.6))(effect@3.19.6)': + dependencies: + '@effect/platform': 0.69.31(effect@3.19.6) + '@parcel/watcher': 2.5.1 + effect: 3.19.6 + multipasta: 0.2.7 + + '@effect/platform-node@0.64.33(@effect/platform@0.69.31(effect@3.19.6))(effect@3.19.6)': + dependencies: + '@effect/platform': 0.69.31(effect@3.19.6) + '@effect/platform-node-shared': 0.19.32(@effect/platform@0.69.31(effect@3.19.6))(effect@3.19.6) + effect: 3.19.6 + mime: 3.0.0 + undici: 6.22.0 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@effect/platform@0.69.31(effect@3.19.6)': + dependencies: + effect: 3.19.6 + find-my-way-ts: 0.1.6 + multipasta: 0.2.7 + + '@effect/schema@0.75.5(effect@3.19.6)': + dependencies: + effect: 3.19.6 + fast-check: 3.23.2 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.1)': + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@10.0.1(eslint@9.39.1)': + optionalDependencies: + eslint: 9.39.1 + + '@eslint/js@9.39.1': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + + '@rollup/rollup-android-arm-eabi@4.53.3': + optional: true + + '@rollup/rollup-android-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-x64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + + '@standard-schema/spec@1.0.0': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@22.19.1': + dependencies: + undici-types: 6.21.0 + + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.56.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + eslint: 9.39.1 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.56.1(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.56.1(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.1 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.56.1': {} + + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.56.1(eslint@9.39.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.1))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@22.19.1) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + cac@6.7.14: {} + + callsites@3.1.0: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + check-error@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + detect-libc@1.0.3: {} + + effect@3.19.19: + dependencies: + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 + + effect@3.19.6: + dependencies: + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 + + es-module-lexer@1.7.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escape-string-regexp@4.0.0: {} + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + expect-type@1.2.2: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-my-way-ts@0.1.6: {} + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fsevents@2.3.3: + optional: true + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime@3.0.0: {} + + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + ms@2.1.3: {} + + multipasta@0.2.7: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + node-addon-api@7.1.1: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + pathe@1.1.2: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@3.8.3: {} + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + resolve-from@4.0.0: {} + + rollup@4.53.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 + fsevents: 2.3.3 + + semver@7.7.4: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.56.1(eslint@9.39.1)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.1)(typescript@5.9.3) + eslint: 9.39.1 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + undici@6.22.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-node@2.1.9(@types/node@22.19.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@22.19.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@22.19.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.53.3 + optionalDependencies: + '@types/node': 22.19.1 + fsevents: 2.3.3 + + vitest@2.1.9(@types/node@22.19.1): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.1)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.2.2 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@22.19.1) + vite-node: 2.1.9(@types/node@22.19.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.1 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + ws@8.18.3: {} + + yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..cc4082d --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@effect-patterns/analysis-core': true + '@parcel/watcher': true + esbuild: true diff --git a/run-lsp.sh b/run-lsp.sh new file mode 100755 index 0000000..1653558 --- /dev/null +++ b/run-lsp.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cd /home/user/git/effect-template && exec pnpm exec typescript-language-server --stdio "$@" diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..1dec7c1 --- /dev/null +++ b/setup.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +echo "Setting up new Effect project..." + +# Install all dependencies (including @effect/eslint-plugin, typescript, etc.) +echo "Installing dependencies..." +pnpm install + +# Pre-fetch the effect-mcp server so it runs instantly the first time +echo "Pre-caching effect-mcp MCP server..." +npx -y effect-mcp@latest --version >/dev/null 2>&1 || true + +echo "Setting up git remotes..." + +# Check if the 'template' remote already exists to avoid errors +if ! git remote | grep -q "^template$"; then + git remote add template git@git.ewynn.in:ada/effect-template.git + echo "Template remote added successfully." +else + echo "Template remote already exists." +fi + +echo "Setup complete! The project is ready to use." diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..4038046 --- /dev/null +++ b/src/README.md @@ -0,0 +1,18 @@ +# Source Directory + +This is the root of the application source code. + +## Structure + +The current codebase still contains an older layer-first layout. +The preferred direction for new work is the context-first structure described in the docs: + +- **`contexts/`**: first-class bounded contexts +- **`workflows/`**: top-level cross-context orchestration +- **`shared/`**: tiny ubiquitous primitives only +- **`tasks/`**: optional cross-context workflow steps + +Legacy folders such as `domain/`, `policies/`, `adapters/`, `registries/`, and `layers/` may still exist during the transition. +Treat them as migration residue rather than the target architecture. + +For detailed documentation, see [docs/reference/directory-layout.md](../docs/reference/directory-layout.md). diff --git a/src/adapters/platform/README.md b/src/adapters/platform/README.md new file mode 100644 index 0000000..a1a3ef7 --- /dev/null +++ b/src/adapters/platform/README.md @@ -0,0 +1,16 @@ +# Platform Adapters + +**Content**: Infrastructure implementations of domain interfaces. + +## Rules +1. **Implementation**: Implements the interfaces defined in `src/domain/interfaces/`. +2. **Impure**: Contains the actual I/O (SQL, Fetch, File System). +3. **Dumb**: Should not contain business rules. Just executes the command. + +## Example + +```typescript +export const StripeAdapter = Layer.succeed(PaymentRepo, { + charge: (amount) => ... +}) +``` diff --git a/src/domain/interfaces/README.md b/src/domain/interfaces/README.md new file mode 100644 index 0000000..2f62835 --- /dev/null +++ b/src/domain/interfaces/README.md @@ -0,0 +1,21 @@ +# Domain Interfaces + +**Content**: Service Contracts (Ports). + +## Rules +1. **Definition**: Define the capability using `Context.Tag`. +2. **No Implementation**: Do not include implementation details (SQL, HTTP calls) here. +3. **Naming**: Use names describing the *capability*, not the tool (e.g., `PaymentRepo`, not `StripeClient`). + +## Example + +```typescript +import { Context, Effect } from "effect" + +export class PaymentRepo extends Context.Tag("PaymentRepo")< + PaymentRepo, + { + readonly charge: (amount: number) => Effect.Effect + } +>() {} +``` diff --git a/src/domain/models/README.md b/src/domain/models/README.md new file mode 100644 index 0000000..ba20790 --- /dev/null +++ b/src/domain/models/README.md @@ -0,0 +1,20 @@ +# Domain Models + +**Content**: Data Schemas (Types) and Pure Logic. + +## Rules +1. **Co-location**: Define the Schema and the pure functions that operate on it in the same file. +2. **Purity**: Functions here must be 100% pure. No side effects. +3. **Scope**: Functions here should primarily operate on the entity defined in the file. + +## Example (`Cart.ts`) + +```typescript +import { Schema } from "@effect/schema" + +export const Cart = Schema.Struct({ ... }) +export type Cart = Schema.Schema.Type + +// Pure Logic +export const isEmpty = (cart: Cart): boolean => cart.items.length === 0 +``` diff --git a/src/layers/Main.layer.ts b/src/layers/Main.layer.ts new file mode 100644 index 0000000..83a38f2 --- /dev/null +++ b/src/layers/Main.layer.ts @@ -0,0 +1,3 @@ +import { Layer } from "effect"; + +export const MainLayer = Layer.empty; diff --git a/src/layers/README.md b/src/layers/README.md new file mode 100644 index 0000000..c4adca4 --- /dev/null +++ b/src/layers/README.md @@ -0,0 +1,18 @@ +# Layers (Dependency Injection) + +**Content**: Static Application Wiring. + +## Usage +Construct the final application layers here. This is where you decide: +"In Production, use Postgres. In Test, use InMemory." + +## Example + +```typescript +// Main.layer.ts +export const MainLayer = Layer.mergeAll( + PostgresLive, + StripeLive, + // ... +) +``` diff --git a/src/lib/README.md b/src/lib/README.md new file mode 100644 index 0000000..6d25f25 --- /dev/null +++ b/src/lib/README.md @@ -0,0 +1,11 @@ +# Generic Libraries + +**Content**: Internal tools with NO domain knowledge. + +## Usage +Put things here that you could theoretically publish to npm as a standalone package. +- Neo4j Client wrappers +- Custom Git utilities +- Date formatting helpers (generic) + +If it imports anything from `src/domain`, it does NOT belong here. diff --git a/src/policies/README.md b/src/policies/README.md new file mode 100644 index 0000000..c02d75a --- /dev/null +++ b/src/policies/README.md @@ -0,0 +1,17 @@ +# Policies + +**Content**: Business Rules and Decision Logic. + +## Rules +1. **Purity**: Must be pure. +2. **Context**: Can accept multiple entities and configuration to make a decision. +3. **Output**: Returns a Result or a Decision (Enum/Strategy), not a side effect. + +## Example + +```typescript +export type PaymentStrategy = 'RETAIL' | 'WHOLESALE' + +export const determinePaymentStrategy = (amount: number): PaymentStrategy => + amount < 1000 ? 'RETAIL' : 'WHOLESALE' +``` diff --git a/src/registries/README.md b/src/registries/README.md new file mode 100644 index 0000000..db281d6 --- /dev/null +++ b/src/registries/README.md @@ -0,0 +1,14 @@ +# Registries + +**Content**: Dynamic Runtime Selection Logic. + +## Usage +Use this ONLY when you need to select an implementation at runtime based on data (Strategy Pattern). +For static wiring (e.g., "Always use Postgres in Prod"), use `src/layers/`. + +## Example + +```typescript +export const getPaymentAdapter = (strategy: 'RETAIL' | 'WHOLESALE') => + strategy === 'RETAIL' ? StripeAdapter : BankAdapter +``` diff --git a/src/workflows/README.md b/src/workflows/README.md new file mode 100644 index 0000000..dd51d67 --- /dev/null +++ b/src/workflows/README.md @@ -0,0 +1,18 @@ +# Workflows + +**Content**: Orchestration Scripts (Transaction Scripts). + +## Rules +1. **Orchestration**: Calls Domain Interfaces, Policies, and other Workflows. +2. **Impure**: This is where side effects happen (via the Interfaces). +3. **Composition**: Use `Effect.gen` to compose steps linearly. + +## Example + +```typescript +export const checkout = (cart: Cart) => Effect.gen(function*(_) { + const strategy = Policy.determinePaymentStrategy(cart.total) + const paymentSvc = yield* _(PaymentRegistry.get(strategy)) + yield* _(paymentSvc.charge(cart.total)) +}) +``` diff --git a/test/sanity.test.ts b/test/sanity.test.ts new file mode 100644 index 0000000..d3021f1 --- /dev/null +++ b/test/sanity.test.ts @@ -0,0 +1,9 @@ +import { describe, expect, it } from "vitest" + +import { MainLayer } from "../src/layers/Main.layer.js" + +describe("template sanity", () => { + it("exports the main layer", () => { + expect(MainLayer).toBeDefined() + }) +}) diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..8f4ec58 --- /dev/null +++ b/todo.md @@ -0,0 +1,25 @@ +# Project Improvement Plan + +## 1. Organize Documentation +- [x] Solidify the documentation structure (Concepts, Guides, Reference, etc.) +- [x] Determine the target audience for different doc sections (Human vs. AI) +- [x] Refine the TDFDDD process documentation +- [x] Move/rename existing files to fit the new structure + +## 2. Create the Golden Example +- [ ] Implement a full feature (e.g., Full Shipping Company) demonstrating TDFDDD + Effect +- [ ] Show pure `Schema.brand` primitives +- [ ] Show Discriminated Unions for State +- [ ] Show pure `decide` Policy returning `Either`/`Result` +- [ ] Show `workflow` using `Effect.gen` and `Match.value` +- [ ] Add tests for the example + +## 3. Deployment Pipeline Standards +- [ ] Setup CI/CD pipeline (e.g., GitHub Actions) +- [ ] Add `pnpm typecheck`, `test`, `lint` steps +- [ ] Add a basic `Dockerfile` for easy deployment + +## 4. Effect Best Practices & Tooling +- [x] Integrate `@effect/eslint-plugin` and configure rules +- [x] Create a "Review Agent" skill to evaluate code for Effect/TDFDDD best practices +- [x] Ensure type-inspectors and linters are properly hooked up diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8dbca3a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "exactOptionalPropertyTypes": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "skipLibCheck": true, + "outDir": "dist", + "sourceMap": true, + "lib": ["ES2022", "DOM"] + }, + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..9b0b38f --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + include: ["**/*.test.ts"], + globals: true + } +})