@ooneex/cli 1.47.0 → 1.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21766,7 +21766,7 @@ var api_issue_fixer_md_default = `---
21766
21766
  name: api-issue-fixer
21767
21767
  description: Implements a single planned issue in a backend API module (\`type: "api"\`) \u2014 controllers and routes with correct HTTP semantics (status codes, request/response DTOs & validation, pagination, roles/permissions), plus the supporting services, repositories, entities, and migrations \u2014 following Clean Architecture, then lints, satisfies the Definition of Done, and marks the issue Done. Use proactively whenever a \`type: "api"\` issue needs implementing.
21768
21768
  tools: Read, Edit, Write, Bash, Grep, Glob, Skill
21769
- model: inherit
21769
+ model: opus
21770
21770
  memory: project
21771
21771
  ---
21772
21772
 
@@ -21885,7 +21885,7 @@ var api_issue_founder_md_default = `---
21885
21885
  name: api-issue-founder
21886
21886
  description: Audits a backend API module's source for issues \u2014 HTTP/REST contract design, status codes, request/response DTOs and validation, versioning, pagination, rate limiting, auth/authz on routes, error-response shape, and contract consistency \u2014 plus the standard backend categories (Security, Performance, Architecture, Missing Tests, Improvement, Code Quality, Database). Use proactively whenever a \`type: "api"\` module needs review. It only finds and reports \u2014 it never writes issue files or runs oo commands.
21887
21887
  tools: Read, Grep, Glob
21888
- model: inherit
21888
+ model: opus
21889
21889
  memory: project
21890
21890
  ---
21891
21891
 
@@ -21963,7 +21963,7 @@ var design_issue_fixer_md_default = `---
21963
21963
  name: design-issue-fixer
21964
21964
  description: Implements a single planned issue in a front-end design-system module (\`type: "design"\`) \u2014 components, hooks, icons, fonts, styles, and utils organized by asset kind \u2014 then lints, satisfies the Definition of Done, and marks the issue Done. Use proactively whenever a \`type: "design"\` issue needs implementing.
21965
21965
  tools: Read, Edit, Write, Bash, Grep, Glob, Skill
21966
- model: inherit
21966
+ model: opus
21967
21967
  memory: project
21968
21968
  ---
21969
21969
 
@@ -22064,7 +22064,7 @@ var design_issue_founder_md_default = `---
22064
22064
  name: design-issue-founder
22065
22065
  description: Audits a front-end module's source for Design (UI/UX) issues \u2014 accessibility, contrast, responsiveness, design-system consistency, interaction states, localization, layout stability, and messaging \u2014 and returns the findings. Use proactively whenever a module's UI needs a design review, and especially when the /issue:found skill audits the Design category. It only finds and reports \u2014 it never writes issue files or runs oo commands.
22066
22066
  tools: Read, Grep, Glob
22067
- model: inherit
22067
+ model: opus
22068
22068
  memory: project
22069
22069
  ---
22070
22070
 
@@ -22130,7 +22130,7 @@ var microservice_issue_fixer_md_default = `---
22130
22130
  name: microservice-issue-fixer
22131
22131
  description: Implements a single planned issue in a microservice module (\`type: "microservice"\`) \u2014 services, controllers, repositories, entities, migrations, and event/message handlers \u2014 with attention to idempotency, resilience, message contracts, and observability, following Clean Architecture, then lints, satisfies the Definition of Done, and marks the issue Done. Use proactively whenever a \`type: "microservice"\` issue needs implementing.
22132
22132
  tools: Read, Edit, Write, Bash, Grep, Glob, Skill
22133
- model: inherit
22133
+ model: opus
22134
22134
  memory: project
22135
22135
  ---
22136
22136
 
@@ -22258,7 +22258,7 @@ var microservice_issue_founder_md_default = `---
22258
22258
  name: microservice-issue-founder
22259
22259
  description: Audits a microservice module's source for issues \u2014 service boundaries and data ownership, message/event contracts, idempotency, retries/timeouts/circuit breakers, distributed-transaction/saga handling, eventual consistency, health checks, graceful shutdown, config/secrets, and observability \u2014 plus the standard backend categories (Security, Performance, Architecture, Missing Tests, Improvement, Code Quality, Database). Use proactively whenever a \`type: "microservice"\` module needs review. It only finds and reports \u2014 it never writes issue files or runs oo commands.
22260
22260
  tools: Read, Grep, Glob
22261
- model: inherit
22261
+ model: opus
22262
22262
  memory: project
22263
22263
  ---
22264
22264
 
@@ -22346,7 +22346,7 @@ var module_issue_fixer_md_default = `---
22346
22346
  name: module-issue-fixer
22347
22347
  description: Implements a single planned issue in a backend business-domain module (\`type: "module"\` or untyped) \u2014 entities, repositories, services, controllers/commands, migrations, and optional resources \u2014 following Clean Architecture, then lints, satisfies the Definition of Done, and marks the issue Done. Use proactively whenever a backend \`module\` issue needs implementing, and especially when the /issue:fix skill dispatches a backend module.
22348
22348
  tools: Read, Edit, Write, Bash, Grep, Glob, Skill
22349
- model: inherit
22349
+ model: opus
22350
22350
  memory: project
22351
22351
  ---
22352
22352
 
@@ -22483,7 +22483,7 @@ var module_issue_founder_md_default = `---
22483
22483
  name: module-issue-founder
22484
22484
  description: Audits a backend business-domain module's source for issues across Security, Performance, Architecture (Clean Architecture), Missing Tests, Improvement, Code Quality, and Database \u2014 and returns the findings. Use proactively whenever a \`type: "module"\` (or untyped) backend module needs review, and especially when the /issue:found skill audits a backend module. It only finds and reports \u2014 it never writes issue files or runs oo commands.
22485
22485
  tools: Read, Grep, Glob
22486
- model: inherit
22486
+ model: opus
22487
22487
  memory: project
22488
22488
  ---
22489
22489
 
@@ -22564,7 +22564,7 @@ var spa_issue_fixer_md_default = `---
22564
22564
  name: spa-issue-fixer
22565
22565
  description: Implements a single planned issue in a front-end SPA module (\`type: "spa"\`) \u2014 a TanStack Router + TanStack Query app organized as vertical feature slices (routes, features, shared) \u2014 then lints, satisfies the Definition of Done, and marks the issue Done. Use proactively whenever a \`type: "spa"\` issue needs implementing.
22566
22566
  tools: Read, Edit, Write, Bash, Grep, Glob, Skill
22567
- model: inherit
22567
+ model: opus
22568
22568
  memory: project
22569
22569
  ---
22570
22570
 
@@ -22671,7 +22671,7 @@ var spa_issue_founder_md_default = `---
22671
22671
  name: spa-issue-founder
22672
22672
  description: Audits a front-end module's source for SPA (client-side) issues \u2014 unhandled async states, route guards, render performance, state mutation, effect lifecycles, shared state, API error handling, optimistic-update rollback, code-splitting, and navigation behavior \u2014 and returns the findings. Use proactively whenever a module's client-side behavior needs review, and especially when the /issue:found skill audits the SPA category. It only finds and reports \u2014 it never writes issue files or runs oo commands.
22673
22673
  tools: Read, Grep, Glob
22674
- model: inherit
22674
+ model: opus
22675
22675
  memory: project
22676
22676
  ---
22677
22677
 
@@ -22758,29 +22758,15 @@ var AGENTS_md_default = `# AGENTS.md
22758
22758
 
22759
22759
  Guidance for AI coding agents working in this repository.
22760
22760
 
22761
- This file holds only the always-on **rules**. Detailed reference and task
22762
- workflows live in conditionally-activated **skills** (see the index below) so
22763
- this file stays lean and avoids loading unused context.
22761
+ This file is a lean index. Coding conventions, reference, and task workflows
22762
+ all live in conditionally-activated **skills** (see the index below) so this
22763
+ file stays small and avoids loading unused context. Reach for a skill when you
22764
+ need the detail behind a task.
22764
22765
 
22765
22766
  ## Project Overview
22766
22767
 
22767
22768
  {{NAME}} is a modular, enterprise-grade TypeScript/Bun backend powered by the **@ooneex** ecosystem. Code lives in independent modules under \`modules/\`, each owning its controllers, services, repositories, entities, migrations, and seeds.
22768
22769
 
22769
- ## Core Rules
22770
-
22771
- These apply to every change. Reach for a skill when you need the detail behind one.
22772
-
22773
- - **Prefer @ooneex packages** over third-party alternatives, and inject their services via the DI container rather than instantiating them. Full catalog: the \`ooneex:packages\` skill.
22774
- - **Dependency Injection** \u2014 every DI class is registered with a decorator (InversifyJS via \`@ooneex/container\`) and uses the matching suffix: Services \u2192 \`@decorator.service()\` / \`Service\`; Repositories \u2192 \`@decorator.repository()\` / \`Repository\`; Middlewares \u2192 \`@decorator.middleware()\` / \`Middleware\`; Crons \u2192 \`@decorator.cron()\` / \`Cron\`; Controllers \u2192 controller-specific decorators. Breaking these throws \`ContainerException\` at startup. Use constructor injection via \`@inject()\`.
22775
- - **Environment variables** \u2014 never read \`process.env\` directly. Inject \`AppEnv\` from \`@ooneex/app-env\` and read typed properties.
22776
- - **Exceptions** \u2014 throw typed exceptions extending \`Exception\` from \`@ooneex/exception\` (HTTP status + structured data) instead of returning \`null\` or error codes.
22777
- - **TypeScript** \u2014 strict mode with \`noUncheckedIndexedAccess\` + \`exactOptionalPropertyTypes\`, decorators (\`emitDecoratorMetadata\`), ESNext modules with bundler resolution, target ES2022.
22778
- - **Naming** \u2014 types end with \`Type\`; interfaces start with \`I\`.
22779
- - **Arrow functions** everywhere except class methods.
22780
- - **Visibility** \u2014 explicit \`public\` / \`private\` / \`protected\` on every method and property.
22781
- - **No non-null assertions** \u2014 use defaults or optional types.
22782
- - **Hygiene** \u2014 no unused imports, dead code, or bare \`TODO\` comments.
22783
-
22784
22770
  ## Skills
22785
22771
 
22786
22772
  Skills load on demand based on the task \u2014 invoke or let them activate when relevant; do not duplicate their content here.
@@ -22936,12 +22922,13 @@ describe("<Name>Ai", () => {
22936
22922
  });
22937
22923
  \`\`\`
22938
22924
 
22939
- ### 4. Lint and format
22925
+ ### 4. Lint, format, and test
22940
22926
 
22941
22927
  \`\`\`bash
22942
- bun run fmt
22943
- bun run lint
22928
+ bun run fmt && bun run lint && bun run test
22944
22929
  \`\`\`
22930
+
22931
+ Fix every failure before completing.
22945
22932
  `;
22946
22933
 
22947
22934
  // src/templates/llm/skills/analytics.create.md.txt
@@ -23037,12 +23024,13 @@ describe("<Name>Analytics", () => {
23037
23024
  });
23038
23025
  \`\`\`
23039
23026
 
23040
- ### 4. Lint and format
23027
+ ### 4. Lint, format, and test
23041
23028
 
23042
23029
  \`\`\`bash
23043
- bun run fmt
23044
- bun run lint
23030
+ bun run fmt && bun run lint && bun run test
23045
23031
  \`\`\`
23032
+
23033
+ Fix every failure before completing.
23046
23034
  `;
23047
23035
 
23048
23036
  // src/templates/llm/skills/cache.create.md.txt
@@ -23182,12 +23170,13 @@ describe("<Name>Cache", () => {
23182
23170
  });
23183
23171
  \`\`\`
23184
23172
 
23185
- ### 4. Lint and format
23173
+ ### 4. Lint, format, and test
23186
23174
 
23187
23175
  \`\`\`bash
23188
- bun run fmt
23189
- bun run lint
23176
+ bun run fmt && bun run lint && bun run test
23190
23177
  \`\`\`
23178
+
23179
+ Fix every failure before completing.
23191
23180
  `;
23192
23181
 
23193
23182
  // src/templates/llm/skills/command.create.md.txt
@@ -23326,12 +23315,13 @@ describe("<Name>Command", () => {
23326
23315
  });
23327
23316
  \`\`\`
23328
23317
 
23329
- ### 4. Lint and format
23318
+ ### 4. Lint, format, and test
23330
23319
 
23331
23320
  \`\`\`bash
23332
- bun run fmt
23333
- bun run lint
23321
+ bun run fmt && bun run lint && bun run test
23334
23322
  \`\`\`
23323
+
23324
+ Fix every failure before completing.
23335
23325
  `;
23336
23326
 
23337
23327
  // src/templates/llm/skills/commit.md.txt
