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.
Files changed (207) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/bin/prjct.ts +131 -10
  3. package/core/__tests__/agentic/memory-system.test.ts +39 -26
  4. package/core/__tests__/agentic/plan-mode.test.ts +64 -46
  5. package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
  6. package/core/__tests__/services/project-index.test.ts +353 -0
  7. package/core/__tests__/types/fs.test.ts +3 -3
  8. package/core/__tests__/utils/date-helper.test.ts +10 -10
  9. package/core/__tests__/utils/output.test.ts +9 -6
  10. package/core/__tests__/utils/project-commands.test.ts +5 -6
  11. package/core/agentic/agent-router.ts +9 -10
  12. package/core/agentic/chain-of-thought.ts +16 -4
  13. package/core/agentic/command-executor.ts +66 -40
  14. package/core/agentic/context-builder.ts +8 -5
  15. package/core/agentic/ground-truth.ts +15 -9
  16. package/core/agentic/index.ts +145 -152
  17. package/core/agentic/loop-detector.ts +40 -11
  18. package/core/agentic/memory-system.ts +98 -35
  19. package/core/agentic/orchestrator-executor.ts +135 -71
  20. package/core/agentic/plan-mode.ts +46 -16
  21. package/core/agentic/prompt-builder.ts +108 -42
  22. package/core/agentic/services.ts +10 -9
  23. package/core/agentic/skill-loader.ts +9 -15
  24. package/core/agentic/smart-context.ts +129 -79
  25. package/core/agentic/template-executor.ts +13 -12
  26. package/core/agentic/template-loader.ts +7 -4
  27. package/core/agentic/tool-registry.ts +16 -13
  28. package/core/agents/index.ts +1 -1
  29. package/core/agents/performance.ts +10 -27
  30. package/core/ai-tools/formatters.ts +8 -6
  31. package/core/ai-tools/generator.ts +4 -4
  32. package/core/ai-tools/index.ts +1 -1
  33. package/core/ai-tools/registry.ts +21 -11
  34. package/core/bus/bus.ts +23 -16
  35. package/core/bus/index.ts +2 -2
  36. package/core/cli/linear.ts +3 -5
  37. package/core/cli/start.ts +28 -25
  38. package/core/commands/analysis.ts +287 -29
  39. package/core/commands/analytics.ts +52 -44
  40. package/core/commands/base.ts +15 -13
  41. package/core/commands/cleanup.ts +6 -13
  42. package/core/commands/command-data.ts +49 -8
  43. package/core/commands/commands.ts +60 -23
  44. package/core/commands/context.ts +4 -4
  45. package/core/commands/design.ts +3 -10
  46. package/core/commands/index.ts +5 -8
  47. package/core/commands/maintenance.ts +7 -4
  48. package/core/commands/planning.ts +179 -56
  49. package/core/commands/register.ts +14 -9
  50. package/core/commands/registry.ts +15 -14
  51. package/core/commands/setup.ts +26 -14
  52. package/core/commands/shipping.ts +11 -16
  53. package/core/commands/snapshots.ts +16 -32
  54. package/core/commands/uninstall.ts +541 -0
  55. package/core/commands/workflow.ts +24 -28
  56. package/core/constants/index.ts +10 -22
  57. package/core/context/generator.ts +82 -33
  58. package/core/context-tools/files-tool.ts +583 -0
  59. package/core/context-tools/imports-tool.ts +403 -0
  60. package/core/context-tools/index.ts +433 -0
  61. package/core/context-tools/recent-tool.ts +307 -0
  62. package/core/context-tools/signatures-tool.ts +501 -0
  63. package/core/context-tools/summary-tool.ts +307 -0
  64. package/core/context-tools/token-counter.ts +284 -0
  65. package/core/context-tools/types.ts +253 -0
  66. package/core/domain/agent-generator.ts +7 -5
  67. package/core/domain/agent-loader.ts +2 -2
  68. package/core/domain/analyzer.ts +19 -16
  69. package/core/domain/architecture-generator.ts +6 -3
  70. package/core/domain/context-estimator.ts +3 -4
  71. package/core/domain/snapshot-manager.ts +25 -22
  72. package/core/domain/task-stack.ts +24 -14
  73. package/core/errors.ts +1 -1
  74. package/core/events/events.ts +2 -4
  75. package/core/events/index.ts +1 -2
  76. package/core/index.ts +28 -12
  77. package/core/infrastructure/agent-detector.ts +3 -3
  78. package/core/infrastructure/ai-provider.ts +23 -20
  79. package/core/infrastructure/author-detector.ts +16 -10
  80. package/core/infrastructure/capability-installer.ts +2 -2
  81. package/core/infrastructure/claude-agent.ts +6 -6
  82. package/core/infrastructure/command-installer.ts +22 -17
  83. package/core/infrastructure/config-manager.ts +18 -14
  84. package/core/infrastructure/editors-config.ts +8 -4
  85. package/core/infrastructure/path-manager.ts +8 -6
  86. package/core/infrastructure/permission-manager.ts +20 -17
  87. package/core/infrastructure/setup.ts +42 -38
  88. package/core/infrastructure/update-checker.ts +5 -5
  89. package/core/integrations/issue-tracker/enricher.ts +8 -19
  90. package/core/integrations/issue-tracker/index.ts +2 -2
  91. package/core/integrations/issue-tracker/manager.ts +15 -15
  92. package/core/integrations/issue-tracker/types.ts +5 -22
  93. package/core/integrations/jira/client.ts +67 -59
  94. package/core/integrations/jira/index.ts +11 -14
  95. package/core/integrations/jira/mcp-adapter.ts +5 -10
  96. package/core/integrations/jira/service.ts +10 -10
  97. package/core/integrations/linear/client.ts +27 -18
  98. package/core/integrations/linear/index.ts +9 -12
  99. package/core/integrations/linear/service.ts +11 -11
  100. package/core/integrations/linear/sync.ts +8 -8
  101. package/core/outcomes/analyzer.ts +5 -18
  102. package/core/outcomes/index.ts +2 -2
  103. package/core/outcomes/recorder.ts +3 -3
  104. package/core/plugin/builtin/webhook.ts +19 -15
  105. package/core/plugin/hooks.ts +29 -21
  106. package/core/plugin/index.ts +7 -7
  107. package/core/plugin/loader.ts +19 -19
  108. package/core/plugin/registry.ts +12 -23
  109. package/core/schemas/agents.ts +1 -1
  110. package/core/schemas/analysis.ts +1 -1
  111. package/core/schemas/enriched-task.ts +62 -49
  112. package/core/schemas/ideas.ts +13 -13
  113. package/core/schemas/index.ts +17 -27
  114. package/core/schemas/issues.ts +40 -25
  115. package/core/schemas/metrics.ts +143 -0
  116. package/core/schemas/outcomes.ts +70 -62
  117. package/core/schemas/permissions.ts +15 -12
  118. package/core/schemas/prd.ts +27 -14
  119. package/core/schemas/project.ts +3 -3
  120. package/core/schemas/roadmap.ts +47 -34
  121. package/core/schemas/schemas.ts +3 -4
  122. package/core/schemas/shipped.ts +3 -3
  123. package/core/schemas/state.ts +43 -29
  124. package/core/server/index.ts +5 -6
  125. package/core/server/routes-extended.ts +68 -72
  126. package/core/server/routes.ts +3 -3
  127. package/core/server/server.ts +31 -26
  128. package/core/services/agent-generator.ts +237 -0
  129. package/core/services/agent-service.ts +2 -2
  130. package/core/services/breakdown-service.ts +2 -4
  131. package/core/services/context-generator.ts +299 -0
  132. package/core/services/context-selector.ts +420 -0
  133. package/core/services/doctor-service.ts +426 -0
  134. package/core/services/file-categorizer.ts +448 -0
  135. package/core/services/file-scorer.ts +270 -0
  136. package/core/services/git-analyzer.ts +267 -0
  137. package/core/services/index.ts +27 -10
  138. package/core/services/memory-service.ts +3 -4
  139. package/core/services/project-index.ts +911 -0
  140. package/core/services/project-service.ts +4 -4
  141. package/core/services/skill-installer.ts +14 -17
  142. package/core/services/skill-lock.ts +3 -3
  143. package/core/services/skill-service.ts +12 -6
  144. package/core/services/stack-detector.ts +245 -0
  145. package/core/services/sync-service.ts +170 -329
  146. package/core/services/watch-service.ts +294 -0
  147. package/core/session/compaction.ts +23 -31
  148. package/core/session/index.ts +11 -5
  149. package/core/session/log-migration.ts +3 -3
  150. package/core/session/metrics.ts +19 -14
  151. package/core/session/session-log-manager.ts +12 -17
  152. package/core/session/task-session-manager.ts +25 -25
  153. package/core/session/utils.ts +1 -1
  154. package/core/storage/ideas-storage.ts +41 -57
  155. package/core/storage/index-storage.ts +514 -0
  156. package/core/storage/index.ts +41 -13
  157. package/core/storage/metrics-storage.ts +320 -0
  158. package/core/storage/queue-storage.ts +35 -45
  159. package/core/storage/shipped-storage.ts +17 -20
  160. package/core/storage/state-storage.ts +50 -30
  161. package/core/storage/storage-manager.ts +6 -6
  162. package/core/storage/storage.ts +18 -15
  163. package/core/sync/auth-config.ts +3 -3
  164. package/core/sync/index.ts +13 -19
  165. package/core/sync/oauth-handler.ts +3 -3
  166. package/core/sync/sync-client.ts +4 -9
  167. package/core/sync/sync-manager.ts +12 -14
  168. package/core/types/commands.ts +42 -7
  169. package/core/types/index.ts +284 -302
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +49 -0
  172. package/core/types/utils.ts +3 -3
  173. package/core/utils/agent-stream.ts +3 -1
  174. package/core/utils/animations.ts +14 -11
  175. package/core/utils/branding.ts +7 -7
  176. package/core/utils/cache.ts +1 -3
  177. package/core/utils/collection-filters.ts +3 -15
  178. package/core/utils/date-helper.ts +2 -7
  179. package/core/utils/file-helper.ts +13 -8
  180. package/core/utils/jsonl-helper.ts +13 -10
  181. package/core/utils/keychain.ts +4 -8
  182. package/core/utils/logger.ts +1 -1
  183. package/core/utils/next-steps.ts +3 -3
  184. package/core/utils/output.ts +58 -11
  185. package/core/utils/project-commands.ts +6 -6
  186. package/core/utils/project-credentials.ts +5 -12
  187. package/core/utils/runtime.ts +2 -2
  188. package/core/utils/session-helper.ts +3 -4
  189. package/core/utils/version.ts +3 -3
  190. package/core/wizard/index.ts +13 -0
  191. package/core/wizard/onboarding.ts +633 -0
  192. package/core/workflow/state-machine.ts +7 -7
  193. package/dist/bin/prjct.mjs +18907 -13189
  194. package/dist/core/infrastructure/command-installer.js +96 -111
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +256 -257
  197. package/dist/core/utils/version.js +9 -9
  198. package/package.json +11 -12
  199. package/scripts/build.js +3 -3
  200. package/scripts/postinstall.js +2 -2
  201. package/templates/mcp-config.json +6 -1
  202. package/templates/permissions/permissive.jsonc +1 -1
  203. package/templates/permissions/strict.jsonc +5 -9
  204. package/templates/global/docs/agents.md +0 -88
  205. package/templates/global/docs/architecture.md +0 -103
  206. package/templates/global/docs/commands.md +0 -96
  207. 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 { showNextSteps, showStateInfo } from '../utils/next-steps'
24
- import { workflowStateMachine } from '../workflow/state-machine'
12
+ import { templateExecutor } from '../agentic/template-executor'
25
13
  import { linearService } from '../integrations/linear'
26
- import { getProjectCredentials, getLinearApiKey } from '../utils/project-credentials'
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(task: string | null = null, projectPath: string = process.cwd()): Promise<CommandResult> {
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 agentsList = availableAgents.length > 0
89
- ? availableAgents.join(', ')
90
- : 'none (run p. sync)'
81
+ const _agentsList =
82
+ availableAgents.length > 0 ? availableAgents.join(', ') : 'none (run p. sync)'
91
83
 
92
- out.done(`${task}`)
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(taskId: string | null = null, projectPath: string = process.cwd()): Promise<CommandResult> {
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
@@ -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(`Agent: ${agent}${confidence ? ` (${Math.round(confidence * 100)}% confidence)` : ''}`)
24
+ lines.push(
25
+ `Agent: ${agent}${confidence ? ` (${Math.round(confidence * 100)}% confidence)` : ''}`
26
+ )
31
27
  }
32
- return lines.join('\n') + '\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') + '\n\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') + '\n\n'
113
+ return `${lines.join('\n')}\n\n`
126
114
  },
127
115
  } as const
128
116