wize-dev-kit 0.2.5 → 0.3.1

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.
Files changed (31) hide show
  1. package/AGENTS.md +21 -0
  2. package/ARCH.md +40 -4
  3. package/CHANGELOG.md +62 -1
  4. package/README.md +4 -1
  5. package/package.json +3 -2
  6. package/src/method-skills/1-analysis/wize-document-project/documentation-requirements.csv +12 -0
  7. package/src/method-skills/1-analysis/wize-document-project/templates/api-contracts-template.md +38 -0
  8. package/src/method-skills/1-analysis/wize-document-project/templates/architecture-template.md +54 -0
  9. package/src/method-skills/1-analysis/wize-document-project/templates/component-inventory-template.md +40 -0
  10. package/src/method-skills/1-analysis/wize-document-project/templates/contribution-guide-template.md +34 -0
  11. package/src/method-skills/1-analysis/wize-document-project/templates/data-models-template.md +36 -0
  12. package/src/method-skills/1-analysis/wize-document-project/templates/deep-dive-template.md +312 -0
  13. package/src/method-skills/1-analysis/wize-document-project/templates/deployment-guide-template.md +42 -0
  14. package/src/method-skills/1-analysis/wize-document-project/templates/development-guide-template.md +61 -0
  15. package/src/method-skills/1-analysis/wize-document-project/templates/index-template.md +185 -0
  16. package/src/method-skills/1-analysis/wize-document-project/templates/project-overview-template.md +110 -0
  17. package/src/method-skills/1-analysis/wize-document-project/templates/project-scan-report-schema.json +159 -0
  18. package/src/method-skills/1-analysis/wize-document-project/templates/source-tree-template.md +142 -0
  19. package/src/method-skills/1-analysis/wize-document-project/workflow.md +23 -0
  20. package/src/tea-skills/wize-tea-risk/workflow.md +11 -0
  21. package/tools/installer/commands/doctor.js +319 -0
  22. package/tools/installer/commands/document-project.js +93 -0
  23. package/tools/installer/document-project/batch-scanner.js +93 -0
  24. package/tools/installer/document-project/classify.js +170 -0
  25. package/tools/installer/document-project/modes/deep-dive.js +196 -0
  26. package/tools/installer/document-project/modes/full-rescan.js +15 -0
  27. package/tools/installer/document-project/modes/initial-scan.js +100 -0
  28. package/tools/installer/document-project/modes/quick.js +211 -0
  29. package/tools/installer/document-project/render-index.js +101 -0
  30. package/tools/installer/document-project/state.js +110 -0
  31. package/tools/installer/wize-cli.js +49 -30