@@ -23641,13 +23631,14 @@ describe("<Name>Controller", () => {
23641
23631
 
23642
23632
  Add \`<Name>Controller\` to the \`controllers\` array in \`src/<PascalModuleName>Module.ts\` (see \`ooneex:scaffold\` for the \`ModuleType\` shape).
23643
23633
 
23644
- ### 7. Lint and format
23634
+ ### 7. Lint, format, and test
23645
23635
 
23646
23636
  \`\`\`bash
23647
- bun run fmt
23648
- bun run lint
23637
+ bun run fmt && bun run lint && bun run test
23649
23638
  \`\`\`
23650
23639
 
23640
+ Fix every failure before completing.
23641
+
23651
23642
  ### 8. Create the service
23652
23643
 
23653
23644
  \`\`\`
@@ -24013,12 +24004,13 @@ describe("<Name>Cron", () => {
24013
24004
 
24014
24005
  Add \`<Name>Cron\` to the \`cronJobs\` array in \`src/<PascalModuleName>Module.ts\` (see \`ooneex:scaffold\` for the \`ModuleType\` shape).
24015
24006
 
24016
- ### 5. Lint and format
24007
+ ### 5. Lint, format, and test
24017
24008
 
24018
24009
  \`\`\`bash
24019
- bun run fmt
24020
- bun run lint
24010
+ bun run fmt && bun run lint && bun run test
24021
24011
  \`\`\`
24012
+
24013
+ Fix every failure before completing.
24022
24014
  `;
24023
24015
 
24024
24016
  // src/templates/llm/skills/database.create.md.txt
@@ -24143,12 +24135,13 @@ describe("<Name>Database", () => {
24143
24135
  });
24144
24136
  \`\`\`
24145
24137
 
24146
- ### 4. Lint and format
24138
+ ### 4. Lint, format, and test
24147
24139
 
24148
24140
  \`\`\`bash
24149
- bun run fmt
24150
- bun run lint
24141
+ bun run fmt && bun run lint && bun run test
24151
24142
  \`\`\`
24143
+
24144
+ Fix every failure before completing.
24152
24145
  `;
24153
24146
 
24154
24147
  // src/templates/llm/skills/entity.create.md.txt
@@ -24483,12 +24476,13 @@ describe("<Name>Entity", () => {
24483
24476
 
24484
24477
  Add \`<Name>Entity\` to the \`entities\` array in \`src/<PascalModuleName>Module.ts\` (see \`ooneex:scaffold\` for the \`ModuleType\` shape).
24485
24478
 
24486
- ### 5. Lint and format
24479
+ ### 5. Lint, format, and test
24487
24480
 
24488
24481
  \`\`\`bash
24489
- bun run fmt
24490
- bun run lint
24482
+ bun run fmt && bun run lint && bun run test
24491
24483
  \`\`\`
24484
+
24485
+ Fix every failure before completing.
24492
24486
  `;
24493
24487
 
24494
24488
  // src/templates/llm/skills/flag.create.md.txt
@@ -24599,12 +24593,13 @@ describe("<Name>FeatureFlag", () => {
24599
24593
  });
24600
24594
  \`\`\`
24601
24595
 
24602
- ### 4. Lint and format
24596
+ ### 4. Lint, format, and test
24603
24597
 
24604
24598
  \`\`\`bash
24605
- bun run fmt
24606
- bun run lint
24599
+ bun run fmt && bun run lint && bun run test
24607
24600
  \`\`\`
24601
+
24602
+ Fix every failure before completing.
24608
24603
  `;
24609
24604
 
24610
24605
  // src/templates/llm/skills/issue.fix.md.txt
@@ -24717,6 +24712,10 @@ Infer which modules the user wants audited from their input \u2014 this may be o
24717
24712
 
24718
24713
  ## Workflow
24719
24714
 
24715
+ ### 0. Switch to Plan Mode
24716
+
24717
+ Before doing anything else, switch to **plan mode**. This skill only reads and audits source code \u2014 it never writes YAML, creates issue files, or runs \`oo\` directly \u2014 so the whole audit must run as a read-only investigation. Stay in plan mode through steps 1\u20132 (resolving modules and delegating to founders); the hand-off to \`/issue:plan\` in step 3 is where any actual issue creation happens.
24718
+
24720
24719
  ### 1. Resolve the Modules to Audit from the User Input
24721
24720
 
24722
24721
  The user does **not** have to pass explicit flags \u2014 infer the target modules from whatever they provide. The input may name one or more modules. Resolve it into a concrete list of modules to audit:
@@ -24784,7 +24783,7 @@ Once every resolved module has been audited, report a summary covering the whole
24784
24783
  `;
24785
24784
 
24786
24785
  // src/templates/llm/skills/issue.plan.md.txt
24787
- var issue_plan_md_default = "---\nname: issue:plan\ndescription: Create and plan one or more YAML issues from the user's input. Infers the target issues and modules from whatever the user says \u2014 existing issue files/IDs and/or free-form descriptions, across one or more modules. For each description it first scaffolds the issue with oo issue:create (inferring the module name from the input), then plans it \u2014 restructuring into context, goal, definition of done, and dependencies, extracting labels, and optionally splitting into ordered sub-issues sharing that same structure. Reads from / writes to modules/<module>/issues/<ID>.yml.\n---\n\n# Issue Plan\n\nInfer which issues the user wants planned from their input \u2014 this may be one or more issues spread across one or more modules \u2014 and plan each one. Each input item is **either** an existing issue (ID or path) **or** a free-form description of work to do. Either way the result is a planned issue: restructured into `context` / `goal` / `dod` / `dependencies`, with suggested labels, set state and priority, and optionally broken into ordered, self-contained sub-issues (same structure) that read as a step-by-step implementation guide.\n\n## Workflow\n\n### 0. Resolve the Targets and Mode\n\nThe user does **not** have to plan a single issue. Infer the full set of issues to plan from their input \u2014 it may name several issues across several modules, mixing existing issues with new descriptions. Resolve the input into a list of targets, each tagged with its mode:\n\n- **Plan mode** \u2014 an existing issue ID (e.g. `OON-123456`) or a path to a `.yml` file under `modules/<module>/issues/`. If an ID is given without a module, glob `modules/*/issues/<ID>.yml` to find the owning module; if several match, plan each. Goes straight to step 1.\n- **Create mode** \u2014 a free-form description of work with no existing issue. Run step 0a to scaffold it first, then continue to step 1 to plan it.\n\nSplitting the input into targets:\n- Multiple IDs/paths (repeated, comma-separated, or listed) each become a plan-mode target.\n- A free-form description that covers **distinct, unrelated pieces of work \u2014 especially work spanning different modules** \u2014 becomes **one create-mode target per piece** (e.g. \"add an org create API and a billing settings page\" \u2192 a backend issue in the relevant module + a spa issue in another). Keep tightly related work as a single target; step 4 decides whether to split it into sub-issues.\n- When it's unclear whether a fragment is one issue or several, prefer fewer targets and let the splitting step (4\u20135) break them down; only ask the user when the grouping genuinely can't be inferred.\n\nWhen unclear whether a fragment is an existing issue or a description, treat anything that isn't a recognizable issue ID or existing file path as a description (create mode).\n\nBuild the full target list first, then run steps 0a\u20136 for **each** target in turn. If the input resolves to no targets at all, tell the user nothing matched and stop.\n\n### 0a. Create Mode \u2014 Scaffold the Issue First\n\n**Always run commands from the monorepo root**, not from inside individual packages.\n\nRun this for each create-mode target. Derive these fields from that target's slice of the description \u2014 infer reasonable values, ask only when a required value genuinely can't be inferred:\n\n| Field | Default | How to derive |\n|-------|---------|---------------|\n| `title` | \u2014 (required) | Concise, action-oriented (verb + noun). Use the user's wording; never invent. |\n| `module` | `shared` | **Infer from the input** \u2014 match domain nouns in the description to a module under `modules/` (e.g. \"user profile\" \u2192 `user`, \"checkout\" \u2192 `order`). Verify the module exists; if no match, default to `shared` and say so. |\n| `priority` | inferred (see step 3) | Infer from the description; honor any explicitly stated priority. |\n| `labels` | `[]` | Suggest from the description (see step 3 vocabulary); `/issue:plan` refines them below. |\n| `description` | `null` | The user's free-form text, as-is \u2014 the planning steps structure it. |\n\n`state` is **always** `Todo` at creation \u2014 never ask for it. The planning steps move it to `Planned`.\n\nRun:\n\n```bash\noo issue:create \\\n --title=\"<title>\" \\\n --module=<module> \\\n --state=\"Todo\" \\\n --priority=\"<priority>\" \\\n [--labels=\"<label1>,<label2>\"] \\\n [--description=\"<description>\"]\n```\n\nThis writes a YAML skeleton to `modules/<module>/issues/<ID>.yml` (`<ID>` auto-generated, e.g. `ABC-012345`). Note the created `<ID>` and `<module>`, then continue to step 1 to plan the issue you just created.\n\n### 1. Locate the Issue File and Module Type\n\nFor the current target \u2014 plan mode: use its resolved issue ID/path (if a plan-mode target's file can't be found, record the exact path checked, skip it, and continue with the remaining targets). Create mode: use the file just scaffolded in step 0a. Read `modules/<module>/issues/<ID>.yml` (default module: `shared`) with the Read tool.\n\nIf an existing (plan-mode) issue's `state` is already `Planned`, it has already been planned \u2014 skip it (do not re-plan or restructure it) and continue with the remaining targets. Note the skip for the step 7 summary. (Newly scaffolded create-mode issues are always `Todo`, so this never skips them.)\n\nThen read the module's config at `modules/<module>/<module>.yml` to find its `type`. This decides which technical vocabulary the `goal` uses (see **Technical Structure by Module Type** below):\n- `type: \"module\"`, `\"api\"`, `\"microservice\"` (or no `type`) \u2014 a backend module \u2192 `### Data Model` with TypeORM relations.\n- `type: \"spa\"` \u2014 a front-end single-page application \u2192 `### Front-End Structure` with features/routes/layouts/hooks/services.\n- `type: \"design\"` \u2014 a front-end design system \u2192 `### Design System Structure` with components/hooks/icons/styles.\n\n### 2. Restructure the Parent Issue\n\nReplace the parent's free-form `description` with the **same four fields used by sub-issues**, so the parent is structurally identical to a sub-issue:\n\n- `context` \u2014 relevant background and why the issue exists\n- `goal` \u2014 the concrete work to do, including any **Technical Notes** (constraints, hints) and the technical subsection that matches the module type (`### Data Model`, `### Front-End Structure`, or `### Design System Structure` \u2014 see **Technical Structure by Module Type**), when applicable\n- `dod` \u2014 acceptance criteria as checkboxes (`- [ ]`), all of which must be satisfied\n- `dependencies` \u2014 issue IDs that must be completed first (usually `[]` for a standalone parent)\n\nRules:\n- Preserve all factual information from the original description; keep fields concise and actionable.\n- `dod` items are checkboxes, never prose. When a `dod` item covers a data model, add indented sub-checkboxes per field: ` - [ ] \\`fieldName\\` \u2014 <description>`.\n- `dod` descriptions are plain English outcomes \u2014 no implementation syntax. For backend entities: `` `type` \u2014 b2b | school | internal `` (not `ENUM(...)`); `` `createdAt` \u2014 Created date `` (not `TIMESTAMPTZ via @CreateDateColumn`); `` `packs` \u2014 One organization has many packs `` (not the `@OneToMany` decorator). For spa/design: `` Profile page renders the user's avatar and name `` (not `<UserAvatar/> in features/user/components`).\n- Use the entity name, not an ID suffix: `` `address` \u2014 User has one address `` (not `addressId`); `` `organization` \u2014 Membership belongs to one organization `` (not `organizationId`).\n- Implementation specifics (TypeORM decorators, component/file paths, hook names) appear only in the `goal` field's technical subsection, never in `dod`.\n- This step only applies when the issue is **not** split \u2014 when split, the parent file is deleted (step 5) and its intent lives entirely in the sub-issues.\n\n### 3. Extract Labels, Set State and Priority\n\n**Labels** \u2014 Suggest relevant labels: short (1\u20133 words), Title Case for general terms, uppercase for acronyms. Deduplicate against labels already in the YAML. Common vocabulary (use these exact casings): `Feature`, `Bug`, `Improvement`, `Enhancement`, `Performance`, `Refactor`, `Security`, `Breaking Change`, `Documentation`, `Testing`, `Database`, `API`, `UI`, `Infrastructure`, `Cleanup`.\n\n**State** \u2014 Valid: `Todo`, `Planned`, `In Progress`, `Done`. This skill produces a plan, so always set `state: \"Planned\"` (on every sub-issue when split, or the parent when not). `Planned` means ready to pick up \u2014 `/issue:fix` takes it from there. Never set any other state.\n\n**Priority** \u2014 Always set/confirm `priority`. Valid: `Urgent`, `High`, `Medium`, `Low`. Infer from the title and description rather than asking:\n- `Urgent` \u2014 outages, security vulnerabilities, data loss, broken builds, blockers (\"critical\", \"asap\", \"broken\", \"down\", \"vulnerability\").\n- `High` \u2014 important bugs/features users are waiting on, regressions, time-sensitive work.\n- `Medium` \u2014 standard features, improvements, non-blocking bugs (fallback when no signal points elsewhere).\n- `Low` \u2014 nice-to-haves, polish, refactors, docs, chores.\n\nHonor an explicitly stated priority over the inferred value.\n\n### 4. Check Whether Splitting Is Needed\n\nSplit when the issue: spans multiple unrelated concerns/areas, can't be done in one focused session, or has several independent acceptance criteria that could ship separately. Skip splitting if it's already small and focused.\n\n### 5. Plan the Sub-Issues (if needed)\n\nBreak the issue into 3\u20137 small, self-contained, independently implementable sub-issues that read as an ordered implementation guide. For each:\n- Generate an ID in the format `XXX-000000` (3 uppercase letters + 6 digits).\n- Write a new YAML file to the same `modules/<module>/issues/` directory.\n- Inherit `priority` and `labels` from the parent; set `state: \"Planned\"`.\n- Order by implementation sequence, expressed through `dependencies`.\n\nEach sub-issue uses the same four fields as the parent (`context` scoped to the sub-issue plus how it fits the larger plan; `goal`; `dod`; `dependencies` = sub-issue IDs to complete first, `[]` when none).\n\nWhen a sub-issue needs implementation detail, the `goal` includes the technical subsection matching the module type (see **Technical Structure by Module Type** for the full vocabulary of each). For backend modules that means a `### Data Model` subsection listing every relation with the exact field name on the owning entity, the TypeORM decorator, and the inverse field / FK-or-join-table owner:\n\n```\n- `EntityA.fieldName` \u2192 `@OneToMany(() => EntityB, (b) => b.a)` \u2014 one A has many Bs\n- `EntityB.fieldName` \u2192 `@ManyToOne(() => EntityA, (a) => a.bs)` \u2014 many Bs belong to one A\n- `EntityA.fieldName` \u2192 `@ManyToMany(() => EntityB)` + `@JoinTable()` \u2014 pivot table owned by A\n- `EntityA.fieldName` \u2192 `@OneToOne(() => EntityB)` + `@JoinColumn()` \u2014 one-to-one, FK on A\n```\n\nFor spa modules use `### Front-End Structure` and for design modules use `### Design System Structure`, listing the concrete files/folders to add or change under that module's conventions.\n\nAfter writing all sub-issues, **delete the parent issue file** \u2014 the sub-issues fully replace it. First confirm every piece of the parent's intent is carried into at least one sub-issue's `context`, `goal`, or `dod`. List each created sub-issue (ID and title) so the user sees what replaced the parent.\n\nSub-issue YAML structure:\n```yaml\nid: \"<generated-id>\"\ntitle: \"<action-oriented title: verb + noun>\"\nstate: \"Planned\"\npriority: \"<parent priority>\"\nlabels:\n - \"<label>\"\ncontext: |\n <Details needed to understand this sub-issue \u2014 2\u20133 sentences scoped to it>\ngoal: |\n <What this sub-issue specifically achieves>\n\n ## Technical Notes\n <Optional \u2014 omit if not applicable>\n\n ### Data Model | Front-End Structure | Design System Structure\n <Subsection matching the module type \u2014 omit if not applicable>\ndod: |\n - [ ] <Condition 1>\n - [ ] <\u2026>\ndependencies:\n - \"<id of a sub-issue to complete first>\"\n```\n\n### 6. Save Changes\n\n- **If split:** write all sub-issue files, then `rm modules/<module>/issues/<ID>.yml` (Bash). Confirm each sub-issue written and the parent removed, showing relative paths.\n- **If not split:** rewrite the parent YAML with `context`/`goal`/`dod`/`dependencies` (replacing `description`) plus new labels, via Edit or Write. Confirm with the relative path.\n\n### 7. Confirm the Batch\n\nOnce every resolved target has been planned, report a summary covering the whole batch. For **each** issue: its `id`, `title`, module, mode (created vs. planned-in-place), final `priority`/`labels`, and whether it was split (listing the sub-issue IDs/titles that replaced it). Then list any targets that were skipped or could not be planned (e.g. an existing issue already in `Planned` state, a plan-mode file not found with the exact path checked, or an ambiguous grouping awaiting confirmation).\n\n## YAML Structure Reference\n\nParent issue when not split \u2014 same structure as a sub-issue, plus any existing `comments`:\n```yaml\nid: \"OON-123456\"\ntitle: \"Add user validation\"\nstate: \"Planned\"\npriority: \"High\"\nlabels:\n - \"Enhancement\"\n - \"API\"\ncontext: |\n <Details needed to understand it>\ngoal: |\n <What to do>\n\n ## Technical Notes\n <Optional \u2014 omit if not applicable>\n\n ### Data Model | Front-End Structure | Design System Structure\n <Subsection matching the module type \u2014 omit if not applicable>\ndod: |\n - [ ] <Condition 1>\n - [ ] <\u2026>\ndependencies: []\ncomments:\n - author: \"Alice\"\n message: \"Some comment\"\n```\n\n## Technical Structure by Module Type\n\nThe `goal` field's technical subsection follows the conventions of the module the issue lives in (from step 1's `<module>.yml` `type`). Use exactly one of the three, matching the type; omit it for issues with no structural component (pure bug fix, copy change, chore).\n\n### Backend module (`type: \"module\"`, `\"api\"`, `\"microservice\"`, or no `type`) \u2014 `### Data Model`\n\nThe module owns controllers, services, repositories, entities, migrations, and seeds under `src/`. List TypeORM relations with the exact field name on the owning entity, the decorator, and the inverse field / FK-or-join-table owner (see the block in step 5). Reference services, repositories, controllers, and DI by their `@ooneex` conventions; entities register in `SharedModule`.\n\n### SPA module (`type: \"spa\"`) \u2014 `### Front-End Structure`\n\nA front-end single-page app (TanStack Router + TanStack Query), **not** registered into `AppModule`/`SharedModule`. Code is organized as vertical slices \u2014 name the concrete files/folders to add or change:\n\n- `src/routes/<kebab>.tsx` \u2014 file-based route mapping to a URL; keep thin, delegate UI to features and data to services.\n- `src/features/<feature>/` \u2014 self-contained slice owning its `assets/`, `components/`, `hooks/` (data fetching / API calls / local UI state), `layouts/`, `services/` (the only layer that talks to the backend), `store/` (client state), `styles/`, `types/`, `utils/`. A feature must not import another feature's internals \u2014 promote anything shared to `src/shared/`.\n- `src/shared/<sub-layer>/` \u2014 the only place \u22652 features may import from in common (same sub-layout as a feature).\n\nWhen the work is a new feature, note that `oo spa:feature:create --name <Name> --module <module>` (skill `/spa:feature:create`) scaffolds the route, the page/skeleton/error/not-found layouts under `features/<feature>/layouts/`, and example query (`useGet<Name>`) + mutation (`useUpdate<Name>`) hooks under `features/<feature>/hooks/`. Describe the feature, its route path, the layouts, and the query/mutation hooks needed. Spell hooks as `useGet<Name>` / `useUpdate<Name>` and components/layouts in PascalCase.\n\n### Design module (`type: \"design\"`) \u2014 `### Design System Structure`\n\nA front-end design system (reusable UI primitives), **not** registered into `AppModule`/`SharedModule`. Code under `src/` is organized by asset kind \u2014 name the concrete files/folders to add or change:\n\n- `src/components/<component>/` \u2014 one folder per component grouping its variants (e.g. `button/` holds `Button.tsx`, `ButtonSave.tsx`, \u2026). Compose existing primitives instead of ad-hoc markup.\n- `src/hooks/` \u2014 generic presentation-layer hooks (state, DOM measurement, events); no domain/data-fetching logic.\n- `src/icons/` \u2014 SVG icons in `fill/` + `outline/` variants, grouped by category and size (`sm`, `md`, `lg`); add to the matching category folder, never inline SVG.\n- `src/fonts/` \u2014 bundled web fonts with their `@font-face` CSS; no external CDNs.\n- `src/styles/` \u2014 global stylesheets (`app.css`, `brand.css`, `typography.css`, \u2026) for app-wide tokens/themes; prefer shared styles + component-scoped classes over one-off CSS.\n- `src/utils/` \u2014 small pure presentation helpers (`cn`, `staleChunk`); no backend/business logic.\n\n## Notes\n\n- Never invent facts \u2014 only restructure and clarify what is already in the issue.\n- If the original description is missing or empty, tell the user and stop.\n- Only delete the parent after every sub-issue file is written \u2014 never leave the plan with neither parent nor sub-issues.\n- Keep dependencies acyclic \u2014 a sub-issue must never (directly or transitively) depend on itself.\n- When the batch spans related issues, wire `dependencies` across them \u2014 if one resolved issue must be implemented before another (even in a different module), reference the prerequisite's ID so `/issue:fix` picks them up in order.\n- Process targets independently \u2014 one target failing (missing file, empty description) skips only that target; continue planning the rest and report the skips in step 7.\n";
24786
+ var issue_plan_md_default = "---\nname: issue:plan\ndescription: Create and plan one or more YAML issues from the user's input. Infers target issues and modules from whatever the user says \u2014 existing issue files/IDs and/or free-form descriptions, across one or more modules. For each description it scaffolds the issue with oo issue:create (inferring the module), then plans it \u2014 restructuring into context, goal, definition of done, and dependencies, extracting labels, and optionally splitting into ordered sub-issues with the same structure. Reads/writes modules/<module>/issues/<ID>.yml.\n---\n\n# Issue Plan\n\nInfer which issues to plan from the user's input \u2014 one or more issues across one or more modules. Each input item is **either** an existing issue (ID or path) **or** a free-form description. Either way, the result is a planned issue: restructured into `context` / `goal` / `dod` / `dependencies`, labelled, with state and priority set, and optionally broken into ordered, self-contained sub-issues (same structure) that read as a step-by-step implementation guide.\n\n## Workflow\n\n### Switch to Plan Mode First\n\nBefore anything else, switch to **plan mode** (Claude Code's read-only mode \u2014 distinct from this skill's create/plan *target* modes in step 0). Do all investigation there: resolve targets, read existing issue files and module configs, work out how each issue is restructured, labelled, and split. Present the plan, then **exit plan mode to execute** \u2014 repo writes (`oo issue:create`, rewriting/Editing YAML, deleting a parent on split) only happen after you leave plan mode.\n\n### 0. Resolve the Targets and Mode\n\nThe input may name several issues across several modules, mixing existing issues with new descriptions. Resolve it into a list of targets, each tagged with its mode:\n\n- **Plan mode** \u2014 an existing issue ID (e.g. `OON-123456`) or path to a `.yml` under `modules/<module>/issues/`. If an ID is given without a module, glob `modules/*/issues/<ID>.yml`; plan each match. Goes straight to step 1.\n- **Create mode** \u2014 a free-form description with no existing issue. Run step 0a to scaffold, then step 1 to plan.\n\nSplitting input into targets:\n- Multiple IDs/paths (repeated, comma-separated, or listed) each become a plan-mode target.\n- A description covering **distinct, unrelated work \u2014 especially work spanning different modules** \u2014 becomes **one create-mode target per piece** (e.g. \"add an org create API and a billing settings page\" \u2192 a backend issue + a spa issue in another module). Keep tightly related work as one target; step 4 decides whether to split it.\n- When it's unclear whether a fragment is one issue or several, prefer fewer targets and let steps 4\u20135 break them down; only ask when the grouping genuinely can't be inferred.\n- Treat anything that isn't a recognizable issue ID or existing file path as a description (create mode).\n\nBuild the full target list first, then run steps 0a\u20136 for **each** target. If no targets resolve, tell the user nothing matched and stop.\n\n### 0a. Create Mode \u2014 Scaffold the Issue First\n\n**Always run commands from the monorepo root.** Run this per create-mode target. Derive fields from that target's slice of the description \u2014 infer reasonable values, ask only when a required value genuinely can't be inferred:\n\n| Field | Default | How to derive |\n|-------|---------|---------------|\n| `title` | \u2014 (required) | Concise, action-oriented (verb + noun). Use the user's wording; never invent. |\n| `module` | `shared` | **Infer from input** \u2014 match domain nouns to a module under `modules/` (e.g. \"user profile\" \u2192 `user`, \"checkout\" \u2192 `order`). Verify it exists; if no match, default to `shared` and say so. |\n| `priority` | inferred (step 3) | Infer from the description; honor any stated priority. |\n| `labels` | `[]` | Suggest from the description (step 3 vocabulary); refined below. |\n| `description` | `null` | The user's free-form text, as-is \u2014 planning steps structure it. |\n\n`state` is **always** `Todo` at creation \u2014 never ask. Planning moves it to `Planned`.\n\n```bash\noo issue:create \\\n --title=\"<title>\" \\\n --module=<module> \\\n --state=\"Todo\" \\\n --priority=\"<priority>\" \\\n [--labels=\"<label1>,<label2>\"] \\\n [--description=\"<description>\"]\n```\n\nThis writes a YAML skeleton to `modules/<module>/issues/<ID>.yml` (`<ID>` auto-generated, e.g. `ABC-012345`). Note the `<ID>` and `<module>`, then continue to step 1.\n\n### 1. Locate the Issue File and Module Type\n\nFor the current target \u2014 plan mode: use its resolved ID/path (if not found, record the exact path checked, skip it, continue with the rest). Create mode: use the file scaffolded in 0a. Read `modules/<module>/issues/<ID>.yml` (default module: `shared`).\n\nIf a plan-mode issue's `state` is already `Planned`, it's done \u2014 skip it (don't re-plan), note the skip for step 7, continue. (Create-mode issues are always `Todo`, so this never skips them.)\n\nThen read the module config `modules/<module>/<module>.yml` for its `type`, which decides the `goal`'s technical vocabulary (see **Technical Structure by Module Type**):\n- `type: \"module\"`, `\"api\"`, `\"microservice\"` (or none) \u2014 backend \u2192 `### Data Model` with TypeORM relations.\n- `type: \"spa\"` \u2014 front-end SPA \u2192 `### Front-End Structure` (features/routes/layouts/hooks/services).\n- `type: \"design\"` \u2014 design system \u2192 `### Design System Structure` (components/hooks/icons/styles).\n\n### 2. Restructure the Parent Issue\n\nReplace the free-form `description` with the **same four fields used by sub-issues**, so the parent is structurally identical to one:\n\n- `context` \u2014 relevant background and why the issue exists.\n- `goal` \u2014 concrete work to do, including any **Technical Notes** (constraints, hints) and the technical subsection matching the module type (`### Data Model` / `### Front-End Structure` / `### Design System Structure`), when applicable.\n- `dod` \u2014 acceptance criteria as checkboxes (`- [ ]`), all required.\n- `dependencies` \u2014 issue IDs to complete first (usually `[]` for a standalone parent).\n\nRules:\n- Preserve all factual information from the original; keep fields concise and actionable.\n- `dod` items are checkboxes, never prose. For data models, add indented sub-checkboxes per field: ` - [ ] \\`fieldName\\` \u2014 <description>`.\n- `dod` descriptions are plain-English outcomes \u2014 no implementation syntax. Backend: `` `type` \u2014 b2b | school | internal `` (not `ENUM(...)`); `` `createdAt` \u2014 Created date `` (not `TIMESTAMPTZ via @CreateDateColumn`); `` `packs` \u2014 One organization has many packs `` (not `@OneToMany`). SPA/design: `` Profile page renders the user's avatar and name `` (not `<UserAvatar/> in features/user/components`).\n- Use the entity name, not an ID suffix: `` `address` \u2014 User has one address `` (not `addressId`); `` `organization` \u2014 Membership belongs to one organization `` (not `organizationId`).\n- Implementation specifics (decorators, file paths, hook names) appear only in `goal`'s technical subsection, never in `dod`.\n- This step applies only when **not** split \u2014 when split, the parent is deleted (step 5) and its intent lives in the sub-issues.\n\n### 3. Extract Labels, Set State and Priority\n\n**Labels** \u2014 Suggest relevant labels: short (1\u20133 words), Title Case for general terms, uppercase for acronyms. Deduplicate against existing YAML labels. Vocabulary (exact casing): `Feature`, `Bug`, `Improvement`, `Enhancement`, `Performance`, `Refactor`, `Security`, `Breaking Change`, `Documentation`, `Testing`, `Database`, `API`, `UI`, `Infrastructure`, `Cleanup`.\n\n**State** \u2014 Valid: `Todo`, `Planned`, `In Progress`, `Done`. This skill produces a plan, so always set `state: \"Planned\"` (on every sub-issue when split, or the parent when not). `Planned` means ready to pick up \u2014 `/issue:fix` takes over. Never set any other state.\n\n**Priority** \u2014 Always set/confirm. Valid: `Urgent`, `High`, `Medium`, `Low`. Infer rather than ask:\n- `Urgent` \u2014 outages, security vulnerabilities, data loss, broken builds, blockers (\"critical\", \"asap\", \"broken\", \"down\", \"vulnerability\").\n- `High` \u2014 important bugs/features users await, regressions, time-sensitive work.\n- `Medium` \u2014 standard features, improvements, non-blocking bugs (fallback when no signal).\n- `Low` \u2014 nice-to-haves, polish, refactors, docs, chores.\n\nHonor an explicitly stated priority over the inferred value.\n\n### 4. Check Whether Splitting Is Needed\n\nSplit when the issue spans multiple unrelated concerns, can't be done in one focused session, or has several independent acceptance criteria that could ship separately. Skip if it's already small and focused.\n\n### 5. Plan the Sub-Issues (if needed)\n\nBreak into 3\u20137 small, self-contained, independently implementable sub-issues that read as an ordered guide. For each:\n- Generate an ID `XXX-000000` (3 uppercase letters + 6 digits).\n- Write a new YAML file to the same `modules/<module>/issues/` directory.\n- Inherit `priority` and `labels` from the parent; set `state: \"Planned\"`.\n- Order by implementation sequence, expressed through `dependencies`.\n\nEach sub-issue uses the same four fields (`context` scoped to it plus how it fits the larger plan; `goal`; `dod`; `dependencies` = sub-issue IDs to complete first, `[]` when none).\n\nWhen a sub-issue needs implementation detail, `goal` includes the technical subsection matching the module type (see **Technical Structure by Module Type**). For backend, `### Data Model` lists every relation with the exact field name on the owning entity, the TypeORM decorator, and the inverse field / FK-or-join-table owner:\n\n```\n- `EntityA.fieldName` \u2192 `@OneToMany(() => EntityB, (b) => b.a)` \u2014 one A has many Bs\n- `EntityB.fieldName` \u2192 `@ManyToOne(() => EntityA, (a) => a.bs)` \u2014 many Bs belong to one A\n- `EntityA.fieldName` \u2192 `@ManyToMany(() => EntityB)` + `@JoinTable()` \u2014 pivot table owned by A\n- `EntityA.fieldName` \u2192 `@OneToOne(() => EntityB)` + `@JoinColumn()` \u2014 one-to-one, FK on A\n```\n\nFor spa use `### Front-End Structure`, for design `### Design System Structure`, naming the concrete files/folders to add or change under that module's conventions.\n\nAfter writing all sub-issues, **delete the parent issue file** \u2014 the sub-issues fully replace it. First confirm every piece of the parent's intent is carried into at least one sub-issue's `context`, `goal`, or `dod`. List each created sub-issue (ID and title) so the user sees what replaced the parent.\n\nSub-issue YAML:\n```yaml\nid: \"<generated-id>\"\ntitle: \"<action-oriented title: verb + noun>\"\nstate: \"Planned\"\npriority: \"<parent priority>\"\nlabels:\n - \"<label>\"\ncontext: |\n <Details needed to understand this sub-issue \u2014 2\u20133 sentences scoped to it>\ngoal: |\n <What this sub-issue specifically achieves>\n\n ## Technical Notes\n <Optional \u2014 omit if not applicable>\n\n ### Data Model | Front-End Structure | Design System Structure\n <Subsection matching the module type \u2014 omit if not applicable>\ndod: |\n - [ ] <Condition 1>\n - [ ] <\u2026>\ndependencies:\n - \"<id of a sub-issue to complete first>\"\n```\n\n### 6. Save Changes\n\n- **If split:** write all sub-issue files, then `rm modules/<module>/issues/<ID>.yml` (Bash). Confirm each sub-issue written and the parent removed, with relative paths.\n- **If not split:** rewrite the parent YAML with `context`/`goal`/`dod`/`dependencies` (replacing `description`) plus new labels, via Edit or Write. Confirm with the relative path.\n\n### 7. Confirm the Batch\n\nOnce every target is planned, report a batch summary. Per issue: `id`, `title`, module, mode (created vs. planned-in-place), final `priority`/`labels`, and whether it was split (listing the sub-issue IDs/titles that replaced it). Then list any skipped or unplannable targets (already `Planned`, plan-mode file not found with the exact path checked, or an ambiguous grouping awaiting confirmation).\n\n## YAML Structure Reference\n\nParent issue when not split \u2014 same structure as a sub-issue, plus any existing `comments`:\n```yaml\nid: \"OON-123456\"\ntitle: \"Add user validation\"\nstate: \"Planned\"\npriority: \"High\"\nlabels:\n - \"Enhancement\"\n - \"API\"\ncontext: |\n <Details needed to understand it>\ngoal: |\n <What to do>\n\n ## Technical Notes\n <Optional \u2014 omit if not applicable>\n\n ### Data Model | Front-End Structure | Design System Structure\n <Subsection matching the module type \u2014 omit if not applicable>\ndod: |\n - [ ] <Condition 1>\n - [ ] <\u2026>\ndependencies: []\ncomments:\n - author: \"Alice\"\n message: \"Some comment\"\n```\n\n## Technical Structure by Module Type\n\nThe `goal`'s technical subsection follows the module's conventions (from step 1's `<module>.yml` `type`). Use exactly one of the three, matching the type; omit it for issues with no structural component (pure bug fix, copy change, chore).\n\n### Backend module (`type: \"module\"`, `\"api\"`, `\"microservice\"`, or none) \u2014 `### Data Model`\n\nThe module owns controllers, services, repositories, entities, migrations, and seeds under `src/`. List TypeORM relations with the exact field name on the owning entity, the decorator, and the inverse field / FK-or-join-table owner (see the block in step 5). Reference services, repositories, controllers, and DI by their `@ooneex` conventions; entities register in `SharedModule`.\n\n### SPA module (`type: \"spa\"`) \u2014 `### Front-End Structure`\n\nA front-end SPA (TanStack Router + TanStack Query), **not** registered into `AppModule`/`SharedModule`. Code is organized as vertical slices \u2014 name the concrete files/folders to add or change:\n\n- `src/routes/<kebab>.tsx` \u2014 file-based route mapping to a URL; keep thin, delegate UI to features and data to services.\n- `src/features/<feature>/` \u2014 self-contained slice owning its `assets/`, `components/`, `hooks/` (data fetching / API calls / local UI state), `layouts/`, `services/` (the only layer talking to the backend), `store/` (client state), `styles/`, `types/`, `utils/`. A feature must not import another feature's internals \u2014 promote shared code to `src/shared/`.\n- `src/shared/<sub-layer>/` \u2014 the only place \u22652 features may import from in common (same sub-layout as a feature).\n\nFor a new feature, note that `oo spa:feature:create --name <Name> --module <module>` (skill `/spa:feature:create`) scaffolds the route, the page/skeleton/error/not-found layouts under `features/<feature>/layouts/`, and example query (`useGet<Name>`) + mutation (`useUpdate<Name>`) hooks under `features/<feature>/hooks/`. Describe the feature, its route path, the layouts, and the hooks needed. Spell hooks as `useGet<Name>` / `useUpdate<Name>` and components/layouts in PascalCase.\n\n### Design module (`type: \"design\"`) \u2014 `### Design System Structure`\n\nA front-end design system (reusable UI primitives), **not** registered into `AppModule`/`SharedModule`. Code under `src/` is organized by asset kind \u2014 name the concrete files/folders to add or change:\n\n- `src/components/<component>/` \u2014 one folder per component grouping its variants (e.g. `button/` holds `Button.tsx`, `ButtonSave.tsx`, \u2026). Compose existing primitives instead of ad-hoc markup.\n- `src/hooks/` \u2014 generic presentation-layer hooks (state, DOM measurement, events); no domain/data-fetching logic.\n- `src/icons/` \u2014 SVG icons in `fill/` + `outline/` variants, grouped by category and size (`sm`, `md`, `lg`); add to the matching category folder, never inline SVG.\n- `src/fonts/` \u2014 bundled web fonts with their `@font-face` CSS; no external CDNs.\n- `src/styles/` \u2014 global stylesheets (`app.css`, `brand.css`, `typography.css`, \u2026) for app-wide tokens/themes; prefer shared styles + component-scoped classes over one-off CSS.\n- `src/utils/` \u2014 small pure presentation helpers (`cn`, `staleChunk`); no backend/business logic.\n\n## Notes\n\n- Never invent facts \u2014 only restructure and clarify what's already in the issue.\n- If the original description is missing or empty, tell the user and stop.\n- Only delete the parent after every sub-issue file is written \u2014 never leave the plan with neither parent nor sub-issues.\n- Keep dependencies acyclic \u2014 a sub-issue must never (directly or transitively) depend on itself.\n- When the batch spans related issues, wire `dependencies` across them \u2014 if one must be implemented before another (even in a different module), reference the prerequisite's ID so `/issue:fix` picks them up in order.\n- Process targets independently \u2014 one failing (missing file, empty description) skips only that target; continue with the rest and report skips in step 7.\n";
24788
24787
 
24789
24788
  // src/templates/llm/skills/logger.create.md.txt
24790
24789
  var logger_create_md_default = `---
@@ -24943,12 +24942,13 @@ describe("<Name>Logger", () => {
24943
24942
  });
24944
24943
  \`\`\`
24945
24944
 
24946
- ### 4. Lint and format
24945
+ ### 4. Lint, format, and test
24947
24946
 
24948
24947
  \`\`\`bash
24949
- bun run fmt
24950
- bun run lint
24948
+ bun run fmt && bun run lint && bun run test
24951
24949
  \`\`\`
24950
+
24951
+ Fix every failure before completing.
24952
24952
  `;
24953
24953
 
24954
24954
  // src/templates/llm/skills/mailer.create.md.txt
@@ -25131,12 +25131,13 @@ describe("<Name>MailerTemplate", () => {
25131
25131
  });
25132
25132
  \`\`\`
25133
25133
 
25134
- ### 5. Lint and format
25134
+ ### 5. Lint, format, and test
25135
25135
 
25136
25136
  \`\`\`bash
25137
- bun run fmt
25138
- bun run lint
25137
+ bun run fmt && bun run lint && bun run test
25139
25138
  \`\`\`
25139
+
25140
+ Fix every failure before completing.
25140
25141
  `;
25141
25142
 
25142
25143
  // src/templates/llm/skills/middleware.create.md.txt
@@ -25258,12 +25259,13 @@ describe("<Name>Middleware", () => {
25258
25259
 
25259
25260
  Add \`<Name>Middleware\` to the \`middlewares\` array in \`src/<PascalModuleName>Module.ts\` (see \`ooneex:scaffold\` for the \`ModuleType\` shape).
25260
25261
 
25261
- ### 5. Lint and format
25262
+ ### 5. Lint, format, and test
25262
25263
 
25263
25264
  \`\`\`bash
25264
- bun run fmt
25265
- bun run lint
25265
+ bun run fmt && bun run lint && bun run test
25266
25266
  \`\`\`
25267
+
25268
+ Fix every failure before completing.
25267
25269
  `;
25268
25270
 
25269
25271
  // src/templates/llm/skills/migration.create.md.txt
@@ -25394,12 +25396,13 @@ describe("Migration<version>", () => {
25394
25396
  });
25395
25397
  \`\`\`
25396
25398
 
25397
- ### 4. Lint and format
25399
+ ### 4. Lint, format, and test
25398
25400
 
25399
25401
  \`\`\`bash
25400
- bun run fmt
25401
- bun run lint
25402
+ bun run fmt && bun run lint && bun run test
25402
25403
  \`\`\`
25404
+
25405
+ Fix every failure before completing.
25403
25406
  `;
25404
25407
 
25405
25408
  // src/templates/llm/skills/ooneex.architecture.md.txt
@@ -25723,141 +25726,135 @@ var ooneex_scaffold_md_default = "---\nname: ooneex:scaffold\ndescription: Share
25723
25726
  // src/templates/llm/skills/optimize.md.txt
25724
25727
  var optimize_md_default = `---
25725
25728
  name: optimize
25726
- description: Optimize a module's codebase for quality, performance, and clean conventions. Enforces arrow functions (except class methods), type/interface naming, removes duplication, and ensures only important tests remain.
25729
+ description: Optimize a module's codebase for quality, performance, and clean conventions. Enforces arrow functions (except class methods), Type/I naming, explicit visibility, nullable columns; removes duplication and dead code; prunes trivial tests. Use to optimize/clean up/refactor a module \u2014 not for new features, bug fixes, or issues.
25727
25730
  ---
25728
25731
 
25729
25732
  # Optimize Codebase
25730
25733
 
25731
- Optimize a module's codebase for quality, performance, and clean conventions.
25734
+ Bring a module in line with project conventions: clean code, no duplication, only meaningful tests. Not for new features, bug fixes, or issues \u2014 use the matching workflow instead.
25732
25735
 
25733
- ## When to use
25736
+ ## Rules
25734
25737
 
25735
- Use this skill when:
25738
+ - Run every command from the **monorepo root**, never from inside a package.
25739
+ - Start clean: no uncommitted changes before you begin (\`git status\`). Refactor only \u2014 never alter behavior or public APIs without checking callers first.
25740
+ - Tests must pass before and after. If they were failing before, say so; don't claim a fix you didn't make.
25736
25741
 
25737
- - The user asks to optimize, clean up, or refactor a module's codebase.
25738
- - A module needs to be brought in line with the project conventions (arrow functions, \`Type\`/\`I\` naming, explicit visibility, nullable columns).
25739
- - You want to remove code duplication, dead code, or unused imports across a module.
25740
- - A module's tests need pruning \u2014 dropping trivial existence checks while keeping meaningful behavior, edge-case, and error-handling tests.
25741
- - React modules (\`design\`, \`spa\`) need to adopt the recommended patterns (custom hooks, compound components, Zustand, TanStack Query/Virtual/Pacer/Hotkeys).
25742
+ ## Routing \u2014 load on demand
25742
25743
 
25743
- Do **not** use this skill for adding new features, fixing a specific bug, or implementing an issue \u2014 use the relevant feature/issue workflow instead.
25744
+ Invoke each sub-skill only at the step that needs it; skip ones that don't apply.
25744
25745
 
25745
- ## Important
25746
+ | Invoke | Before | When |
25747
+ |---|---|---|
25748
+ | \`optimize:conventions\` | steps 3\u20135 | always |
25749
+ | \`optimize:testing\` | step 6 | the module has tests |
25750
+ | \`optimize:react\` | step 7 | module is \`design\` or \`spa\` only |
25746
25751
 
25747
- Always run all commands from the **root of the project** (the monorepo root), not from inside individual packages.
25752
+ ## Steps
25748
25753
 
25749
- ## Coding Conventions
25754
+ 1. **Target** \u2014 work in \`modules/<module>/\`; ask if unspecified.
25755
+
25756
+ 2. **Map (sub-agent)** \u2014 reading every file inline floods context. Spawn one read-only \`Explore\` sub-agent scoped to \`modules/<module>/\` and have it return *only* a digest, not file contents:
25757
+ - **Inventory** \u2014 each type, interface, class, standalone function + path.
25758
+ - **Naming violations** \u2014 type not ending \`Type\`; interface not starting \`I\`; non-arrow standalone function; method/property missing visibility; non-null assertion (\`!\`); optional entity property missing \`null\`/\`nullable\`.
25759
+ - **Duplication** \u2014 repeated logic, types, or utilities + paths.
25760
+ - **Dead code** \u2014 unused imports, unreachable branches, unused vars, empty files.
25761
+
25762
+ Apply every fix yourself in the steps below.
25763
+
25764
+ 3. **Conventions** \u2014 invoke the \`optimize:conventions\` skill, then fix each reported violation; rename and update all references.
25765
+
25766
+ 4. **Duplication & dead code** \u2014 extract shared logic into helper arrows or base classes; consolidate types; merge near-duplicate utilities; delete dead code.
25767
+
25768
+ 5. **Performance** \u2014 apply the performance rules from \`optimize:conventions\`.
25769
+
25770
+ 6. **Tests** \u2014 invoke \`optimize:testing\`, then prune trivial tests, keep/improve meaningful ones, consolidate redundancy.
25771
+
25772
+ 7. **React** \u2014 if \`design\`/\`spa\`, invoke \`optimize:react\` and adopt its patterns.
25773
+
25774
+ 8. **Verify** \u2014 from the root:
25775
+
25776
+ \`\`\`bash
25777
+ bun run fmt && bun run lint && bun run test
25778
+ \`\`\`
25779
+
25780
+ Fix every failure before completing.
25781
+ `;
25782
+
25783
+ // src/templates/llm/skills/optimize.conventions.md.txt
25784
+ var optimize_conventions_md_default = `---
25785
+ name: optimize:conventions
25786
+ description: Project coding conventions \u2014 explicit visibility, arrow functions vs class methods, Type/I naming (DI-enforced), no non-null assertions, nullable entity columns, DI wiring, code hygiene, duplication/dead-code removal, and performance rules. Use when enforcing conventions, refactoring, or reviewing a module's code style.
25787
+ ---
25788
+
25789
+ # Coding Conventions
25790
+
25791
+ Used by the \`optimize\` skill (steps 3\u20135) for enforcing conventions and removing duplication/dead code.
25750
25792
 
25751
- ### Visibility Modifiers
25793
+ ## Visibility
25752
25794
 
25753
- Always explicitly declare visibility on every class method and property.
25795
+ Declare explicit visibility (\`public\`/\`private\`/\`protected\`) on every class method and property.
25754
25796
 
25755
25797
  \`\`\`typescript
25756
- // correct
25757
25798
  export class UserService {
25758
25799
  private readonly repository: UserRepository;
25759
25800
  public async execute(data?: ServiceDataType): Promise<void> {}
25760
25801
  protected validate(): boolean {}
25761
25802
  }
25762
-
25763
- // incorrect \u2014 visibility is implicit
25764
- export class UserService {
25765
- repository: UserRepository;
25766
- async execute() {}
25767
- }
25768
25803
  \`\`\`
25769
25804
 
25770
- ### Functions vs Class Methods
25805
+ ## Arrow Functions vs Class Methods
25771
25806
 
25772
- Use arrow functions everywhere except class methods.
25807
+ Arrow functions everywhere, except class methods (use regular method syntax).
25773
25808
 
25774
25809
  \`\`\`typescript
25775
- // correct \u2014 arrow function for standalone function
25776
- const formatName = (name: string): string => name.trim();
25777
-
25778
- // correct \u2014 regular method syntax inside a class
25779
- export class UserService {
25780
- public async execute(data?: ServiceDataType): Promise<void> {
25781
- const formatted = formatName(data?.name ?? "");
25782
- }
25783
- }
25810
+ const formatName = (name: string): string => name.trim(); // standalone: arrow
25784
25811
 
25785
- // incorrect \u2014 arrow function as class method
25786
25812
  export class UserService {
25787
- public execute = async (data?: ServiceDataType): Promise<void> => {};
25813
+ public async execute(): Promise<void> {} // method: regular syntax, NOT \`execute = async () =>\`
25788
25814
  }
25789
25815
  \`\`\`
25790
25816
 
25791
- ### Type and Interface Naming
25817
+ ## Type & Interface Naming
25792
25818
 
25793
- - Type aliases **must** end with \`Type\`
25794
- - Interface names **must** start with \`I\`
25819
+ Type aliases **must** end with \`Type\`; interfaces **must** start with \`I\`. **Strictly enforced by DI decorators \u2014 violations throw at startup:**
25795
25820
 
25796
- \`\`\`typescript
25797
- // correct
25798
- type ServiceDataType = Record<string, unknown>;
25799
- interface IService { execute(): Promise<void>; }
25800
-
25801
- // incorrect
25802
- type ServiceData = Record<string, unknown>;
25803
- interface Service { execute(): Promise<void>; }
25804
- \`\`\`
25805
-
25806
- **Strictly enforced** by DI decorators \u2014 violations throw at startup:
25807
-
25808
- | Artefact | Must end/start with | Example |
25821
+ | Artefact | Convention | Example |
25809
25822
  |---|---|---|
25810
- | Service | \`Service\` | \`UserService\` |
25811
- | Repository | \`Repository\` | \`UserRepository\` |
25812
- | Middleware | \`Middleware\` | \`AuthMiddleware\` |
25813
- | Cron | \`Cron\` | \`ExpiredTokenCleanupCron\` |
25814
- | Type alias | \`Type\` | \`ServiceDataType\` |
25815
- | Interface | \`I\` (prefix) | \`IService\` |
25823
+ | Service | ends \`Service\` | \`UserService\` |
25824
+ | Repository | ends \`Repository\` | \`UserRepository\` |
25825
+ | Middleware | ends \`Middleware\` | \`AuthMiddleware\` |
25826
+ | Cron | ends \`Cron\` | \`ExpiredTokenCleanupCron\` |
25827
+ | Type alias | ends \`Type\` | \`ServiceDataType\` |
25828
+ | Interface | starts \`I\` | \`IService\` |
25816
25829
 
25817
- ### Non-null Assertions
25830
+ ## Non-null Assertions
25818
25831
 
25819
- Never use \`!\` on class properties. Use a default value or optional type instead.
25832
+ Never use \`!\` on class properties \u2014 use a default value or optional type.
25820
25833
 
25821
25834
  \`\`\`typescript
25822
- // correct
25823
25835
  export class UserEntity {
25824
- public name: string = "";
25836
+ public name: string = ""; // not \`name!: string\`
25825
25837
  public email?: string | null;
25826
25838
  }
25827
-
25828
- // incorrect
25829
- export class UserEntity {
25830
- public name!: string;
25831
- }
25832
25839
  \`\`\`
25833
25840
 
25834
- ### Optional Entity Properties
25841
+ ## Optional Entity Properties
25835
25842
 
25836
- - Optional properties (\`?\`) must include \`null\` in their type union
25837
- - Do NOT use \`= undefined\` as an initializer
25838
- - Always specify \`nullable\` explicitly in every \`@Column\`
25843
+ - Optional (\`?\`) types must include \`null\` in the union.
25844
+ - Never initialize with \`= undefined\`.
25845
+ - Always set \`nullable\` explicitly in every \`@Column\`.
25839
25846
 
25840
25847
  \`\`\`typescript
25841
- // correct
25842
25848
  export class BookEntity {
25843
25849
  @Column({ name: "title", type: "varchar", length: 255, nullable: false })
25844
25850
  public title: string = "";
25845
25851
 
25846
25852
  @Column({ name: "subtitle", type: "varchar", length: 255, nullable: true })
25847
- public subtitle?: string | null;
25848
- }
25849
-
25850
- // incorrect
25851
- export class BookEntity {
25852
- @Column({ name: "title", type: "varchar", length: 255 }) // nullable missing
25853
- public title: string = "";
25854
-
25855
- @Column({ name: "subtitle", type: "varchar", length: 255, nullable: true })
25856
- public subtitle?: string; // null missing from type union
25853
+ public subtitle?: string | null; // not \`subtitle?: string\`, and not \`nullable\` omitted
25857
25854
  }
25858
25855
  \`\`\`
25859
25856
 
25860
- ### Dependency Injection
25857
+ ## Dependency Injection
25861
25858
 
25862
25859
  \`\`\`typescript
25863
25860
  import { inject } from "@ooneex/container";
@@ -25871,25 +25868,44 @@ export class BookSeed implements ISeed {
25871
25868
  }
25872
25869
  \`\`\`
25873
25870
 
25874
- ### Code Hygiene
25871
+ ## Code Hygiene
25875
25872
 
25876
- - Remove all unused imports
25877
- - Remove dead code: unreachable branches, unused variables, empty files
25878
- - Never leave \`TODO\` comments without a corresponding task
25873
+ - Remove unused imports and dead code (unreachable branches, unused variables, empty files).
25874
+ - No \`TODO\` comments without a corresponding task.
25879
25875
 
25880
- ### Testing Conventions
25876
+ ## Duplication & Dead Code
25881
25877
 
25882
- - Test files mirror \`src/\` under \`tests/\` with the \`.spec.ts\` suffix.
25883
- - Run \`bun run test\` (all modules) or \`bun test tests\` (inside a module).
25884
- - Every public method with logic needs \u22651 happy-path + \u22651 edge-case test.
25885
- - Avoid trivial existence checks \u2014 test actual behavior.
25886
- - Keep tests deterministic: no random values, no time-dependent data.
25878
+ - Extract shared logic into helper arrow functions or base classes.
25879
+ - Consolidate repeated type definitions; merge similar utilities.
25880
+
25881
+ ## Performance
25882
+
25883
+ - Replace inefficient loops with single-pass approaches.
25884
+ - Use \`Map\`/\`Set\` instead of arrays for lookups.
25885
+ - Prefer early returns to reduce nesting.
25886
+ - Drop unnecessary \`async\`/\`await\` where a direct return suffices.
25887
+ - Eliminate redundant null/undefined checks.
25888
+ `;
25887
25889
 
25888
- ## React (design & spa modules)
25890
+ // src/templates/llm/skills/optimize.react.md.txt
25891
+ var optimize_react_md_default = `---
25892
+ name: optimize:react
25893
+ description: Recommended React patterns for design and spa modules \u2014 custom hooks, compound components, Zustand state, and TanStack Query/Virtual/Pacer/Hotkeys. Use only when building or optimizing a React module (design or spa).
25894
+ ---
25895
+
25896
+ # React Patterns (design & spa modules)
25897
+
25898
+ Apply **only** to a React module (\`design\` or \`spa\`) \u2014 \`optimize\` calls this for step 7.
25899
+
25900
+ Install missing deps at the project root:
25901
+
25902
+ \`\`\`bash
25903
+ bun add zustand @tanstack/react-query @tanstack/react-virtual @tanstack/react-pacer @tanstack/react-hotkeys
25904
+ \`\`\`
25889
25905
 
25890
- ### Hooks pattern
25906
+ ## Hooks
25891
25907
 
25892
- Extract stateful, reusable logic into custom hooks (\`useXxx\`) instead of duplicating it across components or reaching for HOCs/render props.
25908
+ Extract reusable stateful logic into custom hooks (\`useXxx\`) instead of duplicating it or using HOCs/render props.
25893
25909
 
25894
25910
  \`\`\`typescript
25895
25911
  import { useEffect, useState } from "react";
@@ -25907,13 +25923,12 @@ const useWindowWidth = (): number => {
25907
25923
  };
25908
25924
 
25909
25925
  // usage
25910
- const width = useWindowWidth();
25911
- const isMobile = width < 768;
25926
+ const isMobile = useWindowWidth() < 768;
25912
25927
  \`\`\`
25913
25928
 
25914
- ### Compound pattern
25929
+ ## Compound components
25915
25930
 
25916
- Build flexible components as a set of related parts that share implicit state via context, instead of passing many props to one monolith.
25931
+ Build flexible components as related parts sharing state via context, instead of one prop-heavy monolith.
25917
25932
 
25918
25933
  \`\`\`tsx
25919
25934
  import { createContext, useContext, useState } from "react";
@@ -25950,15 +25965,13 @@ Tabs.Panel = TabPanel;
25950
25965
  // usage
25951
25966
  <Tabs defaultValue="overview">
25952
25967
  <Tabs.Tab value="overview">Overview</Tabs.Tab>
25953
- <Tabs.Tab value="settings">Settings</Tabs.Tab>
25954
25968
  <Tabs.Panel value="overview">Overview content</Tabs.Panel>
25955
- <Tabs.Panel value="settings">Settings content</Tabs.Panel>
25956
25969
  </Tabs>;
25957
25970
  \`\`\`
25958
25971
 
25959
- ### State management \u2014 Zustand
25972
+ ## Global state \u2014 Zustand
25960
25973
 
25961
- Use Zustand for global/shared state. Keep stores small and colocated; expose selectors to avoid re-renders. https://zustand.docs.pmnd.rs/learn/getting-started/introduction
25974
+ Keep stores small and colocated; use selectors to avoid re-renders. https://zustand.docs.pmnd.rs/learn/getting-started/introduction
25962
25975
 
25963
25976
  \`\`\`typescript
25964
25977
  import { create } from "zustand";
@@ -25973,13 +25986,13 @@ const useCounterStore = create<CounterStoreType>((set) => ({
25973
25986
  increment: () => set((state) => ({ count: state.count + 1 })),
25974
25987
  }));
25975
25988
 
25976
- // select only what you need to minimize re-renders
25989
+ // select only what you need
25977
25990
  const count = useCounterStore((state) => state.count);
25978
25991
  \`\`\`
25979
25992
 
25980
- ### Data fetching \u2014 TanStack Query
25993
+ ## Server state \u2014 TanStack Query
25981
25994
 
25982
- Use TanStack Query for server state. Wrap each query/mutation in a custom hook. https://tanstack.com/query/latest
25995
+ Wrap each query/mutation in a custom hook. https://tanstack.com/query/latest
25983
25996
 
25984
25997
  \`\`\`typescript
25985
25998
  import { useQuery } from "@tanstack/react-query";
@@ -25997,9 +26010,9 @@ const useUsers = () =>
25997
26010
  const { data, isLoading, error } = useUsers();
25998
26011
  \`\`\`
25999
26012
 
26000
- ### Long lists \u2014 virtualization
26013
+ ## Long lists \u2014 TanStack Virtual
26001
26014
 
26002
- Use TanStack Virtual to render only visible rows in long lists. https://tanstack.com/virtual/latest
26015
+ Render only visible rows. https://tanstack.com/virtual/latest
26003
26016
 
26004
26017
  \`\`\`typescript
26005
26018
  import { useVirtualizer } from "@tanstack/react-virtual";
@@ -26014,21 +26027,21 @@ const virtualizer = useVirtualizer({
26014
26027
  virtualizer.getVirtualItems().map((item) => rows[item.index]);
26015
26028
  \`\`\`
26016
26029
 
26017
- ### Debounce, Throttle, Queue, Batch \u2014 TanStack Pacer
26030
+ ## Debounce / throttle / queue / batch \u2014 TanStack Pacer
26018
26031
 
26019
- Use TanStack Pacer for rate-limiting expensive work (search input, scroll handlers, API calls). https://tanstack.com/pacer/latest
26032
+ Rate-limit expensive work (search input, scroll handlers, API calls). https://tanstack.com/pacer/latest
26020
26033
 
26021
26034
  \`\`\`typescript
26022
26035
  import { useDebouncedValue } from "@tanstack/react-pacer";
26023
26036
 
26024
26037
  const [search, setSearch] = useState("");
26025
26038
  const [debouncedSearch] = useDebouncedValue(search, { wait: 300 });
26026
- // debouncedSearch updates 300ms after the user stops typing
26039
+ // updates 300ms after the user stops typing
26027
26040
  \`\`\`
26028
26041
 
26029
- ### Hotkeys (Shortcut, Mod, ...) \u2014 TanStack Hotkeys
26042
+ ## Keyboard shortcuts \u2014 TanStack Hotkeys
26030
26043
 
26031
- Use TanStack Hotkeys for keyboard shortcuts. https://tanstack.com/hotkeys/latest
26044
+ https://tanstack.com/hotkeys/latest
26032
26045
 
26033
26046
  \`\`\`typescript
26034
26047
  import { useHotkeys } from "@tanstack/react-hotkeys";
@@ -26039,72 +26052,31 @@ useHotkeys("mod+s", (event) => {
26039
26052
  save();
26040
26053
  });
26041
26054
  \`\`\`
26055
+ `;
26042
26056
 
26043
- If dependencies are not installed, install them in the root of the project using \`bun add\`:
26044
-
26045
- \`\`\`bash
26046
- bun add zustand @tanstack/react-query @tanstack/react-virtual @tanstack/react-pacer @tanstack/react-hotkeys
26047
- \`\`\`
26048
-
26049
- ## Steps
26050
-
26051
- ### 1. Identify the target
26052
-
26053
- Work in \`modules/<module>/\`. Ask the user if no module is specified.
26054
-
26055
- ### 2. Analyze the module
26056
-
26057
- Reading every \`src/**/*.ts\` and \`tests/**/*.ts\` file inline floods the conversation with file dumps you do not need to keep. Delegate this read-heavy mapping to a sub-agent and act on the digest it returns.
26058
-
26059
- Spawn an \`Explore\` sub-agent (read-only) scoped to \`modules/<module>/\`, asking it to map all types, interfaces, classes, functions, and their dependencies, and to return only:
26060
-
26061
- - **Inventory** \u2014 every type, interface, class, and standalone function with its file path.
26062
- - **Naming violations** \u2014 types not ending with \`Type\`, interfaces not starting with \`I\`, non-arrow standalone functions, methods/properties missing visibility modifiers, non-null assertions, optional entity properties missing \`null\`/\`nullable\`.
26063
- - **Duplication candidates** \u2014 repeated logic, type definitions, or utilities that could be consolidated, with file paths.
26064
- - **Dead code** \u2014 unused imports, unreachable branches, unused variables, empty files.
26065
-
26066
- The sub-agent returns the map, not the file contents \u2014 keep the main context clean. Apply every fix yourself in the steps below, where the full conventions above are in context.
26067
-
26068
- ### 3. Enforce conventions
26069
-
26070
- Apply every rule from [Coding Conventions](#coding-conventions) across all files \u2014 fix each violation the sub-agent reported, renaming and updating all references where naming changes.
26057
+ // src/templates/llm/skills/optimize.testing.md.txt
26058
+ var optimize_testing_md_default = `---
26059
+ name: optimize:testing
26060
+ description: Project testing conventions and test-pruning rules \u2014 tests mirror src/ under tests/ as .spec.ts, every public method needs happy-path + edge-case coverage, drop trivial existence checks, keep deterministic behavior tests, consolidate redundancy. Use when writing, pruning, or improving a module's tests.
26061
+ ---
26071
26062
 
26072
- ### 4. Remove duplication & dead code
26063
+ # Testing Conventions
26073
26064
 
26074
- Beyond the [Code Hygiene](#code-hygiene) cleanup:
26075
- - Extract shared logic into helper arrow functions or base classes
26076
- - Consolidate repeated type definitions
26077
- - Merge similar utility functions
26065
+ Apply when pruning or improving a module's tests (the \`optimize\` skill calls this for step 6).
26078
26066
 
26079
- ### 5. Optimize for performance
26067
+ ## Conventions
26080
26068
 
26081
- - Replace inefficient loops with single-pass approaches
26082
- - Use \`Map\`/\`Set\` instead of arrays for lookups
26083
- - Prefer early returns to reduce nesting
26084
- - Remove unnecessary \`async\`/\`await\` where a direct return suffices
26085
- - Eliminate redundant null/undefined checks
26069
+ - Test files mirror \`src/\` under \`tests/\` with the \`.spec.ts\` suffix.
26070
+ - Run \`bun run test\` (all modules) or \`bun test tests\` (inside a module).
26071
+ - Every public method with logic needs \u22651 happy-path + \u22651 edge-case test.
26072
+ - Avoid trivial existence checks \u2014 test actual behavior.
26073
+ - Keep tests deterministic: no random values, no time-dependent data.
26086
26074
 
26087
- ### 6. Optimize tests
26075
+ ## Pruning tests
26088
26076
 
26089
- Bring tests in line with [Testing Conventions](#testing-conventions), and:
26090
26077
  - Remove trivial tests (class name checks, method existence) unless they are smoke tests for generated code
26091
26078
  - Keep and improve tests that verify actual business logic, edge cases, error handling
26092
26079
  - Consolidate redundant test cases into parameterized patterns
26093
-
26094
- ### 7. Lint and format
26095
-
26096
- \`\`\`bash
26097
- bun run fmt
26098
- bun run lint
26099
- \`\`\`
26100
-
26101
- ### 8. Run tests
26102
-
26103
- \`\`\`bash
26104
- bun run test
26105
- \`\`\`
26106
-
26107
- Fix any failures before completing.
26108
26080
  `;
26109
26081
 
26110
26082
  // src/templates/llm/skills/permission.create.md.txt
@@ -26239,12 +26211,13 @@ describe("<Name>Permission", () => {
26239
26211
  });
26240
26212
  \`\`\`
26241
26213
 
26242
- ### 4. Lint and format
26214
+ ### 4. Lint, format, and test
26243
26215
 
26244
26216
  \`\`\`bash
26245
- bun run fmt
26246
- bun run lint
26217
+ bun run fmt && bun run lint && bun run test
26247
26218
  \`\`\`
26219
+
26220
+ Fix every failure before completing.
26248
26221
  `;
26249
26222
 
26250
26223
  // src/templates/llm/skills/pubsub.create.md.txt
@@ -26381,12 +26354,13 @@ describe("<Name>Event", () => {
26381
26354
 
26382
26355
  Add \`<Name>Event\` to the \`events\` array in \`src/<PascalModuleName>Module.ts\` (see \`ooneex:scaffold\` for the \`ModuleType\` shape).
26383
26356
 
26384
- ### 5. Lint and format
26357
+ ### 5. Lint, format, and test
26385
26358
 
26386
26359
  \`\`\`bash
26387
- bun run fmt
26388
- bun run lint
26360
+ bun run fmt && bun run lint && bun run test
26389
26361
  \`\`\`
26362
+
26363
+ Fix every failure before completing.
26390
26364
  `;
26391
26365
 
26392
26366
  // src/templates/llm/skills/repository.create.md.txt
@@ -26636,12 +26610,13 @@ describe("<Name>Repository", () => {
26636
26610
  });
26637
26611
  \`\`\`
26638
26612
 
26639
- ### 4. Lint and format
26613
+ ### 4. Lint, format, and test
26640
26614
 
26641
26615
  \`\`\`bash
26642
- bun run fmt
26643
- bun run lint
26616
+ bun run fmt && bun run lint && bun run test
26644
26617
  \`\`\`
26618
+
26619
+ Fix every failure before completing.
26645
26620
  `;
26646
26621
 
26647
26622
  // src/templates/llm/skills/sdk.create.md.txt
@@ -26794,12 +26769,13 @@ stream: (input: {
26794
26769
  },
26795
26770
  \`\`\`
26796
26771
 
26797
- ### 7. Lint and format
26772
+ ### 7. Lint, format, and test
26798
26773
 
26799
26774
  \`\`\`bash
26800
- bun run fmt
26801
- bun run lint
26775
+ bun run fmt && bun run lint && bun run test
26802
26776
  \`\`\`
26777
+
26778
+ Fix every failure before completing.
26803
26779
  `;
26804
26780
 
26805
26781
  // src/templates/llm/skills/seed.create.md.txt
@@ -26918,12 +26894,13 @@ describe("<Name>Seed", () => {
26918
26894
  });
26919
26895
  \`\`\`
26920
26896
 
26921
- ### 5. Lint and format
26897
+ ### 5. Lint, format, and test
26922
26898
 
26923
26899
  \`\`\`bash
26924
- bun run fmt
26925
- bun run lint
26900
+ bun run fmt && bun run lint && bun run test
26926
26901
  \`\`\`
26902
+
26903
+ Fix every failure before completing.
26927
26904
  `;
26928
26905
 
26929
26906
  // src/templates/llm/skills/service.create.md.txt
@@ -27027,16 +27004,17 @@ describe("<Name>Service", () => {
27027
27004
  });
27028
27005
  \`\`\`
27029
27006
 
27030
- ### 4. Lint and format
27007
+ ### 4. Lint, format, and test
27031
27008
 
27032
27009
  \`\`\`bash
27033
- bun run fmt
27034
- bun run lint
27010
+ bun run fmt && bun run lint && bun run test
27035
27011
  \`\`\`
27012
+
27013
+ Fix every failure before completing.
27036
27014
  `;
27037
27015
 
27038
27016
  // src/templates/llm/skills/spa.feature.create.md.txt
27039
- var spa_feature_create_md_default = "---\nname: spa:feature:create\ndescription: Generate a new SPA feature (route, layout, error/not-found/skeleton boundaries, and TanStack Query hooks), then complete the generated code. Use when adding a feature to a SPA module built on @tanstack/react-router and @tanstack/react-query.\n---\n\n# Make SPA Feature\n\nGenerate a SPA feature \u2014 a route, its page layout, the route's pending/error/not-found boundaries, and example TanStack Query read/write hooks \u2014 then complete the implementation. Follow the shared workflow in the `ooneex:scaffold` skill (run-from-root, `--name`/`--module` inference, lint/format, and coding conventions); this skill covers only the SPA-feature-specific parts.\n\n## Important\n\nA SPA feature only makes sense inside a **SPA module** (a module whose `type` is `spa` in its `<name>.yml`). Create the SPA first with `spa:create` if it does not exist. The SPA is a standalone Vite front-end: keep features focused on presentation and client state, and reach the backend through HTTP APIs rather than importing server modules. Reuse UI primitives from the linked `design` module instead of duplicating them.\n\n**Place code in the canonical SPA folders \u2014 do not invent new ones.** The `### SPA Structure` section of `AGENTS.md` is the source of truth. A feature folder (`features/<feature>/`) and `shared/` share the same fixed sub-layout, and every artefact has exactly one home:\n\n| Sub-folder | What goes there |\n|---|---|\n| `assets/` | Images/SVG/media used only by this feature |\n| `components/` | Presentational + container React components scoped to the feature |\n| `hooks/` | Feature React hooks \u2014 data fetching, API calls, local UI state |\n| `layouts/` | Layout wrappers arranging this feature's pages/sections |\n| `services/` | Feature business logic + API clients; the only layer that talks to the backend |\n| `store/` | Feature-local client state (signals/store slices), not server data |\n| `styles/` | CSS/style modules scoped to this feature |\n| `types/` | TypeScript types/interfaces for the feature's domain |\n| `utils/` | Pure helper functions used within the feature |\n\n**Split, don't cram.** As you complete a file, break it into the smallest sensible pieces and move each into its logical folder above rather than leaving everything inside one layout or hook. Reuse the existing folders the generator created and add sibling folders **only** from this fixed list when a piece needs one \u2014 never a new name. Anything reused by \u22652 features moves up to `shared/` (same sub-layout); never import another feature's internals.\n\n**One file per element.** Give every component, hook, service, store slice, and util its own `.ts`/`.tsx` file named after it. The **only** exceptions are `types/` and `styles/`: group those **by logic**, not one file per item \u2014 e.g. all of the feature's domain types in `types/<kebab>.ts` and its related style rules in `styles/<kebab>.css`, splitting into a second file only when a distinct concern warrants it.\n\n## Steps\n\n### 1. Infer the options from the request, then run the generator\n\nMap the user's request to the options below, then run:\n\n```bash\nbunx @ooneex/cli@latest spa:feature:create --name=<name> --module=<module>\n```\n\n**Inferring options from the user's request:**\n\n- `--name` \u2014 the feature name, taken from the screen or resource it represents (e.g., \"a settings page\" \u2192 `Settings`, \"the user profile feature\" \u2192 `UserProfile`). Pass it in any casing; the CLI normalizes to PascalCase and strips a trailing `Feature` or `Layout` suffix, so omit those suffixes.\n- `--module` \u2014 the target SPA module, inferred from phrasing like \"in the `dashboard` SPA\" or \"for the admin app\". There is **no default** \u2014 the generator prompts for it when omitted.\n- `--override` \u2014 pass when the request is to regenerate an existing feature. Without it, the generator prompts before overwriting and aborts if declined.\n\nAlso installs `@tanstack/react-query` if it is not already a dependency.\n\n**Files generated** (under `modules/<module>/src/`, with `<kebab>` the kebab-case feature name and `<Name>` the PascalCase name):\n\n- `routes/<kebab>.tsx` \u2014 the TanStack Router file route, wiring the layout and boundaries together\n- `features/<kebab>/layouts/<Name>Layout.tsx` \u2014 the page layout (the route `component`)\n- `features/<kebab>/layouts/<Name>SkeletonLayout.tsx` \u2014 the route `pendingComponent`\n- `features/<kebab>/layouts/<Name>ErrorLayout.tsx` \u2014 the route `errorComponent`\n- `features/<kebab>/layouts/<Name>NotFoundLayout.tsx` \u2014 the route `notFoundComponent`\n- `features/<kebab>/hooks/useGet<Name>.ts` \u2014 example TanStack Query read hook + query-key factory\n- `features/<kebab>/hooks/useUpdate<Name>.ts` \u2014 example TanStack Query mutation hook\n\n### 2. Resolve the linked design module and discover its components\n\nBefore writing any UI, find which design module this SPA builds on and what it offers \u2014 the layouts must be composed from its components, not styled from scratch.\n\n1. Read the SPA module's yml config at `modules/<module>/<module>.yml` and read the `design:` field. Its value is the kebab-case name of the design module (e.g. `design: \"ui\"`).\n - If there is **no** `design:` field, the SPA was created without a linked design module. Skip the design-specific guidance below and build the UI with plain elements (suggest the user run `oo spa:create --design <name>` or `oo design:create` if they want a shared design system).\n2. List the design module's source to learn the available primitives:\n\n ```bash\n ls modules/<design>/src/components 2>/dev/null && cat modules/<design>/src/index.ts 2>/dev/null\n ```\n\n Inspect the component files / barrel exports to see what exists (buttons, cards, inputs, layout primitives, etc.) and their props.\n3. Import design components through the module path alias \u2014 `@module/<design>/...`:\n\n ```typescript\n import { Button } from \"@module/<design>/components/Button\";\n // or, if the design module re-exports from a barrel:\n import { Button, Card } from \"@module/<design>\";\n ```\n\n Match the actual export style you found in step 2 (named exports per file vs. a barrel `index.ts`).\n\n### 3. Complete the route\n\nRead `modules/<module>/src/routes/<kebab>.tsx`, then:\n\n- Adjust the route path passed to `createFileRoute` if the feature should not live at `/<kebab>` (e.g. a nested or parameterized path like `/<kebab>/$id`).\n- Add a `loader` and `pendingMs` when the page needs data before it renders \u2014 the generated `pendingComponent`, `errorComponent`, and `notFoundComponent` exist to cover that loader's pending, failure, and `notFound()` states.\n- Keep the boundary components wired to the generated layouts.\n\n### 4. Complete the layouts\n\nRead each file in `modules/<module>/src/features/<kebab>/layouts/` and fill in the real UI, composing the design module's components (resolved in step 2) instead of re-implementing primitives. Keep the layouts as thin arrangers: extract every reusable piece into the canonical folders rather than inlining it.\n\n- `<Name>Layout` \u2014 the page content, arranged from design components and the feature's own components. It renders `children ?? <Outlet />`, so leave the `<Outlet />` in place if the route has children. Pull each presentational block out into `features/<kebab>/components/` (one component per file) and compose them here.\n- `<Name>SkeletonLayout` \u2014 a skeleton that mirrors the loaded layout's shape; reuse the design's skeleton/placeholder primitive if it has one, and keep the `aria-busy`/`aria-live` attributes for accessibility.\n- `<Name>ErrorLayout` \u2014 render a useful message from `error` using the design's alert/feedback components, and keep the `reset` retry action (wire it to the design's button).\n- `<Name>NotFoundLayout` \u2014 the empty/missing-resource state, using the design's empty-state primitives where available.\n\nAs you split (one file per element, except types/styles which group by logic):\n- presentational/container components \u2192 `features/<kebab>/components/` (one component per file)\n- feature-local client state \u2192 `features/<kebab>/store/` (one slice per file)\n- pure helpers (formatting, guards) \u2192 `features/<kebab>/utils/` (one helper per file)\n- domain types (props/view-model types) \u2192 grouped in `features/<kebab>/types/`\n- scoped styles \u2192 grouped in `features/<kebab>/styles/`\n- media \u2192 `features/<kebab>/assets/`\n\nIf the design module lacks a primitive you need, prefer adding it to the design module (`@module/<design>`) so it stays reusable, rather than styling it inline in the feature.\n\n### 5. Complete the hooks\n\nRead the two hooks in `modules/<module>/src/features/<kebab>/hooks/` and adapt the examples to the real resource, splitting each into its logical folder so the hook stays a thin TanStack Query wrapper:\n\n- Move the resource type (the placeholder `<Name>Type`) out of the hook into the feature's grouped types file (`features/<kebab>/types/<kebab>.ts`) and import it back.\n- Move the `fetch`/API-call functions (`get<Name>`, `update<Name>`) into an API client in `features/<kebab>/services/` \u2014 services are the only layer that talks to the backend. Keep one service file per element and have the hooks import these instead of calling `fetch` directly.\n- `useGet<Name>.ts` \u2014 keep only the `useQuery` wiring and the `<camel>Keys` query-key factory; extend the factory with any list/filtered keys the feature needs. The factory is the single source of truth for this feature's keys \u2014 reads and invalidations must go through it so they always agree.\n- `useUpdate<Name>.ts` \u2014 keep only the `useMutation` wiring; keep `onSuccess` seeding the cache via `setQueryData` then returning the `invalidateQueries` promise so the mutation stays pending until the refetch settles.\n- Forward the query's `signal` from the hook into the service call to `fetch` so in-flight requests cancel on unmount/refetch.\n\nAdd further hooks (lists, additional mutations) in the same folder following these patterns, keeping their fetch logic in `services/` and their types in `types/`.\n\n### 6. Lint and format\n\n```bash\nbun run fmt\nbun run lint\n```\n";
27017
+ var spa_feature_create_md_default = "---\nname: spa:feature:create\ndescription: Generate a new SPA feature (route, layout, error/not-found/skeleton boundaries, and TanStack Query hooks), then complete the generated code. Use when adding a feature to a SPA module built on @tanstack/react-router and @tanstack/react-query.\n---\n\n# Make SPA Feature\n\nGenerate a SPA feature \u2014 a route, its page layout, the route's pending/error/not-found boundaries, and example TanStack Query read/write hooks \u2014 then complete the implementation. Follow the shared workflow in the `ooneex:scaffold` skill (run-from-root, `--name`/`--module` inference, lint/format, and coding conventions); this skill covers only the SPA-feature-specific parts.\n\n## Important\n\nA SPA feature only makes sense inside a **SPA module** (a module whose `type` is `spa` in its `<name>.yml`). Create the SPA first with `spa:create` if it does not exist. The SPA is a standalone Vite front-end: keep features focused on presentation and client state, and reach the backend through HTTP APIs rather than importing server modules. Reuse UI primitives from the linked `design` module instead of duplicating them.\n\n**Place code in the canonical SPA folders \u2014 do not invent new ones.** The `### SPA Structure` section of `AGENTS.md` is the source of truth. A feature folder (`features/<feature>/`) and `shared/` share the same fixed sub-layout, and every artefact has exactly one home:\n\n| Sub-folder | What goes there |\n|---|---|\n| `assets/` | Images/SVG/media used only by this feature |\n| `components/` | Presentational + container React components scoped to the feature |\n| `hooks/` | Feature React hooks \u2014 data fetching, API calls, local UI state |\n| `layouts/` | Layout wrappers arranging this feature's pages/sections |\n| `services/` | Feature business logic + API clients; the only layer that talks to the backend |\n| `store/` | Feature-local client state (signals/store slices), not server data |\n| `styles/` | CSS/style modules scoped to this feature |\n| `types/` | TypeScript types/interfaces for the feature's domain |\n| `utils/` | Pure helper functions used within the feature |\n\n**Split, don't cram.** As you complete a file, break it into the smallest sensible pieces and move each into its logical folder above rather than leaving everything inside one layout or hook. Reuse the existing folders the generator created and add sibling folders **only** from this fixed list when a piece needs one \u2014 never a new name. Anything reused by \u22652 features moves up to `shared/` (same sub-layout); never import another feature's internals.\n\n**One file per element.** Give every component, hook, service, store slice, and util its own `.ts`/`.tsx` file named after it. The **only** exceptions are `types/` and `styles/`: group those **by logic**, not one file per item \u2014 e.g. all of the feature's domain types in `types/<kebab>.ts` and its related style rules in `styles/<kebab>.css`, splitting into a second file only when a distinct concern warrants it.\n\n## Steps\n\n### 1. Infer the options from the request, then run the generator\n\nMap the user's request to the options below, then run:\n\n```bash\nbunx @ooneex/cli@latest spa:feature:create --name=<name> --module=<module>\n```\n\n**Inferring options from the user's request:**\n\n- `--name` \u2014 the feature name, taken from the screen or resource it represents (e.g., \"a settings page\" \u2192 `Settings`, \"the user profile feature\" \u2192 `UserProfile`). Pass it in any casing; the CLI normalizes to PascalCase and strips a trailing `Feature` or `Layout` suffix, so omit those suffixes.\n- `--module` \u2014 the target SPA module, inferred from phrasing like \"in the `dashboard` SPA\" or \"for the admin app\". There is **no default** \u2014 the generator prompts for it when omitted.\n- `--override` \u2014 pass when the request is to regenerate an existing feature. Without it, the generator prompts before overwriting and aborts if declined.\n\nAlso installs `@tanstack/react-query` if it is not already a dependency.\n\n**Files generated** (under `modules/<module>/src/`, with `<kebab>` the kebab-case feature name and `<Name>` the PascalCase name):\n\n- `routes/<kebab>.tsx` \u2014 the TanStack Router file route, wiring the layout and boundaries together\n- `features/<kebab>/layouts/<Name>Layout.tsx` \u2014 the page layout (the route `component`)\n- `features/<kebab>/layouts/<Name>SkeletonLayout.tsx` \u2014 the route `pendingComponent`\n- `features/<kebab>/layouts/<Name>ErrorLayout.tsx` \u2014 the route `errorComponent`\n- `features/<kebab>/layouts/<Name>NotFoundLayout.tsx` \u2014 the route `notFoundComponent`\n- `features/<kebab>/hooks/useGet<Name>.ts` \u2014 example TanStack Query read hook + query-key factory\n- `features/<kebab>/hooks/useUpdate<Name>.ts` \u2014 example TanStack Query mutation hook\n\n### 2. Resolve the linked design module and discover its components\n\nBefore writing any UI, find which design module this SPA builds on and what it offers \u2014 the layouts must be composed from its components, not styled from scratch.\n\n1. Read the SPA module's yml config at `modules/<module>/<module>.yml` and read the `design:` field. Its value is the kebab-case name of the design module (e.g. `design: \"ui\"`).\n - If there is **no** `design:` field, the SPA was created without a linked design module. Skip the design-specific guidance below and build the UI with plain elements (suggest the user run `oo spa:create --design <name>` or `oo design:create` if they want a shared design system).\n2. List the design module's source to learn the available primitives:\n\n ```bash\n ls modules/<design>/src/components 2>/dev/null && cat modules/<design>/src/index.ts 2>/dev/null\n ```\n\n Inspect the component files / barrel exports to see what exists (buttons, cards, inputs, layout primitives, etc.) and their props.\n3. Import design components through the module path alias \u2014 `@module/<design>/...`:\n\n ```typescript\n import { Button } from \"@module/<design>/components/Button\";\n // or, if the design module re-exports from a barrel:\n import { Button, Card } from \"@module/<design>\";\n ```\n\n Match the actual export style you found in step 2 (named exports per file vs. a barrel `index.ts`).\n\n### 3. Complete the route\n\nRead `modules/<module>/src/routes/<kebab>.tsx`, then:\n\n- Adjust the route path passed to `createFileRoute` if the feature should not live at `/<kebab>` (e.g. a nested or parameterized path like `/<kebab>/$id`).\n- Add a `loader` and `pendingMs` when the page needs data before it renders \u2014 the generated `pendingComponent`, `errorComponent`, and `notFoundComponent` exist to cover that loader's pending, failure, and `notFound()` states.\n- Keep the boundary components wired to the generated layouts.\n\n### 4. Complete the layouts\n\nRead each file in `modules/<module>/src/features/<kebab>/layouts/` and fill in the real UI, composing the design module's components (resolved in step 2) instead of re-implementing primitives. Keep the layouts as thin arrangers: extract every reusable piece into the canonical folders rather than inlining it.\n\n- `<Name>Layout` \u2014 the page content, arranged from design components and the feature's own components. It renders `children ?? <Outlet />`, so leave the `<Outlet />` in place if the route has children. Pull each presentational block out into `features/<kebab>/components/` (one component per file) and compose them here.\n- `<Name>SkeletonLayout` \u2014 a skeleton that mirrors the loaded layout's shape; reuse the design's skeleton/placeholder primitive if it has one, and keep the `aria-busy`/`aria-live` attributes for accessibility.\n- `<Name>ErrorLayout` \u2014 render a useful message from `error` using the design's alert/feedback components, and keep the `reset` retry action (wire it to the design's button).\n- `<Name>NotFoundLayout` \u2014 the empty/missing-resource state, using the design's empty-state primitives where available.\n\nAs you split (one file per element, except types/styles which group by logic):\n- presentational/container components \u2192 `features/<kebab>/components/` (one component per file)\n- feature-local client state \u2192 `features/<kebab>/store/` (one slice per file)\n- pure helpers (formatting, guards) \u2192 `features/<kebab>/utils/` (one helper per file)\n- domain types (props/view-model types) \u2192 grouped in `features/<kebab>/types/`\n- scoped styles \u2192 grouped in `features/<kebab>/styles/`\n- media \u2192 `features/<kebab>/assets/`\n\nIf the design module lacks a primitive you need, prefer adding it to the design module (`@module/<design>`) so it stays reusable, rather than styling it inline in the feature.\n\n### 5. Complete the hooks\n\nRead the two hooks in `modules/<module>/src/features/<kebab>/hooks/` and adapt the examples to the real resource, splitting each into its logical folder so the hook stays a thin TanStack Query wrapper:\n\n- Move the resource type (the placeholder `<Name>Type`) out of the hook into the feature's grouped types file (`features/<kebab>/types/<kebab>.ts`) and import it back.\n- Move the `fetch`/API-call functions (`get<Name>`, `update<Name>`) into an API client in `features/<kebab>/services/` \u2014 services are the only layer that talks to the backend. Keep one service file per element and have the hooks import these instead of calling `fetch` directly.\n- `useGet<Name>.ts` \u2014 keep only the `useQuery` wiring and the `<camel>Keys` query-key factory; extend the factory with any list/filtered keys the feature needs. The factory is the single source of truth for this feature's keys \u2014 reads and invalidations must go through it so they always agree.\n- `useUpdate<Name>.ts` \u2014 keep only the `useMutation` wiring; keep `onSuccess` seeding the cache via `setQueryData` then returning the `invalidateQueries` promise so the mutation stays pending until the refetch settles.\n- Forward the query's `signal` from the hook into the service call to `fetch` so in-flight requests cancel on unmount/refetch.\n\nAdd further hooks (lists, additional mutations) in the same folder following these patterns, keeping their fetch logic in `services/` and their types in `types/`.\n\n### 6. Lint, format, and test\n\n```bash\nbun run fmt && bun run lint && bun run test\n```\n\nFix every failure before completing.\n";
27040
27018
 
27041
27019
  // src/templates/llm/skills/storage.create.md.txt
27042
27020
  var storage_create_md_default = `---
@@ -27204,12 +27182,13 @@ describe("<Name>Storage", () => {
27204
27182
  });
27205
27183
  \`\`\`
27206
27184
 
27207
- ### 4. Lint and format
27185
+ ### 4. Lint, format, and test
27208
27186
 
27209
27187
  \`\`\`bash
27210
- bun run fmt
27211
- bun run lint
27188
+ bun run fmt && bun run lint && bun run test
27212
27189
  \`\`\`
27190
+
27191
+ Fix every failure before completing.
27213
27192
  `;
27214
27193
 
27215
27194
  // src/templates/llm/skills/vector-database.create.md.txt
@@ -27347,12 +27326,13 @@ describe("<Name>VectorDatabase", () => {
27347
27326
  });
27348
27327
  \`\`\`
27349
27328
 
27350
- ### 4. Lint and format
27329
+ ### 4. Lint, format, and test
27351
27330
 
27352
27331
  \`\`\`bash
27353
- bun run fmt
27354
- bun run lint
27332
+ bun run fmt && bun run lint && bun run test
27355
27333
  \`\`\`
27334
+
27335
+ Fix every failure before completing.
27356
27336
  `;
27357
27337
 
27358
27338
  // src/templates/llm/skills/index.ts
@@ -27387,7 +27367,10 @@ var skills = {
27387
27367
  "issue.found": issue_found_md_default,
27388
27368
  "issue.plan": issue_plan_md_default,
27389
27369
  commit: commit_md_default,
27390
- optimize: optimize_md_default
27370
+ optimize: optimize_md_default,
27371
+ "optimize.conventions": optimize_conventions_md_default,
27372
+ "optimize.testing": optimize_testing_md_default,
27373
+ "optimize.react": optimize_react_md_default
27391
27374
  };
27392
27375
 
27393
27376
  // src/commands/ClaudeInitCommand.ts
@@ -27482,7 +27465,7 @@ class AppInitCommand {
27482
27465
  return "Initialize an application";
27483
27466
  }
27484
27467
  async run(options) {
27485
- let { name, destination, silent, appType } = options;
27468
+ let { name, destination, silent } = options;
27486
27469
  if (!name) {
27487
27470
  name = await askName({ message: "Enter application name" });
27488
27471
  }
@@ -27510,13 +27493,8 @@ class AppInitCommand {
27510
27493
  envData.rate_limit.redis.url = "redis://localhost:6379";
27511
27494
  envData.database.url = "postgresql://ooneex:ooneex@localhost:5432/ooneex";
27512
27495
  envData.database.redis.url = "redis://localhost:6379";
27513
- if (appType === "api") {
27514
- await Bun.write(join8(destination, "modules", "shared", ".env.yml"), `${toYaml(envData)}
27515
- `);
27516
- } else {
27517
- await Bun.write(join8(destination, ".env.yml"), `${toYaml(envData)}
27496
+ await Bun.write(join8(destination, ".env.yml"), `${toYaml(envData)}
27518
27497
  `);
27519
- }
27520
27498
  const addDevDeps = Bun.spawn([
27521
27499
  "bun",
27522
27500
  "add",
@@ -112965,6 +112943,21 @@ ${item}`;
112965
112943
  }
112966
112944
  await Bun.write(appYmlPath, content);
112967
112945
  }
112946
+ async nextAvailablePort(cwd) {
112947
+ const used = new Set([3000]);
112948
+ const modulesDir = join25(cwd, "modules");
112949
+ const glob = new Bun.Glob("*/.env.yml");
112950
+ for await (const match of glob.scan({ cwd: modulesDir, onlyFiles: true })) {
112951
+ const content = await Bun.file(join25(modulesDir, match)).text();
112952
+ const portMatch = content.match(/^\s*port:\s*(\d+)/m);
112953
+ if (portMatch)
112954
+ used.add(Number(portMatch[1]));
112955
+ }
112956
+ let port = 3001;
112957
+ while (used.has(port))
112958
+ port++;
112959
+ return port;
112960
+ }
112968
112961
  async addToEnvYml(envYmlPath, kebabName) {
112969
112962
  let content = await Bun.file(envYmlPath).text();
112970
112963
  const entry = ` ${kebabName}:
@@ -113009,12 +113002,16 @@ ${entry}`;
113009
113002
  await Bun.write(join25(srcDir, "OnAppStart.ts"), OnAppStart_ts_default);
113010
113003
  const dockerfileContent = Dockerfile_default.replace(/{{NAME}}/g, snakeName).replace(/modules\/app\//g, `modules/${kebabName}/`);
113011
113004
  await Bun.write(join25(moduleDir, "Dockerfile"), dockerfileContent);
113005
+ const envData = structuredClone(env_default);
113006
+ envData.app.port = await this.nextAvailablePort(cwd);
113007
+ await Bun.write(join25(moduleDir, ".env.yml"), `${toYaml(envData)}
113008
+ `);
113012
113009
  if (kebabName !== "app") {
113013
113010
  const appYmlPath = join25(cwd, "modules", "app", "app.yml");
113014
113011
  if (await Bun.file(appYmlPath).exists()) {
113015
113012
  await this.declareInAppYml(appYmlPath, kebabName, snakeName.toUpperCase());
113016
113013
  }
113017
- const envYmlPath = join25(cwd, "modules", "shared", ".env.yml");
113014
+ const envYmlPath = join25(cwd, ".env.yml");
113018
113015
  if (await Bun.file(envYmlPath).exists()) {
113019
113016
  await this.addToEnvYml(envYmlPath, kebabName);
113020
113017
  }
@@ -115146,4 +115143,4 @@ VectorDatabaseCreateCommand = __legacyDecorateClassTS([
115146
115143
  // src/index.ts
115147
115144
  await run();
115148
115145
 
115149
- //# debugId=89D60FAB309DA44564756E2164756E21
115146
+ //# debugId=365BCB542AC44A9064756E2164756E21