@buba_71/levit 0.4.0 → 0.8.2
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/README.md +182 -10
- package/dist/bin/cli.js +58 -48
- package/dist/src/commands/decision.js +34 -19
- package/dist/src/commands/feature.js +117 -19
- package/dist/src/commands/handoff.js +34 -12
- package/dist/src/commands/index.js +3 -1
- package/dist/src/commands/init.js +53 -39
- package/dist/src/commands/validate.js +84 -0
- package/dist/src/core/error_helper.js +93 -0
- package/dist/src/core/errors.js +25 -0
- package/dist/src/core/frontmatter.js +62 -0
- package/dist/src/core/logger.js +77 -0
- package/dist/src/core/table.js +63 -0
- package/dist/src/init.js +9 -0
- package/dist/src/readers/decision_reader.js +47 -0
- package/dist/src/readers/feature_reader.js +48 -0
- package/dist/src/readers/handoff_reader.js +47 -0
- package/dist/src/services/decision_service.js +49 -0
- package/dist/src/services/feature_service.js +89 -0
- package/dist/src/services/handoff_service.js +37 -0
- package/dist/src/services/manifest_service.js +89 -0
- package/dist/src/services/project_service.js +39 -0
- package/dist/src/services/validation_service.js +461 -0
- package/dist/src/types/domain.js +2 -0
- package/dist/src/types/index.js +2 -0
- package/dist/src/types/manifest.js +26 -0
- package/dist/tests/{init.test.js → cli/integration.test.js} +79 -39
- package/dist/tests/services/decision_service.test.js +27 -0
- package/dist/tests/services/feature_service.test.js +42 -0
- package/dist/tests/services/handoff_service.test.js +28 -0
- package/dist/tests/services/manifest_service.test.js +189 -0
- package/dist/tests/services/validation_service.test.js +196 -0
- package/package.json +7 -2
- package/templates/default/.github/workflows/README.md +56 -0
- package/templates/default/.github/workflows/levit-validate.yml +93 -0
- package/templates/default/.gitlab-ci.yml +73 -0
- package/templates/default/.levit/AGENT_CONTRACT.md +34 -0
- package/templates/default/.levit/AGENT_ONBOARDING.md +5 -4
- package/templates/default/.levit/decisions/.gitkeep +0 -0
- package/templates/default/{features → .levit/features}/INTENT.md +9 -0
- package/templates/default/{features → .levit/features}/README.md +2 -2
- package/templates/default/.levit/handoff/.gitkeep +0 -0
- package/templates/default/HUMAN_AGENT_MANAGER.md +654 -0
- package/templates/default/MIGRATION_GUIDE.md +597 -0
- package/templates/default/README.md +26 -7
- package/templates/symfony/.github/workflows/README.md +56 -0
- package/templates/symfony/.github/workflows/levit-validate.yml +82 -0
- package/templates/symfony/.gitlab-ci.yml +62 -0
- package/templates/symfony/.levit/AGENT_CONTRACT.md +34 -0
- package/templates/symfony/.levit/AGENT_ONBOARDING.md +124 -0
- package/templates/symfony/.levit/decisions/.gitkeep +0 -0
- package/templates/symfony/.levit/features/INTENT.md +32 -0
- package/templates/symfony/.levit/features/README.md +11 -0
- package/templates/symfony/.levit/handoff/.gitkeep +0 -0
- package/templates/symfony/.levit/prompts/global-rules.md +10 -0
- package/templates/symfony/.levit/prompts/refactoring-guidelines.md +9 -0
- package/templates/symfony/.levit/workflows/example-task.md +9 -0
- package/templates/symfony/.levit/workflows/submit-for-review.md +18 -0
- package/templates/symfony/HUMAN_AGENT_MANAGER.md +654 -0
- package/templates/symfony/MIGRATION_GUIDE.md +597 -0
- package/templates/symfony/README.md +101 -0
- package/templates/symfony/SOCIAL_CONTRACT.md +34 -0
- package/templates/default/.levit/decision-record.md +0 -19
- package/templates/default/package.json +0 -11
- /package/templates/default/{agents → .levit/agents}/AGENTS.md +0 -0
- /package/templates/default/{agents → .levit/agents}/boundaries.md +0 -0
- /package/templates/default/{docs → .levit/docs}/architecture.md +0 -0
- /package/templates/default/{evals → .levit/evals}/README.md +0 -0
- /package/templates/default/{evals → .levit/evals}/conformance.eval.ts +0 -0
- /package/templates/default/{pipelines → .levit/pipelines}/README.md +0 -0
- /package/templates/default/{roles → .levit/roles}/README.md +0 -0
- /package/templates/default/{roles → .levit/roles}/devops.md +0 -0
- /package/templates/default/{roles → .levit/roles}/qa.md +0 -0
- /package/templates/default/{roles → .levit/roles}/security.md +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# levit-kit
|
|
2
2
|
|
|
3
|
-
Hybrid starter kit for **
|
|
3
|
+
Hybrid starter kit for **AI-Driven Development (AIDD)** projects
|
|
4
4
|
(template + CLI + agentic conventions)
|
|
5
5
|
|
|
6
6
|
---
|
|
@@ -22,7 +22,7 @@ Hybrid starter kit for **Antigravity** projects
|
|
|
22
22
|
|
|
23
23
|
## Why levit-kit exists
|
|
24
24
|
|
|
25
|
-
Levit-kit provides a **clean, readable, and predictable starting point** for initiating projects with
|
|
25
|
+
Levit-kit provides a **clean, readable, and predictable starting point** for initiating projects with **AI-Driven Development (AIDD)**.
|
|
26
26
|
|
|
27
27
|
It does not attempt to automate development, orchestrate agents, or generate business logic.
|
|
28
28
|
|
|
@@ -58,36 +58,208 @@ These limits are intentional.
|
|
|
58
58
|
Levit-kit is used **once**, at the very beginning of a project.
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
+
# Default template (generic)
|
|
62
|
+
npx @buba_71/levit init my-project
|
|
63
|
+
|
|
64
|
+
# Symfony template
|
|
65
|
+
npx @buba_71/levit init my-project symfony
|
|
66
|
+
|
|
67
|
+
# Interactive template selection (if multiple templates available)
|
|
61
68
|
npx @buba_71/levit init my-project
|
|
62
69
|
```
|
|
63
70
|
|
|
64
71
|
### Commands & Options
|
|
65
72
|
|
|
66
|
-
- `init <name
|
|
73
|
+
- `init <name> [template]`: Initializes a new project in the specified directory. Optionally specify a template (default, symfony).
|
|
74
|
+
- `feature new`: Creates a new feature intent (auto-assigns ID).
|
|
75
|
+
- `feature list`: Lists all features with their status.
|
|
76
|
+
- `feature status <id> <status>`: Updates a feature's status.
|
|
77
|
+
- `decision new`: Creates a new Architecture Decision Record (ADR) (auto-assigns ID).
|
|
78
|
+
- `handoff new`: Creates a workspace handoff brief for an agent.
|
|
79
|
+
- `validate`: Validates project structure and cognitive scaffolding.
|
|
80
|
+
- `--json`: Outputs machine-readable JSON (works with all commands).
|
|
67
81
|
- `-v, --version`: Displays the current version.
|
|
68
82
|
- `-h, --help`: Displays the help message.
|
|
69
83
|
|
|
84
|
+
### Available Templates
|
|
85
|
+
|
|
86
|
+
- **default**: Generic template for any type of project (structure only)
|
|
87
|
+
- **symfony**: Template with Symfony-specific guidelines in `.levit/AGENT_ONBOARDING.md` (structure only)
|
|
88
|
+
|
|
89
|
+
> **Important**: Templates provide **structure and governance**, not business logic or framework files.
|
|
90
|
+
> For Symfony projects, create your Symfony application separately (e.g., `composer create-project symfony/skeleton`)
|
|
91
|
+
> and integrate it with the Levit-Kit structure.
|
|
92
|
+
|
|
70
93
|
### What happens during init?
|
|
71
94
|
|
|
72
95
|
The `init` command:
|
|
73
96
|
1. Creates a new project directory.
|
|
74
|
-
2. Copies the **
|
|
75
|
-
3.
|
|
76
|
-
4.
|
|
97
|
+
2. Copies the **levit-kit template** (structure only, no business logic).
|
|
98
|
+
3. Generates a **`levit.json` manifest** with project metadata, governance rules, discovered roles, and constraints.
|
|
99
|
+
4. Includes a base `.gitignore`.
|
|
100
|
+
5. Exits immediately.
|
|
101
|
+
|
|
102
|
+
> **Note**: Templates provide only the **AIDD governance structure** (`.levit/` directory with all governance),
|
|
103
|
+
> not project-specific configuration files. You should create your project files (package.json, composer.json, etc.)
|
|
104
|
+
> separately according to your needs.
|
|
105
|
+
|
|
106
|
+
The `levit.json` file serves as the **central machine-readable contract** for AI agents, containing:
|
|
107
|
+
- Project name and description
|
|
108
|
+
- Governance settings (autonomy level, risk tolerance)
|
|
109
|
+
- Active features and available roles
|
|
110
|
+
- Technical constraints (file size limits, allowed dependencies, forbidden patterns)
|
|
111
|
+
- Paths to key directories
|
|
77
112
|
|
|
78
113
|
Levit-kit does not remain in the project after initialization and installs no dependencies.
|
|
79
114
|
|
|
115
|
+
|
|
80
116
|
---
|
|
81
117
|
|
|
82
118
|
## The AIDD Workflow (Human + Agent)
|
|
83
119
|
|
|
84
120
|
Levit-kit installs a cognitive pipeline in your project:
|
|
85
121
|
|
|
86
|
-
1. **Human Intent**: You define *what* you want
|
|
122
|
+
1. **Human Intent**: You define *what* you want using `levit feature new`.
|
|
87
123
|
2. **Agent Onboarding**: Your AI reads `.levit/AGENT_ONBOARDING.md` to learn your rules.
|
|
88
|
-
3. **Collaborative Decision**: The agent proposes technical choices in `.levit/
|
|
89
|
-
4. **
|
|
90
|
-
5. **
|
|
124
|
+
3. **Collaborative Decision**: The agent or human proposes technical choices using `levit decision new` (stored in `.levit/decisions/`).
|
|
125
|
+
4. **Handoff**: You package the task for an agent using `levit handoff new`.
|
|
126
|
+
5. **Verification**: You or the agent run quality tests in `.levit/evals/`.
|
|
127
|
+
6. **Review**: The agent submits its work following the protocol in `.levit/workflows/`.
|
|
128
|
+
|
|
129
|
+
> **📖 New to managing AI agents?** After initializing a project, read `HUMAN_AGENT_MANAGER.md` for a comprehensive guide on effectively managing AI agents, including best practices, anti-patterns, and troubleshooting.
|
|
130
|
+
>
|
|
131
|
+
> **🔄 Migrating an existing project?** See `MIGRATION_GUIDE.md` for step-by-step instructions on adopting levit-kit in your existing codebase.
|
|
132
|
+
>
|
|
133
|
+
> **🚀 Setting up CI/CD?** Templates for GitHub Actions and GitLab CI are included in all projects for automatic validation.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Examples
|
|
138
|
+
|
|
139
|
+
### Example 1: Creating a New Feature
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Interactive mode (prompts for input)
|
|
143
|
+
$ levit feature new
|
|
144
|
+
Feature title: User Authentication
|
|
145
|
+
Feature slug [user-authentication]:
|
|
146
|
+
Created .levit/features/001-user-authentication.md
|
|
147
|
+
|
|
148
|
+
# Non-interactive mode (with flags)
|
|
149
|
+
$ levit feature new --title "User Authentication" --slug user-authentication --yes
|
|
150
|
+
Created .levit/features/001-user-authentication.md
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The created feature file includes:
|
|
154
|
+
- Frontmatter with metadata (id, status, owner, risk_level, etc.)
|
|
155
|
+
- Template sections: Vision, Success Criteria, Boundaries, Technical Constraints, Agent Task
|
|
156
|
+
|
|
157
|
+
### Example 2: Creating an Architecture Decision
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Link a decision to a feature
|
|
161
|
+
$ levit decision new --title "Use PostgreSQL for user data" --feature .levit/features/001-user-authentication.md --yes
|
|
162
|
+
Created .levit/decisions/ADR-001-use-postgresql-for-user-data.md
|
|
163
|
+
|
|
164
|
+
# Auto-assign ID
|
|
165
|
+
$ levit decision new --title "Implement JWT authentication" --yes
|
|
166
|
+
Created .levit/decisions/ADR-002-implement-jwt-authentication.md
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Example 3: Creating an Agent Handoff
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Handoff a feature to a developer agent
|
|
173
|
+
$ levit handoff new --feature .levit/features/001-user-authentication.md --role developer --yes
|
|
174
|
+
Created .levit/handoff/2026-01-01-001-user-authentication-developer.md
|
|
175
|
+
|
|
176
|
+
# Handoff to security reviewer
|
|
177
|
+
$ levit handoff new --feature .levit/features/001-user-authentication.md --role security --yes
|
|
178
|
+
Created .levit/handoff/2026-01-01-001-user-authentication-security.md
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Example 4: Validating Project Structure
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Human-readable output
|
|
185
|
+
$ levit validate
|
|
186
|
+
🔍 Validating project cognitive scaffolding...
|
|
187
|
+
✨ All cognitive scaffolding checks passed!
|
|
188
|
+
|
|
189
|
+
# JSON output (for automation)
|
|
190
|
+
$ levit validate --json
|
|
191
|
+
{"level":"INFO","message":"🔍 Validating project cognitive scaffolding...","timestamp":"2026-01-01T12:00:00.000Z"}
|
|
192
|
+
{"level":"INFO","message":"✨ All cognitive scaffolding checks passed!","timestamp":"2026-01-01T12:00:00.000Z"}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Example 5: Complete Workflow
|
|
196
|
+
|
|
197
|
+
Here's a complete workflow from feature creation to agent handoff:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# 1. Initialize project
|
|
201
|
+
$ npx @buba_71/levit init my-api-project
|
|
202
|
+
$ cd my-api-project
|
|
203
|
+
|
|
204
|
+
# 2. Create a feature
|
|
205
|
+
$ levit feature new --title "API Rate Limiting" --slug api-rate-limiting --yes
|
|
206
|
+
Created .levit/features/001-api-rate-limiting.md
|
|
207
|
+
|
|
208
|
+
# 3. Create a technical decision
|
|
209
|
+
$ levit decision new --title "Use Redis for rate limiting" --feature .levit/features/001-api-rate-limiting.md --yes
|
|
210
|
+
Created .levit/decisions/ADR-001-use-redis-for-rate-limiting.md
|
|
211
|
+
|
|
212
|
+
# 4. Handoff to developer
|
|
213
|
+
$ levit handoff new --feature .levit/features/001-api-rate-limiting.md --role developer --yes
|
|
214
|
+
Created .levit/handoff/2026-01-01-001-api-rate-limiting-developer.md
|
|
215
|
+
|
|
216
|
+
# 5. Agent reads handoff and implements
|
|
217
|
+
# (Agent reads .levit/handoff/2026-01-01-001-api-rate-limiting-developer.md)
|
|
218
|
+
|
|
219
|
+
# 6. Validate after implementation
|
|
220
|
+
$ levit validate
|
|
221
|
+
🔍 Validating project cognitive scaffolding...
|
|
222
|
+
✨ All cognitive scaffolding checks passed!
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Example 6: Working with the Manifest
|
|
226
|
+
|
|
227
|
+
The `levit.json` manifest is automatically synced when you create features or decisions. You can also manually inspect it:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"version": "1.0.0",
|
|
232
|
+
"project": {
|
|
233
|
+
"name": "my-api-project",
|
|
234
|
+
"description": "AI-Driven Development project powered by levit-kit"
|
|
235
|
+
},
|
|
236
|
+
"governance": {
|
|
237
|
+
"autonomy_level": "low",
|
|
238
|
+
"risk_tolerance": "low"
|
|
239
|
+
},
|
|
240
|
+
"features": [
|
|
241
|
+
{
|
|
242
|
+
"id": "001",
|
|
243
|
+
"slug": "api-rate-limiting",
|
|
244
|
+
"status": "active",
|
|
245
|
+
"title": "API Rate Limiting",
|
|
246
|
+
"path": ".levit/features/001-api-rate-limiting.md"
|
|
247
|
+
}
|
|
248
|
+
],
|
|
249
|
+
"roles": [
|
|
250
|
+
{
|
|
251
|
+
"name": "developer",
|
|
252
|
+
"description": "Developer Role",
|
|
253
|
+
"path": "roles/developer.md"
|
|
254
|
+
}
|
|
255
|
+
],
|
|
256
|
+
"constraints": {
|
|
257
|
+
"max_file_size": 1000000,
|
|
258
|
+
"allowed_dependencies": [],
|
|
259
|
+
"forbidden_patterns": []
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
91
263
|
|
|
92
264
|
---
|
|
93
265
|
|
package/dist/bin/cli.js
CHANGED
|
@@ -8,10 +8,14 @@ const init_1 = require("../src/commands/init");
|
|
|
8
8
|
const decision_1 = require("../src/commands/decision");
|
|
9
9
|
const feature_1 = require("../src/commands/feature");
|
|
10
10
|
const handoff_1 = require("../src/commands/handoff");
|
|
11
|
+
const validate_1 = require("../src/commands/validate");
|
|
11
12
|
const version_1 = require("../src/core/version");
|
|
12
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const logger_1 = require("../src/core/logger");
|
|
15
|
+
const errors_1 = require("../src/core/errors");
|
|
16
|
+
const error_helper_1 = require("../src/core/error_helper");
|
|
13
17
|
function showHelp() {
|
|
14
|
-
|
|
18
|
+
logger_1.Logger.info(`
|
|
15
19
|
Usage: levit [command] [options]
|
|
16
20
|
|
|
17
21
|
Commands:
|
|
@@ -19,14 +23,19 @@ Commands:
|
|
|
19
23
|
feature new Create a new feature intent file (wizard)
|
|
20
24
|
decision new Create a new decision record (wizard)
|
|
21
25
|
handoff new Create an agent handoff brief (wizard)
|
|
26
|
+
validate Validate project cognitive scaffolding (cognitive linter)
|
|
22
27
|
|
|
23
28
|
Options:
|
|
24
29
|
-v, --version Show version number
|
|
25
30
|
-h, --help Show help
|
|
31
|
+
--json Output in JSON format
|
|
26
32
|
`);
|
|
27
33
|
}
|
|
28
34
|
async function main() {
|
|
29
35
|
const args = process.argv.slice(2);
|
|
36
|
+
if (args.includes("--json")) {
|
|
37
|
+
logger_1.Logger.setJsonMode(true);
|
|
38
|
+
}
|
|
30
39
|
if (args.includes("-h") || args.includes("--help") || args.length === 0) {
|
|
31
40
|
showHelp();
|
|
32
41
|
process.exit(0);
|
|
@@ -35,57 +44,58 @@ async function main() {
|
|
|
35
44
|
console.log(`levit-kit v${(0, version_1.getVersion)()}`);
|
|
36
45
|
process.exit(0);
|
|
37
46
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
else if (args[0] === "decision") {
|
|
69
|
-
try {
|
|
70
|
-
await (0, decision_1.decisionCommand)(args.slice(1), process.cwd());
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
console.error(error instanceof Error ? `Error: ${error.message}` : "Unexpected error");
|
|
74
|
-
process.exit(1);
|
|
47
|
+
const command = args[0];
|
|
48
|
+
try {
|
|
49
|
+
switch (command) {
|
|
50
|
+
case "init":
|
|
51
|
+
const projectName = args[1];
|
|
52
|
+
const templateName = args[2] || undefined;
|
|
53
|
+
if (!projectName) {
|
|
54
|
+
throw new errors_1.LevitError(errors_1.LevitErrorCode.MISSING_REQUIRED_ARG, "Project name is required. Usage: levit init <project-name> [template]");
|
|
55
|
+
}
|
|
56
|
+
await (0, init_1.initProject)(projectName, node_path_1.default.resolve(process.cwd(), projectName), templateName);
|
|
57
|
+
break;
|
|
58
|
+
case "feature":
|
|
59
|
+
await (0, feature_1.featureCommand)(args.slice(1), process.cwd());
|
|
60
|
+
break;
|
|
61
|
+
case "decision":
|
|
62
|
+
await (0, decision_1.decisionCommand)(args.slice(1), process.cwd());
|
|
63
|
+
break;
|
|
64
|
+
case "handoff":
|
|
65
|
+
await (0, handoff_1.handoffCommand)(args.slice(1), process.cwd());
|
|
66
|
+
break;
|
|
67
|
+
case "validate":
|
|
68
|
+
await (0, validate_1.validateCommand)(args.slice(1), process.cwd());
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
(0, error_helper_1.displayError)(new errors_1.LevitError(errors_1.LevitErrorCode.INVALID_COMMAND, `Unknown command "${command}"`));
|
|
72
|
+
logger_1.Logger.info("");
|
|
73
|
+
showHelp();
|
|
74
|
+
process.exit(1);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error instanceof errors_1.LevitError) {
|
|
79
|
+
(0, error_helper_1.displayError)(error);
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
else {
|
|
82
|
+
const isJsonMode = logger_1.Logger.getJsonMode();
|
|
83
|
+
if (isJsonMode) {
|
|
84
|
+
logger_1.Logger.error(JSON.stringify({
|
|
85
|
+
error: {
|
|
86
|
+
code: "UNEXPECTED_ERROR",
|
|
87
|
+
message: error instanceof Error ? error.message : "Unexpected error",
|
|
88
|
+
details: error instanceof Error ? error.stack : String(error),
|
|
89
|
+
},
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
logger_1.Logger.error(error instanceof Error ? error.message : "Unexpected error");
|
|
94
|
+
if (error instanceof Error && error.stack) {
|
|
95
|
+
logger_1.Logger.debug(error.stack);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
84
98
|
}
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
console.error(`Error: Unknown command "${args[0]}"`);
|
|
88
|
-
showHelp();
|
|
89
99
|
process.exit(1);
|
|
90
100
|
}
|
|
91
101
|
}
|
|
@@ -4,11 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.decisionCommand = decisionCommand;
|
|
7
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
8
7
|
const promises_1 = __importDefault(require("node:readline/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const levit_project_1 = require("../core/levit_project");
|
|
10
10
|
const cli_args_1 = require("../core/cli_args");
|
|
11
|
-
const
|
|
11
|
+
const logger_1 = require("../core/logger");
|
|
12
|
+
const decision_service_1 = require("../services/decision_service");
|
|
13
|
+
const errors_1 = require("../core/errors");
|
|
14
|
+
const table_1 = require("../core/table");
|
|
12
15
|
const ids_1 = require("../core/ids");
|
|
13
16
|
function normalizeSlug(input) {
|
|
14
17
|
return input
|
|
@@ -21,39 +24,51 @@ async function decisionCommand(argv, cwd) {
|
|
|
21
24
|
const { positional, flags } = (0, cli_args_1.parseArgs)(argv);
|
|
22
25
|
const sub = positional[0];
|
|
23
26
|
if (sub !== "new") {
|
|
24
|
-
throw new
|
|
27
|
+
throw new errors_1.LevitError(errors_1.LevitErrorCode.INVALID_COMMAND, 'Usage: levit decision new [--title "..."] [--id "001"]');
|
|
25
28
|
}
|
|
26
29
|
const projectRoot = (0, levit_project_1.requireLevitProjectRoot)(cwd);
|
|
27
30
|
const yes = (0, cli_args_1.getBooleanFlag)(flags, "yes");
|
|
28
31
|
const overwrite = (0, cli_args_1.getBooleanFlag)(flags, "force");
|
|
32
|
+
const isJsonMode = logger_1.Logger.getJsonMode();
|
|
29
33
|
let title = (0, cli_args_1.getStringFlag)(flags, "title");
|
|
30
34
|
let id = (0, cli_args_1.getStringFlag)(flags, "id");
|
|
31
35
|
const featureRef = (0, cli_args_1.getStringFlag)(flags, "feature");
|
|
32
|
-
const computedId = (0, ids_1.nextSequentialId)(node_path_1.default.join(projectRoot, ".levit", "decisions"), /^ADR-(\d+)-/);
|
|
33
36
|
if (!yes) {
|
|
34
37
|
const rl = promises_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
35
38
|
if (!title) {
|
|
36
39
|
title = (await rl.question("Decision title: ")).trim();
|
|
37
40
|
}
|
|
38
|
-
if (!id) {
|
|
39
|
-
id = (await rl.question(`Decision id [${computedId}]: `)).trim() || computedId;
|
|
40
|
-
}
|
|
41
41
|
await rl.close();
|
|
42
42
|
}
|
|
43
|
-
if (!id) {
|
|
44
|
-
id = computedId;
|
|
45
|
-
}
|
|
46
43
|
if (!title) {
|
|
47
|
-
throw new
|
|
44
|
+
throw new errors_1.LevitError(errors_1.LevitErrorCode.MISSING_REQUIRED_ARG, "Missing --title");
|
|
48
45
|
}
|
|
46
|
+
// Generate ID if not provided
|
|
49
47
|
if (!id) {
|
|
50
|
-
|
|
48
|
+
const baseDir = node_path_1.default.join(projectRoot, ".levit", "decisions");
|
|
49
|
+
id = (0, ids_1.nextSequentialId)(baseDir, /^ADR-(\d+)-/);
|
|
50
|
+
}
|
|
51
|
+
// Preview before creation
|
|
52
|
+
if (!yes && !isJsonMode) {
|
|
53
|
+
const slug = normalizeSlug(title);
|
|
54
|
+
const decisionPath = node_path_1.default.relative(projectRoot, node_path_1.default.join(projectRoot, ".levit", "decisions", `ADR-${id}-${slug}.md`));
|
|
55
|
+
const preview = (0, table_1.createBox)("Decision Preview", {
|
|
56
|
+
"ID": `ADR-${id}`,
|
|
57
|
+
"Title": title,
|
|
58
|
+
"Feature": featureRef || "(none)",
|
|
59
|
+
"Path": decisionPath
|
|
60
|
+
});
|
|
61
|
+
logger_1.Logger.info("");
|
|
62
|
+
logger_1.Logger.info(preview);
|
|
63
|
+
logger_1.Logger.info("");
|
|
64
|
+
const rl = promises_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
65
|
+
const confirm = await rl.question("Create this decision? [y/N]: ");
|
|
66
|
+
await rl.close();
|
|
67
|
+
if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") {
|
|
68
|
+
logger_1.Logger.info("Cancelled.");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
51
71
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
const decisionPath = node_path_1.default.join(projectRoot, ".levit", "decisions", fileName);
|
|
55
|
-
const featureLine = featureRef ? `- **Feature**: ${featureRef}\n` : "";
|
|
56
|
-
const content = `# ADR ${id}: ${title}\n\n- **Date**: [YYYY-MM-DD]\n- **Status**: [Draft / Proposed / Approved]\n${featureLine}\n## Context\n[fill]\n\n## Decision\n[fill]\n\n## Rationale\n[fill]\n\n## Alternatives Considered\n[fill]\n\n## Consequences\n[fill]\n`;
|
|
57
|
-
(0, write_file_1.writeTextFile)(decisionPath, content, { overwrite });
|
|
58
|
-
process.stdout.write(`Created ${node_path_1.default.relative(projectRoot, decisionPath)}\n`);
|
|
72
|
+
const createdPath = decision_service_1.DecisionService.createDecision(projectRoot, { title, featureRef, id, overwrite });
|
|
73
|
+
logger_1.Logger.success(`Created ${createdPath}`);
|
|
59
74
|
}
|
|
@@ -4,11 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.featureCommand = featureCommand;
|
|
7
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
8
7
|
const promises_1 = __importDefault(require("node:readline/promises"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const levit_project_1 = require("../core/levit_project");
|
|
10
11
|
const cli_args_1 = require("../core/cli_args");
|
|
11
|
-
const
|
|
12
|
+
const logger_1 = require("../core/logger");
|
|
13
|
+
const feature_service_1 = require("../services/feature_service");
|
|
14
|
+
const errors_1 = require("../core/errors");
|
|
15
|
+
const table_1 = require("../core/table");
|
|
12
16
|
const ids_1 = require("../core/ids");
|
|
13
17
|
function normalizeSlug(input) {
|
|
14
18
|
return input
|
|
@@ -20,16 +24,31 @@ function normalizeSlug(input) {
|
|
|
20
24
|
async function featureCommand(argv, cwd) {
|
|
21
25
|
const { positional, flags } = (0, cli_args_1.parseArgs)(argv);
|
|
22
26
|
const sub = positional[0];
|
|
23
|
-
if (sub !== "new") {
|
|
24
|
-
throw new Error('Usage: levit feature new [--title "..."] [--slug "..."] [--id "001"]');
|
|
25
|
-
}
|
|
26
27
|
const projectRoot = (0, levit_project_1.requireLevitProjectRoot)(cwd);
|
|
28
|
+
switch (sub) {
|
|
29
|
+
case "new":
|
|
30
|
+
await handleFeatureNew(projectRoot, flags);
|
|
31
|
+
break;
|
|
32
|
+
case "list":
|
|
33
|
+
handleFeatureList(projectRoot, flags);
|
|
34
|
+
break;
|
|
35
|
+
case "status":
|
|
36
|
+
await handleFeatureStatus(projectRoot, positional.slice(1), flags);
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
throw new errors_1.LevitError(errors_1.LevitErrorCode.INVALID_COMMAND, 'Usage: levit feature <new|list|status> [options]\n' +
|
|
40
|
+
' new: Create a new feature\n' +
|
|
41
|
+
' list: List all features\n' +
|
|
42
|
+
' status: Update feature status (usage: levit feature status <id> <status>)');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function handleFeatureNew(projectRoot, flags) {
|
|
27
46
|
const yes = (0, cli_args_1.getBooleanFlag)(flags, "yes");
|
|
28
47
|
const overwrite = (0, cli_args_1.getBooleanFlag)(flags, "force");
|
|
48
|
+
const isJsonMode = logger_1.Logger.getJsonMode();
|
|
29
49
|
let title = (0, cli_args_1.getStringFlag)(flags, "title");
|
|
30
50
|
let slug = (0, cli_args_1.getStringFlag)(flags, "slug");
|
|
31
51
|
let id = (0, cli_args_1.getStringFlag)(flags, "id");
|
|
32
|
-
const computedId = (0, ids_1.nextSequentialId)(node_path_1.default.join(projectRoot, "features"), /^(\d+)-/);
|
|
33
52
|
if (!yes) {
|
|
34
53
|
const rl = promises_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
35
54
|
if (!title) {
|
|
@@ -39,23 +58,102 @@ async function featureCommand(argv, cwd) {
|
|
|
39
58
|
const computed = normalizeSlug(title || "");
|
|
40
59
|
slug = (await rl.question(`Feature slug [${computed}]: `)).trim() || computed;
|
|
41
60
|
}
|
|
42
|
-
if (!id) {
|
|
43
|
-
id = (await rl.question(`Feature id [${computedId}]: `)).trim() || computedId;
|
|
44
|
-
}
|
|
45
61
|
await rl.close();
|
|
46
62
|
}
|
|
47
|
-
if (!id) {
|
|
48
|
-
id = computedId;
|
|
49
|
-
}
|
|
50
63
|
if (!title) {
|
|
51
|
-
throw new
|
|
64
|
+
throw new errors_1.LevitError(errors_1.LevitErrorCode.MISSING_REQUIRED_ARG, "Missing --title");
|
|
52
65
|
}
|
|
53
66
|
if (!slug) {
|
|
54
|
-
throw new
|
|
67
|
+
throw new errors_1.LevitError(errors_1.LevitErrorCode.MISSING_REQUIRED_ARG, "Missing --slug");
|
|
68
|
+
}
|
|
69
|
+
// Generate ID if not provided
|
|
70
|
+
if (!id) {
|
|
71
|
+
const baseDir = node_path_1.default.join(projectRoot, ".levit", "features");
|
|
72
|
+
id = (0, ids_1.nextSequentialId)(baseDir, /^(\d+)-/);
|
|
73
|
+
}
|
|
74
|
+
// Preview before creation
|
|
75
|
+
if (!yes && !isJsonMode) {
|
|
76
|
+
const featurePath = node_path_1.default.relative(projectRoot, node_path_1.default.join(projectRoot, ".levit", "features", `${id}-${slug}.md`));
|
|
77
|
+
const preview = (0, table_1.createBox)("Feature Preview", {
|
|
78
|
+
"ID": id,
|
|
79
|
+
"Title": title,
|
|
80
|
+
"Slug": slug,
|
|
81
|
+
"Path": featurePath
|
|
82
|
+
});
|
|
83
|
+
logger_1.Logger.info("");
|
|
84
|
+
logger_1.Logger.info(preview);
|
|
85
|
+
logger_1.Logger.info("");
|
|
86
|
+
const rl = promises_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
87
|
+
const confirm = await rl.question("Create this feature? [y/N]: ");
|
|
88
|
+
await rl.close();
|
|
89
|
+
if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") {
|
|
90
|
+
logger_1.Logger.info("Cancelled.");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const createdPath = feature_service_1.FeatureService.createFeature(projectRoot, { title, slug, id, overwrite });
|
|
95
|
+
logger_1.Logger.success(`Created ${createdPath}`);
|
|
96
|
+
}
|
|
97
|
+
function handleFeatureList(projectRoot, flags) {
|
|
98
|
+
const features = feature_service_1.FeatureService.listFeatures(projectRoot);
|
|
99
|
+
const isJsonMode = logger_1.Logger.getJsonMode();
|
|
100
|
+
if (features.length === 0) {
|
|
101
|
+
logger_1.Logger.info("No features found.");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Create formatted table
|
|
105
|
+
const table = (0, table_1.createTable)(["ID", "Status", "Title"]);
|
|
106
|
+
for (const feature of features) {
|
|
107
|
+
// Color status based on value
|
|
108
|
+
let statusDisplay = feature.status;
|
|
109
|
+
switch (feature.status) {
|
|
110
|
+
case "active":
|
|
111
|
+
statusDisplay = isJsonMode ? "active" : chalk_1.default.green("● active");
|
|
112
|
+
break;
|
|
113
|
+
case "draft":
|
|
114
|
+
statusDisplay = isJsonMode ? "draft" : chalk_1.default.yellow("○ draft");
|
|
115
|
+
break;
|
|
116
|
+
case "deprecated":
|
|
117
|
+
statusDisplay = isJsonMode ? "deprecated" : chalk_1.default.red("× deprecated");
|
|
118
|
+
break;
|
|
119
|
+
case "completed":
|
|
120
|
+
statusDisplay = isJsonMode ? "completed" : chalk_1.default.cyan("✓ completed");
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
table.push([
|
|
124
|
+
feature.id,
|
|
125
|
+
statusDisplay,
|
|
126
|
+
feature.title
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
if (!isJsonMode) {
|
|
130
|
+
logger_1.Logger.info("");
|
|
131
|
+
logger_1.Logger.info(chalk_1.default.bold("Features:"));
|
|
132
|
+
}
|
|
133
|
+
(0, table_1.renderTable)(table, isJsonMode);
|
|
134
|
+
if (!isJsonMode) {
|
|
135
|
+
logger_1.Logger.info(`\nTotal: ${chalk_1.default.bold(features.length.toString())} feature(s)`);
|
|
136
|
+
logger_1.Logger.info("");
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
logger_1.Logger.info(JSON.stringify({ total: features.length }));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function handleFeatureStatus(projectRoot, args, flags) {
|
|
143
|
+
const featureId = args[0] || (0, cli_args_1.getStringFlag)(flags, "id");
|
|
144
|
+
let newStatus = args[1] || (0, cli_args_1.getStringFlag)(flags, "status");
|
|
145
|
+
if (!featureId) {
|
|
146
|
+
throw new errors_1.LevitError(errors_1.LevitErrorCode.MISSING_REQUIRED_ARG, "Missing feature ID. Usage: levit feature status <id> <status>");
|
|
147
|
+
}
|
|
148
|
+
const validStatuses = ['active', 'draft', 'deprecated', 'completed'];
|
|
149
|
+
if (!newStatus) {
|
|
150
|
+
const rl = promises_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
151
|
+
newStatus = (await rl.question(`New status [active|draft|deprecated|completed]: `)).trim();
|
|
152
|
+
await rl.close();
|
|
153
|
+
}
|
|
154
|
+
if (!validStatuses.includes(newStatus)) {
|
|
155
|
+
throw new errors_1.LevitError(errors_1.LevitErrorCode.VALIDATION_FAILED, `Invalid status "${newStatus}". Must be one of: ${validStatuses.join(", ")}`);
|
|
55
156
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const content = `# INTENT: ${title}\n\n## 1. Vision (The \"Why\")\n- **User Story**: [fill]\n- **Priority**: [Low / Medium / High / Critical]\n\n## 2. Success Criteria (The \"What\")\n- [ ] Criterion 1\n\n## 3. Boundaries (The \"No\")\n- Non-goal 1\n\n## 4. Technical Constraints\n- [fill]\n\n## 5. Agent Task\n- [fill]\n`;
|
|
59
|
-
(0, write_file_1.writeTextFile)(featurePath, content, { overwrite });
|
|
60
|
-
process.stdout.write(`Created ${node_path_1.default.relative(projectRoot, featurePath)}\n`);
|
|
157
|
+
feature_service_1.FeatureService.updateFeatureStatus(projectRoot, featureId, newStatus);
|
|
158
|
+
logger_1.Logger.info(`Updated feature ${featureId} status to "${newStatus}"`);
|
|
61
159
|
}
|