@@ -0,0 +1,159 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://qwize.io/wize-dev-kit/schemas/project-scan-report.schema.json",
4
+ "title": "Project Scan Report",
5
+ "description": "State tracking file for wize-document-project resumability",
6
+ "type": "object",
7
+ "required": ["workflow_version", "timestamps", "mode", "scan_level", "completed_steps", "current_step"],
8
+ "properties": {
9
+ "workflow_version": {
10
+ "type": "string",
11
+ "description": "Version of document-project workflow"
12
+ },
13
+ "timestamps": {
14
+ "type": "object",
15
+ "required": ["started", "last_updated"],
16
+ "properties": {
17
+ "started": {
18
+ "type": "string",
19
+ "format": "date-time",
20
+ "description": "ISO 8601 timestamp when workflow started"
21
+ },
22
+ "last_updated": {
23
+ "type": "string",
24
+ "format": "date-time",
25
+ "description": "ISO 8601 timestamp of last state update"
26
+ },
27
+ "completed": {
28
+ "type": "string",
29
+ "format": "date-time",
30
+ "description": "ISO 8601 timestamp when workflow completed"
31
+ }
32
+ }
33
+ },
34
+ "mode": {
35
+ "type": "string",
36
+ "enum": ["initial_scan", "full_rescan", "deep_dive", "quick"],
37
+ "description": "Workflow execution mode"
38
+ },
39
+ "scan_level": {
40
+ "type": "string",
41
+ "enum": ["quick", "deep", "exhaustive"],
42
+ "description": "Scan depth level"
43
+ },
44
+ "project_root": {
45
+ "type": "string",
46
+ "description": "Absolute path to project root directory"
47
+ },
48
+ "project_knowledge": {
49
+ "type": "string",
50
+ "description": "Absolute path to project knowledge folder"
51
+ },
52
+ "completed_steps": {
53
+ "type": "array",
54
+ "items": {
55
+ "type": "object",
56
+ "required": ["step", "status"],
57
+ "properties": {
58
+ "step": {
59
+ "type": "string",
60
+ "description": "Step identifier"
61
+ },
62
+ "status": {
63
+ "type": "string",
64
+ "enum": ["completed", "partial", "failed"]
65
+ },
66
+ "timestamp": {
67
+ "type": "string",
68
+ "format": "date-time"
69
+ },
70
+ "outputs": {
71
+ "type": "array",
72
+ "items": { "type": "string" },
73
+ "description": "Files written during this step"
74
+ },
75
+ "summary": {
76
+ "type": "string",
77
+ "description": "1-2 sentence summary of step outcome"
78
+ }
79
+ }
80
+ }
81
+ },
82
+ "current_step": {
83
+ "type": "string",
84
+ "description": "Current step identifier for resumption"
85
+ },
86
+ "findings": {
87
+ "type": "object",
88
+ "description": "High-level summaries only",
89
+ "properties": {
90
+ "project_classification": {
91
+ "type": "object",
92
+ "properties": {
93
+ "repository_type": { "type": "string" },
94
+ "parts_count": { "type": "integer" },
95
+ "primary_language": { "type": "string" },
96
+ "architecture_type": { "type": "string" }
97
+ }
98
+ },
99
+ "technology_stack": {
100
+ "type": "array",
101
+ "items": {
102
+ "type": "object",
103
+ "properties": {
104
+ "part_id": { "type": "string" },
105
+ "tech_summary": { "type": "string" }
106
+ }
107
+ }
108
+ },
109
+ "batches_completed": {
110
+ "type": "array",
111
+ "items": {
112
+ "type": "object",
113
+ "properties": {
114
+ "path": { "type": "string" },
115
+ "files_scanned": { "type": "integer" },
116
+ "summary": { "type": "string" }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ },
122
+ "outputs_generated": {
123
+ "type": "array",
124
+ "items": { "type": "string" },
125
+ "description": "List of all output files generated"
126
+ },
127
+ "resume_instructions": {
128
+ "type": "string",
129
+ "description": "Instructions for resuming from current_step"
130
+ },
131
+ "validation_status": {
132
+ "type": "object",
133
+ "properties": {
134
+ "last_validated": {
135
+ "type": "string",
136
+ "format": "date-time"
137
+ },
138
+ "validation_errors": {
139
+ "type": "array",
140
+ "items": { "type": "string" }
141
+ }
142
+ }
143
+ },
144
+ "deep_dive_targets": {
145
+ "type": "array",
146
+ "description": "Track deep-dive areas analyzed",
147
+ "items": {
148
+ "type": "object",
149
+ "properties": {
150
+ "target_name": { "type": "string" },
151
+ "target_path": { "type": "string" },
152
+ "files_analyzed": { "type": "integer" },
153
+ "output_file": { "type": "string" },
154
+ "timestamp": { "type": "string", "format": "date-time" }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
@@ -0,0 +1,142 @@
1
+ ---
2
+ status: baseline
3
+ owner: Pepper Potts + Peggy Carter
4
+ created: {{date}}
5
+ last_refreshed: {{date}}
6
+ ---
7
+
8
+ # {{project_name}} - Source Tree Analysis
9
+
10
+ **Date:** {{date}}
11
+
12
+ ## Overview
13
+
14
+ {{source_tree_overview}}
15
+
16
+ {{#if is_multi_part}}
17
+
18
+ ## Multi-Part Structure
19
+
20
+ This project is organized into {{parts_count}} distinct parts:
21
+
22
+ {{#each project_parts}}
23
+
24
+ - **{{part_name}}** (`{{root_path}}`): {{purpose}}
25
+ {{/each}}
26
+ {{/if}}
27
+
28
+ ## Complete Directory Structure
29
+
30
+ ```
31
+ {{complete_source_tree}}
32
+ ```
33
+
34
+ ## Critical Directories
35
+
36
+ {{#each critical_folders}}
37
+
38
+ ### `{{folder_path}}`
39
+
40
+ {{description}}
41
+
42
+ **Purpose:** {{purpose}}
43
+ **Contains:** {{contents_summary}}
44
+ {{#if entry_points}}**Entry Points:** {{entry_points}}{{/if}}
45
+ {{#if integration_note}}**Integration:** {{integration_note}}{{/if}}
46
+
47
+ {{/each}}
48
+
49
+ {{#if is_multi_part}}
50
+
51
+ ## Part-Specific Trees
52
+
53
+ {{#each project_parts}}
54
+
55
+ ### {{part_name}} Structure
56
+
57
+ ```
58
+ {{source_tree}}
59
+ ```
60
+
61
+ **Key Directories:**
62
+ {{#each critical_directories}}
63
+
64
+ - **`{{path}}`**: {{description}}
65
+ {{/each}}
66
+
67
+ {{/each}}
68
+
69
+ ## Integration Points
70
+
71
+ {{#each integration_points}}
72
+
73
+ ### {{from_part}} → {{to_part}}
74
+
75
+ - **Location:** `{{integration_path}}`
76
+ - **Type:** {{integration_type}}
77
+ - **Details:** {{details}}
78
+ {{/each}}
79
+
80
+ {{/if}}
81
+
82
+ ## Entry Points
83
+
84
+ {{#if is_single_part}}
85
+
86
+ - **Main Entry:** `{{main_entry_point}}`
87
+ {{#if additional_entry_points}}
88
+ - **Additional:**
89
+ {{#each additional_entry_points}}
90
+ - `{{path}}`: {{description}}
91
+ {{/each}}
92
+ {{/if}}
93
+ {{else}}
94
+ {{#each project_parts}}
95
+
96
+ ### {{part_name}}
97
+
98
+ - **Entry Point:** `{{entry_point}}`
99
+ - **Bootstrap:** {{bootstrap_description}}
100
+ {{/each}}
101
+ {{/if}}
102
+
103
+ ## File Organization Patterns
104
+
105
+ {{file_organization_patterns}}
106
+
107
+ ## Key File Types
108
+
109
+ {{#each file_type_patterns}}
110
+
111
+ ### {{file_type}}
112
+
113
+ - **Pattern:** `{{pattern}}`
114
+ - **Purpose:** {{purpose}}
115
+ - **Examples:** {{examples}}
116
+ {{/each}}
117
+
118
+ ## Asset Locations
119
+
120
+ {{#if has_assets}}
121
+ {{#each asset_locations}}
122
+
123
+ - **{{asset_type}}**: `{{location}}` ({{file_count}} files, {{total_size}})
124
+ {{/each}}
125
+ {{else}}
126
+ No significant assets detected.
127
+ {{/if}}
128
+
129
+ ## Configuration Files
130
+
131
+ {{#each config_files}}
132
+
133
+ - **`{{path}}`**: {{description}}
134
+ {{/each}}
135
+
136
+ ## Notes for Development
137
+
138
+ {{development_notes}}
139
+
140
+ ---
141
+
142
+ _Generated using Wize Dev Kit `document-project` workflow_
@@ -28,6 +28,27 @@ Skip:
28
28
  - `git log --since="1 year ago" --oneline | wc -l` to scope.
29
29
  - Any prior README / ARCHITECTURE / docs that exist.
30
30
 
31
+ ## CLI usage
32
+
33
+ ```bash
34
+ wize-dev-kit document-project # quick baseline (default)
35
+ wize-dev-kit document-project quick # same as default
36
+ wize-dev-kit document-project initial_scan # pattern-only initial scan
37
+ wize-dev-kit document-project initial_scan deep # reads critical directories
38
+ wize-dev-kit document-project initial_scan exhaustive # reads all source files in batches
39
+ wize-dev-kit document-project full_rescan # archives old state, re-runs initial_scan
40
+ wize-dev-kit document-project deep_dive --target src/tools/installer
41
+ ```
42
+
43
+ ## Modes
44
+
45
+ | Mode | What it does | Scan levels |
46
+ |---|---|---|
47
+ | `quick` | Writes the 6 baseline files. Does not read source files. | `quick` only |
48
+ | `initial_scan` | Classifies project type, writes index + overview + source tree + conditional docs. | `quick`, `deep`, `exhaustive` |
49
+ | `full_rescan` | Archives `project-scan-report.json` and re-runs `initial_scan`. | `quick`, `deep`, `exhaustive` |
50
+ | `deep_dive` | Exhaustive analysis of a specific folder/file/feature. | `exhaustive` |
51
+
31
52
  ## Outputs
32
53
 
33
54
  - `.wize/knowledge/document-project/overview.md` — what the project is, who uses it, how big it is.
@@ -36,6 +57,8 @@ Skip:
36
57
  - `.wize/knowledge/document-project/dependencies.md` — runtime deps + dev deps + their roles.
37
58
  - `.wize/knowledge/document-project/risk-spots.md` — areas of concentrated complexity, undocumented behavior, or known fragility.
38
59
  - `.wize/knowledge/document-project/open-questions.md` — things the code doesn't answer; route to humans.
60
+ - `.wize/knowledge/document-project/index.md` — master navigation with `_(To be generated)_` markers for missing conditional docs.
61
+ - `.wize/knowledge/document-project/project-scan-report.json` — resume state.
39
62
 
40
63
  ## Steps
41
64
 
@@ -19,6 +19,7 @@ Hawkeye drives. Tony co-signs. Runs **once**, right after architecture is signed
19
19
  - `.wize/solutioning/epics/`
20
20
  - `.wize/planning/nfr-principles.md`
21
21
  - `.wize/knowledge/document-project/risk-spots.md` (brownfield)
22
+ - `.wize/knowledge/document-project/index.md` (documentation gap markers)
22
23
 
23
24
  ## Output
24
25
 
@@ -115,6 +116,16 @@ The narrative explains the matrix; the YAML is the structured truth. Hawkeye wri
115
116
  - Other stories follow the default 70/20/10 split.
116
117
  ```
117
118
 
119
+ ## Documentation gap risk category
120
+
121
+ In brownfield repos, missing or stale documentation is an operational risk: new contributors make unsafe assumptions, AI agents hallucinate context, and reviews miss implicit contracts. Hawkeye treats it as a first-class risk area.
122
+
123
+ | Symptom | Severity | Rationale | Mitigation |
124
+ |---|---|---|---|
125
+ | `index.md` contains `_(To be generated)_` markers | medium | Conditional docs were skipped; the baseline is incomplete. | Schedule `wize-document-project initial_scan` for the next cooldown. |
126
+ | Baseline files older than 60 days | medium | Docs may no longer reflect the codebase. | Run `wize-refresh-knowledge` at sprint end. |
127
+ | No baseline exists in a brownfield repo | high | Team is flying blind; architecture and conventions are tribal knowledge. | Run `wize-dev-kit document-project quick` immediately. |
128
+
118
129
  ## Anti-patterns Hawkeye rejects
119
130
 
120
131
  - **Findings without mitigation.** A finding without a contract is a wish.
@@ -0,0 +1,319 @@
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
+
134
+ const indexPath = path.join(root, 'index.md');
135
+ const indexContent = fileExists(indexPath) ? readSafe(indexPath) : '';
136
+ const toBeGeneratedMarkers = (indexContent.match(/_\(To be generated\)_/g) || []).length;
137
+
138
+ const statePath = path.join(root, 'project-scan-report.json');
139
+ const stateExists = fileExists(statePath);
140
+ const stateContent = stateExists ? readSafe(statePath) : '';
141
+ let stateAgeDays = null;
142
+ try {
143
+ const state = JSON.parse(stateContent);
144
+ stateAgeDays = daysAgo(state.timestamps && state.timestamps.last_updated);
145
+ } catch (_) {}
146
+
147
+ return { exists: true, files: refreshed, pendingLines, toBeGeneratedMarkers, stateExists, stateAgeDays };
148
+ }
149
+
150
+ function gitInfo(projectRoot) {
151
+ const dotGit = path.join(projectRoot, '.git');
152
+ if (!dirExists(dotGit)) return { isRepo: false };
153
+ let branch = '?'; let head = '?';
154
+ try {
155
+ const HEAD = readSafe(path.join(dotGit, 'HEAD')).trim();
156
+ if (HEAD.startsWith('ref: ')) {
157
+ branch = HEAD.slice(5).replace('refs/heads/', '');
158
+ } else {
159
+ head = HEAD.slice(0, 7);
160
+ }
161
+ } catch (_) {}
162
+ return { isRepo: true, branch, head };
163
+ }
164
+
165
+ function severityIcon(level) {
166
+ return level === 'error' ? '✖' : level === 'warn' ? '⚠' : 'ℹ';
167
+ }
168
+
169
+ async function cmdDoctor({ kitRoot, projectRoot, opts = {} } = {}) {
170
+ const log = opts.log || console.log;
171
+ const cwd = projectRoot;
172
+ const cfg = loadProjectConfig(cwd);
173
+ const installed = loadInstalledKitVersion(kitRoot);
174
+ const phase = detectPhase(cwd);
175
+ const gates = gateStats(cwd);
176
+ const knowledge = knowledgeStatus(cwd);
177
+ const git = gitInfo(cwd);
178
+ const targets = (cfg.install && cfg.install.ide_targets) || [];
179
+ const profiles = (cfg.install && cfg.install.profiles) || [];
180
+ const harnesses = detectHarnessCli({ preferIde: targets });
181
+
182
+ // Try registry — best-effort, never blocks.
183
+ let registryLatest = null;
184
+ try { registryLatest = await getLatestVersion(); } catch (_) {}
185
+
186
+ const suggestions = [];
187
+
188
+ // -- Section: Kit --
189
+ log('');
190
+ log('Wize Dev Kit — Doctor');
191
+ log('─────────────────────');
192
+ log(`Kit version (installed): ${installed || '?'}`);
193
+ log(`Kit version (project): ${cfg.project && cfg.project.kit_version || '— (no install)'}`);
194
+ log(`Kit version (registry): ${registryLatest || '(offline or skipped)'}`);
195
+ if (installed && cfg.project && cfg.project.kit_version && installed !== cfg.project.kit_version) {
196
+ 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.` });
197
+ }
198
+ if (registryLatest && installed && semverGreater(registryLatest, installed)) {
199
+ suggestions.push({ level: 'info', text: `Registry has ${registryLatest}; you're on ${installed}. Run \`npx wize-dev-kit@latest update\` to pick it up.` });
200
+ }
201
+
202
+ // -- Section: Project --
203
+ log('');
204
+ log('Project');
205
+ log('───────');
206
+ if (!cfg.project) {
207
+ log('No .wize/config/project.toml — kit is not installed here.');
208
+ suggestions.push({ level: 'error', text: 'Run `npx wize-dev-kit install` to set the kit up in this repo.' });
209
+ } else {
210
+ log(`Name: ${cfg.project.name || '?'}`);
211
+ log(`Profiles: ${profiles.join(', ') || '(none)'}`);
212
+ log(`IDE targets: ${targets.join(', ') || '(none)'}`);
213
+ log(`Communication lang: ${cfg.language && cfg.language.communication || '?'}`);
214
+ log(`Document lang: ${cfg.language && cfg.language.document_output || '?'}`);
215
+ log(`Current phase: ${phase}`);
216
+ }
217
+
218
+ // -- Section: Adapters --
219
+ if (cfg.install) {
220
+ log('');
221
+ log('IDE Adapters');
222
+ log('────────────');
223
+ for (const t of targets) {
224
+ const dir = adapterTargetPath(t, cwd);
225
+ const count = countAdapterFiles(t, cwd);
226
+ const status = count > 0 ? `✓ ${count} files` : '✖ none';
227
+ log(` ${t.padEnd(14)} ${status.padEnd(15)} ${dir ? path.relative(cwd, dir) : '(no path)'}`);
228
+ if (count === 0) suggestions.push({ level: 'warn', text: `Adapter "${t}" has no rendered files. Run \`npx wize-dev-kit sync\`.` });
229
+ }
230
+ }
231
+
232
+ // -- Section: TEA gates --
233
+ if (gates.total > 0) {
234
+ log('');
235
+ log('TEA gates');
236
+ log('─────────');
237
+ log(`Total gate.md files: ${gates.total}`);
238
+ log(`PASS: ${gates.PASS}`);
239
+ log(`CONCERNS: ${gates.CONCERNS}`);
240
+ log(`FAIL: ${gates.FAIL}`);
241
+ log(`WAIVED: ${gates.WAIVED}`);
242
+ 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).` });
243
+ if (gates.CONCERNS > 0) suggestions.push({ level: 'warn', text: `${gates.CONCERNS} story gate(s) at CONCERNS. Review findings before next sprint.` });
244
+ }
245
+
246
+ // -- Section: Knowledge --
247
+ if (knowledge.exists) {
248
+ log('');
249
+ log('Knowledge baseline (`document-project/`)');
250
+ log('────────────────────────────────────────');
251
+ for (const f of knowledge.files) {
252
+ const age = f.days == null ? 'no last_refreshed' : `${f.days}d ago`;
253
+ log(` ${f.name.padEnd(28)} ${age}`);
254
+ if (f.days != null && f.days > 60) {
255
+ suggestions.push({ level: 'warn', text: `\`${f.name}\` last refreshed ${f.days}d ago. Run \`wize-refresh-knowledge\` after current sprint.` });
256
+ }
257
+ }
258
+ if (knowledge.pendingLines > 0) {
259
+ log(` _pending.md: ${knowledge.pendingLines} inline note(s) waiting consolidation`);
260
+ if (knowledge.pendingLines >= 5) {
261
+ suggestions.push({ level: 'info', text: `${knowledge.pendingLines} notes piled up in _pending.md. Time to run \`wize-refresh-knowledge\`.` });
262
+ }
263
+ }
264
+ const markerText = knowledge.toBeGeneratedMarkers > 0 ? `${knowledge.toBeGeneratedMarkers} marker(s)` : 'none';
265
+ log(` To be generated markers: ${markerText}`);
266
+ if (knowledge.toBeGeneratedMarkers >= 5) {
267
+ suggestions.push({ level: 'warn', text: `${knowledge.toBeGeneratedMarkers} docs marked "To be generated" in index.md. Run \`wize-dev-kit document-project initial_scan deep\` to fill them.` });
268
+ }
269
+ const stateText = knowledge.stateExists
270
+ ? (knowledge.stateAgeDays == null ? 'project-scan-report.json exists (age unknown)' : `project-scan-report.json ${knowledge.stateAgeDays}d old`)
271
+ : 'no project-scan-report.json';
272
+ log(` Scan state: ${stateText}`);
273
+ if (!knowledge.stateExists) {
274
+ suggestions.push({ level: 'info', text: 'No scan state file found. Run `wize-dev-kit document-project quick` to create a baseline + state.' });
275
+ }
276
+ }
277
+
278
+ // -- Section: Harness CLIs --
279
+ log('');
280
+ log('AI Harness CLIs on PATH');
281
+ log('───────────────────────');
282
+ if (harnesses.length === 0) {
283
+ log(' (none detected)');
284
+ suggestions.push({ level: 'info', text: 'No harness CLI detected. Brownfield baseline + auto-run features only work in your IDE; the headless flow is unavailable.' });
285
+ } else {
286
+ for (const h of harnesses) log(` ${h.binary.padEnd(10)} ${h.path}`);
287
+ }
288
+
289
+ // -- Section: Git --
290
+ log('');
291
+ log('Git');
292
+ log('───');
293
+ if (!git.isRepo) {
294
+ log(' (not a git repo)');
295
+ suggestions.push({ level: 'warn', text: 'Not a git repository. Run `git init` before installing the kit so version history is recorded.' });
296
+ } else {
297
+ log(` branch: ${git.branch}`);
298
+ log(` head: ${git.head}`);
299
+ }
300
+
301
+ // -- Section: Suggestions --
302
+ log('');
303
+ log('Suggestions');
304
+ log('───────────');
305
+ if (suggestions.length === 0) {
306
+ log(' ✓ Everything looks good.');
307
+ } else {
308
+ for (const s of suggestions) log(` ${severityIcon(s.level)} ${s.text}`);
309
+ }
310
+ log('');
311
+
312
+ return { suggestions, phase, gates, knowledge, targets, profiles, installed, registryLatest };
313
+ }
314
+
315
+ module.exports = {
316
+ cmdDoctor,
317
+ // exported for tests:
318
+ detectPhase, gateStats, knowledgeStatus, gitInfo, adapterTargetPath, countAdapterFiles
319
+ };