meto-cli 0.4.0 → 0.7.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/CHANGELOG.md +31 -0
- package/README.md +47 -19
- package/dist/cli/doctor-checks.d.ts +52 -0
- package/dist/cli/doctor-checks.d.ts.map +1 -0
- package/dist/cli/doctor-checks.js +331 -0
- package/dist/cli/doctor-checks.js.map +1 -0
- package/dist/cli/doctor.d.ts +19 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +87 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.js +17 -22
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/prompts.d.ts.map +1 -1
- package/dist/cli/prompts.js +39 -20
- package/dist/cli/prompts.js.map +1 -1
- package/dist/cli/stacks.d.ts.map +1 -1
- package/dist/cli/stacks.js +248 -0
- package/dist/cli/stacks.js.map +1 -1
- package/dist/cli/types.d.ts +1 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/.claude/agent-memory/lom-developer/MEMORY.md +11 -11
- package/templates/.claude/agent-memory/lom-pm/MEMORY.md +9 -14
- package/templates/.claude/agent-memory/lom-tester/MEMORY.md +9 -10
- package/templates/.claude/agents/developer-agent.md +6 -0
- package/templates/.claude/agents/pm-agent.md +6 -0
- package/templates/.claude/agents/tester-agent.md +6 -0
- package/templates/CLAUDE.md +10 -0
- package/templates/ai/workflows/session-checkpoint.md +49 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [0.7.0] - 2026-03-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Doctor command (`meto-cli doctor`) that checks methodology health of a Meto-scaffolded project
|
|
13
|
+
- 7 health checks: required files, context content, board format, epics content, agent definitions, WIP limit, orphan tasks
|
|
14
|
+
- Project root detection by walking up from current directory looking for `ai/tasks/`
|
|
15
|
+
- Scannable report with pass/warn/fail per check and summary line
|
|
16
|
+
- Exit code 1 when any check fails (0 for pass or warnings only)
|
|
17
|
+
|
|
18
|
+
## [0.6.0] - 2026-03-05
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- 4 new stack presets: Python (FastAPI), Go, Vite + React, Flutter
|
|
23
|
+
- Each preset includes tailored tech-stack description, definition of done, and starter epics
|
|
24
|
+
|
|
25
|
+
## [0.4.0] - 2026-03-05
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- AI-powered init: when Claude Code is detected, generates product vision, epics, and sliced backlog automatically
|
|
30
|
+
- User answers 5 questions instead of 10 when AI is available
|
|
31
|
+
- `--no-ai` flag to force static prompts even when Claude Code is installed
|
|
32
|
+
- AI generation with 90-second timeout and progress spinner
|
|
33
|
+
- Fallback to static prompts when AI generation fails (timeout, parse error, subprocess crash)
|
|
34
|
+
- Section-delimited AI output parsing with validation
|
|
35
|
+
- AI generation summary note showing what was generated
|
|
36
|
+
- Agent memory files scaffolded for PM, developer, and tester agents
|
|
37
|
+
- Agent Teams support with `.claude/settings.json` and file ownership boundaries
|
|
38
|
+
|
|
8
39
|
## [0.1.0] - 2026-03-04
|
|
9
40
|
|
|
10
41
|
### Added
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Lovable gives you an app. Meto gives you a project -- built the right way, your way.
|
|
4
4
|
|
|
5
|
-
Meto scaffolds structured software projects with built-in methodology. You describe what you want to build, and Meto bootstraps a project with a
|
|
5
|
+
Meto scaffolds structured software projects with built-in methodology. You describe what you want to build, and Meto bootstraps a project with AI-generated epics, a sliced backlog, agent definitions, product context, and coding conventions -- ready for your first Claude Code session.
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
@@ -14,23 +14,45 @@ Meto scaffolds structured software projects with built-in methodology. You descr
|
|
|
14
14
|
npx meto-cli init
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Answer a few questions
|
|
17
|
+
Answer a few questions, and Meto generates a fully structured repository in seconds -- with AI-powered content if Claude Code is installed.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## How It Works
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Meto detects whether Claude Code is installed on your machine and offers two paths:
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
**With Claude Code (AI-powered):**
|
|
26
|
+
1. You answer 5 questions -- project name, description, target users, tech stack, output directory
|
|
27
|
+
2. Claude Code generates your product vision, problem statement, epics, and a sliced backlog with acceptance criteria
|
|
28
|
+
3. Meto renders everything into a structured project ready for your first sprint
|
|
29
|
+
|
|
30
|
+
**Without Claude Code (static):**
|
|
31
|
+
1. You answer 10 questions -- the 5 above plus problem statement, success criteria, value proposition, out of scope, and code conventions
|
|
32
|
+
2. Meto renders your answers into the same structured project with sensible defaults
|
|
33
|
+
|
|
34
|
+
Both paths produce the same project structure. The AI path just fills in more content so you spend less time on setup and more time building.
|
|
35
|
+
|
|
36
|
+
Use `--no-ai` to force the static path even when Claude Code is available.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Stack Presets
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
Choose from 7 built-in tech stacks, each with a tailored description, definition of done, and starter epics:
|
|
43
|
+
|
|
44
|
+
| Stack | What you get |
|
|
45
|
+
|---|---|
|
|
46
|
+
| **Next.js + Supabase** | Full-stack web app with auth, database, and edge functions |
|
|
47
|
+
| **React Native** | Cross-platform mobile app |
|
|
48
|
+
| **Node.js CLI** | Command-line tool distributed via npm |
|
|
49
|
+
| **Python (FastAPI)** | REST API with async support, auto-generated docs |
|
|
50
|
+
| **Go** | Compiled backend service or CLI tool |
|
|
51
|
+
| **Vite + React** | Client-side SPA with fast dev server |
|
|
52
|
+
| **Flutter** | Cross-platform mobile and web app with Dart |
|
|
53
|
+
| **Custom** | Describe your own stack |
|
|
54
|
+
|
|
55
|
+
Each preset populates your tech-stack description, definition of done (with stack-specific checks), and starter epics so your backlog has real structure from day one.
|
|
34
56
|
|
|
35
57
|
---
|
|
36
58
|
|
|
@@ -70,6 +92,15 @@ your-project/
|
|
|
70
92
|
└── CLAUDE.md
|
|
71
93
|
```
|
|
72
94
|
|
|
95
|
+
**What's inside:**
|
|
96
|
+
- **CLAUDE.md** -- project instructions that Claude Code reads every session, pre-filled with your vision, stack, and conventions
|
|
97
|
+
- **Kanban board** -- task pipeline (backlog, todo, in-progress, testing, done) ready for your first sprint
|
|
98
|
+
- **Agent definitions** -- PM, developer, and tester agents configured to follow your methodology from day one
|
|
99
|
+
- **Agent memory** -- persistent memory files so agents retain context across sessions
|
|
100
|
+
- **Product context** -- vision, tech stack, and decisions captured in structured files
|
|
101
|
+
- **Epics and workflows** -- definition of done, commit conventions, and an epic backlog to plan against
|
|
102
|
+
- **Agent Teams ready** -- three agents configured to work in parallel with file ownership boundaries
|
|
103
|
+
|
|
73
104
|
---
|
|
74
105
|
|
|
75
106
|
## Agent Teams
|
|
@@ -86,10 +117,6 @@ Meto scaffolds projects ready for Agent Teams out of the box:
|
|
|
86
117
|
|
|
87
118
|
> "Create an agent team with @meto-pm for planning, @meto-developer for building, @meto-tester for validation"
|
|
88
119
|
|
|
89
|
-
**Display modes:** use Shift+Down to cycle between agents in-process, or run each agent in its own split pane (tmux/iTerm2).
|
|
90
|
-
|
|
91
|
-
This feature is experimental and enabled via `.claude/settings.json` in the scaffold.
|
|
92
|
-
|
|
93
120
|
---
|
|
94
121
|
|
|
95
122
|
## Next Steps
|
|
@@ -107,8 +134,8 @@ This feature is experimental and enabled via `.claude/settings.json` in the scaf
|
|
|
107
134
|
| Requirement | Version | Notes |
|
|
108
135
|
|---|---|---|
|
|
109
136
|
| Node.js | >= 18 | Required to run the CLI |
|
|
110
|
-
| git | any | Recommended -- Meto
|
|
111
|
-
| Claude Code | latest |
|
|
137
|
+
| git | any | Recommended -- Meto initializes a repository if git is available |
|
|
138
|
+
| Claude Code | latest | Optional -- enables AI-powered generation. Without it, Meto uses static prompts |
|
|
112
139
|
|
|
113
140
|
---
|
|
114
141
|
|
|
@@ -116,7 +143,8 @@ This feature is experimental and enabled via `.claude/settings.json` in the scaf
|
|
|
116
143
|
|
|
117
144
|
| Command | Description |
|
|
118
145
|
|---|---|
|
|
119
|
-
| `meto-cli init` | Scaffold a new structured project |
|
|
146
|
+
| `meto-cli init` | Scaffold a new structured project (AI-powered if Claude Code is detected) |
|
|
147
|
+
| `meto-cli init --no-ai` | Scaffold using static prompts only, skip AI generation |
|
|
120
148
|
| `meto-cli init --dry-run` | Preview the generated file tree without writing to disk |
|
|
121
149
|
| `meto-cli --help` | Show available commands and options |
|
|
122
150
|
| `meto-cli --version` | Show the installed version |
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a single health check performed by the doctor command.
|
|
3
|
+
*/
|
|
4
|
+
export interface HealthCheckResult {
|
|
5
|
+
/** Human-readable name of the check */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Whether the check passed, produced a warning, or failed */
|
|
8
|
+
status: "pass" | "warn" | "fail";
|
|
9
|
+
/** Descriptive message explaining the result */
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Extracts all task slice identifiers (e.g. "slice-001") from a markdown file's
|
|
14
|
+
* ## [slice-NNN] headings.
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractSliceIds(content: string): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Check 1: Required files exist.
|
|
19
|
+
* Each required file that is missing produces a "fail" result.
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkRequiredFiles(projectRoot: string): Promise<HealthCheckResult[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Check 2: Context files are not empty.
|
|
24
|
+
* Files with 2 or fewer lines of content produce a "warn" result.
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkContextFilesNotEmpty(projectRoot: string): Promise<HealthCheckResult[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Check 3: Board files start with a # heading.
|
|
29
|
+
*/
|
|
30
|
+
export declare function checkBoardFileFormat(projectRoot: string): Promise<HealthCheckResult[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Check 4: Epics file has content (at least one ## E heading).
|
|
33
|
+
*/
|
|
34
|
+
export declare function checkEpicsFileHasContent(projectRoot: string): Promise<HealthCheckResult[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Check 5: Agent definitions exist in .claude/agents/.
|
|
37
|
+
*/
|
|
38
|
+
export declare function checkAgentDefinitions(projectRoot: string): Promise<HealthCheckResult[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Check 6: In-progress limit (max 1 task in tasks-in-progress.md).
|
|
41
|
+
*/
|
|
42
|
+
export declare function checkInProgressLimit(projectRoot: string): Promise<HealthCheckResult[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Check 7: No orphan tasks (tasks in in-progress or in-testing must exist in backlog).
|
|
45
|
+
*/
|
|
46
|
+
export declare function checkNoOrphanTasks(projectRoot: string): Promise<HealthCheckResult[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Runs all methodology health checks against the given project root.
|
|
49
|
+
* Returns an array of check results -- one per individual check.
|
|
50
|
+
*/
|
|
51
|
+
export declare function runHealthChecks(projectRoot: string): Promise<HealthCheckResult[]>;
|
|
52
|
+
//# sourceMappingURL=doctor-checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-checks.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-checks.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;CACjB;AAsED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CASzD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAqB9B;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA2B9B;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA2B9B;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAyB9B;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAoC9B;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA4B9B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA+D9B;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAmB9B"}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { readFile, readdir, access } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Files that must exist in every Meto-scaffolded project.
|
|
5
|
+
*/
|
|
6
|
+
const REQUIRED_FILES = [
|
|
7
|
+
"CLAUDE.md",
|
|
8
|
+
"ai/context/product-vision.md",
|
|
9
|
+
"ai/context/tech-stack.md",
|
|
10
|
+
"ai/context/decisions.md",
|
|
11
|
+
"ai/backlog/epics.md",
|
|
12
|
+
"ai/tasks/tasks-backlog.md",
|
|
13
|
+
"ai/tasks/tasks-todo.md",
|
|
14
|
+
"ai/tasks/tasks-in-progress.md",
|
|
15
|
+
"ai/tasks/tasks-in-testing.md",
|
|
16
|
+
"ai/tasks/tasks-done.md",
|
|
17
|
+
"ai/workflows/definition-of-done.md",
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Context files that should have meaningful content (more than 2 lines).
|
|
21
|
+
*/
|
|
22
|
+
const CONTEXT_FILES = [
|
|
23
|
+
"ai/context/product-vision.md",
|
|
24
|
+
"ai/context/tech-stack.md",
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Board files that should start with a # heading.
|
|
28
|
+
*/
|
|
29
|
+
const BOARD_FILES = [
|
|
30
|
+
"ai/tasks/tasks-backlog.md",
|
|
31
|
+
"ai/tasks/tasks-todo.md",
|
|
32
|
+
"ai/tasks/tasks-in-progress.md",
|
|
33
|
+
"ai/tasks/tasks-in-testing.md",
|
|
34
|
+
"ai/tasks/tasks-done.md",
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Agent definition files that must exist in .claude/agents/.
|
|
38
|
+
*/
|
|
39
|
+
const REQUIRED_AGENTS = [
|
|
40
|
+
"pm-agent.md",
|
|
41
|
+
"developer-agent.md",
|
|
42
|
+
"tester-agent.md",
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Checks whether a file exists at the given path.
|
|
46
|
+
*/
|
|
47
|
+
async function fileExists(filePath) {
|
|
48
|
+
try {
|
|
49
|
+
await access(filePath);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Safely reads a file, returning undefined if the file does not exist.
|
|
58
|
+
*/
|
|
59
|
+
async function safeReadFile(filePath) {
|
|
60
|
+
try {
|
|
61
|
+
return await readFile(filePath, "utf-8");
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extracts all task slice identifiers (e.g. "slice-001") from a markdown file's
|
|
69
|
+
* ## [slice-NNN] headings.
|
|
70
|
+
*/
|
|
71
|
+
export function extractSliceIds(content) {
|
|
72
|
+
const matches = content.match(/## \[slice-\d+\]/g);
|
|
73
|
+
if (matches === null) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
return matches.map((m) => {
|
|
77
|
+
const idMatch = /\[slice-\d+\]/.exec(m);
|
|
78
|
+
return idMatch ? idMatch[0] : "";
|
|
79
|
+
}).filter((id) => id.length > 0);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check 1: Required files exist.
|
|
83
|
+
* Each required file that is missing produces a "fail" result.
|
|
84
|
+
*/
|
|
85
|
+
export async function checkRequiredFiles(projectRoot) {
|
|
86
|
+
const results = [];
|
|
87
|
+
for (const file of REQUIRED_FILES) {
|
|
88
|
+
const exists = await fileExists(join(projectRoot, file));
|
|
89
|
+
if (exists) {
|
|
90
|
+
results.push({
|
|
91
|
+
name: `Required file: ${file}`,
|
|
92
|
+
status: "pass",
|
|
93
|
+
message: "File exists",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
results.push({
|
|
98
|
+
name: `Required file: ${file}`,
|
|
99
|
+
status: "fail",
|
|
100
|
+
message: `Missing required file: ${file}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check 2: Context files are not empty.
|
|
108
|
+
* Files with 2 or fewer lines of content produce a "warn" result.
|
|
109
|
+
*/
|
|
110
|
+
export async function checkContextFilesNotEmpty(projectRoot) {
|
|
111
|
+
const results = [];
|
|
112
|
+
for (const file of CONTEXT_FILES) {
|
|
113
|
+
const content = await safeReadFile(join(projectRoot, file));
|
|
114
|
+
if (content === undefined) {
|
|
115
|
+
// File missing is caught by checkRequiredFiles; skip here
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const lines = content.split("\n").filter((line) => line.trim().length > 0);
|
|
119
|
+
if (lines.length > 2) {
|
|
120
|
+
results.push({
|
|
121
|
+
name: `Content check: ${file}`,
|
|
122
|
+
status: "pass",
|
|
123
|
+
message: "File has meaningful content",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
results.push({
|
|
128
|
+
name: `Content check: ${file}`,
|
|
129
|
+
status: "warn",
|
|
130
|
+
message: `${file} has ${lines.length} non-empty lines -- expected more than 2`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return results;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check 3: Board files start with a # heading.
|
|
138
|
+
*/
|
|
139
|
+
export async function checkBoardFileFormat(projectRoot) {
|
|
140
|
+
const results = [];
|
|
141
|
+
for (const file of BOARD_FILES) {
|
|
142
|
+
const content = await safeReadFile(join(projectRoot, file));
|
|
143
|
+
if (content === undefined) {
|
|
144
|
+
// File missing is caught by checkRequiredFiles; skip here
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const firstLine = content.trimStart().split("\n")[0];
|
|
148
|
+
if (firstLine.startsWith("# ")) {
|
|
149
|
+
results.push({
|
|
150
|
+
name: `Board format: ${file}`,
|
|
151
|
+
status: "pass",
|
|
152
|
+
message: "File starts with a heading",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
results.push({
|
|
157
|
+
name: `Board format: ${file}`,
|
|
158
|
+
status: "warn",
|
|
159
|
+
message: `${file} does not start with a # heading`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Check 4: Epics file has content (at least one ## E heading).
|
|
167
|
+
*/
|
|
168
|
+
export async function checkEpicsFileHasContent(projectRoot) {
|
|
169
|
+
const content = await safeReadFile(join(projectRoot, "ai/backlog/epics.md"));
|
|
170
|
+
if (content === undefined) {
|
|
171
|
+
// File missing is caught by checkRequiredFiles; skip here
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
const hasEpicHeading = /## E/.test(content);
|
|
175
|
+
if (hasEpicHeading) {
|
|
176
|
+
return [
|
|
177
|
+
{
|
|
178
|
+
name: "Epics file content",
|
|
179
|
+
status: "pass",
|
|
180
|
+
message: "Epics file contains at least one epic",
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
return [
|
|
185
|
+
{
|
|
186
|
+
name: "Epics file content",
|
|
187
|
+
status: "warn",
|
|
188
|
+
message: "epics.md does not contain any ## E headings",
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Check 5: Agent definitions exist in .claude/agents/.
|
|
194
|
+
*/
|
|
195
|
+
export async function checkAgentDefinitions(projectRoot) {
|
|
196
|
+
const results = [];
|
|
197
|
+
const agentsDir = join(projectRoot, ".claude", "agents");
|
|
198
|
+
let agentFiles;
|
|
199
|
+
try {
|
|
200
|
+
agentFiles = await readdir(agentsDir);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Directory doesn't exist -- all agents are missing
|
|
204
|
+
for (const agent of REQUIRED_AGENTS) {
|
|
205
|
+
results.push({
|
|
206
|
+
name: `Agent definition: ${agent}`,
|
|
207
|
+
status: "fail",
|
|
208
|
+
message: `Missing agent definition: .claude/agents/${agent}`,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return results;
|
|
212
|
+
}
|
|
213
|
+
for (const agent of REQUIRED_AGENTS) {
|
|
214
|
+
if (agentFiles.includes(agent)) {
|
|
215
|
+
results.push({
|
|
216
|
+
name: `Agent definition: ${agent}`,
|
|
217
|
+
status: "pass",
|
|
218
|
+
message: "Agent definition exists",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
results.push({
|
|
223
|
+
name: `Agent definition: ${agent}`,
|
|
224
|
+
status: "fail",
|
|
225
|
+
message: `Missing agent definition: .claude/agents/${agent}`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return results;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Check 6: In-progress limit (max 1 task in tasks-in-progress.md).
|
|
233
|
+
*/
|
|
234
|
+
export async function checkInProgressLimit(projectRoot) {
|
|
235
|
+
const content = await safeReadFile(join(projectRoot, "ai/tasks/tasks-in-progress.md"));
|
|
236
|
+
if (content === undefined) {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
const sliceIds = extractSliceIds(content);
|
|
240
|
+
const count = sliceIds.length;
|
|
241
|
+
if (count <= 1) {
|
|
242
|
+
return [
|
|
243
|
+
{
|
|
244
|
+
name: "WIP limit",
|
|
245
|
+
status: "pass",
|
|
246
|
+
message: `${count} task(s) in progress`,
|
|
247
|
+
},
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
return [
|
|
251
|
+
{
|
|
252
|
+
name: "WIP limit",
|
|
253
|
+
status: "warn",
|
|
254
|
+
message: `WIP limit exceeded: ${count} tasks in progress, expected max 1.`,
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Check 7: No orphan tasks (tasks in in-progress or in-testing must exist in backlog).
|
|
260
|
+
*/
|
|
261
|
+
export async function checkNoOrphanTasks(projectRoot) {
|
|
262
|
+
const backlogContent = await safeReadFile(join(projectRoot, "ai/tasks/tasks-backlog.md"));
|
|
263
|
+
const inProgressContent = await safeReadFile(join(projectRoot, "ai/tasks/tasks-in-progress.md"));
|
|
264
|
+
const inTestingContent = await safeReadFile(join(projectRoot, "ai/tasks/tasks-in-testing.md"));
|
|
265
|
+
if (backlogContent === undefined) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
const backlogIds = extractSliceIds(backlogContent);
|
|
269
|
+
const activeTasks = [];
|
|
270
|
+
if (inProgressContent !== undefined) {
|
|
271
|
+
for (const id of extractSliceIds(inProgressContent)) {
|
|
272
|
+
activeTasks.push({ id, source: "tasks-in-progress.md" });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (inTestingContent !== undefined) {
|
|
276
|
+
for (const id of extractSliceIds(inTestingContent)) {
|
|
277
|
+
activeTasks.push({ id, source: "tasks-in-testing.md" });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (activeTasks.length === 0) {
|
|
281
|
+
return [
|
|
282
|
+
{
|
|
283
|
+
name: "Orphan tasks",
|
|
284
|
+
status: "pass",
|
|
285
|
+
message: "No active tasks to check",
|
|
286
|
+
},
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
const results = [];
|
|
290
|
+
let hasOrphan = false;
|
|
291
|
+
for (const task of activeTasks) {
|
|
292
|
+
if (!backlogIds.includes(task.id)) {
|
|
293
|
+
hasOrphan = true;
|
|
294
|
+
results.push({
|
|
295
|
+
name: `Orphan task: ${task.id}`,
|
|
296
|
+
status: "warn",
|
|
297
|
+
message: `${task.id} in ${task.source} has no matching entry in tasks-backlog.md`,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (!hasOrphan) {
|
|
302
|
+
results.push({
|
|
303
|
+
name: "Orphan tasks",
|
|
304
|
+
status: "pass",
|
|
305
|
+
message: "All active tasks have backlog entries",
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return results;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Runs all methodology health checks against the given project root.
|
|
312
|
+
* Returns an array of check results -- one per individual check.
|
|
313
|
+
*/
|
|
314
|
+
export async function runHealthChecks(projectRoot) {
|
|
315
|
+
const allResults = [];
|
|
316
|
+
const checkFunctions = [
|
|
317
|
+
checkRequiredFiles,
|
|
318
|
+
checkContextFilesNotEmpty,
|
|
319
|
+
checkBoardFileFormat,
|
|
320
|
+
checkEpicsFileHasContent,
|
|
321
|
+
checkAgentDefinitions,
|
|
322
|
+
checkInProgressLimit,
|
|
323
|
+
checkNoOrphanTasks,
|
|
324
|
+
];
|
|
325
|
+
for (const checkFn of checkFunctions) {
|
|
326
|
+
const results = await checkFn(projectRoot);
|
|
327
|
+
allResults.push(...results);
|
|
328
|
+
}
|
|
329
|
+
return allResults;
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=doctor-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-checks.js","sourceRoot":"","sources":["../../src/cli/doctor-checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { HealthCheckResult } from "./doctor-checks.js";
|
|
2
|
+
/**
|
|
3
|
+
* Searches upward from the given starting directory for a Meto project root.
|
|
4
|
+
* A Meto project root is identified by the presence of an `ai/tasks/` directory.
|
|
5
|
+
*
|
|
6
|
+
* Returns the absolute path to the project root, or undefined if not found.
|
|
7
|
+
*/
|
|
8
|
+
export declare function findProjectRoot(startDir: string): Promise<string | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* Determines the exit code based on health check results.
|
|
11
|
+
* Returns 0 if no failures (warnings are OK), 1 if any check failed.
|
|
12
|
+
*/
|
|
13
|
+
export declare function determineExitCode(results: HealthCheckResult[]): number;
|
|
14
|
+
/**
|
|
15
|
+
* Entry point for the `meto-cli doctor` command.
|
|
16
|
+
* Detects the project root, runs health checks, and displays a report.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runDoctor(): Promise<void>;
|
|
19
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAoB7B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAGtE;AA2BD;;;GAGG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CA6B/C"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { join, dirname, resolve } from "node:path";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import { runHealthChecks } from "./doctor-checks.js";
|
|
5
|
+
/**
|
|
6
|
+
* Searches upward from the given starting directory for a Meto project root.
|
|
7
|
+
* A Meto project root is identified by the presence of an `ai/tasks/` directory.
|
|
8
|
+
*
|
|
9
|
+
* Returns the absolute path to the project root, or undefined if not found.
|
|
10
|
+
*/
|
|
11
|
+
export async function findProjectRoot(startDir) {
|
|
12
|
+
let current = resolve(startDir);
|
|
13
|
+
// eslint-disable-next-line no-constant-condition
|
|
14
|
+
while (true) {
|
|
15
|
+
const candidate = join(current, "ai", "tasks");
|
|
16
|
+
try {
|
|
17
|
+
await access(candidate);
|
|
18
|
+
return current;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// ai/tasks/ not found at this level, move up
|
|
22
|
+
}
|
|
23
|
+
const parent = dirname(current);
|
|
24
|
+
if (parent === current) {
|
|
25
|
+
// Reached filesystem root without finding a project
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
current = parent;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Determines the exit code based on health check results.
|
|
33
|
+
* Returns 0 if no failures (warnings are OK), 1 if any check failed.
|
|
34
|
+
*/
|
|
35
|
+
export function determineExitCode(results) {
|
|
36
|
+
const hasFail = results.some((r) => r.status === "fail");
|
|
37
|
+
return hasFail ? 1 : 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Formats and displays health check results using clack output functions.
|
|
41
|
+
*/
|
|
42
|
+
function displayResults(results) {
|
|
43
|
+
for (const result of results) {
|
|
44
|
+
switch (result.status) {
|
|
45
|
+
case "pass":
|
|
46
|
+
p.log.success(result.name);
|
|
47
|
+
break;
|
|
48
|
+
case "warn":
|
|
49
|
+
p.log.warning(`${result.name}: ${result.message}`);
|
|
50
|
+
break;
|
|
51
|
+
case "fail":
|
|
52
|
+
p.log.error(`${result.name}: ${result.message}`);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const passed = results.filter((r) => r.status === "pass").length;
|
|
57
|
+
const warnings = results.filter((r) => r.status === "warn").length;
|
|
58
|
+
const failed = results.filter((r) => r.status === "fail").length;
|
|
59
|
+
p.note(`${passed} passed, ${warnings} warnings, ${failed} failed`, "Summary");
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Entry point for the `meto-cli doctor` command.
|
|
63
|
+
* Detects the project root, runs health checks, and displays a report.
|
|
64
|
+
*/
|
|
65
|
+
export async function runDoctor() {
|
|
66
|
+
p.intro("meto-cli doctor");
|
|
67
|
+
const projectRoot = await findProjectRoot(process.cwd());
|
|
68
|
+
if (projectRoot === undefined) {
|
|
69
|
+
p.log.error("No Meto project found. Run this command from within a Meto-scaffolded project.");
|
|
70
|
+
p.outro("");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
p.log.info(`Meto project detected at ${projectRoot}`);
|
|
74
|
+
const results = await runHealthChecks(projectRoot);
|
|
75
|
+
displayResults(results);
|
|
76
|
+
const exitCode = determineExitCode(results);
|
|
77
|
+
if (exitCode === 0) {
|
|
78
|
+
p.outro("All checks passed.");
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
p.outro("Some checks failed. Review the issues above.");
|
|
82
|
+
}
|
|
83
|
+
if (exitCode !== 0) {
|
|
84
|
+
process.exit(exitCode);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=doctor.js.map
|