feat: add type checking and linting improvements across the project
All checks were successful
ci / ci (22) (push) Successful in 11m57s

- Added "check-types" script to package.json for type checking in both api and web apps.
- Updated ESLint configuration to use a shared tooling package for consistent linting rules.
- Refactored TypeScript configurations to extend from shared tooling configurations.
- Improved code formatting and consistency in various files, including Vue components and TypeScript files.
- Introduced CI workflow for automated linting, formatting checks, and type checking on push and pull request events.
- Cleaned up unnecessary dependencies and updated existing ones for better performance and maintainability.
This commit is contained in:
Nicolas HOARAU 2026-06-04 16:33:06 +04:00
parent 0169b002a7
commit f11c306ac6
44 changed files with 566 additions and 326 deletions

40
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,40 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v6
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Format check
run: pnpm format:check
- name: Lint
run: pnpm lint
- name: Typecheck
run: pnpm check-types

View file

@ -18,9 +18,11 @@ Implement tasks from an OpenSpec change.
Always announce: "Using change: <name>" and how to override (e.g., `/opsx-apply <other>`). Always announce: "Using change: <name>" and how to override (e.g., `/opsx-apply <other>`).
2. **Check status to understand the schema** 2. **Check status to understand the schema**
```bash ```bash
openspec status --change "<name>" --json openspec status --change "<name>" --json
``` ```
Parse the JSON to understand: Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven") - `schemaName`: The workflow being used (e.g., "spec-driven")
- `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints
@ -135,6 +137,7 @@ What would you like to do?
``` ```
**Guardrails** **Guardrails**
- Keep going through tasks until done or blocked - Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output) - Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing - If task is ambiguous, pause and ask before implementing

View file

@ -64,6 +64,7 @@ Archive a completed change in the experimental workflow.
5. **Perform the archive** 5. **Perform the archive**
Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: Create an `archive` directory under `planningHome.changesDir` if it doesn't exist:
```bash ```bash
mkdir -p "<planningHome.changesDir>/archive" mkdir -p "<planningHome.changesDir>/archive"
``` ```
@ -148,6 +149,7 @@ Target archive directory already exists.
``` ```
**Guardrails** **Guardrails**
- Always prompt for change selection if not provided - Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking - Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm - Don't block archive on warnings - just inform and confirm

View file

@ -9,6 +9,7 @@ Enter explore mode. Think deeply. Visualize freely. Follow the conversation wher
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. **This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
**Input**: The argument after `/opsx-explore` is whatever the user wants to think about. Could be: **Input**: The argument after `/opsx-explore` is whatever the user wants to think about. Could be:
- A vague idea: "real-time collaboration" - A vague idea: "real-time collaboration"
- A specific problem: "the auth system is getting unwieldy" - A specific problem: "the auth system is getting unwieldy"
- A change name: "add-dark-mode" (to explore in context of that change) - A change name: "add-dark-mode" (to explore in context of that change)
@ -33,24 +34,28 @@ Enter explore mode. Think deeply. Visualize freely. Follow the conversation wher
Depending on what the user brings, you might: Depending on what the user brings, you might:
**Explore the problem space** **Explore the problem space**
- Ask clarifying questions that emerge from what they said - Ask clarifying questions that emerge from what they said
- Challenge assumptions - Challenge assumptions
- Reframe the problem - Reframe the problem
- Find analogies - Find analogies
**Investigate the codebase** **Investigate the codebase**
- Map existing architecture relevant to the discussion - Map existing architecture relevant to the discussion
- Find integration points - Find integration points
- Identify patterns already in use - Identify patterns already in use
- Surface hidden complexity - Surface hidden complexity
**Compare options** **Compare options**
- Brainstorm multiple approaches - Brainstorm multiple approaches
- Build comparison tables - Build comparison tables
- Sketch tradeoffs - Sketch tradeoffs
- Recommend a path (if asked) - Recommend a path (if asked)
**Visualize** **Visualize**
``` ```
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │ │ Use ASCII diagrams liberally │
@ -69,6 +74,7 @@ Depending on what the user brings, you might:
``` ```
**Surface risks and unknowns** **Surface risks and unknowns**
- Identify what could go wrong - Identify what could go wrong
- Find gaps in understanding - Find gaps in understanding
- Suggest spikes or investigations - Suggest spikes or investigations
@ -82,11 +88,13 @@ You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context ### Check for context
At the start, quickly check what exists: At the start, quickly check what exists:
```bash ```bash
openspec list --json openspec list --json
``` ```
This tells you: This tells you:
- If there are active changes - If there are active changes
- Their names, schemas, and status - Their names, schemas, and status
- What the user might be working on - What the user might be working on
@ -115,14 +123,14 @@ If the user mentions a change or you detect one is relevant:
3. **Offer to capture when decisions are made** 3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture | | Insight Type | Where to Capture |
|----------------------------|--------------------------------| | -------------------------- | ---------------------------- |
| New requirement discovered | `specs/<capability>/spec.md` | | New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` | | Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` | | Design decision made | `design.md` |
| Scope changed | `proposal.md` | | Scope changed | `proposal.md` |
| New work identified | `tasks.md` | | New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact | | Assumption invalidated | Relevant artifact |
Example offers: Example offers:
- "That's a design decision. Capture it in design.md?" - "That's a design decision. Capture it in design.md?"

View file

@ -5,6 +5,7 @@ description: Propose a new change - create it and generate all artifacts in one
Propose a new change - create the change and generate all artifacts in one step. Propose a new change - create the change and generate all artifacts in one step.
I'll create a change with artifacts: I'll create a change with artifacts:
- proposal.md (what & why) - proposal.md (what & why)
- design.md (how) - design.md (how)
- tasks.md (implementation steps) - tasks.md (implementation steps)
@ -20,6 +21,7 @@ When ready to implement, run /opsx-apply
1. **If no input provided, ask what they want to build** 1. **If no input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix." > "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
@ -27,15 +29,19 @@ When ready to implement, run /opsx-apply
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build. **IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory** 2. **Create the change directory**
```bash ```bash
openspec new change "<name>" openspec new change "<name>"
``` ```
This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`.
3. **Get the artifact build order** 3. **Get the artifact build order**
```bash ```bash
openspec status --change "<name>" --json openspec status --change "<name>" --json
``` ```
Parse the JSON to get: Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies - `artifacts`: list of all artifacts with their status and dependencies
@ -48,30 +54,30 @@ When ready to implement, run /opsx-apply
Loop through artifacts in dependency order (artifacts with no pending dependencies first): Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**: a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions: - Get instructions:
```bash ```bash
openspec instructions <artifact-id> --change "<name>" --json openspec instructions <artifact-id> --change "<name>" --json
``` ```
- The instructions JSON includes: - The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output) - `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file - `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type - `instruction`: Schema-specific guidance for this artifact type
- `resolvedOutputPath`: Resolved path or pattern to write the artifact - `resolvedOutputPath`: Resolved path or pattern to write the artifact
- `dependencies`: Completed artifacts to read for context - `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context - Read any completed dependency files for context
- Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`
- Apply `context` and `rules` as constraints - but do NOT copy them into the file - Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "Created <artifact-id>" - Show brief progress: "Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete** b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json` - After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done - Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context): c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify - Use **AskUserQuestion tool** to clarify
- Then continue with creation - Then continue with creation
5. **Show final status** 5. **Show final status**
```bash ```bash
@ -81,6 +87,7 @@ When ready to implement, run /opsx-apply
**Output** **Output**
After completing all artifacts, summarize: After completing all artifacts, summarize:
- Change name and location - Change name and location
- List of artifacts created with brief descriptions - List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation." - What's ready: "All artifacts created! Ready for implementation."
@ -97,6 +104,7 @@ After completing all artifacts, summarize:
- These guide what you write, but should never appear in the output - These guide what you write, but should never appear in the output
**Guardrails** **Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`) - Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one - Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum - If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum

View file

@ -21,6 +21,7 @@ This is an **agent-driven** operation - you will read delta specs and directly e
2. **Resolve change context** 2. **Resolve change context**
Run: Run:
```bash ```bash
openspec status --change "<name>" --json openspec status --change "<name>" --json
``` ```
@ -49,28 +50,28 @@ This is an **agent-driven** operation - you will read delta specs and directly e
c. **Apply changes intelligently**: c. **Apply changes intelligently**:
**ADDED Requirements:** **ADDED Requirements:**
- If requirement doesn't exist in main spec → add it - If requirement doesn't exist in main spec → add it
- If requirement already exists → update it to match (treat as implicit MODIFIED) - If requirement already exists → update it to match (treat as implicit MODIFIED)
**MODIFIED Requirements:** **MODIFIED Requirements:**
- Find the requirement in main spec - Find the requirement in main spec
- Apply the changes - this can be: - Apply the changes - this can be:
- Adding new scenarios (don't need to copy existing ones) - Adding new scenarios (don't need to copy existing ones)
- Modifying existing scenarios - Modifying existing scenarios
- Changing the requirement description - Changing the requirement description
- Preserve scenarios/content not mentioned in the delta - Preserve scenarios/content not mentioned in the delta
**REMOVED Requirements:** **REMOVED Requirements:**
- Remove the entire requirement block from main spec - Remove the entire requirement block from main spec
**RENAMED Requirements:** **RENAMED Requirements:**
- Find the FROM requirement, rename to TO - Find the FROM requirement, rename to TO
d. **Create new main spec** if capability doesn't exist yet: d. **Create new main spec** if capability doesn't exist yet:
- Create `openspec/specs/<capability>/spec.md` - Create `openspec/specs/<capability>/spec.md`
- Add Purpose section (can be brief, mark as TBD) - Add Purpose section (can be brief, mark as TBD)
- Add Requirements section with the ADDED requirements - Add Requirements section with the ADDED requirements
5. **Show summary** 5. **Show summary**
@ -84,16 +85,20 @@ This is an **agent-driven** operation - you will read delta specs and directly e
## ADDED Requirements ## ADDED Requirements
### Requirement: New Feature ### Requirement: New Feature
The system SHALL do something new. The system SHALL do something new.
#### Scenario: Basic case #### Scenario: Basic case
- **WHEN** user does X - **WHEN** user does X
- **THEN** system does Y - **THEN** system does Y
## MODIFIED Requirements ## MODIFIED Requirements
### Requirement: Existing Feature ### Requirement: Existing Feature
#### Scenario: New scenario to add #### Scenario: New scenario to add
- **WHEN** user does A - **WHEN** user does A
- **THEN** system does B - **THEN** system does B
@ -110,8 +115,9 @@ The system SHALL do something new.
**Key Principle: Intelligent Merging** **Key Principle: Intelligent Merging**
Unlike programmatic merging, you can apply **partial updates**: Unlike programmatic merging, you can apply **partial updates**:
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios - To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
- The delta represents *intent*, not a wholesale replacement - The delta represents _intent_, not a wholesale replacement
- Use your judgment to merge changes sensibly - Use your judgment to merge changes sensibly
**Output On Success** **Output On Success**
@ -133,6 +139,7 @@ Main specs are now updated. The change remains active - archive when implementat
``` ```
**Guardrails** **Guardrails**
- Read both delta and main specs before making changes - Read both delta and main specs before making changes
- Preserve existing content not mentioned in delta - Preserve existing content not mentioned in delta
- If something is unclear, ask for clarification - If something is unclear, ask for clarification

View file

@ -25,9 +25,11 @@ Implement tasks from an OpenSpec change.
Always announce: "Using change: <name>" and how to override (e.g., `/opsx-apply <other>`). Always announce: "Using change: <name>" and how to override (e.g., `/opsx-apply <other>`).
2. **Check status to understand the schema** 2. **Check status to understand the schema**
```bash ```bash
openspec status --change "<name>" --json openspec status --change "<name>" --json
``` ```
Parse the JSON to understand: Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven") - `schemaName`: The workflow being used (e.g., "spec-driven")
- `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints
@ -142,6 +144,7 @@ What would you like to do?
``` ```
**Guardrails** **Guardrails**
- Keep going through tasks until done or blocked - Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output) - Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing - If task is ambiguous, pause and ask before implementing

