wize-dev-kit 0.2.5 → 0.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/CHANGELOG.md +36 -1
- package/README.md +1 -0
- package/package.json +1 -1
- package/tools/installer/commands/doctor.js +293 -0
- package/tools/installer/wize-cli.js +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ Format inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.3.0] — 2026-06-12
|
|
9
|
+
|
|
10
|
+
Adds a single-command diagnostic plus traceable GitHub Releases on every tag.
|
|
11
|
+
|
|
12
|
+
### Added — `wize-dev-kit doctor`
|
|
13
|
+
|
|
14
|
+
Single-command snapshot of kit + project + environment, plus a ranked list of suggestions. Designed to be the first command a new developer runs in an unfamiliar wize-enabled repo, and the go-to command when something looks off. Sections covered:
|
|
15
|
+
|
|
16
|
+
- **Kit versions** (installed, project-pinned in `kit_version`, registry-latest) — flags drift between any of them.
|
|
17
|
+
- **Project** — name, profiles, IDE targets, languages, current phase (heuristic: brief → trigger-map → PRD → UX → tech-vision → architecture → stories → risk profile → sprint planning → implementation).
|
|
18
|
+
- **IDE Adapters** — file count per target (`.claude/skills/`, `.cursor/rules/`, etc.). Flags adapters that didn't render and points at `wize-dev-kit sync`.
|
|
19
|
+
- **TEA gates** — counts PASS / CONCERNS / FAIL / WAIVED across `gate.md` files in `.wize/implementation/tea/`. Flags any FAIL or CONCERNS.
|
|
20
|
+
- **Knowledge baseline** — `last_refreshed` age per `document-project/*.md` file, plus inline-note count in `_pending.md`. Suggests `wize-refresh-knowledge` when files go stale (> 60 days) or pending notes pile up (≥ 5).
|
|
21
|
+
- **Harness CLIs on PATH** — claude / codex / opencode, with detected install paths.
|
|
22
|
+
- **Git** — branch / head, repo presence.
|
|
23
|
+
|
|
24
|
+
Implementation in `tools/installer/commands/doctor.js`. 11 new unit tests cover phase detection, gate counting, knowledge parsing, adapter path mapping, and end-to-end run on a minimal install.
|
|
25
|
+
|
|
26
|
+
Output is plain text (no colors) so it's grep-friendly and pipe-friendly. Section headers are stable for editors / dashboards to parse.
|
|
27
|
+
|
|
28
|
+
### Added — GitHub Release on every tag
|
|
29
|
+
|
|
30
|
+
After a successful `npm publish` from a `v*` tag, the workflow now:
|
|
31
|
+
|
|
32
|
+
1. Extracts the matching version's CHANGELOG entry (`## [VERSION]` to the next `## [`) with a small awk filter.
|
|
33
|
+
2. Creates a GitHub Release at the tag using `softprops/action-gh-release@v2`, with the CHANGELOG entry as the release body.
|
|
34
|
+
3. If no CHANGELOG entry is found for the version, falls back to GitHub's auto-generated notes.
|
|
35
|
+
|
|
36
|
+
Pre-release tags (`-alpha`, `-beta`, `-rc`) are marked as pre-release automatically. Requires `permissions: contents: write` (added to the publish job).
|
|
37
|
+
|
|
38
|
+
### Tests
|
|
39
|
+
|
|
40
|
+
- Total: **115 passing** (was 104).
|
|
41
|
+
|
|
8
42
|
## [0.2.5] — 2026-06-12
|
|
9
43
|
|
|
10
44
|
Fixes a real install-time bug that bit non-TTY users (CI smoke + anyone piping input into `wize-dev-kit install`).
|
|
@@ -262,7 +296,8 @@ Ignore (handled by the suggested block): `.wize/config/user.toml`, `.wize/scratc
|
|
|
262
296
|
- Inspired by [BMAD Method v6.8.0](https://github.com/bmad-code-org/BMAD-METHOD).
|
|
263
297
|
- WDS module inspired by [bmad-method-wds-expansion](https://github.com/bmad-code-org/bmad-method-wds-expansion).
|
|
264
298
|
|
|
265
|
-
[Unreleased]: https://github.com/qwize-br/wize-development-kit/compare/v0.
|
|
299
|
+
[Unreleased]: https://github.com/qwize-br/wize-development-kit/compare/v0.3.0...HEAD
|
|
300
|
+
[0.3.0]: https://github.com/qwize-br/wize-development-kit/compare/v0.2.5...v0.3.0
|
|
266
301
|
[0.2.5]: https://github.com/qwize-br/wize-development-kit/compare/v0.2.4...v0.2.5
|
|
267
302
|
[0.2.4]: https://github.com/qwize-br/wize-development-kit/compare/v0.2.3...v0.2.4
|
|
268
303
|
[0.2.3]: https://github.com/qwize-br/wize-development-kit/compare/v0.2.2...v0.2.3
|
package/README.md
CHANGED
|
@@ -144,6 +144,7 @@ npx wize-dev-kit sync # re-render IDE adapters after editing config
|
|
|
144
144
|
npx wize-dev-kit agent list # list built-in + custom agents
|
|
145
145
|
npx wize-dev-kit agent create # scaffold a new custom agent (validated + dry-run)
|
|
146
146
|
npx wize-dev-kit agent edit <code> # override a built-in via .wize/custom/agents/<code>/customize.toml
|
|
147
|
+
npx wize-dev-kit doctor # diagnose kit / project / adapters / gates and suggest fixes
|
|
147
148
|
npx wize-dev-kit validate # structural checks on the kit assets
|
|
148
149
|
npx wize-dev-kit uninstall # remove .wize/ (your code is left untouched)
|
|
149
150
|
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "wize-dev-kit",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Full-lifecycle AI-assisted development kit with Test Architect and Whiteport Design Studio embedded. Inspired by BMAD Method and WDS.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ai",
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// `wize-dev-kit doctor` — single-command diagnose.
|
|
2
|
+
//
|
|
3
|
+
// Prints a structured snapshot of the kit + the project + the surrounding
|
|
4
|
+
// environment, plus a list of actionable suggestions ranked by severity.
|
|
5
|
+
// Designed to be the *first* command a new dev runs in an unfamiliar
|
|
6
|
+
// wize-enabled repo, and the *go-to* command when something looks off.
|
|
7
|
+
//
|
|
8
|
+
// Output is plain text (no colors by default) so it's grep-friendly and pipe-
|
|
9
|
+
// friendly. Sections are stable so editors / dashboards can parse them.
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const path = require('node:path');
|
|
15
|
+
const { loadProjectConfig, loadInstalledKitVersion } = require('./update.js');
|
|
16
|
+
const { detectHarnessCli } = require('../baseline.js');
|
|
17
|
+
const { getLatestVersion, semverGreater } = require('../version-check.js');
|
|
18
|
+
|
|
19
|
+
function fileExists(p) { try { return fs.statSync(p).isFile(); } catch { return false; } }
|
|
20
|
+
function dirExists(p) { try { return fs.statSync(p).isDirectory(); } catch { return false; } }
|
|
21
|
+
function readSafe(p) { try { return fs.readFileSync(p, 'utf-8'); } catch { return ''; } }
|
|
22
|
+
|
|
23
|
+
function listFilesUnder(root, pattern) {
|
|
24
|
+
if (!dirExists(root)) return [];
|
|
25
|
+
const out = [];
|
|
26
|
+
const stack = [root];
|
|
27
|
+
while (stack.length) {
|
|
28
|
+
const dir = stack.pop();
|
|
29
|
+
let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { continue; }
|
|
30
|
+
for (const e of entries) {
|
|
31
|
+
const full = path.join(dir, e.name);
|
|
32
|
+
if (e.isDirectory()) stack.push(full);
|
|
33
|
+
else if (e.isFile() && pattern.test(e.name)) out.push(full);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseGateStatus(content) {
|
|
40
|
+
// Find a `status: PASS|CONCERNS|FAIL|WAIVED` line in YAML frontmatter.
|
|
41
|
+
const m = content.match(/^status:\s*(PASS|CONCERNS|FAIL|WAIVED)\s*$/m);
|
|
42
|
+
return m ? m[1] : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseLastRefreshed(content) {
|
|
46
|
+
const m = content.match(/^last_refreshed:\s*([\d-]+)/m);
|
|
47
|
+
return m ? m[1] : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function daysAgo(dateStr) {
|
|
51
|
+
const t = Date.parse(dateStr);
|
|
52
|
+
if (Number.isNaN(t)) return null;
|
|
53
|
+
return Math.floor((Date.now() - t) / (24 * 3600 * 1000));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function adapterTargetPath(targetCode, projectRoot) {
|
|
57
|
+
// Mirror of the adapter render conventions documented in adapters/README.md.
|
|
58
|
+
switch (targetCode) {
|
|
59
|
+
case 'claude-code': return path.join(projectRoot, '.claude/skills');
|
|
60
|
+
case 'antigravity': return path.join(projectRoot, '.agent/skills');
|
|
61
|
+
case 'codex': return path.join(projectRoot, '.agents/skills');
|
|
62
|
+
case 'kimi-code': return path.join(projectRoot, '.kimi/skills');
|
|
63
|
+
case 'cursor': return path.join(projectRoot, '.cursor/rules');
|
|
64
|
+
case 'windsurf': return path.join(projectRoot, '.windsurf/rules');
|
|
65
|
+
case 'continue': return path.join(projectRoot, '.continue/prompts');
|
|
66
|
+
case 'opencode': return path.join(projectRoot, '.opencode/agents');
|
|
67
|
+
case 'generic': return path.join(projectRoot, '.wize/agents');
|
|
68
|
+
default: return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function countAdapterFiles(targetCode, projectRoot) {
|
|
73
|
+
const dir = adapterTargetPath(targetCode, projectRoot);
|
|
74
|
+
if (!dir || !dirExists(dir)) return 0;
|
|
75
|
+
// Be permissive on file extension; .mdc, .md, .prompt, and SKILL.md dirs all count.
|
|
76
|
+
let count = 0;
|
|
77
|
+
const stack = [dir];
|
|
78
|
+
while (stack.length) {
|
|
79
|
+
const d = stack.pop();
|
|
80
|
+
let entries; try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { continue; }
|
|
81
|
+
for (const e of entries) {
|
|
82
|
+
const full = path.join(d, e.name);
|
|
83
|
+
if (e.isDirectory()) stack.push(full);
|
|
84
|
+
else if (e.isFile()) count++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return count;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function detectPhase(projectRoot) {
|
|
91
|
+
const has = (p) => fileExists(path.join(projectRoot, p)) || dirExists(path.join(projectRoot, p));
|
|
92
|
+
if (!dirExists(path.join(projectRoot, '.wize'))) return 'no-install';
|
|
93
|
+
if (!has('.wize/planning/brief.md')) return '1-analysis (brief pending)';
|
|
94
|
+
if (!has('.wize/planning/ux/trigger-map.md')) return '1-analysis (trigger map pending)';
|
|
95
|
+
if (!has('.wize/planning/prd.md')) return '2-plan (PRD pending)';
|
|
96
|
+
if (!has('.wize/planning/ux/ux-scenarios.md')) return '2-plan (UX scenarios pending)';
|
|
97
|
+
if (!has('.wize/planning/tech-vision.md')) return '2→3 boundary (Fury pending)';
|
|
98
|
+
if (!has('.wize/solutioning/architecture.md')) return '3-solutioning (architecture pending)';
|
|
99
|
+
if (!dirExists(path.join(projectRoot, '.wize/solutioning/stories')) ||
|
|
100
|
+
listFilesUnder(path.join(projectRoot, '.wize/solutioning/stories'), /\.md$/).length === 0)
|
|
101
|
+
return '3-solutioning (stories pending)';
|
|
102
|
+
if (!has('.wize/implementation/tea/risk-profile.md')) return '3-closeout (risk profile pending)';
|
|
103
|
+
if (!has('.wize/implementation/sprint-status.md')) return '4-implementation (sprint planning pending)';
|
|
104
|
+
return '4-implementation';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function gateStats(projectRoot) {
|
|
108
|
+
const root = path.join(projectRoot, '.wize/implementation/tea');
|
|
109
|
+
const files = listFilesUnder(root, /^gate\.md$/);
|
|
110
|
+
const stats = { PASS: 0, CONCERNS: 0, FAIL: 0, WAIVED: 0, total: files.length };
|
|
111
|
+
for (const f of files) {
|
|
112
|
+
const s = parseGateStatus(readSafe(f));
|
|
113
|
+
if (s && stats[s] !== undefined) stats[s]++;
|
|
114
|
+
}
|
|
115
|
+
return stats;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function knowledgeStatus(projectRoot) {
|
|
119
|
+
const root = path.join(projectRoot, '.wize/knowledge/document-project');
|
|
120
|
+
const exists = dirExists(root);
|
|
121
|
+
if (!exists) return { exists: false };
|
|
122
|
+
const files = ['overview.md', 'architecture-snapshot.md', 'conventions.md', 'dependencies.md', 'risk-spots.md']
|
|
123
|
+
.map(name => path.join(root, name))
|
|
124
|
+
.filter(fileExists);
|
|
125
|
+
const refreshed = files.map(f => ({
|
|
126
|
+
name: path.basename(f),
|
|
127
|
+
days: daysAgo(parseLastRefreshed(readSafe(f)))
|
|
128
|
+
}));
|
|
129
|
+
const pendingFile = path.join(root, '_pending.md');
|
|
130
|
+
const pendingLines = fileExists(pendingFile)
|
|
131
|
+
? readSafe(pendingFile).split('\n').filter(l => l.trim() && !l.startsWith('#')).length
|
|
132
|
+
: 0;
|
|
133
|
+
return { exists: true, files: refreshed, pendingLines };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function gitInfo(projectRoot) {
|
|
137
|
+
const dotGit = path.join(projectRoot, '.git');
|
|
138
|
+
if (!dirExists(dotGit)) return { isRepo: false };
|
|
139
|
+
let branch = '?'; let head = '?';
|
|
140
|
+
try {
|
|
141
|
+
const HEAD = readSafe(path.join(dotGit, 'HEAD')).trim();
|
|
142
|
+
if (HEAD.startsWith('ref: ')) {
|
|
143
|
+
branch = HEAD.slice(5).replace('refs/heads/', '');
|
|
144
|
+
} else {
|
|
145
|
+
head = HEAD.slice(0, 7);
|
|
146
|
+
}
|
|
147
|
+
} catch (_) {}
|
|
148
|
+
return { isRepo: true, branch, head };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function severityIcon(level) {
|
|
152
|
+
return level === 'error' ? '✖' : level === 'warn' ? '⚠' : 'ℹ';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function cmdDoctor({ kitRoot, projectRoot, opts = {} } = {}) {
|
|
156
|
+
const log = opts.log || console.log;
|
|
157
|
+
const cwd = projectRoot;
|
|
158
|
+
const cfg = loadProjectConfig(cwd);
|
|
159
|
+
const installed = loadInstalledKitVersion(kitRoot);
|
|
160
|
+
const phase = detectPhase(cwd);
|
|
161
|
+
const gates = gateStats(cwd);
|
|
162
|
+
const knowledge = knowledgeStatus(cwd);
|
|
163
|
+
const git = gitInfo(cwd);
|
|
164
|
+
const targets = (cfg.install && cfg.install.ide_targets) || [];
|
|
165
|
+
const profiles = (cfg.install && cfg.install.profiles) || [];
|
|
166
|
+
const harnesses = detectHarnessCli({ preferIde: targets });
|
|
167
|
+
|
|
168
|
+
// Try registry — best-effort, never blocks.
|
|
169
|
+
let registryLatest = null;
|
|
170
|
+
try { registryLatest = await getLatestVersion(); } catch (_) {}
|
|
171
|
+
|
|
172
|
+
const suggestions = [];
|
|
173
|
+
|
|
174
|
+
// -- Section: Kit --
|
|
175
|
+
log('');
|
|
176
|
+
log('Wize Dev Kit — Doctor');
|
|
177
|
+
log('─────────────────────');
|
|
178
|
+
log(`Kit version (installed): ${installed || '?'}`);
|
|
179
|
+
log(`Kit version (project): ${cfg.project && cfg.project.kit_version || '— (no install)'}`);
|
|
180
|
+
log(`Kit version (registry): ${registryLatest || '(offline or skipped)'}`);
|
|
181
|
+
if (installed && cfg.project && cfg.project.kit_version && installed !== cfg.project.kit_version) {
|
|
182
|
+
suggestions.push({ level: 'warn', text: `Project pinned to ${cfg.project.kit_version} but ${installed} is installed. Run \`npx wize-dev-kit update\` to refresh adapters.` });
|
|
183
|
+
}
|
|
184
|
+
if (registryLatest && installed && semverGreater(registryLatest, installed)) {
|
|
185
|
+
suggestions.push({ level: 'info', text: `Registry has ${registryLatest}; you're on ${installed}. Run \`npx wize-dev-kit@latest update\` to pick it up.` });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// -- Section: Project --
|
|
189
|
+
log('');
|
|
190
|
+
log('Project');
|
|
191
|
+
log('───────');
|
|
192
|
+
if (!cfg.project) {
|
|
193
|
+
log('No .wize/config/project.toml — kit is not installed here.');
|
|
194
|
+
suggestions.push({ level: 'error', text: 'Run `npx wize-dev-kit install` to set the kit up in this repo.' });
|
|
195
|
+
} else {
|
|
196
|
+
log(`Name: ${cfg.project.name || '?'}`);
|
|
197
|
+
log(`Profiles: ${profiles.join(', ') || '(none)'}`);
|
|
198
|
+
log(`IDE targets: ${targets.join(', ') || '(none)'}`);
|
|
199
|
+
log(`Communication lang: ${cfg.language && cfg.language.communication || '?'}`);
|
|
200
|
+
log(`Document lang: ${cfg.language && cfg.language.document_output || '?'}`);
|
|
201
|
+
log(`Current phase: ${phase}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// -- Section: Adapters --
|
|
205
|
+
if (cfg.install) {
|
|
206
|
+
log('');
|
|
207
|
+
log('IDE Adapters');
|
|
208
|
+
log('────────────');
|
|
209
|
+
for (const t of targets) {
|
|
210
|
+
const dir = adapterTargetPath(t, cwd);
|
|
211
|
+
const count = countAdapterFiles(t, cwd);
|
|
212
|
+
const status = count > 0 ? `✓ ${count} files` : '✖ none';
|
|
213
|
+
log(` ${t.padEnd(14)} ${status.padEnd(15)} ${dir ? path.relative(cwd, dir) : '(no path)'}`);
|
|
214
|
+
if (count === 0) suggestions.push({ level: 'warn', text: `Adapter "${t}" has no rendered files. Run \`npx wize-dev-kit sync\`.` });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// -- Section: TEA gates --
|
|
219
|
+
if (gates.total > 0) {
|
|
220
|
+
log('');
|
|
221
|
+
log('TEA gates');
|
|
222
|
+
log('─────────');
|
|
223
|
+
log(`Total gate.md files: ${gates.total}`);
|
|
224
|
+
log(`PASS: ${gates.PASS}`);
|
|
225
|
+
log(`CONCERNS: ${gates.CONCERNS}`);
|
|
226
|
+
log(`FAIL: ${gates.FAIL}`);
|
|
227
|
+
log(`WAIVED: ${gates.WAIVED}`);
|
|
228
|
+
if (gates.FAIL > 0) suggestions.push({ level: 'error', text: `${gates.FAIL} story gate(s) at FAIL. Stories must not merge until resolved (advisory mode) or are blocking (enforcing mode).` });
|
|
229
|
+
if (gates.CONCERNS > 0) suggestions.push({ level: 'warn', text: `${gates.CONCERNS} story gate(s) at CONCERNS. Review findings before next sprint.` });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// -- Section: Knowledge --
|
|
233
|
+
if (knowledge.exists) {
|
|
234
|
+
log('');
|
|
235
|
+
log('Knowledge baseline (`document-project/`)');
|
|
236
|
+
log('────────────────────────────────────────');
|
|
237
|
+
for (const f of knowledge.files) {
|
|
238
|
+
const age = f.days == null ? 'no last_refreshed' : `${f.days}d ago`;
|
|
239
|
+
log(` ${f.name.padEnd(28)} ${age}`);
|
|
240
|
+
if (f.days != null && f.days > 60) {
|
|
241
|
+
suggestions.push({ level: 'warn', text: `\`${f.name}\` last refreshed ${f.days}d ago. Run \`wize-refresh-knowledge\` after current sprint.` });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (knowledge.pendingLines > 0) {
|
|
245
|
+
log(` _pending.md: ${knowledge.pendingLines} inline note(s) waiting consolidation`);
|
|
246
|
+
if (knowledge.pendingLines >= 5) {
|
|
247
|
+
suggestions.push({ level: 'info', text: `${knowledge.pendingLines} notes piled up in _pending.md. Time to run \`wize-refresh-knowledge\`.` });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// -- Section: Harness CLIs --
|
|
253
|
+
log('');
|
|
254
|
+
log('AI Harness CLIs on PATH');
|
|
255
|
+
log('───────────────────────');
|
|
256
|
+
if (harnesses.length === 0) {
|
|
257
|
+
log(' (none detected)');
|
|
258
|
+
suggestions.push({ level: 'info', text: 'No harness CLI detected. Brownfield baseline + auto-run features only work in your IDE; the headless flow is unavailable.' });
|
|
259
|
+
} else {
|
|
260
|
+
for (const h of harnesses) log(` ${h.binary.padEnd(10)} ${h.path}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// -- Section: Git --
|
|
264
|
+
log('');
|
|
265
|
+
log('Git');
|
|
266
|
+
log('───');
|
|
267
|
+
if (!git.isRepo) {
|
|
268
|
+
log(' (not a git repo)');
|
|
269
|
+
suggestions.push({ level: 'warn', text: 'Not a git repository. Run `git init` before installing the kit so version history is recorded.' });
|
|
270
|
+
} else {
|
|
271
|
+
log(` branch: ${git.branch}`);
|
|
272
|
+
log(` head: ${git.head}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// -- Section: Suggestions --
|
|
276
|
+
log('');
|
|
277
|
+
log('Suggestions');
|
|
278
|
+
log('───────────');
|
|
279
|
+
if (suggestions.length === 0) {
|
|
280
|
+
log(' ✓ Everything looks good.');
|
|
281
|
+
} else {
|
|
282
|
+
for (const s of suggestions) log(` ${severityIcon(s.level)} ${s.text}`);
|
|
283
|
+
}
|
|
284
|
+
log('');
|
|
285
|
+
|
|
286
|
+
return { suggestions, phase, gates, knowledge, targets, profiles, installed, registryLatest };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = {
|
|
290
|
+
cmdDoctor,
|
|
291
|
+
// exported for tests:
|
|
292
|
+
detectPhase, gateStats, knowledgeStatus, gitInfo, adapterTargetPath, countAdapterFiles
|
|
293
|
+
};
|
|
@@ -20,6 +20,7 @@ const { detectHarnessCli, runHeadlessBaseline, manualInstructions, defaultPrompt
|
|
|
20
20
|
const { printUpdateHintIfAny } = require('./version-check.js');
|
|
21
21
|
const { cmdSync: cmdSyncReal } = require('./commands/sync.js');
|
|
22
22
|
const { cmdAgentList, cmdAgentCreate, cmdAgentEdit } = require('./commands/agent.js');
|
|
23
|
+
const { cmdDoctor } = require('./commands/doctor.js');
|
|
23
24
|
|
|
24
25
|
const INTERACTIVE = process.stdout.isTTY && process.stdin.isTTY;
|
|
25
26
|
|
|
@@ -72,6 +73,7 @@ Commands:
|
|
|
72
73
|
agent <create|list> Manage agents (built-in or custom).
|
|
73
74
|
workflow <create|list> Manage workflows.
|
|
74
75
|
validate Run schema + lint + dry-run validators.
|
|
76
|
+
doctor Diagnose the kit + project state, suggest fixes.
|
|
75
77
|
help Show this message.
|
|
76
78
|
|
|
77
79
|
Documentation:
|
|
@@ -540,7 +542,7 @@ function cmdValidate() {
|
|
|
540
542
|
// for `update` (already updating), `install` (already setting up), `uninstall`
|
|
541
543
|
// (already leaving), `validate` (developer-tool), and `version` (the user is
|
|
542
544
|
// already asking about versions).
|
|
543
|
-
const HINT_COMMANDS = new Set(['list', 'sync', 'agent', 'workflow', 'help']);
|
|
545
|
+
const HINT_COMMANDS = new Set(['list', 'sync', 'agent', 'workflow', 'help', 'doctor']);
|
|
544
546
|
|
|
545
547
|
async function main() {
|
|
546
548
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
@@ -561,6 +563,7 @@ async function main() {
|
|
|
561
563
|
case 'agent': return cmdAgent(rest);
|
|
562
564
|
case 'workflow': return cmdWorkflow(rest);
|
|
563
565
|
case 'validate': return cmdValidate();
|
|
566
|
+
case 'doctor': return cmdDoctor({ kitRoot: KIT_ROOT, projectRoot: process.cwd() });
|
|
564
567
|
case 'version':
|
|
565
568
|
case '--version':
|
|
566
569
|
case '-v':
|