prjct-cli 0.44.1 → 0.45.3
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 +114 -0
- package/bin/prjct.ts +131 -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 +287 -29
- 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 +49 -8
- package/core/commands/commands.ts +60 -23
- 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 +14 -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 +583 -0
- package/core/context-tools/imports-tool.ts +403 -0
- package/core/context-tools/index.ts +433 -0
- package/core/context-tools/recent-tool.ts +307 -0
- package/core/context-tools/signatures-tool.ts +501 -0
- package/core/context-tools/summary-tool.ts +307 -0
- package/core/context-tools/token-counter.ts +284 -0
- package/core/context-tools/types.ts +253 -0
- 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 -12
- 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 +143 -0
- 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 +170 -329
- 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 -13
- package/core/storage/metrics-storage.ts +320 -0
- 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 -302
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +49 -0
- 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 +18907 -13189
- package/dist/core/infrastructure/command-installer.js +96 -111
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +256 -257
- 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,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uninstall Command
|
|
3
|
+
*
|
|
4
|
+
* Complete system removal of prjct-cli.
|
|
5
|
+
* Handles cleanup of all prjct files, configurations, and installations.
|
|
6
|
+
*
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execSync } from 'node:child_process'
|
|
11
|
+
import fsSync from 'node:fs'
|
|
12
|
+
import fs from 'node:fs/promises'
|
|
13
|
+
import os from 'node:os'
|
|
14
|
+
import path from 'node:path'
|
|
15
|
+
import readline from 'node:readline'
|
|
16
|
+
import chalk from 'chalk'
|
|
17
|
+
import { getProviderPaths } from '../infrastructure/command-installer'
|
|
18
|
+
import pathManager from '../infrastructure/path-manager'
|
|
19
|
+
import type { CommandResult, UninstallOptions } from '../types'
|
|
20
|
+
import { PrjctCommandsBase } from './base'
|
|
21
|
+
|
|
22
|
+
// Markers for prjct section in CLAUDE.md
|
|
23
|
+
const PRJCT_START_MARKER = '<!-- prjct:start - DO NOT REMOVE THIS MARKER -->'
|
|
24
|
+
const PRJCT_END_MARKER = '<!-- prjct:end - DO NOT REMOVE THIS MARKER -->'
|
|
25
|
+
|
|
26
|
+
interface UninstallItem {
|
|
27
|
+
path: string
|
|
28
|
+
type: 'directory' | 'file' | 'section'
|
|
29
|
+
description: string
|
|
30
|
+
size?: number
|
|
31
|
+
count?: number
|
|
32
|
+
exists: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface InstallationInfo {
|
|
36
|
+
homebrew: boolean
|
|
37
|
+
npm: boolean
|
|
38
|
+
homebrewFormula?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get directory size recursively
|
|
43
|
+
*/
|
|
44
|
+
async function getDirectorySize(dirPath: string): Promise<number> {
|
|
45
|
+
let totalSize = 0
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
|
49
|
+
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
const entryPath = path.join(dirPath, entry.name)
|
|
52
|
+
|
|
53
|
+
if (entry.isDirectory()) {
|
|
54
|
+
totalSize += await getDirectorySize(entryPath)
|
|
55
|
+
} else {
|
|
56
|
+
try {
|
|
57
|
+
const stats = await fs.stat(entryPath)
|
|
58
|
+
totalSize += stats.size
|
|
59
|
+
} catch {
|
|
60
|
+
// Skip files we can't stat
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// Directory doesn't exist or can't be read
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return totalSize
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Format bytes to human readable size
|
|
73
|
+
*/
|
|
74
|
+
function formatSize(bytes: number): string {
|
|
75
|
+
if (bytes === 0) return '0 B'
|
|
76
|
+
|
|
77
|
+
const units = ['B', 'KB', 'MB', 'GB']
|
|
78
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024))
|
|
79
|
+
const size = bytes / 1024 ** i
|
|
80
|
+
|
|
81
|
+
return `${size.toFixed(1)} ${units[i]}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Count items in a directory
|
|
86
|
+
*/
|
|
87
|
+
async function countDirectoryItems(dirPath: string): Promise<number> {
|
|
88
|
+
try {
|
|
89
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
|
90
|
+
return entries.filter((e) => e.isDirectory()).length
|
|
91
|
+
} catch {
|
|
92
|
+
return 0
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Detect installation method
|
|
98
|
+
*/
|
|
99
|
+
function detectInstallation(): InstallationInfo {
|
|
100
|
+
const info: InstallationInfo = {
|
|
101
|
+
homebrew: false,
|
|
102
|
+
npm: false,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check Homebrew
|
|
106
|
+
try {
|
|
107
|
+
const result = execSync('brew list prjct-cli 2>/dev/null', { encoding: 'utf-8' })
|
|
108
|
+
if (result) {
|
|
109
|
+
info.homebrew = true
|
|
110
|
+
info.homebrewFormula = 'prjct-cli'
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// Not installed via Homebrew
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check npm global
|
|
117
|
+
try {
|
|
118
|
+
const result = execSync('npm list -g prjct-cli --depth=0 2>/dev/null', { encoding: 'utf-8' })
|
|
119
|
+
if (result.includes('prjct-cli')) {
|
|
120
|
+
info.npm = true
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
// Not installed via npm
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return info
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Gather all items to uninstall
|
|
131
|
+
*/
|
|
132
|
+
async function gatherUninstallItems(): Promise<UninstallItem[]> {
|
|
133
|
+
const items: UninstallItem[] = []
|
|
134
|
+
const providerPaths = getProviderPaths()
|
|
135
|
+
|
|
136
|
+
// 1. ~/.prjct-cli/ (main data directory)
|
|
137
|
+
const prjctCliPath = pathManager.getGlobalBasePath()
|
|
138
|
+
const prjctCliExists = fsSync.existsSync(prjctCliPath)
|
|
139
|
+
const projectCount = prjctCliExists
|
|
140
|
+
? await countDirectoryItems(path.join(prjctCliPath, 'projects'))
|
|
141
|
+
: 0
|
|
142
|
+
const prjctCliSize = prjctCliExists ? await getDirectorySize(prjctCliPath) : 0
|
|
143
|
+
|
|
144
|
+
items.push({
|
|
145
|
+
path: prjctCliPath,
|
|
146
|
+
type: 'directory',
|
|
147
|
+
description: `All project data${projectCount > 0 ? `, ${projectCount} project${projectCount > 1 ? 's' : ''}` : ''}`,
|
|
148
|
+
size: prjctCliSize,
|
|
149
|
+
count: projectCount,
|
|
150
|
+
exists: prjctCliExists,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// 2. ~/.claude/CLAUDE.md (prjct section only)
|
|
154
|
+
const claudeMdPath = path.join(providerPaths.claude.config, 'CLAUDE.md')
|
|
155
|
+
const claudeMdExists = fsSync.existsSync(claudeMdPath)
|
|
156
|
+
let hasPrjctSection = false
|
|
157
|
+
|
|
158
|
+
if (claudeMdExists) {
|
|
159
|
+
try {
|
|
160
|
+
const content = fsSync.readFileSync(claudeMdPath, 'utf-8')
|
|
161
|
+
hasPrjctSection = content.includes(PRJCT_START_MARKER) && content.includes(PRJCT_END_MARKER)
|
|
162
|
+
} catch {
|
|
163
|
+
// Can't read file
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
items.push({
|
|
168
|
+
path: claudeMdPath,
|
|
169
|
+
type: 'section',
|
|
170
|
+
description: 'prjct section in CLAUDE.md',
|
|
171
|
+
exists: claudeMdExists && hasPrjctSection,
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
// 3. ~/.claude/commands/p/ (prjct commands)
|
|
175
|
+
const claudeCommandsPath = providerPaths.claude.commands
|
|
176
|
+
const claudeCommandsExists = fsSync.existsSync(claudeCommandsPath)
|
|
177
|
+
const claudeCommandsSize = claudeCommandsExists ? await getDirectorySize(claudeCommandsPath) : 0
|
|
178
|
+
|
|
179
|
+
items.push({
|
|
180
|
+
path: claudeCommandsPath,
|
|
181
|
+
type: 'directory',
|
|
182
|
+
description: 'Claude commands',
|
|
183
|
+
size: claudeCommandsSize,
|
|
184
|
+
exists: claudeCommandsExists,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// 4. ~/.claude/commands/p.md (router)
|
|
188
|
+
const claudeRouterPath = providerPaths.claude.router
|
|
189
|
+
const claudeRouterExists = fsSync.existsSync(claudeRouterPath)
|
|
190
|
+
|
|
191
|
+
items.push({
|
|
192
|
+
path: claudeRouterPath,
|
|
193
|
+
type: 'file',
|
|
194
|
+
description: 'Claude router',
|
|
195
|
+
exists: claudeRouterExists,
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// 5. ~/.claude/prjct-statusline.sh (status line script)
|
|
199
|
+
const statusLinePath = path.join(providerPaths.claude.config, 'prjct-statusline.sh')
|
|
200
|
+
const statusLineExists = fsSync.existsSync(statusLinePath)
|
|
201
|
+
|
|
202
|
+
items.push({
|
|
203
|
+
path: statusLinePath,
|
|
204
|
+
type: 'file',
|
|
205
|
+
description: 'Status line script',
|
|
206
|
+
exists: statusLineExists,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// 6. ~/.gemini/commands/p.toml (Gemini router, if exists)
|
|
210
|
+
const geminiRouterPath = providerPaths.gemini.router
|
|
211
|
+
const geminiRouterExists = fsSync.existsSync(geminiRouterPath)
|
|
212
|
+
|
|
213
|
+
items.push({
|
|
214
|
+
path: geminiRouterPath,
|
|
215
|
+
type: 'file',
|
|
216
|
+
description: 'Gemini router',
|
|
217
|
+
exists: geminiRouterExists,
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
// 7. ~/.gemini/GEMINI.md (prjct section only, if exists)
|
|
221
|
+
const geminiMdPath = path.join(providerPaths.gemini.config, 'GEMINI.md')
|
|
222
|
+
const geminiMdExists = fsSync.existsSync(geminiMdPath)
|
|
223
|
+
let hasGeminiPrjctSection = false
|
|
224
|
+
|
|
225
|
+
if (geminiMdExists) {
|
|
226
|
+
try {
|
|
227
|
+
const content = fsSync.readFileSync(geminiMdPath, 'utf-8')
|
|
228
|
+
hasGeminiPrjctSection =
|
|
229
|
+
content.includes(PRJCT_START_MARKER) && content.includes(PRJCT_END_MARKER)
|
|
230
|
+
} catch {
|
|
231
|
+
// Can't read file
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (geminiMdExists && hasGeminiPrjctSection) {
|
|
236
|
+
items.push({
|
|
237
|
+
path: geminiMdPath,
|
|
238
|
+
type: 'section',
|
|
239
|
+
description: 'prjct section in GEMINI.md',
|
|
240
|
+
exists: true,
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return items
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Remove prjct section from a markdown file
|
|
249
|
+
*/
|
|
250
|
+
async function removePrjctSection(filePath: string): Promise<boolean> {
|
|
251
|
+
try {
|
|
252
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
253
|
+
|
|
254
|
+
if (!content.includes(PRJCT_START_MARKER) || !content.includes(PRJCT_END_MARKER)) {
|
|
255
|
+
return false
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const startIndex = content.indexOf(PRJCT_START_MARKER)
|
|
259
|
+
const endIndex = content.indexOf(PRJCT_END_MARKER) + PRJCT_END_MARKER.length
|
|
260
|
+
|
|
261
|
+
// Remove the section and any trailing newlines
|
|
262
|
+
let newContent = content.substring(0, startIndex) + content.substring(endIndex)
|
|
263
|
+
newContent = newContent.replace(/\n{3,}/g, '\n\n').trim()
|
|
264
|
+
|
|
265
|
+
// If the file is now empty or just whitespace, delete it
|
|
266
|
+
if (!newContent || newContent.trim().length === 0) {
|
|
267
|
+
await fs.unlink(filePath)
|
|
268
|
+
} else {
|
|
269
|
+
await fs.writeFile(filePath, `${newContent}\n`, 'utf-8')
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return true
|
|
273
|
+
} catch {
|
|
274
|
+
return false
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Create backup of prjct data
|
|
280
|
+
*/
|
|
281
|
+
async function createBackup(): Promise<string | null> {
|
|
282
|
+
const homeDir = os.homedir()
|
|
283
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19)
|
|
284
|
+
const backupDir = path.join(homeDir, `.prjct-backup-${timestamp}`)
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
await fs.mkdir(backupDir, { recursive: true })
|
|
288
|
+
|
|
289
|
+
const prjctCliPath = pathManager.getGlobalBasePath()
|
|
290
|
+
|
|
291
|
+
if (fsSync.existsSync(prjctCliPath)) {
|
|
292
|
+
// Copy entire .prjct-cli directory
|
|
293
|
+
await copyDirectory(prjctCliPath, path.join(backupDir, '.prjct-cli'))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return backupDir
|
|
297
|
+
} catch {
|
|
298
|
+
return null
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Recursively copy a directory
|
|
304
|
+
*/
|
|
305
|
+
async function copyDirectory(src: string, dest: string): Promise<void> {
|
|
306
|
+
await fs.mkdir(dest, { recursive: true })
|
|
307
|
+
const entries = await fs.readdir(src, { withFileTypes: true })
|
|
308
|
+
|
|
309
|
+
for (const entry of entries) {
|
|
310
|
+
const srcPath = path.join(src, entry.name)
|
|
311
|
+
const destPath = path.join(dest, entry.name)
|
|
312
|
+
|
|
313
|
+
if (entry.isDirectory()) {
|
|
314
|
+
await copyDirectory(srcPath, destPath)
|
|
315
|
+
} else {
|
|
316
|
+
await fs.copyFile(srcPath, destPath)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Perform the actual uninstallation
|
|
323
|
+
*/
|
|
324
|
+
async function performUninstall(
|
|
325
|
+
items: UninstallItem[],
|
|
326
|
+
installation: InstallationInfo,
|
|
327
|
+
options: UninstallOptions
|
|
328
|
+
): Promise<{ deleted: string[]; errors: string[] }> {
|
|
329
|
+
const deleted: string[] = []
|
|
330
|
+
const errors: string[] = []
|
|
331
|
+
|
|
332
|
+
for (const item of items) {
|
|
333
|
+
if (!item.exists) continue
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
if (item.type === 'section') {
|
|
337
|
+
// Remove prjct section from file
|
|
338
|
+
const success = await removePrjctSection(item.path)
|
|
339
|
+
if (success) {
|
|
340
|
+
deleted.push(item.path)
|
|
341
|
+
}
|
|
342
|
+
} else if (item.type === 'directory') {
|
|
343
|
+
await fs.rm(item.path, { recursive: true, force: true })
|
|
344
|
+
deleted.push(item.path)
|
|
345
|
+
} else if (item.type === 'file') {
|
|
346
|
+
await fs.unlink(item.path)
|
|
347
|
+
deleted.push(item.path)
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
errors.push(`${item.path}: ${(error as Error).message}`)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Uninstall package managers
|
|
355
|
+
if (!options.keepPackage) {
|
|
356
|
+
if (installation.homebrew && installation.homebrewFormula) {
|
|
357
|
+
try {
|
|
358
|
+
if (!options.dryRun) {
|
|
359
|
+
execSync(`brew uninstall ${installation.homebrewFormula}`, { stdio: 'pipe' })
|
|
360
|
+
}
|
|
361
|
+
deleted.push('Homebrew: prjct-cli')
|
|
362
|
+
} catch (error) {
|
|
363
|
+
errors.push(`Homebrew: ${(error as Error).message}`)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (installation.npm) {
|
|
368
|
+
try {
|
|
369
|
+
if (!options.dryRun) {
|
|
370
|
+
execSync('npm uninstall -g prjct-cli', { stdio: 'pipe' })
|
|
371
|
+
}
|
|
372
|
+
deleted.push('npm: prjct-cli')
|
|
373
|
+
} catch (error) {
|
|
374
|
+
errors.push(`npm: ${(error as Error).message}`)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return { deleted, errors }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Prompt user for confirmation
|
|
384
|
+
*/
|
|
385
|
+
async function promptConfirmation(message: string): Promise<boolean> {
|
|
386
|
+
const rl = readline.createInterface({
|
|
387
|
+
input: process.stdin,
|
|
388
|
+
output: process.stdout,
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
return new Promise((resolve) => {
|
|
392
|
+
rl.question(message, (answer) => {
|
|
393
|
+
rl.close()
|
|
394
|
+
resolve(answer.toLowerCase() === 'uninstall')
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Main uninstall function
|
|
401
|
+
*/
|
|
402
|
+
export async function uninstall(
|
|
403
|
+
options: UninstallOptions = {},
|
|
404
|
+
_projectPath: string = process.cwd()
|
|
405
|
+
): Promise<CommandResult> {
|
|
406
|
+
const items = await gatherUninstallItems()
|
|
407
|
+
const installation = detectInstallation()
|
|
408
|
+
const existingItems = items.filter((i) => i.exists)
|
|
409
|
+
|
|
410
|
+
// Check if there's anything to uninstall
|
|
411
|
+
if (existingItems.length === 0 && !installation.homebrew && !installation.npm) {
|
|
412
|
+
console.log(chalk.yellow('\nNo prjct installation found.'))
|
|
413
|
+
return {
|
|
414
|
+
success: true,
|
|
415
|
+
message: 'Nothing to uninstall',
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Calculate total size
|
|
420
|
+
const totalSize = existingItems.reduce((sum, item) => sum + (item.size || 0), 0)
|
|
421
|
+
|
|
422
|
+
// Display warning and items
|
|
423
|
+
console.log('')
|
|
424
|
+
console.log(chalk.red.bold(' WARNING: This action is DANGEROUS and IRREVERSIBLE'))
|
|
425
|
+
console.log('')
|
|
426
|
+
console.log(chalk.white('The following will be permanently deleted:'))
|
|
427
|
+
console.log('')
|
|
428
|
+
|
|
429
|
+
for (const item of existingItems) {
|
|
430
|
+
const displayPath = pathManager.getDisplayPath(item.path)
|
|
431
|
+
let info = ''
|
|
432
|
+
|
|
433
|
+
if (item.type === 'section') {
|
|
434
|
+
info = chalk.dim('(section only)')
|
|
435
|
+
} else if (item.size) {
|
|
436
|
+
info = chalk.dim(`(${formatSize(item.size)})`)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
console.log(` ${chalk.cyan(displayPath.padEnd(35))} ${info}`)
|
|
440
|
+
console.log(` ${chalk.dim(item.description)}`)
|
|
441
|
+
console.log('')
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Show package manager installations
|
|
445
|
+
if (installation.homebrew) {
|
|
446
|
+
console.log(` ${chalk.cyan('Homebrew'.padEnd(35))} ${chalk.dim('prjct-cli formula')}`)
|
|
447
|
+
console.log('')
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (installation.npm) {
|
|
451
|
+
console.log(` ${chalk.cyan('npm global'.padEnd(35))} ${chalk.dim('prjct-cli package')}`)
|
|
452
|
+
console.log('')
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (totalSize > 0) {
|
|
456
|
+
console.log(chalk.dim(` Total size: ${formatSize(totalSize)}`))
|
|
457
|
+
console.log('')
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Handle dry run
|
|
461
|
+
if (options.dryRun) {
|
|
462
|
+
console.log(chalk.yellow('Dry run - no changes made'))
|
|
463
|
+
return {
|
|
464
|
+
success: true,
|
|
465
|
+
message: 'Dry run complete',
|
|
466
|
+
itemsFound: existingItems.length,
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Handle backup
|
|
471
|
+
if (options.backup) {
|
|
472
|
+
console.log(chalk.blue('Creating backup...'))
|
|
473
|
+
const backupPath = await createBackup()
|
|
474
|
+
|
|
475
|
+
if (backupPath) {
|
|
476
|
+
console.log(chalk.green(`Backup created: ${pathManager.getDisplayPath(backupPath)}`))
|
|
477
|
+
console.log('')
|
|
478
|
+
} else {
|
|
479
|
+
console.log(chalk.yellow('Failed to create backup, continuing...'))
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Require confirmation unless --force
|
|
484
|
+
if (!options.force) {
|
|
485
|
+
console.log(chalk.yellow('Type "uninstall" to confirm:'))
|
|
486
|
+
const confirmed = await promptConfirmation('> ')
|
|
487
|
+
|
|
488
|
+
if (!confirmed) {
|
|
489
|
+
console.log(chalk.yellow('\nUninstall cancelled.'))
|
|
490
|
+
return {
|
|
491
|
+
success: false,
|
|
492
|
+
message: 'Uninstall cancelled by user',
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Perform uninstallation
|
|
498
|
+
console.log('')
|
|
499
|
+
console.log(chalk.blue('Removing prjct...'))
|
|
500
|
+
|
|
501
|
+
const { deleted, errors } = await performUninstall(items, installation, options)
|
|
502
|
+
|
|
503
|
+
// Report results
|
|
504
|
+
console.log('')
|
|
505
|
+
|
|
506
|
+
if (deleted.length > 0) {
|
|
507
|
+
console.log(chalk.green(`Removed ${deleted.length} items`))
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (errors.length > 0) {
|
|
511
|
+
console.log(chalk.yellow(`\n${errors.length} errors:`))
|
|
512
|
+
for (const error of errors) {
|
|
513
|
+
console.log(chalk.red(` - ${error}`))
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
console.log('')
|
|
518
|
+
console.log(chalk.green('prjct has been uninstalled.'))
|
|
519
|
+
console.log(chalk.dim('Thanks for using prjct! We hope to see you again.'))
|
|
520
|
+
console.log('')
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
success: errors.length === 0,
|
|
524
|
+
message: `Removed ${deleted.length} items`,
|
|
525
|
+
deleted,
|
|
526
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* UninstallCommands class for integration with command system
|
|
532
|
+
*/
|
|
533
|
+
export class UninstallCommands extends PrjctCommandsBase {
|
|
534
|
+
async uninstall(
|
|
535
|
+
options: UninstallOptions = {},
|
|
536
|
+
projectPath: string = process.cwd()
|
|
537
|
+
): Promise<CommandResult> {
|
|
538
|
+
// Uninstall doesn't require project init
|
|
539
|
+
return uninstall(options, projectPath)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
@@ -8,28 +8,24 @@
|
|
|
8
8
|
* TypeScript provides infrastructure; Claude decides via templates.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { CommandResult, ProjectContext } from '../types'
|
|
12
|
-
import { generateUUID } from '../schemas'
|
|
13
|
-
import {
|
|
14
|
-
PrjctCommandsBase,
|
|
15
|
-
contextBuilder,
|
|
16
|
-
configManager,
|
|
17
|
-
dateHelper,
|
|
18
|
-
out
|
|
19
|
-
} from './base'
|
|
20
|
-
import { stateStorage, queueStorage } from '../storage'
|
|
21
|
-
import { templateExecutor } from '../agentic/template-executor'
|
|
22
11
|
import commandExecutor from '../agentic/command-executor'
|
|
23
|
-
import {
|
|
24
|
-
import { workflowStateMachine } from '../workflow/state-machine'
|
|
12
|
+
import { templateExecutor } from '../agentic/template-executor'
|
|
25
13
|
import { linearService } from '../integrations/linear'
|
|
26
|
-
import {
|
|
14
|
+
import { generateUUID } from '../schemas'
|
|
15
|
+
import { queueStorage, stateStorage } from '../storage'
|
|
16
|
+
import type { CommandResult } from '../types'
|
|
17
|
+
import { showNextSteps, showStateInfo } from '../utils/next-steps'
|
|
18
|
+
import { getLinearApiKey, getProjectCredentials } from '../utils/project-credentials'
|
|
19
|
+
import { configManager, dateHelper, out, PrjctCommandsBase } from './base'
|
|
27
20
|
|
|
28
21
|
export class WorkflowCommands extends PrjctCommandsBase {
|
|
29
22
|
/**
|
|
30
23
|
* /p:now - Set or show current task
|
|
31
24
|
*/
|
|
32
|
-
async now(
|
|
25
|
+
async now(
|
|
26
|
+
task: string | null = null,
|
|
27
|
+
projectPath: string = process.cwd()
|
|
28
|
+
): Promise<CommandResult> {
|
|
33
29
|
try {
|
|
34
30
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
35
31
|
if (!initResult.success) return initResult
|
|
@@ -58,10 +54,7 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
58
54
|
const creds = await getProjectCredentials(projectId)
|
|
59
55
|
const apiKey = await getLinearApiKey(projectId)
|
|
60
56
|
if (apiKey && creds.linear?.teamId) {
|
|
61
|
-
await linearService.initializeFromApiKey(
|
|
62
|
-
apiKey,
|
|
63
|
-
creds.linear.teamId
|
|
64
|
-
)
|
|
57
|
+
await linearService.initializeFromApiKey(apiKey, creds.linear.teamId)
|
|
65
58
|
const issue = await linearService.fetchIssue(task)
|
|
66
59
|
if (issue) {
|
|
67
60
|
linearId = task
|
|
@@ -85,11 +78,14 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
85
78
|
|
|
86
79
|
// Get available agents for backward compatibility
|
|
87
80
|
const availableAgents = await templateExecutor.getAvailableAgents(projectPath)
|
|
88
|
-
const
|
|
89
|
-
? availableAgents.join(', ')
|
|
90
|
-
: 'none (run p. sync)'
|
|
81
|
+
const _agentsList =
|
|
82
|
+
availableAgents.length > 0 ? availableAgents.join(', ') : 'none (run p. sync)'
|
|
91
83
|
|
|
92
|
-
|
|
84
|
+
// Build metrics from orchestrator context
|
|
85
|
+
const agentCount = result.orchestratorContext?.agents?.length || availableAgents.length
|
|
86
|
+
out.done(`${task}`, {
|
|
87
|
+
agents: agentCount > 0 ? agentCount : undefined,
|
|
88
|
+
})
|
|
93
89
|
showStateInfo('working')
|
|
94
90
|
showNextSteps('task')
|
|
95
91
|
|
|
@@ -167,10 +163,7 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
167
163
|
const creds = await getProjectCredentials(projectId)
|
|
168
164
|
const apiKey = await getLinearApiKey(projectId)
|
|
169
165
|
if (apiKey && creds.linear?.teamId) {
|
|
170
|
-
await linearService.initializeFromApiKey(
|
|
171
|
-
apiKey,
|
|
172
|
-
creds.linear.teamId
|
|
173
|
-
)
|
|
166
|
+
await linearService.initializeFromApiKey(apiKey, creds.linear.teamId)
|
|
174
167
|
await linearService.markDone(linearId)
|
|
175
168
|
out.done(`${task}${duration ? ` (${duration})` : ''} → Linear ✓`)
|
|
176
169
|
} else {
|
|
@@ -275,7 +268,10 @@ export class WorkflowCommands extends PrjctCommandsBase {
|
|
|
275
268
|
/**
|
|
276
269
|
* /p:resume - Resume most recently paused task
|
|
277
270
|
*/
|
|
278
|
-
async resume(
|
|
271
|
+
async resume(
|
|
272
|
+
_taskId: string | null = null,
|
|
273
|
+
projectPath: string = process.cwd()
|
|
274
|
+
): Promise<CommandResult> {
|
|
279
275
|
try {
|
|
280
276
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
281
277
|
if (!initResult.success) return initResult
|
package/core/constants/index.ts
CHANGED
|
@@ -19,17 +19,13 @@ export const NOW = {
|
|
|
19
19
|
|
|
20
20
|
/** Generate NOW file content */
|
|
21
21
|
content: (task: string, startedAt: string, agent?: string, confidence?: number): string => {
|
|
22
|
-
const lines = [
|
|
23
|
-
'# NOW',
|
|
24
|
-
'',
|
|
25
|
-
`**${task}**`,
|
|
26
|
-
'',
|
|
27
|
-
`Started: ${startedAt}`,
|
|
28
|
-
]
|
|
22
|
+
const lines = ['# NOW', '', `**${task}**`, '', `Started: ${startedAt}`]
|
|
29
23
|
if (agent) {
|
|
30
|
-
lines.push(
|
|
24
|
+
lines.push(
|
|
25
|
+
`Agent: ${agent}${confidence ? ` (${Math.round(confidence * 100)}% confidence)` : ''}`
|
|
26
|
+
)
|
|
31
27
|
}
|
|
32
|
-
return lines.join('\n')
|
|
28
|
+
return `${lines.join('\n')}\n`
|
|
33
29
|
},
|
|
34
30
|
|
|
35
31
|
/** Extract task from NOW content */
|
|
@@ -48,15 +44,11 @@ export const SHIPPED = {
|
|
|
48
44
|
|
|
49
45
|
/** Generate ship entry */
|
|
50
46
|
entry: (feature: string, date: string, duration?: string): string => {
|
|
51
|
-
const lines = [
|
|
52
|
-
`## ${feature}`,
|
|
53
|
-
'',
|
|
54
|
-
`Shipped: ${date}`,
|
|
55
|
-
]
|
|
47
|
+
const lines = [`## ${feature}`, '', `Shipped: ${date}`]
|
|
56
48
|
if (duration) {
|
|
57
49
|
lines.push(`Duration: ${duration}`)
|
|
58
50
|
}
|
|
59
|
-
return lines.join('\n')
|
|
51
|
+
return `${lines.join('\n')}\n\n`
|
|
60
52
|
},
|
|
61
53
|
} as const
|
|
62
54
|
|
|
@@ -113,16 +105,12 @@ export const ROADMAP = {
|
|
|
113
105
|
|
|
114
106
|
/** Generate feature entry */
|
|
115
107
|
entry: (feature: string, status: RoadmapStatusKey, tasks?: string[]): string => {
|
|
116
|
-
const lines = [
|
|
117
|
-
`## ${feature}`,
|
|
118
|
-
'',
|
|
119
|
-
`Status: ${ROADMAP_STATUS[status]}`,
|
|
120
|
-
]
|
|
108
|
+
const lines = [`## ${feature}`, '', `Status: ${ROADMAP_STATUS[status]}`]
|
|
121
109
|
if (tasks && tasks.length > 0) {
|
|
122
110
|
lines.push('', '### Tasks', '')
|
|
123
|
-
tasks.forEach(task => lines.push(`- [ ] ${task}`))
|
|
111
|
+
tasks.forEach((task) => lines.push(`- [ ] ${task}`))
|
|
124
112
|
}
|
|
125
|
-
return lines.join('\n')
|
|
113
|
+
return `${lines.join('\n')}\n\n`
|
|
126
114
|
},
|
|
127
115
|
} as const
|
|
128
116
|
|