View file

@ -71,6 +71,7 @@ Archive a completed change in the experimental workflow.
5. **Perform the archive** 5. **Perform the archive**
Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: Create an `archive` directory under `planningHome.changesDir` if it doesn't exist:
```bash ```bash
mkdir -p "<planningHome.changesDir>/archive" mkdir -p "<planningHome.changesDir>/archive"
``` ```
@ -108,6 +109,7 @@ All artifacts complete. All tasks complete.
``` ```
**Guardrails** **Guardrails**
- Always prompt for change selection if not provided - Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking - Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm - Don't block archive on warnings - just inform and confirm

View file

@ -33,24 +33,28 @@ Enter explore mode. Think deeply. Visualize freely. Follow the conversation wher
Depending on what the user brings, you might: Depending on what the user brings, you might:
**Explore the problem space** **Explore the problem space**
- Ask clarifying questions that emerge from what they said - Ask clarifying questions that emerge from what they said
- Challenge assumptions - Challenge assumptions
- Reframe the problem - Reframe the problem
- Find analogies - Find analogies
**Investigate the codebase** **Investigate the codebase**
- Map existing architecture relevant to the discussion - Map existing architecture relevant to the discussion
- Find integration points - Find integration points
- Identify patterns already in use - Identify patterns already in use
- Surface hidden complexity - Surface hidden complexity
**Compare options** **Compare options**
- Brainstorm multiple approaches - Brainstorm multiple approaches
- Build comparison tables - Build comparison tables
- Sketch tradeoffs - Sketch tradeoffs
- Recommend a path (if asked) - Recommend a path (if asked)
**Visualize** **Visualize**
``` ```
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │ │ Use ASCII diagrams liberally │
@ -69,6 +73,7 @@ Depending on what the user brings, you might:
``` ```
**Surface risks and unknowns** **Surface risks and unknowns**
- Identify what could go wrong - Identify what could go wrong
- Find gaps in understanding - Find gaps in understanding
- Suggest spikes or investigations - Suggest spikes or investigations
@ -82,11 +87,13 @@ You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context ### Check for context
At the start, quickly check what exists: At the start, quickly check what exists:
```bash ```bash
openspec list --json openspec list --json
``` ```
This tells you: This tells you:
- If there are active changes - If there are active changes
- Their names, schemas, and status - Their names, schemas, and status
- What the user might be working on - What the user might be working on
@ -113,14 +120,14 @@ If the user mentions a change or you detect one is relevant:
3. **Offer to capture when decisions are made** 3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture | | Insight Type | Where to Capture |
|----------------------------|--------------------------------| | -------------------------- | ---------------------------- |
| New requirement discovered | `specs/<capability>/spec.md` | | New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` | | Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` | | Design decision made | `design.md` |
| Scope changed | `proposal.md` | | Scope changed | `proposal.md` |
| New work identified | `tasks.md` | | New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact | | Assumption invalidated | Relevant artifact |
Example offers: Example offers:
- "That's a design decision. Capture it in design.md?" - "That's a design decision. Capture it in design.md?"
@ -145,6 +152,7 @@ If the user mentions a change or you detect one is relevant:
## Handling Different Entry Points ## Handling Different Entry Points
**User brings a vague idea:** **User brings a vague idea:**
``` ```
User: I'm thinking about adding real-time collaboration User: I'm thinking about adding real-time collaboration
@ -168,6 +176,7 @@ You: Real-time collab is a big space. Let me think about this...
``` ```
**User brings a specific problem:** **User brings a specific problem:**
``` ```
User: The auth system is a mess User: The auth system is a mess
@ -199,6 +208,7 @@ You: [reads codebase]
``` ```
**User is stuck mid-implementation:** **User is stuck mid-implementation:**
``` ```
User: /opsx-explore add-auth-system User: /opsx-explore add-auth-system
The OAuth integration is more complex than expected The OAuth integration is more complex than expected
@ -216,6 +226,7 @@ You: [reads change artifacts]
``` ```
**User wants to compare options:** **User wants to compare options:**
``` ```
User: Should we use Postgres or SQLite? User: Should we use Postgres or SQLite?

