prjct-cli 0.45.0 → 0.45.4
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 +82 -0
- package/bin/prjct.ts +117 -10
- package/core/__tests__/agentic/memory-system.test.ts +39 -26
- package/core/__tests__/agentic/plan-mode.test.ts +64 -46
- package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
- package/core/__tests__/services/project-index.test.ts +353 -0
- package/core/__tests__/types/fs.test.ts +3 -3
- package/core/__tests__/utils/date-helper.test.ts +10 -10
- package/core/__tests__/utils/output.test.ts +9 -6
- package/core/__tests__/utils/project-commands.test.ts +5 -6
- package/core/agentic/agent-router.ts +9 -10
- package/core/agentic/chain-of-thought.ts +16 -4
- package/core/agentic/command-executor.ts +66 -40
- package/core/agentic/context-builder.ts +8 -5
- package/core/agentic/ground-truth.ts +15 -9
- package/core/agentic/index.ts +145 -152
- package/core/agentic/loop-detector.ts +40 -11
- package/core/agentic/memory-system.ts +98 -35
- package/core/agentic/orchestrator-executor.ts +135 -71
- package/core/agentic/plan-mode.ts +46 -16
- package/core/agentic/prompt-builder.ts +108 -42
- package/core/agentic/services.ts +10 -9
- package/core/agentic/skill-loader.ts +9 -15
- package/core/agentic/smart-context.ts +129 -79
- package/core/agentic/template-executor.ts +13 -12
- package/core/agentic/template-loader.ts +7 -4
- package/core/agentic/tool-registry.ts +16 -13
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +10 -27
- package/core/ai-tools/formatters.ts +8 -6
- package/core/ai-tools/generator.ts +4 -4
- package/core/ai-tools/index.ts +1 -1
- package/core/ai-tools/registry.ts +21 -11
- package/core/bus/bus.ts +23 -16
- package/core/bus/index.ts +2 -2
- package/core/cli/linear.ts +3 -5
- package/core/cli/start.ts +28 -25
- package/core/commands/analysis.ts +58 -39
- package/core/commands/analytics.ts +52 -44
- package/core/commands/base.ts +15 -13
- package/core/commands/cleanup.ts +6 -13
- package/core/commands/command-data.ts +28 -4
- package/core/commands/commands.ts +57 -24
- package/core/commands/context.ts +4 -4
- package/core/commands/design.ts +3 -10
- package/core/commands/index.ts +5 -8
- package/core/commands/maintenance.ts +7 -4
- package/core/commands/planning.ts +179 -56
- package/core/commands/register.ts +13 -9
- package/core/commands/registry.ts +15 -14
- package/core/commands/setup.ts +26 -14
- package/core/commands/shipping.ts +11 -16
- package/core/commands/snapshots.ts +16 -32
- package/core/commands/uninstall.ts +541 -0
- package/core/commands/workflow.ts +24 -28
- package/core/constants/index.ts +10 -22
- package/core/context/generator.ts +82 -33
- package/core/context-tools/files-tool.ts +18 -19
- package/core/context-tools/imports-tool.ts +13 -33
- package/core/context-tools/index.ts +29 -54
- package/core/context-tools/recent-tool.ts +16 -22
- package/core/context-tools/signatures-tool.ts +17 -26
- package/core/context-tools/summary-tool.ts +20 -22
- package/core/context-tools/token-counter.ts +25 -20
- package/core/context-tools/types.ts +5 -5
- package/core/domain/agent-generator.ts +7 -5
- package/core/domain/agent-loader.ts +2 -2
- package/core/domain/analyzer.ts +19 -16
- package/core/domain/architecture-generator.ts +6 -3
- package/core/domain/context-estimator.ts +3 -4
- package/core/domain/snapshot-manager.ts +25 -22
- package/core/domain/task-stack.ts +24 -14
- package/core/errors.ts +1 -1
- package/core/events/events.ts +2 -4
- package/core/events/index.ts +1 -2
- package/core/index.ts +28 -16
- package/core/infrastructure/agent-detector.ts +3 -3
- package/core/infrastructure/ai-provider.ts +23 -20
- package/core/infrastructure/author-detector.ts +16 -10
- package/core/infrastructure/capability-installer.ts +2 -2
- package/core/infrastructure/claude-agent.ts +6 -6
- package/core/infrastructure/command-installer.ts +22 -17
- package/core/infrastructure/config-manager.ts +18 -14
- package/core/infrastructure/editors-config.ts +8 -4
- package/core/infrastructure/path-manager.ts +8 -6
- package/core/infrastructure/permission-manager.ts +20 -17
- package/core/infrastructure/setup.ts +42 -38
- package/core/infrastructure/update-checker.ts +5 -5
- package/core/integrations/issue-tracker/enricher.ts +8 -19
- package/core/integrations/issue-tracker/index.ts +2 -2
- package/core/integrations/issue-tracker/manager.ts +15 -15
- package/core/integrations/issue-tracker/types.ts +5 -22
- package/core/integrations/jira/client.ts +67 -59
- package/core/integrations/jira/index.ts +11 -14
- package/core/integrations/jira/mcp-adapter.ts +5 -10
- package/core/integrations/jira/service.ts +10 -10
- package/core/integrations/linear/client.ts +27 -18
- package/core/integrations/linear/index.ts +9 -12
- package/core/integrations/linear/service.ts +11 -11
- package/core/integrations/linear/sync.ts +8 -8
- package/core/outcomes/analyzer.ts +5 -18
- package/core/outcomes/index.ts +2 -2
- package/core/outcomes/recorder.ts +3 -3
- package/core/plugin/builtin/webhook.ts +19 -15
- package/core/plugin/hooks.ts +29 -21
- package/core/plugin/index.ts +7 -7
- package/core/plugin/loader.ts +19 -19
- package/core/plugin/registry.ts +12 -23
- package/core/schemas/agents.ts +1 -1
- package/core/schemas/analysis.ts +1 -1
- package/core/schemas/enriched-task.ts +62 -49
- package/core/schemas/ideas.ts +13 -13
- package/core/schemas/index.ts +17 -27
- package/core/schemas/issues.ts +40 -25
- package/core/schemas/metrics.ts +25 -25
- package/core/schemas/outcomes.ts +70 -62
- package/core/schemas/permissions.ts +15 -12
- package/core/schemas/prd.ts +27 -14
- package/core/schemas/project.ts +3 -3
- package/core/schemas/roadmap.ts +47 -34
- package/core/schemas/schemas.ts +3 -4
- package/core/schemas/shipped.ts +3 -3
- package/core/schemas/state.ts +43 -29
- package/core/server/index.ts +5 -6
- package/core/server/routes-extended.ts +68 -72
- package/core/server/routes.ts +3 -3
- package/core/server/server.ts +31 -26
- package/core/services/agent-generator.ts +237 -0
- package/core/services/agent-service.ts +2 -2
- package/core/services/breakdown-service.ts +2 -4
- package/core/services/context-generator.ts +299 -0
- package/core/services/context-selector.ts +420 -0
- package/core/services/doctor-service.ts +426 -0
- package/core/services/file-categorizer.ts +448 -0
- package/core/services/file-scorer.ts +270 -0
- package/core/services/git-analyzer.ts +267 -0
- package/core/services/index.ts +27 -10
- package/core/services/memory-service.ts +3 -4
- package/core/services/project-index.ts +911 -0
- package/core/services/project-service.ts +4 -4
- package/core/services/skill-installer.ts +14 -17
- package/core/services/skill-lock.ts +3 -3
- package/core/services/skill-service.ts +12 -6
- package/core/services/stack-detector.ts +245 -0
- package/core/services/sync-service.ts +87 -345
- package/core/services/watch-service.ts +294 -0
- package/core/session/compaction.ts +23 -31
- package/core/session/index.ts +11 -5
- package/core/session/log-migration.ts +3 -3
- package/core/session/metrics.ts +19 -14
- package/core/session/session-log-manager.ts +12 -17
- package/core/session/task-session-manager.ts +25 -25
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +41 -57
- package/core/storage/index-storage.ts +514 -0
- package/core/storage/index.ts +41 -17
- package/core/storage/metrics-storage.ts +39 -34
- package/core/storage/queue-storage.ts +35 -45
- package/core/storage/shipped-storage.ts +17 -20
- package/core/storage/state-storage.ts +50 -30
- package/core/storage/storage-manager.ts +6 -6
- package/core/storage/storage.ts +18 -15
- package/core/sync/auth-config.ts +3 -3
- package/core/sync/index.ts +13 -19
- package/core/sync/oauth-handler.ts +3 -3
- package/core/sync/sync-client.ts +4 -9
- package/core/sync/sync-manager.ts +12 -14
- package/core/types/commands.ts +42 -7
- package/core/types/index.ts +284 -305
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +14 -14
- package/core/types/utils.ts +3 -3
- package/core/utils/agent-stream.ts +3 -1
- package/core/utils/animations.ts +14 -11
- package/core/utils/branding.ts +7 -7
- package/core/utils/cache.ts +1 -3
- package/core/utils/collection-filters.ts +3 -15
- package/core/utils/date-helper.ts +2 -7
- package/core/utils/file-helper.ts +13 -8
- package/core/utils/jsonl-helper.ts +13 -10
- package/core/utils/keychain.ts +4 -8
- package/core/utils/logger.ts +1 -1
- package/core/utils/next-steps.ts +3 -3
- package/core/utils/output.ts +58 -11
- package/core/utils/project-commands.ts +6 -6
- package/core/utils/project-credentials.ts +5 -12
- package/core/utils/runtime.ts +2 -2
- package/core/utils/session-helper.ts +3 -4
- package/core/utils/version.ts +3 -3
- package/core/wizard/index.ts +13 -0
- package/core/wizard/onboarding.ts +633 -0
- package/core/workflow/state-machine.ts +7 -7
- package/dist/bin/prjct.mjs +18755 -15574
- package/dist/core/infrastructure/command-installer.js +86 -79
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +246 -225
- package/dist/core/utils/version.js +9 -9
- package/package.json +11 -12
- package/scripts/build.js +3 -3
- package/scripts/postinstall.js +2 -2
- package/templates/mcp-config.json +6 -1
- package/templates/permissions/permissive.jsonc +1 -1
- package/templates/permissions/strict.jsonc +5 -9
- package/templates/global/docs/agents.md +0 -88
- package/templates/global/docs/architecture.md +0 -103
- package/templates/global/docs/commands.md +0 -96
- package/templates/global/docs/validation.md +0 -95
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DoctorService - System health checks and diagnostics
|
|
3
|
+
*
|
|
4
|
+
* Checks:
|
|
5
|
+
* - Required tools (git, node)
|
|
6
|
+
* - Optional tools (bun, gh, claude, gemini)
|
|
7
|
+
* - Project configuration
|
|
8
|
+
* - Generated file staleness
|
|
9
|
+
* - Provides actionable recommendations
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execSync } from 'node:child_process'
|
|
13
|
+
import fs from 'node:fs/promises'
|
|
14
|
+
import path from 'node:path'
|
|
15
|
+
import chalk from 'chalk'
|
|
16
|
+
import configManager from '../infrastructure/config-manager'
|
|
17
|
+
import pathManager from '../infrastructure/path-manager'
|
|
18
|
+
import { VERSION } from '../utils/version'
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// TYPES
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
interface CheckResult {
|
|
25
|
+
name: string
|
|
26
|
+
status: 'ok' | 'warn' | 'error'
|
|
27
|
+
version?: string
|
|
28
|
+
message?: string
|
|
29
|
+
optional?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface DoctorResult {
|
|
33
|
+
success: boolean
|
|
34
|
+
tools: CheckResult[]
|
|
35
|
+
project: CheckResult[]
|
|
36
|
+
recommendations: string[]
|
|
37
|
+
hasErrors: boolean
|
|
38
|
+
hasWarnings: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// DOCTOR SERVICE
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
class DoctorService {
|
|
46
|
+
private projectPath: string = ''
|
|
47
|
+
private projectId: string | null = null
|
|
48
|
+
private globalPath: string = ''
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Run all health checks
|
|
52
|
+
*/
|
|
53
|
+
async check(projectPath: string = process.cwd()): Promise<DoctorResult> {
|
|
54
|
+
this.projectPath = projectPath
|
|
55
|
+
this.projectId = await configManager.getProjectId(projectPath)
|
|
56
|
+
if (this.projectId) {
|
|
57
|
+
this.globalPath = pathManager.getGlobalProjectPath(this.projectId)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const tools = await this.checkTools()
|
|
61
|
+
const project = await this.checkProject()
|
|
62
|
+
const recommendations = this.generateRecommendations(tools, project)
|
|
63
|
+
|
|
64
|
+
const hasErrors = [...tools, ...project].some((c) => c.status === 'error' && !c.optional)
|
|
65
|
+
const hasWarnings = [...tools, ...project].some(
|
|
66
|
+
(c) => c.status === 'warn' || (c.status === 'error' && c.optional)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
success: !hasErrors,
|
|
71
|
+
tools,
|
|
72
|
+
project,
|
|
73
|
+
recommendations,
|
|
74
|
+
hasErrors,
|
|
75
|
+
hasWarnings,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Run checks and print formatted output
|
|
81
|
+
*/
|
|
82
|
+
async run(projectPath: string = process.cwd()): Promise<number> {
|
|
83
|
+
const result = await this.check(projectPath)
|
|
84
|
+
|
|
85
|
+
this.printHeader()
|
|
86
|
+
this.printSection('System Tools', result.tools)
|
|
87
|
+
this.printSection('Project Status', result.project)
|
|
88
|
+
|
|
89
|
+
if (result.recommendations.length > 0) {
|
|
90
|
+
this.printRecommendations(result.recommendations)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.printSummary(result)
|
|
94
|
+
|
|
95
|
+
return result.hasErrors ? 1 : 0
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ==========================================================================
|
|
99
|
+
// TOOL CHECKS
|
|
100
|
+
// ==========================================================================
|
|
101
|
+
|
|
102
|
+
private async checkTools(): Promise<CheckResult[]> {
|
|
103
|
+
const checks: CheckResult[] = []
|
|
104
|
+
|
|
105
|
+
// Git (required)
|
|
106
|
+
checks.push(this.checkCommand('git', 'git --version', /git version ([\d.]+)/, false))
|
|
107
|
+
|
|
108
|
+
// Node (required)
|
|
109
|
+
checks.push(this.checkCommand('node', 'node --version', /v([\d.]+)/, false))
|
|
110
|
+
|
|
111
|
+
// Bun (optional)
|
|
112
|
+
checks.push(this.checkCommand('bun', 'bun --version', /([\d.]+)/, true))
|
|
113
|
+
|
|
114
|
+
// GitHub CLI (optional)
|
|
115
|
+
checks.push(
|
|
116
|
+
this.checkCommand('gh', 'gh --version', /gh version ([\d.]+)/, true, 'needed for PR commands')
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
// Claude Code (optional)
|
|
120
|
+
checks.push(
|
|
121
|
+
this.checkCommand(
|
|
122
|
+
'claude',
|
|
123
|
+
'claude --version',
|
|
124
|
+
/claude ([\d.]+)/,
|
|
125
|
+
true,
|
|
126
|
+
'Anthropic Claude Code CLI'
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// Gemini CLI (optional)
|
|
131
|
+
checks.push(
|
|
132
|
+
this.checkCommand('gemini', 'gemini --version', /gemini ([\d.]+)/, true, 'Google Gemini CLI')
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return checks
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private checkCommand(
|
|
139
|
+
name: string,
|
|
140
|
+
command: string,
|
|
141
|
+
versionRegex: RegExp,
|
|
142
|
+
optional: boolean,
|
|
143
|
+
description?: string
|
|
144
|
+
): CheckResult {
|
|
145
|
+
try {
|
|
146
|
+
const output = execSync(command, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] })
|
|
147
|
+
const match = output.match(versionRegex)
|
|
148
|
+
const version = match ? match[1] : 'unknown'
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
name,
|
|
152
|
+
status: 'ok',
|
|
153
|
+
version,
|
|
154
|
+
optional,
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
return {
|
|
158
|
+
name,
|
|
159
|
+
status: 'error',
|
|
160
|
+
message: description ? `not found (${description})` : 'not found',
|
|
161
|
+
optional,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ==========================================================================
|
|
167
|
+
// PROJECT CHECKS
|
|
168
|
+
// ==========================================================================
|
|
169
|
+
|
|
170
|
+
private async checkProject(): Promise<CheckResult[]> {
|
|
171
|
+
const checks: CheckResult[] = []
|
|
172
|
+
|
|
173
|
+
// prjct config
|
|
174
|
+
checks.push(await this.checkPrjctConfig())
|
|
175
|
+
|
|
176
|
+
// CLAUDE.md
|
|
177
|
+
checks.push(await this.checkClaudeMd())
|
|
178
|
+
|
|
179
|
+
// Git repo
|
|
180
|
+
checks.push(await this.checkGitRepo())
|
|
181
|
+
|
|
182
|
+
// State file
|
|
183
|
+
checks.push(await this.checkStateFile())
|
|
184
|
+
|
|
185
|
+
return checks
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async checkPrjctConfig(): Promise<CheckResult> {
|
|
189
|
+
const configPath = path.join(this.projectPath, '.prjct', 'prjct.config.json')
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await fs.access(configPath)
|
|
193
|
+
return {
|
|
194
|
+
name: 'prjct config',
|
|
195
|
+
status: 'ok',
|
|
196
|
+
message: 'initialized',
|
|
197
|
+
}
|
|
198
|
+
} catch {
|
|
199
|
+
return {
|
|
200
|
+
name: 'prjct config',
|
|
201
|
+
status: 'error',
|
|
202
|
+
message: 'not initialized - run "prjct init"',
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private async checkClaudeMd(): Promise<CheckResult> {
|
|
208
|
+
if (!this.globalPath) {
|
|
209
|
+
return {
|
|
210
|
+
name: 'CLAUDE.md',
|
|
211
|
+
status: 'warn',
|
|
212
|
+
message: 'project not initialized',
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const claudePath = path.join(this.globalPath, 'context', 'CLAUDE.md')
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const stat = await fs.stat(claudePath)
|
|
220
|
+
const ageMs = Date.now() - stat.mtimeMs
|
|
221
|
+
const ageHours = Math.floor(ageMs / (1000 * 60 * 60))
|
|
222
|
+
const ageDays = Math.floor(ageHours / 24)
|
|
223
|
+
|
|
224
|
+
let ageStr: string
|
|
225
|
+
if (ageDays > 0) {
|
|
226
|
+
ageStr = `${ageDays} day${ageDays > 1 ? 's' : ''} ago`
|
|
227
|
+
} else if (ageHours > 0) {
|
|
228
|
+
ageStr = `${ageHours} hour${ageHours > 1 ? 's' : ''} ago`
|
|
229
|
+
} else {
|
|
230
|
+
ageStr = 'recently'
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Warn if older than 24 hours
|
|
234
|
+
if (ageHours > 24) {
|
|
235
|
+
return {
|
|
236
|
+
name: 'CLAUDE.md',
|
|
237
|
+
status: 'warn',
|
|
238
|
+
message: `stale (last sync: ${ageStr})`,
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
name: 'CLAUDE.md',
|
|
244
|
+
status: 'ok',
|
|
245
|
+
message: `synced ${ageStr}`,
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
248
|
+
return {
|
|
249
|
+
name: 'CLAUDE.md',
|
|
250
|
+
status: 'error',
|
|
251
|
+
message: 'not found - run "prjct sync"',
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private async checkGitRepo(): Promise<CheckResult> {
|
|
257
|
+
try {
|
|
258
|
+
execSync('git rev-parse --git-dir', {
|
|
259
|
+
cwd: this.projectPath,
|
|
260
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
// Check for uncommitted changes
|
|
264
|
+
const status = execSync('git status --porcelain', {
|
|
265
|
+
cwd: this.projectPath,
|
|
266
|
+
encoding: 'utf-8',
|
|
267
|
+
})
|
|
268
|
+
const hasChanges = status.trim().length > 0
|
|
269
|
+
|
|
270
|
+
if (hasChanges) {
|
|
271
|
+
const lines = status.trim().split('\n').filter(Boolean)
|
|
272
|
+
return {
|
|
273
|
+
name: 'git repo',
|
|
274
|
+
status: 'ok',
|
|
275
|
+
message: `${lines.length} uncommitted change${lines.length > 1 ? 's' : ''}`,
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
name: 'git repo',
|
|
281
|
+
status: 'ok',
|
|
282
|
+
message: 'clean',
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
return {
|
|
286
|
+
name: 'git repo',
|
|
287
|
+
status: 'warn',
|
|
288
|
+
message: 'not a git repository',
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private async checkStateFile(): Promise<CheckResult> {
|
|
294
|
+
if (!this.globalPath) {
|
|
295
|
+
return {
|
|
296
|
+
name: 'task state',
|
|
297
|
+
status: 'warn',
|
|
298
|
+
message: 'project not initialized',
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const statePath = path.join(this.globalPath, 'storage', 'state.json')
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const content = await fs.readFile(statePath, 'utf-8')
|
|
306
|
+
const state = JSON.parse(content)
|
|
307
|
+
|
|
308
|
+
if (state.currentTask) {
|
|
309
|
+
return {
|
|
310
|
+
name: 'task state',
|
|
311
|
+
status: 'ok',
|
|
312
|
+
message: `active: ${state.currentTask.description?.slice(0, 30)}...`,
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
name: 'task state',
|
|
318
|
+
status: 'ok',
|
|
319
|
+
message: 'no active task',
|
|
320
|
+
}
|
|
321
|
+
} catch {
|
|
322
|
+
return {
|
|
323
|
+
name: 'task state',
|
|
324
|
+
status: 'ok',
|
|
325
|
+
message: 'no state file (normal for new projects)',
|
|
326
|
+
optional: true,
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ==========================================================================
|
|
332
|
+
// RECOMMENDATIONS
|
|
333
|
+
// ==========================================================================
|
|
334
|
+
|
|
335
|
+
private generateRecommendations(tools: CheckResult[], project: CheckResult[]): string[] {
|
|
336
|
+
const recommendations: string[] = []
|
|
337
|
+
|
|
338
|
+
// Check for missing optional tools
|
|
339
|
+
const missingGh = tools.find((t) => t.name === 'gh' && t.status === 'error')
|
|
340
|
+
if (missingGh) {
|
|
341
|
+
recommendations.push('Install GitHub CLI (gh) for PR commands: https://cli.github.com')
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Check for stale CLAUDE.md
|
|
345
|
+
const claudeCheck = project.find((p) => p.name === 'CLAUDE.md')
|
|
346
|
+
if (claudeCheck?.status === 'warn' && claudeCheck.message?.includes('stale')) {
|
|
347
|
+
recommendations.push('Run "prjct sync" to update context')
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Check for missing init
|
|
351
|
+
const configCheck = project.find((p) => p.name === 'prjct config')
|
|
352
|
+
if (configCheck?.status === 'error') {
|
|
353
|
+
recommendations.push('Run "prjct init" to initialize this project')
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check for missing CLAUDE.md
|
|
357
|
+
const claudeMissing = project.find((p) => p.name === 'CLAUDE.md' && p.status === 'error')
|
|
358
|
+
if (claudeMissing && !configCheck?.status?.includes('error')) {
|
|
359
|
+
recommendations.push('Run "prjct sync" to generate context files')
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return recommendations
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ==========================================================================
|
|
366
|
+
// OUTPUT
|
|
367
|
+
// ==========================================================================
|
|
368
|
+
|
|
369
|
+
private printHeader(): void {
|
|
370
|
+
console.log('')
|
|
371
|
+
console.log(chalk.bold(`prjct doctor v${VERSION}`))
|
|
372
|
+
console.log(chalk.dim('─'.repeat(40)))
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private printSection(title: string, checks: CheckResult[]): void {
|
|
376
|
+
console.log('')
|
|
377
|
+
console.log(chalk.bold(title))
|
|
378
|
+
|
|
379
|
+
for (const check of checks) {
|
|
380
|
+
const icon = this.getStatusIcon(check.status, check.optional)
|
|
381
|
+
const name = check.name.padEnd(14)
|
|
382
|
+
const detail = check.version || check.message || ''
|
|
383
|
+
const optionalTag = check.optional && check.status === 'error' ? chalk.dim(' (optional)') : ''
|
|
384
|
+
|
|
385
|
+
console.log(` ${icon} ${name} ${chalk.dim(detail)}${optionalTag}`)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private printRecommendations(recommendations: string[]): void {
|
|
390
|
+
console.log('')
|
|
391
|
+
console.log(chalk.bold('Recommendations'))
|
|
392
|
+
|
|
393
|
+
for (const rec of recommendations) {
|
|
394
|
+
console.log(` ${chalk.yellow('•')} ${rec}`)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private printSummary(result: DoctorResult): void {
|
|
399
|
+
console.log('')
|
|
400
|
+
console.log(chalk.dim('─'.repeat(40)))
|
|
401
|
+
|
|
402
|
+
if (result.hasErrors) {
|
|
403
|
+
console.log(chalk.red('✗ Some required checks failed'))
|
|
404
|
+
} else if (result.hasWarnings) {
|
|
405
|
+
console.log(chalk.yellow('⚠ All required checks passed (some warnings)'))
|
|
406
|
+
} else {
|
|
407
|
+
console.log(chalk.green('✓ All checks passed'))
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
console.log('')
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private getStatusIcon(status: 'ok' | 'warn' | 'error', optional?: boolean): string {
|
|
414
|
+
switch (status) {
|
|
415
|
+
case 'ok':
|
|
416
|
+
return chalk.green('✓')
|
|
417
|
+
case 'warn':
|
|
418
|
+
return chalk.yellow('⚠')
|
|
419
|
+
case 'error':
|
|
420
|
+
return optional ? chalk.dim('○') : chalk.red('✗')
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export const doctorService = new DoctorService()
|
|
426
|
+
export { DoctorService, type DoctorResult, type CheckResult }
|