@lousy-agents/cli 2.2.0 → 2.3.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/api/copilot-with-fastify/biome.json +1 -1
- package/cli/copilot-with-citty/.devcontainer/devcontainer.json +76 -0
- package/cli/copilot-with-citty/.editorconfig +16 -0
- package/cli/copilot-with-citty/.github/ISSUE_TEMPLATE/feature-to-spec.yml +54 -0
- package/cli/copilot-with-citty/.github/copilot-instructions.md +228 -0
- package/cli/copilot-with-citty/.github/instructions/pipeline.instructions.md +92 -0
- package/cli/copilot-with-citty/.github/instructions/software-architecture.instructions.md +166 -0
- package/cli/copilot-with-citty/.github/instructions/spec.instructions.md +127 -0
- package/cli/copilot-with-citty/.github/instructions/test.instructions.md +157 -0
- package/cli/copilot-with-citty/.github/specs/README.md +84 -0
- package/cli/copilot-with-citty/.github/workflows/assign-copilot.yml +59 -0
- package/cli/copilot-with-citty/.github/workflows/ci.yml +67 -0
- package/cli/copilot-with-citty/.nvmrc +1 -0
- package/cli/copilot-with-citty/.vscode/extensions.json +13 -0
- package/cli/copilot-with-citty/.vscode/launch.json +25 -0
- package/cli/copilot-with-citty/.vscode/mcp.json +19 -0
- package/cli/copilot-with-citty/.yamllint +18 -0
- package/cli/copilot-with-citty/biome.json +31 -0
- package/cli/copilot-with-citty/package.json +29 -0
- package/cli/copilot-with-citty/tsconfig.json +28 -0
- package/cli/copilot-with-citty/vitest.config.ts +15 -0
- package/cli/copilot-with-citty/vitest.setup.ts +2 -0
- package/dist/index.js +224 -59
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +32 -1
- package/dist/mcp-server.js.map +1 -1
- package/package.json +10 -9
- package/ui/copilot-with-react/biome.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= it.projectName %>",
|
|
3
|
+
"image": "mcr.microsoft.com/devcontainers/javascript-node:24-bookworm",
|
|
4
|
+
"features": {
|
|
5
|
+
"ghcr.io/devcontainers/features/github-cli:1": {
|
|
6
|
+
"version": "2.83.2"
|
|
7
|
+
},
|
|
8
|
+
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
|
|
9
|
+
"packages": "yamllint, shellcheck",
|
|
10
|
+
"upgradePackages": true
|
|
11
|
+
},
|
|
12
|
+
"ghcr.io/devcontainers-extra/features/actionlint:1": {
|
|
13
|
+
"version": "1.7.9"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"forwardPorts": [],
|
|
17
|
+
"postCreateCommand": "npm cache add @upstash/context7-mcp @modelcontextprotocol/server-sequential-thinking",
|
|
18
|
+
"waitFor": "postCreateCommand",
|
|
19
|
+
"remoteUser": "node",
|
|
20
|
+
"customizations": {
|
|
21
|
+
"vscode": {
|
|
22
|
+
"extensions": [
|
|
23
|
+
"biomejs.biome",
|
|
24
|
+
"editorconfig.editorconfig",
|
|
25
|
+
"vitest.explorer",
|
|
26
|
+
"ms-vscode-remote.remote-containers",
|
|
27
|
+
"GitHub.codespaces",
|
|
28
|
+
"GitHub.Copilot",
|
|
29
|
+
"GitHub.Copilot-Chat",
|
|
30
|
+
"GitHub.github-vscode-theme",
|
|
31
|
+
"redhat.vscode-yaml"
|
|
32
|
+
],
|
|
33
|
+
"settings": {
|
|
34
|
+
"editor.formatOnSave": true,
|
|
35
|
+
"editor.defaultFormatter": "biomejs.biome",
|
|
36
|
+
"[json]": {
|
|
37
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
38
|
+
},
|
|
39
|
+
"[javascript]": {
|
|
40
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
41
|
+
},
|
|
42
|
+
"[typescript]": {
|
|
43
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
44
|
+
},
|
|
45
|
+
"[yaml]": {
|
|
46
|
+
"editor.defaultFormatter": "redhat.vscode-yaml",
|
|
47
|
+
"editor.tabSize": 2
|
|
48
|
+
},
|
|
49
|
+
"vitest.enable": true,
|
|
50
|
+
"vitest.commandLine": "vitest",
|
|
51
|
+
"github.copilot.chat.agent.model": "claude-sonnet-4.5",
|
|
52
|
+
"github.copilot.chat.ask.model": "claude-sonnet-4.5",
|
|
53
|
+
"yaml.schemas": {
|
|
54
|
+
"https://json.schemastore.org/github-workflow.json": ".github/workflows/*.yml"
|
|
55
|
+
},
|
|
56
|
+
"github.copilot.chat.mcp.enabled": true
|
|
57
|
+
},
|
|
58
|
+
"mcp": {
|
|
59
|
+
"servers": {
|
|
60
|
+
"context7": {
|
|
61
|
+
"type": "stdio",
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["-y", "@upstash/context7-mcp"]
|
|
64
|
+
},
|
|
65
|
+
"sequential-thinking": {
|
|
66
|
+
"command": "npx",
|
|
67
|
+
"args": [
|
|
68
|
+
"-y",
|
|
69
|
+
"@modelcontextprotocol/server-sequential-thinking"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
indent_size = 4
|
|
7
|
+
indent_style = space
|
|
8
|
+
insert_final_newline = true
|
|
9
|
+
max_line_length = off
|
|
10
|
+
trim_trailing_whitespace = true
|
|
11
|
+
|
|
12
|
+
[{*.json}]
|
|
13
|
+
indent_size = 4
|
|
14
|
+
|
|
15
|
+
[{*.yaml,*.yml}]
|
|
16
|
+
indent_size = 2
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Copilot Feature To Spec
|
|
3
|
+
description: Create a spec for the Copilot coding agent.
|
|
4
|
+
title: "[Spec]: "
|
|
5
|
+
labels: ["copilot-ready", "enhancement"]
|
|
6
|
+
body:
|
|
7
|
+
- type: markdown
|
|
8
|
+
attributes:
|
|
9
|
+
value: |
|
|
10
|
+
## Copilot Specification
|
|
11
|
+
Please fill out the details below.
|
|
12
|
+
This structure is parsed by the Copilot Agent.
|
|
13
|
+
|
|
14
|
+
- type: textarea
|
|
15
|
+
id: context
|
|
16
|
+
attributes:
|
|
17
|
+
label: Context & Goal
|
|
18
|
+
description: What is the goal? What part of the codebase?
|
|
19
|
+
placeholder: |
|
|
20
|
+
We need to add a new command to the CLI
|
|
21
|
+
tool...
|
|
22
|
+
validations:
|
|
23
|
+
required: true
|
|
24
|
+
|
|
25
|
+
- type: textarea
|
|
26
|
+
id: acceptance-criteria
|
|
27
|
+
attributes:
|
|
28
|
+
label: Acceptance Criteria
|
|
29
|
+
description: How will we verify this is done?
|
|
30
|
+
placeholder: |
|
|
31
|
+
I run `npm run dev -- my-command`.
|
|
32
|
+
I see the expected output.
|
|
33
|
+
validations:
|
|
34
|
+
required: true
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------
|
|
37
|
+
# This section triggers the GitHub Script integration
|
|
38
|
+
# ---------------------------------------------------------
|
|
39
|
+
- type: textarea
|
|
40
|
+
id: extra-instructions
|
|
41
|
+
attributes:
|
|
42
|
+
label: Extra Instructions
|
|
43
|
+
description: Specific prompt engineering for the agent. This will be posted as a comment to guide the bot.
|
|
44
|
+
value: |
|
|
45
|
+
1. Review .github/instructions/spec.instructions.md for more details and use this as your guide for the feature specification.
|
|
46
|
+
2. Ensure you review all other repository instructions before starting work to understand engineering requirements.
|
|
47
|
+
3. Create your final spec output in the .github/specs directory and ensure requirements are captured in the EARS format.
|
|
48
|
+
4. Create a data flow diagram and sequence diagram to illustrate the feature's operation.
|
|
49
|
+
5. Break down the feature into manageable tasks with clear descriptions.
|
|
50
|
+
6. As you complete each task in the spec, mark the checkboxes as complete in the spec file using [x] notation.
|
|
51
|
+
7. If you need to make assumptions, ask questions on the issue.
|
|
52
|
+
8. Use the diagrams to inform your implementation plan.
|
|
53
|
+
validations:
|
|
54
|
+
required: false
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# CLI Application
|
|
6
|
+
|
|
7
|
+
A TypeScript CLI application using citty for command handling and consola for terminal output, following Test-Driven Development, Clean Architecture, and strict validation workflows.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
Run `nvm use` before any npm command. During development, use file-scoped commands for faster feedback, and run the full validation suite (`npx biome check && npm test && npm run build`) before commits.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# ALWAYS run first
|
|
15
|
+
nvm use
|
|
16
|
+
|
|
17
|
+
# Core commands
|
|
18
|
+
npm install # Install deps (updates package-lock.json)
|
|
19
|
+
npm test # Run tests (vitest)
|
|
20
|
+
npm run build # Production build
|
|
21
|
+
npm run dev # Start development with hot reload
|
|
22
|
+
npx biome check # Lint check
|
|
23
|
+
npx biome check --write # Auto-fix lint/format
|
|
24
|
+
|
|
25
|
+
# File-scoped (faster feedback)
|
|
26
|
+
npx biome check path/to/file.ts
|
|
27
|
+
npm test path/to/file.test.ts
|
|
28
|
+
|
|
29
|
+
# Validation suite (run before commits)
|
|
30
|
+
npx biome check && npm test && npm run build
|
|
31
|
+
|
|
32
|
+
# Other
|
|
33
|
+
npm audit # Security check
|
|
34
|
+
npm run lint:workflows # Validate GitHub Actions (actionlint)
|
|
35
|
+
npm run lint:yaml # Validate YAML (yamllint)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Workflow: TDD Required
|
|
39
|
+
|
|
40
|
+
Follow this exact sequence for ALL code changes. Work in small increments — make one change at a time and validate before proceeding.
|
|
41
|
+
|
|
42
|
+
1. **Research**: Search codebase for existing patterns, commands, utilities. Use Context7 MCP tools for library/API documentation.
|
|
43
|
+
2. **Write failing test**: Create test describing desired behavior
|
|
44
|
+
3. **Verify failure**: Run `npm test` — confirm clear failure message
|
|
45
|
+
4. **Implement minimal code**: Write just enough to pass
|
|
46
|
+
5. **Verify pass**: Run `npm test` — confirm pass
|
|
47
|
+
6. **Refactor**: Clean up, remove duplication, keep tests green
|
|
48
|
+
7. **Validate**: `npx biome check && npm test && npm run build`
|
|
49
|
+
|
|
50
|
+
Task is NOT complete until all validation passes.
|
|
51
|
+
|
|
52
|
+
## Tech Stack
|
|
53
|
+
|
|
54
|
+
- **Framework**: citty — lightweight CLI framework with command definitions and argument parsing
|
|
55
|
+
- **Language**: TypeScript (strict mode)
|
|
56
|
+
- **Terminal Output**: consola — elegant console logging with levels and formatting
|
|
57
|
+
- **Validation**: Zod for runtime validation of external data
|
|
58
|
+
- **Testing**: Vitest (never Jest), Chance.js for test fixtures
|
|
59
|
+
- **Linting**: Biome (never ESLint/Prettier separately)
|
|
60
|
+
- **HTTP**: fetch API only (for external service calls)
|
|
61
|
+
- **Architecture**: Clean Architecture principles
|
|
62
|
+
|
|
63
|
+
## Project Structure
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
.github/ GitHub Actions workflows
|
|
67
|
+
src/ Application source code
|
|
68
|
+
entities/ Layer 1: Business domain entities
|
|
69
|
+
use-cases/ Layer 2: Application business rules
|
|
70
|
+
gateways/ Layer 3: External system adapters
|
|
71
|
+
commands/ Layer 3: CLI command handlers
|
|
72
|
+
lib/ Utilities and helpers
|
|
73
|
+
index.ts Application entry point
|
|
74
|
+
tests/ Test files (mirror src/ structure)
|
|
75
|
+
.nvmrc Node.js version (latest LTS)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Code Style
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { defineCommand } from 'citty';
|
|
82
|
+
import { consola } from 'consola';
|
|
83
|
+
import { z } from 'zod';
|
|
84
|
+
|
|
85
|
+
// Define schema for runtime validation
|
|
86
|
+
const ConfigSchema = z.object({
|
|
87
|
+
name: z.string(),
|
|
88
|
+
version: z.string(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
type Config = z.infer<typeof ConfigSchema>;
|
|
92
|
+
|
|
93
|
+
// ✅ Good - small, typed, single purpose, descriptive names, runtime validation
|
|
94
|
+
async function loadConfig(filePath: string): Promise<Config> {
|
|
95
|
+
if (!filePath) {
|
|
96
|
+
throw new Error('File path required');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const content = await readFile(filePath, 'utf-8');
|
|
100
|
+
const data: unknown = JSON.parse(content);
|
|
101
|
+
return ConfigSchema.parse(data);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ❌ Bad - untyped, no validation, multiple responsibilities
|
|
105
|
+
async function doStuff(x) {
|
|
106
|
+
console.log('loading');
|
|
107
|
+
const data = JSON.parse(await readFile(x));
|
|
108
|
+
return data as Config;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Rules:**
|
|
113
|
+
- Always use TypeScript type hints
|
|
114
|
+
- Use descriptive names for variables, functions, and modules
|
|
115
|
+
- Functions must be small and have single responsibility
|
|
116
|
+
- Avoid god functions and classes — break into smaller, focused units
|
|
117
|
+
- Avoid repetitive code — extract reusable functions
|
|
118
|
+
- Extract functions when there are multiple code paths
|
|
119
|
+
- Favor immutability and pure functions
|
|
120
|
+
- Avoid temporal coupling
|
|
121
|
+
- Keep cyclomatic complexity low
|
|
122
|
+
- Remove all unused imports and variables
|
|
123
|
+
- Validate external data at runtime with Zod — never use type assertions (`as Type`) on API responses
|
|
124
|
+
- Run lint and tests after EVERY change
|
|
125
|
+
|
|
126
|
+
## Testing Standards
|
|
127
|
+
|
|
128
|
+
Tests are executable documentation. Use Arrange-Act-Assert pattern. Generate test fixtures with Chance.js.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import Chance from 'chance';
|
|
132
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
133
|
+
import { createConfigLoader } from './config-loader';
|
|
134
|
+
|
|
135
|
+
const chance = new Chance();
|
|
136
|
+
|
|
137
|
+
// ✅ Good - describes behavior, uses generated fixtures, mocks dependencies
|
|
138
|
+
describe('Config Loader', () => {
|
|
139
|
+
describe('given a valid config file path', () => {
|
|
140
|
+
it('loads and validates the configuration', async () => {
|
|
141
|
+
// Arrange
|
|
142
|
+
const configPath = chance.word() + '.json';
|
|
143
|
+
const expectedConfig = {
|
|
144
|
+
name: chance.word(),
|
|
145
|
+
version: chance.semver(),
|
|
146
|
+
};
|
|
147
|
+
const mockReader = vi.fn().mockResolvedValue(JSON.stringify(expectedConfig));
|
|
148
|
+
const loader = createConfigLoader(mockReader);
|
|
149
|
+
|
|
150
|
+
// Act
|
|
151
|
+
const result = await loader.load(configPath);
|
|
152
|
+
|
|
153
|
+
// Assert
|
|
154
|
+
expect(result).toEqual(expectedConfig);
|
|
155
|
+
expect(mockReader).toHaveBeenCalledWith(configPath);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('given an empty file path', () => {
|
|
160
|
+
it('throws a validation error', async () => {
|
|
161
|
+
// Arrange
|
|
162
|
+
const mockReader = vi.fn();
|
|
163
|
+
const loader = createConfigLoader(mockReader);
|
|
164
|
+
|
|
165
|
+
// Act & Assert
|
|
166
|
+
await expect(loader.load('')).rejects.toThrow('File path required');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Rules:**
|
|
173
|
+
- Tests are executable documentation — describe behavior, not implementation
|
|
174
|
+
- Name `describe` blocks for features/scenarios, not function names
|
|
175
|
+
- Name `it` blocks as specifications that read as complete sentences
|
|
176
|
+
- Use nested `describe` blocks for "given/when" context
|
|
177
|
+
- Use Chance.js to generate test fixtures — avoid hardcoded test data
|
|
178
|
+
- Extract test data to constants — never duplicate values across arrange/act/assert
|
|
179
|
+
- Use Vitest (never Jest)
|
|
180
|
+
- Follow Arrange-Act-Assert pattern
|
|
181
|
+
- Tests must be deterministic — same result every run
|
|
182
|
+
- Avoid conditional logic in tests unless absolutely necessary
|
|
183
|
+
- Ensure all code paths have corresponding tests
|
|
184
|
+
- Test happy paths, unhappy paths, and edge cases
|
|
185
|
+
- Never modify tests to pass without understanding root cause
|
|
186
|
+
|
|
187
|
+
## Dependencies
|
|
188
|
+
|
|
189
|
+
- Use latest LTS Node.js — check with `nvm ls-remote --lts`, update `.nvmrc`
|
|
190
|
+
- Pin ALL dependencies to exact versions (no ^ or ~)
|
|
191
|
+
- Use explicit version numbers when adding new dependencies
|
|
192
|
+
- Search npm for latest stable version before adding
|
|
193
|
+
- Run `npm audit` after any dependency change
|
|
194
|
+
- Ensure `package-lock.json` is updated correctly
|
|
195
|
+
- Use Dependabot to keep dependencies current
|
|
196
|
+
|
|
197
|
+
## GitHub Actions
|
|
198
|
+
|
|
199
|
+
- Validation must be automated via GitHub Actions and runnable locally the same way
|
|
200
|
+
- Validate all workflows using actionlint
|
|
201
|
+
- Validate all YAML files using yamllint
|
|
202
|
+
- Pin all 3rd party Actions to specific version or commit SHA
|
|
203
|
+
- Keep all 3rd party Actions updated to latest version
|
|
204
|
+
|
|
205
|
+
## Boundaries
|
|
206
|
+
|
|
207
|
+
**✅ Always do:**
|
|
208
|
+
- Run `nvm use` before any npm command
|
|
209
|
+
- Write tests before implementation (TDD)
|
|
210
|
+
- Run lint and tests after every change
|
|
211
|
+
- Run full validation before commits
|
|
212
|
+
- Use existing patterns from codebase
|
|
213
|
+
- Work in small increments
|
|
214
|
+
- Validate all external data with Zod
|
|
215
|
+
- Use Context7 MCP tools for code generation and documentation
|
|
216
|
+
|
|
217
|
+
**⚠️ Ask first:**
|
|
218
|
+
- Adding new dependencies
|
|
219
|
+
- Changing project structure
|
|
220
|
+
- Modifying GitHub Actions workflows
|
|
221
|
+
|
|
222
|
+
**🚫 Never do:**
|
|
223
|
+
- Skip the TDD workflow
|
|
224
|
+
- Store secrets in code (use environment variables)
|
|
225
|
+
- Use Jest (use Vitest)
|
|
226
|
+
- Modify tests to pass without fixing root cause
|
|
227
|
+
- Add dependencies without explicit version numbers
|
|
228
|
+
- Use type assertions (`as Type`) on external/API data
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: ".github/workflows/*.{yml,yaml}"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Pipeline Instructions for CLI
|
|
6
|
+
|
|
7
|
+
## MANDATORY: After Modifying Workflows
|
|
8
|
+
|
|
9
|
+
Run these validation commands in order:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm run lint:workflows # Validate GitHub Actions workflows with actionlint
|
|
13
|
+
npm run lint:yaml # Validate YAML syntax with yamllint
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Workflow Structure Requirements
|
|
17
|
+
|
|
18
|
+
1. Every workflow MUST include test and lint jobs.
|
|
19
|
+
2. Reference Node.js version from `.nvmrc` using `actions/setup-node` with `node-version-file` input.
|
|
20
|
+
3. Use official setup actions: `actions/checkout`, `actions/setup-node`, `actions/cache`.
|
|
21
|
+
|
|
22
|
+
## Action Pinning Format
|
|
23
|
+
|
|
24
|
+
Pin ALL third-party actions to exact commit SHA with version comment:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
# CORRECT format:
|
|
28
|
+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
|
29
|
+
|
|
30
|
+
# INCORRECT formats (do NOT use):
|
|
31
|
+
uses: actions/checkout@v4 # ❌ version tag only
|
|
32
|
+
uses: actions/checkout@v4.1.1 # ❌ version tag only
|
|
33
|
+
uses: actions/checkout@main # ❌ branch reference
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Before adding any action:
|
|
37
|
+
1. Check GitHub for the LATEST stable version
|
|
38
|
+
2. Find the full commit SHA for that version
|
|
39
|
+
3. Add both SHA and version comment
|
|
40
|
+
|
|
41
|
+
## Runner Requirements
|
|
42
|
+
|
|
43
|
+
| Workflow | Runner |
|
|
44
|
+
|----------|--------|
|
|
45
|
+
| Default (all workflows) | `ubuntu-latest` |
|
|
46
|
+
|
|
47
|
+
## Example CI Workflow
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
name: CI
|
|
51
|
+
|
|
52
|
+
on:
|
|
53
|
+
push:
|
|
54
|
+
branches: [main]
|
|
55
|
+
pull_request:
|
|
56
|
+
branches: [main]
|
|
57
|
+
|
|
58
|
+
jobs:
|
|
59
|
+
lint:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
63
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
64
|
+
with:
|
|
65
|
+
node-version-file: '.nvmrc'
|
|
66
|
+
cache: 'npm'
|
|
67
|
+
- run: npm ci
|
|
68
|
+
- run: npx biome check
|
|
69
|
+
|
|
70
|
+
test:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
74
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
75
|
+
with:
|
|
76
|
+
node-version-file: '.nvmrc'
|
|
77
|
+
cache: 'npm'
|
|
78
|
+
- run: npm ci
|
|
79
|
+
- run: npm test
|
|
80
|
+
|
|
81
|
+
build:
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
needs: [lint, test]
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
86
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
87
|
+
with:
|
|
88
|
+
node-version-file: '.nvmrc'
|
|
89
|
+
cache: 'npm'
|
|
90
|
+
- run: npm ci
|
|
91
|
+
- run: npm run build
|
|
92
|
+
```
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "src/**/*.ts"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Clean Architecture Instructions for CLI
|
|
6
|
+
|
|
7
|
+
## The Dependency Rule
|
|
8
|
+
|
|
9
|
+
Dependencies point inward only. Outer layers depend on inner layers, never the reverse.
|
|
10
|
+
|
|
11
|
+
**Layers (innermost to outermost):**
|
|
12
|
+
1. Entities — Enterprise business rules
|
|
13
|
+
2. Use Cases — Application business rules
|
|
14
|
+
3. Adapters — Interface converters (commands, gateways)
|
|
15
|
+
4. Infrastructure — Frameworks, drivers, composition root
|
|
16
|
+
|
|
17
|
+
## Directory Structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
src/
|
|
21
|
+
├── entities/ # Layer 1: Business domain entities
|
|
22
|
+
├── use-cases/ # Layer 2: Application business rules
|
|
23
|
+
├── gateways/ # Layer 3: External system adapters (file system, APIs)
|
|
24
|
+
├── commands/ # Layer 3: CLI command handlers
|
|
25
|
+
├── lib/ # Layer 3: Configuration and utilities
|
|
26
|
+
└── index.ts # Layer 4: Composition root
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Layer 1: Entities
|
|
30
|
+
|
|
31
|
+
**Location:** `src/entities/`
|
|
32
|
+
|
|
33
|
+
- MUST NOT import from any other layer
|
|
34
|
+
- MUST NOT depend on frameworks or infrastructure
|
|
35
|
+
- MUST NOT use non-deterministic or side-effect-producing global APIs (e.g., `crypto.randomUUID()`, `Date.now()`)
|
|
36
|
+
- MUST be plain TypeScript objects/classes with business logic
|
|
37
|
+
- MAY contain validation and business rules
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// src/entities/project.ts
|
|
41
|
+
export interface Project {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly version: string;
|
|
44
|
+
readonly type: ProjectType;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type ProjectType = 'cli' | 'webapp' | 'api';
|
|
48
|
+
|
|
49
|
+
export function isValidProjectName(name: string): boolean {
|
|
50
|
+
return /^[a-z][a-z0-9._-]*$/.test(name) && name.length <= 214;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Layer 2: Use Cases
|
|
55
|
+
|
|
56
|
+
**Location:** `src/use-cases/`
|
|
57
|
+
|
|
58
|
+
- MUST only import from entities and ports (interfaces)
|
|
59
|
+
- MUST define input/output DTOs
|
|
60
|
+
- MUST define ports for external dependencies
|
|
61
|
+
- MUST NOT import concrete implementations
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// src/use-cases/initialize-project.ts
|
|
65
|
+
import type { Project } from '../entities/project';
|
|
66
|
+
|
|
67
|
+
export interface InitializeProjectInput {
|
|
68
|
+
name: string;
|
|
69
|
+
type: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ProjectGateway {
|
|
73
|
+
createStructure(project: Project): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class InitializeProjectUseCase {
|
|
77
|
+
constructor(private readonly gateway: ProjectGateway) {}
|
|
78
|
+
|
|
79
|
+
async execute(input: InitializeProjectInput): Promise<void> {
|
|
80
|
+
if (!input.name) {
|
|
81
|
+
throw new Error('Project name is required');
|
|
82
|
+
}
|
|
83
|
+
await this.gateway.createStructure({
|
|
84
|
+
name: input.name,
|
|
85
|
+
version: '0.1.0',
|
|
86
|
+
type: input.type as any,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Layer 3: Adapters
|
|
93
|
+
|
|
94
|
+
**Location:** `src/commands/`, `src/gateways/`, and `src/lib/`
|
|
95
|
+
|
|
96
|
+
- MUST implement ports defined by use cases
|
|
97
|
+
- MAY import from entities and use cases
|
|
98
|
+
- MAY use framework-specific code
|
|
99
|
+
- MUST NOT contain business logic
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// src/commands/init.ts
|
|
103
|
+
import { defineCommand } from 'citty';
|
|
104
|
+
import { consola } from 'consola';
|
|
105
|
+
|
|
106
|
+
export const initCommand = defineCommand({
|
|
107
|
+
meta: {
|
|
108
|
+
name: 'init',
|
|
109
|
+
description: 'Initialize a new project',
|
|
110
|
+
},
|
|
111
|
+
args: {
|
|
112
|
+
name: { type: 'string', description: 'Project name' },
|
|
113
|
+
},
|
|
114
|
+
async run({ args }) {
|
|
115
|
+
consola.info(`Initializing project: ${args.name}`);
|
|
116
|
+
// Delegate to use case
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Layer 4: Infrastructure
|
|
122
|
+
|
|
123
|
+
**Location:** `src/index.ts` (composition root)
|
|
124
|
+
|
|
125
|
+
- Composition root wires dependencies
|
|
126
|
+
- MAY import from all layers
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// src/index.ts
|
|
130
|
+
import { defineCommand, runMain } from 'citty';
|
|
131
|
+
import { initCommand } from './commands/init';
|
|
132
|
+
|
|
133
|
+
const main = defineCommand({
|
|
134
|
+
meta: { name: 'my-cli', description: 'My CLI tool' },
|
|
135
|
+
subCommands: { init: initCommand },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
runMain(main);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Import Rules Summary
|
|
142
|
+
|
|
143
|
+
| From | Entities | Use Cases | Commands/Gateways/Lib | Index (Root) |
|
|
144
|
+
|------|----------|-----------|----------------------|--------------|
|
|
145
|
+
| Entities | ✓ | ✗ | ✗ | ✗ |
|
|
146
|
+
| Use Cases | ✓ | ✓ | ✗ | ✗ |
|
|
147
|
+
| Commands/Gateways/Lib | ✓ | ✓ | ✓ | ✗ |
|
|
148
|
+
| Index (Root) | ✓ | ✓ | ✓ | ✓ |
|
|
149
|
+
|
|
150
|
+
## Anti-Patterns
|
|
151
|
+
|
|
152
|
+
**Anemic Domain Model:** Entities as data-only containers with logic in services. Put business rules in entities.
|
|
153
|
+
|
|
154
|
+
**Leaky Abstractions:** Ports exposing framework types. Use domain concepts only.
|
|
155
|
+
|
|
156
|
+
**Business Logic in Adapters:** Validation rules or decisions in commands. Move to entities/use cases.
|
|
157
|
+
|
|
158
|
+
**Framework Coupling:** Use cases accepting CLI `args` objects. Use plain DTOs.
|
|
159
|
+
|
|
160
|
+
## Code Review Checklist
|
|
161
|
+
|
|
162
|
+
- Entities have zero imports from other layers
|
|
163
|
+
- Use cases define ports for all external dependencies
|
|
164
|
+
- Adapters implement ports, contain no business logic
|
|
165
|
+
- Only composition root instantiates concrete implementations
|
|
166
|
+
- Use cases testable with simple mocks (no file system, no HTTP)
|