@ooneex/cli 1.46.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 +269 -272
- package/dist/index.js.map +6 -6
- package/package.json +2 -2
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
22762
|
-
|
|
22763
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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),
|
|
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
|
-
|
|
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
|
-
##
|
|
25736
|
+
## Rules
|
|
25734
25737
|
|
|
25735
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25744
|
+
Invoke each sub-skill only at the step that needs it; skip ones that don't apply.
|
|
25744
25745
|
|
|
25745
|
-
|
|
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
|
-
|
|
25752
|
+
## Steps
|
|
25748
25753
|
|
|
25749
|
-
|
|
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
|
-
|
|
25793
|
+
## Visibility
|
|
25752
25794
|
|
|
25753
|
-
|
|
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
|
-
|
|
25805
|
+
## Arrow Functions vs Class Methods
|
|
25771
25806
|
|
|
25772
|
-
|
|
25807
|
+
Arrow functions everywhere, except class methods (use regular method syntax).
|
|
25773
25808
|
|
|
25774
25809
|
\`\`\`typescript
|
|
25775
|
-
|
|
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
|
|
25813
|
+
public async execute(): Promise<void> {} // method: regular syntax, NOT \`execute = async () =>\`
|
|
25788
25814
|
}
|
|
25789
25815
|
\`\`\`
|
|
25790
25816
|
|
|
25791
|
-
|
|
25817
|
+
## Type & Interface Naming
|
|
25792
25818
|
|
|
25793
|
-
|
|
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
|
-
|
|
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\`
|
|
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
|
-
|
|
25830
|
+
## Non-null Assertions
|
|
25818
25831
|
|
|
25819
|
-
Never use \`!\` on class properties
|
|
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
|
-
|
|
25841
|
+
## Optional Entity Properties
|
|
25835
25842
|
|
|
25836
|
-
- Optional
|
|
25837
|
-
-
|
|
25838
|
-
- Always
|
|
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
|
-
|
|
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
|
-
|
|
25871
|
+
## Code Hygiene
|
|
25875
25872
|
|
|
25876
|
-
- Remove
|
|
25877
|
-
-
|
|
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
|
-
|
|
25876
|
+
## Duplication & Dead Code
|
|
25881
25877
|
|
|
25882
|
-
-
|
|
25883
|
-
-
|
|
25884
|
-
|
|
25885
|
-
|
|
25886
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25906
|
+
## Hooks
|
|
25891
25907
|
|
|
25892
|
-
Extract stateful
|
|
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
|
|
25911
|
-
const isMobile = width < 768;
|
|
25926
|
+
const isMobile = useWindowWidth() < 768;
|
|
25912
25927
|
\`\`\`
|
|
25913
25928
|
|
|
25914
|
-
|
|
25929
|
+
## Compound components
|
|
25915
25930
|
|
|
25916
|
-
Build flexible components as
|
|
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
|
-
|
|
25972
|
+
## Global state \u2014 Zustand
|
|
25960
25973
|
|
|
25961
|
-
|
|
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
|
|
25989
|
+
// select only what you need
|
|
25977
25990
|
const count = useCounterStore((state) => state.count);
|
|
25978
25991
|
\`\`\`
|
|
25979
25992
|
|
|
25980
|
-
|
|
25993
|
+
## Server state \u2014 TanStack Query
|
|
25981
25994
|
|
|
25982
|
-
|
|
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
|
-
|
|
26013
|
+
## Long lists \u2014 TanStack Virtual
|
|
26001
26014
|
|
|
26002
|
-
|
|
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
|
-
|
|
26030
|
+
## Debounce / throttle / queue / batch \u2014 TanStack Pacer
|
|
26018
26031
|
|
|
26019
|
-
|
|
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
|
-
//
|
|
26039
|
+
// updates 300ms after the user stops typing
|
|
26027
26040
|
\`\`\`
|
|
26028
26041
|
|
|
26029
|
-
|
|
26042
|
+
## Keyboard shortcuts \u2014 TanStack Hotkeys
|
|
26030
26043
|
|
|
26031
|
-
|
|
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
|
-
|
|
26044
|
-
|
|
26045
|
-
|
|
26046
|
-
|
|
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
|
-
|
|
26063
|
+
# Testing Conventions
|
|
26073
26064
|
|
|
26074
|
-
|
|
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
|
-
|
|
26067
|
+
## Conventions
|
|
26080
26068
|
|
|
26081
|
-
-
|
|
26082
|
-
-
|
|
26083
|
-
-
|
|
26084
|
-
-
|
|
26085
|
-
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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, "
|
|
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=
|
|
115146
|
+
//# debugId=365BCB542AC44A9064756E2164756E21
|