View file

@ -12,6 +12,7 @@ metadata:
Propose a new change - create the change and generate all artifacts in one step. Propose a new change - create the change and generate all artifacts in one step.
I'll create a change with artifacts: I'll create a change with artifacts:
- proposal.md (what & why) - proposal.md (what & why)
- design.md (how) - design.md (how)
- tasks.md (implementation steps) - tasks.md (implementation steps)
@ -27,6 +28,7 @@ When ready to implement, run /opsx-apply
1. **If no clear input provided, ask what they want to build** 1. **If no clear input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix." > "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
@ -34,15 +36,19 @@ When ready to implement, run /opsx-apply
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build. **IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory** 2. **Create the change directory**
```bash ```bash
openspec new change "<name>" openspec new change "<name>"
``` ```
This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`.
3. **Get the artifact build order** 3. **Get the artifact build order**
```bash ```bash
openspec status --change "<name>" --json openspec status --change "<name>" --json
``` ```
Parse the JSON to get: Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies - `artifacts`: list of all artifacts with their status and dependencies
@ -55,30 +61,30 @@ When ready to implement, run /opsx-apply
Loop through artifacts in dependency order (artifacts with no pending dependencies first): Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**: a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions: - Get instructions:
```bash ```bash
openspec instructions <artifact-id> --change "<name>" --json openspec instructions <artifact-id> --change "<name>" --json
``` ```
- The instructions JSON includes: - The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output) - `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file - `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type - `instruction`: Schema-specific guidance for this artifact type
- `resolvedOutputPath`: Resolved path or pattern to write the artifact - `resolvedOutputPath`: Resolved path or pattern to write the artifact
- `dependencies`: Completed artifacts to read for context - `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context - Read any completed dependency files for context
- Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`
- Apply `context` and `rules` as constraints - but do NOT copy them into the file - Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "Created <artifact-id>" - Show brief progress: "Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete** b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json` - After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done - Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context): c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify - Use **AskUserQuestion tool** to clarify
- Then continue with creation - Then continue with creation
5. **Show final status** 5. **Show final status**
```bash ```bash
@ -88,6 +94,7 @@ When ready to implement, run /opsx-apply
**Output** **Output**
After completing all artifacts, summarize: After completing all artifacts, summarize:
- Change name and location - Change name and location
- List of artifacts created with brief descriptions - List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation." - What's ready: "All artifacts created! Ready for implementation."
@ -104,6 +111,7 @@ After completing all artifacts, summarize:
- These guide what you write, but should never appear in the output - These guide what you write, but should never appear in the output
**Guardrails** **Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`) - Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one - Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum - If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum

View file

@ -28,6 +28,7 @@ This is an **agent-driven** operation - you will read delta specs and directly e
2. **Resolve change context** 2. **Resolve change context**
Run: Run:
```bash ```bash
openspec status --change "<name>" --json openspec status --change "<name>" --json
``` ```
@ -56,28 +57,28 @@ This is an **agent-driven** operation - you will read delta specs and directly e
c. **Apply changes intelligently**: c. **Apply changes intelligently**:
**ADDED Requirements:** **ADDED Requirements:**
- If requirement doesn't exist in main spec → add it - If requirement doesn't exist in main spec → add it
- If requirement already exists → update it to match (treat as implicit MODIFIED) - If requirement already exists → update it to match (treat as implicit MODIFIED)
**MODIFIED Requirements:** **MODIFIED Requirements:**
- Find the requirement in main spec - Find the requirement in main spec
- Apply the changes - this can be: - Apply the changes - this can be:
- Adding new scenarios (don't need to copy existing ones) - Adding new scenarios (don't need to copy existing ones)
- Modifying existing scenarios - Modifying existing scenarios
- Changing the requirement description - Changing the requirement description
- Preserve scenarios/content not mentioned in the delta - Preserve scenarios/content not mentioned in the delta
**REMOVED Requirements:** **REMOVED Requirements:**
- Remove the entire requirement block from main spec - Remove the entire requirement block from main spec
**RENAMED Requirements:** **RENAMED Requirements:**
- Find the FROM requirement, rename to TO - Find the FROM requirement, rename to TO
d. **Create new main spec** if capability doesn't exist yet: d. **Create new main spec** if capability doesn't exist yet:
- Create `openspec/specs/<capability>/spec.md` - Create `openspec/specs/<capability>/spec.md`
- Add Purpose section (can be brief, mark as TBD) - Add Purpose section (can be brief, mark as TBD)
- Add Requirements section with the ADDED requirements - Add Requirements section with the ADDED requirements
5. **Show summary** 5. **Show summary**
@ -91,16 +92,20 @@ This is an **agent-driven** operation - you will read delta specs and directly e
## ADDED Requirements ## ADDED Requirements
### Requirement: New Feature ### Requirement: New Feature
The system SHALL do something new. The system SHALL do something new.
#### Scenario: Basic case #### Scenario: Basic case
- **WHEN** user does X - **WHEN** user does X
- **THEN** system does Y - **THEN** system does Y
## MODIFIED Requirements ## MODIFIED Requirements
### Requirement: Existing Feature ### Requirement: Existing Feature
#### Scenario: New scenario to add #### Scenario: New scenario to add
- **WHEN** user does A - **WHEN** user does A
- **THEN** system does B - **THEN** system does B
@ -117,8 +122,9 @@ The system SHALL do something new.
**Key Principle: Intelligent Merging** **Key Principle: Intelligent Merging**
Unlike programmatic merging, you can apply **partial updates**: Unlike programmatic merging, you can apply **partial updates**:
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios - To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
- The delta represents *intent*, not a wholesale replacement - The delta represents _intent_, not a wholesale replacement
- Use your judgment to merge changes sensibly - Use your judgment to merge changes sensibly
**Output On Success** **Output On Success**
@ -140,6 +146,7 @@ Main specs are now updated. The change remains active - archive when implementat
``` ```
**Guardrails** **Guardrails**
- Read both delta and main specs before making changes - Read both delta and main specs before making changes
- Preserve existing content not mentioned in delta - Preserve existing content not mentioned in delta
- If something is unclear, ask for clarification - If something is unclear, ask for clarification

7
.prettierignore Normal file
View file

@ -0,0 +1,7 @@
node_modules
.turbo
dist
.nuxt
.output
coverage
pnpm-lock.yaml

4
.prettierrc.mjs Normal file
View file

@ -0,0 +1,4 @@
// @ts-check
import config from "@workspace/tooling/prettier";
export default config;

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View file

@ -80,9 +80,9 @@ All commands run from the repository root.
- **ESLint flat config** (`eslint.config.mjs`) in each app. - **ESLint flat config** (`eslint.config.mjs`) in each app.
- API uses `@typescript-eslint` with `recommendedTypeChecked` rules, plus `eslint-plugin-prettier`. - API uses `@typescript-eslint` with `recommendedTypeChecked` rules, plus `eslint-plugin-prettier`.
- Key relaxed rules: - Key relaxed rules:
- `@typescript-eslint/no-explicit-any: off` - `@typescript-eslint/no-explicit-any: off`
- `@typescript-eslint/no-floating-promises: warn` - `@typescript-eslint/no-floating-promises: warn`
- `@typescript-eslint/no-unsafe-argument: warn` - `@typescript-eslint/no-unsafe-argument: warn`
- Web uses `@nuxt/eslint` with stylistic config (`commaDangle: never`, `braceStyle: 1tbs`). - Web uses `@nuxt/eslint` with stylistic config (`commaDangle: never`, `braceStyle: 1tbs`).
### TypeScript ### TypeScript

View file

@ -1,4 +0,0 @@
{
"singleQuote": false,
"trailingComma": "all"
}

View file

@ -1,35 +1,3 @@
// @ts-check import { createNestjsConfig } from "@workspace/tooling/eslint/nestjs";
import eslint from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config( export default createNestjsConfig(import.meta.dirname);
{
ignores: ['eslint.config.mjs'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintPluginPrettierRecommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
sourceType: 'commonjs',
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
"prettier/prettier": ["error", { endOfLine: "auto" }],
},
},
);

View file

@ -13,6 +13,7 @@
"dev": "nest start --watch", "dev": "nest start --watch",
"start:debug": "nest start --debug --watch", "start:debug": "nest start --debug --watch",
"start:prod": "node dist/main", "start:prod": "node dist/main",
"check-types": "tsc --noEmit",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
@ -29,8 +30,6 @@
"rxjs": "^7.8.1" "rxjs": "^7.8.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0", "@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0", "@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1", "@nestjs/testing": "^11.0.1",
@ -38,10 +37,8 @@
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/node": "^22.10.7", "@types/node": "^22.10.7",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"@workspace/tooling": "workspace:*",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0", "jest": "^30.0.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
@ -50,8 +47,7 @@
"ts-loader": "^9.5.2", "ts-loader": "^9.5.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3", "typescript": "^5.7.3"
"typescript-eslint": "^8.20.0"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [

View file

@ -17,9 +17,6 @@ describe("AppController (e2e)", () => {
}); });
it("/ (GET)", () => { it("/ (GET)", () => {
return request(app.getHttpServer()) return request(app.getHttpServer()).get("/").expect(200).expect("Hello World!");
.get("/")
.expect(200)
.expect("Hello World!");
}); });
}); });

View file

@ -1,25 +1,8 @@
{ {
"extends": "@workspace/tooling/tsconfig/nestjs.json",
"compilerOptions": { "compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"resolvePackageJsonExports": true,
"esModuleInterop": true,
"isolatedModules": true,
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2023",
"sourceMap": true,
"outDir": "./dist", "outDir": "./dist",
"baseUrl": "./", "baseUrl": "./",
"incremental": true, "incremental": true
"skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false
} }
} }

View file

@ -1,8 +1,8 @@
export default defineAppConfig({ export default defineAppConfig({
ui: { ui: {
colors: { colors: {
primary: 'green', primary: "green",
neutral: 'slate' neutral: "slate",
} },
} },
}) });

View file

@ -1,27 +1,27 @@
<script setup> <script setup>
useHead({ useHead({
meta: [ meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' } { name: "viewport", content: "width=device-width, initial-scale=1", },
], ],
link: [ link: [
{ rel: 'icon', href: '/favicon.ico' } { rel: "icon", href: "/favicon.ico", },
], ],
htmlAttrs: { htmlAttrs: {
lang: 'en' lang: "en",
} },
}) },)
const title = 'Nuxt Starter Template' const title = "Nuxt Starter Template"
const description = 'A production-ready starter template powered by Nuxt UI. Build beautiful, accessible, and performant applications in minutes, not hours.' const description = "A production-ready starter template powered by Nuxt UI. Build beautiful, accessible, and performant applications in minutes, not hours."
useSeoMeta({ useSeoMeta({
title, title,
description, description,
ogTitle: title, ogTitle: title,
ogDescription: description, ogDescription: description,
ogImage: 'https://ui.nuxt.com/assets/templates/nuxt/starter-light.png', ogImage: "https://ui.nuxt.com/assets/templates/nuxt/starter-light.png",
twitterCard: 'summary_large_image' twitterCard: "summary_large_image",
}) },)
</script> </script>
<template> <template>

View file

@ -7,28 +7,28 @@
to: 'https://starter-template.nuxt.dev/', to: 'https://starter-template.nuxt.dev/',
color: 'primary', color: 'primary',
checked: true, checked: true,
type: 'checkbox' type: 'checkbox',
}, { }, {
label: 'Landing', label: 'Landing',
to: 'https://landing-template.nuxt.dev/' to: 'https://landing-template.nuxt.dev/',
}, { }, {
label: 'Docs', label: 'Docs',
to: 'https://docs-template.nuxt.dev/' to: 'https://docs-template.nuxt.dev/',
}, { }, {
label: 'SaaS', label: 'SaaS',
to: 'https://saas-template.nuxt.dev/' to: 'https://saas-template.nuxt.dev/',
}, { }, {
label: 'Dashboard', label: 'Dashboard',
to: 'https://dashboard-template.nuxt.dev/' to: 'https://dashboard-template.nuxt.dev/',
}, { }, {
label: 'Chat', label: 'Chat',
to: 'https://chat-template.nuxt.dev/' to: 'https://chat-template.nuxt.dev/',
}, { }, {
label: 'Portfolio', label: 'Portfolio',
to: 'https://portfolio-template.nuxt.dev/' to: 'https://portfolio-template.nuxt.dev/',
}, { }, {
label: 'Changelog', label: 'Changelog',
to: 'https://changelog-template.nuxt.dev/' to: 'https://changelog-template.nuxt.dev/',
}]" }]"
:content="{ align: 'start' }" :content="{ align: 'start' }"
:ui="{ content: 'min-w-fit' }" :ui="{ content: 'min-w-fit' }"
@ -42,7 +42,7 @@
class="-mb-[6px] font-semibold rounded-full truncate" class="-mb-[6px] font-semibold rounded-full truncate"
:class="[open && 'bg-primary/15']" :class="[open && 'bg-primary/15']"
:ui="{ :ui="{
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ') trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' '),
}" }"
/> />
</UDropdownMenu> </UDropdownMenu>

View file

@ -8,7 +8,7 @@
to: 'https://ui.nuxt.com/docs/getting-started/installation/nuxt', to: 'https://ui.nuxt.com/docs/getting-started/installation/nuxt',
target: '_blank', target: '_blank',
trailingIcon: 'i-lucide-arrow-right', trailingIcon: 'i-lucide-arrow-right',
size: 'xl' size: 'xl',
}, { }, {
label: 'Use this template', label: 'Use this template',
to: 'https://github.com/nuxt-ui-templates/starter', to: 'https://github.com/nuxt-ui-templates/starter',
@ -16,7 +16,7 @@
icon: 'i-simple-icons-github', icon: 'i-simple-icons-github',
size: 'xl', size: 'xl',
color: 'neutral', color: 'neutral',
variant: 'subtle' variant: 'subtle',
}]" }]"
/> />
@ -27,27 +27,27 @@
:features="[{ :features="[{
icon: 'i-lucide-rocket', icon: 'i-lucide-rocket',
title: 'Production-ready from day one', title: 'Production-ready from day one',
description: 'Pre-configured with TypeScript, ESLint, Tailwind CSS, and all the best practices. Focus on building features, not setting up tooling.' description: 'Pre-configured with TypeScript, ESLint, Tailwind CSS, and all the best practices. Focus on building features, not setting up tooling.',
}, { }, {
icon: 'i-lucide-palette', icon: 'i-lucide-palette',
title: 'Beautiful by default', title: 'Beautiful by default',
description: 'Leveraging Nuxt UI\'s design system with automatic dark mode, consistent spacing, and polished components that look great out of the box.' description: 'Leveraging Nuxt UI\'s design system with automatic dark mode, consistent spacing, and polished components that look great out of the box.',
}, { }, {
icon: 'i-lucide-zap', icon: 'i-lucide-zap',
title: 'Lightning fast', title: 'Lightning fast',
description: 'Optimized for performance with SSR/SSG support, automatic code splitting, and edge-ready deployment. Your users will love the speed.' description: 'Optimized for performance with SSR/SSG support, automatic code splitting, and edge-ready deployment. Your users will love the speed.',
}, { }, {
icon: 'i-lucide-blocks', icon: 'i-lucide-blocks',
title: '100+ components included', title: '100+ components included',
description: 'Access Nuxt UI\'s comprehensive component library. From forms to navigation, everything is accessible, responsive, and customizable.' description: 'Access Nuxt UI\'s comprehensive component library. From forms to navigation, everything is accessible, responsive, and customizable.',
}, { }, {
icon: 'i-lucide-code-2', icon: 'i-lucide-code-2',
title: 'Developer experience first', title: 'Developer experience first',
description: 'Auto-imports, hot module replacement, and TypeScript support. Write less boilerplate and ship more features.' description: 'Auto-imports, hot module replacement, and TypeScript support. Write less boilerplate and ship more features.',
}, { }, {
icon: 'i-lucide-shield-check', icon: 'i-lucide-shield-check',
title: 'Built for scale', title: 'Built for scale',
description: 'Enterprise-ready architecture with proper error handling, SEO optimization, and security best practices built-in.' description: 'Enterprise-ready architecture with proper error handling, SEO optimization, and security best practices built-in.',
}]" }]"
/> />
@ -61,14 +61,14 @@
to: 'https://ui.nuxt.com/docs/getting-started/installation/nuxt', to: 'https://ui.nuxt.com/docs/getting-started/installation/nuxt',
target: '_blank', target: '_blank',
trailingIcon: 'i-lucide-arrow-right', trailingIcon: 'i-lucide-arrow-right',
color: 'neutral' color: 'neutral',
}, { }, {
label: 'View on GitHub', label: 'View on GitHub',
to: 'https://github.com/nuxt-ui-templates/starter', to: 'https://github.com/nuxt-ui-templates/starter',
target: '_blank', target: '_blank',
icon: 'i-simple-icons-github', icon: 'i-simple-icons-github',
color: 'neutral', color: 'neutral',
variant: 'outline' variant: 'outline',
}]" }]"
/> />
</UPageSection> </UPageSection>

View file

@ -1,6 +1,9 @@
// @ts-check // @ts-check
import withNuxt from './.nuxt/eslint.config.mjs' import withNuxt from "./.nuxt/eslint.config.mjs"
import { baseRules, } from "@workspace/tooling/eslint/base"
export default withNuxt( export default withNuxt({
// Your custom configs here rules: {
) ...baseRules,
},
},)

View file

@ -1,28 +1,25 @@
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
modules: [ modules: ["@nuxt/eslint", "@nuxt/ui"],
'@nuxt/eslint',
'@nuxt/ui'
],
devtools: { devtools: {
enabled: true enabled: true,
}, },
css: ['~/assets/css/main.css'], css: ["~/assets/css/main.css"],
routeRules: { routeRules: {
'/': { prerender: true } "/": { prerender: true },
}, },
compatibilityDate: '2025-01-15', compatibilityDate: "2025-01-15",
eslint: { eslint: {
config: { config: {
stylistic: { stylistic: {
commaDangle: 'never', quotes: "double",
braceStyle: '1tbs' braceStyle: "1tbs",
} },
} },
} },
}) });

View file

@ -8,7 +8,8 @@
"preview": "nuxt preview", "preview": "nuxt preview",
"postinstall": "nuxt prepare", "postinstall": "nuxt prepare",
"lint": "eslint .", "lint": "eslint .",
"typecheck": "nuxt typecheck" "typecheck": "nuxt typecheck",
"check-types": "nuxt typecheck"
}, },
"dependencies": { "dependencies": {
"@iconify-json/lucide": "^1.2.108", "@iconify-json/lucide": "^1.2.108",
@ -27,6 +28,7 @@
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint": "^1.15.2", "@nuxt/eslint": "^1.15.2",
"@workspace/tooling": "workspace:*",
"eslint": "^10.4.0", "eslint": "^10.4.0",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"vue-tsc": "^3.3.0" "vue-tsc": "^3.3.0"

View file

@ -6,9 +6,11 @@
"dev": "turbo run dev", "dev": "turbo run dev",
"lint": "turbo run lint", "lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\"", "format": "prettier --write \"**/*.{ts,tsx,md}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,md}\"",
"check-types": "turbo run check-types" "check-types": "turbo run check-types"
}, },
"devDependencies": { "devDependencies": {
"@workspace/tooling": "workspace:*",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"turbo": "^2.9.16", "turbo": "^2.9.16",
"typescript": "5.9.2" "typescript": "5.9.2"

View file

@ -0,0 +1,23 @@
// @ts-check
import { base } from "@workspace/tooling/eslint/base";
import { defineConfig } from "eslint/config";
export default defineConfig(
{ ignores: ["eslint.config.mjs", "dist"] },
...base,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
rules: {
"@typescript-eslint/no-floating-promises": "warn",
"@typescript-eslint/no-unsafe-argument": "warn",
"@typescript-eslint/no-unsafe-return": "warn",
},
},
);

View file

@ -14,7 +14,9 @@
}, },
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"clean": "rm -rf dist" "clean": "rm -rf dist",
"check-types": "tsc --noEmit",
"lint": "eslint \"src/**/*.ts\" --fix"
}, },
"dependencies": { "dependencies": {
"drizzle-orm": "^0.43.1", "drizzle-orm": "^0.43.1",
@ -23,6 +25,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.14.1", "@types/node": "^22.14.1",
"@workspace/tooling": "workspace:*",
"eslint": "^9.0.0",
"typescript": "^5.7.3" "typescript": "^5.7.3"
} }
} }

View file

@ -1,11 +1,6 @@
export const PROJECT_STATUSES = ["active", "archived", "completed"] as const; export const PROJECT_STATUSES = ["active", "archived", "completed"] as const;
export const TASK_STATUSES = [ export const TASK_STATUSES = ["todo", "in_progress", "in_review", "done"] as const;
"todo",
"in_progress",
"in_review",
"done",
] as const;
export const PRIORITIES = ["low", "medium", "high", "urgent"] as const; export const PRIORITIES = ["low", "medium", "high", "urgent"] as const;

View file

@ -11,17 +11,8 @@ export const projects = pgTable(
name: text("name").notNull(), name: text("name").notNull(),
description: text("description"), description: text("description"),
status: text("status").notNull().default("active"), status: text("status").notNull().default("active"),
createdAt: timestamp("created_at", { mode: "string" }) createdAt: timestamp("created_at", { mode: "string" }).notNull().defaultNow(),
.notNull() updatedAt: timestamp("updated_at", { mode: "string" }).notNull().defaultNow(),
.defaultNow(),
updatedAt: timestamp("updated_at", { mode: "string" })
.notNull()
.defaultNow(),
}, },
(table) => [ (table) => [check("project_status_check", sql`${table.status} IN (${sql.raw(projectStatusValues)})`)],
check(
"project_status_check",
sql`${table.status} IN (${sql.raw(projectStatusValues)})`,
),
],
); );

View file

@ -1,11 +1,4 @@
import { import { pgTable, uuid, text, integer, timestamp, check } from "drizzle-orm/pg-core";
pgTable,
uuid,
text,
integer,
timestamp,
check,
} from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm"; import { sql } from "drizzle-orm";
import { TASK_STATUSES, PRIORITIES } from "./enums"; import { TASK_STATUSES, PRIORITIES } from "./enums";
import { projects } from "./projects"; import { projects } from "./projects";
@ -26,21 +19,11 @@ export const tasks = pgTable(
priority: text("priority").notNull().default("medium"), priority: text("priority").notNull().default("medium"),
sortOrder: integer("sort_order").notNull().default(0), sortOrder: integer("sort_order").notNull().default(0),
dueDate: timestamp("due_date", { mode: "string" }), dueDate: timestamp("due_date", { mode: "string" }),
createdAt: timestamp("created_at", { mode: "string" }) createdAt: timestamp("created_at", { mode: "string" }).notNull().defaultNow(),
.notNull() updatedAt: timestamp("updated_at", { mode: "string" }).notNull().defaultNow(),
.defaultNow(),
updatedAt: timestamp("updated_at", { mode: "string" })
.notNull()
.defaultNow(),
}, },
(table) => [ (table) => [
check( check("task_status_check", sql`${table.status} IN (${sql.raw(taskStatusValues)})`),
"task_status_check", check("task_priority_check", sql`${table.priority} IN (${sql.raw(priorityValues)})`),
sql`${table.status} IN (${sql.raw(taskStatusValues)})`,
),
check(
"task_priority_check",
sql`${table.priority} IN (${sql.raw(priorityValues)})`,
),
], ],
); );

View file

@ -8,14 +8,7 @@ export {
tasksRelations, tasksRelations,
} from "./drizzle/index"; } from "./drizzle/index";
export { export { projectSelect, projectInsert, projectUpdate, taskSelect, taskInsert, taskUpdate } from "./valibot";
projectSelect,
projectInsert,
projectUpdate,
taskSelect,
taskInsert,
taskUpdate,
} from "./valibot";
import type { InferOutput } from "valibot"; import type { InferOutput } from "valibot";
import { projectSelect, taskSelect } from "./valibot"; import { projectSelect, taskSelect } from "./valibot";

View file

@ -1,16 +1,6 @@
import { import { createInsertSchema, createSelectSchema, createUpdateSchema } from "drizzle-valibot";
createInsertSchema,
createSelectSchema,
createUpdateSchema,
} from "drizzle-valibot";
import { pipe, picklist } from "valibot"; import { pipe, picklist } from "valibot";
import { import { projects, tasks, PROJECT_STATUSES, TASK_STATUSES, PRIORITIES } from "./drizzle/index";
projects,
tasks,
PROJECT_STATUSES,
TASK_STATUSES,
PRIORITIES,
} from "./drizzle/index";
export const projectSelect = createSelectSchema(projects, { export const projectSelect = createSelectSchema(projects, {
status: (schema: any) => pipe(schema, picklist([...PROJECT_STATUSES])) as any, status: (schema: any) => pipe(schema, picklist([...PROJECT_STATUSES])) as any,

View file

@ -1,20 +1,8 @@
{ {
"extends": "@workspace/tooling/tsconfig/library.json",
"compilerOptions": { "compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ES2023",
"lib": ["ES2023"],
"strict": true,
"strictNullChecks": true,
"esModuleInterop": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist", "outDir": "./dist",
"rootDir": "./src", "rootDir": "./src"
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]

View file

@ -0,0 +1,55 @@
// @ts-check
import eslint from "@eslint/js";
import { defineConfig } from "eslint/config";
import stylistic from "@stylistic/eslint-plugin";
import tseslint from "typescript-eslint";
export { eslint, tseslint };
/** @type {import("eslint").Linter.RulesRecord} */
export const baseRules = {
"@typescript-eslint/no-explicit-any": "off",
"no-constructor-return": "error",
"no-duplicate-imports": "error",
"no-unreachable-loop": "error",
"require-atomic-updates": "error",
"class-methods-use-this": "error",
complexity: "warn",
"max-params": "warn",
"max-depth": "warn",
"max-lines": "warn",
"max-lines-per-function": "warn",
"max-statements": "warn",
"max-nested-callbacks": "warn",
"no-magic-numbers": [
"warn",
{
ignore: [0, 1, 10, 100, 1000, 60, 200, 201, 204, 301, 302, 304, 400, 401, 403, 404, 500],
ignoreArrayIndexes: true,
ignoreDefaultValues: true,
ignoreClassFieldInitialValues: true,
enforceConst: true,
detectObjects: true,
ignoreEnums: true,
ignoreNumericLiteralTypes: true,
ignoreReadonlyClassProperties: true,
ignoreTypeIndexes: true,
},
],
"no-eval": "error",
"no-implicit-coercion": "error",
"no-negated-condition": "warn",
"no-useless-rename": "error",
"no-var": "error",
"prefer-const": "error",
"@stylistic/quotes": ["error", "double"],
"@stylistic/semi": "off",
"@stylistic/comma-dangle": "off",
};
export const base = defineConfig(eslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, {
plugins: {
"@stylistic": stylistic,
},
rules: baseRules,
});

View file

@ -0,0 +1,42 @@
// @ts-check
import { base as baseConfig } from "./base.mjs";
import { defineConfig } from "eslint/config";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import globals from "globals";
export { globals };
/**
* @param {string} tsconfigRootDir
* @param {Record<string, import("eslint").Linter.RuleEntry>} [overrides]
* @returns {ReturnType<import("eslint/config").defineConfig>}
*/
export function createNestjsConfig(tsconfigRootDir, overrides = {}) {
return defineConfig(
{ ignores: ["eslint.config.mjs"] },
...baseConfig,
eslintPluginPrettierRecommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
sourceType: "commonjs",
parserOptions: {
projectService: true,
tsconfigRootDir,
},
},
},
{
rules: {
"class-methods-use-this": "off",
"@typescript-eslint/no-floating-promises": "warn",
"@typescript-eslint/no-unsafe-argument": "warn",
"prettier/prettier": ["error", { endOfLine: "auto" }],
...overrides,
},
},
);
}

View file

@ -0,0 +1,27 @@
{
"name": "@workspace/tooling",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
"./eslint/base": "./eslint/base.mjs",
"./eslint/nestjs": "./eslint/nestjs.mjs",
"./prettier": "./prettier/index.mjs",
"./tsconfig/base.json": "./tsconfig/base.json",
"./tsconfig/nestjs.json": "./tsconfig/nestjs.json",
"./tsconfig/library.json": "./tsconfig/library.json"
},
"dependencies": {
"@eslint/js": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"@stylistic/eslint-plugin": "^5.0.0",
"globals": "^16.0.0",
"typescript-eslint": "^8.20.0"
},
"peerDependencies": {
"eslint": "^9.0.0",
"prettier": "^3.0.0",
"typescript": "^5.0.0"
}
}

View file

@ -0,0 +1,12 @@
// @ts-check
/** @type {import("prettier").Config} */
const config = {
singleQuote: false,
trailingComma: "all",
bracketSameLine: true,
printWidth: 120,
semi: true,
tabWidth: 2,
};
export default config;

View file

@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2023",
"lib": ["ES2023"],
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true,
"resolvePackageJsonExports": true
}
}

View file

@ -0,0 +1,11 @@
{
"extends": "@workspace/tooling/tsconfig/base.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
}
}

View file

@ -0,0 +1,16 @@
{
"extends": "@workspace/tooling/tsconfig/base.json",
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": true,
"sourceMap": true,
"declaration": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false,
"allowSyntheticDefaultImports": true
}
}

View file

@ -8,6 +8,9 @@ importers:
.: .:
devDependencies: devDependencies:
'@workspace/tooling':
specifier: workspace:*
version: link:packages/tooling
prettier: prettier:
specifier: ^3.7.4 specifier: ^3.7.4
version: 3.7.4 version: 3.7.4
@ -39,12 +42,6 @@ importers:
specifier: ^7.8.1 specifier: ^7.8.1
version: 7.8.2 version: 7.8.2
devDependencies: devDependencies:
'@eslint/eslintrc':
specifier: ^3.2.0
version: 3.3.1
'@eslint/js':
specifier: ^9.18.0
version: 9.39.1
'@nestjs/cli': '@nestjs/cli':
specifier: ^11.0.0 specifier: ^11.0.0
version: 11.0.21(@types/node@22.15.3)(prettier@3.7.4) version: 11.0.21(@types/node@22.15.3)(prettier@3.7.4)
@ -66,18 +63,12 @@ importers:
'@types/supertest': '@types/supertest':
specifier: ^6.0.2 specifier: ^6.0.2
version: 6.0.3 version: 6.0.3
'@workspace/tooling':
specifier: workspace:*
version: link:../../packages/tooling
eslint: eslint:
specifier: ^9.18.0 specifier: ^9.18.0
version: 9.39.1(jiti@2.7.0) version: 9.39.1(jiti@2.7.0)
eslint-config-prettier:
specifier: ^10.0.1
version: 10.1.1(eslint@9.39.1(jiti@2.7.0))
eslint-plugin-prettier:
specifier: ^5.2.2
version: 5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.1(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0))(prettier@3.7.4)
globals:
specifier: ^16.0.0
version: 16.5.0
jest: jest:
specifier: ^30.0.0 specifier: ^30.0.0
version: 30.4.2(@types/node@22.15.3)(ts-node@10.9.2(@types/node@22.15.3)(typescript@5.9.2)) version: 30.4.2(@types/node@22.15.3)(ts-node@10.9.2(@types/node@22.15.3)(typescript@5.9.2))
@ -105,9 +96,6 @@ importers:
typescript: typescript:
specifier: ^5.7.3 specifier: ^5.7.3
version: 5.9.2 version: 5.9.2
typescript-eslint:
specifier: ^8.20.0
version: 8.50.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.2)
apps/web: apps/web:
dependencies: dependencies:
@ -154,6 +142,9 @@ importers:
'@nuxt/eslint': '@nuxt/eslint':
specifier: ^1.15.2 specifier: ^1.15.2
version: 1.15.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.35)(eslint-webpack-plugin@4.2.0(eslint@10.4.1(jiti@2.7.0))(webpack@5.106.0(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)))(eslint@10.4.1(jiti@2.7.0))(magicast@0.5.3)(typescript@6.0.3)(vite@7.3.5(@types/node@22.15.3)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) version: 1.15.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.35)(eslint-webpack-plugin@4.2.0(eslint@10.4.1(jiti@2.7.0))(webpack@5.106.0(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)))(eslint@10.4.1(jiti@2.7.0))(magicast@0.5.3)(typescript@6.0.3)(vite@7.3.5(@types/node@22.15.3)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0))
'@workspace/tooling':
specifier: workspace:*
version: link:../../packages/tooling
eslint: eslint:
specifier: ^10.4.0 specifier: ^10.4.0
version: 10.4.1(jiti@2.7.0) version: 10.4.1(jiti@2.7.0)
@ -179,10 +170,46 @@ importers:
'@types/node': '@types/node':
specifier: ^22.14.1 specifier: ^22.14.1
version: 22.15.3 version: 22.15.3
'@workspace/tooling':
specifier: workspace:*
version: link:../tooling
eslint:
specifier: ^9.0.0
version: 9.39.1(jiti@2.7.0)
typescript: typescript:
specifier: ^5.7.3 specifier: ^5.7.3
version: 5.9.2 version: 5.9.2
packages/tooling:
dependencies:
'@eslint/js':
specifier: ^9.18.0
version: 9.39.4
'@stylistic/eslint-plugin':
specifier: ^5.0.0
version: 5.10.0(eslint@9.39.1(jiti@2.7.0))
eslint:
specifier: ^9.0.0
version: 9.39.1(jiti@2.7.0)
eslint-config-prettier:
specifier: ^10.0.1
version: 10.1.1(eslint@9.39.1(jiti@2.7.0))
eslint-plugin-prettier:
specifier: ^5.2.2
version: 5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.1(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0))(prettier@3.8.3)
globals:
specifier: ^16.0.0
version: 16.5.0
prettier:
specifier: ^3.0.0
version: 3.8.3
typescript:
specifier: ^5.0.0
version: 5.9.2
typescript-eslint:
specifier: ^8.20.0
version: 8.50.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.2)
packages: packages:
'@alloc/quick-lru@5.2.0': '@alloc/quick-lru@5.2.0':
@ -9043,16 +9070,16 @@ snapshots:
eslint: 10.4.1(jiti@2.7.0) eslint: 10.4.1(jiti@2.7.0)
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.7.0))':
dependencies:
eslint: 9.39.1(jiti@2.7.0)
eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(jiti@2.7.0))': '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(jiti@2.7.0))':
dependencies: dependencies:
eslint: 10.4.1(jiti@2.7.0) eslint: 10.4.1(jiti@2.7.0)
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.1(jiti@2.7.0))':
dependencies:
eslint: 9.39.1(jiti@2.7.0)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {} '@eslint-community/regexpp@4.12.2': {}
'@eslint/compat@2.1.0(eslint@10.4.1(jiti@2.7.0))': '@eslint/compat@2.1.0(eslint@10.4.1(jiti@2.7.0))':
@ -9114,7 +9141,7 @@ snapshots:
'@eslint/eslintrc@3.3.1': '@eslint/eslintrc@3.3.1':
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.15.0
debug: 4.4.3 debug: 4.4.3
espree: 10.4.0 espree: 10.4.0
globals: 14.0.0 globals: 14.0.0
@ -11392,6 +11419,16 @@ snapshots:
estraverse: 5.3.0 estraverse: 5.3.0
picomatch: 4.0.4 picomatch: 4.0.4
'@stylistic/eslint-plugin@5.10.0(eslint@9.39.1(jiti@2.7.0))':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1(jiti@2.7.0))
'@typescript-eslint/types': 8.60.1
eslint: 9.39.1(jiti@2.7.0)
eslint-visitor-keys: 4.2.1
espree: 10.4.0
estraverse: 5.3.0
picomatch: 4.0.4
'@swc/helpers@0.5.23': '@swc/helpers@0.5.23':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
@ -11795,7 +11832,7 @@ snapshots:
'@types/eslint@9.6.1': '@types/eslint@9.6.1':
dependencies: dependencies:
'@types/estree': 1.0.8 '@types/estree': 1.0.9
'@types/json-schema': 7.0.15 '@types/json-schema': 7.0.15
'@types/esrecurse@4.3.1': {} '@types/esrecurse@4.3.1': {}
@ -11894,7 +11931,7 @@ snapshots:
eslint: 9.39.1(jiti@2.7.0) eslint: 9.39.1(jiti@2.7.0)
ignore: 7.0.5 ignore: 7.0.5
natural-compare: 1.4.0 natural-compare: 1.4.0
ts-api-utils: 2.1.0(typescript@5.9.2) ts-api-utils: 2.5.0(typescript@5.9.2)
typescript: 5.9.2 typescript: 5.9.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -11995,7 +12032,7 @@ snapshots:
'@typescript-eslint/utils': 8.50.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.2) '@typescript-eslint/utils': 8.50.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.2)
debug: 4.4.3 debug: 4.4.3
eslint: 9.39.1(jiti@2.7.0) eslint: 9.39.1(jiti@2.7.0)
ts-api-utils: 2.1.0(typescript@5.9.2) ts-api-utils: 2.5.0(typescript@5.9.2)
typescript: 5.9.2 typescript: 5.9.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -12063,7 +12100,7 @@ snapshots:
'@typescript-eslint/utils@8.50.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.2)': '@typescript-eslint/utils@8.50.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.2)':
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.7.0)) '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1(jiti@2.7.0))
'@typescript-eslint/scope-manager': 8.50.0 '@typescript-eslint/scope-manager': 8.50.0
'@typescript-eslint/types': 8.50.0 '@typescript-eslint/types': 8.50.0
'@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.2) '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.2)
@ -13523,10 +13560,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-plugin-prettier@5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.1(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0))(prettier@3.7.4): eslint-plugin-prettier@5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.1(eslint@9.39.1(jiti@2.7.0)))(eslint@9.39.1(jiti@2.7.0))(prettier@3.8.3):
dependencies: dependencies:
eslint: 9.39.1(jiti@2.7.0) eslint: 9.39.1(jiti@2.7.0)
prettier: 3.7.4 prettier: 3.8.3
prettier-linter-helpers: 1.0.1 prettier-linter-helpers: 1.0.1
synckit: 0.11.13 synckit: 0.11.13
optionalDependencies: optionalDependencies:
@ -13661,7 +13698,7 @@ snapshots:
eslint@9.39.1(jiti@2.7.0): eslint@9.39.1(jiti@2.7.0):
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.7.0)) '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.1(jiti@2.7.0))
'@eslint-community/regexpp': 4.12.2 '@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.21.1 '@eslint/config-array': 0.21.1
'@eslint/config-helpers': 0.4.2 '@eslint/config-helpers': 0.4.2
@ -13672,8 +13709,8 @@ snapshots:
'@humanfs/node': 0.16.7 '@humanfs/node': 0.16.7
'@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.3 '@humanwhocodes/retry': 0.4.3
'@types/estree': 1.0.8 '@types/estree': 1.0.9
ajv: 6.12.6 ajv: 6.15.0
chalk: 4.1.2 chalk: 4.1.2
cross-spawn: 7.0.6 cross-spawn: 7.0.6
debug: 4.4.3 debug: 4.4.3
@ -13681,7 +13718,7 @@ snapshots:
eslint-scope: 8.4.0 eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1 eslint-visitor-keys: 4.2.1
espree: 10.4.0 espree: 10.4.0
esquery: 1.6.0 esquery: 1.7.0
esutils: 2.0.3 esutils: 2.0.3
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
file-entry-cache: 8.0.0 file-entry-cache: 8.0.0
@ -14403,7 +14440,7 @@ snapshots:
is-reference@1.2.1: is-reference@1.2.1:
dependencies: dependencies:
'@types/estree': 1.0.8 '@types/estree': 1.0.9
is-stream@2.0.1: {} is-stream@2.0.1: {}
@ -16858,6 +16895,10 @@ snapshots:
dependencies: dependencies:
typescript: 6.0.3 typescript: 6.0.3
ts-api-utils@2.5.0(typescript@5.9.2):
dependencies:
typescript: 5.9.2
ts-api-utils@2.5.0(typescript@6.0.3): ts-api-utils@2.5.0(typescript@6.0.3):
dependencies: dependencies:
typescript: 6.0.3 typescript: 6.0.3